/* ============================================================================
   TBB — SHARED DESIGN SYSTEM
   ----------------------------------------------------------------------------
   Every surface loads this file exactly once, from index.html. It is the only
   place tokens are defined.

   WHAT BELONGS HERE
     Anything two or more surfaces use: tokens, reset, top bar, buttons, chips,
     search, tables, the detail panel skeleton, field rows, inputs, toast,
     banner.

   WHAT DOES NOT
     Anything one surface uses (the board's columns, the library's channel
     sidebar) belongs in views/<name>.css.

   WHY THIS FILE EXISTS
     The board and the library were separate pages with separate copies of the
     same CSS. 61 selectors were defined in both; 22 had already drifted apart
     — .detail-title was 19px on one and 18px on the other, .frow's label
     column 112px vs 132px, .mono's font stack had lost "SF Mono" on one side.
     None of that drift was intentional. It was just two copies aging apart.

     Tokens had NOT drifted, which is why this was worth doing now rather than
     later: the palette is still one palette.

   THE RULE
     A value used by more than one surface is defined here and nowhere else.
     If a surface needs a different value, it overrides under its own scope
     class (.v-board, .v-library) in its own stylesheet — never by editing
     this file. That way the override is visible as an override.
   ========================================================================= */


/* ============ TOKENS ========================================================
   Light is the complete set; dark redefines the same names.

   This is the UNION of what the two pages had. The board owned --due-* (the
   due-date proximity ramp) and --shadow-card; the library owned --warn /
   --warn-bg. Neither could render the other's states. Now both can, and so
   can every surface added later.
   ========================================================================= */
:root {
  /* harmon-e brand (2026-10-05) — straight from brand/logo/*.ai. The logo
     is black → navy → plum; neutrals below lean toward that navy rather
     than plain gray, and --accent is the navy lifted enough to read as
     clickable. Style guide: brand/harmon-e-style-guide.html. */
  --brand-black:   #000000;
  --brand-navy:    #00085E;
  --brand-ink:     #0D001B;
  --brand-grad:    linear-gradient(90deg, #000000 0%, #00085E 50%, #0D001B 100%);

  --bg:            #EEEFF6;
  --surface:       #FFFFFF;
  --surface-2:     #F5F6FB;
  --surface-3:     #E9EBF4;
  --border:        #DADDEB;
  --border-strong: #B9BED3;
  --text:          #0D0F24;
  --text-muted:    #4F5470;
  --text-faint:    #7A7F99;

  --accent:        #1F2FC4;
  --accent-hover:  #18259E;
  --accent-fg:     #FFFFFF;
  --accent-tint:   #E7E9FB;
  --focus:         #1F2FC4;

  /* which logo file shows — the dark blocks flip these */
  --logo-on-light: inline-block;
  --logo-on-dark:  none;

  /* MOTION. Three speeds and three curves, used the same way everywhere.
       --dur-1  hover, press, color
       --dur-2  menus, chips, toasts leaving
       --dur-3  dialogs, changing surface, toasts arriving
     Nothing waits on an animation — no data load or click is delayed. */
  --dur-1: 120ms;
  --dur-2: 200ms;
  --dur-3: 320ms;
  --ease-out:    cubic-bezier(.2, .8, .2, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  --ease-spring: cubic-bezier(.34, 1.36, .64, 1);

  /* status — matches the five board columns */
  --s-na:        #C22A30;  --s-na-bg:        #FCEBEC;
  --s-await:     #4F5470;  --s-await-bg:     #ECEDF4;
  --s-queue:     #0E7490;  --s-queue-bg:     #E0F4F9;
  --s-prep:      #6D3FC4;  --s-prep-bg:      #F1EAFD;
  --s-done:      #147038;  --s-done-bg:      #E4F6EA;
  --s-cannot:    #8A5A00;  --s-cannot-bg:    #FBF1DC;

  /* warning — a refused write, a version mismatch, a hidden program */
  --warn:        #A16207;  --warn-bg:        #FEF6E0;

  /* container-type tags — series is red, season is blue.
     DEDICATED TOKENS, not --s-na and --accent, and the reason is collision:
     --s-na IS the NA status pill, which sits in the same table row as the
     tag, so a series tag borrowing it would read as "this row has a
     problem"; --accent is the clickable/action color used everywhere else.
     A tag is a classification, not a state and not an action, so it gets its
     own pair. Deeper red and deeper blue than either, for that reason. */
  --tag-series:  #B21E36;  --tag-series-bg:  #FCE9EC;
  --tag-season:  #1D4FD8;  --tag-season-bg:  #E8EEFE;
  /* There was a --tag-game pair here, for Live's fourth PROGRAM TYPE. The
     tag was removed — every list row already shows its type in its own
     column, so on a Game the tag repeated the cell beside it — and the
     tokens went with it rather than sitting unused. Deleting unreachable
     code, same call as CONFIG.DEMO_MODE. */

  /* FIRST AIR TYPE — how urgent this airing is, at a glance.
       red     Live, Channel App Live — going out as it happens
       purple  Quick Turnaround — not live, but not waiting either
       blue    Stunt (Scheduled), Promo — placed, not urgent
       gray    Not Live

     DEDICATED TOKENS AGAIN, and the same collision argument as the tags
     above, which is not hypothetical here: in the list view an air-type
     chip sits in the column NEXT TO the status pill, and the status pill
     uses --s-na red for the "Airing" lane. Two reds meaning two different
     things, side by side, is exactly what its own pair avoids. */
  --air-live:    #C22A30;  --air-live-bg:    #FCEBEC;
  --air-quick:   #6D3FC4;  --air-quick-bg:   #F1EAFD;
  --air-sched:   #1D4FD8;  --air-sched-bg:   #E8EEFE;
  --air-off:     #4F5470;  --air-off-bg:     #ECEDF4;

  /* due-date proximity ramp, far → overdue */
  --due-far:   #15803D;
  --due-week:  #4D7C0F;
  --due-soon:  #A16207;
  --due-near:  #C2410C;
  --due-today: #DC2626;
  --due-late:  #991B1B;

  --shadow-card: 0 1px 2px rgba(13,15,36,.08), 0 1px 1px rgba(13,15,36,.04);
  --shadow-lift: 0 8px 22px rgba(13,15,36,.14), 0 2px 6px rgba(13,15,36,.08);
  --shadow-pop:  0 20px 50px rgba(13,15,36,.22), 0 4px 12px rgba(13,15,36,.12);
  --radius: 6px;

  /* aliases some views reached for — now real tokens, not silent fallbacks */
  --danger:    var(--s-na);
  --font-sans: var(--font-ui);

  --font-ui:   "Tilt Warp", ui-rounded, "Trebuchet MS", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:            #08091A;
    --surface:       #11132A;
    --surface-2:     #171A35;
    --surface-3:     #1F2342;
    --border:        #272B4C;
    --border-strong: #3A3F66;
    --text:          #E8EAFA;
    --text-muted:    #A0A5C8;
    --text-faint:    #767B9E;

    --accent:        #8C9BFF;
    --accent-hover:  #A9B4FF;
    --accent-fg:     #050824;
    --accent-tint:   #1A1F4A;
    --focus:         #8C9BFF;
    --logo-on-light: none;
    --logo-on-dark:  inline-block;

    --s-na:        #F0666B;  --s-na-bg:        #33191B;
    --s-await:     #A0A5C8;  --s-await-bg:     #20233D;
    --s-queue:     #38BDF8;  --s-queue-bg:     #0C2A38;
    --s-prep:      #B18CFA;  --s-prep-bg:      #241B3A;
    --s-done:      #4ADE80;  --s-done-bg:      #122C1D;
    --s-cannot:    #F2B84B;  --s-cannot-bg:    #2E2410;

    --warn:        #FACC15;  --warn-bg:        #2E2712;

    --tag-series:  #FF8A93;  --tag-series-bg:  #3A171C;
    --tag-season:  #7FB0FF;  --tag-season-bg:  #16243F;

    --air-live:    #F0666B;  --air-live-bg:    #33191B;
    --air-quick:   #B18CFA;  --air-quick-bg:   #241B3A;
    --air-sched:   #7FB0FF;  --air-sched-bg:   #16243F;
    --air-off:     #A0A5C8;  --air-off-bg:     #20233D;

    --due-far:   #4ADE80;
    --due-week:  #A3E635;
    --due-soon:  #FACC15;
    --due-near:  #FB923C;
    --due-today: #F87171;
    --due-late:  #EF4444;

    --shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 1px 1px rgba(0,0,0,.3);
    --shadow-lift: 0 10px 26px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.4);
    --shadow-pop:  0 24px 60px rgba(0,0,0,.7), 0 4px 12px rgba(0,0,0,.5);
  }
}

