/* ============================================================
   Ad Maker — styles · Design System v3 "Atelier"
   Gallery-white shell, ink text, ONE Mediterranean blue accent
   (Sidi Bou Said: white walls, blue doors — and blue never fights
   the warm product photography the app exists to frame).
   Type: IBM Plex Sans / Plex Sans Arabic / Plex Mono (money, ids).
   4px spacing scale. Light default; dark = deep blue-ink, same rules.
   The v2 covenant stands: a neutral shell, colour only on the
   committing action, the chosen state, and a status that is true.
   Icon paths: Lucide — ISC, (c) Lucide Contributors.
   ============================================================ */

:root {
  /* spacing scale (4px base) */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;
  /* radii */
  --r-xs: 6px; --r-sm: 8px; --r: 10px; --r-lg: 14px; --r-xl: 18px; --r-pill: 999px;

  /* palette — light (gallery white, cool ink) */
  --bg:          #F5F6F8;
  --panel:       #FFFFFF;
  --panel-2:     #FAFBFC;
  --panel-3:     #EFF1F4;
  --text:        #16181D;
  --text-2:      #3F444D;
  --muted:       #676D78;   /* 4.9:1 on --panel-3 — clears AA for small text */
  --border:      #E4E7EC;
  --border-2:    #D3D7DF;

  /* accent — Sidi Bou blue. --accent fills, --accent-ink is accent-coloured
     TEXT on white (#2743C9 is 7.4:1), --accent-btn carries white labels
     (#2E4BDB is 5.9:1 with white — clears AA at any size). One hue, no drift. */
  --accent:      #2E4BDB;
  --accent-fg:   #FFFFFF;
  --accent-ink:  #2743C9;
  --accent-soft: #EDF0FD;
  --accent-line: #C7D0F6;
  --accent-btn:  #2E4BDB;

  /* THE RAIL — the fourth surface, and the one thing the references all share.
     Foreplay and StudyPhoria both anchor the app with a solid colour column on
     the left; a white sidebar against a white canvas is why this app read
     flatter than either. The hue is the accent's own (hsl ~228), dropped to an
     ink so it reads as structure rather than a second accent competing with the
     buttons. Dark mode keeps it, one step off the page so the seam survives. */
  --rail-bg:     #151E4A;
  --rail-2:      #1D2960;   /* hover / chosen fill inside the rail */
  --rail-text:   #F2F4FB;
  --rail-text-2: #C6CCE6;
  --rail-muted:  #99A2C8;   /* 4.8:1 on --rail-bg */
  --rail-line:   #2A3672;

  /* TEAL — the fourth colour, and it has a job: "this is running right now".
     The directory exists to answer who is advertising, so an active ad count is
     a status that is true, which is exactly what DESIGN.md gives colour to.
     Green already means "done/ok"; teal means "live", and the two never appear
     in the same column. --live-ink is the text weight (5.3:1 on white). */
  --live:      #0E9E86;
  --live-ink:  #0A7A68;
  --live-soft: #E4F7F3;
  --live-line: #A8E4D9;
  --live-dot:  #19E0B6;   /* the pip only — too light to carry text */

  --ok-ink: #0B2F1C;
  --ok:     #13763F;  --ok-soft:     #E7F4EC;  --ok-line:     #B7E0C6;   /* spec #157F45 = 4.47:1 on --ok-soft */
  --warn:   #93610A;  --warn-soft:   #FCF3E1;  --warn-line:   #EBD6A8;
  --danger: #C22F2F;  --danger-soft: #FCEDEC;  --danger-line: #F2C4C1;
  --danger-fg: #FFFFFF;   /* ink on a --danger fill; inverts in dark */

  /* two extra hues for the agent's hats: the palette has orange, green, amber
     and red, and there are five roles. Ink on soft clears 6:1 in both themes,
     well past the 3:1 a 22px glyph needs. */
  --indigo:      #3B4CA8;  --indigo-soft: #EEF0FB;  --indigo-line: #C6CEEE;
  --violet:      #7A3FA6;  --violet-soft: #F5EDFA;  --violet-line: #DFC9EC;

  --shadow-xs: 0 1px 2px rgba(18,22,33,.06);
  --shadow:    0 1px 2px rgba(18,22,33,.05), 0 8px 24px rgba(18,22,33,.07);
  --shadow-lg: 0 16px 56px rgba(18,22,33,.22);
  --focus:     0 0 0 1px var(--accent), 0 0 0 4px rgba(46,75,219,.22);
  --shimmer:   rgba(255,255,255,.55);

  /* Plex everywhere: Sans for the shell, Sans Arabic ahead of it wherever the
     content is the owner's (Derja renders in its OWN face, not a fallback),
     Mono for money, ids and counts — the ledger voice of a tool that spends. */
  --font: "IBM Plex Sans", "IBM Plex Sans Arabic", -apple-system, "Segoe UI",
          Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
          "Apple Color Emoji", "Segoe UI Emoji";
  --font-arabic: "IBM Plex Sans Arabic", "IBM Plex Sans", "Segoe UI", Tahoma, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
          Consolas, "Liberation Mono", "Courier New", monospace;

  --sidebar-w: 248px;
  /* what the left column measures at THIS width: the full sidebar, the icon
     rail, or nothing. One token drives the grid and the toast inset, so they
     can never disagree. */
  --rail-w: var(--sidebar-w);
  --preview-w: 384px;
  --split-w: 6px;

  /* chevron-down, as a mask so it takes currentColor rather than a baked hex */
  --caret: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
}

/* Keep native chrome — scrollbars, <select> popups, form controls — on the
   theme the owner picked, not the one the OS picked. */
:root[data-theme="dark"] { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* Dark mode uses Pico's three-state pattern so a manual toggle can win in both
   directions: the media query is guarded with :not([data-theme="light"]), and
   [data-theme="dark"] repeats the values for an explicit opt-in. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:          #0C0E13;
    --panel:       #12151C;
    --panel-2:     #171B24;
    --panel-3:     #1F2430;
    --text:        #E8EAEF;
    --text-2:      #BEC3CE;
    --muted:       #9AA1AE;
    --border:      #262C39;
    --border-2:    #364052;
    --accent:      #5C77F2;
    --accent-fg:   #0A0F26;
    --accent-ink:  #8FA3F7;
    --accent-soft: #161D3A;
    --accent-line: #2B3866;
    --accent-btn:  #5C77F2;   /* dark ink on this blue is 6.8:1 — no darker step needed */
    --ok-ink: #08351E;
    --ok:     #4ED188;  --ok-soft:     #10281B;  --ok-line:     #1E4933;
    --warn:   #E9AE52;  --warn-soft:   #2A2111;  --warn-line:   #4A3A1B;
    --danger: #FF8A80;  --danger-soft: #321715;  --danger-line: #5A2925;
    --danger-fg: #2A0F10;
    --indigo: #8FA2F2;  --indigo-soft: #141A33;  --indigo-line: #2A3459;
    --violet: #C79BE8;  --violet-soft: #241534;  --violet-line: #432C5C;
    /* The rail stays the darkest surface in dark mode too, so the shell keeps
       the same silhouette in both themes instead of dissolving into the page. */
    --rail-bg:     #0A0D1A;
    --rail-2:      #161C2E;
    --rail-text:   #E8EAEF;
    --rail-text-2: #B4BBCC;
    --rail-muted:  #8B93A6;
    --rail-line:   #212940;
    --live:      #3FD9BC;
    --live-ink:  #5FE3C9;
    --live-soft: #0C2A26;
    --live-line: #1B4A43;
    --live-dot:  #19E0B6;
    --shadow-xs: 0 1px 2px rgba(0,0,0,.4);
    --shadow:    0 1px 2px rgba(0,0,0,.4), 0 6px 20px rgba(0,0,0,.35);
    --shadow-lg: 0 12px 48px rgba(0,0,0,.6);
    --focus:     0 0 0 1px var(--accent), 0 0 0 4px rgba(92,119,242,.32);
    --shimmer:   rgba(255,255,255,.09);
  }
}
:root[data-theme="dark"] {
  --bg:          #0C0E13;
  --panel:       #12151C;
  --panel-2:     #171B24;
  --panel-3:     #1F2430;
  --text:        #E8EAEF;
  --text-2:      #BEC3CE;
  --muted:       #9AA1AE;
  --border:      #262C39;
  --border-2:    #364052;
  --accent:      #5C77F2;
  --accent-fg:   #0A0F26;
  --accent-ink:  #8FA3F7;
  --accent-soft: #161D3A;
  --accent-line: #2B3866;
  --accent-btn:  #5C77F2;
  --ok-ink: #08351E;
  --ok:     #4ED188;  --ok-soft:     #10281B;  --ok-line:     #1E4933;
  --warn:   #E9AE52;  --warn-soft:   #2A2111;  --warn-line:   #4A3A1B;
  --danger: #FF8A80;  --danger-soft: #321715;  --danger-line: #5A2925;
  --danger-fg: #2A0F10;
  --indigo: #8FA2F2;  --indigo-soft: #141A33;  --indigo-line: #2A3459;
  --violet: #C79BE8;  --violet-soft: #241534;  --violet-line: #432C5C;
  --rail-bg:     #0A0D1A;
  --rail-2:      #161C2E;
  --rail-text:   #E8EAEF;
  --rail-text-2: #B4BBCC;
  --rail-muted:  #8B93A6;
  --rail-line:   #212940;
  --live:      #3FD9BC;
  --live-ink:  #5FE3C9;
  --live-soft: #0C2A26;
  --live-line: #1B4A43;
  --live-dot:  #19E0B6;
  --shadow-xs: 0 1px 2px rgba(0,0,0,.45);
  --shadow:    0 1px 2px rgba(0,0,0,.45), 0 8px 24px rgba(0,0,0,.38);
  --shadow-lg: 0 16px 56px rgba(0,0,0,.6);
  --focus:     0 0 0 1px var(--accent), 0 0 0 4px rgba(92,119,242,.32);
  --shimmer:   rgba(255,255,255,.09);
}

*, *::before, *::after { box-sizing: border-box; }

/* class rules set `display`, so [hidden] needs to win everywhere */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* ------------------------------ icons ------------------------------ */
/* Lucide ships stroke-width="2" as a presentation attribute, which sits at the
   bottom of the cascade - this one rule governs every icon in the app.
   1.75 in a 24-unit viewBox renders as ~1.17 device px at our 16px size. */
.ico { width: 16px; height: 16px; flex: none; display: block; }
svg.ico {
  stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round; fill: none;
}
.ico-xs { width: 12px; height: 12px; }

.label {
  font-size: 10.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}
.spacer { flex: 1 1 auto; }

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }

/* ------------------------------ buttons / inputs ------------------------------ */

.btn {
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
  padding: 6px 11px;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  text-decoration: none;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.btn .ico { width: 14px; height: 14px; color: var(--muted); }
.btn-primary .ico, .btn-danger .ico { color: currentColor; }
.btn:hover:not(:disabled) { background: var(--panel-3); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary {
  background: var(--accent-btn);
  border-color: var(--accent-btn);
  color: var(--accent-fg);
}
.btn-primary:hover:not(:disabled) { filter: brightness(1.09); background: var(--accent-btn); }
.btn-primary:active:not(:disabled) { filter: brightness(.94); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover:not(:disabled) { background: var(--panel-3); color: var(--text); }
.btn-danger { color: var(--danger); border-color: var(--border-2); }
.btn-danger:hover:not(:disabled) { background: var(--danger-soft); }
.btn-block { width: 100%; justify-content: center; }
.btn-stop { background: var(--danger); border-color: var(--danger); color: var(--danger-fg); }
.btn-stop .ico { color: currentColor; }

/* a quiet inline action - used in .run-hd, .pv-hd and the board header */
.linky {
  font: inherit; font-size: 11.5px; color: var(--muted); text-decoration: none;
  background: none; border: 0; padding: 2px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
}
.linky:hover { color: var(--accent-ink); }
.linky .ico { width: 13px; height: 13px; }

/* ---------------------------- icon buttons ---------------------------- */
/* ONE primitive for a control that is only an icon. The card actions each had
   their own size (20px), colour and hover, none had a visible focus ring, and
   the Save star had no styles at all — it rendered as the browser's default
   button. 24px meets WCAG 2.5.8 on desktop; a touch screen gets 40px.
   States: hover, active, focus-visible, disabled, pressed/on, busy, danger. */
.ib {
  --ib: 24px;
  display: inline-grid; place-items: center; flex: none;
  width: var(--ib); height: var(--ib); padding: 0;
  border: 1px solid transparent; border-radius: var(--r-xs);
  background: transparent; color: var(--muted); cursor: pointer;
  transition: color .12s ease, background-color .12s ease;
}
.ib svg { width: 14px; height: 14px; }
.ib:hover:not(:disabled) { color: var(--text); background: var(--panel-3); }
.ib:active:not(:disabled) { background: var(--border); }
.ib:focus-visible { outline: none; box-shadow: var(--focus); }
.ib:disabled, .ib[aria-disabled="true"] { opacity: .35; cursor: default; }
.ib.is-on, .ib[aria-pressed="true"] { color: var(--accent); }
.ib[aria-pressed="true"] svg { fill: currentColor; }
.ib.is-busy { cursor: progress; }
.ib.is-busy svg { animation: spin 1s linear infinite; }
.ib.is-danger:hover:not(:disabled) { color: var(--danger); background: var(--danger-soft); }
@media (pointer: coarse) {
  .ib { --ib: 40px; }
  .ib svg { width: 18px; height: 18px; }
}
@media (prefers-reduced-motion: reduce) {
  .ib { transition: none; }
  .ib.is-busy svg { animation: none; }
}

.iconbtn {
  width: 26px; height: 26px; flex: none; display: grid; place-items: center;
  border-radius: var(--r-xs); border: 0; background: transparent;
  color: var(--muted); cursor: pointer;
}
.iconbtn:hover:not(:disabled) { background: var(--panel-3); color: var(--text); }
.iconbtn:disabled { opacity: .35; cursor: default; }

/* ------------------------------ popup menus ------------------------------ */
.menu-anchor { position: relative; }
.menu {
  position: absolute; z-index: 60; left: 0; top: calc(100% + 6px);
  min-width: 210px; max-width: 300px;
  padding: var(--s1);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r); box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: 1px;
  max-height: 60vh; overflow-y: auto; scrollbar-width: thin;
}
.menu[hidden] { display: none; }
.menu-up { top: auto; bottom: calc(100% + 6px); }
.menu-row {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; padding: 6px 8px; border: 0; border-radius: var(--r-xs);
  background: transparent; font: inherit; font-size: 13px; color: var(--text-2);
  cursor: pointer; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.menu-row:hover { background: var(--panel-3); color: var(--text); }
.menu-row .ico { color: var(--muted); }
.menu-row.is-active { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }
.menu-row.is-active .ico { color: var(--accent); }
.menu-sep { height: 1px; background: var(--border); margin: var(--s1) 2px; }
.menu-switch { padding: 6px 8px; }
.menu-field { display: flex; align-items: center; gap: var(--s2); padding: 4px 8px; }
.menu-field .ico { color: var(--muted); }
.menu-field-label { font-size: 12px; color: var(--muted); }
.menu-field .input { width: 96px; }
.menu-hint { margin: 2px 8px 4px; font-size: 11px; color: var(--muted); }

.linkbtn {
  font: inherit; font-size: 12px; font-weight: 500;
  background: none; border: 0; padding: 2px 4px;
  color: var(--text-2); cursor: pointer; text-decoration: none;
}
.linkbtn:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }

.input {
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  width: 100%;
}
.input-sm { padding: 4px 7px; font-size: 12.5px; }
.input:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus); }

.sel {
  font: inherit; font-size: 13px;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
  padding: 5px 8px;
  max-width: 190px;
  cursor: pointer;
}
.sel:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus); }

/* ------------------------------ login ------------------------------ */

.login-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  padding: var(--s4);
}
.login-overlay[hidden] { display: none; }
.login-card {
  width: 100%; max-width: 340px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  padding: var(--s6) var(--s5) var(--s5);
  display: flex; flex-direction: column; gap: var(--s3);
}
.login-title { font-size: 20px; margin: 0; letter-spacing: -.01em; }
.login-sub { margin: 0; color: var(--muted); font-size: 13px; }
.login-error { margin: 0; min-height: 18px; color: var(--danger); font-size: 13px; }
.login-card.shake { animation: shake .38s cubic-bezier(.36,.07,.19,.97); }
@keyframes shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-7px); }
  40%, 60% { transform: translateX(7px); }
}
@media (prefers-reduced-motion: reduce) { .login-card.shake { animation: none; } }

/* ------------------------------ the shell ------------------------------ */
/* Four columns — sidebar, thread, splitter, preview. Each one owns its own
   scroller, the body never scrolls. The preview column's width lives in
   --preview-w; app.js writes a clamped px value onto .shell while dragging, so
   the media-query defaults below act as the first-run value only. */

.shell {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0,1fr) var(--split-w) var(--preview-w);
  height: 100%;
}
.shell.is-preview-off { grid-template-columns: var(--rail-w) minmax(0,1fr) var(--split-w) 0px; }
.shell.is-preview-off .preview { display: none; }
/* The preview rail belongs to the Creative Studio. On Explore, Tracking,
   Stores and Settings it showed Nefha's own renders beside a table of other
   people's stores and took ~390px from it. This is a CONTEXT class, separate
   from .is-preview-off (the owner's persisted choice), so leaving the Studio
   never overwrites whether he had the rail open or how wide he dragged it. */
.shell.is-preview-context-off { grid-template-columns: var(--rail-w) minmax(0,1fr) 0px 0px; }
.shell.is-preview-context-off .preview,
.shell.is-preview-context-off .splitwrap { display: none; }

/* ------------------------------ splitter ------------------------------ */
/* A 6px column: a hairline you can actually grab, plus a collapse handle
   floated over it. The separator itself holds no interactive children, so the
   `separator` role stays honest. */

.splitwrap { position: relative; width: var(--split-w); background: var(--bg); z-index: 3; }
.splitter {
  position: absolute; inset: 0;
  cursor: col-resize;
  display: grid; place-items: center;
  touch-action: none;                 /* or the browser pans instead of dragging */
  border-radius: var(--r-pill);
}
/* the visible hairline — the grabbable area is 3x wider than what you see */
.splitter::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px; margin-left: -.5px;
  background: var(--border);
  transition: background .14s ease, width .14s ease, margin-left .14s ease;
}
.splitter:hover::before, .splitter.is-dragging::before {
  background: var(--accent); width: 2px; margin-left: -1px;
}
.splitter:focus-visible { outline: none; box-shadow: none; }
.splitter:focus-visible::before { background: var(--accent); width: 3px; margin-left: -1.5px; }
.splitter:focus-visible .splitter-grip { border-color: var(--accent); box-shadow: var(--focus); }
/* three dots, only while the pointer is near — a permanent grip is visual noise */
.splitter-grip {
  position: relative; z-index: 1;
  width: 5px; height: 26px; border-radius: var(--r-pill);
  background: var(--panel); border: 1px solid var(--border);
  opacity: 0; transition: opacity .14s ease;
}
.splitter:hover .splitter-grip,
.splitter:focus-visible .splitter-grip,
.splitter.is-dragging .splitter-grip { opacity: 1; }
.splitter.is-dragging .splitter-grip { border-color: var(--accent); }

/* while dragging, nothing else may claim the pointer or show a text caret */
body.is-splitting { cursor: col-resize; user-select: none; }
body.is-splitting iframe, body.is-splitting .pv-scroll, body.is-splitting .transcript { pointer-events: none; }

/* Expand-only, and only while the panel is closed.
   It used to sit invisibly in the middle of the bar at every width, which meant
   the one spot a person naturally grabs to drag was actually a collapse button.
   Closing now happens from the labelled control in the preview header; this is
   purely the way back. */
