* { margin: 0; padding: 0; box-sizing: border-box; }

/* The HTML5 `hidden` attribute should always hide the element, but the
   user-agent rule `[hidden] { display: none }` loses to any explicit
   `display: flex/grid/block/...` rule with class specificity. Without
   this !important, components that toggle `el.hidden = true/false` -
   the feature-requests section bodies, the new-request form, the
   delete-confirmation row, the auth panels - stay visible when set
   to hidden. Bug reported by Trevor on the feature-requests modal. */
[hidden] { display: none !important; }

:root {
    --bg: #0f1117;
    --surface: #1a1d27;
    --surface2: #252837;
    --border: #2e3147;
    --text: #e8eaf0;
    --muted: #7c7f96;
    --accent: #6c63ff;
    --green: #43d9a2;
    --red: #ff6584;
}

/* ── THEMES ──────────────────────────────────────────────────
 * Two-color standard: --bg is the page color, --surface is the
 * bubble/panel color, the two are clearly distinct. --border doubles
 * as the small-text color (per design intent - secondary info reads
 * the same shade as bubble outlines). --text always has high
 * contrast against --surface (passes AA at 12-13px).
 *
 * `body.theme-dark` is also the default - its rules duplicate :root
 * so applyTheme() can always add a class without thinking about a
 * "no-class" branch.
 */

body.theme-dark {
    --bg:       #0f1117;
    --surface:  #1a1d27;
    --surface2: #252837;
    --border:   #2e3147;
    --text:     #e8eaf0;
    --muted:    #7c7f96;
    --accent:   #6c63ff;
    --green:    #43d9a2;
    --red:      #ff6584;
}

/* Light - bubbles are white-on-pale-gray. Borders bumped from
   #d8dbe6 to #9ba0b3 so panel outlines, pill chips, and toggles all
   stand out clearly without being harsh. */
body.theme-light {
    --bg:       #eceef5;
    --surface:  #ffffff;
    --surface2: #f4f6fa;
    --border:   #9ba0b3;
    --text:     #1a1d27;
    --muted:    #5b6075;
    --accent:   #5a52e0;
    --green:    #1f9b6a;
    --red:      #e0436a;
}

/* Sky Blue - clouds (white bubbles) drifting on a calm summer sky.
   Gold accent is the warm sun. */
body.theme-sky-blue {
    --bg:       #cfe6f5;
    --surface:  #ffffff;
    --surface2: #f3f8fd;
    --border:   #5a8db8;
    --text:     #1a3a5c;
    --muted:    #406a8e;
    --accent:   #e6a82b;
    --green:    #2c8c6b;
    --red:      #c45a5a;
}

/* Parchment - aged paper, deep-brown ink, oxblood accent. */
body.theme-parchment {
    --bg:       #e8d9b6;
    --surface:  #f7ecd0;
    --surface2: #ecdec0;
    --border:   #8a6e3e;
    --text:     #3a2a16;
    --muted:    #6e5a30;
    --accent:   #8b3a3a;
    --green:    #4a6b3a;
    --red:      #a13a2a;
}

/* Forest - pine-needle dark green, cream text, gold accent. */
body.theme-forest {
    --bg:       #1f3a2f;
    --surface:  #2e4f43;
    --surface2: #3a5d50;
    --border:   #73a890;
    --text:     #f3ecd1;
    --muted:    #b8d4c4;
    --accent:   #d4a045;
    --green:    #7ed092;
    --red:      #d97c5e;
}

/* Sunset - salmon widgets sitting on a barely-darker salmon page, the
   way the other tinted lights do (Mint / Coral / Rose). Keeps the
   original salmon surface but drops the deep dusk-wine bg in favor
   of a slightly deeper shade of the same family so the panels read
   as elevated cards instead of clouds against a different sky. The
   accent is a deep raspberry/wine - replaces the cold teal AND the
   yellow marigold; reads as the twilight band at the horizon and
   gives strong contrast against salmon. */
body.theme-sunset {
    --bg:       #e89578;
    --surface:  #f0a585;
    --surface2: #df8b6c;
    --border:   #8c3a26;
    --text:     #3a1a0e;
    --muted:    #6b2a1a;
    --accent:   #9c2a52;
    --green:    #4a7a35;
    --red:      #b8253c;
}

/* Mint - pale mint background, white bubbles, coral accent. */
body.theme-mint {
    --bg:       #cdedda;
    --surface:  #ffffff;
    --surface2: #ebf6ee;
    --border:   #5cb088;
    --text:     #1a3a2a;
    --muted:    #437a5c;
    --accent:   #f06c8c;
    --green:    #3a8c5a;
    --red:      #d04060;
}

/* Lavender - soft purple, white bubbles, plum accent. */
body.theme-lavender {
    --bg:       #e6dcf0;
    --surface:  #ffffff;
    --surface2: #f3eef8;
    --border:   #a890c0;
    --text:     #3a2a52;
    --muted:    #6e5a85;
    --accent:   #b04a8c;
    --green:    #4a8c6a;
    --red:      #c43a5a;
}

/* Midnight - deep navy with electric-blue accents. */
body.theme-midnight {
    --bg:       #0a0e1f;
    --surface:  #142540;
    --surface2: #1c3055;
    --border:   #3a5680;
    --text:     #e8f0ff;
    --muted:    #8aa0c8;
    --accent:   #4dc8ff;
    --green:    #3ae0a0;
    --red:      #ff5c8c;
}

/* Coral - warm peach, white bubbles, crisp teal accents. */
body.theme-coral {
    --bg:       #ffd9c0;
    --surface:  #ffffff;
    --surface2: #fff0e6;
    --border:   #d9785a;
    --text:     #6e1a0e;
    --muted:    #a04a3a;
    --accent:   #1f8a8a;
    --green:    #3a8c5a;
    --red:      #c4302a;
}

/* ── New dark themes ────────────────────────────── */

/* Slate - cool gray with cyan accent. Neutral, professional. */
body.theme-slate {
    --bg:       #1e2530;
    --surface:  #2c3645;
    --surface2: #3a4555;
    --border:   #4a5566;
    --text:     #e8ecf2;
    --muted:    #8b95a8;
    --accent:   #5fc4d4;
    --green:    #4ed4a8;
    --red:      #ff7088;
}

/* Crimson - deep oxblood with warm cream text and gold accent. */
body.theme-crimson {
    --bg:       #2a0e14;
    --surface:  #4a1a24;
    --surface2: #5a2030;
    --border:   #7a3040;
    --text:     #f5e8d8;
    --muted:    #c4a890;
    --accent:   #e6b964;
    --green:    #7ed092;
    --red:      #ff5c8c;
}

/* Charcoal - pure dark gray with warm amber accent. */
body.theme-charcoal {
    --bg:       #1a1a1a;
    --surface:  #262626;
    --surface2: #333333;
    --border:   #4a4a4a;
    --text:     #ebebeb;
    --muted:    #8a8a8a;
    --accent:   #ffb347;
    --green:    #6dd093;
    --red:      #ff6b6b;
}

/* Espresso - coffee browns with cream and gold. Cozy. */
body.theme-espresso {
    --bg:       #2b1d12;
    --surface:  #3d2a1a;
    --surface2: #4a3322;
    --border:   #6b4a30;
    --text:     #f0e2cf;
    --muted:    #c0a280;
    --accent:   #d4a045;
    --green:    #8cc065;
    --red:      #d97560;
}

/* Ocean - deep navy-teal with coral pop. */
body.theme-ocean {
    --bg:       #0a1f2e;
    --surface:  #122c40;
    --surface2: #1a3a52;
    --border:   #305d80;
    --text:     #d8ecf5;
    --muted:    #7a9bb8;
    --accent:   #ff9e5e;
    --green:    #43d9a2;
    --red:      #ff5c8c;
}

/* ── New light themes ───────────────────────────── */

/* Rose - pale pink with deep rose accent. Soft, romantic. */
body.theme-rose {
    --bg:       #f5dee0;
    --surface:  #ffffff;
    --surface2: #fbecee;
    --border:   #d49ba2;
    --text:     #4a1a2a;
    --muted:    #8a4a5a;
    --accent:   #c44d6e;
    --green:    #4a8c5a;
    --red:      #b8253c;
}

/* Sage - pale sage green with terracotta accent. Earthy, calm. */
body.theme-sage {
    --bg:       #d4dec5;
    --surface:  #f5f5e8;
    --surface2: #ebebd5;
    --border:   #8a9b6e;
    --text:     #2e3a1a;
    --muted:    #5a6b3a;
    --accent:   #c45a3e;
    --green:    #5a8c3a;
    --red:      #c4302a;
}

/* Cream - buttery cream with chocolate accent. Warm vintage. */
body.theme-cream {
    --bg:       #f5ead4;
    --surface:  #fffaee;
    --surface2: #f9f0dc;
    --border:   #b59a6e;
    --text:     #4a2a14;
    --muted:    #7a5a30;
    --accent:   #8b4513;
    --green:    #5a8c3a;
    --red:      #b8252a;
}

/* Arctic - icy white-blue with steel accent. Crisp, minimal. */
body.theme-arctic {
    --bg:       #e0eaf2;
    --surface:  #ffffff;
    --surface2: #f0f5fa;
    --border:   #8aa0b8;
    --text:     #1a2a3e;
    --muted:    #5a6e88;
    --accent:   #3e7ab8;
    --green:    #2a8c6a;
    --red:      #d04060;
}

/* Sunflower - pale yellow with deep navy accent. Bright, cheerful. */
body.theme-sunflower {
    --bg:       #f5e890;
    --surface:  #fffbe6;
    --surface2: #faf0c8;
    --border:   #b8a040;
    --text:     #3a2a08;
    --muted:    #6e5a18;
    --accent:   #1a3a8c;
    --green:    #4a8c3a;
    --red:      #b8302a;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    padding: 16px;
}

/* ── HEADER ───────────────────────────────────── */
.header {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 10px 18px;
    margin-bottom: 10px;
    position: relative;
}
.header-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 14px;
    /* Always allow wrap. The .header-tight class set by
       js/header-layout.js (and the <=900px media query as a
       fallback) drops the weather strip onto its own row when
       brand-area + clock + weather + gaps would otherwise
       overflow. Above the wrap threshold this is a no-op
       because items already fit on one line. */
    flex-wrap: wrap;
}

/* Class toggled by js/header-layout.js when header-top can't fit
   brand-area, the verse chip (if inline), the weather minimum
   width, the clock, and gaps on a single row. Mirrors what the
   <=900px media query does, but driven by actual measured widths
   so a long username triggers wrap earlier and a short one
   later. */
.header-top.header-tight .hw-strip-wrap {
    flex: 1 1 100%;
    order: 10;
    padding-right: 0;
    padding-left: 0;
}
.header-top.header-tight .hw-strip {
    width: 100%;
    max-width: none;
    padding-bottom: 2px;
    /* gap inherited from the base .hw-strip rule (5px) keeps a thin
       seam between pills even when each one stretches. */
}
/* When wrapped to its own row, each pill grows equally so the strip
   spans the full row width instead of looking like a centered block
   with empty space on either side. flex: 1 1 0 distributes the row
   evenly across all 7 pills regardless of their natural content
   widths. */
.header-top.header-tight .hw-pill {
    flex: 1 1 0;
    min-width: 0;
}
/* Brand and clock take their content width and never shrink. The
   weather strip is the only flex item that's allowed to be squeezed -
   below the breakpoint it drops to its own row instead. */
.brand-area { flex: 0 0 auto; min-width: 0; }
.brand { font-size: 22px; font-weight: 700; color: var(--accent); white-space: nowrap; }
/* Inner flex row for the five header icon buttons (gear, person,
   lightbulb, bell, shield). Lives inside .brand-area but with its
   own display so .brand-area's outer dimensions stay block-driven
   by its widest child - the weather strip + bible verse chip in
   .header-top size themselves against that and must not change.
   The `gap` here is the single knob for icon spacing; individual
   button classes no longer set margin-right or margin-top. */
.brand-icons {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
}
/* (.brand-sub greeting was removed from the header. The class no
   longer renders anywhere; the rule is left commented in case a
   future sub-text under the brand wants to reuse the styling.) */
/* .brand-sub { font-size: 13px; color: var(--muted); margin-top: 3px; white-space: nowrap; } */
.clock-wrap { text-align: right; flex: 0 0 auto; }
.clock-row { display: flex; align-items: baseline; gap: 6px; justify-content: flex-end; }
/* tabular-nums locks every digit to the same width so the header
   doesn't twitch every tick. Without it, a "1" is noticeably
   narrower than an "8", which made the whole header reflow every
   second on desktop. (Trevor's 0.7 bug report.) Applied to every
   numeric chip in the header for the same reason. */
#clock { font-size: 28px; font-weight: 200; letter-spacing: 2px; font-variant-numeric: tabular-nums; }
#ampm { font-size: 12px; font-weight: 600; color: var(--muted); letter-spacing: 1px; }
#date-label { font-size: 11px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; }

/* Phase 5.4 extra timezone chip. Sits above the main clock-row
   when userState.clock_extra_timezone is set; absent otherwise.
   Small enough not to compete with the main clock. */
.extra-clock-chip {
    display: inline-block;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.4px;
    color: var(--muted);
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 2px 8px;
    margin-bottom: 3px;
    line-height: 1.4;
}