/* Dark + True Black share every color; True Black then swaps the surfaces below. */
:root[data-theme="black"],
:root[data-theme="dark"] {
  --bg:            #08091A;
  --surface:       #11132A;
  --surface-2:     #171A35;
  --surface-3:     #1F2342;
  --border:        #272B4C;
  --border-strong: #3A3F66;
  --text:          #E8EAFA;
  --text-muted:    #A0A5C8;
  --text-faint:    #767B9E;

  --accent:        #8C9BFF;
  --accent-hover:  #A9B4FF;
  --accent-fg:     #050824;
  --accent-tint:   #1A1F4A;
  --focus:         #8C9BFF;
    --logo-on-light: none;
    --logo-on-dark:  inline-block;

  --s-na:        #F0666B;  --s-na-bg:        #33191B;
  --s-await:     #A0A5C8;  --s-await-bg:     #20233D;
  --s-queue:     #38BDF8;  --s-queue-bg:     #0C2A38;
  --s-prep:      #B18CFA;  --s-prep-bg:      #241B3A;
  --s-done:      #4ADE80;  --s-done-bg:      #122C1D;
  --s-cannot:    #F2B84B;  --s-cannot-bg:    #2E2410;

  --warn:        #FACC15;  --warn-bg:        #2E2712;

  --tag-series:  #FF8A93;  --tag-series-bg:  #3A171C;
  --tag-season:  #7FB0FF;  --tag-season-bg:  #16243F;

  --air-live:    #F0666B;  --air-live-bg:    #33191B;
  --air-quick:   #B18CFA;  --air-quick-bg:   #241B3A;
  --air-sched:   #7FB0FF;  --air-sched-bg:   #16243F;
  --air-off:     #A0A5C8;  --air-off-bg:     #20233D;

  --due-far:   #4ADE80;
  --due-week:  #A3E635;
  --due-soon:  #FACC15;
  --due-near:  #FB923C;
  --due-today: #F87171;
  --due-late:  #EF4444;

  --shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 1px 1px rgba(0,0,0,.3);
    --shadow-lift: 0 10px 26px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.4);
  --shadow-pop:  0 24px 60px rgba(0,0,0,.7), 0 4px 12px rgba(0,0,0,.5);
}

/* TRUE BLACK (2026-10-11g) — Dark's colors on pure #000 surfaces (OLED).
   Cards keep a hairline border instead of a lighter fill, so they still read
   as cards. Picked in ⚙ → Appearance, and (since 2026-10-11m) what System uses
   when the device is in dark mode — see the block at the end of this file. */
:root[data-theme="black"] {
  --bg:            #000000;
  --surface:       #000000;
  --surface-2:     #0B0B0F;
  --surface-3:     #16161D;
  --border:        #1F2030;
  --border-strong: #34364B;
  --accent-tint:   #0E1230;
  --s-await-bg:    #121219;
  --shadow-card: 0 0 0 1px #1F2030;
  --shadow-lift: 0 0 0 1px #34364B, 0 10px 26px rgba(0,0,0,.9);
  --shadow-pop:  0 0 0 1px #34364B, 0 24px 60px rgba(0,0,0,.95);
}


/* ============ BASE ======================================================= */
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: var(--font-ui);
  font-size: 14px; line-height: 1.45; -webkit-font-smoothing: antialiased;
  display: flex; flex-direction: column; overflow: hidden;
}
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }
button { font: inherit; color: inherit; cursor: pointer; }
a { color: var(--accent); }
mark { background: var(--accent-tint); color: var(--accent); border-radius: 2px; padding: 0 1px; }
.grow { flex: 1; }


/* ============ TOP BAR ====================================================
   Owned by the shell, not by any view. A view adds its own controls into
   #viewControls; it never rebuilds the bar.
   ======================================================================== */
.topbar {
  display: flex; align-items: center; gap: 14px; padding: 0 16px;
  height: 56px; flex: 0 0 auto;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; cursor: pointer; }   /* click → Home */
.brand-logo { height: 20px; width: auto; flex: none; display: var(--logo-on-light); }
.brand-logo.on-dark { display: var(--logo-on-dark); }
.brand-icon { height: 22px; width: auto; flex: none; display: none; }
.brand-sub {
  font-size: 11px; color: var(--text-faint); letter-spacing: .08em;
  text-transform: uppercase; border-left: 1px solid var(--border);
  padding-left: 10px; white-space: nowrap;
}
.topbar-spacer { flex: 1; }

/* ============ SETTINGS (2026-10-05a) =====================================
   The ⚙ at the far right of the top bar. Reuses .menu / .menu-pop; only the
   segmented Light / Dark / System control is new. */
.setbtn { padding: 6px 8px; display: inline-flex; align-items: center; color: var(--text-muted); }
.setbtn svg { transition: transform var(--dur-3) var(--ease-out); }
.setmenu[data-open="true"] .setbtn svg { transform: rotate(60deg); }
.setpop { min-width: 268px; padding: 6px 8px 10px; }
.themeseg {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 2px; padding: 2px; margin: 4px 2px 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
}
.themeopt {
  border: none; background: none; color: var(--text-muted); padding: 7px 8px; border-radius: 4px; font-size: 13px;
  transition: background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.themeopt:hover { color: var(--text); }
.themeopt.on { background: var(--accent); color: var(--accent-fg); }
.setnote { font-size: 11.5px; color: var(--text-faint); line-height: 1.4; margin: 9px 4px 0; }
/* 2026-10-15b: Account — who is signed in, and Sign out. */
.setsep { border-top: 1px solid var(--border); margin: 10px -8px 4px; }
.setacct { font-size: 12.5px; color: var(--text-muted); margin: 2px 4px 8px; overflow-wrap: anywhere; }
.setacct:empty { display: none; }
.setsignout { width: 100%; justify-content: center; }

/* ============ WIKI BUTTON (2026-10-11p) ==================================
   The "?" left of the bell, and the phone-only "Wiki" line in the ☰ menu.
   Here, not in views/wiki.css, because both show before the Wiki has loaded. */
.helpbtn { padding: 6px 8px; display: inline-flex; align-items: center; color: var(--text-muted); }
.surfbtn.phone-only { display: none; }
@media (max-width: 760px) { .helpbtn { display: none; } .surfnav .surfbtn.phone-only { display: block; } }

/* ============ NOTIFICATIONS (2026-10-05k) ================================
   The bell left of ⚙. Reuses .menu / .menu-pop. Red count = unread. */
.bellmenu[hidden] { display: none; }
.bellbtn { padding: 6px 8px; display: inline-flex; align-items: center; color: var(--text-muted); position: relative; }
.bellcount {
  position: absolute; top: 1px; right: 0; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: var(--radius-pill, 999px); background: var(--s-na); color: #fff;
  font-size: 10px; line-height: 16px; text-align: center; font-variant-numeric: tabular-nums;
}
.bellcount[hidden] { display: none; }
.bellpop { width: min(380px, calc(100vw - 24px)); padding: 6px 4px 4px; }
.bellhead { padding: 0 6px 4px; display: flex; align-items: center; justify-content: space-between; }
.bellclear { background: none; border: 0; padding: 2px 4px; font: inherit; font-size: 12px; color: var(--accent); cursor: pointer; }
.bellclear:hover { text-decoration: underline; }
.bellclear[hidden] { display: none; }
.bellrow { display: flex; align-items: flex-start; }
.bellrow .bellitem { flex: 1; min-width: 0; }
.bellx { flex: none; background: none; border: 0; padding: 6px 8px; font-size: 16px; line-height: 1; color: var(--text-muted); cursor: pointer; border-radius: 6px; opacity: .55; }
.bellrow:hover .bellx, .bellx:focus-visible { opacity: 1; }
.bellx:hover { background: var(--surface-2); color: var(--text); }
.belllist { max-height: min(60vh, 460px); overflow-y: auto; }
.bellitem {
  display: block; width: 100%; text-align: left; background: none; border: 0; border-radius: 5px;
  padding: 8px 10px 8px 18px; position: relative; color: var(--text); font: inherit; cursor: pointer;
}
.bellitem:hover { background: var(--surface-2); }
.bellitem.unread::before {
  content: ""; position: absolute; left: 6px; top: 13px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
}
.bellitem-title { display: block; font-size: 13px; line-height: 1.3; }
.bellitem-detail { display: block; font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.bellitem-meta { display: block; font-size: 11px; color: var(--text-muted); margin-top: 2px; }
.bellitem.k-overdue .bellitem-detail { color: var(--due-late); }
.bellitem.k-due_soon .bellitem-detail { color: var(--due-soon); }
.bellempty { padding: 14px 10px; font-size: 12.5px; color: var(--text-muted); }

/* Where each view injects its own toolbar. Cleared on every surface change. */
#viewControls { display: flex; align-items: center; gap: 10px; min-width: 0; }


/* ============ SURFACE NAV ================================================
   The tabs that move between Board, Library and whatever comes next. This is
   the thing that did not exist before: navigation used to be
   `location.href = "content-view.html"`, a full page teardown that destroyed
   every cache and forced a second sign-in.
   ======================================================================== */
.surfnav {
  display: flex; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 2px; gap: 2px;
}
.surfbtn {
  border: none; background: none; color: var(--text-muted);
  padding: 5px 13px; border-radius: 4px; font-size: 13px; white-space: nowrap;
  transition: background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.surfbtn:hover { color: var(--text); }
.surfbtn.on { background: var(--accent); color: var(--accent-fg); }

/* The "Boards" nav menu (2026-09-30e) — the shared .menu, opened by hover,
   keyboard focus, or a click (data-open, for touch). Left-aligned under the
   button rather than the toolbar menus' right edge. */
.navmenu .menu-pop { left: 0; right: auto; top: calc(100% + 4px); min-width: 190px; }
/* a bridge over the gap, so moving the mouse down does not close it */
.navmenu .menu-pop::before { content: ""; position: absolute; left: 0; right: 0; top: -6px; height: 6px; }
.navmenu:hover .menu-pop, .navmenu:focus-within .menu-pop { display: block; }
.navmenu .menu-item.on { color: var(--accent); font-weight: 500; }
/* Bigger arrow on the Boards button (Evann, 30f) */
.navmenu .menu-btn::after { font-size: 14px; margin-left: 6px; transform: translateY(0); }
/* A surface the signed-in person has no permission for is not rendered at
   all — see core.js. There is deliberately no disabled state: showing a door
   that will not open is worse than not showing the door. */


/* ============ BUTTONS ==================================================== */
.btn {
  border: 1px solid var(--border-strong); background: var(--surface-2);
  border-radius: var(--radius); padding: 8px 13px; font-size: 13px;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.btn:active:not(:disabled) { transform: scale(.97); }
.btn:hover { background: var(--surface-3); border-color: var(--text-faint); }
.btn-primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 28%, transparent); }
.btn-primary:disabled { opacity: .5; cursor: default; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); padding: 8px 10px; }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-sm { padding: 3px 11px; font-size: 12px; }


/* ============ MENU =======================================================
   A button that drops a short list of actions beneath it. Built here rather
   than in a view because every surface ends up wanting one, and because the
   open/close behavior is fiddly enough that two copies would diverge.

   Deliberately NOT a <select>: these are actions, not a value. A select that
   performs something on change reads as a setting, and there is no good
   resting label for it once the action has run.
   ======================================================================== */
.menu { position: relative; display: inline-flex; }

.menu-btn::after {
  content: "▾";
  font-size: 10px; opacity: .75; margin-left: 7px;
  transform: translateY(-1px);
}
.menu[data-open="true"] .menu-btn::after { opacity: 1; }

.menu-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 65;
  min-width: 248px; padding: 5px;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: 8px; box-shadow: var(--shadow-pop);
  display: none;
}
.menu[data-open="true"] .menu-pop { display: block; animation: pop-in var(--dur-2) var(--ease-out); }
@keyframes pop-in { from { opacity: 0; transform: translateY(-4px) scale(.98); } }