.split-toggle {
  position: absolute; z-index: 2; top: 50%; right: 0;
  transform: translateY(-50%);
  width: 17px; height: 54px; padding: 0;
  display: none; place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-sm) 0 0 var(--r-sm);
  background: var(--panel); color: var(--muted);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: color .14s ease, background .14s ease;
}
.split-toggle .ico { width: 13px; height: 13px; }
.shell.is-preview-off .split-toggle { display: grid; }
.split-toggle:hover { color: var(--accent-ink); background: var(--panel-3); }
.shell.is-preview-off .splitter { cursor: default; }
.shell.is-preview-off .splitter::before { background: var(--border); width: 1px; margin-left: -.5px; }
.shell.is-preview-off .splitter-grip { display: none; }

@media (prefers-reduced-motion: reduce) {
  .splitter::before, .splitter-grip, .split-toggle { transition: none; }
}

/* ------------------------------ sidebar ------------------------------ */

/* The rail re-points the shell tokens inside itself rather than restating every
   nav rule in a dark variant. One consequence worth knowing: any rule that uses
   --text / --muted / --panel-3 inside .side now reads the rail's values
   automatically, so the nav, recents, theme toggle and usercard all followed
   this change without being touched. The accent is re-pointed too — the ink
   blues are tuned for white and vanish on this ground. */
.side {
  --panel:    var(--rail-2);
  --panel-2:  var(--rail-2);
  --panel-3:  var(--rail-2);
  --text:     var(--rail-text);
  --text-2:   var(--rail-text-2);
  --muted:    var(--rail-muted);
  --border:   var(--rail-line);
  --border-2: var(--rail-line);
  --accent:      #8FA3F7;
  --accent-ink:  #9DB0FF;
  --accent-soft: var(--rail-2);
  --shadow-xs: 0 1px 2px rgba(0,0,0,.35);

  background: var(--rail-bg);
  border-right: 1px solid var(--rail-bg);
  color: var(--rail-text);
  display: flex; flex-direction: column;
  min-height: 0; padding: var(--s3); gap: var(--s2);
}
/* The brand avatar was --accent-btn, which is the same blue as the rail and
   disappeared into it. On the rail it inverts: light chip, rail-coloured mark. */
.side .avatar { background: var(--rail-text); color: var(--rail-bg); }

/* The mycrea mark, painted in the rail's own text colour rather than dropped on
   as a black tile. It is masked, not <img>-coloured, so it follows the theme.
   The mask uses mycrea-wordmark.svg — the SAME artwork with the background
   plate removed: masking the full icon would have matched that opaque rounded
   square and rendered a solid block, since an SVG mask reads alpha. */
.usercard-mark {
  /* 34, not 30: the artwork carries its own margin, so the glyph inside a 30px
     box measured smaller than the avatar letter it replaced. */
  width: 34px; height: 34px; flex: none;
  -webkit-mask: url("/mycrea-wordmark.svg") center / contain no-repeat;
  mask: url("/mycrea-wordmark.svg") center / contain no-repeat;
  background: var(--rail-text);
}
.side-scrim { display: none; }

.brandpill {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; padding: 6px 8px;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--r);
  font: inherit; color: var(--text); cursor: pointer; text-align: left;
}
.brandpill:hover { background: var(--panel-3); }
.avatar {
  width: 26px; height: 26px; flex: none; border-radius: var(--r-xs);
  display: grid; place-items: center;
  background: var(--accent-btn); color: var(--accent-fg);
  font-size: 12px; font-weight: 700; letter-spacing: .01em;
  text-transform: uppercase;
}
.avatar-lg { width: 32px; height: 32px; border-radius: var(--r-sm); font-size: 13px; }
.brandpill-name {
  font-size: 13px; font-weight: 600; flex: 1 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.brandpill .ico { color: var(--muted); width: 14px; height: 14px; }
#brandMenu { width: calc(var(--sidebar-w) - 2 * var(--s3)); }

.newchat {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; height: 32px; padding: 0 var(--s2);
  background: transparent; border: 1px solid transparent; border-radius: var(--r-sm);
  font: inherit; font-size: 13px; font-weight: 500; color: var(--text);
  cursor: pointer; text-align: left;
}
.newchat:hover { background: var(--panel-3); }
.newchat .ico { color: var(--accent); }
.newchat > span:not(.kbd) { flex: 1 1 auto; }
.kbd {
  margin-left: auto; font-family: var(--mono); font-size: 10.5px;
  color: var(--muted); background: var(--panel-3);
  border: 1px solid var(--border); border-radius: var(--r-xs); padding: 1px 5px;
}

.nav { display: flex; flex-direction: column; gap: 2px; margin-top: var(--s2); }
.nav-row {
  display: flex; align-items: center; gap: var(--s2);
  height: 32px; padding: 0 var(--s2); border-radius: var(--r-sm);
  font: inherit; font-size: 13px; color: var(--text-2);
  background: transparent; border: 0; cursor: pointer; text-align: left; width: 100%;
}
.nav-row .ico { color: var(--muted); }
.nav-row:hover:not([disabled]) { background: var(--panel-3); color: var(--text); }
/* "You are here" is ONE cue: the fill, plus weight. The old 2px accent bar
   and tinted icon said the same thing twice more, which is exactly the
   over-decorated tell the redesign removes (21st.dev 19361: active = token
   shift + weight, nothing else). */
.nav-row.is-active {
  background: var(--panel-3); color: var(--text); font-weight: 600;
}
/* A "Soon" row dims its own content, not the row: whole-row opacity stacked on
   --muted pushed the Soon tag to ~1.9:1 — the least legible text in the app. */
.nav-row[disabled] { cursor: default; }
.nav-row[disabled] .ico, .nav-row[disabled] .nav-label { opacity: .5; }
.nav-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-count {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* A tag, not a whisper: the owner wants visitors to SEE that video is coming. */
.nav-soon {
  font-size: 10.5px; font-weight: 600; line-height: 1; letter-spacing: .02em;
  color: var(--accent-ink); background: var(--accent-soft);
  padding: 3px 6px; border-radius: var(--r-pill); flex: 0 0 auto;
}
/* The Competitors badge is UNREAD (new ads since last look), not a total —
   the Board badge next to it is a total. Same chip, two meanings, was the
   last place the app contradicted itself; the accent marks this one as news
   (a status that is true right now — DESIGN.md gives it the colour). */
#navCompetitorsUnread { color: var(--accent-ink); font-weight: 600; }

.side-sep { height: 1px; background: var(--border); margin: var(--s3) 2px var(--s2); }

.recents {
  display: flex; flex-direction: column; gap: 1px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin;
  margin: 0 -2px; padding: 0 2px;
}
.recents .label { padding: 0 var(--s2) var(--s1); }
.recents-empty { margin: 2px var(--s2); font-size: 12px; color: var(--muted); }
.recent {
  display: block; width: 100%; text-align: left;
  height: 28px; line-height: 28px; padding: 0 var(--s2); border: 0; border-radius: var(--r-sm);
  background: transparent; font: inherit; font-size: 12.5px; color: var(--muted);
  cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recent:hover { background: var(--panel-3); color: var(--text-2); }
/* chosen looks the same here as in the nav above: fill + weight, one grammar */
.recent.is-active { background: var(--panel-3); color: var(--text); font-weight: 600; }

/* ---- theme toggle ---- */
/* Three radios in a track, same shape as .seg so the app has one segmented
   idiom rather than two. Collapses to an icon column in the 64px rail. */
.theme-seg {
  display: flex; gap: 2px; padding: 2px; flex: none;
  margin-top: var(--s2);
  background: var(--panel-3); border-radius: var(--r-sm);
}
.theme-opt {
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  padding: 4px 6px; border: 0; border-radius: var(--r-xs);
  background: transparent; font: inherit; font-size: 11.5px; font-weight: 500;
  color: var(--muted); cursor: pointer; white-space: nowrap;
  transition: background .12s ease, color .12s ease;
}
.theme-opt .ico { width: 14px; height: 14px; }
.theme-opt:hover { color: var(--text-2); }
.theme-opt[aria-checked="true"] {
  background: var(--panel); color: var(--text); box-shadow: var(--shadow-xs);
}
.theme-opt[aria-checked="true"]:focus-visible { box-shadow: var(--shadow-xs), var(--focus); }

.theme-opt-txt { overflow: hidden; text-overflow: ellipsis; }
@media (prefers-reduced-motion: reduce) { .theme-opt { transition: none; } }

.usercard-anchor { margin-top: var(--s2); flex: none; }
.usercard {
  display: flex; align-items: center; gap: var(--s2);
  padding: 6px 8px; border-radius: var(--r);
}
.usercard-txt { min-width: 0; flex: 1 1 auto; display: block; }
.usercard-name { display: block; font-size: 12.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usercard-mail { display: block; font-size: 11px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#userMenu, #settingsMenu { right: 0; left: auto; }

/* the build this page is talking to — tiny, factual, next to the account
   menu button. Its whole job is making "am I on the latest?" a glance. */
.build-stamp {
  flex: none; align-self: center;
  font-size: 10px; color: var(--muted); opacity: .75;
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.build-stamp:empty { display: none; }
/* A release id can be long (sha + date): it never pushes the name out of the
   account card — it truncates, and its title carries the whole id. */
.build-stamp { max-width: 9ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; min-width: 0; }

/* ------------------------------ thread column ------------------------------ */

.thread {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0; background: var(--bg);
  position: relative;
}

.thread-hd {
  flex: none; display: flex; align-items: center; gap: var(--s2);
  padding: 0 var(--s5); height: 52px;
  border-bottom: 1px solid var(--border);
}
.thread-title {
  /* an <h1> for the document outline (AUDIT.md A5); the browser's default
     heading margin and size must not leak into the header bar */
  margin: 0;
  font-size: 14px; font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.rail-toggle { display: none; }

.seg {
  display: inline-flex; padding: 2px; gap: 2px; flex: none;
  background: var(--panel-3); border-radius: var(--r-sm);
}
.seg button {
  font: inherit; font-size: 12px; font-weight: 500; color: var(--muted);
  background: transparent; border: 0; border-radius: var(--r-xs);
  padding: 3px 10px; cursor: pointer; white-space: nowrap;
}
.seg button:hover { color: var(--text-2); }
/* competitors.js also toggles .is-active on these buttons for its own
   bookkeeping - the selected look lives on .is-sel so the two never fight. */
.seg button.is-sel { background: var(--panel); color: var(--text); box-shadow: var(--shadow-xs); }
.seg button:focus-visible { outline: none; box-shadow: var(--focus); }
/* no opacity: it multiplied against --muted and landed at 4.47:1, just under
   AA. The dimming is expressed in the colour instead (AUDIT.md A4). */
.seg-count { font-family: var(--mono); font-size: 10.5px; color: var(--muted); margin-left: 3px; font-variant-numeric: tabular-nums; }
.seg-sm button { font-size: 11px; padding: 2px 8px; }

.cost {
  font-family: var(--mono);
  font-size: 11.5px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  letter-spacing: -0.01em;
}
.badges { display: flex; gap: var(--s1); }
.badge {
  font-size: 11px; line-height: 1.6;
  padding: 1px 8px; border-radius: var(--r-pill);
  background: var(--panel-3); color: var(--muted);
  border: 1px solid var(--border); white-space: nowrap;
}

.switch { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 30px; height: 17px; border-radius: var(--r-pill);
  background: var(--border-2); position: relative;
  transition: background .15s ease; flex: 0 0 auto;
}
.switch-knob {
  position: absolute; top: 2px; left: 2px;
  width: 13px; height: 13px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.3);
  transition: transform .15s ease;
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track .switch-knob { transform: translateX(13px); }
.switch input:focus-visible + .switch-track { box-shadow: var(--focus); }
.switch-label { font-size: 12.5px; color: var(--text-2); white-space: nowrap; }

/* the three panes stack inside .thread; exactly one carries .is-active */
.pane {
  flex: 1 1 auto; min-height: 0; min-width: 0;
  display: none; flex-direction: column; overflow: hidden;
}
.pane.is-active { display: flex; }

/* ------------------------------ chat pane ------------------------------ */

.chat-pane { position: relative; }

.brand-empty {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
  padding: var(--s5);
}
.brand-empty[hidden] { display: none; }
.brand-empty-card {
  max-width: 380px; width: 100%;
  text-align: left;
}
.brand-empty-card h2 { margin: 0 0 var(--s2); font-size: 18px; letter-spacing: -.01em; }
.brand-empty-card p { margin: 0 0 var(--s4); color: var(--muted); font-size: 13.5px; }
.brand-empty-form { display: flex; gap: var(--s2); }
.brand-empty-form .input { flex: 1 1 auto; }
.hint { font-size: 12px; color: var(--muted); margin: var(--s3) 0 0 !important; }
.hint code, .menu-hint code {
  font-family: var(--mono); font-size: .92em;
  background: var(--panel-3); border-radius: var(--r-xs); padding: 0 4px;
}

.chat-body { flex: 1 1 auto; min-height: 0; position: relative; display: flex; }
/* One centred reading column: the padding does the centring so `align-self`
   on the user bubble still works. */
.transcript {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--s5) max(var(--s5), calc((100% - 760px) / 2)) var(--s3);
  display: flex; flex-direction: column; gap: var(--s5);
  scrollbar-width: thin;
  overscroll-behavior: contain;
}
/* The scroller is also the flex container, so its children would otherwise be
   SHRUNK to fit the viewport instead of overflowing it — which silently clips
   the bottom of a tall run card. `min-width:0` + `max-width:100%` let a child
   be as tall as it likes but never WIDER than the column: without them a long
   code block or a wide option grid inflates the whole message past the reading
   width and the second and third option cards fall off the right edge. */
.transcript > * { flex: 0 0 auto; min-width: 0; max-width: 100%; }
/* Chrome focuses scroll containers for keyboard users; keep the affordance but
   do not paint a full-height halo around the reading column */
.transcript:focus-visible, .pv-scroll:focus-visible, .board-scroll:focus-visible {
  box-shadow: inset 0 0 0 2px var(--accent-line);
}

.new-pill {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: var(--s2); z-index: 5;
  display: inline-flex; align-items: center; gap: 5px;
  font: inherit; font-size: 12px; font-weight: 500;
  background: var(--accent-btn); color: var(--accent-fg);
  border: 0; border-radius: var(--r-pill); padding: 5px 12px;
  box-shadow: var(--shadow); cursor: pointer;
}
.new-pill .ico { width: 13px; height: 13px; }

/* drag-and-drop overlay: the whole run pane is the drop target now */
.drop-overlay {
  position: absolute; inset: 0; z-index: 40;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent-soft) 88%, transparent);
  outline: 2px dashed var(--accent); outline-offset: -10px;
}
.drop-card {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s4); border-radius: var(--r);
  background: var(--panel); border: 1px solid var(--accent-line);
  box-shadow: var(--shadow); font-size: 13px; color: var(--text-2);
}
.drop-card .ico { color: var(--accent); }

/* messages */
.msg { display: flex; flex-direction: column; max-width: 78%; gap: 4px; min-width: 0; }
.msg-user { align-self: flex-end; align-items: flex-end; }
/* gap 24 + margin -16 = 8px: the answer sits with its question, and the NEXT
   assistant turn keeps the full 24px — turns group into exchanges */
.msg-assistant + .msg-user { margin-top: calc(var(--s2) - var(--s5)); }
.msg-assistant { align-self: stretch; align-items: flex-start; max-width: 100%; min-width: 0; }
/* the answer column and its content must be allowed to shrink, so a wide child
   (a <pre>, an option grid) scrolls or wraps inside instead of stretching the
   message. Without this, min-width:auto on a flex-column item takes the child's
   intrinsic width and the message runs off the page. */
.msg > *, .msg-bubble, .msg-body { min-width: 0; max-width: 100%; }
.msg-bubble {
  padding: 9px 13px;
  border-radius: var(--r-lg);
  background: var(--panel-3);
  border: 1px solid var(--border);
  overflow-wrap: anywhere;
}
.msg-assistant .msg-bubble {
  /* the assistant's answer is the page, not a bubble */
  background: none; border: 0; padding: 0; border-radius: 0;
  font-size: 14px; color: var(--text);
}
.msg-user .msg-bubble {
  background: var(--panel-3); color: var(--text);
  border: 1px solid var(--border); font-size: 13.5px;
  border-bottom-right-radius: var(--r-xs);
}
.msg-auto { opacity: .78; max-width: 70%; }
.msg-auto .msg-bubble {
  font-size: 12.5px; padding: 6px 10px; background: var(--panel-2);
  color: var(--muted); border: 1px dashed var(--border-2);
}
.msg-tag {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); padding: 0 4px;
}
.msg-time {
  font-size: 11px; color: var(--muted); padding: 0 4px;
  opacity: 0; transition: opacity .12s ease;
}
.msg:hover .msg-time, .msg:focus-within .msg-time { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .msg-time { transition: none; } }

/* ---- the hat the agent is wearing ---- */
/* A badge and a name above the answer, from the bold label the agent puts on
   its first line. No hat named, no row: an unlabelled message renders exactly
   as it always did. */
.msg-role {
  display: flex; align-items: center; gap: var(--s2);
  margin-bottom: 6px;
}
/* The badge is a <canvas> carrying a pattern generated from the role id — see
   public/agent-avatar.mjs. It stays a 22px circle so the row does not move, and
   keeps the tint border, which is what ties it to the name beside it.
   `.role-badge .ico` still applies because a browser without a 2d context falls
   back to the sprite glyph rather than an empty hole. */
.role-badge {
  width: 22px; height: 22px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--role-line); background: var(--role-soft); color: var(--role-ink);
  /* the pattern is drawn to the edge; the border is the only chrome */
  overflow: hidden;
}
.role-badge .ico { width: 13px; height: 13px; }
/* two hats at once: their avatars sit shoulder to shoulder */
.role-badges { display: inline-flex; align-items: center; gap: 3px; flex: none; }
.role-name {
  font-size: 12px; font-weight: 600; letter-spacing: .01em;
  color: var(--role-ink);
}
/* one tint per role, both themes, defined off the palette tokens */
.role-accent { --role-ink: var(--accent-ink); --role-soft: var(--accent-soft); --role-line: var(--accent-line); }
.role-indigo { --role-ink: var(--indigo);     --role-soft: var(--indigo-soft); --role-line: var(--indigo-line); }
.role-green  { --role-ink: var(--ok);         --role-soft: var(--ok-soft);     --role-line: var(--ok-line); }
.role-violet { --role-ink: var(--violet);     --role-soft: var(--violet-soft); --role-line: var(--violet-line); }
.role-amber  { --role-ink: var(--warn);       --role-soft: var(--warn-soft);   --role-line: var(--warn-line); }
.msg-typing::after {
  content: "▍"; margin-left: 1px; animation: blink 1s steps(2) infinite; color: var(--muted);
}
@keyframes blink { 50% { opacity: 0; } }

/* markdown */
.md > *:first-child { margin-top: 0; }
.md > *:last-child { margin-bottom: 0; }
.md h1 { font-size: 17px; margin: var(--s4) 0 var(--s2); letter-spacing: -.01em; }
.md h2 { font-size: 15.5px; margin: var(--s4) 0 var(--s2); }
.md h3 { font-size: 14.5px; margin: var(--s3) 0 var(--s1); color: var(--text-2); }
.md p { margin: 0 0 var(--s2); }
.md ul, .md ol { margin: 0 0 var(--s2); padding-left: 20px; }
.md li { margin: 2px 0; }
.md li > ul, .md li > ol { margin: 2px 0; }
.md code {
  font-family: var(--mono); font-size: .88em;
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: var(--r-xs); padding: 0 4px;
}
.md pre {
  margin: 0 0 var(--s2);
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: var(--s3);
  overflow-x: auto;
  max-width: 100%;   /* scroll the long lines inside, never widen the message */
}
.md pre code { background: none; border: 0; padding: 0; font-size: 12.5px; line-height: 1.55; }
.md a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
.md blockquote {
  margin: 0 0 var(--s2); padding: 2px 0 2px var(--s3);
  border-left: 3px solid var(--border-2); color: var(--muted);
}
.md hr { border: 0; border-top: 1px solid var(--border); margin: var(--s3) 0; }
.md .table-wrap { overflow-x: auto; margin: 0 0 var(--s2); }
.md table { border-collapse: collapse; font-size: 13px; width: 100%; }
.md th, .md td {
  border: 1px solid var(--border); padding: 6px 10px; text-align: left; vertical-align: top;
  /* NOT `anywhere` - that makes min-content one character wide and a two-letter
     column header wraps mid-word when the table is squeezed */
  overflow-wrap: break-word;
}
.md th { white-space: nowrap; }
.md td:last-child, .md th:last-child { font-variant-numeric: tabular-nums; }
.md th { background: var(--panel-3); font-weight: 600; }
.msg-user .md a { color: inherit; }
.msg-user .md code, .msg-user .md pre, .msg-user .md th {
  background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.25);
}