/* ── WEATHER PILL STRIP (inside header) ────────── */
.hw-strip {
    display: flex; gap: 5px;
    justify-content: flex-end; align-items: center;
    flex-wrap: nowrap; overflow-x: auto;
    position: relative;
}
.hw-pill {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 5px 9px; border-radius: 10px;
    border: 1px solid var(--border); background: var(--surface2);
    cursor: pointer; flex-shrink: 0;
    transition: border-color 0.15s, background 0.15s;
    min-width: 48px;
}
.hw-pill:hover { border-color: var(--muted); }
.hw-pill.today { border-color: var(--accent); background: rgba(108,99,255,0.13); }
.hw-pill.selected { border-color: #e8eaf0 !important; background: rgba(255,255,255,0.08) !important; }
.hw-name { font-size: 9px; font-weight: 700; text-transform: uppercase; color: var(--muted); letter-spacing: 0.5px; }
.hw-icon { font-size: 16px; line-height: 1; }
.hw-rain { font-size: 9px; font-weight: 700; padding: 1px 5px; border-radius: 10px; }

/* ── WEATHER DETAIL DROPDOWN (floats below pill strip) ─── */
.hw-strip-wrap {
    /* flex: 0 1 auto so the strip sits at its natural pill-row width
       instead of growing and accumulating empty space on its left
       side. With header-top's justify-content: space-between, the
       extra horizontal space gets distributed evenly between brand,
       weather, and clock instead of all of it landing in the gap
       between the brand-area icons and the first weather pill. */
    flex: 0 1 auto;
    min-width: 0;            /* allow shrinking below intrinsic strip width */
    display: flex;
    justify-content: flex-end;
    position: relative;
}
#weather-detail {
    display: none;
    position: absolute;
    top: 100%;
    z-index: 200;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 10px 12px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.35);
    margin-top: 6px;
    /* left & width set dynamically by JS to match #hw-strip exactly */
}
#weather-detail.open { display: block; }
.detail-header {
    display: flex; justify-content: space-between; align-items: center;
    margin-bottom: 8px;
}
.detail-title { font-size: 11px; font-weight: 700; }
.detail-note { font-size: 9px; color: var(--muted); }
.detail-close {
    background: none; border: none; color: var(--muted);
    font-size: 14px; cursor: pointer; padding: 0 2px; line-height: 1;
}
.detail-close:hover { color: var(--text); }
.detail-slots { display: flex; gap: 5px; justify-content: space-between; }
.detail-slot {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: 10px; padding: 5px 9px; text-align: center;
    flex: 1;
}
.slot-time { font-size: 9px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.slot-icon { font-size: 16px; line-height: 1; }
.slot-temp { font-size: 9px; font-weight: 700; }
.slot-rain {
    display: inline-flex; align-items: center; gap: 2px;
    font-size: 9px; font-weight: 700;
    padding: 1px 5px; border-radius: 10px;
}
.slot-desc { display: none; }
.rain-low  { background: rgba(67,217,162,0.15); color: #43d9a2; }
.rain-med  { background: rgba(255,179,71,0.15);  color: #ffb347; }
.rain-high { background: rgba(255,101,132,0.18); color: #ff6584; }
/* Placeholder slot for chosen hours that NWS didn't return data for
   (almost always: the hour already passed today, since the hourly
   endpoint only goes from the current hour forward). Same layout
   slot - just muted so the eye skips past it. */
.detail-slot-empty {
    opacity: 0.4;
}
.detail-slot-empty .slot-icon,
.detail-slot-empty .slot-temp,
.detail-slot-empty .slot-rain {
    color: var(--muted);
    background: transparent;
}

/* ── TASK EDIT MODAL ─────────────────────────── */
.modal-overlay {
    display: none; position: fixed; inset: 0;
    background: rgba(0,0,0,0.55); z-index: 500;
    align-items: flex-start; justify-content: center;
    padding-top: 60px;
}
.modal-overlay.open { display: flex; }

/* Lock the page underneath whenever any modal-overlay is open
   so wheel/touchpad/touch scrolls inside the modal don't bleed
   through to the dashboard. Uses :has so we don't have to wire
   every open/close site to add/remove a body class; the
   selector engages on any descendant .modal-overlay.open.
   Same selector also clamps overscroll bounce on iOS Safari. */
body:has(.modal-overlay.open) {
    overflow: hidden;
    overscroll-behavior: contain;
}
.modal {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 14px; padding: 20px 22px;
    width: 480px; max-width: 95vw; max-height: 80vh;
    overflow-y: auto;
    box-shadow: 0 16px 48px rgba(0,0,0,0.5);
}
.modal-title {
    font-size: 13px; font-weight: 700; margin-bottom: 16px;
    display: flex; justify-content: space-between; align-items: center;
}
.modal-close {
    background: none; border: none; color: var(--muted);
    font-size: 18px; cursor: pointer; line-height: 1; padding: 0 2px;
}
.modal-close:hover { color: var(--text); }
.modal-field { margin-bottom: 13px; }
.modal-label { font-size: 10px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 5px; }
.modal-input {
    width: 100%; background: var(--surface2); border: 1px solid var(--border);
    border-radius: 8px; padding: 7px 10px; color: var(--text);
    font-size: 12px; box-sizing: border-box;
}
.modal-input:focus { outline: none; border-color: var(--accent); }

/* ── 20260639: GOOGLE-STYLE TIME PICKER ───────────────────────────
   bindTimePicker(input) wraps an existing <input> in this combobox.
   The original input becomes .tp-original (hidden in DOM, still
   the canonical 24h value); .tp-display is the visible 12h-format
   text field; .tp-dropdown is the positioned preset list. */
.tp-wrap {
    position: relative;
    display: block;
}
.tp-original {
    /* Hide the underlying <input type="time"> visually but keep it
       in the DOM so its .value remains the canonical source of
       truth and any third-party code reading it keeps working. */
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0 0 0 0) !important;
    border: 0 !important;
}
.tp-display.tp-invalid {
    border-color: var(--red);
    color: var(--red);
}
.tp-dropdown {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    z-index: 1000;
    max-height: 200px;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
    padding: 4px 0;
    display: flex;
    flex-direction: column;
}
.tp-option {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--text);
    cursor: pointer;
    padding: 6px 12px;
    text-align: left;
    font-family: inherit;
    font-size: 12px;
    width: 100%;
    transition: background 80ms ease;
}
.tp-option:hover { background: var(--surface2); }
.tp-option-active {
    background: color-mix(in srgb, var(--accent) 18%, var(--surface));
    color: var(--accent);
    font-weight: 600;
}
.tp-option-empty {
    padding: 8px 12px;
    color: var(--muted);
    font-size: 11px;
    font-style: italic;
    text-align: center;
}

.modal-cat-pills { display: flex; gap: 5px; flex-wrap: wrap; }
.modal-cat-pill {
    font-size: 10px; font-weight: 700; padding: 3px 10px;
    border-radius: 20px; border: 1px solid transparent;
    cursor: pointer; opacity: 0.45; transition: opacity 0.15s;
}
.modal-cat-pill.active { opacity: 1; border-color: currentColor; }
.modal-pri-pills { display: flex; gap: 6px; }
.modal-pri-pill {
    font-size: 10px; font-weight: 700; padding: 3px 12px;
    border-radius: 20px; border: 1px solid var(--border);
    cursor: pointer; background: var(--surface2); color: var(--muted);
}
.modal-pri-pill.active-high  { background: rgba(255,101,132,0.2); color: #ff6584; border-color: #ff6584; }
.modal-pri-pill.active-medium{ background: rgba(255,179,71,0.2);  color: #ffb347; border-color: #ffb347; }
.modal-pri-pill.active-low   { background: rgba(67,217,162,0.2);  color: #43d9a2; border-color: #43d9a2; }
/* Subtasks inside modal */
.modal-sub-list { display: flex; flex-direction: column; gap: 5px; margin-bottom: 8px; }
.modal-sub-row {
    display: flex; align-items: center; gap: 7px;
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: 8px; padding: 5px 8px;
}
.modal-sub-check {
    width: 14px; height: 14px; border-radius: 50%;
    border: 2px solid var(--border); flex-shrink: 0; cursor: pointer;
    background: transparent;
}
.modal-sub-check.done { background: var(--accent); border-color: var(--accent); }
.modal-sub-text {
    flex: 1; background: none; border: none; color: var(--text);
    font-size: 11px; padding: 0;
}
.modal-sub-text:focus { outline: none; }
.modal-sub-text.done-text { text-decoration: line-through; color: var(--muted); }
.modal-sub-del {
    background: none; border: none; color: var(--muted);
    font-size: 14px; cursor: pointer; line-height: 1; padding: 0 2px;
}
.modal-sub-del:hover { color: #ff6584; }
.modal-add-sub-row {
    display: flex; gap: 6px;
}
.modal-add-sub-inp {
    flex: 1; background: var(--surface2); border: 1px solid var(--border);
    border-radius: 8px; padding: 5px 9px; color: var(--text); font-size: 11px;
}
.modal-add-sub-inp:focus { outline: none; border-color: var(--accent); }
.modal-add-sub-btn {
    background: var(--accent); border: none; border-radius: 8px;
    color: #fff; font-size: 11px; font-weight: 700;
    padding: 5px 12px; cursor: pointer;
}
.modal-save-btn {
    width: 100%; background: var(--accent); border: none; border-radius: 8px;
    color: #fff; font-size: 12px; font-weight: 700;
    padding: 9px; cursor: pointer; margin-top: 16px;
}
.modal-save-btn:hover { opacity: 0.9; }

/* (.stats-bar / .chip / .streak-chip styles were retired alongside
   the stats-bar div in index.html - those metrics now live in the
   KPI bands widget below. The streak NUMBER still updates in
   userState (js/state.js) on day rollover, and js/streak.js's
   renderStreak() runs as a no-op so the logic is ready when we
   want to surface it again.) */

/* ── MOBILE STACK (Phase 2.16 mobile slice) ─────────────────────
   Below 600px, shell.js bypasses Gridstack entirely and renders
   widgets as a CSS flex column. This fixes the longstanding KPI-
   band-overlapping-the-widget-below bug because each widget host
   now has natural height instead of fighting Gridstack's absolute
   positioning. Mobile order comes from user_widgets.mobile_order
   (with desktop column-flow as the fallback for unset rows). */
.mobile-stack {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 100%;
    /* Reset any Gridstack-style absolute positioning that may have
       been applied to children by a previous desktop mount. */
    position: static;
    height: auto !important;
    min-height: 0 !important;
}
.mobile-stack-item {
    width: 100%;
    /* Each widget gets whatever height it needs. The widget's own
       .panel/.kpi-grid root sets internal padding and scroll. */
    position: relative;
    height: auto;
    min-height: 0;
}
.mobile-stack-item .widget-host {
    width: 100%;
    height: auto;
    min-height: 0;
}


/* ── TAB BAR (Phase 2.8 Slice 1+2) ─────────────────
   Strip of chips between the header and the widget grid.
   Slice 2 added click-to-switch, edit pencil, "+ Add tab" button,
   and the soft 3-tab cap with a "Pro coming soon" tooltip on the
   disabled add button.

   The .tab-fading class on .main-grid / #region-top is toggled by
   tab-bar.js during a tab switch to fade the body content out and
   back in around the swap. */
.tab-bar {
    display: block;
    margin: 0 0 10px 0;
}
.tab-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}
.tab-chip {
    /* The element is a <button> in Slice 2; reset native button styles. */
    appearance: none;
    background: var(--surface2, var(--surface));
    border: 1px solid var(--border);
    color: var(--muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px 6px 12px;
    border-radius: 999px;
    font-size: 13px;
    line-height: 1;
    user-select: none;
    transition: background 120ms ease, color 120ms ease,
                border-color 120ms ease, box-shadow 120ms ease;
}
.tab-chip:hover {
    color: var(--text);
    border-color: var(--tab-accent, var(--accent));
}
.tab-chip:focus-visible {
    outline: 2px solid var(--tab-accent, var(--accent));
    outline-offset: 2px;
}
.tab-chip.active {
    background: var(--surface);
    border-color: var(--tab-accent, var(--accent));
    color: var(--text);
    font-weight: 600;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--tab-accent, var(--accent)) 18%, transparent);
}
.tab-chip-icon {
    font-size: 14px;
    line-height: 1;
}
.tab-chip-label {
    white-space: nowrap;
}
.tab-chip-edit {
    font-size: 11px;
    color: var(--muted);
    margin-left: 2px;
    opacity: 0;
    transition: opacity 120ms ease, color 120ms ease;
    cursor: pointer;
    padding: 0 2px;
    border-radius: 4px;
}
.tab-chip:hover .tab-chip-edit,
.tab-chip:focus-within .tab-chip-edit { opacity: 0.8; }
.tab-chip-edit:hover { opacity: 1; color: var(--accent); }

/* Tab-chip notification dot. Surfaces when a state-layer predicate
   reports something on the tab needs attention. The
   "-accountability" variant is the red "an accountability partner
   posted today and you haven't responded yet" cue placed by
   tab-bar.js. Sits between the label and the pencil affordance so
   it stays visible even when the chip isn't hovered. */
.tab-chip-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-left: 2px;
    flex-shrink: 0;
    pointer-events: none;
}
.tab-chip-dot-accountability {
    background: var(--red);
    box-shadow: 0 0 0 2px var(--surface);
}

.tab-add {
    appearance: none;
    background: transparent;
    border: 1px dashed var(--border);
    color: var(--muted);
    cursor: pointer;
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 13px;
    line-height: 1;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.tab-add:hover:not(.disabled) {
    color: var(--accent);
    border-color: var(--accent);
    border-style: solid;
    background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.tab-add.disabled {
    opacity: 0.6;
    cursor: not-allowed;
    /* The native title tooltip is what surfaces "Pro coming soon"
       to the user. The disabled visual signals the cap was hit. */
}

/* Body fade applied during tab switch. Both columns and the top
   region fade together so the swap feels like a single transition.
   The remount happens between fade-out and fade-in so the user
   only sees the final state of the new tab. */
#dashboard-grid {
    transition: opacity 140ms ease;
}
#dashboard-grid.tab-fading {
    opacity: 0;
    pointer-events: none;
}

/* ── GRIDSTACK OVERRIDES (Phase 2.16 Slice 1) ─────────
   Gridstack ships its own gridstack.min.css from the CDN that
   styles .grid-stack and .grid-stack-item with grey fills + a
   light border. We override those so our existing .panel widget
   styling shines through cleanly. The grid item is a transparent
   container; the panel paints its own background and borders. */
.grid-stack > .grid-stack-item {
    background: transparent !important;
}
.grid-stack > .grid-stack-item > .grid-stack-item-content {
    inset: 0;
    background: transparent !important;
    border: none !important;
    overflow: visible;
    display: flex;
    flex-direction: column;
}
.widget-host {
    height: 100%;
    display: flex;
    flex-direction: column;
}
/* Every widget's root element (.panel for most, .kpi-grid for the
   KPI bands widget, anything else for future widgets) should fill
   the widget-host so the dashed-outline edit-mode rectangle wraps
   the actual content rather than leaving empty space below. */
.widget-host > * {
    flex: 1 1 auto;
    box-sizing: border-box;
}
.widget-host > .panel {
    /* Keep the explicit height:100% on .panel because some panels
       have an internal `overflow-y: auto` scroll body that needs
       a fixed parent height to scroll inside. flex: 1 1 auto by
       itself isn't enough for those widgets. */
    height: 100%;
}

/* Gridstack's default placeholder (when dragging a widget over a
   target cell) is a solid grey block. We tint it with the accent
   color so it matches our theme. Slice 2 uses this; Slice 1 ships
   it ready. */
.grid-stack-placeholder > .placeholder-content {
    background: color-mix(in srgb, var(--accent) 18%, transparent) !important;
    border: 2px dashed var(--accent) !important;
    border-radius: 14px !important;
}

/* ── EDIT LAYOUT PILL (Phase 2.16 Slice 1) ────────────
   Lives at the right end of .tab-strip. Visually distinct from
   the tab chips: solid accent border on hover, fill-with-accent
   when active so it's obvious you're in edit mode. Hidden on
   mobile (handled in JS by setting .hidden). */
/* Wrapper that holds Reset + Edit Layout together at the right
   end of the tab strip. Margin-left: auto on the WRAPPER (not on
   Edit Layout itself) keeps the two pills adjacent to each other
   regardless of whether Reset is currently visible. */
.edit-pill-group {
    margin-left: auto;
    display: flex;
    gap: 6px;
    align-items: center;
}

.edit-layout-pill {
    appearance: none;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--muted);
    cursor: pointer;
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    transition: background 120ms ease, border-color 120ms ease,
                color 120ms ease;
    white-space: nowrap;
}
.edit-layout-pill:hover {
    color: var(--accent);
    border-color: var(--accent);
}
.edit-layout-pill.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.edit-layout-pill.active:hover {
    filter: brightness(1.1);
}
/* Mobile collapses the pill to an icon-only button so the tab
 * strip beside it has room to scroll. The flex-shrink:0 keeps it
 * from being squashed to zero width when the strip overflows. */
.edit-layout-pill-mobile {
    width: 36px;
    height: 36px;
    padding: 0;
    font-size: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* ── SNAP + RESET PILLS (Phase 2.16 Slice 3) ───────
   Sit immediately to the left of the Edit Layout pill in the tab
   strip. Hidden outside edit mode (the JS toggles `hidden`).
   Smaller / lighter visual weight than the primary pill so they
   feel like sub-actions of edit mode rather than peers. */
.edit-secondary-pill {
    appearance: none;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--muted);
    cursor: pointer;
    padding: 5px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    transition: background 120ms ease, border-color 120ms ease,
                color 120ms ease;
    white-space: nowrap;
}
.edit-secondary-pill:hover {
    color: var(--accent);
    border-color: var(--accent);
}
/* Reset is destructive (clears the tab's customizations); tint
   subtly red on hover so the user thinks twice. */
.edit-reset-pill:hover {
    color: var(--red);
    border-color: var(--red);
}

/* Admin-only template-save mode: when the active tab has a
   template_slug AND the user is admin, the Save as default pill
   becomes a live action that snapshots the layout into
   tab_templates. Tinted accent so it stands out from the
   placeholder Pro variant. */
.edit-save-default-pill-admin {
    color: var(--accent);
    border-color: var(--accent);
}
.edit-save-default-pill-admin:hover {
    background: rgba(108, 99, 255, 0.12);
}

/* "+ Add widget" pill is the constructive twin of Reset; tint
   green on hover so the user reads it as a positive action. */
.edit-add-widget-pill:hover {
    color: var(--green);
    border-color: var(--green);
}

/* Floating popup that lists widgets not currently on the tab. */
.edit-add-popup {
    position: absolute;
    z-index: 600;
    width: 360px;
    max-width: 92vw;
    max-height: 60vh;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 18px 38px rgba(0,0,0,0.45);
    padding: 10px;
    color: var(--text);
    animation: edit-add-popup-in 120ms ease;
}
@keyframes edit-add-popup-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.edit-add-head {
    font-size: 10px;
    font-weight: 700;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 8px;
}
.edit-add-empty {
    color: var(--muted);
    font-size: 11px;
    line-height: 1.45;
    padding: 8px 4px;
}
.edit-add-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.edit-add-row {
    appearance: none;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 8px 10px;
    cursor: pointer;
    text-align: left;
    color: var(--text);
    display: flex;
    align-items: flex-start;
    gap: 10px;
    transition: background 120ms ease, border-color 120ms ease;
}
.edit-add-row:hover {
    background: color-mix(in srgb, var(--green) 8%, var(--surface2));
    border-color: var(--green);
}
.edit-add-row:disabled {
    opacity: 0.5;
    cursor: progress;
}
.edit-add-icon {
    font-size: 18px;
    line-height: 1;
    flex: 0 0 auto;
}
.edit-add-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.edit-add-title {
    font-size: 12px;
    font-weight: 600;
    color: var(--text);
}
.edit-add-tier {
    font-size: 9px;
    font-weight: 700;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    padding: 1px 5px;
    border-radius: 999px;
    margin-left: 4px;
    text-transform: uppercase;
}
.edit-add-desc {
    font-size: 10px;
    color: var(--muted);
    line-height: 1.4;
}
.edit-add-action {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 700;
    color: var(--green);
    align-self: center;
}

/* 20260638: the old .edit-reset-pill.armed pulse rule (two-step
   click-to-confirm) was retired when Reset switched to a single
   click + window.confirm dialog. Removing the keyframes here so
   it doesn't show up as dead CSS in the next audit. */

/* Undo pill: same base shape as the other secondary pills, but
   tinted faintly so it reads as a "go back" action distinct from
   destructive Reset. */
.edit-undo-pill:hover {
    border-color: var(--accent);
    color: var(--accent);
}

@media (max-width: 600px) {
    .edit-secondary-pill { display: none !important; }
}

/* ── EDIT MODE VISUALIZATION (Slice 1) ────────────────
   When body.edit-mode is on, every widget in the grid gets a
   visible accent border + a faint top "drag handle bar" + a
   bottom-right resize indicator. In Slice 1 these are purely
   visual; the actual drag/resize handlers stay disabled
   (Gridstack staticGrid stays true). Slice 2 will flip static
   off so the cursors and handles become functional.

   Outside edit mode the dashboard looks identical to the legacy
   column layout. No borders, no handles. Pure read view. */
body.edit-mode #dashboard-grid {
    /* Faint background grid pattern hints at the underlying
       12-column structure during edit mode. Not visible outside
       edit mode. */
    background-image:
        linear-gradient(to right,  color-mix(in srgb, var(--accent) 12%, transparent) 1px, transparent 1px),
        linear-gradient(to bottom, color-mix(in srgb, var(--accent) 12%, transparent) 1px, transparent 1px);
    background-size: calc(100% / 12) 100px;
    background-origin: content-box;
}
/* Extra scroll headroom below the grid in edit mode so dragging
   a widget toward the bottom or resizing one taller doesn't
   fight the browser's autoscroll-into-view. Without this, the
   page rubber-bands as you try to grow a widget down past the
   current bottom edge of the layout. The hint background grid
   above stops at the actual grid bounds, so the headroom just
   reads as plain page space. Mobile is unaffected (no edit
   mode there; the list-style modal handles reorder). */
body.edit-mode {
    padding-bottom: 480px;
}
@media (max-width: 600px) {
    body.edit-mode { padding-bottom: 8px; }
}
body.edit-mode .widget-host {
    /* Outline the full widget cell, regardless of whether the
       widget's root element is .panel, .kpi-grid, or anything
       else. Outline (not border) so it doesn't affect layout
       size and overlays the content cleanly. */
    outline: 2px dashed color-mix(in srgb, var(--accent) 60%, transparent);
    outline-offset: -2px;
    position: relative;
    border-radius: 14px;        /* matches .panel rounding so the dashed line follows the visual edge */
}

/* ── REAL DRAG HANDLE (Slice 2) ─────────────────────
   Injected by shell.js as the first child of every widget-host.
   Hidden outside edit mode (display:none) and slim-pill in edit
   mode. Gridstack's GRID_OPTIONS.handle = '.grid-drag-handle' so
   only this element triggers a drag; widget content below
   stays interactive. */
.grid-drag-handle {
    display: none;             /* invisible until edit mode opens */
}
body.edit-mode .grid-drag-handle {
    /* Absolutely positioned in the top-left corner of the widget's
       visible root (.panel / .kpi-grid / etc.) so edit-mode chrome
       doesn't displace widget content; the panel keeps its full
       visual height in both normal and edit mode. Mirror layout to
       the × button's top-right placement. */
    display: inline-flex;
    align-items: center;
    gap: 4px;
    position: absolute;
    top: 8px;
    left: 8px;
    padding: 3px 8px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    color: var(--accent);
    cursor: grab;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    line-height: 1;
    user-select: none;
    z-index: 3;
}
body.edit-mode .grid-drag-handle:active { cursor: grabbing; }
.grid-drag-handle-dots {
    font-size: 11px;
    line-height: 1;
}

/* The widget's visible root (.panel / .kpi-grid / etc.) hosts the
   absolutely-positioned × button. Setting position: relative here
   anchors the × to the widget's content area instead of letting it
   bubble up to a higher positioned ancestor. Applied via JS at
   inject time (shell.js) so legacy widgets that don't expect a
   relative parent still work. */
.grid-remove-btn-host {
    position: relative;
}

/* Remove (×) button on each widget. Hidden by default. In edit
   mode it shows in the top-right corner of every widget; clicking
   it removes the widget from the active tab (with a confirm). */
.grid-remove-btn {
    display: none;
    position: absolute;
    top: 8px;
    right: 8px;
    width: 22px;
    height: 22px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid color-mix(in srgb, var(--red) 50%, transparent);
    background: var(--surface);
    color: var(--red);
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
    z-index: 4;
    transition: background 120ms ease, color 120ms ease, transform 120ms ease;
}
body.edit-mode .grid-remove-btn { display: inline-flex; align-items: center; justify-content: center; }
.grid-remove-btn:hover {
    background: var(--red);
    color: #fff;
    transform: scale(1.1);
}

/* While Gridstack is dragging an item, the body gets the
   gs-dragging class. Use it to keep the cursor consistent and
   prevent accidental text selection. */
body.gs-dragging,
body.gs-dragging * {
    cursor: grabbing !important;
    user-select: none !important;
}

/* ── GRIDSTACK RESIZE HANDLES (Slice 2) ───────────────
   In edit mode the grid is non-static and Gridstack adds
   .ui-resizable-handle div(s) to each grid item per our
   resizable.handles option ('e, se, s'). Their default styling
   is invisible-but-present; surface them with subtle accent
   color so users can see where to grab. */
body.edit-mode .grid-stack-item .ui-resizable-handle {
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    transition: background 120ms ease;
    z-index: 2;
}
body.edit-mode .grid-stack-item .ui-resizable-handle:hover {
    background: color-mix(in srgb, var(--accent) 40%, transparent);
}
body.edit-mode .grid-stack-item .ui-resizable-e {
    width: 6px;
    right: 0;
    cursor: ew-resize;
    border-radius: 3px;
}
body.edit-mode .grid-stack-item .ui-resizable-s {
    height: 6px;
    bottom: 0;
    cursor: ns-resize;
    border-radius: 3px;
}
body.edit-mode .grid-stack-item .ui-resizable-se {
    width: 14px;
    height: 14px;
    right: 2px;
    bottom: 2px;
    cursor: nwse-resize;
    border-radius: 4px;
    background:
        linear-gradient(135deg, transparent 0%, transparent 30%,
                                 var(--accent) 30%, var(--accent) 45%,
                                 transparent 45%, transparent 55%,
                                 var(--accent) 55%, var(--accent) 70%,
                                 transparent 70%);
}
body.edit-mode .grid-stack-item .ui-resizable-se:hover {
    background:
        linear-gradient(135deg, transparent 0%, transparent 30%,
                                 var(--accent) 30%, var(--accent) 45%,
                                 transparent 45%, transparent 55%,
                                 var(--accent) 55%, var(--accent) 70%,
                                 transparent 70%);
    filter: brightness(1.3);
}

/* Mobile: the Edit Layout pill stays visible. Tapping it opens
   the list-style mobile edit modal (js/mobile-edit-modal.js)
   instead of toggling the desktop drag-and-drop overlay. The
   secondary pills (Reset / Save as default / + Add widget) are
   the ones hidden on mobile because their actions are surfaced
   inside the modal. (See the .edit-secondary-pill rule above.) */

/* ── TAB EDIT / NEW MODALS ─────────────────────────
   Both reuse .modal-overlay / .modal scaffolding. .tab-modal is a
   thin override for tab-specific layout. */
.tab-modal { width: 460px; }
.modal-help {
    font-size: 11px;
    color: var(--muted);
    margin-top: 4px;
    line-height: 1.35;
}
.tab-color-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.tab-color-input {
    width: 60px !important;
    height: 32px;
    padding: 2px;
    cursor: pointer;
}
.tab-icon-input {
    /* Larger font so a single emoji renders comfortably. */
    font-size: 18px;
    text-align: center;
    width: 80px;
}
.tab-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}
.tab-edit-default-row,
.tab-edit-danger-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}
.tab-edit-danger-row { padding-top: 8px; }

