/* CampaignDock — design system. Native CSS: cascade layers, nesting, OKLCH. */
@layer tokens, base, layout, primitives, components;

@layer tokens {
  :root {
    /* Surfaces */
    --page: oklch(0.9601 0.0108 76.6);   /* #F6F1EA — visibly not the card */
    --sidebar: oklch(0.9473 0.0103 81.8); /* #F1EDE6 — chips and neutral fills. The name is
       historical: the rail reads --surface-frame. */
    --card: oklch(1 0 0);                 /* #FFFFFF */
    --inset: oklch(0.9771 0.0074 80.7);   /* #FAF7F2 — subdued strips have to differ from white */

    /* Lines */
    --border: oklch(0.8694 0.0168 79.3);  /* #DAD3C8 */
    --border2: oklch(0.9087 0.0150 80.7); /* #E6E0D6 */
    --hairline: oklch(0.9387 0.0120 79.8);/* #EFEAE2 */

    /* Text */
    --ink: oklch(0.2070 0.0075 67.4);     /* #1A1714 */
    /* A second ink, between --ink and --soft. It arrived as a bare hex in nine places
       and as --bulk-ink-2 in the Bulk palette, and once as the fallback half of a
       reference to --muted-strong, a token nobody ever defined — so the fallback was
       quietly doing all the work. It is a real tier, so it gets a real name. */
    --ink-2: oklch(0.3903 0.0141 71.7);   /* #4A443D */
    /* Three greys, not five. --muted and --label were a fourth and fifth tier a point or two
       from their neighbours; Warm Sunset folds them into --soft and --dim. --soft is the
       lightest grey that still passes 4.5:1 on white. */
    --soft: oklch(0.5069 0.0165 74.3);    /* #6B645B */
    --dim: oklch(0.6438 0.0187 76.0);     /* #948C81 */

    /* Accent */
    --accent: oklch(0.4941 0.1841 270.5);     /* #3D52C8 */
    --accent-soft: oklch(0.9548 0.0170 267.8);/* #EBF0FC */
    --accent-ink: oklch(0.4271 0.1812 269.5); /* #2B3EB0 */
    --accent-tint: oklch(0.9714 0.0107 268.4); /* #F2F5FD — palest blue, glyph tiles */
    --accent-border: oklch(0.8694 0.0486 271.2); /* #C8D3F5 */

    /* ── Navigation v2 ───────────────────────────────────────────────────────
       The first tokens from Ben's Claude Design system, scoped to the nav. They keep
       the design system's role names — long and semantic where the app's are short and
       appearance-based (--ink, --soft, --r-full) — because those names are already
       right for when the rest of the app migrates, and renaming twice is worse than
       reading two vocabularies for one release.

       ⚠ --border-frame / -card / -divider / -row / -control read confusingly next to
       --border / --border2 / --hairline. They cannot collide: custom properties match
       on the exact name only. When the app migrates the mapping is
       --border-frame <- --border, --border-divider <- --border2, --hairline stays.

       Three design-system tokens are deliberately NOT here, because the app already
       has them within a point or two and a second home for one colour is how a palette
       rots: --text-secondary and --text-tertiary are --soft, --text-default is
       --ink. */
    --surface-frame: oklch(0.9801 0.0074 80.7);          /* #FBF8F3 — the rail */
    --surface-inverse: oklch(0.2070 0.0075 67.4 / 0.86);  /* --ink at 86%, blurred — the top bar, and
       the only inverse surface in the product. It never scrolls, never gains a colour
       and never holds a primary button. */
    --surface-inverse-raised: oklch(1 0 0 / 0.08);       /* chips on the bar */
    --surface-inverse-hover: oklch(1 0 0 / 0.12);        /* and their border */

    --text-on-inverse: oklch(1 0 0);
    --text-on-inverse-label: oklch(1 0 0 / 0.75);
    --text-on-inverse-icon: oklch(1 0 0 / 0.75);
    --text-nav: var(--ink-2);                    /* an idle rail row */

    /* Four border weights, not interchangeable: card, divider, row, control. Warm Sunset
       gives the first three the same values as --border / --border2 / --hairline, so they
       READ those rather than repeating them — one literal per colour. */
    --border-frame: var(--border);
    --border-card: var(--border);
    --border-divider: var(--border2);
    --border-row: var(--hairline);
    --border-control: oklch(0.8328 0.0187 78.2); /* #CFC7BB */

    /* Frame metrics. */
    --nav-w: 232px;
    --nav-row-h: 32px;
    --topbar-h: 48px;
    --radius-badge: 5px;
    /* The primary button is a keycap: a lit top edge, a hard 2px edge underneath that it
       travels into when pressed. Ink, never the accent — blue is for links and selection. */
    --shadow-key: inset 0 1px 0 oklch(1 0 0 / 0.14), 0 2px 0 oklch(0 0 0), 0 3px 3px oklch(0 0 0 / 0.12);
    --shadow-key-pressed: inset 0 1px 0 oklch(1 0 0 / 0.1), 0 0 0 oklch(0 0 0), 0 1px 1px oklch(0 0 0 / 0.1);


    /* Status */
    --badge-amber: oklch(0.6658 0.1574 58.3); /* #D97706 — status dots. Not the nav's
       counts any more: those went ink and mono in v2. */
    --icon-tile: oklch(0.9556 0.0075 84.6);   /* #F4F1EB — glyph tiles. The menu sheet
       dropped its own in v2; the rest of the app still uses these. */
    --chevron: oklch(0.7801 0.0155 67.6);     /* #C0B8AE — row chevrons */
    --ok: oklch(0.5960 0.1274 163.2);    /* #059669 */
    /* Tint + strong pairs for delta/status treatments (text-on-tint). The four
       semantic status tints (bg / ink / dot) reuse these: green=ok-*, danger=err-*,
       amber=the two below + badge-amber dot, neutral=sidebar/soft/dim. */
    --ok-tint: #E4F3EC;   --ok-strong: oklch(0.5081 0.1049 165.6); /* #047857 */
    --err-tint: #F9E9E5;  --err-strong: oklch(0.5165 0.1566 34.5); /* #B23E25 */
    --amber-tint: #F6EEDD; --amber-ink: oklch(0.5553 0.1455 49.0); /* #B45309 */
    --warn: var(--badge-amber);
    --err: oklch(0.5771 0.2152 27.3);    /* #DC2626 */
    --on-accent: oklch(0.9761 0.0041 91.4);

    /* Type, on whole pixels: 11 / 12 / 13 / 14. Nothing below 11 — the old 9px step is
       gone, and the half-pixel tiers (10.5, 11.5, 12.5 beside 13) were the design decision
       this comment used to say was still owed. Warm Sunset made it. */
    --t-xs: 11px;
    --t-sm: 12px;
    --t-base: 13px;
    --t-lg: 14px;
    /* The design system named one the app actually uses, so one stops being a literal:
       Campaign Page v6 sets a page title at 20px. Its 18px card title has no consumer here,
       and Drift on /styleguide lists a token nothing reads — so it stays unnamed until one
       does. The rest (16/17/19/22/26/40) are still literals, still waiting on a decision. */
    --t-2xl: 20px;

    --r-sm: 5px;
    --r: 6px;
    --r-md: 8px;
    --r-lg: 10px;
    --r-xl: 14px;
    --r-tile: 7px;   /* icon tiles — the header badge. Panels and popovers use --r-card */
    /* 12px, not the 16 this file carried since the first restyle: the design system's
       radius scale is card 12 / control 8 / badge 6 / tile 7 / pill 999, and one card
       radius for the app is the whole point of the token. */
    --r-card: 12px;  /* cards */
    --r-full: 999px; /* pills, avatars, anything fully round */

    /* ── Density ─────────────────────────────────────────────────────────────
       The design system's component density, and the first spacing tokens in the app:
       every other padding and gap in this file is a literal. These are the ones Campaign
       Page v6 consumes, and no more. The wider --space-1..10 scale is deliberately absent
       until something reads it — /styleguide lists declared-but-never-referenced tokens
       under Drift, so an unused token is a visible defect rather than a free option.

       ⚠ Read the design system's other names as the app's existing tokens, never as new
       ones. A second home for one value is how a palette rots:
         --surface-card      -> --card          --surface-canvas    -> --page
         --surface-subdued   -> --inset         --surface-subdued-alt -> --inset
         --text-default/body -> --ink/--ink-2   --text-secondary    -> --soft
         --text-tertiary     -> --soft          --text-disabled     -> --dim
         --accent-tint-bg    -> --accent-soft   --accent-tint-text  -> --accent-ink
         --accent-tint-border-> --accent-border --shadow-card       -> --shadow-sm
         --success-dot       -> --ok            --warning-dot       -> --badge-amber
         --critical          -> --err           --neutral-dot       -> --dim
         --radius-card/control/badge/tile/pill  -> --r-card/--r-md/--r/--r-tile/--r-full
         (--radius-panel also -> --r-card: an inset panel and its card share one radius)
         --font-mono         -> --mono
         --fw- and --lh- weights and line-heights  -> plain literals
       ⚠ Never write a star followed by a slash in here: it closes this comment early and
       eats the declaration underneath. /styleguide's Drift panel caught exactly that.
       ⚠ And NOT the --fs- family: --t-* is already the app's type scale across 509
       declarations, and two type scales is the rot this rule exists to prevent. */
    --card-header-pad: 12px 16px;
    --card-footer-pad: 10px 16px;
    --control-gap: 8px;
    --card-stack-gap: 16px;
    --grid-gutter: 20px;
    --control-h: 32px;
    /* 26, not the design system's 28: its 28 is the segmented control, and every small
       control on the Campaign v6 artboard measures 24–26. */
    --control-h-sm: 26px;
    /* A tab is taller than a control: it is a target you hit while reading, not one you
       aim at. Both tab rows read it — the page tabs and Client Detail's. */
    --tab-h: 34px;
    /* Everything that is a chip rather than a control: the status pill, the creative-launch
       marker, the queued capsule, a count. One height is what makes a row of mixed chips
       sit on one line. */
    --badge-h: 20px;
    --rail-w: 276px;
    --content-max: 1040px;
    /* The index pages' column. --content-max is the reading width a campaign page uses; this
       is the wider one a roster or a dashboard table gets, and it is what the artboards draw. */
    --content-wide: 1320px;

    /* Elevation, in three steps: a hairline for resting cards, a lift for popovers
       and menus, an overlay for modals and drawers. */
    --shadow-sm: 0 1px 2px oklch(0.25 0.05 50 / 0.06), inset 0 0 0 0.5px oklch(1 0 0 / 0.6);
    --shadow-md: 0 10px 28px oklch(0.2 0 0 / 0.14);
    --shadow-lg: 0 20px 50px oklch(0.2 0 0 / 0.25);
    /* Not elevation either: a resting control's one-pixel bevel. */
    --shadow-button: 0 1px 0 oklch(0.22 0.005 68 / 0.04);
    /* Not elevation: the active tab's underline. A shadow rather than a border so it can
       overlap the header's own rule without taking part in layout — which is the whole
       trick that lets a tab row sit ON the line instead of above it. */
    --shadow-tab-active: inset 0 -2px 0 var(--ink);

    /* Not elevation — this says "focused". Ink since Warm Sunset: a focused control takes
       an ink border and this soft ink halo, and the accent stays out of the chrome. */
    --ring: 0 0 0 2px color-mix(in srgb, var(--ink) 9%, transparent);

    --mono: ui-monospace, "SF Mono", Menlo, monospace;
  }

  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
  @keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
  @keyframes toast {
    0% { opacity: 0; transform: translateY(8px); }
    8%, 88% { opacity: 1; transform: none; }
    /* ⚠ visibility, not just opacity: a faded toast is still in the DOM, and now that it
       holds buttons it would go on swallowing clicks over the foot of the page. */
    100% { opacity: 0; transform: translateY(8px); visibility: hidden; }
  }
}

@layer base {
  * { box-sizing: border-box; }

  body {
    margin: 0;
    background: var(--page);
    position: relative;
    color: var(--ink);
    font-family: -apple-system, "Helvetica Neue", Helvetica, system-ui, sans-serif;
    -webkit-font-smoothing: antialiased;
    /* Tabular figures everywhere: a column of numbers must not jitter as it updates. */
    font-feature-settings: "tnum", "cv11";
  }

  /* Sunset Bloom: four soft blooms over a warm base, and a 4px grain. Signed-in shell only —
     :has(> .app) — so login, the share gate, the consent screen and print keep the flat
     canvas. Fixed and behind everything; a page shows it by not painting its own background. */
  body:has(> .app)::before {
    content: ""; position: fixed; inset: 0; z-index: -2;
    background:
      radial-gradient(42% 46% at 18% 38%, oklch(0.75 0.17 55 / 0.30), transparent 70%),
      radial-gradient(40% 44% at 78% -6%, oklch(0.86 0.17 92 / 0.30), transparent 70%),
      radial-gradient(38% 40% at 56% 108%, oklch(0.65 0.22 16 / 0.22), transparent 70%),
      radial-gradient(44% 46% at 104% 72%, oklch(0.77 0.16 70 / 0.28), transparent 70%),
      oklch(0.9876 0.0085 67.7);
  }
  body:has(> .app)::after {
    content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background-image: radial-gradient(circle at center, oklch(0 0 0) 1px, transparent 1px);
    background-size: 4px 4px; opacity: 0.028;
  }

  button, input, select, textarea { font-family: inherit; }

  /* `hidden` always wins. A class with its own `display` beats the UA rule, which is how a
     hidden panel stays on screen — this app fixed that four separate times before this line. */
  [hidden] { display: none !important; }

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

  /* Two type roles, two classes. The design system names a card title and a column/field
     micro-label as separate roles; this app had one .section-label doing both jobs, at values
     matching neither — which is why every card in it shouted in 9px capitals. A heading should
     not change meaning depending on which container it lands in, so the split is by name.

     ⚠ 23 further classes still duplicate the micro-label idea — .details-label, .perf-kpi-label,
     .links-key, .lib-label, .cd-person-head and the rest. They are the next reduction, not this
     one; /styleguide names them so the size of it stays visible. */
  .cd-card-title {
    flex: 1;
    min-width: 0;
    font-size: var(--t-base);
    font-weight: 600;
    color: var(--ink);
  }
  .section-label {
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--soft);
    letter-spacing: 0.02em;
    text-transform: uppercase;
  }
}

@layer layout {
  /* A column since v2: the top bar spans the full width, and .app-body is the
     rail-plus-content row beneath it. Mobile keeps the same column and simply
     swaps the rail for the bottom bar, so there is no direction to override. */
  .app { display: flex; flex-direction: column; height: 100dvh; overflow: hidden; }
  .app-body { flex: 1 1 auto; display: flex; min-height: 0; }
  /* min-width: 0 or a wide table pushes the row instead of scrolling inside it. */
  .main { flex: 1; position: relative; overflow: hidden; min-width: 0; }

  .view {
    position: absolute; inset: 0; overflow-y: auto; animation: fadeIn 0.12s ease;
    /* The page's own side gutter: 20px until the window is wider than the column, then
       whatever centres the column. Declared here rather than on the header because the header
       and the body have to agree — and the way to guarantee two things agree is to have them
       read one thing. `100%` resolves where the variable is USED, and every consumer's
       containing block is this element, which is why one declaration serves them all. */
    --page-gutter: max(20px, (100% - var(--content-wide)) / 2);
  }
  .view--column { display: flex; flex-direction: column; }
  /* The Clipper sets its own gutter — a 1200px column — here and for phones, because the
     phone rule below still skips it (it used to be the one full-bleed page). */
  .view.clipper-page { --page-gutter: max(20px, (100% - 1200px) / 2); }
  @media (max-width: 700px) { .view.clipper-page { --page-gutter: 16px; } }

  .flash { margin: 0; padding: 10px 16px; font-size: var(--t-base); }
}

/* What every page is built from. Below `components` on purpose: a page block can still
   override a primitive without !important, and a primitive never has to know about a page. */
@layer primitives {
  /* Shared button system, at the design system's control metrics: a control is
     --control-h tall and --r-md round, so a button lines up with the input beside it.
     ⚠ inline-flex + height rather than vertical padding, because half this app's buttons
     are a <label> or a <summary> (file pickers, disclosures) — padding sized those two
     differently from a real <button> and always had. */
  .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    height: var(--control-h);
    border: 1px solid transparent;
    border-radius: var(--r-md);
    padding: 0 12px;
    font-size: var(--t-base);
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    background: transparent;
  }
  /* A bare .btn that is a link — a Cancel — reads as the quiet button it is, not as a blue
     link. :where keeps this below every variant, which set their own colour. */
  :where(a).btn { color: var(--ink-2); text-decoration: none; }
  :where(a).btn:hover { color: var(--ink); text-decoration: none; }
  /* One primary per view, and it is the keycap. .btn-dark was the same idea under a second
     name before there was a keycap; it keeps the name its seven callers use. */
  .btn-primary, .btn-dark, .btn-delete {
    padding: 0 14px; font-weight: 600;
    background: var(--ink); border-color: var(--ink); color: var(--text-on-inverse);
    box-shadow: var(--shadow-key);
    transition: transform 80ms linear, box-shadow 80ms linear;

    &:hover { background: color-mix(in srgb, var(--ink) 90%, white); }
    &:active { transform: translateY(2px); box-shadow: var(--shadow-key-pressed); }
    &:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }
  }
  /* The destructive keycap: the same key in red, for the confirm at the end of a delete. */
  .btn-delete {
    background: var(--err); border-color: color-mix(in srgb, var(--err) 82%, black);
    --shadow-key: inset 0 1px 0 oklch(1 0 0 / 0.14), 0 2px 0 color-mix(in srgb, var(--err) 55%, black), 0 3px 3px oklch(0 0 0 / 0.12);

    &:hover { background: color-mix(in srgb, var(--err) 88%, black); }
  }
  .btn-outline { border-color: var(--border-control); background: var(--card); color: var(--ink); box-shadow: var(--shadow-button); }
  .btn-ghost { border-color: var(--border-control); background: var(--card); color: var(--ink-2); box-shadow: var(--shadow-button); }
  .btn-ghost:hover, .btn-outline:hover { background: var(--inset); color: var(--ink); }
  .btn-sm { height: var(--control-h-sm); padding: 0 10px; font-size: var(--t-sm); border-radius: var(--r); }
  .btn-lg { height: 40px; padding: 0 22px; font-size: var(--t-base); }

  /* The card. Eighteen class names each spelled this recipe out; they keep their names (the
     views and the tests ask for them) and share the one rule. A new card adds its selector
     here rather than its own copy. */
  .portal-card, .timeline-panel,
  .cd-card, .integrations-list, .consent-card, .lib-table,
  .lib-panel, .lib-form, .q-group, .dk-trail, .dk-table,
  .call-card, .call-transcript, .sg-section, .clients-card, .inline-new {
    background: var(--card); border: 1px solid var(--border-card);
    border-radius: var(--r-card); box-shadow: var(--shadow-sm);
  }

  /* ── A new-record form, in the page ──
     It loads into a <turbo-frame data-inline-form> above the list it adds to. The frame is
     empty until then and takes no room; the form brings its own gap. */
  turbo-frame[data-inline-form] { display: block; }
  .inline-new { margin: 16px 0 0; box-shadow: var(--shadow-sm), 0 10px 30px -14px oklch(0.3 0.05 50 / 0.25); }
  .inline-new-head { padding: 12px 16px; border-bottom: 1px solid var(--border-divider); font-size: var(--t-lg); font-weight: 600; }
  .inline-new .form-errors { margin: 14px 16px 0; font-size: var(--t-sm); padding: 9px 12px; }
  .inline-new-body { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 16px; padding: 16px; }
  .inline-new .field-stack { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
  .inline-new .field-stack > label { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  .inline-new .label-optional { font-weight: 400; }
  .inline-new :is(input:not([type=submit], [type=hidden], [type=checkbox], [type=radio]), select) {
    height: 36px; width: 100%; min-width: 0; padding: 0 10px; font: inherit; font-size: var(--t-base); color: var(--ink);
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md); outline: 0;

    &::placeholder { color: var(--dim); }
    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
    &[aria-invalid] { border-color: var(--err); box-shadow: 0 0 0 2px color-mix(in srgb, var(--err) 12%, transparent); }
  }
  .inline-new input.code-input { width: 110px; font-family: var(--mono); font-weight: 600; letter-spacing: 0.06em; }
  .inline-new input.code-input::placeholder { font-weight: 500; text-transform: none; }
  .inline-new-foot {
    display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 12px 16px;
    border-top: 1px solid var(--border-divider); background: var(--inset);
    border-radius: 0 0 var(--r-card) var(--r-card);
  }
  /* The pieces a longer new-record form needs: a full-width cell, a textarea, a segmented
     choice, a currency + amount + period row, two dates, and a line of help. */
  .inline-new-full { grid-column: 1 / -1; }
  .inline-new-label { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  .inline-new textarea {
    width: 100%; min-width: 0; padding: 8px 10px; font: inherit; font-size: var(--t-base); line-height: 1.5; color: var(--ink);
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md); outline: 0; resize: vertical;

    &::placeholder { color: var(--dim); }
    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .inline-new select { appearance: none; }
  .inline-new select:has(option[value=""]:checked) { color: var(--dim); }
  .choice-pills {
    display: inline-flex; align-self: flex-start; gap: 2px; height: 36px; padding: 3px;
    background: var(--sidebar); border: 1px solid var(--border-row); border-radius: var(--r-md);
  }
  .choice-pills label {
    position: relative; display: inline-flex; align-items: center; padding: 0 12px; border-radius: 5px;
    font-size: 12.5px; font-weight: 500; color: var(--ink-2); white-space: nowrap; cursor: pointer;
  }
  .choice-pills input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
  .choice-pills label:has(input:checked) { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px oklch(0 0 0 / 0.08), 0 0 0 1px var(--border-row); }
  .choice-pills label:has(input:focus-visible) { box-shadow: var(--ring); }
  .budget-fields { display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; gap: 8px; }
  .date-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .inline-new-help { margin: 0; font-size: var(--t-sm); font-weight: 400; color: var(--soft); line-height: 1.45; }
  .inline-new-help strong { color: var(--ink); font-weight: 600; }
  .inline-new .register-toggle { display: flex; align-items: center; gap: 10px; font-size: var(--t-base); color: var(--ink); cursor: pointer; }
  .inline-new .form-errors ul { margin: 0; padding-left: 16px; }

  @media (max-width: 700px) {
    .budget-fields { grid-template-columns: 90px minmax(0, 1fr); }
    .budget-fields .choice-pills { grid-column: 1 / -1; }
    .inline-new-body { grid-template-columns: 1fr; padding: 14px; }
    .inline-new-foot .btn-primary { flex: 1; height: 38px; }
  }
}

@layer components {
  /* ── Top bar ─────────────────────────────────────────
     The only inverse surface in the product. Above the page (z-index 20) and below
     the toasts and the mobile sheet, all of which are meant
     to cover it. */
  .topbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 0 0 auto;
    height: var(--topbar-h);
    padding: 0 16px;
    /* Ink at 86% over a blur: the bloom shows through just enough that the bar reads as
       glass set on the page rather than a slab across it. */
    border-bottom: 1px solid oklch(0 0 0 / 0.35);
    color: var(--text-on-inverse);
    position: relative;
    z-index: 20;

    /* ⚠ The glass is a pseudo-element, not the bar's own background: backdrop-filter makes
       an element the containing block for position: fixed, and the account menu — a child
       of this bar — is a fixed bottom sheet on a phone. */
    &::before {
      content: ""; position: absolute; inset: 0; z-index: -1;
      background: var(--surface-inverse);
      backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%);
    }
  }

  .topbar-brand {
    display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
    font-size: var(--t-base); font-weight: 600;
    color: var(--text-on-inverse);

    & img { display: block; border-radius: var(--r-sm); }
  }

  /* Pushed right by margin, not by a spacer element — so the search field can be
     inserted before it later without anything to delete first. */
  .topbar-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }

  /* The chip says who, in a word. The full name and the address are in the menu it opens. */
  .account-chip {
    display: flex; align-items: center; gap: 8px;
    height: var(--control-h); padding: 0 10px 0 4px;
    border-radius: var(--r-md);
    border: 1px solid var(--surface-inverse-hover);
    cursor: pointer; list-style: none; white-space: nowrap;
    transition: background 90ms linear;

    &::-webkit-details-marker { display: none; }
    &:hover { background: var(--surface-inverse-raised); }
  }
  .user-menu[open] > .account-chip { background: var(--surface-inverse-hover); }

  /* avatar_tag renders an uploaded photo when there is one, initials when there
     isn't — so this has to work as both a disc and an image frame. */
  .account-mono {
    display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; flex-shrink: 0; overflow: hidden;
    border-radius: 50%;
    background: var(--text-on-inverse); color: var(--ink);
    font-size: 10px; font-weight: 600; line-height: 1;

    & img { width: 100%; height: 100%; object-fit: cover; }
  }
  .account-name { font-size: var(--t-base); font-weight: 500; color: var(--text-on-inverse); }
  .account-caret { color: var(--text-on-inverse-icon); opacity: 0.7; }

  /* ── Rail ────────────────────────────────────────────
     Whose workspace, then where you can go. */
  .rail {
    width: var(--nav-w);
    min-width: var(--nav-w);
    padding: 12px;
    overflow-y: auto;
    background: var(--surface-frame);
    border-right: 1px solid var(--border-frame);
    display: flex;
    flex-direction: column;
    gap: 14px;
  }

  /* A label, not a switcher — see layouts/_workspace. */
  .workspace {
    display: flex; align-items: center; gap: 9px; padding: 8px 10px;
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md);

    & .client-chip { width: 26px; height: 26px; }
  }
  .workspace-tile {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 26px; height: 26px; border-radius: var(--r); background: var(--ink);
    color: var(--text-on-inverse);
  }
  .workspace-text {
    min-width: 0; flex: 1;
    & b, & small { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    & b { font-size: var(--t-base); font-weight: 600; line-height: 1.2; }
    & small { font-size: var(--t-xs); color: var(--soft); }
  }

  .nav { display: grid; gap: 14px; }

  .nav-item {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    height: var(--nav-row-h);
    padding: 0 10px;
    /* The left edge is always there and only ever changes colour, so making a row active
       never moves its label by two pixels. */
    border-left: 2px solid transparent;
    border-radius: 7px;
    background: transparent;
    font-size: var(--t-base);
    font-weight: 500;
    line-height: 1;
    color: var(--text-nav);
    cursor: pointer;
    text-align: left;
    transition: background 90ms linear;

    /* The icons are stroked in currentColor, and sit one tier lighter than the label
       until the row is active, when both go to full ink. */
    & svg { flex: 0 0 auto; color: var(--soft); }

    /* The label, and the only thing here allowed to shrink — a long tool name ellipses
       rather than pushing its own count out of the rail. Named, not "& span": the count
       is a span too, and it was being stretched by the same rule. */
    & .nav-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    &:hover { background: var(--card); }

    /* The card's own fill and a 2px ink edge: the row reads as the open page's tab. */
    &.is-active {
      background: var(--card);
      border-left-color: var(--ink);
      border-radius: 0 7px 7px 0;
      box-shadow: var(--shadow-sm);
      color: var(--ink);

      & svg, & .count-badge { color: var(--ink); }
    }
    &.is-disabled { color: var(--dim); cursor: not-allowed; }
    &:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
  }

  /* The ungrouped rows are one block and each labelled group is another; .nav's gap is
     the space between blocks.
     ⚠ No divider rule — that gap is the whole separator. A border-top here has been
     reported as a bug once already. */
  .nav > .nav-item { margin-bottom: -13px; }
  .nav > .nav-item:last-of-type, .nav > .nav-item:last-child { margin-bottom: 0; }
  .nav-group { display: grid; gap: 1px; }
  /* A group head is a quiet sentence-case label, not the uppercase micro-label it borrows
     its element from. */
  .nav-group .section-label {
    display: block; padding: 4px 10px 6px;
    font-size: var(--t-xs); letter-spacing: 0.01em; text-transform: none; color: var(--dim);
  }

  /* The menu itself outlived the rail cell it used to sit in — the top-bar chip is
     the trigger now, and .account-chip is that trigger's skin. */
  .user-menu { position: relative; }
  .user-menu > summary { list-style: none; cursor: pointer; }
  .user-menu > summary::-webkit-details-marker { display: none; }

  .user-options {
    position: absolute; right: 0; top: 100%; margin-top: 6px; z-index: 40; width: 240px;
    background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card);
    box-shadow: 0 16px 40px -12px oklch(0 0 0 / 0.3); padding: 5px; color: var(--ink);
  }
  .user-options form { margin: 0; }
  .user-head {
    display: flex; align-items: center; gap: 10px; padding: 8px 9px 10px;
    border-bottom: 1px solid var(--border-row); margin-bottom: 4px;
  }
  .user-head-avatar {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 30px; height: 30px; border-radius: 50%; overflow: hidden;
    background: var(--accent); color: var(--on-accent); font-size: var(--t-xs); font-weight: 600;
    & img { width: 100%; height: 100%; object-fit: cover; }
  }
  .user-head-text {
    min-width: 0;
    & b { display: block; font-size: var(--t-base); font-weight: 600; }
    & small { display: block; font-size: var(--t-sm); color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  }
  .user-option {
    display: flex; align-items: center; gap: 9px; width: 100%; height: 34px; padding: 0 9px; border-radius: 7px;
    font-size: var(--t-base); font-weight: 400; color: var(--ink); background: none; border: none; cursor: pointer; text-align: left;

    & svg { color: var(--soft); }
    &:hover { background: var(--sidebar); }
    &.is-destructive, &.is-destructive svg { color: var(--err-strong); }
    &.is-destructive:hover { background: var(--err-tint); }
  }
  .user-menu-divider { height: 1px; background: var(--border-row); margin: 4px 2px; }

  /* The count — rail rows, mobile tabs and menu-sheet rows. ⚠ Despite living in shared/,
     it is nav-only. In a row it is a plain mono figure, right-aligned so the counts make a
     column; only the mobile tab and the Queue pill, where it has to be read off an icon,
     put it in a badge. */
  .count-badge {
    margin-left: auto; flex-shrink: 0;
    font-family: var(--mono); font-size: var(--t-xs); font-weight: 500; line-height: 1; color: var(--soft);
  }

  .avatar {
    width: 26px; height: 26px; border-radius: 50%;
    background: var(--border);
    display: flex; align-items: center; justify-content: center;
    font-size: var(--t-xs); font-weight: 600; color: var(--soft);
    flex-shrink: 0;
    overflow: hidden;
  }
  .avatar img, .person-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

  /* ── Tool header ── */
  /* The page header bar, built to its own front-end brief and shared by every full-app
     page: 32px tint tile + icon, 15px h1, 11.5px subtitle, pill actions on the right.
     Pages differ only in their furniture — a tile means "section home", a .tool-crumb link
     above the title means "one level in"; subtitles, tab chips and the outbox button are
     per-page. */
  /* ⚠ The page header is the CANVAS with a rule under it, not a white bar. .campaign-show-head
     has been exactly this since campaign-page-v6 and every artboard draws it this way; this
     copy and .cd-head simply never got it, which is what read as "the design didn't update".
     ⚠ padding-block / padding-inline, not the shorthand: the inline axis is the page's gutter
     and is shared with the body, so no override may reset it by accident. */
  .tool-header {
    padding-block: 16px;
    padding-inline: var(--page-gutter);
    min-height: 64px;
    border-bottom: 1px solid var(--border-frame);
    background: transparent;
    display: flex; align-items: center; justify-content: space-between; gap: 20px;
    flex-shrink: 0;

    /* Ring whatever is focusable, rather than listing classes: some header actions
       (.tool-crumb, for one) aren't .btn, and the next one won't be either. */
    & :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  }
  /* "One level in": a text crumb above the title naming the parent list. A detail page has
     no tile, so the title stays on the same left edge as a section home's tile would. */
  .tool-crumb {
    display: inline-flex; align-items: center; gap: 3px; margin-bottom: 2px;
    font-size: var(--t-sm); font-weight: 500; color: var(--soft);
    &:hover { color: var(--ink); text-decoration: none; }
    & svg { flex: none; margin-left: -3px; }
  }
  .tool-header .title-group { display: flex; align-items: center; gap: 13px; min-width: 0; }
  /* The middle of the flex chain is an unclassed wrapper div on 5 of the pages. Without
     min-width:0 an over-long title can't ellipsis — it shoves the button out of the bar. */
  .tool-header .title-group > * { min-width: 0; }
  .tool-header .title {
    font-size: var(--t-2xl); font-weight: 700; letter-spacing: -0.01em; line-height: 1.2;
    margin: 0; /* it's an <h1>; the UA's .67em would grow every bar by ~20px */
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tool-badge {
    width: 32px; height: 32px; background: var(--accent-soft); color: var(--accent);
    border-radius: var(--r-tile); display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  }

  /* ── Video Clipper ─────────────────────────────────
     A stage and the frames clipped from it, to tools.css's .vc / .stage / .pc2 / .frp.
     ⚠ The Clipper carries its own gutter (see .view.clipper-page) — it used to be the one
     full-bleed page, and the phone rule that sets every other page's gutter skips it. */
  .clipper { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 14px; align-items: start; padding: 14px var(--page-gutter) 24px; overflow-y: auto; }
  .player, .frames-panel { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .player { overflow: hidden; }
  /* The one dark surface on the page: it is a screen. */
  .player-stage { position: relative; aspect-ratio: 16 / 9; display: flex; align-items: center; justify-content: center; background: var(--ink); }
  .player-stage.is-over { outline: 2px dashed oklch(1 0 0 / 0.5); outline-offset: -10px; }
  .player-stage video { display: none; width: 100%; height: 100%; object-fit: contain; background: #000; cursor: pointer; }
  .is-loaded .player-stage video { display: block; }
  .is-loaded .player-empty { display: none; }
  .player-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 20px; text-align: center; color: oklch(1 0 0 / 0.7); }
  .player-empty-icon { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 12px; background: oklch(1 0 0 / 0.08); color: var(--text-on-inverse); }
  .player-empty b { font-size: var(--t-lg); font-weight: 600; color: var(--text-on-inverse); }
  .player-empty > span { max-width: 40ch; font-size: 12.5px; text-wrap: pretty; }
  .player-empty .btn { margin-top: 6px; }

  .player-controls { display: none; align-items: center; gap: 12px; padding: 10px 12px; border-top: 1px solid var(--border-divider); }
  .is-loaded .player-controls { display: flex; }
  .timecode { font: 500 12px var(--mono); color: var(--ink-2); white-space: nowrap; }
  /* A 6px bar inside a taller hit area — the padding is clickable, the bar is what shows. */
  .scrubber { flex: 1; min-width: 60px; height: 6px; padding: 10px 0; box-sizing: content-box; background: var(--icon-tile); background-clip: content-box; border-radius: var(--r-full); cursor: pointer; outline: 0; }
  .scrubber:focus-visible { box-shadow: var(--ring); }
  .scrubber-fill { width: 0; height: 100%; border-radius: var(--r-full); background: var(--ink); }
  .player-file { max-width: 22ch; font-size: var(--t-sm); color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .player-key { margin-left: 4px; padding: 1px 5px; border: 1px solid oklch(1 0 0 / 0.2); border-radius: 4px; background: oklch(1 0 0 / 0.12); font: 500 10.5px var(--mono); color: inherit; }
  @media (hover: none) { .player-key { display: none; } }

  .frames-panel { position: sticky; top: 0; display: flex; flex-direction: column; max-height: calc(100vh - 150px); }
  .frames-head { flex-shrink: 0; display: flex; align-items: baseline; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border-divider); }
  .frames-head b { font-size: var(--t-lg); font-weight: 600; }
  .frames-count { font: 12px var(--mono); color: var(--soft); }
  .frames-list { overflow-y: auto; display: flex; flex-direction: column; }
  .frames-empty { display: none; flex-direction: column; align-items: center; gap: 4px; padding: 36px 20px; text-align: center; color: var(--soft); }
  .frames-list:not(:has(.frame-row)) + .frames-empty { display: flex; }
  .frames-empty svg { margin-bottom: 4px; color: var(--dim); }
  .frames-empty b { font-size: 13.5px; font-weight: 600; color: var(--ink); }
  .frames-empty span { max-width: 44ch; font-size: 12.5px; text-wrap: pretty; }

  .frame-row {
    display: grid; grid-template-columns: 96px minmax(0, 1fr) 28px; gap: 10px; align-items: start;
    padding: 10px 10px 10px 12px; border-top: 1px solid var(--border-row);
    &:first-child { border-top: 0; }

    & .frame-thumb { display: block; width: 100%; aspect-ratio: 16 / 9; border: 1px solid var(--border-row); border-radius: 6px; object-fit: cover; background: var(--icon-tile); }
    & .frame-body { min-width: 0; }
    & .frame-name { display: flex; justify-content: space-between; gap: 8px; font-size: 12.5px; font-weight: 600; }
    & .frame-name small { font: 500 11px var(--mono); color: var(--dim); }
    & form { margin: 0; }
    /* Quiet until touched; the -7px puts its text on the frame name's left edge. */
    & textarea {
      display: block; width: calc(100% + 7px); min-height: 44px; margin: 4px 0 0 -7px; padding: 5px 7px; resize: none;
      border: 1px solid transparent; border-radius: 7px; background: transparent; outline: 0;
      font: inherit; font-size: 12.5px; line-height: 1.45; color: var(--ink);
      &:hover { background: var(--inset); border-color: var(--border-row); }
      &:focus { background: var(--card); border-color: var(--ink); }
    }
  }
  .frame-delete { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 7px; background: none; color: var(--dim); cursor: pointer; &:hover { background: var(--err-tint); color: var(--err); } }

  @media (max-width: 980px) {
    .clipper { grid-template-columns: minmax(0, 1fr); padding-bottom: 96px; }
    .frames-panel { position: static; max-height: none; }
  }
  @media (max-width: 700px) {
    .frame-row { grid-template-columns: 80px minmax(0, 1fr) 28px; }
    .player-controls { flex-wrap: wrap; }
    .player-file { display: none; }
    .scrubber { order: 3; flex-basis: 100%; padding: 19px 0; }   /* a 44px hit area for a thumb */
  }

  /* Shared empties + hints + copy feedback */
  .empty-state { margin: 0; padding: 22px; text-align: center; font-size: var(--t-base); color: var(--soft); }
  .hint { font-size: var(--t-base); color: var(--soft); margin: 6px 0; }
  .copied { color: var(--ok) !important; }

  /* ── Campaigns ───────────────────────────────────── */
  .campaigns-subtitle { font-size: var(--t-sm); color: var(--soft); margin-top: 2px; }
  /* A name inside a header's meta line that is also a way back to that list. Inherits the
     line's own size and colour — it is the same sentence, not a control dropped into it. */
  .campaigns-header-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
  /* A header action is a button, nothing more. This rule used to re-declare the radius as a
     pill and re-size by padding, which quietly undid the button system's own metrics on every
     header in the app — and it was the reason the design system's controls appeared not to
     land. What is left is the one thing a header genuinely needs: the action must not
     compress, because the title truncates first (that is what the min-width:0 chain on
     .title-group is for). Hover darkens the fill and nothing else — no lift, no growth. */
  .tool-header .btn { flex-shrink: 0; }
  /* A header carrying tabs becomes a column: title row first, tab row last, and no bottom
     padding, so the tabs reach the bar's own border and overlap it. Two pages pass tabs;
     every other header is a single centred row and never sees this rule. */
  .tool-header:has(.tool-tabs) {
    flex-wrap: wrap; align-items: flex-end; padding-bottom: 0; row-gap: 12px;

    & .title-group { align-items: flex-start; }
    & .tool-tabs { flex-basis: 100%; }
  }

  .campaigns-body { flex: 1; overflow: auto; padding-block: 0 40px; padding-inline: var(--page-gutter); }

  /* Controls row (agency): search + client/status filter menus. */
  .campaigns-controls { display: flex; align-items: center; gap: var(--control-gap); flex-wrap: wrap; padding: 16px 0 12px; }
  .filter-reset {
    height: var(--control-h); display: inline-flex; align-items: center; padding: 0 8px;
    font-size: var(--t-base); font-weight: 500; color: var(--soft);
    text-decoration: underline; text-underline-offset: 2px;

    &:hover { color: var(--ink); }
  }

  /* Card-wrapped table. overflow stays VISIBLE — a clip context (even overflow-x:auto
     forces overflow-y:auto) would crop the status/filter dropdowns that open below a
     row. The rounded look comes from the border-radius here + rounding the corner
     header/last cells so their backgrounds don't poke past the border. */
  /* border-collapse: separate is load-bearing — collapsed tables ignore cell
     border-radius, so the corner cells couldn't round and their square bg poked
     past the wrap's rounded border (overflow must stay visible for dropdowns).
     Separated borders let the corner cells round; dividers all live on the row's
     TOP edge, so a group separator is just a darker recolor of that one border. */
  .campaigns-table { width: 100%; min-width: 860px; border-collapse: separate; border-spacing: 0; }
  .campaigns-table th {
    text-align: left; font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
    color: var(--dim); padding: 13px 12px; white-space: nowrap;
  }
  .campaigns-table thead tr { background: var(--inset); }
  .campaigns-table thead th { border-bottom: 1px solid var(--border2); }
  .campaigns-table thead th:first-child { border-top-left-radius: var(--r-card); }
  .campaigns-table thead th:last-child { border-top-right-radius: var(--r-card); }
  .campaigns-table tbody tr:last-child td:first-child { border-bottom-left-radius: var(--r-card); }
  .campaigns-table tbody tr:last-child td:last-child { border-bottom-right-radius: var(--r-card); }
  .campaigns-table tbody td { border-top: 1px solid var(--hairline); }
  .campaigns-table tbody tr:first-child td { border-top: none; }
  .campaigns-table td { padding: 14px 12px; font-size: var(--t-base); vertical-align: middle; white-space: nowrap; }
  .campaigns-table th:first-child, .campaigns-table td:first-child { padding-left: 20px; }
  .campaigns-table th:last-child, .campaigns-table td:last-child { padding-right: 20px; }
  /* The table's own footer strip, inside the card — the count used to float under it as
     loose text, which read as a stray caption rather than the end of the table. */
  /* ── Campaigns, agency: one card of controls and a table grouped by client ──
     Scoped with .cmp-* because .campaigns-table is also the Approvals table, which keeps
     its own look until its page is done. Measured against the design system's campaigns.css. */
  .cmp-card { margin-top: 16px; max-width: 1240px; background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .cmp-card > .campaigns-controls { padding: 10px 16px; gap: 8px; border-bottom: 1px solid var(--border-divider); }
  .cmp-card .search-pill { flex: 1 1 140px; min-width: 0; width: auto; max-width: 260px; box-shadow: none; }
  .cmp-scroll { overflow-x: auto; }
  /* ⚠ While a menu is open the wrapper must not clip it; scrolling sideways can wait. */
  .cmp-card:has(details[open]) .cmp-scroll { overflow: visible; }
  .campaigns-table.cmp-table { min-width: 820px; table-layout: fixed; border-collapse: collapse; }
  .cmp-table thead tr { background: none; }
  .cmp-table th {
    padding: 9px 12px; background: var(--card); border-bottom: 1px solid var(--border-divider);
    font-size: var(--t-sm); font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--soft); text-align: left; white-space: nowrap;
  }
  .cmp-table th:first-child, .cmp-table tr.cmp-row td:first-child { padding-left: 16px; }
  .cmp-table th:last-child, .cmp-table td:last-child { padding-right: 16px; }
  .cmp-col-status { width: 210px; } .cmp-col-budget { width: 176px; } .cmp-col-creative { width: 116px; }
  .cmp-col-activity { width: 92px; } .cmp-col-update { width: 124px; }
  .cmp-table tbody td { padding: 10px 12px; border-top: 1px solid var(--border-row); vertical-align: middle; }
  .campaigns-table.cmp-table tbody tr.cmp-row { background: none; }
  .campaigns-table.cmp-table tbody tr.cmp-row:hover { background: var(--inset); }
  .cmp-table tr.cmp-group td { padding: 14px 16px 6px; border-top: 1px solid var(--border-divider); background: transparent; }
  .cmp-table tbody tr.cmp-group:first-child td { border-top: 0; }
  .campaigns-table.cmp-table tbody tr.cmp-group:hover { background: none; }
  .cmp-group-label { display: flex; align-items: center; gap: 10px; }
  .cmp-group-label .client-chip { width: 26px; height: 26px; border-radius: 7px; font-size: 9.5px; }
  .cmp-group-label b { font-size: 12.5px; font-weight: 600; color: var(--ink); }
  .cmp-group-label small { font-size: 11.5px; color: var(--soft); }
  /* ⚠ Two classes on the table: the shared rule that bolds this cell is declared later. */
  .campaigns-table.cmp-table .campaign-name { white-space: normal; font-weight: 400; }
  .cmp-table .campaign-name a { display: block; font-size: var(--t-base); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cmp-table .campaign-name a:hover { color: var(--accent-ink); text-decoration: none; }
  .cmp-sub { display: flex; align-items: center; gap: 6px; margin-top: 2px; font-size: var(--t-sm); color: var(--soft); white-space: nowrap; }
  .cmp-sep, .cmp-ongoing { color: var(--dim); }
  .cmp-budget-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; max-width: 150px; font-size: 12.5px; font-weight: 500; color: var(--ink); }
  .cmp-pct { font: 500 var(--t-xs) var(--mono); color: var(--soft); }
  .cmp-pct.is-over { color: var(--err); font-weight: 600; }
  .cmp-table .budget-bar { height: 4px; max-width: 150px; margin-top: 6px; background: var(--sidebar); }
  .cmp-table .budget-bar > span { background: var(--ink); }
  .cmp-table .budget-bar.is-over > span { background: var(--err); }
  .cmp-table .budget-bar--none { background: repeating-linear-gradient(90deg, var(--sidebar) 0 4px, transparent 4px 7px); }
  .cmp-table .campaign-last-creative, .cmp-table .campaign-last-update { font-family: inherit; font-size: 12.5px; color: var(--ink-2); }
  .cmp-table .campaign-last-creative.is-none { color: var(--dim); }
  .cmp-table .campaign-last-update { color: var(--soft); text-align: right; }
  .cmp-table .heatmap { grid-template-rows: repeat(7, 4px); grid-template-columns: none; grid-auto-columns: 4px; }
  .cmp-table .heatmap-cell { width: 4px; height: 4px; border-radius: 1px; }
  /* The status control: words with a dot until the row is pointed at, then visibly a control. */
  .cmp-status .status-menu > summary {
    display: inline-flex; align-items: center; gap: 7px; height: 28px; padding: 0 8px 0 9px;
    border: 1px solid transparent; border-radius: 7px; transition: border-color 0.12s, background 0.12s;
  }
  .cmp-status .status-pill { font-size: 12.5px; font-weight: 500; }
  .cmp-status .status-caret { opacity: 0; color: var(--dim); transition: opacity 0.12s, transform 0.18s; }
  .cmp-row:hover .cmp-status .status-menu > summary, .cmp-status .status-menu > summary:focus-visible { border-color: var(--border-control); background: var(--card); }
  .cmp-row:hover .cmp-status .status-caret { opacity: 1; }
  .cmp-status .status-menu[open] > summary { border-color: var(--ink); background: var(--card); box-shadow: var(--ring); }
  .cmp-status .status-menu[open] .status-caret { opacity: 1; transform: rotate(180deg); }
  .cmp-status .status-options { top: calc(100% + 6px); width: 220px; padding: 4px; border-color: var(--border-card); border-radius: 10px; box-shadow: 0 14px 34px -10px oklch(0 0 0 / 0.25); }
  .cmp-status .status-options.is-flipped { top: auto; bottom: calc(100% + 6px); margin: 0; }
  .cmp-status .status-option { padding: 7px 9px; border-radius: 7px; font-size: 12.5px; }
  .cmp-status .status-option:hover, .cmp-status .status-option:focus-visible { background: var(--inset); outline: 0; }
  .cmp-foot { margin: 0; padding: 10px 16px; border-top: 1px solid var(--border-divider); background: var(--inset); border-radius: 0 0 var(--r-card) var(--r-card); font-size: var(--t-sm); color: var(--soft); }
  .cmp-empty { padding: 40px 20px; text-align: center; color: var(--soft); }
  .cmp-empty svg { color: var(--dim); }
  .cmp-empty p { margin: 8px 0 0; font-size: var(--t-base); }

  @media (max-width: 1100px) and (min-width: 701px) {
    .campaigns-table.cmp-table { min-width: 0; }
    .cmp-table .cmp-col-creative, .cmp-table td.campaign-last-creative { display: none; }
    .cmp-col-status { width: 168px; } .cmp-col-budget { width: 140px; } .cmp-col-activity { width: 70px; } .cmp-col-update { width: 92px; }
    .cmp-table .heatmap { grid-template-rows: repeat(7, 3px); grid-auto-columns: 3px; }
    .cmp-table .heatmap-cell { width: 3px; height: 3px; }
  }
  @media (max-width: 700px) {
    .cmp-card .search-pill { flex-basis: 100%; max-width: none; }
    .cmp-card .filter-menu { flex: 1 1 calc(50% - 4px); }
    .cmp-card .filter-select, .cmp-card .filter-pill { width: 100%; min-width: 0; }
    .campaigns-table.cmp-table { min-width: 0; }
    .cmp-table, .cmp-table tbody, .cmp-table tr, .cmp-table td { display: block; }
    .cmp-table thead { display: none; }
    .cmp-table tr.cmp-group td { padding: 10px 14px 8px; }
    .cmp-table tr.cmp-row {
      display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "n n" "s b" "m u";
      gap: 8px 12px; padding: 12px 14px; border-top: 1px solid var(--border-row);
    }
    .cmp-table tr.cmp-row td { padding: 0; border: 0; white-space: normal; }
    .cmp-table td.campaign-activity { display: none; }
    .cmp-table td.campaign-name { grid-area: n; }
    .cmp-table td.cmp-status { grid-area: s; align-self: center; }
    .cmp-table td.campaign-budget { grid-area: b; width: 132px; align-self: center; }
    .cmp-table td.campaign-last-creative { grid-area: m; font-size: var(--t-sm); color: var(--soft); }
    .cmp-table td.campaign-last-creative::before { content: "Last creative "; color: var(--dim); }
    .cmp-table td.campaign-last-update { grid-area: u; font-size: var(--t-sm); }
    .cmp-status .status-menu > summary { border-color: var(--border-control); background: var(--card); }
    .cmp-status .status-caret { opacity: 1; }
    .cmp-status .status-options { width: min(260px, calc(100vw - 40px)); }
    .cmp-foot { flex-direction: column; align-items: flex-start; gap: 2px; }
  }

  .campaigns-foot {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: var(--card-footer-pad); background: var(--inset);
    border-top: 1px solid var(--border2);
    border-radius: 0 0 var(--r-card) var(--r-card);
    font-size: var(--t-sm); color: var(--dim);
  }
  .campaigns-foot-hint { color: var(--dim); }
  .campaigns-table th.th-right, .campaigns-table td.campaign-last-update { text-align: right; }
  .campaign-last-creative, .campaign-last-update {
    font-family: var(--mono); font-size: var(--t-sm); color: var(--ink-2);
  }
  .campaign-last-update { color: var(--dim); }
  .campaign-last-creative.is-none { font-family: inherit; color: var(--dim); }
  /* Zebra by client group (set server-side in campaigns/_campaign): even groups
     white, odd groups a whisper of warm grey. Hover is darker than both tints so
     it still reads on grey rows. Color is reserved for the status pill. */
  .campaigns-table tbody tr.campaign-row { background: var(--card); }
  .campaigns-table tbody tr.campaign-row--alt { background: #F8F5EF; }
  .campaigns-table tbody tr:hover { background: #EFEAE1; }
  .campaigns-table .campaign-name { font-weight: 700; }

  /* The agency Campaigns table reflows to stacked rows at phone width in its own block
     (.cmp-table, above). The older shared reflow that lived here served only that page
     and the Calls list, which is no longer a table. Approvals has its own, scoped under
     .creative-approvals. */
  .campaigns-table .campaign-last-update { color: var(--soft); font-size: var(--t-sm); }
  .campaign-row { cursor: pointer; }
  .campaign-name a { color: var(--ink); text-decoration: none; }
  .kpi-soon { color: var(--dim); font-size: var(--t-xs); font-style: italic; }

  /* ── Search pill + column filter menus (dashboard + approvals) ── */
  /* A search field is a control, so it measures like one: --control-h tall, --r-md round,
     --border-control edged. It used to be a full-round pill sized by padding, which is why it
     stood a whole 8px taller than the button beside it and never lined up. */
  .search-pill {
    display: flex; align-items: center; gap: 8px; margin: 0;
    height: var(--control-h); padding: 0 10px; width: 260px; max-width: 100%;
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md);
    box-shadow: var(--shadow-button); color: var(--dim);

    &:focus-within { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .search-pill input {
    border: none; background: transparent; outline: none; font-size: var(--t-base); color: var(--ink);
    width: 100%; padding: 0;
  }
  .search-pill input::placeholder { color: var(--dim); }
  /* The × is there only while there is something to clear. */
  .search-clear {
    display: none; flex: none; align-items: center; justify-content: center; width: 20px; height: 20px;
    margin-right: -4px; padding: 0; border: 0; border-radius: var(--r); background: transparent; color: var(--dim); cursor: pointer;

    &:hover { color: var(--ink); background: var(--inset); }
  }
  .search-pill.has-value .search-clear { display: inline-flex; }

  .filter-menu { position: relative; display: inline-block; }
  .filter-menu > summary { list-style: none; cursor: pointer; }
  .filter-menu > summary::-webkit-details-marker { display: none; }
  .filter-pill {
    display: inline-flex; align-items: center; gap: 7px;
    height: var(--control-h); padding: 0 10px; white-space: nowrap;
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md);
    box-shadow: var(--shadow-button); font-size: var(--t-base); font-weight: 500; color: var(--ink);
    transition: border-color 0.12s ease, background 0.12s ease;
  }
  .filter-pill:hover { background: var(--inset); }
  .filter-pill.is-active { color: var(--accent-ink); border-color: var(--accent-border); background: var(--accent-soft); }
  .filter-caret { opacity: 0.55; flex-shrink: 0; }

  /* The single-choice filter (shared/_filter_menu): label over value, and a menu flush with
     the control whose current option is marked by its left edge. */
  .filter-select {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    height: var(--control-h); min-width: 150px; padding: 0 10px; text-align: left;
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md);
    color: var(--dim);

    &:hover { border-color: var(--soft); }
    &.is-active, .filter-menu[open] > & { border-color: var(--ink); }
    & .filter-pill-label { display: block; font-size: 10px; line-height: 1; color: var(--dim); }
    & .filter-pill-value { display: block; margin-top: 2px; font-size: var(--t-sm); font-weight: 500; line-height: 1.1; color: var(--ink); white-space: nowrap; }
    & .filter-pill-value::before { content: none; }
  }
  .filter-menu--select .filter-options {
    top: calc(100% + 4px); right: auto; min-width: 100%; padding: 4px 0; gap: 0;
    border-color: var(--border-card); border-radius: var(--r-md); box-shadow: 0 8px 24px -8px oklch(0 0 0 / 0.12);
  }
  .filter-menu--select .filter-option {
    padding: 7px 12px; border-radius: 0; border-left: 2px solid transparent;
    font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); white-space: nowrap;

    &:hover { background: var(--inset); border-left-color: var(--border-control); text-decoration: none; }
    &.is-active { background: var(--inset); color: var(--ink); border-left-color: var(--ink); font-weight: 500; }
  }

  /* The popover shell, shared by the filter/sort menus, the share panel (.share-pop, in the
     Creative Set block) and a table row's ⋯ menu — each variant owns only its edge, width
     and spacing. */
  .filter-options, .share-pop, .row-menu-pop {
    position: absolute; top: calc(100% + 6px); z-index: 30;
    background: var(--card); border: 1px solid var(--border2); border-radius: var(--r-card);
    box-shadow: 0 12px 30px oklch(0.22 0.005 68 / 0.16);
    display: flex; flex-direction: column;
  }
  .filter-options { left: 0; padding: 6px; min-width: 214px; gap: 1px; }

  /* A row's ⋯ menu. Right-anchored because it hangs off the last column, and it opens
     inside a <details>, which row_link already treats as "not a click on the row". */
  /* Sits at the far end of its cell, not tacked onto the timestamp — so the ⋯ column lines
     up down the table and reads as a control rather than as more text. */
  .campaign-last-update:has(.row-menu) { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
  .row-menu { position: relative; flex-shrink: 0; }
  .row-menu > summary { list-style: none; cursor: pointer; }
  .row-menu > summary::-webkit-details-marker { display: none; }
  .row-menu-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; border-radius: 6px; color: var(--dim);
  }
  .row-menu-btn:hover { background: var(--hairline); color: var(--ink); }
  .row-menu-pop { right: 0; padding: 6px; min-width: 160px; gap: 1px; }
  .row-menu-item {
    padding: 8px 10px; border-radius: 6px; font-size: var(--t-base); font-weight: 600;
    color: var(--ink); text-decoration: none;
  }
  .row-menu-item:hover { background: var(--hairline); }
  .filter-options-head { padding: 8px 10px 6px; }
  .filter-option {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 8px 10px; border-radius: var(--r-md); font-size: var(--t-base); font-weight: 500; color: var(--ink);
  }
  .filter-option:hover { background: var(--inset); }
  .filter-option.is-active { color: var(--accent-ink); font-weight: 600; }
  .filter-check { color: var(--accent); }

  /* The multi-select sibling (shared/_filter_checklist): same popover, but each option is a
     tick box and the foot says how many are on. */
  /* The multi-select sibling (shared/_filter_checklist): the same select, a wider menu,
     and each option a tick box with its dot, its name and how many it would show. */
  .filter-checklist .filter-options { width: 270px; min-width: 0; padding: 0; gap: 0; }
  .filter-checklist .filter-options-head { padding: 10px 12px 8px; border-bottom: 1px solid var(--border-divider); }
  .filter-checklist .filter-options-head b { display: block; font-size: var(--t-sm); font-weight: 600; }
  .filter-options-hint { display: block; margin: 0; font-size: 11.5px; color: var(--soft); line-height: 1.4; }
  .filter-menu--select.filter-checklist .filter-option {
    display: grid; grid-template-columns: 15px 8px minmax(0, 1fr) auto; gap: 9px; align-items: center;
    margin: 0; padding: 7px 12px; border-left: 0;

    &.is-active { background: none; }
    &:hover, &.is-active:hover { background: var(--inset); }
    & .status-dot { width: 8px; height: 8px; }
  }
  .filter-option-label { flex: 1; }
  .filter-option-count { font: 500 var(--t-xs) var(--mono); color: var(--dim); }
  .filter-box {
    position: relative; width: 15px; height: 15px; flex-shrink: 0; border-radius: 4px;
    border: 1px solid var(--border-control); background: var(--card);
    display: inline-grid; place-content: center;

    &.is-on { border-color: var(--ink); background: var(--ink); }
    &.is-on::before { content: ""; width: 8px; height: 4px; border-left: 2px solid #fff; border-bottom: 2px solid #fff; transform: translateY(-1px) rotate(-45deg); }
  }
  .filter-count {
    display: inline-block; margin-left: 5px; padding: 2px 5px; border-radius: 999px; vertical-align: 1px;
    background: var(--ink); color: var(--text-on-inverse); font: 600 10px/1 var(--mono);
  }
  .filter-options-foot {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 8px 12px; background: var(--inset);
    border-top: 1px solid var(--border-divider); border-radius: 0 0 var(--r-md) var(--r-md);
    font-size: 11.5px; color: var(--soft);
  }
  .filter-clear {
    font-size: 11.5px; font-weight: 600; color: var(--accent-ink); text-decoration: none;

    &.is-disabled { color: var(--dim); pointer-events: none; }
  }

  /* Segmented count-pill filter (approvals Active/Approved/All) — active = blue tint. */
  /* The segmented control — a filter with two or three mutually exclusive answers, which is
     a different job from a tab and stays a different control. It is the shape
     .composer-toggle already is: an --r-md track holding --r segments, at --control-h-sm. */
  /* The segmented control: a 32px well holding 24px segments, so it lines up with the
     filter and the button beside it. The chosen segment is a raised white key, not a tint. */
  .seg-filter {
    display: inline-flex; gap: 2px; margin-left: auto; padding: 3px; height: var(--control-h);
    background: var(--sidebar); border: 1px solid var(--border-row); border-radius: var(--r-md);
  }
  .seg {
    display: inline-flex; align-items: center; gap: 6px;
    height: 24px; padding: 0 10px; border-radius: var(--r-sm);
    font-size: var(--t-base); font-weight: 500; color: var(--ink-2); white-space: nowrap;
  }
  .seg.is-active {
    background: var(--card); color: var(--ink);
    box-shadow: 0 1px 2px oklch(0 0 0 / 0.08), 0 0 0 1px var(--border-row);
  }
  .seg-count { font-family: var(--mono); font-size: var(--t-xs); font-weight: 500; color: var(--dim); }
  .seg.is-active .seg-count { color: var(--ink-2); }

  /* Board sortable column headers. */
  .cs-sort { display: inline-flex; align-items: center; gap: 4px; color: var(--dim); cursor: pointer; }
  .cs-sort:hover { color: var(--soft); }
  .cs-sort.is-active { color: var(--accent); }

  /* Striped "coming soon" budget bar */
  .budget-bar {
    height: 8px; border-radius: var(--r-full); overflow: hidden;
    background: repeating-linear-gradient(45deg, #E7E1D8, #E7E1D8 4px, #F0ECE6 4px, #F0ECE6 8px);
  }

  /* Generic stat label/value (perf module + portal; portal overrides below) */
  .stat-label { font-size: var(--t-xs); font-weight: 600; color: var(--dim); letter-spacing: 0.08em; text-transform: uppercase; }
  .stat-value { font-size: var(--t-lg); font-weight: 700; color: var(--ink); margin-top: 4px; }

  /* ── Client portal cards ─────────────────────────── */
  /* Portal header (client Campaigns view) shares the 920px column with the cards. */
  /* One rule, not two: these used to be equal-specificity selectors whose padding fought
     on source order alone. Only campaigns#index ever carries .portal. */
  .view.portal .tool-header {
    background: transparent; border-bottom: none;
    max-width: 920px; margin: 0 auto; width: 100%; padding: 32px 32px 8px;
  }
  .view.portal .tool-header .title { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.2; }
  .view.portal .campaigns-subtitle { font-size: 12.5px; color: var(--soft); margin-top: 2px; line-height: 1.5; text-wrap: pretty; }

  /* ── The client's campaign cards ──
     Name and status, what we are working on, then budget · the one number · last update
     side by side, and the creative along the bottom. Measured against the design system's
     campaigns.css (.pc and below). */
  .portal-cards { max-width: 1040px; margin: 0 auto; padding: 16px 32px 48px; display: flex; flex-direction: column; gap: 14px; }
  .portal-card {
    display: block; overflow: hidden; padding: 0; text-decoration: none; color: var(--ink);
    transition: border-color 0.15s, box-shadow 0.15s;

    &:hover { text-decoration: none; border-color: var(--soft); box-shadow: var(--shadow-sm), 0 12px 28px -16px oklch(0.3 0.05 50 / 0.3); }
  }
  .portal-card-head { display: flex; align-items: center; gap: 10px; padding: 16px 18px 8px; }
  .portal-card-head .client-chip { width: 26px; height: 26px; border-radius: 7px; font-size: 9.5px; }
  .portal-card-name { flex: 1; min-width: 0; font-size: 15.5px; font-weight: 650; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .portal-card .status-pill { flex-shrink: 0; font-size: 12.5px; font-weight: 500; }
  .portal-focus-block { display: flex; flex-direction: column; gap: 2px; padding: 0 18px 14px; }
  .portal-focus { max-width: 70ch; font-size: var(--t-lg); line-height: 1.5; color: var(--ink); text-wrap: pretty; }
  .portal-card-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--border-row); }
  .portal-cell { min-width: 0; padding: 12px 18px 14px; border-left: 1px solid var(--border-row); }
  .portal-cell:first-child { border-left: 0; }
  .portal-card .stat-label { margin: 0; font-size: 11.5px; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--soft); }
  .portal-card .stat-value { margin-top: 3px; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
  .portal-card .budget-bar { height: 5px; margin-top: 8px; background: var(--sidebar); }
  .portal-card .budget-bar > span { background: var(--ink); }
  .portal-card .budget-bar.is-over > span { background: var(--err); }
  .portal-card .stat-note { margin-top: 5px; font-size: 11.5px; font-weight: 400; color: var(--soft); }
  .portal-kpi-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 3px; }
  .portal-kpi { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; color: var(--ink); }
  .portal-card .delta-pill { padding: 2px 6px; border-radius: 999px; font: 600 var(--t-xs) var(--mono); white-space: nowrap; }
  .portal-spark { display: block; margin-top: 6px; }
  .portal-spark .spark { display: block; width: 100%; height: 22px; overflow: visible; }
  .portal-last { display: flex; align-items: center; justify-content: space-between; margin-top: 3px; }
  .portal-last .stat-value { margin-top: 0; }
  .portal-last svg { color: var(--dim); transition: transform 0.15s; }
  .portal-card:hover .portal-last svg { transform: translateX(2px); color: var(--ink); }
  .portal-card .creative-band { gap: 14px; padding: 10px 18px; border-top: 1px solid var(--border-row); font-size: 12.5px; }
  .portal-card .creative-band-label { padding: 0; border: 0; gap: 6px; font-size: var(--t-sm); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--ink-2); }
  .portal-card .creative-band-label svg { color: var(--soft); }
  .portal-card .creative-band-seg { gap: 6px; font-size: 12.5px; font-weight: 500; color: var(--ink); white-space: nowrap; }
  .portal-card .creative-band-seg.is-muted { font-weight: 400; color: var(--soft); }
  .portal-card .creative-band-dot { width: 7px; height: 7px; }
  .portal-card .creative-band-seg.is-muted .creative-band-dot { opacity: 0.55; }

  @media (max-width: 700px) {
    .portal-cards { padding: 14px 14px 96px; }
    .portal-card-head { padding: 14px 14px 6px; flex-wrap: wrap; }
    /* ⚠ overflow/text-overflow too, not just white-space: a wrapping name that keeps
       `ellipsis` still gets clipped with a … on its last line. */
    .portal-card-name { flex-basis: calc(100% - 140px); white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.3; }
    .portal-focus-block { padding: 0 14px 12px; }
    .portal-card-grid { grid-template-columns: 1fr 1fr; }
    .portal-cell { padding: 10px 14px 12px; }
    .portal-cell--update { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--border-row); }
    .portal-card .creative-band { padding: 10px 14px; gap: 6px 12px; }
  }

  /* Reusable delta pill + direction colouring. One `dir-*` class on the parent
     tints the pill and (via currentColor) the sparkline end-dot. */
  .delta-pill { display: inline-flex; align-items: center; gap: 3px; font-size: var(--t-sm); font-weight: 700; padding: 2px 8px; border-radius: var(--r-full); }
  .dir-good .delta-pill { color: var(--ok-strong); background: var(--ok-tint); }
  .dir-bad  .delta-pill { color: var(--err-strong); background: var(--err-tint); }
  .dir-flat .delta-pill { color: var(--soft); background: var(--hairline); }
  .portal-spark.dir-good { color: var(--ok-strong); }
  .portal-spark.dir-bad  { color: var(--err-strong); }
  .portal-spark.dir-flat { color: var(--dim); }

  /* ── Campaign show page ──────────────────────────── */
  .campaign-show { overflow-y: auto; container-type: inline-size; }
  /* Every band on this page measures the same column. */
  .campaign-width { max-width: var(--content-max); margin: 0 auto; width: 100%; }
  .campaign-show-head { padding: 14px 0 0; border-bottom: 1px solid var(--border-frame); }
  .campaign-show-head > .campaign-width { padding: 0 20px; }
  .campaign-crumb { padding-bottom: 5px; }
  .campaign-back {
    display: inline-flex; align-items: center; gap: 5px;
    color: var(--soft); font-size: var(--t-base); font-weight: 500;
  }
  .campaign-back:hover { color: var(--ink); }
  .campaign-show-title { display: flex; align-items: center; gap: 10px; min-width: 0; padding-bottom: 11px; }
  .campaign-show-title h1 {
    flex: 1 1 auto; min-width: 0; margin: 0; font-size: var(--t-2xl); font-weight: 700;
    letter-spacing: -0.02em; line-height: 1.15;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .campaign-show-body { padding: 16px 20px 40px; display: flex; flex-direction: column; gap: var(--card-stack-gap); }
  /* ── Campaign page: one column, a facts row, tabs ──
     The page is a single 860px column with one pane at a time; the rail it used to have is
     the Assets, Library and Setup tabs. */
  .campaign-show .campaign-width { max-width: 860px; }
  .campaign-details { margin: 0; }
  /* What the campaign is, read at a glance: a small label over each value, in a row. */
  .campaign-facts { display: flex; align-items: flex-end; gap: 6px 22px; flex-wrap: wrap; padding-bottom: 12px; }
  .campaign-fact { display: grid; grid-template-columns: auto auto; column-gap: 8px; align-items: center; min-width: 0; }
  .campaign-fact small { grid-column: 1 / -1; font-size: 11.5px; font-weight: 500; color: var(--soft); }
  .campaign-fact b { font-size: var(--t-base); font-weight: 600; color: var(--ink); white-space: nowrap; font-variant-numeric: tabular-nums; }
  .campaign-fact b.is-over { color: var(--err); }
  .campaign-fact .budget-bar { width: 64px; height: 4px; margin: 0; background: var(--sidebar); }
  .campaign-fact .budget-bar > span { background: var(--ink); }
  .campaign-fact .budget-bar.is-over > span { background: var(--err); }
  .campaign-facts .details-edit-btn { margin-left: auto; }
  /* ⚠ The details card is not on the page until Edit: the facts row above it IS its read
     view. card-edit puts is-editing on the form that wraps both. */
  .campaign-details:not(.is-editing) .details-card { display: none; }
  .campaign-details.is-editing .details-card { margin: 0 0 14px; box-shadow: var(--ring), var(--shadow-sm); }
  .campaign-details.is-editing .details-card .details-foot { display: flex; justify-content: flex-end; }
  .campaign-details.is-editing .details-card .cd-card-head .cd-card-title { display: block; }
  .campaign-details .details-editing-note { color: var(--soft); font-weight: 400; }
  .campaign-details .details-static, .campaign-details .details-card .budget-bar, .campaign-details .details-card .stat-note { display: none; }
  .campaign-tabs { max-width: none; margin: 0 0 -1px; padding: 0; overflow-x: auto; scrollbar-width: none; }
  .campaign-tabs .tab-count { margin-left: 6px; font: 500 var(--t-xs) var(--mono); color: var(--dim); }
  .campaign-tabs .cd-tab.is-active .tab-count { color: var(--ink-2); }
  .campaign-tab-empty { padding: 28px 16px; text-align: center; font-size: var(--t-base); color: var(--soft); }

  /* ── Campaign page: what is inside the panes ──
     Measured against the design system's campaign.html. Its stylesheet is a history — later
     blocks override earlier ones — and these are the values it ends on. */
  .campaign-show .campaign-show-title { gap: 12px; padding-bottom: 0; }
  .campaign-show .campaign-show-title h1 { flex: 0 1 auto; font-size: 22px; letter-spacing: -0.015em; line-height: 1.2; }
  .campaign-show .campaign-show-title .client-chip { width: 32px; height: 32px; border-radius: 8px; font-size: 10.5px; }
  .campaign-show .campaign-show-title .status-pill { font-size: 12.5px; font-weight: 500; }
  .campaign-facts { gap: 8px 22px; padding: 10px 0 0; }
  .campaign-fact { display: inline-flex; flex-direction: column; gap: 1px; white-space: nowrap; }
  .campaign-fact small { font-size: 10.5px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--dim); }
  .campaign-fact b { font-size: var(--t-base); font-weight: 500; }
  .campaign-fact-value { display: inline-flex; align-items: center; gap: 8px; }
  /* How much of the budget is gone: the spent part inked, what is left striped green. */
  .campaign-fact .budget-bar {
    width: 110px; height: 8px; border-radius: 4px; overflow: hidden;
    background: repeating-linear-gradient(135deg, #E3F1E8 0 4px, #CFE7D8 4px 6px); box-shadow: inset 0 0 0 1px #CFE7D8;
  }
  .campaign-fact .budget-bar > span { border-radius: 4px 0 0 4px; background: var(--ink); }
  .campaign-fact-left { font-size: 12.5px; font-weight: 500; color: var(--ok-strong); }
  .campaign-tabs { margin-top: 14px; }
  .campaign-tabs .cd-tab { height: 36px; }

  /* The numbers and the chart are one card: the strip is its top band. */
  .campaign-show-body > .metric-strip {
    margin-bottom: calc(-1 * var(--card-stack-gap)); position: relative; z-index: 1;
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
    background: var(--inset); border: 1px solid var(--border-card); border-bottom: 1px solid var(--border-divider);
    border-radius: var(--r-card) var(--r-card) 0 0; box-shadow: none;
  }
  .campaign-show-body > .metric-strip + .perf-card { border-top: 0; border-radius: 0 0 var(--r-card) var(--r-card); }
  .campaign-show .metric-strip-cell { min-width: 0; padding: 12px 18px; }
  .campaign-show .metric-strip-cell + .metric-strip-cell { border-left: 1px solid var(--border-row); }
  .campaign-show .metric-strip-label { font-size: var(--t-sm); font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--soft); }
  .campaign-show .metric-strip-figure { flex-direction: column; align-items: flex-start; gap: 4px; margin-top: 3px; }
  .campaign-show .metric-strip-value { font-family: inherit; font-size: 22px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; color: var(--ink); white-space: nowrap; }
  .campaign-show .metric-strip-delta { gap: 6px; font-family: inherit; font-size: var(--t-sm); color: var(--ink-2); }
  .campaign-show .metric-strip-dot { width: 7px; height: 7px; background: var(--border-control); }
  .campaign-show .metric-strip-dot--good { background: var(--ok); }
  .campaign-show .metric-strip-dot--bad { background: var(--warn); }
  .campaign-show .metric-strip-edit { align-items: flex-start; padding: 10px; border-left: 0; }
  .campaign-show .metric-strip .stat-picker-trigger {
    display: inline-flex; align-items: center; gap: 3px; height: 28px; padding: 0 8px; border-color: transparent; border-radius: 7px;
    background: transparent; font: 500 11.5px var(--mono); color: var(--soft);
  }
  .campaign-show .metric-strip .stat-picker-trigger:hover { background: var(--sidebar); color: var(--ink); }

  /* Performance footer: tools on the left, export and the ⋯ on the right. */
  .perf-foot-actions .perf-export { color: var(--ink-2); }
  .perf-more { position: relative; }
  .perf-more .row-menu-btn { width: 28px; height: 28px; border-radius: 7px; }
  /* ⚠ Upward: the footer is the bottom of the card, and often of the screen. */
  .perf-more .row-menu-pop { top: auto; bottom: calc(100% + 6px); right: 0; min-width: 160px; padding: 4px; border-color: var(--border-card); border-radius: 10px; }
  .perf-more .row-menu-item { display: block; width: 100%; padding: 7px 10px; border: 0; border-radius: 7px; background: none; text-align: left; font: inherit; font-size: 12.5px; font-weight: 400; cursor: pointer; }
  .perf-more .row-menu-item--danger { color: var(--err); }
  .perf-more .row-menu-item--danger:hover { background: var(--err-tint); }
  .perf-more form { margin: 0; }

  /* Assets tab: one row a set — title and kind, where it stands, when it last moved. */
  .campaign-assets .cd-card-head .cd-card-title { flex: none; font-size: var(--t-lg); }
  .campaign-assets .cd-card-head .cd-card-sub { flex: 1; }
  .asset-row {
    display: grid; grid-template-columns: minmax(0, 1fr) 200px 72px; gap: 14px; align-items: center;
    padding: 11px 16px; border-top: 1px solid var(--border-row); color: var(--ink); text-decoration: none; transition: background 0.12s;
  }
  .cd-card-head + .asset-row { border-top: 0; }
  .asset-row:hover { background: var(--inset); text-decoration: none; }
  .asset-row-text { min-width: 0; }
  .asset-row-text b { display: block; font-size: 13.5px; font-weight: 600; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .asset-row-text small { display: block; font-size: var(--t-sm); color: var(--soft); }
  .asset-row-text small span { margin: 0 4px; color: var(--dim); }
  .asset-row .status-pill { font-size: 12.5px; font-weight: 500; }
  .asset-row-updated { font: 500 11.5px var(--mono); color: var(--soft); text-align: right; white-space: nowrap; }
  .asset-settled {
    display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--border-divider);
    background: var(--inset); border-radius: 0 0 var(--r-card) var(--r-card); font-size: 12.5px; color: var(--soft); text-decoration: none;
  }
  .asset-settled:hover { color: var(--ink); text-decoration: none; }
  .asset-settled .status-dot { --sc: var(--border-control); }
  .asset-settled b { margin-left: auto; font-weight: 500; color: var(--accent-ink); }

  /* Library tab: the Library page's rows in one card, then what the client passes down. */
  .campaign-library { overflow: visible; }
  .campaign-library-head { cursor: default; }
  .campaign-library-head:hover b { color: inherit; }
  .campaign-library .lib-table { background: none; border: 0; border-radius: 0; box-shadow: none; max-width: none; min-width: 0; }
  .campaign-library-inherited { display: flex; align-items: baseline; gap: 8px; margin-top: 4px; padding: 14px 16px 6px; border-top: 1px solid var(--border-divider); }
  .campaign-library-inherited b { font-size: var(--t-base); font-weight: 600; }
  .campaign-library-inherited small { font-size: var(--t-sm); color: var(--soft); }
  .campaign-library-inherited a { margin-left: auto; font-size: 12.5px; font-weight: 500; color: var(--accent-ink); }
  .campaign-library-inherited-row { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 12px; align-items: center; padding: 7px 16px; font-size: var(--t-base); color: var(--ink-2); }
  .campaign-library-inherited-row .lib-tile { width: 26px; height: 26px; }
  .campaign-library-inherited-row:last-of-type { padding-bottom: 12px; }
  .campaign-library-foot { justify-content: flex-start; gap: 8px; border-radius: 0 0 var(--r-card) var(--r-card); }
  .campaign-library-foot-gap { flex: 1; }

  /* Danger zone, folded: quiet until opened, red once it is. */
  .cd-card.danger-fold { overflow: hidden; }
  .danger-fold > summary { list-style: none; cursor: pointer; border-bottom: 0; }
  .danger-fold > summary::-webkit-details-marker { display: none; }
  .danger-fold > summary .cd-card-title { flex: none; color: var(--ink-2); font-size: var(--t-base); }
  .danger-fold > summary .danger-hint { margin-left: auto; }
  .danger-fold > summary svg { color: var(--dim); transition: transform 0.2s; }
  .danger-fold[open] { border-color: color-mix(in srgb, var(--err) 28%, var(--card)); overflow: visible; }
  .danger-fold[open] > summary { border-bottom: 1px solid color-mix(in srgb, var(--err) 16%, var(--card)); }
  .danger-fold[open] > summary .cd-card-title { color: var(--err-strong); }
  .danger-fold[open] > summary svg { transform: rotate(180deg); }

  /* A client's "Request a change": the field opens in the band, in the flow of the card. */
  .request-change-band { padding: 10px 16px 14px; }
  .request-change > summary { width: auto; display: inline-flex; }
  .request-change-form { flex-direction: row; align-items: center; gap: 8px; padding-top: 2px; }
  .request-change-form .quick-log-input { flex: 1; min-width: 0; height: 34px; }

  @media (max-width: 700px) {
    .campaign-show .campaign-show-title h1 { font-size: 18px; }
    .campaign-facts { gap: 8px 16px; }
    .campaign-show-body > .metric-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .campaign-show .metric-strip-cell { padding: 10px 14px; border-top: 0; }
    .campaign-show .metric-strip-cell:nth-child(odd) { border-left: 0; }
    .campaign-show .metric-strip-cell:nth-child(n+3) { border-top: 1px solid var(--border-row); }
    .campaign-show .metric-strip-value { font-size: 18px; }
    .campaign-show .metric-strip-edit { grid-column: 1 / -1; padding: 4px 8px; border-top: 1px solid var(--border-row); }
    .asset-row { grid-template-columns: minmax(0, 1fr) auto; }
    .asset-row-updated { display: none; }
    .request-change-form { flex-wrap: wrap; }
    .request-change-form .quick-log-input { flex-basis: 100%; }
  }
  @media (max-width: 700px) {
    .campaign-facts .details-edit-btn { margin-left: 0; flex-basis: 100%; justify-content: center; height: 34px; }
  }
  /* Main column plus right rail — the campaign page's shape, and now the client Reference
     tab's. Named for the shape once the second page wanted it: a client tab wearing a class
     called .campaign-show-grid is worse than the rename, and a byte-identical second rule is
     the "two homes for one decision" this stylesheet keeps collapsing.
     ⚠ .cd-split-main is a container, so the cards inside it size to the column they are in
     rather than to the window — see the @container query on the scope rows. */
  .cd-split { display: grid; grid-template-columns: minmax(0, 1fr) var(--rail-w); gap: var(--grid-gutter); align-items: start; }
  .cd-split-main, .cd-split-side { display: flex; flex-direction: column; gap: var(--card-stack-gap); min-width: 0; }
  .cd-split-main { container-type: inline-size; }

  /* ── Key metrics — an ink strip above the fold, out of the performance card ──── */
  /* No overflow clip: the strip's own background and radius round it, and the stat picker
     hangs out of the bottom edge. */
  .metric-strip {
    display: flex; flex-wrap: wrap;
    background: var(--ink); border-radius: var(--r-card); box-shadow: var(--shadow-sm);
  }
  .metric-strip-cell { flex: 1 1 0; min-width: 152px; padding: 12px 16px; }
  .metric-strip-cell + .metric-strip-cell { border-left: 1px solid var(--surface-inverse-hover); }
  .metric-strip-label {
    font-size: var(--t-sm); font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase;
    color: var(--text-on-inverse-label);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .metric-strip-figure { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; margin-top: 5px; }
  .metric-strip-value {
    font-family: var(--mono); font-size: var(--t-2xl); font-weight: 600;
    color: var(--text-on-inverse); line-height: 1.1; font-variant-numeric: tabular-nums;
  }
  /* ⚠ The dot carries the judgement and the text stays neutral. Status is a dot, never coloured
     text — the one rule the design system repeats for every status treatment it has. */
  .metric-strip-delta {
    display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0;
    font-family: var(--mono); font-size: var(--t-sm); font-weight: 500;
    color: var(--text-on-inverse-label);
  }
  .metric-strip-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: var(--dim); }
  .metric-strip-dot--good { background: var(--ok); }
  .metric-strip-dot--bad { background: var(--err); }
  .metric-strip-edit {
    display: flex; align-items: center; padding: 0 12px; flex: 0 0 auto;
    border-left: 1px solid var(--surface-inverse-hover);
  }
  .metric-strip .stat-picker-trigger {
    border-color: var(--surface-inverse-hover); background: transparent; color: var(--text-on-inverse-label);
  }
  .metric-strip .stat-picker-trigger:hover { background: var(--surface-inverse-hover); color: var(--text-on-inverse); }

  /* ── Focus — the caption for the timeline, so it sits at the timeline's head ──
     It was a card of its own between the chart and the conversation, which put a heading
     where a heading answered nothing. */
  /* What we are working on now: a label and a sentence at the head of the timeline, plain
     rather than a tinted bar — it is the first line of the conversation, not a banner. */
  .focus-band { display: flex; align-items: center; gap: 12px; padding: 12px 16px 4px; }
  .focus-band-label {
    flex: 0 0 auto; white-space: nowrap;
    font-size: var(--t-xs); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--soft);
  }
  .focus-band-value { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
  .focus-band-text {
    flex: 1; min-width: 0; text-align: left; font-family: inherit; line-height: 1.4;
    font-size: 13.5px; font-weight: 500; color: var(--ink);
  }
  button.focus-band-text {
    display: flex; align-items: center; gap: 8px; cursor: pointer;
    background: transparent; border: none; border-radius: 6px; padding: 3px 6px; margin: -3px -6px;
  }
  button.focus-band-text:hover { background: var(--inset); }
  button.focus-band-text svg { flex-shrink: 0; color: var(--dim); opacity: 0; transition: opacity 0.12s; }
  button.focus-band-text:hover svg, button.focus-band-text:focus-visible svg { opacity: 1; }
  .focus-band-form { display: none; flex: 1; align-items: center; gap: var(--control-gap); margin: 0; }
  .is-editing .focus-band-form { display: flex; }
  .is-editing .focus-band-text { display: none; }
  .focus-band-input {
    flex: 1; min-width: 0; height: var(--control-h); padding: 0 10px;
    border: 1px solid var(--ink); border-radius: var(--r-md); box-shadow: var(--ring);
    font-size: var(--t-lg); font-weight: 600; color: var(--ink); background: var(--card);
    outline: none; font-family: inherit;
  }

  /* Details card. The chrome is .cd-card's; only the editing behaviour lives here.
     Rows stack label-over-value: the rail is --rail-w wide and a 120px label column ate
     nearly half of it. */
  .details-card { margin: 0; }
  .details-editing-note { display: none; flex: 1; font-size: var(--t-sm); font-weight: 500; color: var(--accent-ink); text-align: right; }
  .is-editing .details-editing-note { display: block; }
  .is-editing .cd-card-head .cd-card-title { display: none; }
  .is-editing .details-edit-btn { display: none; }

  .details-row { padding: 10px 16px; min-width: 0; }
  .details-row + .details-row { border-top: 1px solid var(--border-row); }
  .details-label { font-size: var(--t-sm); font-weight: 600; color: var(--soft); letter-spacing: 0.02em; text-transform: uppercase; }
  .details-value { margin-top: 4px; font-size: var(--t-base); font-weight: 600; min-width: 0; line-height: 1.4; }
  .details-value .status-pill { font-size: var(--t-base); }
  .stat-note { font-size: var(--t-xs); color: var(--dim); margin-top: 6px; font-weight: 600; }
  .details-value .budget-bar { margin-top: 8px; }

  .details-edit { display: none; }
  .is-editing .details-static { display: none; }
  .is-editing .details-edit { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  /* One field treatment for every control in the card — the base .details-edit is already
     flex-wrap, so a full-width child takes its own line and the two dates share a row. */
  .details-edit :is(.details-select, .details-input, .budget-input, .budget-period, input[type="date"]) {
    width: 100%; height: var(--control-h); padding: 0 10px; appearance: none;
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);
    font-family: inherit; font-size: var(--t-base); font-weight: 500; color: var(--ink); outline: none;
  }
  .details-edit .details-select { cursor: pointer; }
  .details-edit input[type="date"] { flex: 1; min-width: 0; width: auto; font-family: var(--mono); font-size: var(--t-sm); }
  .details-edit :is(.details-select, .details-input, .budget-input, .budget-period, input[type="date"]):focus {
    border-color: var(--ink); box-shadow: var(--ring);
  }

  /* ⚠ Two class selectors, not one: .cd-card-foot supplies `display: flex` and is declared
     later in the file, so a single-class hide loses the tie and the save bar shows on a card
     nobody is editing. */
  .details-card .details-foot { display: none; }
  .details-card.is-editing .details-foot { display: flex; justify-content: flex-end; }
  .code-input { text-transform: uppercase; }
  /* Live chip preview beside the code field, so you see the mark you're editing. */
  .field-note { font-size: var(--t-sm); color: var(--dim); line-height: 1.5; margin: 2px 0 0; }

  /* Name field + read-only client code (both edit-only rows in the details card) */
  .details-input {
    padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; width: 100%;
    font-size: var(--t-base); font-weight: 600; color: var(--ink); background: var(--card); outline: none; font-family: inherit;
  }
  .details-input:focus { border-color: var(--accent); }
  .details-row--edit-only { display: none; }
  .is-editing .details-row--edit-only { display: grid; }
  .details-note { display: block; font-size: var(--t-sm); color: var(--dim); margin-top: 5px; max-width: 42ch; }

  /* Archived pill (reuses .status-pill via --sc) + banner */
  .archived-banner {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 14px; border: 1px solid var(--border-card); border-radius: var(--r-md);
    background: var(--amber-tint); color: var(--amber-ink);
  }
  .archived-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--badge-amber); flex-shrink: 0; }
  .archived-banner-text { flex: 1; min-width: 0; }
  .archived-banner-title { font-size: var(--t-base); font-weight: 600; color: var(--ink); }
  .archived-banner-note { margin-top: 1px; font-size: var(--t-sm); color: var(--soft); line-height: 1.5; }
  .archived-banner .btn { flex-shrink: 0; }

  /* Danger zone (own disclosure card, shown regardless of the details edit mode) */
  /* Shown only while the Campaign Details card is being edited (sibling of the form). */
  /* ⚠ A card clips to its radius — that is what tucks a header band and a footer bar inside
     the corner. A card hosting an absolutely-positioned popover cannot: the metrics pickers
     hang below the performance card's header, and the type-to-confirm box below the delete
     button. Two class names, because .cd-card's `overflow: hidden` is declared later in this
     file and a single-class opt-out loses the tie. Whoever opts out rounds its own footer. */
  .cd-card.danger-zone, .cd-card.perf-card { overflow: visible; }
  .cd-card.perf-card > .cd-card-foot { border-radius: 0 0 var(--r-card) var(--r-card); }
  /* Hiding it until you're editing is the CAMPAIGN page's rule, not the danger zone's
     nature — so it hangs off that page's editing card rather than off the block itself.
     Elsewhere (the client's Settings tab) the zone simply shows. */
  .details-card ~ .danger-zone { display: none; }
  .details-card.is-editing ~ .danger-zone { display: block; }
  .danger-head { align-items: baseline; }
  .danger-head .cd-card-title { color: var(--err-strong); }
  .danger-hint { font-size: var(--t-sm); color: var(--soft); font-weight: 500; }
  /* A left rule rather than a red card: destructive is a property of these two rows, not
     of the panel they sit in. */
  .danger-item {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    margin: 12px 16px; padding-left: 13px; border-left: 3px solid var(--err);
  }
  .danger-item-text { min-width: 0; }
  .danger-item-title { font-size: var(--t-base); font-weight: 600; color: var(--ink); }
  .danger-item-desc { font-size: var(--t-sm); color: var(--soft); font-weight: 500; margin-top: 1px; line-height: 1.4; }
  .danger-item .btn { flex-shrink: 0; }
  .danger-delete .confirm-box .person-input {
    width: 100%; margin-bottom: 10px; border: 1px solid var(--border-control);
    border-radius: var(--r-md); padding: 7px 9px; background: var(--card);
  }

  /* Timeline panel (wraps the composer + history in the main column). NOT
     `.timeline-card` — that class is the internal-note callout body (below). */
  .timeline-panel { overflow: hidden; }
  /* The timeline: one rail down the left, a marker on it for every entry. Shared by the
     campaign page and the client page. */
  .timeline { position: relative; display: flex; flex-direction: column; padding: 6px 16px 10px; }
  .timeline::before { content: ""; position: absolute; left: 26px; top: 22px; bottom: 22px; width: 1px; background: var(--border-divider); }
  .request-change-band { padding: 12px 16px; border-bottom: 1px solid var(--border-divider); }
  .timeline-item { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 12px; padding: 14px 0; position: relative; }
  .timeline-icon {
    position: relative; z-index: 1; width: 20px; height: 22px; display: flex; align-items: center; justify-content: center;
  }
  /* ⚠ Every marker sits on the rail and paints the card behind itself, so the line never
     shows through a dot or an icon. */
  .timeline-icon svg { display: block; width: 18px; height: 16px; padding: 2px; background: var(--card); }
  .timeline-dot { width: 10px; height: 10px; border-radius: 50%; box-shadow: 0 0 0 3px var(--card); }
  .timeline-body { min-width: 0; }
  .timeline-date { font: 500 11.5px var(--mono); color: var(--soft); }
  .timeline-date > :is(.timeline-source, .internal-pill) { font-family: -apple-system, "Helvetica Neue", Helvetica, system-ui, sans-serif; }
  .timeline-text { max-width: 72ch; margin-top: 3px; font-size: 13.5px; line-height: 1.55; color: var(--ink); text-wrap: pretty; }

  /* Timeline agency controls (edit / re-send / delete) */
  .timeline-head { display: flex; align-items: center; gap: 8px; min-height: 22px; flex-wrap: wrap; }
  /* The date owns the slack, so the flag and the hover verbs stay together on the right
     whatever the date says. */
  .timeline-head > .timeline-date { display: inline-flex; align-items: center; gap: 0; }
  /* The entry's ⋯ menu: there on hover, on focus, and while it is open. */
  .timeline-actions { margin-left: auto; flex: none; opacity: 0; transition: opacity 0.12s ease; }
  .timeline-item:hover .timeline-actions, .timeline-actions[open], .timeline-actions:focus-within { opacity: 1; }
  .timeline-item:has(.timeline-actions[open]) { position: relative; z-index: 5; }
  .timeline-actions .row-menu-btn { width: 24px; height: 22px; border-radius: 6px; }
  .timeline-actions .row-menu-pop { top: calc(100% + 4px); min-width: 140px; padding: 4px; border-color: var(--border-card); border-radius: 10px; box-shadow: 0 12px 32px -10px oklch(0 0 0 / 0.22); }
  .timeline-action-form { display: contents; }
  .timeline-action { display: block; width: 100%; padding: 7px 10px; border: 0; border-radius: 7px; background: none; font: inherit; font-size: 12.5px; font-weight: 400; color: var(--ink-2); text-align: left; cursor: pointer; }
  .timeline-action:hover { background: var(--inset); color: var(--ink); }
  .timeline-action--danger { color: var(--err); }
  .timeline-action--danger:hover { background: var(--err-tint); color: var(--err); }
  /* The creative-launch toggle. It sits outside .timeline-actions because a mark is a
     standing fact about the entry — it has to be readable without hovering the row. */
  .timeline-flag {
    display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; height: 22px; padding: 0 8px;
    border: 1px dashed transparent; border-radius: 999px; background: transparent;
    font: inherit; font-size: 11.5px; font-weight: 500; line-height: 1; color: var(--dim); white-space: nowrap; cursor: pointer;
    transition: opacity 0.12s;

    &:hover { border-color: var(--border-control); color: var(--ink-2); }
  }
  /* An unset mark is an offer, so it waits for the row to be pointed at; a set one is a
     fact about the entry and is always readable. */
  .timeline-item:not(:hover):not(:focus-within) .timeline-flag:not(.is-on) { opacity: 0; }
  .timeline-flag.is-on { border: 1px solid #FED7AA; background: #FFF1E6; color: #9A3412; }
  .timeline-flag-dot { width: 6px; height: 6px; border-radius: 50%; background: #EA580C; flex-shrink: 0; }
  @media (hover: none) { .timeline-item .timeline-flag:not(.is-on) { opacity: 1; } .timeline-actions { opacity: 1; } }
  /* On a phone the verbs stay on the date's line, right-aligned and tighter, and an unset
     creative mark steps aside for them — it is on the campaign page's composer too. */
  @media (max-width: 700px) {
    .timeline-head { flex-wrap: nowrap; }
    .timeline-head > .timeline-date { min-width: 0; flex-wrap: wrap; row-gap: 4px; }
    .timeline-item .timeline-flag:not(.is-on) { display: none; }
    .timeline-actions { opacity: 1; }
    .timeline-text { font-size: var(--t-base); }
  }

  /* "Last creative" in the timeline card header. */
  .last-creative { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
  .last-creative-ago { font-family: var(--mono); font-size: var(--t-sm); color: var(--ink-2); white-space: nowrap; }
  .timeline-edit-form { display: none; flex-direction: column; gap: 6px; margin: 6px 0 0; }
  .is-editing .timeline-edit-form { display: flex; }
  .is-editing .timeline-text { display: none; }
  .timeline-edit-input {
    width: 100%; padding: 6px 9px; border: 1px solid var(--accent); border-radius: 6px;
    font-size: var(--t-base); color: var(--ink); background: var(--inset); outline: none; font-family: inherit; resize: vertical;
  }
  .timeline-edit-row { display: flex; align-items: center; gap: 6px; }

  .quick-log { display: flex; flex-direction: column; gap: 8px; margin: 0 0 16px; }
  .quick-log-input {
    height: var(--control-h); padding: 0 10px; border: 1px solid var(--border-control);
    border-radius: var(--r-md); font-size: var(--t-base);
    color: var(--ink); background: var(--card); outline: none; font-family: inherit; width: 100%;

    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .quick-log .btn { width: 100%; }
  .request-change > summary { list-style: none; width: 100%; text-align: center; }
  .request-change > summary::-webkit-details-marker { display: none; }
  .request-change[open] > summary { display: none; }
  .request-change-form { display: flex; flex-direction: column; gap: 6px; margin: 0; }

  /* Composer: For-client / Internal-note toggle. All visual state is CSS off the
     checked radio (:has); only the placeholder is JS (composer_controller). */
  .composer {
    display: flex; flex-direction: column; gap: var(--control-gap); margin: 0;
    padding: 12px 16px; border-bottom: 1px solid var(--border-divider);
  }
  /* The whole band goes quiet when you're writing for yourself — the strongest signal
     available that this one isn't going anywhere. */
  .composer:has(.composer-seg--internal :checked) { background: var(--inset); }
  /* The toggle and whatever the page puts beside it (the campaign timeline adds the
     creative-launch tickbox and the date; the Client Detail one adds nothing) share one
     wrapping row, so a narrow rail drops the extras under the segments rather than
     squashing them. */
  .composer-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .composer-toggle {
    display: inline-flex; align-self: flex-start; gap: 2px; padding: 2px;
    background: var(--inset); border: 1px solid var(--border-row); border-radius: var(--r-md);
  }
  .composer-flag, .composer-date {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: var(--t-sm); font-weight: 500; color: var(--soft); cursor: pointer; user-select: none;
  }
  .composer-flag-box { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; cursor: pointer; }
  .composer:has(.composer-flag-box:checked) .composer-flag { color: var(--accent-ink); font-weight: 600; }
  .composer-date-input {
    height: var(--control-h); padding: 0 8px; border: 1px solid var(--border-control);
    border-radius: var(--r-md); background: var(--card); box-shadow: var(--shadow-button);
    font-family: var(--mono); font-size: var(--t-sm); color: var(--ink); outline: none;

    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .composer-seg {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    height: 24px; padding: 0 11px; border: 1px solid transparent; border-radius: var(--r);
    font-size: var(--t-sm); font-weight: 500; color: var(--soft);
    cursor: pointer; user-select: none; transition: color 0.12s ease, background 0.12s ease;
  }
  .composer-seg svg { display: block; }
  .composer-radio { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
  .composer-seg:has(:checked) {
    background: var(--card); border-color: var(--border-control); box-shadow: var(--shadow-button);
    color: var(--ink); font-weight: 600;
  }
  .composer-row { display: flex; align-items: center; gap: var(--control-gap); }
  .composer-field { flex: 1; min-width: 0; }
  .composer-submit { flex-shrink: 0; }
  /* An internal note is not the page's primary action going out to a client, so its key is a
     shade quieter than the keycap beside every other form. */
  .composer:has(.composer-seg--internal :checked) .composer-submit { background: var(--ink-2); border-color: var(--ink-2); }
  .composer-submit svg { display: block; }
  .composer-label-client, .composer-label-internal { display: inline-flex; align-items: center; gap: 5px; }
  .composer-label-internal { display: none; }
  .composer:has(.composer-seg--internal :checked) .composer-label-client { display: none; }
  .composer:has(.composer-seg--internal :checked) .composer-label-internal { display: inline-flex; }

  /* Internal timeline entry: slate padlock rail, INTERNAL pill, carded body. */
  .internal-pill {
    font-size: 10.5px; font-weight: 600; color: #4B4664; background: #EEECF5;
    border-radius: 999px; padding: 2px 7px; margin-left: 6px;
  }
  .timeline-card { margin-top: 3px; padding: 8px 11px; background: #F7F6FB; border: 1px solid #E6E3F0; border-radius: 8px; color: #3B3750; }

  /* Rounded square, not a circle: the chip reads as a brand mark rather than a person,
     which matters now that people and clients sit side by side on the same cards. Still
     the single seam for a future uploaded client photo — hence overflow: hidden. */
  .client-chip {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 28px; height: 28px; border-radius: 7px; overflow: hidden;
    /* 10px, the one size under the scale's floor: a four-letter code has to fit the tile. */
    font-family: var(--mono); font-size: 10px; font-weight: 600;
    letter-spacing: 0.02em; line-height: 1;
    color: var(--text-on-inverse); background: var(--cc, var(--accent));
  }
  .client-chip--lg { width: 34px; height: 34px; font-size: var(--t-xs); }
  /* As a link (page headers only — see clients/_chip) it is a way back to this tool's list
     filtered to that client, so it lifts on hover rather than underlining: the mark is the
     target, and a rule under a 30px tile reads as damage. */
  a.client-chip {
    text-decoration: none; transition: transform 120ms ease, box-shadow 120ms ease;
    &:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
  }

  /* Inline-editable campaign fields (agency): type, budget, schedule */
  .kind-edit { margin: 0; }
  .kind-select {
    appearance: none; border: 1px solid var(--border); cursor: pointer; font-family: inherit;

    &:focus { outline: none; border-color: var(--accent); }
  }
  .budget-input {
    width: 72px; padding: 4px 6px; border: 1px solid var(--border); border-radius: 6px;
    font-size: var(--t-base); font-weight: 700; color: var(--ink); background: var(--card); outline: none; font-family: inherit;

    &:focus { border-color: var(--accent); }
  }
  .budget-period {
    appearance: none; padding: 4px 6px; border: 1px solid var(--border); border-radius: 6px;
    font-size: var(--t-sm); color: var(--soft); background: var(--card); outline: none; font-family: inherit; cursor: pointer;

    &:focus { border-color: var(--accent); }
  }

  .name-row { display: flex; gap: 8px; }
  .name-row input { flex: 1; min-width: 0; }

  /* Status is a 6px dot and text — never a filled pill. The dot carries the hue (inline
     --sc from the Ruby STATUSES hash, the one home those colours have, because the emails
     need them as hex); the text stays ink so a column of statuses reads as words.
     The --green/--amber/--danger tints colour the WORD as well, for the handful of states
     that are a verdict rather than a stage. The class keeps the name "pill": 14 tests and
     every view ask for it, and a rename would be the only thing they noticed.
     ⚠ Still sized by --badge-h, not padding: status_pill renders as a <span> OR a <summary>
     (the status menu), and it has to sit on one line with the chips beside it. */
  .status-pill {
    display: inline-flex; align-items: center; gap: 6px;
    height: var(--badge-h);
    font-size: var(--t-base); font-weight: 500; line-height: 1; white-space: nowrap;
    color: var(--ink);
  }
  .status-pill--green   { color: var(--ok-strong); }
  .status-pill--amber   { color: var(--amber-ink); }
  .status-pill--danger  { color: var(--err-strong); }
  .status-pill--neutral, .status-pill--muted { color: var(--soft); }
  .status-pill--green   .status-dot { background: var(--ok); }
  .status-pill--amber   .status-dot { background: var(--badge-amber); }
  .status-pill--danger  .status-dot { background: var(--err); }
  .status-pill--neutral .status-dot { background: var(--dim); }
  /* Settled but not shipped: a hollow dot, so it separates from the other greyscale status
     by shape rather than by a hue it has not earned. */
  .status-pill--muted   .status-dot { background: transparent; box-shadow: inset 0 0 0 1.5px var(--dim); }
  /* The fallback is the answer for a status that has no hue of its own — currently the
     Docket registry's "No campaign" scope. Without it an unset --sc is an invalid
     background and the dot vanishes rather than reading as neutral. */
  .status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--sc, var(--dim)); flex-shrink: 0; }
  .status-caret { opacity: 0.6; }

  .status-menu { position: relative; display: inline-block; }
  .status-menu > summary { list-style: none; cursor: pointer; }
  .status-menu > summary::-webkit-details-marker { display: none; }
  /* Opens upward from a row near the bottom of the screen (menu#place). */
  .status-options.is-flipped { top: auto; bottom: 100%; margin: 0 0 4px; }

  .status-options {
    position: absolute; top: 100%; left: 0; margin-top: 4px; z-index: 30;
    background: var(--card); border: 1px solid var(--border2); border-radius: 8px;
    box-shadow: var(--shadow-md); min-width: 190px; padding: 4px;
    display: flex; flex-direction: column; margin-bottom: 0;
  }
  .status-option {
    display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 10px;
    border: none; background: transparent; border-radius: 6px; cursor: pointer; text-align: left;
    font-size: var(--t-base); font-weight: 500; color: var(--ink);

    &:hover { background: var(--hairline); }
  }
  .status-option-label { flex: 1; }
  .status-check { color: var(--accent); font-weight: 700; }

  /* A rule above the ways a thing ends (see shared/_status_menu). */
  .status-options hr { width: 100%; border: 0; border-top: 1px solid var(--border-divider); margin: 4px 0; }

  .heatmap {
    display: grid; grid-template-rows: repeat(7, 3px); grid-auto-flow: column;
    grid-template-columns: repeat(12, 3px); gap: 1px;
  }
  .heatmap-cell {
    width: 3px; height: 3px;
    background: #EFEBE4;

    &.heat-1 { background: #B9C6F2; }
    &.heat-2 { background: var(--accent); }
  }

  .form-errors { background: oklch(0.95 0.03 25); border: 1px solid oklch(0.85 0.08 25); border-radius: var(--r); padding: 10px 14px; font-size: var(--t-base); color: var(--err); }
  .form-errors ul { margin: 0; padding-left: 18px; }

  .label-optional { color: var(--dim); font-weight: 500; text-transform: none; letter-spacing: 0; }

  .register-toggle {
    display: flex; align-items: center; gap: 10px; font-size: var(--t-base); color: var(--soft);
    background: var(--inset); border: 1px solid var(--border2); border-radius: var(--r-md); padding: 11px 12px; cursor: pointer;
  }

  /* Toast — flash notice, bottom-center, self-dismissing */
  /* The container is the fixed thing; a toast is an ordinary box inside it, so a notice
     and a refusal arriving together stack instead of landing on top of each other. */
  #toasts {
    position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 60;
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    width: max-content; max-width: calc(100vw - 32px); pointer-events: none;
  }
  .toast {
    display: flex; align-items: center; gap: 10px; pointer-events: auto;
    background: var(--ink); color: var(--text-on-inverse); padding: 9px 8px 9px 14px;
    border-radius: var(--r-lg); box-shadow: 0 10px 30px oklch(0 0 0 / 0.25);
    font-size: var(--t-base); font-weight: 500;
    animation: toast 3.2s ease forwards;

    /* How long it stays is decided here rather than in a timer: an Undo needs reading and
       reaching, and nothing should slide away from under a pointer or a focused button. */
    &:has(.toast-undo) { animation-duration: 6.5s; }
    &:hover, &:focus-within { animation-play-state: paused; }

    /* A refusal is longer than an FYI and worth reading twice — it wraps, and it stays put
       until dismissed rather than sliding away mid-sentence. */
    &.toast--alert { background: oklch(0.45 0.17 25); line-height: 1.5; animation: none; max-width: min(480px, 100%); }
  }
  .toast-form { display: contents; }
  .toast-undo, .toast-close {
    flex-shrink: 0; border: none; cursor: pointer; font: inherit; color: inherit;
    background: oklch(1 0 0 / 0.12); border-radius: var(--r);
    &:hover { background: oklch(1 0 0 / 0.2); }
  }
  .toast-undo { height: 24px; padding: 0 9px; font-weight: 600; }
  .toast-close { width: 24px; height: 24px; background: transparent; font-size: 16px; line-height: 1; opacity: 0.7; }

  /* ── Clients page ────────────────────────────────── */
  /* ── Tabs ─────────────────────────────────────────────────────────────────────────────
     ONE underline tab, for the two places that switch what a page is showing: the page
     header's row (.tool-tabs) and Client Detail's (.cd-tabs). .cd-tab was already this
     shape; .tab-chip — a solid accent pill sized by padding — was the outlier and its name
     is gone rather than aliased.

     ⚠ The active underline is a SHADOW, not a border. A border takes part in layout, so it
     could not overlap the header's own rule; the shadow can, which is what lets the row sit
     ON the line with margin-bottom:-1px instead of floating above it. It is ink, not accent:
     the tab you are on is a statement of where you are, not a call to action. */
  .tool-tabs, .cd-tabs { display: flex; gap: 2px; margin-bottom: -1px; }
  .tool-tab, .cd-tab {
    display: inline-flex; align-items: center; gap: 6px;
    height: var(--tab-h); padding: 0 10px;
    font-size: var(--t-base); font-weight: 500; color: var(--soft);
    text-decoration: none; white-space: nowrap; background: transparent; border: none;
    transition: color 0.12s ease;

    &:hover { color: var(--ink-2); }
    &.is-active { font-weight: 600; color: var(--ink); box-shadow: var(--shadow-tab-active); }
  }
  .tab-count { font-family: var(--mono); font-size: var(--t-xs); color: var(--soft); }

  /* Shared people atoms — used by the Client Detail team card + add-staff form. */
  .person-avatar {
    width: 30px; height: 30px; border-radius: 50%; background: var(--sidebar); color: var(--ink-2);
    flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 10.5px; font-weight: 600;
    overflow: hidden;

    &--primary { background: var(--ink); color: var(--text-on-inverse); }
  }
  .person-input {
    border: none; background: transparent; font-size: var(--t-base); font-weight: 500; color: var(--ink);
    outline: none; padding: 3px 4px; font-family: inherit; border-radius: 4px; min-width: 40px;

    &:focus { background: var(--inset); }
  }
  /* Shape encodes access: a circle for someone who can sign in, a rounded square for an
     address we only ever write to. */
  .person-avatar--square { border-radius: 7px; background: var(--card); border: 1px dashed var(--border-control); }
  .primary-badge { font-size: 10.5px; font-weight: 600; color: var(--ink); background: var(--sidebar); border-radius: 999px; padding: 1px 7px; }

  /* One form, two homes: the panel under the People card's head (Add) and the panel under
     a row (Edit). Three columns; the longer pieces take the full width. */
  .person-form { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 0; }
  .person-form .person-input {
    width: 100%; min-width: 0; height: var(--control-h); padding: 0 10px; font: inherit; font-size: var(--t-base); font-weight: 400; color: var(--ink);
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);

    &:focus { outline: 0; border-color: var(--ink); box-shadow: var(--ring); background: var(--card); }
  }
  .person-form select.person-input { appearance: none; }
  .person-form-full { grid-column: 1 / -1; }
  .person-form-check { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-2); }
  .person-form-check input { accent-color: var(--ink); margin: 0; }
  .person-form-move { display: flex; flex-direction: column; gap: 4px; }
  .person-form-move .person-input { max-width: 280px; }
  .person-form-label { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  .person-form-note { margin: 0; font-size: var(--t-sm); color: var(--soft); line-height: 1.45; }
  .person-form-foot { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
  .person-form-remove {
    margin-right: auto; height: 28px; padding: 0 6px; border: 0; border-radius: 6px; background: transparent;
    font: inherit; font-size: 12.5px; color: var(--soft); cursor: pointer;

    &:hover { color: var(--err); background: var(--err-tint); }
  }

  /* Inline confirm disclosure (archive / remove) */
  .inline-confirm { position: relative; }
  .inline-confirm > summary { list-style: none; cursor: pointer; }
  .inline-confirm > summary::-webkit-details-marker { display: none; }
  .confirm-box {
    position: absolute; top: 100%; right: 0; margin-top: 6px; z-index: 30; width: 260px;
    background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-md);
    padding: 14px; box-shadow: var(--shadow-md); border-left: 3px solid var(--err);
  }
  .confirm-box p { margin: 0 0 12px; font-size: var(--t-sm); color: var(--soft); line-height: 1.5; }
  .confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
  .confirm-actions form { margin: 0; }

  /* ── Clients roster table ────────────────────────── */
  .clients-card { margin: 16px 0 0; }
  .clients-card > .campaigns-controls { padding: 10px 16px; gap: 8px; border-bottom: 1px solid var(--border-divider); }
  .clients-card .search-pill { width: 240px; box-shadow: none; }
  .clients-scroll { overflow-x: auto; border-radius: 0 0 var(--r-card) var(--r-card); }
  .clients-table {
    /* ⚠ The six fixed columns and their gaps need 596px. Without a floor the name column's
       minmax(0,…) collapses to nothing and the client's name silently disappears — which is
       exactly what adding the Status column did. Same answer .campaigns-table and .dk-table
       already give: floor the table and let the body scroll it. 840 = 596 + room for a name. */
    min-width: 840px;
  }
  .clients-row {
    display: grid; grid-template-columns: minmax(0,1.5fr) 92px 108px 64px 116px 128px 16px; gap: 12px;
    align-items: center; padding: 10px 16px; border-top: 1px solid var(--border-row);
    text-decoration: none; color: inherit; transition: background 0.12s ease;
  }
  .clients-row:hover { background: var(--inset); }
  .clients-row:hover .clients-chev { color: var(--ink); }
  /* A sub-client, tucked under the brand that relays for it. */
  .clients-row--nested { background: var(--inset); }
  .clients-elbow {
    width: 14px; height: 14px; margin: -10px 2px 0 11px; flex: none;
    border-left: 1px solid var(--border-control); border-bottom: 1px solid var(--border-control); border-bottom-left-radius: 4px;
  }
  /* The totals row: the table's own columns, so every number sits under the one it sums. */
  .clients-row--total {
    background: var(--inset); border-top: 1px solid var(--border-divider); cursor: default;
    & .clients-cell-client { font-size: var(--t-base); font-weight: 500; color: var(--ink); }
    & .clients-count { color: var(--ink); font-weight: 500; }
  }
  .clients-subs { font-size: var(--t-xs); font-weight: 500; color: var(--dim); flex: none; white-space: nowrap; }
  .clients-row--head {
    padding: 9px 16px; border-top: none; border-bottom: 1px solid var(--border-divider);
    font-size: var(--t-sm); font-weight: 500; color: var(--soft);
  }
  .clients-row--head > :is(:nth-child(2), :nth-child(3), :nth-child(4)), .clients-count { text-align: right; }
  .clients-cell-client { display: flex; align-items: center; gap: 10px; min-width: 0; }
  .clients-name { font-size: var(--t-base); font-weight: 500; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .queued-capsule {
    font-size: var(--t-xs); font-weight: 500; color: var(--accent-ink); background: var(--accent-tint);
    border-radius: 999px; padding: 2px 7px; flex: none; white-space: nowrap;
  }
  .clients-count { font-size: var(--t-base); color: var(--ink-2); font-variant-numeric: tabular-nums; }
  .clients-activity { font-size: var(--t-base); color: var(--soft); }
  .clients-status .status-pill { max-width: 100%; font-weight: 500; }
  /* The status says itself in its own colour; Archived is the quiet one. */
  .clients-status--active .status-pill { color: var(--ok-strong); }
  .clients-status--paused .status-pill { color: var(--amber-ink); }
  .clients-status--archived .status-pill { color: var(--soft); font-weight: 400; }
  .clients-chev { color: var(--dim); justify-self: end; }

  /* ── The roster as stacked rows, at phone width ──
     One DOM, one set of cells: the name takes the first line, and the numbers follow on a
     second, each saying what it is — a bare "3 · 2" under a name means nothing. */
  @media (max-width: 700px) {
    .clients-table { min-width: 0; }
    .clients-card .search-pill { width: 100%; }
    .clients-card .filter-menu { flex: 1 1 0; }
    /* ⚠ The row is indented by the chip's width and the name line pulled back out over it,
       so a second line of numbers that wraps still lines up under the name, not the chip. */
    .clients-row {
      display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0;
      position: relative; padding: 10px 40px 10px 58px;
    }
    .clients-row.clients-row--head { display: none; }
    .clients-cell-client { flex: 0 0 calc(100% + 44px); margin-left: -44px; }
    .clients-row > :is(.clients-count, .clients-status, .clients-activity) {
      display: inline-flex; align-items: center; font-size: var(--t-sm); color: var(--soft); white-space: nowrap;
    }
    .clients-row > :is(.clients-count--fee, .clients-count--team, .clients-status, .clients-activity)::before {
      content: "·"; margin: 0 6px; color: var(--dim);
    }
    .clients-count--campaigns::before { content: "Campaigns"; margin-right: 4px; }
    .clients-row > .clients-count--team::before { content: "· Team"; margin: 0 4px 0 6px; }
    .clients-row .status-pill { font-size: var(--t-sm); }
    .clients-chev { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); }
    .clients-row.clients-row--total { display: none; }
  }

  /* ── Client Detail page ──────────────────────────── */
  .client-detail { overflow-y: auto; container-type: inline-size; }
  /* Identity + tabs: the page's fixed chrome. Only .cd-panel changes between tabs. */
  .cd-head { border-bottom: 1px solid var(--border-frame); }
  .cd-head-top { max-width: var(--content-wide); margin: 0 auto; padding: 16px 32px 0; }
  .cd-crumb { display: flex; align-items: center; gap: 6px; padding-bottom: 10px; font-size: var(--t-base); }
  .cd-crumb-sep { color: var(--dim); }
  .cd-head-row { display: flex; align-items: center; gap: 14px; }
  .client-chip--xl { width: 42px; height: 42px; border-radius: 11px; font-size: var(--t-sm); }
  .cd-title { flex: 1; min-width: 0; }
  .cd-title-line { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
  .cd-title h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; }
  .cd-title .status-pill { font-size: var(--t-base); font-weight: 500; }
  .cd-head-sub { display: block; margin-top: 2px; font-size: var(--t-base); color: var(--soft); }
  .cd-head-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; margin-left: auto; }

  /* Client Detail's row is the shared tab (above); all it adds is where it sits. */
  .cd-tabs { max-width: var(--content-wide); margin: 10px auto -1px; padding: 0 32px; }

  .cd-panel { display: flex; flex-direction: column; gap: 16px; max-width: var(--content-wide); margin: 0 auto; width: 100%; padding: 24px 32px 48px; }
  .client-detail .archived-banner { max-width: var(--content-wide); margin: 16px auto 0; }

  /* ── Settings tab ──
     Label left, value right; scoped to this card because the Account page has rows of the
     same name with a different shape. */
  .cd-settings .settings-row {
    display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 14px; align-items: start;
    padding: 12px 16px; border-top: 1px solid var(--border-row);
  }
  .cd-settings > .cd-card-head + .settings-row { border-top: 0; }
  .cd-settings .settings-key { padding-top: 7px; font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
  .cd-settings .settings-value { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; min-height: var(--control-h); font-size: var(--t-base); font-weight: 400; color: var(--ink); }
  .cd-settings .settings-value .client-chip { width: 30px; height: 30px; }
  .cd-settings .settings-note { font-size: var(--t-sm); color: var(--soft); line-height: 1.45; text-wrap: pretty; }
  .cd-settings .settings-value > .settings-note:last-child:not(:first-child) { flex: 1 1 220px; }
  .settings-input {
    height: var(--control-h); width: 100%; min-width: 0; padding: 0 10px; font: inherit; font-size: var(--t-base); color: var(--ink);
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);

    &:focus { outline: 0; border-color: var(--ink); box-shadow: var(--ring); }
  }
  select.settings-input { appearance: none; }
  .cd-settings .settings-value .settings-input { width: auto; flex: 0 0 auto; }
  .cd-settings .settings-input.code-input { width: 90px; font-family: var(--mono); font-weight: 600; letter-spacing: 0.06em; }
  .cd-settings .settings-input--num { width: 120px; }
  .cd-settings .cd-card-head--sub { padding: 9px 16px; border-top: 1px solid var(--border-divider); background: var(--inset); }

  /* Parent client: the value and its Edit, or the select that replaces them. */
  .settings-parent-body { min-width: 0; }
  .settings-parent-read { display: flex; align-items: center; gap: 10px; min-height: var(--control-h); font-size: var(--t-base); }
  .settings-parent-read .cd-mini-btn { margin-left: auto; }
  .settings-parent-edit { display: none; flex-direction: column; gap: 8px; }
  .settings-parent.is-editing { background: var(--inset); }
  .settings-parent.is-editing .settings-parent-read { display: none; }
  .settings-parent.is-editing .settings-parent-edit { display: flex; }
  .settings-parent-actions { display: flex; justify-content: flex-end; gap: 6px; }

  /* Three states that exclude each other, each carrying the sentence that says what it
     does. Radio rows rather than pills — this is a choice, not a set of toggles. */
  .status-choice {
    display: grid; grid-template-columns: 18px 8px 110px minmax(0, 1fr); gap: 10px; align-items: center;
    padding: 10px 16px; border-top: 1px solid var(--border-row); font-size: 12.5px; cursor: pointer;
  }
  .cd-card-head--sub + .status-choice { border-top: 0; }
  .status-choice:hover, .status-choice:has(.status-radio:checked) { background: var(--inset); }
  .status-radio { margin: 0; accent-color: var(--ink); }
  .status-choice-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sc); }
  .status-choice-name { font-size: var(--t-base); font-weight: 600; }
  .status-choice-hint { color: var(--soft); line-height: 1.45; }

  /* The bar reveals itself once the flag inside it goes dirty — no second controller and
     no state to keep in step, just :has over draft_form's own class. */
  .settings-bar {
    display: none; align-items: center; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--border-divider);
    background: color-mix(in srgb, var(--amber-tint) 55%, var(--card)); border-radius: 0 0 var(--r-card) var(--r-card);
  }
  .settings-bar:has(.settings-dirty.is-dirty) { display: flex; }
  .settings-dirty { flex: 1; font-size: var(--t-sm); color: var(--amber-ink); }

  /* The client's danger zone: a red-edged card, and the type-the-name box opens inside it. */
  .cd-panel .danger-zone { border-color: color-mix(in srgb, var(--err) 28%, var(--card)); }
  .cd-panel .danger-zone .cd-card-head { border-bottom-color: color-mix(in srgb, var(--err) 16%, var(--card)); }
  .cd-panel .danger-item { flex-wrap: wrap; margin: 0; padding: 12px 16px; border-left: 0; }
  .cd-panel .danger-item-text { flex: 1; }
  .cd-panel .danger-item-desc { font-weight: 400; margin-top: 2px; }
  .danger-confirm > summary { list-style: none; }
  .danger-confirm > summary::-webkit-details-marker { display: none; }
  .danger-open { border-color: color-mix(in srgb, var(--err) 28%, var(--card)); background: var(--card); color: var(--err); }
  .danger-open:hover { background: var(--err-tint); }
  .danger-confirm[open] { flex-basis: 100%; }
  .danger-confirm[open] > summary { display: none; }
  .danger-box {
    display: flex; flex-direction: column; gap: 8px; margin: 2px 0 2px; padding: 12px;
    border: 1px solid color-mix(in srgb, var(--err) 28%, var(--card)); border-radius: var(--r-md);
    background: color-mix(in srgb, var(--err-tint) 35%, var(--card)); font-size: 12.5px; color: var(--ink-2);
  }
  .danger-box-actions { display: flex; justify-content: flex-end; gap: 6px; }

  /* "+ Link a client", inside the Sub-clients card: the head is the <summary>, the panel
     opens under it. Two panes, switched by radios and :has — no controller. */
  .cd-link > summary { list-style: none; cursor: pointer; border-bottom: 0; }
  .cd-link > summary::-webkit-details-marker { display: none; }
  .cd-link[open] > summary .cd-mini-btn { background: var(--ink); border-color: var(--ink); color: var(--text-on-inverse); }
  .cd-subclients > .cd-link + * { border-top: 1px solid var(--border-divider); }
  .link-panel {
    display: flex; flex-direction: column; gap: 10px; padding: 14px 16px;
    border-top: 1px solid var(--border-divider); background: var(--inset);
  }
  .link-modes {
    align-self: flex-start; display: inline-flex; gap: 2px; height: 32px; padding: 3px;
    background: var(--sidebar); border: 1px solid var(--border-row); border-radius: var(--r-md);
  }
  .link-mode {
    display: inline-flex; align-items: center; height: 24px; padding: 0 10px; border-radius: 5px;
    font-size: var(--t-base); font-weight: 500; color: var(--ink-2); white-space: nowrap; cursor: pointer;
  }
  .link-mode-radio { position: absolute; opacity: 0; width: 0; height: 0; }
  .link-mode:has(.link-mode-radio:checked) { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px oklch(0 0 0 / 0.08), 0 0 0 1px var(--border-row); }
  .link-mode:has(.link-mode-radio:focus-visible) { box-shadow: var(--ring); }
  .link-pane { display: flex; flex-direction: column; gap: 8px; margin: 0; }
  .link-panel:has(.link-mode-radio[value=attach]:checked) .link-pane--create,
  .link-panel:has(.link-mode-radio[value=create]:checked) .link-pane--attach { display: none; }
  .link-panel .field-note { margin: 0; font-size: var(--t-sm); color: var(--soft); line-height: 1.45; text-wrap: pretty; }
  .link-input {
    height: var(--control-h); width: 100%; min-width: 0; padding: 0 10px; font: inherit; font-size: var(--t-base); color: var(--ink);
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card); outline: 0;

    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .link-foot { display: flex; justify-content: flex-end; align-items: center; gap: 8px; }
  /* The confirm reads the radios directly, so "nothing is selected" is stated once. A
     disabled attribute would be a second copy of it to keep in step — including after
     list-filter clears a hidden pick. */
  .link-pane--attach:not(:has(.attach-radio:checked)) [type=submit] { opacity: 0.45; pointer-events: none; }
  .attach-list { max-height: 200px; overflow: auto; border: 1px solid var(--border-divider); border-radius: var(--r-md); background: var(--card); }
  .attach-option {
    display: grid; grid-template-columns: 16px 24px minmax(0, 1fr) auto; gap: 10px; align-items: center;
    padding: 7px 10px; border-top: 1px solid var(--border-row); font-size: var(--t-base); cursor: pointer;
  }
  .attach-option:first-child { border-top: 0; }
  .attach-option:hover { background: var(--inset); }
  .attach-option .client-chip { width: 24px; height: 24px; border-radius: 6px; font-size: 9px; }
  .attach-radio { margin: 0; accent-color: var(--ink); }
  .attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .attach-meta { font-size: var(--t-sm); color: var(--soft); }
  .attach-list .empty-state, .attach-list .field-note { padding: 12px; }

  /* Library TAB. Only the card chrome now: the rows themselves are the Library page's own
     rows (documents/_row), which is why fifteen rules of parallel row design are gone.
     They used to reuse the same class names under a .lib-list scope, so every change to
     one silently half-applied to the other. */
  .lib-list .cd-card-head { padding: 14px 16px; }
  .lib-list .search-pill { max-width: 300px; }
  .lib-list-foot { display: flex; justify-content: flex-end; padding: 10px 16px; border-top: 1px solid var(--hairline); background: var(--inset); }

  /* Scope of work: a fixed table of service lines, four exclusive owners each. Every client
     gets the same editable card — nothing here is ever disabled or rolled up. */
  /* The Type control on the Engagement card. Two mutually exclusive values are what a segmented
     control is for, and :has(input:checked) — which this stylesheet already leans on in five
     places — is what lets the monthly/project fields swap with no request and no controller.
     ⚠ These rules survived the owner-pill retirement precisely because this card took them
     over; the pills they used to sit beside are gone. */
  .scope-segments {
    display: inline-flex; gap: 2px; height: 32px; padding: 3px; align-self: flex-start;
    background: var(--sidebar); border: 1px solid var(--border-row); border-radius: var(--r-md);
  }
  .scope-seg {
    display: inline-flex; align-items: center; height: 24px; padding: 0 10px; border-radius: 5px;
    font-size: var(--t-base); font-weight: 500; color: var(--ink-2); white-space: nowrap; cursor: pointer;
  }
  .scope-seg-radio { position: absolute; opacity: 0; width: 0; height: 0; }
  .scope-seg:has(.scope-seg-radio:checked) { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px oklch(0 0 0 / 0.08), 0 0 0 1px var(--border-row); }
  .scope-seg:has(.scope-seg-radio:focus-visible) { box-shadow: var(--ring); }
  /* The Type control needs two columns of room once it is a control rather than a word. */
  .cd-edit.is-editing .eng-cell:first-child { grid-column: span 2; }

  /* ── Read-first cards (Engagement, Scope of work) ──
     Each value has a read form (.cd-ro) and a field (.cd-rw) side by side in the markup;
     the card shows one set or the other. Edit is card_edit's is-editing class, which the
     server also renders when a save on that card failed. */
  .cd-edit .cd-rw, .cd-edit .cd-edit-bar { display: none; }
  .cd-edit.is-editing .cd-ro, .cd-edit.is-editing .cd-edit-open { display: none; }
  .cd-edit.is-editing .cd-rw { display: block; }
  .cd-edit.is-editing :is(.scope-segments, .eng-switch).cd-rw { display: inline-flex; }
  .cd-edit.is-editing { box-shadow: var(--ring), var(--shadow-sm); }
  .cd-edit.is-editing .cd-edit-bar {
    display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 10px 16px;
    border-top: 1px solid var(--border-divider); background: var(--inset);
    border-radius: 0 0 var(--r-card) var(--r-card);
  }
  .cd-ro-none { color: var(--dim); font-weight: 400; }

  /* ── Engagement card ── */
  .eng-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px 20px; padding: 16px; }
  .eng-cell { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
  .eng-cell .details-label { font-size: 11.5px; font-weight: 600; color: var(--soft); text-transform: none; letter-spacing: 0; }
  .eng-cell b.cd-ro { font-size: 13.5px; font-weight: 500; color: var(--ink); }
  .eng-cell :is(select, input[type="date"]) {
    width: 100%; height: var(--control-h); padding: 0 10px; appearance: none;
    border: 1px solid var(--border-control); border-radius: var(--r-md);
    background: var(--card); font-family: inherit; font-size: var(--t-base); color: var(--ink);

    &:focus { outline: 0; border-color: var(--ink); box-shadow: var(--ring); }
  }
  .eng-switch { align-items: center; gap: 10px; height: var(--control-h); font-size: var(--t-base); cursor: pointer; }
  .eng-switch-label { font-size: var(--t-base); color: var(--ink); }
  /* ⚠ Both conditional cells are visible by default and each rule hides one, so an engine
     without :has() shows two extra fields — ugly and lossless — rather than hiding one the
     user then cannot reach. Client#settle_engagement_fields nils whichever was hidden, because
     a hidden input still submits. */
  .eng-grid:has(.eng-type-radio[value="monthly"]:checked) .eng-cell--project { display: none; }
  .eng-grid:has(.eng-type-radio[value="project"]:checked) .eng-cell--monthly { display: none; }

  /* ── Scope of work ── */
  .scope-row {
    display: grid; grid-template-columns: 30px minmax(0, 1fr) 92px minmax(0, 1.2fr); gap: 12px; align-items: center;
    min-height: 38px; padding: 6px 16px; border-top: 1px solid var(--border-row); font-size: var(--t-base);
  }
  .cd-edit.is-editing .scope-row { grid-template-columns: 30px minmax(0, 1fr) 120px minmax(0, 1.2fr); }
  .scope-row--head { min-height: 0; padding-top: 10px; border-top: 0; font-size: 11.5px; font-weight: 600; color: var(--dim); }
  .scope-name { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .scope-code { font: 500 var(--t-xs) var(--mono); color: var(--dim); text-align: center; padding: 2px 0; border-radius: 4px; }
  .scope-owner-text { font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); }
  .scope-terms-text { font-size: 12.5px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .scope-owner, .scope-terms {
    width: 100%; min-width: 0; height: 30px; padding: 0 10px; font: inherit; font-size: 12.5px; color: var(--ink);
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);

    &:focus { outline: 0; border-color: var(--ink); box-shadow: var(--ring); }
  }
  .scope-owner { appearance: none; }
  /* Who owns the line, read off the row: ours is the inked tile, someone else's the quiet
     one, and a line nobody has (or that is out of scope) recedes. */
  .scope-row[data-owner="us"] .scope-code { color: var(--text-on-inverse); background: var(--ink); }
  .scope-row[data-owner="us"] .scope-owner-text { color: var(--ink); font-weight: 600; }
  .scope-row:is([data-owner="client"], [data-owner="external"]) .scope-code { color: var(--ink-2); background: var(--sidebar); }
  .scope-row:is([data-owner=""], [data-owner="not_included"]) :is(.scope-name, .scope-owner-text, .scope-terms-text) { color: var(--dim); font-weight: 400; }
  .scope-row:is([data-owner=""], [data-owner="not_included"]) .scope-name { font-weight: 500; }

  /* ── Links & IDs rail, editing in place ──────────────────────────────────────────────── */
  /* The .details-static / .details-edit swap the Campaign Details card uses, under the rail's
     own names. .is-editing comes from card_edit_controller and is never rendered by the
     server, so a save that redirects here lands back in read mode by construction. */
  .links-card .refs-edit, .links-card.is-editing .refs-static { display: none; }
  .links-card.is-editing .refs-edit { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
  .links-card.is-editing .refs-edit-btn, .links-card .refs-done-btn { display: none; }
  .links-card.is-editing .refs-done-btn { display: inline-flex; }
  .links-none { font-size: var(--t-base); color: var(--dim); }
  .links-copy {
    max-width: 100%; height: 24px; padding: 0 7px; border-radius: 6px; background: var(--inset);
    font: 500 var(--t-xs) var(--mono); color: var(--ink-2);
  }
  .links-copy.is-copied { color: var(--ok-strong); border-color: color-mix(in srgb, var(--ok) 35%, var(--card)); }
  .links-card :is(.ref-input, .ref-label-input) {
    width: 100%; min-width: 0; height: var(--control-h); padding: 0 10px; font: inherit; font-size: var(--t-base); color: var(--ink);
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);

    &:focus { outline: 0; border-color: var(--ink); box-shadow: var(--ring); }
  }

  /* ⚠ A container query, not a media query: these cards are constrained by the main column
     (--nav-w 232 + --rail-w 276 + gutters), so a 1180px WINDOW is a ~612px card. Sized to the
     viewport this collapsed the terms field to nothing on a small laptop — the same mistake
     the People table already records. */
  @container (max-width: 620px) {
    .eng-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  /* Campaigns & creative band */
  /* .cd-panel centres and pads its children, so the band no longer carries its own. */
  .campaigns-band { width: 100%; }
  .campaigns-band-summary { font-size: var(--t-sm); color: var(--soft); }
  .campaigns-band-need { font-weight: 600; color: var(--amber-ink); }
  .campaigns-band-list { display: flex; flex-direction: column; }
  .campaigns-band-row {
    display: grid; grid-template-columns: minmax(0, 1.3fr) 150px minmax(0, 1.4fr) 16px; gap: 16px; align-items: center;
    padding: 12px 16px; border-top: 1px solid var(--border-row); color: var(--ink); text-decoration: none;
  }
  .campaigns-band-row:first-child { border-top: none; }
  .campaigns-band-row:hover { background: var(--inset); text-decoration: none; }
  .campaigns-band-row > svg { color: var(--dim); }
  .campaigns-band-row:hover > svg { color: var(--ink); }
  .cbr-name { min-width: 0; }
  .cbr-title { display: block; font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cbr-name .status-pill { margin-top: 3px; font-size: var(--t-sm); font-weight: 500; }
  .cbr-budget-top { display: flex; justify-content: space-between; gap: 10px; font-size: var(--t-sm); color: var(--ink-2); white-space: nowrap; }
  .cbr-money { font-variant-numeric: tabular-nums; }
  .cbr-pct { font: 500 var(--t-xs) var(--mono); color: var(--soft); }
  .cbr-pct.is-over { color: var(--err); font-weight: 600; }
  .cbr-budget .budget-bar { height: 4px; margin-top: 5px; }
  .cbr-assets { display: flex; flex-wrap: wrap; gap: 4px 12px; min-width: 0; font-size: var(--t-sm); }
  .cbr-assets-label { font-size: var(--t-xs); font-weight: 600; color: var(--dim); text-transform: uppercase; letter-spacing: 0.04em; margin-right: 2px; }
  .cbr-assets-seg { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; color: var(--ink); white-space: nowrap; }
  .cbr-assets-seg.is-muted { font-weight: 400; color: var(--soft); }
  .cbr-assets-seg .creative-band-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--sc, var(--dim)); flex-shrink: 0; }
  .cbr-assets-seg.is-muted .creative-band-dot { opacity: 0.55; }
  .cd-split--overview { grid-template-columns: minmax(0, 1fr) 300px; }

  /* Right-rail cards (Library module + team) */
  /* The card. One wrapper for the whole app — the campaign page used to carry four
     private copies of this rule, one of them byte-identical. */
  .cd-card { overflow: hidden; }
  /* A card's two bands. The header rules off from the body, the footer is the inset bar
     that carries a card's decisions (Save, Export, "nothing sends until…"). */
  .cd-card-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
    padding: var(--card-header-pad); border-bottom: 1px solid var(--border-divider);
  }
  .cd-card-foot {
    display: flex; align-items: center; justify-content: space-between; gap: var(--control-gap);
    flex-wrap: wrap; padding: var(--card-footer-pad);
    background: var(--inset); border-top: 1px solid var(--border-divider);
  }
  /* A card body that isn't a list of rows still wants breathing room. */
  .cd-card-body { padding: 14px 16px 16px; }
  /* A square icon control: the metrics pencil, the month stepper. One system, not two. */
  .cd-icon-btn {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 26px; height: 26px; padding: 0; line-height: 0; cursor: pointer; list-style: none;
    border: 1px solid var(--border-control); border-radius: var(--r);
    background: var(--card); box-shadow: var(--shadow-button); color: var(--soft);
  }
  .cd-icon-btn::-webkit-details-marker { display: none; }
  .cd-icon-btn:hover { background: var(--inset); color: var(--ink); }
  /* The small control in a card's head or a row's end: Edit, + Add person, + Link a client. */
  .cd-mini-btn {
    display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 9px; flex: none;
    border: 1px solid var(--border-control); border-radius: 7px; background: var(--card);
    font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); white-space: nowrap;
    cursor: pointer; list-style: none; text-decoration: none;
  }
  .cd-mini-btn::-webkit-details-marker { display: none; }
  .cd-mini-btn:hover { border-color: var(--soft); color: var(--ink); text-decoration: none; }
  .cd-mini-btn--on { background: var(--ink); border-color: var(--ink); color: var(--text-on-inverse); }
  /* Copy-button label swap, driven by clipboard_controller's .is-copied. Unscoped so any
     button can wear it — each then says for itself what "copied" looks like (.cd-mini-btn
     greens just the word; .share-copy greens the whole button). */
  .cd-mini-done { display: none; }
  .is-copied .cd-mini-label { display: none; }
  .is-copied .cd-mini-done { display: inline; }
  .cd-mini-btn .cd-mini-done { color: var(--ok-strong); }

  /* Links & IDs — external addresses (Asana/Canva/Drive/inboxes) on the client rail */
  .links-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; padding: 10px 16px; border-top: 1px solid var(--border-row); }
  .links-card > .cd-card-head + .links-row { border-top: 0; }
  .links-row-main { min-width: 0; display: flex; flex-direction: column; }
  .links-key { font-size: 11.5px; font-weight: 600; color: var(--soft); }
  .links-name { display: block; font-size: var(--t-base); font-weight: 500; color: var(--accent-ink); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .links-name:hover { text-decoration: underline; }
  .links-inboxes { display: flex; flex-direction: column; font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere; }
  .links-id { flex-shrink: 0; font-size: var(--t-sm); color: var(--soft); }

  .links-row.is-inherited .links-name { color: var(--ink-2); }
  .links-via { font-size: 11.5px; color: var(--dim); }
  .links-card > .refs-static { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border-row); }
  .links-card.is-editing > .refs-static { display: none; }
  .links-empty-lead { margin: 0; font-size: var(--t-sm); color: var(--soft); line-height: 1.45; text-wrap: pretty; }
  .ref-label-input { margin-top: 4px; }

  /* Meta campaigns — the rail card mapping Meta's campaigns onto this one. */
  .meta-map-id { font-family: var(--mono); font-size: var(--t-xs); color: var(--soft); overflow-wrap: anywhere; }
  .meta-map-note { display: flex; flex-direction: column; gap: 8px; margin: 0; font-size: var(--t-sm); color: var(--ink-2); line-height: 1.5; }
  .meta-map-note p { margin: 0; }
  .meta-map-note.is-warning { display: block; }
  .meta-map-note.is-warning::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 8px; border-radius: 50%; background: var(--badge-amber); vertical-align: 1px; }
  .meta-map-row { display: flex; align-items: center; gap: 8px; padding: 9px 10px 9px 16px; border-top: 1px solid var(--hairline); }
  .cd-card-head + .meta-map-row { border-top: 0; }
  .meta-map-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
  .meta-map-name { font-size: var(--t-base); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .meta-map-name.is-unnamed { font-weight: 400; color: var(--soft); }
  .meta-map-remove:hover { color: var(--err); }
  .meta-map-add { flex-direction: column; align-items: stretch; }
  .meta-map-add-row { display: flex; gap: var(--control-gap); }
  /* ⚠ flex: 1 only in the row. The footer is a column, where flex: 1 means a zero-height basis. */
  .meta-map-add-row .meta-map-input { flex: 1; }
  .meta-map-input {
    min-width: 0; height: var(--control-h); padding: 0 10px; font: inherit; font-size: var(--t-base);
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card); color: var(--ink);
    box-shadow: var(--shadow-button);
  }
  .meta-map-input.is-mono { font-family: var(--mono); font-size: var(--t-sm); }

  /* A card head with a line or two under its title. */
  .cd-card-headings { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
  .cd-card-sub { font-size: var(--t-sm); color: var(--soft); }
  .cd-card-hint { font-size: var(--t-sm); color: var(--dim); }

  /* ── People ──
     The card's head is the <summary> of the add panel; each row is a grid whose Edit form
     and access question are full-width children that open underneath it. */
  .cd-add > summary { list-style: none; cursor: pointer; }
  .cd-add > summary::-webkit-details-marker { display: none; }
  .cd-add:not([open]) > summary { border-bottom: 0; }
  .cd-add[open] > summary .cd-mini-btn { background: var(--ink); border-color: var(--ink); color: var(--text-on-inverse); }
  .cd-add-panel { padding: 14px 16px; background: var(--inset); }
  /* ⚠ Not clipped: the Updates-for popover hangs out of a row, and out of the card when
     the row is the last one. The first and last children round their own corners. */
  .cd-card.cd-team { overflow: visible; }

  .cd-person-row {
    display: grid; grid-template-columns: minmax(0, 1.8fr) 124px minmax(0, 1fr) 108px 56px; gap: 0 12px;
    align-items: center; padding: 10px 16px;
  }
  .cd-person-head {
    padding-top: 10px; padding-bottom: 6px; border-top: 1px solid var(--border-divider); border-bottom: 1px solid var(--border-divider);
    font-size: 11.5px; font-weight: 600; color: var(--dim);
  }
  .cd-person { position: relative; border-top: 1px solid var(--border-row); }
  .cd-person-head + .cd-person { border-top: 0; }
  .cd-person.is-inherited { background: var(--inset); }
  .cd-person:last-child { border-radius: 0 0 var(--r-card) var(--r-card); }
  .cd-person:has([open]) { z-index: 5; }
  .cd-person-cell { display: flex; align-items: center; min-width: 0; margin: 0; }
  .cd-person-id { gap: 10px; }
  .cd-person-who { min-width: 0; }
  .cd-person-line { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
  .cd-person-name { font-size: var(--t-base); font-weight: 600; }
  .cd-person-title { font-size: var(--t-sm); color: var(--soft); }
  .cd-person-email { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: var(--t-sm); color: var(--soft); }
  .cd-person-via { color: var(--accent-ink); }
  .cd-person-dash { color: var(--dim); }
  .cd-person-actions { justify-content: flex-end; }
  .role-tag { font-size: 10.5px; font-weight: 500; padding: 1px 6px; border-radius: 999px; background: var(--sidebar); color: var(--ink-2); }
  .role-tag--relay { background: var(--accent-tint); color: var(--accent-ink); }
  .role-tag--internal { background: #EEECF5; color: #4B4664; }

  /* Access: a quiet word with a dot until you point at it, then visibly a control. */
  .perm-form { margin: 0; }
  .perm-pill {
    display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 6px; margin-left: -6px;
    border: 1px solid transparent; border-radius: 6px; background: transparent;
    font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); white-space: nowrap; cursor: pointer;
  }
  .perm-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--border-control); }
  .perm-pill--on { color: var(--ok-strong); }
  .perm-pill--on .perm-dot { background: var(--ok); }
  .perm-pill:hover { background: var(--card); border-color: var(--border-control); }
  .perm-pill--on:hover { border-color: color-mix(in srgb, var(--ok) 35%, var(--card)); }
  .perm-pill.is-static { cursor: default; }
  .perm-pill.is-static:hover { background: transparent; border-color: transparent; }

  /* Updates for: the scope as words, and the popover that sets it. */
  .scope-picker { position: relative; flex: 1; min-width: 0; }
  .scope-trigger {
    display: flex; align-items: center; gap: 6px; width: fit-content; max-width: 100%; height: 26px; padding: 0 8px; margin-left: -8px;
    border: 1px solid transparent; border-radius: 7px; font-size: var(--t-sm); color: var(--ink);
    list-style: none; cursor: pointer;
  }
  .scope-trigger::-webkit-details-marker { display: none; }
  .scope-trigger > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .scope-trigger > svg { flex: none; color: var(--dim); }
  .scope-trigger.is-off { color: var(--dim); }
  .cd-person:hover .scope-trigger, .scope-picker[open] > .scope-trigger { border-color: var(--border-control); background: var(--card); }
  .scope-box {
    position: absolute; z-index: 40; left: -8px; top: calc(100% + 6px); width: 270px;
    background: var(--card); border: 1px solid var(--border-card); border-radius: 10px;
    box-shadow: 0 14px 34px -10px oklch(0 0 0 / 0.25);
  }
  .scope-box form { margin: 0; }
  .scope-list { max-height: 260px; overflow: auto; padding: 6px; }
  .scope-account { padding: 4px 0; }
  .scope-account + .scope-account { border-top: 1px solid var(--border-row); }
  .scope-line { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 6px; font-size: 12.5px; cursor: pointer; }
  .scope-line:hover { background: var(--inset); }
  .scope-line--all { font-weight: 600; }
  .scope-line--campaign { padding-left: 28px; color: var(--ink-2); }
  .scope-check { accent-color: var(--ink); margin: 0; }
  .scope-empty { margin: 0; padding: 4px 8px 4px 28px; font-size: var(--t-sm); color: var(--dim); }
  .scope-foot {
    display: flex; align-items: center; gap: 6px; padding: 8px 10px;
    border-top: 1px solid var(--border-divider); background: var(--inset); border-radius: 0 0 10px 10px;
  }
  .scope-foot-note { flex: 1; font-size: var(--t-sm); color: var(--soft); line-height: 1.35; }

  /* The panels under a row. */
  .cd-person-panel { grid-column: 1 / -1; padding: 12px 0 4px; }
  .grant-box {
    display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: var(--r-md);
    background: color-mix(in srgb, var(--ok-tint) 55%, var(--card)); border: 1px solid color-mix(in srgb, var(--ok) 22%, var(--card));
    font-size: 12.5px; color: var(--ink);
  }
  .grant-box > span { flex: 1; min-width: 0; }
  .grant-box b { font-weight: 600; }
  .grant-box small { display: block; font-size: var(--t-sm); color: var(--soft); }
  .grant-box form { margin: 0; }

  /* Sub-clients: the brands this client relays for. */
  .subclient-row {
    display: grid; grid-template-columns: 28px minmax(0, 1fr) 14px; gap: 12px; align-items: center;
    padding: 10px 16px; border-top: 1px solid var(--border-row); color: var(--ink); text-decoration: none;
  }
  .subclient-row:hover { background: var(--inset); text-decoration: none; }
  .subclient-row > svg { color: var(--dim); }
  .subclient-text { display: flex; flex-direction: column; min-width: 0; }
  .subclient-name { font-size: var(--t-base); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .subclient-meta { font-size: var(--t-sm); color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .subclient-empty { margin: 0; padding: 20px 16px; font-size: var(--t-base); color: var(--soft); line-height: 1.5; }

  @media (max-width: 700px) {
    .cd-head-row { flex-wrap: wrap; }
    .client-chip--xl { width: 36px; height: 36px; font-size: var(--t-xs); }
    .cd-title h1 { font-size: 19px; }
    .cd-head-actions { width: 100%; }
    .cd-head-actions .btn { width: 100%; height: 38px; }
    /* ⚠ Scoped under .client-detail to outrank the @container rule further down, which
       sets the same three properties for a narrow card on a wide screen. */
    .client-detail .campaigns-band-row { grid-template-columns: minmax(0, 1fr) 16px; row-gap: 8px; }
    .client-detail .cbr-budget { grid-column: 1 / -1; grid-row: 2; }
    .client-detail .cbr-assets { grid-row: 3; }
    .client-detail .campaigns-band-row > svg { grid-row: 1; grid-column: 2; }
    .eng-grid { grid-template-columns: 1fr 1fr; }
    .scope-row, .cd-edit.is-editing .scope-row { grid-template-columns: 28px minmax(0, 1fr) auto; }
    .scope-row--head > span:last-child { display: none; }
    .scope-row :is(.scope-terms-text, .scope-terms) { grid-column: 2 / -1; }
    .scope-terms-text:empty { display: none; }
    .cd-settings .settings-row { grid-template-columns: 1fr; gap: 6px; }
    .cd-settings .settings-key { padding-top: 0; }
    .status-choice { grid-template-columns: 18px 8px minmax(0, 1fr); }
    .status-choice-hint { grid-column: 3; }
    .cd-edit.is-editing .scope-owner { width: 110px; }
  }

  .timeline-source { font-size: 11.5px; font-weight: 500; color: var(--ink-2); background: var(--sidebar); border-radius: 999px; padding: 1px 8px; margin-left: 6px; }

  @container (max-width: 900px) {
    .cd-head-top { padding: 14px 20px 0; }
    .cd-tabs { padding: 0 20px; overflow-x: auto; scrollbar-width: none; }
    .cd-panel { padding: 20px; }
    .campaigns-band-row { grid-template-columns: minmax(0, 1fr) 140px 16px; }
    .cbr-assets { grid-column: 1 / -1; grid-row: 2; }
    .campaigns-band-row > svg { grid-row: 1; grid-column: 3; }

    /* ⚠ A @container, not a @media query: the People table is constrained by the CARD it
       sits in, not by the window — the sidebar takes ~240px of whatever the window is. */
    .cd-person-row { grid-template-columns: minmax(0, 1.6fr) 120px minmax(0, 1fr) 96px 56px; }
  }

  /* Below here the People columns stop being columns: the person and Edit share a line,
     access and scope the next, and the switch says what it is — a bare switch means
     nothing once "Approve creative" is no longer written above it. */
  @container (max-width: 620px) {
    .cd-person-head { display: none; }
    .cd-person-row { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "who edit" "access scope" "approve approve"; row-gap: 8px; }
    .cd-person-id { grid-area: who; }
    .cd-person-actions { grid-area: edit; }
    .cd-person-access { grid-area: access; }
    .cd-person-row > [data-label] { grid-area: scope; justify-content: flex-end; }
    .cd-person-approve { grid-area: approve; gap: 8px; font-size: var(--t-sm); color: var(--soft); }
    .cd-person-approve::after { content: "Approve creative"; }
    .cd-person-panel { grid-area: auto; grid-column: 1 / -1; }
    .person-form { grid-template-columns: 1fr; }
    .scope-box { left: auto; right: 0; width: min(280px, calc(100vw - 40px)); }
    .grant-box { flex-wrap: wrap; }
    .grant-box > span { flex-basis: 100%; }
  }

  /* ── Settings / Account ──────────────────────────── */
  /* ── Account (the profile page) ──
     One 640px column. A row is label | control | status, and the status column is where a
     silent auto-save says "Saved" — or where a lock says the value is not yours to change. */
  .profile { flex: 1; overflow-y: auto; padding-block: 20px 72px; padding-inline: var(--page-gutter); display: flex; flex-direction: column; gap: 12px; }
  /* ⚠ flex: none. A card is overflow: hidden, which makes its minimum height zero, and a flex
     column that can scroll will happily crush it to fit rather than scroll. */
  .profile > * { flex: none; width: 100%; max-width: 640px; margin-inline: auto; }
  /* The same trap, on the two other pages built as a scrolling column of cards. */
  .integrations-body > *, .sg-body > * { flex-shrink: 0; }
  .profile > .section-label { display: block; margin-top: 12px; font-size: var(--t-xs); letter-spacing: 0.05em; color: var(--dim); }
  .profile-identity { display: flex; align-items: center; gap: 14px; padding: 18px 18px 16px; border-bottom: 1px solid var(--border-divider); }
  .profile-avatar-form { margin: 0; flex: none; }
  .profile-avatar { position: relative; display: block; width: 56px; height: 56px; border-radius: 50%; overflow: hidden; cursor: pointer; }
  .profile-avatar-face {
    width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
    background: var(--accent); color: var(--on-accent); font-size: 18px; font-weight: 700;
    & img { width: 100%; height: 100%; object-fit: cover; display: block; }
  }
  .profile-avatar-hint {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    background: oklch(0 0 0 / 0.45); color: var(--text-on-inverse); font-size: var(--t-xs); font-weight: 600;
    opacity: 0; transition: opacity 150ms;
  }
  .profile-avatar:hover .profile-avatar-hint, .profile-avatar:focus-within .profile-avatar-hint { opacity: 1; }
  .profile-avatar-input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
  .profile-identity-text { flex: 1; min-width: 0; }
  .profile-identity-text b { display: block; font-size: 16px; font-weight: 700; letter-spacing: -0.01em; }
  .profile-identity-text small { display: block; margin-top: 2px; font-size: var(--t-base); color: var(--soft); }

  .profile-row {
    display: grid; grid-template-columns: 140px minmax(0, 1fr) 56px; gap: 12px; align-items: center;
    min-height: 48px; padding: 6px 10px 6px 18px; border-top: 1px solid var(--border-row);
  }
  .profile-identity + .profile-row { border-top: 0; }
  .profile-form { display: contents; }
  .profile-key { font-size: var(--t-base); font-weight: 500; color: var(--soft); }
  .profile-value { font-size: var(--t-lg); color: var(--ink-2); }
  /* A quiet input: it reads as the value until you reach for it. The negative margin puts the
     TEXT on the column's edge, so a field and a read-only value line up down the card. */
  .profile-input {
    width: 100%; height: 34px; margin-left: -11px; padding: 0 10px;
    border: 1px solid transparent; border-radius: var(--r-md); background: transparent;
    font: inherit; font-size: var(--t-lg); color: var(--ink); outline: none;
    transition: background 120ms, border-color 120ms;

    &:hover { background: var(--inset); border-color: var(--border-row); }
    &:focus { background: var(--card); border-color: var(--ink); box-shadow: var(--ring); }
  }
  .profile-input--select {
    appearance: none; cursor: pointer; padding-right: 28px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23948C81' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 10px center;
  }
  .profile-saved, .profile-locked { justify-self: end; display: inline-flex; align-items: center; gap: 4px; }
  .profile-saved { font-size: var(--t-sm); font-weight: 500; color: var(--ok-strong); opacity: 0; transition: opacity 200ms; }
  .profile-form.is-saved .profile-saved { opacity: 1; }
  .profile-locked { color: var(--dim); }
  .profile-split { display: flex; align-items: center; gap: 16px; padding: 14px 16px 14px 18px; }
  .profile-split > div:first-child { flex: 1; min-width: 0; }
  .profile-split form { margin: 0; }

  .settings-item-title { font-size: var(--t-base); font-weight: 600; }
  .settings-item-desc { font-size: var(--t-sm); color: var(--dim); margin-top: 3px; line-height: 1.4; }

  .soon-tag { margin-left: 6px; padding: 2px 6px; border-radius: var(--radius-badge); background: var(--sidebar); font-size: var(--t-xs); font-weight: 600; color: var(--soft); text-transform: uppercase; letter-spacing: 0.04em; }

  /* Toggle switch */
  .switch { position: relative; display: inline-block; cursor: pointer; flex-shrink: 0; }
  .switch input { position: absolute; opacity: 0; width: 0; height: 0; }
  .switch-track { display: block; width: 36px; height: 20px; border-radius: 10px; background: oklch(0.86 0.012 84.6); transition: background 0.15s ease; }
  .switch-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px oklch(0.2 0 0 / 0.25); transition: left 0.15s ease; }
  .switch:has(input:checked) .switch-track { background: var(--ok); }
  .switch:has(input:focus-visible) .switch-track { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 15%, transparent); }
  .switch:has(input:checked) .switch-track::after { left: 18px; }

  /* ── Integrations ────────────────────────────────── */
  .tool-sub { font-size: var(--t-sm); color: var(--dim); margin-top: 2px; }
  /* Full-width scroll region (scrollbar sits at the window edge, not mid-screen); the
     content itself is capped, so the list reads at a comfortable width. */
  .integrations-body { flex: 1; overflow-y: auto; padding-block: 24px 72px; padding-inline: var(--page-gutter); display: flex; flex-direction: column; gap: 16px; }
  .integrations-body > * { flex-shrink: 0; }
  .integrations-list, .integration-request { width: 100%; max-width: 720px; margin-inline: auto; }

  /* One list of rows that open in place. The list is the card; a row is a <details>, and
     the rows share a `name`, so the browser keeps one open at a time. */
  .integrations-list {
    background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card);
    box-shadow: var(--shadow-sm); overflow: hidden;
  }
  .integration-row { border-top: 1px solid var(--border-row); }
  .integration-row:first-child { border-top: 0; }
  .integration-summary {
    display: grid; grid-template-columns: 40px minmax(0, 1fr) auto; gap: 14px; align-items: center;
    padding: 14px 14px 14px 16px; cursor: pointer; list-style: none;
  }
  .integration-summary::-webkit-details-marker { display: none; }
  .integration-summary:hover { background: var(--inset); }
  .integration-row[open] > .integration-summary:hover { background: transparent; }
  .integration-row[open] > .integration-summary { cursor: default; }
  .integration-summary .btn { min-width: 84px; }
  .integration-row[open] > .integration-summary .integration-toggle { visibility: hidden; }

  /* The logo tile. `--full` is a mark that fills its tile edge to edge (Fathom's);
     without the file on disk it falls back to the integration's initial. */
  .integration-tile {
    width: 40px; height: 40px; flex: none; display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--border-row); border-radius: 10px; background: var(--card);
    overflow: hidden; font-size: var(--t-lg); font-weight: 600; color: var(--soft);
  }
  .integration-tile img { width: 22px; height: 22px; display: block; }
  .integration-tile--full { border: 0; }
  .integration-tile--full img { width: 100%; height: 100%; object-fit: cover; }

  .integration-headtext { min-width: 0; }
  .integration-title { display: flex; align-items: center; gap: 8px; font-size: var(--t-lg); font-weight: 600; }
  .integration-desc { font-size: var(--t-sm); color: var(--dim); margin-top: 2px; line-height: 1.45; text-wrap: pretty; }
  /* A failing sync's own line: the error string is as long as the upstream API made it, and
     it has to wrap inside the row rather than widen it. */
  .integration-desc.is-bad { color: var(--err-strong); overflow-wrap: anywhere; }

  /* Connected / Not working — a dot and a word, the badge shape the rest of the app uses. */
  .conn-badge {
    display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
    font-size: var(--t-xs); font-weight: 500; color: var(--ok-strong); background: var(--ok-tint);
    border-radius: var(--r-full); padding: 1px 8px;
  }
  .conn-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
  .conn-badge--bad { color: var(--err-strong); background: var(--err-tint); }

  /* The row's expanded body, indented under the title past the logo tile. */
  .integration-panel { padding: 2px 16px 18px 70px; display: flex; flex-direction: column; gap: 14px; }

  /* What the connector can do, said once at the top of the panel. */
  .conn-ro {
    display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
    font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); background: var(--inset);
    border-radius: var(--r-tile); padding: 5px 9px; line-height: 1.35;
  }
  .conn-ro svg { flex: none; color: var(--soft); }

  .conn-fail {
    display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 8px;
    font-size: var(--t-sm); line-height: 1.5; color: var(--err-strong);
    background: color-mix(in srgb, var(--err-tint) 40%, var(--card));
    border: 1px solid color-mix(in srgb, var(--err-strong) 25%, transparent);
    border-radius: 9px; padding: 9px 11px;
  }
  .conn-fail svg { margin-top: 2px; }

  .conn-nudge {
    font-size: var(--t-sm); line-height: 1.5; color: var(--amber-ink);
    background: var(--amber-tint); border: 1px solid color-mix(in srgb, var(--amber-ink) 25%, transparent);
    border-radius: 9px; padding: 9px 11px;
  }

  .conn-steps { counter-reset: step; list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
  .conn-steps li {
    counter-increment: step; display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 8px;
    font-size: var(--t-base); line-height: 1.5; color: var(--ink-2);
  }
  .conn-steps li::before {
    content: counter(step); width: 20px; height: 20px; margin-top: 1px; border-radius: 50%;
    background: var(--inset); color: var(--ink-2); font: 600 var(--t-xs)/20px var(--mono); text-align: center;
  }
  .conn-steps strong { color: var(--ink); font-weight: 600; }

  /* The copy list: label | value | Copy, one bordered group. The value is a readonly input
     (clipboard_controller reads its .value) dressed as plain mono text. */
  .conn-fields { display: flex; flex-direction: column; border: 1px solid var(--border-row); border-radius: 10px; overflow: hidden; }
  .conn-field-group {
    display: grid; grid-template-columns: 150px minmax(0, 1fr) auto; gap: 12px; align-items: center;
    padding: 8px 8px 8px 12px; border-top: 1px solid var(--border-row);
  }
  .conn-field-group:first-child { border-top: 0; }
  .conn-label { font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); line-height: 1.3; }
  .conn-label-hint { display: block; font-weight: 400; font-size: var(--t-xs); color: var(--dim); }
  .conn-code {
    min-width: 0; width: 100%; border: 0; background: transparent; padding: 0; outline: 0;
    font: var(--t-base) var(--mono); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .conn-copy {
    height: 26px; padding: 0 9px; border: 0; background: transparent; border-radius: var(--r);
    font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  }
  .conn-copy:hover { background: var(--inset); color: var(--ink); }
  .conn-copy .cd-mini-done { color: var(--ok-strong); }

  /* The two key cards: a labelled password field with its Save button. */
  .conn-key { display: flex; flex-direction: column; gap: 6px; }
  .conn-key-row { display: flex; gap: 8px; }
  .conn-code--input {
    flex: 1; height: var(--control-h); border: 1px solid var(--border-control); border-radius: var(--r-md);
    padding: 0 10px; white-space: normal; background: var(--card);
  }
  .conn-code--input:focus { border-color: var(--ink); box-shadow: var(--ring); }
  .conn-code--input::placeholder { font-family: inherit; color: var(--dim); }

  /* Advanced, inside Claude's row. Its summary is a quiet line with a chevron that turns. */
  .conn-advanced { border-top: 1px solid var(--border-row); padding-top: 12px; }
  .conn-advanced > summary {
    display: flex; align-items: center; gap: 6px; list-style: none; cursor: pointer;
    font-size: var(--t-sm); font-weight: 500; color: var(--soft);
  }
  .conn-advanced > summary::-webkit-details-marker { display: none; }
  .conn-advanced > summary:hover { color: var(--ink); }
  .conn-advanced > summary svg { transition: transform 0.18s; }
  .conn-advanced[open] > summary svg { transform: rotate(90deg); }
  .conn-advanced-body { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
  .conn-advanced-body form, .conn-advanced-body .btn { align-self: flex-start; }

  .conn-hint { margin: 0; font-size: var(--t-sm); color: var(--dim); line-height: 1.5; }
  .conn-hint strong { color: var(--ink-2); font-weight: 600; }
  .conn-hint code { font: var(--t-xs) var(--mono); color: var(--ink-2); }

  /* The row's footer: what removes the connection on the left, what leaves on the right. */
  .integration-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
  .integration-foot form { margin: 0; }
  .integration-foot-spacer { flex: 1; }
  .integration-danger { color: var(--err-strong); }

  /* The inline confirm, in the footer. Closed it is just its trigger; open it takes the
     full row above the footer's other buttons, so the question sits over the answer. */
  .integration-foot .inline-confirm[open] { flex-basis: 100%; order: -1; }
  .integration-foot .inline-confirm[open] > summary { display: none; }
  .integration-panel .confirm-box {
    position: static; width: auto; margin: 0; box-shadow: none;
    border: 1px solid color-mix(in srgb, var(--err-strong) 25%, transparent);
    background: color-mix(in srgb, var(--err-tint) 40%, var(--card));
    border-radius: 10px; padding: 12px;
    display: flex; flex-direction: column; gap: 10px;
  }
  .integration-panel .confirm-box p { margin: 0; font-size: var(--t-base); color: var(--ink); line-height: 1.5; }
  .confirm-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
  .confirm-input {
    width: 140px; height: var(--control-h-sm); border: 1px solid var(--border-control); border-radius: var(--r-md);
    padding: 0 9px; font: var(--t-sm) var(--mono); background: var(--card); color: var(--ink);
  }
  .confirm-input:focus { outline: 0; border-color: var(--ink); box-shadow: var(--ring); }
  .confirm-form .confirm-actions { margin-left: auto; }

  /* "Need another tool?" — a dashed invitation, not a card. */
  .integration-request {
    display: grid; grid-template-columns: 40px minmax(0, 1fr) auto; gap: 14px; align-items: center;
    padding: 12px 14px 12px 16px; border: 1px dashed var(--border-control); border-radius: var(--r-card);
  }
  .integration-request-tile {
    width: 40px; height: 40px; border-radius: 10px; background: var(--inset); color: var(--soft);
    display: inline-flex; align-items: center; justify-content: center;
  }
  .integration-request-text { min-width: 0; }

  @media (max-width: 700px) {
    .integration-summary { grid-template-columns: 36px minmax(0, 1fr); gap: 12px; padding: 14px; }
    .integration-toggle { grid-column: 1 / 3; width: 100%; height: 36px; }
    .integration-row[open] > .integration-summary .integration-toggle { display: none; }
    .integration-tile { width: 36px; height: 36px; }
    .integration-panel { padding: 0 14px 16px; }
    .conn-field-group { grid-template-columns: minmax(0, 1fr) auto; row-gap: 2px; }
    .conn-field-group .conn-label { grid-column: 1 / 3; }
    .conn-key-row { flex-direction: column; }
    .conn-key-row .conn-code--input { flex: none; height: 36px; }
    .conn-key-row .btn { height: 36px; justify-content: center; }
    .integration-foot { flex-direction: column-reverse; align-items: stretch; }
    .integration-foot-spacer { display: none; }
    .integration-foot .btn, .integration-foot .inline-confirm > summary { height: 36px; justify-content: center; }
    .integration-request { grid-template-columns: 36px minmax(0, 1fr); }
    .integration-request-tile { width: 36px; height: 36px; }
    .integration-request .btn { grid-column: 1 / 3; justify-content: center; height: 36px; }
  }

  /* Claude OAuth consent screen (doorkeeper) */
  /* The OAuth consent screen (its own layout — no shell, no Turbo). One card, top-aligned
     because it is a long read, with who you are signed in as underneath it. */
  .consent-page { min-height: 100vh; display: flex; align-items: flex-start; justify-content: center; padding: 56px 16px 40px; background: var(--inset); }
  .consent { width: 100%; max-width: 460px; display: flex; flex-direction: column; gap: 14px; }
  .consent-card { border-radius: 16px; padding: 28px; box-shadow: 0 1px 2px oklch(0 0 0 / 0.04), 0 12px 32px -16px oklch(0 0 0 / 0.12); }
  .consent-pair { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 20px; }
  .consent-mark {
    width: 44px; height: 44px; border-radius: 11px; flex: none; overflow: hidden;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--card); border: 1px solid var(--border-row);
  }
  .consent-mark--app { background: var(--ink); border-color: var(--ink); color: var(--text-on-inverse); font-size: 18px; font-weight: 700; }
  .consent-link { display: flex; align-items: center; gap: 4px; color: var(--dim); }
  .consent-link i { width: 5px; height: 5px; border-radius: 50%; background: var(--border-control); }
  .consent-title { font-size: var(--t-2xl); font-weight: 700; letter-spacing: -0.015em; line-height: 1.25; margin: 0; text-align: center; text-wrap: balance; }
  .consent-sub { font-size: var(--t-lg); color: var(--ink-2); line-height: 1.55; margin: 8px 0 0; text-align: center; text-wrap: pretty; }
  .consent-sub strong { color: var(--ink); font-weight: 600; }
  .consent-group { margin-top: 20px; }
  .consent-group-head { margin: 0 0 8px; font-size: var(--t-xs); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--dim); }
  .consent-scopes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
  .consent-scopes li { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 9px; font-size: var(--t-base); line-height: 1.5; color: var(--ink); text-wrap: pretty; }
  .consent-scopes--cant li { color: var(--ink-2); }
  .consent-ok, .consent-no { width: 18px; height: 18px; margin-top: 1px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; }
  .consent-ok svg, .consent-no svg { stroke-width: 2.6; }
  .consent-ok { background: var(--ok-tint); color: var(--ok-strong); }
  .consent-no { background: var(--sidebar); color: var(--soft); }
  .consent-redirect { margin: 20px 0 0; padding-top: 16px; border-top: 1px solid var(--border-row); font-size: var(--t-base); color: var(--soft); text-align: center; }
  .consent-redirect code { font-family: var(--mono); font-size: var(--t-sm); color: var(--ink-2); background: var(--sidebar); border-radius: var(--r-sm); padding: 1px 6px; }
  /* Deny is a third, Allow two — the answer most people came to give is the bigger target. */
  .consent-actions { display: grid; grid-template-columns: 1fr 2fr; gap: 8px; margin-top: 16px; }
  .consent-actions form { margin: 0; }
  .consent-actions .btn { width: 100%; height: 40px; }
  .consent-signed { font-size: var(--t-sm); color: var(--soft); text-align: center; }
  .consent-signed b { color: var(--ink-2); font-weight: 500; }
  .consent-switch-form { display: inline; margin: 0; }
  .consent-switch { background: none; border: none; padding: 0; font: inherit; color: var(--soft); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
  @media (max-width: 480px) {
    .consent-page { padding: 20px 12px; }
    .consent-card { padding: 22px 18px; }
    /* Stacked, Allow first. */
    .consent-actions { grid-template-columns: 1fr; }
    .consent-allow { order: -1; }
  }

  /* ── Login ───────────────────────────────────────── */
  /* Signed-out pages: login, and the share gate that borrows its shell. A centred column,
     the brand above one card, the credit pill pinned to the foot. No bloom — that belongs
     to the signed-in shell. */
  .auth { min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 0 16px; background: var(--inset); }
  .auth-inner { flex: 1; width: 100%; max-width: 400px; display: flex; flex-direction: column; justify-content: center; gap: 18px; padding: 48px 0 24px; }
  .auth-brand { display: flex; align-items: center; gap: 9px; justify-content: center; font-size: 16px; font-weight: 700; letter-spacing: -0.01em; }
  .auth-brand img { border-radius: 7px; }
  .auth-card {
    background: var(--card); border: 1px solid var(--border-card); border-radius: 16px; padding: 28px;
    box-shadow: 0 1px 2px oklch(0 0 0 / 0.04), 0 12px 32px -16px oklch(0 0 0 / 0.12);
  }
  .auth-card h1 { font-size: 21px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; margin: 0; }
  .auth-sub { font-size: var(--t-lg); color: var(--ink-2); line-height: 1.55; margin: 6px 0 0; text-wrap: pretty; }
  .auth-fields { display: flex; flex-direction: column; margin-top: 20px; }
  .auth-field { display: flex; flex-direction: column; gap: 6px; }
  .auth-field label { font-size: var(--t-base); font-weight: 600; }
  .auth-field input {
    height: 42px; padding: 0 12px; border: 1px solid var(--border-control); border-radius: 9px;
    font-size: var(--t-lg); color: var(--ink); background: var(--card); outline: none;

    &::placeholder { color: var(--dim); }
    &:focus { border-color: var(--ink); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 9%, transparent); }
  }
  .auth-field.is-bad input { border-color: var(--err); box-shadow: 0 0 0 3px color-mix(in srgb, var(--err) 12%, transparent); }
  .auth-field-error { font-size: var(--t-base); color: var(--err-strong); }
  /* ⚠ Taller by HEIGHT, never by padding: a fixed-height .btn with padding inside it pushed
     its label out of the bottom when this was an <input type=submit>. */
  .auth-submit { width: 100%; height: 42px; margin-top: 16px; font-size: var(--t-lg); }
  .auth-flash {
    margin: 0 0 16px; padding: 9px 11px; border-radius: 9px; font-size: var(--t-base); line-height: 1.45;
    color: var(--err-strong); background: var(--err-tint); border: 1px solid color-mix(in srgb, var(--err-strong) 22%, transparent);
  }
  .auth-error { display: flex; align-items: flex-start; gap: 6px; margin: 8px 0 0; font-size: var(--t-base); line-height: 1.45; color: var(--err-strong); }
  .auth-error svg { flex: none; margin-top: 2px; }
  .auth-fields:not(.is-bad) .auth-error { display: none; }
  .auth-footer { display: flex; align-items: center; justify-content: center; gap: 4px; flex-wrap: wrap; margin: 0; font-size: var(--t-base); color: var(--soft); text-align: center; }
  .auth-card .auth-footer { margin-top: 16px; }
  .auth-footer form { display: inline; margin: 0; }

  /* Which address the code went to, and the way back to change it. */
  .auth-back {
    display: inline-flex; align-items: center; gap: 5px; max-width: 100%; height: 28px;
    padding: 0 10px 0 6px; margin: -4px 0 16px -4px; border: none; border-radius: var(--r-full);
    background: var(--sidebar); font: inherit; font-size: var(--t-base); font-weight: 500; color: var(--ink-2); cursor: pointer;

    & span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    &:hover { background: var(--border2); color: var(--ink); }
  }
  .auth-link {
    background: none; border: none; padding: 0; font: inherit; font-weight: 600; color: var(--ink);
    text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
  }

  /* The one-time code: a real input stretched invisibly over six painted slots. The slots
     take no pointer events, so a tap anywhere lands in the input — which is what makes the
     keyboard, paste and the phone's autofill behave as they do on any single field. */
  .otp { display: block; position: relative; }
  .otp-input {
    position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; margin: 0; padding: 0; border: 0;
    opacity: 0; cursor: text;
    font-size: 16px; /* ⚠ the iOS zoom threshold, not a style */
  }
  .otp-slots { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; pointer-events: none; }
  .otp-slot {
    height: 52px; display: flex; align-items: center; justify-content: center;
    font-family: var(--mono); font-size: 22px; font-weight: 600; color: var(--ink);
    background: var(--card); border: 1px solid var(--border-control); border-radius: 10px;
    transition: border-color 120ms, box-shadow 120ms;

    &.is-filled { background: var(--inset); }
  }
  .otp:focus-within .otp-slot.is-current { border-color: var(--ink); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 9%, transparent); }
  /* The caret, in the slot that is waiting for a digit. */
  .otp:focus-within .otp-slot.is-current:empty::after { content: ""; width: 1.5px; height: 22px; background: var(--ink); animation: otp-caret 1s steps(1) infinite; }
  .auth-fields.is-bad .otp-slot { border-color: var(--err); background: color-mix(in srgb, var(--err) 4%, var(--card)); }
  @keyframes otp-caret { 50% { opacity: 0; } }

  @media (max-width: 420px) {
    .auth-card { padding: 22px 18px; }
    .otp-slots { gap: 6px; }
    .otp-slot { height: 48px; font-size: 20px; }
  }

  /* Access gate — where a share link lands someone who can't open it yet. The .auth
     shell, centred: a tile saying which gate this is, a strip naming the work behind it,
     and one way in. Scoped under .gate so every rule outspecifies the login page's. */
  .gate .auth-card { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 12px; }
  .gate .auth-sub { margin: 0; line-height: 1.6; }
  .gate-tile {
    width: 44px; height: 44px; border-radius: 11px; flex-shrink: 0;
    background: var(--sidebar); color: var(--ink-2);
    display: flex; align-items: center; justify-content: center;
  }
  /* A dead link is the one gate with no way through, and it says so in red. */
  .gate-tile--dead { background: var(--err-tint); color: var(--err-strong); }
  .gate-strip {
    width: 100%; margin-top: 4px; padding: 10px 12px; text-align: left;
    background: var(--inset); border: 1px solid var(--border-divider); border-radius: var(--r-lg);
    display: flex; align-items: center; gap: 11px;
  }
  .gate-strip-text { min-width: 0; }
  .gate-set { font-size: var(--t-base); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .gate-meta { font-size: var(--t-sm); color: var(--soft); margin-top: 1px; }
  .gate-cta { margin-top: 6px; text-decoration: none; }
  .gate-note { font-size: var(--t-sm); color: var(--soft); margin-top: 2px; }
  .gate-signout { display: inline; }
  /* The agency's note to itself, above the card it is previewing. */
  .gate-preview {
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
    padding: 9px 12px; border-radius: var(--r-lg); font-size: var(--t-base); line-height: 1.45;
    background: var(--amber-tint); color: var(--amber-ink);
    border: 1px solid color-mix(in srgb, var(--amber-ink) 18%, transparent);
    & a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; white-space: nowrap; }
  }

  /* Creator credit pill (login only) */
  .credit-pill {
    display: inline-flex; align-items: center; gap: 8px; flex: none;
    height: 32px; padding: 0 12px 0 4px; margin-bottom: 24px;
    background: var(--card); border: 1px solid var(--border-row); border-radius: var(--r-full);
    font-size: var(--t-sm); font-weight: 500; color: var(--ink-2);
    text-decoration: none; white-space: nowrap;

    &:hover { color: var(--ink); border-color: var(--border-control); }
  }
  .credit-pill-avatar { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }

  /* ── Asset Approvals board ───────────────────────────
     Rows in cards, not a table: one card per client while the list is sorted by client
     (a client's own view: one per campaign), one card for any other sort. Built to
     explorations/opensource-direction/approvals.css — .bctl / .bg / .sr / .hidf. */
  /* A list page in the mockups' 1040px column. The cap is the page's GUTTER, not a
     max-width on its body, so the header band, the tabs and the body all move together. */
  .view.creative-approvals, .view--capped { --page-gutter: max(20px, (100% - var(--content-max)) / 2); }
  .apv-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 16px 0; }
  .apv-controls .seg-filter { flex: none; margin: 0; }
  .apv-controls .search-pill { flex: 1 1 120px; min-width: 0; width: auto; margin-left: auto; }
  .apv-controls .filter-menu--select { flex: 0 0 130px; }
  .apv-controls .filter-select { width: 100%; min-width: 0; }
  @media (min-width: 701px) { .apv-controls { flex-wrap: nowrap; } }

  .apv-list { display: flex; flex-direction: column; gap: 14px; }
  .apv-group { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  /* ⚠ Not clipped — a row's ⋯ menu drops out of the card. The open one is lifted over the
     cards below it. */
  .apv-group:has(.row-menu[open]) { position: relative; z-index: 5; }
  .apv-group-head { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid var(--border-divider); }
  .apv-group-head .client-chip { width: 30px; height: 30px; font-size: 10.5px; }
  .apv-group-head b { font-size: 13.5px; font-weight: 600; }
  .apv-group-head > span:last-child { font-size: var(--t-sm); color: var(--soft); }

  .apv-row {
    display: grid; grid-template-columns: minmax(0, 1fr) 200px 92px 28px; gap: 14px; align-items: center;
    padding: 11px 12px 11px 16px; border-top: 1px solid var(--border-row); cursor: pointer; outline: 0;
    transition: background 0.12s;
  }
  .apv-group > .apv-row:first-child, .apv-group-head + .apv-row { border-top: 0; }
  .apv-group > .apv-row:first-child { border-start-start-radius: 11px; border-start-end-radius: 11px; }
  .apv-row:has(+ .apv-edit:last-child), .apv-group > .apv-row:last-child { border-end-start-radius: 11px; border-end-end-radius: 11px; }
  .apv-row:hover, .apv-row:focus-visible { background: var(--inset); }
  /* Edit details: the frame under the row has its form in it, so the row steps aside and
     the form stands where it was. */
  .apv-row:has(+ .apv-edit > *) { display: none; }
  .apv-row-main { display: flex; align-items: center; gap: 12px; min-width: 0; }
  .apv-row-main .client-chip { width: 28px; height: 28px; }
  .apv-kind { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 30px; height: 30px; border-radius: 8px; background: var(--icon-tile); color: var(--ink-2); }
  .apv-row-text { min-width: 0; }
  .apv-row-text b, .apv-row-text small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .apv-row-text b { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
  .apv-row-text small { font-size: var(--t-sm); color: var(--soft); }
  .apv-row-text i { margin: 0 4px; font-style: normal; color: var(--dim); }
  /* The dot carries the status' own colour and the words stay ink — five tinted labels
     down a list is a rainbow; settled ones go quiet instead. */
  .apv-row .status-pill { gap: 7px; font-size: 12.5px; font-weight: 500; color: var(--ink); white-space: nowrap; }
  .apv-row .status-pill .status-dot { width: 7px; height: 7px; background: var(--sc); box-shadow: none; }
  .apv-row .status-pill.is-settled { color: var(--soft); }
  .apv-updated { font: 500 11.5px var(--mono); color: var(--soft); text-align: right; white-space: nowrap; }
  .apv-menu .row-menu-btn { width: 28px; height: 28px; border-radius: 7px; opacity: 0; transition: opacity 0.12s; }
  .apv-row:hover .row-menu-btn, .apv-menu[open] .row-menu-btn, .apv-menu .row-menu-btn:focus-visible { opacity: 1; }
  .apv-menu .row-menu-btn:hover { background: var(--icon-tile); }
  .apv-menu .row-menu-pop { top: calc(100% + 4px); min-width: 150px; padding: 4px; border-color: var(--border-card); border-radius: 10px; box-shadow: 0 12px 32px -10px oklch(0 0 0 / 0.22); }
  .apv-menu .row-menu-item { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 7px; font-size: 12.5px; font-weight: 400; color: var(--ink-2); }
  .apv-menu .row-menu-item:hover { background: var(--inset); color: var(--ink); }

  .apv-hidden { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: var(--inset); font-size: 12.5px; color: var(--soft); text-decoration: none; }
  .apv-hidden:hover { color: var(--ink); }
  .apv-hidden .status-dot { width: 7px; height: 7px; }
  .apv-hidden b { margin-left: auto; font-weight: 500; color: var(--accent); }

  /* The set form (New set above the board, Edit details in a row): campaign and title
     side by side, then the five kinds as tiles. */
  .inline-new--set .inline-new-body { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); }
  .inline-new-note { flex: 1; display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-sm); color: var(--soft); }
  .inline-new-note svg { flex: none; color: var(--dim); }
  .kind-tiles { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; }
  .kind-tiles label {
    position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    height: 66px; border: 1px solid var(--border-control); border-radius: 9px; background: var(--card);
    font-size: 12.5px; font-weight: 500; color: var(--ink-2); text-align: center; cursor: pointer;
    transition: border-color 0.12s, background 0.12s, color 0.12s;
  }
  .kind-tiles label:hover { border-color: var(--dim); color: var(--ink); }
  .kind-tiles input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
  .kind-tiles svg { color: var(--dim); transition: color 0.12s; }
  .kind-tiles label:has(input:checked) { border-color: var(--ink); background: var(--inset); color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
  .kind-tiles label:has(input:checked) svg { color: var(--ink); }
  .kind-tiles label:has(input:focus-visible) { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 15%, transparent); }
  /* In a row the form is not a card of its own — it is the row, opened. */
  .apv-edit .inline-new { margin: 0; border: 0; border-top: 1px solid var(--border-row); border-radius: 0; box-shadow: none; }
  .apv-group > .apv-row:first-child + .apv-edit .inline-new, .apv-group-head + .apv-row + .apv-edit .inline-new { border-top: 0; }
  .apv-group > .apv-row:first-child + .apv-edit .inline-new { border-radius: 11px 11px 0 0; }
  .apv-edit .inline-new-body { padding: 14px 16px; }
  .apv-edit .inline-new-foot { border-radius: 0; }
  .apv-edit:last-child .inline-new, .apv-edit:last-child .inline-new-foot { border-end-start-radius: 11px; border-end-end-radius: 11px; }

  @media (hover: none) { .apv-menu .row-menu-btn { opacity: 1; } }
  @media (max-width: 860px) {
    .apv-row { grid-template-columns: minmax(0, 1fr) auto 28px; }
    .apv-updated { display: none; }
  }
  @media (max-width: 700px) {
    .apv-controls .seg-filter { width: 100%; }
    .apv-controls .seg-filter .seg { flex: 1; justify-content: center; }
    .apv-controls .search-pill { flex: 1 1 100%; margin-left: 0; }
    .apv-controls .filter-menu--select { flex: 1 1 0; }
    /* The status drops under the title, in line with it (30px tile + 12px gap). */
    .apv-row { grid-template-columns: minmax(0, 1fr) 28px; row-gap: 6px; padding: 12px; }
    .apv-row .status-pill { grid-row: 2; grid-column: 1; padding-left: 42px; }
    .apv-menu { grid-row: 1 / 3; grid-column: 2; }
    .inline-new--set .inline-new-body { grid-template-columns: 1fr; padding: 14px; }
    .kind-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .kind-tiles label { height: 58px; }
    .inline-new--set .inline-new-foot { flex-wrap: wrap; }
    .inline-new-note { flex-basis: 100%; }
  }

  /* Creative-set page (agency management) */
  .creative-show { overflow-y: auto; }

  /* Portal-card creative footer band */
  /* ⚠ No negative margins any more: the card stopped padding its own body, so the band is
     simply the last of three bands rather than a strip clawing its way back out to the edge. */
  .creative-band {
    display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
    padding: var(--card-footer-pad);
    border-top: 1px solid var(--border-divider); background: var(--inset); border-radius: 0 0 var(--r-card) var(--r-card);
  }
  .creative-band-label {
    display: inline-flex; align-items: center; gap: 7px;
    padding-right: 16px; border-right: 1px solid var(--border2);
    color: var(--dim); font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  }
  .creative-band-label svg { color: var(--dim); }
  .creative-band-seg { display: inline-flex; align-items: center; gap: 7px; font-size: var(--t-sm); font-weight: 700; color: color-mix(in srgb, var(--sc) 84%, #241A12); }
  .creative-band-seg.is-muted { font-weight: 600; color: var(--soft); }
  .creative-band-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--sc); flex-shrink: 0; }

  /* Campaign-show "Creatives in production" card */
}

@layer components {
  /* ── Mobile (single breakpoint) ──────────────────────
     The sidebar goes off-canvas behind a CSS-only checkbox toggle (#nav_toggle
     in the layout) — sibling selectors only, no :has(), no JS. Turbo re-renders
     <body> on every visit, so the menu closes itself after navigating. */
  .nav-toggle-input, .bottom-bar, .menu-sheet { display: none; }

  @media (max-width: 700px) {
    /* .app is already a 100dvh column — the rail simply gives way to the bar below. */

    /* Bottom tab bar — a flex child pinned to the base of the column, so the .main
       above it simply shrinks (no fixed-overlap padding needed). */
    .bottom-bar {
      display: flex; flex-shrink: 0; position: relative;
      height: calc(56px + env(safe-area-inset-bottom)); padding-bottom: env(safe-area-inset-bottom);
      background: oklch(1 0 0 / 0.92);
      backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
      border-top: 1px solid var(--border-card);
    }
    /* flex: 1 rather than fixed widths: the two audiences carry their tabs differently
       (three and a Menu, or four), and equal division gets both right without the bar
       having to know which it is showing. */
    .tab {
      display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; flex: 1;
      text-decoration: none; color: var(--soft); background: none; border: none; cursor: pointer;
    }
    .tab-icon { position: relative; display: flex; }
    .tab-label { font-size: var(--t-xs); font-weight: 500; }
    /* Ink, and nothing else: the bar is structure, like the rail. */
    .tab.is-active { color: var(--ink); }

    .count-badge--tab {
      position: absolute; top: -7px; left: calc(50% + 4px); margin: 0;
      min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
      background: var(--ink); color: var(--text-on-inverse);
      font-size: 10px; font-weight: 600; line-height: 16px; text-align: center;
    }

    /* The desktop rail is replaced by the bottom bar + Menu sheet on mobile. */
    .rail { display: none; }

    /* The top bar stays — it is the app's frame, not a desktop affordance — but the
       account chip drops to its avatar: a 375px bar has no room for a name beside it. */
    .topbar { padding: 0 12px; }
    .account-name { display: none; }
    .account-chip { padding: 0 6px 0 4px; gap: 4px; }

    /* The Queue, floating above the bar's right end (agency only). Labelled: a bare
       coloured disc makes the reader guess what the icon meant. */
    .nav-pill {
      position: absolute; right: 14px; bottom: calc(100% + 12px); z-index: 5;
      display: flex; align-items: center; gap: 7px;
      height: 40px; padding: 0 14px 0 12px;
      border-radius: var(--r-full);
      background: var(--ink); color: var(--text-on-inverse);
      box-shadow: 0 6px 18px oklch(0 0 0 / 0.22), inset 0 1px 0 oklch(1 0 0 / 0.12);
      font-size: var(--t-base); font-weight: 600;

      &:hover { background: color-mix(in srgb, var(--ink) 90%, white); color: var(--text-on-inverse); }
    }
    .count-badge--pill {
      margin: 0; padding: 3px 6px; border-radius: var(--r-full);
      background: oklch(1 0 0 / 0.18); color: var(--text-on-inverse); font-weight: 600;
    }

    /* Full-screen Menu sheet, toggled by #nav_toggle. Agency only — a client's four tabs
       are their whole nav. */
    .nav-toggle-input:checked ~ .menu-sheet { display: flex; }
    .menu-sheet {
      position: fixed; inset: 0; z-index: 70; flex-direction: column; gap: 14px;
      padding: 14px 14px calc(14px + env(safe-area-inset-bottom)); overflow: auto;
      background: var(--page); animation: fadeIn 0.15s ease;
    }
    .menu-head { display: flex; flex-direction: column; gap: 14px; }
    .menu-head-row { display: flex; align-items: center; justify-content: space-between; }
    .menu-title { font-size: 17px; font-weight: 600; }
    .menu-close {
      width: 32px; height: 32px; flex-shrink: 0;
      border: 1px solid var(--border-control); border-radius: var(--r-md);
      background: var(--card); color: var(--ink-2);
      display: flex; align-items: center; justify-content: center; font-size: 18px; line-height: 1; cursor: pointer;
    }
    .menu-sheet .workspace { min-height: 52px; }

    .menu-groups { display: flex; flex-direction: column; gap: 14px; }
    .menu-group-label { font-size: var(--t-xs); font-weight: 600; color: var(--dim); padding: 0 4px 6px; }
    .menu-card {
      overflow: hidden; background: var(--card);
      border: 1px solid var(--border-card); border-radius: var(--r-lg);
    }
    .menu-item {
      display: flex; align-items: center; gap: 12px; padding: 8px 12px; min-height: 46px;
      border-top: 1px solid var(--border-row); color: var(--ink); text-decoration: none;

      &:first-child { border-top: 0; }
      & > svg:first-child { flex-shrink: 0; color: var(--soft); }
      &.is-active { background: var(--inset); }
    }
    /* One baseline, not a stack: label and subtitle read as a single line. */
    .menu-text { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
    .menu-label { font-size: var(--t-base); font-weight: 500; }
    .menu-sub { font-size: var(--t-sm); color: var(--soft); }
    .menu-chevron { color: var(--dim); flex-shrink: 0; }

    /* Two-column layouts stack; fixed desktop paddings shrink.
       (The campaign show page stacks via a @container query — see below.) */
    /* The label stacks above its field, and the status slot keeps the row's end. */
    .profile-row { grid-template-columns: minmax(0, 1fr) auto; row-gap: 0; padding: 10px 12px 8px 16px; }
    .profile-key { grid-column: 1 / -1; }
    .profile-split { flex-wrap: wrap; }
    .profile-split > .btn { width: 100%; height: 36px; }
    /* A phone tightens the PAGE's gutter, once — so the header and every body tighten
       together and cannot drift apart. Three separate bodies used to say 16px for themselves
       while the header said 24, which is exactly the mismatch this replaces.
       ⚠ :not(.clipper-page), and specificity is NOT what saves it — this rule is in
       @layer components and the Clipper's opt-out is in @layer layout, and a later layer
       beats any specificity. The exclusion has to be written, not assumed. */
    .view:not(.clipper-page) { --page-gutter: 16px; }
    /* Wrap the actions under the title on narrow screens. */
    .tool-header { flex-wrap: wrap; gap: 10px; }

    /* iOS Safari zooms into any focused text field below 16px — the !important
       is needed to beat the drawer's class-level 11–13px sizes here only.
       ⚠ 16px is a threshold, not a style: it must stay a literal. Routed through a
       type token, the next person to rebalance the scale silently reintroduces
       zoom-on-focus for every input on a phone. */
    input:not([type="checkbox"], [type="radio"]), select, textarea { font-size: 16px !important; }

    /* Above the bottom bar, not behind it. */
    #toasts { bottom: calc(72px + env(safe-area-inset-bottom)); }
    /* ...and above the Queue pill where there is one, rather than across it. */
    .app:has(.nav-pill) #toasts { bottom: calc(120px + env(safe-area-inset-bottom)); }

    /* The Queue pill floats over the foot of the page, so every scroller ends with room
       for it — otherwise the last row of a list sits underneath, unreachable.
       A spacer rather than padding: each of these already sets its own. */
    .app:has(.nav-pill) .main :is(.campaigns-body, .integrations-body, .profile, .sg-body,
                                   .view:not(.view--column))::after {
      content: ""; display: block; height: 64px; flex: none;
    }

    /* A menu is a bottom sheet on a phone: the same <details>, the same markup, the same
       controller — only where the panel sits. The scrim is the <details>' own ::before, so
       a tap on it is a tap on the element itself, which menu_controller reads as "outside".
       ⚠ Not inside .campaign-show or .client-detail: both are container-query roots, and
       containment makes them the containing block for `position: fixed` — the sheet would
       be pinned to the scrolled page instead of the screen. Those two pages place their own
       menus when they are rebuilt. (The top bar would have been a third — backdrop-filter
       does the same — which is why its glass is a ::before and not its own background.) */
    .user-menu[open]::before,
    .view:not(.campaign-show, .client-detail) :is(.filter-menu, .status-menu)[open]::before {
      content: ""; position: fixed; inset: 0; z-index: 79; background: oklch(0 0 0 / 0.32);
    }
    .user-options,
    .view:not(.campaign-show, .client-detail) :is(.filter-options, .row-menu-pop, .share-pop, .status-options) {
      position: fixed; inset: auto 0 0 0; z-index: 80;
      width: auto; min-width: 0; max-width: none; max-height: 70dvh; overflow-y: auto; margin: 0;
      padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
      border-width: 1px 0 0; border-radius: var(--r-xl) var(--r-xl) 0 0;
      box-shadow: 0 -12px 40px oklch(0 0 0 / 0.2);
    }
  }

  /* Campaign show stacks on its own content width (sidebar-agnostic), so it
     folds on a narrow laptop window too — not just at the mobile breakpoint. */
  @container (max-width: 860px) {
    .cd-split { grid-template-columns: minmax(0, 1fr); }
    .campaign-show-body { padding: 14px 16px 32px; }
    .campaign-show-head > .campaign-width { padding: 0 16px; }
    .metric-strip-cell { min-width: 128px; }
  }

  /* Two cells per row. ⚠ The dividers have to be re-cut by hand: `border-left` on every cell
     but the first draws one down the strip's own left edge as soon as the row wraps, and CSS
     cannot ask whether a flex item starts a line. Exactly two fit here, so nth-child can. */
  @container (max-width: 560px) {
    .metric-strip-cell { flex: 1 1 calc(50% - 1px); min-width: 0; }
    .metric-strip-cell:nth-child(odd) { border-left: none; }
    .metric-strip-cell:nth-child(n+3) { border-top: 1px solid var(--surface-inverse-hover); }
    .metric-strip-edit {
      flex: 1 1 100%; justify-content: flex-end; padding: 8px 16px;
      border-left: none; border-top: 1px solid var(--surface-inverse-hover);
    }
  }

  /* Styled confirmation dialog (see application.js — every data-turbo-confirm) */
  .confirm-dialog {
    border: none; border-radius: var(--r-card); padding: 0; max-width: 380px; width: calc(100% - 40px);
    background: var(--card); color: var(--ink); box-shadow: var(--shadow-lg);
  }
  .confirm-dialog::backdrop { background: oklch(0.2 0 0 / 0.35); }
  .confirm-card { margin: 0; padding: 22px 24px; display: flex; flex-direction: column; gap: 8px; }
  .confirm-title { font-size: var(--t-lg); font-weight: 700; margin: 0; letter-spacing: -0.01em; }
  .confirm-message { font-size: var(--t-base); color: var(--soft); line-height: 1.55; margin: 0 0 8px; }
  .confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
}

/* ── Performance reporting ───────────────────────────── */
@layer components {
  .budget-bar:has(> span) { background: var(--hairline); }
  .budget-bar > span { display: block; height: 100%; background: var(--accent); border-radius: var(--r-full); }
  .budget-bar.is-over > span { background: var(--err); }

  .perf-card { display: block; }
  .perf-head-actions { display: flex; align-items: center; gap: var(--control-gap); flex-wrap: wrap; }

  .perf-metrics { position: relative; }
  .perf-metrics > summary { list-style: none; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
  .perf-metrics > summary::-webkit-details-marker { display: none; }

  .perf-kpi-label { font-size: var(--t-sm); letter-spacing: 0.02em; text-transform: uppercase; color: var(--soft); font-weight: 600; }
  .perf-kpi-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 4px; }
  .perf-kpi-value {
    font-family: var(--mono); font-size: 28px; font-weight: 600; line-height: 1.05;
    color: var(--ink); font-variant-numeric: tabular-nums;
  }
  .perf-kpi-caption { font-family: var(--mono); font-size: var(--t-sm); color: var(--soft); margin-top: 5px; }
  .perf-delta {
    display: inline-flex; align-items: center; font-size: var(--t-sm); font-weight: 600;
    padding: 2px 8px; border-radius: var(--r); background: var(--inset); color: var(--soft);
  }
  .perf-delta-good { background: color-mix(in srgb, var(--ok) 12%, transparent); color: var(--ok); }
  .perf-delta-bad  { background: color-mix(in srgb, var(--err) 12%, transparent); color: var(--err); }

  .perf-stepper { display: flex; align-items: center; gap: 6px; }
  .perf-stepper-label { font-size: var(--t-base); font-weight: 600; color: var(--ink); min-width: 96px; text-align: center; }
  .perf-step { font-size: var(--t-lg); line-height: 1; text-decoration: none; }
  .perf-step.is-off { opacity: 0.35; pointer-events: none; }

  /* The chart is stretched to the card's width, so nothing inside the SVG may be a glyph and
     every stroke is non-scaling. The axis labels are HTML over the same box, lined up with the
     plot through --plot-l / --plot-r, which the server sets from the chart's own padding. */
  .perf-chart-block { margin: 8px 0 4px; }
  .perf-chart-wrap { position: relative; }
  .perf-chart { display: block; width: 100%; height: 170px; }
  .perf-line { stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
  .perf-grid { stroke: var(--border-row); stroke-width: 1; vector-effect: non-scaling-stroke; }
  /* The best day of the window, not the biggest number — see best_index. */
  .perf-peak { stroke: var(--badge-amber); stroke-width: 1; stroke-dasharray: 2 3; vector-effect: non-scaling-stroke; }
  .perf-peak-dot { fill: var(--badge-amber); }

  .perf-axis-y {
    position: absolute; left: 0; width: var(--plot-l); padding-right: 6px; text-align: right; white-space: nowrap;
    transform: translateY(-50%); pointer-events: none;
    font-family: var(--mono); font-size: var(--t-xs); color: var(--soft);
  }
  .perf-axis-x {
    display: flex; justify-content: space-between; margin-top: 2px;
    padding-left: var(--plot-l); padding-right: var(--plot-r);
    font-family: var(--mono); font-size: var(--t-xs); color: var(--soft);
  }

  /* One state class drives the whole readout, so hover is on or off rather than four opacities
     drifting apart. */
  .perf-guide, .perf-dots, .perf-tip { opacity: 0; pointer-events: none; transition: opacity 0.1s; }
  .perf-chart-wrap.is-hovered :is(.perf-guide, .perf-dots, .perf-tip) { opacity: 1; }
  .perf-guide {
    position: absolute; top: 12px; bottom: 22px; width: 0;
    border-left: 1px dashed var(--border-control); transform: translateX(-0.5px);
  }
  .perf-dots { position: absolute; inset: 0; }
  .perf-dot {
    position: absolute; width: 7px; height: 7px; border-radius: 50%;
    background: var(--card); border: 2px solid var(--mc); transform: translate(-50%, -50%);
  }
  .perf-tip {
    position: absolute; top: 0; transform: translateX(-50%); z-index: 2; white-space: nowrap;
    background: var(--ink); color: var(--text-on-inverse);
    border-radius: var(--r); padding: 7px 9px; font-size: var(--t-sm); box-shadow: var(--shadow-md);
  }
  .perf-tip-date { font-family: var(--mono); font-size: var(--t-xs); color: var(--text-on-inverse-icon); margin-bottom: 2px; }
  .perf-tip-row { display: flex; align-items: center; gap: 7px; margin-top: 3px; color: var(--text-on-inverse-label); }
  .perf-tip-row b { margin-left: auto; padding-left: 12px; font-family: var(--mono); color: var(--text-on-inverse); }
  .perf-tip-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--mc); flex-shrink: 0; }

  .perf-legend { display: flex; flex-wrap: wrap; gap: 16px; margin: 6px 0 14px; }
  .perf-legend-item { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-base); color: var(--soft); }
  .perf-legend-item b { color: var(--ink); }
  .perf-legend-dot { width: 8px; height: 8px; border-radius: 50%; }

  /* ── The agency's data tools ── one row of quiet toggles under the chart, each a <details>
     that opens a panel beneath (campaign.css .tools / .tg / .tpi). The three share
     name="perf-tools", so the browser keeps one open; the open one takes the whole row. */
  .perf-tools { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border-row); }
  .perf-tool[open] { flex: 0 0 100%; }
  .perf-tool-toggle {
    display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; list-style: none; cursor: pointer;
    border: 1px solid transparent; border-radius: 7px; background: var(--icon-tile);
    font-size: var(--t-sm); font-weight: 500; color: var(--ink-2);
    &::-webkit-details-marker { display: none; }
    &:hover { color: var(--ink); }
    & svg { color: var(--dim); transition: transform 0.18s; }
  }
  .perf-tool[open] > .perf-tool-toggle { background: var(--card); border-color: var(--border-control); color: var(--ink); & svg { transform: rotate(180deg); } }
  .perf-tool-panel { margin: 12px 0 0; padding: 12px; border: 1px solid var(--border-divider); border-radius: 10px; background: var(--inset); }

  .perf-adsets-list { margin: 0; }
  .perf-adset { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px solid var(--border-row); font-size: 12.5px; cursor: pointer; }
  .perf-adset:first-child { border-top: 0; }
  .perf-adset-check { margin: 0; accent-color: var(--ink); }
  .perf-adset-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; }
  .perf-adset-name { flex: 1; min-width: 0; font: 12px var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .perf-adset-dates { font-size: var(--t-sm); color: var(--soft); white-space: nowrap; }

  .perf-adsets-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--border-row); }
  .perf-adsets-hint-line { flex: 1; min-width: 120px; font-size: var(--t-sm); color: var(--soft); line-height: 1.5; }
  .perf-adsets-actions { display: flex; align-items: center; gap: 7px; flex-shrink: 0; }
  .perf-add-form { margin: 0; }
  .perf-add-btn { cursor: pointer; }
  .perf-remove { display: none; color: var(--err); border-color: color-mix(in srgb, var(--err) 30%, var(--card)); }
  .perf-adsets-body:has(.perf-adset-check:checked) .perf-adsets-hint-line { display: none; }
  .perf-adsets-body:has(.perf-adset-check:checked) .perf-remove { display: inline-flex; }

  .perf-foot-note { font-size: var(--t-sm); color: var(--soft); }
  .perf-foot-actions { display: flex; align-items: center; gap: 7px; }

  .perf-import { padding: 16px; }
  .perf-import-compact { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
  .perf-drop {
    display: flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer;
    border: 1px dashed var(--border-control); border-radius: var(--r-md); padding: 26px 24px;
    background: var(--inset); color: var(--soft); font-size: var(--t-base); text-align: center;
  }
  .perf-import-compact .perf-drop { flex: 1; padding: 8px 14px; }
  .perf-drop:hover { border-color: var(--accent); }
  .perf-drop.is-over { border-color: var(--accent); background: oklch(0.49 0.18 270 / 0.07); }
  .perf-drop-input { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
  .perf-import-note { font-size: var(--t-sm); color: var(--dim); line-height: 1.5; margin: 8px 2px 0; }
  .perf-appt-form { display: flex; flex-direction: column; gap: 8px; }
  .perf-appt-input, .perf-calc-input, .perf-calc-unit {
    min-width: 0; border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);
    font: inherit; font-size: var(--t-base); color: var(--ink); outline: 0;
    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .perf-appt-input { width: 100%; padding: 8px 10px; line-height: 1.5; resize: vertical; }
  .perf-appt-form .btn { align-self: flex-start; }
  /* The empty state has no row of tools, so there the one toggle keeps its own line. */
  .perf-import .perf-tool { width: 100%; margin-top: 10px; }

  .perf-calc-existing { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; }
  .perf-calc-existing + .perf-calc-existing, .perf-calc-existing + .perf-calc-add { border-top: 1px solid var(--border-row); padding-top: 10px; margin-top: 4px; }
  .perf-calc-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; margin-top: 12px; }
  .perf-calc-form { flex: 1; min-width: 0; margin: 0; }
  .perf-calc-fields { display: flex; flex-direction: column; gap: 8px; }
  .perf-calc-row { display: flex; align-items: center; gap: 8px; }
  .perf-calc-existing > form:last-child { margin: 0; }
  .perf-calc-remove { height: 32px; padding: 0 6px; border: 0; background: none; cursor: pointer; font: inherit; font-size: var(--t-sm); color: var(--soft); white-space: nowrap; &:hover { color: var(--err); } }
  .perf-calc-chips { display: flex; flex-wrap: wrap; gap: 4px; }
  .perf-calc-chip {
    height: 22px; padding: 0 7px; border: 1px solid var(--border-control); border-radius: 5px; background: var(--card); cursor: pointer;
    font: 500 11px var(--mono); color: var(--ink-2);
    &:hover { border-color: var(--dim); color: var(--ink); }
  }
  .perf-calc-op { font-weight: 600; color: var(--ink); background: var(--icon-tile); border-color: transparent; }
  .perf-calc-input { flex: 1; height: 32px; padding: 0 10px; }
  /* ⚠ The name field is a child of a COLUMN flex box: there `flex: 1` is about height,
     and it squashed the field to its border. Only the formula field, in a row, should grow. */
  .perf-calc-fields > .perf-calc-input { flex: none; }
  .perf-calc-mono { font-family: var(--mono); font-size: var(--t-sm); }
  .perf-calc-unit { height: 32px; padding: 0 6px; }
  .perf-calc-hint { margin: 10px 0 0; font-size: var(--t-sm); line-height: 1.5; color: var(--soft); }
  .perf-calc-hint code { font: 500 11.5px var(--mono); background: var(--card); border-radius: 4px; padding: 1px 5px; color: var(--ink); }

  /* Both perf pickers (metrics + summary-stats) share ONE popover treatment */
  .metrics-picker, .stat-picker-menu {
    position: absolute; right: 0; z-index: 6; margin: 0; padding: 0; background: var(--card);
    border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-md);
  }
  .metrics-picker { top: calc(100% + 6px); width: 290px; }
  /* ⚠ Opens DOWNWARD. It used to open upward because the picker sat at the bottom of the
     performance card; it now sits in the key-metrics strip at the top of the page. */
  .stat-picker-menu { top: calc(100% + 6px); width: 250px; }
  .metrics-picker-head, .stat-picker-head { font-size: var(--t-base); font-weight: 700; letter-spacing: -0.01em; padding: 11px 14px 0; }
  .metrics-picker-hint, .stat-picker-hint {
    font-size: var(--t-xs); color: var(--dim); line-height: 1.45; margin: 3px 0 0; padding: 0 14px 9px;
    border-bottom: 1px solid var(--hairline);
  }
  .metrics-picker-list, .stat-picker-list { max-height: 284px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; padding: 5px; }
  .metrics-picker-row, .stat-pick-row { padding: 6px 7px; border-radius: 7px; }
  .metrics-picker-row:hover, .stat-pick-row:hover { background: var(--inset); }
  .metrics-picker-foot, .stat-picker-foot { display: flex; justify-content: flex-end; padding: 8px 12px 12px; margin: 0; }

  /* metrics-picker-only bits */
  .metrics-picker-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 8px; }
  .metrics-check {
    appearance: none; width: 17px; height: 17px; border-radius: 5px; border: 1.5px solid var(--border);
    background: var(--card); cursor: pointer; flex-shrink: 0;
  }
  .metrics-check:checked { background: var(--accent); border-color: var(--accent); }
  .metrics-rename { border: 1px solid transparent; background: transparent; font-size: var(--t-base); padding: 4px 6px; border-radius: var(--r-sm); color: var(--ink); }
  .metrics-rename:hover, .metrics-rename:focus { border-color: var(--border2); background: var(--inset); outline: none; }
  .metrics-star { cursor: pointer; color: var(--dim); font-size: var(--t-lg); line-height: 1; }
  .metrics-star.is-on { color: var(--accent); }
  .metrics-star-input { position: absolute; opacity: 0; width: 0; height: 0; }
  .metrics-name { font-size: var(--t-base); color: var(--ink); cursor: pointer; }

  /* Summary-stats picker — travelled with the stats into the key-metrics strip. */
  .stat-picker { position: relative; }
  .stat-picker > summary { list-style: none; }
  .stat-picker > summary::-webkit-details-marker { display: none; }
  .stat-picker-trigger {
    display: inline-flex; align-items: center; gap: 6px; cursor: pointer; height: 26px;
    font-size: var(--t-sm); font-weight: 500; color: var(--soft);
    border: 1px solid var(--border-control); border-radius: var(--r); padding: 0 9px; background: var(--card);
  }
  .stat-picker-trigger:hover { border-color: var(--accent-border); color: var(--ink); }
  .stat-pick-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 9px; cursor: pointer; }
  .stat-pick-input { position: absolute; opacity: 0; width: 0; height: 0; }
  .stat-pick-box {
    width: 17px; height: 17px; border-radius: 5px; border: 1.5px solid var(--border);
    display: inline-flex; align-items: center; justify-content: center; color: transparent;
  }
  .stat-pick-input:checked ~ .stat-pick-box { background: var(--mc); border-color: var(--mc); color: #fff; }
  .stat-pick-name { font-size: var(--t-base); color: var(--ink); }
  .stat-pick-badge:not(:empty) {
    min-width: 18px; height: 18px; border-radius: 5px; background: var(--mc); color: #fff;
    font-size: var(--t-sm); font-weight: 700; display: inline-flex; align-items: center; justify-content: center; padding: 0 5px;
  }
  .stat-picker.is-full .stat-pick-row:not(:has(.stat-pick-input:checked)) { opacity: 0.4; cursor: not-allowed; }
  .stat-picker.is-full .stat-pick-row:not(:has(.stat-pick-input:checked)):hover { background: transparent; }

  /* The two pickers, to the design system's popover: a titled head with one line of help,
     a tight list, and a subdued foot holding Apply. */
  .metrics-picker, .stat-picker-menu { padding: 0; border-color: var(--border-card); border-radius: 10px; box-shadow: 0 14px 34px -10px oklch(0 0 0 / 0.25); }
  .metrics-picker { width: 300px; }
  .metrics-picker-head, .stat-picker-head { padding: 10px 12px 0; font-size: 12.5px; font-weight: 600; letter-spacing: 0; }
  .metrics-picker-hint, .stat-picker-hint { margin: 2px 0 0; padding: 0 12px 8px; border-bottom: 1px solid var(--border-divider); font-size: 11.5px; line-height: 1.45; color: var(--soft); }
  .metrics-picker-list, .stat-picker-list { gap: 0; padding: 4px; }
  .metrics-picker-row, .stat-pick-row { padding: 4px 8px; border-radius: 7px; }
  .metrics-check { accent-color: var(--ink); }
  .metrics-check:checked { background: var(--ink); border-color: var(--ink); }
  .metrics-rename {
    min-width: 0; height: 28px; padding: 0 6px; border: 1px solid transparent; border-radius: 6px; background: transparent;
    font: inherit; font-size: 12.5px; color: var(--ink);

    &::placeholder { color: var(--ink); }
    &:hover { border-color: var(--border-row); background: var(--card); }
    &:focus { outline: 0; border-color: var(--ink); background: var(--card); }
    &:focus::placeholder { color: var(--dim); }
  }
  .metrics-name, .stat-pick-name { font-size: 12.5px; }
  /* The headline star: a quiet glyph until it is the one, then amber. Read off the radio. */
  .metrics-star {
    position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 26px; height: 26px; border-radius: 6px; font-size: var(--t-lg); line-height: 1; color: var(--border-control); cursor: pointer;

    &:hover { background: var(--sidebar); color: var(--soft); }
    &:has(.metrics-star-input:checked), &.is-on { color: #D97706; background: #FEF3E2; }
    &:has(.metrics-star-input:focus-visible) { box-shadow: var(--ring); }
  }
  .metrics-star-input { position: absolute; inset: 0; width: auto; height: auto; opacity: 0; margin: 0; cursor: pointer; }
  .metrics-picker-foot, .stat-picker-foot { padding: 8px 10px; border-top: 1px solid var(--border-divider); background: var(--inset); border-radius: 0 0 10px 10px; }
  @media (max-width: 700px) {
    .metrics-picker { width: min(300px, calc(100vw - 40px)); right: 0; left: auto; }
  }
}

/* ── KPI sparklines (dashboard + portal) ─────────────── */
@layer components {
  .kpi-trend-cell { display: inline-flex; align-items: center; gap: 8px; }
  .spark { width: 60px; height: 20px; display: block; }
  .spark-delta { font-size: var(--t-sm); font-weight: 600; }
  .spark-good { color: var(--ok); }
  .spark-bad  { color: var(--err); }
  .spark-flat { color: var(--dim); }
  td.kpi-trend { white-space: nowrap; }
}

/* ── Creative Set page (assets, add flow, review, decision) ─────────── */
@layer components {
  /* Share popover — the .filter-menu <details> shell with the .filter-options panel
     (see there) re-anchored right, since it hangs off a control in the set header. */
  .share-pop { right: 0; width: 372px; max-width: calc(100vw - 32px); padding: 15px; gap: 12px; text-align: left; }
  .share-btn { display: inline-flex; align-items: center; gap: 7px; }
  .share-title { font-size: var(--t-base); font-weight: 700; letter-spacing: -0.01em; }
  .share-sub { font-size: var(--t-sm); color: var(--soft); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .share-row { display: flex; align-items: center; gap: 8px; }
  .share-url {
    flex: 1; min-width: 0; padding: 8px 10px;
    background: var(--inset); border: 1px solid var(--border); border-radius: var(--r-md);
    font-family: var(--mono); font-size: var(--t-sm); color: var(--soft);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* Below the app's mobile breakpoint the set header wraps, so the Share button lands on
     the left and a right-anchored panel hangs 200px off the edge. Pin it to the viewport
     instead, clear of the bottom bar — no header-height guess to go stale. */
  @media (max-width: 700px) {
    .share-pop { position: fixed; inset: auto 16px 80px; width: auto; max-width: none; }
    /* Icon only. The word costs 47px, which is exactly what pushed the pre-existing
       "All sets" link off a 375px screen — the summary's aria-label still names it. */
    .share-label { display: none; }
  }
  .share-copy { flex-shrink: 0; padding: 9px 14px; border-radius: var(--r-md); }
  .share-copy.is-copied { background: var(--ok); }
  .share-note { display: flex; align-items: flex-start; gap: 8px; margin: 0; font-size: var(--t-sm); color: var(--soft); line-height: 1.5; }
  .share-note svg { flex-shrink: 0; margin-top: 2px; color: var(--dim); }
  .share-preview { font-weight: 600; color: var(--accent); text-decoration: none; white-space: nowrap; }
  .share-preview:hover { text-decoration: underline; }

  /* ── The set page (agency) ───────────────────────────
     One column: the way back, whose it is and what state it's in, then the assets, with
     the next step pinned to the foot. Built to approvals.css — .sw / .shd / .bn / .cp /
     .ig / .lk2 / .sbar. The client's reading of the same set is "Client review", below. */
  .cs-page { width: 100%; max-width: calc(960px + 2 * var(--page-gutter)); margin: 0 auto; padding: 18px var(--page-gutter) 84px; display: flex; flex-direction: column; gap: 16px; }
  .creative-show { --page-gutter: 24px; }
  .cs-page:has(> .cs-review) { max-width: calc(832px + 2 * var(--page-gutter)); }
  .cs-crumb { align-self: flex-start; }
  .cs-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: -6px; }
  .cs-head > .client-chip { width: 36px; height: 36px; border-radius: 9px; font-size: var(--t-xs); text-decoration: none; }
  .cs-head-title { flex: 1; min-width: 0; }
  .cs-head-campaign { font-size: 12.5px; font-weight: 500; color: var(--soft); text-decoration: none; }
  .cs-head-campaign:hover { color: var(--accent); }
  /* ⚠ margin: 0 — the browser's default h1 margin is 0.67em, which pushes the campaign
     line off its own title. */
  .cs-head h1 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; }
  .cs-head-actions { display: flex; align-items: center; gap: 6px; }

  /* The status, as a control: a bordered pill that opens the lifecycle. */
  .cs-head-actions .status-menu > summary {
    display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px 0 12px; white-space: nowrap;
    border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);
    font-size: 12.5px; font-weight: 500; color: var(--ink); transition: border-color 0.12s, box-shadow 0.12s;
  }
  .cs-head-actions .status-menu > summary:hover { border-color: var(--dim); }
  .cs-head-actions .status-menu[open] > summary { border-color: var(--ink); box-shadow: var(--ring); }
  .cs-head-actions .status-dot { width: 8px; height: 8px; background: var(--sc); box-shadow: none; }
  .cs-head-actions .status-caret { width: 13px; height: 13px; margin-left: auto; opacity: 1; color: var(--dim); transition: transform 0.18s; }
  .cs-head-actions .status-menu[open] .status-caret { transform: rotate(180deg); }
  .cs-head-actions .status-options { left: auto; right: 0; top: calc(100% + 6px); margin: 0; width: 248px; padding: 4px; border-color: var(--border-card); border-radius: 10px; box-shadow: 0 12px 32px -10px oklch(0 0 0 / 0.22); }
  .cs-head-actions .status-option { display: grid; grid-template-columns: 8px minmax(0, 1fr) 14px; gap: 10px; padding: 8px 10px; border-radius: 7px; font-size: 12.5px; font-weight: 400; }
  .cs-head-actions .status-option:hover, .cs-head-actions .status-option:focus-visible { background: var(--inset); outline: 0; }
  .cs-head-actions .status-option.is-current { font-weight: 600; }
  .cs-head-actions .status-check { color: var(--ink); font-weight: 400; }

  /* Banners: what the client said, and what their sign-off locked. */
  .cs-banner { display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 12px 14px; border: 1px solid var(--border-card); border-radius: var(--r-card); background: var(--card); }
  .cs-banner-icon { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: var(--r-md); }
  .cs-banner b { display: block; font-size: var(--t-base); font-weight: 600; }
  .cs-banner p { margin: 2px 0 0; font-size: 12.5px; color: var(--ink-2); line-height: 1.5; text-wrap: pretty; }
  .cs-banner q { display: block; margin-top: 4px; font-size: 13.5px; color: var(--ink); white-space: pre-line; quotes: "\201C" "\201D"; }
  .cs-banner--feedback { background: color-mix(in srgb, var(--err-tint) 40%, var(--card)); border-color: color-mix(in srgb, var(--err-strong) 22%, var(--card)); }
  .cs-banner--feedback .cs-banner-icon { background: var(--err-tint); color: var(--err-strong); }
  .cs-banner--locked { background: color-mix(in srgb, var(--ok) 6%, var(--card)); border-color: color-mix(in srgb, var(--ok) 24%, var(--card)); }
  .cs-banner--locked .cs-banner-icon { background: var(--ok-tint); color: var(--ok-strong); }
  .cs-banner--locked .btn { border-color: color-mix(in srgb, var(--ok) 40%, var(--card)); color: var(--ok-strong); }
  .cs-banner--editing { grid-template-columns: minmax(0, 1fr) auto; padding: 9px 12px; background: color-mix(in srgb, var(--badge-amber) 7%, var(--card)); border-color: color-mix(in srgb, var(--badge-amber) 28%, var(--card)); }
  .cs-banner--editing p { margin: 0; color: var(--amber-ink); }

  /* Assets header */
  .cs-assets-head { display: flex; align-items: center; gap: 10px; margin-top: 4px; }
  .cs-assets-head h2 { margin: 0; font-size: 15px; font-weight: 600; }
  .cs-assets-head .btn { margin-left: auto; }
  .cs-assets { display: flex; flex-direction: column; gap: 16px; }
  .cs-count { font-size: var(--t-sm); font-weight: 600; color: var(--dim); }
  .cs-hint { font-size: var(--t-sm); color: var(--dim); margin: 3px 0 0; line-height: 1.4; }
  .cs-section-head { display: flex; align-items: baseline; gap: 9px; }

  /* Add panel */
  .cs-add-panel { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); }
  .cs-add-title { font-size: 13.5px; font-weight: 600; }
  .cs-add-form { display: flex; flex-direction: column; gap: 10px; }
  .cs-add-hint { margin: 0; max-width: 70ch; font-size: 12.5px; color: var(--soft); line-height: 1.5; }
  .cs-add-hint code { font: 500 11.5px var(--mono); background: var(--icon-tile); border-radius: 4px; padding: 1px 5px; color: var(--ink); }
  .cs-add-panel .cs-field { display: flex; flex-direction: column; gap: 4px; width: 220px; }
  .cs-add-panel .cs-field > span { font-size: var(--t-xs); font-weight: 600; color: var(--soft); }
  .cs-add-select, .cs-num { height: 30px; min-width: 0; padding: 0 9px; border: 1px solid var(--border-control); border-radius: 7px; background: var(--card); font: inherit; font-size: 12.5px; color: var(--ink); outline: 0; }
  .cs-num { width: 58px; }
  .cs-mono-textarea { width: 100%; padding: 10px 12px; border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--inset); font: 12.5px/1.6 var(--mono); color: var(--ink); resize: vertical; outline: 0; }
  .cs-mono-textarea:focus, .cs-add-select:focus, .cs-num:focus { border-color: var(--ink); box-shadow: var(--ring); background: var(--card); }
  .cs-add-foot { display: flex; align-items: center; gap: 8px; }
  .cs-parse-note { flex: 1; font-size: var(--t-sm); color: var(--ok-strong); }
  .cs-add-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
  .cs-text-btn { height: 28px; padding: 0 8px; background: none; border: 0; font: inherit; font-size: 12.5px; font-weight: 500; color: var(--soft); cursor: pointer; }
  .cs-text-btn:hover { color: var(--ink); }
  .cs-drop { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 26px; border: 1.5px dashed var(--border-control); border-radius: 10px; background: var(--inset); text-align: center; cursor: pointer; transition: border-color 0.12s, background 0.12s; }
  .cs-drop:hover, .cs-drop.is-over { border-color: var(--soft); background: var(--card); }
  .cs-drop svg { margin-bottom: 4px; color: var(--dim); }
  .cs-drop-title { font-size: var(--t-base); font-weight: 600; }
  .cs-drop-sub { font-size: var(--t-sm); color: var(--soft); }
  .cs-add-blank { display: flex; align-items: center; gap: 8px; margin: 0; padding-top: 10px; border-top: 1px solid var(--border-row); }
  .cs-add-blank .cs-text-btn { margin-left: auto; }

  /* Empty state — it IS the "add something" button. */
  .cs-empty { display: flex; flex-direction: column; align-items: center; gap: 6px; width: 100%; padding: 40px 20px; border: 1.5px dashed var(--border-control); border-radius: var(--r-card); background: var(--card); font: inherit; cursor: pointer; }
  .cs-empty:hover { border-color: var(--soft); }
  .cs-empty-plus { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: var(--icon-tile); font-size: 18px; color: var(--ink-2); }
  .cs-empty-title { font-size: 13.5px; font-weight: 600; color: var(--ink); }
  .cs-empty-sub { font-size: 12.5px; color: var(--soft); }

  .cs-rows { display: flex; flex-direction: column; gap: 10px; }

  /* Quiet fields: they read as text until touched. Every editable thing on an asset is
     one of these, so the page looks like the work rather than like a form about it. */
  .cs-field { display: flex; flex-direction: column; gap: 3px; }
  .cs-input, .cs-textarea {
    min-width: 0; padding: 3px 6px; border: 1px solid transparent; border-radius: 6px; background: transparent;
    font: inherit; color: var(--ink); outline: 0; transition: border-color 0.12s, background 0.12s;
  }
  .cs-input:hover, .cs-textarea:hover { border-color: var(--border-row); background: var(--inset); }
  .cs-input:focus, .cs-textarea:focus { border-color: var(--ink); background: var(--card); box-shadow: var(--ring); }
  .cs-input--mono { font-family: var(--mono); font-size: var(--t-sm); }
  select.cs-input {
    appearance: none; padding-right: 18px; cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%23948C81' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 4px center;
  }
  /* field-sizing grows the box to its text, so a long line is never behind a scrollbar. */
  .cs-textarea { width: 100%; min-height: 28px; field-sizing: content; resize: none; font-size: 13.5px; line-height: 1.5; }

  /* Copy row */
  .cs-copy-row { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); transition: border-color 0.15s, box-shadow 0.15s; }
  .cs-copy-row:focus-within { border-color: var(--dim); box-shadow: 0 0 0 3px oklch(0.2 0 0 / 0.04); }
  .cs-copy-form { margin: 0; }
  .cs-copy-head { display: flex; align-items: center; gap: 10px; padding: 9px 10px 0 16px; }
  .cs-copy-head .cs-field { flex-direction: row; align-items: center; gap: 6px; min-width: 0; }
  .cs-copy-head .cs-field > span, .cs-link-fields .cs-field > span { font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dim); white-space: nowrap; }
  .cs-copy-head .cs-select { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  .cs-placement-custom { width: 170px; font-size: var(--t-sm); }
  .cs-variant { width: 44px; padding: 4px 6px; border: 1px solid transparent; border-radius: 5px; background: var(--icon-tile); font: 600 11px/1 var(--mono); color: var(--ink-2); text-align: center; outline: 0; }
  .cs-variant:hover { border-color: var(--border-control); }
  .cs-variant:focus { border-color: var(--ink); background: var(--card); }
  .cs-copy-gap { flex: 1; }
  /* Informative, so it stays quiet until it has something to say — past the limit, amber. */
  .cs-char-count { font: 500 11px var(--mono); color: var(--dim); white-space: nowrap; }
  .cs-char-count.is-long { color: var(--amber-ink); }
  .cs-copy-btn { display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 9px; border: 1px solid var(--border-control); border-radius: 7px; background: var(--card); font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); cursor: pointer; transition: color 0.15s, border-color 0.15s; }
  .cs-copy-btn:hover { border-color: var(--dim); color: var(--ink); }
  .cs-copy-btn.is-copied { color: var(--ok-strong); border-color: color-mix(in srgb, var(--ok) 40%, var(--card)); }
  .cs-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 7px; background: none; color: var(--soft); cursor: pointer; }
  .cs-icon-btn:hover { background: var(--icon-tile); color: var(--ink); }
  .cs-icon-btn--danger:hover { background: var(--err-tint); color: var(--err); }
  .cs-icon-form { margin: 0; display: inline-flex; }
  .cs-row-actions-edit { display: inline-flex; }
  .cs-copy-text { display: block; width: 100%; padding: 6px 16px 14px; border: 0; background: none; resize: none; field-sizing: content; font: inherit; font-size: 15px; line-height: 1.55; color: var(--ink); outline: 0; text-wrap: pretty; }
  /* What you can do TO a row waits for the row: hover it, or be inside it. */
  .cs-reveal, .cs-row-actions-edit, .cs-tile-del { opacity: 0; transition: opacity 0.12s; }
  :is(.cs-copy-row, .cs-link-wrap, .cs-script-head, .cs-script-row, .cs-tile-wrap):is(:hover, :focus-within) :is(.cs-reveal, .cs-row-actions-edit, .cs-tile-del) { opacity: 1; }
  @media (hover: none) { .cs-reveal, .cs-row-actions-edit, .cs-tile-del { opacity: 1; } }

  /* Image grid */
  .cs-img-card { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); }
  .cs-img-head { display: flex; align-items: baseline; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border-divider); }
  .cs-img-head .cd-card-title { font-size: 13.5px; font-weight: 600; }
  .cs-img-head .cs-hint { margin: 0; }
  /* Tiles are drawn at each image's real shape (an inline aspect-ratio, from the blob's
     analyzed size), so start-align them rather than stretching a short tile to match a
     tall neighbour. The 1/1 below is the fallback for an unanalyzed upload and for an
     empty drop tile. */
  .cs-img-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(176px, 1fr)); gap: 14px; padding: 16px; align-items: start; }
  .cs-tile-wrap { position: relative; }
  .cs-tile { display: flex; flex-direction: column; gap: 6px; margin: 0; }
  .cs-tile-slot { display: flex; align-items: center; justify-content: center; aspect-ratio: 1 / 1; overflow: hidden; border: 1px solid var(--border-row); border-radius: 9px; background: var(--inset); cursor: pointer; transition: border-color 0.12s; }
  .cs-tile-slot:not(:has(img)) { border: 1.5px dashed var(--border-control); }
  .cs-tile-slot:not(:has(img)):hover { border-color: var(--soft); }
  .cs-tile-img { width: 100%; height: 100%; object-fit: contain; display: block; }
  .cs-tile-drop { font-size: var(--t-sm); font-weight: 500; color: var(--soft); }
  .cs-tile-label { width: 100%; font-size: 12.5px; }
  .cs-tile-label + .cs-tile-label { font-family: var(--mono); font-size: 11.5px; color: var(--ink-2); }
  .cs-tile-dims { padding-left: 7px; font: 500 11px var(--mono); color: var(--dim); }
  .cs-tile-del { position: absolute; top: 6px; right: 6px; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 6px; background: color-mix(in srgb, var(--ink) 70%, transparent); color: var(--text-on-inverse); cursor: pointer; }

  /* Link (video / landing page) row */
  .cs-link-wrap { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px; align-items: center; padding: 12px; background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); }
  .cs-link-row { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 16px; align-items: center; min-width: 0; margin: 0; }
  .cs-link-thumb { display: flex; align-items: center; justify-content: center; aspect-ratio: 16 / 10; overflow: hidden; border: 1.5px dashed var(--border-control); border-radius: var(--r-md); background: var(--inset); cursor: pointer; }
  .cs-link-thumb:has(img) { border: 0; }
  .cs-link-img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .cs-thumb-drop { font-size: 11.5px; font-weight: 500; color: var(--soft); }
  .cs-link-fields { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
  .cs-link-fields .cs-field { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 10px; align-items: center; }
  .cs-link-fields .cs-input { width: 100%; font-size: var(--t-lg); font-weight: 600; }
  .cs-link-fields .cs-input--mono { font-size: var(--t-sm); font-weight: 400; color: var(--accent); }
  .cs-link-actions { display: flex; align-items: center; gap: 4px; }
  .cs-open-btn { text-decoration: none; }

  /* The send bar is the client's decision bar (see below) with the agency's two steps in
     it; these are its only differences. */
  .cs-decision-icon.cs-decision-icon--plain { background: oklch(1 0 0 / 0.1); color: inherit; }
  .cs-btn-ghost.cs-btn-ghost--bare { height: 30px; padding: 0 10px; border-color: transparent; font-size: 12.5px; text-decoration: none; }
  .cs-send-bar form { margin: 0; flex: none; }
  .cs-send-bar .cs-btn-approve { text-decoration: none; }

  .cs-danger { display: flex; justify-content: center; padding: 6px 0; }
  .cs-danger form { margin: 0; }
  .cs-danger-btn { padding: 6px 10px; border: 0; border-radius: 7px; background: none; font: inherit; font-size: 12.5px; color: var(--soft); white-space: nowrap; cursor: pointer; }
  .cs-danger-btn:hover { color: var(--err); background: var(--err-tint); }

  /* Locked state: the fields go inert and the edit affordances go away; Copy stays. */
  /* ⚠ Every editable surface has to be listed here by hand. A new asset type that
     invents its own control classes is editable after sign-off unless it is added —
     a script's variant chip and reference slot were exactly that, and neither looked
     wrong on screen. */
  .cs-assets.is-locked :is(.cs-input, .cs-textarea, .cs-variant, .cs-copy-text, .cs-line-chip, .cs-ref-slot, .cs-tile-slot) { pointer-events: none; }
  .cs-assets.is-locked select.cs-input { padding-right: 6px; background-image: none; }
  /* ⚠ Two fields survive the freeze: a filing name and a Meta media-library id. The lock is
     about what the client signed off on, and neither of those is — you rename an approved
     image to file it, and you paste its hash so a bulk sheet can reuse it. update_asset draws
     the same line over MCP, so the app and the connector cannot disagree.
     Three classes, so it wins the tie against the freeze above whatever the order. */
  .cs-assets.is-locked .cs-input.cs-input--open { pointer-events: auto; }
  .cs-assets.is-locked .cs-edit-only,
  .cs-assets.is-locked .cs-row-actions-edit { display: none !important; }

  @media (max-width: 700px) {
    .creative-show { --page-gutter: 16px; }
    .cs-page { padding-block: 14px 40px; }
    .cs-head { align-items: flex-start; }
    .cs-head-actions { width: 100%; }
    .cs-head-actions .status-menu { flex: 1; }
    .cs-head-actions .status-menu > summary { width: 100%; height: 38px; }
    .cs-copy-head { flex-wrap: wrap; row-gap: 6px; padding: 10px 10px 0 14px; }
    .cs-copy-gap { display: none; }
    .cs-copy-head .cs-char-count { margin-left: auto; }
    .cs-placement-custom { width: 100%; }
    .cs-copy-text { padding: 6px 14px 14px; }
    .cs-img-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 12px; }
    .cs-link-wrap { grid-template-columns: minmax(0, 1fr); gap: 12px; }
    .cs-link-row { grid-template-columns: 84px minmax(0, 1fr); gap: 12px; }
    .cs-link-fields .cs-field { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .cs-link-actions { justify-content: flex-end; }
    /* A placeholder is not what iOS measures before it zooms — the field's own 16px is —
       so the hint can stay small enough to be read whole in a half-width tile. */
    .cs-input::placeholder { font-size: var(--t-sm); }
    /* The floating Queue pill would sit under the bar, half covered. The bar IS this
       page's way to the Queue, so the pill stands down while one is showing. */
    .app:has(.cs-send-bar) .nav-pill { display: none; }
    /* The bar wraps like the client's: the question, then both steps side by side. */
    .cs-send-bar form { flex: 1; display: flex; }
    .cs-send-bar form > button { flex: 1; }
  }

  /* ── Client review ───────────────────────────────
     What a client reads and answers. One column: who sent it, what was decided, the work in
     a card, then the comment box and the pinned bar. Read-only markup throughout. */
  .cs-review { display: flex; flex-direction: column; gap: 18px; }
  .cs-review-intro-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
  .cs-review-intro-sub { font-size: var(--t-lg); color: var(--ink-2); margin-top: 3px; line-height: 1.5; text-wrap: pretty; }
  .cs-review-section { overflow: hidden; }
  .cs-review-section > .cs-section-head { align-items: center; gap: 8px; padding: 12px 18px; border-bottom: 1px solid var(--border-divider); }
  .cs-review-section .cs-count { font-family: var(--mono); font-size: var(--t-sm); font-weight: 500; color: var(--soft); background: var(--sidebar); border-radius: var(--r); padding: 2px 7px; }

  /* WHERE | LABEL | COPY. One grid template shared by the header row and every body row,
     so the columns can't drift apart. */
  .cs-copy-thead, .cs-copy-trow { display: grid; grid-template-columns: 188px 74px minmax(0, 1fr); align-items: start; padding: 0 18px; }
  .cs-copy-thead { padding-block: 10px 8px; }
  .cs-copy-thead .section-label { font-size: var(--t-xs); letter-spacing: 0.04em; color: var(--dim); }
  .cs-copy-trow { padding-block: 13px; border-top: 1px solid var(--border-row); }
  .cs-copy-trow > :not(:last-child) { padding-right: 18px; }
  .cs-cell-place { font-size: var(--t-base); font-weight: 500; color: var(--ink-2); }
  .cs-cell-var { font-family: var(--mono); font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  /* The copy is the thing being approved, so it is the largest text on the page. pre-line
     keeps a set of dot points as the lines they were written as. */
  .cs-cell-copy { font-size: 14.5px; line-height: 1.55; color: var(--ink); white-space: pre-line; text-wrap: pretty; overflow-wrap: anywhere; }

  /* Images, at each one's real shape. */
  .cs-review-imgs { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 18px; padding: 18px; align-items: start; }
  .cs-review-img { margin: 0; display: flex; flex-direction: column; gap: 8px; }
  .cs-review-img-slot { aspect-ratio: 1 / 1; background: var(--sidebar); border: 1px solid var(--border-row); border-radius: var(--r-lg); overflow: hidden; display: flex; }
  .cs-review-img-slot img { width: 100%; height: 100%; object-fit: contain; }
  .cs-review-img figcaption { font-size: var(--t-base); font-weight: 500; color: var(--ink-2); }

  /* Video and landing page: one card each — a frame, then its name, its link and the way out. */
  .cs-review-medias { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 18px; padding: 18px; }
  .cs-review-media { display: flex; flex-direction: column; overflow: hidden; background: var(--card); border: 1px solid var(--border-row); border-radius: var(--r-card); }
  .cs-review-still, .cs-review-page-shot { position: relative; overflow: hidden; background: var(--sidebar); display: flex; align-items: center; justify-content: center; }
  .cs-review-still { aspect-ratio: 16 / 9; background: var(--ink); }
  .cs-review-page-shot { aspect-ratio: 4 / 3; }
  .cs-review-still img { width: 100%; height: 100%; object-fit: contain; }
  .cs-review-page-shot img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
  .cs-play-badge { position: absolute; width: 44px; height: 44px; border-radius: 50%; background: oklch(0 0 0 / 0.45); color: var(--text-on-inverse); display: flex; align-items: center; justify-content: center; }
  .cs-review-media-foot { display: flex; align-items: center; gap: 10px; padding: 12px 14px; }
  .cs-review-media-name { flex: 1; min-width: 0; font-size: var(--t-lg); font-weight: 600; }
  .cs-review-media-link { margin-top: 2px; font-family: var(--mono); font-size: var(--t-sm); font-weight: 400; color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cs-review-media-foot .cs-open-btn { flex: none; }

  /* The comment box. Optional for an approval, needed for a change request — and when it
     was needed and empty, it says so right here. */
  .cs-comment { padding: 14px 16px; }
  .cs-comment label { display: block; font-size: var(--t-base); font-weight: 600; margin-bottom: 8px; }
  .cs-optional { color: var(--soft); font-weight: 400; }
  .cs-comment-box {
    width: 100%; min-height: 78px; resize: vertical; padding: 10px 12px; outline: none;
    font: inherit; font-size: var(--t-lg); line-height: 1.5; color: var(--ink);
    background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md);
    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .cs-comment.is-bad .cs-comment-box { border-color: var(--err); box-shadow: 0 0 0 2px color-mix(in srgb, var(--err) 12%, transparent); }
  .cs-comment-error { margin: 6px 0 0; font-size: var(--t-base); color: var(--err-strong); }
  #decide { scroll-margin-top: 80px; }

  /* The answer, pinned to the foot of the reading column for as long as the page scrolls:
     the one thing this person came to do stays in reach while they read. Ink, like every
     bar that floats over the page. */
  .cs-decision-bar {
    position: sticky; bottom: 16px; z-index: 30;
    display: flex; align-items: center; gap: 12px; padding: 10px 10px 10px 14px;
    background: color-mix(in srgb, var(--ink) 94%, transparent);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    color: var(--text-on-inverse); border-radius: var(--r-xl);
    box-shadow: 0 14px 34px -8px oklch(0 0 0 / 0.35), inset 0 1px 0 oklch(1 0 0 / 0.08);
  }
  .cs-decision-icon { width: 30px; height: 30px; flex: none; border-radius: var(--r-md); background: oklch(0.83 0.16 84 / 0.18); color: oklch(0.88 0.15 92); display: inline-flex; align-items: center; justify-content: center; }
  .cs-decision-text { flex: 1; min-width: 0; }
  .cs-decision-title { font-size: var(--t-lg); font-weight: 600; }
  .cs-decision-sub { font-size: var(--t-sm); color: oklch(1 0 0 / 0.66); margin-top: 1px; line-height: 1.45; text-wrap: pretty; }
  .cs-btn-ghost, .cs-btn-approve {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    height: var(--control-h); border-radius: var(--r-md); cursor: pointer; white-space: nowrap;
    font: inherit; font-size: var(--t-base);
  }
  .cs-btn-ghost { padding: 0 12px; background: transparent; border: 1px solid oklch(1 0 0 / 0.22); color: oklch(1 0 0 / 0.85); font-weight: 500; &:hover { background: oklch(1 0 0 / 0.08); color: var(--text-on-inverse); } }
  .cs-btn-approve { padding: 0 14px; background: var(--card); border: 1px solid var(--card); color: var(--ink); font-weight: 600; &:hover { background: var(--inset); } }

  /* What was decided, at the top of the page. */
  .cs-decided { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 12px; align-items: start; padding: 14px 16px; border-radius: var(--r-card); border: 1px solid var(--border-card); outline: none; }
  .cs-decided-icon { width: 30px; height: 30px; border-radius: var(--r-md); display: inline-flex; align-items: center; justify-content: center; }
  .cs-decided-title { margin-top: 5px; font-size: var(--t-lg); font-weight: 600; }
  .cs-decided-sub { margin-top: 3px; font-size: var(--t-base); color: var(--ink-2); line-height: 1.5; }
  .cs-decided-note { display: block; margin-top: 4px; font-size: var(--t-lg); line-height: 1.5; color: var(--ink); quotes: "\201C" "\201D"; }
  .cs-decided--ok { background: color-mix(in srgb, var(--ok) 6%, var(--card)); border-color: color-mix(in srgb, var(--ok) 24%, var(--card)); }
  .cs-decided--ok .cs-decided-icon { background: var(--ok-tint); color: var(--ok-strong); }
  .cs-decided--amber { background: color-mix(in srgb, var(--badge-amber) 7%, var(--card)); border-color: color-mix(in srgb, var(--badge-amber) 28%, var(--card)); }
  .cs-decided--amber .cs-decided-icon { background: var(--amber-tint); color: var(--amber-ink); }
  .cs-review-wait { padding: 14px 16px; text-align: center; font-size: var(--t-base); color: var(--soft); border: 1px dashed var(--border-control); border-radius: var(--r-card); }

  @media (max-width: 700px) {
    .cs-review-section > .cs-section-head { padding: 12px 14px; }
    .cs-review-imgs, .cs-review-medias { padding: 14px; gap: 14px; }
    .cs-review-imgs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cs-review-media-foot { flex-wrap: wrap; }
    .cs-review-media-name { flex-basis: 100%; }
    .cs-review-media-foot .cs-open-btn { width: 100%; height: 36px; }
    /* The bar wraps: the question on its own line, then both answers side by side at a
       size a thumb can hit. */
    .cs-decision-bar { bottom: 12px; flex-wrap: wrap; row-gap: 8px; padding: 10px; }
    .cs-decision-icon { display: none; }
    .cs-decision-text { flex-basis: 100%; }
    .cs-decision-sub { display: none; }
    .cs-btn-ghost, .cs-btn-approve { flex: 1; height: 40px; }
  }
  /* ⚠ Below 430px the three columns stack: WHERE and LABEL ride one line above the copy,
     which gets the full width. Squeezing them side by side is what left the copy in a
     two-word gutter before the column became a heading — and this brings the column back. */
  @media (max-width: 430px) {
    .cs-copy-thead { display: none; }          /* the row says both words itself */
    .cs-copy-trow { grid-template-columns: minmax(0, 1fr) auto; gap: 2px 8px; padding: 12px 14px 13px; }
    .cs-copy-trow > :not(:last-child) { padding-right: 0; }
    .cs-cell-var { justify-self: end; }
    .cs-cell-copy { grid-column: 1 / -1; }
  }
}

/* The `hidden` attribute must beat the display rules on these toggled panels. */
@layer components {

  /* The empty state IS the "add something" affordance, so it has nothing to say once the
     add panel is open — and leaving it there left a second, focus-ringed box under the form. */
  .cs-add-panel:not([hidden]) + .cs-empty { display: none; }

  /* File inputs are triggered by their wrapping label — keep them out of the
     layout (mirrors .settings-avatar-input) so they don't clobber the tiles. */
  /* ⚠ position: absolute, so give it a positioned ancestor INSIDE the scroller whenever it
     repeats down a long list. Without one it anchors to .view — the app's outer scroller —
     and every instance adds its own static position to .view's scrollable height. Measured
     once at 905px of phantom scroll below a long page, which dragged the header off-screen
     as you scrolled into it. One `position: relative` on the repeating component fixes it. */
  .cs-visually-hidden { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

  /* ── Ad scripts ──────────────────────────────────────────────────────────
     Section | Reference | Script, to approvals.css's .scr / .st / .sec. Three columns
     down to 700px; below that each section stacks (name, then its shot beside its lines),
     which is what the client's read-only table deliberately does NOT do — see there. */
  .cs-script-card { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); }
  .cs-script-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 12px 12px 16px; border-bottom: 1px solid var(--border-divider); }
  .cs-script-kind { flex: none; padding: 4px 6px; border: 1px solid var(--border-control); border-radius: 5px; background: var(--inset); font: 600 10.5px/1 var(--mono); color: var(--ink-2); white-space: nowrap; }
  .cs-script-fields { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 160px; margin: 0; }
  .cs-script-name { flex: 1; min-width: 0; font-size: var(--t-lg); font-weight: 600; }
  .cs-script-len { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-xs); font-weight: 600; color: var(--soft); }
  /* 58, not the spec's 52: at this mono size "00:30" clips its own last digit at 52,
     and a length field that can't show a length is worse than six pixels. */
  .cs-script-dur { width: 58px; font: 500 12px var(--mono); }
  .cs-script-meta { font-size: var(--t-sm); color: var(--soft); white-space: nowrap; }

  /* ⚠ minmax(0, 1fr), not 1fr. A grid track's automatic minimum is its content, so a
     plain 1fr refuses to shrink past the widest line and bursts the card. */
  .cs-script-thead, .cs-script-row { display: grid; grid-template-columns: 120px 92px minmax(0, 1fr); column-gap: 16px; align-items: start; padding-inline: 16px; }
  .cs-script-thead { padding-block: 10px 6px; }
  .cs-script-thead .section-label { font-size: var(--t-xs); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--dim); }
  .cs-script-row { position: relative; grid-template-rows: auto 1fr; padding-block: 12px; border-top: 1px solid var(--border-row); }
  /* The form owns all three columns, so it inherits the row's grid rather than nesting one. */
  .cs-script-form { display: contents; }
  .cs-script-secname { width: 100%; font-size: var(--t-base); font-weight: 600; }
  /* ⚠ Every cell is placed by hand. Remove sits in the name's own cell, under it — and a
     cell taken by hand is one auto-placement steps around, so leaving the other three to
     flow would push the name down a row. */
  .cs-script-sec { grid-area: 1 / 1; }
  .cs-script-lines { grid-area: 1 / 3; }
  /* The shot is the tall thing in a row, so it spans both lines of it and the second line
     (1fr, see the row) takes up its slack — "+ Add variants" stays tucked under the lines
     instead of being pushed to the foot of the reference. */
  .cs-script-ref { grid-area: 1 / 2 / 3 / 3; }
  .cs-script-sec-foot { grid-area: 1 / 1 / 3 / 2; align-self: start; margin-top: 34px; padding-left: 6px; }
  .cs-script-lines-foot { grid-area: 2 / 3; align-self: start; margin-top: 8px; padding-left: 6px; }

  /* ⚠ A definite width, so the writer's ratio drives the HEIGHT. With an auto width a
     min-height wins first and aspect-ratio computes the width back from it — a 16:9 ref
     once measured wider than its own column and spilled into the script beside it. */
  .cs-ref-slot { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 64px; overflow: hidden; border: 1.5px dashed var(--border-control); border-radius: 7px; background: var(--inset); cursor: pointer; }
  .cs-ref-slot:has(img) { border: 1px solid var(--border-row); }
  .cs-ref-img { width: 100%; height: 100%; object-fit: contain; display: block; }
  .cs-ref-drop { display: flex; flex-direction: column; align-items: center; gap: 1px; font-size: 11.5px; font-weight: 500; color: var(--soft); text-align: center; }
  .cs-ref-drop small { font-size: 10px; font-weight: 400; line-height: 1.2; color: var(--dim); }

  .cs-ratio { display: flex; gap: 2px; margin-top: 6px; }
  .cs-ratio-btn { padding: 2px 4px; border-radius: 4px; font: 500 10px var(--mono); color: var(--dim); cursor: pointer; }
  .cs-ratio-btn:hover { color: var(--ink); }
  .cs-ratio input:checked + .cs-ratio-btn { background: var(--icon-tile); color: var(--ink); }
  .cs-ratio input:focus-visible + .cs-ratio-btn { box-shadow: var(--ring); }

  .cs-script-lines { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
  .cs-line { display: flex; align-items: flex-start; gap: 8px; }
  .cs-line .cs-textarea { flex: 1; }
  .cs-line-chip { flex: none; width: 30px; margin-top: 1px; padding: 5px 0; border: 1px solid transparent; border-radius: 5px; background: var(--accent-tint); font: 600 11px/1 var(--mono); color: var(--accent); text-align: center; outline: 0; }
  .cs-line-chip:focus { border-color: var(--accent); background: var(--card); }
  .cs-add-variant { padding: 0; border: 0; background: none; font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--soft); cursor: pointer; }
  .cs-add-variant:hover { color: var(--ink); }
  .cs-add-section { display: block; width: 100%; padding: 10px 16px; border: 0; border-top: 1px dashed var(--border-divider); border-radius: 0 0 11px 11px; background: none; font: inherit; font-size: 12.5px; font-weight: 500; color: var(--soft); text-align: left; cursor: pointer; }
  .cs-add-section:hover { color: var(--ink); background: var(--inset); }
  .cs-add-variant-form, .cs-add-section-form { margin: 0; }
  .cs-text-link-danger { padding: 0; border: 0; background: none; font: inherit; font-size: 11.5px; color: var(--dim); cursor: pointer; }
  .cs-text-link-danger:hover { color: var(--err); }

  @media (max-width: 700px) {
    .cs-script-head { gap: 8px 12px; }
    .cs-script-fields { flex-basis: 100%; }
    .cs-script-meta { flex: 1; min-width: 0; white-space: normal; }
    .cs-script-thead { display: none; }
    /* One section: its name across the top with Remove at the far end, then the shot in a
       narrow column beside what gets said. */
    .cs-script-row { grid-template-columns: 56px minmax(0, 1fr); grid-template-rows: none; gap: 8px 12px; padding: 12px 14px; }
    .cs-script-sec { grid-area: 1 / 1 / 2 / -1; padding-right: 110px; }
    .cs-script-sec-foot { grid-area: 1 / 1 / 2 / -1; justify-self: end; align-self: center; margin: 0; }
    .cs-script-ref { grid-area: 2 / 1; }
    .cs-script-lines { grid-area: 2 / 2; }
    .cs-script-lines-foot { grid-area: 3 / 2; margin-top: 0; }
    .cs-ref-slot { width: 56px; }
    .cs-ratio { flex-wrap: wrap; }
  }

  /* Client review of ad scripts — the same three columns as the agency's editor, no
     controls. Scripts stack inside the section card with a rule between them. */
  .cs-rv-script + .cs-rv-script { border-top: 1px solid var(--border-divider); }
  .cs-rv-script-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding: 14px 18px 4px; }
  .cs-rv-script-name { font-size: var(--t-lg); font-weight: 600; }
  .cs-rv-script-meta { font-size: var(--t-sm); color: var(--soft); }
  .cs-rv-script-table { padding: 0 18px 6px; }
  .cs-rv-script-thead, .cs-rv-script-row { display: grid; grid-template-columns: 128px 138px minmax(0, 1fr); align-items: start; }
  .cs-rv-script-thead > :not(:last-child), .cs-rv-script-row > :not(:last-child) { padding-right: 18px; }
  .cs-rv-script-thead { padding: 10px 0 8px; }
  .cs-rv-script-thead .section-label { font-size: var(--t-xs); letter-spacing: 0.04em; color: var(--dim); }
  .cs-rv-script-row { padding: 14px 0; border-top: 1px solid var(--border-row); }
  /* A script that briefs no shots drops the column rather than showing empty boxes. */
  .cs-rv-script--noref .cs-rv-script-thead, .cs-rv-script--noref .cs-rv-script-row { grid-template-columns: 128px minmax(0, 1fr); }
  .cs-rv-sec { font-size: var(--t-base); font-weight: 600; color: var(--ink-2); }
  .cs-rv-ref { width: 100%; display: block; background: var(--sidebar); border-radius: var(--r-md); overflow: hidden; }
  .cs-rv-ref img { width: 100%; height: 100%; object-fit: contain; display: block; }
  .cs-rv-lines { min-width: 0; display: flex; flex-direction: column; gap: 8px; }
  .cs-rv-line { display: flex; gap: 10px; align-items: baseline; }
  .cs-rv-letter { flex: none; font-family: var(--mono); font-size: var(--t-xs); font-weight: 600; color: var(--ink-2); background: var(--sidebar); border-radius: var(--r-sm); padding: 1px 6px; }
  .cs-rv-text { font-size: 14.5px; line-height: 1.55; color: var(--ink); white-space: pre-line; text-wrap: pretty; overflow-wrap: anywhere; }
  .cs-rv-notes { display: flex; flex-direction: column; gap: 4px; padding: 12px 18px 16px; border-top: 1px solid var(--border-divider); }
  .cs-rv-refnote { margin: 0; font-size: var(--t-base); color: var(--soft); line-height: 1.5; }

  @media (max-width: 560px) {
    /* ⚠ The client's table shrinks in place — three columns of TEXT fit a phone, and
       keeping the beat beside its shot is the whole point of the layout. (The agency's
       editor cannot shrink — its third column holds a chip, a growing textarea and a
       delete button — so it stacks each section instead; see "Ad scripts" above.) */
    .cs-rv-script-head { padding: 14px 14px 4px; }
    .cs-rv-script-table { padding: 0 14px 6px; }
    .cs-rv-script-thead, .cs-rv-script-row { grid-template-columns: 88px 84px minmax(0, 1fr); }
    .cs-rv-script-thead > :not(:last-child), .cs-rv-script-row > :not(:last-child) { padding-right: 12px; }
    .cs-rv-script--noref .cs-rv-script-thead, .cs-rv-script--noref .cs-rv-script-row { grid-template-columns: 88px minmax(0, 1fr); }
  }

}

@layer components {
  /* ── Library ──────────────────────────────────────────────────────────────────
     Rebuilt to `Library v2 - Admin` and `Library Document v2 - Admin`. The rule this
     block follows, and the reason it is shorter than the one it replaced: a Library
     surface that has a shared primitive uses it. The list card is a card like every
     other, the empty state is .empty-state, the row menu is the campaigns table's
     <details> menu, and the copy-button label swap is .cd-mini-label / .cd-mini-done.
     What is left here is what only the Library has. */

  /* ── Crumb (the one thing that sits before the header's icon tile) ── */
  .lib-back {
    display: flex; align-items: center; gap: 5px;
    font-size: var(--t-base); font-weight: 600; color: var(--soft);
    text-decoration: none; white-space: nowrap;

    & span:first-child { font-size: var(--t-lg); }
    &:hover { color: var(--ink); }
  }
  .lib-crumb-sep { color: var(--dim); }
  .lib-crumb-scope { font-size: var(--t-base); font-weight: 600; color: var(--soft); white-space: nowrap; }
  /* The client's name is a way back to their shelf; the workspace's isn't a link at all,
     so the affordance rides the modifier rather than the base class. */
  .lib-crumb-scope--link { text-decoration: none; &:hover { color: var(--ink); } }
  .lib-divider { width: 1px; height: 22px; background: var(--border2); flex-shrink: 0; }

  /* ── List: controls, then a shelf of rows per kind ── */
  .lib-controls { padding: 16px 0 0; gap: 8px; }
  .lib-controls .search-pill { width: 260px; }
  .lib-count { margin-left: auto; font-size: var(--t-sm); color: var(--soft); white-space: nowrap; }
  /* The one-option filter on a scoped library: a chip that is either on or off. */
  .lib-chip {
    display: inline-flex; align-items: center; height: var(--control-h); padding: 0 12px; border-radius: 999px;
    border: 1px solid var(--border-control); background: var(--card);
    font-size: 12.5px; font-weight: 500; color: var(--ink-2); text-decoration: none;

    &:hover { border-color: var(--soft); text-decoration: none; }
    &.is-active { background: var(--ink); border-color: var(--ink); color: var(--text-on-inverse); }
  }
  .lib-listhead {
    display: flex; align-items: center; gap: 10px; margin: 20px 0 10px;

    & h2 { margin: 0; font-size: var(--t-lg); font-weight: 600; }
    & .lib-manage { margin-left: auto; font-size: 12.5px; font-weight: 500; color: var(--accent-ink); }
  }

  /* A shelf: a card whose head folds it. ⚠ Not clipped — a row's ⋯ menu hangs out of it —
     so the head and the last row round their own corners. */
  .lib-shelf { margin-top: 16px; max-width: 1080px; background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .lib-shelf:has(.row-menu[open]) { position: relative; z-index: 5; }
  .lib-shelf-head {
    display: flex; align-items: center; gap: 10px; padding: 11px 16px; cursor: pointer;
    border-bottom: 1px solid var(--border-divider); border-radius: var(--r-card) var(--r-card) 0 0;

    & b { font-size: 13.5px; font-weight: 600; }
    /* The count pushes whatever follows — the legend, then the chevron — to the far end. */
    & small { margin-right: auto; font-size: var(--t-sm); color: var(--soft); }
    &:hover b { color: var(--accent-ink); }
  }
  .lib-fold:focus-visible + .lib-shelf-head { box-shadow: var(--ring); }
  .lib-shelf-tile {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 26px; height: 26px; border-radius: 7px; background: var(--sidebar); color: var(--ink-2);
  }
  .lib-shelf--research .lib-shelf-tile { background: var(--accent-tint); color: var(--accent-ink); }
  /* The legend sits over the two toggle columns: 68px of flags, then the 28px menu and the
     14px chevron with their gaps, less the row's narrower right padding. */
  .lib-shelf-legend { display: flex; gap: 2px; margin-right: 22px; color: var(--dim); }
  .lib-shelf-legend > span { display: inline-flex; justify-content: center; width: 30px; }
  .lib-shelf-chev { display: inline-flex; color: var(--dim); transition: transform 0.2s; }
  .lib-shelf:has(.lib-fold:checked) {
    & .lib-shelf-head { border-bottom: 0; border-radius: var(--r-card); }
    & .lib-shelf-chev { transform: rotate(-90deg); }
    & .lib-shelf-body, & .lib-shelf-legend { display: none; }
  }
  /* Past eight rows a shelf shows six and offers the rest. */
  .lib-row-extra { display: none; }
  .lib-shelf-body:has(.lib-more-check:checked) .lib-row-extra { display: contents; }
  .lib-more {
    display: flex; align-items: center; justify-content: center; height: 38px; cursor: pointer;
    border-top: 1px solid var(--border-row); border-radius: 0 0 var(--r-card) var(--r-card);
    font-size: 12.5px; font-weight: 500; color: var(--soft);

    &:hover { color: var(--ink); background: var(--inset); }
  }
  .lib-more-fewer, .lib-shelf-body:has(.lib-more-check:checked) .lib-more-all { display: none; }
  .lib-shelf-body:has(.lib-more-check:checked) .lib-more-fewer { display: inline; }
  .lib-more-check:focus-visible ~ .lib-more { box-shadow: var(--ring); }

  /* One track list for every row, so the columns can't drift. The Library page's rows sit
     on a kind's shelf and have no Type column; a client's Library tab is one flat list and
     keeps it. The agency-wide page adds whose document it is. */
  .lib-table { --lib-client: 0px; --lib-kind: 0px; }
  .lib-shelf .lib-table { background: none; border: 0; border-radius: 0; box-shadow: none; }
  .lib-row {
    display: grid; gap: 12px; align-items: center; padding: 10px 12px 10px 16px;
    grid-template-columns: minmax(0, 1fr) 132px 68px 28px 14px;
    border-top: 1px solid var(--border-row); transition: background 0.12s;

    &:hover { background: var(--inset); }
    &:first-child, .lib-more-check + &, .lib-table > .lib-row-extra:first-child > & { border-top: 0; }
    .lib-shelf-body > &:last-child { border-radius: 0 0 var(--r-card) var(--r-card); }
  }
  .lib-row--client { grid-template-columns: minmax(0, 1fr) 170px 132px 68px 28px 14px; }
  .lib-row--kind { grid-template-columns: minmax(0, 1fr) 120px 120px 68px 28px 14px; }
  .lib-row--kind.lib-row--client { grid-template-columns: minmax(0, 1fr) 170px 120px 120px 68px 28px 14px; }
  /* Inside a client-detail panel the card chrome is the panel's, not the table's. */
  .lib-table--flush { border: 0; border-radius: 0; box-shadow: none; }

  .lib-name {
    display: flex; align-items: center; gap: 8px; min-width: 0; text-decoration: none;
    color: var(--ink); font-size: 13.5px; font-weight: 500;

    &:hover { text-decoration: none; }
    &:hover .lib-title { color: var(--accent-ink); }
  }
  .lib-row--kind .lib-name { gap: 10px; font-size: var(--t-base); }
  .lib-pin-dot { width: 6px; height: 6px; border-radius: 50%; background: #EA580C; flex: none; }
  .lib-tile {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 28px; height: 28px; border-radius: 7px; background: var(--sidebar); color: var(--soft);

    &.is-pinned { background: #FFF1E6; color: #C2410C; }
  }
  .lib-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lib-tag {
    flex: none; font-size: var(--t-xs); font-weight: 500; color: var(--soft);
    background: var(--sidebar); border-radius: 999px; padding: 1px 7px; white-space: nowrap;
  }
  .lib-client { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: var(--t-sm); color: var(--ink-2); }
  .lib-client .client-chip { width: 24px; height: 24px; border-radius: 6px; font-size: 8.5px; }
  .lib-client-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lib-meta { font-size: var(--t-sm); color: var(--soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lib-meta--mono { font: 500 11.5px var(--mono); }
  .lib-chevron { display: flex; color: var(--dim); }
  .lib-rowmenu { display: flex; justify-content: center; }
  /* The ⋯ appears when the row is pointed at, or its menu is open. */
  .lib-rowmenu .row-menu-btn { width: 28px; height: 28px; border-radius: 7px; opacity: 0; transition: opacity 0.12s; }
  .lib-row:hover .row-menu-btn, .lib-rowmenu .row-menu[open] .row-menu-btn, .lib-rowmenu .row-menu-btn:focus-visible { opacity: 1; }
  .lib-rowmenu .row-menu-pop { top: calc(100% + 4px); min-width: 180px; padding: 4px; border-color: var(--border-card); border-radius: 10px; box-shadow: 0 12px 32px -10px oklch(0 0 0 / 0.22); }
  .lib-rowmenu .row-menu-item {
    display: block; width: 100%; padding: 7px 10px; border: 0; border-radius: 7px; background: transparent; text-align: left;
    font: inherit; font-size: 12.5px; font-weight: 400; color: var(--ink-2); cursor: pointer;

    &:hover { background: var(--inset); color: var(--ink); text-decoration: none; }
    &.row-menu-item--danger { color: var(--err); }
    &.row-menu-item--danger:hover { background: var(--err-tint); }
  }
  .lib-rowmenu form { margin: 0; }

  /* Two auto-saving toggles per row. The checkbox is the state; everything visible is
     driven off :has(), so there is no JS beyond the auto-save submit. */
  .lib-flags { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
  .lib-flags form { margin: 0; }
  .lib-toggle {
    position: relative; display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 28px; border-radius: 7px; color: var(--border-control); cursor: pointer;
    transition: color 0.12s, background 0.12s;

    .lib-row:hover & { color: var(--dim); }
    &:hover { background: var(--sidebar); color: var(--ink-2); }
    &:has(.lib-toggle-check:checked) { color: var(--accent-ink); background: var(--accent-tint); }
    &.lib-toggle--pin:has(.lib-toggle-check:checked) { color: #C2410C; background: #FFF1E6; }
    &:has(.lib-toggle-check:focus-visible) { box-shadow: var(--ring); }
    /* The bookmark fills when it is on; the eye never does. */
    &:has(.lib-toggle-check:checked) path[stroke-linejoin] { fill: currentColor; }
  }
  /* The same 28px icon control in the document header, and as a plain button. */
  .lib-icon-btn {
    display: grid; place-items: center; width: 28px; height: 28px; padding: 0;
    border: 1px solid transparent; border-radius: var(--r-md); background: transparent;
    color: var(--chevron); cursor: pointer;

    &:hover { background: var(--inset); }
  }
  .lib-toggle-check { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
  @media (hover: none) {
    .lib-rowmenu .row-menu-btn { opacity: 1; }
    .lib-toggle { color: var(--dim); }
  }

  .lib-table--inherited { max-width: 1080px; }
  .lib-inherited-row {
    display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; gap: 12px; align-items: center;
    padding: 10px 16px; border-top: 1px solid var(--border-row);

    &:first-child { border-top: 0; }
    & .lib-tile { width: 26px; height: 26px; }
    & .lib-title { display: block; font-size: var(--t-base); font-weight: 500; }
  }
  .lib-inherited-main { min-width: 0; }
  .lib-inherited-tag { flex: none; font-size: var(--t-xs); font-weight: 500; color: #9A3412; background: #FFF1E6; border-radius: 999px; padding: 1px 7px; white-space: nowrap; }
  .lib-preview { display: block; font-size: var(--t-sm); color: var(--soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* ── Panels: one card shell for New document, Add from workspace, the record card,
     the body and every group. They were four near-identical cards in the artboard and
     they are one here. ── */
  .lib-panel {
    max-width: 1180px; margin-bottom: 14px;
    overflow: hidden;
  }
  .lib-panel-head {
    display: flex; align-items: center; gap: 10px;
    padding: 13px 18px; border-bottom: 1px solid var(--hairline);
  }
  .lib-panel-title { font-size: var(--t-base); font-weight: 700; color: var(--ink); }
  .lib-panel-sub { flex: 1; min-width: 0; font-size: var(--t-sm); color: var(--dim); }
  .lib-panel-count { margin-left: auto; font-family: var(--mono); font-size: var(--t-xs); color: var(--dim); }
  .lib-panel-x {
    margin-left: auto; font-size: 17px; line-height: 1; color: var(--dim); text-decoration: none;

    &:hover { color: var(--ink); }
  }
  .lib-panel-sub + .lib-panel-x { margin-left: 0; }
  .lib-panel-body { padding: 18px; display: flex; flex-direction: column; gap: 14px; }
  .lib-panel-foot {
    display: flex; align-items: center; justify-content: flex-end; gap: 12px;
    padding: 11px 18px; border-top: 1px solid var(--hairline); background: var(--inset);
  }
  .lib-panel-all { margin-right: auto; font-size: var(--t-sm); font-weight: 600; color: var(--accent); text-decoration: none; }

  /* ── Add-from-workspace picker ── */
  .lib-picker { max-height: 420px; overflow-y: auto; }
  .lib-picker-form { margin: 0; }
  .lib-picker-row {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 12px 18px; border: 0; border-bottom: 1px solid var(--hairline);
    background: transparent; font-family: inherit; text-align: left; cursor: pointer;

    &:hover { background: var(--inset); }
  }
  /* The checkbox is a button face, not an <input> — the row IS the toggle, and a real
     checkbox inside a form that submits on click would fight it. */
  .lib-picker-check {
    display: grid; place-items: center; flex-shrink: 0;
    width: 17px; height: 17px; border-radius: 5px;
    border: 1.5px solid var(--border); background: var(--card); color: transparent;

    &.is-on { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
  }
  .lib-picker-name { flex: 1; min-width: 0; font-size: var(--t-base); font-weight: 700; color: var(--ink);
                     white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lib-picker-where { font-size: var(--t-xs); color: var(--soft); white-space: nowrap; }
  .lib-picker-words { font-family: var(--mono); font-size: var(--t-xs); color: var(--dim); white-space: nowrap; }

  /* One text field for the four places the Library asks for one — a document's name, a
     family, a group's label, a group's name. They were four rules that differed only in
     width and radius; those are the two modifiers. */
  .lib-input {
    width: 100%; padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--r-lg);
    background: var(--card); font-size: var(--t-base); font-weight: 600; color: var(--ink);

    &:focus { outline: none; border-color: var(--accent); }
  }
  .lib-input--pill { flex: 1; max-width: 220px; padding: 7px 11px; border-radius: var(--r-full); font-size: var(--t-base); }
  /* A group's label is a code, not prose: fixed width, centred, and read as one. */

  /* ── New-document panel ── */
  /* auto-fit rather than a fixed four: the artboard drew four tiles and left the third
     document kind it renders with no way to make one. A fifth had to fit. */
  .lib-kinds { display: grid; grid-template-columns: repeat(auto-fit, minmax(136px, 1fr)); gap: 8px; }
  .lib-kind {
    display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
    padding: 12px; border-radius: var(--r-card); border: 1.5px solid var(--border2);
    font-size: var(--t-sm); font-weight: 600; color: var(--soft);

    /* Drawn, not offered: `kind` and its CHECK constraint already exist, so switching
       these on later is additive rather than a migration. */
    &.is-off { opacity: 0.45; }
    & em { font-style: normal; font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.08em;
           text-transform: uppercase; color: var(--dim); }
    /* The radio is the state; the tile is its label. Off-screen rather than display:none
       so it keeps its place in the tab order. */
    &:has(.lib-kind-radio:checked) { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-ink); }
    &:has(.lib-kind-radio:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
  }
  .lib-kind-radio { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
  .lib-body--panel { min-height: 170px; border: 1px solid var(--border); border-radius: var(--r-lg); padding: 12px 13px; }

  .lib-research-only { display: none; }
  .lib-panel-body:has(#document_kind_research:checked) .lib-research-only { display: block; }
  .lib-kind-note { font-size: var(--t-sm); line-height: 1.5; color: var(--dim); }

  .lib-form-options { border: 1px solid var(--hairline); border-radius: var(--r-card); background: var(--inset); }
  .lib-cedit {
    display: flex; align-items: center; gap: 10px; cursor: pointer;
    padding: 10px 14px; border-top: 1px solid var(--hairline);
    font-size: var(--t-base); font-weight: 600; color: var(--soft);

    &:first-child { border-top: 0; }
    & em { font-weight: 400; font-style: normal; color: var(--soft); }
  }
  .lib-errors {
    display: flex; flex-direction: column; gap: 4px;
    padding: 12px 14px; border-radius: var(--r-card);
    background: var(--err-tint); color: var(--err-strong); font-size: var(--t-base);
  }

  /* ── Document page ──
     No rail: the record — id, pin, share, client-can-edit, delete, save — is the page
     header. The Save button therefore sits OUTSIDE the form it submits, which is what the
     `form=` attribute on it is for; the header cannot be inside the form because the pin,
     share and delete controls up there are forms themselves. */
  /* ⚠ The title is a FIELD here, not text, and a field has an intrinsic width (its `size`)
     that a shrink-to-fit parent will happily settle on — leaving "Market Research — Su" in a
     1400px bar. The whole chain has to grow: .title-group takes the free space, its unclassed
     wrapper div passes it on, and the <h1> stops clipping because an input cannot ellipsis
     anyway. Scoped to .lib-doc; no other page puts a control in its title. */
  .lib-doc .title-group { flex: 1 1 auto; }
  .lib-doc .title-group > *:last-child { flex: 1 1 auto; }
  .lib-doc .title { overflow: visible; }
  .lib-doc-title {
    width: 100%; min-width: 0; border: 1px solid transparent; border-radius: var(--r-md);
    background: transparent; padding: 2px 6px; margin-left: -6px;
    font: 800 20px/1.2 inherit; letter-spacing: -0.02em; color: var(--ink);

    /* Editable at rest and bordered only on focus: identical to a heading until you click
       it, which is why there is no rename toggle to build or to keep in step. */
    &:hover:not([readonly]) { border-color: var(--border2); }
    &:focus { outline: none; border-color: var(--accent); background: var(--card); }
  }
  .lib-doc-id { font-family: var(--mono); font-size: var(--t-xs); color: var(--dim); white-space: nowrap; }
  .lib-share-group { display: flex; align-items: center; gap: 6px; }
  /* "Client can edit" is a question only about a shared document, so it shows only while
     the Share box beside it is ticked — read straight off that box, since flicking it
     re-renders nothing. */
  .lib-share-group:not(:has(input[type=checkbox][name="document[share]"]:checked)) .lib-edit-chip { display: none; }
  .lib-edit-chip {
    display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
    border: 1px solid var(--border); border-radius: var(--r-md); cursor: pointer;
    font-size: var(--t-base); font-weight: 600; color: var(--soft); white-space: nowrap;

    &:has(.lib-toggle-check:checked) { border-color: var(--accent-border); background: var(--accent-tint); color: var(--accent-ink); }
  }
  .lib-icon-btn:hover { color: var(--ink); }
  .lib-icon-btn--danger:hover { color: var(--err-strong); }

  /* The Save button and the two footer lines are all one question — is there anything
     unsaved — so one class on the page answers it and nothing needs a Stimulus target. */
  .lib-doc .lib-doc-save { display: none; }
  .lib-doc.is-dirty .lib-doc-save { display: inline-flex; }

  .lib-sync {
    margin: 0 0 14px; padding: 9px 14px; max-width: 1180px;
    background: var(--inset); border: 1px solid var(--hairline); border-radius: var(--r-card);
    font-size: var(--t-sm); color: var(--soft);
  }

  .lib-body {
    display: block; width: 100%; min-height: 420px; resize: vertical;
    border: 0; padding: 18px; background: transparent;
    font-family: var(--mono); font-size: var(--t-base); line-height: 1.75; color: var(--ink);

    &:focus { outline: none; }
  }
  /* A research document's body is its notes — where the work came from, a line or two.
     The groups underneath are what anyone scrolls to. */
  .lib-body--short { min-height: 120px; }
  .lib-dot { width: 5px; height: 5px; border-radius: var(--r-full); background: var(--warn); flex-shrink: 0; }
  .lib-kbd {
    padding: 2px 6px; border: 1px solid var(--border2); border-radius: 5px;
    background: var(--card); font-family: var(--mono); font-size: var(--t-xs); color: var(--dim);
  }

  /* ── Market research: audience groups ── */
  .lib-groupshead { margin-top: 22px; }
  /* One card: a head row (code · name · remove), the description, the word count. The
     code and the name are quiet fields — they read as text until you reach for them. */
  .lib-group { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px 10px; overflow: visible; }
  .lib-group-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
  .lib-group-label {
    flex: none; box-sizing: border-box; height: 24px; min-width: 38px; padding: 0 6px;
    field-sizing: content; text-align: center;
    border: 1px solid transparent; border-radius: 6px; background: var(--inset);
    font: 600 11px/22px var(--mono); color: var(--ink-2); outline: none;
  }
  .lib-group-name {
    flex: 1; min-width: 0; padding: 3px 6px; margin: -3px -6px;
    border: 1px solid transparent; border-radius: 6px; background: transparent;
    font-family: inherit; font-size: 13.5px; font-weight: 600; line-height: 1.4; color: var(--ink); outline: none;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  input.lib-group-label:hover { border-color: var(--border-control); }
  input.lib-group-name:hover { border-color: var(--border-row); background: var(--inset); }
  input.lib-group-label:focus, input.lib-group-name:focus { border-color: var(--ink); background: var(--card); }
  /* The remove button waits until you are on the card — and is always there where nothing hovers. */
  .lib-group-remove { opacity: 0; transition: opacity 120ms; }
  .lib-group:hover .lib-group-remove, .lib-group:focus-within .lib-group-remove { opacity: 1; }
  @media (hover: none) { .lib-group-remove { opacity: 1; } }
  .lib-body--group {
    width: auto; min-height: 0; margin: 0 -6px; padding: 3px 6px; resize: none; overflow: hidden;
    border: 1px solid transparent; border-radius: 6px; background: transparent;
    font-family: inherit; font-size: 13px; line-height: 1.6; color: var(--ink-2);

    &:not([readonly]):hover { border-color: var(--border-row); background: var(--inset); }
    &:not([readonly]):focus { border-color: var(--ink); background: var(--card); color: var(--ink); box-shadow: none; outline: none; }
  }
  .lib-group .lib-panel-count { align-self: flex-end; margin: 0; font-size: 10.5px; font-weight: 500; color: var(--chevron); }
  .lib-addgroup {
    display: flex; align-items: center; justify-content: center; gap: 7px;
    width: 100%; max-width: 1180px; padding: 11px; margin-bottom: 14px;
    border: 1px dashed var(--border); border-radius: var(--r-lg); background: transparent;
    font: 600 var(--t-base)/1 inherit; color: var(--soft); cursor: pointer;

    &:hover { border-color: var(--accent); color: var(--accent); }
  }

  /* ── The move form (documents#edit) — reached from a row's ⋯ menu, which since v2 is
     the only route to it in the app. ── */
  .lib-form {
    padding: 18px; margin-top: 16px; max-width: 940px;
    & .lib-body { min-height: 300px; border: 1px solid var(--border); border-radius: var(--r-lg); padding: 12px 13px; }
    & .lib-form-options { margin-top: 14px; }
  }
  .lib-label { display: block; margin: 14px 0 6px; font-size: var(--t-sm); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--dim); }
  .lib-home-select { width: 100%; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--inset); font-family: inherit; font-size: var(--t-base); color: var(--ink); }
  .lib-home-note { font-size: var(--t-sm); color: var(--dim); margin: 6px 0 0; }
  .lib-form-foot { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
  /* Serves both a link and a button, so it resets the button chrome rather than assuming
     an anchor. */
  .lib-cancel {
    border: 0; background: transparent; padding: 0; cursor: pointer;
    font: 600 12.5px inherit; color: var(--soft); text-decoration: none;

    &:hover { color: var(--ink); }
  }

  /* The document header carries the whole record — id, two toggles, a chip, delete, save —
     so it runs out of room for the title long before the app's 700px breakpoint. Wrapping
     the title onto its own line is what the artboard does at 820px; this is the same move,
     earlier, because our chip is wider than its. */
  @media (max-width: 1040px) {
    .lib-doc .tool-header { flex-wrap: wrap; row-gap: 10px; }
    .lib-doc .title-group { flex: 1 1 100%; }
  }

  /* Phone. The document header carries a crumb, a tile, a title field and five controls,
     which is two more than fit — so the parts that are context rather than action go: the
     crumb keeps its back link and drops the scope it names (the page below says it), and
     the kind tile goes, because the page you are on is not in doubt. */
  @media (max-width: 700px) {
    .lib-doc .tool-badge, .lib-crumb-sep, .lib-crumb-scope, .lib-doc .lib-divider { display: none; }
  }

  /* ── Document page ──
     One reading column: the way back and the actions, the name large, what is true of the
     document as quiet switches, then the writing. Scoped under .lib-doc so these outrank the older panel
     rules above without restating them. */
  .lib-doc-col { display: flex; flex-direction: column; gap: 18px; width: 100%; max-width: 880px; margin: 0 auto; padding-top: 16px; }
  .lib-doc-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
  .lib-doc-crumb { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; padding: 0; font-size: 12.5px; color: var(--soft); white-space: nowrap; }
  .lib-doc-crumb .lib-back { font-size: 12.5px; font-weight: 500; gap: 3px; }
  .lib-doc-crumb .lib-back span:first-child { font-size: inherit; }
  .lib-doc-crumb .lib-crumb-scope { font-size: 12.5px; font-weight: 500; }
  .lib-doc-actions { display: flex; align-items: center; gap: 8px; }
  .lib-doc-actions form { margin: 0; }
  .lib-doc-copy.is-copied { color: var(--ok-strong); border-color: color-mix(in srgb, var(--ok) 35%, var(--card)); }
  .lib-doc .lib-kbd { margin: 0; opacity: 0.7; }
  .lib-doc-dirty { display: inline-flex; visibility: hidden; align-items: center; gap: 6px; font-size: var(--t-sm); font-weight: 500; color: var(--amber-ink); white-space: nowrap; }
  .lib-doc-dirty .lib-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--warn); }
  .lib-doc.is-dirty .lib-doc-dirty { visibility: visible; }
  /* ⚠ Save is always there, as in the mockup — the flag beside it says whether it has
     anything to do. It used to appear only once dirty, which moved the trash sideways. */
  .lib-doc .lib-doc-save, .lib-doc.is-dirty .lib-doc-save { display: inline-flex; }
  .lib-doc .lib-icon-btn--danger { width: 30px; height: 30px; border-radius: var(--r-md); color: var(--soft); }
  .lib-doc .lib-icon-btn--danger:hover { background: var(--err-tint); color: var(--err); }

  .lib-doc-head { display: flex; flex-direction: column; gap: 8px; }
  .lib-doc .lib-doc-title {
    width: 100%; min-width: 0; padding: 4px 8px; margin: -4px -8px; border: 1px solid transparent; border-radius: var(--r-md);
    background: transparent; font: inherit; font-size: 26px; font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; color: var(--ink);

    &:hover:not([readonly]) { border-color: var(--border-row); background: var(--inset); }
    &:focus { outline: 0; border-color: var(--ink); background: var(--card); box-shadow: none; }
  }
  .lib-doc-meta { display: flex; align-items: center; gap: 6px 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--soft); }
  .lib-doc-meta > span { white-space: nowrap; }
  .lib-doc-meta .lib-crumb-sep { color: var(--dim); }
  .lib-doc-kind {
    display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 8px; border-radius: 999px;
    font-size: 11.5px; font-weight: 600; background: var(--sidebar); color: var(--ink-2);
  }
  .lib-doc-kind--formats { background: #F1ECFE; color: #6D4FC2; }
  .lib-doc-kind--research { background: var(--accent-tint); color: var(--accent-ink); }
  .lib-doc .lib-doc-id { font: 500 var(--t-xs) var(--mono); color: var(--dim); }

  /* The properties: a switch and its words. The checkbox is invisible and covers the label. */
  .lib-props { display: flex; align-items: center; gap: 2px 14px; flex-wrap: wrap; padding-top: 6px; }
  .lib-props:not(:has(*)) { display: none; }
  .lib-props form { margin: 0; }
  .lib-props .lib-share-group { gap: 2px 14px; flex-wrap: wrap; }
  .lib-prop {
    position: relative; display: inline-flex; align-items: center; gap: 7px; height: 26px; padding: 0 6px; margin: 0 -6px;
    border: 0; border-radius: 6px; background: transparent; font-size: 12.5px; font-weight: 500; color: var(--soft);
    white-space: nowrap; cursor: pointer;

    &:hover { background: var(--sidebar); color: var(--ink); }
    &:has(.lib-toggle-check:checked) { color: var(--ink); }
    &:has(.lib-toggle-check:focus-visible) { box-shadow: var(--ring); }
  }
  .lib-prop-track {
    position: relative; flex: none; width: 26px; height: 15px; border-radius: 999px;
    background: var(--border-control); transition: background 0.15s;

    &::after {
      content: ""; position: absolute; left: 2px; top: 2px; width: 11px; height: 11px; border-radius: 50%;
      background: #fff; box-shadow: 0 1px 2px oklch(0 0 0 / 0.2); transition: transform 0.15s;
    }
  }
  .lib-prop:has(.lib-toggle-check:checked) .lib-prop-track { background: var(--ink); }
  .lib-prop:has(.lib-toggle-check:checked) .lib-prop-track::after { transform: translateX(11px); }
  /* The older chip look this label used to wear, undone: it is a switch like its siblings. */
  .lib-props .lib-edit-chip { height: 26px; padding: 0 6px; border: 0; background: transparent; }
  .lib-props .lib-edit-chip:has(.lib-toggle-check:checked) { border: 0; background: transparent; color: var(--ink); }
  .lib-props .lib-sync { display: inline-flex; align-items: center; margin: 0; padding: 0; border: 0; background: none; font-size: var(--t-sm); color: var(--accent-ink); }

  /* The writing. The card's head is a small caps label and the count, not a title bar. */
  .lib-doc-form { display: flex; flex-direction: column; gap: 18px; margin: 0; }
  .lib-doc .lib-bodycard { max-width: none; margin: 0; overflow: hidden; }
  .lib-doc .lib-bodycard > .lib-panel-head { display: flex; align-items: baseline; gap: 8px; padding: 14px 22px 0; border: 0; background: none; }
  .lib-doc .lib-bodycard .lib-panel-title { font-size: 11.5px; font-weight: 600; color: var(--soft); letter-spacing: 0.04em; text-transform: uppercase; }
  .lib-doc .lib-bodycard .lib-panel-count { margin: 0; font: 500 var(--t-xs) var(--mono); color: var(--dim); }
  .lib-doc .lib-bodycard .lib-body {
    display: block; width: 100%; min-height: 280px; padding: 10px 22px 26px; border: 0; border-radius: 0; background: none;
    font-family: -apple-system, "Helvetica Neue", Helvetica, system-ui, sans-serif;
    font-size: 15px; line-height: 1.75; color: var(--ink); resize: none; outline: 0;
  }
  .lib-doc .lib-bodycard .lib-body--short { min-height: 110px; }

  /* Personas and angles: a heading, then cards two-up. */
  .lib-doc .lib-groups { display: flex; flex-direction: column; gap: 10px; max-width: none; }
  .lib-doc .lib-groupshead { display: flex; align-items: baseline; gap: 8px; margin: 4px 2px 0; }
  .lib-doc .lib-groupshead h2 { font-size: 15px; font-weight: 600; }
  .lib-doc .lib-groupshead .lib-count { margin: 0; font-size: var(--t-sm); color: var(--soft); background: none; padding: 0; }
  .lib-doc .lib-groups [data-row-list-target="rows"] { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .lib-doc .lib-group { margin: 0; max-width: none; }
  .lib-doc .lib-group:focus-within { border-color: var(--soft); box-shadow: 0 0 0 3px oklch(0 0 0 / 0.04); }
  .lib-doc .lib-groups-empty { padding: 16px; border: 1.5px dashed var(--border-control); border-radius: var(--r-card); background: none; text-align: center; font-size: 12.5px; color: var(--soft); }
  .lib-doc .lib-addgroup { align-self: flex-start; }
  @media (max-width: 700px) {
    /* ⚠ !important, to outrank the phone rule that forces every input to 16px so iOS does
       not zoom on focus. 21px is above that threshold, so the rule's purpose still holds. */
    .lib-doc .lib-doc-title { font-size: 21px !important; }
    .lib-doc .lib-kbd, .lib-doc-copy .cd-mini-label { display: none; }
    .lib-doc .lib-bodycard > .lib-panel-head { padding: 12px 16px 0; }
    /* ⚠ 16px, and on BOTH layers. The field is forced to 16px by that same phone rule; the
       highlight mirror behind it is a div and is not, so at any other size the two lay the
       same words out on different lines and the text reads doubled. */
    .lib-doc .lib-bodycard .lib-body { padding: 8px 16px 20px; font-size: 16px; }
    .lib-doc .lib-groups .lib-body { font-size: 16px; }
    .lib-doc-crumb { flex-basis: 100%; }
    .lib-doc-actions { width: 100%; justify-content: flex-end; }
    .lib-doc .lib-groups [data-row-list-target="rows"] { grid-template-columns: 1fr; }
  }

  /* Narrower, the byline goes first, then the client's name, then the columns themselves. */
  @media (max-width: 1100px) {
    .lib-row { grid-template-columns: minmax(0, 1fr) 68px 28px 14px; }
    .lib-row--client { grid-template-columns: minmax(0, 1fr) 30px 68px 28px 14px; }
    .lib-row--kind, .lib-row--kind.lib-row--client { grid-template-columns: minmax(0, 1fr) 68px 28px 14px; }
    .lib-row .lib-meta, .lib-client-name, .lib-row--kind .lib-client { display: none; }
  }
  @media (max-width: 900px) {
    .lib-row--client { grid-template-columns: minmax(0, 1fr) 68px 28px 14px; }
    .lib-row .lib-client { display: none; }
  }
  @media (max-width: 700px) {
    .lib-controls .search-pill { width: 100%; }
    .lib-controls .filter-menu { flex: 1; }
    .lib-count, .lib-shelf-legend { display: none; }
  }
  @media (max-width: 900px) {
    .lib-kinds { grid-template-columns: 1fr 1fr; }
    .lib-doc-title { font-size: 17px; }
  }
}

/* ── Queue (/queue) ───────────────────────────────────────────────────────────
   The full-page queue, built to explorations/opensource-direction/queue.html.
   Throughput is the whole point: every row answers "what happens to this, and who
   does it reach" in the same column, from a button that says the current answer.
   Nothing here re-renders on a click — the radio commits visually before the
   request leaves, and the counts are the only thing JS touches. The picker's
   label, the recipient line, selection tint, the expanded editor, the schedule
   date and the batch bar are all CSS off real form controls and the row's
   data-disposition. */
@layer components {
  .view.q { --page-gutter: max(20px, (100% - var(--content-max)) / 2); }
  .q-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex-shrink: 0; padding: 16px var(--page-gutter) 0; }
  .q-toolbar .seg-filter { margin: 0; }
  .q-toolbar .filter-menu--select { flex: 0 0 200px; }
  .q-toolbar .filter-select { width: 100%; min-width: 0; }

  .q .campaigns-body { padding-block: 14px 96px; }
  .q-list { display: flex; flex-direction: column; gap: 14px; }

  .q-selectall { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-height: 22px; padding: 2px 4px 14px; font-size: 12.5px; color: var(--ink-2); }
  .q-check-wrap { display: inline-flex; align-items: center; gap: 8px; flex: none; white-space: nowrap; cursor: pointer; }
  .q-legend { margin-left: auto; display: flex; gap: 14px; flex-wrap: wrap; font-size: var(--t-sm); color: var(--soft); }
  .q-legend-item { display: inline-flex; align-items: center; gap: 6px; }

  /* The four dispositions' dots: the legend, the picker button, the batch menu. */
  .q-dot, .q-pick-dot { width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--dim); }
  .q-dot--send, .q-row[data-disposition="send"] .q-pick-dot { background: var(--ok); }
  .q-dot--schedule, .q-row[data-disposition="schedule"] .q-pick-dot { background: var(--accent); }
  .q-dot--hold, .q-row[data-disposition="hold"] .q-pick-dot { background: var(--badge-amber); }

  /* One shared checkbox for row, group and select-all. */
  .q-check {
    appearance: none; display: inline-grid; place-content: center; flex: none; width: 16px; height: 16px; margin: 0;
    border: 1px solid var(--border-control); border-radius: 4px; background: var(--card); cursor: pointer;
    transition: opacity 0.15s, background 0.12s;

    &:checked, &:indeterminate { background: var(--ink); border-color: var(--ink); }
    &:checked::before { content: ""; width: 9px; height: 5px; border-left: 2px solid var(--card); border-bottom: 2px solid var(--card); transform: translateY(-1px) rotate(-45deg); }
    &:indeterminate::before { content: ""; width: 8px; height: 2px; background: var(--card); }
    &:focus-visible { outline: 0; box-shadow: 0 0 0 2px color-mix(in srgb, var(--ink) 12%, transparent); }
  }
  /* Checkboxes recede until you reach for them — and all come forward once one is ticked. */
  @media (hover: hover) {
    .q-row .q-check, .q-group-head .q-check { opacity: 0; }
    .q-row:hover .q-check, .q-group-head:hover .q-check, .q-check:checked, .q-check:indeterminate,
    .q-check:focus-visible, .q:has(.q-select:checked) .q-check, .q-row.is-cursor .q-check { opacity: 1; }
  }

  /* ── Client group card ── ⚠ not clipped: a row's picker drops out of it. */
  /* ⚠ 31, one over the batch bar's 30: a picker opened on a low row would otherwise drop
     its menu underneath the bar, with the bar's buttons showing through it. */
  .q-group:has(.q-pick[open]) { position: relative; z-index: 31; }
  .q-group-head { display: flex; align-items: center; gap: 12px; padding: 10px 14px 10px 16px; border-bottom: 1px solid var(--border-divider); }
  .q-group-head .client-chip { width: 30px; height: 30px; font-size: 10.5px; }
  .q-group-name { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
  .q-group-title { font-size: 13.5px; font-weight: 600; }
  .q-group-sub { font-size: var(--t-sm); color: var(--soft); }
  .q-group-send { margin: 0; }
  .q-group-send .btn { height: 28px; padding: 0 10px; font-size: 12.5px; }

  /* ── Row ── */
  .q-expand { display: none; }
  .q-row { position: relative; border-top: 1px solid var(--border-row); transition: background 0.15s; }
  .q-rows > .q-row:first-child { border-top: 0; }
  .q-rows > .q-row:last-child { border-radius: 0 0 11px 11px; }
  .q-row:has(.q-pick[open]) { z-index: 6; }
  .q-row:hover { background: var(--inset); }
  .q-row:has(.q-expand:checked) { background: var(--card); }
  .q-row > form { margin: 0; }
  .q-row-main { display: grid; grid-template-columns: 16px minmax(0, 1fr) 188px 28px; gap: 14px; align-items: start; padding: 13px 14px 13px 16px; }
  .q-row-main > .q-check { margin-top: 3px; }

  .q-msg { display: block; min-width: 0; cursor: pointer; }
  .q-body { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 13.5px; line-height: 1.5; color: var(--ink); text-wrap: pretty; }
  .q-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 5px; font-size: var(--t-sm); color: var(--soft); }
  .q-meta i { font-style: normal; color: var(--dim); }
  .q-when { font-family: var(--mono); font-size: 11.5px; }

  /* The decision column: what happens + who it reaches, read together. */
  .q-decide { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; min-width: 0; }
  .q-to, .q-to-record { display: inline-flex; align-items: center; gap: 5px; max-width: 100%; font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .q-to svg { flex: none; color: var(--dim); }
  .q-to--nobody { padding: 2px 7px 2px 5px; border-radius: var(--r-full); background: var(--amber-tint); color: var(--amber-ink); }
  .q-to--nobody svg { color: var(--badge-amber); }
  .q-to-record { font-weight: 400; color: var(--soft); white-space: normal; text-align: right; }
  /* ⚠ The one pair of rules that makes the line honest the instant a disposition is clicked:
     queue_controller.js rewrites data-disposition client-side (it must, or the page's central
     control would round-trip), so both states ship and CSS chooses. Hold keeps the names —
     that is who it reaches when it is released. */
  .q-row[data-disposition="record"] .q-to { display: none; }
  .q-row:not([data-disposition="record"]) .q-to-record { display: none; }

  /* ── The disposition picker ── */
  .q-pick { position: relative; }
  .q-pick > summary { list-style: none; }
  .q-pick > summary::-webkit-details-marker { display: none; }
  .q-pick-btn {
    display: inline-flex; align-items: center; gap: 7px; height: 28px; padding: 0 7px 0 10px; cursor: pointer;
    border: 1px solid var(--border-control); border-radius: 7px; background: var(--card);
    font-size: 12.5px; font-weight: 500; color: var(--ink); transition: border-color 0.12s, box-shadow 0.12s;
  }
  .q-pick-btn svg { color: var(--dim); transition: transform 0.18s; }
  .q-pick-btn:hover { border-color: var(--dim); }
  .q-pick[open] .q-pick-btn { border-color: var(--ink); box-shadow: var(--ring); }
  .q-pick[open] .q-pick-btn svg { transform: rotate(180deg); }
  /* The button says all four words; the row's data-disposition picks the one that shows. */
  .q-pick-label { display: none; }
  .q-row[data-disposition="send"] .q-pick-label--send,
  .q-row[data-disposition="schedule"] .q-pick-label--schedule,
  .q-row[data-disposition="hold"] .q-pick-label--hold,
  .q-row[data-disposition="record"] .q-pick-label--record { display: inline; }

  .q-disp {
    position: absolute; top: calc(100% + 6px); right: 0; z-index: 40; width: 236px; margin: 0; padding: 4px;
    display: flex; flex-direction: column; background: var(--card); border: 1px solid var(--border-card);
    border-radius: 10px; box-shadow: 0 12px 32px -10px oklch(0 0 0 / 0.22);
  }
  .q-disp.is-flipped { top: auto; bottom: calc(100% + 6px); }
  .q-disp-target { display: grid; grid-template-columns: 16px minmax(0, 1fr) 14px; gap: 10px; align-items: center; padding: 7px 9px; border-radius: 7px; color: var(--ink-2); text-align: left; cursor: pointer; }
  .q-disp-target:hover, .q-disp-target:has(:focus-visible) { background: var(--inset); }
  .q-disp-target > svg { color: var(--dim); }
  .q-disp-text b { display: block; font-size: 12.5px; font-weight: 500; color: var(--ink); }
  .q-disp-text small { display: block; margin-top: 1px; font-size: 11.5px; color: var(--soft); }
  /* Hidden to the eye, present to a screen reader and to the keyboard. Zeroing
     width/height (or opacity alone) drops a control out of the accessibility
     tree, which would cost the radiogroup its whole point. */
  .q-disp-input { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .q-disp-check { display: inline-flex; color: var(--ink); visibility: hidden; }
  .q-disp-target:has(:checked) .q-disp-check { visibility: visible; }
  .q-disp-target:has(:checked) > svg { color: var(--ink); }
  .q-disp-send:has(:checked) > svg { color: var(--ok); }
  .q-disp-schedule:has(:checked) > svg { color: var(--accent); }
  .q-disp-hold:has(:checked) > svg { color: var(--badge-amber); }

  .q-chevron { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 7px; color: var(--dim); cursor: pointer; }
  .q-chevron:hover { background: var(--icon-tile); color: var(--ink); }
  .q-chevron svg { transition: transform 0.2s; }

  /* The keyboard cursor is a left rail, and is a separate idea from selection —
     a row can be both. */
  .q-row.is-cursor { box-shadow: inset 3px 0 0 var(--ink); }

  /* Selection tint and the open state follow the controls directly — no restream. */
  .q-row:has(.q-select:checked) { background: var(--accent-tint); }
  /* Open, the two-line preview steps aside: the editor below is the same text, whole. */
  .q-row:has(.q-expand:checked) .q-msg .q-body { display: none; }
  .q-row:has(.q-expand:checked) .q-msg .q-meta { margin-top: 0; }
  .q-row:has(.q-expand:checked) .q-chevron svg { transform: rotate(180deg); }
  /* A held row dims where it sits. It must NOT leave the list: a row that
     vanishes shifts every row under a cursor travelling down the column. */
  .q-row[data-disposition="hold"] .q-msg { opacity: 0.55; }

  /* ── Expanded editor ── */
  .q-editor { display: none; padding: 2px 16px 16px 46px; animation: fadeIn 0.18s ease; }
  .q-row:has(.q-expand:checked) .q-editor { display: block; }
  .q-remove-form { display: none; }
  .q-field-label { display: block; margin-bottom: 5px; font-size: 11.5px; font-weight: 600; letter-spacing: 0.01em; color: var(--soft); }
  .q-textarea, .q-editor input[type="date"] {
    padding: 8px 10px; border: 1px solid var(--border-control); border-radius: var(--r-md); background: var(--card);
    font: inherit; font-size: 13.5px; line-height: 1.5; color: var(--ink); outline: 0;
    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
  }
  .q-textarea { width: 100%; min-height: 76px; resize: vertical; }
  .q-recap .q-textarea { min-height: 52px; }
  .q-editor input[type="date"] { height: 32px; padding-block: 0; font-size: var(--t-base); }
  .q-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-top: 14px; }
  .q-field--wide { grid-column: 1 / -1; }
  .q-row:not([data-disposition="schedule"]) .q-field--send { display: none; }
  .q-origin { font-size: var(--t-base); color: var(--ink); }
  .q-editor-foot { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-row); font-size: var(--t-sm); color: var(--dim); }
  .q-saves-done { display: none; align-items: center; gap: 5px; color: var(--ok-strong); }
  form.is-saved .q-saves-idle { display: none; }
  form.is-saved .q-saves-done { display: inline-flex; }
  .q-remove { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border: 0; border-radius: 7px; background: none; font: inherit; font-size: 12.5px; font-weight: 500; color: var(--soft); cursor: pointer; }
  .q-remove:hover { color: var(--err); background: var(--err-tint); }

  /* ── Sent view rows (read-only history) ── */
  .q-row--sent { padding: 11px 16px; }
  .q-row--sent:hover { background: none; }
  .q-row--sent .q-body { display: block; }
  .q-row--sent .q-meta { margin-top: 3px; }

  /* ── Batch bar ── Ink, like every bar that floats over the page. */
  .q-batch-form { display: none; }
  .q-batch {
    position: absolute; left: 50%; bottom: 16px; z-index: 30; width: max-content; max-width: calc(100% - 32px);
    display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 14px; border-radius: var(--r-card);
    background: color-mix(in srgb, var(--ink) 92%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    color: var(--text-on-inverse); font-size: var(--t-base);
    box-shadow: 0 10px 30px oklch(0 0 0 / 0.25), inset 0 1px 0 oklch(1 0 0 / 0.08);
    opacity: 0; transform: translate(-50%, 10px) scale(0.98); pointer-events: none; visibility: hidden;
    transition: opacity 0.18s, transform 0.18s, visibility 0s 0.18s;
  }
  .q:has(.q-select:checked) .q-batch { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; visibility: visible; transition: opacity 0.18s, transform 0.18s; }
  .q-batch-count { margin-right: 4px; padding-right: 12px; border-right: 1px solid oklch(1 0 0 / 0.14); font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
  .q-batch-btn {
    display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px; white-space: nowrap; list-style: none;
    border: 0; border-radius: 7px; background: none; color: oklch(1 0 0 / 0.85); cursor: pointer;
    font: inherit; font-size: 12.5px; font-weight: 500;
    &:hover { background: oklch(1 0 0 / 0.1); color: var(--text-on-inverse); }
    &::-webkit-details-marker { display: none; }
  }
  .q-batch-btn--primary { background: var(--card); color: var(--ink); &:hover { background: var(--inset); color: var(--ink); } }
  .q-batch-clear { width: 30px; padding: 0; justify-content: center; color: oklch(1 0 0 / 0.6); }
  .q-more { position: relative; }
  .q-more[open] > summary svg { transform: rotate(180deg); }
  .q-more-options { right: 0; top: auto; bottom: calc(100% + 8px); min-width: 220px; padding: 4px; gap: 0; color: var(--ink); border-color: var(--border-card); border-radius: 10px; box-shadow: 0 8px 24px -8px oklch(0 0 0 / 0.2); }
  .q-more-options hr { width: 100%; border: 0; border-top: 1px solid var(--border-divider); margin: 4px 0; }
  .q-more-item {
    display: flex; align-items: center; gap: 6px; width: 100%; padding: 8px 10px; text-align: left;
    border: 0; border-radius: 7px; background: none; cursor: pointer;
    font: inherit; font-size: 12.5px; font-weight: 500; color: var(--ink-2);
    &:hover { background: var(--inset); color: var(--ink); }
    &.is-destructive { color: var(--err); &:hover { background: var(--err-tint); color: var(--err); } }
  }

  /* ── Empty state ── */
  .q-empty { padding: 40px 20px; text-align: center; }
  .q-empty > svg { color: var(--dim); }
  .q-empty-title { margin: 10px 0 0; font-size: 15px; font-weight: 600; }
  .q-empty-body { margin: 4px 0 0; font-size: 12.5px; color: var(--soft); text-wrap: pretty; }
  .q-empty-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 14px; }

  .q-short { display: none; }
}

@media (max-width: 700px) {
  @layer components {
    .q-toolbar { flex-direction: column; align-items: stretch; padding-top: 12px; }
    /* Five segments don't fit 375px: the strip scrolls rather than the page. */
    .q-toolbar .seg-filter { width: 100%; overflow-x: auto; scrollbar-width: none; }
    .q-toolbar .seg-filter .seg { flex: 1 0 auto; justify-content: center; }
    .q-toolbar .filter-menu--select { flex: none; width: 100%; }
    .q-long { display: none; }
    .q-short { display: inline; }
    .q-legend { display: none; }
    /* The page's one primary action gets the full width and a thumb's height. */
    .q-send-form { width: 100%; }
    .q-send-form .btn { width: 100%; height: 40px; justify-content: center; }

    /* The decision drops under the message: the button at the left, who it reaches at the
       right, with the chevron staying up beside the text. */
    .q-row-main { grid-template-columns: 16px minmax(0, 1fr) 28px; grid-template-areas: "c m v" "c d v"; gap: 10px 12px; padding: 12px; }
    .q-row-main > .q-check { grid-area: c; }
    .q-msg { grid-area: m; }
    .q-chevron { grid-area: v; }
    .q-decide { grid-area: d; flex-direction: row; align-items: center; justify-content: space-between; gap: 10px; }
    .q-pick-btn { height: 34px; }
    .q-disp { right: auto; left: 0; width: min(280px, 80vw); }
    .q-disp-target { padding: 10px; }
    .q-editor { padding: 4px 12px 12px; }
    .q-batch { bottom: 12px; width: calc(100% - 24px); justify-content: space-between; padding-left: 12px; }
    .q-batch-count { border: 0; padding-right: 0; }
  }
}

@media (prefers-reduced-motion: reduce) {
  @layer components {
    .q-editor { animation: none; }
    .q-batch { transition: none; }
  }
}

/* ── Docket ───────────────────────────────────────────────────────────────────
   The sweep, its ledger, and the actions registry. The brief's palette is the
   design system's already: its canvas var(--page) is --page, var(--border2) is --border2,
   var(--ink) is --ink, var(--accent) is --accent and var(--badge-amber) is --badge-amber. Only its
   success green (#2F9E6E) has no token, and --ok is a percent away — one shade
   is not worth a thirteenth colour variable.

   ⚠ This column is CENTRED, unlike the Queue's, which is deliberately left
   aligned to match the Clients roster. A sweep is one card you look at; a queue
   is a list you scan down. */
@layer components {
  .view.dk { --page-gutter: max(20px, (100% - var(--content-max)) / 2); }
  .dk-column { padding: 16px 0 60px; display: flex; flex-direction: column; gap: 16px; }

  /* How much of the day is left: a bar per proposal, and the count. */
  .dk-progress { display: flex; align-items: center; gap: 12px; padding: 0 2px; }
  .dk-dots { display: flex; gap: 3px; flex: 1; max-width: 320px; }
  .dk-dot-bar { flex: 1; height: 4px; border-radius: 2px; background: var(--border-card);
                &.is-passed { background: var(--ink-2); }
                &.is-current { background: var(--accent); } }
  .dk-progress-count { font: 500 12px/1 var(--mono); color: var(--soft); white-space: nowrap; }

  /* The proposal. On the sweep it shares one card with its controls (.dk-stage); in
     History's detail it stands alone. To docket.html's .dk / .where / .stamp / .why. */
  .dk-stage, .dk-card, .dk-receipt { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .dk-stage > .dk-card { border: 0; border-radius: 0; box-shadow: none; background: none; }
  .dk-card { position: relative; display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 14px; padding: 18px; }
  .dk-card > .client-chip { width: 34px; height: 34px; font-size: var(--t-xs); }
  .dk-card-main { min-width: 0; }
  .dk-card-head { display: flex; align-items: center; gap: 8px; min-width: 0; min-height: 34px; }
  /* ⚠ min-width: 0 or the flex item refuses to shrink and the ellipsis never fires. */
  .dk-where { flex: 1; min-width: 0; font-size: 12.5px; color: var(--soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .dk-where b { font-weight: 600; color: var(--ink); }
  .dk-where i { margin: 0 8px; font-style: normal; color: var(--dim); }
  .dk-card-head .status-pill { flex: none; font-size: var(--t-sm); }
  .dk-title { margin: 12px 0 0; max-width: 34ch; font-size: 19px; font-weight: 600; letter-spacing: -0.012em; line-height: 1.25; color: var(--ink); text-wrap: pretty; }
  .dk-body { margin: 8px 0 0; max-width: 66ch; font-size: var(--t-lg); line-height: 1.55; color: var(--ink); text-wrap: pretty; }
  /* The sentence that fired, as one quiet line. */
  .dk-stamp { display: flex; align-items: flex-start; gap: 8px; margin: 14px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--ink-2); text-wrap: pretty; }
  .dk-rule { flex: none; margin-top: 1px; padding: 3px 5px; border: 1px solid var(--border-control); border-radius: 5px; background: var(--inset); font: 600 10px/1 var(--mono); color: var(--ink-2); text-decoration: none; &:hover { border-color: var(--dim); color: var(--ink); } }
  .dk-stamp-trigger::before { content: "Fired by "; color: var(--dim); }
  /* The reasoning, as a labelled inset. */
  .dk-why { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; align-items: start; margin-top: 14px; padding: 12px 14px; border: 1px solid var(--border-divider); border-radius: 10px; background: var(--inset); }
  .dk-why-label { display: inline-flex; align-items: center; height: 20px; margin-top: 1px; padding: 0 7px; border: 1px solid var(--border-control); border-radius: 5px; background: var(--card); font: 600 10.5px/1 var(--mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-2); }
  .dk-why-body { margin: 0; max-width: 66ch; font-size: var(--t-base); line-height: 1.55; color: var(--ink); text-wrap: pretty; }
  .dk-again { display: flex; align-items: flex-start; gap: 8px; margin-top: 12px; padding: 8px 12px; border-radius: var(--r-md); background: var(--accent-tint); font-size: 12.5px; line-height: 1.45; color: var(--accent-ink); text-decoration: none; }
  .dk-again svg { flex: none; margin-top: 1px; }
  .dk-again:hover { filter: brightness(0.98); }
  .dk-source { display: flex; align-items: center; gap: 6px; margin: 12px 0 0; font-size: 12.5px; color: var(--soft); }
  .dk-source svg { flex: none; color: var(--dim); }
  .dk-source a { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--border-control); }
  .dk-source a:hover { color: var(--ink); border-bottom-color: var(--ink-2); }

  .dk-verdict { display: inline-flex; align-items: center; gap: 6px; flex: none; font-size: var(--t-sm); font-weight: 500; color: var(--soft); white-space: nowrap; }
  .dk-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
  /* Never colour alone: each dot sits beside its own word. */
  .dk-verdict--approved, .dk-verdict--executed { color: var(--ok-strong); }
  .dk-verdict--dismissed { color: var(--soft); font-weight: 400; }

  /* Decide: the card's footer. The key hints at the left, the answers at the right, and
     Approve — the only filled button on the screen — last.
     ⚠ `order`, because the DOM order is the KEY order (see _sweep). */
  .dk-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: var(--card-footer-pad); border-top: 1px solid var(--border-divider); background: var(--inset); border-radius: 0 0 11px 11px; }
  /* Written by the controller from the buttons actually rendered. */
  .dk-keys { order: 0; flex: 1; min-width: 0; margin: 0; font-size: var(--t-sm); color: var(--soft); }
  .dk-form { order: 1; display: flex; margin: 0; }
  .dk-back-key { order: 2; }
  .dk-next { order: 3; }
  .dk-decide--dismiss { order: 4; }
  .dk-decide--approve { order: 5; }
  .dk-decide > summary { list-style: none; &::-webkit-details-marker { display: none; } }

  /* A verdict's note opens IN the footer: it takes the whole row and every other control
     steps aside — including the OTHER drawer, which is what keeps two of them from being
     open at once. So the proposal stays readable above it and there is one thing to answer.
     ⚠ Excluding .dk-decide[open] rather than .dk-decide: exempt them both and the drawer
     you didn't open stays sitting there, live, beside the one you did. */
  .dk-decide[open] { flex: 1 0 100%; & > summary { display: none; } }
  .dk-controls:has(.dk-decide[open]) { padding: 12px 16px 14px; background: var(--card); }
  .dk-controls:has(.dk-decide[open]) > *:not(.dk-decide[open]) { display: none; }
  .dk-drawer form { margin: 0; }
  .dk-drawer-label { display: block; font-size: 12.5px; font-weight: 500; color: var(--ink); }
  .dk-optional { margin-left: 4px; font-size: inherit; font-weight: 400; color: var(--dim); }
  /* ⚠ font-weight after `font: inherit`, and load-bearing: the textarea sits inside its
     own <label> (which is how it gets away with having no id), and the label is 500. */
  .dk-note { display: block; width: 100%; min-height: 56px; margin-top: 8px; padding: 8px 10px;
             background: var(--card); border: 1px solid var(--border-control); border-radius: var(--r-md);
             font: inherit; font-size: var(--t-base); font-weight: 400; color: var(--ink); resize: vertical;
             &:focus { outline: none; border-color: var(--ink); box-shadow: var(--ring); } }
  .dk-drawer-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
  .dk-drawer-hint { flex: 1; font-size: 11.5px; color: var(--dim); }
  .dk-drawer-hint kbd, .dk-keys kbd { padding: 1px 5px; border: 1px solid var(--border-control); border-radius: 4px; background: var(--card); font: 500 10.5px var(--mono); color: var(--ink-2); }

  /* The end of a sweep */
  .dk-receipt-top { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px; align-items: center; padding: 20px 18px; }
  .dk-receipt-title { display: flex; align-items: center; gap: 10px; margin: 0; font-size: 17px; font-weight: 600; color: var(--ink); }
  .dk-check { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 22px; height: 22px; border-radius: 50%; background: var(--ok); color: var(--card); }
  .dk-receipt-note { margin: 4px 0 0; font-size: 12.5px; color: var(--soft); }
  .dk-tiles { display: flex; gap: 24px; }
  .dk-tile { text-align: right; }
  .dk-tile-num { display: block; font-size: 22px; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
  .dk-tile-num--approved { color: var(--ok-strong); }
  .dk-tile-label { font-size: var(--t-sm); color: var(--soft); }
  .dk-receipt-foot { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; padding: var(--card-footer-pad); background: var(--inset); border-top: 1px solid var(--border-divider); border-radius: 0 0 11px 11px; }

  /* ── History: how it has been doing, then the ledger ──
     Two marks over one series. The chart's colours are the ledger's own verdict colours,
     so the chart and the rows under it cannot disagree about which is which. */
  .dk-accuracy { padding: 14px 16px 12px; display: flex; flex-direction: column; gap: 12px; }
  .dk-accuracy-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px; align-items: end; }
  .dk-accuracy-label { font-size: var(--t-sm); font-weight: 500; color: var(--soft); }
  .dk-accuracy-figure { display: flex; align-items: baseline; gap: 8px;
                        & strong { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; color: var(--ink); }
                        & span { font-size: 12.5px; color: var(--soft); } }
  .dk-accuracy-none { font-size: var(--t-base); color: var(--dim); }
  /* Stretched to the card, so it carries no text — labels are the HTML row beneath. */
  .dk-chart { display: block; width: 100%; height: 90px; }
  .dk-bar--approved { fill: var(--ok); }
  .dk-bar--dismissed { fill: var(--border-card); }
  .dk-rate-line { fill: none; stroke: var(--ok); stroke-width: 2; stroke-linejoin: round;
                  stroke-linecap: round; vector-effect: non-scaling-stroke; }
  .dk-rate-dot { fill: var(--ok); }
  .dk-chart-axis { display: flex; justify-content: space-between; gap: 8px; margin-top: -8px; font: 500 10.5px var(--mono); color: var(--dim); }

  /* ⚠ Not clipped: the client filter's menu drops out of the card. */
  .dk-ledger:has(.filter-menu[open]) { position: relative; z-index: 5; }
  .dk-filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 16px; border-bottom: 1px solid var(--border-divider); }
  .dk-filters .seg-filter { margin: 0; }
  .dk-filters .filter-menu--select { flex: 0 0 200px; }
  .dk-filters .filter-select { width: 100%; min-width: 0; }
  .dk-truncated { margin-left: auto; font-size: var(--t-sm); color: var(--soft); }
  .dk-day-head { display: flex; align-items: baseline; gap: 8px; padding: 10px 16px 6px; }
  .dk-day-label { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  .dk-day-count { font-size: var(--t-sm); color: var(--dim); }
  .dk-log-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto auto; gap: 12px; align-items: center; padding: 9px 16px; border-top: 1px solid var(--border-row); cursor: pointer;
                &:hover { background: var(--inset); } }
  .dk-ledger > .dk-day-group:last-child .dk-log-row:last-child { border-radius: 0 0 11px 11px; }
  .dk-log-main { min-width: 0; }
  .dk-log-title { display: block; font-size: var(--t-base); font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .dk-log-where, .dk-log-note { display: block; font-size: var(--t-sm); color: var(--soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .dk-log-note { margin-top: 2px; color: var(--ink-2); &::before { content: "\201C"; } &::after { content: "\201D"; } }
  .dk-log-outcome { white-space: nowrap; }
  .dk-log-time { font: 500 11px/1 var(--mono); color: var(--dim); }

  /* History detail */
  .dk-back { align-self: flex-start; }
  .dk-trail { padding: 14px 16px 16px; }
  .dk-trail-label { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
  .dk-final { font-size: var(--t-base); line-height: 1.5; color: var(--soft); text-align: center;
              text-wrap: pretty; padding: 0 12px; }
  .dk-trail-row { display: grid; grid-template-columns: 96px 8px 1fr; align-items: center;
                  gap: 10px; margin-top: 10px; font-size: var(--t-base); color: var(--ink); }
  .dk-trail-time { font-family: var(--mono); font-size: var(--t-sm); color: var(--soft); }
  .dk-trail-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--soft); }
  .dk-trail-dot--proposed { background: var(--badge-amber); }
  .dk-trail-dot--approved { background: var(--ok); }
  .dk-trail-dot--executed { background: var(--ok); }
  .dk-trail-text { min-width: 0; overflow-wrap: anywhere; }
  /* A fourth child of the 3-column row: pinned to column 3, so auto-placement drops it on
     an implicit second row under the text. align-items centres each item within its OWN
     row track, which is how the time and dot stay level with the first line rather than
     drifting to the middle of a three-line note. */
  .dk-trail-note { grid-column: 3; min-width: 0; margin: 2px 0 0; font-size: var(--t-base);
                   line-height: 1.45; color: var(--soft); text-wrap: pretty;
                   overflow-wrap: anywhere; }
  /* The one action under the trail — requeue, withdraw or clear a receipt. Three buttons
     that never appear together. */
  .dk-item-action { align-self: flex-start; }

  /* ── Actions — the registry ──
     Built to explorations/opensource-direction/actions.css (.ar / .arh / .slug). One card
     of grid rows in an 820px column. ⚠ .dk-table / .dk-row are shared with the recap
     headings registry, which only swaps the grid — the two read as siblings on purpose. */
  .view.dka-index, .view.dka-page { --page-gutter: max(20px, (100% - 820px) / 2); }
  .dka-search { width: min(260px, 100%); }
  .dka-search input { font: inherit; font-size: var(--t-base); }
  .dka-search input::-webkit-search-cancel-button { display: none; }

  .dk-table, .dk-empty { margin: 16px 0 0; }
  .dk-row {
    display: grid; grid-template-columns: minmax(0, 1fr) auto 110px 72px; gap: 14px; align-items: center;
    padding: 11px 12px 11px 16px; border-top: 1px solid var(--border-row); cursor: pointer; transition: background 0.12s;
    &:hover { background: var(--inset); }
  }
  .dk-table > .dk-row:first-child, .dk-row--head + .dk-row { border-top: 0; }
  .dk-table > .dk-row:last-child { border-radius: 0 0 11px 11px; }
  .dk-row--head {
    padding-block: 8px 6px; border-bottom: 1px solid var(--border-divider); border-radius: 11px 11px 0 0;
    font-size: var(--t-sm); font-weight: 500; color: var(--soft); cursor: default;
    &:hover { background: none; }
  }
  .dk-row--head > :nth-child(3) { justify-self: end; }
  .dk-row.is-archived .dk-title { color: var(--soft); }
  /* ⚠ Scoped to the row and resetting the margin: a bare .dk-title is the Docket review
     card's 20px heading (above), and the two rules were overwriting each other — the card's
     heading shrank to this size and every row here inherited its 16px top margin. */
  .dk-row .dk-title { margin: 0; font-size: 13.5px; font-weight: 600; letter-spacing: 0; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* A button, not a span — that is also what stops the click reaching row-link, which
     already ignores clicks inside buttons (the same split the Archive button relies on). */
  .dk-slug {
    padding: 3px 7px; border: 1px solid transparent; border-radius: 6px; background: var(--icon-tile); cursor: pointer;
    font: 500 11.5px var(--mono); color: var(--ink-2); white-space: nowrap;
    &:hover { border-color: var(--border-control); color: var(--ink); }
    &.is-copied { background: var(--ok-tint); color: var(--ok-strong); }
  }
  .dk-slug .dk-slug-done { display: none; }
  .dk-slug.is-copied .dk-slug-label { display: none; }
  .dk-slug.is-copied .dk-slug-done { display: inline; }
  .dk-updated { font-size: var(--t-sm); color: var(--soft); text-align: right; white-space: nowrap; }
  .dk-delete-cell { text-align: right; }
  .dk-delete-cell form { display: inline; }
  /* What you can do to a row waits for the row — except in the archive, where Restore is
     the only reason the row is on screen. */
  .dk-delete {
    height: 26px; padding: 0 8px; border: 0; border-radius: 6px; background: none; cursor: pointer;
    font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--soft); opacity: 0; transition: opacity 0.12s;
    &:hover { background: var(--icon-tile); color: var(--ink); }
  }
  .dk-row:hover .dk-delete, .dk-delete:focus-visible, .dk-row.is-archived .dk-delete { opacity: 1; }
  @media (hover: none) { .dk-delete { opacity: 1; } }

  .dk-empty, .dk-nomatch { padding: 28px; text-align: center; font-size: var(--t-base); color: var(--soft); }
  .dk-empty { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .dk-nomatch { margin: 0; }

  .dk-archived { margin-top: 16px; }
  .dk-archived-toggle {
    display: inline-flex; align-items: center; gap: 6px; padding: 2px 4px; list-style: none; cursor: pointer;
    font-size: 12.5px; font-weight: 500; color: var(--soft);
    &:hover { color: var(--ink); }
    &::-webkit-details-marker { display: none; }
    & svg { transition: transform 0.2s; }
  }
  .dk-archived[open] .dk-archived-toggle svg { transform: rotate(180deg); }

  /* Actions — the detail/create page: a crumb, the name with its Save, then ONE card of four
     steps (.eh / .rc / .step in actions.css).
     ⚠ A `dka-` prefix, not `dk-`: .dk-title, .dk-body, .dk-dot and .dk-form already belong
     to the Docket REVIEW page a few hundred lines above. Same feature, same prefix, two
     different screens — so a new meaning for one of those names would silently restyle the
     sweep. */
  .dka-page .campaigns-body { padding-top: 16px; }
  /* ⚠ The crumb, the header and the body all read the same --page-gutter, so the three
     cannot fall out of line with each other. */
  .dka-crumb { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding-block: 16px 0; padding-inline: var(--page-gutter); font-size: 12.5px; color: var(--soft); white-space: nowrap; }
  .dka-crumb-back { display: inline-flex; align-items: center; gap: 3px; color: var(--soft); text-decoration: none; font-weight: 500; &:hover { color: var(--ink); } }
  .dka-crumb-sep { color: var(--dim); }

  .dka-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; padding: 12px var(--page-gutter) 0; }
  .dka-h1 { flex: 1 1 300px; min-width: 0; margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; }
  .dka-slug-line { flex-basis: 100%; order: 3; display: flex; }

  /* The record's name, renamed in place. Visibility is driven by .is-editing on the
     container — `hidden` doesn't work, because each half's own display rule wins. */
  .dka-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
  .dka-title-shown { display: flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
  .dka-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dka-title-edit-box { display: none; align-items: center; gap: 6px; flex: 1; min-width: 0; }
  .dka-title.is-editing .dka-title-shown { display: none; }
  .dka-title.is-editing .dka-title-edit-box { display: flex; }
  /* ⚠ Dimmed, never hidden. A hover-to-reveal rename is unreachable on a touch screen,
     and this page is one of the ones that has to work on a phone. */
  .dka-title-edit { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 7px; background: none; color: var(--dim); cursor: pointer; &:hover { background: var(--icon-tile); color: var(--ink); } }
  .dka-title-input {
    /* Inherits the <h1>'s type so the field and the heading are the same words in the same
       place, not a form control that happens to sit near one. The -13px pulls its text
       back onto the heading's left edge, where the padding and border would indent it. */
    flex: 1; min-width: 0; height: 40px; margin-left: -13px; padding: 0 12px; font: inherit; letter-spacing: inherit; color: var(--ink);
    background: transparent; border: 1px solid transparent; border-radius: var(--r-md); outline: 0;
    &::placeholder { color: var(--dim); }
    &:hover { background: var(--inset); border-color: var(--border-row); }
    &:focus { background: var(--card); border-color: var(--ink); box-shadow: var(--ring); }
  }
  .dka-title-done { font-weight: 500; letter-spacing: 0; }

  .dka-head-actions { display: flex; align-items: center; gap: 8px; flex: none; margin-left: auto; }
  .dka-dirty { display: none; align-items: center; gap: 6px; font-size: var(--t-sm); font-weight: 500; color: var(--amber-ink); }
  .dka-dirty i { width: 6px; height: 6px; border-radius: 50%; background: var(--badge-amber); }
  .dka-dirty.is-dirty { display: inline-flex; }
  /* The derived slug: a chip like the registry's, but not a button — there is nothing to
     copy from a page whose subject it is. */
  .dka-slug { padding: 3px 7px; border-radius: 6px; background: var(--icon-tile); font: 500 11.5px var(--mono); color: var(--ink-2); white-space: nowrap; }
  .dka-cancel { display: inline-flex; align-items: center; height: 28px; padding: 0 10px; border-radius: 7px; font-size: 12.5px; font-weight: 500; color: var(--soft); text-decoration: none; &:hover { color: var(--ink); background: var(--icon-tile); } }
  .dka-save { padding: 0 14px; }
  /* Disabled until something changes, and visibly so — an inert button that looks live is
     worse than one that looks inert. */
  .dka-save:disabled { opacity: 0.45; cursor: default; transform: none; }

  /* The recipe: one card, four steps, each a label beside its content. */
  .dka-card { padding: 6px 0; }
  .dka-step { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 16px; padding: 16px 20px; border-top: 1px solid var(--border-row); }
  .dka-step:first-child { border-top: 0; }
  .dka-step > h2 { display: flex; flex-direction: column; gap: 2px; margin: 0; padding-top: 7px; font-size: var(--t-base); font-weight: 600; color: var(--ink); }
  .dka-step > h2 small { font-size: 11.5px; font-weight: 400; line-height: 1.4; color: var(--soft); }

  /* A rule row: its number, the sentence, and the way to remove it. */
  .dka-rules { display: flex; flex-direction: column; gap: 6px; counter-reset: rule; }
  /* ⚠ The number is a counter, not markup. Rows are added and removed in the browser, so it
     has to renumber — and a counter does that with no code, and correctly for a row that has
     been taken out of the box tree. --rule-prefix is set per list ("T" or "S"). */
  .dka-rule { display: grid; grid-template-columns: 30px minmax(0, 1fr) 28px; gap: 8px; align-items: center; counter-increment: rule; }
  .dka-tag { display: inline-flex; align-items: center; justify-content: center; height: 28px; border-radius: 6px; background: var(--icon-tile); font: 600 11px var(--mono); color: var(--ink-2); &::before { content: var(--rule-prefix) counter(rule); } }
  .dka-rule-remove { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 7px; background: none; color: var(--dim); cursor: pointer; opacity: 0; transition: opacity 0.12s; &:hover { background: var(--err-tint); color: var(--err); } }
  .dka-rule:hover .dka-rule-remove, .dka-rule:focus-within .dka-rule-remove { opacity: 1; }
  @media (hover: none) { .dka-rule-remove { opacity: 1; } }
  .dka-rules-empty { margin: 0; padding: 7px 0; font-size: 12.5px; color: var(--soft); }
  /* Shown only when the list is actually empty. The server cannot answer this any more —
     the last row may have been removed a second ago and not saved yet. */
  .dka-rules:has(.dka-rule) .dka-rules-empty { display: none; }
  /* Same rule on the Library's groups, for the same reason. */
  .lib-groups:has(.lib-group) .lib-groups-empty { display: none; }
  .dka-add { display: inline-flex; align-items: center; gap: 5px; height: 28px; margin-top: 8px; padding: 0 9px; border: 1px dashed var(--border-control); border-radius: 7px; background: none; font: inherit; font-size: 12.5px; font-weight: 500; color: var(--ink-2); white-space: nowrap; cursor: pointer; &:hover { border-style: solid; color: var(--ink); } }

  .dka-field {
    width: 100%; min-width: 0; height: 34px; padding: 0 10px; background: var(--inset);
    border: 1px solid var(--border-row); border-radius: var(--r-md);
    font: inherit; font-size: 13.5px; color: var(--ink);
    &::placeholder { color: var(--dim); }
    &:hover { border-color: var(--border-control); background: var(--card); }
    &:focus { outline: none; background: var(--card); border-color: var(--ink); box-shadow: var(--ring); }
  }
  .dka-field--area { height: auto; min-height: 110px; padding: 10px 12px; line-height: 1.65; resize: vertical; overflow: hidden; }

  /* ── Slug bubbles ──
     A mirror behind a see-through textarea, so a word that names a document or an action
     reads as a reference while you are still typing. Shared with the Library's body field,
     which is why nothing here is prefixed: it belongs to neither page.

     ⚠ The field must be POSITIONED, not just transparent. An absolutely-positioned mirror
     paints above an in-flow textarea whatever the source order says; once both are
     positioned, source order decides and the field wins. */
  .slug-field { position: relative; }
  /* Transparent in BOTH states — .dka-field:focus paints a background, and an opaque field
     hides the very thing this exists to show. Positioned so the field paints above the
     absolutely-positioned mirror; source order decides once both are positioned. */
  .slug-field > textarea,
  .slug-field > textarea:focus { position: relative; background: transparent; }
  /* ⚠ The mirror wears the FIELD's classes as well as this one, which is what makes it break
     lines in the same places. Hence `div.` — a bare .slug-mirror ties with .dka-field and
     .lib-body on specificity and loses to whichever is declared later. */
  div.slug-mirror {
    position: absolute; inset: 0; box-sizing: border-box; overflow: hidden;
    min-height: 0; resize: none; color: transparent; pointer-events: none;
    /* It borrows the field's metrics and none of its skin: the box below is the wrapper's. */
    background: transparent; border-color: transparent; box-shadow: none;
    /* ⚠ pre-wrap is a UA style on <textarea>, not on the classes it wears — a div borrowing
       them collapses every newline, and the mirror ran the whole text into one paragraph. */
    white-space: pre-wrap; overflow-wrap: break-word;
  }
  /* ⚠ No horizontal padding — it would shift every word after the bubble out from under
     the real text. The pill's room comes from its radius, not from a gutter. */
  .slug-ref {
    background: var(--accent-soft); color: transparent;
    border-radius: var(--r-full); box-shadow: 0 0 0 1px var(--accent-border);
  }
  /* The box .dka-field used to draw moves to the wrapper, focus and all, because the field
     itself has gone see-through. The Library's body has no box to move. */
  .slug-field:has(> .dka-field) {
    background: var(--inset); border-radius: var(--r-md);
    &:hover, &:has(> .dka-field:focus) { background: var(--card); }
  }
  .dka-how-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
  .dka-select {
    height: 30px; padding: 0 28px 0 10px; appearance: none; font: inherit; font-size: 12.5px; color: var(--ink-2);
    border: 1px solid var(--border-control); border-radius: 7px;
    background: var(--card) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%23948C81' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 8px center;
    &:focus { outline: none; border-color: var(--ink); box-shadow: var(--ring); }
  }
  /* Derived from the saved text, so it is a report rather than a control. */
  .dka-linked { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: var(--t-sm); color: var(--soft); }
  .dka-linked-label { font-size: var(--t-xs); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dim); }
  .dka-chip { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px; border-radius: 6px; background: var(--accent-tint); font-size: var(--t-sm); font-weight: 500; color: var(--accent-ink); text-decoration: none; & code { font: 500 11px var(--mono); color: var(--accent); } &:hover { filter: brightness(0.97); } }

  /* Which statuses this action is considered in at all: a row of chips, each a checkbox. */
  .dka-status-list { display: flex; flex-wrap: wrap; gap: 6px; }
  .dka-status {
    position: relative; display: inline-flex; align-items: center; gap: 7px; height: 30px; padding: 0 11px 0 9px;
    border: 1px solid var(--border-control); border-radius: var(--r-full); background: var(--card);
    font-size: 12.5px; font-weight: 500; color: var(--soft); white-space: nowrap; cursor: pointer; transition: border-color 0.12s, background 0.12s, color 0.12s;
    &:hover { border-color: var(--dim); color: var(--ink); }
    &:has(:checked) { color: var(--ink); border-color: var(--ink); background: var(--inset); }
    &:has(:focus-visible) { box-shadow: 0 0 0 2px color-mix(in srgb, var(--ink) 20%, transparent); }
  }
  .dka-status-check { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
  .dka-status .status-dot { width: 7px; height: 7px; opacity: 0.45; }
  .dka-status:has(:checked) .status-dot { opacity: 1; }
  /* The scope that is not a campaign status at all, set apart rather than sorted in. */
  /* ⚠ The rule is on its RIGHT: DocketAction::SCOPES lists it first, where the mockup's
     sample data put it last. */
  .dka-status--apart { margin-right: 10px; }
  .dka-status--apart::after { content: ""; position: absolute; right: -9px; top: 6px; bottom: 6px; width: 1px; background: var(--border-divider); }
  .dka-status-note { font-size: var(--t-xs); font-weight: 400; color: var(--dim); }
  .dka-selectall { margin-top: 8px; padding: 0; border: 0; background: none; font: inherit; font-size: var(--t-sm); font-weight: 500; color: var(--accent); cursor: pointer; }

  /* The column header AS the control. The caret is on the sorted column only, so the other
     three read as labels rather than as buttons competing for the click. */
  .dka-sort { display: inline-flex; align-items: center; gap: 4px; justify-self: start; color: inherit;
              font: inherit; text-decoration: none; cursor: pointer;
              &:hover { color: var(--ink); }
              &.is-on { color: var(--ink); }
              & svg { transition: transform 140ms ease; } }
  /* The shared chevron points DOWN at rest, which is what descending should look like —
     so ascending is the one that turns, not descending. */
  .dka-sort[aria-sort="ascending"] svg { transform: rotate(180deg); }

}

/* Actions on a phone: the head row goes (the columns have folded, so it labels nothing),
   each row is its name and Archive over its slug and its age, and each step's label sits
   above its content. */
@media (max-width: 700px) {
  @layer components {
    .dk-row--head:not(.dk-row--heading) { display: none; }
    .dka-index .dk-row { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "t a" "m m"; row-gap: 4px; padding: 11px 10px 11px 14px; }
    .dka-index .dk-title { grid-area: t; white-space: normal; }
    .dka-index .dk-delete-cell { grid-area: a; }
    .dka-index .dk-slug { grid-area: m; justify-self: start; padding: 2px 6px; font-size: var(--t-xs); }
    .dka-index .dk-updated { grid-area: m; justify-self: end; font-size: 11.5px; }
    .dka-index .tool-header .dka-search { order: 3; width: 100%; }

    .dka-head { flex-direction: column; align-items: stretch; gap: 8px; }
    .dka-h1 { flex-basis: auto; font-size: 19px; line-height: 1.25; }
    .dka-title-text { white-space: normal; }
    .dka-slug-line { order: 0; margin-top: -2px; }
    .dka-head-actions { order: 1; width: 100%; justify-content: flex-end; }
    .dka-head-actions .dka-save { flex: 1; justify-content: center; height: 38px; }
    .dka-step { grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 14px; }
    .dka-step > h2 { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; padding-top: 0; }
    .dka-rule { grid-template-columns: 28px minmax(0, 1fr) 28px; }
    .dka-status-list { gap: 5px; }
    .dka-status { height: 32px; }
    .dka-status--apart { margin-right: 0; }
    .dka-status--apart::after { display: none; }
    .dka-how-foot { flex-direction: column; align-items: stretch; }
    .dka-select { width: 100%; }
  }
}

@media (max-width: 520px) {
  @layer components {
    .view.dk { --page-gutter: 12px; }   /* .dka-page carries .dk too */
  }
}

@media (max-width: 700px) {
  @layer components {
    .dk-column { padding: 14px 0 96px; }
    .dk .campaigns-body { padding-block: 0 96px; }
    /* ⚠ The Actions page carries BOTH classes (.dk for the shared .dk- primitives), so the
       Docket's shorthand above was resetting its padding-top to 0 on every phone. Three
       classes, so this holds whatever order the two rules end up in. */
    .dka-page.dk .campaigns-body { padding-top: 16px; }
    /* The tile sits in the corner and the "where" line clears it; the title takes the width. */
    .dk-card { grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 14px; }
    .dk-card > .client-chip { position: absolute; top: 14px; left: 14px; }
    .dk-card-head { flex-wrap: wrap; padding-left: 44px; }
    .dk-where { white-space: normal; }
    .dk-title { margin-top: 10px; font-size: 17px; }
    .dk-body { font-size: 13.5px; }
    .dk-why { grid-template-columns: minmax(0, 1fr); gap: 6px; }
    .dk-why-label { justify-self: start; }
    /* No keyboard to advertise; the answers share the row and Next keeps its own width. */
    .dk-keys, .dk-drawer-hint { display: none; }
    /* The answer stays in reach: the card's footer pins to the foot of the page while the
       proposal scrolls above it. It is the card's own footer made sticky, not a second bar.
       The floating Queue pill would sit on top of it, so it stands down on this page. */
    .dk-controls { position: sticky; bottom: 0; z-index: 2; padding: 10px 12px; }
    .app:has(.dk-controls) .nav-pill { display: none; }
    .dk-decide { flex: 1 1 0; }
    .dk-decide > summary { width: 100%; justify-content: center; height: 40px; }
    .dk-drawer-foot { justify-content: flex-end; }
    .dk-receipt-top { grid-template-columns: minmax(0, 1fr); }
    .dk-tile { text-align: left; }
    .dk-accuracy-head { grid-template-columns: minmax(0, 1fr); }
    .dk-accuracy-head .seg-filter { justify-self: start; }
    .dk-filters .filter-menu--select { flex: 1 1 100%; }
    .dk-log-row { grid-template-columns: 28px minmax(0, 1fr) auto; grid-template-areas: "c main tm" "c status tm"; row-gap: 2px; }
    .dk-log-row > .client-chip { grid-area: c; }
    .dk-log-main { grid-area: main; }
    .dk-log-outcome { grid-area: status; justify-self: start; }
    .dk-log-time { grid-area: tm; }
    .dk-trail-row { grid-template-columns: 88px 8px 1fr; }
  }
}

/* ── Calls ──
   The list of recorded calls, and one call's record. Built on the campaigns dashboard
   table (.campaigns-table / .campaign-row / .client-chip) and on the .status-menu
   popover, the same way Asset Approvals was — only the assignment pill and the detail
   page's cards are new.

   ⚠ Not .dk-table: a card that keeps
   overflow visible precisely so a per-row dropdown can open past it, and .dk-table sets
   overflow: hidden, which would clip the assignment menu on every row. */
@layer components {
  /* ── Calls ──
     A list of grid rows in one card, a record page of two columns. Measured against the
     design system's calls.html. */
  .calls-col { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 1040px; margin: 0 auto; padding-top: 16px; }
  .calls-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .calls-controls .seg-filter { margin: 0; }
  .calls-controls .filter-menu { margin-left: auto; }
  .calls-controls .filter-select { width: 190px; }

  /* ⚠ Not clipped: the assignment menu opens out of a row and past the card. */
  .calls-list { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .calls-list:has(.call-assign[open]) { position: relative; z-index: 5; }
  .calls-row {
    display: grid; grid-template-columns: 64px minmax(0, 1.5fr) 232px minmax(0, 1fr) 92px; gap: 14px; align-items: center;
    min-height: 52px; padding: 0 16px; border-top: 1px solid var(--border-row); cursor: pointer; transition: background 0.12s;
  }
  .calls-row:hover { background: var(--inset); }
  .calls-row:focus-visible { outline: 0; box-shadow: inset 0 0 0 2px var(--ink); }
  .calls-row:last-child { border-radius: 0 0 var(--r-card) var(--r-card); }
  .calls-row--head {
    min-height: 0; height: 36px; border-top: 0; border-bottom: 1px solid var(--border-divider); cursor: default;
    font-size: var(--t-sm); font-weight: 500; color: var(--soft);

    &:hover { background: none; }
    & span:last-child { text-align: right; }
  }
  .calls-row--head + .calls-row { border-top: 0; }
  .call-when { font: 500 var(--t-sm) var(--mono); color: var(--soft); white-space: nowrap; }
  .call-title { font-size: 13.5px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .call-guests { font-size: 12.5px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .call-read { font-size: var(--t-sm); color: var(--soft); text-align: right; white-space: nowrap; }
  .call-client-cell { min-width: 0; }
  /* An ignored call is still in the record; it just stops asking for attention. */
  .calls-row--ignored :is(.call-title, .call-guests) { color: var(--dim); }
  .calls-row--ignored .call-title { text-decoration: line-through; text-decoration-color: var(--border-control); }

  /* The assignment control — the one thing on this page that writes. Filed, it is a chip
     and a name that become a control when the row is pointed at; unfiled, it is a dashed
     slot that says why nothing matched. */
  .call-assign { position: relative; min-width: 0; }
  .call-assign > summary { list-style: none; cursor: pointer; }
  .call-assign > summary::-webkit-details-marker { display: none; }
  .call-assign-pill {
    display: flex; align-items: center; gap: 8px; width: fit-content; max-width: 100%; height: 30px; padding: 0 8px 0 4px;
    border: 1px solid transparent; border-radius: var(--r-md); font-size: 12.5px; font-weight: 500; color: var(--ink);
    transition: border-color 0.12s, background 0.12s;

    & .client-chip { width: 22px; height: 22px; border-radius: 6px; font-size: 8.5px; }
    & > svg { flex: none; color: var(--dim); transition: transform 0.18s; }
    .calls-row:hover &, &:focus-visible { border-color: var(--border-control); background: var(--card); outline: 0; }
    .call-assign[open] > & { border-color: var(--ink); background: var(--card); box-shadow: var(--ring); }
    .call-assign[open] > & > svg { transform: rotate(180deg); }
  }
  .call-assign-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .call-assign-pill.is-ignored { padding-left: 10px; color: var(--soft); font-weight: 400; }
  .call-assign-pill.is-empty {
    height: auto; min-height: 30px; padding: 4px 8px 4px 10px;
    border: 1px dashed var(--border-control); background: var(--card); color: var(--ink-2); font-weight: 400;
  }
  .call-assign-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--warn); flex: none; }
  .call-assign-pill.is-empty .call-assign-name { font: 11.5px/1.3 var(--mono); white-space: normal; overflow-wrap: anywhere; text-align: left; }
  .call-assign-pill.is-empty small {
    display: block; font-family: -apple-system, "Helvetica Neue", Helvetica, system-ui, sans-serif;
    font-size: 10px; font-weight: 600; line-height: 1.3; letter-spacing: 0.02em; color: var(--amber-ink);
  }
  .call-menu {
    position: absolute; left: 0; top: calc(100% + 6px); z-index: 40; width: 272px; max-height: 340px; overflow: auto;
    margin: 0; padding: 4px; background: var(--card); border: 1px solid var(--border-card); border-radius: 10px;
    box-shadow: 0 14px 34px -10px oklch(0 0 0 / 0.25);
  }
  /* Opens upward from a row near the bottom of the screen (menu#place). */
  .call-menu.is-flipped { top: auto; bottom: calc(100% + 6px); }
  .call-menu hr { border: 0; border-top: 1px solid var(--border-divider); margin: 4px 0; }
  .call-option {
    display: grid; grid-template-columns: 24px minmax(0, 1fr) auto 14px; gap: 9px; align-items: center;
    width: 100%; padding: 6px 8px; border: 0; border-radius: 7px; background: transparent; text-align: left;
    font: inherit; font-size: 12.5px; color: var(--ink); cursor: pointer;

    &:hover, &:focus-visible { background: var(--inset); outline: 0; }
    & .client-chip { width: 24px; height: 24px; border-radius: 6px; font-size: 9px; }
    & .status-check { display: inline-flex; color: var(--ink); }
  }
  .call-option-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .call-option--plain { grid-template-columns: minmax(0, 1fr) 14px; padding: 8px 10px; color: var(--ink-2); }
  .call-option--undo { color: var(--accent-ink); }
  /* Marks a client who has left, in a list that deliberately still offers them. */
  .call-assign-tag { font-size: 10.5px; font-weight: 600; color: var(--soft); background: var(--sidebar); border-radius: 999px; padding: 1px 6px; }

  .calls-empty { padding: 40px 20px; text-align: center; color: var(--soft); }
  .calls-empty svg { color: var(--dim); }
  .calls-empty p { margin: 8px 0 0; font-size: var(--t-base); }
  .calls-foot { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; padding: 2px 4px; font-size: var(--t-sm); color: var(--soft); }
  /* Never colour alone — the sentence says what failed; this only makes it findable. */
  .calls-sync-failed { display: inline-flex; align-items: center; gap: 6px; color: var(--err); font-weight: 500; --sc: var(--err); }
  .campaigns-subtitle b { font-weight: 500; color: var(--ink); }

  /* ── One call ── */
  .call-crumb { padding: 0; }
  .call-head { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin-top: -6px; }
  .call-head-text { flex: 1; min-width: 0; }
  .call-head h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; }
  .call-head-meta { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; margin-top: 3px; font-size: 12.5px; color: var(--soft); }
  .call-head-meta a { color: var(--ink-2); font-weight: 500; }
  .call-head-meta a:hover { color: var(--accent-ink); }
  .call-head-sep { color: var(--dim); }
  .call-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 16px; align-items: start; }
  .call-main, .call-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
  .call-card { margin: 0; padding: 0; overflow: hidden; }
  .call-card-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border-divider); }
  .call-card-head b, .call-transcript > summary b { font-size: var(--t-lg); font-weight: 600; }
  .call-card-head small, .call-transcript-size { margin-left: auto; font: 500 11.5px var(--mono); color: var(--dim); }
  .call-summary { padding: 12px 18px 16px; max-width: 72ch; font-size: 13.5px; line-height: 1.6; color: var(--ink); white-space: pre-wrap; text-wrap: pretty; }
  .call-empty { margin: 0; padding: 16px; font-size: var(--t-base); color: var(--soft); list-style: none; }

  .call-people { list-style: none; margin: 0; padding: 4px 0; }
  .call-people li:not(.call-empty) { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 7px 16px; }
  .call-person-avatar {
    display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%;
    background: var(--sidebar); color: var(--ink-2); font-size: 10px; font-weight: 600;
  }
  .call-person-text { min-width: 0; }
  .call-person-name { display: block; font-size: var(--t-base); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .call-person-email { display: block; font-size: var(--t-sm); color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .call-person-us { font-size: 10.5px; font-weight: 600; color: var(--soft); background: var(--sidebar); border-radius: 999px; padding: 2px 7px; }

  .call-outcomes { list-style: none; margin: 0; padding: 0; }
  .call-outcomes li { display: grid; grid-template-columns: 8px minmax(0, 1fr); gap: 12px; padding: 12px 16px; border-top: 1px solid var(--border-row); }
  .call-outcomes li:first-child { border-top: 0; }
  .call-outcome-dot { margin-top: 7px; --sc: var(--dim); }
  .call-outcome-title { font-size: 13.5px; font-weight: 600; color: var(--ink); }
  .call-outcome-title:hover { color: var(--accent-ink); text-decoration: none; }
  .call-outcome-meta { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; margin-top: 2px; font-size: var(--t-sm); color: var(--soft); }
  .call-outcome-meta a { font: 11.5px var(--mono); }

  .call-transcript > summary { list-style: none; display: flex; align-items: center; gap: 10px; padding: 12px 16px; cursor: pointer; }
  .call-transcript > summary::-webkit-details-marker { display: none; }
  .call-transcript > summary .call-transcript-size { margin-left: 0; }
  .call-transcript > summary svg { margin-left: auto; color: var(--dim); transition: transform 0.2s; }
  .call-transcript[open] > summary { border-bottom: 1px solid var(--border-divider); }
  .call-transcript[open] > summary svg { transform: rotate(180deg); }
  .call-transcript:hover > summary b { color: var(--accent-ink); }
  .call-transcript-body { padding: 14px 18px; max-width: 76ch; font-size: var(--t-base); line-height: 1.7; color: var(--ink-2); white-space: pre-wrap; overflow-wrap: anywhere; }

  @media (max-width: 980px) {
    .calls-row { grid-template-columns: 56px minmax(0, 1fr) 210px 80px; }
    .calls-row > :nth-child(4) { display: none; }
  }
  @media (max-width: 860px) {
    .call-grid { grid-template-columns: minmax(0, 1fr); }
  }
  @media (max-width: 700px) {
    .calls-controls .seg-filter { width: 100%; }
    .calls-controls .seg-filter .seg { flex: 1; justify-content: center; }
    .calls-controls .filter-menu, .calls-controls .filter-select { width: 100%; margin-left: 0; }
    .calls-row--head { display: none; }
    .calls-row { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "t w" "a a" "g r"; gap: 4px 10px; padding: 12px 14px; }
    .calls-row > :nth-child(4) { display: block; }
    .call-title { grid-area: t; white-space: normal; }
    .call-when { grid-area: w; align-self: start; padding-top: 2px; }
    .call-client-cell { grid-area: a; margin: 2px 0 0 -4px; }
    .call-guests { grid-area: g; font-size: var(--t-sm); color: var(--soft); }
    .call-read { grid-area: r; }
    .call-assign-pill { height: 34px; border-color: var(--border-control); background: var(--card); }
    .call-menu { width: min(300px, calc(100vw - 40px)); }
    .call-option { padding: 9px 8px; }
  }
}

/* ── Bulk Builder (/bulk) ─────────────────────────────────────────────────────
   Seeds as cards, each holding the sheets built from it; and a sheet's own page, which
   is the file, cell for cell. Built to explorations/opensource-direction/tools.css
   (.sds / .sdh / .shl / .shr2 / .sht). The page wears the shared header and controls
   now — its private palette (`--bulk-*`) and private header bar are gone. */
@layer components {
  .view.bulk { --page-gutter: max(20px, (100% - 1100px) / 2); }
  .bulk-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px 0; }
  .bulk-controls .filter-menu--select { flex: 0 0 200px; }
  .bulk-controls .filter-select { width: 100%; min-width: 0; }
  .bulk-count { margin-left: auto; font: 12px var(--mono); color: var(--soft); }
  .bulk-count b { margin-right: 6px; font: 600 14px system-ui, sans-serif; color: var(--ink); }

  /* The upload form, in the page (see reference_imports/new). */
  .bulk-upload-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .bulk-upload-body { display: flex; flex-direction: column; gap: 12px; padding: 16px 18px; }
  .bulk-upload .field-stack { max-width: 360px; }
  .bulk-drop { position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 22px; border: 1.5px dashed var(--border-control); border-radius: 10px; background: var(--inset); text-align: center; cursor: pointer; }
  .bulk-drop:hover, .bulk-drop.is-over { border-color: var(--ink); background: var(--card); }
  .bulk-drop input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
  .bulk-drop-tile { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; margin-bottom: 4px; border: 1px solid var(--border-row); border-radius: 9px; background: var(--card); color: var(--ink-2); }
  .bulk-drop b { font-size: var(--t-base); font-weight: 600; }
  .bulk-drop span:last-child { font-size: var(--t-sm); color: var(--soft); }
  .bulk-help { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--soft); text-wrap: pretty; }

  /* ── Seeds ── */
  .bulk-seeds { display: flex; flex-direction: column; gap: 10px; }
  .bulk-seed { background: var(--card); border: 1px solid var(--border-card); border-radius: var(--r-card); box-shadow: var(--shadow-sm); }
  .bulk-seed-head { display: grid; grid-template-columns: 32px minmax(0, 1fr) 28px; gap: 12px; align-items: start; padding: 14px 10px 14px 14px; }
  .bulk-seed-head > .client-chip { width: 32px; height: 32px; margin-top: 2px; font-size: 10px; }
  .bulk-seed-head > form { margin-top: 4px; }
  .bulk-seed-text { min-width: 0; }
  .bulk-seed-names { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .bulk-seed-campaign, .bulk-seed-adset { font: 12.5px/18px var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .bulk-seed-campaign { font-size: var(--t-base); font-weight: 600; color: var(--ink); }
  .bulk-seed-adset { display: flex; align-items: center; gap: 5px; color: var(--ink-2); }
  .bulk-seed-adset svg { flex: none; color: var(--dim); }
  .bulk-seed-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; font-size: var(--t-sm); line-height: 20px; color: var(--soft); }
  .bulk-seed-meta i { font-style: normal; color: var(--dim); }
  .bulk-pill, .bulk-stale { display: inline-flex; align-items: center; height: 20px; padding: 0 8px; border-radius: var(--r-full); font-size: 11.5px; white-space: nowrap; }
  .bulk-pill { background: var(--icon-tile); font-weight: 500; color: var(--ink-2); }
  .bulk-stale { background: var(--amber-tint); font-weight: 600; color: var(--amber-ink); }
  .bulk-icon-form { margin: 0; display: inline-flex; }
  .bulk-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 7px; background: none; color: var(--dim); cursor: pointer; &:hover { background: var(--err-tint); color: var(--err); } }

  /* The sheets built from a seed, inside its card. */
  .bulk-sheets { border-top: 1px solid var(--border-divider); background: var(--inset); border-radius: 0 0 11px 11px; }
  .bulk-sheet { display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 10px 10px 10px 14px; border-top: 1px solid var(--border-row); }
  .bulk-sheet:first-child { border-top: 0; }
  .bulk-sheet-icon { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: var(--r-md); background: var(--ok-tint); color: var(--ok-strong); }
  .bulk-sheet-icon:hover { filter: brightness(0.97); }
  .bulk-sheet-main { min-width: 0; }
  .bulk-sheet-main form { margin: 0; }
  /* Quiet until touched; the -7px puts its text on the meta line's left edge. */
  .bulk-rename {
    display: block; width: 100%; height: 24px; margin: 0 0 0 -7px; padding: 0 6px; outline: 0;
    border: 1px solid transparent; border-radius: 6px; background: transparent;
    font: inherit; font-size: 13.5px; font-weight: 600; line-height: 22px; color: var(--ink);
    &:hover { background: var(--card); border-color: var(--border-row); }
    &:focus { background: var(--card); border-color: var(--ink); }
  }
  .bulk-sheet-main small { display: block; margin-top: 1px; font-size: var(--t-sm); line-height: 18px; color: var(--soft); }
  .bulk-sheet-actions { display: flex; align-items: center; gap: 6px; }
  .bulk-none { padding: 10px 14px 11px 58px; border-top: 1px solid var(--border-divider); border-radius: 0 0 11px 11px; background: var(--inset); font-size: 12.5px; line-height: 18px; color: var(--soft); }

  .bulk-empty { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 36px 20px; text-align: center; color: var(--soft); }
  .bulk-empty svg { margin-bottom: 4px; color: var(--dim); }
  .bulk-empty b { font-size: 13.5px; font-weight: 600; color: var(--ink); }
  .bulk-empty span { max-width: 44ch; font-size: 12.5px; text-wrap: pretty; }

  /* ── A sheet's own page ── */
  .bulk-crumb { padding: 16px var(--page-gutter) 0; }
  .bulk-sheet-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 8px var(--page-gutter) 0; }
  .bulk-sheet-title { flex: 1 1 300px; min-width: 0; }
  .bulk-sheet-title h1 { margin: 0; font-size: var(--t-2xl); font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; overflow-wrap: anywhere; }
  .bulk-sheet-title p { margin: 2px 0 0; font-size: 12.5px; color: var(--soft); }
  .bulk-sheet-head form { margin: 0; }
  .bulk-sheet-page .campaigns-body { display: flex; flex-direction: column; gap: 14px; padding-top: 14px; }
  .bulk-warn { display: flex; gap: 8px; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--badge-amber) 28%, var(--card)); border-radius: 10px; background: color-mix(in srgb, var(--badge-amber) 7%, var(--card)); font-size: 12.5px; line-height: 1.5; color: var(--amber-ink); }
  .bulk-warn svg { flex: none; margin-top: 2px; color: var(--badge-amber); }
  /* The file, cell for cell. It scrolls inside its own card, the first column pinned. */
  .bulk-table { flex-shrink: 0; overflow: auto; max-height: 70vh; }
  .bulk-table table { border-collapse: separate; border-spacing: 0; min-width: 100%; font-size: 12.5px; }
  .bulk-table th { position: sticky; top: 0; z-index: 1; padding: 8px 12px; border-bottom: 1px solid var(--border-divider); background: var(--inset); text-align: left; font-size: 11.5px; font-weight: 500; color: var(--soft); white-space: nowrap; }
  .bulk-table td { padding: 9px 12px; border-bottom: 1px solid var(--border-row); font: 12px var(--mono); color: var(--ink); white-space: nowrap; }
  .bulk-table td.is-empty { color: var(--dim); }
  .bulk-table tbody tr:last-child td { border-bottom: 0; }
  .bulk-table :is(th, td):first-child { position: sticky; left: 0; background: var(--card); box-shadow: inset -1px 0 0 var(--border-row); }
  .bulk-table th:first-child { z-index: 2; background: var(--inset); }

  @media (max-width: 700px) {
    .bulk-controls .filter-menu--select { flex: 1 1 100%; }
    .bulk-sheet { grid-template-columns: 32px minmax(0, 1fr); row-gap: 8px; }
    .bulk-sheet-actions { grid-column: 1 / 3; justify-content: flex-end; }
    .bulk-sheet-actions .btn { flex: 1; justify-content: center; height: 34px; }
    .bulk-none { padding-left: 14px; }
    .bulk-sheet-head .btn-primary { flex: 1; justify-content: center; }
  }
}

/* The separator the filter pill used to carry inside its label string. Global so
   every other page keeps reading "Client · All"; the Bulk Builder turns it off. */
@layer components {
  .filter-pill-value::before { content: "·"; margin: 0 5px; color: var(--dim); }
}

/* ── Styleguide (/styleguide) ─────────────────────────────────────────────────
   Furniture for the design system page itself. Page-scoped like every other
   section, and deliberately plain: it must never be interesting enough to
   compete with the primitives it is displaying. */
@layer components {
  .sg-body { flex: 1; overflow-y: auto; padding-block: 24px; padding-inline: var(--page-gutter); display: flex; flex-direction: column; gap: 28px; }

  .sg-section {
    padding: 22px 24px;
  }
  .sg-h { margin: 0 0 4px; font-size: var(--t-lg); font-weight: 700; letter-spacing: -0.02em; }
  .sg-note { margin: 0 0 18px; font-size: var(--t-base); color: var(--soft); max-width: 68ch; line-height: 1.6; }
  .sg-warn { color: var(--warn); }

  .sg-group { margin-bottom: 18px; &:last-child { margin-bottom: 0; } }
  .sg-group .section-label { margin-bottom: 9px; display: block; }

  .sg-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
  /* On the page a toast fades after three seconds; an exhibit that vanishes exhibits nothing. */
  .sg-section .toast { animation: none; }

  /* A monospace label under every specimen. The swatch is the point; the name is
     what you copy, so it stays selectable and small. */
  .sg-code { font-family: var(--mono); font-size: var(--t-xs); color: var(--soft); }

  .sg-swatches { display: flex; flex-wrap: wrap; gap: 14px; }
  .sg-swatch { display: flex; flex-direction: column; gap: 5px; }
  .sg-chip {
    width: 78px; height: 44px; border-radius: var(--r-md);
    border: 1px solid var(--border); display: block;
  }

  /* Every specimen is the same shape — sample, name, computed value — because
     design_system_controller builds them all from one method. Only the sample differs,
     and it differs because a --t-sm drawn as a background colour renders as nothing,
     which is exactly what a BROKEN token looks like. */
  .sg-value { font-family: var(--mono); font-size: var(--t-xs); color: var(--dim); }
  .sg-swatch--type { flex-direction: row; align-items: baseline; gap: 12px; flex: 1 0 100%; }
  .sg-swatch--type .sg-code { width: 62px; flex: none; }
  .sg-swatch--type .sg-value { margin-left: auto; }
  .sg-type-sample { flex: 1; min-width: 0; }
  /* .confirm-box is absolutely positioned; on the real page it hangs off a Delete button. */
  .sg-confirm-stage { position: relative; min-height: 200px; }
  .sg-radius-box { width: 64px; height: 44px; background: var(--accent-soft); border: 1px solid var(--accent-border); display: block; }
  /* A length drawn at its own length. min-height keeps a 4px space token clickable-sized
     rather than a hairline; a padding pair sets both axes and draws as a box. */
  .sg-size { display: block; min-width: 2px; height: 10px; min-height: 4px; background: var(--accent); border-radius: 2px; }
  .sg-shadow {
    width: 150px; height: 66px; background: var(--card); border-radius: var(--r-card);
    display: grid; place-items: center;
  }
  .sg-group--shadow .sg-swatches { padding: 6px 0 10px; }

  /* Ungrouped is empty at rest and has to LOOK like a to-do when it isn't, or a token
     nobody filed just sits there reading as a category. */
  .sg-ungrouped {
    border: 1px dashed var(--border); border-radius: var(--r-lg);
    padding: 13px 14px; background: var(--inset);
  }
  .sg-drift {
    display: flex; flex-wrap: wrap; gap: 6px 14px;
    padding: 11px 13px; border: 1px solid var(--border2); border-radius: var(--r-card);
    background: var(--inset);
  }
  .sg-none { font-size: var(--t-base); color: var(--dim); }

  /* The demo bars are real .tool-header elements rendered through the shared partial —
     that is the point, this page should break if the partial does. The frame goes on a
     wrapper rather than on .tool-header itself, so the shared component needs no
     styleguide-only modifier. */
  .sg-demo {
    border: 1px solid var(--border2); border-radius: var(--r-lg);
    overflow: hidden; margin-bottom: 12px;
  }

  .sg-empties {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px;
    & > div { display: flex; flex-direction: column; gap: 6px; }
  }
}

/* ── Meeting recaps ────────────────────────────────────────────────────────────
   The Queue row and the timeline card for a meeting_recap update.

   ⚠ NO new colour tokens. The design-system artboard names --warning-panel,
   --accent-tint-bg, --neutral-dot and friends; this app already has every one of
   them under its own names, and the token block above already says why a second
   home for one colour is how a palette rots. The mapping used here:

     DS --neutral-dot     -> --dim          (identical value, #A8A098)
     DS --warning-dot     -> --badge-amber  (identical value, #B8862E)
     DS --warning-panel   -> --amber-tint
     DS --warning-text    -> --amber-ink
     DS --accent-tint-bg  -> --accent-soft
     DS --success-dot     -> --ok
     DS --fs-hint (11.5)  -> --t-sm         (identical)
     DS --fs-control (13) -> --t-base       (identical)
     DS --font-mono       -> --mono

   Type is the app's --t-* scale throughout: the Queue is ADAPTED to carry these
   rows, not restyled, and no font changes. */
@layer components {

  /* KindChip — a machine label, fixed width so kinds align into a column when
     several stack up. Never a status; that is .status-pill. */
  .kind-chip {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 92px; height: 19px; padding: 0 7px; flex: 0 0 auto;
    border-radius: var(--r-sm); background: var(--accent-soft);
    border: 1px solid var(--accent-border); color: var(--accent-ink);
    font-family: var(--mono); font-size: var(--t-xs); font-weight: 600;
  }

  /* Section + point tones. Ruby passes a NAME (RecapSection#tone); the colour is
     resolved here, so the palette keeps one home. */
  .rc-dot { width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto; margin-top: 8px; }
  .rc-dot--neutral { background: var(--dim); }
  .rc-dot--accent  { background: var(--accent); }
  .rc-dot--warning { background: var(--badge-amber); }
  .rc-dot--success { background: var(--ok); }

  /* ── The Queue row ── */
  .q-recap-head { display: flex; align-items: center; gap: 8px; min-width: 0; margin-bottom: 4px; }
  /* In a row the kind is a quiet tag beside the title, not the accent chip the Docket
     stacks into a column. */
  .q-recap-head .kind-chip, .rc-card-head .kind-chip { min-width: 0; height: auto; padding: 3px 6px; border-color: var(--border-control); background: var(--inset); color: var(--ink-2); font-size: 10.5px; line-height: 1; }
  .q-recap-title { min-width: 0; font-size: var(--t-base); font-weight: 600; line-height: 1.35; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* ── The expanded editor ── each section a small bordered table: its name on a tinted
     head, a line per point, "+ Add point" along its foot. To queue.html's .rc. */
  .q-recap .rc-hint { margin: 5px 0 0; font-size: var(--t-sm); color: var(--dim); }
  .rc-section { margin-top: 14px; border: 1px solid var(--border-divider); border-radius: var(--r-md); overflow: hidden; }
  .rc-section-head { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--inset); border-bottom: 1px solid var(--border-row); }
  .rc-section-head .rc-dot { margin-top: 0; }
  .rc-section-input { flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink); outline: 0; }
  .rc-section-tools { display: flex; align-items: center; gap: 2px; opacity: 0; transition: opacity 0.15s; }
  .rc-section:hover .rc-section-tools, .rc-section:focus-within .rc-section-tools { opacity: 1; }
  .rc-move, .rc-remove {
    height: 24px; padding: 0; border: 0; border-radius: 5px; background: none; cursor: pointer;
    font: inherit; font-size: var(--t-sm); line-height: 1; color: var(--dim);
    &:hover { background: var(--icon-tile); color: var(--ink); }
  }
  .rc-move { width: 24px; }
  .rc-remove { padding: 0 6px; font-size: 11.5px; }
  .rc-remove--point { width: 24px; padding: 0; font-size: var(--t-lg); opacity: 0; transition: opacity 0.12s; &:hover { background: var(--err-tint); color: var(--err); } }
  .rc-point:hover .rc-remove--point, .rc-point:focus-within .rc-remove--point { opacity: 1; }

  /* ⚠ A flex row, not the mockup's fixed grid: a point in an owner-heading has an Owner
     field as well as a campaign, so the number of cells differs by section. */
  .rc-point { display: flex; align-items: center; gap: 10px; padding: 7px 12px; border-top: 1px solid var(--border-row); }
  .rc-section-head + .rc-point { border-top: 0; }
  .rc-point .rc-dot { width: 8px; height: 8px; margin-top: 0; }
  .rc-text, .rc-owner, .rc-tag { min-width: 0; border: 0; background: none; font: inherit; font-size: var(--t-base); color: var(--ink); outline: 0; }
  .rc-text { flex: 1 1 auto; }
  .rc-owner { flex: 0 0 auto; width: 90px; font-size: var(--t-sm); color: var(--soft); }
  .rc-tag { flex: 0 0 auto; width: 110px; font-size: var(--t-sm); color: var(--soft); cursor: pointer; &.is-tagged { color: var(--accent); font-weight: 500; } }
  .rc-vis { flex: 0 0 auto; padding: 2px 7px; border: 0; border-radius: var(--r-full); background: var(--icon-tile); font: inherit; font-size: var(--t-xs); font-weight: 600; color: var(--ink-2); cursor: pointer; }
  .rc-point.is-withheld .rc-vis { background: var(--amber-tint); color: var(--amber-ink); }
  .rc-point.is-withheld .rc-text { color: var(--soft); }
  .rc-add {
    height: 26px; padding: 0 9px; cursor: pointer; font: inherit; font-size: var(--t-sm); color: var(--ink-2);
    border: 1px dashed var(--border-control); border-radius: var(--r); background: none;
    &:hover { border-style: solid; color: var(--ink); }
  }
  .rc-add--point { display: block; width: 100%; height: auto; padding: 7px 12px; border: 0; border-top: 1px dashed var(--border-divider); border-radius: 0; font-size: 12.5px; color: var(--soft); text-align: left; &:hover { color: var(--ink); background: var(--inset); } }
  .rc-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; font-size: var(--t-sm); }
  .rc-foot-manage { margin-left: auto; color: var(--soft); }
  .rc-foot .rc-hint { margin: 0 0 0 4px; }

  @media (max-width: 700px) {
    .rc-tag { display: none; }
    .rc-section-tools, .rc-remove--point { opacity: 1; }
  }

  /* ── The headings registry (/recap_headings) ──
     Rides the Actions table (.dk-table / .dk-row) so the two registries read as siblings;
     only the grid and the click behaviour differ — these rows edit in place rather than
     navigating, so they are not a pointer. */
  .dk-row--heading {
    grid-template-columns: minmax(0, 1fr) 80px;
    cursor: default;
    &:hover { background: transparent; }
  }
  .dk-row--head.dk-row--heading, .dk-row--add {
    grid-template-columns: minmax(0, 1.6fr) 130px 100px 80px;
  }
  .dk-row--add { border-top: 1px solid var(--border2); background: var(--inset);
                 &:hover { background: var(--inset); } }
  .rh-form { display: grid; grid-template-columns: minmax(0, 1.6fr) 130px 100px;
             gap: 16px; align-items: center; min-width: 0; }
  .rh-label { display: flex; align-items: center; gap: 9px; min-width: 0; }
  .rh-label .rc-dot { margin-top: 0; width: 7px; height: 7px; }
  .rh-input {
    height: var(--control-h); min-width: 0; padding: 0 10px; background: var(--card);
    border: 1px solid var(--border-control); border-radius: var(--r);
    font: inherit; font-size: var(--t-base); color: var(--ink); outline: none;
    &:focus { border-color: var(--ink); box-shadow: var(--ring); }
    &:disabled { background: transparent; border-color: transparent; color: var(--soft); }
  }
  .rh-input { width: 100%; }
  /* Four swatches: the colour is the control. The radio is invisible and on top, so the
     whole swatch is the target; the ring says which one is chosen. */
  .rh-swatches { display: inline-flex; align-items: center; gap: 6px; }
  .rh-swatch { position: relative; display: inline-flex; width: 22px; height: 22px; cursor: pointer; }
  .rh-swatch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
  .rh-swatch span { width: 14px; height: 14px; margin: auto; border-radius: 50%; background: var(--sw); transition: box-shadow 0.12s; }
  .rh-swatch--neutral { --sw: var(--dim); }
  .rh-swatch--accent  { --sw: var(--accent); }
  .rh-swatch--warning { --sw: var(--badge-amber); }
  .rh-swatch--success { --sw: var(--ok); }
  .rh-swatch:hover span { box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--border-control); }
  .rh-swatch:has(input:checked) span { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--sw); }
  .rh-swatch:has(input:focus-visible) span { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--ink); }
  .rh-swatch:has(input:disabled) { cursor: default; opacity: 0.45; }
  .rh-swatch:has(input:disabled) input { cursor: default; }
  /* A name reads as text until it is pointed at or edited. */
  .dk-row--heading:not(.dk-row--add) .rh-input:not(:disabled) {
    border-color: transparent; background: transparent; font-weight: 500;

    &:hover { border-color: var(--border-row); background: var(--inset); }
    &:focus { border-color: var(--ink); background: var(--card); }
  }
  .rh-owners { display: inline-flex; align-items: center; gap: 6px;
               font-size: var(--t-base); color: var(--soft); cursor: pointer; }

  /* ── The timeline card ── */
  .rc-card { margin-top: 6px; border: 1px solid var(--border-divider); border-radius: 10px; overflow: hidden; background: var(--card); }
  .rc-card-head { display: flex; align-items: center; gap: 8px; padding: 9px 12px; background: var(--inset); border-bottom: 1px solid var(--border-row); }
  .rc-card-title { min-width: 0; font-size: var(--t-base); font-weight: 600; color: var(--ink); }
  .rc-card-when { margin-left: auto; font-size: var(--t-sm); color: var(--soft); white-space: nowrap; }
  .rc-card-section { padding: 8px 12px; & + & { border-top: 1px solid var(--border-row); } }
  .rc-card-label { margin-bottom: 4px; font-size: var(--t-xs); font-weight: 600; color: var(--soft); }
  .rc-card-point { display: flex; align-items: baseline; gap: 8px; padding: 3px 0; font-size: var(--t-base); }
  .rc-card-point .rc-dot { margin-top: 0; transform: translateY(-2px); }
  .rc-card-text { flex: 1 1 auto; min-width: 0; line-height: 1.5; color: var(--ink); overflow-wrap: anywhere; }
  .rc-card-point.is-withheld .rc-card-text { color: var(--soft); }
  .rc-card-owner, .rc-card-tag, .rc-card-held { flex: 0 0 auto; white-space: nowrap; }
  .rc-card-owner { font-size: var(--t-sm); color: var(--soft); }
  .rc-card-tag { padding: 1px 7px; border-radius: var(--r-full); background: var(--accent-tint); font-size: 11.5px; color: var(--accent-ink); }
  .rc-card-held { padding: 1px 7px; border-radius: var(--r-full); background: var(--amber-tint); font-size: var(--t-xs); font-weight: 600; color: var(--amber-ink); }
  .rc-card-empty { margin: 0; padding: 12px; font-size: var(--t-base); color: var(--soft); }

  /* The editor is the most input-dense surface in the app, so it reflows rather than
     scrolls sideways. ⚠ Font sizes are NOT touched here — the 16px input rule in the
     mobile query below is the iOS zoom threshold, not a style. */
  @media (max-width: 720px) {
    .rc-point { flex-wrap: wrap; }
    .rc-text { flex: 1 1 100%; }
    .rc-owner, .rc-tag { flex: 1 1 auto; width: auto; }
  }
}