/* notices */
.notice {
  align-self: stretch;
  border-radius: var(--r); padding: var(--s3);
  font-size: 13.5px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  display: flex; flex-direction: column; gap: var(--s2); align-items: flex-start;
}
.notice-error { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.notice-warn  { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.notice-title { font-weight: 600; }
.notice p { margin: 0; }

/* ---- empty-chat backdrop: a masked dot pattern behind the greeting ---- */
/* The dot grid fills the empty transcript and fades to nothing at the edges
   via a radial mask, so it reads as a calm texture, not a busy field. It sits
   behind the greeting notice and disappears the instant the first message
   lands (the whole block is removed). */
/* fills the empty transcript so the dots have room; width stays 100% of the
   reading column, never the viewport, so it cannot bring back h-overflow */
.transcript > .empty-chat { flex: 1 1 auto; }
.empty-chat {
  position: relative; align-self: stretch;
  min-height: min(72vh, 640px);
  /* centre the hero AND the cards as one group. The hero used to carry
     margin:auto, which centred it alone and left the cards stranded at the
     bottom of the canvas, a screen away from the thing they belong to. */
  display: flex; flex-direction: column; justify-content: center; align-items: center;
}
/* The dot field is a FULL-PANE backdrop: it fills .chat-body edge to edge
   (past the 760px reading column) and sits behind the transcript, which is
   transparent, so the dots read across the whole empty canvas. */
.chat-backdrop {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none;
}
.chat-body > .transcript { position: relative; z-index: 1; }
.dot-pattern {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: 100%;
  pointer-events: none;
  fill: var(--dot-ink, var(--border-2));
  /* near-full coverage with a soft outer fade, so dots reach almost to the
     pane edges instead of hugging the centre column */
  -webkit-mask-image: radial-gradient(ellipse 78% 88% at 50% 46%, #000 62%, transparent 100%);
  mask-image: radial-gradient(ellipse 78% 88% at 50% 46%, #000 62%, transparent 100%);
}
.empty-chat .notice { position: relative; z-index: 1; }

/* The empty-state hero: the greeting IS the design. Derja set large in Plex
   Sans Arabic, one quiet sub-line, no box, centred in the free canvas. */
.empty-hero {
  position: relative; z-index: 1;
  margin: 0 auto;             /* the column centres the group; this centres the text */
  padding: var(--s6) var(--s5);
  text-align: center;
  max-width: 560px;
}
.empty-hero-ar {
  margin: 0 0 var(--s3);
  font-family: var(--font-arabic);
  font-size: clamp(34px, 5vw, 52px);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  letter-spacing: 0;
}
.empty-hero-sub {
  margin: 0;
  font-size: 15px;
  color: var(--muted);
}
:root { --dot-ink: rgba(20, 20, 19, .16); }
:root:not([data-theme="light"]) { --dot-ink: rgba(255, 255, 255, .12); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --dot-ink: rgba(255, 255, 255, .12); } }
:root[data-theme="dark"] { --dot-ink: rgba(255, 255, 255, .12); }
:root[data-theme="light"] { --dot-ink: rgba(20, 20, 19, .16); }

/* ------------------------------ the step timeline ------------------------------ */
/* One .run card per assistant turn, holding a vertical rail of steps.
   Liveness comes from timeline.mjs's phaseOf(); every class here reads it. */

.run {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel); box-shadow: var(--shadow-xs);
  overflow: hidden;
}
.run-hd {
  display: flex; align-items: center; gap: var(--s2);
  padding: 10px var(--s4); border-bottom: 1px solid var(--border);
  background: var(--panel-2);
}
.run-hd .label {
  flex: 1 1 auto;
  /* sentence case: `.label`'s uppercase turned the duration into "0.1S" */
  text-transform: none; letter-spacing: 0; font-size: 12.5px; font-weight: 600;
  color: var(--text-2); font-variant-numeric: tabular-nums;
}
.run-hd .ld { flex: 1 1 auto; }
/* The run card no longer lists its steps — the owner does not want visitors
   reading "Noting it in a file" or "Saving ad #". It shows only while an ad
   is being created (an orb and one line), goes away once the ad is in the
   preview, and stays to say so if the render failed. */
.run { display: none; }
.run.has-render { display: block; }
.run.has-render.render-ok { display: none; }
.run .steps, .run .run-hd > .linky { display: none; }
.run.render-fail .ld-label { color: var(--text); }
.run-retry { margin-left: auto; }
.orb { display: block; flex: 0 0 auto; width: 20px; height: 20px; }
.ld-cyc { gap: 9px; }
.run-hd .spacer { flex: 0 0 auto; }

/* ---- the pixel-grid loader ---- */
/* A 3x3 grid of cells with staggered pulse delays makes a bright wavefront
   sweep across, next to a shimmering label and a live elapsed clock. The maths
   is in loader.mjs; the animation is delays only, so reduced motion just holds
   every cell dim and the clock keeps ticking. */
.ld { display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
.ld-grid {
  flex: none; display: grid; gap: 1.5px;
  grid-template-columns: repeat(3, 4px); grid-template-rows: repeat(3, 4px);
}
.ld-grid.is-round .ld-cell { border-radius: 50%; }
.ld-cell {
  width: 4px; height: 4px; border-radius: 1px;
  background: var(--text-2); opacity: .15;
}
.ld-cell.is-off { opacity: .07; animation: none !important; }

/* the label shimmers a highlight left-to-right across itself */
.ld-label {
  font-size: 12.5px; font-weight: 600; letter-spacing: 0;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  color: var(--text-2);
  background-image: linear-gradient(90deg, var(--muted) 35%, var(--text) 50%, var(--muted) 65%);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: ld-shimmer 1.4s linear infinite;
}
.ld-clock {
  flex: none; font-size: 11.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* a finished run: the grid holds still and the label stops shimmering */
.ld.is-frozen .ld-cell { animation: none !important; opacity: .22; }
.ld.is-frozen .ld-label {
  animation: none; background: none;
  -webkit-text-fill-color: currentColor; color: var(--text-2);
}

@keyframes ld-pixel { 0%, 100% { opacity: .12; } 50% { opacity: 1; } }
@keyframes ld-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

/* ---- the cycling shimmer-text loader ---- */
/* The "Thinking… / Composing…" line in the empty answer bubble while the agent
   works. One shimmering word that fades up as it swaps to the next. Sits at the
   transcript's own body size — it is standing in for the answer, not a caption. */
.ld-cyc { display: inline-flex; align-items: center; min-height: 1.5em; }
.ld-cyc-word {
  display: inline-block;
  font-size: 15px; font-weight: 600; letter-spacing: -0.01em; white-space: nowrap;
  color: var(--text-2);
  background-image: linear-gradient(90deg, var(--muted) 30%, var(--text) 50%, var(--muted) 70%);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: ld-shimmer 2.2s linear infinite;
}
/* re-triggered on every swap by toggling the class in JS */
.ld-cyc-word.is-in { animation: ld-shimmer 2.2s linear infinite, ld-cyc-in .32s ease both; }
@keyframes ld-cyc-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  /* no shimmer, no slide — the words still cycle (that is content, not motion) */
  .ld-cyc-word, .ld-cyc-word.is-in {
    animation: none; background: none;
    -webkit-text-fill-color: currentColor; color: var(--text-2);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* hold the grid dim and stop the shimmer; the clock still ticks in JS */
  .ld-cell { animation: none !important; opacity: .18; }
  .ld-label {
    animation: none; background: none;
    -webkit-text-fill-color: currentColor; color: var(--text-2);
  }
}
.dot-live {
  width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--accent);
  animation: pulse 1.4s ease-in-out infinite;
}
.run.is-done .dot-live { background: var(--ok); animation: none; }
.run.is-fail .dot-live { background: var(--danger); animation: none; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .25 } }
.run.is-collapsed .steps { display: none; }
.run.is-collapsed .run-hd { border-bottom: 0; }

/* tabular figures stop the durations jittering as they tick */
.steps { padding: var(--s2) var(--s4) var(--s4); font-variant-numeric: tabular-nums; }

.step { position: relative; padding-left: 30px; }
.step::before {
  content: ""; position: absolute; left: 9px; top: 0; bottom: 0;
  width: 1px; background: var(--border);
}
.steps > *:first-child::before { top: 16px; }
.steps > *:last-child::before { bottom: auto; height: 16px; }

.step-hd {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 9px 0; border: 0; background: none;
  font: inherit; color: inherit; cursor: pointer; text-align: left;
}
.step-mark {
  position: absolute; left: 0; top: 9px;
  width: 19px; height: 19px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--ok-soft); color: var(--ok);
  box-shadow: 0 0 0 3px var(--panel);          /* punches a hole in the rail */
  transition: background .18s ease, color .18s ease;
}
.step-mark .ico { width: 12px; height: 12px; stroke-width: 2.5; }
.step-title {
  font-size: 13.5px; font-weight: 500; color: var(--text);
  flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere;
}
/* only the sub-line may ellipsise; without min-width:0 a long tool argument
   blows the row past the column width */
.step-sub {
  font-size: 12px; color: var(--muted);
  min-width: 0; flex: 0 1 auto; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.step-meta { font-size: 11.5px; color: var(--muted); flex: none; }
.step-caret { color: var(--muted); width: 14px; height: 14px; transition: transform .15s ease; flex: none; }

/* the grid trick animates to auto height with no JS measurement */
.step-art {
  display: grid; grid-template-rows: 1fr; padding: 2px 0 var(--s4);
  transition: grid-template-rows .28s cubic-bezier(.16,1,.3,1), opacity .2s ease;
}
.step-art > * { min-height: 0; overflow: hidden; }
.step.is-collapsed .step-caret { transform: rotate(-90deg); }
.step.is-collapsed .step-art { grid-template-rows: 0fr; opacity: 0; padding-bottom: 0; }
.step.is-empty .step-caret { visibility: hidden; }

.step.is-running .step-mark { background: var(--accent-soft); color: var(--accent); }
.step.is-running .step-title { color: var(--text-2); }
.step.is-running .step-mark .ico { animation: spin .9s linear infinite; }
.step.is-fail .step-mark { background: var(--danger-soft); color: var(--danger); }
.step.is-cancel .step-mark { background: var(--panel-3); color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .step.is-running .step-mark .ico { animation-duration: 3s; }
  .dot-live { animation: none; }
  /* .01ms, never `none`: `transition: none` emits no transitionend */
  .step-art { transition-duration: .01ms; }
  .msg-typing::after { animation: none; }
  /* colour fades are harmless, movement is not: kill transforms everywhere */
  .card, .ov-frame, .btn, .cbtn, .card-dl, .chip { transition-duration: .01ms; }
}

.step-err {
  font-size: 12.5px; color: var(--danger);
  background: var(--danger-soft); border: 1px solid var(--danger-line);
  border-radius: var(--r-sm); padding: 7px 9px; overflow-wrap: anywhere;
}

/* narration line: on the same rail, no mark, never a step */
.narr {
  position: relative; padding: 4px 0 4px 30px;
  font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere;
}
.narr::before { content: ""; position: absolute; left: 9px; top: 0; bottom: 0; width: 1px; background: var(--border); }
.narr::after {
  content: ""; position: absolute; left: 6.5px; top: 11px; width: 6px; height: 6px;
  border-radius: 50%; background: var(--border-2); box-shadow: 0 0 0 3px var(--panel);
}

/* ------------------------------ step artifacts ------------------------------ */

.art-cap { display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
.art-cap .n { font-size: 10.5px; font-weight: 700; color: var(--text-2); }

/* arbitrary images (view_image) - the cap is the important part: one tall
   portrait render and the whole rail scrolls off screen */
.thumbs { display: flex; gap: var(--s2); flex-wrap: wrap; }
.thumbs a { display: block; line-height: 0; }
.thumbs img {
  max-width: 100%; max-height: 22rem; width: auto; height: auto;
  object-fit: contain; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: var(--panel-3); display: block;
}
.thumbs.thumbs-sm img { height: 88px; width: 88px; object-fit: cover; }

/* reference-ad thumbs */
.refstrip { display: flex; gap: var(--s2); flex-wrap: wrap; }
.refcard {
  width: 108px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--panel-2); overflow: hidden;
}
.refcard .shot {
  width: 100%; height: 116px; object-fit: cover; display: block;
  border-bottom: 1px solid var(--border); background: var(--panel-3);
}
.refcard-ft { display: block; padding: 5px 7px; }
.refcard-stat { display: block; font-size: 11.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.refcard-sub { display: block; font-size: 10.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* product card */
.prodcard {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s2); max-width: 360px;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--panel-2);
}
.prodcard .shot { width: 46px; height: 46px; border-radius: var(--r-xs);
  border: 1px solid var(--border); object-fit: cover; flex: none; background: var(--panel-3); }
.prodcard-b { min-width: 0; }
.prodcard-name { display: block; font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
.prodcard-row { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.tag {
  font-size: 10.5px; color: var(--text-2); background: var(--panel-3);
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 0 7px;
}
.prodcard-size { font-size: 11.5px; color: var(--muted); }

/* colour swatches */
.swatches { display: flex; gap: var(--s2); flex-wrap: wrap; }
.sw { width: 62px; }
.sw-chip {
  height: 44px; border-radius: var(--r-sm); border: 0;
  /* a grey alpha ring, never rgba(0,0,0,..) - a black swatch vanishes in dark */
  box-shadow: inset 0 0 0 1px rgba(128,124,116,.35), var(--shadow-xs);
}
.sw-hex { font-family: var(--mono); font-size: 10px; color: var(--muted);
  margin-top: 4px; text-align: center; letter-spacing: -.01em; }

/* logo / type sheet card */
.logocard {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 148px; max-width: 100%; height: 62px; padding: 0 var(--s3);
  border-radius: var(--r); border: 1px solid var(--border); background: var(--panel-2);
}
.logocard img { max-width: 180px; max-height: 44px; object-fit: contain; display: block; }
.logocard-wide { height: auto; padding: var(--s2); }
.logocard-wide img { max-width: 100%; max-height: 180px; }

/* brand-facts card */
.facts {
  display: grid; grid-template-columns: auto 1fr; gap: 4px var(--s3);
  max-width: 460px; margin: 0;
  padding: var(--s3); border: 1px solid var(--border); border-radius: var(--r);
  background: var(--panel-2); font-size: 12.5px;
}
.facts dt { color: var(--muted); white-space: nowrap; }
.facts dd { margin: 0; color: var(--text-2); overflow-wrap: anywhere; }

/* generating creatives */
.gens { display: flex; gap: var(--s3); flex-wrap: wrap; }
.gen { width: 118px; }
.gen-frame {
  position: relative; aspect-ratio: 4/5; border-radius: var(--r-sm);
  border: 1px solid var(--border); overflow: hidden; background: var(--panel-3);
  padding: 0; cursor: pointer; display: block; width: 100%;
}
.gen-frame .shot { width: 100%; height: 100%; object-fit: cover; display: block; }
/* the centred spinner shown while a tile renders or finalizes — never a
   broken <img> box */
.gen-spin { position: absolute; inset: 0; display: grid; place-items: center; z-index: 1; color: var(--muted); }
.gen-spin .ico { width: 22px; height: 22px; animation: spin .9s linear infinite; }
@media (prefers-reduced-motion: reduce) { .gen-spin .ico { animation-duration: 2.4s; } }
.gen.is-busy .gen-frame::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 20%, var(--shimmer) 50%, transparent 80%);
  background-size: 220% 100%;
  animation: shimmer 1.5s linear infinite;
}
@keyframes shimmer { from { background-position: 140% 0 } to { background-position: -60% 0 } }
@media (prefers-reduced-motion: reduce) { .gen.is-busy .gen-frame::after { animation: none; } }
.gen-cap { display: flex; align-items: center; gap: 5px; margin-top: 6px;
  font-size: 11px; color: var(--muted); }
.gen-cap .ico { width: 12px; height: 12px; }
.gen.is-done .gen-cap { color: var(--ok); }
.gen.is-fail .gen-cap { color: var(--danger); }
.gen.is-busy .gen-cap .ico { animation: spin .9s linear infinite; }
.gen.is-sel .gen-frame { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* links artifact (web_search) */
.linklist { display: flex; flex-direction: column; gap: 3px; font-size: 12.5px; max-width: 520px; }
.linklist a { color: var(--accent-ink); text-decoration: none; overflow-wrap: anywhere; }
.linklist a:hover { text-decoration: underline; }

.spinner {
  width: 12px; height: 12px; display: inline-block;
  border: 2px solid var(--border-2); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* ------------------------------ composer ------------------------------ */

.composer-wrap {
  flex: none; padding: 0 var(--s5) var(--s4);
  background: var(--bg); position: relative;
}
.composer-wrap::before {
  content: ""; position: absolute; left: 0; right: 0; top: -28px; height: 28px;
  background: linear-gradient(to bottom, transparent, var(--bg)); pointer-events: none;
}
.composer-col { max-width: 760px; margin: 0 auto; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--s2); }
.chip {
  font: inherit; font-size: 12px;
  background: var(--panel); color: var(--text-2);
  border: 1px solid var(--border); border-radius: var(--r-pill);
  padding: 3px 11px; cursor: pointer;
}
.chip:hover:not(:disabled) { background: var(--panel-2); border-color: var(--border-2); color: var(--text); }
.chip:disabled { opacity: .5; cursor: not-allowed; }
/* The chips are not five equal choices (DESIGN §1.8): the main action reads as
   primary, the rest as quiet "or try". A thin divider separates the two groups. */
.chip.is-primary {
  background: var(--accent-btn); color: var(--accent-fg);
  border-color: var(--accent-btn); font-weight: 600;
}
.chip.is-primary:hover:not(:disabled) { background: var(--accent-ink); border-color: var(--accent-ink); color: var(--accent-fg); }
.chip-sep { width: 1px; align-self: stretch; margin: 2px 2px; background: var(--border); flex: none; }

/* A calm card, not a pill: two lines of room to type, a hairline border and a
   soft shadow. Focus is a border-colour change and nothing more — a ring
   around the whole card shouts at the owner every time they click into it. */
/* Composer polish drawn from 21st.dev's AI Prompt Box (johuniq, id 10442): a
   rounder card that lifts and glows softly the moment it takes focus, on the
   app's own accent — the modern chat-input feel, kept calm (no shouting halo). */
.composer {
  position: relative;
  border: 1px solid var(--border-2); border-radius: var(--r-xl);
  background: var(--panel); box-shadow: var(--shadow);
  padding: var(--s4) var(--s4) var(--s3);
  transition: border-color .16s ease, box-shadow .18s ease, transform .18s ease;
}
.composer:has(.composer-input:focus) {
  border-color: var(--accent);
  /* a soft accent bloom + a touch more depth — subtle, not a ring */
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent),
    0 6px 22px color-mix(in srgb, var(--accent) 10%, transparent),
    var(--shadow);
}
.composer-input:focus-visible { box-shadow: none; }
.composer-input {
  display: block; width: 100%; border: 0; background: none; resize: none;
  font: inherit; font-size: 14.5px; line-height: 1.5; color: var(--text);
  padding: 0 2px; min-height: 44px; max-height: 190px; outline: none;
}
.composer-input::placeholder { color: var(--muted); }
/* ---- the ad attached to this message ---- */
/* Revise puts the creative HERE rather than into a three-character input on the
   card. It sits inside the composer card, above the box, so the thing being
   changed and the description of the change read as one message. */
.composer:has(.cx-attach:not([hidden])) { border-color: var(--accent-line); }
.cx-attach { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s3); }
.cx-attach[hidden] { display: none; }
.cx-chip {
  display: inline-flex; align-items: center; gap: var(--s2);
  max-width: 100%; min-width: 0;
  padding: 5px 6px 5px 5px; border-radius: var(--r);
  border: 1px solid var(--accent-line); background: var(--accent-soft);
}
.cx-thumb {
  width: 48px; height: 48px; flex: none; border-radius: var(--r-xs);
  overflow: hidden; background: var(--panel-3);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 600; color: var(--muted);
}
.cx-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cx-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.cx-name {
  font-size: 12.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30ch;
}
.cx-id { font-size: 11px; color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.cx-drop {
  flex: none; width: 24px; height: 24px; display: grid; place-items: center;
  border: 0; border-radius: var(--r-xs); background: transparent;
  color: var(--muted); cursor: pointer;
}
.cx-drop:hover { background: var(--panel); color: var(--text); }
.cx-drop:focus-visible { outline: none; box-shadow: var(--focus); }
.cx-drop .ico { width: 14px; height: 14px; }

/* ---- the two-item Revise menu ---- */
/* Positioned in JS off the trigger's own rect, so one menu serves the board
   card, the preview panel and the lightbox. */
.revise-menu { position: fixed; z-index: 200; min-width: 190px; }

/* wraps rather than overflows: the destination pill plus the models pill do not
   always fit on one line in a narrow thread, and the body must never scroll */
.composer-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: var(--s3); }
.composer-bar .spacer { flex: 1 1 0; min-width: 0; }
/* the two original <select> pickers: still in the DOM, still wired, never seen */
.pick-legacy { display: none; }
.cbtn-tag {
  font-size: 9.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); flex: none;
}
.cbtn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 9px; border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--panel-2);
  font: inherit; font-size: 12px; font-weight: 500; color: var(--text-2);
  cursor: pointer; white-space: nowrap;
}
.cbtn:hover { background: var(--panel-3); color: var(--text); }
.cbtn .ico { width: 14px; height: 14px; color: var(--muted); }
.cbtn-round { width: 30px; padding: 0; justify-content: center; }
.cbtn-static { cursor: default; }
.cbtn-static:hover { background: var(--panel-2); color: var(--text-2); }