/* Mobile tab strip: switch from flex-wrap to horizontal scroll
   so 4+ tabs don't stack on multiple rows on a phone. Scroll-snap
   keeps each chip aligned to the left edge as the user pans. The
   scrollbar is hidden visually but kept functional via the OS
   touch overlay; on desktop the strip stays wrap-based via the
   default rules above. */
@media (max-width: 600px) {
    .tab-bar {
        margin: 0 -12px 10px -12px;     /* bleed past content padding */
        padding: 0 12px;
        /* Sibling layout on mobile: strip scrolls, pill group
         * stays pinned to the viewport's right edge. Requires
         * the JS to have moved the pill group out of .tab-strip
         * and into .tab-bar (see layout-edit-mode.js). */
        display: flex;
        align-items: center;
        gap: 6px;
    }
    .tab-strip {
        flex: 1 1 auto;
        min-width: 0;                   /* allow shrink-to-fit */
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        scroll-snap-type: x proximity;
        scrollbar-width: none;          /* Firefox */
        -ms-overflow-style: none;        /* IE / Edge legacy */
        -webkit-overflow-scrolling: touch;
        padding-bottom: 4px;
    }
    .tab-strip::-webkit-scrollbar { display: none; }
    /* On mobile the pill group lives outside .tab-strip (sibling
     * of it in .tab-bar). Drop the auto-margin since flex layout
     * already places it after the strip. */
    .tab-bar > .edit-pill-group {
        margin-left: 0;
        flex-shrink: 0;
    }
    .tab-chip,
    .tab-add {
        scroll-snap-align: start;
        flex: 0 0 auto;
    }
    /* Touch targets a touch larger on mobile so tapping a chip
       isn't a precision sport. */
    .tab-chip { padding: 8px 14px; }
    .tab-add  { padding: 8px 14px; }
}

