
/* ══ Archivo Narrow (OFL), self-hosted ════════════════════════════════════
   Four files, 84 KB total: one VARIABLE woff2 per subset per style, so the whole
   300-700 weight range costs one request instead of one per weight. Served from
   /static/fonts by the same public route as htmx.min.js.

   Self-hosted rather than linked from Google: fonts.gstatic.com sees the IP of
   everyone who opens the admin, which is a third-party disclosure this app would
   then owe a line in the Datenschutz - and Google is neither EU-owned nor
   EU-hosted. The licence permits it (SIL OFL 1.1), the CSP lost its two remote
   origins as a result (see server.js), and the app now has no external requests
   at all. */
@font-face {
  font-family: 'Archivo Narrow';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/archivo-narrow-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo Narrow';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/archivo-narrow-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Archivo Narrow';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/archivo-narrow-latin-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo Narrow';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/archivo-narrow-latin-ext-italic.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
:root, [data-theme="light"] {
  --canvas: #f5f5f7;
  /* Gradient stripe behind a photo cell's number/dots row so they stay legible
     over the brought-forward photo: from this scrim at the top to a same-hue
     0-alpha stop (avoids the grey tinge the transparent keyword would give). */
  --cal-toprow-scrim: rgba(255,255,255,0.72);
  --cal-toprow-scrim-0: rgba(255,255,255,0);
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --surface-inset: #f2f2f7;
  --separator: rgba(60,60,67,0.08);
  --separator-opaque: #e5e5ea;
  --text-primary: #1d1d1f;
  --text-secondary: #6e6e73;
  --text-tertiary: #aeaeb2;
  --accent: #0071e3;
  --accent-hover: #0077ED;
  --accent-bg: rgba(0,113,227,0.06);
  --green: #34c759;
  --green-bg: rgba(52,199,89,0.08);
  --red: #ff3b30;
  --red-bg: rgba(255,59,48,0.06);
  --amber: #ff9500;
  --amber-bg: rgba(255,149,0,0.06);
  /* Ink weights of the same hues, for marks and small text. The fills above are
     iOS system colours: correct as blocks, unreadable at 5-9px on white. */
  --green-ink: #0a7a3c; --amber-ink: #8a5300; --red-ink: #c02a20; --violet-ink: #7a3ec4;
  --cal-published: #079400; --cal-media: #007ee6; --cal-needs: #0200ff; --cal-draft: #d400ff;
  --cal-sel: rgba(0,113,227,0.18); --cal-sel-line: #0071e3;
  --cal-hover: #fafafa;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
  --shadow-md: 0 2px 8px rgba(0,0,0,0.06), 0 0 1px rgba(0,0,0,0.04);
  --shadow-lg: 0 8px 28px rgba(0,0,0,0.08), 0 0 1px rgba(0,0,0,0.06);
  --shadow-toast: 0 8px 32px rgba(0,0,0,0.14);
  --radius: 14px;
  --radius-sm: 10px;
  --radius-xs: 8px;
  /* CLASSIC's type: the system stack. Archivo Narrow belongs to broadcast (it is
     set in that skin's token block), and classic reads badly in it. The old
     Hanken Grotesk / Inconsolata pair went with the Google Fonts link when the
     app stopped making external requests; self-hosting those two as well would
     cost ~100 KB for a skin that is on its way out, so classic falls back to
     whatever the OS provides. */
  --body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --tab: var(--body);
  --glass-bg: rgba(255,255,255,0.72);
  --glass-blur: blur(20px) saturate(180%);

  /* ── Scale tokens ─────────────────────────────────────────────
     Theme-independent (defined once here; the dark block below only
     overrides colours, so these carry through). Additive: nothing uses them
     yet — they are the vocabulary for the redesign. Prefer them over hardcoded
     px/rem going forward. See DESIGN.md. */
  /* Spacing — 4px base; half-steps (0.5/1.5/2.5/3.5) cover existing 6/10/14px. */
  --space-0: 0; --space-0-5: 2px; --space-1: 4px; --space-1-5: 6px;
  --space-2: 8px; --space-2-5: 10px; --space-3: 12px; --space-3-5: 14px;
  --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-7: 28px;
  --space-8: 32px; --space-10: 40px; --space-12: 48px; --space-16: 64px;
  /* Type scale (rem, on the 15px root). */
  --text-2xs: 0.68rem; --text-xs: 0.72rem; --text-sm: 0.8rem; --text-base: 0.9rem;
  --text-md: 1rem; --text-lg: 1.15rem; --text-xl: 1.35rem; --text-2xl: 1.6rem; --text-3xl: 2rem;
  --leading-tight: 1.2; --leading-snug: 1.35; --leading-normal: 1.47;
  /* Font weights (Archivo Narrow's variable range is 400–700; 300 resolves to 400). */
  --weight-normal: 400; --weight-medium: 500; --weight-semibold: 600; --weight-bold: 700;
  /* Motion + layering. */
  --transition-fast: 0.15s ease; --transition-base: 0.2s ease;
  --z-nav: 20; --z-toast: 100; --z-modal: 200;

  /* Backward-compat aliases for inline styles in route files */
  --bg-0: var(--canvas);
  --bg-1: var(--surface);
  --bg-2: var(--surface-inset);
  --bg-3: var(--surface-raised);
  --text-0: var(--text-primary);
  --text-1: var(--text-secondary);
  --text-2: var(--text-tertiary);
  --border: var(--separator);
  --accent-dim: var(--accent-bg);
  --sans: var(--body);
}
[data-theme="dark"] {
  --canvas: #000000;
  --cal-toprow-scrim: rgba(24,24,26,0.74);
  --cal-toprow-scrim-0: rgba(24,24,26,0);
  --surface: #1c1c1e;
  --surface-raised: #2c2c2e;
  --surface-inset: #1c1c1e;
  --separator: rgba(84,84,88,0.36);
  --separator-opaque: #38383a;
  --text-primary: #f5f5f7;
  --text-secondary: #98989d;
  --text-tertiary: #636366;
  --accent: #0a84ff;
  --accent-hover: #409cff;
  --accent-bg: rgba(10,132,255,0.1);
  --green: #30d158;
  --green-bg: rgba(48,209,88,0.12);
  --red: #ff453a;
  --red-bg: rgba(255,69,58,0.1);
  --amber: #ff9f0a;
  --amber-bg: rgba(255,159,10,0.1);
  --green-ink: #30d158; --amber-ink: #ff9f0a; --red-ink: #ff453a; --violet-ink: #c08cf5;
  --cal-published: #4dff3d; --cal-media: #45a9ff; --cal-needs: #6f6bff; --cal-draft: #e77bff;
  --cal-sel: rgba(10,132,255,0.28); --cal-sel-line: #4da3ff;
  --cal-hover: rgba(255,255,255,0.045);
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
  --shadow-md: 0 2px 8px rgba(0,0,0,0.3);
  --shadow-lg: 0 8px 28px rgba(0,0,0,0.4);
  --shadow-toast: 0 8px 32px rgba(0,0,0,0.5);
  --glass-bg: rgba(28,28,30,0.72);
  --glass-blur: blur(20px) saturate(180%);
  --bg-0: var(--canvas);
  --bg-1: var(--surface);
  --bg-2: var(--surface-inset);
  --bg-3: var(--surface-raised);
  --text-0: var(--text-primary);
  --text-1: var(--text-secondary);
  --text-2: var(--text-tertiary);
  --border: var(--separator);
  --accent-dim: var(--accent-bg);
  --sans: var(--body);
}

/* ══ SKIN: "broadcast" ═════════════════════════════════════════════════════
   A genuine departure (data-skin="broadcast" on <html>), in the language of
   independent cultural-radio sites (Café Oto / Kiosk Radio / Black Rhino): FLAT,
   high-contrast, hard-ruled, near-square; monospace UPPERCASE metadata; heavy,
   tight headings; a loud green signature used in solid blocks. No shadows.
   Purely additive — with no data-skin the CLASSIC design above is untouched.
   The app always stamps data-theme, so two scoped token blocks (light + dark)
   suffice; (0,2,0)/(0,3,0) specificity overrides the base tokens; the component
   overrides deliberately reshape structure (allowed to override DESIGN.md). */
:root[data-skin="broadcast"] {
  /* One family in all three roles. The tokens stay separate so any one of them
     can be given its own face back with a single edit - --mono in particular,
     which is the one carrying a real risk: nothing here is fixed-width any more,
     and columns hold because Archivo Narrow ships tnum (verified in its GSUB)
     and body sets font-variant-numeric: tabular-nums. */
  --body: 'Archivo Narrow', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono: 'Archivo Narrow', 'SF Mono', ui-monospace, monospace;
  --tab: 'Archivo Narrow', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  /* White paper. (Was a warm cream; the palette picker still overrides all of
     this per browser - see tentaPalDerive.) */
  --canvas: #ffffff; --surface: #ffffff; --surface-raised: #ffffff; --surface-inset: #f4f4f4;
  --separator: rgba(16,15,10,0.14); --separator-opaque: #201f1a;
  --text-primary: #0e0e08; --text-secondary: #4a4842; --text-tertiary: #86847a;
  /* Accent is monochrome ink (the black masthead + primary buttons); green is
     status only. The active tab uses a softer warm grey (--nav-active-*), not
     full black, and lifts on hover (see .nav a:hover below). */
  --accent: #14140d; --accent-hover: #34332a; --accent-bg: rgba(20,20,13,0.06);
  --nav-active-bg: #5a5850; --nav-active-fg: #fbfaf4;
  --green: #12be5f; --green-bg: rgba(18,190,95,0.12);
  --red: #e63328; --red-bg: rgba(230,51,40,0.09);
  --amber: #d88400; --amber-bg: rgba(216,132,0,0.11);
  /* The FILL colours above are for blocks and tints. As marks or small text on
     paper they measure 2.45:1 (green) and 2.91:1 (amber) — under even the 3:1
     non-text floor, which made the two status signals the least visible things
     on a page whose whole job is signalling status. These are the same hues at
     ink weight, for anything small: dots, "ready", "CLOSED", draft titles.
     Measured on white: 5.44 / 6.33 / 5.84 / 6.34. */
  --green-ink: #0a7a3c; --amber-ink: #8a5300; --red-ink: #c02a20; --violet-ink: #7a3ec4;
  /* The calendar's four status marks, chosen by hand. The hues are as given; the
     two lightest are deepened along their own hue until they hold on white
     (#0cff00 measured 1.37:1 as an 8px dot, #008cff 3.39:1). Measured now:
     4.00 / 4.10 / 8.58 / 4.01. The selection tint is the blue this calendar
     always had, kept as a token so it is no longer hardcoded in three places. */
  --cal-published: #079400; --cal-media: #007ee6; --cal-needs: #0200ff; --cal-draft: #d400ff;
  --cal-sel: rgba(0,113,227,0.18); --cal-sel-line: #0071e3;
  /* Paler than --surface-inset: a hovered day should barely tint the paper. */
  --cal-hover: #fafafa;
  --shadow-sm: none; --shadow-md: none; --shadow-lg: 4px 4px 0 rgba(16,15,10,0.14);
  --radius: 2px; --radius-sm: 2px; --radius-xs: 2px;
  --on-accent: #fbfaf4; --on-accent-border: rgba(255,255,255,0.30);
  --bg-0: var(--canvas); --bg-1: var(--surface); --bg-2: var(--surface-inset); --bg-3: var(--surface-raised);
  --text-0: var(--text-primary); --text-1: var(--text-secondary); --text-2: var(--text-tertiary);
  --border: var(--separator); --accent-dim: var(--accent-bg);
}
:root[data-skin="broadcast"][data-theme="dark"] {
  /* Warm dark (cream-tinted), matching the beige light mode. */
  --canvas: #0a0a08; --surface: #111110; --surface-raised: #171613; --surface-inset: #1a1a17;
  --separator: rgba(245,243,233,0.16); --separator-opaque: #4a4940;
  --text-primary: #f5f3e9; --text-secondary: #a6a498; --text-tertiary: #6a6960;
  /* Dark inverts the masthead: accent becomes a paper band with dark ink on it. */
  --accent: #f2f0e6; --accent-hover: #ffffff; --accent-bg: rgba(242,240,230,0.10);
  --nav-active-bg: #5f5d52; --nav-active-fg: #f5f3e9;
  --green: #26d97a; --green-bg: rgba(38,217,122,0.15);
  --red: #ff5b4d; --red-bg: rgba(255,91,77,0.13);
  --amber: #f0a63a; --amber-bg: rgba(240,166,58,0.14);
  /* On the dark canvas these already measure 10.65 / 9.64 / 7.84, so the ink
     tokens are the fills themselves — no second palette to keep in step. */
  --green-ink: #26d97a; --amber-ink: #f0a63a; --red-ink: #ff5b4d; --violet-ink: #c08cf5;
  /* Same four hues, lifted rather than deepened - on a near-black canvas it is
     the DARK end that disappears, so #0200ff becomes a readable blue here. */
  --cal-published: #4dff3d; --cal-media: #45a9ff; --cal-needs: #6f6bff; --cal-draft: #e77bff;
  --cal-sel: rgba(10,132,255,0.28); --cal-sel-line: #4da3ff;
  --cal-hover: rgba(255,255,255,0.045);
  --shadow-sm: none; --shadow-md: none; --shadow-lg: 4px 4px 0 rgba(0,0,0,0.6);
  --on-accent: #0a0a08; --on-accent-border: rgba(0,0,0,0.28);
}
/* Structure: flat + hard borders + near-square everywhere. */
[data-skin="broadcast"] .card,
[data-skin="broadcast"] .run-scroll,
[data-skin="broadcast"] .form-input,
[data-skin="broadcast"] .form-select { border: 1px solid var(--separator-opaque); box-shadow: none; }
[data-skin="broadcast"] .card-header { border-bottom: 1px solid var(--separator); }
/* Metadata in mono, uppercase, tracked — the "radio schedule" register. */
[data-skin="broadcast"] .card-header h2,
[data-skin="broadcast"] .section-title,
[data-skin="broadcast"] .badge,
[data-skin="broadcast"] .platform-chip,
[data-skin="broadcast"] .tag-chip,
[data-skin="broadcast"] .run-table th { font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.06em; border-radius: 2px; }
/* Same register, no radius: the weekday header is a row of grid cells sitting
   against square day cells, not a chip, and 2px of rounding on each one is
   visible against its neighbours. (Lower case on purpose - this comment ships
   inside every page's <style>, and two 4+ letter capitalised words in a row read
   as a base32 secret to test/account-mfa.test.js' scraper.) */
[data-skin="broadcast"] .cal-weekday { font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.06em; border-radius: 0; }
/* Heavy, tight headings. */
[data-skin="broadcast"] header h1,
[data-skin="broadcast"] .topbar-name { font-weight: 800; letter-spacing: -0.025em; }
/* Segmented nav → hard-bordered box; the active tab is a solid ink block. */
[data-skin="broadcast"] .nav { background: var(--surface); border: 1px solid var(--separator-opaque); border-radius: 2px; padding: 0; gap: 0; }
[data-skin="broadcast"] .nav a { border-radius: 0; border-right: 1px solid var(--separator); }
[data-skin="broadcast"] .nav a.active { background: var(--nav-active-bg); color: var(--nav-active-fg); box-shadow: none; }
/* Buttons: square; primary is a solid block; secondary is outlined. */
[data-skin="broadcast"] .btn,
[data-skin="broadcast"] .trigger-btn { text-transform: uppercase; letter-spacing: 0.05em; }
[data-skin="broadcast"] .btn { border-radius: 2px; }
[data-skin="broadcast"] .btn-primary { color: var(--on-accent); }
[data-skin="broadcast"] .btn-secondary { background: transparent; border: 1px solid var(--separator-opaque); }
/* The base secondary hover fills with --separator-opaque, which is near-black in
   broadcast — so the dark text vanished. Flip to accent fill + on-accent text. */
[data-skin="broadcast"] .btn-secondary:hover { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
/* ── Kiosk-style header: a solid ink band pinned to the very top, edge-to-edge,
   then the tabs as one full-width row of equal cells directly beneath. The
   container is uncapped (max-width:none) so the whole app fills the viewport
   instead of sitting in a centred 1400px column. ── */
[data-skin="broadcast"] .container { padding-top: 0; max-width: none; }
[data-skin="broadcast"] .topbar {
  background: var(--accent); margin: 0 -32px; padding: 9px 32px; margin-bottom: 0;
  border-bottom: 1px solid var(--separator-opaque);
}
[data-skin="broadcast"] .topbar-user,
[data-skin="broadcast"] .topbar-name,
[data-skin="broadcast"] .topbar-brand-name,
[data-skin="broadcast"] .topbar-city,
[data-skin="broadcast"] .topbar-actions a,
[data-skin="broadcast"] .topbar-actions button { color: var(--on-accent); }
[data-skin="broadcast"] .topbar-name { text-transform: uppercase; font-weight: 800; }
/* Brand version is on-accent, dimmed; the wordmark stays on-accent on hover
   (the base rule would turn it ink-on-ink against the black band). */
[data-skin="broadcast"] .topbar-brand-ver { color: var(--on-accent); opacity: 0.65; }
[data-skin="broadcast"] .topbar-brand:hover { opacity: 0.8; }
[data-skin="broadcast"] .topbar-brand:hover .topbar-brand-name { color: var(--on-accent); }
[data-skin="broadcast"] .topbar-actions a,
[data-skin="broadcast"] .topbar-actions form button {
  font-family: var(--tab); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.74rem;
}
/* The Sign Out button is wrapped in a <form>, which was an inline box in the
   flex row - so the button sat on the form's baseline instead of being centred
   with its neighbours. The form also carried an inline style="display:inline",
   which outranked this rule until it was removed from the markup. */
.topbar-actions form { display: inline-flex; align-items: center; line-height: 1; }
.topbar-actions a, .topbar-actions button { line-height: 1; }
[data-skin="broadcast"] .topbar-actions #skin-btn,
[data-skin="broadcast"] .topbar-actions #theme-btn { border-color: var(--on-accent-border); }
/* Hover in the ink band: the base rule paints --accent, which IS the band colour
   here (near-black in light, beige in dark), so every action link faded into the
   background. Hover greys instead — the label steps back toward the band without
   ever reaching it, in both light and dark. */
[data-skin="broadcast"] .topbar-actions a:hover,
[data-skin="broadcast"] .topbar-actions button:hover {
  color: color-mix(in srgb, var(--on-accent) 58%, var(--accent));
}
/* ...and the menu button keeps its full contrast even then: it is the control
   that holds the rest of the bar, so it must never step back toward the band.
   (On touch this matters twice over - a tap leaves :hover stuck on the element
   until you tap something else, so a hover colour that reads as "invisible"
   stays that way.) */
[data-skin="broadcast"] .topbar-actions .topbar-more,
[data-skin="broadcast"] .topbar-actions .topbar-more:hover,
[data-skin="broadcast"] .topbar-actions .topbar-more:focus { color: var(--on-accent); }
.topbar-actions .topbar-more:hover,
.topbar-actions .topbar-more:focus { color: var(--text-primary); }
/* Broadcast keeps the class-based back-links out of the band→tabs seam by default.
   The within-app "‹ Setup" links that MUST stay visible (operator pages, and the
   client setup sub-pages) opt back in with an inline display:inline-flex on the
   element itself — inline styles ride along in hx-boost's body swap, so they show
   even before a full reload refreshes this <head>. */
[data-skin="broadcast"] .back { display: none; }
[data-skin="broadcast"] .cal-topline { display: block; margin: 0; }
[data-skin="broadcast"] .nav {
  display: flex; margin: 0 -32px 20px; padding: 0; gap: 0;
  background: var(--surface); border: none; border-bottom: 1px solid var(--separator-opaque); border-radius: 0;
}
/* Equal-width cells, and the icon+label pair CENTRED inside each one: the anchor
   is a flex box, so text-align alone left-packed the pair. Mono uppercase puts
   the tabs in broadcast's metadata register (same as the mobile tab bar). */
[data-skin="broadcast"] .nav a {
  flex: 1 1 0; justify-content: center; border-radius: 0; padding: 11px 6px;
  border-right: 1px solid var(--separator); color: var(--text-secondary);
  font-family: var(--tab); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600;
  transition: background 0.12s ease, color 0.12s ease;
}
[data-skin="broadcast"] .nav a:last-child { border-right: none; }
/* Inactive tabs warm up a touch on hover (active tab keeps its grey block). */
[data-skin="broadcast"] .nav a:not(.active):hover { background: var(--surface-inset); color: var(--text-primary); }
/* Active tab: a solid ink block with the label knocked out of it, and an inner
   stroke in the paper colour so the block reads as inset into the row rather
   than painted over it. */
[data-skin="broadcast"] .nav a.active {
  /* Not full ink: mixed a quarter of the way toward the paper so the block sits
     in the page rather than punching a hole in it. The label stays on-accent,
     still around 11:1 against this. */
  background: color-mix(in srgb, var(--accent) 75%, var(--canvas));
  color: var(--on-accent);
  box-shadow: inset 0 0 0 2px var(--canvas);
}
/* Calendar matches the bands: full-bleed to the viewport edges (same -32px
   break-out as the masthead/nav), and the grid meets the day detail with no
   gutter — one shared seam instead of a 16px gap + a double border. */
[data-skin="broadcast"] .cal-layout { margin: 0 -32px; gap: 0; }
[data-skin="broadcast"] .cal-sidebar { border-left: none; }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* Buttons and form controls do NOT inherit font-family from their ancestors -
   every UA substitutes its own. That is why the calendar's month+year button and
   the month-picker cells kept rendering in the system grotesque while everything
   around them changed face. Inherit once here rather than per component. */
button, input, select, textarea { font-family: inherit; }
html { font-size: 15px; scroll-behavior: smooth; }
body {
  font-family: var(--body);
  /* Tabular figures app-wide: this is an operator tool, nearly every number in
     it is data in a column (run times, counts, dates, ids), and this is what
     replaces the fixed-width glyphs the mono token used to provide. */
  font-variant-numeric: tabular-nums;
  background: var(--canvas);
  color: var(--text-primary);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background 0.4s ease, color 0.3s ease;
  line-height: 1.47;
}

/* ── Paper grain (broadcast) ──────────────────────────────────
   A film of fine noise over the whole page, so a full-bleed white does not read
   as a blank screen. It is an inline SVG feTurbulence as a data URI: no request,
   no image file, a few hundred bytes, and it tiles at any zoom. Fixed and
   pointer-events:none, so it films the cards too rather than only the margins -
   at 3.5% it is texture, not tint. Turn it off by deleting this block; --grain
   sets the strength. */
[data-skin="broadcast"] { --grain: 0.035; }
[data-skin="broadcast"] body::after {
  content: ''; position: fixed; inset: 0; z-index: 9; pointer-events: none;
  opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* The grain must not sit over anything the user reaches into: dialogs, the
   dropdown lists and the bug panel all outrank it. */
[data-skin="broadcast"] dialog, [data-skin="broadcast"] .sel-list,
[data-skin="broadcast"] .bug-panel, [data-skin="broadcast"] .toast { z-index: 400; }

/* ── Bare pages (the sign-in flow) ───────────────────────────── */
body.bare-page .container { max-width: none; padding: 0; }
.legal-corner {
  position: fixed; right: 16px; bottom: 12px; z-index: 3;
  font-family: var(--tab); font-size: 0.62rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-tertiary); opacity: 0.75;
}
.legal-corner a { color: var(--text-tertiary); }
.legal-corner a:hover { color: var(--text-primary); }
.legal-corner span { margin: 0 6px; }

/* Visible to assistive tech, not to the eye. Used where the page's structure is
   carried visually by something that cannot be a heading (the calendar's month
   is a button that opens a picker) or where a control's label is implied by
   position rather than stated. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

::selection { background: var(--accent); color: #fff; }

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

.container { max-width: 1400px; margin: 0 auto; padding: 28px 32px 60px; }

/* ── Top Bar (one row: user · client name+city · actions) ───── */
.topbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}
.topbar-title {
  justify-self: center;
  display: flex; align-items: baseline; gap: 10px;
  min-width: 0; text-align: center;
}
.topbar-name {
  font-size: 1.35rem; font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.2; color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar-city { font-size: 0.85rem; font-weight: 400; color: var(--text-secondary); white-space: nowrap; }
.topbar-actions {
  display: flex; align-items: center; gap: 16px;
  justify-self: end;
}
/* The collapsible group. Desktop shows it inline - same row, same order, same
   look as before there was a button: the rows are display:contents, so their
   children are laid out by .topbar-menu itself, and Sign Out is ordered back to
   the end where it has always been. */
.topbar-menu { display: flex; align-items: center; gap: 16px; }
.tm-row { display: contents; }
.topbar-menu form { order: 10; }
.topbar-more { display: none; }
.topbar-actions a, .topbar-actions button {
  font-family: var(--body);
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-secondary);
  background: none;
  border: none;
  cursor: pointer;
  transition: color 0.15s;
  text-decoration: none;
}
.topbar-actions a:hover, .topbar-actions button:hover { color: var(--accent); }
.topbar-user {
  justify-self: start;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-tertiary);
}
/* Left cluster: the Tenta wordmark (home link) sits beside the username. The
   wordmark is bigger than the username but smaller than the centred client
   name; the version rides just beneath it in small mono. */
