:root {
    --bg: #f4f5f7;
    --surface: #ffffff;
    --border: #e2e4e9;
    --text: #1f2430;
    --text-muted: #6b7280;

    /* ---- Pulskr brand palette (v4) ----
       Deep Charcoal (nav), Pulse Neon (accent), Board Gray (columns) are
       PERMANENT brand chrome - they stay the same in both light and dark
       mode, unlike --bg/--surface/--text above which still respect the
       light/dark toggle. These are the CSS defaults; admin/settings.php's
       "Appearance" section can override them per-install via values
       stored in app_settings and emitted as a :root override block by
       admin/partials/appearance-style.php (included right after this
       stylesheet in every page's <head>, so it wins). */
    --topnav-bg: #121214;      /* Deep Charcoal */
    --topnav-text: #f2f3f5;
    --board-bg: #2A2A2E;       /* Board Gray */
    --board-text: #e9eaed;
    --primary: #00F5D4;        /* Pulse Neon */
    --primary-dark: color-mix(in srgb, var(--primary) 78%, black);
    /* Pulse Neon is very bright - white text on it fails contrast, so
       buttons/accents use a near-black text color on top of it instead. */
    --on-primary: #0a1613;

    --danger: #ef4444;
    --success: #22c55e;
    --radius: 8px;
    --shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 1px 3px rgba(16, 24, 40, 0.08);
}

/* ---------- Dark mode ----------
   Toggled via a data-theme="dark" attribute on <html> (see
   partials/theme-init.php + assets/js/theme.js). Everything else in this
   file references the --* custom properties above, so overriding them
   here is enough to re-theme the whole app. Note: --topnav-bg, --board-bg,
   --primary/--primary-dark/--on-primary are deliberately NOT overridden
   here - they're permanent brand chrome, identical in both modes. */
html[data-theme="dark"] {
    --bg: #14161c;
    --surface: #1c1f27;
    --border: #2c303c;
    --text: #e6e8ee;
    --text-muted: #9aa0b0;
    --danger: #f87171;
    --success: #4ade80;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.45);
}
html[data-theme="dark"] input,
html[data-theme="dark"] select,
html[data-theme="dark"] textarea { background: #262a34; color: var(--text); border-color: var(--border); }
html[data-theme="dark"] .modal { background: var(--surface); }
html[data-theme="dark"] .error-msg { background: #3a1c1c; color: #fca5a5; }

* { box-sizing: border-box; }

body {
    margin: 0;
    /* --gt-body-font/--gc-body/--gt-body-size are the v5 "Style Editor -
       Extended" Global typography/colour overrides (Settings -> Appearance
       -> Global), emitted by admin/partials/appearance-style.php. Falls
       back to the original literal defaults when unset. */
    font-family: var(--gt-body-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif);
    background: var(--bg);
    color: var(--gc-body, var(--text));
    font-size: var(--gt-body-size, 14px);
    /* v5-revision-2 Global typography "case" control (Body level). */
    text-transform: var(--gt-body-transform, none);
    /* v5-revision-3 Global typography "weight" control (Body level). */
    font-weight: var(--gt-body-weight, 400);
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: none; }

/* ---------- Top nav ---------- */
.topnav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Permanent brand chrome - always Deep Charcoal, in both light and
       dark mode (see the --topnav-bg default in :root above). */
    background: var(--topnav-bg);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    padding: 0 20px;
    height: 72px;
    position: sticky;
    top: 0;
    z-index: 50;
}
.topnav .brand { font-weight: 700; font-size: 18px; color: #00F5D4; display: flex; align-items: center; }
.topnav .brand:hover { text-decoration: none; }
/* Enlarged from max-height:32px/max-width:180px so the tagline baked into
   the logo image (admin/assets/img/logo.png and any uploaded
   Settings > Styles company logo of the same aspect ratio) is actually
   legible - the original size scaled the ~1023x242px source down so far
   the tagline text rendered at ~5px tall. Paired with the topnav height
   bump above (56px -> 72px) so the taller logo still sits centered with
   breathing room instead of touching the header edges. */
.topnav .brand-logo { max-height: 52px; max-width: 230px; display: block; }
.topnav nav { display: flex; gap: 4px; align-items: center; }
/* ---------- Global Search (v6.03) ---------- */
.global-search { position: relative; flex: 1; max-width: 320px; margin: 0 16px; }
.global-search input {
    width: 100%; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.14);
    color: var(--topnav-text); border-radius: 999px; padding: 7px 14px; font-size: 13px;
}
.global-search input::placeholder { color: rgba(255, 255, 255, 0.45); }
.global-search input:focus { background: var(--surface); color: var(--text); border-color: var(--primary); }
.global-search-results {
    position: absolute; top: calc(100% + 6px); left: 0; right: 0; background: var(--surface);
    border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
    max-height: 360px; overflow-y: auto; z-index: 60; padding: 4px;
}
.global-search-group-label {
    font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-muted); padding: 6px 8px 2px;
}
.global-search-result {
    display: flex; flex-direction: column; gap: 1px; padding: 6px 8px; border-radius: 6px;
    color: var(--text); text-decoration: none;
}
.global-search-result:hover, .global-search-result.active { background: var(--bg); text-decoration: none; }
.global-search-result .gsr-title { font-size: 13px; font-weight: 500; }
.global-search-result .gsr-meta { font-size: 11px; color: var(--text-muted); }
.global-search-empty { padding: 10px 8px; font-size: 12px; color: var(--text-muted); }
@media (max-width: 900px) {
    .global-search { display: none; } /* narrow header - shortcut/search still reachable via boards.php etc, avoids cramming the topnav */
}