/* ── TAB TEMPLATE PICKER (Phase 2.8 Slice 3) ───────
   Lives at the top of the New Tab modal. Cards previewing each
   starter template; greyed out (.disabled) for stubs that ship
   with future phases. Picking a card creates the tab and applies
   the template's layout in one step. */
.tab-new-modal-wide {
    width: 640px;
    max-width: 95vw;
}
.tab-tpl-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 8px;
    margin-top: 8px;
}
.tab-tpl-loading,
.tab-tpl-empty {
    grid-column: 1 / -1;
    color: var(--muted);
    font-size: 12px;
    padding: 12px;
    text-align: center;
}
.tab-tpl-card {
    appearance: none;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 10px;
    text-align: left;
    color: var(--text);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 6px;
    transition: background 120ms ease, border-color 120ms ease,
                transform 80ms ease, box-shadow 120ms ease;
}
.tab-tpl-card:hover:not(.disabled) {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 6%, var(--surface2));
    transform: translateY(-1px);
}
.tab-tpl-card.disabled {
    opacity: 0.55;
    cursor: not-allowed;
    background: var(--surface2);
}
.tab-tpl-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.tab-tpl-icon {
    font-size: 18px;
    line-height: 1;
}
.tab-tpl-label {
    font-weight: 600;
    font-size: 13px;
    flex: 1 1 auto;
}
.tab-tpl-tier {
    font-size: 9px;
    font-weight: 700;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    padding: 2px 6px;
    border-radius: 999px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.tab-tpl-desc {
    font-size: 11px;
    color: var(--muted);
    line-height: 1.4;
    /* Clamp to keep cards roughly the same height regardless of
       description length. */
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.tab-tpl-foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 6px;
    margin-top: auto;
    padding-top: 4px;
    font-size: 10px;
    color: var(--muted);
}
.tab-tpl-badge {
    padding: 2px 6px;
    border-radius: 999px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.tab-tpl-badge.live {
    background: color-mix(in srgb, var(--green) 15%, transparent);
    color: var(--green);
}
.tab-tpl-badge.coming {
    background: var(--surface);
    color: var(--muted);
    border: 1px solid var(--border);
}
.tab-tpl-widget-count {
    font-variant-numeric: tabular-nums;
}
.tab-new-divider {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 18px 0 12px 0;
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
}
.tab-new-divider::before,
.tab-new-divider::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--border);
}
.tab-new-divider-text {
    flex: 0 0 auto;
}

/* ── WIDGET FULL-PAGE VIEW (Phase 2.8 Slice 4) ─────
   When a widget opts in to a full-page render via the optional
   renderFullPage method, js/widgets/full-page.js hides the column
   layout and pops a full-width container with a "back to {tab}"
   breadcrumb. No widget uses this yet; the styles ship ready for
   Phase 2.10 Calendar and Phase 2.11 Meals to opt in.

   The hide is implemented via a class on .main-grid / #region-top
   instead of `display: none` so the existing widgets keep their
   in-memory state (open dropdowns, scroll positions). They're
   simply offscreen until the full page closes. */
.widget-full-page {
    padding: 8px 0 16px 0;
}
.full-page-hidden {
    /* Visibility hidden + zero size keeps the DOM live but renders
       nothing and reserves no space, which is what we want during
       a full-page mode. */
    visibility: hidden;
    height: 0;
    overflow: hidden;
    margin: 0;
    padding: 0;
}
.widget-full-back {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--muted);
    cursor: pointer;
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 13px;
    margin-bottom: 12px;
    transition: color 120ms ease, border-color 120ms ease;
}
.widget-full-back:hover {
    color: var(--accent);
    border-color: var(--accent);
}
.widget-full-body {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 16px;
    min-height: 60vh;
}

/* ── MAIN GRID ────────────────────────────────── */
.main-grid {
    display: grid;
    grid-template-columns: 1fr 2.2fr;
    gap: 12px;
    align-items: start;
}
.left-col { display: flex; flex-direction: column; gap: 12px; }
.panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    /* Generic widget scroll behavior (May 2026, Lukeinator007 ask):
     * when a widget's content is taller than the grid cell allocates,
     * the panel's BORDERS stay put and the content scrolls inside.
     *
     * overflow-y: auto gives us the scrollbar inside the rounded
     * border. overflow-x: hidden keeps horizontal layout stable -
     * widgets with their own horizontal scroll regions (tab strips,
     * sparklines) handle that themselves and shouldn't induce a
     * panel-level horizontal scrollbar.
     *
     * Widgets that have their own internal scroll container (tasks,
     * shopping, wins, calendar day-grid, etc.) still work as before:
     * their inner overflow-y: auto handles the in-list scroll first
     * and the panel-level scroll only kicks in if their wrapper
     * itself exceeds the panel height. The original Slice-A reason
     * for overflow:hidden (clipping scrollable children to the
     * rounded border) is preserved because overflow-y: auto also
     * clips at the panel border - the scrollbar paints inside it. */
    overflow-y: auto;
    overflow-x: hidden;
}
/* Pin the panel header so the title and any header buttons (⚙ cog,
   ↻ refresh, etc.) stay visible while the content below scrolls.
   z-index: 1 only stacks above the scrolling siblings within the
   panel; modals + tooltips live at document.body via position:fixed
   so this doesn't fight them. The negative side+top margins paired
   with matching padding let the sticky header span the full panel
   width INCLUDING the panel's own 16px padding so the surface-color
   background covers content sliding underneath instead of leaving
   a vertical gutter on either side. The 12px gap below the header
   stays the same as before via padding-bottom. */
.panel-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    position: sticky;
    top: -16px;
    margin: -16px -16px 12px -16px;
    padding: 16px 16px 12px 16px;
    background: var(--surface);
    z-index: 1;
}
.panel-title { font-size: 13px; font-weight: 700; }
.panel-sub { font-size: 10px; color: var(--muted); margin-top: 2px; }
.panel-icon { font-size: 18px; }
.sort-select {
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 10px; font-family: inherit;
    padding: 3px 6px; cursor: pointer; outline: none;
}
.sort-select:focus { border-color: var(--accent); color: var(--text); }
/* "Assigned to me" toggle button uses data-active to track its state
   so we get visual feedback (accent-tinted) when the filter is on.
   The button is a .sort-select so it inherits the base chip shape. */
#assigned-to-me-filter[data-active="1"] {
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    border-color: color-mix(in srgb, var(--accent) 55%, transparent);
    color: var(--accent);
}

/* (.streak-chip rule was retired with the stats-bar; the streak now
   surfaces as one of the KPI cards. See the kpi-card[data-kpi=streak]
   rules above. js/streak.js still runs as a defensive no-op so the
   logic is ready when we want to surface the chip again somewhere.) */

/* ── EMPTY ────────────────────────────────────── */
.empty {
    text-align: center; color: var(--muted);
    font-size: 12px; padding: 28px 12px;
    opacity: 0.7; line-height: 1.7;
}

/* ── SCROLLBARS ───────────────────────────────── */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }

/* ── LOGIN OVERLAY ──────────────────────────────── */
.login-overlay {
    position: fixed; inset: 0; background: var(--bg);
    z-index: 10000; display: flex;
    align-items: center; justify-content: center;
    padding: 16px;
}
.login-card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 14px; padding: 28px 24px;
    width: min(360px, 100%);
    box-shadow: 0 16px 48px rgba(0,0,0,0.4);
}
.login-title { font-size: 22px; font-weight: 700; color: var(--accent); text-align: center; }
.login-sub   { font-size: 13px; color: var(--muted); text-align: center; margin: 4px 0 18px; }
.login-card input {
    width: 100%; padding: 10px 12px; margin-bottom: 10px;
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: 8px; color: var(--text); font-size: 14px;
    font-family: inherit;
}
.login-card input:focus { outline: none; border-color: var(--accent); }
#login-btn {
    width: 100%; padding: 10px; margin-top: 6px;
    background: var(--accent); border: 0; border-radius: 8px;
    color: white; font-size: 14px; font-weight: 600; cursor: pointer;
    font-family: inherit;
}
#login-btn:hover:not(:disabled) { filter: brightness(1.1); }
#login-btn:disabled { opacity: 0.6; cursor: default; }
.login-err { color: var(--red); font-size: 12px; margin-top: 10px; text-align: center; min-height: 16px; }
.login-toggle-row { text-align: center; margin-top: 12px; }
.login-toggle-link {
    color: var(--muted); font-size: 12px; text-decoration: none;
    border-bottom: 1px dashed transparent;
}
.login-toggle-link:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* (.user-email removed - email lives inside the Account Settings modal now.) */

/* (#logout-btn removed - Sign out lives inside the Account Settings modal now.) */

/* ── PASSWORD FIELD WITH EYE TOGGLE ────────────────────────────
   The .password-field wrapper sits where the bare <input> used to live
   (js/password-eye.js inserts it on bindLoginForm / bindAccountEditor).
   Padding-right on the input is tuned to leave room for the absolutely-
   positioned eye button without overlapping typed characters. The
   button sits inside the input visually but is its own element so we
   can style it independently. Trevor's request. */
.password-field {
    position: relative;
    width: 100%;
    margin-bottom: 10px;
}
/* Cancel the input's bottom margin so the wrapper owns the spacing. */
.password-field > input {
    margin-bottom: 0 !important;
    padding-right: 40px;     /* leave room for the eye button */
}
.password-eye {
    position: absolute;
    right: 6px; top: 50%;
    transform: translateY(-50%);
    width: 32px; height: 32px;
    background: transparent; border: 0;
    color: var(--muted);
    font-size: 16px;
    cursor: pointer;
    border-radius: 6px;
    display: flex; align-items: center; justify-content: center;
    line-height: 1;
    user-select: none;
}
.password-eye:hover  { background: var(--surface); color: var(--text); }
.password-eye:active { background: var(--surface2); }

/* ── TOAST ────────────────────────────────────── */
#toast-container {
    position: fixed;
    bottom: 16px; right: 16px;
    display: flex; flex-direction: column; gap: 8px;
    z-index: 9999; max-width: 420px;
    pointer-events: none;
}
.toast {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: 10px;
    padding: 10px 14px;
    color: var(--text);
    font-size: 12px;
    line-height: 1.4;
    box-shadow: 0 8px 24px rgba(0,0,0,0.4);
    opacity: 0;
    transform: translateX(20px);
    transition: opacity 0.25s, transform 0.25s;
    pointer-events: auto;
    word-break: break-word;
}
.toast.toast-in  { opacity: 1; transform: translateX(0); }
.toast.toast-out { opacity: 0; transform: translateX(20px); }
.toast-error { border-left-color: var(--red); }
.toast-ok    { border-left-color: var(--green); }
.toast-info  { border-left-color: var(--accent); }

/* ── LAYOUT EDITOR (gear button + modal) ──────── */
.layout-editor-btn {
    margin-top: 4px; margin-right: 6px;
    background: none; border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 12px; padding: 2px 7px;
    cursor: pointer; font-family: inherit;
    line-height: 1;
}
.layout-editor-btn:hover { border-color: var(--accent); color: var(--text); }
.layout-editor-btn .sr {
    position: absolute; left: -9999px;
}

.layout-editor-modal {
    width: 640px; max-width: 95vw; max-height: 85vh;
    overflow-y: auto;
}
/* Per-widget cog button - CANONICAL widget settings affordance.
   Lives in .panel-header next to .panel-icon. Quiet by default,
   accent on hover. Use this class for any widget-scoped settings
   popup; row-level edit pencils have their own per-widget classes
   (.habit-edit-btn, .shop-edit-btn, etc.). The widget contract in
   js/widgets/contract.js documents the canonical template; new
   widgets should adopt it without exception. */
.widget-cog-btn {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--muted);
    cursor: pointer;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    font-size: 13px;
    line-height: 1;
    padding: 0;
    transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
.widget-cog-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* Phase 2.16 polish: when the layout-editor modal is opened from
   inside a widget (via openCategoriesEditor), hide the legacy
   widget-toggle/reorder list and the unrelated category section
   so the user only sees what they came for. */
#layout-editor-modal.le-categories-only #layout-editor-list,
#layout-editor-modal.le-categories-only .layout-editor-tabnote,
#layout-editor-modal.le-categories-only .layout-editor-help {
    display: none;
}
#layout-editor-modal.le-categories-only.le-kind-task .le-cats-block[data-cats-kind="shop"] {
    display: none;
}
#layout-editor-modal.le-categories-only.le-kind-shop .le-cats-block[data-cats-kind="task"] {
    display: none;
}

.layout-editor-tabnote {
    display: flex;
    align-items: center;
    gap: 8px;
    background: color-mix(in srgb, var(--accent) 8%, var(--surface2));
    border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border));
    border-radius: 8px;
    padding: 8px 10px;
    margin-bottom: 12px;
    font-size: 11px;
    color: var(--text);
    line-height: 1.4;
}
.le-tabnote-icon { font-size: 16px; line-height: 1; flex: 0 0 auto; }
.le-tabnote-text { flex: 1 1 auto; }
.le-tabnote-text b { color: var(--accent); }
.layout-editor-help {
    font-size: 11px; color: var(--muted); line-height: 1.5;
    margin-bottom: 14px;
}
.layout-editor-list {
    display: flex; flex-direction: column; gap: 16px;
    margin-bottom: 14px;
}
.le-column {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 8px 10px;
}
.le-column-header {
    font-size: 10px; font-weight: 700; text-transform: uppercase;
    color: var(--muted); letter-spacing: 0.5px;
    padding: 4px 2px 8px;
}
.le-column-empty {
    font-size: 11px; color: var(--muted);
    padding: 12px 4px;
    text-align: center;
}
.le-row {
    display: grid;
    grid-template-columns: auto 22px 1fr 130px auto;
    grid-template-areas: "toggle icon meta col order";
    gap: 8px;
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 8px 10px;
    margin-bottom: 6px;
}
.le-toggle { grid-area: toggle; }
.le-icon   { grid-area: icon; }
.le-meta   { grid-area: meta; }
.le-col    { grid-area: col; }
.le-order  { grid-area: order; }
/* Narrow viewports: column dropdown + reorder buttons stack to a
   second row so the title/description gets the full width on row 1. */