.topbar-left { justify-self: start; display: flex; align-items: center; gap: 14px; min-width: 0; }
.topbar-brand { display: flex; flex-direction: column; gap: 1px; line-height: 1.05; text-decoration: none; }
.topbar-brand-name { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; color: var(--text-primary); }
.topbar-brand-ver { font-family: var(--mono); font-size: 0.6rem; color: var(--text-tertiary); }
.topbar-brand:hover .topbar-brand-name { color: var(--accent); }
/* Theme picker dropdown (light / dark / system) */
.theme-ctl { position: relative; display: inline-flex; }
.theme-menu {
  position: absolute; top: 100%; right: 0; margin-top: 6px; z-index: 200;
  min-width: 132px; padding: 4px; display: none; flex-direction: column;
  background: var(--surface-raised); border: 1px solid var(--separator-opaque);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.theme-menu.open { display: flex; }
/* id selector so these win over the broadcast .topbar-actions button colour
   rule (which otherwise paints the menu text --on-accent → white-on-white). */
#theme-menu button {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; cursor: pointer; padding: 7px 10px;
  font-family: var(--body); font-size: 0.82rem; color: var(--text-secondary);
  border-radius: var(--radius-xs);
}
#theme-menu button:hover { background: var(--surface-inset); color: var(--text-primary); }
#theme-menu button.sel { color: var(--text-primary); font-weight: 600; }