.menu-item {
  display: block; width: 100%; text-align: left;
  background: none; border: none; border-radius: 5px;
  padding: 8px 10px; font-size: 13px; color: var(--text); line-height: 1.35;
}
.menu-item:hover { background: var(--surface-2); }
.menu-item .sub {
  display: block; font-size: 11.5px; color: var(--text-muted); margin-top: 2px;
}
.menu-sep { height: 1px; background: var(--border); margin: 5px 2px; }
.menu-label {
  padding: 6px 10px 3px; font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint);
}


/* ============ CHIPS ====================================================== */
.chip {
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-muted);
  border-radius: 20px; padding: 5px 11px; font-size: 12px; white-space: nowrap;
  transition: background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out);
}
.chip:hover { border-color: var(--text-faint); color: var(--text); }
.chip.on { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.chip .n { font-family: var(--font-mono); opacity: .75; margin-left: 4px; }
.chips { display: flex; gap: 4px; }


/* ============ SEARCH =====================================================
   Width is the one thing that differs per surface (the library searches more
   fields and wants more room), so it is a token the view sets.
   ======================================================================== */
.search-wrap { position: relative; display: flex; align-items: center; }
.search {
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius); padding: 7px 26px 7px 28px; font-size: 12.5px;
  font-family: var(--font-mono); width: var(--search-w, 190px);
}
.search:focus { border-color: var(--accent); }
.search::placeholder { font-family: var(--font-ui); color: var(--text-faint); font-size: 12px; }
.search-ico { position: absolute; left: 9px; color: var(--text-faint); font-size: 12px; pointer-events: none; }
.search-clear {
  position: absolute; right: 5px; background: none; border: none; color: var(--text-faint);
  font-size: 14px; padding: 2px 4px; line-height: 1; display: none;
}
.search-clear.on { display: block; }


/* ============ SELECT FILTER ============================================== */
select.filter {
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius); padding: 7px 9px; font-size: 13px; font-family: inherit;
  max-width: 210px;
}
select.filter:not([value=""]) { border-color: var(--border-strong); }
/* Dashed + dimmed says "this field is not in the API response", which is
   almost always an undeployed Config.gs. See DEPLOY.md. */
select.filter:disabled { opacity: .55; cursor: not-allowed; border-style: dashed; }


/* ============ TABLE ======================================================
   Sorting affordances live here because every surface with a list wants them.
   The board's List view does not sort by header today; it simply does not add
   the handler. Cursor is therefore opt-in via .sortable.
   ======================================================================== */
.listwrap { flex: 1 1 auto; overflow: auto; padding: var(--listwrap-pad, 12px); }
table.list {
  width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
table.list th {
  text-align: left; font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-faint); padding: var(--th-pad, 10px 12px); background: var(--surface-2);
  border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 1;
  white-space: nowrap;
}
table.list th.sortable { cursor: pointer; user-select: none; }
table.list th.sortable:hover { color: var(--text); }
table.list th .dir { opacity: .55; margin-left: 3px; }
table.list td { padding: var(--td-pad, 9px 12px); border-bottom: 1px solid var(--border); font-size: 13px; }
table.list td.preview {
  color: var(--text-muted); font-size: 12.5px; max-width: 400px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
table.list tr:last-child td { border-bottom: none; }
table.list tbody tr { cursor: pointer; }
table.list tbody tr:hover { background: var(--surface-2); }
table.list tbody tr.hidden-row td:not(.keep) { opacity: .45; }


/* ============ COPY AFFORDANCE ============================================
   Clicking these copies rather than opening the row.
   ======================================================================== */
.copyable {
  cursor: copy; border-radius: 3px; padding: 0 3px; margin: 0 -3px;
  transition: background var(--dur-1), color var(--dur-1);
}
.copyable:hover { background: var(--accent-tint); color: var(--accent); }
.copied { background: var(--s-done-bg) !important; color: var(--s-done) !important; }


/* ============ PILLS + TAGS =============================================== */
.pill {
  display: inline-flex; align-items: center; font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; background: var(--cbg); color: var(--cc);
  padding: 2px 8px; border-radius: 3px; white-space: nowrap;
}
.status-pill {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; letter-spacing: .07em;
  text-transform: uppercase; background: var(--cbg); color: var(--cc);
  padding: var(--statuspill-pad, 4px 10px); border-radius: 4px;
}
.tag {
  font-size: 10px; letter-spacing: .05em; text-transform: uppercase; padding: 1px 5px;
  border-radius: 3px; border: 1px solid currentColor;
}
.tag-series { background: var(--tag-series-bg); color: var(--tag-series); }
.tag-season { background: var(--tag-season-bg); color: var(--tag-season); }
.tag-hidden { background: var(--warn-bg);       color: var(--warn); }


/* ============ BREADCRUMB =================================================
   The trail left by drilling down: series › season › episode. Shared rather
   than library-only because any surface that nests records wants the same
   thing, and a second copy is how components drift apart (see the header).

   Each crumb is a BUTTON, not a link — it pops the trail back to that level
   inside the open card and navigates nothing. The last crumb is the record
   you are looking at, so it is not clickable and says so by being plain
   text: an affordance that does nothing is worse than no affordance.
   ======================================================================== */
.crumbs {
  display: flex; align-items: center; flex-wrap: wrap; gap: 2px;
  font-size: 12px; margin-bottom: 10px; color: var(--text-faint);
}
.crumbs .crumb {
  background: none; border: 0; padding: 2px 5px; border-radius: 4px;
  font: inherit; color: var(--accent); cursor: pointer; max-width: 240px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crumbs .crumb:hover { background: var(--accent-tint); }
.crumbs .crumb:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.crumbs .sep { opacity: .6; }
.crumbs .here {
  padding: 2px 5px; color: var(--text-muted); max-width: 240px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* ============ DETAIL PANEL ===============================================
   Skeleton only. Width and internal padding differ legitimately between
   surfaces — a prep card is a glance, a library record is a full metadata
   sheet — so those are tokens each view sets.
   ======================================================================== */
.scrim {
  position: fixed; inset: 0; background: rgba(5,8,36,.5); opacity: 0;
  pointer-events: none; transition: opacity var(--dur-3) var(--ease-out); z-index: 40;
}
.scrim.open { opacity: 1; pointer-events: auto; }
.detail {
  position: fixed; z-index: 50; top: 50%; left: 50%; transform: translate(-50%,-48%) scale(.97);
  width: min(var(--detail-w, 620px), calc(100vw - 28px));
  max-height: min(var(--detail-h, 84vh), var(--detail-max, 780px));
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 10px;
  box-shadow: var(--shadow-pop); display: flex; flex-direction: column;
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-3) var(--ease-spring);
}
.detail.open { opacity: 1; pointer-events: auto; transform: translate(-50%,-50%) scale(1); }
.detail-head {
  flex: 0 0 auto; position: relative; padding: var(--detail-head-pad, 15px 18px 14px);
  border-bottom: 1px solid var(--border); border-top: 4px solid var(--cc);
  border-radius: 6px 6px 0 0;
}
.detail-hc { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
.detail-title {
  font-size: var(--detail-title-size, 19px); line-height: 1.25;
  margin: var(--detail-title-margin, 5px 46px 10px 0);
  letter-spacing: -.01em; text-wrap: balance;
}
.detail-close { position: absolute; top: var(--detail-close-top, 12px); right: var(--detail-close-right, 13px); font-size: 17px; line-height: 1; }
.detail-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.detail-body { flex: 1 1 auto; overflow-y: auto; padding: var(--detail-body-pad, 2px 18px 20px); overscroll-behavior: contain; }
/* While a card is open only the card scrolls, never the page behind (05p). */
html.detail-lock, html.detail-lock body { overflow: hidden; }
.detail-foot {
  flex: 0 0 auto; padding: 8px 16px; border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: 10px; min-height: 38px;
}


/* ============ MODAL ======================================================
   A centered dialog for something that needs a decision before it happens.
   Lives here rather than in a view because the board (complete, push, sync)
   and the library (send to Active) both need one, and a second copy is how
   the two surfaces' components drifted apart the first time.

   Sits above .detail (z-index 50) so a dialog raised FROM a card covers it.
   ======================================================================== */
.modal {
  position: fixed; z-index: 60; top: 50%; left: 50%; transform: translate(-50%,-47%) scale(.97);
  width: min(var(--modal-w, 500px), calc(100vw - 28px)); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 10px; box-shadow: var(--shadow-pop);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-3) var(--ease-spring);
}
.modal.open { opacity: 1; pointer-events: auto; transform: translate(-50%,-50%) scale(1); }
.modal-head { padding: 15px 18px 12px; border-bottom: 1px solid var(--border); }
.modal-eyebrow { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--s-done); }
.modal-title { font-size: 17px; margin-top: 3px; line-height: 1.3; }
.modal-body { padding: 14px 18px; }
.modal-foot { padding: 12px 18px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
/* 2026-10-15a: the sign-in box (core.js showReauth) — above every other dialog,
   because the dialog it interrupts may be the one holding the typed work. */
.modal.reauth { z-index: 70; --modal-w: 400px; }
.reauth-sub { margin: 0 0 14px; line-height: 1.5; color: var(--text-muted); }
.reauth-btn { display: flex; justify-content: center; min-height: 44px; }

/* An inline caution inside a modal. Accent by default — this is "read this
   before you continue", not "something failed". */
.warn {
  display: flex; gap: 8px; padding: 10px 12px; border-radius: var(--radius);
  background: var(--accent-tint); color: var(--accent);
  font-size: 12.5px; line-height: 1.5; margin-bottom: 13px;
}
/* For a caution that really is a caution. */
.warn.caution { background: var(--warn-bg); color: var(--warn); }


/* ============ FIELD ROWS =================================================
   Rows are deliberately tight — a metadata card is scanned, not read.
   ======================================================================== */
.frow {
  display: grid; grid-template-columns: var(--frow-key-w, 112px) 1fr;
  gap: var(--frow-gap, 6px 10px); padding: var(--frow-pad, 4px 0); align-items: baseline;
}
.fkey {
  font-size: 11px; color: var(--text-faint); letter-spacing: .03em;
  text-transform: uppercase; line-height: 1.5;
}
.fval { font-size: 13px; word-break: break-word; line-height: 1.5; }
.fval.novalue { color: var(--text-faint); }
.fval.code { font-family: var(--font-mono); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.fval a { text-decoration: none; border-bottom: 1px solid currentColor; }
.fval a:hover { opacity: .78; }
.fval a::after { content: " ↗"; font-size: 10px; opacity: .7; }
/* An empty field collapses to a hairline rather than holding a full row. */
.frow.blank { padding: 0; }
.frow.blank .fkey, .frow.blank .fval { font-size: 10.5px; line-height: 1.35; opacity: .6; }

.tail { margin-top: 8px; padding-top: 7px; border-top: 1px dashed var(--border); }
.tail .tail-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; min-height: 24px; }
.tail .tail-lbl { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-faint); }
.tail .body { font-size: 13px; line-height: 1.5; white-space: pre-wrap; }
.tail .body.novalue { color: var(--text-faint); font-size: 12px; }
.tail .body.copyable { display: block; padding: 3px 5px; margin: 0 -5px; }

/* More air above a section than there is between its own rows, so the
   heading reads as the start of something rather than one more line. */
.section { margin-top: 18px; }
.section:first-child { margin-top: 4px; }
/* ── Section headings ──
   Shared, so this is one rule for every card in the system: the Content
   Library's record, Live's event, and anything added later.

   WHY THEY GOT LOST, WHICH WAS NOT ONLY SIZE

   This was 11px uppercase --text-faint. `.fkey` — the label on EVERY field
   underneath it — is 11px uppercase --text-faint. Identical on every axis
   that carries weight, so a heading had exactly as much presence as the
   twenty labels it was meant to be organizing, and on a card with eight
   sections and forty fields it simply dissolved into them.

   So it separates on FOUR axes at once, rather than getting a point or two
   bigger and still competing:

     size     11px  →  14px   (the top of a three-step ladder:
                               .fkey 11  →  .fval 13  →  heading 14)
     weight   normal → 600
     color   faint  → text   (the strongest text color, not a gray)
     border   1px    → 2px    (a rule under a heading, not a hairline)

   Letter-spacing comes DOWN from .12em to .07em. Wide tracking makes 11px
   read as a label; at 14px and 600 weight it would just look stretched,
   and the extra width would push the buttons on a section header around.

   The aside and the buttons keep their own sizes — .sec-loading is 11px
   and .btn-sm is 12px, both set explicitly — so only the heading grows.
   ─────────────────────────────────────────────────────────────── */
.section-label {
  font-size: 14px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text);
  padding-bottom: 5px; border-bottom: 2px solid var(--border); margin-bottom: 7px;
  display: flex; align-items: center; gap: 8px; min-height: 28px;
}
/* The heading is now bold and 13px, and everything inside it inherits
   that. These two sit IN the heading but are not part of it, so each
   resets what it borrowed — the same three properties, plus weight. */