@media (max-width: 600px) {
    .le-row {
        grid-template-columns: auto 22px 1fr auto;
        grid-template-areas:
            "toggle icon meta meta"
            "col    col  col  order";
        row-gap: 8px;
    }
    .le-col   { grid-area: col; width: 100%; }
    .le-order { grid-area: order; }
}
.le-row:last-child { margin-bottom: 0; }
.le-row-disabled { opacity: 0.45; }

.le-toggle {
    position: relative; width: 30px; height: 16px; cursor: pointer;
}
.le-toggle input {
    position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer;
}
.le-toggle-track {
    position: absolute; inset: 0;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 999px;
    transition: background 0.15s, border-color 0.15s;
}
.le-toggle-track::after {
    content: '';
    position: absolute; top: 1px; left: 1px;
    width: 12px; height: 12px;
    background: var(--muted); border-radius: 50%;
    transition: transform 0.15s, background 0.15s;
}
.le-toggle input:checked ~ .le-toggle-track {
    background: rgba(108,99,255,0.25);
    border-color: var(--accent);
}
.le-toggle input:checked ~ .le-toggle-track::after {
    transform: translateX(14px);
    background: var(--accent);
}

.le-icon { font-size: 18px; line-height: 1; }
.le-meta { min-width: 0; }
.le-title { font-size: 13px; font-weight: 700; color: var(--text); }
.le-desc  { font-size: 11px; color: var(--muted); margin-top: 2px;
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.le-col {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    font-size: 11px; font-family: inherit;
    padding: 4px 6px;
    cursor: pointer;
}
.le-col:focus { outline: none; border-color: var(--accent); }

.le-order { display: flex; gap: 2px; }
.le-arrow {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--muted);
    font-size: 12px;
    width: 24px; height: 24px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    padding: 0;
}
.le-arrow:hover { color: var(--text); border-color: var(--text); }

/* ── CATEGORIES inside the layout-editor modal ──────────────── */
.le-cats-block {
    margin-top: 14px;
    padding-top: 0;
    border-top: 1px solid var(--border);
}
/* The header is now a click-to-toggle button (item 2). */
.le-cats-toggle {
    width: 100%;
    display: flex; align-items: center; gap: 8px;
    padding: 12px 4px;
    background: transparent; border: 0;
    color: var(--text);
    font-family: inherit; font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    cursor: pointer;
    text-align: left;
}
.le-cats-toggle:hover { color: var(--accent); }
.le-cats-caret {
    display: inline-block;
    width: 12px; text-align: center;
    color: var(--muted); font-size: 10px;
    transition: transform 0.12s ease;
}
.le-cats-toggle-label { flex: 1; }
.le-cats-toggle-count {
    font-size: 11px; font-weight: 600;
    color: var(--muted);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1px 8px;
    min-width: 22px;
    text-align: center;
    text-transform: none; letter-spacing: 0;
}
.le-cats-block-open .le-cats-toggle-label { color: var(--text); }
.le-cats-body {
    /* When the cats section is collapsed, the body's `hidden` attribute
       hides it (now reliably, thanks to the global `[hidden]
       !important` rule). When open, simple block flow stacks the help
       text + list + add button. */
    padding: 0 4px 4px;
}
.le-cats-help {
    font-size: 11px; color: var(--muted); line-height: 1.5;
    margin-bottom: 10px;
}
.le-cats-list {
    display: flex; flex-direction: column; gap: 4px;
    margin-bottom: 8px;
}
.le-cats-compose-slot:empty { display: none; }
.le-cats-compose-slot:not(:empty) { margin-bottom: 8px; }
/* Compose row - the explicit Add-button pattern (item 1). Looks like a
   regular cat row but ends in [Add] [×] buttons. The accent border
   makes it visually obvious that this row is in-flight, not committed. */
.le-cat-compose {
    border-color: var(--accent) !important;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
    grid-template-columns: 28px 1fr auto !important;
}
.le-cat-compose-actions {
    display: flex; gap: 4px; align-items: center;
}
.le-cat-compose-add {
    background: var(--accent); border: 0; color: white;
    border-radius: 6px;
    font-size: 12px; font-weight: 600; font-family: inherit;
    padding: 6px 12px;
    cursor: pointer;
}
.le-cat-compose-add:hover { filter: brightness(1.1); }
.le-cat-compose-cancel {
    background: transparent; border: 1px solid var(--border);
    color: var(--muted);
    border-radius: 6px;
    width: 28px; height: 28px;
    font-size: 16px; line-height: 1;
    cursor: pointer;
    padding: 0;
    display: flex; align-items: center; justify-content: center;
}
.le-cat-compose-cancel:hover { color: var(--red); border-color: var(--red); }
.le-cat-row {
    display: grid;
    grid-template-columns: 28px 1fr auto;
    gap: 8px;
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 6px 8px;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
/* Briefly highlights a row added via "+ Add list" / "+ Add category"
   so iPhone users see visual confirmation even when programmatic
   focus() doesn't pop the keyboard. */
.le-cat-row-new {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
/* Highlights the row that blocked Save because its label was empty.
   The matching toast also names the row, but the colored border makes
   the offender unmistakable. */
.le-cat-row-error {
    border-color: var(--red);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent);
}
.le-cat-color {
    width: 28px; height: 24px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface2);
    cursor: pointer;
    padding: 0;
}
.le-cat-color::-webkit-color-swatch-wrapper { padding: 2px; }
.le-cat-color::-webkit-color-swatch { border: none; border-radius: 4px; }
.le-cat-label {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 5px 8px;
    color: var(--text);
    font-size: 12px; font-family: inherit;
    min-width: 0;
}
.le-cat-label:focus { outline: none; border-color: var(--accent); }
.le-cat-del {
    background: none; border: none;
    color: var(--muted);
    font-size: 18px; line-height: 1;
    cursor: pointer; padding: 0 4px;
}
.le-cat-del:hover { color: var(--red); }
.le-cats-add {
    background: var(--surface2);
    border: 1px dashed var(--border);
    border-radius: 8px;
    color: var(--muted);
    font-size: 12px; font-family: inherit; font-weight: 600;
    padding: 7px 14px;
    cursor: pointer;
    width: 100%;
}
.le-cats-add:hover { color: var(--accent); border-color: var(--accent); }

.layout-editor-footer {
    display: flex; gap: 8px; justify-content: flex-end;
    margin-top: 18px;
}
.layout-editor-cancel,
.layout-editor-save {
    font-family: inherit; font-size: 12px; font-weight: 600;
    padding: 8px 16px;
    border-radius: 8px;
    cursor: pointer;
}
.layout-editor-cancel {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--muted);
}
.layout-editor-cancel:hover { color: var(--text); }
.layout-editor-save {
    background: var(--accent);
    border: 0;
    color: #fff;
}
.layout-editor-save:hover { filter: brightness(1.1); }

/* ── ACCOUNT EDITOR (person button + settings modal) ── */
.account-editor-btn {
    /* Spacing comes from .brand-icons's flex gap; no per-button
       margin here. */
    background: none; border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 12px; padding: 2px 7px;
    cursor: pointer; font-family: inherit;
    line-height: 1;
}
.account-editor-btn:hover { border-color: var(--accent); color: var(--text); }
.account-editor-btn .sr { position: absolute; left: -9999px; }

/* Header-button popups (account / friends / feature-requests /
   app-updates / admin) all share the same outer dimensions so
   the popups feel like a consistent set. 900px width keeps
   the form fields and lists readable; 95vw + 88vh ceilings
   handle narrower windows and shorter viewports gracefully. */
.account-editor-modal {
    width: 900px; max-width: 95vw; max-height: 88vh;
    overflow-y: auto;
}
.ae-section {
    border-top: 1px solid var(--border);
    padding: 16px 0;
}
.ae-section:first-of-type { border-top: 0; padding-top: 0; }
.ae-section-title {
    font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--text);
    margin-bottom: 6px;
}
.ae-section-help {
    font-size: 11px; color: var(--muted); line-height: 1.5;
    margin-bottom: 10px;
}
.ae-section-help b { color: var(--text); font-weight: 600; }
.ae-section .modal-input { margin-bottom: 8px; }
.ae-row-msg {
    font-size: 11px; min-height: 14px; margin: 4px 0 8px;
    color: var(--red);
}
.ae-row-msg[data-kind="ok"]   { color: var(--green); }
.ae-row-msg[data-kind="warn"] { color: #ffb347; }

/* Slice C-fast (C.1): header chip toggles in Account Settings.
 * Each label is a row with a checkbox + emoji + name. */
.ae-chip-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 0;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
    user-select: none;
}
.ae-chip-toggle input {
    accent-color: var(--accent);
    width: 16px;
    height: 16px;
    cursor: pointer;
}
.ae-chip-toggle:hover { color: var(--accent); }

.ae-btn-primary,
.ae-btn-secondary,
.ae-btn-danger,
.ae-btn-danger-strong,
.ae-btn-cancel {
    font-family: inherit; font-size: 12px; font-weight: 600;
    padding: 7px 14px;
    border-radius: 8px;
    cursor: pointer;
}
.ae-btn-primary {
    background: var(--accent); border: 0; color: #fff;
}
.ae-btn-primary:hover { filter: brightness(1.1); }
.ae-btn-secondary {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
}
.ae-btn-secondary:hover { border-color: var(--accent); color: var(--accent); }

/* Location section (Item 5) */
.ae-loc-row { display: flex; gap: 8px; margin-bottom: 6px; }
.ae-loc-row > .modal-input { flex: 1; min-width: 0; }
.ae-loc-buttons { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 4px; }

/* Theme grid picker (Item 7 + theme expansion) */
.ae-theme-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 8px;
}
.ae-theme-card {
    background: var(--surface2);
    border: 2px solid var(--border);
    border-radius: 10px;
    padding: 6px;
    cursor: pointer;
    transition: border-color 0.15s, transform 0.1s;
    font-family: inherit;
    text-align: left;
}
.ae-theme-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.ae-theme-card.active {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent), 0 6px 16px rgba(0,0,0,0.15);
}
/* The preview shows the theme's actual colors via inline styles set
   by the renderer - these rules just lay it out. */
.ae-theme-preview {
    height: 56px;
    border-radius: 6px;
    display: flex; align-items: center; justify-content: center;
    margin-bottom: 6px;
}
.ae-theme-bubble {
    width: 70%; height: 60%;
    border-radius: 5px;
    border: 1.5px solid;        /* color set inline */
    display: flex; align-items: center; justify-content: center;
    font-size: 16px; font-weight: 700;
    line-height: 1;
}
.ae-theme-name {
    font-size: 11px; font-weight: 700;
    color: var(--text);
    text-align: center;
    line-height: 1.2;
}
.ae-theme-desc {
    font-size: 9px; color: var(--muted);
    text-align: center;
    line-height: 1.3;
    margin-top: 2px;
    /* Truncate to one line to keep cards uniform height. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Theme picker trigger button. Replaces the always-visible inline
   theme grid in the Account Settings modal so the modal stays short.
   Clicking opens the .ae-theme-modal sub-overlay which contains the
   full grid. */
.ae-theme-picker-btn {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 10px 12px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
.ae-theme-picker-btn:hover {
    border-color: var(--accent);
    background: var(--surface);
}
.ae-theme-picker-swatch {
    width: 22px; height: 22px;
    border-radius: 50%;
    border: 1px solid var(--border);
    flex: 0 0 auto;
    /* background, border-color, and box-shadow are all set inline by
       updateThemePickerLabel so the swatch reflects the active theme's
       actual colors. */
}
.ae-theme-picker-label { flex: 1 1 auto; text-align: left; }
.ae-theme-picker-label b { color: var(--text); font-weight: 600; }
.ae-theme-picker-chevron {
    color: var(--muted);
    font-size: 18px;
    line-height: 1;
    flex: 0 0 auto;
}

/* Theme picker sub-modal. Layered above the account editor via a
   higher z-index. The .modal-overlay base rules handle positioning
   and the open/closed visibility class. */
#ae-theme-modal { z-index: 10100; }
.ae-theme-modal-inner {
    width: 720px; max-width: 95vw; max-height: 88vh;
    overflow-y: auto;
}

/* Custom-theme card adornments - the "+ New custom" tile and the
   ✏ edit button overlay on saved customs. The wrapper takes the
   normal grid cell and the card stretches inside it; the edit
   button sits absolutely on top so hovering reveals it. */
.ae-theme-card-wrap {
    position: relative;
    display: block;
}
.ae-theme-card-wrap > .ae-theme-card {
    width: 100%;
    height: 100%;
}
.ae-theme-card-edit {
    position: absolute;
    top: 6px; right: 6px;
    width: 24px; height: 24px;
    background: rgba(0,0,0,0.55);
    border: 1px solid rgba(255,255,255,0.2);
    border-radius: 6px;
    color: #fff;
    font-size: 11px; line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s, transform 0.12s;
    z-index: 2;
}
.ae-theme-card-wrap:hover .ae-theme-card-edit,
.ae-theme-card-edit:focus-visible { opacity: 1; }
.ae-theme-card-edit:hover { transform: scale(1.05); }

.ae-theme-card-new .ae-theme-preview-new {
    background: var(--surface2);
    border: 1.5px dashed var(--border);
}
.ae-theme-card-new:hover .ae-theme-preview-new { border-color: var(--accent); }
.ae-theme-new-plus {
    font-size: 28px; font-weight: 200;
    color: var(--muted);
    line-height: 1;
}
.ae-theme-card-new:hover .ae-theme-new-plus { color: var(--accent); }

/* Custom-theme editor sub-sub-modal. Layered above the picker. */
#ae-theme-editor-modal { z-index: 10200; }
.ae-theme-editor-inner {
    width: 560px; max-width: 95vw; max-height: 90vh;
    overflow-y: auto;
}
.ce-name-row {
    margin-bottom: 14px;
}
.ce-field-label {
    display: block;
    font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--muted);
    margin-bottom: 4px;
}
.ce-vars {
    display: flex; flex-direction: column; gap: 6px;
    margin-bottom: 8px;
}
.ce-var-row {
    display: grid;
    grid-template-columns: 38px 1fr 96px;
    gap: 10px;
    align-items: center;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 6px 10px;
}
.ce-var-color {
    width: 38px; height: 32px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
}
.ce-var-color::-webkit-color-swatch-wrapper { padding: 2px; }
.ce-var-color::-webkit-color-swatch { border: none; border-radius: 4px; }
.ce-var-meta { min-width: 0; }
.ce-var-label {
    font-size: 12px; font-weight: 600;
    color: var(--text);
    line-height: 1.2;
}
.ce-var-help {
    font-size: 10px;
    color: var(--muted);
    line-height: 1.3;
    margin-top: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ce-var-hex {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 5px 8px;
    color: var(--text);
    font-size: 12px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    text-align: center;
    text-transform: lowercase;
}
.ce-var-hex:focus { outline: none; border-color: var(--accent); }
.ce-var-hex.ce-bad-hex { border-color: var(--red); }

.ce-footer {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
}
.ce-footer-spacer { flex: 1 1 auto; }

/* Editor stack collapses on phones - variable rows stack their meta
   beneath the swatch + hex inputs. */
@media (max-width: 520px) {
    .ce-var-row {
        grid-template-columns: 38px 1fr 84px;
        gap: 8px;
    }
    .ce-var-help { white-space: normal; }
}

/* Inline link inside section help text (Replay tour) */
.ae-inline-link {
    color: var(--accent);
    text-decoration: none;
    border-bottom: 1px dashed transparent;
}
.ae-inline-link:hover { border-bottom-color: var(--accent); }

.ae-section.ae-danger .ae-section-title { color: var(--red); }
.ae-btn-danger {
    background: transparent;
    border: 1px solid var(--red);
    color: var(--red);
}
.ae-btn-danger:hover { background: rgba(255,101,132,0.08); }

.ae-confirm-row {
    display: flex; flex-direction: column; gap: 8px;
    margin-top: 10px;
    padding: 10px; border-radius: 8px;
    background: rgba(255,101,132,0.06);
    border: 1px solid var(--red);
}
.ae-btn-danger-strong {
    background: var(--red); border: 0; color: #fff;
}
.ae-btn-danger-strong:hover { filter: brightness(1.05); }
.ae-btn-cancel {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--muted);
}
.ae-btn-cancel:hover { color: var(--text); }

/* ── ONBOARDING TOUR (Item 8) ─────────────────────────────── */
#tour-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.65);
    z-index: 9000;
    opacity: 0; pointer-events: none;
    transition: opacity 0.25s;
}
#tour-overlay.open { opacity: 1; pointer-events: auto; }