/* ---- quiet ghost pills, and the one that holds both models ---- */
/* Ghost, not filled: the composer's job is the text, and three filled pills
   under it turn the card into a toolbar. */
.cbtn-static, .cbtn-models { background: transparent; border-color: transparent; }
.cbtn-static:hover { background: transparent; }
.cbtn-models:hover, .cbtn-models[aria-expanded="true"] { background: var(--panel-3); color: var(--text); }
.cbtn-models.is-set { color: var(--accent-ink); }
.cbtn-models.is-set .ico { color: var(--accent-ink); }
.cbtn-label { max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cbtn:focus-visible { outline: none; box-shadow: var(--focus); }

/* the panel behind the pill: two labelled sections and an Auto reset */
.menu-models { right: 0; left: auto; min-width: 262px; }
.menu-head {
  font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); padding: 7px 8px 3px;
}
.menu-row-model { gap: var(--s3); }
.mr-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
.mr-note {
  flex: none; font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.menu-row-model.is-active .mr-note { color: var(--accent-ink); }
.menu-row:disabled { opacity: .55; cursor: not-allowed; }
.menu-row:disabled:hover { background: transparent; color: var(--text-2); }

.send {
  width: 36px; height: 36px; border-radius: 50%; border: 0; flex: none;
  margin-left: 2px;
  background: var(--accent); color: var(--accent-fg);
  display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent);
  transition: filter .12s ease, transform .12s ease, box-shadow .12s ease;
}
.send:hover:not(:disabled) {
  filter: brightness(1.06); transform: translateY(-1px);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 45%, transparent);
}
.send:active:not(:disabled) { transform: translateY(0) scale(.94); }
.send:disabled { opacity: .45; cursor: default; box-shadow: none; }
.send .ico { width: 16px; height: 16px; stroke-width: 2.2; }
.send.is-stop { background: var(--danger); color: var(--danger-fg); box-shadow: 0 2px 8px color-mix(in srgb, var(--danger) 35%, transparent); }

.dz-progress {
  height: 4px; margin-top: var(--s2); border-radius: var(--r-pill);
  background: var(--panel-3); overflow: hidden;
}
.dz-bar { height: 100%; width: 0%; background: var(--accent); transition: width .15s linear; }

/* ------------------------------ board ------------------------------ */

.board-hd {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--s2);
  padding: 10px var(--s4);
  border-bottom: 1px solid var(--border);
}
/* pull the first pill's own padding back out, so its LABEL lines up with the
   left edge of the first card rather than sitting 10px inside it */
.filters { display: flex; gap: 4px; flex: 1 1 auto; flex-wrap: wrap; margin-left: -10px; }
.filter {
  font: inherit; font-size: 12px;
  background: transparent; color: var(--muted);
  border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 3px 10px; cursor: pointer; white-space: nowrap;
}
.filter:hover { background: var(--panel-3); color: var(--text); }
.filter.is-active { background: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent-line); }
.fcount { font-size: 10.5px; opacity: .75; margin-left: 3px; font-variant-numeric: tabular-nums; }

.board-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--s4); scrollbar-width: thin; }
/* The card holds a thumbnail, a name, a pill, a cost and the actions — nothing
   else — so more of them fit across, and each one is mostly creative. */
.board-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(184px, 1fr));
  gap: var(--s3);
  align-items: start;
}
.board-empty { text-align: center; padding: var(--s6) var(--s4); color: var(--muted); }
.board-empty[hidden] { display: none; }
.board-empty-title { margin: 0 0 var(--s1); font-weight: 600; color: var(--text-2); }
.board-empty-sub { margin: 0; font-size: 12.5px; max-width: 40ch; margin-inline: auto; }

/* Ported from 21st.dev "Reveal on hover" (youcefbnm, id 1816): image-forward
   card, gentle zoom on hover, and a frosted panel that slides up carrying the
   actions. Rebuilt in vanilla CSS on the app's own tokens — the zoom is 1.04
   over 300ms, the panel 350ms ease with a 120% start offset, both from the
   source's motion values. */
.card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-xs);
  transition: box-shadow .14s ease, border-color .14s ease;
}
.card:hover { box-shadow: var(--shadow); }
.card-media {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--panel-2);
  display: flex; align-items: center; justify-content: center;
  padding: 0; margin: 0; border: 0;
  width: 100%; cursor: pointer; font: inherit; color: inherit;
  overflow: hidden;
}
.card-media[disabled] { cursor: default; }
/* .card is overflow:hidden, so an outset ring here would be sliced off */
.card-media:focus-visible { box-shadow: none; outline: 2px solid var(--accent); outline-offset: -2px; }
/* CONTAIN, not cover. The board mixes 1:1, 4:5 and 9:16, so a fixed frame
   with `cover` cropped the headline off every square ad — and the headline is
   how the owner recognises which ad this is. Contain shows the whole creative
   on a neutral ground, which is what a contact sheet does. */
.card-media img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  transition: transform .3s ease;
}
.card:hover .card-media img, .card:focus-within .card-media img { transform: scale(1.04); }
.card-ph { display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--muted); font-size: 12px; }
.card-ph-el { font-variant-numeric: tabular-nums; }
.card-ph-ico { width: 24px; height: 24px; color: var(--muted); }
.card-ph-ico.is-danger { color: var(--danger); }

/* A failed render explains itself INSIDE the media box, which is the same
   fixed 4:5 frame every other card has. The reason used to sit under the
   footer in an unstyled div, so one failed ad stood a paragraph taller than
   its neighbours and pushed the whole grid row down with it. */
.card-ph-failed { padding: var(--s3); text-align: center; }
.card-ph-why {
  max-width: 26ch;
  line-height: 1.45;
  color: var(--muted);
  /* the full text is on the card's title and in the lightbox — the box holds
     four lines, and a long provider error must not reopen the height problem */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}

/* The non-failed job error keeps its own row, clamped for the same reason. */
.card-err {
  padding: 0 var(--s3) var(--s2);
  font-size: 12px;
  line-height: 1.45;
  color: var(--danger);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
.card-err[hidden] { display: none; }

/* ---- the card, after superscale.ai's board ----
   The creative is never painted over: the image runs clean, and the name,
   shape, status and actions sit on a plain footer beneath it — a contact
   sheet labelling a print in the margin. Tools appear ON the image only while
   the card is hovered or keyboard-focused. */
.card-shot { position: relative; }

/* The status MARK: one small icon in the creative's top-right corner, shown
   only for a status that means something (see STATUS_MARK in app.js).
   `rendered` shows nothing at all — it is the resting state of most of the
   board, so badging it labelled almost every card with the same word. */
.card-mark {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 1px solid var(--border);
  color: var(--text-2);
  pointer-events: none;
}
.card-mark[hidden] { display: none; }
/* In the Overview rail the mark sits inline in the footer next to the id,
   not floated over the thumbnail. */
.card-mark.ov-mark {
  position: static; width: 16px; height: 16px;
  background: none; border: 0; backdrop-filter: none; -webkit-backdrop-filter: none;
}
.card-mark.ov-mark .ico { width: 12px; height: 12px; }
.card-mark .ico { width: 13px; height: 13px; }
.card-mark-approved  { color: var(--ok);     border-color: var(--ok-line); }
.card-mark-killed    { color: var(--danger); border-color: var(--danger-line); }
.card-mark-failed    { color: var(--danger); border-color: var(--danger-line); }
.card-mark-revise    { color: var(--warn);   border-color: var(--warn-line); }
.card-mark-rendering .ico,
.card-mark-planned .ico { animation: spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .card-mark-rendering .ico, .card-mark-planned .ico { animation: none; }
}

/* ---- hover tools, floating on the creative ---- */
.card-actions {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  display: flex; gap: 5px; align-items: center;
  opacity: 0; transform: translateY(-4px);
  transition: opacity .18s ease, transform .18s ease;
  pointer-events: none;
}
.card:hover .card-actions, .card:focus-within .card-actions {
  opacity: 1; transform: none; pointer-events: auto;
}
.card-tool, .card-dl {
  width: 28px; height: 28px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); cursor: pointer;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 1px solid var(--border);
  color: var(--text-2); text-decoration: none; padding: 0;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.card-tool .ico, .card-dl .ico { width: 14px; height: 14px; }
.card-tool:hover:not(:disabled), .card-dl:hover { background: var(--panel); color: var(--text); border-color: var(--border-2); }
.card-tool:focus-visible, .card-dl:focus-visible { outline: none; box-shadow: var(--focus); }
.card-tool:disabled { opacity: .4; cursor: default; }
.card-tool-ok:hover:not(:disabled)     { color: var(--ok);     border-color: var(--ok-line); }
.card-tool-danger:hover:not(:disabled) { color: var(--danger); border-color: var(--danger-line); }

/* ---- the footer ---- */
.card-foot {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 11px 11px;
  background: var(--panel);
  border-top: 1px solid var(--border);
}
.card-foot-row { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.card-foot-actions { justify-content: space-between; }
/* The one thing the card says about the ad. The full name is on hover
   (title) and in the preview panel. */
.card-name {
  flex: 1 1 auto; min-width: 0;
  font-weight: 600; font-size: 13px; line-height: 1.3; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* the shape, which actually varies across the board */
.card-chip {
  display: inline-flex; align-items: center;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  color: var(--text-2); background: var(--panel-3);
  border-radius: var(--r-pill); padding: 3px 9px;
}
.card-chip[hidden] { display: none; }
/* the card's one action: open it to judge it — the quiet ghost every
   grid-card action wears, so Board and Competitors read as one app */
.card-review {
  font: inherit; font-size: 12px; font-weight: 500;
  color: var(--text); background: var(--panel);
  border: 1px solid var(--border-2); border-radius: var(--r-sm); padding: 4px 13px;
  cursor: pointer; white-space: nowrap;
  transition: background .12s ease, border-color .12s ease;
}
.card-review:hover:not(:disabled) { background: var(--panel-3); border-color: var(--border-2); }
.card-review:focus-visible { outline: none; box-shadow: var(--focus); }
.card-review:disabled { opacity: .5; cursor: default; }

.card.no-reveal .card-actions { display: none; }
.card.is-sel { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

.card-fb { display: flex; gap: 5px; width: 100%; margin-top: 4px; }
.card-fb .input { flex: 1 1 auto; }

/* ---------------------- the name, and renaming it ---------------------- */
/* One row, two surfaces: the preview panel and the lightbox both show the ad's
   name in an input that is readonly until the pencil unlocks it. Reading a name
   can never start an edit; Enter saves, Escape cancels. */
.namerow { display: flex; align-items: center; gap: 4px; }
.name-input {
  flex: 1 1 auto; min-width: 0;
  width: 100%; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 7px 10px;
  text-overflow: ellipsis;
}
.name-input:read-only { border-color: transparent; background: transparent; padding-inline: 4px; cursor: default; }
.name-input:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus); }
.name-edit { flex: none; color: var(--muted); }
.name-edit:hover:not(:disabled) { color: var(--accent-ink); }
.name-edit:disabled { opacity: .4; cursor: default; }
.name-edit .ico { width: 15px; height: 15px; }

/* ---------------------- the detail metadata list ----------------------- */
/* Label above value: both detail columns are narrow, and "persona · angle ·
   format" has nowhere to go beside its value in 320px. */
.facts-detail {
  display: block; max-width: none; margin: 0;
  padding: var(--s3); border: 1px solid var(--border); border-radius: var(--r);
  background: var(--panel-2);
}
.facts-detail:empty { display: none; }
.facts-detail dt {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin-top: 9px; white-space: normal;
}
.facts-detail dt:first-child { margin-top: 0; }
.facts-detail dd { margin: 1px 0 0; font-size: 12.5px; line-height: 1.4; color: var(--text-2); overflow-wrap: anywhere; }

.pill {
  font-size: 10.5px; font-weight: 600;
  border-radius: var(--r-pill); padding: 1px 8px;
  border: 1px solid transparent;
  white-space: nowrap;
}
/* `rendered` is the resting state of almost every card - painting it accent
   made the board one solid block of orange and left nothing for the states
   that actually need attention. Quiet here, loud for approved / revise /
   failed, accent only while something is genuinely in flight. */
.pill-planned   { background: var(--panel-3); color: var(--muted); border-color: var(--border-2); }
.pill-rendering { background: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent-line); }
.pill-rendered  { background: var(--panel-3); color: var(--text-2); border-color: var(--border-2); }
.pill-approved  { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.pill-revise    { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.pill-failed    { background: var(--danger-soft); color: var(--danger); border-color: var(--danger-line); }
.pill-killed    { background: var(--panel-3); color: var(--muted); border-color: var(--border-2); }

/* ------------------------------ lightbox ------------------------------ */

.lightbox { position: fixed; inset: 0; z-index: 150; }
.lightbox[hidden] { display: none; }
.lb-backdrop { position: absolute; inset: 0; background: rgba(14,14,13,.88); backdrop-filter: blur(2px); }
.lb-shell {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: var(--s4);
  padding: var(--s5) var(--s5) var(--s4);
  pointer-events: none;
}
.lb-shell > * { pointer-events: auto; }
.lb-stage {
  position: relative;          /* the loading pulse is absolute inside it */
  min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center;
}
.lb-stage img {
  width: 100%; height: 100%;
  max-width: 100%; max-height: 100%;
  object-fit: contain;
  border-radius: var(--r-sm);
}
.lb-close {
  position: absolute; top: 10px; right: 12px; z-index: 3;
  display: grid; place-items: center;
  width: 34px; height: 34px;
  /* it overlaps the light side panel, so it needs its own ground - a 10%
     white fill with a white glyph disappears the moment the panel is behind it */
  background: rgba(18,18,16,.78); color: #fff;
  border: 1px solid rgba(255,255,255,.28); border-radius: 50%;
  cursor: pointer; backdrop-filter: blur(4px);
}
.lb-close .ico { width: 17px; height: 17px; }
.lb-close:hover { background: rgba(18,18,16,.94); }
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  display: grid; place-items: center;
  width: 38px; height: 54px;
  background: rgba(255,255,255,.1); color: #fff;
  border: 1px solid rgba(255,255,255,.2); border-radius: var(--r-sm);
  cursor: pointer;
}
.lb-nav .ico { width: 20px; height: 20px; }
.lb-nav:hover:not(:disabled) { background: rgba(255,255,255,.22); }
.lb-nav:disabled { opacity: .25; cursor: default; }
.lb-prev { left: 10px; }
.lb-next { right: 360px; }   /* 320px panel + 24px padding + 16px clear */
.lb-side {
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s4);
  display: flex; flex-direction: column; gap: var(--s2);
  font-size: 13px;
  scrollbar-width: thin;
}
.lb-side h2 { margin: 0; font-size: 15px; display: flex; align-items: center; gap: var(--s2); }
.lb-side > .namerow, .lb-side > .facts-detail, .lb-side > .lb-actions { flex: 0 0 auto; }
/* the concept the agent recorded, under the name it belongs to */
.lb-concept { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--text-2); overflow-wrap: anywhere; }
/* the app's own bookkeeping line: present, and visibly not the owner's idea */
.lb-note { margin: 0; font-size: 11px; line-height: 1.4; color: var(--muted); }
/* Approve is the primary verdict, so it gets the full row; Revise and Kill
   share the next one, and Download closes it. */
.lb-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: var(--s1); }
.lb-actions .btn { justify-content: center; }
.lb-actions .btn-wide { grid-column: 1 / -1; text-decoration: none; }
/* JPG for Meta + PNG original: one full row, two halves */
.lb-actions .lb-dl { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.lb-actions .lb-dl .btn { text-decoration: none; }
.lb-count { font-size: 11.5px; color: var(--muted); margin-top: auto; padding-top: var(--s2); }

@media (max-width: 900px) {
  .lb-shell { grid-template-columns: 1fr; grid-template-rows: 1fr auto; padding: var(--s4) var(--s2) var(--s2); }
  .lb-side { max-height: 34vh; }
  .lb-next { right: 10px; }
  .lb-nav { top: 34%; }
}

/* ------------------------------ toasts ------------------------------ */

.toasts {
  /* clear of the sidebar: a toast landing on the theme toggle and the user card
     is the one place a bottom-left stack is exactly wrong */
  position: fixed; left: calc(var(--rail-w) + var(--s3)); bottom: var(--s3); z-index: 180;
  display: flex; flex-direction: column; gap: var(--s2);
  max-width: min(360px, calc(100vw - var(--rail-w) - 32px));
  pointer-events: none;
}
.toast {
  display: flex; align-items: flex-start; gap: var(--s2);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r); box-shadow: var(--shadow);
  padding: var(--s2) var(--s3); font-size: 13px; color: var(--text-2);
  pointer-events: auto; animation: toast-in .18s ease;
  overflow-wrap: anywhere;
}
.toast .ico { width: 15px; height: 15px; margin-top: 1px; flex: none; }
.toast-ok { color: var(--ok); }
.toast-ok .ico { color: var(--ok); }
.toast-err { color: var(--danger); }
.toast-err .ico { color: var(--danger); }
.toast-info .ico { color: var(--accent); }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }

/* ------------------------------ option cards ------------------------------ */
/* Cards replace the agent's ```options block inside an assistant message.     */

.options-loading {
  display: flex; align-items: center; gap: var(--s2);
  margin: 0 0 var(--s2);
  padding: var(--s3);
  border: 1px dashed var(--border-2); border-radius: var(--r);
  background: var(--panel-2);
  color: var(--muted); font-size: 12.5px;
}
.options-spinner {
  width: 12px; height: 12px; flex: none;
  border: 2px solid var(--border-2); border-top-color: var(--accent);
  border-radius: 50%;
  animation: opt-spin .7s linear infinite;
}
@keyframes opt-spin { to { transform: rotate(360deg); } }

.options { margin: 0 0 var(--s3); }
.options-hd {
  display: flex; align-items: baseline; gap: var(--s3);
  margin: 0 0 var(--s2);
}
.options-q {
  font-weight: 600; font-size: 15px;
  color: var(--text);
  min-width: 0;
}
.options-multi { margin-left: auto; flex: none; font-size: 12px; }
.options-multi[aria-pressed="true"] { color: var(--accent-ink); font-weight: 600; }
.options.is-answered .options-multi { visibility: hidden; }
.options-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s2);
  align-items: stretch;
  /* the grid must measure against the message width, so cards wrap to the next
     row rather than marching off the right edge */
  width: 100%; min-width: 0;
}

/* Option cards, refreshed from 21st.dev's "Radio Group with Plan Cards"
   (shadcnspace, id 10156): more breathing room, a soft accent wash on hover
   and when selected/chosen instead of a hard box, and — the big fix — the
   inner content is no longer a bordered box inside the card. One surface, not
   three nested rectangles: that stacking was what read as "AI slop". */
.opt-card {
  display: flex; flex-direction: column; gap: 7px;
  padding: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
  transition: border-color .14s ease, background .14s ease, opacity .12s ease, box-shadow .14s ease;
  min-width: 0;
}
/* hover is a border + shadow shift, never an accent wash (DESIGN v2 §11) */
.opt-card:hover { box-shadow: var(--shadow); border-color: var(--border-2); }

.opt-hd { display: flex; align-items: center; gap: 8px; }
.opt-badge {
  flex: none;
  width: 20px; height: 20px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--panel-3); border: 1px solid var(--border);
  color: var(--text-2);
  font-family: var(--mono); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.opt-title {
  margin: 0; font-size: 13.5px; font-weight: 600; line-height: 1.3;
  color: var(--text); overflow-wrap: anywhere; letter-spacing: -0.01em;
}
.opt-hd .opt-select { margin-left: auto; }
.opt-chosen-tag {
  display: none; margin-left: auto; flex: none;
  font-size: 10.5px; font-weight: 600; letter-spacing: .02em;
  color: var(--accent-ink);
}
.opt-chosen-tag::before { content: "\2713\00a0"; }
.options .opt-summary { margin: 0 0 3px; font-size: 12px; color: var(--muted); line-height: 1.5; }

/* flat: the persona/product/design content sits ON the card, not in a nested
   framed box. Only the specific visuals that truly need a frame (a colour
   swatch row) opt back into one, below. */
.opt-visual {
  margin-top: 1px;
  border: 0; background: none; padding: 0;
  min-width: 0;
}
.opt-choose {
  margin-top: auto; align-self: flex-end;
  font-size: 12px; padding: 3px 12px;
}
.opt-card.is-clickable { cursor: pointer; }
.opt-card:hover .opt-choose { border-color: var(--accent-line); color: var(--accent-ink); }

/* --- the Select pill (multi-pick) --- */
/* The checkbox is a MULTI-PICK affordance, not a default (DESIGN §3): it is
   hidden until the owner turns on "Select several", so single-pick cards show
   one action — Choose — and nothing competing with it. */
.opt-select { display: none; }
.options.is-multipick .opt-select {
  flex: none;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px 2px 5px;
  border: 1px solid var(--border-2); border-radius: var(--r-pill);
  background: var(--panel-2);
  color: var(--muted);
  font: inherit; font-size: 11px; font-weight: 600; line-height: 1.6;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.opt-select:hover:not(:disabled) { border-color: var(--accent-line); color: var(--accent-ink); }
.opt-select:focus-visible { outline: none; box-shadow: var(--focus); }
.opt-select:disabled { opacity: .5; cursor: not-allowed; }
.opt-select-box {
  width: 13px; height: 13px; flex: none;
  border: 1px solid var(--border-2); border-radius: var(--r-xs);
  background: var(--panel);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; line-height: 1; color: var(--accent-fg);
}
.opt-select[aria-checked="true"] {
  background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink);
}
.opt-select[aria-checked="true"] .opt-select-box {
  background: var(--accent-btn); border-color: var(--accent-btn);
}
.opt-select[aria-checked="true"] .opt-select-box::before { content: "\2713"; }

/* selected (ticked, pre-send): a soft accent wash, like the 21st plan card's
   has-checked state — not just a hard border */
.opt-card.is-selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent-soft) 70%, var(--panel));
}
.opt-card.is-chosen .opt-select { display: none; }

/* thumbnails (option.image / product.photo) — a taller, cleaner frame so the
   packshot reads as a real product shot, not a cramped stamp */
.opt-thumb {
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--panel-3);
  overflow: hidden; line-height: 0;
}
/* cover, not contain: the packshots ship on white, so contain painted a white
   rectangle on the dark card. cover fills the frame edge to edge — the jar
   crops slightly top/bottom but the card reads clean in both themes */
.opt-thumb img { width: 100%; height: 120px; object-fit: cover; object-position: center; display: block; }

/* --- format wireframe --- */
.wf-canvas { fill: var(--panel); stroke: currentColor; stroke-opacity: .3; stroke-width: 1; }
.wf-area {
  fill: currentColor; fill-opacity: .055;
  stroke: currentColor; stroke-opacity: .38; stroke-width: 1;
}
.wf-area-text { fill-opacity: .085; }
.wf-area-product { fill: var(--accent); fill-opacity: .12; stroke: var(--accent); stroke-opacity: .55; }
.wf-label {
  fill: currentColor; fill-opacity: .82;
  font-family: var(--font); font-weight: 600; letter-spacing: .01em;
}
.wf-glyph { stroke: currentColor; stroke-opacity: .4; stroke-linecap: round; }
.wf-cross { stroke: currentColor; stroke-opacity: .16; stroke-width: 1; }
.wf-prod { fill: var(--accent); fill-opacity: .5; }
.wf-prod-body { fill-opacity: .38; }
.wf-prod-band { fill: var(--panel); fill-opacity: .55; }

/* --- copy preview --- */
/* a copy SET, one row per slot of the reference layout */
.opt-copy-slots { display: flex; flex-direction: column; width: 100%; min-width: 0; }
.opt-slot {
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px 0;
  border-top: 1px solid var(--panel-3);
  min-width: 0;
}
.opt-slot:first-child { border-top: 0; padding-top: 0; }
.opt-slot-k {
  font-size: 10px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted);
}
.opt-slot-v {
  font-size: 13px; font-weight: 600; line-height: 1.55; color: var(--text);
  text-align: start; unicode-bidi: plaintext; overflow-wrap: anywhere;
}
.opt-slot-v[dir] { line-height: 1.7; }

.opt-copy { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; }
.opt-copy .pfeat-stage.pfeat-text { flex: 1 1 auto; }
.opt-copy .pfeat-caption { flex-wrap: nowrap; align-items: flex-start; }
.opt-copy .pfeat-caption .pfeat-name { flex: 1 1 auto; }
.opt-copy-text {
  font-family: var(--font);
  font-weight: 600; line-height: 1.3; color: var(--text);
  unicode-bidi: plaintext;
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
  overflow: hidden;
  /* the copy preview keeps a light card of its own — it reads as "here is the
     line", which the flat persona/product content does not need */
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--panel-2); padding: 9px 11px;
}
.opt-copy-text[dir="rtl"] { text-align: right; }
.opt-copy-display .opt-copy-text {
  font-weight: 600; letter-spacing: -.01em;
}
/* Derja renders in its OWN face, not a fallback (DESIGN.md type covenant) */
.opt-copy .opt-copy-text[dir="rtl"] {
  font-family: var(--font-arabic);
  line-height: 1.8;
}

.opt-tags { display: flex; flex-wrap: wrap; gap: 4px; }
.opt-tag {
  font-size: 10.5px; line-height: 1.6;
  padding: 0 6px; border-radius: var(--r-pill);
  background: var(--panel-3); border: 1px solid var(--border);
  color: var(--muted);
}
.opt-tag-lang {
  background: var(--panel-3); border-color: var(--border);
  color: var(--text-2); font-weight: 600; letter-spacing: .03em;
}
/* the angle's source — a quiet monospace tag, so it reads as a reference, not
   a claim of its own */
.opt-tag-angle {
  font-family: var(--mono); font-size: 10px; letter-spacing: .01em;
  background: var(--panel-2); color: var(--text-2);
  padding: 1px 7px;
}

/* --- persona --- */
/* the persona card leads with a face: an illustrated, deterministic human
   drawn from the persona's identity — the owner reads the person at a glance
   instead of parsing a block of bold text */
.opt-persona-hd { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.opt-persona-face {
  width: 56px; height: 56px; flex: none; border-radius: 50%;
  background: var(--panel-3); border: 1px solid var(--border);
  object-fit: cover;
}
.opt-persona-id { min-width: 0; }
.opt-persona-name { font-size: 13px; font-weight: 600; }
.opt-persona-meta { font-size: 11.5px; color: var(--muted); margin-bottom: 4px; }
.opt-persona-hd .opt-persona-meta { margin-bottom: 0; }
.opt-dl {
  margin: 0;
  display: grid; grid-template-columns: auto 1fr; gap: 3px var(--s3);
  font-size: 11.5px; line-height: 1.45;
  /* a hairline off the face row, not a full box around it */
  padding-top: 8px; margin-top: 2px; border-top: 1px solid var(--border);
}
.opt-dl dt {
  color: var(--muted); white-space: nowrap;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  padding-top: 1px;
}
.opt-dl dd { margin: 0; color: var(--text-2); overflow-wrap: anywhere; }

/* --- angle --- */
/* The angle is a strategic CLAIM — the argument the ad makes. Styled from the
   quote treatment in 21st.dev's Testimonial cards (ncdai, id 18907): an elegant
   serif statement led by a large accent quotation mark, so it reads as an
   insight, not a tiny grey aside. */
.opt-angle { position: relative; }
/* --- design --- */
.opt-swatches { display: flex; gap: var(--s2); }
.opt-swatch { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; }
.opt-swatch-chip {
  width: 34px; height: 34px; border-radius: var(--r-sm); border: 0; display: block;
  /* a grey alpha ring, never rgba(0,0,0,…) — a black swatch vanishes otherwise */
  box-shadow: inset 0 0 0 1px rgba(128,124,116,.35), var(--shadow-xs);
}
.opt-swatch-hex { font-family: var(--mono); font-size: 10px; color: var(--muted); }
.opt-design-fonts { margin-top: 6px; font-size: 11.5px; color: var(--text-2); }
.opt-design-notes { margin-top: 2px; font-size: 11.5px; color: var(--muted); line-height: 1.4; }

/* --- product --- */
.opt-product { display: flex; flex-direction: column; gap: 6px; }
.opt-product-name { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }

/* ---- feature-card treatment (option cards) ----
   ONE surface (DESIGN §3): the subject sits on a flat, quiet stage that is
   part of the card, and the caption sits BELOW it. No per-position colour, no
   radial glow, no decorative index, no glass box floating over the subject —
   those were the "AI card" tells this surface used to stack. */
.opt-card:has(.pfeat) { position: relative; }
.opt-card:has(.pfeat) .opt-title,
.opt-card:has(.pfeat) .opt-summary { display: none; }
/* the number the answer protocol speaks in ("Option 2: ...") stays visible:
   a quiet mono chip, leading the header strip */
.opt-card:has(.pfeat) .opt-badge {
  width: 18px; height: 18px; font-size: 10.5px;
  background: var(--panel); border-color: var(--border);
  color: var(--muted);
}
/* the checkbox corner — hidden until the step is in multi-pick mode (below) */
.opt-card:has(.pfeat) .opt-hd {
  position: absolute; top: 12px; left: 12px; right: 12px; z-index: 3;
  pointer-events: none;
}
.opt-card:has(.pfeat) .opt-hd > * { pointer-events: auto; }

.pfeat { position: relative; }
.pfeat-stage {
  position: relative; width: 100%; height: 210px; border-radius: var(--r);
  overflow: hidden; display: grid; place-items: center; padding: 14px;
  background: var(--panel-2); border: 1px solid var(--border);
}
.pfeat-stage[role="button"] { cursor: pointer; }
.pfeat-stage:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* the persona subject: the illustrated avatar, or the drawn portrait */
.pfeat-face {
  width: 128px; height: 128px; border-radius: 50%;
  filter: drop-shadow(0 8px 18px rgba(0,0,0,.16));
}
/* The portrait is a FULL BODY on paper — shown whole, never circle-cropped,
   and filling the placeholder it replaces so the card does not jump. Its own
   paper ground is the frame, so it needs no shadow of ours. */
.pfeat-face.is-portrait {
  width: 100%; height: 100%; border-radius: var(--r-sm);
  object-fit: contain; filter: none;
  animation: portrait-in .4s ease;
}
@keyframes portrait-in { from { opacity: 0 } to { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .pfeat-face.is-portrait { animation: none } }

/* The persona subject box: a placeholder that BECOMES the drawing. It holds the
   portrait's own proportions and paper tone from the start, so the card does
   not resize or change character when the render lands. */
.pfeat-portrait {
  width: 100%; height: 100%;
  display: grid; place-items: center;
}
/* While the render is out (~50s): a calm sheet of paper, gently shimmering.
   Deliberately NOT a cartoon face — a flat vector avatar beside a pencil
   sketch is two illustration styles fighting, and a half-loaded row of three
   looked broken. Neutral placeholders keep the row one design. */
.pfeat-pending {
  width: 62%; height: 100%; border-radius: var(--r-sm);
  background:
    linear-gradient(90deg, transparent 0%, var(--shimmer) 50%, transparent 100%),
    var(--panel-3);
  background-size: 200% 100%, auto;
  animation: ld-shimmer 1.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .pfeat-pending { animation: none; } }

/* the product subject: the floating cutout jar */
.pfeat-img { max-width: 82%; max-height: 100%; object-fit: contain; }
.pfeat-img.is-cutout { filter: drop-shadow(0 10px 20px rgba(0,0,0,.22)); }

/* The packshot sits on a soft vertical wash rather than a flat panel: a white
   jar on a flat white card disappears, and the product is the whole point of
   the card. The glow is the product's own light, not decoration keyed to the
   card's position (DESIGN.md banned that). */
/* A shop packshot is photographed on white. Dropping that white rectangle onto
   a dark tile reads as a bug, not a product — so for product cards the STAGE is
   the white tile, the way a storefront shows a jar. It stays light in both
   themes on purpose: it is a photograph's mount, not a surface of the app. */
.opt-product .pfeat-stage {
  height: 170px;
  /* flat white: the packshots are photographed on pure white, and a gradient
     mount showed the photo rectangle as a seam inside the tile */
  background: #ffffff;
  border-color: color-mix(in srgb, var(--border-2) 70%, transparent);
  padding: 0;
}
.opt-product .pfeat-stage::after {
  /* the shelf the jar stands on — one hairline, no drop-shadow theatre */
  content: ""; position: absolute; left: 12%; right: 12%; bottom: 22px; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(18,22,33,.16), transparent);
}
/* No hover lift: DESIGN.md §1.9 — hover is a border/shadow shift, and a card
   that rises on hover is the blanket effect that made v1 feel generated. The
   packshot just gets more room than the other subjects, because it IS the
   answer to this question. */
/* Fill the mount and letterbox: the packshots are 3:4 portrait in a landscape
   tile, and relying on max-height alone let the jar's base clip off. */
.opt-product .pfeat-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none;
  object-fit: contain; padding: 10px;
}

/* The reference a creative was replicated from, under the creative itself. */
.pv-ref {
  display: block; width: 100%; padding: 0; cursor: zoom-in;
  background: none; border: 1px solid var(--border); border-radius: var(--r);
  overflow: hidden;
}
.pv-ref:hover { border-color: var(--border-2); }
.pv-ref:focus-visible { outline: none; box-shadow: var(--focus); }
.pv-ref-img { display: block; width: 100%; height: auto; }

/* A bundle card. No jar, on purpose: borrowing one scent's photo to stand for
   three is what made the options indistinguishable. */
.pfeat-bundle {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; text-align: center; padding: 14px; width: 100%; height: 100%;
}
/* No photo, no photo-mount: the white tile read as a glaring blank in dark.
   The bundle sits on an app surface and speaks in tokens. */
.opt-product .pfeat-stage:has(.pfeat-bundle) {
  background: var(--panel-2); border-color: var(--border);
}
.opt-product .pfeat-stage:has(.pfeat-bundle)::after { display: none; }
.pfeat-bundle-tag {
  font-size: 11px; font-weight: 600;
  color: var(--text-2); background: var(--panel-3);
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 2px 10px;
}
.pfeat-bundle-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 3px;
  font-size: 12.5px; color: var(--text-2); line-height: 1.35;
}
.pfeat-bundle-note { font-size: 11px; color: var(--muted); text-wrap: balance; }

/* No packshot on file. Legible absence beats a deleted card or an empty frame. */
.pfeat-missing {
  display: grid; place-items: center; gap: 8px; text-align: center; padding: 12px;
}
.pfeat-missing-mark {
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 22px; font-weight: 600; color: var(--muted);
  background: var(--panel-3); border: 1px dashed var(--border-2);
}
.pfeat-missing-note { font-size: 11.5px; color: var(--muted); }

/* ========================= settings + research pages ====================== */
/* The field language is ported from shadcn's fieldset/field (21st.dev id
   25058): a legend, then rows of label + hint on the left and the control on
   the right. Two columns on a wide screen so the eye scans labels down one
   edge; one column when there is no room for two. */
.page-pane { overflow: auto; }
.page-pane[hidden] { display: none; }
.page-head {
  padding: var(--s6) var(--s6) var(--s4);
  max-width: 880px; margin: 0 auto; width: 100%;
}
.page-title { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.page-sub { margin: 6px 0 0; font-size: 13px; color: var(--muted); }
.page-body { padding: 0 var(--s6) var(--s7); max-width: 880px; margin: 0 auto; width: 100%; }
.page-empty { color: var(--muted); font-size: 13.5px; padding: var(--s5) 0; }

.set-group {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel); padding: var(--s5);
  margin-bottom: var(--s5);
}
.set-legend {
  margin: 0 0 var(--s4); font-size: 12px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.set-field {
  display: grid; grid-template-columns: minmax(180px, 34%) 1fr;
  gap: var(--s4); align-items: start;
  padding: var(--s4) 0; border-top: 1px solid var(--border);
}
.set-group > .set-field:first-of-type { border-top: 0; padding-top: 0; }
.set-field-label { display: flex; flex-direction: column; gap: 3px; }
.set-label { font-size: 13.5px; font-weight: 500; color: var(--text); }
.set-hint { font-size: 12px; color: var(--muted); line-height: 1.45; }
.set-field-control { min-width: 0; }
.set-row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.set-col { display: flex; flex-direction: column; gap: 6px; }

.set-input {
  font: inherit; font-size: 13.5px; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--border-2);
  border-radius: var(--r-sm); padding: 7px 10px; min-width: 0; flex: 1 1 220px;
}
.set-input:focus-visible { outline: none; box-shadow: var(--focus); border-color: var(--accent); }
/* A select inside a flex column stretches to the row's height unless told
   not to — it filled a whole field row before this. */