/* Palette picker (admin only) — same popover chrome as the theme menu. Ids for
   the same reason: they have to outrank the broadcast .topbar-actions rules. */
.pal-ctl { position: relative; display: inline-flex; }
#pal-menu { min-width: 208px; padding: 8px; gap: 2px; }
.pal-row { display: flex; align-items: center; gap: 10px; padding: 5px 6px; }
.pal-lbl { flex: 1; font-family: var(--body); font-size: 0.8rem; color: var(--text-secondary); }
/* Id-scoped, all of it: the broadcast .topbar-actions rules strip background and
   border off every button in this bar, and a bare .pal-sw would lose to them. */
#pal-menu .pal-sw {
  width: 32px; height: 22px; flex: 0 0 auto; padding: 0; cursor: pointer;
  border: 1px solid var(--separator-opaque); border-radius: var(--radius-xs);
}
#pal-menu .pal-sw.on { box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--accent); }
/* The picker itself: an S/V square + a hue bar, both plain divs with CSS
   gradients (no canvas, no library). The square's right edge is the live hue,
   fed in as --pal-hue. */
#pal-pick { padding: 6px 6px 2px; }
#pal-menu .pal-sv {
  position: relative; height: 92px; border-radius: 6px; cursor: crosshair; touch-action: none;
  border: 1px solid var(--separator);
  background-image: linear-gradient(to top, #000, rgba(0,0,0,0)),
                    linear-gradient(to right, #fff, var(--pal-hue, #ff0000));
}
#pal-menu .pal-hue {
  position: relative; height: 12px; margin-top: 8px; border-radius: 6px;
  cursor: ew-resize; touch-action: none; border: 1px solid var(--separator);
  background-image: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
#pal-menu .pal-knob {
  position: absolute; width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
  transform: translate(-50%, -50%); pointer-events: none;
}
#pal-menu .pal-knob-h { top: 50%; }
.pal-hex { display: flex; align-items: center; gap: 5px; margin-top: 8px; padding: 0 1px;
  font-family: var(--mono); font-size: 0.74rem; color: var(--text-tertiary); }