#tour-spotlight {
    position: fixed;
    z-index: 9001;
    border-radius: 10px;
    box-shadow:
        0 0 0 9999px rgba(0,0,0,0.65),    /* "cuts" the overlay */
        0 0 0 2px var(--accent),
        0 0 24px rgba(108,99,255,0.5);
    pointer-events: none;
    transition: top 0.25s, left 0.25s, width 0.25s, height 0.25s, opacity 0.25s;
    opacity: 0;
}
#tour-spotlight.open { opacity: 1; }
/* Override the overlay underneath the spotlight - only the spotlight
   provides darkening once it's open. */
#tour-overlay.open + #tour-spotlight.open ~ * { /* no-op selector - kept for ref */ }
#tour-overlay.open ~ #tour-spotlight.open { background: transparent; }
/* The bigger trick: when the spotlight is open we hide the overlay so
   the spotlight's outset shadow is the only darkening. That keeps the
   highlighted element fully clear of the dimming. */
#tour-overlay.open:has(~ #tour-spotlight.open) { background: transparent; }

#tour-tooltip {
    position: fixed;
    z-index: 9002;
    width: 320px; max-width: calc(100vw - 32px);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px 18px;
    color: var(--text);
    box-shadow: 0 16px 48px rgba(0,0,0,0.5);
    opacity: 0; transform: translateY(6px);
    transition: opacity 0.25s, transform 0.25s, top 0.25s, left 0.25s;
    pointer-events: none;
}
#tour-tooltip.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.tour-title { font-size: 14px; font-weight: 700; margin-bottom: 6px; color: var(--text); }
.tour-body  { font-size: 12px; color: var(--muted); line-height: 1.5; }
.tour-footer {
    display: flex; justify-content: space-between; align-items: center;
    margin-top: 14px; gap: 8px;
}
.tour-progress { font-size: 10px; color: var(--muted); letter-spacing: 0.5px; }
.tour-actions  { display: flex; gap: 6px; }
.tour-btn-skip,
.tour-btn-next {
    font-family: inherit; font-size: 11px; font-weight: 600;
    padding: 6px 12px; border-radius: 6px; cursor: pointer;
}
.tour-btn-skip {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--muted);
}
.tour-btn-skip:hover { color: var(--text); }
.tour-btn-next {
    background: var(--accent); border: 0; color: #fff;
}
.tour-btn-next:hover { filter: brightness(1.1); }

/* ── FEATURE REQUESTS ─────────────────────────────────────────
   Lightbulb header button + shared roadmap modal. Mirrors the
   visual language of the layout-editor and account-editor modals
   (same .modal frame, same button shapes), but the body is a
   board of collapsible status sections with cards inside.
   ────────────────────────────────────────────────────────────── */
.feature-requests-btn {
    /* Spacing comes from .brand-icons's flex gap; no per-button
       margin here. */
    background: none; border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 12px; padding: 2px 7px;
    cursor: pointer; font-family: inherit;
    line-height: 1;
}

/* ── BIBLE VERSE HEADER CHIP ─────────────────────
   Mounted into .header-top as a sibling of .brand-area, so it always
   sits to the right of the brand-area icons regardless of which
   order the various bind* calls run in. Verse text is shown in
   full (no truncation) and wraps to multiple lines when needed.
   On <=900px viewports the .header-top wraps via flex-wrap, so the
   chip flows onto its own line below the brand area, where there's
   plenty of horizontal room. */
.bible-verse-chip {
    display: inline-flex;
    align-items: flex-start;
    gap: 6px;
    /* Slice A polish: let the chip be wider than the old 320px
     * cap so a long verse stretches across fewer lines instead
     * of stacking 3-4 lines tall. CRITICAL: flex-grow stays 0
     * (the `0` in `0 1 auto`) so the chip never claims weather's
     * or the clock's space. Wrap priority is enforced by the JS
     * math + chip-tight class: chip is the first thing to wrap
     * to its own row when the inline layout doesn't fit, before
     * weather or clock get touched. flex-shrink stays 1 so the
     * chip can collapse under pressure rather than overflow.
     * max-width: 520px is the new cap; the JS reads this same
     * number via the CHIP_MAX_WIDTH constant. */
    flex: 0 1 auto;
    max-width: 520px;
    min-width: 0;
    padding: 6px 12px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 12px;
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
.bible-verse-chip:hover { border-color: var(--accent); }
.bible-verse-chip .bv-icon {
    font-size: 12px;
    flex: 0 0 auto;
    line-height: 1.4;
}
.bible-verse-chip .bv-text {
    /* Wrap freely. No nowrap, no ellipsis, no max-width.
       The chip grows as needed; on narrow viewports the
       header-top flex-wrap drops it onto its own row. */
    font-style: italic;
    color: var(--text);
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.bible-verse-chip .bv-ref {
    color: var(--muted);
    font-weight: 600;
    flex: 0 0 auto;
    margin-left: 4px;
    white-space: nowrap;
}

/* ── BIBLE VERSE TRANSLATION PICKER ────────────────
   Floating popup anchored below the chip. Shows the five supported
   public-domain translations with the current one marked. Click
   outside, press Escape, or click the chip again to close. Position
   is computed in JS so it stays viewport-clamped on narrow windows. */
.bible-verse-picker {
    position: absolute;
    z-index: 600;                /* above modals' backdrop start */
    width: 320px;
    max-width: 92vw;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 18px 38px rgba(0,0,0,0.45);
    padding: 10px;
    color: var(--text);
    animation: bvp-fadein 120ms ease;
}
@keyframes bvp-fadein {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.bvp-head {
    font-size: 10px;
    font-weight: 700;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 8px;
}
.bvp-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.bvp-row {
    appearance: none;
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
    cursor: pointer;
    text-align: left;
    padding: 8px 10px;
    border-radius: 8px;
    transition: background 120ms ease, border-color 120ms ease;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.bvp-row:hover {
    background: color-mix(in srgb, var(--accent) 8%, var(--surface2));
    border-color: var(--accent);
}
.bvp-row.active {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, var(--surface2));
}
.bvp-row-head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.bvp-row-short {
    font-weight: 700;
    font-size: 11px;
    letter-spacing: 0.5px;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    padding: 2px 6px;
    border-radius: 4px;
    min-width: 50px;
    text-align: center;
}
.bvp-row-name {
    font-size: 12px;
    font-weight: 600;
    flex: 1 1 auto;
}
.bvp-row-check {
    color: var(--accent);
    font-weight: 700;
    font-size: 13px;
}
.bvp-row-note {
    font-size: 10px;
    color: var(--muted);
    line-height: 1.35;
    padding-left: 58px;            /* aligns under the row name, past the SHORT badge */
}
.bvp-foot {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
    font-size: 10px;
    color: var(--muted);
    line-height: 1.4;
}

/* The chip wraps to its own row whenever js/header-layout.js
   decides the brand + chip + weather + clock + gaps don't fit on
   one line at their preferred sizes. The class is set per actual
   measured widths, so it adapts to title length, theme font reflow,
   any post-auth icon insertions, etc. Order:99 puts the chip last
   in flex visual order so it lands on its own wrapped row instead
   of pushing brand or clock around. */
.header-top.chip-tight .bible-verse-chip {
    flex: 1 1 100%;
    max-width: none;
    order: 99;
}

/* Safety-net media query for environments where JS doesn't run:
   below 700px the chip definitely cannot fit alongside brand and
   clock, so we force the wrap regardless. Above 700px the JS
   class is what controls things. */
@media (max-width: 700px) {
    .bible-verse-chip {
        flex: 1 1 100%;
        max-width: none;
        order: 99;
    }
}
.feature-requests-btn:hover { border-color: var(--accent); color: var(--text); }
.feature-requests-btn .sr   { position: absolute; left: -9999px; }

.feature-requests-modal {
    /* Standardized with the other header-button popups. */
    width: 900px; max-width: 95vw; max-height: 88vh;
    overflow-y: auto;
}
/* Two-tab strip at the top of the modal - Features / Bugs.
   Inactive tabs show as dim chips; the active one fills with the
   accent color and a subtle bottom underline so it reads as the
   "current page". Counts are rendered as small inline pills so
   users can see the volume on each side at a glance. */
.fr-tabs {
    display: flex; gap: 6px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 4px;
    margin-bottom: 12px;
}
.fr-tab {
    flex: 1;
    display: flex; align-items: center; justify-content: center; gap: 6px;
    padding: 8px 10px;
    background: transparent; border: 0; border-radius: 7px;
    color: var(--muted);
    font-family: inherit; font-size: 12px; font-weight: 600;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}
.fr-tab:hover { color: var(--text); background: var(--surface); }
.fr-tab-active {
    background: var(--surface);
    color: var(--text);
    box-shadow: inset 0 -2px 0 var(--accent);
}
.fr-tab-icon  { font-size: 14px; line-height: 1; }
.fr-tab-label { font-size: 12px; }
.fr-tab-count {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--muted);
    border-radius: 10px;
    padding: 1px 7px;
    font-size: 10px; font-weight: 600;
    min-width: 20px; text-align: center;
}
.fr-tab-active .fr-tab-count {
    background: var(--accent); color: white; border-color: transparent;
}
.fr-help {
    font-size: 11px; color: var(--muted); line-height: 1.5;
    margin-bottom: 12px;
}
.fr-help b { color: var(--text); font-weight: 600; }
/* Admin kind-change select sits next to the status select on each card. */
.fr-kind-select {
    background: var(--surface2);
    color: var(--muted);
    border: 1px solid var(--border);
}
.fr-kind-select:hover { border-color: var(--accent); color: var(--text); }

.fr-new-row {
    display: flex; gap: 8px; align-items: center;
    margin-bottom: 10px;
}
.fr-new-form {
    display: flex; flex-direction: column; gap: 8px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px;
    margin-bottom: 14px;
}
.fr-form-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
.fr-row-msg {
    font-size: 11px; min-height: 14px;
    color: var(--red);
}
.fr-row-msg[data-kind="ok"]   { color: var(--green); }
.fr-row-msg[data-kind="warn"] { color: #ffb347; }

.fr-btn-primary,
.fr-btn-secondary,
.fr-btn-cancel {
    font-family: inherit; font-size: 12px; font-weight: 600;
    padding: 7px 14px;
    border-radius: 8px;
    cursor: pointer;
}
.fr-btn-primary  { background: var(--accent); border: 0; color: #fff; }
.fr-btn-primary:hover  { filter: brightness(1.1); }
.fr-btn-secondary {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
}
.fr-btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
.fr-btn-cancel {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--muted);
}
.fr-btn-cancel:hover { color: var(--text); }

/* Board = vertical stack of status sections. */
.fr-board { display: flex; flex-direction: column; gap: 10px; }
.fr-empty {
    font-size: 12px; color: var(--muted);
    padding: 18px 4px; text-align: center;
}

.fr-section {
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--surface2);
}
.fr-section-header {
    width: 100%;
    display: flex; align-items: center; gap: 8px;
    padding: 10px 12px;
    background: transparent;
    border: 0; border-bottom: 1px solid transparent;
    cursor: pointer;
    font-family: inherit;
    color: var(--text);
    text-align: left;
}
.fr-section-header:hover { background: rgba(0,0,0,0.06); }
.fr-section-caret {
    display: inline-block;
    width: 14px; text-align: center;
    color: var(--muted); font-size: 10px;
}
.fr-section-icon { font-size: 14px; line-height: 1; }
.fr-section-label {
    font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    flex: 1;
}
.fr-section-count {
    font-size: 11px; font-weight: 600;
    color: var(--muted);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1px 8px;
    min-width: 20px;
    text-align: center;
}
.fr-section-body {
    display: flex; flex-direction: column; gap: 6px;
    padding: 8px;
    border-top: 1px solid var(--border);
}
.fr-section-empty {
    font-size: 11px; color: var(--muted);
    padding: 6px 8px;
    font-style: italic;
}

/* Card layout: vote column (40px) + body (rest). */
.fr-card {
    display: flex; gap: 10px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 12px;
}
.fr-card-vote {
    display: flex; flex-direction: column; align-items: center;
    gap: 1px;
    width: 32px; flex-shrink: 0;
    user-select: none;
}
.fr-vote-btn {
    background: transparent; border: 0;
    color: var(--muted);
    font-size: 14px; line-height: 1;
    padding: 2px 6px;
    border-radius: 4px;
    cursor: pointer;
    font-family: inherit;
}
.fr-vote-btn:hover { background: var(--surface2); color: var(--text); }
.fr-vote-btn.fr-vote-active     { color: var(--green); }
.fr-vote-btn.fr-vote-active-dn  { color: var(--red); }
.fr-vote-count {
    font-size: 12px; font-weight: 700;
    color: var(--text);
    line-height: 1; min-width: 18px; text-align: center;
}

.fr-card-body { flex: 1; min-width: 0; }
.fr-card-title {
    font-size: 13px; font-weight: 700;
    color: var(--text);
    line-height: 1.3;
    word-wrap: break-word;
}
.fr-card-desc {
    font-size: 11px; color: var(--muted);
    line-height: 1.5;
    margin-top: 4px;
    white-space: pre-wrap;
    word-wrap: break-word;
}
.fr-card-meta {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 6px;
    font-size: 10px; color: var(--muted);
    margin-top: 6px;
}
.fr-card-author { font-weight: 600; color: var(--text); }
.fr-card-dot    { opacity: 0.6; }
.fr-card-when   { }

/* Status pill - read-only chip (non-admins). */
.fr-status {
    margin-left: auto;
    font-size: 10px; font-weight: 700;
    padding: 2px 8px;
    border-radius: 10px;
    border: 1px solid currentColor;
    text-transform: uppercase; letter-spacing: 0.5px;
}
.fr-status-requested   { color: var(--muted); }
.fr-status-in_progress { color: var(--accent); }
.fr-status-shipped     { color: var(--green); }
.fr-status-declined    { color: var(--red); }

/* Status select - admin variant. Keeps the same shape as the pill
   but is interactive. We tint the border + text per current value
   via the same fr-status-* classes the JS adds. */
.fr-status-select {
    margin-left: auto;
    font-family: inherit;
    font-size: 10px; font-weight: 700;
    padding: 2px 8px;
    border-radius: 10px;
    background: var(--surface);
    border: 1px solid currentColor;
    text-transform: uppercase; letter-spacing: 0.5px;
    cursor: pointer;
}
.fr-status-select:focus { outline: none; box-shadow: 0 0 0 2px rgba(108,99,255,0.25); }

.fr-card-actions {
    display: flex; gap: 12px;
    margin-top: 6px;
}
.fr-card-link {
    background: transparent; border: 0;
    color: var(--muted);
    font-family: inherit; font-size: 10px; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 0; cursor: pointer;
}
.fr-card-link:hover { color: var(--accent); }
.fr-card-link-danger:hover { color: var(--red); }

/* Editing card - same outer frame as a normal card but stretches
   full width (no vote column, no actions row). */
.fr-card-editing {
    flex-direction: column;
    background: var(--surface2);
    border-color: var(--accent);
}
.fr-card-edit-form {
    display: flex; flex-direction: column; gap: 8px;
    width: 100%;
}

/* ── ADMIN PANEL ──────────────────────────────────────────────
   Visible only to admin accounts (the JS-side check is in
   admin-panel.js; the DB-side guarantee is is_admin() RLS).
   Shows a profiles + activity table plus a feature-request roll-up.
   ────────────────────────────────────────────────────────────── */
.admin-panel-btn {
    /* Spacing comes from .brand-icons's flex gap; no per-button
       margin here. */
    background: none; border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 12px; padding: 2px 7px;
    cursor: pointer; font-family: inherit;
    line-height: 1;
}
.admin-panel-btn:hover { border-color: var(--accent); color: var(--text); }
.admin-panel-btn .sr   { position: absolute; left: -9999px; }

.admin-panel-modal {
    width: 900px; max-width: 95vw; max-height: 88vh;
    overflow-y: auto;
}
.ap-help {
    font-size: 11px; color: var(--muted); line-height: 1.5;
    margin-bottom: 12px;
}
.ap-toolbar {
    display: flex; gap: 10px; align-items: center;
    margin-bottom: 14px;
}
.ap-toolbar-meta {
    font-size: 11px; color: var(--muted);
}
.ap-btn-secondary {
    font-family: inherit; font-size: 12px; font-weight: 600;
    padding: 6px 12px;
    border-radius: 8px; cursor: pointer;
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--text);
}
.ap-btn-secondary:hover { border-color: var(--accent); color: var(--accent); }

.ap-section {
    border-top: 1px solid var(--border);
    padding: 14px 0;
}
.ap-section:first-of-type { border-top: 0; padding-top: 0; }
.ap-section-title {
    font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--text);
    margin-bottom: 10px;
}