select.set-input { cursor: pointer; flex: 0 0 auto; align-self: start; width: 100%; max-width: 420px; }
.set-col { align-items: flex-start; }
.set-col > .set-row { width: 100%; }
.set-note { margin: 0; font-size: 12px; color: var(--muted); line-height: 1.45; }
/* The save state of one field, beside it — not only a toast that disappears. */
.set-status { margin: 0; font-size: 12px; line-height: 1.45; min-height: 1.45em; color: var(--muted); }
.set-status[data-kind="ok"] { color: var(--ok, var(--accent)); }
.set-status[data-kind="error"] { color: var(--danger); }

/* the tier picker: a segmented control, not three radio buttons */
.set-seg { display: inline-flex; gap: 2px; padding: 2px; background: var(--panel-3); border-radius: var(--r-sm); }
.set-seg-opt {
  font: inherit; font-size: 12.5px; font-weight: 500; color: var(--text-2);
  background: none; border: 0; border-radius: var(--r-xs); padding: 5px 14px; cursor: pointer;
}
.set-seg-opt.is-on { background: var(--panel); color: var(--text); box-shadow: var(--shadow-xs); }
.set-seg-opt:focus-visible { outline: none; box-shadow: var(--focus); }

.set-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.set-tag {
  font-family: var(--mono); font-size: 11.5px; color: var(--text-2);
  background: var(--panel-3); border-radius: var(--r-pill); padding: 3px 10px;
}

/* what a sync actually did — a receipt, in the ledger voice */
.set-result {
  font-family: var(--mono); font-size: 11.5px; color: var(--text-2);
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: var(--s3); margin: 0;
}
.set-result-row { white-space: pre-wrap; line-height: 1.7; }

/* ---------------------------- research page ---------------------------- */
.res-block { margin-bottom: var(--s6); }
.res-products { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--s4); }
/* Settings shows the same packshots smaller, under the shop they came from. */
.res-products.is-compact { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: var(--s3); }
.res-products.is-compact .res-product-name { font-size: 12px; margin: 0; padding: 6px 8px 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.set-shots { margin-top: var(--s3); align-self: stretch; width: 100%; }
.set-shots .res-products { width: 100%; }
.set-shots-head { justify-content: space-between; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.res-product {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel); overflow: hidden;
}
/* Packshots are photographed on white; the mount is white so the jar belongs
   to it rather than floating on a dark card (same rule as the option cards). */
.res-shot {
  position: relative; height: 170px; overflow: hidden;
  background: linear-gradient(180deg, #ffffff, #f2f3f5);
  border-bottom: 1px solid var(--border);
}
/* Absolute, not a percentage height on a grid item: `height: 100%` did not
   resolve there, so the image sized itself from its own 3:4 ratio and spilled
   over the card's caption. */
.res-shot-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; padding: 10px; }
.res-shot-none {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 11.5px; color: #8b91a0;
}
.res-product-name { margin: var(--s3) var(--s3) 0; font-size: 13.5px; font-weight: 600; }
.res-product-alias { margin: 3px var(--s3) var(--s3); font-size: 11.5px; color: var(--muted); }

.res-docs { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.res-doc {
  display: grid; grid-template-columns: 92px 1fr auto; gap: var(--s3); align-items: center;
  padding: 11px var(--s4); background: var(--panel); color: var(--text);
  text-decoration: none; border-top: 1px solid var(--border);
}
.res-doc:first-child { border-top: 0; }
.res-doc:hover { background: var(--panel-2); }
.res-doc:focus-visible { outline: none; box-shadow: var(--focus); }
.res-doc-kind {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .02em;
  color: var(--muted); background: var(--panel-3); border-radius: var(--r-pill);
  padding: 2px 8px; text-align: center;
}
.res-doc-title { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.res-doc-meta { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

.res-kit { display: flex; gap: var(--s3); flex-wrap: wrap; }
.res-kit-cell { width: 128px; display: flex; flex-direction: column; gap: 6px; }
.res-kit-img {
  width: 128px; height: 84px; object-fit: contain; padding: 8px;
  background: linear-gradient(180deg, #ffffff, #f2f3f5);
  border: 1px solid var(--border); border-radius: var(--r-sm);
}
.res-kit-name { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.res-cta { padding-top: var(--s2); }

@media (max-width: 720px) {
  .page-head, .page-body { padding-left: var(--s4); padding-right: var(--s4); }
  .set-field { grid-template-columns: 1fr; gap: var(--s2); }
}

/* ========================= new chat command palette ========================= */
/* A floating palette, not a form. The old one was a 680px card with a "Start
   chat" button in it and it read as a dialog box bolted to the page; the owner
   put superscale.ai next to it and he was right.
   Every number here is MEASURED off that reference screenshot (1690px wide,
   which is a 1365 css viewport), not guessed:
     panel        448 img px = 362 css = 26.5% of the viewport, 216 img = 174 css tall
     top edge     25% down the viewport
     scrim        page chrome reads rgb(150) over a near-white app = black at 41%
     divider      under the toolbar, luminance 233
     rows         22 css apart, hairline between each at luminance 245, full bleed
   What is NOT copied is the colour: our own panel, border and blue send button,
   in both themes. The geometry is theirs; the brand is ours. */
.ncp { position: fixed; inset: 0; z-index: 70; display: grid; place-items: start center; }
.ncp[hidden] { display: none; }
/* while the palette is up the app behind it holds still. */
body.is-locked { overflow: hidden; }
.ncp-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, .41); }

.ncp-dialog {
  position: relative; z-index: 1;
  width: min(460px, calc(100vw - 32px));
  margin-top: min(22vh, 240px);
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
}

/* Section A — the field IS the panel. No inner box and no focus ring: a border
   here is exactly what made the old version read as a form. */
.ncp-input {
  display: block; width: 100%; border: 0; background: none; resize: none;
  font: inherit; font-size: 15px; line-height: 1.5; color: var(--text);
  padding: 15px 16px 0; min-height: 54px; max-height: 40vh; outline: none;
}
.ncp-input::placeholder { color: var(--muted); }

/* Section B — one compact row: attach left, destination, models, send right. */
.ncp-bar { display: flex; align-items: center; gap: 3px; height: 34px; padding: 0 11px 0 9px; }
.ncp-round {
  width: 28px; height: 28px; padding: 0; flex: none;
  display: grid; place-items: center;
  border: 0; border-radius: var(--r-pill); background: none;
  color: var(--muted); cursor: pointer;
}
.ncp-round .ico { width: 17px; height: 17px; }
.ncp-round:hover { background: var(--panel-3); color: var(--text-2); }
.ncp-round:focus-visible { outline: none; box-shadow: var(--focus); }

.ncp-tag {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  height: 26px; padding: 0 9px; border-radius: var(--r-pill);
  border: 0; background: none;
  font: inherit; font-size: 12.5px; font-weight: 500; color: var(--text-2);
  white-space: nowrap;
}
.ncp-tag .ico { width: 15px; height: 15px; color: var(--muted); }
.ncp-tag .ico-xs { width: 12px; height: 12px; }
.ncp-tag-static { cursor: default; }
.ncp-tag-btn { cursor: pointer; }
.ncp-tag-btn:hover, .ncp-tag-btn[aria-expanded="true"] { background: var(--panel-3); color: var(--text); }
.ncp-tag-btn:focus-visible { outline: none; box-shadow: var(--focus); }
.ncp-tag-btn.is-set { color: var(--accent-ink); }
.ncp-tag-btn.is-set .ico { color: var(--accent-ink); }
.ncp-tag-label { max-width: 11ch; overflow: hidden; text-overflow: ellipsis; }

/* The reference's send is a small grey disc. Ours stays blue — DESIGN.md §0
   gives the accent to the one committing action — but takes the reference's
   size rather than the composer's 36px. */
.ncp-send {
  width: 30px; height: 30px; border-radius: 50%; border: 0; flex: none;
  margin-left: 5px;
  background: var(--accent); color: var(--accent-fg);
  display: grid; place-items: center; cursor: pointer;
  transition: filter .12s ease;
}
.ncp-send .ico { width: 16px; height: 16px; stroke-width: 2.3; }
.ncp-send:hover { filter: brightness(1.06); }
.ncp-send:focus-visible { outline: none; box-shadow: var(--focus); }

/* Section C — categories on one line, then the rows they carry.
   nowrap is load-bearing: a wrapped second line adds 22px and pushes every row
   below it out of register with the reference. */
/* These WRAP now. nowrap was load-bearing only while the palette was being
   matched to a reference screenshot line for line; at the readable chip size it
   sliced "Board" in half at the right edge instead, and a chip cut down the
   middle reads as broken, not as scrollable. */
.ncp-cats {
  display: flex; flex-wrap: wrap; gap: 5px;
  padding: 7px 13px 6px;
  border-top: 1px solid var(--border);
}
.ncp-cats::-webkit-scrollbar { display: none; }
.ncp-cat {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  height: 25px; padding: 0 10px; border-radius: var(--r-sm);
  border: 0; background: var(--panel-3);
  font: inherit; font-size: 12px; font-weight: 500; color: var(--muted);
  cursor: pointer; white-space: nowrap;
}
.ncp-cat .ico { width: 13px; height: 13px; }
.ncp-cat:hover { color: var(--text-2); }
.ncp-cat:focus-visible { outline: none; box-shadow: var(--focus); }
/* The chosen chip is a STATE, not a colour: it darkens the way every other
   picked thing in the app does. */
.ncp-cat.is-on { background: var(--panel-3); color: var(--text); font-weight: 600; box-shadow: inset 0 0 0 1px var(--border-2); }

/* The rows run edge to edge and the last one meets the panel's bottom corner.
   Pitch is 34, not the 22 this was first measured at: the whole palette was
   built to a reference screenshot pixel-for-pixel, and copying that reference's
   density produced 10.5px chips and 22px rows that the owner read as "so small,
   not UI friendly". Matching a picture is not the same as being legible. */
.ncp-rows { display: flex; flex-direction: column; }
.ncp-row {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; height: 34px; padding: 0 16px;
  border: 0; background: none;
  font: inherit; font-size: 13.5px; color: var(--text-2);
  text-align: left; cursor: pointer;
}
.ncp-row + .ncp-row { border-top: 1px solid var(--panel-3); }
/* The last row meets the panel's bottom corner, so its hover has to be
   rounded there. The dialog cannot just clip its overflow: the models menu
   hangs out of the panel and clipping cut it in half. */
.ncp-row:last-child { border-bottom-left-radius: var(--r-lg); border-bottom-right-radius: var(--r-lg); }
.ncp-row-text { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the reference's trailing mark is a diagonal ↗ */
.ncp-row .ico { width: 14px; height: 14px; flex: none; color: var(--muted); opacity: .5; }
.ncp-row:hover { background: var(--panel-3); color: var(--text); }
.ncp-row:hover .ico { opacity: 1; }
.ncp-row:focus-visible { outline: none; box-shadow: var(--focus); z-index: 1; position: relative; }

/* The models menu is borrowed from the composer, which opens it upward from a
   bar at the bottom of the screen. In the palette it hangs down. */
#newChatModelsAnchor .menu { top: calc(100% + 6px); bottom: auto; right: 0; }

@media (max-width: 560px) {
  .ncp-dialog { margin-top: 12vh; width: calc(100vw - 24px); }
}

/* ---- the folded lead-in: analysis and preflight, out of the way ---- */
.lead-in { margin: 0 0 var(--s3); }
.lead-in-toggle {
  font: inherit; font-size: 12.5px; color: var(--muted);
  background: none; border: 0; padding: 2px 0; cursor: pointer;
  border-bottom: 1px dashed var(--border-2);
}
.lead-in-toggle:hover { color: var(--text-2); border-bottom-color: var(--muted); }
.lead-in-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
.lead-in-body { margin-top: var(--s3); }

/* the format subject: the layout wireframe */
.pfeat-wire { display: grid; place-items: center; }
.pfeat-wire .wf {
  width: 150px; height: auto; max-height: 180px;
  display: block; color: var(--text-2);
}

/* the design subject: the palette as three colour blocks */
.pfeat-palette { display: flex; gap: 8px; }
.pfeat-swatch {
  width: 52px; height: 72px; border-radius: var(--r); display: block;
  box-shadow: inset 0 0 0 1px rgba(128,124,116,.3);
}
.pfeat-hexes { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 5px; }
.pfeat-hex {
  font-family: var(--mono); font-size: 10px; color: var(--muted);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-xs); padding: 1px 5px;
}
.pfeat-design-detail { display: flex; flex-direction: column; gap: 3px; }

/* caption BELOW the stage: the name is the main label, a SHORT tag pill beside
   it, and a muted meta line under both. Sentence case, nothing truncated. */
.pfeat-caption {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 3px 2px;
}
.pfeat-caption .pfeat-name { order: 1; }
.pfeat-caption .pfeat-tag { order: 2; }
.pfeat-caption .pfeat-tag2 { order: 3; }
.pfeat-caption .pfeat-meta {
  order: 3; flex-basis: 100%;
  font-size: 12px; color: var(--muted); line-height: 1.4;
}
/* Product summaries are a sentence, not a label: give them room, and clamp at
   three lines so a long one cannot push the Choose button out of alignment
   across a row of cards. */
.opt-product .pfeat-meta {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* format needs no caption clutter — just the wireframe and a quiet name */
.opt-wire .pfeat-caption { justify-content: center; padding-top: 8px; }
.opt-wire .pfeat-caption .pfeat-name { font-size: 12.5px; color: var(--text-2); font-weight: 600; }

/* text stages (placement caption, angle claim, copy line): auto-height so the
   words fit on the flat stage */
.pfeat-stage.pfeat-text {
  height: auto; min-height: 96px;
  padding: 18px 6px 6px;
  background: none; border: 0;
  place-items: start stretch;
}

/* placement: the aspect ratio as a proportional frame — a neutral wireframe */
.pfeat-ar {
  border-radius: var(--r);
  border: 2px solid var(--border-2);
  background: var(--panel);
  display: grid; place-items: center;
}
.pfeat-ar-label {
  font-family: var(--mono); font-size: 13px; font-weight: 700;
  color: var(--muted); letter-spacing: .04em;
}

/* copy line as the subject — centred, no inner frame (the stage is the frame) */
.pfeat-copytext {
  border: 0; background: none; padding: 0;
  text-align: start; max-width: 100%;
  -webkit-line-clamp: 5;
}
.pfeat-copytext[dir="rtl"] { line-height: 1.8; }

/* angle claim as the subject — a centred serif statement */
.pfeat-claim, .md blockquote.pfeat-claim {
  margin: 0; border: 0; padding: 0;
  font-size: 15px; font-weight: 600; font-style: normal; line-height: 1.5;
  color: var(--text); text-align: center;
  overflow-wrap: anywhere; max-width: 100%;
  unicode-bidi: plaintext;
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
}

/* a SHORT tag pill: sentence case, soft accent tint, never truncated (DESIGN §3) */
.pfeat-tag {
  display: inline-block;
  padding: 2px 9px; border-radius: var(--r-pill);
  background: var(--panel-3); color: var(--text-2);
  font-size: 10.5px; font-weight: 600;
  max-width: 100%; overflow-wrap: anywhere;
}
/* The life stage. Outlined rather than filled so it reads as a second,
   quieter fact next to the awareness pill — and so three cards in a row can be
   scanned for "are these actually three different people?" in one look. That
   question is the whole reason it exists (public/life-stages.mjs). */
.pfeat-tag2 {
  background: none;
  border: 1px solid var(--border-2);
  color: var(--muted);
  font-weight: 500;
}

.pfeat-name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.opt-angle .pfeat-caption .pfeat-name { font-size: 14px; }

/* the pains: folded until the stage is clicked */
.pfeat-details {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows .28s ease, opacity .22s ease;
}
.pfeat-details > * { overflow: hidden; }
.pfeat.is-open .pfeat-details { grid-template-rows: 1fr; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .pfeat-details { transition: none; }
}

/* Hover is a shadow + border shift, not a lift (DESIGN §3). One considered
   state beats the same translateY on every card in the grid. */
.opt-card { transition: border-color .14s ease, background .14s ease, opacity .12s ease, box-shadow .16s ease; }

@media (prefers-reduced-motion: reduce) {
  .opt-card { transition: none; }
}

/* --- chosen / answered --- */
/* ---- "Write my own": the fourth card on every copy step ---- */
/* Dashed, because it is an empty slot the owner fills, not a proposal. Only
   ever on a step whose answer is a line of text — public/options.mjs decides. */
.opt-own { border-style: dashed; border-color: var(--border-2); background: var(--panel-2); }
.opt-own:hover { border-color: var(--accent-line); }
.opt-own.has-text, .opt-own:focus-within { border-color: var(--accent); border-style: solid; }
.opt-badge-own { background: transparent; border: 1px dashed var(--border-2); color: var(--muted); }
.opt-badge-own .ico { width: 11px; height: 11px; }
.opt-own-box {
  display: block; width: 100%; resize: vertical;
  min-height: 66px; max-height: 220px;
  font: inherit; font-size: 12.5px; line-height: 1.45; color: var(--text);
  padding: 7px 9px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: var(--panel);
}
.opt-own-box::placeholder { color: var(--muted); }
.opt-own-box:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus); }
.opt-own-box:read-only { opacity: .8; cursor: default; }
.opt-own-use { margin-top: auto; align-self: flex-end; font-size: 12px; padding: 3px 12px; }
.opt-own:not(.is-armed) .opt-own-box,
.opt-own:not(.is-armed) .opt-own-use { display: none; }
.opt-own:not(.is-armed) { cursor: pointer; }
.opt-own .opt-summary { margin-bottom: 6px; }

.options.is-answered .opt-card { opacity: .55; }
.options.is-answered .opt-card.is-chosen { opacity: 1; }
.options.is-answered .opt-card:not(.is-chosen) .pfeat-stage,
.options.is-answered .opt-card:not(.is-chosen) .opt-visual:not(.pfeat),
.options.is-answered .opt-card:not(.is-chosen) .opt-thumb,
.options.is-answered .opt-card:not(.is-chosen) .opt-summary,
.options.is-answered .opt-card:not(.is-chosen) .pfeat-meta,
.options.is-answered .opt-card:not(.is-chosen) .opt-badge,
.options.is-answered .opt-card:not(.is-chosen) .opt-own-box,
.options.is-answered .opt-choose,
.options.is-answered .opt-own-use { display: none; }
/* folded cards shrink-wrap their label instead of stretching to match the
   chosen card's full height */
.options.is-answered .options-grid { align-items: start; }
.options.is-answered .opt-card:not(.is-chosen) .pfeat-caption { padding-top: 2px; }
.opt-card.is-chosen {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent-soft) 55%, var(--panel));
}
.opt-card.is-chosen .opt-badge {
  background: var(--accent-btn); border-color: var(--accent-btn); color: var(--accent-fg);
}
.opt-card.is-chosen .opt-chosen-tag { display: inline; }
.opt-card.is-chosen .opt-visual { background: var(--panel); }

.options-actions { display: flex; gap: var(--s2); margin-top: var(--s2); }
.options-actions .btn { font-size: 12px; padding: 4px 10px; }

/* the multi-pick bar rides the bottom of the viewport while you scroll cards */
.options-bar {
  position: sticky; bottom: var(--s2); z-index: 3;
  display: flex; align-items: center; gap: var(--s2);
  margin-top: var(--s2);
  padding: var(--s2);
  border: 1px solid var(--accent-line); border-radius: var(--r);
  background: var(--panel);
  box-shadow: var(--shadow);
}
.options-bar .btn { font-size: 12px; padding: 4px 10px; }
.options-bar .opt-clear { background: var(--panel); }

@media (prefers-reduced-motion: reduce) {
  .opt-card, .opt-select { transition: none; }
}

@media (min-width: 561px) {
  /* four cards read as 2x2, not three and an orphan with a hole beside it */
  .options-grid:has(> .opt-card:nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(200px, 1fr)); }
}
@media (max-width: 560px) {
  .options-grid { grid-template-columns: 1fr; }
}