#pal-menu .pal-hex input {
  flex: 1; min-width: 0; padding: 4px 6px; text-transform: uppercase;
  font-family: var(--mono); font-size: 0.75rem; color: var(--text-primary);
  background: var(--surface); border: 1px solid var(--separator-opaque); border-radius: var(--radius-xs);
}
/* Direct child only — the swatches are buttons too, and this is the Reset. */
#pal-menu > button {
  width: 100%; margin-top: 8px; padding: 6px 10px; cursor: pointer;
  background: none; border: 1px solid var(--separator-opaque); border-radius: var(--radius-xs);
  font-family: var(--body); font-size: 0.78rem; color: var(--text-secondary);
}
#pal-menu > button:hover { background: var(--surface-inset); color: var(--text-primary); }
#pal-note { padding: 2px 6px 6px; font-size: 0.68rem; line-height: 1.35; color: var(--text-tertiary); }
/* Held while a swatch is being dragged, and around the one-off read of the other
   theme's tokens. body carries a 0.4s background transition, so without this
   every drag frame starts a fresh interpolation and the page smears behind the
   cursor instead of tracking it. */
[data-pal-still] *, [data-pal-still] *::before, [data-pal-still] *::after { transition: none !important; }

/* ── Page Header ───────────────────────────────────────────── */
.page-header {
  margin-bottom: 28px;
}
.page-header h1 {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.15;
  color: var(--text-primary);
}
.page-header .subtitle {
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--text-secondary);
  margin-top: 4px;
}