.section-label .sec-btn {
  text-transform: none; letter-spacing: 0; font-weight: normal; font-size: 12px;
}
.section.editing {
  background: var(--accent-tint); margin-left: -8px; margin-right: -8px;
  padding: 0 8px 8px; border-radius: 8px;
}
.section.editing .section-label { border-bottom-color: var(--accent); }
.section.pending { opacity: .6; }
.sec-loading {
  font-size: 11px; color: var(--text-faint); text-transform: none;
  letter-spacing: 0; font-weight: normal;
}

.split, .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px; }
.split { align-items: start; }
@media (max-width: 680px) { .split, .grid2 { grid-template-columns: 1fr; } }


/* ============ INPUTS ===================================================== */
.inp {
  width: 100%; background: var(--surface); border: 1px solid var(--border-strong); color: var(--text);
  border-radius: 4px; padding: 4px 7px; font-size: 13px; font-family: inherit;
}
.inp.code { font-family: var(--font-mono); font-size: 12px; }
.inp:focus { border-color: var(--accent); outline: none; }
.inp.dirty { border-color: var(--accent); background: var(--accent-tint); }
textarea.inp { resize: vertical; min-height: 76px; line-height: 1.5; }
.chk { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }
.chk input { width: 15px; height: 15px; accent-color: var(--accent); }


/* ============ NOTICES (inside a card) ==================================== */
.notice {
  display: flex; gap: 9px; padding: 10px 12px; border-radius: var(--radius);
  font-size: 12.5px; line-height: 1.5; margin-top: 14px;
  background: var(--s-na-bg); color: var(--s-na); border: 1px solid currentColor;
}
.notice.info { background: var(--s-done-bg); color: var(--s-done); }
.notice strong { display: block; margin-bottom: 2px; }

/* Footer message — persists until the next save, which is what a failure
   reason needs. It must be readable in full and selectable, since it names
   the tab or column to go and fix. */
.foot-msg {
  font-size: 12.5px; color: var(--text-muted); flex: 1;
  user-select: text; -webkit-user-select: text; line-height: 1.45;
}
.foot-msg.warn { color: var(--warn); white-space: normal; max-height: 5.5em; overflow-y: auto; }
.foot-msg.err  { color: var(--s-na); }
.foot-msg.ok   { color: var(--s-done); }


/* ============ TOAST — transient only =====================================
   Use for "saved", "copied", "reloaded in 240ms". Never for a failure.
   See the banner below, and HANDOFF §5 bug 7d.
   ======================================================================== */
.toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(12px);
  background: var(--text); color: var(--bg); padding: 10px 16px; border-radius: var(--radius);
  font-size: 13px; box-shadow: var(--shadow-pop); z-index: 70; opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-3) var(--ease-spring); max-width: calc(100vw - 32px); text-align: center;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }


/* ============ BANNER — persistent, for anything that must be acted on =====
   A partial success: the board moved, the workbook did not.

   This does NOT auto-dismiss. A timer is fine for "saved"; it is the wrong
   shape for a failure you have to read, understand and act on. This stays
   until dismissed, and its text is selectable so the reason can be pasted
   into the diagnostic.

   Promoted from the board, where it was called .alertbar and only the board
   could show one. Every surface needs this, and every surface writes refused
   operations to audit_log as well — the banner is the fast path, not the
   record. See HANDOFF §5 bug 7d.
   ======================================================================== */
.banner {
  position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
  z-index: 80; width: min(620px, calc(100vw - 32px));
  background: var(--surface); color: var(--text);
  border: 1px solid var(--s-na); border-left: 4px solid var(--s-na);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
  padding: 13px 15px; font-size: 13px; line-height: 1.5;
  display: none;
}
.banner.show { display: block; }
.banner.warn { border-color: var(--warn); border-left-color: var(--warn); }
.banner.warn h4 { color: var(--warn); }
.banner.warn .why { background: var(--warn-bg); }
.banner h4 { margin: 0 0 5px; font-size: 13px; font-weight: 600; color: var(--s-na); padding-right: 78px; }
.banner .why {
  user-select: text; -webkit-user-select: text;
  font-family: var(--font-mono); font-size: 12px;
  background: var(--s-na-bg); border-radius: 4px; padding: 7px 9px; margin: 7px 0 0;
  word-break: break-word; max-height: 30vh; overflow-y: auto;
}
.banner .hint { margin: 8px 0 0; color: var(--text-muted); font-size: 12px; }
.banner .acts { position: absolute; top: 10px; right: 10px; display: flex; gap: 6px; }
.banner button {
  background: var(--surface); color: var(--text-muted); border: 1px solid var(--border);
  border-radius: 5px; font-size: 11px; padding: 3px 8px; cursor: pointer; font-family: inherit;
}
.banner button:hover { color: var(--text); border-color: var(--border-strong); }