/* Status roll-up pills. Same color tokens as the feature-requests
   pills so the two surfaces feel related. */
.ap-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.ap-pill {
    font-size: 11px; padding: 4px 10px;
    border-radius: 14px;
    border: 1px solid currentColor;
    text-transform: uppercase; letter-spacing: 0.5px;
}
.ap-pill b { font-weight: 700; margin-right: 4px; color: var(--text); }
.ap-pill-requested   { color: var(--muted); }
.ap-pill-in_progress { color: var(--accent); }
.ap-pill-shipped     { color: var(--green); }
.ap-pill-declined    { color: var(--red); }

.ap-table-wrap {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: 10px;
}
.ap-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
}
.ap-table th,
.ap-table td {
    padding: 8px 10px;
    text-align: left;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
.ap-table th {
    background: var(--surface2);
    font-weight: 700;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 10px;
}
.ap-table tbody tr:last-child td { border-bottom: 0; }
.ap-table tbody tr:hover { background: var(--surface2); }
.ap-cell-email {
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--text); font-weight: 600;
}
.ap-empty {
    text-align: center;
    color: var(--muted);
    padding: 18px 12px !important;
    font-style: italic;
}

/* "Log in as" button per row - small, accent-tinted, slightly more
   prominent than a regular table cell to telegraph it's an action. */
.ap-cell-action { white-space: nowrap; }
.ap-impersonate-btn {
    font-family: inherit;
    font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 4px 10px;
    border-radius: 6px;
    border: 1px solid var(--accent);
    background: transparent;
    color: var(--accent);
    cursor: pointer;
}
.ap-impersonate-btn:hover {
    background: var(--accent);
    color: #fff;
}
.ap-impersonate-btn:disabled {
    opacity: 0.6; cursor: default;
}
/* Replaces the button on the admin's own row so they don't try to
   impersonate themselves (the Edge Function rejects it anyway). */
.ap-self-tag {
    font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 4px 10px;
    color: var(--muted);
    border: 1px dashed var(--border);
    border-radius: 6px;
}

/* ── IMPERSONATION BANNER ──────────────────────────────────────
   Sticky strip at the very top of <body> (inserted via
   insertAdjacentHTML 'afterbegin' so it's the first child). Fixed
   position so it doesn't displace the rest of the layout - instead
   we add top padding so the header isn't covered. */
.impersonation-banner {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 9500;     /* below the login overlay (10000) but above modals (500) */
    display: flex; align-items: center; gap: 10px;
    padding: 8px 16px;
    background: linear-gradient(90deg, #ffb347 0%, #ff8c42 100%);
    color: #1a1d27;
    font-size: 12px; font-weight: 600;
    box-shadow: 0 2px 8px rgba(0,0,0,0.25);
}
.impersonation-banner .ib-icon { font-size: 16px; line-height: 1; }
.impersonation-banner .ib-text { flex: 1; line-height: 1.4; }
.impersonation-banner .ib-text b { color: #1a1d27; font-weight: 800; }
.impersonation-banner .ib-end-btn {
    font-family: inherit; font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 5px 12px;
    background: rgba(0,0,0,0.18);
    border: 1px solid rgba(0,0,0,0.35);
    border-radius: 6px;
    color: #1a1d27;
    cursor: pointer;
}
.impersonation-banner .ib-end-btn:hover { background: rgba(0,0,0,0.28); }
/* Push the rest of the page down so the fixed banner doesn't overlap
   the header. ~36px matches its padded height. */
body:has(#impersonation-banner) { padding-top: 36px; }

/* ── USERNAME PROMPT ──────────────────────────────────────────
   Shown after sign-in for users who haven't set a username yet.
   Same .modal-overlay frame as the other modals, narrower, with
   a single input + Save / Dismiss buttons. */
.username-prompt-modal {
    width: 420px; max-width: 92vw;
}
.up-help {
    font-size: 13px; color: var(--muted); line-height: 1.55;
    margin-bottom: 14px;
}
.up-help b { color: var(--text); font-weight: 600; }
.username-prompt-modal .modal-input { margin-bottom: 6px; }
.up-row-msg {
    font-size: 11px; min-height: 14px; margin: 4px 0 12px;
    color: var(--red);
}
.up-row-msg[data-kind="ok"]   { color: var(--green); }
.up-row-msg[data-kind="warn"] { color: #ffb347; }
.up-buttons {
    display: flex; gap: 8px; justify-content: flex-end;
}
.up-btn-primary,
.up-btn-cancel {
    font-family: inherit; font-size: 12px; font-weight: 600;
    padding: 7px 14px;
    border-radius: 8px;
    cursor: pointer;
}
.up-btn-primary { background: var(--accent); border: 0; color: #fff; }
.up-btn-primary:hover { filter: brightness(1.1); }
.up-btn-cancel  {
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--muted);
}
.up-btn-cancel:hover { color: var(--text); }

/* ── LOGIN OVERLAY - username + verify-email panel ────────────
   Adds styles for the new sign-up username input (just inherits
   .login-card input rules), and the post-signup verification screen
   with its 30s resend cooldown button.
   ────────────────────────────────────────────────────────────── */
.login-verify-icon {
    text-align: center;
    font-size: 38px;
    line-height: 1;
    margin: 4px 0 12px;
}
.login-verify-msg {
    font-size: 13px;
    color: var(--text);
    line-height: 1.5;
    text-align: center;
    margin: 8px 0;
}
.login-verify-msg b { color: var(--accent); }
.login-verify-tip {
    font-size: 12px;
    color: var(--muted);
    line-height: 1.5;
    text-align: center;
    margin: 14px 0 10px;
}
#login-verify-resend,
#login-reset-send,
#login-recovery-save {
    width: 100%; padding: 10px; margin-top: 4px;
    background: var(--accent); border: 0; border-radius: 8px;
    color: white; font-size: 14px; font-weight: 600; cursor: pointer;
    font-family: inherit;
}
#login-verify-resend:hover:not(:disabled),
#login-reset-send:hover:not(:disabled),
#login-recovery-save:hover:not(:disabled) { filter: brightness(1.1); }
#login-verify-resend:disabled,
#login-reset-send:disabled,
#login-recovery-save:disabled { opacity: 0.6; cursor: default; }

/* Forgot-password row sits under the sign-up toggle. Slightly more
   muted than the toggle so it's clearly secondary. */
.login-forgot-row {
    text-align: center;
    margin-top: 6px;
    font-size: 11px;
}
.login-forgot-row .login-toggle-link { font-size: 11px; }

/* Remember-me row - sits between the password field and the Sign in
   button. The checkbox itself overrides .login-card input's full-width
   styling so it stays a tight square next to its label. */
.login-remember-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 2px 0 10px;
    font-size: 12px;
    color: var(--muted);
    cursor: pointer;
    user-select: none;
}
.login-remember-row input[type="checkbox"] {
    width: auto;
    margin: 0;
    padding: 0;
    accent-color: var(--accent);
    cursor: pointer;
}
.login-remember-row:hover { color: var(--text); }

/* ── RESPONSIVE ───────────────────────────────── */
@media (max-width: 960px) {
    .main-grid { grid-template-columns: 1fr 1fr; }
}

/* Header layout breakpoint. Below ~900px the brand + 7-pill weather +
   clock would start to feel cramped, so before ANY squishing happens
   we drop the weather strip to its own row below the header.
   * Brand and clock never resize or wrap (their CSS forces nowrap and
     flex-shrink: 0 - they just stay put on row 1).
   * Weather strip becomes a full-width centered row below.
   * The hourly-detail popup's width is matched to the visible pills
     by JS in showDayDetail(), so it always exactly mirrors the strip
     even when the strip is centered or partially scrolled. */
@media (max-width: 900px) {
    .header { padding: 10px 14px; }
    .header-top {
        flex-wrap: wrap;
        gap: 10px;
    }

    /* Weather strip jumps to its own row below brand + clock. */
    .hw-strip-wrap {
        flex: 1 1 100%;
        order: 10;
        padding-right: 0;
        justify-content: center;       /* center the strip in its row */
    }
    .hw-strip {
        justify-content: safe center;  /* center pills; fall back to start if they overflow */
        max-width: 100%;
        padding-bottom: 2px;            /* room for the scrollbar if pills overflow */
    }
    .detail-slots { flex-wrap: wrap; }
    .detail-slot  { min-width: 60px; }
}

@media (max-width: 600px) {
    body { padding: 8px; }
    .main-grid { grid-template-columns: 1fr; }
    /* (#clock font-size override removed - clock keeps its full size at every breakpoint.) */
    /* Detail slots wrap to 2-per-row at very narrow widths if the popup
       isn't wide enough for 4 across. */
    .detail-slots { gap: 4px; }
    .detail-slot { flex: 1 1 calc(50% - 4px); min-width: 0; }

    /* iOS Safari auto-zooms whenever a text input gets focused if the
       computed font-size is below 16px. The desktop design uses 11-13px
       inputs throughout (modal-input, task-input, money editors, etc.)
       so iPhone users got a hard zoom-and-pan on every focus, with no
       way back without pinching out manually. Force a 16px floor on
       every native form control at the mobile breakpoint. !important
       is load-bearing here: per-widget CSS files (styles/widgets/*.css)
       load AFTER main.css and many of them set explicit smaller font-
       sizes on input class selectors that would otherwise win on
       specificity AND cascade order. The override is mobile-only so
       the desktop compact look is preserved. Reported by BigBirkinBag,
       fixed in 20260619. */
    input,
    textarea,
    select {
        font-size: 16px !important;
    }
}

/* ── APP UPDATES - "What's new" inbox ──────────────────────────
   🔔 bell button in the header (next to the lightbulb / shield) plus
   the modal users open to see release notes. Visible to everyone
   signed in; admin posts via the admin panel. Per-user read marks
   live in app_update_reads. */
.app-updates-btn {
    /* position: relative is kept so the absolutely-positioned
       .au-badge child anchors to this button. Spacing comes from
       .brand-icons's flex gap; no per-button margin here. */
    position: relative;
    background: none; border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 12px; padding: 2px 7px;
    cursor: pointer; font-family: inherit;
    line-height: 1;
}
.app-updates-btn:hover { border-color: var(--accent); color: var(--text); }
.app-updates-btn .sr   { position: absolute; left: -9999px; }

/* Red unread-count pill in the top-right corner of the bell. Hidden
   when the user has nothing new - `[hidden]` works because we only
   ever toggle it via the .hidden DOM property in setBadge(). */
.au-badge {
    position: absolute;
    top: -6px; right: -6px;
    min-width: 16px; height: 16px;
    padding: 0 4px;
    background: var(--red); color: white;
    border-radius: 999px;
    font-size: 10px; font-weight: 700;
    line-height: 16px; text-align: center;
    box-shadow: 0 0 0 2px var(--surface);
}

.app-updates-modal {
    /* Standardized with the other header-button popups. */
    width: 900px; max-width: 95vw; max-height: 88vh;
    overflow-y: auto;
}
.au-help {
    font-size: 11px; color: var(--muted); line-height: 1.5;
    margin-bottom: 12px;
}
.au-help b { color: var(--text); font-weight: 600; }

.au-toolbar {
    display: flex; gap: 8px; align-items: center;
    margin-bottom: 12px; flex-wrap: wrap;
}
.au-toolbar-meta {
    margin-left: auto;
    font-size: 11px; color: var(--muted);
}
.au-btn-secondary {
    padding: 6px 10px;
    background: var(--surface2);
    border: 1px solid var(--border); border-radius: 6px;
    color: var(--text); font-size: 12px;
    cursor: pointer; font-family: inherit;
}
.au-btn-secondary:hover { border-color: var(--accent); }

/* Card list - each app_updates row renders as a card. Unread cards
   get a left accent border and a small dot in the title row so the
   eye lands on them first. Read cards are dimmer but still readable
   so users can scroll back through the history. */
.au-list { display: flex; flex-direction: column; gap: 10px; }
.au-empty {
    padding: 24px 12px;
    text-align: center; color: var(--muted);
    font-size: 13px;
}
.au-card {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    padding: 12px 14px;
}
.au-card-read {
    opacity: 0.65;
    border-left-color: var(--border);
}
.au-card-header {
    display: flex; align-items: center; gap: 8px;
    margin-bottom: 6px; flex-wrap: wrap;
}
.au-card-title {
    font-size: 14px; font-weight: 600; color: var(--text);
    flex: 1 1 auto;
}
.au-card-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--accent);
    flex: 0 0 auto;
}
.au-card-body {
    font-size: 12px; color: var(--text); line-height: 1.55;
    margin-bottom: 8px; white-space: pre-wrap;
}
.au-card-meta {
    display: flex; align-items: center; gap: 6px;
    font-size: 11px; color: var(--muted); flex-wrap: wrap;
}
.au-card-author b   { color: var(--text); font-weight: 600; }
.au-card-credit b   { color: var(--text); font-weight: 600; }
.au-card-sep        { opacity: 0.6; }
.au-card-spacer     { flex: 1 1 auto; }
.au-card-link {
    background: none; border: 0;
    color: var(--muted); font-size: 11px;
    cursor: pointer; padding: 2px 4px;
    border-bottom: 1px dashed transparent;
    font-family: inherit;
}
.au-card-link:hover { color: var(--accent); border-bottom-color: var(--accent); }
.au-card-link-strong { color: var(--accent); font-weight: 600; }

