Files
immich-folio/app/admin/admin.css
Ralksta 10ec68f493 release: v0.12.0 (#520)
Forty-one commits since v0.11.0. See CHANGELOG.md for the full list and the upgrade notes.
2026-08-23 23:18:14 +02:00

4783 lines
97 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ── Admin Panel Styles ─────────────────────────────────────── */
/* Self-contained — does not affect the public-facing gallery. */
/* Supports light/dark mode via data-theme attribute on <html>. */
/* ── Theme Variables ───────────────────────────────────────── */
/* .admin-listbox-popup is listed alongside .admin-layout because it is
portalled to <body>, outside the layout subtree. Without its own copy every
var below resolves to nothing there, and the popup paints transparent. */
.admin-layout,
.admin-listbox-popup {
/* Dark mode (default) */
--admin-bg: #111111;
--admin-bg-raised: #1a1a1a;
--admin-bg-elevated: #222222;
--admin-bg-input: #161616;
--admin-border: #2e2e2e;
--admin-border-hover: #444444;
--admin-border-focus: #666666;
--admin-text: #f0f0f0;
--admin-text-secondary: #a0a0a0;
--admin-text-muted: #707070;
--admin-accent: #f59e0b;
/* One height for every control in the header row. */
--admin-control-height: 36px;
--admin-success: #4ade80;
--admin-error: #f87171;
--admin-error-bg: #2d1111;
--admin-error-border: #5c2020;
--admin-btn-primary-bg: #ffffff;
--admin-btn-primary-text: #000000;
--admin-btn-primary-hover: #e5e5e5;
--admin-overlay: rgba(0, 0, 0, 0.7);
}
[data-theme='light'] .admin-layout,
[data-theme='light'] .admin-listbox-popup {
--admin-bg: #f8f8f8;
--admin-bg-raised: #ffffff;
--admin-bg-elevated: #f0f0f0;
--admin-bg-input: #ffffff;
--admin-border: #e0e0e0;
--admin-border-hover: #c0c0c0;
--admin-border-focus: #999999;
--admin-text: #1a1a1a;
--admin-text-secondary: #555555;
--admin-text-muted: #888888;
--admin-accent: #d97706;
--admin-success: #16a34a;
--admin-error: #dc2626;
--admin-error-bg: #fef2f2;
--admin-error-border: #fecaca;
--admin-btn-primary-bg: #1a1a1a;
--admin-btn-primary-text: #ffffff;
--admin-btn-primary-hover: #333333;
--admin-overlay: rgba(0, 0, 0, 0.4);
}
.admin-layout {
min-height: 100vh;
background: var(--admin-bg);
color: var(--admin-text);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
}
/* ── Loading / Disabled ─────────────────────────────────────── */
.admin-loading {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.admin-spinner {
width: 24px;
height: 24px;
border: 2px solid var(--admin-border);
border-top-color: var(--admin-text);
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.admin-disabled {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.admin-disabled-card {
max-width: 420px;
text-align: center;
}
.admin-disabled-card h1 {
font-size: 1.5rem;
margin-bottom: 0.75rem;
}
.admin-disabled-card code {
background: var(--admin-bg-raised);
padding: 0.2em 0.5em;
border-radius: 4px;
font-size: 0.85em;
color: var(--admin-accent);
}
/* ── Login ─────────────────────────────────────────────────── */
.admin-login {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.admin-login-card {
width: 100%;
max-width: 360px;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
padding: 2rem;
}
.admin-login-header {
text-align: center;
margin-bottom: 1.5rem;
}
.admin-login-header h1 {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
}
.admin-login-header p {
color: var(--admin-text-secondary);
margin: 0.25rem 0 0;
font-size: 0.875rem;
}
/* ── Fields ─────────────────────────────────────────────────── */
.admin-field {
margin-bottom: 0.75rem;
}
.admin-field label {
display: block;
font-size: 0.75rem;
font-weight: 500;
color: var(--admin-text-secondary);
margin-bottom: 0.3rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.admin-field input,
.admin-field select,
.admin-field textarea {
width: 100%;
padding: 0.6rem 0.75rem;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 6px;
color: var(--admin-text);
font-size: 0.875rem;
transition: border-color 0.15s;
}
.admin-field input:focus,
.admin-field select:focus,
.admin-field textarea:focus {
outline: none;
border-color: var(--admin-border-focus);
}
.admin-field textarea {
resize: vertical;
min-height: 60px;
}
/* ── Listbox (select replacement that can render SVG icons) ────── */
.admin-listbox {
position: relative;
width: 100%;
}
.admin-listbox-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
padding: 0.6rem 0.75rem;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 6px;
color: var(--admin-text);
font-size: 0.875rem;
font-family: inherit;
text-align: left;
cursor: pointer;
transition: border-color 0.15s;
}
.admin-listbox-trigger:hover:not(:disabled) {
border-color: var(--admin-border-hover);
}
.admin-listbox-trigger:focus-visible {
outline: none;
border-color: var(--admin-border-focus);
}
.admin-listbox-trigger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.admin-listbox-value {
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
}
.admin-listbox-value > span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-listbox-value .svg-icon,
.admin-listbox-option > .svg-icon {
color: var(--admin-text-secondary);
}
.admin-listbox-caret {
color: var(--admin-text-secondary);
transition: transform 0.15s ease;
}
.admin-listbox-trigger[aria-expanded='true'] .admin-listbox-caret {
transform: rotate(180deg);
}
/* Portalled to <body>, so it escapes the overflow of any scrolling ancestor.
left/top/bottom/width/max-height are measured from the trigger and set inline
by the component; the z-index clears the modal overlay (1000). */
.admin-listbox-popup {
position: fixed;
z-index: 1100;
overflow-y: auto;
/* A long list scrolls internally; without this, reaching its end hands the
wheel to whatever is behind the modal. */
overscroll-behavior: contain;
margin: 0;
padding: 0.25rem;
list-style: none;
/* Also outside .admin-layout, so the font does not inherit either. */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
border-radius: 8px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
}
[data-theme='light'] .admin-listbox-popup {
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
}
.admin-listbox-option {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.5rem;
border-radius: 5px;
color: var(--admin-text);
font-size: 0.875rem;
cursor: pointer;
}
.admin-listbox-option.is-active {
background: var(--admin-bg-raised);
}
.admin-listbox-option.is-selected {
color: var(--admin-accent);
}
.admin-listbox-option.is-selected > .svg-icon {
color: var(--admin-accent);
}
.admin-listbox-option-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-listbox-check {
color: var(--admin-accent);
}
.admin-field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
.admin-field-checks {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin: 0.75rem 0;
}
.admin-field-checks label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--admin-text);
cursor: pointer;
text-transform: none;
letter-spacing: 0;
}
.admin-field-checks input[type='checkbox'] {
width: 16px;
height: 16px;
accent-color: var(--admin-btn-primary-bg);
}
/* ── Error ─────────────────────────────────────────────────── */
.admin-error {
background: var(--admin-error-bg);
border: 1px solid var(--admin-error-border);
color: var(--admin-error);
padding: 0.5rem 0.75rem;
border-radius: 6px;
font-size: 0.8rem;
margin-bottom: 0.75rem;
}
/* ── Buttons ───────────────────────────────────────────────── */
.admin-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
padding: 0 1.2rem;
min-height: var(--admin-control-height);
/* <button> gets this from the UA stylesheet, <a> does not — without it the
* ghost border pushed the "Site" link 2px past its neighbours. */
box-sizing: border-box;
border: none;
border-radius: 6px;
font-family: inherit;
font-size: 0.875rem;
font-weight: 500;
/*
* Explicit, because <a class="admin-btn"> and <button class="admin-btn">
* otherwise resolve `normal` against different inline boxes — the "Site" link
* ended up half a pixel taller than the buttons next to it.
*/
line-height: 1;
cursor: pointer;
transition: all 0.15s;
}
.admin-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.admin-btn-primary {
background: var(--admin-btn-primary-bg);
color: var(--admin-btn-primary-text);
}
.admin-btn-primary:hover:not(:disabled) {
background: var(--admin-btn-primary-hover);
}
.admin-btn-ghost {
background: transparent;
color: var(--admin-text-secondary);
border: 1px solid var(--admin-border);
}
.admin-btn-ghost:hover:not(:disabled) {
color: var(--admin-text);
border-color: var(--admin-border-hover);
}
/*
* Written as `.admin-btn.admin-btn-*` on purpose: these variants are combined
* with the size classes (`admin-btn-sm`, `admin-btn-xs`), which set their own
* background further down. The doubled class outranks them whatever the order.
*/
/* Neutral action next to a primary one. */
.admin-btn.admin-btn-secondary {
background: var(--admin-bg-elevated);
color: var(--admin-text);
border: 1px solid var(--admin-border);
}
.admin-btn.admin-btn-secondary:hover:not(:disabled) {
background: var(--admin-bg-raised);
border-color: var(--admin-border-hover);
}
/* Destructive action — must read as destructive before it is clicked. */
.admin-btn.admin-btn-danger {
background: transparent;
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.35);
}
.admin-btn.admin-btn-danger:hover:not(:disabled) {
background: rgba(239, 68, 68, 0.14);
border-color: rgba(239, 68, 68, 0.6);
color: #fca5a5;
}
.admin-btn-sm {
padding: 0 0.8rem;
min-height: 30px;
font-size: 0.8rem;
background: var(--admin-bg-elevated);
color: var(--admin-text-secondary);
border: 1px solid var(--admin-border);
}
.admin-btn-sm:hover {
background: var(--admin-border);
border-color: var(--admin-border-hover);
color: var(--admin-text);
}
.admin-btn-xs {
padding: 0 0.6rem;
min-height: 26px;
font-size: 0.75rem;
background: var(--admin-bg-raised);
color: var(--admin-text-secondary);
border: 1px solid var(--admin-border);
}
.admin-btn-xs:hover {
color: var(--admin-text);
border-color: var(--admin-border-hover);
}
.admin-btn-icon {
background: none;
border: none;
color: var(--admin-text-muted);
cursor: pointer;
font-size: 1rem;
padding: 0.25rem;
border-radius: 4px;
transition: color 0.15s;
}
.admin-btn-icon:hover {
color: var(--admin-text);
}
.admin-btn-icon:disabled {
opacity: 0.3;
cursor: not-allowed;
}
/* ── Dashboard Layout ──────────────────────────────────────── */
.admin-dashboard {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.admin-header {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.5rem;
background: var(--admin-bg);
border-bottom: 1px solid var(--admin-border);
backdrop-filter: blur(8px);
}
.admin-header h1 {
font-size: 1rem;
font-weight: 600;
margin: 0;
white-space: nowrap;
}
.admin-header-left {
display: flex;
align-items: center;
gap: 1.5rem;
}
.admin-header-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.admin-header-divider {
width: 1px;
height: 20px;
background: var(--admin-border);
margin: 0 0.25rem;
}
.admin-tabs {
display: flex;
gap: 0;
}
/* Rendered as an <a> — each tab is its own route. */
.admin-tab {
background: none;
border: none;
color: var(--admin-text-muted);
padding: 0.5rem 1rem;
font-size: 0.85rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.15s;
display: inline-block;
text-decoration: none;
}
.admin-tab:hover {
color: var(--admin-text-secondary);
}
.admin-tab.active {
color: var(--admin-text);
border-bottom-color: var(--admin-text);
}
.admin-main {
flex: 1;
/* Extra bottom padding so the floating save bar never covers page content. */
padding: 1.5rem 1.5rem 6rem;
max-width: 1120px;
margin: 0 auto;
width: 100%;
}
/* ── Save Bar ──────────────────────────────────────────────── */
.save-bar-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.unsaved-badge {
display: inline-flex;
align-items: center;
font-size: 0.75rem;
color: var(--admin-accent);
font-weight: 500;
}
.save-message {
font-size: 0.75rem;
font-weight: 500;
}
.save-message.success {
color: var(--admin-success);
}
.save-message.error {
color: var(--admin-error);
}
/* ── Page Builder ──────────────────────────────────────────── */
.builder-section {
margin-bottom: 2rem;
}
.builder-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.builder-section-header h2 {
font-size: 1rem;
font-weight: 600;
margin: 0;
}
.empty-hint {
color: var(--admin-text-muted);
font-size: 0.8rem;
font-style: italic;
padding: 0.75rem;
border: 1px dashed var(--admin-border);
border-radius: 6px;
text-align: center;
}
/* Hero Grid */
.hero-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 0.75rem;
}
.hero-tile {
position: relative;
aspect-ratio: 16 / 10;
border-radius: 10px;
overflow: hidden;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
transition: border-color 0.15s;
}
.hero-tile:hover {
border-color: var(--admin-border-hover);
}
.hero-tile img {
width: 100%;
height: 100%;
object-fit: cover;
}
.hero-tile-remove {
position: absolute;
top: 6px;
right: 6px;
width: 26px;
height: 26px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
border: none;
color: #fff;
font-size: 0.9rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.15s;
}
.hero-tile:hover .hero-tile-remove {
opacity: 1;
}
.hero-tile-remove:hover {
background: rgba(220, 40, 40, 0.8);
}
.hero-tile-index {
position: absolute;
bottom: 6px;
left: 6px;
width: 22px;
height: 22px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 0.7rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
/* Album Grid */
.album-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1rem;
}
.album-tile {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 10px;
overflow: hidden;
transition:
border-color 0.15s,
box-shadow 0.15s;
}
.album-tile:hover {
border-color: var(--admin-border-hover);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.album-tile-cover {
position: relative;
aspect-ratio: 1;
background: var(--admin-bg-elevated);
overflow: hidden;
}
.album-tile-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.album-tile-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.album-tile-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
opacity: 0;
transition: opacity 0.15s;
}
.album-tile:hover .album-tile-overlay {
opacity: 1;
}
.album-tile-btn {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.25);
color: #fff;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 0.95rem;
transition: background 0.1s;
}
.album-tile-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.album-tile-info {
padding: 0.6rem 0.75rem;
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.album-tile-name {
font-size: 0.875rem;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.album-tile-count {
font-size: 0.75rem;
color: var(--admin-text-muted);
}
.album-tile-details {
padding: 0.75rem;
border-top: 1px solid var(--admin-border);
background: var(--admin-bg);
}
/* Album Hero Field */
.album-hero-field {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.album-hero-preview {
position: relative;
width: 80px;
height: 54px;
border-radius: 6px;
overflow: hidden;
flex-shrink: 0;
border: 1px solid var(--admin-border);
}
.album-hero-preview img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.album-hero-remove {
position: absolute;
top: 2px;
right: 2px;
width: 18px;
height: 18px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.65);
color: #fff;
border: none;
cursor: pointer;
font-size: 0.75rem;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.album-hero-empty {
font-size: 0.8rem;
color: var(--admin-text-muted);
font-style: italic;
}
.album-tile-uuid {
margin-top: 0.5rem;
}
.album-tile-uuid code {
font-size: 0.7rem;
color: var(--admin-text-muted);
word-break: break-all;
}
/* Subpage Overview Grid */
.subpage-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.75rem;
margin-bottom: 1rem;
}
.subpage-tile {
position: relative;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 10px;
overflow: hidden;
cursor: pointer;
transition:
border-color 0.15s,
box-shadow 0.15s,
transform 0.1s;
}
.subpage-tile:hover {
border-color: var(--admin-border-hover);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.subpage-tile.active {
border-color: var(--admin-accent);
box-shadow: 0 0 0 1px var(--admin-accent);
}
.subpage-tile-cover {
position: relative;
aspect-ratio: 16 / 10;
background: var(--admin-bg-elevated);
overflow: hidden;
}
.subpage-tile-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.subpage-tile-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.subpage-tile-info {
padding: 0.4rem 0.6rem;
display: flex;
flex-direction: column;
gap: 0.05rem;
}
.subpage-tile-name {
font-size: 0.78rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.subpage-tile-meta {
font-size: 0.65rem;
color: var(--admin-text-muted);
}
/* Subpage Expanded Detail */
.subpage-detail {
background: var(--admin-bg);
border: 1px solid var(--admin-border);
border-radius: 10px;
overflow: hidden;
animation: slideDown 0.15s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.subpage-detail-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: var(--admin-bg-raised);
border-bottom: 1px solid var(--admin-border);
}
.subpage-detail-header-left {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
min-width: 0;
}
.subpage-detail-header-right {
display: flex;
align-items: center;
gap: 0.25rem;
}
.subpage-move-btns {
display: flex;
gap: 0;
}
.subpage-move-btns .admin-btn-icon {
font-size: 0.75rem;
padding: 0.1rem 0.3rem;
}
.subpage-name-input {
flex: 1;
background: transparent;
border: 1px solid transparent;
color: var(--admin-text);
font-size: 0.95rem;
font-weight: 600;
padding: 0.3rem 0.5rem;
border-radius: 4px;
transition: border-color 0.15s;
}
.subpage-name-input:hover {
border-color: var(--admin-border);
}
.subpage-name-input:focus {
outline: none;
border-color: var(--admin-border-focus);
background: var(--admin-bg-input);
}
.subpage-meta {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--admin-border);
}
.subpage-albums {
padding: 0.75rem 1rem;
}
.subpage-albums-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
font-size: 0.75rem;
color: var(--admin-text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.subpage-sections {
padding: 0 1rem 0.5rem;
}
.subpage-actions {
display: flex;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-top: 1px solid var(--admin-border);
}
/* Section Card */
.section-card {
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 8px;
padding: 0.75rem;
margin-bottom: 0.5rem;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.section-title-input {
flex: 1;
background: transparent;
border: 1px solid transparent;
color: var(--admin-text);
font-size: 0.85rem;
font-weight: 500;
padding: 0.2rem 0.4rem;
border-radius: 4px;
}
.section-title-input:focus {
outline: none;
border-color: var(--admin-border-hover);
background: var(--admin-bg);
}
.section-desc-input {
font-size: 0.8rem !important;
padding: 0.4rem 0.5rem !important;
}
/* ── Album Picker ──────────────────────────────────────────── */
.picker-overlay {
position: fixed;
inset: 0;
z-index: 1000;
background: var(--admin-overlay);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
backdrop-filter: blur(4px);
}
.picker-modal {
width: 100%;
max-width: 560px;
max-height: 80vh;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.picker-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--admin-border);
}
.picker-header h3 {
font-size: 1rem;
font-weight: 600;
margin: 0;
}
.picker-search {
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--admin-border);
}
.picker-search input {
width: 100%;
padding: 0.6rem 0.75rem;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 6px;
color: var(--admin-text);
font-size: 0.85rem;
}
.picker-search input:focus {
outline: none;
border-color: var(--admin-border-focus);
}
.picker-list {
flex: 1;
overflow-y: auto;
overscroll-behavior: contain;
padding: 0.5rem;
}
.picker-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 0.75rem;
border-radius: 8px;
cursor: pointer;
transition: background 0.1s;
}
.picker-item:hover:not(.used) {
background: var(--admin-bg-elevated);
}
.picker-item.used {
opacity: 0.5;
cursor: not-allowed;
}
.picker-item-thumb {
flex-shrink: 0;
}
.picker-thumb-placeholder {
width: 52px;
height: 52px;
display: flex;
align-items: center;
justify-content: center;
background: var(--admin-bg-elevated);
border-radius: 8px;
font-size: 1.2rem;
}
.picker-thumb-img {
width: 52px;
height: 52px;
object-fit: cover;
border-radius: 8px;
background: var(--admin-bg-elevated);
}
.picker-item-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.picker-item-name {
font-size: 0.85rem;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.picker-item-meta {
font-size: 0.7rem;
color: var(--admin-text-muted);
}
.picker-item-action {
flex-shrink: 0;
}
.picker-used-badge {
font-size: 0.7rem;
color: var(--admin-text-muted);
padding: 0.2rem 0.5rem;
border: 1px solid var(--admin-border);
border-radius: 4px;
}
.picker-select-badge {
font-size: 0.7rem;
color: var(--admin-success);
padding: 0.2rem 0.5rem;
border: 1px solid var(--admin-success);
border-radius: 4px;
opacity: 0;
transition: opacity 0.1s;
}
.picker-item:hover .picker-select-badge {
opacity: 1;
}
/* ── Settings Editor ──────────────────────────────────────── */
.settings-live-sync-note {
margin: 0 0 1.25rem;
font-size: 0.75rem;
color: var(--admin-text-muted);
display: flex;
align-items: center;
gap: 6px;
}
.settings-layout {
display: grid;
grid-template-columns: 160px 1fr;
gap: 2rem;
}
.settings-nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
/* Rendered as an <a> — each settings section is its own route. */
.settings-nav-item {
background: none;
border: none;
color: var(--admin-text-muted);
text-align: left;
padding: 0.5rem 0.75rem;
font-size: 0.85rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.1s;
display: block;
text-decoration: none;
}
.settings-nav-item:hover {
color: var(--admin-text);
background: var(--admin-bg-raised);
}
.settings-nav-item.active {
color: var(--admin-text);
background: var(--admin-bg-elevated);
}
.settings-content {
min-width: 0;
}
.settings-panel h3 {
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem;
}
/* Preset Card Grid */
.preset-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
width: 100%;
}
.preset-card {
display: flex;
flex-direction: column;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 10px;
overflow: hidden;
cursor: pointer;
padding: 0;
text-align: left;
transition:
transform 0.2s cubic-bezier(0.16, 1, 0.3, 1),
border-color 0.2s,
box-shadow 0.2s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.preset-card:hover {
transform: translateY(-2px);
border-color: var(--admin-border-hover);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.preset-card.active {
border-color: var(--admin-accent);
box-shadow: 0 0 0 2px var(--admin-accent);
}
/* ── Preset Mini Preview Mockup ──────────────────────────────────
* Driven entirely by the --preset-* custom properties the card sets inline,
* so each mockup reflects that preset's real background, tile colour, corner
* radius, grid density and photo frame instead of looking identical.
*/
.preset-card-preview {
width: 100%;
aspect-ratio: 16 / 10;
padding: 0.55rem;
display: flex;
flex-direction: column;
gap: 0.45rem;
border-bottom: 1px solid var(--admin-border);
position: relative;
overflow: hidden;
background: var(--preset-bg);
}
.preset-card-preview .mini-header {
display: flex;
align-items: baseline;
gap: 0.4rem;
flex-shrink: 0;
}
/* Type specimen: generic families stand in for the preset's real webfont,
* which is not loaded inside the admin panel. */
.preset-card-preview .mini-specimen {
font-size: 0.95rem;
line-height: 1;
color: var(--preset-accent);
}
.preset-card-preview .mini-specimen.type-serif {
font-family: Georgia, 'Times New Roman', serif;
}
.preset-card-preview .mini-specimen.type-sans {
font-family: 'Helvetica Neue', Arial, sans-serif;
font-weight: 600;
letter-spacing: -0.02em;
}
.preset-card-preview .mini-specimen.type-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.85rem;
}
.preset-card-preview .mini-line {
height: 2px;
flex: 1;
max-width: 46px;
border-radius: 1px;
background: var(--preset-accent);
opacity: 0.4;
}
.preset-card.active .preset-card-preview .mini-line {
opacity: 1;
}
.preset-card-preview .mini-grid {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--preset-gap, 4px);
}
.preset-card-preview .mini-tile {
background: var(--preset-tile);
border-radius: var(--preset-radius, 0);
/* Keeps near-white tiles on a white page (Minimal, Classic) readable. */
box-shadow: inset 0 0 0 1px rgba(128, 128, 128, 0.16);
}
/* Passepartout: a mat board in the page colour framing the photo. */
.preset-card-preview[data-frame='passepartout'] .mini-tile {
padding: 4px;
background-clip: content-box;
border: 1px solid rgba(128, 128, 128, 0.3);
}
.preset-card-preview[data-frame='shadow'] .mini-tile {
box-shadow:
0 2px 5px rgba(0, 0, 0, 0.28),
inset 0 0 0 1px rgba(128, 128, 128, 0.16);
}
.preset-card-specs {
font-size: 0.62rem;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
color: var(--admin-text-muted);
opacity: 0.75;
margin-top: 0.15rem;
}
.preset-card-info {
padding: 0.6rem 0.75rem;
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.preset-card-name {
font-size: 0.85rem;
font-weight: 600;
color: var(--admin-text);
text-transform: capitalize;
}
.preset-card-desc {
font-size: 0.68rem;
color: var(--admin-text-muted);
line-height: 1.3;
}
/* ── Status Diagnostics & Badge ──────────────────────────────── */
.status-indicator-container {
position: relative;
display: inline-block;
margin-right: 0.5rem;
}
/*
* A status readout, not an action: same height and typography as the buttons
* beside it, but pill-shaped so the row still reads as "state | actions".
* It inherited the browser's default button font before, which is why it looked
* like it came from a different app.
*/
.status-badge-btn {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0 0.85rem;
min-height: var(--admin-control-height);
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 999px;
font-family: inherit;
font-size: 0.8rem;
font-weight: 500;
line-height: 1;
color: var(--admin-text-secondary);
cursor: pointer;
transition: all 0.15s;
}
.status-badge-btn:hover {
background: var(--admin-bg-elevated);
border-color: var(--admin-border-hover);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
position: relative;
}
/* Healthy is the resting state — no animation. A permanently pulsing dot pulls
* the eye toward the one thing that needs no attention. */
.status-badge-btn.connected .status-dot {
background-color: var(--admin-success, #10b981);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.18);
}
.status-badge-btn.disconnected .status-dot {
background-color: var(--admin-error, #ef4444);
box-shadow: 0 0 8px var(--admin-error, #ef4444);
animation: pulse-red 1.5s infinite;
}
/* The diagnostics request itself did not run — deliberately not red, and not
animated: an unanswered check is not a fault report. */
.status-badge-btn.unknown .status-dot {
background-color: var(--admin-text-secondary);
}
.status-text {
font-size: inherit;
font-weight: inherit;
color: inherit;
}
/* Status Dropdown */
.status-dropdown-backdrop {
position: fixed;
inset: 0;
z-index: 99;
background: transparent;
}
.status-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 0.5rem;
width: 260px;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
z-index: 100;
overflow: hidden;
animation: slideDown 0.15s cubic-bezier(0.16, 1, 0.3, 1);
}
.status-dropdown-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.65rem 0.85rem;
border-bottom: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
}
.status-dropdown-header h4 {
margin: 0;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--admin-text-muted);
}
.status-refresh-btn {
background: none;
border: none;
color: var(--admin-text-muted);
cursor: pointer;
font-size: 0.95rem;
padding: 0 0.2rem;
line-height: 1;
transition: color 0.15s;
}
.status-refresh-btn:hover {
color: var(--admin-text);
}
.status-dropdown-body {
padding: 0.5rem 0.85rem;
display: flex;
flex-direction: column;
}
/* ── Grouped feature settings (#510) ──────────────────────────
The features panel used to be one flat grid of identical cards. These give it
the structure the config already had: labelled groups, and sub-settings that
look like sub-settings instead of peers. */
.settings-group {
margin-bottom: 1.75rem;
}
.settings-group-head {
display: flex;
flex-direction: column;
gap: 0.2rem;
margin-bottom: 0.65rem;
}
.settings-group-title {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--admin-text-secondary);
}
/* Marks a group whose switches publish something, so it does not read like the
one next to it that controls a fade. */
.settings-group-chip {
margin-left: 4px;
padding: 2px 7px;
border: 1px solid var(--admin-border);
border-radius: 999px;
font-size: 0.62rem;
font-weight: 600;
letter-spacing: 0.06em;
color: var(--admin-text-secondary);
}
.settings-group-desc {
font-size: 0.75rem;
color: var(--admin-text-muted);
line-height: 1.35;
}
.settings-subpanel {
padding: 1rem 1.25rem;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
}
.settings-subpanel-head {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding-bottom: 0.75rem;
}
.settings-subpanel-title {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.88rem;
font-weight: 600;
color: var(--admin-text);
}
.settings-subpanel-desc {
font-size: 0.75rem;
color: var(--admin-text-muted);
}
/* Quiet marker on an album Immich still considers private (#515). Deliberately
not styled as a warning: publishing it is allowed, this only says what it is. */
.picker-item-note {
display: inline-flex;
align-items: center;
margin-top: 0.2rem;
font-size: 0.68rem;
letter-spacing: 0.02em;
color: var(--admin-text-muted);
}
/* ── Config doctor report (#491) ──────────────────────────────
Each finding is one line with a severity dot: the point is scanning a list
for the ones that are not green, not reading prose. */
.doctor-list {
margin: 0;
padding: 0;
list-style: none;
}
.doctor-item {
display: flex;
align-items: flex-start;
gap: 0.7rem;
padding: 0.8rem 0;
border-bottom: 1px solid var(--admin-border);
}
.doctor-item:last-child {
border-bottom: none;
}
.doctor-dot {
width: 8px;
height: 8px;
margin-top: 0.42rem;
border-radius: 50%;
flex-shrink: 0;
background: var(--admin-success);
}
.doctor-item--warn .doctor-dot {
background: var(--admin-accent);
}
.doctor-item--error .doctor-dot {
background: var(--admin-error);
}
.doctor-item-body {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.doctor-item-title {
font-size: 0.84rem;
font-weight: 600;
color: var(--admin-text);
}
/* A finding that needs nothing recedes; the eye should land on the others. */
.doctor-item--ok .doctor-item-title {
font-weight: 500;
color: var(--admin-text-secondary);
}
.doctor-item-detail {
font-size: 0.75rem;
line-height: 1.45;
color: var(--admin-text-muted);
}
/* The metadata group as one card with an expander: four permanently visible
sub-switches read as four decisions, when they are details of one. */
.metadata-card {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
overflow: hidden;
transition: border-color 0.15s ease;
}
.metadata-card.active {
border-color: var(--admin-accent);
box-shadow: 0 0 0 1px var(--admin-accent);
}
.metadata-card-main {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.25rem;
background: none;
border: none;
color: inherit;
text-align: left;
font: inherit;
cursor: pointer;
}
.metadata-details-btn {
display: inline-flex;
align-items: center;
gap: 5px;
margin: 0 1.25rem 0.9rem;
padding: 0;
background: none;
border: none;
color: var(--admin-text-secondary);
font: inherit;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
}
.metadata-details-btn:hover {
color: var(--admin-text);
}
.metadata-details-chevron {
transition: transform 0.15s ease;
}
.metadata-details-chevron.open {
transform: rotate(180deg);
}
.metadata-details {
padding: 0 1.25rem 0.6rem;
border-top: 1px solid var(--admin-border);
}
/* The first row sits directly under that border and does not need its own. */
.metadata-details .setting-row:first-child {
border-top: none;
}
.setting-row {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.7rem 0;
background: none;
border: none;
border-top: 1px solid var(--admin-border);
color: inherit;
text-align: left;
cursor: pointer;
font: inherit;
}
/* A sub-setting of the rows above it — indented so the dependency is visible
rather than only true. */
.setting-row--indented {
padding-left: 0.9rem;
margin-top: 0.35rem;
}
.setting-row--disabled {
cursor: default;
}
.setting-row--disabled .setting-row-title,
.setting-row--disabled .setting-row-desc {
color: var(--admin-text-muted);
}
.setting-row-info {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.setting-row-title {
font-size: 0.81rem;
font-weight: 500;
color: var(--admin-text);
}
.setting-row-desc {
font-size: 0.72rem;
color: var(--admin-text-muted);
line-height: 1.3;
}
/* Says why an inert row is inert, instead of leaving it looking broken. */
.setting-row-hint {
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 0.2rem;
font-size: 0.69rem;
color: var(--admin-text-secondary);
}
.switch-toggle--sm {
width: 34px;
height: 20px;
border-radius: 10px;
}
.switch-toggle--sm .switch-slider {
width: 14px;
height: 14px;
}
.switch-toggle--sm.on .switch-slider {
transform: translateX(14px);
}
/* An inert switch must not look like an off switch someone can turn on. */
.setting-row--disabled .switch-toggle {
background: var(--admin-bg-elevated);
}
.setting-row--disabled .switch-slider {
background: var(--admin-text-muted);
}
/* Explains an unfinished installation before the indicators below it, which
would otherwise read as a fault rather than a pending step. */
.status-setup-note {
margin: 0 0 0.35rem;
padding: 0.5rem 0.6rem;
border-radius: 6px;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
color: var(--admin-text-secondary);
font-size: 0.75rem;
line-height: 1.45;
}
.status-setup-note a {
color: var(--admin-text);
text-decoration: underline;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.45rem 0;
border-bottom: 1px dashed var(--admin-border);
font-size: 0.78rem;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
color: var(--admin-text-muted);
}
.status-val {
font-weight: 500;
color: var(--admin-text);
}
.status-val.ok {
color: var(--admin-success, #10b981);
}
.status-val.error {
color: var(--admin-error, #ef4444);
}
.status-val.unknown {
color: var(--admin-text-secondary);
}
/* ── Search Bar ────────────────────────────────────────────── */
.builder-search-container {
padding: 0.75rem 1.5rem;
background: var(--admin-bg-raised);
border-bottom: 1px solid var(--admin-border);
margin-bottom: 1rem;
}
.builder-search-wrapper {
position: relative;
display: flex;
align-items: center;
max-width: 480px;
}
.builder-search-icon {
position: absolute;
left: 10px;
font-size: 0.85rem;
color: var(--admin-text-muted);
pointer-events: none;
}
.builder-search-input {
width: 100%;
padding: 0.5rem 2rem 0.5rem 2rem;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 8px;
color: var(--admin-text);
font-size: 0.82rem;
transition: all 0.15s;
}
.builder-search-input:focus {
outline: none;
border-color: var(--admin-accent);
box-shadow: 0 0 0 1px var(--admin-accent);
background: var(--admin-bg);
}
.builder-search-clear {
position: absolute;
right: 8px;
background: none;
border: none;
color: var(--admin-text-muted);
font-size: 1.1rem;
cursor: pointer;
padding: 0 4px;
}
.builder-search-clear:hover {
color: var(--admin-text);
}
/* Keyframes */
@keyframes pulse-green {
0% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
@keyframes pulse-red {
0% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
}
}
/* Color Field */
.color-field {
display: flex;
align-items: center;
gap: 0.5rem;
}
.color-field input[type='color'] {
width: 36px;
height: 36px;
padding: 0;
border: 1px solid var(--admin-border);
border-radius: 6px;
background: none;
cursor: pointer;
}
.color-field input[type='text'] {
flex: 1;
padding: 0.6rem 0.75rem;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 6px;
color: var(--admin-text);
font-size: 0.875rem;
font-family: monospace;
}
/* Radio Group */
.radio-group {
display: flex;
gap: 1rem;
}
.radio-group label {
display: flex;
align-items: center;
gap: 0.3rem;
font-size: 0.85rem;
color: var(--admin-text);
cursor: pointer;
}
/* ── Asset Picker (Hero) ───────────────────────────────────── */
.asset-picker-modal {
width: 100%;
max-width: 720px;
height: 85vh;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.asset-picker-tabs {
display: flex;
border-bottom: 1px solid var(--admin-border);
}
.asset-picker-tab {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
padding: 0.7rem 1rem;
background: none;
border: none;
color: var(--admin-text-muted);
font-size: 0.85rem;
cursor: pointer;
transition: all 0.15s;
border-bottom: 2px solid transparent;
}
.asset-picker-tab:hover {
color: var(--admin-text-secondary);
background: var(--admin-bg-elevated);
}
.asset-picker-tab.active {
color: var(--admin-text);
border-bottom-color: var(--admin-accent);
}
.asset-picker-grid {
flex: 1;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding: 0.75rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
grid-auto-rows: min-content;
gap: 0.5rem;
align-content: start;
}
.asset-picker-loading {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: center;
padding: 3rem;
}
.asset-picker-loading-more {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.asset-picker-tile {
position: relative;
aspect-ratio: 1;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
background: var(--admin-bg-elevated);
border: 2px solid transparent;
transition:
border-color 0.15s,
transform 0.1s;
}
.asset-picker-tile:hover:not(.used) {
border-color: var(--admin-accent);
transform: scale(1.03);
}
.asset-picker-tile.used {
opacity: 0.4;
cursor: not-allowed;
}
.asset-picker-tile img {
width: 100%;
height: 100%;
object-fit: cover;
}
.asset-picker-used-badge {
position: absolute;
top: 4px;
right: 4px;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--admin-accent);
color: #000;
font-size: 0.7rem;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
.asset-picker-fav-badge {
position: absolute;
top: 4px;
left: 4px;
display: flex;
font-size: 0.85rem;
color: var(--admin-accent);
/* drop-shadow, not text-shadow: the badge is an inline SVG, not a glyph. */
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6));
}
.asset-picker-uuid-section {
padding: 0.75rem 1rem;
border-top: 1px solid var(--admin-border);
background: var(--admin-bg);
}
.asset-picker-uuid-form {
display: flex;
gap: 0.5rem;
align-items: center;
}
.asset-picker-uuid-input {
flex: 1;
padding: 0.5rem 0.75rem;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 6px;
color: var(--admin-text);
font-size: 0.8rem;
font-family: monospace;
}
.asset-picker-uuid-input:focus {
outline: none;
border-color: var(--admin-border-focus);
}
.asset-picker-uuid-error {
font-size: 0.7rem;
color: var(--admin-error);
margin-top: 0.3rem;
display: block;
}
/* ── Drag Handles ──────────────────────────────────────────── */
.hero-tile-drag,
.album-tile-drag,
.subpage-tile-drag {
position: absolute;
z-index: 5;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.8);
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
border-radius: 4px;
cursor: grab;
opacity: 0;
transition: opacity 0.15s;
user-select: none;
touch-action: none;
}
.hero-tile-drag:active,
.album-tile-drag:active,
.subpage-tile-drag:active {
cursor: grabbing;
}
.hero-tile-drag {
top: 6px;
left: 6px;
}
.album-tile-drag {
top: 6px;
left: 6px;
}
.subpage-tile-drag {
top: 6px;
left: 6px;
}
.hero-tile:hover .hero-tile-drag,
.album-tile:hover .album-tile-drag,
.subpage-tile:hover .subpage-tile-drag {
opacity: 1;
}
/* ── Preview & Slug ───────────────────────────────────────────── */
.subpage-slug-preview {
font-size: 0.72rem;
color: var(--admin-text-muted);
font-family: monospace;
padding: 0.15rem 0.4rem;
background: var(--admin-bg);
border-radius: 3px;
white-space: nowrap;
}
.save-bar-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.admin-btn-preview {
font-size: 0.8rem;
padding: 0.4rem 0.8rem;
text-decoration: none;
}
/* ── Danger icon button ───────────────────────────────────────── */
.admin-btn-icon-danger:hover {
color: var(--admin-error) !important;
}
/* ── Responsive ──────────────────────────────────────────────── */
@media (max-width: 768px) {
.admin-main {
padding: 1rem 1rem 6rem;
}
.floating-save-bar {
bottom: 1rem;
width: calc(100% - 2rem);
padding: 0.625rem 0.875rem;
gap: 0.5rem;
}
.admin-header {
padding: 0.5rem 1rem;
}
.admin-header h1 {
display: none;
}
.settings-layout {
grid-template-columns: 1fr;
gap: 1rem;
}
.settings-nav {
flex-direction: row;
overflow-x: auto;
gap: 0;
}
.settings-nav-item {
white-space: nowrap;
}
.admin-field-row {
grid-template-columns: 1fr;
}
}
/* ── Premium Slide-over Drawer & Badges Redesign ──────────────── */
/* Inline SVG Icon Global Styling */
.svg-icon {
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
width: 1em;
height: 1em;
stroke-width: 2px;
transition:
transform 0.2s ease,
color 0.15s ease;
}
/* Drag handles use IconGripVertical's square 24x24 viewBox, so keep the box
square too — a 12x18 box would letterbox the glyph down to 12x12. */
.svg-drag {
width: 18px;
height: 18px;
}
h2 .svg-icon,
h3 .svg-icon {
margin-right: 0.5rem;
font-size: 1.15em;
color: var(--admin-accent);
}
/* Album Card Simplification & Grid Spacing */
.album-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1.25rem;
margin: 0.5rem 0 1.25rem;
}
.album-tile {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
overflow: hidden;
position: relative;
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.album-tile:hover {
transform: translateY(-2px);
border-color: var(--admin-border-hover);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
[data-theme='light'] .album-tile:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
.album-tile-cover {
aspect-ratio: 16 / 11;
border-bottom: 1px solid var(--admin-border);
position: relative;
background: var(--admin-bg-elevated);
overflow: hidden;
}
.album-tile-placeholder {
color: var(--admin-text-muted);
font-size: 1.75rem;
}
.album-tile-overlay {
display: flex;
gap: 0.5rem;
}
.album-tile-btn {
background: rgba(17, 17, 17, 0.85);
backdrop-filter: blur(8px);
border: 1px solid var(--admin-border);
color: var(--admin-text);
width: 32px;
height: 32px;
border-radius: 8px;
transition: all 0.15s ease;
}
.album-tile-btn:hover {
background: var(--admin-accent);
color: #000;
border-color: var(--admin-accent);
}
.album-tile-btn-danger:hover {
background: var(--admin-error) !important;
color: #fff !important;
border-color: var(--admin-error) !important;
}
.album-tile-info {
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.album-tile-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.album-tile-name {
font-size: 0.85rem;
font-weight: 600;
color: var(--admin-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.album-tile-name.custom-title {
color: var(--admin-accent);
font-style: italic;
}
/* Badges for status indications */
.album-tile-badges {
display: flex;
gap: 0.25rem;
flex-shrink: 0;
}
.album-tile-badges .badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 4px;
font-size: 0.7rem;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
color: var(--admin-text-secondary);
}
.album-tile-badges .badge-password {
color: var(--admin-accent);
border-color: rgba(245, 158, 11, 0.3);
background: rgba(245, 158, 11, 0.05);
}
.album-tile-badges .badge-hero {
color: var(--admin-success);
border-color: rgba(74, 222, 128, 0.3);
background: rgba(74, 222, 128, 0.05);
}
/* Carries a mode name ("filename", "oldest"), so it cannot use the fixed
icon-sized box the other badges share. */
.album-tile-badges .badge-sort {
width: auto;
min-width: 18px;
padding: 0 5px;
font-size: 0.62rem;
letter-spacing: 0.03em;
text-transform: uppercase;
}
/* ── Breitwand-Kiste Centered Modal styling ── */
.album-drawer-overlay {
position: fixed;
inset: 0;
z-index: 1000;
background: var(--admin-overlay);
backdrop-filter: blur(12px);
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
opacity: 0;
pointer-events: none;
transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.album-drawer-overlay.open {
opacity: 1;
pointer-events: auto;
}
.album-drawer {
width: 100%;
/* Narrower than the subpage sheet (1100px) so the nested editor still reads
as a layer on top of it, but wide enough for the same two-column body. */
max-width: 960px;
max-height: 90vh;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 16px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
overflow: hidden;
transform: scale(0.95) translateY(10px);
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.album-drawer-overlay.open .album-drawer {
transform: scale(1) translateY(0);
}
.album-drawer-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--admin-border);
display: flex;
align-items: center;
justify-content: space-between;
background: var(--admin-bg-elevated);
}
.album-drawer-header h3 {
font-size: 1.15rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* The drawer's only scroll container. Nesting a second scroller here is what
used to clip the sort listbox popup — the two columns inside must stay
overflow-free and scroll together. */
.album-drawer-body {
flex: 1;
min-height: 0;
overflow-y: auto;
/* Stop the scroll handing off to the page once the form hits its end. */
overscroll-behavior: contain;
padding: 1.75rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
@media (max-width: 768px) {
.album-drawer {
max-height: 95vh;
}
.album-drawer-body {
padding: 1.25rem;
}
}
/* Full-screen on phones, mirroring the subpage sheet. */
@media (max-width: 560px) {
.album-drawer-overlay {
padding: 0;
}
.album-drawer {
max-width: none;
max-height: none;
height: 100dvh;
border-radius: 0;
border: none;
}
}
.album-drawer-body::-webkit-scrollbar {
width: 6px;
}
.album-drawer-body::-webkit-scrollbar-track {
background: transparent;
}
.album-drawer-body::-webkit-scrollbar-thumb {
background: var(--admin-border);
border-radius: 3px;
}
.album-drawer-body::-webkit-scrollbar-thumb:hover {
background: var(--admin-border-hover);
}
.album-drawer-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.75rem;
}
/* Narrow screens cannot fit both buttons plus the UUID on one line; drop the
UUID onto its own row rather than squeezing the actions. */
@media (max-width: 560px) {
.album-drawer-footer .uuid-copy-box {
order: 1;
flex-basis: 100%;
}
}
/* Cover banner across the top of the drawer. flex-shrink must be off: the img
inside is height:100%, so the box has no min-content height and the scrolling
flex column would otherwise crush it to a couple of pixels. */
.modal-cover-container {
width: 100%;
flex: none;
aspect-ratio: 21 / 9;
max-height: 180px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--admin-border);
background: var(--admin-bg);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.modal-cover-container img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.modal-cover-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--admin-text-muted);
font-size: 2.5rem;
}
/* Album name + photo count, directly under the cover banner */
.modal-meta-box {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding-bottom: 0.25rem;
border-bottom: 1px solid var(--admin-border);
}
.modal-album-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--admin-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.modal-album-subtitle {
font-size: 0.8rem;
color: var(--admin-text-secondary);
}
/* Drawer Copy UUID element */
.uuid-copy-box {
display: flex;
align-items: center;
background: var(--admin-bg-input);
border: 1px solid var(--admin-border);
border-radius: 6px;
padding: 0.4rem 0.5rem 0.4rem 0.75rem;
gap: 0.5rem;
}
.uuid-copy-box code {
font-family: monospace;
font-size: 0.75rem;
color: var(--admin-text-secondary);
flex: 1;
word-break: break-all;
user-select: all;
}
/* In the footer the UUID is secondary to the two action buttons: it takes the
leftover width and truncates rather than wrapping the row onto two lines. */
.album-drawer-footer .uuid-copy-box {
flex: 1;
min-width: 0;
padding: 0.3rem 0.35rem 0.3rem 0.6rem;
}
.album-drawer-footer .uuid-copy-box code {
word-break: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.uuid-copy-btn {
background: none;
border: none;
color: var(--admin-text-muted);
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.15s ease;
}
.uuid-copy-btn:hover {
color: var(--admin-accent);
background: var(--admin-bg-elevated);
}
/* Section Card Improvements */
.section-card {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
padding: 1.25rem;
margin-bottom: 1.25rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.section-card:hover {
border-color: var(--admin-border-hover);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.section-header {
border-bottom: 1px solid var(--admin-border);
padding-bottom: 0.5rem;
margin-bottom: 0.75rem;
}
.section-title-input {
font-size: 0.95rem !important;
font-weight: 700 !important;
color: var(--admin-text) !important;
border-radius: 6px !important;
}
.section-desc-input {
border-color: transparent !important;
background: transparent !important;
padding-left: 0.4rem !important;
color: var(--admin-text-secondary) !important;
}
.section-desc-input:focus {
border-color: var(--admin-border) !important;
background: var(--admin-bg-input) !important;
}
/* ── Modern Subpage Details Spacing ── */
.subpage-detail {
margin-top: 1.5rem;
border: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}
[data-theme='light'] .subpage-detail {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.05);
}
.subpage-meta {
padding: 1.25rem;
background: var(--admin-bg-raised);
border-bottom: 1px solid var(--admin-border);
}
/* ── Status Dropdown Footer ── */
.status-dropdown-footer {
padding: 0.5rem 0.85rem;
background: var(--admin-bg-elevated);
border-top: 1px solid var(--admin-border);
display: flex;
justify-content: flex-end;
}
.admin-btn-sm {
padding: 0.35rem 0.65rem !important;
font-size: 0.75rem !important;
}
/* ── Backup Manager Modal ────────────────────────────────────── */
.backup-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(4px);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
animation: fadeIn 0.15s ease-out;
}
.backup-modal-container {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 14px;
width: 100%;
max-width: 640px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.35);
overflow: hidden;
animation: scaleUp 0.15s cubic-bezier(0.16, 1, 0.3, 1);
}
.backup-modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 1.25rem 1.5rem 0.75rem;
border-bottom: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
}
.backup-modal-header h2 {
margin: 0;
font-size: 1.15rem;
font-weight: 700;
color: var(--admin-text);
}
.backup-modal-subtitle {
margin: 0.25rem 0 0;
font-size: 0.8rem;
color: var(--admin-text-muted);
}
.backup-modal-close-btn {
background: none;
border: none;
font-size: 1.1rem;
color: var(--admin-text-muted);
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 6px;
line-height: 1;
transition: all 0.15s;
}
.backup-modal-close-btn:hover {
color: var(--admin-text);
background: var(--admin-bg-input);
}
/* Modal Tabs */
.backup-modal-tabs {
display: flex;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--admin-border);
background: var(--admin-bg-raised);
}
.backup-modal-tab {
padding: 0.4rem 0.85rem;
font-size: 0.82rem;
font-weight: 600;
border-radius: 8px;
background: transparent;
border: 1px solid transparent;
color: var(--admin-text-muted);
cursor: pointer;
transition: all 0.15s;
}
.backup-modal-tab:hover {
color: var(--admin-text);
background: var(--admin-bg-elevated);
}
.backup-modal-tab.active {
background: var(--admin-bg-elevated);
border-color: var(--admin-border);
color: var(--admin-accent);
}
/* Modal Status Alert */
.backup-status-alert {
margin: 0.75rem 1.5rem 0;
padding: 0.65rem 0.85rem;
border-radius: 8px;
font-size: 0.82rem;
font-weight: 500;
}
.backup-status-alert.error {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--admin-error, #ef4444);
color: var(--admin-error, #ef4444);
}
.backup-status-alert.success {
background: rgba(16, 185, 129, 0.1);
border: 1px solid var(--admin-success, #10b981);
color: var(--admin-success, #10b981);
}
/* Confirmation Box */
.backup-confirm-box {
margin: 0.75rem 1.5rem 0;
padding: 1rem;
background: rgba(239, 68, 68, 0.05);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 10px;
}
.backup-confirm-content strong {
display: block;
font-size: 0.9rem;
color: var(--admin-text);
margin-bottom: 0.25rem;
}
.backup-confirm-content p {
margin: 0 0 0.75rem;
font-size: 0.8rem;
color: var(--admin-text-secondary);
line-height: 1.4;
}
.backup-confirm-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
/* Modal Body & List */
.backup-modal-body {
flex: 1;
overflow-y: auto;
overscroll-behavior: contain;
padding: 1rem 1.5rem;
}
.backup-loading,
.backup-empty {
text-align: center;
padding: 2.5rem 1rem;
font-size: 0.85rem;
color: var(--admin-text-muted);
}
.backup-list {
display: flex;
flex-direction: column;
gap: 0.65rem;
}
.backup-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
border-radius: 10px;
transition: border-color 0.15s;
}
.backup-item:hover {
border-color: var(--admin-border-hover);
}
.backup-item-info {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.backup-item-title {
display: flex;
align-items: center;
gap: 0.5rem;
}
.backup-filename {
font-size: 0.88rem;
font-weight: 600;
color: var(--admin-text);
}
.backup-badge {
font-size: 0.68rem;
font-weight: 600;
padding: 0.15rem 0.45rem;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.backup-badge.pre-restore {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
border: 1px solid rgba(245, 158, 11, 0.3);
}
.backup-item-meta code {
font-family: monospace;
font-size: 0.72rem;
color: var(--admin-text-muted);
}
.backup-modal-footer {
padding: 0.85rem 1.5rem;
border-top: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
display: flex;
justify-content: flex-end;
}
.backup-toggle-btn {
margin-top: 0.4rem;
padding: 0.55rem 0.85rem;
background: var(--admin-bg-elevated);
border: 1px dashed var(--admin-border);
border-radius: 8px;
color: var(--admin-text-secondary);
font-size: 0.78rem;
font-weight: 500;
cursor: pointer;
width: 100%;
text-align: center;
transition: all 0.15s ease;
}
.backup-toggle-btn:hover {
background: var(--admin-bg-raised);
border-color: var(--admin-accent);
color: var(--admin-accent);
}
/* ── Visual Cards for Photo Frame & Hero Style ────────────────── */
.frame-card-preview,
.hero-card-preview {
width: 100%;
aspect-ratio: 16 / 10;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--admin-bg-elevated);
border-bottom: 1px solid var(--admin-border);
position: relative;
overflow: hidden;
}
/* Photo Frame Demos */
.mini-frame-demo {
width: 70%;
height: 70%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.mini-frame-photo {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #444, #222);
border-radius: 2px;
}
.mini-frame-demo.frame-none .mini-frame-photo {
border: none;
}
.mini-frame-demo.frame-passepartout {
padding: 6px;
background: #ffffff;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
}
.mini-frame-demo.frame-shadow .mini-frame-photo {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);
}
/* Hero Style Demos */
.mini-hero-demo {
width: 100%;
height: 100%;
display: flex;
gap: 0.35rem;
padding: 0.25rem;
}
.demo-line {
height: 3px;
background: var(--admin-text-muted);
border-radius: 2px;
}
.demo-line.title {
width: 80%;
height: 5px;
background: var(--admin-accent);
}
.demo-line.title.giant {
width: 95%;
height: 8px;
margin-bottom: 0.2rem;
}
.demo-line.title.short {
width: 50%;
margin: 0 auto;
}
.demo-line.title.light {
width: 60%;
background: #ffffff;
}
.demo-line.sub {
width: 50%;
}
.hero-demo-split {
align-items: center;
}
.hero-demo-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.hero-demo-photo {
flex: 1;
height: 100%;
background: linear-gradient(135deg, #3a3a3a, #1a1a1a);
border-radius: 3px;
}
.hero-demo-photo.small {
height: 60%;
width: 80%;
margin: 0 auto;
}
.hero-demo-photo.banner {
width: 100%;
height: 65%;
}
.hero-demo-full {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #555, #111);
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
}
.hero-demo-minimal,
.hero-demo-stacked {
flex-direction: column;
justify-content: center;
}
.hero-demo-center-text {
text-align: center;
width: 100%;
margin-bottom: 0.2rem;
}
.hero-demo-typographic {
flex-direction: column;
}
.hero-demo-grid2 {
display: flex;
gap: 0.25rem;
flex: 1;
}
.hero-demo-mosaic {
display: flex;
gap: 0.25rem;
width: 100%;
height: 100%;
}
.hero-demo-photo.big {
flex: 1.5;
}
.hero-demo-photo-col {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
/* ── Global Button System ────────────────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
font-family: var(--font-sans, system-ui, sans-serif);
font-size: 0.82rem;
font-weight: 600;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.15s ease;
line-height: 1.2;
}
.btn-sm {
padding: 0.35rem 0.65rem !important;
font-size: 0.75rem !important;
border-radius: 6px !important;
}
.btn-secondary {
background: var(--admin-bg-elevated, #242424);
border-color: var(--admin-border, #333333);
color: var(--admin-text, #ffffff);
}
.btn-secondary:hover {
background: var(--admin-bg-raised, #2e2e2e);
border-color: var(--admin-border-hover, #444444);
}
.btn-danger {
background: #ef4444;
color: #ffffff;
border-color: #ef4444;
}
.btn-danger:hover {
background: #dc2626;
border-color: #dc2626;
}
.btn-outline-danger {
background: transparent;
border-color: rgba(239, 68, 68, 0.4);
color: #ef4444;
}
.btn-outline-danger:hover {
background: rgba(239, 68, 68, 0.12);
border-color: #ef4444;
}
/* ── Preset Grid & Buttons (Grid Layout & Aspect Ratio) ───────── */
.preset-grid {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.35rem;
}
.preset-btn {
padding: 0.45rem 0.85rem;
background: var(--admin-bg-input, #1c1c1c);
border: 1px solid var(--admin-border, #333333);
border-radius: 8px;
color: var(--admin-text-secondary, #cccccc);
font-family: var(--font-sans, system-ui, sans-serif);
font-size: 0.8rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
text-transform: capitalize;
}
.preset-btn:hover {
background: var(--admin-bg-elevated, #282828);
border-color: var(--admin-border-hover, #555555);
color: var(--admin-text, #ffffff);
}
.preset-btn.active {
background: var(--admin-bg-elevated, #2a2a2a);
border-color: var(--admin-accent, #e60012);
color: var(--admin-accent, #e60012);
font-weight: 600;
box-shadow: 0 0 0 1px var(--admin-accent, #e60012);
}
/* ── Theme Settings Section Upgrades ─────────────────────────── */
.theme-settings-panel {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.settings-section-header {
margin-bottom: 0.5rem;
}
.settings-section-header h3 {
margin: 0 0 0.25rem 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--admin-text);
}
.settings-section-sub {
margin: 0;
font-size: 0.8rem;
color: var(--admin-text-muted);
}
.settings-section-divider {
height: 1px;
background: var(--admin-border);
margin: 0.75rem 0;
}
/* Segmented Control (Mode Toggle) */
.segmented-control {
display: inline-flex;
background: var(--admin-bg-input, #1c1c1c);
padding: 4px;
border-radius: 10px;
border: 1px solid var(--admin-border, #333);
gap: 4px;
}
.segment-btn {
padding: 0.5rem 1.1rem;
font-size: 0.82rem;
font-weight: 600;
border-radius: 7px;
border: none;
background: transparent;
color: var(--admin-text-muted);
cursor: pointer;
transition: all 0.15s ease;
}
.segment-btn:hover {
color: var(--admin-text);
}
.segment-btn.active {
background: var(--admin-bg-elevated, #2d2d2d);
color: var(--admin-text, #ffffff);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
/* Accent Color Swatches */
.accent-picker-wrapper {
display: flex;
flex-direction: column;
gap: 0.85rem;
}
.color-swatches-row {
display: flex;
align-items: center;
gap: 0.65rem;
flex-wrap: wrap;
}
.color-swatch-btn {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
transition:
transform 0.15s ease,
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.color-swatch-btn:hover {
transform: scale(1.15);
}
.color-swatch-btn.active {
border-color: #ffffff;
box-shadow: 0 0 0 2px var(--admin-accent, #e60012);
transform: scale(1.15);
}
/* Custom Toggle Cards Grid */
.admin-toggle-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.admin-toggle-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
cursor: pointer;
text-align: left;
transition: all 0.15s ease;
}
.admin-toggle-card:hover {
border-color: var(--admin-border-hover);
background: var(--admin-bg-elevated);
}
.admin-toggle-card.active {
border-color: var(--admin-accent);
box-shadow: 0 0 0 1px var(--admin-accent);
}
.toggle-card-info {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding-right: 1rem;
}
.toggle-card-title {
font-size: 0.88rem;
font-weight: 600;
color: var(--admin-text);
display: inline-flex;
align-items: center;
gap: 6px;
}
.toggle-card-desc {
font-size: 0.75rem;
color: var(--admin-text-muted);
line-height: 1.3;
}
/* iOS Style Switch Slider */
.switch-toggle {
width: 42px;
height: 24px;
background: var(--admin-border, #444);
border-radius: 12px;
position: relative;
transition: background-color 0.2s ease;
flex-shrink: 0;
}
.switch-toggle.on {
background: var(--admin-accent, #e60012);
}
.switch-slider {
width: 18px;
height: 18px;
background: #ffffff;
border-radius: 50%;
position: absolute;
top: 3px;
left: 3px;
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.switch-toggle.on .switch-slider {
transform: translateX(18px);
}
/* ── Google Search Result Snippet Preview ────────────────────── */
.google-snippet-preview {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
padding: 1rem 1.25rem;
margin-bottom: 1.25rem;
}
.google-snippet-header {
font-size: 0.8rem;
font-weight: 600;
color: var(--admin-text-muted);
margin-bottom: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.google-snippet-card {
background: #ffffff;
border-radius: 8px;
padding: 1rem 1.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
font-family: Arial, sans-serif;
color: #202124;
}
.google-snippet-url {
font-size: 0.78rem;
color: #202124;
margin-bottom: 0.2rem;
word-break: break-all;
}
.google-snippet-arrow {
font-size: 0.6rem;
color: #5f6368;
}
.google-snippet-title {
font-size: 1.15rem;
font-weight: 400;
color: #1a0dab;
line-height: 1.3;
margin-bottom: 0.25rem;
cursor: pointer;
}
.google-snippet-title:hover {
text-decoration: underline;
}
.google-snippet-desc {
font-size: 0.85rem;
color: #4d5156;
line-height: 1.4;
word-break: break-word;
}
/* Aspect Ratio Mini Preview Box */
.ratio-card-preview {
width: 100%;
aspect-ratio: 16 / 10;
display: flex;
align-items: center;
justify-content: center;
background: var(--admin-bg-elevated);
border-bottom: 1px solid var(--admin-border);
}
.mini-aspect-box {
width: 44px;
height: 44px;
border: 2px dashed var(--admin-accent);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.mini-aspect-box.ratio-1 {
width: 34px;
height: 34px;
}
.mini-aspect-box.ratio-3-2 {
width: 46px;
height: 30px;
}
.mini-aspect-box.ratio-2-3 {
width: 30px;
height: 44px;
}
.mini-aspect-box.ratio-16-9 {
width: 50px;
height: 28px;
}
.mini-aspect-box.ratio-auto {
width: 42px;
height: 32px;
border-style: solid;
}
.mini-aspect-inner {
width: 100%;
height: 100%;
background: var(--admin-accent);
opacity: 0.2;
}
/* Grid Layout Mini Demos */
/*
* Same framing as .ratio-card-preview. Without an explicit box here the demos
* (which size themselves with height: 100%) collapse to zero height.
*/
.grid-card-preview {
width: 100%;
aspect-ratio: 16 / 10;
background: var(--admin-bg-elevated);
border-bottom: 1px solid var(--admin-border);
overflow: hidden;
}
.mini-layout-demo {
width: 100%;
height: 100%;
padding: 0.35rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.demo-masonry-col-group {
display: flex;
gap: 0.25rem;
height: 100%;
width: 100%;
}
.demo-col {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.demo-tile {
background: var(--admin-text-secondary);
opacity: 0.35;
border-radius: 2px;
flex: 1;
transition:
background 0.15s ease,
opacity 0.15s ease;
}
/* The selected layout tints its diagram with the accent colour. */
.preset-card.active .demo-tile {
background: var(--admin-accent);
opacity: 0.55;
}
.demo-tile.h-high {
flex: 2;
}
.demo-tile.h-low {
flex: 1;
}
.demo-tile.h-med {
flex: 1.5;
}
.demo-uniform-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.25rem;
height: 100%;
}
.demo-showcase-grid {
display: flex;
gap: 0.25rem;
height: 100%;
}
/* Deliberately NOT named .hero-tile — that class belongs to the page builder
* and its `aspect-ratio` would leak into this demo. */
.demo-tile.demo-hero {
flex: 2;
}
.demo-filmstrip-row {
display: flex;
gap: 0.25rem;
height: 100%;
align-items: center;
}
/* A band of wide tiles running off the right edge — the horizontal scroll hint
* that separates this demo from the uniform grid. */
.demo-tile.strip {
width: 46%;
height: 62%;
flex-shrink: 0;
}
.demo-editorial-flow {
display: flex;
flex-direction: column;
gap: 0.25rem;
height: 100%;
}
.demo-tile.wide {
width: 100%;
height: 45%;
}
.demo-row {
display: flex;
gap: 0.25rem;
flex: 1;
}
/* ── Subpage Tile Badges & Hover Actions ─────────────────────── */
.subpage-badge-protected {
position: absolute;
top: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(8px);
color: #ffca28;
font-size: 0.7rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 12px;
border: 1px solid rgba(255, 202, 40, 0.3);
z-index: 2;
}
.subpage-hover-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(3px);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
}
.subpage-tile:hover .subpage-hover-overlay {
opacity: 1;
}
.hover-action-btn {
font-size: 0.78rem;
font-weight: 600;
color: #ffffff;
background: var(--admin-accent, #e60012);
padding: 0.4rem 0.85rem;
border-radius: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.subpage-tile-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.subpage-tile-slug {
font-size: 0.7rem;
color: var(--admin-text-muted);
font-family: var(--font-mono, monospace);
background: var(--admin-bg-input);
padding: 1px 6px;
border-radius: 4px;
}
/* ── Subpage Editor Sheet (centered two-column modal) ─────────── */
/* z-index 999 is load-bearing: the album detail modal (1000) and the
portalled Listbox popup (1100) must stack above this sheet. */
.subpage-drawer-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(6px);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
animation: fadeIn 0.2s ease-out;
}
.subpage-drawer-container {
width: 100%;
max-width: 1100px;
max-height: 90vh;
background: var(--admin-bg-card, #181818);
border: 1px solid var(--admin-border, #333);
border-radius: 16px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
/* No fill-mode: after the entry animation the computed transform must
return to none, so the container never becomes a containing block for
fixed-position descendants (Listbox popup measurement). */
animation: sheetIn 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes sheetIn {
from {
opacity: 0;
transform: scale(0.96) translateY(10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.subpage-drawer-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--admin-border);
display: flex;
align-items: center;
justify-content: space-between;
background: var(--admin-bg-raised);
}
.subpage-drawer-breadcrumb {
font-size: 0.75rem;
color: var(--admin-text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
.subpage-drawer-title {
margin: 0.2rem 0 0 0;
font-size: 1.2rem;
font-weight: 700;
color: var(--admin-text);
}
.subpage-drawer-header-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.subpage-drawer-body {
padding: 1.5rem;
overflow-y: auto;
overscroll-behavior: contain;
flex: 1;
/* Required so the flex child scrolls under max-height instead of growing */
min-height: 0;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.subpage-drawer-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--admin-border);
background: var(--admin-bg-raised);
display: flex;
align-items: center;
justify-content: space-between;
}
/* ── Shared two-column layout for admin editor sheets ─────────── */
/* Used by the subpage editor and the album detail editor so both read as the
same surface. Columns always live inside their sheet's single body scroller
— never give a column its own overflow, a nested scroller clips the
portalled Listbox popup (see the .album-drawer-body note). */
.admin-sheet-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: start;
}
/* The subpage editor is asymmetric on purpose: the left column is a settings
form with a natural maximum, the right one holds the album list and wants
every pixel it can get. */
.admin-sheet-columns--settings-aside {
grid-template-columns: minmax(380px, 460px) 1fr;
}
.admin-sheet-col {
display: flex;
flex-direction: column;
gap: 1.25rem;
min-width: 0;
}
.admin-sheet-col--divided {
border-left: 1px solid var(--admin-border);
padding-left: 2rem;
}
@media (max-width: 1024px) {
.admin-sheet-columns,
.admin-sheet-columns--settings-aside {
grid-template-columns: 1fr;
gap: 1.25rem;
}
.admin-sheet-col--divided {
border-left: none;
padding-left: 0;
}
}
@media (max-width: 768px) {
.subpage-drawer-backdrop {
padding: 0;
}
.subpage-drawer-container {
max-width: none;
max-height: none;
height: 100dvh;
border-radius: 0;
border: none;
}
}
@media (prefers-reduced-motion: reduce) {
.subpage-drawer-container,
.subpage-drawer-backdrop {
animation: none;
}
}
/* Breadcrumb + title stack on the left of the drawer header. */
.subpage-drawer-title-group {
display: flex;
flex-direction: column;
min-width: 0;
}
/* One thematic group of fields inside the drawer body. */
.subpage-drawer-section {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 8px;
}
.subpage-drawer-section > .admin-field:only-child {
margin-bottom: 0;
}
/* Heading + hint at the top of a drawer section. */
.drawer-section-heading h4 {
margin: 0;
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
}
.drawer-section-heading p {
margin: 4px 0 0;
font-size: 0.8rem;
color: var(--admin-text-muted);
}
/* Published / hidden marker, replacing the traffic-light emoji. */
/* Named apart from the header badge's .status-dot, which is a different dot. */
.page-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
display: inline-block;
margin-right: 2px;
}
.page-status-dot.is-on {
background: #22c55e;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
}
.page-status-dot.is-off {
background: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.18);
}
.input-with-icon {
position: relative;
display: flex;
align-items: center;
width: 100%;
}
.input-with-icon > input {
width: 100%;
}
.input-slug-wrapper {
position: relative;
display: flex;
align-items: center;
}
.input-slug-preview {
position: absolute;
right: 12px;
font-size: 0.75rem;
font-family: var(--font-mono, monospace);
color: var(--admin-text-muted);
pointer-events: none;
background: var(--admin-bg-elevated);
padding: 2px 8px;
border-radius: 4px;
}
.password-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.password-icon {
position: absolute;
left: 12px;
font-size: 0.85rem;
pointer-events: none;
}
.password-input-wrapper input {
padding-left: 2.25rem !important;
}
/* ── Dynamic Floating Save Bar ───────────────────────────────── */
.floating-save-bar {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
background: var(--admin-bg-raised, #1f1f1f);
border: 1px solid var(--admin-border-hover, #444);
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.05);
border-radius: 16px;
padding: 0.75rem 1.25rem;
z-index: 900;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 1rem;
width: calc(100% - 3rem);
max-width: 800px;
transition: border-color 0.2s ease;
animation: save-bar-in 0.2s ease-out;
}
.floating-save-bar.dirty {
border-color: var(--admin-accent);
}
/* Keyframes must carry the translateX(-50%) or the bar jumps horizontally. */
@keyframes save-bar-in {
from {
opacity: 0;
transform: translate(-50%, 1rem);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
.badge-pulse-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #ff9800;
display: inline-block;
margin-right: 6px;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 152, 0, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 8px rgba(255, 152, 0, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 152, 0, 0);
}
}
@media (prefers-reduced-motion: reduce) {
.floating-save-bar {
animation: none;
}
.badge-pulse-dot {
animation: none;
}
}
/* ── Analytics View ────────────────────────────────────────── */
.analytics-view {
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 1100px;
}
.analytics-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.analytics-header h2 {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.analytics-subtitle {
font-size: 0.8rem;
color: var(--admin-text-muted);
margin: 0.25rem 0 0;
}
.analytics-metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
}
.metric-card {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.metric-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--admin-text-muted);
}
.metric-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--admin-text);
font-family: var(--font-mono, monospace);
}
.metric-sub {
font-size: 0.7rem;
color: var(--admin-text-muted);
}
.analytics-panel {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 12px;
padding: 1.5rem;
}
.analytics-section-title h3 {
font-size: 0.95rem;
font-weight: 600;
margin: 0 0 1.25rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.analytics-bar-chart {
display: flex;
align-items: flex-end;
gap: 0.75rem;
height: 180px;
padding-top: 1.5rem;
}
.bar-col {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.bar-wrapper {
flex: 1;
width: 100%;
max-width: 32px;
display: flex;
align-items: flex-end;
background: var(--admin-bg-elevated);
border-radius: 6px;
overflow: hidden;
}
.bar-fill {
width: 100%;
background: var(--admin-primary, #3b82f6);
border-radius: 6px 6px 0 0;
transition: height 0.3s ease;
position: relative;
display: flex;
align-items: flex-start;
justify-content: center;
}
.bar-val {
font-size: 0.65rem;
font-weight: 600;
color: #fff;
margin-top: 4px;
}
.bar-label {
font-size: 0.68rem;
color: var(--admin-text-muted);
margin-top: 0.5rem;
font-family: var(--font-mono, monospace);
}
.analytics-top-pages {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.top-page-row {
display: flex;
align-items: center;
gap: 1rem;
}
.page-rank {
font-size: 0.8rem;
font-family: var(--font-mono, monospace);
color: var(--admin-text-muted);
width: 24px;
}
.page-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.page-path-line {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.85rem;
}
.page-path {
font-family: var(--font-mono, monospace);
color: var(--admin-text);
}
.page-count {
font-size: 0.75rem;
color: var(--admin-text-muted);
}
.page-progress-bg {
width: 100%;
height: 6px;
background: var(--admin-bg-elevated);
border-radius: 3px;
overflow: hidden;
}
/* The bar itself. Without this the track stayed empty and the view count had
* no visual weight at all. */
.page-progress-fill {
height: 100%;
min-width: 2px;
border-radius: 3px;
background: var(--admin-accent);
transition: width 0.3s ease;
}
.analytics-empty {
margin: 0;
padding: 2rem 1rem;
text-align: center;
font-size: 0.85rem;
color: var(--admin-text-muted);
}
.admin-loading-container {
display: flex;
align-items: center;
justify-content: center;
min-height: 320px;
}
/* ── Drawer Live Preview ────────────────────────────────────── */
.drawer-live-preview-panel {
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 0.5rem 0;
}
.preview-hero-banner {
text-align: center;
padding: 1.75rem 1rem;
background: var(--admin-bg-elevated);
border-radius: 12px;
border: 1px dashed var(--admin-border);
}
.preview-hero-title {
font-size: 1.3rem;
font-weight: 700;
margin: 0;
color: var(--admin-text);
}
.preview-hero-sub {
font-size: 0.82rem;
color: var(--admin-text-muted);
margin: 0.35rem 0 0;
}
.preview-section-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.preview-section-title {
font-size: 0.95rem;
font-weight: 600;
margin: 0;
border-bottom: 1px solid var(--admin-border);
padding-bottom: 0.35rem;
}
.preview-section-desc {
font-size: 0.78rem;
color: var(--admin-text-muted);
margin: 0;
}
.preview-albums-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 0.75rem;
}
.preview-album-tile {
background: var(--admin-bg-raised);
border: 1px solid var(--admin-border);
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.preview-album-cover {
width: 100%;
aspect-ratio: 4/3;
background: var(--admin-bg-elevated);
overflow: hidden;
}
.preview-album-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.preview-album-title {
font-size: 0.75rem;
font-weight: 500;
padding: 0.4rem 0.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ── Photo Essay & Storytelling Builder ───────────────────────── */
.essay-block-editor {
display: flex;
flex-direction: column;
gap: 1rem;
}
.essay-toolbar-sticky {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: var(--admin-bg-raised, #1a1a1a);
border: 1px solid var(--admin-border, #2e2e2e);
border-radius: 10px;
backdrop-filter: blur(8px);
}
.essay-toolbar-label {
font-size: 0.8rem;
font-weight: 600;
color: var(--admin-text, #f0f0f0);
margin-right: 0.5rem;
}
.essay-toolbar-divider {
width: 1px;
height: 20px;
background: var(--admin-border, #2e2e2e);
margin: 0 0.25rem;
}
.essay-block-card {
background: var(--admin-bg-raised, #161616);
border: 1px solid var(--admin-border, #2e2e2e);
border-radius: 10px;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.essay-block-card:hover {
border-color: var(--admin-border-hover, #444444);
}
.essay-block-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--admin-border, rgba(255, 255, 255, 0.06));
}
.essay-block-badge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.2rem 0.55rem;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
background: var(--admin-bg-elevated, #222);
color: var(--admin-text-secondary, #a0a0a0);
border: 1px solid var(--admin-border, #2e2e2e);
}
/* Position prefix inside the chip, e.g. "3 · Quote". */
.essay-block-badge-index {
color: var(--admin-text-muted);
font-variant-numeric: tabular-nums;
}
.essay-block-badge-index::after {
content: ' ·';
}
/* The card's own hover state is enough of a highlight; the chip stays quiet. */
.essay-block-card:hover .essay-block-badge {
border-color: var(--admin-border-hover);
color: var(--admin-text);
}
.essay-block-actions {
display: flex;
align-items: center;
gap: 0.25rem;
}
/* Polished dark inputs for Essay Editor */
.essay-block-editor input[type='text'],
.essay-block-editor textarea,
.essay-block-editor select {
background: var(--admin-bg-input, #111111) !important;
border: 1px solid var(--admin-border, #2e2e2e) !important;
border-radius: 6px !important;
color: var(--admin-text, #f0f0f0) !important;
font-size: 0.85rem !important;
padding: 0.55rem 0.75rem !important;
outline: none !important;
transition: border-color 0.15s !important;
}
.essay-block-editor input[type='text']:focus,
.essay-block-editor textarea:focus,
.essay-block-editor select:focus {
border-color: var(--admin-border-focus, #888888) !important;
}
.essay-photo-card {
display: flex;
flex-direction: column;
gap: 0.75rem;
background: var(--admin-bg, #111111);
padding: 0.85rem;
border-radius: 8px;
border: 1px solid var(--admin-border, #2e2e2e);
}
.essay-photo-preview-container {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.essay-photo-thumbnail {
width: 120px;
height: 80px;
border-radius: 6px;
background: var(--admin-bg-elevated, #222);
object-fit: cover;
border: 1px solid var(--admin-border, #333);
flex-shrink: 0;
}
.essay-photo-empty {
width: 120px;
height: 80px;
border-radius: 6px;
background: var(--admin-bg-elevated, #1a1a1a);
border: 1px dashed var(--admin-border, #333);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--admin-text-muted, #707070);
font-size: 0.75rem;
gap: 4px;
flex-shrink: 0;
}
.essay-photo-meta {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.essay-pill-selector {
display: inline-flex;
background: var(--admin-bg-input, #000);
padding: 2px;
border-radius: 6px;
border: 1px solid var(--admin-border, #2e2e2e);
}
.essay-pill-btn {
padding: 0.25rem 0.65rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
border: none;
background: transparent;
color: var(--admin-text-muted, #888);
cursor: pointer;
transition: all 0.15s ease;
}
.essay-pill-btn.active {
background: var(--admin-btn-primary-bg, #ffffff);
color: var(--admin-btn-primary-text, #000000);
}
/* ── Per-album photo order (sort: manual) ────────────────────── */
.admin-field-hint {
display: block;
margin-top: 0.35rem;
font-size: 0.75rem;
line-height: 1.4;
color: var(--admin-text-muted);
}
.order-editor-body {
flex: 1;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding: 0.75rem;
}
.order-editor-notice {
margin: 0 0 0.75rem;
padding: 0.5rem 0.7rem;
border-radius: 6px;
font-size: 0.78rem;
line-height: 1.4;
color: var(--admin-accent);
border: 1px solid rgba(245, 158, 11, 0.3);
background: rgba(245, 158, 11, 0.05);
}
.order-editor-section + .order-editor-section {
margin-top: 1.25rem;
padding-top: 1rem;
border-top: 1px solid var(--admin-border);
}
.order-editor-section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.order-editor-section-head h4 {
margin: 0;
font-size: 0.85rem;
font-weight: 600;
color: var(--admin-text);
}
.order-editor-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
gap: 0.5rem;
margin-top: 0.5rem;
}
.order-tile {
position: relative;
aspect-ratio: 1;
border-radius: 8px;
overflow: hidden;
background: var(--admin-bg-elevated);
border: 2px solid transparent;
cursor: pointer;
transition:
border-color 0.15s,
opacity 0.15s;
}
.order-tile img {
width: 100%;
height: 100%;
object-fit: cover;
/* The whole tile is a drag handle; without this the browser's native image
drag wins and dnd-kit never sees the gesture. */
-webkit-user-drag: none;
user-select: none;
}
.order-tile-pinned {
border-color: var(--admin-accent);
cursor: grab;
}
.order-tile-pinned:active {
cursor: grabbing;
}
.order-tile-unpinned {
opacity: 0.55;
}
.order-tile-unpinned:hover {
opacity: 1;
border-color: var(--admin-border);
}
.order-tile-index {
position: absolute;
bottom: 4px;
left: 4px;
min-width: 18px;
padding: 0 4px;
border-radius: 4px;
font-size: 0.68rem;
font-weight: 700;
text-align: center;
color: #000;
background: var(--admin-accent);
}
.order-tile-action {
position: absolute;
top: 4px;
right: 4px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 50%;
font-size: 0.85rem;
line-height: 1;
cursor: pointer;
color: var(--admin-text);
background: var(--admin-overlay, rgba(0, 0, 0, 0.6));
}
.order-editor-footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 0.75rem;
border-top: 1px solid var(--admin-border);
}
/* ── Favicon upload (Settings › General) ─────────────────────── */
.favicon-field {
margin-top: 1rem;
}
.favicon-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.favicon-row input[type='file'] {
font-size: 0.8rem;
}