/* Version mismatch strip, across the top of the content area. */
.verbanner {
  flex: 0 0 auto; background: var(--warn-bg); color: var(--warn);
  border-bottom: 1px solid currentColor; padding: 9px 16px; font-size: 12.5px; line-height: 1.5;
}
.verbanner b { font-weight: normal; }


/* ============ EMPTY + LOADING ============================================
   `.empty` is a PANEL — a whole-region "there is nothing here" state, centered
   in its container with 60px of vertical padding. It is a block, not a
   modifier, and it must never be combined with another class.

   It was, and it cost an afternoon. A blank FIELD used to render as
   `class="fval empty"`, meaning "this value is absent" — a completely
   different idea that happened to pick the same word. The panel rule won:
   every empty field on every card became `display: grid` with `place-items:
   center` and 120px of vertical padding, so a one-line row measured 134px and
   the dash sat centered in its column instead of beside its label.

   `.frow.blank`, written specifically to collapse those rows to a hairline,
   had therefore never once worked — it set `padding: 0` on the row while this
   rule put 120px back on the child. No error, no warning, in both surfaces.

   The absent-value modifier is now `.novalue`. HANDOFF §5 bug 13.
   ========================================================================= */
.empty, .empty-state {
  flex: 1; display: grid; place-items: center; text-align: center;
  color: var(--text-faint); padding: 60px 20px;
}
.shimmer {
  display: inline-block; height: 10px; width: 55%; border-radius: 3px;
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 50%, var(--surface-3) 75%);
  background-size: 200% 100%; animation: sh 1.4s linear infinite;
}
@keyframes sh { to { background-position: -200% 0; } }
/* The harmon-e swirl is the spinner (2026-10-05). It is the icon file used
   as a MASK, so it takes --accent in both themes and never needs a second
   file for dark mode. Same size and class as the old ring — every caller
   is unchanged. Slightly larger box than the ring because the swirl's
   silhouette is lighter than a solid ring. */
.spin {
  width: 13px; height: 13px; flex: 0 0 auto; display: inline-block;
  background: var(--accent);
  -webkit-mask: url("brand/harmon-e-icon.svg") center / contain no-repeat;
          mask: url("brand/harmon-e-icon.svg") center / contain no-repeat;
  animation: spin 1.1s var(--ease-in-out) infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }


/* ============ SHELL LAYOUT =============================================== */
#viewRoot { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* Views are mounted once and kept in the DOM. Hiding rather than destroying
   is the whole point: it is what makes returning to a surface cost zero
   requests and preserves in-memory caches. */
.view { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.view[hidden] { display: none !important; }
/* Changing surface: a short fade. Opacity ONLY — a transform here would
   make every position:fixed dialog inside the view position against the
   view instead of the window while it ran. */
.view { animation: view-in var(--dur-3) var(--ease-out); }
@keyframes view-in { from { opacity: 0; } }

/* Sign-in gate */
#gate {
  position: fixed; inset: 0; z-index: 90; display: grid; place-items: center;
  background: var(--bg); text-align: center; padding: 24px;
}
.gate-title { margin-bottom: 14px; }
.gate-title img { height: 34px; width: auto; display: var(--logo-on-light); margin: 0 auto; }
.gate-title img.on-dark { display: var(--logo-on-dark); }
#gate > div { animation: gate-in var(--dur-3) var(--ease-out) both; }
@keyframes gate-in { from { opacity: 0; transform: translateY(8px); } }
.gate-sub { font-size: 13px; color: var(--text-muted); margin-bottom: 18px; }
.gate-msg { font-size: 12.5px; color: var(--s-na); margin-top: 14px; }

/* First paint of a lazily-loaded view. */
.view-loading { flex: 1; display: grid; place-items: center; color: var(--text-faint); font-size: 13px; }



/* ============ MOTION KIT (2026-10-19) — core.js → motion ================
   Skeletons: the page's own layout in grey while it loads, so nothing jumps
   when the data lands. Same shimmer as .shimmer. Flash: a change that came
   from someone else (realtime) glows the accent once. */
.sk-wrap { flex: 1; padding: 16px; overflow: hidden; }
.sk {
  display: block; border-radius: 4px;
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 50%, var(--surface-3) 75%);
  background-size: 200% 100%; animation: sh 1.4s linear infinite;
}
.sk-line { height: 10px; margin: 0 0 10px; }
.sk-cols { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.sk-col { display: flex; flex-direction: column; gap: 10px; }
.sk-card, .sk-tile {
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 12px 4px;
}
.sk-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.sk-tile { min-height: 120px; }
.sk-rows { display: flex; flex-direction: column; }
.sk-row { display: flex; align-items: center; gap: 14px; padding: 12px 4px; border-bottom: 1px solid var(--border); }
.sk-row .sk-line { margin: 0; }
.sk-row .sk-line:last-child { margin-left: auto; }
.sk-dot { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; }
.sk-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.sk-cell { height: 54px; border-radius: 6px; }
@media (max-width: 760px) {
  .sk-cols { grid-template-columns: 1fr; }
  .sk-col:nth-child(n+2) { display: none; }
}
.mo-flash { animation: mo-flash 1.2s var(--ease-out); }
@keyframes mo-flash {
  0%, 25% { box-shadow: 0 0 0 2px var(--accent), var(--shadow-lift); background-color: var(--accent-tint); }
}


/* ============ POLISH (2026-10-19, batch 2) ===============================
   Empty states carry the swirl; tabs slide; live things pulse; focus is one
   ring everywhere; the top bar has a navy hairline and lifts on scroll.
   (A frosted/blurred bar was considered and dropped: content never slides
   UNDER the bar here — every page scrolls inside #viewRoot — so a blur would
   have nothing to blur.) */
.empty, .empty-state { align-content: center; }
.empty::before, .empty-state::before {
  content: ""; display: block; width: 34px; height: 34px; margin: 0 auto 14px;
  background: var(--accent); opacity: .22;
  -webkit-mask: url("brand/harmon-e-icon.svg") center / contain no-repeat;
          mask: url("brand/harmon-e-icon.svg") center / contain no-repeat;
}
.empty-title { font-size: 17px; color: var(--text-muted); margin-bottom: 6px; }
.empty-sub { font-size: 12.5px; max-width: 380px; line-height: 1.6; margin: 0 auto 14px; }

.seg, .views { position: relative; }
.seg-ghost {
  position: absolute; z-index: 0; border-radius: 4px; background: var(--accent);
  pointer-events: none; transform-origin: left top;
}
.seg-btn, .viewbtn { position: relative; z-index: 1; }
.seg-sliding .seg-btn.on, .seg-sliding .viewbtn.on, .seg-sliding .seg-btn[aria-selected="true"] { background: transparent; }

/* One focus ring for keyboard users, everywhere — buttons, tabs, cards, chips. */
:where(button, a, [tabindex], .card, .chip, .seg-btn, .menu-item, select):focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px; border-radius: inherit;
}
:where(input, textarea).inp:focus-visible, .search:focus-visible { outline: none; }

/* Live now: a pulsing dot. Live board → the Airing lane head while it has
   cards; Home → each row under Currently Live. */
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--s-na) 55%, transparent); }
  70%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.live-dot, .v-live .lane[data-lane="Airing"]:has(.livecard) .lane-name::before, #liveNowBody .lv-pr::before {
  content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--s-na); margin-right: 7px; vertical-align: 1px;
  animation: live-pulse 1.8s var(--ease-out) infinite;
}

/* The top bar: a soft shadow once the page scrolls. (A brand line under it
   was tried 2026-10-19 and removed — Evann: looks terrible.) */
/* position without z-index: paints above the page's ordinary content (so the
   shadow shows) without making a stacking context that would trap the bell
   and nav dropdowns under dialogs. */
.topbar { position: relative; transition: box-shadow var(--dur-2) var(--ease-out); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .topbar.scrolled { box-shadow: none; }
}
.topbar.scrolled { box-shadow: var(--shadow-card); }
:root[data-theme="black"] .topbar.scrolled { box-shadow: none; }

/* One-time "Turn on notifications?" (2026-10-19, SQL 077) — core.js pushAskOnce. */
.pa-modal { --modal-w: 460px; }
.pa-body { font-size: 14px; line-height: 1.55; }
.pa-body p { margin: 0 0 12px; }
.pa-howto { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 14px; margin-bottom: 12px; font-size: 13px; }
.pa-howto-title { font-weight: 600; margin-bottom: 6px; }
.pa-howto-sub { margin: 0 0 4px; color: var(--text-muted); }
.pa-os { margin-top: 6px; color: var(--text-muted); }
.pa-howto ol { margin: 2px 0 0; padding-left: 20px; }
.pa-howto li { margin: 2px 0; }
.pa-note { color: var(--text-muted); font-size: 12.5px; }

/* ============ MOTION + NARROW ============================================ */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
@media (max-width: 820px) {
  .brand-sub { display: none; }
  .topbar { gap: 9px; padding: 0 11px; }
}


/* ============ ANNOUNCEMENTS (shared, 2026-09-25d) ==========================
   Used by Home's dropdown AND the Announcements page — moved here from
   home.css so the two draw one component rather than two copies (§2a).
   ========================================================================= */
.an-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }

/* TITLES ONLY until clicked. Bodies run long and this is the landing page,
   so a panel that printed four of them in full would push everything under
   it off the screen.

   Rows are separated by a rule rather than boxed. Collapsed, this panel is a
   list of headlines — a card each would make four short titles look like a
   wall, and the accent rail from the old expanded design fought the rule. */
.an-item + .an-item { border-top: 1px solid var(--border); }