/* Type chips - matches the look of the feature-request status chips
   so the visual vocabulary stays consistent across modals. */
.au-chip {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em;
    flex: 0 0 auto;
}
.au-chip-new      { background: rgba(108, 99, 255, 0.18); color: var(--accent); }
.au-chip-improved { background: rgba( 67, 217, 162, 0.18); color: var(--green);  }
.au-chip-fixed    { background: rgba(255, 101, 132, 0.18); color: var(--red);    }
/* Pinned chip - the always-visible overview card. Slightly warmer
   than .au-chip-new so the visual hierarchy reads "this is the
   permanent map" at a glance. */
.au-chip-pinned   { background: rgba(255, 196, 0, 0.18); color: #f0b400; }

/* Pinned card. Thicker accent border + amber tint on the left bar
   so it doesn't get lost among regular updates. Override the
   read-state dim so the overview always renders fully readable. */
.au-card-pinned {
    border-left-width: 4px;
    border-left-color: #f0b400;
    background: linear-gradient(
        90deg,
        rgba(255, 196, 0, 0.06) 0%,
        var(--surface2) 30%
    );
}
.au-card-pinned.au-card-read { opacity: 1; border-left-color: #f0b400; }

/* Dismissed card - only visible when the "Show deleted" toolbar
   toggle is on. Dimmer than read, strikethrough title, dashed
   border so it reads as "you removed this". */
.au-card-dismissed {
    opacity: 0.45;
    border-left-style: dashed;
    border-left-color: var(--muted);
}
.au-card-dismissed .au-card-title { text-decoration: line-through; }

/* Per-card action buttons. Delete is muted by default and turns
   red on hover. Restore picks up the green accent. */
.au-card-link-delete:hover {
    color: var(--red);
    border-bottom-color: var(--red);
}
.au-card-link-restore {
    color: var(--green);
}
.au-card-link-restore:hover {
    color: var(--green);
    border-bottom-color: var(--green);
}

/* "Show deleted" toolbar toggle - small checkbox + label tucked
   into the toolbar row. Visually quiet so it doesn't compete with
   the primary buttons. */
.au-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 11px; color: var(--muted);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 6px;
    user-select: none;
}
.au-toggle input { margin: 0; cursor: pointer; }
.au-toggle:hover { color: var(--text); }

/* Leaderboard - aggregated client-side from the entries'
   requested_by fields. Lives in its own 🏆 Leaderboard tab
   (markup under .au-leaderboard-full below); the Updates pane
   does not carry a duplicate copy. Past contributors (pre-reset)
   live in the 🏛 Hall of Fame inside the pinned overview card. */
.au-section-title {
    font-size: 12px; font-weight: 700; color: var(--text);
    text-transform: uppercase; letter-spacing: 0.04em;
    margin-bottom: 4px;
}
.au-leaderboard-help {
    font-size: 11px; color: var(--muted); line-height: 1.4;
    margin-bottom: 10px;
}
.au-leaderboard-list {
    list-style: none;
    display: flex; flex-direction: column; gap: 4px;
    padding: 0; margin: 0;
}
.au-leader-row {
    display: flex; align-items: center; gap: 10px;
    padding: 6px 8px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 12px;
}
.au-leader-medal { font-size: 14px; width: 18px; text-align: center; }
.au-leader-name  { flex: 1 1 auto; color: var(--text); font-weight: 600; }
.au-leader-count { color: var(--muted); font-size: 11px; }

/* ── Tab strip ────────────────────────────────────────────────
   Mirrors the conversations-tab pattern used elsewhere in the
   app. Two flat buttons separated by a bottom border; the
   active one picks up the accent color and an underline. */
.au-tabs {
    display: flex; gap: 4px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 12px;
}
.au-tab {
    background: none; border: 0;
    padding: 8px 14px;
    color: var(--muted); font-size: 13px; font-weight: 600;
    cursor: pointer; font-family: inherit;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.au-tab:hover { color: var(--text); }
.au-tab-active {
    color: var(--accent);
    border-bottom-color: var(--accent);
}
.au-tab-pane[hidden] { display: none; }

/* ── Full-width Leaderboard pane ───────────────────────────── */
.au-leaderboard-full {
    padding: 6px 0;
}
.au-section-title-large {
    font-size: 16px;
    text-transform: none;
    letter-spacing: 0;
    margin-bottom: 8px;
}
.au-leaderboard-list-large {
    gap: 6px;
}
.au-leaderboard-list-large .au-leader-row {
    padding: 10px 14px;
    font-size: 14px;
}
.au-leaderboard-list-large .au-leader-medal {
    font-size: 18px; width: 28px;
}
.au-leaderboard-list-large .au-leader-count {
    font-size: 12px;
}
.au-leaderboard-empty {
    padding: 24px 14px;
    text-align: center; color: var(--muted);
    font-size: 13px;
    background: var(--surface2);
    border: 1px dashed var(--border);
    border-radius: 8px;
    margin-top: 8px;
}

/* ── ADMIN PANEL - "Post a new feature update" form ───────────
   Slides into the admin panel modal. Only admins can see the modal
   in the first place; the DB-side is_admin() RLS is the actual
   guarantee. */
.ap-update-row {
    display: flex; gap: 10px; align-items: stretch;
    margin-bottom: 8px; flex-wrap: wrap;
}
.ap-update-form {
    display: flex; flex-direction: column; gap: 8px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px;
    margin-top: 6px;
}
.ap-update-label {
    display: flex; flex-direction: column; gap: 4px;
    font-size: 11px; color: var(--muted);
    flex: 0 0 auto;
}
.ap-update-label-grow { flex: 1 1 auto; min-width: 200px; }
.ap-row-msg {
    font-size: 11px; color: var(--red); min-height: 14px;
}
.ap-row-msg[data-kind="ok"] { color: var(--green); }
.ap-form-buttons {
    display: flex; gap: 8px; justify-content: flex-end;
}
.ap-btn-primary {
    padding: 8px 14px;
    background: var(--accent); border: 0; border-radius: 6px;
    color: white; font-size: 12px; font-weight: 600;
    cursor: pointer; font-family: inherit;
}
.ap-btn-primary:hover { filter: brightness(1.1); }
.ap-btn-cancel {
    padding: 8px 14px;
    background: transparent; border: 1px solid var(--border);
    border-radius: 6px; color: var(--muted);
    font-size: 12px; cursor: pointer; font-family: inherit;
}
.ap-btn-cancel:hover { color: var(--text); border-color: var(--accent); }

/* ── MOBILE EDIT MODAL (Phase 2.16 mobile slice) ────────────────
   List-style editor that opens when the user taps Edit Layout on
   mobile. Reuses the .modal-overlay / .modal stack (so the open
   animation, backdrop, and Escape handling come for free). */
.mobile-edit-modal {
    width: min(440px, 92vw);
    max-height: 88vh;
    overflow-y: auto;
}
.mobile-edit-help {
    font-size: 12px;
    color: var(--muted);
    line-height: 1.4;
    padding: 0 0 10px 0;
    border-bottom: 1px solid var(--border);
    margin-bottom: 10px;
}
.mobile-edit-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding-bottom: 4px;
}
.mobile-edit-empty {
    color: var(--muted);
    font-size: 12px;
    text-align: center;
    padding: 16px 8px;
}
.mobile-edit-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface2);
}
.mobile-edit-row-arrows {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.mobile-edit-arrow {
    width: 28px; height: 22px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 5px;
    color: var(--text);
    font-size: 13px;
    cursor: pointer;
    font-family: inherit;
    line-height: 1;
    padding: 0;
}
.mobile-edit-arrow:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent);
}
.mobile-edit-arrow:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}
.mobile-edit-icon {
    font-size: 22px;
    flex: 0 0 auto;
    width: 28px;
    text-align: center;
}
.mobile-edit-meta {
    flex: 1 1 auto;
    min-width: 0;
}
.mobile-edit-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mobile-edit-desc {
    font-size: 11px;
    color: var(--muted);
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.mobile-edit-remove {
    width: 28px; height: 28px;
    flex: 0 0 auto;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 50%;
    color: var(--red);
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    font-family: inherit;
    padding: 0;
}
.mobile-edit-remove:hover {
    border-color: var(--red);
    background: color-mix(in oklab, var(--red) 12%, transparent);
}
/* Slice A: eye toggle to hide a widget on mobile while keeping
   it visible on desktop. Sits between the meta column and the
   remove button. */
.mobile-edit-eye {
    width: 28px; height: 28px;
    flex: 0 0 auto;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 50%;
    color: var(--text);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    font-family: inherit;
    padding: 0;
    margin-right: 4px;
}
.mobile-edit-eye:hover {
    border-color: var(--accent);
    background: color-mix(in oklab, var(--accent) 12%, transparent);
}
.mobile-edit-row-hidden {
    opacity: 0.55;
}
.mobile-edit-row-hidden .mobile-edit-title {
    text-decoration: line-through;
}
.mobile-edit-add-row {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
}
.mobile-edit-add-btn {
    display: block;
    width: 100%;
    padding: 10px;
    background: transparent;
    border: 1px dashed var(--accent);
    border-radius: 8px;
    color: var(--accent);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
}
.mobile-edit-add-btn:hover {
    background: color-mix(in oklab, var(--accent) 10%, transparent);
}
.mobile-edit-add-popup {
    margin-top: 8px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    padding: 6px;
    max-height: 240px;
    overflow-y: auto;
}
.mobile-edit-add-empty {
    color: var(--muted);
    font-size: 12px;
    text-align: center;
    padding: 12px 8px;
}
.mobile-edit-add-pop-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.mobile-edit-add-pop-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    color: var(--text);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    width: 100%;
}
.mobile-edit-add-pop-row:hover {
    border-color: var(--accent);
    background: var(--surface2);
}
.mobile-edit-add-pop-icon { font-size: 18px; flex: 0 0 auto; width: 24px; text-align: center; }
.mobile-edit-add-pop-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.mobile-edit-add-pop-title { font-size: 12px; font-weight: 600; }
.mobile-edit-add-pop-desc {
    font-size: 11px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mobile-edit-add-pop-action { font-size: 11px; color: var(--accent); flex: 0 0 auto; }
.mobile-edit-footer {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
}

