Files
immich-folio/app/admin/components/journal/journal-studio.css
Ralksta ad8b09700b release: v0.19.0
Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
2026-09-29 22:50:30 +02:00

761 lines
18 KiB
CSS

/* ── Journal Studio & Editor CSS ──────────────────────────────────── */
/*
* The studio is a full-width workspace: it breaks out of the 1120px
* `.admin-main` column so the split view has room for editor *and* preview.
*/
.admin-main:has(> .journal-studio),
.admin-main:has(> .journal-editor-container) {
max-width: none;
padding: 0;
}
/* ── Form controls ─────────────────────────────────────────────────
* Local field styling for the studio. Scoped so the generic class name
* cannot leak into the rest of the admin panel.
*/
.journal-studio .admin-input,
.journal-editor-container .admin-input,
.journal-modal-card .admin-input {
display: block;
width: 100%;
padding: 0.55rem 0.7rem;
background: var(--admin-bg-input, #161616);
border: 1px solid var(--admin-border, #2e2e2e);
border-radius: 8px;
color: var(--admin-text, #f0f0f0);
font-family: inherit;
font-size: 0.875rem;
line-height: 1.5;
transition:
border-color 0.15s ease,
background 0.15s ease;
}
.journal-studio .admin-input:hover,
.journal-editor-container .admin-input:hover,
.journal-modal-card .admin-input:hover {
border-color: var(--admin-border-hover, #444);
}
.journal-studio .admin-input:focus-visible,
.journal-editor-container .admin-input:focus-visible,
.journal-modal-card .admin-input:focus-visible {
outline: var(--admin-focus-ring);
outline-offset: 1px;
border-color: var(--admin-border-focus, #6366f1);
}
.journal-studio .admin-input::placeholder,
.journal-editor-container .admin-input::placeholder,
.journal-modal-card .admin-input::placeholder {
color: var(--admin-text-secondary, #a0a0a0);
opacity: 0.7;
}
.journal-editor-container textarea.admin-input,
.journal-modal-card textarea.admin-input {
resize: vertical;
min-height: 88px;
}
.journal-editor-container select.admin-input {
cursor: pointer;
appearance: none;
padding-right: 2rem;
background-image:
linear-gradient(45deg, transparent 50%, currentColor 50%),
linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position:
calc(100% - 16px) calc(50% + 1px),
calc(100% - 11px) calc(50% + 1px);
background-size: 5px 5px;
background-repeat: no-repeat;
}
.journal-level-select {
width: 5.5rem;
flex-shrink: 0;
}
.journal-studio {
display: flex;
flex-direction: column;
height: 100%;
min-height: calc(100vh - 70px);
}
.journal-studio-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 2rem;
border-bottom: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
}
.journal-studio-header h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
}
.journal-list-grid {
display: grid;
/* min(): on a phone the column is narrower than 320px, and a fixed minimum
pushed every card past the right edge. */
grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
gap: 1.5rem;
padding: 2rem;
}
.journal-admin-card {
background: var(--admin-surface, rgba(255, 255, 255, 0.03));
border: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
border-radius: 10px;
overflow: hidden;
display: flex;
flex-direction: column;
transition:
border-color 0.2s ease,
transform 0.2s ease;
}
.journal-admin-card:hover {
border-color: var(--admin-accent, #6366f1);
transform: translateY(-2px);
}
.journal-admin-card-cover {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.journal-admin-card-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.journal-admin-card-body {
padding: 1.25rem;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.journal-admin-card-title {
font-size: 1.15rem;
font-weight: 600;
margin: 0 0 0.4rem;
}
.journal-admin-card-slug {
font-size: 0.75rem;
font-family: monospace;
opacity: 0.6;
margin-bottom: 0.75rem;
}
.journal-admin-card-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.75rem;
opacity: 0.7;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.journal-admin-card-actions {
display: flex;
align-items: center;
gap: 8px;
margin-top: auto;
padding-top: 1rem;
border-top: 1px solid var(--admin-border, rgba(255, 255, 255, 0.06));
}
/* ── Split-Screen Studio Editor ────────────────────────────────────── */
.journal-editor-container {
display: flex;
flex-direction: column;
height: calc(100vh - 65px);
overflow: hidden;
}
.journal-editor-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.5rem;
background: var(--admin-surface, #18181b);
border-bottom: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
gap: 1rem;
z-index: 10;
}
.journal-editor-topbar-left {
display: flex;
align-items: center;
gap: 1rem;
flex-grow: 1;
}
.journal-editor-title-input {
font-size: 1.1rem;
font-weight: 600;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
color: inherit;
padding: 4px 8px;
max-width: 400px;
width: 100%;
}
.journal-editor-title-input:focus {
border-color: var(--admin-accent, #6366f1);
background: rgba(255, 255, 255, 0.05);
}
.journal-editor-topbar-right {
display: flex;
align-items: center;
gap: 0.75rem;
}
.journal-editor-split {
display: grid;
/* Left width is driven by the draggable divider; the preview takes the rest. */
grid-template-columns: var(--journal-split-left, 46%) 7px minmax(0, 1fr);
flex-grow: 1;
overflow: hidden;
height: 100%;
}
/* Kill text selection and pointer flicker while the divider is being dragged. */
.journal-editor-split.is-dragging {
user-select: none;
cursor: col-resize;
}
.journal-editor-split.is-dragging .journal-editor-pane-right {
pointer-events: none;
}
.journal-editor-resizer {
position: relative;
cursor: col-resize;
background: var(--admin-border, rgba(255, 255, 255, 0.08));
transition: background 0.15s ease;
touch-action: none;
}
.journal-editor-resizer:hover,
.journal-editor-resizer:focus-visible,
.journal-editor-split.is-dragging .journal-editor-resizer {
background: var(--admin-accent, #6366f1);
}
/* The resizer used to change colour on focus exactly as it does on hover,
so from the keyboard there was no telling whether it had the focus or the
mouse was merely resting on it. */
.journal-editor-resizer:focus-visible {
outline: var(--admin-focus-ring);
outline-offset: 2px;
}
/* Grip dots, and a widened invisible hit area so the divider is easy to grab. */
.journal-editor-resizer-grip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 15px;
height: 44px;
border-radius: 4px;
background: repeating-linear-gradient(to bottom, currentColor 0 2px, transparent 2px 5px);
color: rgba(255, 255, 255, 0.35);
background-clip: content-box;
padding: 0 6px;
}
.journal-editor-resizer:hover .journal-editor-resizer-grip {
color: #fff;
}
.journal-editor-pane-left {
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 1.5rem 2rem 4rem;
background: var(--admin-bg, #09090b);
}
.journal-editor-pane-right {
display: flex;
flex-direction: column;
overflow-y: auto;
background: var(--admin-bg, #111);
position: relative;
}
.journal-preview-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
background: rgba(0, 0, 0, 0.4);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 0.75rem;
position: sticky;
top: 0;
z-index: 5;
backdrop-filter: blur(8px);
}
.journal-preview-frame {
padding: 2.5rem 2rem;
max-width: 1080px;
margin: 0 auto;
width: 100%;
transition: max-width 0.3s ease;
}
/*
* The essay's full-bleed and wide figures are sized against the viewport on the
* live site. Inside the preview pane that overshoots — the photo would bleed out
* of the pane and drift as the split is dragged — so they are re-anchored to the
* frame. 100% is the container's content box, so reaching the pane edges means
* adding back both horizontal paddings: 4rem for this frame plus 3rem for
* .essay-container. Keep in sync if either padding changes.
*/
.journal-preview-frame .essay-container {
--essay-bleed: calc(100% + 7rem);
--essay-wide: 100%;
}
.journal-preview-frame.mobile {
max-width: 380px;
border-left: 1px solid rgba(255, 255, 255, 0.1);
border-right: 1px solid rgba(255, 255, 255, 0.1);
min-height: 100%;
background: var(--background, #000);
}
.journal-raw-markdown-editor {
width: 100%;
height: 100%;
min-height: 500px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.9rem;
line-height: 1.6;
background: rgba(0, 0, 0, 0.3);
color: #f4f4f5;
border: 1px solid var(--admin-border, rgba(255, 255, 255, 0.1));
border-radius: 8px;
padding: 1.25rem;
resize: none;
}
.journal-raw-markdown-editor:focus-visible {
outline: var(--admin-focus-ring);
outline-offset: 1px;
border-color: var(--admin-accent, #6366f1);
}
/* Modal styling for Entry creation / Metadata */
.journal-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.75);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
backdrop-filter: blur(4px);
}
.journal-modal-card {
background: var(--admin-surface, #18181b);
border: 1px solid var(--admin-border, rgba(255, 255, 255, 0.1));
border-radius: 12px;
width: 90%;
max-width: 620px;
max-height: 88vh;
overflow-y: auto;
padding: 1.75rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
}
/* ── Template picker (Create modal) ──────────────────────────────── */
.journal-template-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
max-height: 260px;
overflow-y: auto;
padding: 2px;
}
.journal-template-card {
display: flex;
flex-direction: column;
gap: 2px;
align-items: flex-start;
text-align: left;
padding: 0.55rem 0.7rem;
background: var(--admin-bg-input, #161616);
/* <button> does not inherit color or font from its parent — without these
the cards render in the UA default, black on the dark admin surface. */
color: var(--admin-text, #f0f0f0);
font: inherit;
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
transition: border-color 0.15s ease;
}
.journal-template-card:hover {
border-color: var(--admin-border-hover, #444);
}
.journal-template-card.is-selected {
border-color: var(--admin-accent, #6366f1);
}
.journal-template-card-name {
font-size: 0.85rem;
font-weight: 600;
}
.journal-template-card-desc {
font-size: 0.75rem;
opacity: 0.65;
}
/* ── Photo grid block (editor tiles) ─────────────────────────────── */
.journal-grid-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: 8px;
}
.journal-grid-tile {
position: relative;
}
.journal-grid-tile-pick {
height: 80px;
background: rgba(0, 0, 0, 0.3);
border: 1px dashed rgba(255, 255, 255, 0.15);
border-radius: 6px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 0.8rem;
opacity: 0.85;
}
.journal-grid-tile-pick img {
width: 100%;
height: 100%;
object-fit: cover;
}
.journal-grid-tile-remove {
position: absolute;
top: 4px;
right: 4px;
padding: 2px 5px;
}
/* ── Facts block (editor rows) ───────────────────────────────────── */
.journal-facts-row {
display: grid;
grid-template-columns: 1fr 2fr auto;
gap: 8px;
align-items: center;
}
/* ── Album block (editor) ────────────────────────────────────────── */
.journal-album-row {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.journal-album-field {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 0.7rem;
opacity: 0.85;
flex: 1 1 120px;
min-width: 0;
}
.journal-album-field .admin-input {
width: 100%;
}
/* ── Map block (editor pin rows) ─────────────────────────────────── */
.journal-map-row {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
}
.journal-map-row .admin-input {
flex: 1 1 140px;
width: auto;
min-width: 0;
}
.journal-map-row .journal-map-coord {
flex: 0 1 110px;
}
.journal-map-index {
width: 1.4rem;
font-size: 0.7rem;
opacity: 0.6;
text-align: right;
flex: none;
}
.journal-map-photo {
width: 96px;
height: 64px;
flex: none;
background: rgba(0, 0, 0, 0.3);
border: 1px dashed rgba(255, 255, 255, 0.15);
border-radius: 6px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 0.75rem;
opacity: 0.85;
}
.journal-map-photo img {
width: 100%;
height: 100%;
object-fit: cover;
}
.journal-map-all {
flex: 1;
font-size: 0.8rem;
opacity: 0.8;
}
.journal-map-line-toggle {
margin-left: auto;
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
cursor: pointer;
}
/* ── Card meta & status ───────────────────────────────────────────── */
.journal-admin-card-meta span {
display: inline-flex;
align-items: center;
gap: 4px;
}
.journal-card-cover-placeholder {
opacity: 0.25;
}
.journal-status-pill {
padding: 0.15rem 0.5rem;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
opacity: 1;
}
.journal-status-pill.is-draft {
background: rgba(234, 179, 8, 0.15);
color: #eab308;
border: 1px solid rgba(234, 179, 8, 0.3);
}
.journal-status-pill.is-published {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
border: 1px solid rgba(34, 197, 94, 0.3);
}
.journal-preview-bar-label {
display: inline-flex;
align-items: center;
gap: 6px;
}
/* ── Block field rows ─────────────────────────────────────────────
* The generic `.admin-input { width: 100% }` would blow these rows apart,
* so the fixed-width controls are pinned here (higher specificity wins).
*/
.journal-heading-row,
.journal-photo-layout-row {
display: flex;
align-items: center;
gap: 8px;
}
.journal-editor-container .journal-heading-row .journal-level-select {
flex: 0 0 5.5rem;
width: 5.5rem;
}
.journal-editor-container .journal-heading-row input {
flex: 1 1 auto;
min-width: 0;
}
.journal-editor-container .journal-photo-layout-row select {
flex: 1 1 auto;
min-width: 0;
}
.journal-photo-layout-row .admin-btn {
flex-shrink: 0;
}
/* Stack the split view on narrow screens instead of squeezing both panes. */
@media (max-width: 1100px) {
.journal-editor-split {
grid-template-columns: 1fr;
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
}
.journal-editor-resizer {
display: none;
}
.journal-editor-pane-left {
border-bottom: 1px solid var(--admin-border, rgba(255, 255, 255, 0.08));
}
}
/* An unresolvable photo reference — shown in the block editor, not to visitors. */
.journal-block-warning {
margin: 0 0 0.75rem;
padding: 0.55rem 0.7rem;
border-radius: 8px;
font-size: 0.78rem;
line-height: 1.45;
color: #fca5a5;
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
}
/* Phones: the top bar was one non-wrapping row, so the title input shrank to
nothing and Save ran off the screen — and there is no ⌘S on a phone (#675).
Below 700px it wraps: back button and mode toggle, the title on a line of
its own, then the actions with Save taking the remaining width. */
@media (max-width: 700px) {
/* The workspace already keeps a gutter; a second 2rem one left the cards
under 300px wide. */
.journal-list-grid {
padding: 1.25rem 0;
}
.journal-editor-topbar {
flex-wrap: wrap;
padding: 0.75rem 1rem;
gap: 0.75rem;
}
.journal-editor-topbar-left {
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
width: 100%;
}
.journal-editor-title-input {
order: 3;
flex: 1 1 100%;
max-width: none;
min-width: 0;
border-color: var(--admin-border, rgba(255, 255, 255, 0.08));
}
.journal-editor-topbar-right {
width: 100%;
gap: 0.5rem;
}
.journal-editor-topbar-right > .admin-btn-primary {
flex: 1;
justify-content: center;
}
}
/* ── Content page settings: a side panel, not a modal (#722) ── */
.page-settings-panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: min(360px, 100vw);
z-index: 100;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.25rem;
overflow-y: auto;
background: var(--admin-bg-raised);
border-left: 1px solid var(--admin-border);
box-shadow: -12px 0 32px rgba(0, 0, 0, 0.25);
}
.page-settings-panel-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.page-settings-panel-head h3 {
margin: 0;
font-size: 1rem;
}
.page-settings-panel textarea {
width: 100%;
resize: vertical;
}
.page-settings-panel-note {
margin: 0;
font-size: 0.75rem;
color: var(--admin-text-muted);
}