/* The head is a real <button> when there is a body to show, and a plain div
   when there is not — a notice with no body must not advertise an expansion
   that will never happen. Both share this layout so the titles line up
   whichever they are. */
.an-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  width: 100%;
  padding: 9px 2px;
  text-align: left;
  font: inherit;
  color: inherit;
}
.an-toggle {
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: var(--radius);
}
.an-toggle:hover { background: var(--surface-2); }
.an-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.an-static { cursor: default; }

/* Fixed width whether or not a caret is drawn, so a body-less notice's title
   still lines up with the ones above and below it. */
.an-caret {
  flex: 0 0 auto;
  width: 11px;
  font-size: 10px;
  color: var(--text-faint);
  line-height: 1.5;
}

.an-title { font-size: 13.5px; line-height: 1.35; }
.an-item.open .an-title { color: var(--accent); }
.an-when { margin-left: auto; font-size: 10.5px; color: var(--text-faint); white-space: nowrap; }

/* ---- the reader ----------------------------------------------------------
   The body opens in the SHARED `.modal` from ui.css rather than in a
   Home-only dialog — the board and the library both use it, and a third copy
   of a dialog shell is how components drift apart (§2a).

   ui.css describes .modal as "a decision before it happens". This is a read,
   not a decision, so it is given no action buttons beyond Close and nothing
   in it can change anything. Widened because it holds prose, where the
   500px default is built for a question. */
.an-modal { --modal-w: 620px; }

.an-modal-eyebrow {
  /* The default eyebrow is green — it belongs to the board's completion
     modal, where green means done. Here it is just attribution. */
  color: var(--text-faint);
  text-transform: none;
  letter-spacing: .02em;
  font-size: 11.5px;
}

/* The one piece of real prose in the system, so it gets a reading measure and
   room to breathe. pre-wrap keeps the line breaks someone typed into the
   spreadsheet cell; max-height so a very long notice scrolls inside the
   dialog instead of growing it past the window. */
.an-modal-body {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text);
  max-width: 68ch;
  white-space: pre-wrap;
  max-height: min(58vh, 460px);
  overflow-y: auto;
}

/* The New badge. Present until THIS person opens the notice — per person,
   stored in the announcement_reads sheet, so it follows them across devices.
   Accent-filled rather than a dot: it has to survive being skimmed. */
.an-new {
  flex: 0 0 auto;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  line-height: 1;
  padding: 3px 6px;
  border-radius: 20px;
  background: var(--accent);
  color: var(--accent-fg);
  align-self: center;
}
/* On a selected chip the chip is already accent — invert, or it vanishes. */
.chip.on .an-new { background: var(--accent-fg); color: var(--accent); }
.an-tab-new { margin-left: 6px; }


/* ============ ACTIVITY SPINNER (2026-09-25d) ===============================
   In the top bar, shown by core.js whenever any request is in flight. */
.netspin {
  width: 16px; height: 16px; flex: none;
  background: var(--accent);
  -webkit-mask: url("brand/harmon-e-icon.svg") center / contain no-repeat;
          mask: url("brand/harmon-e-icon.svg") center / contain no-repeat;
  animation: netspin 1.1s var(--ease-in-out) infinite;
}
/* ⚠ Required: `display` would otherwise beat [hidden] — §12. */
.netspin[hidden] { display: none; }
@keyframes netspin { to { transform: rotate(360deg); } }
/* Reduced motion keeps the signal and slows it rather than removing it —
   a static ring would not say "working". */
@media (prefers-reduced-motion: reduce) { .netspin { animation-duration: 2.4s; } }


/* ============ KANBAN ======================================================
   Moved from views/board.css (2026-09-30e) because the Operations and
   Scheduling task boards (views/tasks.js) draw the same columns and cards.
   Board-only pieces (NA rail, Cannot Complete, calendar, badge colors) stay
   in board.css.
   ======================================================================== */
/* ============ STAGE ====================================================== */
.stage { flex: 1 1 auto; min-height: 0; display: flex; }


/* ============ METRICS + CONTROLS ========================================= */
.metrics-bar {
  display: flex; align-items: stretch; flex: 0 0 auto;
  background: var(--surface); border-bottom: 1px solid var(--border); min-height: 56px;
}
.metrics { display: flex; align-items: stretch; flex: 0 0 auto; }
.metric {
  padding: 9px 16px; border-right: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 1px; white-space: nowrap;
}
.metric-label { font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--text-faint); }
.metric-value { font-size: 20px; letter-spacing: -.01em; }
.metric-value small { font-size: 12px; color: var(--text-muted); margin-left: 4px; }

.controls {
  display: flex; align-items: center; gap: 12px; padding: 0 16px;
  flex: 1 1 auto; min-width: 0; overflow-x: auto;
}

/* ============ PANELS (shared — was home.css) ===================================================== */

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  min-height: 44px;
}
.panel-title {
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
  font-weight: normal;
}
.panel-body { padding: var(--panel-pad); }

/* The Quick Links tab strip sits on the right of its own heading rather than
   in a bar of its own — it switches what ONE panel shows, so putting it in a
   separate bar would imply it governs the page. Same reasoning that keeps the
   board's Board/List/Calendar buttons out of the surface tabs (§3). */
.chips { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }


/* ============ UI STANDARD (2026-10-11g) — see UI-STANDARD.md =============
   ONE tab style, ONE toolbar shape, on every page.

   TABS = SEGMENTED.  <div class="seg" role="tablist"><button class="seg-btn on" role="tab">…
     A bordered strip on --surface-2; the chosen tab is filled --accent. A count
     inside a tab is <b> (or .n). Old class names (.views/.viewbtn …) stay on the
     elements because the JS finds them by those names — they no longer style.
   PAGE LAYOUT
     .pagetabs   row 1 — the page's tabs (only when the page has tabs)
     .pagebar    row 2 — filters + search LEFT · .grow · ghost buttons · the
                 ONE main action (.btn-primary) LAST, far right
     .page-title an h1 when a page needs a title (20px)
   BUTTONS  .btn · .btn-primary (one per bar) · .btn-ghost · .btn-sm · .btn-danger
   ======================================================================== */
.seg, .views {
  display: inline-flex; flex-wrap: wrap; align-items: stretch; gap: 2px; padding: 2px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
  max-width: 100%;
}
.seg-btn, .viewbtn {
  border: none; background: none; color: var(--text-muted); font: inherit; font-size: 13px; line-height: 1.3;
  padding: 5px 12px; border-radius: 4px; white-space: nowrap; cursor: pointer;
  transition: background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.seg-btn:hover, .viewbtn:hover { color: var(--text); background: var(--surface-3); }
.seg-btn.on, .viewbtn.on, .seg-btn[aria-selected="true"] { background: var(--accent); color: var(--accent-fg); }
.seg-btn:disabled, .seg-btn[disabled] { opacity: .45; cursor: not-allowed; background: none; }
.seg-btn b, .seg-btn .n { font-family: var(--font-mono); font-weight: 500; font-size: 11.5px; margin-left: 5px; opacity: .8; }
.seg-btn.on b, .seg-btn.on .n { opacity: 1; }
.seg-btn.warn:not(.on) { color: var(--s-cannot); }
.seg.fill { display: flex; }                       /* stretch across, equal tabs */
.seg.fill .seg-btn { flex: 1 1 0; }

.pagetabs { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 10px; }
.pagebar  { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; min-width: 0; }
.pagebar > .grow { flex: 1 1 auto; }
.page-title { font-size: 20px; font-weight: 500; letter-spacing: -.01em; margin: 0 0 12px; line-height: 1.25; }

.btn-danger { color: var(--s-na); border-color: color-mix(in srgb, var(--s-na) 45%, var(--border)); background: var(--surface); }
.btn-danger:hover { background: var(--s-na-bg); border-color: var(--s-na); color: var(--s-na); }
.btn:disabled { opacity: .5; cursor: default; }


/* ============ BOARD ====================================================== */
.board {
  flex: 1 1 auto; display: flex; gap: 10px; padding: 12px;
  overflow-x: auto; overflow-y: hidden; align-items: stretch; min-height: 0;
}
.col {
  flex: 1 1 0; min-width: 210px; display: flex; flex-direction: column; min-height: 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
}


.col-head {
  flex: 0 0 auto; padding: 9px 11px; border-bottom: 1px solid var(--border);
  border-top: 3px solid var(--cc); border-radius: 6px 6px 0 0;
  display: flex; align-items: center; gap: 7px;
}
.col-title { font-size: 13px; letter-spacing: .05em; text-transform: uppercase; color: var(--cc); }
.col-count {
  margin-left: auto; font-size: 12px; font-family: var(--font-mono);
  background: var(--surface-3); color: var(--text-muted); border-radius: 20px; padding: 0 8px;
}
.col-hide { background: none; border: none; color: var(--text-faint); font-size: 15px; line-height: 1; padding: 0 2px; }
.col-hide:hover { color: var(--text); }
.col-body { flex: 1 1 auto; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 8px; }
.col-body.dragover { background: var(--accent-tint); box-shadow: inset 0 0 0 2px var(--accent); border-radius: 0 0 7px 7px; }
/* A column that refuses the drop says so in red before the drop happens. */
.col-body.nodrop.dragover { background: var(--s-na-bg); box-shadow: inset 0 0 0 2px var(--s-na); }
.col-empty {
  text-align: center; color: var(--text-faint); font-size: 12px; padding: 16px 8px;
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
}


/* ============ CARD ======================================================= */
.card {
  background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--cc);
  border-radius: var(--radius); padding: 9px 10px; box-shadow: var(--shadow-card); cursor: pointer;
  display: flex; flex-direction: column; gap: 6px;
  transition: border-color var(--dur-1), transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.card:hover { border-color: var(--border-strong); border-left-color: var(--cc); transform: translateY(-1px); box-shadow: var(--shadow-lift); }
.card:active { transform: scale(.995); }
.card.dragging { opacity: .35; }
.card.locked { cursor: default; }
.card-top { display: flex; align-items: center; gap: 6px; }
.card-hc { font-family: var(--font-mono); font-size: 11px; font-weight: 500; color: var(--text-muted); }
.card-badges { margin-left: auto; display: flex; gap: 4px; }
.badge { font-size: 10px; letter-spacing: .05em; text-transform: uppercase; padding: 1px 5px; border-radius: 3px; }

.card-title { font-size: 13.5px; line-height: 1.32; }
.card-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-muted); }
.card-meta .net { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--text-faint); }
.card-meta .tk { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); margin-left: auto; }
.card-due-row { display: flex; align-items: center; }