/* ------------------------------ model picker ------------------------------ */
/* Still the same <select id="modelPick"> wired to /api/chat-models and
   PATCH /api/sessions/:id - only the chrome changed: it now reads as one of
   the composer's pills, with the sparkles mark supplied by the wrapper. */

.cbtn-sel { padding-right: 6px; position: relative; }
.model-pick {
  appearance: none;
  max-width: 150px;
  border: 0; background: transparent;
  padding: 0 16px 0 0;
  font: inherit; font-size: 12px; font-weight: 500; line-height: 1.2;
  color: var(--text-2); cursor: pointer;
  text-overflow: ellipsis;
}
/* The caret is a mask painted with a theme token, never a baked-in hex — a
   hard-coded grey chevron disappears against the dark composer. */
.cbtn-sel::after {
  content: ""; position: absolute; right: 7px; top: 50%; margin-top: -6px;
  width: 12px; height: 12px; pointer-events: none; background: var(--muted);
  -webkit-mask: var(--caret) center / 12px 12px no-repeat;
          mask: var(--caret) center / 12px 12px no-repeat;
}
.cbtn-sel:hover::after { background: var(--text-2); }
.model-pick:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-xs); }
.model-pick option { color: var(--text); background: var(--panel); }
.model-pick option:disabled { color: var(--muted); }
.model-pick[hidden] { display: none; }
.cbtn-sel:has(.model-pick[hidden]) { display: none; }

/* ------------------------------ preview panel ------------------------------ */

.preview {
  border-left: 1px solid var(--border); background: var(--panel);
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  position: relative;
}
.pv-hd {
  flex: none; display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s3) var(--s3) var(--s4);
  height: 52px;                          /* matches .thread-hd — one baseline */
  border-bottom: 1px solid var(--border);
}
.pv-panes { flex: 1 1 auto; min-height: 0; display: flex; }
.pv-scroll {
  flex: 1 1 auto; min-height: 0; min-width: 0;
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  padding: var(--s4);
}
.pv-body { display: flex; flex-direction: column; gap: var(--s4); }
.pv-empty { text-align: center; padding: var(--s7) var(--s4); color: var(--muted); }
.pv-empty[hidden] { display: none; }
.pv-empty-title { margin: 0 0 var(--s1); font-size: 13.5px; font-weight: 600; color: var(--text-2); }
.pv-empty-sub { margin: 0; font-size: 12.5px; max-width: 30ch; margin-inline: auto; line-height: 1.5; }

/* ---- Overview: every creative the brand has, newest first ---- */

.ov-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: var(--s3);
  align-items: start;
}
.ov-grid:empty { display: none; }
.ov-card {
  display: flex; flex-direction: column; gap: 5px;
  padding: 0; border: 0; background: none; font: inherit; color: inherit;
  cursor: pointer; text-align: left; width: 100%; min-width: 0;
}
.ov-frame {
  position: relative; display: block; width: 100%; aspect-ratio: 4 / 5;
  border-radius: var(--r-sm); overflow: hidden;
  border: 1px solid var(--border); background: var(--panel-3);
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.ov-frame .shot { width: 100%; height: 100%; object-fit: cover; display: block; }
.ov-card:hover .ov-frame { border-color: var(--border-2); box-shadow: var(--shadow); transform: translateY(-1px); }
.ov-card:focus-visible { outline: none; box-shadow: none; }
.ov-card:focus-visible .ov-frame { border-color: var(--accent); box-shadow: var(--focus); }
.ov-card.is-sel .ov-frame { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.ov-card.is-new .ov-frame { animation: ov-land .5s cubic-bezier(.16,1,.3,1); }
@keyframes ov-land { from { transform: scale(.94); opacity: .35; } }

/* placeholder while a job is still drawing */
.ov-ph {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  color: var(--muted); font-size: 10.5px;
}
.ov-ph .ico { width: 14px; height: 14px; opacity: .6; }
.ov-frame.is-busy::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 20%, var(--shimmer) 50%, transparent 80%);
  background-size: 220% 100%; animation: shimmer 1.5s linear infinite;
}
.ov-ft { display: flex; align-items: center; gap: 5px; min-width: 0; }
.ov-id {
  font-size: 11.5px; font-weight: 600; color: var(--text-2);
  font-variant-numeric: tabular-nums; flex: none;
}
.ov-ft .pill {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  font-size: 9.5px; padding: 0 6px;
}
.ov-card:disabled { cursor: default; }
.ov-card:disabled .ov-frame { border-style: dashed; }
.ov-card:disabled:hover .ov-frame { box-shadow: none; transform: none; border-color: var(--border); }
@media (prefers-reduced-motion: reduce) {
  .ov-frame, .ov-card:hover .ov-frame { transition: none; transform: none; }
  .ov-card.is-new .ov-frame { animation: none; }
  .ov-frame.is-busy::after { animation: none; }
}

/* ---- the "a render landed" bar ---- */
/* Only ever shown when a manual pick inside the last 30 s blocked the steal.
   A row, not a floating pill: it pushes the panel down instead of covering the
   placement tabs it would otherwise land on. */
.pv-new {
  flex: none; width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 8px var(--s4);
  font: inherit; font-size: 12.5px; font-weight: 600;
  background: var(--accent-soft); color: var(--accent-ink);
  border: 0; border-bottom: 1px solid var(--accent-line);
  cursor: pointer; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pv-new:hover { background: var(--panel-3); }
.pv-new .dot-live { background: var(--accent); }
.pv-new[hidden] { display: none; }

/* ---- the stage and the controls laid over it ---- */
/* The creative is the reason the panel exists, so nothing sits on a line of
   its own to describe it: the carousel arrows, the "1 of 26" counter and the
   expand button all float over the stage and only surface on hover. That is
   three rows of chrome reclaimed for the image. */
.pv-view { position: relative; display: grid; }
.pv-view:hover .pv-step:not(:disabled),
.pv-view:focus-within .pv-step:not(:disabled) { opacity: 1; }
.pv-view:hover .pv-expand, .pv-view:focus-within .pv-expand { opacity: 1; }

/* flex:none, or the stage shrinks and crops the creative inside the scroller.
   The frame lives on the IMAGE, not on the stage, so it hugs the creative at
   every panel width - a fixed-height stage letterboxes a 4:5 render in grey the
   moment the owner drags the splitter wider, which is the opposite of the
   point of dragging it. */
.pv-stage { flex: none; display: grid; place-items: center; min-height: 120px; }
.pv-stage .shot {
  display: block; width: auto; height: auto;
  max-width: 100%; max-height: min(58vh, 580px);
  border-radius: var(--r); border: 1px solid var(--border);
  background: var(--panel-3);
  box-shadow: var(--shadow-xs);
}
/* ---- placement mockups ---- */
/* Reels and Feed composite the creative into phone chrome drawn here in CSS:
   no images, no fonts, no requests. The one inviolable rule is object-fit:
   contain — a placement preview that crops the copy off a creative is worse
   than no preview, because it misreports what Meta would run. When the shapes
   disagree the creative letterboxes inside the slot, exactly as Meta does it,
   and #pvFit says so in words. */
.pv-stage[data-frame="reels"], .pv-stage[data-frame="feed"] { width: 100%; }

.ph {
  width: 100%; margin: 0 auto;
  border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--border-2); background: var(--panel);
  box-shadow: var(--shadow-xs);
  /* the mat behind a letterboxed creative — hatched, so empty space reads as
     empty rather than as part of the ad */
  --ph-mat: var(--panel-3);
  --ph-mat-2: var(--panel-2);
}
.ph-feed { max-width: min(100%, 330px); }
.ph-reel {
  max-width: min(100%, 268px); position: relative;
  aspect-ratio: 9/16; background: #000; border-color: #000;
  --ph-mat: #000; --ph-mat-2: #0C0C0C;
}

.ph-hd { display: flex; align-items: center; gap: 8px; padding: 8px 10px; min-width: 0; }
.ph-av {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; line-height: 1;
  color: var(--accent-fg); background: var(--accent);
}
.ph-who { min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.ph-name {
  font-size: 12px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ph-sub { font-size: 10px; color: var(--muted); }
.ph-hd .ph-more { margin-left: auto; color: var(--muted); }
.ph-hd .ph-more .ico { width: 14px; height: 14px; }

/* the slot itself: the creative is contained, never cropped */
.pv-stage .ph-media {
  position: relative; width: 100%;
  aspect-ratio: var(--slot, 4 / 5);
  display: grid; place-items: center; overflow: hidden;
  background:
    repeating-linear-gradient(45deg, var(--ph-mat) 0 7px, var(--ph-mat-2) 7px 14px);
}
.pv-stage .ph-media .shot {
  width: 100%; height: 100%; max-width: none; max-height: none;
  object-fit: contain; display: block;
  border: 0; border-radius: 0; background: transparent;
}

.ph-acts { display: flex; align-items: center; gap: 12px; padding: 8px 10px 4px; color: var(--text-2); }
.ph-acts .ico { width: 17px; height: 17px; }
.ph-acts .ph-save { margin-left: auto; }
.ph-cta {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 10px 10px; font-size: 12px; font-weight: 600; color: var(--text);
  border-top: 1px solid var(--border);
}
.ph-cta .ico { width: 14px; height: 14px; margin-left: auto; color: var(--muted); }

/* ---- the Reels / Stories overlay ---- */
.ph-reel .ph-media { position: absolute; inset: 0; aspect-ratio: auto; height: 100%; }
.ph-ov { position: absolute; inset: 0; display: flex; flex-direction: column; color: #fff; }
.ph-ov::before, .ph-ov::after {
  content: ''; position: absolute; left: 0; right: 0; pointer-events: none;
}
.ph-ov::before { top: 0; height: 86px; background: linear-gradient(rgba(0,0,0,.55), transparent); }
.ph-ov::after { bottom: 0; height: 132px; background: linear-gradient(transparent, rgba(0,0,0,.62)); }
.ph-ov > * { position: relative; z-index: 1; }
.ph-prog { display: flex; gap: 3px; padding: 8px 10px 0; }
.ph-prog i { flex: 1 1 0; height: 2px; border-radius: 2px; background: rgba(255,255,255,.34); }
.ph-prog i.on { background: #fff; }
.ph-reel .ph-hd { padding: 8px 10px; }
.ph-reel .ph-av { width: 22px; height: 22px; font-size: 10.5px; box-shadow: 0 0 0 1.5px rgba(255,255,255,.85); }
.ph-reel .ph-name { color: #fff; font-size: 11.5px; }
.ph-reel .ph-sub { color: rgba(255,255,255,.78); }
.ph-follow {
  margin-left: auto; flex: none;
  font-size: 10px; font-weight: 600; color: #fff;
  border: 1px solid rgba(255,255,255,.7); border-radius: var(--r-xs); padding: 2px 7px;
}
.ph-rail {
  position: absolute; right: 8px; bottom: 74px;
  display: flex; flex-direction: column; align-items: center; gap: 13px;
  color: #fff;
}
/* the rail sits on the creative itself, which may be cream — two shadows keep
   white icons legible on a light ad without painting a slab over the art */
.ph-rail .ico {
  width: 19px; height: 19px;
  filter: drop-shadow(0 0 1px rgba(0,0,0,.85)) drop-shadow(0 1px 3px rgba(0,0,0,.6));
}
.ph-foot { margin-top: auto; padding: 8px 10px 10px; display: flex; flex-direction: column; gap: 7px; }
.ph-track { display: flex; align-items: center; gap: 5px; font-size: 10px; color: rgba(255,255,255,.85); }
.ph-track .ico { width: 11px; height: 11px; }
.ph-learn {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 10px; border-radius: var(--r-sm);
  background: rgba(255,255,255,.92); color: #101010;
  font-size: 11.5px; font-weight: 700;
}
.ph-learn .ico { width: 13px; height: 13px; }

/* ---- the fit verdict under the stage ---- */
.pv-fit {
  margin: 0; font-size: 11.5px; line-height: 1.45;
  padding: 7px 9px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: var(--panel-2); color: var(--muted);
}
.pv-fit.is-ok { border-color: var(--ok-line); background: var(--ok-soft); color: var(--ok); }
.pv-fit.is-warn { border-color: var(--warn-line); background: var(--warn-soft); color: var(--warn); }

/* the counter and the expand button: glass chips in the stage's corners */
.pv-count, .pv-expand {
  position: absolute; z-index: 2; bottom: var(--s2);
  font-size: 11px; font-variant-numeric: tabular-nums; line-height: 1;
  color: #fff; background: rgba(16, 16, 18, .62);
  border: 0; border-radius: var(--r-pill);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
}
.pv-count { left: var(--s2); padding: 5px 9px; }
.pv-count:empty { display: none; }
.pv-expand {
  right: var(--s2); padding: 5px; display: grid; place-items: center;
  cursor: pointer; pointer-events: auto; opacity: 0;
  transition: opacity .12s ease, background .12s ease;
}
.pv-expand .ico { width: 15px; height: 15px; }
.pv-expand:hover { background: rgba(16, 16, 18, .82); }
.pv-expand:focus-visible { outline: none; opacity: 1; box-shadow: var(--focus); }

/* the carousel arrows — centred on the creative, invisible until wanted */
.pv-step {
  position: absolute; z-index: 2; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer;
  color: #fff; background: rgba(16, 16, 18, .62);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  opacity: 0; transition: opacity .12s ease, background .12s ease;
}
.pv-step .ico { width: 17px; height: 17px; }
.pv-step:hover:not(:disabled) { background: rgba(16, 16, 18, .82); }
.pv-step:focus-visible { outline: none; opacity: 1; box-shadow: var(--focus); }
/* a dead end is hidden, not greyed: there is no "previous" to explain */
.pv-step:disabled { opacity: 0 !important; pointer-events: none; }
.pv-step-prev { left: var(--s2); }
.pv-step-next { right: var(--s2); }
/* touch has no hover, so the controls are simply always there */
@media (hover: none) {
  .pv-step:not(:disabled), .pv-expand { opacity: 1; }
}

/* the frame tabs, centred under the creative they reshape */
.pv-frames { align-self: center; margin-top: calc(-1 * var(--s2)); }

/* ---- the hero line: the creative's name ---- */
/* This is the title of the page, so it is set like one. The input carries no
   chrome until the pencil unlocks it, and then it looks like the field it has
   become — the old label/value row read as a form because it was one. */
.pv-title { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.pv-name {
  flex: 1 1 auto; min-width: 0;
  font: inherit; font-size: 17px; font-weight: 600; line-height: 1.25;
  letter-spacing: -.01em; color: var(--text);
  border: 1px solid transparent; border-radius: var(--r-xs);
  background: transparent; padding: 3px 5px; margin-inline-start: -5px;
  text-overflow: ellipsis;
}
.pv-name:read-only { cursor: default; }
.pv-name:not(:read-only) {
  background: var(--panel-2); border-color: var(--accent); margin-inline-start: 0;
}
.pv-name:focus { outline: none; box-shadow: var(--focus); }
.pv-name-edit { flex: none; color: var(--muted); }
.pv-title:hover .pv-name-edit { color: var(--text-2); }
.pv-name-edit:hover:not(:disabled) { color: var(--accent-ink); }
.pv-name-edit:disabled { opacity: .4; cursor: default; }
.pv-name-edit .ico { width: 15px; height: 15px; }
.pv-title .pill { flex: none; }

/* the concept the agent recorded — the subtitle under the title */
.pv-concept {
  margin: calc(-1 * var(--s3)) 0 0;
  font-size: 12.5px; line-height: 1.5; color: var(--text-2); overflow-wrap: anywhere;
}
.pv-concept[hidden] { display: none; }

/* ---- the one quiet caption under the creative ---- */
/* Everything the old STATUS / ASPECT / MODEL / COST / FILE box said, on one
   line, in the weight it deserves. The path is behind the button, not on it. */
.pv-caption {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--s2);
  font-size: 11.5px; color: var(--muted); min-width: 0;
}
.pv-caption:empty { display: none; }
.pv-cap-bit { font-variant-numeric: tabular-nums; }
.pv-cap-bit + .pv-cap-bit::before,
.pv-cap-bit + .pv-cap-file::before { content: "·"; margin-inline-end: var(--s2); color: var(--border-2); }
.pv-cap-file {
  display: inline-flex; align-items: center; gap: 4px; min-width: 0;
  font: inherit; font-size: 11.5px; color: var(--muted);
  border: 0; background: none; padding: 0; cursor: pointer;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pv-cap-file:hover { color: var(--accent-ink); }
.pv-cap-file:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-xs); }
.pv-cap-file .ico { width: 12px; height: 12px; flex: none; }
.pv-cap-file span { overflow: hidden; text-overflow: ellipsis; }

.pv-sec { display: flex; flex-direction: column; gap: var(--s2); }
.pv-note { margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--muted); }

/* the concept's provenance, when the server wrote the record itself — a real
   footnote under the caption now, not a grey box of its own. */
.pv-cnote {
  margin: calc(-1 * var(--s3) + 2px) 0 0;
  font-size: 11px; line-height: 1.45; color: var(--muted); opacity: .8;
  /* one line, then hover. Two wrapped lines of "recorded by the app because
     the render finished before its record was written" sat directly under the
     creative and read louder than the ad it was footnoting. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pv-cnote[hidden] { display: none; }

/* ---- what is left of the detail list ---- */
/* Five of its rows moved to the title and the caption. What remains is the
   concept's own parameters, set as quiet label/value pairs — no box, no fill. */
.pv-facts {
  margin: 0; display: grid; grid-template-columns: minmax(0, 8em) 1fr;
  gap: 3px var(--s3); font-size: 12px;
  padding-top: var(--s3); border-top: 1px solid var(--border);
}
.pv-facts:empty { display: none; }
.pv-facts dt { color: var(--muted); overflow-wrap: anywhere; }
.pv-facts dd { margin: 0; color: var(--text-2); line-height: 1.45; overflow-wrap: anywhere; }

/* ---- placement rows ---- */
/* Five rows, each derived from the board every render. Nothing is stored, so
   nothing can go stale: a tick means a sibling ad really exists. */
/* A list, not a table: icon, name, the ratio right-aligned in mono, then the
   one thing this row lets you do. Rows are separated by a hairline rather than
   by a box, so five of them read as a list and not as a form. */
#pvPlacements { display: flex; flex-direction: column; }
.place {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: nowrap;
  font-size: 12.5px; padding: 7px 0; min-width: 0;
  border-top: 1px solid var(--border);
}
.place:first-child { border-top: 0; }
.place-dot {
  width: 20px; height: 20px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--border-2); background: var(--panel-2); color: var(--muted);
}
.place-dot .ico { width: 12px; height: 12px; }
.place-name {
  flex: 1 1 auto; min-width: 0; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* right-aligned in mono, so five ratios line up into a column you can scan */
.place-ratio {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  flex: none; text-align: right; min-width: 4.2em;
  font-variant-numeric: tabular-nums;
}

.is-current .place-dot { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-ink); }
.is-current .place-name { color: var(--text); font-weight: 600; }
.is-exists .place-dot { border-color: var(--ok-line); background: var(--ok-soft); color: var(--ok); }

.place-state {
  flex: none; font-size: 11px; color: var(--muted);
  padding: 2px 0; text-align: right; min-width: 6.5em;
}
.is-current .place-state { color: var(--accent-ink); font-weight: 600; }

/* the two live actions: open the sibling, or write the render brief */
.place-act {
  flex: none;
  display: inline-flex; align-items: center; gap: 5px;
  font: inherit; font-size: 11px; font-weight: 600;
  padding: 3px 8px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--border-2); background: var(--panel-2); color: var(--text-2);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.place-act:hover { border-color: var(--border-2); color: var(--text); background: var(--panel-2); }
.place-act:focus-visible { outline: none; box-shadow: var(--focus); }
.place-act .place-cost { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
.place-act:hover .place-cost { color: inherit; }
.place-open { border-color: var(--ok-line); background: var(--ok-soft); color: var(--ok); }
.place-open:hover { border-color: var(--ok); background: var(--ok-soft); color: var(--ok); }

.compliance {
  display: flex; align-items: center; gap: var(--s2);
  padding: 8px 10px; border-radius: var(--r-sm);
  background: var(--ok-soft); border: 1px solid var(--ok-line);
  font-size: 12.5px; color: var(--ok);
}
.compliance .ico { width: 14px; height: 14px; }

/* One line of decision, one line of everything else.
   Approving is what the owner opened the panel to do, so it takes the width it
   can and Revise sits beside it. Kill used to share that line at equal weight,
   which read as though throwing the ad away were half the job — it is rare and
   destructive, so it lives at the far end of the quiet row instead, in red. */
.pv-actions { display: grid; grid-template-columns: 1fr auto; gap: var(--s2); }
.btn-wide { grid-column: 1 / -1; }
.pv-quiet {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px var(--s2); margin-top: calc(-1 * var(--s2));
}
.pv-quiet .spacer { flex: 1 1 auto; }
.pv-quiet .linky { font-size: 11.5px; }
.pv-quiet .linky:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-xs); }
/* the separator dot belongs between the links, not around the flexible gap */
.pv-quiet > .linky + .linky::before {
  content: "·"; margin-inline-end: var(--s2); color: var(--border-2);
}
.pv-quiet > .spacer + .linky::before { content: none; }
.linky-danger { color: var(--danger); }
.linky-danger:hover { color: var(--danger); text-decoration: underline; }
.linky-danger:disabled { opacity: .45; cursor: default; text-decoration: none; }

/* the section heading carries its own summary, so a placement count never
   costs a paragraph of its own under the rows */
.pv-sec-hd {
  display: flex; align-items: baseline; gap: var(--s2);
  justify-content: space-between; min-width: 0;
}
.pv-sec-hd .pv-note { text-align: end; }

/* --------------------- compatibility with competitors.css ---------------- */
/* competitors.css (owned by another module, loaded after this file) still
   carries the old two-pane layout's `@media (min-width:1024px){#tabChat{
   display:none}}`. In the new shell the Run tab is part of the segmented
   control and must show at every width, so beat it on specificity here rather
   than editing a file this stylesheet does not own. */
.seg #tabChat { display: inline-block; }

/* ------------------------------ responsive ------------------------------ */

@media (max-width: 1400px) { :root { --preview-w: 340px; } }

/* the sidebar collapses to an icon rail */
@media (max-width: 1200px) {
  :root { --rail-w: 64px; }
  .side { padding: var(--s2) 10px; align-items: center; }
  .brandpill-name, .newchat > span:not(.kbd), .kbd, .nav-label, .nav-badge,
  .nav-count, .nav-soon, .recents, .side-sep, .usercard-txt, .theme-opt-txt { display: none; }
  /* three icon buttons stacked: the rail has the height, not the width */
  .theme-seg { flex-direction: column; width: 40px; margin-top: auto; }
  .theme-opt { padding: 6px; }
  .usercard-anchor { margin-top: var(--s2); }
  .brandpill { justify-content: center; padding: 5px; }
  .brandpill .ico { display: none; }
  .newchat { justify-content: center; padding: 7px; }
  .nav-row { justify-content: center; padding: 8px; }
  .usercard { padding: 5px; justify-content: center; }
  .usercard .iconbtn { display: none; }
  .menu-anchor { position: static; }
  .side .menu { left: 8px; width: 240px; min-width: 240px; }
  #brandMenu { width: 240px; }
  .thread-scroll, .transcript { padding-left: var(--s4); padding-right: var(--s4); }
}

/* single column: the sidebar becomes a drawer, the preview folds away */
@media (max-width: 900px) {
  :root { --rail-w: 0px; }
  /* .is-preview-context-off is listed too: its desktop four-track template
     outranks a bare .shell, and with the sidebar fixed out of the flow the page
     landed in the 0px rail track — Settings at 390px rendered blank. */
  .shell, .shell.is-preview-off, .shell.is-preview-context-off { grid-template-columns: minmax(0,1fr); }
  .preview, .shell.is-preview-off .preview { display: none; }
  .splitwrap { display: none; }
  /* The shadow belongs to the OPEN drawer. On the closed one it spilled past
     the drawer's edge into the viewport: a grey smear down the left side of
     every page on a phone. */
  .side {
    position: fixed; inset: 0 auto 0 0; z-index: 120; width: 280px;
    transform: translateX(-100%); transition: transform .22s ease, box-shadow .22s ease;
  }
  .side.is-open { transform: none; box-shadow: var(--shadow-lg); }
  /* The drawer is 280px wide, so it gets the FULL sidebar back — the 1200px
     icon-rail rules above still match at this width and would otherwise leave
     a wide drawer full of unlabelled, centred icons. */
  .side { padding: var(--s3); align-items: stretch; }
  .brandpill-name, .nav-label, .usercard-txt, .side-sep { display: block; }
  .recents { display: flex; }
  .newchat > span:not(.kbd), .kbd, .nav-badge, .nav-count, .nav-soon,
  .theme-opt-txt { display: inline; }
  .brandpill { justify-content: flex-start; padding: 6px 8px; }
  .brandpill .ico { display: block; }
  .newchat { justify-content: flex-start; padding: 7px 9px; }
  .nav-row { justify-content: flex-start; padding: 6px 9px; }
  .theme-seg { flex-direction: row; width: auto; margin-top: var(--s2); }
  .theme-opt { padding: 4px 6px; }
  .usercard { padding: 7px 8px; justify-content: flex-start; }
  .usercard .iconbtn { display: grid; }
  .menu-anchor { position: relative; }
  .side .menu { left: 0; width: auto; min-width: 210px; }
  #brandMenu { width: calc(280px - 2 * var(--s3)); }
  .side-scrim { position: fixed; inset: 0; z-index: 110; background: rgba(14,14,13,.5); }
  .side-scrim[hidden] { display: none; }
  .side-scrim { display: block; }
  .rail-toggle { display: inline-grid; }
  .transcript { padding-left: var(--s3); padding-right: var(--s3); }
  .composer-wrap { padding-left: var(--s3); padding-right: var(--s3); }
  .thread-hd { padding: 0 var(--s3); }
  .board-scroll { padding: var(--s3); }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .side { transition: none; }
}

@media (max-width: 640px) {
  .thread-title { display: none; }
  .thread-hd .cost { display: none; }
  .seg button { padding: 3px 8px; }
  .cbtn-dest { display: none; }        /* cosmetic; the model pickers are not */
  .cbtn-tag { display: none; }
  .model-pick { max-width: 116px; }
}

@media (max-width: 375px) {
  .thread-hd .badge, .thread-hd .cost, .badges { display: none; }
  .steps { padding: var(--s2) var(--s3) var(--s3); }
  .gen { width: 100px; }
  .refcard { width: 96px; }
  .seg button { padding: 3px 7px; }
}

/* -------------------------- recents: rename / delete ---------------------- */
/* The row is the hit target; the two actions only appear on hover or focus so
   the list stays calm, but they are always reachable by keyboard. */
.recent-row {
  display: flex;
  align-items: center;
  gap: 2px;
  border-radius: var(--r-sm);
}
.recent-row .recent { flex: 1 1 auto; min-width: 0; }
.recent-act {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, color .12s ease, background-color .12s ease;
}
.recent-row:hover .recent-act,
.recent-row:focus-within .recent-act { opacity: 1; }
.recent-act:hover { background: var(--panel-3); color: var(--text); }
.recent-act.recent-del:hover { color: var(--danger); background: var(--danger-soft); }
.recent-act:focus-visible { opacity: 1; outline: none; box-shadow: var(--focus); }
.recent-act .ico { width: 14px; height: 14px; }

/* ------------------------------- confirm --------------------------------- */
.confirm-back {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: var(--s4);
  background: rgba(16, 20, 28, .45);
}
.confirm {
  width: min(420px, 100%);
  padding: var(--s5);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--panel);
  box-shadow: var(--shadow-lg);
}
.confirm-title { margin: 0 0 var(--s2); font-size: 16px; font-weight: 600; }
.confirm-body { margin: 0 0 var(--s5); color: var(--text-2); line-height: 1.5; }
.confirm-body:has(+ .confirm-details) { margin-bottom: var(--s3); }
.confirm-details {
  margin: 0 0 var(--s5); padding-left: 1.1em;
  color: var(--text-2); font-size: 13px; line-height: 1.55;
}
.confirm-details li + li { margin-top: 2px; }
.confirm-actions { display: flex; justify-content: flex-end; gap: var(--s2); }

@media (prefers-reduced-motion: reduce) {
  .recent-act { transition: none; }
}

/* ---------------------- the reference behind a Replicate ------------------- */
/* A Replicate click used to send a line of prose and nothing else, so "Replicate
   NYLA's ad" meant nothing an hour later and there was no way to judge the
   render against what it was copying. The picture belongs in the bubble. */

.msg-ref {
  display: block;
  margin-top: var(--s2);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--panel-2);
  overflow: hidden;
  cursor: zoom-in;
  position: relative;
  max-width: 210px;
  line-height: 0;
}
.msg-ref:hover { border-color: var(--border-2); }
.msg-ref:focus-visible { outline: none; box-shadow: var(--focus); }
.msg-ref[hidden] { display: none; }
.msg-ref-img { display: block; width: 100%; height: auto; }