/* ── Header (tag-based, matches .page-header) ─────────────── */
header { margin-bottom: 28px; }
header h1 {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.15;
  color: var(--text-primary);
}
header h1 span { color: var(--accent); font-weight: 300; }
header .meta {
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--text-secondary);
  margin-top: 4px;
}

/* ── Back ──────────────────────────────────────────────────── */
.back {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.87rem; font-weight: 500;
  color: var(--accent);
  text-decoration: none;
  margin-bottom: 12px;
  transition: opacity 0.15s;
}
.back:hover { opacity: 0.7; color: var(--accent); }
.back::before { content: '\2039'; font-size: 1.3em; line-height: 1; margin-top: -1px; }
/* Clients are scoped to one venue and are redirected away from the dashboard, so
   hide the "Dashboard" back-link (href="/") for them. Within-scope back links
   (e.g. the media day page's "Calendar") are not href="/" and stay. */
body[data-role="client"] a.back[href="/"] { display: none !important; }

/* ── Cards ─────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  transition: background 0.3s ease, box-shadow 0.2s ease;
}
.card + .card { margin-top: 20px; }

.card-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 22px 14px;
}
.card-header h2 {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.card-body { padding: 0; }

/* ── Segmented Nav (Apple-style tab bar) ──────────────────── */
.nav {
  display: inline-flex;
  gap: 2px;
  background: var(--surface-inset);
  border-radius: var(--radius-xs);
  padding: 3px;
  margin-bottom: 24px;
}
.nav a {
  font-family: var(--body);
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-secondary);
  text-decoration: none;
  padding: 6px 14px;
  border-radius: 6px;
  transition: all 0.2s ease;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* Section icons (calendar/media/overview) — currentColor, sized to the label,
   shared with the mobile tab bar (components.js NAV_ICONS). */
.nav-ico { display: inline-flex; }
.nav-ico svg { width: 15px; height: 15px; display: block; }
.nav a:hover { color: var(--text-primary); }
.nav a.active {
  color: var(--text-primary);
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 0 0.5px rgba(0,0,0,0.06);
}

/* ── Client Row ────────────────────────────────────────────── */
.client-row {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: 16px;
  align-items: center;
  padding: 16px 22px;
  border-bottom: 1px solid var(--separator);
  text-decoration: none;
  color: inherit;
  transition: background 0.15s ease;
}
.client-row:last-child { border-bottom: none; }
.client-row:hover { background: var(--surface-inset); }
.client-name {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text-primary);
}
.client-city {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-top: 1px;
}