.avatar {
  width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto; display: grid; place-items: center;
  font-size: 9.5px; background: var(--surface-3); color: var(--text-muted);
  border: 1px solid var(--border); font-family: var(--font-mono);
}

/* Due date. --dc is set per card from the --due-* ramp, so the dot, the date
   and the phrase all shift together green → red as the date approaches. */
.due { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dc); white-space: nowrap; }
.due .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dc); flex: 0 0 auto; }
.due .date { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.due .rel { opacity: .82; }
.due .tm { color: var(--text-faint); margin-left: auto; padding-left: 8px; font-family: var(--font-mono); }



@media (max-width: 1180px) {
  .metrics-bar { flex-direction: column; }
  .controls { border-top: 1px solid var(--border); padding: 9px 16px; }
}
@media (max-width: 820px) {
  .col { flex: 0 0 78vw; }
}

/* Person filter + calendar — moved from board.css (2026-09-30f): the task boards use both. */
.avfilter { display: flex; align-items: center; gap: 5px; }
.avfilter-label { font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--text-faint); margin-right: 2px; }
.avbtn {
  width: 29px; height: 29px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-muted);
  font-family: var(--font-mono); font-size: 10.5px;
  display: grid; place-items: center; padding: 0; transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1);
}
.avbtn:hover { border-color: var(--text-faint); color: var(--text); }
.avbtn.on { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.avbtn.dim { opacity: .38; }



/* ============ CALENDAR =================================================== */
.calwrap { flex: 1 1 auto; display: flex; min-height: 0; }
.cal { flex: 1 1 auto; display: flex; flex-direction: column; padding: 12px; min-width: 0; }
.cal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.cal-month { font-size: 17px; }
.cal-grid {
  flex: 1 1 auto; display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: 1fr;
  gap: 1px; background: var(--border); border: 1px solid var(--border);
  border-radius: 8px; overflow: hidden; min-height: 0;
}
.cal-dow {
  background: var(--surface-2); padding: 7px 8px; font-size: 11px; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-faint); text-align: center;
}
.cal-day { background: var(--surface); padding: 5px 6px; display: flex; flex-direction: column; gap: 3px; overflow: hidden; min-height: 0; }
.cal-day.out { background: var(--surface-2); }
.cal-day.today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-daynum { font-family: var(--font-mono); font-size: 11px; color: var(--text-faint); }
.cal-day.today .cal-daynum { color: var(--accent); }
.cal-chip {
  font-size: 10.5px; line-height: 1.25; padding: 2px 5px; border-radius: 3px; cursor: pointer;
  background: var(--cbg); color: var(--cc); border-left: 2px solid var(--cc);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-chip:hover { filter: brightness(1.08); }
.cal-more { font-size: 10px; color: var(--text-faint); padding-left: 2px; }
.cal-side { flex: 0 0 250px; border-left: 1px solid var(--border); background: var(--surface); padding: 12px; overflow-y: auto; }
.cal-side h3 {
  font-size: 12px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-faint); margin: 0 0 9px; font-weight: 400;
}
.cal-side .card { margin-bottom: 8px; }


@media (max-width: 820px) { .avfilter-label { display: none; } .cal-side { display: none; } }


/* ============ PHONE (02m) ================================================
   Evann: "more friendly for mobile" — look and tick, ☰ menu.
   The top bar was ~880px wide, which made a phone lay the WHOLE page out at
   that width and zoom out. On a phone it is now: ☰ · TBB · spinner, then the
   view's own buttons on a second, sideways-scrolling row. The surface list
   opens as a panel from ☰, with Boards/Tools items listed in place.
   ========================================================================= */
.navtoggle { display: none; }
@media (max-width: 760px) {
  .topbar { flex-wrap: wrap; height: auto; min-height: 52px; padding: 6px 12px; row-gap: 6px; position: relative; }
  .navtoggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; font-size: 20px; line-height: 1;
    background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius);
  }
  .brand-logo { height: 16px; }
  #userChip { display: none; }
  #viewActions {
    order: 10; flex: 1 0 100%; display: flex; align-items: center; gap: 8px;
    overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 2px;
  }
  #viewActions:empty { display: none; }
  #viewActions > * { flex: none; }

  .surfnav {
    display: none; position: absolute; z-index: 75; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0; padding: 6px; border-radius: 0 0 var(--radius) var(--radius);
    background: var(--surface); box-shadow: var(--shadow-pop); max-height: calc(100vh - 70px); overflow-y: auto;
  }
  body.nav-open .surfnav { display: flex; }
  .surfnav .surfbtn { text-align: left; padding: 12px 14px; font-size: 15px; }
  .surfnav .navmenu { display: block; }
  .surfnav .navmenu .menu-btn { width: 100%; pointer-events: none; color: var(--text-faint);
    font-size: 11px; letter-spacing: .1em; text-transform: uppercase; padding: 12px 14px 4px; background: none; }
  .surfnav .navmenu .menu-btn::after { display: none; }
  .surfnav .navmenu .menu-pop { display: block; position: static; box-shadow: none; border: 0; min-width: 0; padding: 0; background: none; }
  .surfnav .navmenu .menu-pop::before { display: none; }
  .surfnav .navmenu .menu-item { padding: 12px 14px 12px 24px; font-size: 15px; }

  /* Cards and dialogs fill the phone instead of floating at desktop size. */
  .detail, .modal {
    width: 100vw; max-width: 100vw; left: 0; top: 0; transform: none;
    height: 100%; max-height: 100%; border-radius: 0; border-left: 0; border-right: 0;
  }
  .detail.open, .modal.open { transform: none; }
  .modal { height: auto; max-height: 100%; overflow-y: auto; }
}


/* ============ OVERFLOW → "More ▾" (2026-10-11k) ===========================
   core.js → OVERFLOW. A managed bar stays on one line (.ov-on); what does
   not fit is hidden (.ov-hide) and listed in a "More ▾" menu at the row's
   end. .ov-wrap = even with every button collapsed it is too wide (the
   filters alone don't fit), so nothing is hidden and the bar wraps instead.
   ======================================================================== */
.ov-hide { display: none !important; }
.ov-on { flex-wrap: nowrap !important; }
.ov-on.ov-wrap { flex-wrap: wrap !important; }
.ov-on > button, .ov-on > .menu, .ov-on > a.btn, .ov-on > .seg, .ov-on > .views { flex-shrink: 0; white-space: nowrap; }
.controls.ov-on:not(.ov-wrap) { overflow: visible; }   /* so the More menu is not clipped */
.ov-more { flex: none; }
.ov-more .menu-pop { max-height: 70vh; overflow-y: auto; }
.ov-more .menu-item.on { color: var(--accent); font-weight: 500; }
.ov-more .menu-item:disabled { opacity: .5; cursor: default; }
.seg .ov-more .menu-btn::after { margin-left: 5px; }
/* The top bar: #viewActions becomes a one-line row so its More sits beside
   the buttons, and the logo never gets squeezed instead of collapsing. */
#viewActions.ov-on { display: flex; align-items: center; gap: 8px; }
#viewActions.ov-on > * { flex: none; }
.topbar.ov-on > .brand, .topbar.ov-on > .surfnav { flex-shrink: 0; }


/* ============ MOBILE — viewing first (2026-10-11l) ========================
   Evann: "the goal for mobile is more viewing, not editing/adding."
   Everything here is ≤760px only; desktop is untouched. core.js → MOBILE
   does the parts CSS can't (the tab bar, the Filters button, today's column).

   1. Bottom tab bar (#mtabs): the 4 most-viewed pages + More (the full list
      as a sheet). Replaces the ☰ button.
   2. Create / edit buttons are hidden (.m-edit, #viewActions, the Grids bin).
      Cards still open; read them, edit on a computer.
   3. Filters fold behind one "Filters" button per bar (search stays).
   4. Boards and Live lanes swipe one column at a time.
   5. Wide tables / grids scroll inside themselves, never the whole page.
   6. Inputs are 16px so iOS doesn't zoom in on tap.
   ======================================================================== */