.msg-ref-tag {
  position: absolute;
  left: 6px;
  bottom: 6px;
  font-size: 10.5px;
  line-height: 1;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  color: var(--text-2);
  backdrop-filter: blur(3px);
}

/* Full size. One picture, one way out — not the board lightbox, which is a
   gallery with actions over board records. */
.refov {
  position: fixed;
  inset: 0;
  z-index: 320;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(6px);
}
.refov-img {
  max-width: min(92vw, 900px);
  max-height: 92vh;
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
.refov-close {
  position: absolute;
  top: var(--s3);
  right: var(--s3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--panel);
  color: var(--text-2);
  cursor: pointer;
}
.refov-close:hover { background: var(--panel-2); }
.refov-close .ico { width: 16px; height: 16px; }

/* ---- the empty canvas carries state, not decoration ---- */
/* The reference fills this space with a team of agent avatars; this app has one
   agent, so it shows what the owner can act on instead. Buttons, not cards with
   a link inside: the whole thing is the target, and it reaches the keyboard for
   free. */
.empty-cards {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s3);
  margin-top: var(--s5); position: relative; z-index: 1;
}
.empty-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-width: 168px; padding: var(--s3) var(--s4);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r); box-shadow: var(--shadow-xs);
  font: inherit; text-align: left; cursor: pointer;
  transition: border-color .12s ease, box-shadow .12s ease;
}
/* hover is a border and shadow shift — DESIGN.md keeps the accent for the
   committing action, and a wash here is the tell it exists to prevent */
.empty-card:hover { border-color: var(--border-2); box-shadow: var(--shadow); }
.empty-card:focus-visible { outline: none; box-shadow: var(--focus); }
.empty-card-n {
  font-family: var(--mono); font-size: 22px; font-weight: 600; line-height: 1.1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
/* "new since you last looked" is a true live status, so it takes the live hue
   the store directory uses for ads running right now — one meaning, one colour */
.empty-card.is-live .empty-card-n { color: var(--live-ink); }
.empty-card-label { font-size: 13px; font-weight: 500; color: var(--text); }
.empty-card-hint { font-size: 11.5px; color: var(--muted); }

@media (prefers-reduced-motion: reduce) { .empty-card { transition: none; } }

/* ---- switching sections ---- */
/* Panes used to appear instantly, which reads as a jump rather than a change of
   place — especially next to Settings and Research, which also blanked to
   "Loading..." on every visit (fixed in app.js: they keep their content now).
   A short fade is enough to make the swap feel deliberate; anything longer
   would make a fast app feel slow. The pane does not move, only resolves — a
   slide would fight the sidebar, which does not move either. */
.pane.is-active { animation: pane-in 130ms ease-out both; }
@keyframes pane-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* The chat is the one pane people return to constantly, and a fade on every
   return is a tic. It is also the pane that keeps its scroll position, where a
   fade reads as a reload. */
#chatPane.is-active { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .pane.is-active { animation: none; }
}

/* The lightbox waits for the real file rather than upscaling a 480px thumb, so
   there is a moment with nothing in the frame. A quiet pulse says "coming",
   which an empty rectangle does not. */
.lb-stage.is-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--border-2); border-top-color: var(--accent);
  animation: lb-spin 700ms linear infinite;
}
@keyframes lb-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .lb-stage.is-loading::after { animation: none; border-top-color: var(--border-2); }
}

/* ---- Layers: an ad as a document ---- */
.lay-body { display: flex; flex-direction: column; gap: var(--s4); min-height: 0; }
.lay-bar { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.lay-bar .spacer { flex: 1 1 auto; }
.lay-input {
  font: inherit; font-size: 13px; color: var(--text);
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 6px 8px; max-width: 100%;
}
/* Shrinkable, so the ad picker gives way instead of pushing Export PNG onto a
   second row — five controls plus a select do not fit beside the ad preview. */
.lay-pick { flex: 1 1 150px; min-width: 120px; max-width: 420px; }

.lay-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 280px;
  gap: var(--s4); align-items: start; min-height: 0;
}
/* The stage is the point of the screen, so it gets the room. A fixed height
   keeps the canvas from growing past the fold on a long ad — the document is
   fitted into it, never cropped. */
.lay-stage {
  display: grid; place-items: center;
  min-height: 420px; height: min(62vh, 680px);
  background: var(--panel-3); border: 1px solid var(--border); border-radius: var(--r);
  overflow: hidden;
}
.lay-stage canvas { display: block; cursor: grab; box-shadow: var(--shadow); }
.lay-stage canvas:active { cursor: grabbing; }
/* The canvas takes keyboard focus now (arrows move, Alt+arrows resize), so it
   shows where focus is like every other control. */
.lay-stage canvas:focus-visible { outline: none; box-shadow: var(--shadow), var(--focus); }
/* Exact position and size, four compact number fields in a row. */
.lay-geo { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
.lay-numf { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--muted); }
.lay-num { min-width: 0; font-variant-numeric: tabular-nums; }
.lay-num:disabled { opacity: .55; }
@media (max-width: 480px) { .lay-geo { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Cutting an object out runs two models and takes ~30s. Dimming the canvas is
   the only honest signal that the click landed and the wait is expected. */
.lay-stage canvas.is-busy { opacity: .55; cursor: progress; pointer-events: none; }

/* "Select object" is a mode, not a one-shot: it stays armed until the click
   lands, so it has to look pressed while it waits. */
#layPickObj.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.lay-status { margin: var(--s2) 0 0; font-size: 12px; color: var(--muted); min-height: 1em; }
.lay-status.is-bad { color: var(--danger); }

/* The embedded editor takes the whole screen. Anything smaller is unusable:
   Photopea puts a toolbar, two panel columns, a canvas and a fixed-width ad
   rail in the same frame, and every one of those is a fixed number of pixels.
   Inside the reading column's 880px cap the ad rail alone was 37% of the
   editor. Width is the only lever we have over that, so while the editor is
   open the page gives up its cap, its heading and its bottom padding. */
#layersPane.is-editing .page-head { display: none; }
#layersPane.is-editing .page-body { max-width: none; padding-left: var(--s4); padding-right: var(--s4); padding-bottom: var(--s4); }
.lay-edithost { margin-top: var(--s2); height: min(78vh, 900px); min-height: 520px; }
#layersPane.is-editing .lay-edithost { height: calc(100vh - 116px); }
.pp-frame {
  width: 100%; height: 100%; display: block;
  border: 1px solid var(--border); border-radius: var(--r); background: var(--panel-3);
}
.lay-editbar { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.lay-editbar .spacer { flex: 1 1 auto; }

.lay-side { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.lay-h { margin: 0; font-size: 11.5px; font-weight: 600; color: var(--muted); letter-spacing: .02em; }
.lay-list { display: flex; flex-direction: column; gap: 2px; max-height: 210px; overflow-y: auto; }
.lay-item {
  display: block; width: 100%; text-align: left;
  padding: 7px 9px; border: 1px solid transparent; border-radius: var(--r-sm);
  background: var(--panel); font: inherit; font-size: 12.5px; color: var(--text-2);
  cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lay-item:hover { border-color: var(--border); color: var(--text); }
/* chosen is fill + weight, the same grammar the nav and the recents use */
.lay-item.is-sel { background: var(--panel-3); color: var(--text); font-weight: 600; }
.lay-item:focus-visible { outline: none; box-shadow: var(--focus); }

.lay-inspector {
  display: flex; flex-direction: column; gap: var(--s3);
  padding: var(--s3); background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--r);
}
.lay-field { display: flex; flex-direction: column; gap: 4px; }
.lay-field > span { font-size: 11.5px; color: var(--muted); }
.lay-field input[type="range"] { width: 100%; }
.lay-field input[type="color"] { width: 48px; height: 28px; padding: 2px; border: 1px solid var(--border); border-radius: var(--r-xs); background: var(--panel); }
.lay-field textarea.lay-input { resize: vertical; min-height: 60px; }
.lay-del { align-self: flex-start; color: var(--danger); }
.lay-empty { margin: 0; font-size: 12px; color: var(--muted); }

@media (max-width: 1000px) {
  .lay-grid { grid-template-columns: minmax(0, 1fr); }
  .lay-stage { height: min(48vh, 460px); }
}

/* The sidebar's groups. A hairline instead of a heading: three rules of
   silence say "these are different kinds of place" without adding six words of
   chrome to a list of seven items. */
.nav-sep {
  height: 1px;
  margin: 6px 10px;
  background: var(--rail-line);
}
.nav-foot { margin-top: auto; padding-bottom: 4px; }
/* Spend is a button now, not a readout stuck over the feed. */
.cost {
  border: 0;
  background: transparent;
  font: inherit;
  cursor: pointer;
  border-radius: var(--r-xs);
}
.cost:hover { color: var(--text-2); }

/* On the discovery pages the page states its own title, so the chrome above it
   has nothing left to say. It collapses to the sidebar toggle rather than
   leaving a bare strip across the top. */
/* `height: 52px` is set above, so padding alone cannot collapse this. On the
   discovery pages the bar holds nothing at all — the page states its own title
   and the sidebar owns navigation — so it goes away entirely rather than
   leaving an unexplained empty strip across the top. Below 980px the sidebar
   collapses and the bar earns its place back, because the toggle that reopens
   it lives here. */
.thread-hd:has(#threadTitle:empty) { display: none; }
.thread-hd #threadTitle:empty { display: none; }
@media (max-width: 980px) {
  .thread-hd:has(#threadTitle:empty) { display: flex; height: 44px; border-bottom: 0; }
}
/* …except in the Studio, where the bar also carries Run / Board / Research /
   Layers. Research and Layers own their title, so the rule above folded the
   whole bar — tabs included — and on desktop the only way back to Run was the
   sidebar (audit 2026-09-23). Same specificity, later rule: this one wins. */
.thread-hd:has(#seg:not([hidden])) { display: flex; }
/* Spend, in the account card, on its own line under the name — an account
   fact, kept, but no longer a number you read while browsing ads. */
.usercard .cost {
  display: block; margin-top: 1px; padding: 0; text-align: left;
  font-size: 10.5px; line-height: 1.3; color: var(--rail-muted); white-space: nowrap;
}
.usercard .cost:hover { color: var(--rail-text-2); }
/* Counters the old header owned. The code still updates them; nothing shows
   them, because the sidebar carries the same numbers where they mean something. */
#tabCompetitorsCount, #tabCompetitors { display: none !important; }