/* ── Badge ─────────────────────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--body);
  font-size: 0.72rem;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 6px;
  color: var(--badge-color);
  background: var(--badge-bg);
  white-space: nowrap;
  letter-spacing: 0.01em;
}

/* ── Time Label ────────────────────────────────────────────── */
.time-label {
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

/* ── Buttons ───────────────────────────────────────────────── */
.btn {
  font-family: var(--body);
  font-size: 0.82rem;
  font-weight: 500;
  padding: 8px 18px;
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: all 0.15s ease;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.btn-primary {
  background: var(--accent);
  color: #fff;
}
.btn-primary:hover { background: var(--accent-hover); color: #fff; }
.btn-secondary {
  background: var(--surface-inset);
  color: var(--text-primary);
}
.btn-secondary:hover { background: var(--separator-opaque); }
.btn-ghost {
  background: transparent;
  color: var(--accent);
  padding: 6px 12px;
}
.btn-ghost:hover { background: var(--accent-bg); }
.btn-danger {
  background: transparent;
  color: var(--red);
  padding: 6px 12px;
}
.btn-danger:hover { background: var(--red-bg); }
.btn-sm { font-size: 0.75rem; padding: 5px 12px; }
/* Glyph-only button: square padding, no label gap, never the flexible item
   in a row of actions. Combine with .btn-sm for the compact size. */
.btn-icon { padding: 5px 8px; gap: 0; line-height: 1; flex: 0 0 auto; justify-content: center; }

/* Legacy trigger-btn alias */
.trigger-btn {
  font-family: var(--body);
  font-size: 0.78rem;
  font-weight: 500;
  padding: 6px 14px;
  border: none;
  border-radius: var(--radius-xs);
  background: var(--surface-inset);
  color: var(--text-primary);
  cursor: pointer;
  transition: all 0.15s ease;
}
.trigger-btn:hover {
  background: var(--accent-bg);
  color: var(--accent);
}
.trigger-btn.test {
  color: var(--text-tertiary);
}
.trigger-btn.test:hover {
  color: var(--amber);
  background: var(--amber-bg);
}
/* Single-integration buttons: smaller than the workflow buttons, but full
   text colour — they are live actions, not disabled/secondary. */
.trigger-btn.svc {
  font-size: 0.72rem;
  padding: 4px 10px;
  color: var(--text-primary);
}
.triggers { display: flex; gap: 6px; flex-wrap: wrap; }

/* ── Schedule row (Setup > Schedules, and the operator Config page) ─────────
   Name on the left, the two controls that matter on the right. Both fields keep
   a real width instead of being squeezed by a caption that repeated the row's
   own name. */
.sched-row {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 0; border-bottom: 1px solid var(--separator);
}
.sched-name { min-width: 104px; margin: 0; font-weight: 500; font-size: 0.87rem; }
.sched-fields { display: flex; align-items: center; gap: 10px; margin-left: auto; flex-wrap: wrap; }
.sched-f { display: flex; align-items: center; gap: 6px; }
.sched-f-lbl { font-size: 0.8rem; color: var(--text-secondary); }
.sched-f .form-input { width: 116px; padding: 6px 10px; font-size: 0.85rem; }
.sched-f .form-select, .sched-f .sel-wrap { width: 132px; }
.sched-f .form-select { padding: 6px 10px; font-size: 0.85rem; }
.sched-f-day .sel-wrap { width: 132px; }

/* ── Dashboard trigger block ────────────────────────────────────────────────
   One venue, three labelled rows: its name, its workflows, its channels. The
   buttons sit in an auto-fill grid so every one is the same width and a row
   re-flows into as many columns as fit - which is what stops seven channel
   buttons from wrapping into a ragged scramble on a narrow screen. */
.trig-venue { padding: 14px 20px; border-bottom: 1px solid var(--separator); }
.trig-venue:last-child { border-bottom: none; }
.trig-name { font-size: 0.9rem; font-weight: 600; margin-bottom: 10px; }
.trig-row { display: flex; align-items: baseline; gap: 12px; margin-top: 8px; }
.trig-lbl {
  flex: 0 0 88px; font-family: var(--mono); font-size: 0.66rem;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-tertiary);
}
.trig-btns {
  flex: 1; display: grid; gap: 6px;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
}
.trig-btns .trigger-btn { width: 100%; text-align: center; padding: 7px 8px; }

/* ── Tables ────────────────────────────────────────────────── */
.run-table { width: 100%; border-collapse: collapse; }
.run-table th {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  text-align: left;
  padding: 11px 20px;
  border-bottom: 1px solid var(--separator);
}
.run-table td {
  font-size: 0.85rem;
  padding: 11px 20px;
  border-bottom: 1px solid var(--separator);
  color: var(--text-secondary);
}
.run-table tr:last-child td { border-bottom: none; }
.run-table tbody tr { transition: background 0.12s; }
.run-table tbody tr:hover { background: var(--surface-inset); }
.run-table .mono {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--text-tertiary);
}
.run-table .err {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--red);
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.run-table a { color: var(--accent); text-decoration: none; font-weight: 500; }
.run-table a:hover { text-decoration: underline; }

/* ── Steps ─────────────────────────────────────────────────── */
.step {
  display: grid;
  grid-template-columns: 28px 1fr auto auto;
  gap: 10px;
  align-items: center;
  padding: 12px 22px;
  border-bottom: 1px solid var(--separator);
  font-size: 0.87rem;
}
.step:last-child { border-bottom: none; }
.step-icon { font-size: 1rem; text-align: center; }
.step-name { font-weight: 500; color: var(--text-primary); }
.step-time { font-family: var(--mono); font-size: 0.78rem; color: var(--text-tertiary); }
.step-err { font-family: var(--mono); font-size: 0.75rem; color: var(--red); grid-column: 2/5; margin-top: -2px; }

/* ── Config ────────────────────────────────────────────────── */
.config-block {
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.75;
  color: var(--text-secondary);
  padding: 18px 22px;
  white-space: pre-wrap;
  word-break: break-all;
  background: var(--surface-inset);
  border-radius: 0 0 var(--radius) var(--radius);
}

/* ── Platforms ──────────────────────────────────────────────── */
.platforms { display: flex; gap: 8px; flex-wrap: wrap; padding: 16px 22px; }
.platform-chip {
  font-size: 0.78rem;
  font-weight: 500;
  padding: 5px 12px;
  border-radius: 20px;
  color: var(--text-tertiary);
  background: var(--surface-inset);
}
.platform-chip.on {
  color: var(--green);
  background: var(--green-bg);
}
.platform-chip.off {
  color: var(--text-tertiary);
  text-decoration: line-through;
  opacity: 0.45;
}

/* ── Section Title ─────────────────────────────────────────── */
.section-title {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 32px 0 12px;
}

/* ── Sparkline ─────────────────────────────────────────────── */
.sparkline { display: flex; gap: 3px; align-items: center; }
.spark {
  width: 6px; height: 20px;
  border-radius: 3px;
  opacity: 0.8;
}

/* ── Toast ──────────────────────────────────────────────────── */
.toast {
  position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%) translateY(16px);
  font-size: 0.85rem; font-weight: 500;
  padding: 12px 22px;
  border-radius: 14px;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-toast);
  color: var(--text-primary);
  opacity: 0;
  transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
  z-index: 200;
  pointer-events: none;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }

/* ── Bug-report widget (the clipper) ────────────────────────── */
/* Same fixed-glass family as .toast; z-index below it so a toast (e.g. the
   rate-limit message) is never hidden behind the panel it talks about. */
.bug-fab {
  position: fixed; right: 20px; bottom: 24px; z-index: 150;
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-toast);
  border: 1px solid var(--separator-opaque);
  color: var(--text-secondary); cursor: pointer; padding: 0;
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), color 0.15s;
}
.bug-fab:hover { color: var(--text-primary); transform: translateY(-2px) rotate(-8deg); }
.bug-eye-g { transform-box: fill-box; transform-origin: center; animation: bug-blink 4.6s infinite; }
@keyframes bug-blink {
  0%, 93.5%, 100% { transform: scaleY(1); }
  95.5%, 97.5% { transform: scaleY(0.08); }
}
@media (prefers-reduced-motion: reduce) {
  .bug-eye-g { animation: none; }
  .bug-fab, .bug-fab:hover { transition: none; transform: none; }
}
.bug-panel {
  position: fixed; right: 20px; bottom: 86px; z-index: 151;
  width: min(320px, calc(100vw - 40px));
  /* Never taller than the screen, and scroll inside if the screenshot preview
     makes it tall — otherwise the textarea slid off the top with no way back. */
  max-height: min(76vh, calc(100dvh - 96px));
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-toast);
  border: 1px solid var(--separator-opaque);
  border-radius: 14px; padding: 14px;
}
.bug-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 0.85rem; font-weight: 600; color: var(--text-primary);
  margin-bottom: 10px;
}
.bug-close {
  background: none; border: none; cursor: pointer; padding: 0 2px;
  font-size: 1.15rem; line-height: 1; color: var(--text-tertiary);
}
.bug-close:hover { color: var(--text-primary); }
.bug-panel .form-textarea { resize: vertical; min-height: 88px; font-size: 0.85rem; margin-bottom: 10px; }
.bug-send {
  width: 100%;
  font-size: 0.82rem; font-weight: 600;
  padding: 8px 14px; border-radius: 10px;
  border: 1px solid var(--separator-opaque);
  background: var(--text-primary); color: var(--bg, #fff);
  cursor: pointer; transition: opacity 0.15s;
}
.bug-send:hover { opacity: 0.85; }
.bug-msg { font-size: 0.85rem; color: var(--text-primary); padding: 6px 2px; }
.bug-shot-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; min-height: 30px; }
.bug-shot-btn {
  font-size: 0.78rem; font-weight: 500;
  padding: 6px 10px; border-radius: 8px;
  border: 1px dashed var(--separator-opaque);
  background: transparent; color: var(--text-secondary); cursor: pointer;
}
.bug-shot-btn:hover { color: var(--text-primary); border-color: var(--text-tertiary); }
.bug-shot-edit { margin-bottom: 10px; }
.bug-shot-canvas {
  /* Scale to fit the panel width AND a height cap, keeping aspect ratio, so a
     tall page shot never swallows the whole panel. Mark coords map via
     getBoundingClientRect, so any display size stays accurate. */
  max-width: 100%; max-height: 42vh; width: auto; height: auto; display: block;
  border-radius: 8px; border: 1px solid var(--separator-opaque);
  background: var(--surface-inset); cursor: crosshair; touch-action: none;
}
.bug-shot-tools { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.bug-shot-hint { font-size: 0.72rem; color: var(--text-tertiary); margin-right: auto; }
.bug-shot-clear, .bug-shot-remove {
  font-size: 0.72rem; padding: 3px 8px; border-radius: 6px;
  border: 1px solid var(--separator-opaque); background: transparent;
  color: var(--text-secondary); cursor: pointer;
}
.bug-shot-clear:hover, .bug-shot-remove:hover { color: var(--text-primary); border-color: var(--text-tertiary); }
@media (max-width: 900px) {
  /* Clear the mobile tab bar — same var the bar itself is sized with. */
  body.has-m-tabbar .bug-fab { bottom: calc(var(--m-tabbar-h, 56px) + env(safe-area-inset-bottom, 0px) + 12px); }
  body.has-m-tabbar .bug-panel { bottom: calc(var(--m-tabbar-h, 56px) + env(safe-area-inset-bottom, 0px) + 74px); }
}

/* ── Forms ──────────────────────────────────────────────────── */
.form-group { margin-bottom: 18px; }
.form-label {
  display: block;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 6px;
}
.form-input, .form-select, .form-textarea {
  width: 100%;
  font-family: var(--body);
  font-size: 0.87rem;
  padding: 10px 14px;
  /* --surface, not --surface-inset: an inset fill next to the chip inputs (which
     sit on --surface) read as "this field is disabled" rather than as a field.
     The border carries the affordance on its own, in both skins. */
  background: var(--surface);
  border: 1px solid var(--separator-opaque);
  border-radius: var(--radius-xs);
  color: var(--text-primary);
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
/* iOS gives date/time inputs an intrinsic width of their own and ignores a
   percentage one, so a 1fr grid cell holding a date field pushed the whole form
   past the edge of the dialog - the field ended up half outside the window. The
   appearance reset is what lets width:100% actually apply; min-width:0 lets the
   grid cell shrink; the inner date/time spinner is told not to hold a width
   open either. */
input[type="date"], input[type="time"], input[type="datetime-local"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; min-width: 0; max-width: 100%;
}
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value { text-align: left; min-width: 0; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator { margin-left: 4px; }

.form-input:focus, .form-select:focus, .form-textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-bg);
}
/* One visible keyboard ring for everything that can take focus. The form
   controls above and .sel-btn already draw their own, so they are excluded
   rather than given a second ring on top of it. .tag-chip-entry is excluded
   too: its chip container rings via .tag-chips:focus-within. */
a:focus-visible,
button:not(.sel-btn):focus-visible,
[tabindex]:focus-visible,
input:not(.form-input):not(.tag-chip-entry):focus-visible,
select:not(.form-select):focus-visible,
textarea:not(.form-textarea):focus-visible,
[contenteditable]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.form-input::placeholder { color: var(--text-tertiary); }
.form-textarea { resize: vertical; min-height: 80px; line-height: 1.6; font-family: var(--mono); font-size: 0.82rem; }
.form-select { cursor: pointer; -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%236e6e73' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px;
}
/* ── Custom select ────────────────────────────────────────────
   A native <select> can be styled shut but not open: the option list is drawn by
   the OS, in the OS's font and colours, and no CSS reaches it. So the native
   element stays in the DOM (it is what forms submit, what htmx serialises and
   what every inline onchange is bound to) and this draws a button + listbox over
   it. Desktop only - see tentaSelects(); on touch the platform picker is better
   than anything reimplemented here. */
.sel-wrap { position: relative; display: block; width: 100%; }
.sel-wrap.inline { display: inline-block; width: auto; }
.sel-wrap > select.sel-native {
  position: absolute; opacity: 0; pointer-events: none; width: 100%; height: 100%; left: 0; top: 0;
}
.sel-btn {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-family: var(--body); font-size: 0.87rem; text-align: left; cursor: pointer;
  padding: 10px 14px; color: var(--text-primary);
  background: var(--surface); border: 1px solid var(--separator-opaque); border-radius: var(--radius-xs);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.sel-btn:focus-visible, .sel-wrap.open .sel-btn { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
.sel-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.sel-caret { flex: 0 0 auto; font-size: 0.6rem; color: var(--text-tertiary); line-height: 1; }
/* The list is a POPOVER, so the browser renders it in the top layer: above every
   stacking context, outside every clipping ancestor, and unaffected by the modal
   inertness of a <dialog>. That is what lets it stay a child of .sel-wrap - it
   used to be re-parented to <body> (or to the open dialog) to escape .card's
   overflow:hidden and the calendar sidebar's scroll box, and each of those hosts
   measured coordinates differently. Nothing is re-parented now.
   The UA sheet gives [popover] inset:0 + margin:auto (centred) and its own
   border/padding; these reset that so left/top set from the button's rect are
   what actually positions it. display is left to the UA: none when closed,
   block while open, which is why there is no .open rule here. */
.sel-list {
  position: fixed; inset: auto; margin: 0; z-index: 400;
  max-height: 260px; overflow-y: auto; padding: 4px;
  background: var(--surface-raised); border: 1px solid var(--separator-opaque);
  border-radius: var(--radius-xs); box-shadow: var(--shadow-lg);
  color: var(--text-primary);
}
.sel-list::backdrop { background: transparent; }
.sel-opt {
  padding: 7px 10px; font-size: inherit; color: var(--text-secondary);
  cursor: pointer; border-radius: var(--radius-xs); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.sel-opt:hover, .sel-opt.active { background: var(--surface-inset); color: var(--text-primary); }
.sel-opt.sel { color: var(--text-primary); font-weight: 600; }
.sel-opt[data-disabled] { opacity: 0.45; cursor: not-allowed; }

.form-hint {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  margin-top: 5px;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form-error {
  font-size: 0.82rem;
  color: var(--red);
  margin-bottom: 16px;
  padding: 10px 14px;
  background: var(--red-bg);
  border-radius: var(--radius-xs);
}

/* ── Checkbox ──────────────────────────────────────────────── */
.check-label {
  display: flex; align-items: center; gap: 10px;
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin-bottom: 6px;
  cursor: pointer;
}
.check-label input[type="checkbox"] {
  width: 18px; height: 18px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* ── htmx indicator ────────────────────────────────────────── */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline; }

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 640px) {
  .container { padding: 20px 16px 48px; }
  .page-header h1 { font-size: 1.6rem; }
  .client-row { grid-template-columns: 1fr; gap: 8px; }
  .triggers { flex-wrap: wrap; }
  .form-row { grid-template-columns: 1fr; }
  .nav { flex-wrap: wrap; }
}

/* ── Entrance ──────────────────────────────────────────────── */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
.card { animation: fadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; }
.card:nth-child(2) { animation-delay: 0.04s; }
.card:nth-child(3) { animation-delay: 0.08s; }
.card:nth-child(4) { animation-delay: 0.12s; }

.thumb-media { display: block; cursor: pointer; }
/* The tile is keyboard-reachable (tabindex="0"), but its .thumb wrapper is
   overflow:hidden, which would clip the global ring's 2px outward offset. Draw
   it inside the tile instead. */
.thumb-media:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.vid-badge { position: absolute; left: 5px; bottom: 5px; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; font-size: 0.55rem; color: #fff; background: rgba(0,0,0,0.55); border-radius: 50%; pointer-events: none; }
/* Click-to-play lightbox for video thumbnails (stories / posts / reels). */
#media-lightbox { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
#media-lightbox[hidden] { display: none; }
.mlb-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.82); }
.mlb-stage { position: relative; z-index: 1; }
.mlb-video { max-width: 92vw; max-height: 88vh; display: block; border-radius: 8px; background: #000; }
/* Same stage as the video, for the click-to-zoom image viewer. object-fit
   keeps a portrait story or wide banner whole instead of cropping it. */
.mlb-image { max-width: 92vw; max-height: 88vh; display: block; border-radius: 8px; background: #000; object-fit: contain; }
/* The stage holds both a video and an img; only one is shown at a time. The
   display:block rules above otherwise beat the hidden attribute's UA
   display:none, so the idle element (an empty video player) would still show. */
.mlb-video[hidden], .mlb-image[hidden] { display: none; }
.mlb-close { position: absolute; top: 16px; right: 20px; z-index: 2; width: 40px; height: 40px; border: none; border-radius: 50%; background: rgba(0,0,0,0.5); color: #fff; font-size: 1.6rem; line-height: 1; cursor: pointer; }
.mlb-close:hover { background: rgba(0,0,0,0.75); }
/* Image thumbnails that open in the lightbox rather than navigating away. */
.thumb-img { cursor: zoom-in; }
/* The page body's landmark wrapper: a pass-through box, with one exception. The
   viewport-fit calendar (routes/calendar.js) turns .container into a flex column
   whose item used to be .cal-layout itself, pinned with flex: 0 0 auto so the
   pixel height calFit() measures is never shrunk away. #main is that flex item
   now, so it inherits the no-shrink. Inert everywhere else: without a flex
   parent the property does nothing. */
body.fit-viewport #main { flex: 0 0 auto; }

/* Seamless page transitions (hx-boost + View Transitions). Subtle crossfade on
   navigation; instant (no-op) where unsupported or when reduced motion is set. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 160ms; }
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
  /* The card entrance (fadeIn + its nth-child delays) and the global smooth
     scroll are the two motions that run without anyone asking for them. */
  .card { animation: none; }
  html { scroll-behavior: auto; }
}