#mtabs { display: none; }
@media (max-width: 760px) {
  html, body { height: 100dvh; }
  body { overscroll-behavior-y: none; }
  .topbar { padding-top: max(6px, env(safe-area-inset-top)); }
  .navtoggle { display: none !important; }
  #viewActions { display: none !important; }
  .m-edit { display: none !important; }
  input, select, textarea { font-size: 16px !important; }
  #viewRoot { overflow-x: hidden; }

  /* 1. tab bar */
  #mtabs {
    display: flex; order: 99; flex: none; align-items: stretch;
    background: var(--surface); border-top: 1px solid var(--border);
    padding: 0 4px env(safe-area-inset-bottom); position: relative; z-index: 76;
  }
  .mtab {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; height: 56px; border: 0; background: none; color: var(--text-muted);
    font: inherit; font-size: 10.5px; letter-spacing: .02em; cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .mtab svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
  .mtab span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mtab.on { color: var(--accent); }
  .mtab.on svg { stroke-width: 2.2; }
  body.nav-open .mtab[data-nav-toggle] { color: var(--accent); }

  /* the full list opens as a sheet above the tab bar */
  .surfnav {
    position: fixed !important; top: auto !important; left: 0; right: 0;
    bottom: calc(56px + env(safe-area-inset-bottom)) !important;
    max-height: 72dvh !important; border-radius: 14px 14px 0 0 !important;
    border: 1px solid var(--border); border-bottom: 0;
  }
  body.nav-open::after {
    content: ""; position: fixed; inset: 0; z-index: 74; background: rgb(0 0 0 / .35);
  }

  /* 3. filters fold away */
  .m-fcollapsed > .m-filt { display: none !important; }
  .m-filtbtn { flex: none; display: inline-flex; align-items: center; gap: 6px; }
  .m-filtbtn b { font-family: var(--font-mono); font-weight: 500; font-size: 11px; background: var(--accent); color: var(--accent-fg);
    border-radius: 999px; padding: 0 6px; line-height: 17px; }
  .m-fbar { flex-wrap: wrap !important; row-gap: 8px; }
  .m-fbar > .search-wrap { flex: 1 1 140px; min-width: 0; }
  .m-fbar > .search-wrap .search { width: 100%; }
  .m-fbar:not(.m-fcollapsed) > .m-filt { flex: 1 1 100%; max-width: 100%; }
  .m-fbar .daterange { flex-wrap: wrap; border-radius: var(--radius); }

  /* tab strips scroll sideways like a native segmented row */
  .seg, .views { flex-wrap: nowrap !important; overflow-x: auto; max-width: 100%; scrollbar-width: none; }
  .seg::-webkit-scrollbar, .views::-webkit-scrollbar { display: none; }
  .seg > *, .views > * { flex: none; }
  .pagetabs { flex-wrap: nowrap; min-width: 0; }

  /* chips (quick filters) one swipeable row when shown */
  .chips { flex-wrap: nowrap !important; overflow-x: auto; scrollbar-width: none; }

  /* metrics strip: compact, one row */
  .metrics { overflow-x: auto; scrollbar-width: none; }
  .metric { padding: 7px 12px; }
  .metric-value { font-size: 17px; }

  /* 4. boards swipe a column at a time */
  .board, .v-live .lanes {
    display: flex !important; overflow-x: auto; scroll-snap-type: x mandatory;
    gap: 10px; padding: 10px 12px; scroll-padding: 0 12px;
  }
  .board > .col, .v-live .lanes > .lane {
    flex: 0 0 86vw !important; min-width: 0 !important; scroll-snap-align: start;
  }
  .v-live .lanes { height: 100%; align-items: stretch; }

  /* Library: channels become a swipeable row above the catalog */
  .v-library .shell { flex-direction: column; }
  .v-library .sidebar {
    flex: none !important; width: auto !important; max-width: none !important;
    display: flex; flex-direction: row; align-items: center; gap: 6px;
    overflow-x: auto; overflow-y: hidden; padding: 8px 12px !important;
    border-right: 0 !important; border-bottom: 1px solid var(--border);
  }
  .v-library .sidebar > * { flex: none; }
  .v-library .sidebar .sb-head { display: none; }
  .v-library .sidebar .ch { width: auto !important; border: 1px solid var(--border); border-radius: 999px; padding: 5px 10px; }
  .v-library .sidebar .modesw { width: auto; }

  /* Grids: the grid gets the screen, 2 days at a time, opens on today */
  .gr-main { grid-template-columns: 1fr !important; grid-template-rows: minmax(0, 1fr) !important; }
  .gr-side { display: none !important; }
  .gr-head, .gr-body { grid-template-columns: 52px repeat(7, 42vw) !important; }
  .gr-page { min-height: 0; }
  .gr-grid { min-height: 60dvh; }

  /* Reports: sidebar is already a row under 900px */
  .rpt-filters label { flex: 1 1 100%; }

  /* 5. tables scroll inside themselves */
  table.list, .sc-grid, .ci-table, .tl-table {
    display: block; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .ci-page, .rpt-main, .rpt-page { min-width: 0; max-width: 100vw; box-sizing: border-box; }

  /* cards & dialogs: room to read, a thumb-sized close */
  .detail, .modal { padding-bottom: env(safe-area-inset-bottom); }
}

/* ── Phone lists for Live and Grids (2026-10-11m) ── */
@media (max-width: 760px) {
  .v-live .viewbtn[data-view="pending"] { display: none; }   /* staging = editing */
  .lv-mlist { display: flex; flex-direction: column; padding: 0 0 16px; }
  .lv-mday, .gr-mday {
    position: sticky; top: 0; z-index: 1; padding: 10px 14px 6px;
    font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--text-faint);
    background: var(--bg); border-bottom: 1px solid var(--border);
  }
  .lv-mrow {
    display: flex; flex-direction: column; gap: 4px; width: 100%; text-align: left;
    padding: 10px 14px; border: 0; border-bottom: 1px solid var(--border);
    background: var(--surface); color: var(--text); font: inherit; cursor: pointer;
  }
  .lv-mrow:active { background: var(--surface-2); }
  .lv-mprog { font-size: 14px; line-height: 1.3; }
  .lv-mmeta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); }
  .lv-mmeta .chip, .lv-mmeta .pill { font-size: 11px; padding: 1px 8px; }

  .v-grids .gr-grid { min-height: 0; flex: 1 1 auto; }
  .gr-mlist { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .gr-mday b { color: var(--text); font-weight: 500; margin-left: 4px; }
  .gr-mday.today, .gr-mday.today b { color: var(--accent); }
  .gr-mtoday { margin-left: 6px; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--accent); color: var(--accent-fg); letter-spacing: .04em; }
  .gr-mrow { display: grid; grid-template-columns: 74px 1fr; gap: 10px; align-items: baseline;
    padding: 8px 14px; border-bottom: 1px solid var(--border); background: var(--surface); }
  .gr-mtime { font-size: 12px; color: var(--text-muted); text-align: right; }
  .gr-mcode { font-size: 13px; overflow-wrap: anywhere; }
  .gr-mtype { font-size: 12.5px; color: var(--text-muted); }
}

/* ── System → True Black (2026-10-11m, Evann) ──────────────────────────────
   When Appearance is "System" and the device is in dark mode, the app now
   uses True Black rather than Dark. Picking Dark in ⚙ still gives Dark. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg:            #000000;
    --surface:       #000000;
    --surface-2:     #0B0B0F;
    --surface-3:     #16161D;
    --border:        #1F2030;
    --border-strong: #34364B;
    --accent-tint:   #0E1230;
    --s-await-bg:    #121219;
    --shadow-card: 0 0 0 1px #1F2030;
    --shadow-lift: 0 0 0 1px #34364B, 0 10px 26px rgba(0,0,0,.9);
    --shadow-pop:  0 0 0 1px #34364B, 0 24px 60px rgba(0,0,0,.95);
  }
}

/* ── Pop-ups that were cut off (2026-10-11n) ──────────────────────────────
   Measured on the live site at 375px: Grids' Actions ▾ opened at x = −190
   (right-aligned to a button on the LEFT edge), and every card / dialog
   slid UNDER the tab bar, hiding its last 57px. */
@media (max-width: 760px) {
  /* Any dropdown menu becomes a sheet across the bottom, above the tab bar —
     never off an edge, always thumb-reachable. (Not the More list's own
     nested Boards/Tools items, which are drawn in place.) */
  .menu-pop:not(.surfnav .menu-pop) {
    position: fixed !important; left: 8px !important; right: 8px !important;
    top: auto !important; bottom: calc(64px + env(safe-area-inset-bottom)) !important;
    width: auto !important; min-width: 0 !important; max-width: none !important;
    max-height: 70dvh; overflow-y: auto; z-index: 90 !important; transform: none !important;
    border-radius: 12px;
  }
  .menu-pop:not(.surfnav .menu-pop)::before { display: none; }
  .menu-pop .menu-item { padding: 11px 12px; font-size: 14px; }

  /* A card or dialog is open → the tab bar steps aside so nothing hides under it. */
  body:has(.detail.open) #mtabs, body:has(.modal.open) #mtabs,
  html.detail-lock #mtabs { display: none; }
  .detail, .modal { padding-top: env(safe-area-inset-top); }
  .modal { top: 0 !important; left: 0 !important; transform: none !important;
    width: 100vw !important; max-height: 100dvh !important; border-radius: 0; }
  .modal.open { transform: none !important; }

  /* Toasts and alerts sit above the tab bar and never run off-screen. */
  .toast { bottom: calc(72px + env(safe-area-inset-bottom)); }
  .banner { bottom: calc(72px + env(safe-area-inset-bottom)); max-height: calc(100dvh - 140px); overflow-y: auto; }
  body:has(.detail.open) .toast, body:has(.modal.open) .toast,
  body:has(.detail.open) .banner, body:has(.modal.open) .banner { bottom: calc(16px + env(safe-area-inset-bottom)); }
}

/* Google's sign-in button lives in an iframe. On a dark page the browser
   gives a light iframe an opaque WHITE backdrop — the white box around the
   button on iOS dark mode (2026-10-11o). Declaring the host light keeps the
   iframe transparent; core.js picks Google's dark button for dark pages. */
#gsiBtn { color-scheme: light; background: transparent; }
#gsiBtn iframe { color-scheme: light; background: transparent !important; }