/* ---------- Notifications bell (v6.03) ---------- */
.notif-bell-wrap { position: relative; }
.notif-bell { position: relative; font-size: 14px; padding: 5px 9px; }
.notif-bell-badge {
    position: absolute; top: -4px; right: -4px; background: var(--danger, #d64545); color: #fff;
    border-radius: 999px; font-size: 10px; font-weight: 700; line-height: 1; padding: 3px 5px; min-width: 16px; text-align: center;
}
.notif-dropdown {
    position: absolute; top: calc(100% + 8px); right: 0; width: 300px; background: var(--surface);
    border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
    max-height: 360px; overflow-y: auto; z-index: 60; padding: 4px;
}
.notif-item { display: flex; flex-direction: column; gap: 1px; padding: 8px 10px; border-radius: 6px; color: var(--text); text-decoration: none; }
.notif-item:hover { background: var(--bg); text-decoration: none; }
.notif-item-title { font-size: 13px; font-weight: 500; }
.notif-item-meta { font-size: 11px; color: var(--text-muted); }
.notif-item.overdue .notif-item-meta { color: var(--danger, #d64545); font-weight: 600; }
.notif-empty { padding: 12px 10px; font-size: 12px; color: var(--text-muted); }

/* ---------- Settings > Shortcuts (v6.03) ---------- */
.shortcuts-table { width: 100%; border-collapse: collapse; }
.shortcuts-table th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); padding: 6px 10px; border-bottom: 1px solid var(--border); }
.shortcuts-table td { padding: 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.shortcuts-table tr:last-child td { border-bottom: none; }
kbd {
    display: inline-block; background: var(--bg); border: 1px solid var(--border); border-bottom-width: 2px;
    border-radius: 5px; padding: 3px 8px; font-family: "Courier New", Courier, monospace; font-size: 12px; font-weight: 700;
}
/* --menu-* are the v5 "Menu Items" / "Menu Items Hover" colour pickers
   (Settings -> Appearance -> Global). Fall back to the original
   color-mix()-derived look when unset. */
.topnav nav a {
    color: var(--menu-text, color-mix(in srgb, var(--topnav-text) 65%, transparent));
    background: var(--menu-bg, transparent);
    padding: 8px 12px;
    border-radius: var(--radius);
    font-weight: 500;
}
.topnav nav a:hover, .topnav nav a.active {
    background: var(--menu-hover-bg, color-mix(in srgb, var(--topnav-text) 12%, transparent));
    color: var(--menu-hover-text, var(--topnav-text));
    text-decoration: none;
}
.topnav .timer-indicator {
    display: none;
    align-items: center;
    gap: 6px;
    background: var(--primary);
    color: var(--on-primary);
    padding: 6px 12px;
    border-radius: 999px;
    font-weight: 600;
    font-size: 13px;
    margin-right: 8px;
}
.topnav .timer-indicator.active { display: inline-flex; }
.topnav .theme-toggle { font-size: 15px; padding: 5px 9px; line-height: 1; }
.topnav .timer-indicator .dot {
    width: 8px; height: 8px; border-radius: 50%; background: var(--danger);
    animation: pulse 1.4s infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .3; } }
/* v5-revision-3: small stop/resume buttons inside the header timer
   indicators.
   v6.02: previously background:none, so the button was only visible as
   plain text in the same colour as the pill's own text - effectively
   invisible as a distinct control against the pill. Colours are now
   reversed from the pill (on-primary bg / primary text) so the button
   reads as its own clickable chip rather than blending into the pill. */
.timer-indicator-btn {
    background: var(--on-primary); border: none; color: var(--primary); cursor: pointer;
    font-size: 11px; line-height: 1; padding: 2px 5px; margin-left: 2px;
    border-radius: 4px; opacity: 0.9;
}
.timer-indicator-btn:hover { opacity: 1; filter: brightness(0.92); }
/* "Resume" indicator (v5-revision-3): appears in place of the normal
   active indicator after the header's own stop button was used, showing a
   dimmed/paused look until the user resumes, starts a different timer, or
   reloads (JS-only state, does not persist across reloads - see
   PulskerTimer in timer.js). Reuses the pill shape but muted colours
   instead of the bright --primary "live" look, so it visually reads as
   paused rather than running. */
.timer-indicator-resume {
    display: none;
    align-items: center;
    gap: 6px;
    background: var(--surface);
    color: var(--text-muted);
    border: 1px solid var(--border);
    padding: 6px 12px;
    border-radius: 999px;
    font-weight: 600;
    font-size: 13px;
    margin-right: 8px;
}
.timer-indicator-resume.active { display: inline-flex; }

.container { max-width: 1200px; margin: 0 auto; padding: 24px 20px; }
.container.wide { max-width: 1500px; }
/* Board page only: fill full page width so columns can share available
   space instead of being capped/scrolling horizontally (see .board-columns
   below). Other pages (boards.php, dashboard.php, settings.php, reports.php)
   keep the narrower .container / .container.wide reading widths above. */
.container.full-width { max-width: none; }

/* h1-h5 colour/font-family/font-size are the v5 Global typography +
   colours overrides (Settings -> Appearance -> Global); fall back to the
   original literal defaults (h1/h2 only had explicit sizes pre-v5, h3-h5
   pick sensible steps down since nothing in the app used them before). */
/* --gt-{level}-transform: v5-revision-2 Global typography "case" control
   (Settings -> Appearance -> Global); defaults to `none` so nothing
   changes for installs that haven't touched the new control. */
/* --gt-{level}-weight: v5-revision-3 Global typography "weight" control
   (Settings -> Appearance -> Global). Fallback 700 matches the browser's
   own UA-stylesheet heading bold weight, so unset installs render
   identically to before this control existed. */
h1 { font-size: var(--gt-h1-size, 22px); margin: 0 0 4px; color: var(--gc-h1, inherit); font-family: var(--gt-h1-font, inherit); text-transform: var(--gt-h1-transform, none); font-weight: var(--gt-h1-weight, 700); }
h2 { font-size: var(--gt-h2-size, 17px); margin: 0 0 12px; color: var(--gc-h2, inherit); font-family: var(--gt-h2-font, inherit); text-transform: var(--gt-h2-transform, none); font-weight: var(--gt-h2-weight, 700); }
h3 { font-size: var(--gt-h3-size, 15px); color: var(--gc-h3, inherit); font-family: var(--gt-h3-font, inherit); text-transform: var(--gt-h3-transform, none); font-weight: var(--gt-h3-weight, 700); }
h4 { font-size: var(--gt-h4-size, 14px); color: var(--gc-h4, inherit); font-family: var(--gt-h4-font, inherit); text-transform: var(--gt-h4-transform, none); font-weight: var(--gt-h4-weight, 700); }
h5 { font-size: var(--gt-h5-size, 13px); color: var(--gc-h5, inherit); font-family: var(--gt-h5-font, inherit); text-transform: var(--gt-h5-transform, none); font-weight: var(--gt-h5-weight, 700); }
.page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.subtitle { color: var(--text-muted); margin: 0 0 20px; }

/* ---------- Buttons / forms ---------- */
/* Global "Button"/"Button-hover" colour + typography overrides apply only
   to the primary .btn look, not the .secondary/.danger variants below,
   which are deliberately styled differently. */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--gc-button, var(--primary));
    /* Pulse Neon is a very bright color - dark text reads far better on
       it than white (checked for contrast; see --on-primary in :root). */
    color: var(--on-primary);
    border: none;
    padding: 8px 14px;
    border-radius: var(--radius);
    font-size: var(--gt-button-size, 13px);
    font-family: var(--gt-button-font, inherit);
    /* v5-revision-2 Global typography "case" control (Button level). */
    text-transform: var(--gt-button-transform, none);
    /* v5-revision-3 Global typography "weight" control (Button level) -
       fallback 600 matches the literal weight this always used before the
       control existed. */
    font-weight: var(--gt-button-weight, 600);
    cursor: pointer;
}
.btn:hover { background: var(--gc-button-hover, var(--primary-dark)); color: var(--on-primary); }
.btn.secondary { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.btn.secondary:hover { background: var(--bg); }
.btn.danger { background: var(--danger); }
.btn.danger:hover { background: #dc2626; }
.btn.small { padding: 5px 10px; font-size: 12px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

input, select, textarea {
    font-family: inherit;
    font-size: 13px;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: #fff;
    color: var(--text);
    width: 100%;
}
label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .03em; }
.field { margin-bottom: 14px; }
.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; }

.card-surface {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

/* ---------- Login ---------- */
.login-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.login-box { width: 340px; padding: 32px; }
.login-box h1 { text-align: center; margin-bottom: 20px; }
/* v6.03: brand logo (admin/assets/img/logo.png, shipped with the app -
   not the user-uploadable Settings > Styles company logo) replaces the
   plain APP_NAME text heading on the login page only. */
.login-logo { display: flex; justify-content: center; margin-bottom: 22px; }
.login-logo img { max-width: 220px; width: 100%; height: auto; }
.error-msg { background: #fef2f2; color: #b91c1c; padding: 10px 12px; border-radius: var(--radius); margin-bottom: 14px; font-size: 13px; }

/* ---------- General account form messages (v6 Settings > General) ---------- */
.form-msg { font-size: 13px; margin-bottom: 12px; }
.form-msg:empty { display: none; }
.form-msg.success { color: #16a34a; }
.form-msg.error { color: #b91c1c; }
html[data-theme="dark"] .form-msg.success { color: #4ade80; }
html[data-theme="dark"] .form-msg.error { color: #fca5a5; }

/* ---------- Boards list ---------- */
.board-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.board-tile { padding: 20px; display: block; position: relative; }
.board-tile h3 { margin: 0 24px 4px 0; font-size: 16px; }
.board-tile .meta { color: var(--text-muted); font-size: 12px; }
.board-tile.new-tile { display: flex; align-items: center; justify-content: center; border-style: dashed; color: var(--text-muted); font-weight: 600; cursor: pointer; min-height: 88px; }
.board-tile.new-tile:hover { color: var(--primary); border-color: var(--primary); }
.settings-menu-grid .board-tile { min-height: 96px; display: flex; flex-direction: column; justify-content: center; }
.settings-menu-grid .board-tile h3 { margin: 0 0 4px; }
/* Default-board star toggle (v5-revision-2, top-right of each tile). */
.board-default-star {
    position: absolute; top: 10px; right: 10px; background: none; border: none;
    font-size: 18px; line-height: 1; padding: 2px; cursor: pointer; color: var(--border);
}
.board-default-star:hover { color: var(--primary); }
.board-default-star.is-default { color: var(--primary); }

/* ---------- Board view ---------- */
/* Columns share the available width equally (flex: 1 1 0) and shrink down
   to min-width before wrapping/scrolling kicks in as a last resort - this
   keeps a normal number of columns (~6-8) filling the page with no
   horizontal scrollbar, instead of the old fixed 280px-per-column layout
   that always overflowed past a handful of columns. */
.board-columns { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; padding-bottom: 20px; }
.column {
    /* Permanent brand chrome - always Board Gray, in both light and dark
       mode (see the --board-bg default in :root above) - unless
       explicitly overridden per-section via Settings -> Appearance ->
       Boards -> "Board column background" (--board-col-bg, v5). */
    background: var(--board-col-bg, var(--board-bg));
    color: var(--board-text);
    border-radius: var(--radius);
    flex: 1 1 0;
    min-width: 220px;
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 160px);
}
/* --board-coltitle-text/-font/-size/-transform/-weight: Settings ->
   Appearance -> Boards -> "Column title text" override (v6.01). Fallbacks
   match this rule's original literal values (var(--board-text)/700/13px). */
.column-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    font-weight: var(--board-coltitle-weight, 700);
    font-size: var(--board-coltitle-size, 13px);
    color: var(--board-coltitle-text, var(--board-text));
    font-family: var(--board-coltitle-font, inherit);
    text-transform: var(--board-coltitle-transform, none);
    border-top: 3px solid var(--primary);
    border-radius: var(--radius) var(--radius) 0 0;
}
.column-header .count { font-weight: 500; color: color-mix(in srgb, var(--board-text) 65%, transparent); font-size: 12px; }
.col-drag-handle {
    cursor: grab;
    color: color-mix(in srgb, var(--board-text) 65%, transparent);
    margin-right: 8px;
    font-size: 13px;
    user-select: none;
}
.col-drag-handle:active { cursor: grabbing; }
.column.column-ghost { opacity: 0.4; }
.column-body { flex: 1; overflow-y: auto; padding: 8px; min-height: 40px; }
.column-footer { padding: 8px; }
.column-footer .btn { width: 100%; justify-content: center; }
/* --board-newcardbtn-bg/-text/-hover-bg/-hover-text: Settings ->
   Appearance -> Boards -> "'+ New card' button + hover" override (v6.01).
   Fallbacks match the existing .btn.secondary look this button already
   had, so leaving the section on "use global" changes nothing visually.
   --board-newcardbtn-font/-size/-transform/-weight: typography controls
   for the same section. */
/* .btn.secondary.new-card-btn (rather than plain .new-card-btn) to match
   .btn.secondary's own specificity (two classes) - otherwise .btn.secondary
   (declared earlier but equally/more specific) would keep winning and
   these overrides would silently do nothing. */
.btn.secondary.new-card-btn {
    background: var(--board-newcardbtn-bg, var(--surface));
    color: var(--board-newcardbtn-text, var(--text));
    border-color: var(--border);
    font-size: var(--board-newcardbtn-size, 12px);
    font-weight: var(--board-newcardbtn-weight, 400);
    font-family: var(--board-newcardbtn-font, inherit);
    text-transform: var(--board-newcardbtn-transform, none);
}
.btn.secondary.new-card-btn:hover {
    background: var(--board-newcardbtn-hover-bg, var(--bg));
    color: var(--board-newcardbtn-hover-text, var(--text));
}
.column.wip-exceeded .column-header { color: var(--danger); }

.card {
    /* --board-card-bg: Settings -> Appearance -> Boards -> "Card
       background" override (v5); falls back to the theme surface color. */
    background: var(--board-card-bg, var(--surface));
    border-radius: var(--radius);
    padding: 10px 12px;
    margin-bottom: 8px;
    box-shadow: var(--shadow);
    cursor: grab;
    border-left: 4px solid transparent;
}
.card:active { cursor: grabbing; }
/* --board-cardtitle-text/-font/-size/-transform/-weight: Settings ->
   Appearance -> Boards -> "Task/card title text" override (v6.01).
   v6.01 moved these from the old --board-card-* vars (which used to live
   under the "Task/card background" section, confusingly bundling title
   typography with a background-only swatch) to this dedicated section;
   the "Task/card background" section is now background-only, matching
   "Board column background". Fallbacks match the literal defaults this
   rule always had. */
.card .card-title {
    font-weight: var(--board-cardtitle-weight, 600);
    margin-bottom: 6px;
    font-size: var(--board-cardtitle-size, 13px);
    color: var(--board-cardtitle-text, inherit);
    font-family: var(--board-cardtitle-font, inherit);
    text-transform: var(--board-cardtitle-transform, none);
}
/* --board-time-text: Settings -> Appearance -> Boards -> "Time display"
   override (v5) - covers the time/date text in a card's meta row.
   --board-time-font/-size/-transform/-weight: v5-revision-3 typography
   controls for the same section. */
.card .card-meta {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: var(--board-time-size, 11px);
    color: var(--board-time-text, var(--text-muted));
    font-family: var(--board-time-font, inherit);
    text-transform: var(--board-time-transform, none);
    font-weight: var(--board-time-weight, 400);
}
.badge {
    /* Background is always set inline per-task/category (see board.php)
       so it isn't themeable here. Text color is normally also set inline
       (a computed black/white based on the category color's contrast -
       see contrastTextColor() in board.php/dashboard.php, the v5 fix for
       the "light category colour + hardcoded white badge text" readability
       bug, or the category's own text_color when set - v5-revision-2);
       --board-badge-text is a fallback for the rare case no inline color
       is present.
       Padding bumped (v5-revision-2, was 2px 7px) for a proper "pill"
       look with more breathing room around the text - shared globally
       (board cards + dashboard Active Jobs, both use this same .badge
       class) since both benefit equally and there's no space-pressure
       reason to keep either more cramped. v5-revision-3: selector
       broadened from `.card .badge` to plain `.badge` - the dashboard's
       Active Jobs panel renders `.badge` directly inside `.active-job-row`
       (an <a>, not a `.card`), so the `.card` ancestor requirement was
       silently preventing the pill padding from ever applying there. */
    /* --board-badge-font/-size/-transform/-weight: v5-revision-3
       typography controls for this section. Fallbacks match this rule's
       original literal 10px/700. */
    display: inline-block; padding: 4px 12px; border-radius: 999px;
    font-size: var(--board-badge-size, 10px);
    font-weight: var(--board-badge-weight, 700);
    color: var(--board-badge-text, #fff);
    font-family: var(--board-badge-font, inherit);
    text-transform: var(--board-badge-transform, none);
}
.card .due-date.overdue { color: var(--danger); font-weight: 600; }
/* --board-charge-text: Settings -> Appearance -> Boards -> "Funds/charge
   display" override (v5). --board-charge-font/-size/-transform/-weight:
   v5-revision-3 typography controls for the same section. */
.card .task-charge {
    font-weight: var(--board-charge-weight, 700);
    color: var(--board-charge-text, var(--success));
    font-size: var(--board-charge-size, inherit);
    font-family: var(--board-charge-font, inherit);
    text-transform: var(--board-charge-transform, none);
}
.card .timer-btn {
    /* --board-timerbtn-bg/--board-timerbtn-text: Settings -> Appearance
       -> Boards -> "Start/stop timer button" override (v5). Applies to
       the base (not-running) state; the .running state below keeps its
       distinct red styling regardless. --board-timerbtn-font/-size/
       -transform/-weight: v5-revision-3 typography controls for the same
       section. */
    border: 1px solid var(--border); background: var(--board-timerbtn-bg, #fff); color: var(--board-timerbtn-text, inherit); border-radius: 999px; padding: 2px 8px;
    font-size: var(--board-timerbtn-size, 11px);
    cursor: pointer;
    font-weight: var(--board-timerbtn-weight, 600);
    font-family: var(--board-timerbtn-font, inherit);
    text-transform: var(--board-timerbtn-transform, none);
    /* v6.01: push the timer button to the right-hand side of the card
       meta row, regardless of how many other items (badge/due-date/
       charge) sit before it in the flex row. */
    margin-left: auto;
}
/* --board-timerbtn-hover-bg/--board-timerbtn-hover-text: Settings ->
   Appearance -> Boards -> "Start/stop timer button" hover override (v5-
   revision-2). :not(.running) keeps the running state's own fixed red
   styling always winning on hover too, same as the base-state rule above.
   Falls back to the same look as .btn.secondary:hover when not
   overridden. */
.card .timer-btn:hover:not(.running) {
    background: var(--board-timerbtn-hover-bg, var(--bg));
    color: var(--board-timerbtn-hover-text, inherit);
}
.card .timer-btn.running { background: #fee2e2; color: var(--danger); border-color: #fecaca; }
.card.sortable-ghost { opacity: 0.4; }
.card.active-timer { border-left-color: var(--danger); }

/* Whole-card category tint (badge stays solid for contrast/legibility). */
.card.has-category {
    background: color-mix(in srgb, var(--cat-color, var(--primary)) 16%, var(--surface));
}
html[data-theme="dark"] .card.has-category {
    background: color-mix(in srgb, var(--cat-color, var(--primary)) 26%, var(--surface));
}

/* Completed cards (Done column) read as de-emphasized/greyed out. */
.card.completed {
    opacity: 0.62;
    filter: saturate(0.75);
}
.card.completed .card-title { text-decoration: line-through; text-decoration-color: var(--text-muted); }

/* Date-group dividers within the Done column. */
.date-divider {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--text-muted);
    padding: 10px 2px 6px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 6px;
}
.date-divider:first-child { padding-top: 2px; }

/* ---------- Modal ---------- */
.modal-backdrop {
    display: none;
    position: fixed; inset: 0; background: rgba(15, 18, 25, 0.5);
    align-items: flex-start; justify-content: center; padding: 40px 20px; overflow-y: auto; z-index: 100;
}
.modal-backdrop.open { display: flex; }
.modal {
    background: #fff; border-radius: 10px; width: 600px; max-width: 100%; padding: 24px;
}
.modal-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
.modal-close { background: none; border: none; font-size: 20px; cursor: pointer; color: var(--text-muted); }
.modal-section { margin-bottom: 18px; }
.modal-section h4 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; color: var(--text-muted); letter-spacing: .03em; }
.subtask-list { list-style: none; padding: 0; margin: 0; }
.subtask-list li { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px; }
.subtask-list li.done span { text-decoration: line-through; color: var(--text-muted); }
.timer-panel { display: flex; align-items: center; gap: 12px; background: var(--bg); padding: 12px; border-radius: var(--radius); }
.timer-panel .clock { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.time-entry-list { max-height: 160px; overflow-y: auto; font-size: 12px; }
/* v6.02: the task modal's "Time log" list now inherits the same text
   colour as the board card's Time display override (Settings > Styles >
   Boards > "Time display"), so a themed time colour reads consistently
   in both places instead of the modal always using the plain body text
   colour regardless of what's set there. */
.time-entry-list .entry { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--border); color: var(--board-time-text, inherit); }
.time-entry-list .entry .entry-label { flex: 1; display: flex; flex-direction: column; }
.time-entry-list .entry .entry-note { color: var(--text-muted); font-size: 11px; }
.time-entry-list .entry .entry-delete { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 13px; padding: 0 2px; line-height: 1; }
.time-entry-list .entry .entry-delete:hover { color: var(--danger, #d64545); }
.manual-time-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.manual-time-row input[type="date"] { flex: 0 0 140px; }
.manual-time-row input[type="number"] { width: 56px; text-align: center; }
.manual-time-row .manual-time-sep { color: var(--text-muted); font-size: 12px; }
.manual-time-row input[type="text"] { flex: 1; min-width: 120px; }
.manual-time-row .btn { margin-left: auto; }

/* ---------- Task notes ---------- */
.note-list { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.note-item { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 10px; font-size: 13px; }
.note-item .note-meta { display: flex; justify-content: space-between; align-items: center; font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
.note-item .note-actions { display: flex; gap: 8px; }
.note-item .note-actions button { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 11px; padding: 0; }
.note-item .note-actions button:hover { color: var(--primary); text-decoration: none; }
.note-item .note-body { white-space: pre-wrap; word-break: break-word; }
.note-item textarea { margin-top: 6px; }
.note-add-row { display: flex; gap: 8px; align-items: flex-start; }
.note-add-row textarea { flex: 1; }

/* ---------- Dashboard ---------- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 20px; }
.stat-tile { padding: 18px; }
.stat-tile .label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .03em; margin-bottom: 6px; }
.stat-tile .value { font-size: 26px; font-weight: 700; }
.dash-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 20px; }
/* v5-revision-3: the two chart panels inside .dash-grid are grid items, so
   their own .panel margin-bottom:20px doesn't collapse with .dash-grid's
   own margin-bottom:20px (CSS grid never collapses margins) - that stacked
   up to a ~40px gap after the chart row vs. ~20px everywhere else on the
   page. Zeroing it here leaves .dash-grid's own margin-bottom as the only
   contributor, matching every other panel-to-panel gap. */
.dash-grid .panel { margin-bottom: 0; }
/* Standardized outer spacing for all top-level dashboard panels (Money,
   Active jobs, Tasks-completed chart, Time-by-category, Recently
   completed) so gaps between them match .stat-grid/.dash-grid above
   (all 20px) - previously these plain .panel divs had no explicit
   margin-bottom at all and relied on nothing, so they visually touched. */
.panel { padding: 18px; margin-bottom: 20px; }

/* ---------- Dashboard widget system (v6) ---------- */
/* v6.02: align-items stretch (was start) + the .dash-widget/-body flex
   rules below make every widget in the same grid row match the row's
   tallest widget, instead of each hugging its own content height - a
   short widget (e.g. Time by category with few categories) no longer
   looks visually "shorter" than a tall neighbour (e.g. Active jobs) on
   the same row. */
.dash-widget-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 20px; margin-bottom: 20px; align-items: stretch; }
.dash-widget-body { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.dash-widget-body > * { flex: 1; }
/* v6.01 fix: a widget's inner content (.panel/.stat-grid/etc) must never
   carry its own bottom margin - the widget-grid's own `gap` is the only
   thing that should create space between widgets. A trailing margin on a
   widget's last child collapses through the (border/padding-less)
   .dash-widget wrapper and silently doubles the visible gap below that
   widget specifically (this bit the Overview widget, whose .stat-grid
   still had its normal-context margin-bottom:20px) - zeroed as a
   belt-and-suspenders rule here, in addition to fixing the one widget
   that actually hit it. */
.dash-widget-body > *:last-child { margin-bottom: 0 !important; }
.dash-widget { grid-column: span 6; min-width: 0; display: flex; flex-direction: column; }
.dash-widget.w-third { grid-column: span 2; }
.dash-widget.w-half { grid-column: span 3; }
.dash-widget.w-two-thirds { grid-column: span 4; }
.dash-widget.w-full { grid-column: span 6; }
@media (max-width: 900px) {
    .dash-widget-grid > .dash-widget { grid-column: 1 / -1 !important; }
}
.dash-widget-grid.edit-mode .dash-widget { outline: 1px dashed var(--border); border-radius: var(--radius); }
.dash-widget-toolbar {
    display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--bg);
    border-radius: var(--radius) var(--radius) 0 0; border-bottom: 1px solid var(--border); font-size: 12px;
}
.dash-widget-drag-handle { cursor: grab; color: var(--text-muted); font-weight: 700; letter-spacing: -2px; }
.dash-widget-drag-handle:active { cursor: grabbing; }
.dash-widget-name { font-weight: 600; flex: 1; }
.dash-widget-width-btns { display: flex; gap: 2px; }
.dash-widget-width-btn {
    font-size: 11px; padding: 3px 7px; border: 1px solid var(--border); background: var(--surface);
    color: var(--text-muted); cursor: pointer; border-radius: 4px;
}
.dash-widget-width-btn.active { background: var(--primary); color: #0a1613; border-color: var(--primary); }
.dash-widget-remove-btn { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 13px; padding: 2px 4px; }
.dash-widget-remove-btn:hover { color: var(--danger, #d64545); }
.dash-widget-grid.edit-mode .dash-widget-body { pointer-events: none; opacity: .85; }
.dash-widget-add-box {
    display: flex; align-items: center; justify-content: center; min-height: 96px; position: relative;
    border: 1px dashed var(--border); border-radius: var(--radius);
}
.dash-widget-add-btn { background: none; border: none; color: var(--text-muted); font-weight: 600; cursor: pointer; font-size: 13px; }
.dash-widget-add-btn:hover { color: var(--primary); }
.dash-widget-add-menu {
    position: absolute; top: 100%; left: 50%; transform: translateX(-50%); z-index: 5; margin-top: 4px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: 0 4px 16px rgba(0,0,0,.12); display: flex; flex-direction: column; min-width: 180px; overflow: hidden;
}
.dash-widget-add-option { text-align: left; padding: 8px 12px; border: none; background: none; cursor: pointer; font-size: 13px; }
.dash-widget-add-option:hover { background: var(--bg); }
.dash-layout-editor-actions { display: flex; gap: 10px; margin-top: 4px; }
.bar-chart { display: flex; align-items: flex-end; gap: 8px; height: 160px; }
.bar-chart .bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; }
.bar-chart .bar { width: 100%; background: var(--primary); border-radius: 3px 3px 0 0; min-height: 2px; }
.bar-chart .bar-label { font-size: 10px; color: var(--text-muted); margin-top: 4px; }
.bar-chart .bar-value { font-size: 10px; color: var(--text-muted); }
.cat-breakdown .cat-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 12px; }
.cat-breakdown .swatch { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.cat-breakdown .bar-track { flex: 1; background: var(--bg); border-radius: 4px; height: 8px; overflow: hidden; }
.cat-breakdown .bar-fill { height: 100%; border-radius: 4px; }
.cat-breakdown .cat-time { flex: none; color: var(--text-muted); }

/* Active jobs list (v6.01: restyled to match the "Recently completed
   tasks" table's divider-line rows, per the v6.01 feedback - previously
   each row was its own rounded, category-tinted card; the category pill
   itself (.badge, unchanged below) still shows the category, just without
   the extra full-row background tint. */
.active-jobs-list { display: flex; flex-direction: column; }
.active-job-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border);
    text-decoration: none;
    color: var(--text);
    font-size: 12px;
}
.active-job-row:last-child { border-bottom: none; }
/* v6.02: fixed hover colour per request (#14161C) - there is no dedicated
   Styles setting for this yet (Active jobs only has bg/text/typography
   overrides today); worth adding a "hover background" field to the
   Dashboard > Active jobs section in a future round if a themeable value
   is wanted instead of this fixed one. */
.active-job-row:hover { background: #14161c; color: #e6e8ee; }
.active-job-row:hover .active-job-board,
.active-job-row:hover .active-job-time { color: #cdd1db; }
.active-job-title { font-weight: 600; flex: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.active-job-board { flex: 1; color: var(--text-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.active-job-time { flex: none; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* ---------- v5 per-section Dashboard appearance overrides ---------- */
/* Settings -> Appearance -> Dashboard: each subsection (stat tiles, Money,
   Active jobs, weekly chart, time-by-category) has a "use global
   colours/fonts" toggle (default ON). When ON, admin/partials/
   appearance-style.php simply omits these --dash-* variables, so the
   var(--dash-x, fallback) chains below fall through to the Global body
   colour/font (--gc-body/--gt-body-font/--gt-body-size) and the normal
   theme surface - i.e. "use global" means "no dashboard-specific
   override", not "copy the Global panel's literal values" into a new
   variable. When OFF, the section's own bg/text/font/size are emitted and
   win here via normal CSS variable resolution. */
/* v5-revision-3: each section also gets --dash-{prefix}-transform (case)
   and --dash-{prefix}-weight, alongside the existing bg/text/font/size,
   matching the Global tab's typography controls. Fallbacks are 'none' /
   400, which is a no-op against the plain unstyled text these apply to. */
.dash-stat-tiles .stat-tile {
    background: var(--dash-stat-bg, var(--surface));
    color: var(--dash-stat-text, var(--gc-body, var(--text)));
    font-family: var(--dash-stat-font, var(--gt-body-font, inherit));
    font-size: var(--dash-stat-size, var(--gt-body-size, inherit));
    text-transform: var(--dash-stat-transform, none);
    font-weight: var(--dash-stat-weight, 400);
}
.dash-money.panel {
    background: var(--dash-money-bg, var(--surface));
    color: var(--dash-money-text, var(--gc-body, var(--text)));
    font-family: var(--dash-money-font, var(--gt-body-font, inherit));
    text-transform: var(--dash-money-transform, none);
    font-weight: var(--dash-money-weight, 400);
}
.dash-money .stat-tile {
    background: var(--dash-money-bg, var(--surface));
    color: inherit;
    font-size: var(--dash-money-size, var(--gt-body-size, inherit));
}
.dash-active-jobs.panel {
    background: var(--dash-active-bg, var(--surface));
    color: var(--dash-active-text, var(--gc-body, var(--text)));
    font-family: var(--dash-active-font, var(--gt-body-font, inherit));
    font-size: var(--dash-active-size, var(--gt-body-size, inherit));
    text-transform: var(--dash-active-transform, none);
    font-weight: var(--dash-active-weight, 400);
}
/* v6.01: rows no longer carry their own category-tinted background (see
   .active-job-row above), so the section override now applies to every
   row the same way, matching how Recently Completed's table rows work. */
.dash-active-jobs .active-job-row { color: inherit; }
/* Job title has its own hardcoded weight (600) elsewhere on the page
   (.active-job-title below); route it through the same override chain so
   a Dashboard "Active jobs" weight override actually has a visible effect
   on the row text, not just the (already-bold) panel <h2>. */
.dash-active-jobs .active-job-title { font-weight: var(--dash-active-weight, 600); }
.dash-weekly-chart.panel {
    background: var(--dash-weekly-bg, var(--surface));
    color: var(--dash-weekly-text, var(--gc-body, var(--text)));
    font-family: var(--dash-weekly-font, var(--gt-body-font, inherit));
    font-size: var(--dash-weekly-size, var(--gt-body-size, inherit));
    text-transform: var(--dash-weekly-transform, none);
    font-weight: var(--dash-weekly-weight, 400);
}
.dash-time-by-category.panel {
    background: var(--dash-cat-bg, var(--surface));
    color: var(--dash-cat-text, var(--gc-body, var(--text)));
    font-family: var(--dash-cat-font, var(--gt-body-font, inherit));
    font-size: var(--dash-cat-size, var(--gt-body-size, inherit));
    text-transform: var(--dash-cat-transform, none);
    font-weight: var(--dash-cat-weight, 400);
}

table.simple { width: 100%; border-collapse: collapse; font-size: 12px; }
table.simple th, table.simple td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
table.simple th { color: var(--text-muted); font-weight: 600; text-transform: uppercase; font-size: 11px; }

/* ---------- Settings ---------- */
.category-list { display: flex; flex-direction: column; gap: 8px; }
.category-row { display: flex; align-items: center; gap: 10px; padding: 10px; }
.category-row .swatch { width: 16px; height: 16px; border-radius: 50%; flex: none; }
.category-row .name { flex: 1; font-weight: 600; color: var(--category-name-text, var(--text)); }
/* v5 update 2c: dashboard "Recently completed" table's category cell shares
   the same global override (see admin/dashboard.php's <td class="cat-name-cell">). */
.cat-name-cell { color: var(--category-name-text, var(--text)); }
.category-row .billable-tag { font-size: 11px; color: var(--text-muted); }
.category-row .rate-tag { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* v5-revision-3: inline category edit form (openCategoryEditForm() in
   settings.php). The row's normal single-line nowrap flex layout (above)
   could not fit a name input + two full color-picker components + billable
   + rate + Save/Cancel without overflowing past the panel's right edge,
   making Save unreachable - see the v5-revision-3 fix notes. `.editing`
   switches the row onto a wrapping multi-row column layout instead, using
   the same labeled-field look as the rest of the app's forms (label.php
   convention) so every control stays fully visible/usable at the existing
   .panel max-width:640px. */
.category-row.editing { align-items: stretch; flex-direction: column; gap: 12px; }
.category-row.editing .edit-cat-line { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.category-row.editing .edit-cat-field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 140px; min-width: 0; }
.category-row.editing .edit-cat-field.grow { flex: 2 1 200px; }
.category-row.editing .edit-cat-field label { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .03em; margin: 0; }
.category-row.editing .edit-cat-checkbox-field { display: flex; align-items: center; gap: 6px; font-weight: 400; text-transform: none; font-size: 12px; color: var(--text); margin: 0 0 8px; flex: none; }
.category-row.editing .edit-cat-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* ---------- Reports ---------- */
.filter-bar { display: flex; gap: 12px; align-items: flex-end; margin-bottom: 20px; flex-wrap: wrap; }
.filter-bar .field { margin-bottom: 0; min-width: 160px; }
.tabs { display: flex; gap: 4px; margin-bottom: 16px; }
.tabs button { background: none; border: 1px solid var(--border); padding: 8px 14px; border-radius: var(--radius); cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text-muted); }
.tabs button.active { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }

.empty-state { text-align: center; padding: 40px 20px; color: var(--text-muted); }

/* ---------- Color picker component (assets/js/colorpicker.js) ---------- */
/* Fully custom markup (no native <input type="color">), so the swatch
   preview is plain CSS we control end-to-end and always reflects the
   live-selected color - fixes the v4 "change colour line" bug, where the
   browser's own color-input swatch border stayed a fixed grey in some
   browsers regardless of the picked value.

   v6.02: the R/G/B number fields (.cp-rgb, previously toggled via
   .cp-rgb-toggle) were removed - the swatch already opens a real native
   colour picker (v6.01), so R/G/B text entry was a redundant second path
   to the same result. Only the swatch + hex field remain visible. */
.color-picker { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; position: relative; }
.cp-swatch {
    width: 26px; height: 26px; flex: none; border-radius: 6px; padding: 0;
    border: 1px solid var(--border); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.04);
    cursor: pointer;
}
.cp-swatch:hover { border-color: var(--primary); }
.cp-swatch:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* v6.01: the real picker UI - kept in the layout (not display:none, which
   stops some browsers opening the picker on a programmatic .click()) but
   visually invisible and unreachable by tab/click of its own; the
   .cp-swatch button above is the only visible/clickable trigger. */
.cp-native {
    position: absolute; top: 0; left: 0; width: 26px; height: 26px;
    opacity: 0; pointer-events: none; border: none; padding: 0;
}
.cp-hex { width: 84px; flex: none; font-variant-numeric: tabular-nums; text-transform: lowercase; }

/* ---------- Appearance editor (Settings -> Appearance) ---------- */
.appearance-tabs { margin-bottom: 16px; }
.appearance-tab-panel .section-block { margin-bottom: 22px; }
.appearance-tab-panel .section-block:last-child { margin-bottom: 0; }
.appearance-tab-panel h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .03em; color: var(--text-muted); margin: 0 0 10px; }
.appearance-subsection {
    border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; margin-bottom: 14px;
}
.appearance-subsection:last-child { margin-bottom: 0; }
.appearance-subsection h4 { margin: 0 0 10px; font-size: 13px; color: var(--text); text-transform: none; letter-spacing: normal; }
.use-global-toggle {
    display: flex; align-items: center; gap: 6px; font-weight: 400; text-transform: none;
    font-size: 12px; color: var(--text-muted); margin-bottom: 10px; letter-spacing: normal;
}
.use-global-toggle input { width: auto; }
.appearance-override-fields[data-hidden="1"] { display: none; }
.appearance-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }

/* ---------- Logo / favicon upload (v6 Settings > Styles > Global) ---------- */
.logo-upload-row { display: flex; gap: 24px; flex-wrap: wrap; }
.logo-upload-field { flex: 1; min-width: 220px; }
.logo-upload-field label { display: block; font-weight: 600; font-size: 13px; margin-bottom: 6px; }
.logo-preview {
    display: flex; align-items: center; justify-content: center; height: 64px; padding: 8px;
    background: var(--bg); border: 1px dashed var(--border); border-radius: var(--radius); margin-bottom: 8px;
}
.logo-preview img { max-height: 48px; max-width: 100%; object-fit: contain; }
.favicon-preview img { max-height: 40px; max-width: 40px; }
.logo-preview-empty { color: var(--text-muted); font-size: 12px; }
