/* AdEQ — the web app.
 *
 * BRAND.md holds here exactly as it holds in the panel: dark-first, ONE accent
 * (deep green), never a second, never a gradient. The eight Plutchik colours
 * are semantic and appear only where an emotion is being named — the emotion
 * dot and the report's own rail, both drawn by report-core.js. Nothing on this
 * page repurposes one as chrome.
 *
 * The one thing that is different from the panel is the width, and width is
 * the whole reason this exists. A 320px column has to stack everything; a desk
 * can put the creative beside the copy and forty rows of history on a screen.
 * So: wider measures, real tables, two columns where two columns say more.
 * Density over drama still applies — it is a working tool, not a landing page.
 */

:root {
  --paper:#111413; --card:#191D1B; --ink:#EDEFEA; --ink-soft:#BFC5BE;
  --mute:#8A918A; --rule:#2A2F2C; --rule-faint:#222724; --shade:#1F2422;
  /* THE FOLD'S HOVER TILE, the pencil's dark square, and the two drawn chevrons a summary can't colour for itself. */
  --fold-w: 22px; --fold-tile: rgb(0 0 0 / .3);
  --chev-rest: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238A918A' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --chev-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23EDEFEA' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --accent:#7FC0A8; --accent-ink:#111413; --alarm:#E8827A;
  --c-slate:#6E7A80; --c-moss:#6E8060; --c-clay:#9A6E5C;
  --c-sand:#9A8C63; --c-sea:#5E7E8C; --c-plum:#7E6A8C;
  /* THE WEBSITE'S FACES (Franz, 2026-10-28: "match the app to the website
     fonts and style"). Inter Tight carries every word and number, headings
     included (--font-display stays a name so the 86 places that use it
     follow). Big Shoulders Display is the poster face, used only at 20px and
     up: page titles and the big numbers, never a label, where condensed
     letters would cost reading. The wordmark keeps Archivo, as on the site.
     Was Space Grotesk and Archivo (2026-09-14). The creatives' own faces
     (Anton, Bricolage, Instrument Serif, Space Grotesk) are loaded for drawn
     statics and are not the app's. */
  --font-display:'Inter Tight','Helvetica Neue',Arial,sans-serif;
  --font-body:'Inter Tight','Helvetica Neue',Arial,sans-serif;
  --font-poster:'Big Shoulders Display','Inter Tight',sans-serif;
  --font-mark:'Archivo','Helvetica Neue',Arial,sans-serif;
  /* radius tokens: web/layout.css (SPEC-LAYOUT-V1 §2f) */
  /* EVERY FIELD LABEL IS 14px. Franz, 2026-09-16: "i want them 14 pixels all of
     them" — Template, Side Of The Business, Existing, What's left, Worth doing.
     They are all one class and one token, so this is the one place it changes.
     A column heading in a table is not one of those, and it keeps the smaller
     size under its own token so the work tables do not grow a point. */
  --label-size:14px;
  --col-label-size:12.5px;
  color-scheme: dark;
}
/* DAY IS PAPER, NOT SNOW (Franz, 2026-10-19: "too much white hurts my eyes").
   The page is a soft grey-green and the cards only a step lighter, so no
   surface is near white; the ink and lines are deepened to keep contrast. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --paper:#E3E6E0; --card:#ECEEE9; --ink:#1B1F1D; --ink-soft:#3A403C;
    --mute:#626862; --rule:#C9CEC6; --rule-faint:#D5D9D1; --shade:#DADDD6;
    --fold-tile: rgb(0 0 0 / .1);
    --chev-rest: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23626862' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    --chev-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231B1F1D' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    --accent:#2F5D50; --accent-ink:#F1F3EF; --alarm:#B4453A;
    /* ONLY LIGHT: a dark-mode extension or Chrome's force-dark must not
       repaint day mode (Franz's Chrome has one). */
    color-scheme: only light;
  }
}
/* The same, picked by hand with the day/night switch. */
:root[data-theme="light"] {
  --paper:#E3E6E0; --card:#ECEEE9; --ink:#1B1F1D; --ink-soft:#3A403C;
  --mute:#626862; --rule:#C9CEC6; --rule-faint:#D5D9D1; --shade:#DADDD6;
  --fold-tile: rgb(0 0 0 / .1);
  --chev-rest: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23626862' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --chev-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231B1F1D' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --accent:#2F5D50; --accent-ink:#F1F3EF; --alarm:#B4453A;
  color-scheme: only light;
}

* { box-sizing:border-box; }
body {
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--font-body); font-size:14px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
button { font:inherit; color:inherit; }
a { color:var(--accent); }
[hidden] { display:none !important; }
.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:calc(-1 * var(--s-px));
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

.wordmark { font:600 17px/1 var(--font-mark); letter-spacing:-.01em; margin:0; }
/* The mark sits in front of the wordmark, the same lockup as the extension's
   header. It is generated by assets/make-icons.js and served as /favicon.svg. */
.wordmark, .wordmark-btn, .guest-mark { display:inline-flex; align-items:center; gap:var(--s-1); }
.wordmark-mark { width:22px; height:22px; border-radius:5px; flex:none; }
/* THE WORDMARK IN TWO TONES (memory adeq-wordmark): "Ad" in the text colour,
   "EQ" in the accent, so it reads on night and on day (2026-10-26: "Ad" was
   white on a white page). One span holds both, so the lockup's gap stays outside the name. */
.wordmark .wm-ad, .wordmark-btn .wm-ad, .guest-mark .wm-ad { color: var(--ink); }
.wordmark .wm-eq, .wordmark-btn .wm-eq, .guest-mark .wm-eq { color: var(--accent); }

/* ── the shell ────────────────────────────────────────────────────────────── */

.top {
  position:sticky; top:0; z-index:10;
  display:flex; align-items:center; gap:var(--s-3);
  /* EDGE TO EDGE, A SLIM GUTTER. Franz, 2026-10-17: "the main header can
     stretch to full width and doesnt need padding". It used to take the page's
     gutter (up to 48px) so the wordmark lined up with the titles; it is the
     app's frame now, not part of the page, so it keeps 16px at every width. */
  padding:0 var(--s-2); min-height:var(--header-h);
  background:var(--paper); border-bottom:1px solid var(--rule);
}
/* NO BAND, NO FADE, NO SHADOW UNDER THE BAR. Franz, 2026-09-16: "dont like
   that shadow scrioll under the header". Two goes at giving the page some air
   under a sticky bar — an opaque strip, then a gradient — and both were the bar
   pretending to be taller than it is. A hairline and the page beneath it is the
   honest version, and it is what every tool he uses does.

   AND IT DOES NOT BOUNCE. "the scroll offsets teh header like bouncy effect
   like doesnt look professional at all" — that is the platform's rubber band:
   drag past the top and the whole document, sticky bar included, slides down
   off its own edge and springs back. `overscroll-behavior-y: none` on the root
   turns the bounce off and leaves ordinary scrolling untouched. It is set on
   the ROOT only: on a card or a table it stops a wheel from ever reaching the
   page behind it, which has cost this product two afternoons already. */
html { overscroll-behavior-y: none; }

.tabs { display:flex; gap:var(--s-3); }
/* THE MAIN NAV IS THE MOST-READ TEXT ON THE PAGE, so it is the last place to
   be quiet. It was --mute at 400 weight, the same colour as a hint and a
   timestamp, and the tab you were on was the only one legible. The resting
   state now sits a full step brighter and carries the display face's weight;
   the current one is full ink with the accent under it. */
.tab {
  background:none; border:0; padding:var(--s-2) 0; cursor:pointer;
  font: 500 14px/1 var(--font-display); letter-spacing: -.005em;
  color:var(--ink-soft);
  border-bottom:2px solid transparent;
}
.tab:hover { color:var(--ink); }
.tab[aria-current="page"] { color:var(--ink); border-bottom-color:var(--accent); font-weight: 600; }
.top-right { margin-left:auto; display:flex; align-items:center; gap:var(--s-2); }
.who { font-size:13px; color:var(--mute); }
.linkbtn {
  background:none; border:0; padding:0; cursor:pointer;
  font-size:13px; color:var(--accent);
}
.linkbtn:hover { text-decoration:underline; }

/* ── THE PAGE FRAME AND THE TYPE SCALE ──────────────────────────────────────
   Franz, 2026-09-14: every page the same width, the same top padding, the same
   headers and body. They were not: h1 was 23px on one page and 24px on
   another, section headings ran 13, 14, 16 and 20px depending on who wrote
   them, and half a dozen blocks set their own max-width. Each choice was
   defensible alone; together they read as five people's work.

   FOUR SIZES, and everything on every page is one of them:
     title    a page's name, once per page
     section  a heading inside a page
     label    the header of a card or a field
     body     everything else

   ONE WIDTH: the page. Only prose meant to be READ is narrower, because a
   sentence 1,200px wide is a sentence nobody finishes. */
:root {
  /* --header-h, the stick tokens, --w-page, --w-read and --pad-page live in
     web/layout.css now, with every other size token (SPEC-LAYOUT-V1 §2). */

  /* THE LABEL COLOUR. Franz asked for the labels to carry a colour of their
     own, matching and contrasting: they are the navigation of a long document
     and they were the same white as the prose they head. It is the ONE accent
     mixed a little towards the ink — not a second hue, which BRAND.md forbids,
     and never one of the eight Plutchik colours, which mean a feeling and
     nothing else. */
  --label-ink: color-mix(in srgb, var(--accent) 86%, var(--ink) 14%);

  /* TWO TITLES (Franz, 2026-10-28: a set's long name in the poster face "is
     ruined"). A piece of work's title (a set, a script, a video, a brief) is
     Inter Tight; the poster face is for a short page name only (Home, Today,
     Library, Team, Clients, a client's name: .page-head.is-poster and the
     client head), Title Case, never forced to caps, so AdEQ keeps its d. */
  --t-title: 600 26px/1.2 var(--font-display);
  /* 28px, not 34 (Franz, 2026-10-28: "reduce this font a bit for all,
     proportionately"): the big numbers step down in the same ratio. */
  --t-poster: 800 28px/1 var(--font-poster);
  --t-section: 600 16px/1.3 var(--font-display);
  --t-label: 600 13px/1.3 var(--font-display);
  --tr-title: -.014em;
  --tr-poster: .005em;
  --tr-section: -.012em;
  --tr-label: -.006em;
}

/* Less at the top than at the sides: the header bar above it already gives the
   page its air, and 28px on top of that pushed the first card down the screen. */
main { padding: var(--pad-page-top) var(--pad-page) var(--pad-page-end); max-width: var(--w-page); margin: 0 auto; }

/* ── the sign-in gate ─────────────────────────────────────────────────────── */
/* The SAME token as the extension, from the SAME route. There is no second
   auth here and there must never be one: POST /v1/auth/redeem takes the access
   code Franz minted on /admin and returns a bearer token. */

.gate { min-height:100dvh; display:grid; place-items:center; padding:var(--s-3); }
.gate-card { width:100%; max-width:var(--w-gate); }
.gate-card h1 { font: var(--t-title); letter-spacing: var(--tr-title); margin:var(--s-2) 0 var(--s-1); }
.gate-lede { color:var(--mute); font-size:13.5px; margin:0 0 var(--s-3); }
.gate-err { color:var(--alarm); font-size:13px; margin:var(--s-1) 0 0; }
.gate-foot { color:var(--mute); font-size:13px; margin:var(--s-2) 0 0; }
.gate .btn { margin-top:var(--s-2); }

/* ── form furniture ───────────────────────────────────────────────────────── */

/* 600, NOT 500. Franz, 2026-09-16: "increase the weight of all sub labels that
   are 14 from 500 to 600 mate". At 14px a label at the same weight as the
   answer under it stopped reading as the question. A table's column heading is
   not one of these and keeps its own weight with its own size. */
label, .label {
  display:block; font-weight:600; font-size:var(--label-size);
  color:var(--ink-soft); margin:0 0 var(--s-half);
}
.input, .textarea {
  width:100%; font:400 14px/1.5 var(--font-body); color:var(--ink);
  background:var(--card); border:1px solid var(--rule);
  border-radius:var(--r-md); padding:var(--s-1) var(--s-1h);
}
.input { min-height:var(--h-lg); }
.textarea { display:block; resize:vertical; }
/* NO GLOW, ON HOVER OR ON FOCUS. Franz, 2026-09-16: "i dont want glow on border
   on hover or select". A green ring around whatever the pointer happened to be
   over made the page flicker as you crossed it, and the accent is this
   product's way of saying "this matters", not "you are near this". A field you
   are typing in still says so, quietly, in the same grey the rest of the
   furniture uses. */
.input:hover, .textarea:hover { border-color:var(--rule); }
.input:focus, .textarea:focus { outline:none; border-color:var(--ink-soft); }
.input.is-over, .textarea.is-over { border-color:var(--alarm); }

/* CONTROLS TAKE THEIR HEIGHT FROM A TOKEN (SPEC-LAYOUT-V1 §2c). Padding sets
   the sides only and the label is centred, so two controls in one row agree
   whatever their font. A floor, not a height: a label that wraps still grows. */
.btn {
  font:600 14px/1 var(--font-body); background:var(--accent); color:var(--accent-ink);
  border:0; border-radius:var(--r-md); padding:0 var(--s-2); cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center; gap:var(--gap-inline);
  min-height:var(--h-lg);
}
.btn:hover:not(:disabled) { filter:brightness(1.06); }
.btn:disabled { opacity:.5; cursor:default; }
.btn-quiet {
  background:none; color:var(--ink); border:1px solid var(--rule);
}
.btn-quiet:hover { border-color:var(--ink-soft); filter:none; }

/* HELP TEXT INSIDE A FIELD, one rule for the whole product. Franz has asked
   for this twice: smaller, quieter and italic, so the hint reads as a hint and
   never as something somebody typed. Same shape as .pf-hint, which is the one
   he approved. */
::placeholder { font-size:11.5px; font-style:italic; color:var(--mute); opacity:.8; }

.hint { font-size:13px; color:var(--mute); margin:var(--s-1) 0 0; line-height:1.5; }
.hint-alarm { color:var(--alarm); }

.page-head {
  display:flex; align-items:baseline; gap:var(--gap-toolbar); flex-wrap:wrap;
  margin:0 0 var(--s-2);
}
.page-head h1 { font: var(--t-title); letter-spacing: var(--tr-title); margin:0; }
.page-head.is-poster h1 { font: var(--t-poster); letter-spacing: var(--tr-poster); }
.page-head .count { font-size:13px; color:var(--mute); }

.state { padding:var(--s-5) 0; color:var(--mute); font-size:14px; }

/* ── history, as a list you can actually scan ─────────────────────────────── */
/* The panel's history is nine rows in a 320px column and it is right for what
   it is. This is the other thing: a wide table where a month of work is one
   screen, every row carries its client, its score and its tags, and the
   filters sit above it rather than behind a disclosure. */

.filters {
  display:flex; gap:var(--s-1); flex-wrap:wrap; align-items:center;
  margin:0 0 var(--s-2); padding-bottom:var(--s-2); border-bottom:1px solid var(--rule);
}
.filters .input { width:auto; min-width:200px; flex:1 1 220px; }
.filters select.input { flex:0 0 auto; min-width:150px; }

table.work { width:100%; border-collapse:collapse; }
table.work th {
  text-align:left; font-weight:500; font-size:var(--col-label-size);
  color:var(--mute); padding:0 var(--s-1h) var(--s-1) 0; border-bottom:1px solid var(--rule);
  white-space:nowrap;
}
table.work td {
  padding:var(--s-1h) var(--s-1h) var(--s-1h) 0; border-bottom:1px solid var(--rule-faint);
  vertical-align:top;
}
table.work tr:hover td { background:var(--shade); }
table.work .c-score { width:56px; }
table.work .c-when { width:130px; color:var(--mute); font-size:13px; white-space:nowrap; }
table.work .c-client { width:190px; }
table.work .c-tags { width:210px; }

.rowtitle {
  background:none; border:0; padding:0; cursor:pointer; text-align:left;
  font-size:14px; color:var(--ink);
}
.rowtitle:hover { color:var(--accent); text-decoration:underline; }
.rowkind { display:block; font-size:12.5px; color:var(--mute); }

/* The score is a number and a word, never a colour: BRAND.md keeps colour for
   emotion, and tinting a score green or red would be a second palette doing a
   job the band word already does. */
.score { font:600 19px/1 var(--font-display); letter-spacing:-.01em; font-variant-numeric:tabular-nums; }
.score-band { display:block; font-size:12px; color:var(--mute); }

.dot {
  display:inline-block; width:9px; height:9px; border-radius:50%;
  margin-right:var(--s-1); vertical-align:baseline;
  box-shadow:inset 0 0 0 1px rgb(0 0 0 / .1);
}
.dot.is-slate{background:var(--c-slate)} .dot.is-moss{background:var(--c-moss)}
.dot.is-clay{background:var(--c-clay)}   .dot.is-sand{background:var(--c-sand)}
.dot.is-sea{background:var(--c-sea)}     .dot.is-plum{background:var(--c-plum)}

.tag {
  display:inline-flex; align-items:center; font-size:12px; min-height:20px;
  padding:0 var(--s-1); margin:0 var(--s-half) var(--s-half) 0;
  border:1px solid var(--rule); border-radius:var(--r-pill); color:var(--ink-soft);
}

.more { margin:var(--s-3) 0 0; }

/* ── a client's profile, at full width ────────────────────────────────────── */
/* This is where a client is actually onboarded. Seven boxes, all seven visible
   at once, no wizard and no step counter — a form you can see the end of is a
   form that gets finished. Nothing is required and nothing says it is. */

/* CONTEXT IS THE WIDEST PAGE IN THE PRODUCT, on purpose. Franz: "I hate the
   context page, increase the width." It holds six paragraphs of brand fact
   side by side with a dossier of documents, and squeezing that into a 900px
   column is why it felt cramped. It gets the full page. */
/* The profile gets a heading of its own now that two sections sit above it.
   Without one it read as a caption hanging off the dossier. */
.profile-h { font: var(--t-section); letter-spacing: var(--tr-section); margin: var(--s-4) 0 var(--s-1); }
.profile-lede { max-width: var(--w-read); margin:0 0 var(--s-2); }

/* EACH FIELD IS A CARD. Franz: "the client context cards have to be cards and
   not a list." They were a bare label over a box on a flat page, which reads
   as a form — and a form is a thing you fill in once, not a thing you keep
   coming back to read. A card has an edge, so each fact is a separate object
   you can look at on its own. */
/* PINTEREST, AND THIS TIME IT STAYS. Franz, 2026-09-15, with a screenshot of
   two cards each carrying a screen's depth of empty card under its last row:
   "i had asked for pinterested style arrangement dont like these gaps at all".

   THIS IS THE THIRD SHAPE THIS BOARD HAS HAD, and the argument is worth keeping
   because it comes back. It was a masonry, then a stretched grid on the reason
   written below, and it is a masonry again on Franz's word:

     A GRID stretches every card in a row to the height of the tallest, so the
     slack lands INSIDE the short cards — a hand's depth of empty card under
     "4.8 stars". No ragged bottom, but the hole is on every short card.

     A MASONRY packs: each card is exactly as tall as its contents and the next
     one starts where it ends. No hole anywhere, and the price is that the three
     columns end at three different heights.

   He has now seen both and chosen the ragged bottom over the holes, which is
   the right way round: the bottom of the page is empty either way, and empty
   card between a fact and the card's own edge is the one that reads as broken.

   HOW IT PACKS, since CSS has no masonry yet. One-pixel auto-rows with no row
   gap, and every card spans its own measured height plus the twenty pixels that
   are the gap. cardBoard() in app.js does the measuring, on a ResizeObserver, so
   a list that grows by a row repacks the column under it the same frame.

   The cards are also DRAGGABLE, which is why this is a grid of spans rather than
   CSS columns: `column-count` would pack just as well and there would be no
   such thing as a card's position in it. */
.profile-form {
  display: grid;
  /* TWO COLUMNS, NOT THREE. Franz, 2026-09-15: "i need two proper columns
     there". Every one of these cards is a TABLE now — a sentence, a tag and two
     verbs — and at a third of the page the tag column clipped to "Genera…",
     which is a label nobody can read and therefore a label doing nothing. Two
     columns give each table about 560px, which is the width its three columns
     actually need. The board still collapses to one on a narrow window. */
  grid-template-columns: repeat(auto-fit, minmax(min(460px, 100%), 1fr));
  /* DENSE, BECAUSE THE COLUMN IS NOW THE CARD'S OWN. cardBoard() pins each card
     to a side so that opening one card cannot throw the ones after it across
     the board — and an item with a definite column but an automatic row is
     otherwise placed at the flow cursor, which leaves a card-shaped hole above
     every card whose neighbour on the other side is taller. Dense packing puts
     each one at the first row of its own column that is free, which is exactly
     a column of cards. */
  grid-auto-flow: row dense;
  column-gap: var(--gap-card);
  row-gap: 0;
  grid-auto-rows: 1px;
  /* Never stretch. A stretched card is the hole this whole block is about. */
  align-items: start;
  max-width:none;
}
/* MORE COLUMNS ON A WIDER PAGE, not wider cards (2026-10-16, full width). The
   page has no cap now, so a two-column board at 1920 would be two 900px
   tables. Three columns from 1440 and four from 1920; cardBoard() packs any
   number and saves each card's column per column count. */
@media (min-width: 1440px) {
  .profile-form { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* A FOLDED STRIP KEEPS ITS FOLD CONTROL, at every width. A third of a wide
   page, and the whole of a phone, are narrower than a strip with its search
   and switches, so the search gives way first. (Until 2026-10-26 this held
   from 1440 only, and at 390 Lines of Business pushed its chevron off the card.) */
.profile-form .is-shut .dt-toolbar > *, .profile-form .is-shut .dossier-head > *,
.profile-form .is-shut .dump-h-row > * { white-space: nowrap; }
.profile-form .is-shut .dt-search { min-width: var(--h-md); flex: 0 1 160px; }
/* The card/list switch and the filters only mean something with the card
   open: they sort and narrow what is inside. Folded, they would only push the
   fold control out of the card. */
.profile-form .is-shut .seg-group, .profile-form .is-shut .media-filter { display: none; }
@media (min-width: 1920px) {
  .profile-form { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* The workspace page's boards hold three cards a row, one a table: a fourth
     column left a hole beside them and squeezed Agent Keys. */
  .profile-form.ws-top-board, .profile-form.ws-form { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.profile-form > .pf-field,
.profile-form > * {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* NO `flex: 1 1 auto` ON THE CONTENTS ANY MORE. It existed to push the slack of
   a stretched card down into its list; there is no slack now, and leaving it in
   makes a card measure taller than the thing inside it. */

/* --- moving a card ---------------------------------------------------------
   The title bar is the handle. Franz: "give me smooth and clean functionality
   to move around the cards below and the last position should be live."
   A whole-card handle would mean every click on a row was a possible drag. */
/* THE HANDLE IS THE STRIP AT THE TOP OF A CARD, whatever that strip is: a
   field's head, a table's toolbar, or the dump's own heading. It was `.pf-head`
   alone, and when every list card became a table its heading moved into the
   table's strip — so the cards lost their head and quietly stopped dragging. */
.profile-form .pf-head,
.profile-form .dt-toolbar,
.profile-form .dump-h {
  cursor: grab;
  /* So a drag on a touch screen is a drag and not a scroll. It is a short strip
     and the rest of the card scrolls normally. */
  touch-action: none;
}
/* The controls inside the strip are controls first — a search box is not a
   handle, and neither is "Add Images". */
.profile-form .pf-head button, .profile-form .pf-head a, .profile-form .pf-head .btn,
.profile-form .dt-toolbar button, .profile-form .dt-toolbar input,
.profile-form .dt-toolbar a, .profile-form .dt-toolbar label { cursor: pointer; touch-action: auto; }
.profile-form .dt-toolbar input { cursor: text; }
/* ANY CARD ON A BOARD, not just a field: the tables are board cards too and
   used to lift with no shadow at all (2026-10-06). */
.pf-field.is-dragging, [data-card].is-dragging {
  z-index: 5;
  cursor: grabbing;
  border-color: var(--ink-soft);
  box-shadow: 0 18px 44px rgba(0,0,0,.55);
}
.pf-field.is-dragging .pf-head { cursor: grabbing; }
/* While a card is in the air, nothing else on the page is a text selection or a
   hover target — both of which fight a drag that passes over them. */
body.is-boarding { user-select: none; -webkit-user-select: none; }
body.is-boarding .pf-field:not(.is-dragging),
body.is-boarding [data-card]:not(.is-dragging) { pointer-events: none; }
body.is-boarding, body.is-boarding * { cursor: grabbing !important; }
/* Every card is about to be moved by a transform, so hand them all to the
   compositor for the length of the drag — and take them back afterwards, since
   a permanent will-change is a permanent layer for seven tall cards. */
body.is-boarding .pf-field, body.is-boarding [data-card] { will-change: transform; }
@media (prefers-reduced-motion: reduce) {
  .pf-field { transition: none !important; }
}

/* A CARD HAS A SIZE OF ITS OWN. A one-line answer in a box cropped to one line
   made three cards of three different heights read as an accident; a floor
   under the box makes them a set. It is a floor, not a height — everything here
   still grows to whatever was typed. */
.pf-field .textarea.is-short { min-height: 86px; }
.pf-field .lled { min-height: 64px; }
/* A table brings its own heading and its own top padding in the toolbar. */
.pf-field.is-table { padding-top: var(--s-1h); }

/* THE BOTTOM OF A CARD. Franz, 2026-09-15: "i dont like the bottom part of
   almost every card mate i need smaller extra space and better uniform up and
   down spacing from the adding new entry bar in each".
   There was 62px under the add bar and 10px over it. The 62 was not a decision:
   it was the status line reserving 18px of margin and 20px of height for a word
   that is on screen for 1.4 seconds after a save, plus the card's own padding.
   A row of empty space held for a message that is not there is a hole.
   So the status collapses when it has nothing to say, and the card's bottom
   padding matches the gap above the bar: 10 over, 10 under. */
.scopedtable > .pf-status,
.linktable > .pf-status { margin: 0; min-height: 0; }
.scopedtable > .pf-status:empty,
.linktable > .pf-status:empty, .script-state-msg:empty { display: none; }
.pf-field.is-table { padding-bottom: var(--s-1); }
.pf-field {
  margin:0; background:var(--card); border:1px solid var(--rule);
  border-radius:var(--r-lg); padding:var(--s-2) var(--s-2) var(--s-1h);
}
/* NO LIT BORDER WHEN A CARD IS TOUCHED. Franz, 2026-09-16: "i dont like the
   border highlight when opening any mate remove it and let it be clean".
   Clicking the chevron focuses it, which put the whole card in `:focus-within`
   and lit its border — so the act of opening a card announced itself with a
   ring around something you were already looking at. The border says where the
   card ends and nothing else. */
/* The label is the header of its card, so it is set like one: bolder, darker,
   and given room. It was 12px grey and disappeared into the hint below it. */
.pf-head { display:flex; align-items:baseline; justify-content:space-between; gap:var(--s-1); margin:0 0 var(--s-1); }
/* A CARD'S TITLE IS SET LIKE "DUMP ANYTHING YOU HAVE". Franz, 2026-09-15: "i
   want all labels in every card including the card on top to have this font
   size on title Dump anything you have".
   That heading is --t-section, 16px; every other card on the page was --t-label
   at 13px, so the one card with a paragraph in it out-titled the eight carrying
   the whole brief. They are all the same rank and they are set the same now:
   Products and Services, Facts We Can Use, Audience, Hooks, and Images and
   media at the top, which draws its heading through this same rule. */
/* `:not(.chip)` because the strip can also hold a control that happens to be a
   <label> — "Add Images" is one, since it opens a file input — and a button in
   a title bar is not a title. Without this it inherited the heading's 16px and
   sat a pixel taller than the Columns pill beside it. */
.pf-head label:not(.chip) {
  font: var(--t-section); letter-spacing: var(--tr-section); color:var(--ink);
}
.pf-limit { font-size:12px; color:var(--mute); font-variant-numeric:tabular-nums; }
.pf-limit[hidden] { display:none !important; }
.pf-limit.is-over { color:var(--alarm); }
/* Help is help, not content: smaller, fainter, italic, so it reads as an
   aside rather than competing with the label above it. */
.textarea.is-short { min-height:0; }
.pf-field textarea { width:100%; resize:none; overflow:hidden; font:inherit; line-height:1.5; }
.pf-status { margin:var(--s-2) 0 0; font-size:13px; color:var(--mute); min-height:20px; }
.pf-status.is-alarm { color:var(--alarm); }

.backlink { background:none; border:0; padding:0; cursor:pointer; color:var(--accent); font-size:13.5px; }
.backlink:hover { text-decoration:underline; }

/* ── a report, read at full width ─────────────────────────────────────────── */
/* report-core.js draws it, the same file the panel uses — one renderer, so a
   report never reads as two different judgements depending on where it was
   opened. What the width buys is the creative sitting BESIDE the reading
   rather than above it, which is the one thing 320px can never do. */

.report-wrap { display:grid; gap:var(--s-4); grid-template-columns:minmax(0,1fr); }
@media (min-width: 1000px) {
  .report-wrap { grid-template-columns:minmax(0, 340px) minmax(0, 1fr); align-items:start; }
  /* 76px used to be written here — the same idea as `--stick-top`, spelled as a
     number, and 8px off it. One offset for every sticky thing in the product. */
  .report-aside { position:sticky; top: var(--stick-top); }
}
.report-aside img {
  width:100%; height:auto; display:block; border-radius:var(--r-md);
  background:var(--shade);
}
.aside-sec { margin:var(--s-2) 0 0; }
.aside-sec h3 {
  font-weight:500; font-size:var(--col-label-size); color:var(--mute);
  margin:0 0 var(--s-half); text-transform:none;
}
.aside-sec p { margin:0 0 var(--s-1h); font-size:13.5px; color:var(--ink-soft); line-height:1.5; }

/* The rating rows. Same wording as the panel, same rule: two taps, never
   required, and nothing on this page treats an unrated report as unfinished. */
.rate { margin:var(--s-3) 0 0; padding-top:var(--s-2); border-top:1px solid var(--rule); }
.rate-q { display:flex; align-items:center; gap:var(--s-1h); flex-wrap:wrap; margin:0 0 var(--s-1); }
.rate-ask { font-weight:500; font-size:13px; color:var(--ink-soft); }
.chip {
  background:var(--card); border:1px solid var(--rule); border-radius:var(--r-pill);
  padding:0 var(--pad-chip-x); font-size:13px; cursor:pointer; color:var(--ink-soft);
  display:inline-flex; align-items:center; justify-content:center; gap:var(--s-half);
  min-height:var(--h-sm);
}
.chip:hover { border-color:var(--ink-soft); }
.chip[aria-pressed="true"] {
  background:var(--accent); border-color:var(--accent); color:var(--accent-ink);
}
.rate-note { margin-top:var(--s-2); max-width: var(--w-read); }

/* --- scripts -------------------------------------------------------------
   A script is read, not scanned, so the scene column is narrow on purpose:
   a voice-over line that runs the full width of a 1400px monitor is a line
   nobody can judge the rhythm of. */

.script-form { max-width: none; margin:0 0 var(--s-4); }
.script-form-top {
  display:grid; grid-template-columns:auto 1fr; gap:var(--s-1) var(--s-2);
  align-items:center; margin-bottom:var(--s-1h);
}
.script-form .lbl { color:var(--mute); font-size:12px; font-weight:500; }
.script-form textarea.input { width:100%; resize:vertical; min-height:96px; font:inherit; }
.script-form-go { display:flex; align-items:center; gap:var(--s-2); margin-top:var(--s-1h); }
.script-form-go .muted { margin:0; }

.pills { display:flex; gap:var(--s-1); flex-wrap:wrap; }
.pill {
  font:500 13px/1 var(--font-body); color:var(--ink-soft);
  background:var(--shade); border:1px solid var(--rule); border-radius:7px;
  padding:var(--s-1) var(--s-1h); cursor:pointer;
}
.pill:hover { border-color:var(--mute); }
.pill.is-on { background:var(--accent); border-color:var(--accent); color:var(--accent-ink); }

.script-list { max-width: none; }
.script-row {
  display:grid; grid-template-columns:56px 1fr 140px; gap:var(--s-2); align-items:baseline;
  width:100%; text-align:left; background:none; border:0;
  border-bottom:1px solid var(--rule-faint); padding:var(--s-1h) var(--s-half); cursor:pointer;
  color:var(--ink); font:inherit;
}
.script-row:hover { background:var(--shade); }
.script-len { font:500 13px/1 var(--font-body); color:var(--accent); }
.script-title { display:flex; flex-direction:column; gap:var(--s-half); min-width:0; }
.script-title strong { font-weight:500; }
.script-title .muted { font-size:12.5px; }

.needs {
  max-width: var(--w-read); margin:0 0 var(--s-3); padding:var(--s-2) var(--s-2);
  border:1px solid var(--accent); border-radius:var(--r-md); background:var(--shade);
}
.needs h2 { font:500 13px/1.3 var(--font-body); color:var(--accent); margin:0 0 var(--s-1); }
.needs ul { margin:0; padding-left:var(--s-2); }
.needs li { font-size:13.5px; line-height:1.55; color:var(--ink-soft); }

.problems { max-width: var(--w-read); margin:0 0 var(--s-3); }
.problems summary { cursor:pointer; color:var(--mute); font-size:13px; }
.problems ul { margin:var(--s-1) 0 0; padding-left:var(--s-2); }
.problems li { font-size:13px; line-height:1.55; color:var(--mute); }

/* ── the script page ────────────────────────────────────────────────────────
   It is a document, so it is set like one: a measured column of prose with
   the notes about it alongside, rather than a stack of cards a person scrolls
   past. The brief folds, the scenes have a jump rail, and a scene can be added
   between any two of them. */

/* NO NATIVE DISCLOSURE MARKERS ANYWHERE. Convention §3: a fold is a control
   and controls live at the end of a row. The browser's triangle is on the left
   and cannot be moved, so it is switched off product-wide and every summary
   draws its own chevron. This catches the ones written before the rule, which
   is how "Suggest a replacement line" still had a triangle on its left. */
summary { list-style: none; }
summary::-webkit-details-marker { display: none; }
summary::marker { content: ''; }

details > summary {
  display: flex; align-items: center; gap: var(--s-1); cursor: pointer;
}
details > summary::after {
  content: ''; margin-left: auto; flex: none;
  width: 6px; height: 6px;
  border-right: 1.5px solid var(--mute); border-bottom: 1.5px solid var(--mute);
  transform: rotate(45deg) translate(-1px, -1px); transition: transform .12s ease;
}
details:not([open]) > summary::after { transform: rotate(-45deg) translate(-1px, 1px); }
/* The section head draws its own, so it does not want a second. */
.sec > .sec-head::after { display: none; }

/* A SECTION: a heading, a fold on the RIGHT, and a body. Convention §3. */
.sec { margin: 0 0 var(--s-3); }
/* No rule above or below a section heading: the box underneath already draws
   the edge, and a line plus a border reads as two separators for one idea. */
.sec-head {
  display: flex; align-items: center; gap: var(--s-1); cursor: pointer;
  padding: 0 0 var(--s-1); list-style: none;
}
.sec-head::-webkit-details-marker { display: none; }
.sec-h {
  font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink);
  /* The heading takes the slack, so the chevron is the rightmost thing on the
     row whether or not the section has tools. Convention §3. */
  margin-right: auto;
}
.sec-tools { display: flex; align-items: center; gap: var(--s-1); }
.sec-chev {
  margin-left: var(--s-1h); width: 7px; height: 7px;
  border-right: 1.5px solid var(--mute); border-bottom: 1.5px solid var(--mute);
  transform: rotate(45deg) translate(-1px, -1px); transition: transform .12s ease;
}
.sec:not([open]) .sec-chev { transform: rotate(-45deg) translate(-1px, 1px); }
/* THE BODY IS A BOX. Franz: this has to be boxed. A bare list of rows on the
   page floats; the same rows inside a card are one object you can see the
   edges of, which is what makes a long page navigable. */
/* PADDED LIKE A CREATE CARD. `.c-card` is 16px 18px 14px, and this was 4px 18px
   — the same box with a quarter of the air at the top of it, which is what made
   the brief read as cramped beside the builder. */
.sec-body {
  border: 1px solid var(--rule); border-radius: var(--r-lg);
  background: var(--card); padding: var(--s-2) var(--s-2) var(--s-2);
}
/* The scenes are their own cards, so that section's body is not boxed twice. */
.sec-body.scenes { border: 0; background: none; padding: 0; }
/* EMPTY, DRAWN (brief-art.js emptyArt): the picture centred over its line. */
.wg-empty, .media-empty { align-items: center; text-align: center; }
.wg-empty .ba, .media-empty .ba { display: block; width: calc(var(--s-12) + var(--s-8)); height: auto; }
.media-empty { grid-column: 1 / -1; }
/* Nor a client's Profile and Settings sections, which hold cards (groupedBoards). */
.sec.cm-group { margin: 0 0 var(--gap-section); }
.cm-group > .sec-body { border: 0; background: none; padding: 0; }

/* THE REQUEST DESCRIPTION IS A DOCUMENT, NOT A TABLE. Franz, 2026-09-15: "im
   not liking the table look of this or the extra width on the right".
 
   Both faults had the same cause: a fixed reading measure in the grid
   (`170px minmax(0, --w-read)`) left a dead gutter whenever the page was wider
   than 74ch, and a hairline under every row turned six paragraphs into rows of
   a spreadsheet. The label sits ABOVE its paragraph now, the paragraph takes
   the width it is given, and what separates one from the next is space. */
.req-row {
  display: block; padding: 0 0 var(--s-3); border-bottom: 0;
}
.req-row:last-child { padding-bottom: 0; }
.req-label {
  display: flex; align-items: baseline; gap: var(--s-1); margin: 0 0 var(--s-half);
  font-weight: 600; font-size: var(--label-size); font-family: var(--font-body);
  letter-spacing: normal; color: var(--ink-soft);
}
.req-said { font: 400 11px/1 var(--font-body); color: var(--mute); }
.req-value {
  width: 100%; background: none; border: 1px solid transparent; border-radius: var(--r-md);
  color: var(--ink); font: 400 14px/1.62 var(--font-body); padding: var(--s-half) var(--s-1); margin: -5px calc(-1 * var(--s-1));
  resize: none; overflow: hidden;
  /* NOT JUSTIFIED. Justification stretches the spaces of a short line until
     "Footage to use: Toro aerator at" reads as a typesetting accident, and
     `hyphens: auto` breaks URLs across lines. Ragged right, balanced by
     text-wrap, is what prose in an interface wants. */
  text-align: left; text-wrap: pretty; overflow-wrap: anywhere;
}
.req-value:hover { border-color: var(--rule-faint); }
.req-value:focus { outline: none; border-color: var(--accent); background: var(--card); }

/* THE SCRIPT'S ONE TOOLBAR. The review state, the scene jumps and the two copy
   buttons, in a single sticky bar.
   LEVEL WITH THE ACTIVITY COLUMN. Franz, 2026-09-16: "instead of taking the
   activty column up lets rather bring the scene pills and copy description and
   copy script buttons a bit lower to match activtiy". Both are sticky and they
   came to rest 16px apart, so the bar's rule and the panel's top border never
   lined up. One offset, shared, and the offset is the panel's — bringing the
   bar down rather than the panel up keeps the log where it was. */
/* z-index 9, ABOVE the sticky head's 8, because the two overlap by a pixel on
   purpose — see `sizeHead()`. The bar covers the seam; the head must not cover
   the bar. */
.scene-rail {
  /* ONE SUBTRACTION, SHARED. `--stick-lift` is 0 until the bar pins and 8px
     after, and the Activity card reads the same variable — so the two can
     never rise by different amounts. */
  /* THE LIFT IS A TRANSFORM, NOT AN OFFSET — AND THAT IS NOT A STYLE CHOICE.
     Transitioning `top` on a position:sticky element BREAKS IT. The browser
     is recomputing the sticky offset every frame and the transition owns the
     same property, so the two fight and the value freezes at whatever it
     held first — 68px here, the :root default, which pinned the toolbar 45px
     UNDER the sticky title and let the section chevrons bleed through it.
     Forcing --stick-top to 200px directly on the element still produced
     68px, which is the proof: the declaration had stopped recomputing at all.
     `transform` animates on the compositor, never touches layout, and leaves
     `top` alone for sticky to own. */
  position: sticky; top: var(--stick-top); z-index: 9;
  transform: translateY(calc(var(--stick-lift, 0px) * -1));
  display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap;
  /* `--stick-pad-bare`, not `--stick-pad`: this strip has no border, so it has
     to make up the 1px the Activity card spends on one — otherwise its pills
     sit a pixel above that card's heading, which is exactly the misalignment
     this is here to end. */
  padding: var(--stick-pad-bare) 0 var(--stick-pad); margin: 0 0 var(--s-1);
  background: var(--paper);
  border-bottom: 1px solid var(--rule-faint);
  transition: padding .2s ease, margin .2s ease,
              transform .22s cubic-bezier(.22,.61,.36,1);
}
/**
 * COMPACT ONCE YOU ARE IN THE SCENES. Franz, 2026-09-16: "since its mostly
 * collapsed it should get even more compact when scrolling to the scenes cause
 * that's where most of the magic happens".
 *
 * AND IT TAKES UP EXACTLY THE SAME ROOM IN THE PAGE. A sticky element keeps its
 * box in the flow even while it is painted somewhere else, so shrinking one that
 * is stuck shortens the column and drags everything below it upward — you would
 * see the whole script twitch as you scrolled back to the top. Every pixel taken
 * off the padding is given back as margin, so the flow height never changes and
 * only the painted bar gets shorter.
 *
 * The transition is on the bar, so it eases rather than snaps.
 */
.scene-rail.is-stuck {
  /* THE TOP PADDING NEVER CHANGES. It is the thing holding this bar's contents
     on the same line as the Activity panel's heading, so the compaction is taken
     entirely from below: a shorter bottom padding and shorter controls. Taking
     it from the top saved the same pixels and broke the one alignment this bar
     exists to keep. */
  /* EVEN ABOVE AND BELOW. Franz, 2026-09-22: "theres no unifrom spacing and
     margin bordering like space above to the name and to the contents below
     during scrolll".
     The bar kept 13px above its pills and 7px below them, so the row sat
     visibly high inside its own strip and the rule under it ran too close to
     the controls. The top padding still cannot move — it is what holds these
     pills on the Activity heading's line — so the bottom comes UP to meet it
     and the space the bar gives back is taken from the margin instead, which
     leaves the total gap down to the script exactly as it was. */
  padding-bottom: var(--stick-pad-bare);
  margin-bottom: calc(var(--s-1) + var(--stick-pad) - var(--stick-pad-bare) + var(--s-half));
}
/* NOTHING IN HERE CHANGES SIZE WHEN IT PINS. Franz, 2026-09-22: "i also dont
   like the icons get smaller in the toolbar when scrolling".
   The bar used to drop every control a notch on sticking — scene pills,
   buttons, chips and the divider all shrinking together — to buy back a few
   pixels of height. It cost more than it bought: a control that is one size
   at the top of the page and another size four scenes down is a control you
   re-find rather than reach for, and the resize lands exactly when the eye
   is already tracking the page moving. The bar keeps its own height; the
   eight-pixel lift is the whole of what happens now. */
@media (prefers-reduced-motion: reduce) { .scene-rail { transition: none; } }
/* AND IT COVERS THE GAP IT LEAVES. Coming to rest 16px below the app header
   opens a 16px slot for the script to scroll through — half a field label
   sliding between the header and the toolbar, which reads as a rendering fault
   rather than as a gap. The bar's own paper is extended up to the header to
   close it. It cannot be padding: that would move the pills down again. */
/* NO COVER STRIP ANY MORE. It existed to fill the gap the bar's offset opened
   between itself and the app header; the sticky page head fills that gap now,
   opaquely and with the script's name in it. A second paper rectangle in the
   same place would paint over the title. */
/* A one-pixel mark above the bar. Its leaving the viewport is what `is-stuck`
   is told by, and it must cost the page NOTHING — a single pixel above the bar
   is a single pixel of misalignment against the Activity panel, which is the
   whole thing this page keeps getting wrong. Its height is cancelled by an
   equal negative margin. */
.rail-mark { height: 1px; margin: 0 0 calc(-1 * var(--s-px)); padding: 0; }
.rail-group { display: inline-flex; align-items: center; flex: none; }
/* A phone is narrower than the scene pills in a row: the group wraps rather
   than pushing the page sideways (the screenshot check, 2026-10-16: 419px in
   a 390px window). */
@media (max-width: 639px) {
  .rail-group { flex: 0 1 auto; flex-wrap: wrap; min-width: 0; row-gap: var(--s-half); }
}
.rail-div { width: 1px; height: 18px; background: var(--rule); flex: none; margin: 0 var(--s-half); }
.rail-spacer { flex: 1 1 auto; min-width: 0; }
/* THE SCENE PILLS ARE THE BIGGEST THING IN THE BAR. Franz, 2026-09-16: "the
   tool bar elements are all equal and gone smaller i need a bit bigger s1 s2
   pill sizes". Making everything one height did fix an alignment rounding bug
   and flattened the bar into a row of identical chips — the thing you press
   every minute now reads the same as the thing you set twice.

   Three ranks, and the tallest one sets the row: scenes at 30, the copy buttons
   at 28, the stage at 26. */
.rail-dot {
  min-width: 34px; height: var(--h-md); border-radius: 8px; cursor: pointer;
  background: var(--shade); border: 1px solid var(--rule); color: var(--ink-soft);
  font: 500 12.5px/1 var(--font-body);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-half);
  padding: 0 var(--s-1); position: relative;
}
.rail-dot:hover { border-color: var(--accent); color: var(--accent); }
/* THE TALLEST ITEM IS WHAT THE PANEL IS ALIGNED AGAINST. The row centres the
   shorter ones, which is what makes them read as quieter; the scene pills sit
   flush with the top padding, so their top edge is the line the Activity
   heading meets. */
.scene-rail .btn, .scene-rail .chip {
  height: 28px; display: inline-flex; align-items: center; line-height: 1;
}
.scene-rail .rail-dot { align-self: flex-start; }
/* COPY BRIEF, ARRIVING. It is in the DOM the whole time and takes no width
   until the bar sticks, so nothing on the row moves when it appears — it grows
   out of the gap rather than shoving Copy Script sideways. */
/* COLLAPSED TO NOTHING, NOT MERELY NARROW. `.btn` sets its own padding and a
   flex item's `min-width: auto` refuses to shrink below its padding box, so
   `max-width: 0` alone still left 28px of empty button in the row — twice.
   Both have to be zeroed, and the selector has to out-rank `.btn`. */
.scene-rail .btn.rail-late {
  max-width: 0; min-width: 0; opacity: 0;
  padding-left: 0; padding-right: 0; margin-right: 0;
  border-color: transparent; overflow: hidden; white-space: nowrap;
  pointer-events: none;
  transition: max-width .28s cubic-bezier(.22,.61,.36,1), opacity .2s ease,
              padding .28s cubic-bezier(.22,.61,.36,1), margin .28s cubic-bezier(.22,.61,.36,1);
}
.scene-rail.is-stuck .btn.rail-late {
  max-width: 160px; opacity: 1; margin-right: var(--s-1);
  padding-left: var(--s-1h); padding-right: var(--s-1h); border-color: var(--rule);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) { .scene-rail .btn.rail-late { transition: none; } }
/* A scene with notes still open says so on the rail, so "three things are
   waiting on scene 4" is visible without scrolling to scene 4. */
.rail-open { border-color: var(--accent); color: var(--ink); }
.rail-count {
  min-width: 15px; height: 15px; border-radius: 8px; padding: 0 var(--s-half);
  background: var(--accent); color: var(--accent-ink);
  font: 600 10.5px/15px var(--font-body); text-align: center;
}

/* AIR BETWEEN THE CARDS. Franz, 2026-09-16: "need the between the cards thing
   to have some more space the scene cards i mean". The only space between two
   scenes used to be the 22px insert strip, which put two bordered boxes almost
   touching — and now that the strip carries two real buttons it needs room of
   its own rather than borrowing the gap. */
.scenes { display: flex; flex-direction: column; gap: var(--s-1); max-width: none; }

/* Script left, notes right. Below 980px they stack, because two 300px columns
   of prose is worse than one. */
/* The left column IS the measure: about 68 characters, which is where prose
   stays readable. The notes take whatever is left rather than the script
   stretching to fill a wide monitor. */
.scene {
  display: grid; grid-template-columns: minmax(0, 68ch) minmax(240px, 1fr); gap: var(--s-4);
  border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: var(--s-2) var(--s-3); background: var(--card); scroll-margin-top: 56px;
}
.scene-main { min-width: 0; }
.scene-side {
  min-width: 0; border-left: 1px solid var(--rule-faint); padding-left: var(--s-2);
  display: flex; flex-direction: column; gap: var(--s-1);
}

/* ONE ROW, ALWAYS. The verbs are pills now and the row is right at the width
   where a slightly longer title pushed them onto a line of their own — which
   happened on scene 3 and not on scene 2, so it read as a glitch rather than as
   a rule. Nothing wraps: the title is the only thing that can give, and it
   ellipsises. It stacks below 980px, where the whole layout does. */
.scene-top {
  display: flex; align-items: center; gap: var(--s-1); flex-wrap: nowrap;
  margin-bottom: var(--s-1); min-width: 0;
}
/* THE CARD'S OWN NAME OUTRANKS THE LABELS INSIDE IT. Franz, 2026-09-16: "scene
   1 and feedback are the actual labels but are smaleer than the sub labels
   within it too please check the cards in create a video script mate".
   Create's protocol is two ranks and only two: a card heading at 16px/600 in
   the display face and full ink ("The Ad"), and a field label at 14px/600 in
   the body face and --ink-soft ("Line Of Business"). The scene's own name was
   sitting at 13px while the fields under it had been raised to 14 — the card
   was quieter than its contents. Both are the heading rank now. */
.scene-n { font: var(--t-section); letter-spacing: var(--tr-section); color: var(--accent); }
.scene-name { font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink); }
/* A word, not a coloured pill. */
.scene-beat { font: 400 12px/1 var(--font-body); color: var(--mute); font-style: italic; }
.scene-time { font: 400 12px/1 var(--font-body); color: var(--mute); margin-left: auto; }
/* The three verbs, kept together at the right-hand end. They used to be loose
   children of a wrapping row, which put Remove on its own line under any scene
   whose title ran long. */
.scene-verbs { display: flex; align-items: center; gap: var(--s-half); flex: none; }
.scene-n, .scene-beat-edit, .scene-time { flex: none; }
/* A SCENE'S VERBS ARE ALWAYS THERE. Franz, 2026-09-16: "i need the buttons that
   only come on hover in the script view or ediut mode mate". Copy, Rewrite and
   Remove were `opacity: 0` until the card was hovered — three verbs a person
   could only find by accident, on the one screen where the work actually gets
   done. Quiet is the right register for them; invisible is not. */
/* A SCENE'S VERBS ARE PILLS. Franz, 2026-09-16: "should always be there like
   pills not just text and same with copy remove rewrite too". `.chip` is the
   product's pill and carries the shape; this only sizes it down, because three
   full-size chips in a scene's title row would out-weigh the title. */
.scene-drop.chip {
  padding: var(--s-half) var(--s-1); font-size: 11px; color: var(--mute);
  background: none; white-space: nowrap;
}
.scene-drop.chip:hover { color: var(--alarm); border-color: var(--alarm); background: var(--shade); }
/* THE RED IS FOR REMOVE. Copy and Rewrite share the row's shape but not its
   warning: a verb that takes nothing away should not light up like one that
   does. */
.scene-copy.chip:hover, .scene-rewrite.chip:hover { color: var(--ink); border-color: var(--ink-soft); }
.beat-note { margin: 0 0 var(--s-1); font-size: 12.5px; color: var(--mute); }

/* THE SUB-LABELS ARE THE NAVIGATION OF A SCENE, so they are set like labels
   and not like captions: weight, the display face, and real space above. */
/* 20px BETWEEN ROWS, which is what a `.field` in a Create card ends at. It was
   13, so a scene read tighter than the form that produced it. */
.scene-field { margin: 0 0 var(--s-3); }
.scene-field:last-child { margin-bottom: 0; }
/* The labels are the navigation of a scene, so they win against the prose
   under them. Convention §2. */
/* THE SAME LABEL AS THE SCRIPT BUILDER. Franz, 2026-09-16: "check the script
   builder in create of the r&s lan page cause ive done a lot of ui/ux work
   tehre and need that look here cohesieve esepcailly thr elabels and spacing".
   Create's field labels are the global `label` rule — 600, 14px, body face, in
   `--ink-soft`. This page used `--t-label` in `--label-ink`, which is 13px, the
   display face and tinted green: three differences at once, and the green one
   is why a scene read as a column of accent words while the builder next door
   reads as a form. The accent is kept for the one thing it means here — a field
   with unsaved changes, below. */
.scene-field .lbl {
  display: flex; align-items: baseline; gap: var(--s-1);
  font-weight: 600; font-size: var(--label-size); font-family: var(--font-body);
  letter-spacing: normal; color: var(--ink-soft); margin-bottom: var(--s-half);
}
.scene-field p {
  margin: 0; font-size: 14px; line-height: 1.62;
  /* NOT JUSTIFIED. Justification stretches the spaces of a short line until
     "Footage to use: Toro aerator at" reads as a typesetting accident, and
     `hyphens: auto` breaks URLs across lines. Ragged right, balanced by
     text-wrap, is what prose in an interface wants. */
  text-align: left; text-wrap: pretty; overflow-wrap: anywhere;
}

/* EVERY LINE IS EDITED WHERE IT SITS, convention §9. It looks like text until
   you touch it: no box, no button, and it saves on blur rather than on every
   keystroke, because a save per character is a log nobody can read.

   An empty field is now SHOWN rather than hidden. It used to be dropped, which
   was right while a scene was read-only and wrong the moment it could be
   written: a line you cannot see is a line you cannot add. It is dimmed
   instead, so five quiet labels do not shout as loudly as the two with words
   in them. */
.sf-value {
  width: 100%; background: none; border: 1px solid transparent; border-radius: var(--r-md);
  color: var(--ink); font: 400 14px/1.62 var(--font-body); padding: var(--s-half) var(--s-1); margin: 0 calc(-1 * var(--s-1));
  resize: none; overflow: hidden;
  /* NOT JUSTIFIED. Justification stretches the spaces of a short line until
     "Footage to use: Toro aerator at" reads as a typesetting accident, and
     `hyphens: auto` breaks URLs across lines. Ragged right, balanced by
     text-wrap, is what prose in an interface wants. */
  text-align: left; text-wrap: pretty; overflow-wrap: anywhere;
}
.sf-value:hover { border-color: var(--rule-faint); }
.sf-value:focus { outline: none; border-color: var(--accent); background: var(--card); }

.scene-field.is-empty { margin-bottom: var(--s-1h); }
/* THE READING MEASURE HOLDS ON A FULL-WIDTH PAGE (2026-10-16). The brief card
   now runs the width of the page; its lines stop at --w-read, as prose does
   everywhere (SPEC-UI §4), so no sentence runs edge to edge. */
.scene-field .sf-value { max-width: var(--w-read); }
/* THE VIDEO BRIEF READS FULL WIDTH (Franz, 2026-11-02: "this text needs to
   be full lines"): its six rows are a brief to read across, not prose. */
.sc-brief .scene-field .sf-value, .sc-brief .sf-value { max-width: none; }
.scene-field.is-empty .lbl { color: var(--mute); }
.scene-field.is-empty:focus-within .lbl { color: var(--label-ink); }

/* The two lines that are read aloud or burned in are set larger, and NOT
   justified: a spoken line is read as one breath, and justification stretches
   a short sentence into something nobody says. */
.scene-field.vo .sf-value { font-size: 15.5px; line-height: 1.55; text-align: left; hyphens: manual; }
.scene-field.ost .sf-value { font-weight: 500; text-align: left; hyphens: manual; }

/* The title and the beat, edited in the scene's own top row. */
.scene-name-edit {
  flex: 1 1 auto; min-width: 60px;
  background: none; border: 1px solid transparent; border-radius: var(--r-sm);
  color: var(--ink); font: var(--t-section); letter-spacing: var(--tr-section);
  min-height: var(--h-sm); padding: 0 var(--s-1); margin: 0 calc(var(--s-1) * -1);
}
.scene-name-edit:hover { border-color: var(--rule-faint); }
.scene-name-edit:focus { outline: none; border-color: var(--accent); background: var(--card); }
.scene-beat-edit {
  background: none; border: 1px solid transparent; border-radius: var(--r-sm);
  color: var(--mute); font: 400 12px/1 var(--font-body); font-style: italic;
  min-height: var(--h-sm); padding: 0 var(--s-half); cursor: pointer; appearance: none;
}
.scene-beat-edit:hover { border-color: var(--rule-faint); color: var(--ink-soft); }
.scene-beat-edit:focus { outline: none; border-color: var(--accent); color: var(--ink); }
.scene-beat-edit option { font-style: normal; background: var(--card); color: var(--ink); }
.said-inline { font: 400 11px/1 var(--font-body); color: var(--mute); }

/* The two optional lines. One quiet row under the scene, and once a line is
   added the row loses that control — there is nothing to add twice. */
.scene-extras { display: flex; gap: var(--s-2); margin-top: var(--s-1h); }
.add-field {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: 400 12px/1 var(--font-body); color: var(--mute);
}
.add-field:hover { color: var(--accent); }

/* ADD A SCENE, OR A DISRUPTOR, BETWEEN ANY TWO — AND BOTH ARE VISIBLE.
   A hairline that became a button on hover was a control nobody could find and
   no touch screen could reach. It is a centred pair of pills on a rule now:
   the rule says "something goes between these two scenes", the pills say what. */
.scene-insert-row {
  position: relative; display: flex; justify-content: center; gap: var(--s-1);
  padding: var(--s-1) 0;
}
.scene-insert-row::before {
  content: ''; position: absolute; left: 0; right: 0; top: 50%;
  border-top: 1px solid var(--rule);
}
.scene-insert {
  position: relative; background: none; border: 0; cursor: pointer; padding: 0;
}
.scene-insert[disabled] { cursor: wait; opacity: .5; }
/* The same pill as everything else. Franz, 2026-09-16: "the plus scene and
   disruor buttons need to be rounded like brand style made" — they were a
   hand-rolled box with their own radius; `.chip` is the shape, and the only
   thing set here is the paper background, so the rule behind them is broken
   rather than showing through. */
.scene-insert-mark.chip {
  display: inline-block; background: var(--paper);
  padding: var(--s-half) var(--s-1h); font-size: 11.5px; color: var(--mute);
}
/* Quiet until pointed at. Two accent pills in every gap of an eight-scene
   script is a column of green down the page, which is the opposite of what the
   accent is for. */
.scene-insert:hover .scene-insert-mark,
.scene-insert:focus-visible .scene-insert-mark { color: var(--accent); border-color: var(--accent); }
/* The label sits in a `<span>` inside the button, so the pointer has to read as
   a pointer on both. */
.scene-insert .chip { cursor: pointer; }

@media (max-width: 980px) {
  .scene { grid-template-columns: 1fr; gap: var(--s-2); }
  /* Narrow enough that one row is not possible; wrapping is the honest answer. */
  .scene-top { flex-wrap: wrap; }
  .scene-side { border-left: 0; border-top: 1px solid var(--rule-faint); padding: var(--s-2) 0 0; }
  .script-head-row { grid-template-columns: 1fr; gap: var(--s-half); }
}


/* --- the client dump ----------------------------------------------------- */
/* THE DUMP IS AT THE TOP AND AS WIDE AS EVERYTHING ELSE, but it is compact:
   it is a doorway, not a workspace. The paste box scrolls rather than growing,
   because a 40,000-character paste that pushes the dossier off the screen is
   worse than one you scroll. */
.dump { max-width:none; margin:0 0 var(--s-3); padding:var(--s-2) var(--s-2); border:1px solid var(--rule); border-radius:var(--r-lg); background:var(--card); }
.dump-h { font: var(--t-section); letter-spacing: var(--tr-section); margin:0 0 var(--s-half); }
.dump .hint { margin:0 0 var(--s-1h); }
.hint { opacity:.85; }
.dump-form { display:grid; grid-template-columns:minmax(200px, 280px) 1fr; gap:var(--s-1) var(--s-2); align-items:start; }
/* The paste box spans the whole left stack, so the name, the file picker and
   the button sit against it rather than drifting apart down an empty column. */
.dump-form .dump-area { grid-row:1 / span 3; grid-column:2; }
.dump-form .dump-go { grid-column:1; }
.dump-area { resize:vertical; min-height:96px; max-height:220px; overflow:auto; font:inherit; }
.dump-file { font-size:12.5px; color:var(--mute); }
.dump-go { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; }
.dump-go .muted { margin:0; }
.dump-result { margin-top:var(--s-2); }
.need-kind { margin:var(--s-1) 0 var(--s-half); font:500 12.5px/1.4 var(--font-body); color:var(--accent); }
.need-kind.is-soft { color:var(--mute); }
.prop-list { margin:var(--s-2) 0 0; }
.prop-list h3 { font:500 13px/1.3 var(--font-body); color:var(--mute); margin:0 0 var(--s-1); }
.prop { display:flex; gap:var(--s-1); align-items:flex-start; padding:var(--s-1) 0; border-bottom:1px solid var(--rule-faint); cursor:pointer; }
.prop input { margin-top:var(--s-half); flex:none; accent-color:var(--accent); }
.prop-body { display:flex; flex-direction:column; gap:var(--s-half); min-width:0; }
.prop-label { font:500 12px/1.3 var(--font-body); color:var(--mute); }
.prop-new { font-size:14px; line-height:1.55; white-space:pre-wrap; }
.prop-old { font-size:12px; color:var(--mute); text-decoration:line-through; }
.dump-md-box { margin:var(--s-2) 0 0; }
.dump-md-box summary { cursor:pointer; color:var(--mute); font-size:13px; }
.dump-md { margin-top:var(--s-1); font:13px/1.6 ui-monospace,Menlo,monospace; resize:vertical; }
.dump-actions { display:flex; align-items:center; gap:var(--s-2); margin-top:var(--s-2); }
/* The Clients page's add bar. It held a name box, a workspace dropdown and an
   Add button; it is one button opening `openNewClient()` now, and the rule
   below still sizes whatever sits in it. */
.newclient { display:flex; gap:var(--s-1); align-items:center; margin:0 0 var(--s-3); flex-wrap:wrap; }
/* The add-a-client sheet. Narrow on purpose: three fields, and the widest of
   them is one line about what somebody sells. */
.newclient-flow .textarea.is-short { min-height: 64px; }
.newclient-flow .hint { margin: var(--s-1) 0 0; }
.newclient input.input { flex:1; min-width:200px; }
.newclient select.input { flex:none; width:auto; min-width:210px; }

@media (max-width: 640px) {
  /* The header's and the page's phone gutters come from --pad-page now. */
  .top { gap:var(--s-2); }
  table.work .c-tags, table.work .c-when { display:none; }
}

/* ── the shell, sprint 2 ─────────────────────────────────────────────────────
   SPEC-SHELL-V1. The client is the spine; the output type is a filter. Every
   rule from §6 holds here: no icon without a text label, the eight Plutchik
   colours name emotions and nothing else, density over drama, and every number
   on screen comes from the server. */

/* ROUNDED, LIKE THE PILLS AT THE OTHER END OF THE BAR. Franz, 2026-09-23:
   "the create button has to be rounded too". It was the only 10px corner in a
   header of 999px ones, which made it read as belonging to a different set of
   controls than the row it sits in. The height matches the pickers so the bar
   has one height in it, and it keeps the accent fill — it is still the one
   primary action up here, and that is what says so. */
.btn-create {
  /* min-height too: .btn's 40px floor was what made it tower over the pills.
     Franz, 2026-10-19: "the create pill is too tall compared to the workspace
     and client picker pill". */
  height: 28px; min-height: 0; padding: 0 var(--s-2); border-radius: var(--r-pill);
  font-size: 13.5px; display: inline-flex; align-items: center;
}

.waiting { display: flex; flex-direction: column; }
.waiting-row {
  display: grid;
  grid-template-columns: auto 1fr 200px 110px;
  align-items: center; gap: var(--s-2);
  padding: var(--s-1h) 0; border-bottom: 1px solid var(--rule-faint);
}
.waiting-title { text-align: left; }
.waiting-client { display: flex; align-items: center; gap: var(--s-nudge); font-size: 13px; color: var(--ink-soft); }

/* A state chip is a NEUTRAL or the one accent. A script in changes_requested
   is not "anger" — those eight colours name emotions and never a state. */
.state-chip[aria-pressed="true"], .chip.is-on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.states { display: flex; gap: var(--s-1); flex-wrap: wrap; }
/* SIZED TO THE SCENE PILLS BESIDE THEM. Franz, 2026-09-16: "match the size of
   these acocordingly". Full-size chips next to 28px jump pills made the row two
   heights; these are the same 28px tall and set at the same 12px. */
.script-state { display: inline-flex; align-items: center; gap: var(--s-half); }
/* SMALLER THAN THE SCENES BESIDE THEM. Franz, 2026-09-16: "make these a bit
   smaller mate check tool bar in create a script". Create's toolbar has two
   ranks — 13px/500 pills for what you press, 12px/500 for the quieter selects
   beside them. The scenes are the thing you press; the stage is set twice in a
   script's life, so it takes the quieter rank. The 28px height is kept because
   everything in this bar shares one height, which is what keeps its contents on
   the same line as the Activity panel's heading. */
.script-state .state-chip {
  height: 26px; padding: 0 var(--s-1); font-size: 11.5px; line-height: 1;
  display: inline-flex; align-items: center;
}
.script-state-msg { margin: var(--s-1) 0 0; }

/* ── the client page ─────────────────────────────────────────────────────── */

/* HIGHER UP THE PAGE. Franz, 2026-09-16: "bring the tabs and the page a bit
   higher overall". The name, the stats and the tabs took 14px of their own
   under a page that already pads the top — three separate gaps before the first
   card, on a screen whose whole job is the cards. */
.client-head { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; margin: 0 0 var(--s-1); }
.client-head h1 { font: var(--t-poster); letter-spacing: var(--tr-poster); margin: 0; }
.client-stats { display: flex; gap: var(--s-3); margin-left: auto; }
.stat { display: flex; flex-direction: column; }
.stat-v { font: 500 19px/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
.stat-l { font-size: 12px; color: var(--mute); }

.subtabs {
  display: flex; gap: var(--s-3);
  border-bottom: 1px solid var(--rule); margin: 0 0 var(--s-3);
}
.subtab {
  background: none; border: 0; padding: var(--s-1) 0; cursor: pointer;
  font-size: 13.5px; color: var(--mute); border-bottom: 2px solid transparent;
}
.subtab:hover { color: var(--ink-soft); }
.subtab[aria-current="page"] { color: var(--ink); border-bottom-color: var(--accent); }

/* What is still missing. A list of what to go and ask for — no count, no bar,
   no prompt to finish. Every field is still optional. */
.missing { margin: 0 0 var(--s-3); font-size: 13px; color: var(--ink-soft); }
.missing p { margin: 0 0 var(--s-half); display: flex; gap: var(--s-1); flex-wrap: wrap; }
.missing .label { display: inline; margin: 0; flex: none; }

.work-row, .lib-row {
  display: grid; align-items: baseline; gap: var(--s-2);
  padding: var(--s-1) 0; border-bottom: 1px solid var(--rule-faint);
}
/* FIVE THINGS, FIVE COLUMNS. clientWorkRow() has put five children in this row
   since it gained a client name, and the grid only ever named four — so the
   date wrapped onto a line of its own and the client was crushed into the
   score's 50px. Visible on Home in List view, and unmissable in the Work tab,
   which names a client on every row. */
.work-row { grid-template-columns: 110px minmax(0, 1fr) 150px 50px 110px; }
.lib-row { grid-template-columns: 1fr 160px 110px 110px; }
.work-kind, .lib-client { font-size: 12.5px; color: var(--mute); }
.work-score { font: 500 15px/1 var(--font-display); font-variant-numeric: tabular-nums; text-align: right; }

.team-row { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-1h) 0; border-bottom: 1px solid var(--rule-faint); }
.team-name { font-weight: 500; }

/* ── the sheet ───────────────────────────────────────────────────────────────
   Over the page, never a navigation: creating is something you do while
   looking at something else, and the thing behind it stays there. */

.sheet { position: fixed; inset: 0; z-index: 50; display: flex; justify-content: center; align-items: flex-start; }
.sheet-scrim { position: absolute; inset: 0; background: rgb(0 0 0 / .55); }
.sheet-card {
  position: relative; margin: var(--s-5) var(--s-2); width: 100%; max-width: var(--w-modal);
  max-height: calc(100dvh - 2 * var(--s-5)); overflow: auto;
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: var(--pad-modal);
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); margin: 0 0 var(--s-2); }
/* THE SAME CONTROL AS THE FOLD CHEVRON, because it is the same kind of thing: a
   shape everybody knows, in the corner, with no label. 22px, like every other
   icon control on the Context page. */
.sheet-x {
  flex: none; width: 22px; height: 22px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-sm); background: none; color: var(--mute);
  cursor: pointer; transition: color 120ms ease, background 120ms ease;
}
.sheet-x:hover { color: var(--ink); background: var(--shade); }
.sheet-head h2 { font: var(--t-section); letter-spacing: var(--tr-section); margin: 0; }
.sheet-form .field { margin: 0 0 var(--s-2); }
.sheet-go { display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-1); }
.sheet-result { margin-top: var(--s-3); }

.create-kinds { display: grid; gap: var(--s-1h); grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); margin-top: var(--s-1); }
.kind {
  display: flex; flex-direction: column; gap: var(--s-half); text-align: left;
  padding: var(--s-2); border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--card); cursor: pointer;
}
.kind:hover { border-color: var(--accent); }
.kind-title { font-weight: 600; font-size: 14px; }
.kind-what { font-size: 12.5px; color: var(--mute); line-height: 1.45; }

/* The creative on a LARGE target, not a thin row — the room the 400px panel
   never had is the whole reason this sheet exists. */
.analyse-cols { display: grid; gap: var(--s-3); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 760px) { .analyse-cols { grid-template-columns: minmax(0, 320px) minmax(0, 1fr); } }
.drop {
  border: 1.5px solid var(--rule); border-radius: var(--r-lg);
  min-height: 220px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--s-1);
  padding: var(--s-2); cursor: pointer; text-align: center; background: var(--card);
}
.drop:hover, .drop.is-over, .drop:focus-visible { border-color: var(--accent); outline: none; }
.drop-text { margin: 0; font-size: 13px; color: var(--mute); }
.drop-img { max-width: 100%; max-height: 260px; border-radius: var(--r-md); display: block; }

/* Textareas that grow. The one-line input is banned product-wide: it hides
   what you are typing the moment a sentence passes the width of the box. */
.textarea.grow { field-sizing: content; min-height: 2.6em; max-height: 16em; }

/* ── the create controls ─────────────────────────────────────────────────── */

/* ONE BOX AROUND THE WHOLE CONTROL: the starting points and the eight sliders
   they set are one decision, and two boxes said they were two. Kept here and
   dropped from the run context — Franz, 2026-09-16: "leave it in the flow
   nicely but remove from setup". */
.blend-box {
  padding: var(--s-1h) var(--s-1h) var(--s-2); background: var(--shade);
  border: 1px solid var(--rule); border-radius: var(--r-md);
}
/* THE PILLS SIT ON THE SLIDERS' OWN COLUMNS. Franz, 2026-09-16: "slider and
   pills needs same padding and also justify". Laid out as flex they wrapped
   into ragged rows that lined up with nothing underneath them; on the same two
   columns and the same gutter, every edge in the box agrees. */
/* THREE COLUMNS, NOT TWO. Franz, 2026-09-16: "these are too wide for only two".
   A pill reading "Warmth → Trust" needs about a third of the card and no more;
   at half, the words floated in the middle of a bar. */
/* THREE ACROSS WHEN THREE FIT, AND IT ASKS THE CARD RATHER THAN THE WINDOW.
   Franz, 2026-09-17, on the Create page's third column: "i need preview column
   to be slimmer cause then otherwise this happens". Three fixed columns were
   right while this card was half a page wide; in a third of one they were
   144px, "Aspiration → Belonging" broke over two lines, and because a grid row
   is as tall as its tallest cell it took two more pills with it.
   The window was never the thing that decided this. Auto-fit on the width the
   longest preset actually needs asks the box it is in, so it is three across
   on a wide card, two on a narrow one, and never a pill wrapped mid-phrase. */
.blend-presets {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
  gap: var(--s-1) var(--s-1); margin-bottom: var(--s-1);
}
.blend-presets .chip { width: 100%; text-align: center; }
/* --- the blend: eight sliders, two columns of four ------------------------- */
/* Franz, 2026-09-16: "better sliders and half slider 4 on each side in a nice
   internal card or grid within the right card". So they sit in a panel of their
   own — one inset box, so the eight read as one control rather than eight
   fields that happen to be adjacent. */
/* THE SLIDERS SIT INSIDE THE PILLS' OWN EDGES. Franz, 2026-09-16: "bring the
   sliders in flow inwards and in same max margin as the pills above it". A pill
   holds its word 13px in from its own border; a slider track ran to the raw
   edge of the box, so the two rows agreed on their columns and disagreed on
   every margin inside them. */
.blend-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Franz, 2026-09-16: "the pills and sliders in the flow section needs better
     spacing overall". Room enough between two rows of sliders that a name and
     the track under it read as one thing. */
  gap: var(--s-2) var(--s-3);
  padding: var(--s-1) var(--s-1h) var(--s-half); border-top: 1px solid var(--rule-faint);
}
@media (max-width: 720px) {
  .blend-grid { grid-template-columns: minmax(0, 1fr); }
}
/* THE POINTER BELONGS ON THE TRACK, NOT ON THE NAME. The row was a <label>
   with `cursor: pointer`, so the hand appeared over the word and the number two
   lines above the only thing it could actually drag. */
.spoke { display: grid; gap: var(--s-half); min-width: 0; }
.spoke-range { cursor: pointer; }
.spoke-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1); }
/* EVERY WORD ON THIS FORM IS CAPITALISED. Franz, 2026-09-16: "all labels need
   first letter capital in every word ... and everywhere else on the create".
   The names arrive lower-case from the rubric, where they are keys, and the
   presets arrive as sentences from the Worker — so the rule is applied where
   they are drawn rather than by retyping data that is not the form's to edit. */
.spoke-name { font-size: 12.5px; color: var(--ink-soft); text-transform: capitalize; }
/* The number is a readout, not a box: the slider is the way in and a second
   control for the same value was a second thing to keep in step. */
.spoke-read { font-size: 12.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.spoke.is-set .spoke-name { color: var(--ink); }
.spoke.is-set .spoke-read { color: var(--accent); }
/* EIGHT COLOURS, WHERE THEY MEAN THE EMOTION. BRAND.md forbids the Plutchik
   colours as UI chrome, which is why this control wore one accent — and is also
   why it can wear eight now: a slider labelled "trust" IS the emotion, so the
   colour is doing its only job rather than decorating a control. Nothing else on
   this form takes one. */
.spoke-range { width: 100%; accent-color: var(--accent); margin: 0; }
/* THE TRACK IS PAINTED, NOT DELEGATED. `accent-color` colours the filled half
   and then picks the empty half itself — and against a tint this pale it picks
   a bright grey, which made the one part of the row nobody reads the loudest
   thing on the panel. So the slider draws its own: the emotion up to the value,
   the card's own rule after it, and a thumb in the same tint. `--fill` is
   written by the control from the number it already has. */
.spoke-range {
  -webkit-appearance: none; appearance: none;
  height: 6px; border-radius: var(--r-pill); border: 0; padding: 0;
  background: linear-gradient(
    to right,
    var(--eq, var(--accent)) 0 var(--fill, 0%),
    var(--rule) var(--fill, 0%) 100%
  );
}
.spoke-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; border-radius: 50%; border: 0;
  background: var(--eq, var(--accent));
}
.spoke-range::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%; border: 0;
  background: var(--eq, var(--accent));
}
.spoke-range::-moz-range-track { height: 6px; border-radius: var(--r-pill); background: transparent; }
.spoke-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* THE PALE END OF EACH HUE. Franz, 2026-09-16, pointing at the third row of a
   colour picker: "i want such shades but a variety mate that matches the eight
   emotions accoridngly". That row is the pastels — the same eight hues at a
   fraction of their saturation and most of their lightness. On a dark panel a
   pastel reads as a tint rather than as paint: eight colours you can tell apart
   at a glance, none of them competing with the word beside it. The saturated
   Plutchik set stays on the report chips and the table dots, which are badges
   the size of a full stop and need every bit of their colour. */
/* AND SAT BACK A QUARTER OF THE WAY INTO THE PANEL. Franz, 2026-09-16:
   "perfect but jsut a bit faded mate cause its hurting my eyes". A pastel is
   low in saturation and HIGH in lightness, so eight of them at full strength on
   a near-black card are eight small lights. Mixed three quarters against the
   card they keep every bit of their hue and stop glaring. */
.spoke[data-axis="joy"] { --eq: color-mix(in srgb, #EDE0A8 74%, var(--card)); }
.spoke[data-axis="trust"] { --eq: color-mix(in srgb, #CFE0B0 74%, var(--card)); }
.spoke[data-axis="fear"] { --eq: color-mix(in srgb, #BCDAC6 74%, var(--card)); }
.spoke[data-axis="surprise"] { --eq: color-mix(in srgb, #BAD7E4 74%, var(--card)); }
.spoke[data-axis="sadness"] { --eq: color-mix(in srgb, #C2CFE8 74%, var(--card)); }
.spoke[data-axis="disgust"] { --eq: color-mix(in srgb, #D5C7E4 74%, var(--card)); }
.spoke[data-axis="anger"] { --eq: color-mix(in srgb, #EEC4BC 74%, var(--card)); }
.spoke[data-axis="anticipation"] { --eq: color-mix(in srgb, #F0D4B4 74%, var(--card)); }
/* A row at zero keeps a grey thumb: nothing is being said, so nothing is
   coloured, and eight tinted dots on a row of empty tracks was a rainbow of
   zeroes. */
.spoke:not(.is-set) { --eq: var(--rule); }
.spoke:not(.is-set) .spoke-range::-webkit-slider-thumb { background: var(--ink-soft); }
/* The number takes its own bar's colour. A pastel on this ground is already
   quiet enough to use as it is. */
.spoke.is-set .spoke-read { color: var(--eq); }
@media (prefers-color-scheme: light) {
  /* Against a pale ground the pastels would vanish, so there they are used at
     the strength the badges use, mixed halfway back into the page. */
  :root:not([data-theme="dark"]) .spoke[data-axis="joy"] { --eq: color-mix(in srgb, #F2D338 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="trust"] { --eq: color-mix(in srgb, #96C93D 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="fear"] { --eq: color-mix(in srgb, #2E7D4F 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="surprise"] { --eq: color-mix(in srgb, #3FA9C9 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="sadness"] { --eq: color-mix(in srgb, #3B6FB6 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="disgust"] { --eq: color-mix(in srgb, #8E5CB5 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="anger"] { --eq: color-mix(in srgb, #D6483B 70%, var(--card)); }
  :root:not([data-theme="dark"]) .spoke[data-axis="anticipation"] { --eq: color-mix(in srgb, #E88A2E 70%, var(--card)); }
}
/* The same, picked by hand with the day/night switch. */
:root[data-theme="light"] .spoke[data-axis="joy"] { --eq: color-mix(in srgb, #F2D338 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="trust"] { --eq: color-mix(in srgb, #96C93D 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="fear"] { --eq: color-mix(in srgb, #2E7D4F 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="surprise"] { --eq: color-mix(in srgb, #3FA9C9 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="sadness"] { --eq: color-mix(in srgb, #3B6FB6 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="disgust"] { --eq: color-mix(in srgb, #8E5CB5 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="anger"] { --eq: color-mix(in srgb, #D6483B 70%, var(--card)); }
:root[data-theme="light"] .spoke[data-axis="anticipation"] { --eq: color-mix(in srgb, #E88A2E 70%, var(--card)); }

/* THE ARC PILLS WEAR THE ARC. `--arc` is written on each one by the control
   from that preset's own blend: the emotions it sets, in order, each taking a
   share of the width proportional to how much of it there is. At a fifth of
   their strength they tell nine arcs apart at a glance without the row reading
   as a bag of sweets; picked, the same gradient comes up to half. */
.chip.is-arc { background-image: var(--arc); border-color: var(--rule); }
.chip.is-arc:hover { border-color: var(--ink-soft); }
.chip.is-arc[aria-pressed="true"] {
  background-color: transparent; background-image: var(--arc-on);
  border-color: var(--accent); color: var(--ink);
}

/* The same rule, for every chip and button inside a Create card: the style
   names, the starting points, the lengths and the verbs. */
.c-card .chip, .c-card .btn, .c-card .pick, .create-kinds .chip,
.sheet-form.is-page .sheet-go .btn { text-transform: capitalize; }

.picker { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.chip.is-soon { opacity: .5; cursor: default; }
.soon { font-size: 10.5px; color: var(--mute); margin-left: var(--s-1); }

.hooks { display: grid; gap: var(--s-1); }
.hook { border: 1px solid var(--rule); border-radius: var(--r-md); padding: var(--s-1h) var(--s-1h); background: var(--card); }
.hook.is-chosen { border-color: var(--accent); }
.hook-head { display: flex; align-items: center; gap: var(--s-1); margin-bottom: var(--s-1); }
.hook-n { font-weight: 500; font-size: 12.5px; }
.hook-beat { font-size: 12px; color: var(--mute); }
.hook-spacer { flex: 1; }
.hook .textarea { margin-bottom: var(--s-1); }
.hook .textarea:last-child { margin-bottom: 0; }

/* ── the diff ────────────────────────────────────────────────────────────────
   SPEC-ACTIVITY-V1 §3. Side by side, old left, new right, word level, and
   collapsed to the changed region. Removed words struck through, added words
   marked — and marked with the accent and a neutral, never with red and green,
   which would be a second palette and unreadable to a good few people. */

.diff { margin: var(--s-1) 0 0; border: 1px solid var(--rule-faint); border-radius: var(--r-md); overflow: hidden; }
.diff-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1h);
  padding: var(--s-1) var(--s-1h); background: var(--shade); border-bottom: 1px solid var(--rule-faint);
}
.diff-label { font-size: 12px; font-weight: 500; color: var(--ink-soft); }
.diff-more { background: none; border: 0; padding: 0; cursor: pointer; color: var(--accent); font-size: 12px; }
.diff-more:hover { text-decoration: underline; }
.diff-body { display: grid; grid-template-columns: 1fr; }
@media (min-width: 700px) { .diff-body { grid-template-columns: 1fr 1fr; } }
.diff-col { padding: var(--s-1) var(--s-1h); min-width: 0; }
.diff-col + .diff-col { border-top: 1px solid var(--rule-faint); }
@media (min-width: 700px) { .diff-col + .diff-col { border-top: 0; border-left: 1px solid var(--rule-faint); } }
.diff-colhead { display: block; font-size: 11.5px; color: var(--mute); margin-bottom: var(--s-half); }
.diff-side { font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
.diff-del { text-decoration: line-through; color: var(--mute); }
.diff-add { background: color-mix(in srgb, var(--accent) 22%, transparent); border-radius: 3px; }
.diff-elided { color: var(--mute); }
.diff-empty { color: var(--mute); font-style: italic; }
.diff-note { margin: var(--s-1) var(--s-1h); font-size: 12.5px; color: var(--mute); }
.diff-plain { margin: 0 var(--s-1h) var(--s-1); font-size: 12.5px; white-space: pre-wrap; font-family: inherit; }

/* ── activity and comments ───────────────────────────────────────────────── */

.act { padding: var(--s-1h) 0; border-bottom: 1px solid var(--rule-faint); }
.act:last-child { border-bottom: 0; }
.act-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1h); }
.act-what { font-size: 13.5px; }
.act-when { font-size: 12px; color: var(--mute); white-space: nowrap; }
.act-note { margin: var(--s-1) 0 0; font-size: 13.5px; color: var(--ink-soft); white-space: pre-wrap; }
.act-value { margin: var(--s-half) 0 0; font-size: 13px; color: var(--ink-soft); }
.act-sub { margin: var(--s-half) 0 0; font-size: 12px; color: var(--mute); }
.act-sub.is-gone { text-decoration: line-through; }

.log-box { margin-top: var(--s-3); border-top: 1px solid var(--rule); padding-top: var(--s-1h); }
.log-box summary { cursor: pointer; font-size: 13px; color: var(--mute); }

.scene-comments { margin-top: var(--s-1h); display: grid; gap: var(--s-1); }
.comment { border-left: 2px solid var(--accent); padding: var(--s-1) 0 var(--s-1) var(--s-1h); }
.comment-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1); }
.comment-field { font-size: 12px; font-weight: 500; color: var(--ink-soft); }
.comment-note { margin: var(--s-half) 0 0; font-size: 13.5px; white-space: pre-wrap; }
.comment-actions { margin-top: var(--s-1); }
.btn-small { min-height: var(--h-md); padding: 0 var(--s-1h); font-size: 13px; }

.scene-comment-toggle { margin-top: var(--s-1h); }
.scene-comment-toggle summary { cursor: pointer; font-size: 12.5px; color: var(--accent); }
/* THE NOTE FORM READS LIKE THE SCRIPT BUILDER. Two labelled fields, both open,
   with the same 20px between them a Create card uses — it was a bare box, a row
   of tag chips and a disclosure, three different idioms in a card the width of a
   phone. */
.comment-form { margin-top: var(--s-1); }
.comment-form > .field { margin: 0 0 var(--s-2); }
.comment-form > .field:last-of-type { margin-bottom: var(--s-2); }
.comment-form-foot { display: flex; align-items: center; gap: var(--s-1h); }
/* THE NOTE BOX IS THE BIG ONE. It was two rows sharing the card with a strip of
   tag chips; the chips are gone — the @ menu replaced them — and the box took
   the room back. */
/* The two boxes are given a floor below, after `.textarea.grow` has zeroed it. */
.suggest-body { display: grid; gap: var(--s-1); }

/* --- typing @ ---------------------------------------------------------------
   The menu hangs under the box rather than at the caret: a caret-tracking
   popup needs a mirror element to measure text position and is wrong by a few
   pixels for ever after, and this list is four names. */
.mention-wrap { position: relative; }
.mention-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + var(--s-half)); z-index: 30;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-md);
  box-shadow: 0 12px 30px rgba(0,0,0,.45); padding: var(--s-half); overflow: hidden;
}
.mention-menu[hidden] { display: none; }
.mention-row {
  display: flex; align-items: baseline; gap: var(--s-1); width: 100%;
  background: none; border: 0; border-radius: var(--r-sm); cursor: pointer;
  padding: var(--s-1) var(--s-1); text-align: left; color: var(--ink-soft);
}
.mention-row:hover, .mention-row.is-on { background: var(--shade); color: var(--ink); }
.mention-name { font: 500 13px/1.3 var(--font-body); }
.mention-mail {
  font-size: 11px; color: var(--mute); margin-left: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.comment-current {
  margin: 0; font: italic 11.5px/1.5 var(--font-body); color: var(--mute);
  opacity: .8; white-space: pre-wrap;
}

@media (max-width: 760px) {
  .waiting-row { grid-template-columns: 1fr; gap: var(--s-half); }
  .work-row { grid-template-columns: 1fr auto; }
  .lib-row { grid-template-columns: 1fr auto; }
  .client-stats { margin-left: 0; gap: var(--s-2); }
  .spoke { grid-template-columns: 100px 1fr 54px; }
}

/* An unknown beat must still be legible. The schema enumerates the eight, so
   this should never fire — but an unstyled chip is dark text on a dark ground,
   which is invisible rather than merely plain, and "invisible" is the wrong
   failure for a label. Neutral grey is the palette's own "no clear emotion". */
.beat { background: var(--c-slate); color: #F3F4F1; }

/* ── workspaces, SPEC-WORKSPACES-V1 ──────────────────────────────────────────
   Everything lives in a workspace, always. The chrome here is neutrals and the
   one accent, as everywhere: a workspace is not an emotion, so none of the
   eight colours appears in any of it. */

.ws-switch {
  display: flex; align-items: center; gap: var(--s-1);
  background: none; border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: var(--s-half) var(--s-1); cursor: pointer; font-size: 13px; color: var(--ink);
  max-width: 240px;
}
.ws-switch:hover { border-color: var(--ink-soft); }
.ws-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-chev {
  flex: none; width: 7px; height: 7px;
  border-right: 1.5px solid var(--mute); border-bottom: 1.5px solid var(--mute);
  transform: rotate(45deg) translate(-1px, -1px);
}

/* THE SWITCHER IS A DROPDOWN, ANCHORED UNDER ITS BUTTON.
   It was a centred modal with a dimmed page behind it, which is the right shape
   for a command palette and the wrong shape for "which workspace am I in".
   A modal says STOP; this control is glanced at and dismissed. So it hangs off
   the button that opened it, the page stays lit, and the scrim is an invisible
   click-catcher rather than a curtain. */

.switcher { position: fixed; inset: 0; z-index: 60; }
.switcher-scrim { position: absolute; inset: 0; background: transparent; }
.switcher-card {
  /* Wide enough for its two foot buttons on one line each (2026-11-03). */
  position: absolute; width: calc(var(--w-pop) + var(--s-5)); max-width: calc(100vw - 24px);
  background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--r-lg);
  box-shadow: 0 18px 40px rgb(0 0 0 / .34), 0 2px 6px rgb(0 0 0 / .22);
  overflow: hidden; max-height: calc(100dvh - 110px);
  display: flex; flex-direction: column;
  animation: switcher-in .12s ease-out;
}
@keyframes switcher-in { from { opacity: 0; transform: translateY(-4px); } }

.switcher-q {
  border: 0; border-bottom: 1px solid var(--rule); border-radius: 0;
  padding: var(--s-1h) var(--s-2); font-size: 13.5px; background: none;
}
.switcher-q:focus { outline: none; border-bottom-color: var(--accent); }
.switcher-list { overflow: auto; padding: var(--s-half) 0; }

.switcher-ws, .switcher-client, .switcher-new {
  display: flex; align-items: center; gap: var(--s-1); width: 100%;
  background: none; border: 0; cursor: pointer; text-align: left;
  padding: var(--s-1) var(--s-2); color: var(--ink); font-size: 13.5px; line-height: 1.35;
}
/* A workspace heading is a heading. It gets space above it and the clients sit
   under it looking like they belong to it, which is the whole point of listing
   them here: two clicks from any client to any other. */
.switcher-ws { font-weight: 500; padding-top: var(--s-1); }
.switcher-ws + .switcher-ws { border-top: 1px solid var(--rule-faint); }
.switcher-ws.is-current { color: var(--accent); }
.switcher-ws.is-current .switcher-ws-name::after {
  content: '·'; margin-left: var(--s-1); color: var(--accent);
}
.switcher-ws:hover, .switcher-client:hover, .switcher-new:hover { background: var(--shade); }
.switcher-ws-meta { margin-left: auto; font-size: 11.5px; color: var(--mute); font-weight: 400; }
.switcher-client {
  padding: var(--s-half) var(--s-2) var(--s-half) var(--s-3); font-size: 13px; color: var(--ink-soft);
  position: relative;
}
/* One hairline joining a client to its workspace. Cheaper to read than an
   indent alone, and it disappears into the background when you are not looking
   for it. */
.switcher-client::before {
  content: ''; position: absolute; left: 17px; top: 0; bottom: 0;
  border-left: 1px solid var(--rule-faint);
}
.switcher-new {
  color: var(--accent); border-top: 1px solid var(--rule);
  padding: var(--s-1) var(--s-2); font-weight: 500;
}
.switcher-empty { padding: var(--s-2); color: var(--mute); font-size: 13px; }
.dot.is-none { background: var(--mute); opacity: .4; }

/* ── Home ───────────────────────────────────────────────────────────────────
   Each workspace a section: rename in place, collapse, and its clients as
   CARDS or as a LIST. Cards are for choosing something you recognise by shape;
   a list is for scanning twenty of them. The choice is per workspace, per
   device, and remembered — neither is right for everybody, so neither is
   imposed. */

.home-urgent { margin: 0 0 var(--s-3); }
.home-h { font: var(--t-label); letter-spacing: var(--tr-label); color: var(--mute); margin: 0 0 var(--s-1); }
.waiting-box { border: 1px solid var(--rule); border-radius: var(--r-lg); overflow: hidden; }
/* Scoped to Home's box: unscoped, its three columns overrode Today's four and
   pushed every date onto a line of its own. */
.waiting-box .waiting-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--s-1h); align-items: center;
  padding: var(--s-1) var(--s-2); border-bottom: 1px solid var(--rule-faint);
}
.waiting-box .waiting-row:last-child { border-bottom: 0; }
.waiting-title { text-align: left; min-width: 0; }

/* BETWEEN two workspaces there is real separation, because they are different
   things; INSIDE one it is tight, because it is one thing. */
.home-list { display: flex; flex-direction: column; gap: var(--s-4); }
.home-section { }

/* VERTICAL RHYTHM ON HOME. Franz, 2026-09-15: "i meant vertical spacing not
   horizontal mate". Four gaps stacked between a workspace's name and its first
   row — 14px under the rule, 22px above the block, the toolbar's own padding,
   and the table's border — each reasonable alone and a hole together. One
   decision now: 10px under the name, and the strip sits on the table. */
.home-ws {
  display: flex; align-items: center; gap: var(--s-1);
  padding: 0 0 var(--s-1); border-bottom: 1px solid var(--rule); margin-bottom: var(--s-1);
}
.home-fold {
  background: none; border: 0; cursor: pointer; padding: var(--s-half); display: flex; flex: none;
}
.home-chev {
  width: 7px; height: 7px; border-right: 1.5px solid var(--mute); border-bottom: 1.5px solid var(--mute);
  transform: rotate(45deg) translate(-1px, -1px); transition: transform .12s ease;
}
.home-chev.is-folded { transform: rotate(-45deg) translate(-1px, 1px); }

/* THE WORKSPACE NAME IS THE HEADING OF ITS SECTION, so it is sized like one.
   At --t-section (16px) it was the same weight as "Latest work" inside it and
   the page read as a flat list of rows rather than as several workspaces. */
.home-ws-name {
  font: 600 20px/1.2 var(--font-display); letter-spacing: -.015em; color: var(--ink);
  background: none; border: 0; padding: var(--s-nudge) var(--s-half); margin-left: calc(-1 * var(--s-half)); border-radius: var(--r-sm);
  text-align: left;
}
.home-ws-name.is-editable { cursor: text; }
.home-ws-name.is-editable:hover { background: var(--shade); }
.home-rename {
  font: 600 20px/1.2 var(--font-display); letter-spacing: -.015em;
  padding: var(--s-nudge) var(--s-half); width: 15em; max-width: 50vw;
}
.home-ws-meta { font-size: 12.5px; color: var(--mute); }
.home-ws { align-items: center; }
.home-ws-count { font-size: 12.5px; color: var(--mute); margin-left: auto; }
.home-ws .home-fold { margin-left: var(--s-half); }

/* Two labelled buttons, not an icon pair: an icon for "cards" is a guess the
   reader has to make every time. */
.seg-group { display: flex; border: 1px solid var(--rule); border-radius: var(--r-md); overflow: hidden; }
.seg {
  background: none; border: 0; cursor: pointer; padding: 0 var(--s-1h); min-height: calc(var(--h-md) - 2px);
  font: 500 12px/1 var(--font-body); color: var(--mute);
}
.seg + .seg { border-left: 1px solid var(--rule); }
.seg:hover { color: var(--ink); }
/* THE ONE YOU ARE ON IS PRESSED, AND CANNOT BE PRESSED AGAIN. Franz,
   2026-09-16: "if im in cards or lsit already - dont let me press it again and
   hgihlight it liek the create button in top right header". A button that does
   nothing when you press it is a button that makes you doubt you pressed it. */
.seg.is-on {
  background: var(--accent); color: var(--accent-ink); cursor: default;
}
.seg.is-on:hover { color: var(--accent-ink); }
.seg[disabled] { opacity: 1; }

.home-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(196px, 100%), 1fr)); gap: var(--s-1h); }
.home-card {
  display: flex; flex-direction: column; gap: var(--s-1); align-items: flex-start;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: var(--s-2) var(--s-2); cursor: pointer; text-align: left; color: var(--ink); min-height: 76px;
}
.home-card:hover { border-color: var(--ink-soft); }
.home-card-top { display: flex; align-items: center; gap: var(--s-1); min-width: 0; width: 100%; }
.home-card-name {
  font: 500 14.5px/1.3 var(--font-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Two lines, clamped. A card is for recognising a client, not for reading
   their positioning statement. */
.home-card-sells {
  font: 400 12.5px/1.45 var(--font-body); color: var(--ink-soft);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; text-align: left;
}
.home-card-sells.is-empty { color: var(--mute); font-style: italic; }
.home-row-sells {
  margin-left: auto; font-size: 12.5px; color: var(--mute);
  max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* THE MARK. A logo if there is one, a monogram if there is not — and a
   monogram is the final answer for most clients, not a placeholder waiting to
   be replaced. A grey square tells you nothing and looks broken. */
.mark {
  /* ROUND, EVERYWHERE. Franz, 2026-09-16: "logo everywhere needs to be rounded
     version and not square mate". It was a rounded square at four different
     radii depending on which list it was in — 7px here, 6px in a card, 9px on
     the client page, 13px on the header. A mark is somebody's face in this
     product and a face is round. */
  /* A CIRCLE, AND THE IMAGE FILLS IT. Franz, 2026-09-16: "i want circle literal
     circle". Both halves matter and only one of them was ever true at a time:
     it was a circle around a CONTAINED image, which left four corners of the
     logo's own white sitting inside the ring and a picture that never reached
     its frame. The frame is round and the image covers it, which is what a
     profile mark is everywhere. */
  flex: none; width: var(--h-md); height: var(--h-md); border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  font: 600 11.5px/1 var(--font-display); letter-spacing: .01em;
  background: var(--shade); color: var(--ink-soft); border: 1px solid var(--rule);
}
.mark-img { width: 100%; height: 100%; object-fit: cover; background: var(--paper); }
/* No padding: the image IS the frame's content, edge to edge. */
.mark.has-img { padding: 0; background: var(--paper); }
.home-row .mark { width: 26px; height: 26px; font-size: 10.5px; }

.home-rows { border: 1px solid var(--rule); border-radius: var(--r-lg); overflow: hidden; }
.home-row {
  display: flex; align-items: center; gap: var(--s-1); width: 100%;
  background: none; border: 0; border-bottom: 1px solid var(--rule-faint);
  padding: var(--s-1) var(--s-2); cursor: pointer; text-align: left; color: var(--ink); font-size: 13.5px;
}
.home-row:last-child { border-bottom: 0; }
.home-row:hover { background: var(--shade); }
.home-row-name { font-weight: 500; }
.home-row-meta { margin-left: auto; font-size: 12.5px; color: var(--mute); }

.btn-sm { min-height: var(--h-md); padding: 0 var(--s-1h); font-size: 13px; margin-left: auto; }

.toast {
  position: fixed; left: 18px; bottom: 18px; z-index: 90;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-md);
  box-shadow: 0 10px 26px rgb(0 0 0 / .3);
  padding: var(--s-1) var(--s-2); font-size: 13px; color: var(--ink); max-width: 420px;
}

/* ── a client's workspace line, and the move ─────────────────────────────── */

.client-ws {
  display: flex; align-items: center; gap: var(--s-1h); flex-wrap: wrap;
  margin: 0 0 var(--s-2); font-size: 13px; color: var(--mute);
}
.client-ws-pick { width: auto; min-width: 170px; font-size: 13px; padding: var(--s-half) var(--s-1); }
/* ONE NEAT LINE. Franz, 2026-09-24: "improve the move the thing try ot make in
   one neat line". Inside the settings menu the label and the move control sit
   on the same row, with the label taking the slack so the select stays put
   whatever the workspace is called. No wrap: on a menu 320px wide a wrapped
   row is the thing that made it look like two. */
.client-ws.is-line {
  flex-wrap: nowrap; margin: 0 0 var(--s-half); gap: var(--s-1);
}
.client-ws.is-line .client-ws-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.client-ws.is-line .client-ws-pick { flex: none; min-width: 0; width: 118px; }

/* ── members and invites ────────────────────────────────────────────────── */

.member-row {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding: var(--s-1) 0; border-bottom: 1px solid var(--rule-faint);
}
.member-name { font-weight: 500; font-size: 13.5px; }
.member-role { width: auto; min-width: 130px; margin-left: auto; font-size: 13px; padding: var(--s-half) var(--s-1); }
.invite-row { display: flex; gap: var(--s-1); align-items: flex-start; flex-wrap: wrap; margin: var(--s-half) 0 0; }
.invite-row .textarea { flex: 1 1 220px; }
.invite-row .input { flex: 0 0 auto; min-width: 200px; }
/* An invite code is read off a screen and typed, so it is set in the one place
   this product uses a fixed pitch: a code where l and 1 must be told apart. */
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; color: var(--ink-soft); }
.linkbtn.alarm { color: var(--alarm); }

@media (max-width: 760px) {
  .ws-switch { max-width: 130px; }
  .switcher-card { margin-top: var(--s-3); }
  .member-role { margin-left: 0; }
}

/* A confirmation that survives the redraw it caused. */
.notice-line {
  margin: 0 0 var(--s-2); padding: var(--s-1) var(--s-1h); font-size: 13px;
  background: var(--shade); border-left: 2px solid var(--accent);
  border-radius: var(--r-sm); color: var(--ink-soft);
}

/* ── the dossier ────────────────────────────────────────────────────────────
   Documents as files. A name, a kind, a size, a date and a state — the five
   things a folder tells you — and the markdown itself one click away, because
   it is what actually reaches the prompt and nobody could see it before. */

.dossier-head { display: flex; align-items: baseline; gap: var(--s-1h); margin: var(--s-half) 0 var(--s-1); }
.dossier-count { font-size: 12.5px; color: var(--mute); }
/* A document nobody has read yet is the one thing on this page waiting on a
   person, so it is marked as such rather than sorted quietly to the top. */
.doc-state.is-proposed { border-color: var(--accent); color: var(--accent); }
.doc-decide { margin-top: var(--s-2); padding-top: var(--s-2); border-top: 1px solid var(--rule); }
.dossier { border: 1px solid var(--rule); border-radius: var(--r-lg); overflow: hidden; }
.doc + .doc { border-top: 1px solid var(--rule-faint); }

.doc-row {
  display: grid; grid-template-columns: auto 1fr auto auto auto; gap: var(--s-1h); align-items: center;
  width: 100%; background: none; border: 0; cursor: pointer; text-align: left;
  padding: var(--s-1h) var(--s-2); color: var(--ink); font-size: 13.5px;
}
.doc-row:hover { background: var(--shade); }
.doc-row.is-open { background: var(--shade); }
.doc-icon {
  flex: none; font: 600 9.5px/1 var(--font-body); letter-spacing: .06em;
  color: var(--accent); border: 1px solid var(--accent); border-radius: 3px;
  padding: var(--s-half) var(--s-half) var(--s-half);
}
.doc-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-state.is-accepted { border-color: var(--accent); color: var(--accent); }
.doc-size, .doc-when { font-size: 12px; color: var(--mute); white-space: nowrap; }

.doc-open { padding: 0 var(--s-2) var(--s-2); }
.doc-md {
  margin: 0; padding: var(--s-2) var(--s-2); background: var(--paper);
  border: 1px solid var(--rule-faint); border-radius: var(--r-md);
  font: 12.5px/1.65 ui-monospace, Menlo, monospace; color: var(--ink-soft);
  white-space: pre-wrap; word-break: break-word; max-height: 460px; overflow: auto;
}

@media (max-width: 760px) {
  .doc-row { grid-template-columns: auto 1fr auto; }
  .doc-size, .doc-when { display: none; }
}

/* The mark on a client page doubles as the logo upload. The hint only appears
   on hover, because a permanent "Edit" badge on a logo is noise on every other
   visit. */
.mark-wrap { display: inline-flex; }
.mark-btn {
  position: relative; background: none; border: 0; padding: 0; cursor: pointer;
  display: inline-flex; border-radius: 8px;
}
.mark-btn .mark { width: 38px; height: 38px; font-size: 13px; }
.mark-edit {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: 9px; background: rgb(0 0 0 / .62); color: var(--paper);
  font: 600 9.5px/1 var(--font-body); letter-spacing: .04em; text-transform: uppercase;
  opacity: 0; transition: opacity .12s ease;
}
.mark-btn:hover .mark-edit, .mark-btn:focus-visible .mark-edit { opacity: 1; }
.client-head h1 { display: flex; align-items: center; gap: var(--s-1h); }

/* Work, as cards or as rows. Same control as Home, same reasoning. */
.work-bar { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; margin: 0 0 var(--s-2); }
.work-bar .filters { margin: 0; }
.work-bar .seg-group { margin-left: auto; }

/* WORK CARDS. Pinterest-shaped: the creative leads, at its own aspect ratio,
   and the cards fall into columns rather than a grid of equal boxes. A grid
   forces every card to one height, which means either cropping the ad or
   padding the card, and the first of those is unacceptable here: the thing
   being judged is composition, and a card that lops the top off an ad is lying
   about the work. */
.work-cards { columns: 4 264px; column-gap: var(--s-2); margin-top: var(--s-nudge); }
.work-card {
  display: block; width: 100%; break-inside: avoid; margin: 0 0 var(--s-2);
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: 0; overflow: hidden; cursor: pointer; text-align: left; color: var(--ink);
}
.work-card:hover { border-color: var(--ink-soft); }

/* CONTAINED, NEVER COVERED. A 9:16 story and a 1:1 square both show whole. */
.wc-shot {
  display: block; background: var(--shade); border-bottom: 1px solid var(--rule-faint);
  padding: var(--s-1);
}
.wc-img {
  display: block; width: 100%; height: auto; max-height: 320px;
  object-fit: contain; border-radius: 4px;
}

.wc-body { display: flex; flex-direction: column; gap: var(--s-1); padding: var(--s-1h) var(--s-2) var(--s-1h); }

.wc-chips { display: flex; flex-wrap: wrap; gap: var(--s-half); align-items: center; }
.wc-kind { font: 500 11px/1 var(--font-body); color: var(--accent); }
.wc-chip {
  font: 500 10.5px/1 var(--font-body); color: var(--ink-soft);
  background: var(--shade); border: 1px solid var(--rule); border-radius: 4px; padding: var(--s-half) var(--s-1);
}
/* The eight semantic colours, and this is the one thing they are for. */
.wc-emotion {
  font: 500 10.5px/1 var(--font-body); border-radius: 4px; padding: var(--s-half) var(--s-1);
  color: var(--paper); text-transform: lowercase;
}
.wc-emotion.is-joy { background:#F2D338; } .wc-emotion.is-trust { background:#96C93D; }
.wc-emotion.is-fear { background:#2E7D4F; color:#F3F4F1; } .wc-emotion.is-surprise { background:#3FA9C9; }
.wc-emotion.is-sadness { background:#3B6FB6; color:#F3F4F1; } .wc-emotion.is-disgust { background:#8E5CB5; color:#F3F4F1; }
.wc-emotion.is-anger { background:#D6483B; color:#F3F4F1; } .wc-emotion.is-anticipation { background:#E88A2E; }

.wc-name { font: 500 13.5px/1.4 var(--font-body); }
.wc-sub {
  font: 400 12px/1.45 var(--font-body); color: var(--mute);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.wc-tag {
  align-self: flex-start; font: 400 10.5px/1 var(--font-body); color: var(--mute);
  border: 1px solid var(--rule-faint); border-radius: var(--r-pill); padding: var(--s-half) var(--s-1);
}
.wc-foot {
  display: flex; align-items: baseline; gap: var(--s-1); margin-top: var(--s-half);
  padding-top: var(--s-1); border-top: 1px solid var(--rule-faint);
}
.wc-score { font: 600 17px/1 var(--font-display); font-variant-numeric: tabular-nums; }
.wc-client { margin-left: auto; font-size: 11.5px; color: var(--accent); }
/* The author sits between the client and the date, quiet, and it is the one
   that gives way: a card is 200-odd pixels wide and the name is the longest
   thing that can land in this row. */
.wc-by {
  font-size: 11.5px; color: var(--mute); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Without a client beside it, the author is what pushes the date to the right.
   The first child is always the score, present or empty, so an author sitting
   directly after it is an author with no client in front of it — the same idiom
   the two rules under this one already use. */
.wc-foot > span:first-child + .wc-by { margin-left: auto; }
.wc-foot .wc-client + .wc-by { margin-left: 0; }
.wc-when { font-size: 11.5px; color: var(--mute); flex: none; }
.wc-foot .wc-client + .wc-when { margin-left: 0; }
.wc-foot > span:first-child:empty + .wc-client { margin-left: 0; }

/* A workspace's own latest work, inside its section. */
.ws-recent { margin: 0; }
/* "Latest work" sits INSIDE the table's toolbar now, not in a band above it —
   two strips saying overlapping things, with the count printed in both, was
   the thing that made this section look like two controls for one list. */
.ws-recent-h {
  font: var(--t-label); letter-spacing: var(--tr-label); color: var(--mute); flex: none;
}

@media (max-width: 900px) { .work-cards { columns: 2 200px; } }
@media (max-width: 560px) { .work-cards { columns: 1; } }

/* ── the Work board (2026-10-09) ───────────────────────────────────────────
   Franz: "a third view which would be kabban style like the task thing based
   on the asset type". The Tasks board's shape — tinted lanes, white cards —
   with columns that are asset types, stages or campaigns depending on the
   Type filter (work-board.js says which and why). The lanes share the width
   and the row scrolls sideways only when there are too many to fit; a lane
   itself never scrolls, so a long one makes the page longer. */
/* The lanes are the Tasks board's (.tk-board, .tk-col…). Only the dots are
   the Work board's own: one per asset type from the client palette, which is
   a label and never an emotion, and one per stage the way Tasks does it. */
.tk-board.wb-board { gap: var(--s-1); }
.wb-col.is-t-script .tk-col-head::before { background: var(--c-sea); }
.wb-col.is-t-ad .tk-col-head::before { background: var(--c-clay); }
.wb-col.is-t-creative .tk-col-head::before { background: var(--c-plum); }
.wb-col.is-t-bulk .tk-col-head::before { background: var(--c-sand); }
.wb-col.is-t-form .tk-col-head::before { background: var(--c-moss); }
.wb-col.is-t-analysis .tk-col-head::before { background: var(--c-slate); }
.wb-col.is-s-in_review .tk-col-head::before { background: var(--c-sea); }
.wb-col.is-s-changes_requested .tk-col-head::before { background: var(--c-sand); }
.wb-col.is-s-approved .tk-col-head::before,
.wb-col.is-s-sent .tk-col-head::before { background: var(--accent); }
/* Seven narrow lanes: the head keeps its name whole and gives way on the count. */
.wb-col { min-width: 0; padding: var(--s-1) var(--s-1) var(--s-1h); }
[data-screen="client"] .wb-col .tk-col-head { min-height: 36px; gap: var(--s-1); }
[data-screen="client"] .wb-col .tk-col-name { font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
[data-screen="client"] .wb-col .tk-col-add { width: 24px; height: 24px; flex: none; }
.wb-more {
  align-self: stretch; height: 32px; border-radius: var(--r-md); cursor: pointer;
  background: none; border: 1px solid var(--rule); color: var(--ink-soft);
  font: 500 12.5px/1 var(--font-body);
}
.wb-more:hover { color: var(--ink); border-color: var(--ink-soft); background: var(--card); }

.wb-card { gap: var(--s-1); padding: var(--s-1); min-width: 0; }
.wb-card.is-finished .wb-title { color: var(--ink-soft); }
.wb-shot {
  display: block; margin: calc(-1 * var(--s-nudge)) calc(-1 * var(--s-half)) 0; background: var(--shade);
  border-radius: var(--r-sm); padding: var(--s-1);
}
.wb-img { display: block; width: 100%; height: auto; max-height: 120px; object-fit: contain; border-radius: 3px; }

.wb-facts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-half); }
.wb-chip {
  font: 500 10.5px/1 var(--font-body); color: var(--ink-soft);
  background: var(--shade); border: 1px solid var(--rule); border-radius: 4px; padding: var(--s-half) var(--s-1);
}
.wb-facts .wc-emotion { padding: var(--s-half) var(--s-1); }
.wb-stage {
  display: inline-flex; align-items: center; gap: var(--s-half); height: 22px; padding: 0 var(--s-1);
  border-radius: var(--r-pill); cursor: pointer; border: 1px solid var(--rule);
  background: var(--shade); color: var(--ink-soft); font: 500 11.5px/1 var(--font-body);
}
.wb-stage:hover { border-color: var(--ink-soft); color: var(--ink); }
.wb-stage.is-in_review { background: color-mix(in srgb, var(--c-sea) 16%, var(--card)); border-color: color-mix(in srgb, var(--c-sea) 40%, var(--rule)); }
.wb-stage.is-changes_requested { background: color-mix(in srgb, var(--c-sand) 20%, var(--card)); border-color: color-mix(in srgb, var(--c-sand) 45%, var(--rule)); }
.wb-stage.is-approved { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 35%, var(--rule)); color: var(--accent); }
.wb-stage.is-sent { background: var(--accent); border-color: var(--accent); color: var(--paper); }
.wb-stage-chev { display: inline-grid; place-items: center; opacity: .7; }

.wb-title {
  margin: 0; font: 600 13.5px/1.4 var(--font-body); color: var(--ink); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.wb-sub {
  margin: -3px 0 0; font: 400 12px/1.45 var(--font-body); color: var(--mute);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.wb-tags { display: flex; flex-wrap: wrap; gap: var(--s-half); }
.wb-tags .tag { margin: 0; font-size: 11px; padding: var(--s-px) var(--s-1); }
.wb-camp { font-size: 11.5px; color: var(--accent); align-self: center; margin-right: var(--s-nudge); }
.wb-foot {
  display: flex; align-items: baseline; gap: var(--s-1); min-width: 0;
  padding-top: var(--s-1); border-top: 1px solid var(--rule-faint);
}
.wb-dl { width: 26px; height: 26px; align-self: center; flex: none; }
.wb-score { font: 600 15px/1 var(--font-display); font-variant-numeric: tabular-nums; color: var(--ink); }
.wb-score.is-mark { font-size: 13px; color: var(--ink-soft); }
.wb-who {
  font-size: 11.5px; color: var(--mute); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wb-when { margin-left: auto; font-size: 11.5px; color: var(--mute); flex: none; }

/* ── the Cards view, in folding groups ──────────────────────────────────────
   One separator per asset type: the name and its count, a hairline, and the
   fold at the far right — always the right, the house rule for every fold. */
.wg { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-nudge); }
.wg-head {
  display: flex; align-items: center; gap: var(--s-1); width: 100%;
  padding: var(--s-1) var(--s-nudge) var(--s-1); background: none; border: 0; cursor: pointer;
  text-align: left; color: var(--ink);
}
.wg-name { font: 600 14px/1.2 var(--font-display); flex: none; }
.wg-count { font: 500 13px/1 var(--font-display); color: var(--mute); font-variant-numeric: tabular-nums; flex: none; }
.wg-rule { flex: 1 1 auto; height: 1px; background: var(--rule); min-width: 24px; }
.wg-chev {
  flex: none; width: 7px; height: 7px; margin: 0 var(--s-half) var(--s-half) var(--s-nudge);
  border-right: 1.5px solid var(--mute); border-bottom: 1.5px solid var(--mute);
  transform: rotate(45deg); transition: transform .12s ease;
}
.wg-head:hover .wg-chev { border-color: var(--ink); }
.wg-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
.wg-sec.is-shut .wg-chev { transform: rotate(-45deg); margin-bottom: 0; }
.wg-sec.is-shut > .work-cards { display: none; }
.wg-sec > .work-cards { margin-bottom: var(--s-1); }


/* On Home a piece of work says whose it is; inside a client it must not. */
.work-client { font-size: 11.5px; color: var(--accent); }
.work-cards .work-client { margin-top: var(--s-nudge); }
.work-rows .work-client { margin-left: var(--s-1h); }

/* The campaign row sits under the type filters and is quieter than them: a
   campaign narrows what you are looking at, it does not change what kind of
   thing it is. */
.filters.camps { margin: calc(-1 * var(--s-half)) 0 var(--s-2); gap: var(--s-1); align-items: center; }
.filters.camps .chip { font-size: 12px; padding: var(--s-half) var(--s-1); }
.filters.camps .linkbtn { margin-left: var(--s-half); font-size: 12.5px; }
/* .input sets width:100%, so a plain width here loses to it and the box
   swallows the row. flex:none plus a basis is what actually holds. */
.filters.camps .camp-new { flex: none; width: 200px; padding: var(--s-half) var(--s-1); font-size: 12.5px; }

.ws-settings { margin: 0 0 var(--s-4); }
/* NO COLUMN RULE OF ITS OWN. The board pins each card to column 1 or column 2
   and nothing else, so a grid that opened out to four columns at this width
   left the right-hand half of the block empty. Two columns, the same as every
   other board in the product. */
.ws-settings textarea { width: 100%; resize: none; overflow: hidden; font: inherit; line-height: 1.5; }
.ws-settings textarea[readonly] { opacity: .75; cursor: default; }

/* Notes are open, always. Convention: a note you have to open a disclosure to
   write is a note that does not get written. */
.scene-note-form { display: flex; flex-direction: column; gap: var(--s-1); }
/* The other half of a scene card, and the same rank as the half beside it. */
/* A THREAD WITH NOTHING IN IT TAKES NO ROOM. The side column is a flex stack
   with a 9px gap, so an empty thread between Feedback and the note box spent
   a gap of its own and left the heading floating. */
.fm-thread:empty { display: none; }
/* Feedback sits the same distance above Notes as the fields sit from each
   other: 9px of margin plus the column's 9px gap is the 18px used between
   every other pair of blocks in this rail. */
.fm-sec-side .scene-note-h { margin-bottom: var(--s-1); }
.fm-sec-side .comment-form { margin-top: 0; }

.scene-note-h {
  display: block; margin-bottom: var(--s-1h);
  font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink);
}


/* ── notes waiting, and the live activity rail ────────────────────────────── */
/* Franz, 2026-09-14: a note, a reply, an approval and a resolution all have to
   be visible while the work is being read, not found later in a fold. So the
   count sits top right, the thread sits beside its scene, and the log runs
   down the right of the script and keeps itself current. */

.notif {
  display: inline-flex; align-items: center; gap: var(--s-1); cursor: pointer;
  background: none; border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: var(--s-half) var(--s-1); font-size: 13px; color: var(--ink-soft);
}
.notif:hover { border-color: var(--accent); color: var(--ink); }
.notif-live { border-color: var(--accent); color: var(--ink); }
.notif-count {
  min-width: 18px; height: 18px; border-radius: 9px; padding: 0 var(--s-half);
  background: var(--accent); color: var(--accent-ink);
  font: 600 11px/18px var(--font-body); text-align: center;
}
.notif-panel {
  position: fixed; top: calc(var(--header-h) + var(--s-1)); right: var(--s-2); z-index: 40;
  width: 340px; max-height: 70vh; overflow: auto;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  box-shadow: 0 14px 40px rgba(0,0,0,.34); padding: var(--s-1h);
}
.notif-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--s-1); }
.notif-h { font: var(--t-label); letter-spacing: var(--tr-label); }
.notif-list { display: flex; flex-direction: column; }
.notif-item {
  text-align: left; cursor: pointer; background: none; border: 0;
  border-top: 1px solid var(--rule-faint); padding: var(--s-1) var(--s-half);
}
.notif-item:first-child { border-top: 0; }
.notif-item:hover { background: var(--shade); }
.notif-line { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1); }
.notif-where { font: var(--t-label); letter-spacing: var(--tr-label); }
.notif-note {
  margin: var(--s-half) 0 0; font-size: 13px; color: var(--ink-soft);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.notif-sub { margin: var(--s-half) 0 0; font-size: 11.5px; color: var(--mute); }

/* The script, and what is happening to it. */
.script-layout { display: grid; grid-template-columns: minmax(0, 1fr) calc(350px + (var(--s-4) - var(--gap-rail)) / 2); column-gap: var(--gap-rail); row-gap: var(--s-4); align-items: start; }
.script-col { min-width: 0; }
/* The reference for every sticky thing on this page. Its border is the line the
   scene toolbar's pills are measured against. */
.live-rail {
  position: sticky; top: var(--stick-top);
  /* MEASURED AGAINST ITS OWN TOP, NOT THE HEADER'S. Franz, 2026-09-22:
     "acitvity oclumn is borken on scroll epsceially when we reach the bottom
     ... rather than higher than it".
     It was `100vh - header - 40px`, which on a 903px window is 811px of card
     resting at y=96 — a bottom edge at 907, four pixels past the fold. A
     sticky element taller than the room between its `top` and the viewport
     floor does not stay put: the browser pins it to the BOTTOM instead, so
     on reaching the end of the page the whole card jumped 32px up and sat
     above the toolbar it had been level with all the way down.
     Subtracting its own resting offset is the fix, and it is the only
     arithmetic that stays right when --stick-top changes. */
  /* --pad-page-end: the same gap as between the grid's bottom edge and
     the foot of the page, so the two land on one line. A sticky box is bounded by its grid area, so if the
     card is taller than the area's final on-screen slice it is shoved up in
     the last few pixels of scroll — which showed as an 8px hop at the very
     bottom after the first fix took the 32px one out. A fixed gap rather than
     a ratio, so it holds at any window height. */
  max-height: calc(100vh - var(--stick-top) - var(--pad-page-end)); overflow: auto;
  transition: transform .22s cubic-bezier(.22,.61,.36,1);
  /* Never let the inner scroll hand its overscroll back to the page, which is
     the other half of what "broken on scroll" felt like. */
  overscroll-behavior: contain;
  border: 1px solid var(--rule); border-radius: var(--r-lg);
  background: var(--card);
  /* TWO PIXELS CLEAR OF THE TITLE, NOT TUCKED UNDER IT. Franz, 2026-09-22:
     "activity column is broken from the top".
     The toolbar is SUPPOSED to sit a pixel under the sticky head — it has no
     border and its own paper hides the seam. This card has a border and a
     rounded corner, so the same trick ate them: the top edge disappeared
     under the title and the card read as sheared off. It rests below the
     head instead, and gives back the two pixels from its own top padding so
     its heading stays on the toolbar's line, which is the alignment that
     actually matters. */
  top: calc(var(--stick-top) + var(--s-nudge));
  transform: translateY(calc(var(--stick-lift, 0px) * -1));
  padding: calc(var(--stick-pad) - var(--s-nudge)) var(--s-1h) var(--stick-pad);
  /* ABOVE THE TITLE'S PAPER, OR IT LOSES ITS OWN TOP EDGE.
     Franz, 2026-09-22: "this acitvitiy box goes up too too duirng sticky
     scroll and breaks the broder on top".
     The sticky head paints two pixels of paper below itself to close the
     hairline the script would otherwise show through, and it does that at
     z-index 8. This card had no z-index at all, so that paper landed on
     exactly the two pixels its own top border and rounded corner live in —
     the border was not broken, it was painted over. One rank higher and the
     border wins while the paper still covers the page behind it. */
  z-index: 9;
}
.live-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s-1); }
.live-h { font: var(--t-label); letter-spacing: var(--tr-label); }
.live-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  animation: live-pulse 2.4s ease-in-out infinite;
}
@keyframes live-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }
.live-body { display: flex; flex-direction: column; }

/* --- rewrite a scene ------------------------------------------------------- */
/* The prompter lives in the scene's own column, above its notes — a suggestion
   belongs beside the line it is about. Folded until the scene's Rewrite button
   opens it: eight open prompters down a document is a form, not a script. */
.rw { margin: 0 0 var(--s-1h); }
/* The rewriter's own heading row, so the close sits on the label rather than
   floating over the first suggestion. */
.rw-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1); }
.rw-close {
  background: none; border: 1px solid transparent; border-radius: 5px;
  padding: 0 var(--s-1); font-size: 16px; line-height: 1.4;
  color: var(--mute); cursor: pointer; flex: none;
}
.rw-close:hover { color: var(--accent); border-color: var(--rule); }
.rw-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rw > .scene-note-h { margin-bottom: var(--s-1); }
.rw-body { display: flex; flex-direction: column; gap: var(--s-1); }
.rw-ask { min-height: 54px; }
.rw-go { align-self: flex-start; }
.rw-say { margin: 0; min-height: 16px; }
.rw-options { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-nudge); }
/* An option is a piece of writing, so it is set like one: the line first and
   biggest, the reason above it small, the rest beneath. */
.rw-opt {
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--shade); padding: var(--s-1) var(--s-1h);
  display: flex; flex-direction: column; gap: var(--s-1);
}
.rw-why { margin: 0; font-size: 11.5px; color: var(--mute); }
.rw-vo { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--ink); }
.rw-ost { margin: 0; display: flex; gap: var(--s-1); font-size: 12.5px; color: var(--ink-soft); }
.rw-tag {
  flex: none; font: 600 9.5px/1.7 var(--font-body); letter-spacing: .06em;
  text-transform: uppercase; color: var(--mute);
}
/* Said, never hidden: an option that runs half a second long is still an option,
   and the edit can lose a word. */
.rw-warn { margin: 0; font-size: 11.5px; color: var(--alarm); }
.rw-use { align-self: flex-start; }
/* The way in from the scene itself. Same quiet verb as Remove beside it. */
/* The way in, on the scene itself. Same quiet verb as Remove beside it. */
.scene-rewrite { margin-left: auto; }
/* THREE VERBS ON ONE LINE. A third one arrived with the per-scene Copy and the
   row wrapped, dropping Remove onto a line of its own under the scene's name.
   The gap between them is the row's own, and the name gives way first — a
   scene's title can be clipped, a control cannot be moved somewhere nobody
   expects it. */
.scene-top .scene-drop { flex: none; }
.scene-top .scene-name,
/* The title is an input on a saved script, and an input does not shrink below
   its own intrinsic width without being told to. */
.scene-top .scene-name-edit {
  min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.live-body .act { border-top: 1px solid var(--rule-faint); padding: var(--s-1) 0; }
.live-body .act:first-child { border-top: 0; }
.act-by { margin: var(--s-half) 0 0; font-size: 11.5px; color: var(--mute); }

@media (max-width: 1180px) {
  .script-layout { grid-template-columns: minmax(0, 1fr); }
  .live-rail { position: static; max-height: none; }
}

/* A thread: the note, its replies, and what was done about it. */
.comment-who { font: var(--t-label); letter-spacing: var(--tr-label); }
.comment-head { flex-wrap: wrap; }
.comment-reply { margin: var(--s-1) 0 0 var(--s-1h); padding-left: var(--s-1); border-left: 1px solid var(--rule); }
.comment-done { border-left-color: var(--rule); opacity: .72; }
.comment-settled { margin: var(--s-1) 0 0; display: flex; align-items: center; gap: var(--s-1); font-size: 11.5px; color: var(--mute); }
.comment-chip {
  border: 1px solid var(--rule); border-radius: 20px; padding: var(--s-px) var(--s-1);
  font-size: 11px; color: var(--ink-soft);
}
.reply-box { margin-top: var(--s-1); }
.comment-actions { margin-top: var(--s-1); display: flex; flex-wrap: wrap; gap: var(--s-1); }
.btn-quiet { background: none; border: 1px solid var(--rule); color: var(--ink-soft); }
.btn-quiet:hover { border-color: var(--accent); color: var(--ink); }

/* ── the Work tab ─────────────────────────────────────────────────────────────
   Two sub-tabs over one list. They reuse .subtabs, which the client page
   already uses for exactly this job — a second set of tab styling would be a
   second thing to keep in step for no gain. */

.worktab-body { margin-top: 0; }

/* A script in the Work list. Six columns because six things are what somebody
   scanning forty scripts is actually looking for; .work-row's four would have
   to drop the two that make this list worth having. */
.work-row-script {
  grid-template-columns: 46px minmax(0, 1fr) 150px 132px 112px 96px;
  width: 100%; text-align: left; background: none; border-left: 0;
  border-right: 0; border-top: 0; cursor: pointer;
}
.work-row-script:hover { background: var(--shade); }
.work-row-len {
  font: 500 12.5px/1 var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--mute);
}
.work-row-title {
  font: 500 13.5px/1.4 var(--font-body); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.work-row-client { font-size: 11.5px; color: var(--accent); }
/* Open notes are the one number on this row that is a call to do something, so
   it is the one that is allowed to be loud. It uses the alarm colour rather
   than a Plutchik one: those name emotions and never chrome. */
.notes-chip { border-color: var(--alarm); color: var(--alarm); }
/* A state and a note count are labels, not sentences: wrapped over two lines
   they read as a paragraph in a column and throw the row's baseline out. */
.work-row-script .chip { white-space: nowrap; justify-self: start; }
.chip-gap { display: block; }

@media (max-width: 900px) {
  .work-row-script { grid-template-columns: 46px minmax(0, 1fr) auto; }
  .work-row-script .work-row-client, .work-row-script .c-when { display: none; }
}

/* The generator bar's styles are gone with it — making is a tab now, on Work
   and on every client, so a bar above the list was a second door to a form
   that already had its own. */

/* ── the generator sheet ──────────────────────────────────────────────────────
   What it already knows, then four questions, then the angle. The knowing
   comes first on purpose: it is what makes this different from a text box. */

.knows {
  margin: 0 0 var(--s-3); padding: var(--s-1h) var(--s-2);
  background: var(--shade); border: 1px solid var(--rule-faint); border-radius: var(--r-md);
  font-size: 13px;
}
.knows p { margin: 0 0 var(--s-half); }
.knows .label { display: inline; margin: 0 var(--s-1) 0 0; flex: none; }
/* ONE LINE, AND ONLY WHEN THERE IS SOMETHING TO DO. The inventory of what the
   product knows about somebody is gone: what is left names the gap and offers
   the way to close it. */
.knows-line { color: var(--mute); margin: 0; line-height: 1.5; }
.knows-gap { color: var(--ink-soft); }
.knows:empty { display: none; }
.knows:empty + .tips { margin-top: 0; padding-top: 0; border-top: 0; }
.knows .linkbtn { margin-top: var(--s-half); font-size: 12.5px; }

/* The angle. Cards rather than a radio list: the argument is a sentence, and a
   sentence beside a radio button reads as fine print. */
.angle-field .angle-go { display: flex; align-items: center; gap: var(--s-1h); margin: var(--s-1) 0 0; }
.angles { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-1h); }
.angles:empty { margin-top: 0; }
.angles-needs {
  margin: 0 0 var(--s-nudge); font-size: 12.5px; color: var(--ink-soft);
  display: flex; gap: var(--s-1); flex-wrap: wrap;
}
.angles-needs .label { flex: none; }
.angle {
  display: flex; flex-direction: column; gap: var(--s-half); text-align: left;
  padding: var(--s-1h) var(--s-1h); background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--r-md); cursor: pointer;
}
.angle:hover { border-color: var(--ink-soft); }
.angle[aria-pressed="true"] { border-color: var(--accent); background: var(--shade); }
.angle-name { font: var(--t-label); letter-spacing: var(--tr-label); color: var(--accent); }
.angle-line { font-size: 13.5px; line-height: 1.5; }
.angle-bet { font-size: 12px; color: var(--mute); }

/* ── the data table ───────────────────────────────────────────────────────────
   Columns you can move, resize, hide and sort; rows you can select. Ported from
   fran7esque-sounds, which is React — this is the same behaviour in plain DOM.

   NOT a <table>. Header and rows are flex rows of fixed-width cells, and ONE
   scroll container owns both axes so the sticky header and the rows move in the
   same compositor frame. A JS-mirrored scrollLeft lags by a frame and the
   header visibly slides off its own columns. */

.dt { margin: 0; }

/* The strip sits close to what it filters. 10px under it was fine beside a
   table, which draws its own top border; above a grid of cards, which has no
   edge of its own, it read as a gap nobody put there on purpose. */
.dt-toolbar {
  display: flex; align-items: center; gap: var(--s-1h);
  padding: 0 0 var(--s-1); overflow-x: auto; scrollbar-width: none;
}
.dt-toolbar::-webkit-scrollbar { display: none; }
.dt-count { font-size: 12px; color: var(--mute); flex: none; }
/* A grow-spacer, never margin-left:auto. An auto margin resolves against
   NEGATIVE free space once the strip overflows and shoves the row sideways. */
.dt-spacer { flex: 1 1 auto; min-width: 0; }

.dt-colbtn { display: inline-flex; align-items: center; gap: var(--s-1); flex: none; }
.ic { flex: none; display: block; }

/* THE WHEEL PASSES THROUGH. This said `overscroll-behavior: none`, which is a
   stronger form of the instruction that broke this page once already: do not
   chain the scroll on to whatever is behind me. On a full-page table it was
   invisible — there is nothing behind it to scroll. The moment the dossier and
   the links became tables, two scroll containers appeared in the middle of a
   long page, and the wheel over either of them did nothing: the cards below
   (Audience, Hooks) could not be reached, twice.

   `auto` is the default and the right answer: a table that is full scrolls
   itself, and at its end — or when it is not full at all — the page carries on.
   If a table ever needs to trap the wheel again, it needs a reason written
   here. */
.dt-scroll {
  overflow: auto; -webkit-overflow-scrolling: touch;
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--card);
  max-height: calc(100dvh - 260px);
}
/* ROOM BETWEEN THE FILTERS AND THE COLUMN HEADS. Franz, 2026-09-23: "i want
   the columns to go a bit lower in these main places cause its too close".

   The toolbar's own 8px was all there was, so the header row sat directly under
   the filter chips and the two read as one block — the chips looked like part
   of the table rather than the control above it.

   IT GOES ON THE TABLE, NOT THE TOOLBAR, because `.dt-toolbar` is shared with
   the card grids, where the same 8px is correct: a grid has no top edge of its
   own, so a bigger gap there reads as a hole nobody put in on purpose. The
   comment above .dt-toolbar already records that argument; this is the other
   half of it. `.dt > .dt-scroll` exists only in a table.

   When rows are selected the bulk bar takes this slot and carries its own
   spacing, so the two never stack. */
.dt > .dt-scroll { margin-top: var(--s-1); }
.dt-inner { min-width: 100%; }

.dt-head {
  position: sticky; top: 0; z-index: 3; display: flex;
  background: var(--shade); border-bottom: 1px solid var(--rule);
  /* Its own compositor layer. A sticky header inside a box that scrolls on both
     axes is repositioned on the main thread in WebKit and lags a finger drag. */
  transform: translateZ(0); will-change: transform; backface-visibility: hidden;
}
.dt-h-cell {
  position: relative; flex: none; height: var(--h-md); display: flex; align-items: center;
  padding: 0 var(--pad-row-x); border-right: 1px solid var(--rule-faint);
  cursor: grab; user-select: none; touch-action: none;
}
.dt-h-cell.is-pinned { cursor: default; }
.dt-head.is-dragging .dt-h-cell { cursor: grabbing; }
.dt-h-cell.is-dragged { opacity: .4; }
.dt-h-cell.is-right { justify-content: flex-end; }
.dt-h-cell.is-right .dt-sort { justify-content: flex-end; }

.dt-h-label {
  /* BOLD, IN TITLE CASE, THE TABLE'S OWN VOICE (Franz, 2026-11-05: "bolder and bigger"). table.js headerFloor measures
     this same type, so a change here is a change there. */
  font: 700 13px/1 var(--font-display); color: var(--ink); white-space: nowrap;
}
/* absolute inset-0, NOT width:100% — with width:100% the button fills only the
   cell's content box and the cell's own 10px padding is dead to the click, so
   pressing a few pixels either side of the word does nothing. */
.dt-sort {
  position: absolute; inset: 0; display: flex; align-items: center; gap: var(--s-1);
  padding: 0 var(--pad-row-x); background: none; border: 0; cursor: pointer; color: var(--mute);
}
.dt-sort:hover { color: var(--accent); }
.dt-sort:hover .dt-h-label { color: var(--accent); }
.dt-sort .ic { opacity: .55; }
.dt-sort.is-on, .dt-sort.is-on .dt-h-label { color: var(--accent); }
.dt-sort.is-on .ic { opacity: 1; }

/* The resize strip straddles the border, 7px each side, ABOVE the sort button
   so the last few pixels of a header resize rather than sort. */
.dt-grip {
  position: absolute; top: 0; right: -7px; width: 14px; height: 100%; z-index: 4;
  display: flex; align-items: center; justify-content: center; cursor: col-resize;
}
.dt-grip-line { width: 0; height: 100%; background: var(--accent); transition: width .12s; }
.dt-grip:hover .dt-grip-line { width: 2px; }

/* The drop indicator. Same 2px accent line as the resize hover, on purpose:
   one visual language for "this edge is the one you are about to act on". */
.dt-drop { position: absolute; top: 0; height: 100%; width: 2px; background: var(--accent); z-index: 5; pointer-events: none; }
.dt-drop.is-before { left: calc(-1 * var(--s-px)); }
.dt-drop.is-after { right: calc(-1 * var(--s-px)); }

.dt-row {
  display: flex; border-bottom: 1px solid var(--rule-faint); cursor: pointer;
  /* A FLOOR, NOT A HEIGHT. A row of one line sits at this; a row whose text
     wraps grows, and the padding on the cell below is what stops it hugging its
     own borders when it does. */
  min-height: var(--h-row);
}
.dt-row:last-child { border-bottom: 0; }
.dt-row:hover { background: var(--shade); }
.dt-row.is-selected { background: color-mix(in srgb, var(--accent) 9%, transparent); }

.dt-cell {
  /* VERTICAL PADDING, WHICH THIS HAD NONE OF. Franz, 2026-09-15, pointing at
     the first rows of Audience and Copy That Worked: "the spacing aint proper
     mate i know its double lines but i guess i need some more default height in
     each column so it doesnt look so bad".
     A one-line row never showed the problem because the 40px floor centred it
     with room to spare. A row that WRAPS is exactly as tall as its text, so the
     second line sat on the border and the first sat under the one above — two
     lines crammed into the space one had. Nine pixels top and bottom, so a
     wrapped row is 9 + text + 9 and reads as one block with air around it.
     The floor still governs single lines, so nothing else on any table moves. */
  flex: none; display: flex; align-items: center; padding: var(--s-1) var(--pad-row-x);
  border-right: 1px solid var(--rule-faint); min-width: 0;
}
.dt-cell.is-right { justify-content: flex-end; }
.dt-cell-in {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: inline-flex; align-items: center; gap: var(--s-1); font-size: 13px;
}
/* The filler is gone: its job was to carry the header band and the row hover to
   the right edge, and a column wide enough to reach the edge does that without
   leaving a strip nobody can use. */

.dt-select { justify-content: center; padding: 0; }
.dt-check {
  width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--rule);
  background: none; display: flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0; color: var(--accent);
}
.dt-check:hover { border-color: var(--ink-soft); }
.dt-check.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); }
.dt-check-dash { width: 6px; height: 1px; background: var(--accent); }

.dt-title {
  background: none; border: 0; padding: 0; text-align: left; cursor: pointer;
  font: 500 13px/1.4 var(--font-body); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.dt-title:hover { color: var(--accent); }
.dt-client { color: var(--accent); font-size: 12px; }
/* WHO MADE IT. Plain ink, not the accent: a client is a link you follow and an
   author is a fact you read, and colouring both the same made every row look
   like it had two destinations in it. Ellipsised, because a long name must
   shorten rather than push the columns after it off the table. */
.dt-person {
  display: inline-flex; align-items: baseline; gap: var(--s-1);
  font-size: 12px; color: var(--ink-soft);
  overflow: hidden; max-width: 100%;
}
.dt-person > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "you", once, quietly. It is the only thing anybody scans this column for, and
   a badge that shouted would be a badge on a third of the rows. */
.dt-person-you {
  flex: none; font: 500 10px/1 var(--font-body); letter-spacing: .06em;
  text-transform: uppercase; color: var(--mute);
  border: 1px solid var(--rule-faint); border-radius: var(--r-pill); padding: var(--s-half) var(--s-half);
}
/* Type is a pill too, so the three short facts in the middle of a row read as
   three of a kind rather than as a word, a pill and a word. */
.dt-kind { color: var(--ink-soft); }
.dt-score, .dt-num { font: 500 13px/1 var(--font-display); font-variant-numeric: tabular-nums; }
.dt-tags { display: inline-flex; gap: var(--s-half); }
.dt-empty { padding: var(--s-3) var(--pad-row-x); }
/* A chip in a table's toolbar is a toolbar control, so it stands as tall as
   the search and the view switch beside it (--h-md, SPEC-LAYOUT-V1 §2c). */
.dt-toolbar .chip { min-height: var(--h-md); }

/* The one place the eight semantic colours are allowed outside a report: this
   cell is NAMING an emotion, which is what they are for. */
/* THE WORD, IN A PILL, IN ITS OWN CASE. Franz, 2026-09-16: "text only and
   capital first letter of word always". Eight coloured backgrounds down a
   column were a bar chart of hues nobody asked for; the colours mean the
   emotion on the report, where there is one of them at a time. */
.dt-emotion { text-transform: none; }

/* The utility cell. Its width is arithmetic — see WORK_UTIL_W. */
/* ── the utility column ──────────────────────────────────────────────────────
   Last, and nothing past it. There is no filler any more — the leftover width
   goes into the title column instead, so a narrow table fills its container
   exactly and the verbs sit on the right edge.

   AND IT STAYS THERE WHEN THE TABLE IS WIDER THAN THE PAGE. With every column
   on, eleven of them run past the right edge and the verbs went with them: the
   only controls that change anything were the ones you had to scroll sideways
   to find. Stuck to the right of the viewport, they are always the last thing
   and always reachable, which is what an actions column is for.

   It needs its own background or the columns scroll through it, and its own
   left edge or it reads as floating over the row rather than ending it. */
.dt-util {
  justify-content: center; border-right: 0;
  position: sticky; right: 0; z-index: 2;
  background: var(--card); border-left: 1px solid var(--rule);
  /* Stays on the right edge when the columns come up short of it. */
  margin-left: auto;
}
.dt-h-cell.dt-util { background: var(--shade); }
/* The row's own states have to reach it too, or the pinned cell stays dark
   while the rest of the row lights up under the pointer. */
.dt-row:hover .dt-util { background: var(--shade); }
.dt-row.is-selected .dt-util { background: color-mix(in srgb, var(--accent) 9%, var(--card)); }
/* ROOM BETWEEN THE ICONS. At 24px with a 3px gap these were four glyphs in a
   huddle: the hit targets touched, and which one you were about to press was a
   guess. 26px with 8px between them, and the dividers have air on both sides. */
.dt-ics { display: inline-flex; align-items: center; gap: var(--s-1); }
.dt-ic {
  width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: var(--r-sm); color: var(--mute); cursor: pointer; padding: 0;
}
.dt-ic:hover { color: var(--accent); background: var(--shade); }
.dt-ic.is-danger:hover { color: var(--alarm); }
.dt-ic-div { width: 1px; height: 15px; background: var(--rule-faint); }
/* A TEAM CARD READ ONLY (client-settings.js readOnlyCard, Copy That Worked for a
   client): no Utility column, no add box, and a row's words do not open to edit. */
.pf-field.is-readonly :is(.dt-util, .lled-new) { display: none; }
.pf-field.is-readonly .dt-title { pointer-events: none; }

/* ── the selection bar ───────────────────────────────────────────────────────
   Appears only when something is selected, in the band above the table, so the
   table itself never moves sideways and the toolbar stays put. */

.dt-bulk {
  display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap;
  margin: 0 0 var(--s-1); padding: var(--s-1) var(--s-1);
  background: var(--card); border: 1px solid var(--rule);
  border-left: 2px solid var(--accent); border-radius: var(--r-md);
}
.dt-bulk.is-busy { opacity: .6; cursor: wait; }
.dt-bulk-n { color: var(--accent); font: 500 12.5px/1 var(--font-display); font-variant-numeric: tabular-nums; }
.dt-bulk-div { width: 1px; height: 14px; background: var(--rule); }
.dt-verb { display: inline-flex; align-items: center; gap: var(--s-1); }
.chip.is-danger { border-color: var(--alarm); color: var(--alarm); }
.chip.is-danger:hover { background: color-mix(in srgb, var(--alarm) 14%, transparent); }
.dt-confirm { font-size: 12.5px; color: var(--ink-soft); }
.dt-clear {
  width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--rule);
  background: none; color: var(--mute); display: flex; align-items: center;
  justify-content: center; cursor: pointer; padding: 0; flex: none;
}
.dt-clear:hover { border-color: var(--ink-soft); color: var(--ink); }
.dt-err { font-size: 12.5px; color: var(--alarm); }

/* ── TICK SEVERAL CARDS (web/pick-grid.js, Files 2026-10-31) ─────────────────
   The tick wears the tile's own quiet black square in the top-left corner,
   always in sight; ticked, it fills with the accent and the card takes an
   accent ring. While anything is ticked a click on a card ticks it, so the
   cards say so with their cursor. The download moves beside the remove on
   the top right, out of the tick's corner. */
.pg-card { position: relative; }
.pg-tick {
  /* 5px in: the tile's remove and download sit there too. */
  position: absolute; top: calc(var(--s-half) + var(--s-px)); left: calc(var(--s-half) + var(--s-px)); z-index: 2;
  width: 22px; height: 22px; padding: 0; display: grid; place-items: center;
  border: 1.5px solid rgba(255,255,255,.85); border-radius: var(--r-sm);
  background: rgba(0,0,0,.42); color: var(--accent-ink); cursor: pointer;
  transition: background .12s, border-color .12s;
}
.pg-tick:hover { background: rgba(0,0,0,.62); }
.pg-tick.is-on { background: var(--accent); border-color: var(--accent); }
.pg-tick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pg-card.is-picked { border-color: var(--accent); box-shadow: inset 0 0 0 var(--s-px) var(--accent); }
.is-picking > .pg-card, .is-picking > .pg-card * { cursor: pointer; }
.pg-card .media-dl { left: auto; right: calc(var(--s-half) + var(--s-px)); }
.pg-card:has(.media-x) .media-dl { right: calc(var(--s-3) + var(--s-half) + var(--s-px)); }
/* THE BAR FOLLOWS THE GRID: pinned to the window's foot while the grid runs
   past it, resting under the last row at the end. */
.pg-bar {
  position: sticky; bottom: var(--s-2); z-index: var(--z-sticky-panel);
  margin: var(--s-2) 0 0; box-shadow: 0 var(--s-1) var(--s-3) color-mix(in srgb, black 14%, transparent);
}
.pg-all { font-size: 12.5px; }
.pg-said { font-size: 12.5px; color: var(--ink-soft); }
.dt-pop-row.is-some { color: var(--ink); }
.dt-pop-row.is-some::after { content: 'Some'; margin-left: auto; padding-left: var(--s-2); font-size: 11.5px; color: var(--mute); }

/* ── the column picker and the row menus ─────────────────────────────────────
   Both are portalled to <body> and positioned fixed, because the toolbar is an
   overflow-x box and the table scrolls: either would clip a menu of its own. */

.dt-picker, .dt-pop {
  position: fixed; z-index: 60; background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--r-md);
  box-shadow: 0 10px 28px rgb(0 0 0 / .4); padding: var(--s-1);
  min-width: 190px; max-height: 60vh; overflow: auto;
}
.dt-picker-h, .dt-pop-h {
  margin: var(--s-nudge) var(--s-1) var(--s-1); font: var(--t-label); letter-spacing: var(--tr-label); color: var(--mute);
}
.dt-picker-list { display: flex; flex-direction: column; gap: var(--s-px); }
.dt-prow {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-1);
  padding: var(--s-1) var(--s-1); border-radius: 5px; cursor: pointer; color: var(--accent); font-size: 12.5px;
}
.dt-prow:hover { background: var(--shade); }
.dt-prow.is-off { color: var(--mute); opacity: .7; }
.dt-prow.is-pinned { color: var(--accent); cursor: default; opacity: .85; }
.dt-prow.is-pinned:hover { background: none; }
.dt-prow-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-picker-foot {
  display: flex; align-items: center; gap: var(--s-1h); margin-top: var(--s-1); padding-top: var(--s-1);
  border-top: 1px solid var(--rule-faint);
}
.dt-picker-foot .linkbtn { font-size: 12px; }

.dt-pop-row {
  display: block; width: 100%; text-align: left; padding: var(--s-1) var(--s-1); border: 0;
  border-radius: 5px; background: none; color: var(--ink-soft); cursor: pointer; font-size: 12.5px;
}
.dt-pop-row:hover { background: var(--shade); color: var(--ink); }
.dt-pop-row.is-on { color: var(--accent); }
/* The one row in a menu that destroys something. Quiet until it is pointed at,
   like every other alarm in this product — a red row sitting in a menu is a red
   row somebody stops reading. */
.dt-pop-row.is-danger { color: var(--alarm); }
.dt-pop-row.is-danger:hover { color: var(--alarm); background: color-mix(in srgb, var(--alarm) 12%, transparent); }
.dt-pop-note { margin: 0 var(--s-1) var(--s-1); font-size: 11.5px; color: var(--mute); line-height: 1.5; }
/* A menu with nothing else in it: the note sits in the middle, level with where a row would. */
.dt-pop-note:only-child { margin: 0; padding: var(--s-1); text-align: center; }
.dt-pop-go { display: flex; gap: var(--s-1); padding: 0 var(--s-nudge); }
.dt-pop-field { margin-top: var(--s-1); }

/* The two view buttons are now glyphs. SPEC-UI-V1 forbids an icon IN FRONT OF a
   label; this control has no label to sit in front of, and both buttons still
   carry a title and an aria-label. */
/* The two view buttons get the same air. A segmented pair whose glyphs touch
   their own edges reads as one smudged control. */
.seg.is-icon { padding: var(--s-1) var(--s-1h); display: flex; align-items: center; }

@media (max-width: 760px) {
  .dt-scroll { max-height: none; }
  .dt-toolbar { gap: var(--s-1); }
}

/* ── smart pills and the toolbar search ──────────────────────────────────────
   A pill's LABEL IS ITS STATE: "Campaign" when it is off, "Fall renewal 2026"
   when it is on. A strip of them says what you are looking at without any of
   them being opened, which a row of dropdowns each naming its own category
   cannot do in the same room. */

.pill {
  display: inline-flex; align-items: center; gap: var(--s-1); flex: none;
  white-space: nowrap; height: 26px; padding: 0 var(--s-1);
}
.pill.is-on { background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: var(--accent); color: var(--accent); }
.pill-chev {
  width: 0; height: 0; margin-left: var(--s-px);
  border-left: 3.5px solid transparent; border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor; opacity: .55;
}
.dt-clearsort { flex: none; font-size: 12px; }
.dt-pop-sep { display: block; height: 1px; margin: var(--s-1) var(--s-nudge); background: var(--rule-faint); }

/* Typed text goes accent-coloured, so a query reads as an active filter rather
   than as a label somebody left sitting in a box. */
.dt-search {
  display: inline-flex; align-items: center; gap: var(--s-1); flex: none;
  height: var(--h-md); padding: 0 var(--s-half) 0 var(--s-1); border: 1px solid var(--rule);
  border-radius: var(--r-pill); background: var(--paper); min-width: 132px;
}
/* The glyph that replaced the placeholder. It takes the accent with the box
   when there is a query, so the whole control reads as active at a glance. */
.dt-search-ic { flex: none; display: flex; align-items: center; color: var(--mute); }
.dt-search.is-on .dt-search-ic { color: var(--accent); }
.dt-search:focus-within { border-color: var(--ink-soft); }

/* --- folding a card ------------------------------------------------------- */
/* The chevron lives at the right-hand end of whatever strip a card has, which is
   also its drag handle — so every card that can be moved can be folded. */
.card-fold {
  flex: none; margin-left: auto; width: 22px; height: 22px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-sm); background: none; color: var(--mute);
  cursor: pointer; touch-action: auto;
  transition: transform 160ms ease, color 120ms ease;
}
.card-fold:hover { color: var(--ink); background: var(--shade); }
/* Pointing at what it will reveal. */
.is-shut .card-fold { transform: rotate(-90deg); }
/* FOLDED IS A HEIGHT, NOT A LIST OF THINGS TO HIDE.
   This was a pile of `display: none` selectors naming every part of every kind
   of card — and it hid the wrong thing. A table card's strip is not a child of
   the card: it sits inside the table, inside the table's own box, so
   `> :not(.dt-toolbar)` hid the box the toolbar lives in and the card collapsed
   to an empty bar with no name, no count and no way back open. That is the
   garbage in Franz's screenshot.
   The card is clipped to the height of its strip instead, measured in JS when
   it folds. Nothing needs to know what a card is made of; whatever sits at the
   top stays visible, and everything under it is simply outside the box. */
.pf-field.is-shut, .dossier-card.is-shut, .media.is-shut, .dump.is-shut {
  overflow: hidden;
  /* A fixed-height card — the dossier and the media box are both 486 — has to
     be told it may be shorter than that, and the inline height set when it
     folds is what decides how much shorter. */
  height: auto;
  min-height: 0 !important;
  /* NOT A FLEX COLUMN WHILE IT IS FOLDED. The dossier and the media box lay
     their contents out with flex so the grid inside can take the slack — and a
     flex column asked to be 71px tall does not clip its children, it SQUEEZES
     them. The strip was crushed to half its height with the table's header
     showing underneath, which is not a folded card, it is a broken one.
     As a block, the strip keeps its size and everything after it is simply
     below the bottom edge. */
  display: block;
}
/* EVERY FOLDED CARD IS ONE HEIGHT (2026-10-05). Franz: "i want all collapsed to
   be same height as the ones with search bars in them". The tables folded to
   54px on 13px of padding; Brand Kit, Contact Details, and Campaigns kept their
   own 16px and Brand Voice 15px, so a column of shut cards went 62, 62, 60, 54.
   One padding for all of them, the table's, which also lines up every title and
   every chevron on the same left and right edges. */
.profile-form .pf-field.is-shut { padding: var(--s-1h) var(--s-2) !important; }
.profile-form .pf-field.is-shut .pf-head { margin: 0; height: var(--h-md); min-height: 0; display: flex; align-items: center; }
/* The label's own 5px under it lifted these titles 2px above the tables'. */
.profile-form .pf-field.is-shut .pf-head > label { margin: 0; }
/* AND THE SETTINGS CARDS TOO (2026-10-06). Franz: "the default collapsed view
   has the same height and uniformity as the profile cards". Links folded to
   51, Context Docs to 62 on its own 16px padding, and the Media Library to 65
   because its Upload button made the strip 37 tall. Every folded card on a
   board is now exactly 54: the same padding, a 26px strip with its contents
   centred in it, and the box held to that height whatever sits in the strip. */
.profile-form .dossier-card.is-shut, .profile-form .media.is-shut,
.profile-form .dump.is-shut { padding: var(--s-1h) var(--s-2) !important; }
.profile-form .is-shut .dt-toolbar, .profile-form .is-shut .dossier-head,
.profile-form .is-shut .dump-h-row {
  height: var(--h-md); min-height: 0; margin: 0 !important;
  padding-top: 0 !important; padding-bottom: 0 !important;
  display: flex; align-items: center;
}
.profile-form .pf-field.is-shut, .profile-form .dossier-card.is-shut,
.profile-form .media.is-shut, .profile-form .dump.is-shut {
  /* 12 + a 32px strip (the toolbar's height, SPEC-LAYOUT-V1 §2c) + 12 + the border. */
  box-sizing: border-box; height: calc(var(--h-md) + 2 * var(--s-1h) + 2px) !important; max-height: calc(var(--h-md) + 2 * var(--s-1h) + 2px);
}

/* --- what a document still wants ------------------------------------------
   Counting headings we asked for, so it is free. The number is the state of the
   document; the name beside it is the one thing worth going to get. */
.dpack-parts { display: flex; align-items: baseline; gap: var(--s-1); min-width: 0; }
.dpack-parts-n { font-size: 10.5px; color: var(--mute); font-variant-numeric: tabular-nums; flex: none; }
.dpack-parts-n.is-done { color: var(--accent); }
.dpack-parts-gap {
  font-size: 10.5px; color: var(--ink-soft); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --- Add New: the seven, then the paste box -------------------------------- */
.addnew { display: flex; flex-direction: column; gap: var(--s-2); }
.addnew-next {
  margin: 0; padding: var(--s-1) var(--s-1h); border-radius: var(--r-md);
  border: 1px solid var(--rule); background: var(--shade);
  font-size: 13px; color: var(--ink-soft);
  display: flex; align-items: baseline; gap: var(--s-1); flex-wrap: wrap;
}
.addnew-next-k { font-weight: 600; color: var(--accent); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; flex: none; }
.addnew-list { display: flex; flex-direction: column; }
.addnew-row {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-1h) var(--s-nudge); border-top: 1px solid var(--rule);
}
.addnew-row:last-child { border-bottom: 1px solid var(--rule); }
.addnew-what { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--s-nudge); }
.addnew-name { font: 600 13.5px/1.3 var(--font-display); color: var(--ink); }
.addnew-says { font-size: 11.5px; line-height: 1.4; color: var(--mute); }
.addnew-state { flex: none; font-size: 11.5px; color: var(--mute); text-align: right; }
.addnew-state.is-have { color: var(--ink-soft); }
.addnew-go { flex: none; display: flex; align-items: center; gap: var(--s-1); }
.addnew-dump > summary { cursor: pointer; color: var(--mute); font-size: 12.5px; padding: var(--s-half) 0; }
.addnew-dump > summary:hover { color: var(--ink-soft); }
.addnew-dump .dump { border: 0; padding: 0; background: none; }
.addnew-dump .hint { margin: var(--s-1) 0 var(--s-1h); }
@media (max-width: 620px) {
  .addnew-row { flex-wrap: wrap; }
  .addnew-state { text-align: left; }
}

/* No landing outline (2026-10-17): the dragged card is the only card in the air. */

/* A folded card is not a target for a stray click on what it is hiding. */
.is-shut .dt-scroll, .is-shut .dpack-grid, .is-shut .media-grid { pointer-events: none; }
/* AND THE WHOLE BAR IS THE WAY BACK IN, so it says so — including over the
   search box, which keeps a text caret for a list that is not there otherwise.
   The strip is still a drag handle; a folded card is just a thing you open far
   more often than you move. */
.profile-form .is-shut .dt-toolbar, .profile-form .is-shut .pf-head,
.profile-form .is-shut .dump-h, .profile-form .is-shut .dossier-head,
.profile-form .is-shut .dt-toolbar *, .profile-form .is-shut .pf-head *,
.profile-form .is-shut .dump-h *, .profile-form .is-shut .dossier-head * { cursor: pointer; }

.dump-h-row { display: flex; align-items: center; gap: var(--s-1); }
.dump-h-row .dump-h { flex: 1 1 auto; margin: 0; }
/* A search that is the only thing on its row can have the room. */
.dt-search.is-wide { height: var(--h-md); min-width: 260px; }
/* On a phone a card's strip is narrower than that box: it takes what is left. */
@media (max-width: 639px) { .dt-search.is-wide { min-width: 0; } }
.dt-search.is-on { border-color: var(--accent); }
.dt-search-in {
  border: 0; background: none; color: var(--ink); font: 400 12px/1 var(--font-body);
  width: 100%; min-width: 0; padding: 0; outline: none;
}
.dt-search.is-on .dt-search-in { color: var(--accent); }
.dt-search-in::-webkit-search-cancel-button { display: none; }
.dt-search-x {
  width: 18px; height: 18px; border: 0; border-radius: 50%; background: none;
  color: var(--mute); cursor: pointer; display: flex; align-items: center;
  justify-content: center; padding: 0; flex: none;
}
.dt-search-x:hover { color: var(--ink); }

/* The toolbar never wraps. Wrapping grows a second band and pushes the work
   down the screen, which is the whole thing this row replaced. */
.dt-toolbar { flex-wrap: nowrap; }
.dt-toolbar .seg-group { flex: none; }

/* ── the way back ────────────────────────────────────────────────────────────
   At the LEFT of the page margin, on the heading's own line. It used to be a
   text link on a row of its own above the title — a whole line of vertical
   space on four screens, spent on two words. The words are still there, on the
   title and the aria-label, which is what keeps an arrow from being a riddle. */

.page-back {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  border: 1px solid var(--rule); background: none; color: var(--mute);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0; margin: 0 var(--s-half) 0 0;
}
.page-back:hover { border-color: var(--accent); color: var(--accent); }

/* Inside a heading it has to sit on the text's optical centre, not its
   baseline — .page-head aligns on baseline and a round button on a baseline
   hangs below the letters. */
.page-head { align-items: center; }

.page-head h1, .client-head h1 { display: flex; align-items: center; gap: var(--s-1h); }
.client-head h1 .page-back { margin-right: 0; }

/* The report has no heading row, so its back button sits at the top of the
   left column and keeps its own line there. */
.report-aside > .page-back { margin: 0 0 var(--s-2); align-self: flex-start; }



/* ── the smart bar ───────────────────────────────────────────────────────────
   Every option visible, one of them on. A menu is for a list that GROWS —
   clients, campaigns, states; a bar is for a fixed four that will always be
   four, where hiding them behind a click costs a click and a guess.

   LESS ROUNDED THAN THE PILLS, and that is not decoration: a pill is a thing
   you open and a bar is a thing you are already looking at, and the corner
   radius says which before either is read. */

.bar {
  display: inline-flex; flex: none; align-items: stretch;
  border: 1px solid var(--rule); border-radius: var(--r-sm); overflow: hidden;
  background: var(--card);
}
.bar-seg {
  border: 0; background: none; cursor: pointer; white-space: nowrap;
  padding: 0 var(--s-1h); height: 26px; font: 400 12px/1 var(--font-body); color: var(--mute);
}
.bar-seg + .bar-seg { border-left: 1px solid var(--rule); }
.bar-seg:hover { color: var(--ink); background: var(--shade); }
.bar-seg.is-on {
  background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent);
}

/* ── small data, centred ─────────────────────────────────────────────────────
   A score, a length, a state, a count and a date are each ONE short fact. Ragged
   against a left edge they read as a column of debris; centred in their own
   column they read as a column. Title, Client, Campaign and Tags stay left,
   because those are text somebody reads rather than a value they compare. */

.dt-h-cell.is-center, .dt-cell.is-center { justify-content: center; }
.dt-h-cell.is-center .dt-sort { justify-content: center; }

/* THE STATE PILL WAS TOO TALL. It is a label inside a 38px row, not a button:
   at 24px it was two thirds the height of the row it sat in and read as
   something to press. Shorter, tighter, and centred in its column. */
.dt-cell .state-chip, .dt-cell .notes-chip, .dt-cell .chip {
  height: 19px; padding: 0 var(--s-1); font-size: 11px; line-height: 19px;
  display: inline-flex; align-items: center; border-radius: 20px;
}
/* The stage is set from this pill now, so it is a control and says so. */
.dt-cell .state-chip { cursor: pointer; }
.dt-cell .dt-pill:hover { filter: brightness(1.08); }

/* ── the client header ───────────────────────────────────────────────────────
   The name on the left, Settings on the right, and the three numbers on the
   row under them — the row the workspace line used to take. No line is spent
   and the weight is the right way round: a number about the work is worth a
   row, where the folder lives is not. */

.client-head { align-items: center; }
.client-set { display: inline-flex; align-items: center; gap: var(--s-1); margin-left: auto; flex: none; }
.client-stats { margin: 0 0 var(--s-2); margin-left: 0; }

/* Inside the settings menu the workspace line is a sentence and a select,
   stacked, not a row stretched across a page. */
/* ONE NEAT LINE, INSIDE THE MENU TOO. Franz, 2026-09-24: "improve the move
   the thing try ot make in one neat line".
   This rule is why it was two: `.dt-pop .client-ws` is more specific than
   `.client-ws.is-line`, so it stacked the label over a full-width select and
   quietly won the argument. It is a row now, with the label taking the slack
   and the select a fixed width, so a long workspace name cannot push it. */
.dt-pop .client-ws { display: flex; align-items: center; gap: var(--s-1); padding: 0 var(--s-1) var(--s-nudge); }
.dt-pop .client-ws-name {
  flex: 1; min-width: 0; font-size: 12.5px; color: var(--ink-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dt-pop .client-ws-pick { flex: none; width: 116px; font-size: 12.5px; padding: var(--s-half) var(--s-1); }
/* The message only appears after a move, so it takes its own line when it does
   rather than squeezing the row that is usually alone. */
.dt-pop .client-ws { flex-wrap: wrap; }
.dt-pop .client-ws .hint { flex-basis: 100%; }
.dt-pop .client-ws .hint { margin: 0; font-size: 11.5px; }
.dt-pop { min-width: 230px; }

/* ── the placement picker ────────────────────────────────────────────────────
   Two rows of pills, the platform and then the ad type under it, reading as one
   control. The second row hides itself when a platform has one format — a row
   of one is not a choice. Ported from the panel, where this has always been the
   shape of the form. */

.placement { display: flex; flex-direction: column; gap: var(--s-1); }
.placement-row { margin: 0; gap: var(--s-1); }
.placement-row.is-sub { gap: var(--s-half); }
.placement-row.is-sub .chip { font-size: 12px; padding: var(--s-half) var(--s-1); }
.placement-note { margin: var(--s-nudge) 0 0; font-size: 11.5px; }


/* ── the client's four tabs ──────────────────────────────────────────────────
   "big and bold and distinctive". These are the four things a client page does
   and the two making ones come first, so they are not a row of quiet links
   under a heading — they are the navigation of the page you are on. */

.client-body { margin-top: 0; }
/* TIGHTER UNDER THE TABS. Franz, 2026-09-15: he did not like the spacing
   "especially cards and column section from the toolbar and even toolbar from
   line above". Three gaps were each set generously on their own and stacked
   into a hole: 26px under the tabs, 20px above the body, then the toolbar's own
   padding. They are one rhythm now, not three decisions. */
/* Tighter under the tabs too — the header, the tabs and the first card were
   three separate gaps stacked on a page that is nothing but cards. */
/* AND THE SAME GAP AS BETWEEN THE CARDS. Franz, 2026-09-16: "the top padding
   from the 3 tab line needs to have same spacing as between the cards
   themselves". Twelve was a number picked to close a hole; the page below is a
   board with a twenty-pixel gutter, so the line above it is one more gutter and
   not a decision of its own. */
/* TIGHTER UNDER THE TABS. Franz, 2026-09-24: "we gotta reduce the up and down
   spacing smartly and uniformly from the video script satic ad creative only
   bar mate to maket eh whole page look neat".
   Measured before touching it: 33px above that bar and 34px below, so it was
   already even — just twice what it needed. 20px of it was this margin. */
.subtabs { gap: var(--s-4); border-bottom: 1px solid var(--rule); margin-bottom: var(--s-1); }
.subtab { padding: var(--s-1) 0; }
/* SET LIKE SUB-TABS, WHICH IS WHAT THEY ARE. Franz, 2026-09-16: "the tabs
   context create and work i think are a bit too big compared to the title on
   top ... they are sub tabs". At 18px display they were within a few pixels of
   the client's own name above them, so the page had two headings and no
   hierarchy. 16px, and the underline thins with them. */
.subtab {
  background: none; border: 0; cursor: pointer; padding: var(--s-1) 0 var(--s-1h);
  font: 600 16px/1 var(--font-display); letter-spacing: -.012em;
  color: var(--mute); border-bottom: 2px solid transparent; margin-bottom: calc(-1 * var(--s-px));
}
.subtab:hover { color: var(--ink-soft); }
.subtab[aria-current="page"] { color: var(--ink); border-bottom-color: var(--accent); }
/* THE CLIENT'S NAME AND TABS STAY IN SIGHT (Franz, 2026-10-31: "each page
   needs a sticky scroll showing the tabs"; 2026-11-01: "i need this whole
   thing … not the main header ever disappearing"). The name row and the tabs
   are one block, pinned under the app bar on solid paper; the app bar never
   moves. The block carries the page's top padding inside it, so pinned, the
   name sits the same 16px under the bar it does at rest.
   UNDER IT, --header-h IS THE APP BAR AND THE BLOCK: every sticky thing on
   the page (the toolbar, the brief's head, a rail, a scroll target) rests on
   --header-h or --stick-top, so it stacks under the block without being
   told. --tabs-h is the block's height, measured by client.js (pinTabs). */
.client-top {
  position: sticky; top: var(--app-h); z-index: var(--z-header);
  margin: calc(var(--pad-page-top) * -1) 0 var(--s-1); padding-top: var(--pad-page-top);
  background: var(--paper);
}
.client-top > .subtabs { margin-bottom: 0; }
.client-body {
  --header-h: calc(var(--app-h) + var(--tabs-h, 120px));
  --stick-top: calc(var(--header-h) + var(--s-2));
}
/* A PHONE SCROLLS THE TABS, NOT THE PAGE (2026-10-26). Six client tabs run
   462px at 390, which pushed the whole page sideways. The row scrolls in itself,
   as the brief page's does (screen-portal.css .bf-head .subtabs); the underline
   sits inside it, so nothing is clipped. Wider screens are untouched. */
@media (max-width: 639px) {
  .subtabs { overflow-x: auto; scrollbar-width: none; gap: var(--s-3); }
  .subtabs::-webkit-scrollbar { display: none; }
  .subtabs > .subtab { flex: none; white-space: nowrap; margin-bottom: 0; }
}

/* ── a create tab, at full width ─────────────────────────────────────────────
   The same form the sheet mounts, given the room the modal never had. The two
   decisions made before anything is typed — what the run is given, and how it
   should sound — sit half and half above 1100px; the questions run in one
   column under them. */

.create-page { max-width: 1280px; }
.create-lede { margin: 0 0 var(--s-3); font-size: 13.5px; color: var(--ink-soft); max-width: 70ch; }
.sheet-form.is-page { display: block; }
.create-main { min-width: 0; }
/* THE TOP ROW: the ad, and the material it may read. SIXTY / FORTY. Franz,
   2026-09-16: "instead of 50:50 between the two columns lets do 60 for the left
   and 40 for the right very carefully mate". The left card asks eleven
   questions in halves; the right one holds a strip of documents, two upload
   buttons and a checklist. Equal columns gave the questions the same room as
   the reference, which is why the left column's pairs kept running to two
   lines while the right had space going spare. */
.create-top { display: grid; gap: var(--s-3); align-items: stretch; }
.create-tune { min-width: 0; }
.create-tune .field:last-child { margin-bottom: var(--s-3); }

@media (min-width: 1100px) {
  .create-top { grid-template-columns: minmax(0, 6fr) minmax(0, 4fr); }
  .create-page { max-width: none; }
}

/* --- the Create page, in cards --------------------------------------------- */
/* THE SAME BOXES THE CONTEXT PAGE USES. Franz, 2026-09-16: "box it and organize
   really well look at the context tab mate how its style is and apply as much
   as you can". A card carries `.pf-field` as well as `.c-card`, so the surface,
   the border, the radius and the title rank are the Context page's own rules
   and not a second set that can drift away from them. */
.c-card { margin: 0 0 var(--gap-card); padding: var(--pad-card); }
/* EQUAL HALVES, EQUAL HEIGHTS, the rule the Context page's pairs follow: two
   cards that hug their own contents read as two unrelated boxes. */
.create-top > .c-card { margin: 0; display: flex; flex-direction: column; }
.c-card > .pf-head { margin-bottom: var(--s-1h); }
.c-card-lede { margin: calc(-1 * var(--s-half)) 0 var(--s-2); max-width: var(--w-read); }
/* EVERY ROW IN A CARD ENDS THE SAME DISTANCE FROM THE NEXT. Franz, 2026-09-16:
   "this spacing up and down needs to be uniform the top two new ones have
   disrupted it a bit". The run context's own pair carries 14px of its own,
   which is right inside the Setup card and wrong here, where every other row
   is a `.field` ending at 20px. */
.c-card > .ctx-pair { margin: 0; }
.c-card > .ctx-pair > .field { margin: 0 0 var(--s-3); }

/* The switch sits in from the card's edge like every field above it. */
.c-card > .switch { margin-top: var(--s-2); }
/* A field that heads its own enclosure is set like the card's title, not like
   the one-line labels above it. */
/* A section's heading row: its name, and the one control that undoes it. */
/* THE HEADING ROW ENDS WHERE THE CONTROL UNDER IT ENDS. Franz, 2026-09-16:
   "the clear button in the flow section is slightly otuside the margin margin
   on the right". The Flow's panel is an inset box with 13px of its own padding,
   so its pills and sliders stop 13px short of the card's content edge while
   Clear, sitting in a plain heading row, ran all the way out to it. It looked
   like the one thing on the card that had escaped the margin, because it was. */
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1); }
.c-card > .field.is-section > .section-head { padding-right: var(--s-1h); }
.blend-clear {
  border: 0; background: none; padding: 0; cursor: pointer;
  font-size: 12.5px; color: var(--mute);
}
.blend-clear:hover { color: var(--ink); }

.c-card > .field.is-section > .section-head > .label,
.c-card > .field.is-section > .label {
  font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink);
}
.c-card > .field.is-section > .section-head { margin-bottom: var(--s-1); }
/* The audience picker is a control in the field, not a control beside its name:
   full width, on its own line, with the box it fills under it. */
/* THE SAME GAP AS EVERY OTHER LABEL. Franz, 2026-09-16: "the dropdown spacing
   from the audience label should be same like others". `.pf-field .pick-suggest`
   adds 10px of its own for the profile cards, which stacked on top of the
   label's own 5px and left this one control sitting lower than the rest. */
/* THE AUDIENCE FIELD SITS IN THE SAME RHYTHM AS ITS NEIGHBOURS. Franz,
   2026-09-16: "up and down spacing of left column fields isa bit off betweeen
   audience and tone". It carried 8px of its own under the control, on top of
   the 18px every field in this form already has, so the one row with a picker
   in it sat lower than the rows above and below. The chips carry their own gap
   from the button, which is the space that was actually wanted. */
.c-card .who-pick { display: block; margin: 0; }
.c-card .who-pick .pick, .c-card .who-pick .pick-suggest {
  width: 100%; max-width: none; margin: 0; font-size: 13px; padding: var(--s-1) var(--s-1);
}

/* THE AUDIENCE BUTTON. It is a button because the menu behind it ticks several
   rows and a <select> cannot, but it is the product's own field to look at:
   same box, same chevron, same size as the single-choice menus beside it. */
/* A CONTROL SHOWING ITS PLACEHOLDER IS SHOWING A HINT. Same box, same height,
   same one line — the type is the hint's italic and the size comes down far
   enough that the whole sentence fits without clipping or wrapping. Nothing is
   hidden: an ellipsis here would cut off the half that says what it decides.
   The option inside the open menu matches, so the two never disagree. */
.pick.is-placeholder, .c-card .pick.is-placeholder,
.pick option[value=""], .c-card .pick option[value=""] {
  text-transform: none; font-style: italic; color: var(--mute);
}
/* THE BOX DOES NOT CHANGE SIZE WITH IT. Smaller type in the same control made
   the control itself four pixels shorter than the menu beside it, so the line
   height is pinned to what the normal size gives: 38px box, 20px of content. */
.pick.is-placeholder { font-size: 10.5px; line-height: 20px; white-space: nowrap; }
/* THE HINT IS THE CLOSED CONTROL, NOT THE MENU. The select carries the italic
   and the small size so its own line reads as a hint; without this the whole
   open list inherited both and five real answers were set in italic 10.5px. */
.pick.is-placeholder option { font-style: normal; font-size: 13px; color: var(--ink-soft); }
.whobtn, .c-card .whobtn {
  /* The label is either an instruction or a whole audience sentence, and the
     card's capitalize rule made a headline of both. */
  text-transform: none;
}
.whobtn {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: var(--s-1) var(--s-4) var(--s-1) var(--s-1); font: 400 13px/1.5 var(--font-body);
  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='%238A918A' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 13px center; background-size: 12px 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .whobtn {
    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='%23767C77' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  }
}
/* The same, picked by hand with the day/night switch. */
:root[data-theme="light"] .whobtn {
  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='%23767C77' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
/* A ROW YOU TICK, NOT A ROW THAT TURNS GREEN. Franz, 2026-09-16: "i hate the
   ui/ux of multi select options while picking i need to see things hgighlight
   or ticked in box in the audience dropdown box nciely on brand". A real box on
   the left, the accent inside it when it is on, and the row's own background
   lifting with it — three signals for one state, none of them a colour change
   somebody has to learn. */
.dt-pop-row.is-tickrow {
  display: flex; align-items: flex-start; gap: var(--s-1); text-align: left;
  padding: var(--s-1) var(--s-1); white-space: normal;
}
.tickbox {
  flex: none; width: 15px; height: 15px; margin-top: var(--s-px);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--rule); border-radius: 4px; color: var(--accent-ink);
}
.dt-pop-row.is-tickrow:hover .tickbox { border-color: var(--ink-soft); }
.dt-pop-row.is-tickrow.is-on { background: var(--shade); color: var(--ink); }
.dt-pop-row.is-tickrow.is-on .tickbox { background: var(--accent); border-color: var(--accent); }
.tickrow-body { display: flex; flex-direction: column; gap: var(--s-nudge); min-width: 0; }
.tickrow-name { font-size: 13px; color: var(--ink); }
/* THE SENTENCE, CLAMPED TO TWO LINES. It is the reason the name means anything,
   so it is on screen — but a menu of twelve paragraphs is not a menu. */
.tickrow-say {
  font-size: 11.5px; color: var(--mute); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.dt-pop-row.is-tickrow.is-on .tickrow-say { color: var(--ink-soft); }

/* THE BOX THAT REPLACES THE MENU. Same height, same face, same slot — Franz,
   2026-09-16: "can the write your own convert the dropdown box tiself into a
   text box". Nothing on the page moves while it is open. */
.who-slot { display: block; }
.who-write { width: 100%; font-size: 13px; padding: var(--s-1) var(--s-1); }
.hookpick-slot .hookpick-own { width: 100%; margin: 0; }

/* THE NAME COLUMN in a named list, and what an unnamed row says instead. */
.scoped-name { font-size: 13px; }
.scoped-name.is-unnamed { color: var(--mute); font-style: italic; }
.scoped-addbar.is-named .scoped-newname { flex: 0 1 190px; }

/* WHAT IS TICKED, UNDER THE BUTTON. A count is not an answer anybody can check,
   so each one is named and each one can be taken off where it is read. */
.who-chips { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: var(--s-1) 0 0; }
.who-chip, .c-card .who-chip {
  display: inline-flex; align-items: center; gap: var(--s-1); max-width: 100%;
  /* An audience is a sentence somebody wrote, not a label. The card's
     capitalize rule turned "Kansas City homeowners whose lawns are brown" into
     a headline of initial capitals. */
  text-transform: none;
}
.who-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-chip-x { color: var(--mute); font-size: 14px; line-height: 1; }
.who-chip:hover .who-chip-x { color: var(--ink); }

/* NOTHING ON THIS PAGE LIGHTS UP WHEN IT IS USED. Franz, 2026-09-16: "i dont
   want border to highlight on use anywhere same like current live context". The
   card's focus-within border and the browser's own focus ring on a chip both
   drew a bright outline around whatever had just been clicked; the accent on
   the control itself already says which one is live. Keyboard focus is still
   shown — by :focus-visible, which a mouse click does not trigger. */
.c-card:focus-within { border-color: var(--rule); }
.c-card .chip:focus:not(:focus-visible),
.c-card .btn:focus:not(:focus-visible),
.c-card .pick:focus:not(:focus-visible),
.c-card .spoke-range:focus:not(:focus-visible) { outline: none; box-shadow: none; }
/* The same for a pill or a box being USED rather than focused: no lit border
   while the pointer is down, and no accent border on a select somebody has just
   opened. What is chosen is shown by the chosen state, not by the last thing
   touched. */
.c-card .chip:active, .c-card .btn:active { border-color: var(--rule); }
/* AND A TEXT BOX DOES NOT LIGHT UP EITHER. Franz, 2026-09-16: "i dont wan
   tthese boxes getting highlighted during select mate". `:not(:focus-visible)`
   let this through, because a text field always counts as focus-visible — the
   browser assumes you might be typing blind. In a box you clicked into, the
   caret already says where you are; a second signal drawn around the whole
   field is the form shouting about something you just did on purpose. */
.c-card .input:focus, .c-card .textarea:focus { border-color: var(--rule); }
.c-card .pick:focus { border-color: var(--rule); }

/* WHAT CANNOT BE LEFT BLANK. One mark, the colour of the accent, on the one
   field the Worker will refuse without — and a line under the button saying so,
   because a symbol nobody has been taught is decoration. */
/* The mark is red and it is the whole explanation — Franz, 2026-09-16: "just
   red mark and things to fill popping up in right column as whats left". */
.req { color: var(--alarm); margin-left: var(--s-half); }

/* AN EMPTY BOX IS ONE LINE TALL. Franz, 2026-09-16: "some of the default help
   text in fields arent vertically centred mate and this is not a good look".
   A textarea cannot centre its own text, so the fix is the box: `field-sizing:
   content` already grows it as somebody types, and the 2.6em floor was the only
   thing leaving a placeholder stranded at the top of empty space. */
.c-card .textarea.grow { min-height: 0; }
/* THE TEXT SITS IN THE MIDDLE OF ITS BOX. Franz, 2026-09-16: "the text in the
   field doesnt look centred aligned still". A textarea's text starts at the top
   of its content box and stays there however tall the box is; `align-content`
   is what finally lets one centre its own lines, and it holds when the text
   grows to two. */
.c-card .textarea { align-content: center; }

/* HOW MANY VERSIONS, and what each one costs. The number sits with the button
   that spends it, not up in the form: it is the last decision before writing. */
/* THE BUTTON HAS ROOM. It sat 4px under the last card, which read as part of
   the card rather than as the end of the form. */
.sheet-form.is-page .sheet-go {
  display: flex; flex-direction: column; gap: var(--s-1h); align-items: flex-start;
  margin: var(--s-3) 0 var(--s-4);
}
.sheet-go-row { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.sheet-form.is-page .sheet-go .btn { padding: var(--s-1h) var(--s-3); }
/* The right column is a stack: what the run is given, then what it is still
   missing. */
.create-side { min-width: 0; display: flex; flex-direction: column; }
.create-side > .c-card:last-child { margin-bottom: 0; }

/* THE VERSION BAR, ON THE TAB ROW. The menu asks the question once; the pills
   only appear once the answer is more than one, and they are how you move
   between versions. */
/* ONE RHYTHM ACROSS THE ROW. Franz, 2026-09-16: "all elements in this tool bar
   need better in between spacing ... and mainly the text that comes up". The
   two kind pills sat 8px apart, the versions menu 16px from them and the note
   4px from that — four different gaps in one line. Everything is 10px apart
   now, with one wider break where the row changes subject: what you are making,
   then how many of it. */
.kinds-bar { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; margin: var(--s-2) 0 var(--s-3); }
.kinds-bar .create-kinds { margin: 0; gap: var(--s-1); }
/* ONE GAP ACROSS THE WHOLE BAR. Franz, 2026-09-16: "spacing aint proepr". The
   versions block carried a margin of its own on top of the bar's gap, so the
   one join in the row that should have looked like the others was 22px while
   everything around it was 10. */
.kinds-versions { display: flex; align-items: center; }

/* SAVE, AT THE FAR END. Franz, 2026-09-16: "in the right of the name bar
   extemem right". It is the only thing on the row that acts rather than sets,
   so the gap between it and the settings is the point. */
/* THE TEMPLATE, IN THE TOOL BAR, AS NARROW AS ITS OWN ANSWER. "NLC" in a
   220px control was most of a bar saying one word. */
/* KEEPING A LINE, FROM THE LINE ITSELF. Quiet next to the field's name until
   the scene is hovered, like every other verb on a scene, and it never lights
   up red: it adds, it does not remove. */
/* SAVE FOR LATER, ALWAYS VISIBLE. It was hover-only text beside a field label —
   the shelf that makes the next script better, and the only way to find it was
   to sweep the card with a pointer. */
.sf-keep.chip {
  /* No margin: the label row's own var(--s-1) gap is the space (it was 16, twice). */
  margin-left: 0; padding: var(--s-half) var(--s-1); font-size: 10.5px; color: var(--mute);
  background: none; flex: none;
}
.sf-keep.chip:hover { color: var(--accent); border-color: var(--accent); }
.sf-keep.chip:disabled { opacity: .4; cursor: not-allowed; }

/* The CTA picker is the hook picker's twin and wears the same clothes. */
.ctapick .pick-hook { width: 100%; min-width: 0; }

/* THE MAIN BRIEF IS A BOX PEOPLE PASTE INTO. Deep enough to show what landed
   without scrolling it out of sight, and it still grows from there. */
/* `.c-card .textarea.grow` zeroes the minimum for every box on this form, which
   is right for the one-line ones and wrong for the one people paste into, so
   this says its own minimum after it and with the same weight. Field-sizing
   still grows it from there. */
.c-card .textarea.grow.brief-main, .brief-main {
  min-height: 130px; max-height: 26em; resize: vertical;
  /* Pasted text starts at the top of the box, not floating in the middle of it. */
  align-content: start;
}
/* THE LIMIT, IN THE CORNER OF THE BOX IT BELONGS TO. Under the box it was a
   line of its own, which made the two rows that have a count taller than the
   rows that do not — the uniform spacing undone by the thing meant to be
   unobtrusive. Inside, it costs no height and no width: the field keeps a
   little more room at the bottom so the last line of text never reaches it. */
.counted { position: relative; display: block; }
.counted > .textarea { padding-bottom: var(--s-3); }
.is-count {
  position: absolute; right: var(--s-1h); bottom: 6px; pointer-events: none;
  font: 400 11px/1 var(--font-body); color: var(--mute);
  font-variant-numeric: tabular-nums;
}
.is-count.is-near { color: var(--ink-soft); }
.is-count.is-full { color: var(--alarm); }
/* The main brief carries a resize grip in that corner, so its count steps aside
   for it rather than sitting under it. */
.counted > .brief-main ~ .is-count { right: 22px; }

/* A HOOK AND A CTA ARE SENTENCES. The card title-cases its controls, which is
   right for "Voiceover + B-Roll" and wrong for "Pick a CTA" or for a line
   somebody wrote. */
.c-card .pick-hook { text-transform: none; }

.kinds-tpl-slot, .kinds-tpl { display: flex; align-items: center; min-width: 0; }
/* A SIZE DOWN FROM THE KIND PILLS. Franz, 2026-09-16: "template naming and
   version pills should be slightly smaller than the main video and add pills
   mate". Video Script and Static Ad are the choice the whole page hangs off;
   the template and the version count are settings on it, and a row where
   everything is the same size says they are all the same decision. */
.kinds-tpl .pick-tpl {
  width: auto; min-width: 0; border-radius: var(--r-pill);
  padding: var(--s-half) var(--s-3) var(--s-half) var(--s-1h); font: 500 12px/1.4 var(--font-body); color: var(--mute);
}
.kinds-tpl .pick-tpl:hover { color: var(--ink); border-color: var(--ink-soft); }
.kinds-save-slot { margin-left: auto; display: flex; align-items: center; }
.kinds-save { display: flex; align-items: center; }
.savetpl { padding: var(--s-1) var(--s-2); font: 500 13px/1.4 var(--font-body); }
.savetpl:disabled { opacity: .4; cursor: not-allowed; }
.savetpl:disabled:hover { border-color: var(--rule); color: var(--ink-soft); }
/* The name box in the save menu fills the card it is in. */
.tplname { width: 100%; font-size: 13px; margin: var(--s-nudge) 0 0; }

/* THE AUDIENCE MENU IS WIDER THAN THE REST. Franz, 2026-09-16: "audience
   dropdown box can be a bit wider mate". Every row carries a name and a
   sentence under it, so the width that suits a list of words wraps this one
   into a column of fragments. */
.dt-pop.is-audience { min-width: 360px; max-width: min(460px, calc(100vw - 24px)); }

/* THE NAME, BETWEEN THE PILLS AND THE VERSIONS. One box holding the whole
   title, in the product's own input, sized in script to the line it carries so
   the versions menu sits beside it rather than across the bar. Past the cap the
   input scrolls its own text under the caret, which is the scroll with no
   scrollbar — nothing here draws one. */
.kinds-name { display: flex; align-items: center; min-width: 0; flex: 0 1 auto; }
.namebar { display: flex; min-width: 0; position: relative; }
.namebox { padding: var(--s-1) var(--s-1h); font-size: 13.5px; max-width: 100%; }
/* Measured, never seen. Same face, same size, same padding-free string. */
.namebox-ghost {
  position: absolute; left: 0; top: 0; visibility: hidden; pointer-events: none;
  white-space: pre; font: 400 13.5px/1.5 var(--font-body);
}
/* At phone width the toolbar stacks and the name takes its own line rather
   than squeezing the pills. */
@media (max-width: 760px) {
  .kinds-name { flex-basis: 100%; }
  .namebox { width: 100% !important; }
}
/* ONE LINE, ON THE LEFT, beside the tabs. It was a right-aligned block with a
   sentence under it, which made a second bar out of a single decision. */
.versions { display: flex; align-items: center; gap: var(--s-1); }
.versions .label { margin: 0; }
/* As wide as its longest option and no wider — "3 versions" is eleven
   characters and it was sitting in a 148px bar. */
.versions .pick-versions { width: auto; min-width: 0; font-size: 12.5px; padding: var(--s-1) var(--s-4) var(--s-1) var(--s-1); }
/* IN THE TOOL BAR IT IS A PILL. Franz, 2026-09-16: "make version pill similar
   to video and ad pill rounededness mate". Beside two pills, a rounded
   rectangle reads as a different kind of control than it is, so it borrows the
   kind pill's radius, padding and type exactly. It keeps the plainer shape
   wherever the versions row appears inside a form. */
.kinds-versions .pick-versions {
  border-radius: var(--r-pill); padding: var(--s-half) var(--s-3) var(--s-half) var(--s-1h);
  font: 500 12px/1.4 var(--font-body); color: var(--mute);
}
.kinds-versions .pick-versions:hover { color: var(--ink); border-color: var(--ink-soft); }
.version-pills[hidden] { display: none !important; }
.version-pills { gap: var(--s-half); }
.version-pills .chip { font-size: 12.5px; padding: var(--s-half) var(--s-1h); min-width: 32px; text-align: center; }
/* A version with something missing, or one that is a copy of another, says so
   from the bar — so a gap on the version you are not looking at is still
   visible from the one you are. */
.version-pills .chip.is-gap { border-color: var(--alarm); color: var(--ink); }
.version-pills .chip.is-gap[aria-pressed="true"] { border-color: var(--alarm); }
.versions-say { margin: 0 0 0 var(--s-nudge); font-size: 11.5px; line-height: 1.4; color: var(--mute); }

/* The last field in a card has the card's own padding under it already. */
.c-card > .field:last-child, .c-card > .switch:last-child { margin-bottom: 0; }
.sheet-form.is-page .c-card > .field:last-child { margin-bottom: 0; }

/* The run context loses its own frame inside the card that now names it. */
.create-run .runctx { border: 0; background: none; padding: 0; margin: 0; }

/* Fields stacked directly in a card get air between them — DIRECT children
   only, or the pairs inside the run context inherit it and the right-hand
   column of every pair sits 18px lower than the left. */
.c-card > .field + .field { margin-top: var(--s-2); }

/* A CLIENT WITH NO DECLARED LINES LEAVES NO HOLE. The picker is drawn empty and
   filled when the profile lands; empty, it must take no room, or Length sits
   beside a blank half. */
.line-field:empty { display: none; }

/* THE WIDE CARD ASKS IN HALVES. Franz, 2026-09-16: "whatever left we can add in
   the long width but half fields only". A one-line answer in a 1200px box reads
   as a mistake; two columns give every question the width it actually needs. */
/* ONE GAP BETWEEN ROWS, WHATEVER IS IN THEM. Franz, 2026-09-16: "im not seeing
   uniform spacing between each section stacked mate ... thisn is improtant to
   me". Every row used to carry its own space as the bottom margin of the field
   inside it, so a row with a character count under its box pushed the next row
   20px further down than a row without one, and no two gaps on the card were
   the same. The fields give up their margins and the CARD sets the distance,
   once, which makes it uniform by construction rather than by arithmetic. */
/* A LABEL STARTS WHERE ITS ANSWER STARTS. Franz, 2026-09-16: "all fo the
   labels and sub labels need to be a bit inside the margin to the fields and
   not start at the edge". Every control on this form indents its own text by
   11px; the label above it began at the box's outer edge, so the question and
   the answer were on two different left margins. */
/* 2026-10-20, Franz: "I don't like this extra left side padding on each label,
   it needs to start from where the box or elements start". Labels now sit on
   the box's own left edge, everywhere, as the pill rows already did. */
.c-card .field > .label { padding-left: 0; }
.c-card > .field.is-section > .section-head > .label { padding-left: 0; }

.create-halves { display: grid; gap: var(--s-1) var(--s-4); grid-template-columns: minmax(0, 1fr); }
.create-run .create-halves > .field { margin: 0; }
.create-run .create-halves + .create-halves { margin-top: var(--s-1); }
.create-run .create-halves + .field.is-section { margin-top: var(--s-1); }
@media (min-width: 900px) {
  .create-halves { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* One question that wants the whole card, in the same kind of row as the
     pairs, so the spacing between rows is uniform by construction. */
  .create-halves.is-wide { grid-template-columns: minmax(0, 1fr); }
}
/* An empty line under a picker still takes a line. These two are drawn ahead of
   the message they may never carry, which made the row they are in taller than
   the rows around it.
   `:empty` was not enough for the second one: it holds a box and a button that
   are both HIDDEN until somebody writes their own hook, so it had children, no
   height of its own to speak of, and nine pixels of margin nobody could see —
   which is why the Main Brief sat lower than every other row. A row with
   nothing visible in it takes no room. */
.hookpick-say:empty { display: none; }
.hookpick-new:not(:has(> *:not([hidden]))) { display: none; }
/* The angle runs under both columns, with a rule to say the card changed
   subject: it is what the four answers above it are argued into. */
.sheet-form .c-card > .angle-field {
  margin: var(--s-nudge) 0 0; padding-top: var(--s-2); border-top: 1px solid var(--rule);
}

/* BEFORE YOU WRITE. What the product knows about the client, and what this
   brief is still missing — the half somebody can fix in the ten seconds before
   they press the button. */
/* The same 20px gutter the two cards above it keep between themselves. It sat
   flush against them because `.create-top` ends where its grid ends. */
.create-ready { margin-top: 0; }
.sheet-form.is-page .knows { margin: 0; }
.knows + .tips { margin-top: var(--s-2); padding-top: var(--s-1h); border-top: 1px solid var(--rule-faint); }
.tips-h { display: block; margin: 0 0 var(--s-1); }
/* Advice, never a gate: nothing here disables the button, so nothing here is
   coloured like an error. */
.tip {
  margin: 0 0 var(--s-1); padding-left: var(--s-1h); position: relative;
  font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--w-read);
}
.tip::before {
  content: ''; position: absolute; left: 0; top: var(--s-1);
  width: 4px; height: 4px; border-radius: 50%; background: var(--mute);
}
.tip:last-child { margin-bottom: 0; }
/* A required gap reads in the same red as the star it belongs to; a suggestion
   stays quiet, because it never stops anything. */
.tip.is-req { color: var(--ink); }
.tip.is-req::before { background: var(--alarm); }
.tips-h + .tip { margin-top: 0; }
.tip + .tips-h { display: block; margin-top: var(--s-1h); }
.tip.is-ready { padding-left: 0; color: var(--accent); }
.tip.is-ready::before { display: none; }

/* A LINE ARRIVES AND LEAVES, IT DOES NOT BLINK. The panel is recomputed on
   every keystroke; without this, satisfying one requirement redrew all four and
   read as a flash rather than as an answer moving off the list. 180ms, one
   property pair, no easing anybody has to notice. */
.tips .tip, .tips .tips-h {
  transition: opacity 180ms ease, transform 180ms ease;
}
.tips .is-in { opacity: 0; transform: translateY(-4px); }
.tips .is-out { opacity: 0; transform: translateY(-4px); pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .tips .tip, .tips .tips-h { transition: none; }
}

.sheet-form.is-page .field { margin-bottom: var(--s-3); }

/* ── the client header, aligned ──────────────────────────────────────────────
   THE LOGO AND THE FIRST NUMBER SHARE A LEFT EDGE. They did not: the back
   arrow sat IN the heading's flex row, so even pulled into the page's padding
   it still took part of a gap and pushed the mark about fourteen pixels right
   of the stats underneath. Taking it out of the flow entirely puts the mark on
   the page margin, where the "2" already was.

   And the mark is a LOGO, not a bullet. At 38px it was the size of a favicon
   beside a 23px name; a client is recognised by their mark before their name
   is read, which is the whole reason it is there. */

.client-head { position: relative; }

.client-head h1 { gap: var(--s-2); }
.client-head .mark-btn .mark {
  width: 58px; height: 58px; font-size: 19px;
}
/* THE LOGO SITS IN ITS BOX PROPERLY. Franz, 2026-09-16: "fix the logo placement
   wihtin the assined box mate". `.mark.has-img` carried 2px of padding set for
   a 30px mark, which at 58px left the image hard against three sides and
   floating off the fourth — a wide logo touched the edges while a tall one did
   not. Real padding, and the image centres itself inside what is left. */
.client-head .mark.has-img { padding: 0; }
.client-head .mark-img { object-position: center; }
.client-stats { margin-left: 0; padding-left: 0; }



/* ── the account ─────────────────────────────────────────────────────────────
   One round button. Everything behind it is about YOU rather than about the
   work, which is the whole test for what belongs here. */

.avatar {
  width: var(--h-md); height: var(--h-md); border-radius: 50%; flex: none; cursor: pointer;
  border: 1px solid var(--rule); background: var(--shade); color: var(--ink-soft);
  font: 600 11px/1 var(--font-display); letter-spacing: .02em; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.avatar:hover { border-color: var(--accent); color: var(--ink); }
.avatar[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }

.acct-name { margin: var(--s-nudge) var(--s-1) var(--s-px); font: 600 13px/1.3 var(--font-display); color: var(--ink); }
.acct-sub { margin: 0 var(--s-1) var(--s-half); font-size: 11.5px; color: var(--mute); }
.acct-plan-row { display: flex; align-items: center; gap: var(--s-1); }
.acct-plan {
  font: 600 9.5px/1 var(--font-display); letter-spacing: .08em;
  color: var(--accent); border: 1px solid var(--accent); border-radius: 20px; padding: var(--s-half) var(--s-1);
}

/* ── a workspace ─────────────────────────────────────────────────────────────
   The same conventions as everywhere else: sub-tabs, one table per list, one
   toolbar, housekeeping last and dull. */

.ws-kind { font-size: 12.5px; color: var(--mute); }
.ws-field { max-width: 460px; margin-bottom: var(--s-3); }
/* An invite code you can take with you. A string somebody selects by hand is a
   string somebody mistypes. */
.ws-code {
  /* NO MONO TYPE (house rule): the body face, its figures tabular. */
  display: inline-flex; align-items: center; height: var(--h-xs);
  font: 500 12px/1 var(--font-body); font-variant-numeric: tabular-nums;
  background: var(--shade); border: 1px solid var(--rule); border-radius: var(--r-sm);
  padding: 0 var(--s-1); color: var(--ink-soft); cursor: pointer;
}
.ws-code:hover { border-color: var(--accent); color: var(--accent); }
.chip.is-go { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.chip.is-go:hover { opacity: .9; }
.dt-pop .bar { width: 100%; margin: var(--s-nudge) 0 var(--s-half); }
.dt-pop .bar-seg { flex: 1 1 0; text-align: center; }
.dt-toolbar .btn-sm { flex: none; }

/* One page, four blocks. No tabs: sub-tabs earn their place when each one is a
   screenful somebody works in for a while, and a workspace is four short things
   that are read together. */
.ws-block { margin: 0 0 var(--s-4); }
/* PEOPLE AND CLIENTS, AS CARDS. The same box the Context page's list cards are,
   so the two tables at the top of this page and the four below it are one kind
   of object. The gap between them matches the board's own 20px column gap. */
.ws-table { margin: 0 0 var(--s-3); }
/* THE TABLE SPANS THE CARD, EDGE TO EDGE. The card's 16px of side padding is
   right for a list of rows and wrong for a table: a table draws its own cell
   borders, so padding either side pushes the widest column past the box and the
   last one clips. The toolbar keeps the padding — it is a strip of controls and
   it needs the air — and the table itself is pulled back out to the edges, the
   same thing the dossier card does with its rows. */
.ws-table > .dt { margin: 0 calc(-1 * var(--s-2)); width: calc(100% + 32px); }
.ws-table > .dt > .dt-toolbar { padding-left: var(--s-2); padding-right: var(--s-2); }
.ws-block-head { display: flex; align-items: baseline; gap: var(--s-1h); margin: 0 0 var(--s-1h); }
.ws-block-h { font: var(--t-section); letter-spacing: var(--tr-section); margin: 0; }
.ws-block-hint { font-size: 12px; color: var(--mute); }
.ws-block .ws-settings { margin-top: 0; }

/* Renaming is on the name, so the name has to look like something you can
   press without looking like a button sitting in a heading. */
.ws-title { font: inherit; color: inherit; background: none; border: 0; padding: 0; }
.ws-title.is-editable { cursor: text; border-bottom: 1px solid transparent; }
.ws-title.is-editable:hover { border-bottom-color: var(--rule); }
.ws-rename {
  font: var(--t-title); letter-spacing: var(--tr-title);
  padding: var(--s-nudge) var(--s-1); width: auto; min-width: 260px;
}
.page-head .ws-code { margin-left: var(--s-half); }

/* ── an edit is a decision ───────────────────────────────────────────────────
   Enter saves, Shift-Enter is a new line, Escape reverts, and a Save appears
   beside the field only while there is something to save. Nothing commits on
   blur: that wrote a row of history every time somebody tabbed past a box. */

/* Save and Discard together: they appear and vanish as one, so a field with
   nothing to save carries no controls at all. */
.commit-row { display: inline-flex; align-items: center; gap: var(--s-1); }
/* Discard is the quieter of the two. It is not dangerous — what it throws
   away is still on screen while you decide — so it is not red; it is just
   not the thing you are most likely to want. */
.commit.is-quiet {
  background: none; color: var(--mute);
  border: 1px solid var(--rule);
}
.commit.is-quiet:hover { color: var(--ink); border-color: var(--mute); opacity: 1; }

.commit {
  align-self: flex-start; margin-top: var(--s-1);
  background: var(--accent); border: 1px solid var(--accent); color: var(--accent-ink);
  border-radius: 20px; padding: var(--s-half) var(--s-1h); font: 500 11.5px/1 var(--font-body); cursor: pointer;
}
.commit:hover { opacity: .9; }
.commit:disabled { opacity: .5; cursor: wait; }
/* An unsaved box is marked, and its text is kept. Throwing away what somebody
   typed because they looked at another field is worse than either alternative. */
.is-dirty .sf-value, .is-dirty .req-value { border-color: var(--accent); }
.scene-field.is-dirty .lbl, .req-row.is-dirty .req-label { color: var(--accent); }
.req-row.is-dirty, .scene-field.is-dirty { position: relative; }

/* ── the activity rail, quiet ────────────────────────────────────────────────
   No timer and no live dot. It is drawn when the page opens, again after
   anything you do writes a row, and on Refresh — which covers the only other
   case, the other person changing something while you sit here. */

.live-refresh { margin-left: auto; font-size: 11.5px; }
.act { padding: var(--s-1) 0; border-bottom: 1px solid var(--rule-faint); }
.act:last-child { border-bottom: 0; }
.act-head { gap: var(--s-1); }
.act-by { margin: var(--s-nudge) 0 0; font-size: 11px; color: var(--mute); }
.act-note { margin: var(--s-half) 0 0; font-size: 12px; line-height: 1.5; }

/* The comparison, folded. A row says WHAT changed in one line; the
   before-and-after is one click away, for the one row in twenty somebody wants
   to inspect. */
.act-cmp { margin-top: var(--s-nudge); }
/* ONE CHEVRON, NOT TWO. Franz, 2026-09-16: "in acitivit yhere are two drop down
   icons i only like the one on the right use that one with proepr spacing".
   There were two because this drew its own CSS triangle AND the browser kept
   drawing the native disclosure marker — `::-webkit-details-marker` has not been
   the one Chrome uses for years; `::marker` is, and `display: block` on the
   summary is what actually removes it. The remaining one is the product's own
   chevron glyph, which is the shape every card in AdEQ folds with. */
/* The chevron comes from the global `details > summary::after` rule, like every
   other disclosure in the product — this row used to draw a second one of its
   own next to the word. What is set here is the spacing Franz asked for: the
   summary is a real row with air above and below it rather than a line of 11px
   text jammed under the name of whoever made the change. */
.act-cmp-sum {
  display: flex; align-items: center; cursor: pointer;
  list-style: none; font-size: 11.5px; color: var(--mute);
  padding: var(--s-half) 0;
}
.act-cmp-sum::-webkit-details-marker { display: none; }
.act-cmp-sum::marker { content: ''; }
.act-cmp-sum:hover { color: var(--accent); }
.act-cmp-sum:hover::after { border-color: var(--accent); }
.act-cmp[open] .act-cmp-sum { color: var(--accent); }
.act-cmp .diff { margin-top: var(--s-1); }

/* ── a heading starts at the margin ──────────────────────────────────────────
   The back arrow was IN the heading's flex row, so even hanging in the page's
   padding it still took a gap and pushed the title a dozen pixels right of
   everything under it. Out of the flow, and the title's first letter lines up
   with the first letter of the section headings below — which is the alignment
   a page is read down. */

/* FURTHER OUT, AND ONLY THE ARROW. Franz, 2026-09-15: "the back button needs to
   go more on the left only the back button properly and aesthetically".
 
   It sat 2px inside the page's own padding, so it was almost touching the first
   letter of the title and read as part of it. A full gutter out, it is its own
   thing in its own margin — which is what an escape hatch should look like. */
.page-head { position: relative; }


/* `.script-state { margin-bottom: var(--s-3) }` used to live here, from when the
   review row was a band above both columns. It is in the script's own toolbar
   now and carries no margin of its own. */



/* ── a client's links ────────────────────────────────────────────────────────
   Opened, not read. A column of URLs in a textarea is a column you have to
   select and paste somewhere; the same lines above it are the About page, one
   click away, which is why anybody wrote them down. */

/* A link row is a name and, quietly behind it, the address. The name is what
   you read and what you click; the address is there so you can tell two
   coupons pages apart without opening both. */
.lled-row.is-link { gap: var(--s-1); }
.lled-link {
  flex: 0 0 38%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px; color: var(--ink); text-decoration: none; padding: var(--s-nudge) 0;
}
.lled-link:hover { color: var(--accent); text-decoration: underline; }
.lled-link.is-bad { color: var(--mute); text-decoration: line-through; }
.lled-url {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11.5px; color: var(--mute);
}
/* `.lled-acts` and `.lled-rename` used to be defined here, for a links editor
   that no longer exists — nothing has drawn either since the Links card became
   a table. `.lled-acts` is defined once now, with the list editor it belongs
   to, and it is not hover-hidden. */
/* THE PAIR HUGS ITS CONTENT. As a stretching flex child of a column with a
   floor under it, this row grew to whatever height was left over and both boxes
   came with it — two 56px boxes with their placeholders pinned to the top,
   which is the thing in Franz's screenshot. */
.lled-linknew { display: flex; gap: var(--s-1); align-items: flex-start; flex: 0 0 auto; }
/* The same two columns as the rows above: a name column of the same width, then
   the address. A pair that does not line up with the list it adds to reads as a
   different control. */
.lled-name { flex: 0 0 38%; font-size: 13px; padding: var(--s-1) var(--s-1); }
.lled-url-in { flex: 1 1 auto; min-width: 0; font-size: 12.5px; padding: var(--s-1) var(--s-1); }
/* The add row is not one of the rows above it: a gap says so, and it lines up
   with them rather than sitting flush against the last one. */
.lled-linknew { margin-top: var(--s-1); gap: var(--s-1); }

/* A logo still loading, or one that failed: the box stays the mark's size and
   shows nothing rather than a broken-image glyph. The initials come back if the
   fetch fails, because two letters still identify a client. */
.mark.has-img { background: var(--shade); }
.mark-img:not([src]) { visibility: hidden; }

/* ── personal or shared ──────────────────────────────────────────────────────
   The one fact somebody needs about a workspace before they put anything in it
   is whether anybody else can see it, and it used to be a grey sentence the
   same size and colour as the client count beside it. SHARED carries the
   accent because it is the state with a consequence; PERSONAL is quiet because
   it is the safe one. */

.ws-badge {
  /* A CHIP, NOT AN EYEBROW: Title Case in the body face, no tracked capitals. */
  flex: none; display: inline-flex; align-items: center; height: var(--h-xs);
  font: 600 12px/1 var(--font-body); padding: 0 var(--s-1); border-radius: var(--r-pill);
  border: 1px solid var(--rule); color: var(--mute); background: var(--shade);
}
.ws-badge.is-shared {
  border-color: var(--accent); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.switcher-ws .ws-badge { font-size: 11px; height: calc(var(--h-xs) - var(--s-half)); padding: 0 var(--s-1); }

/* ── the first screen ────────────────────────────────────────────────────────
   Compulsory, and it is ten seconds. Without a personal workspace the product
   has no shelves: Home is empty, the switcher is empty, and the first thing
   somebody makes has nowhere to be. */

.setup { max-width: 540px; margin: 8vh auto 0; }
.setup-eyebrow {
  margin: 0 0 var(--s-1); font: var(--t-label); letter-spacing: var(--tr-label); color: var(--accent);
}
.setup-h { font: var(--t-title); letter-spacing: var(--tr-title); margin: 0 0 var(--s-1h); }
.setup-lede { margin: 0 0 var(--s-3); font-size: 14px; line-height: 1.6; color: var(--ink-soft); }
.setup-field { font-size: 15px; padding: var(--s-1) var(--s-1h); }
.setup-go { display: flex; gap: var(--s-1h); margin-top: var(--s-2); }
.setup-go .btn { padding: var(--s-1) var(--s-2); }
.setup-sep { height: 1px; background: var(--rule-faint); margin: var(--s-3) 0 var(--s-2); }
.setup-quiet { margin: 0; font-size: 12.5px; line-height: 1.6; color: var(--mute); }
.setup-msg { margin: var(--s-1h) 0 0; }
.setup .sec { margin-top: var(--s-3); }

/* One Create page, and what you are making is a bar at the top of it — not two
   tabs in the nav asking the question before the form asks anything. */
.create-kindbar { display: flex; align-items: center; gap: var(--s-1h); margin: 0 0 var(--s-2); }
.create-kindbar .label { margin: 0; }

/* A destructive button that cannot be pressed until the word is typed. Disabled
   is visibly disabled here rather than merely inert: the point of the field is
   that the button is out of reach until you have done something deliberate. */
.dt-pop-go .chip:disabled { opacity: .4; cursor: not-allowed; }
.dt-pop-go .chip.is-danger:disabled:hover { background: none; }

/* What a run is given, and where it lands. Two jobs in one block: one says what
   goes IN to the work, the other where it comes OUT — both about this run, and
   neither about the client in general. */
.runctx { margin: 0 0 var(--s-2); padding: 0; background: none; border: 0; }
.runctx .field:last-child { margin-bottom: 0; }
.runctx-row { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: center; }
.runctx-row .hint { margin: 0; }
.runctx .camp-new { flex: none; width: 170px; padding: var(--s-half) var(--s-1); font-size: 12.5px; }

/* A block heading that lives inside a toolbar. People and Clients each have ONE
   strip: the name, the sentence that explains it, the making button, then the
   count, the search and Columns. A heading row above a strip that already holds
   three of those is a second band saying less than the first. */
/* `.ws-block-inline` used to live here and said what `.sec-label` already says.
   One heading class for a table's toolbar, everywhere. */
.ws-block-hint { white-space: nowrap; }

@media (max-width: 900px) {
  /* The sentence is the first thing to go when the strip runs out of room. */
  .sec-label .ws-block-hint { display: none; }
}

/* ── the tags column ─────────────────────────────────────────────────────────
   Sorted, a fixed two, then a count — Fishpond's rule and the right one: the
   third tag would be half a word, and half a word is worse than a number. The
   cell is also the control, because tagging from the column you are reading
   tags in is the obvious act. */

.dt-tags {
  display: flex; align-items: center; gap: var(--s-half); max-width: 100%; flex-wrap: wrap;
  background: none; border: 0; padding: 0; text-align: left;
}
.dt-cell .tag {
  margin: 0; font-size: 11px; padding: var(--s-nudge) var(--s-1); line-height: 1.5;
  max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  background: var(--shade); color: var(--ink-soft);
}
.dt-cell .tag.is-more {
  background: none; border-color: transparent; color: var(--mute);
  padding: var(--s-nudge) var(--s-nudge); font-variant-numeric: tabular-nums;
}
/* EACH TAG CARRIES ITS OWN WAY OFF. Franz, 2026-09-16: "there should be direct
   in field". The cross appears with the pointer and takes no width when it is
   not there, so a row at rest is a row of words and a row under the hand is a
   row of words you can remove. */
.dt-cell .tag.is-live { display: inline-flex; align-items: center; gap: 0; cursor: pointer; }
.dt-cell .tag-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-cell .tag-x {
  width: 0; overflow: hidden; color: var(--mute); font-size: 13px; line-height: 1;
  transition: width .1s ease, margin-left .1s ease;
}
.dt-row:hover .tag-x { width: 11px; margin-left: var(--s-half); }
.dt-cell .tag.is-live:hover { border-color: var(--alarm); color: var(--alarm); }
.dt-cell .tag.is-live:hover .tag-x { color: var(--alarm); }
/* The plus is the same size as a tag and just as quiet: a column of them on
   forty rows is a column of noise, so it waits for the pointer. */
/* WHOSE TURN IT IS, NEXT TO THE STAGE THAT SAYS IT IS SOMEBODY'S.
   A name, not a chip: the stage pill beside it is already the loud thing in
   the row and two pills would compete. Quiet, one line, and it truncates
   rather than wrapping — the full name is on its title. */
.dt-stage { display: inline-flex; align-items: center; gap: var(--s-1); min-width: 0; }
.dt-waiting {
  font-size: 11.5px; color: var(--mute);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The existing .script-state above already lays these out in a row; this only
   adds the label, set apart from the last chip rather than beside it. */
.script-waiting { font-size: 12.5px; color: var(--mute); margin-left: var(--s-1); }

/* FIVE DOTS, SCORED IN PLACE.
   The form is RatingDots from fran7esque-sounds; the colour is not. That one
   is brand blue to keep it apart from an energy meter in the next column —
   here there is one accent and BRAND.md says there is never a second, so an
   unlit dot is a ring and a lit dot is the accent filled. The difference is
   fill, not hue, which is also what keeps it readable at 8px.
   Quiet at rest and only fully present on hover or focus: five controls in
   every row of a dense table would otherwise be the loudest thing on screen,
   and the number is what you came to read. */
.dt-dots { display: inline-flex; align-items: center; gap: var(--s-half); padding: var(--s-nudge) 0; }
.dt-dot {
  width: 8px; height: 8px; padding: 0; flex: none;
  border-radius: 50%; cursor: pointer;
  background: none; border: 1px solid var(--rule);
  opacity: .55; transition: background-color .12s, border-color .12s, opacity .12s, transform .12s;
}
.dt-row:hover .dt-dot, .dt-dots:focus-within .dt-dot { opacity: 1; }
.dt-dot.is-on { background: var(--accent); border-color: var(--accent); opacity: 1; }
.dt-dot:hover { transform: scale(1.18); }
.dt-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .dt-dot { transition: none; }
  .dt-dot:hover { transform: none; }
}

/* A CELL THAT IS ALSO ITS OWN CONTROL.
   The campaign cell reads as text until you go near it, then it is a button.
   Same argument as .dt-tags-add: the column you read a thing in is the column
   you change it in, and a control that announces itself in every row of a
   dense table is noise. So: no chrome at rest, a quiet underline on hover,
   and a focus ring that is always there for the keyboard. */
.dt-cell-btn {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  border-bottom: 1px solid transparent; transition: color .1s, border-color .1s;
}
.dt-cell-btn:hover { color: var(--accent); border-bottom-color: var(--rule); }
.dt-cell-btn.muted { color: var(--mute); }
.dt-cell-btn.muted:hover { color: var(--accent); }
.dt-cell-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* The line under a popover heading that explains why the list is short. */
.dt-pop-say {
  margin: 0 var(--s-1) var(--s-half); font-size: 12px; line-height: 1.45; color: var(--mute); max-width: 210px;
}

.dt-tags-add {
  flex: none; background: none; border: 0; cursor: pointer; padding: 0 var(--s-half);
  font: 400 14px/1 var(--font-body); color: var(--mute);
  opacity: 0; transition: opacity .1s;
}
.dt-row:hover .dt-tags-add, .dt-tags-add:focus-visible { opacity: 1; }
.dt-tags-add:hover { color: var(--accent); }

/* ── a list you edit as a list ───────────────────────────────────────────────
   Type, Enter, it becomes a row. Click a row to rename it, X to remove it. A
   textarea full of lines is a paragraph that happens to be a list: you cannot
   tell whether the blank line at the end counts, renaming means retyping a word
   inside a wall of text, and deleting one is selecting exactly the right
   characters. The same eight words as eight rows are eight things. */

.lled { display: flex; flex-direction: column; gap: var(--s-1); align-items: stretch; }
.lled-rows { display: flex; flex-direction: column; gap: var(--s-px); }
.lled-none { margin: var(--s-nudge) 0; font-size: 12.5px; color: var(--mute); }
.lled-row {
  display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-half) var(--s-1); border-radius: var(--r-sm); background: var(--shade);
}
.lled-row:hover { background: var(--card); }
.lled-n {
  flex: none; width: 15px; font: 500 10.5px/1 var(--font-display);
  color: var(--mute); font-variant-numeric: tabular-nums;
}
.lled-text {
  flex: 1 1 auto; min-width: 0; text-align: left; background: none; border: 0;
  padding: 0; color: var(--ink); font: 400 13px/1.5 var(--font-body); cursor: text;
}
.lled-text:disabled { cursor: default; }
.lled-text:hover:not(:disabled) { color: var(--accent); }
.lled-input { flex: 1 1 auto; min-width: 0; padding: var(--s-nudge) var(--s-1); font-size: 13px; }
/* How many pieces of work carry it. The reason somebody hesitates before
   deleting a tag, and the only place it has ever been visible. */
.lled-count {
  flex: none; font-size: 11px; color: var(--mute); font-variant-numeric: tabular-nums;
}
/* `.lled-acts` was the row's verbs on the old list editor. Those cards are
   tables now and their verbs come from `utilIcons()` in the utility cell, like
   every other table — the rule went with the control. */
.lled-new { font-size: 13px; padding: var(--s-1) var(--s-1); }

/* --- a scoped list, as two columns ---------------------------------------- */
/* The same table Links and the Dossier use, so a card that holds facts and a
   card that holds links are recognisably the same control. Two columns: what it
   says, and which line of business it belongs to. */
.scopedtable { display: flex; flex-direction: column; gap: var(--s-1); }
.scopedtable .dt-toolbar { padding-left: 0; padding-right: 0; }
/* The fact itself. Left, and it wraps — a rule is a sentence, and a sentence
   clipped to a column width is a rule nobody can check. */
.scoped-text {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 0; color: var(--ink); font: 400 13px/1.5 var(--font-body); cursor: text;
  white-space: normal;
}
.scoped-text:hover { color: var(--accent); }

/* WHAT A ROW CARRIES BESIDE ITS LINE (migration 54): a price, a source, a date.
   Empty reads as a quiet prompt, never as a dash that looks like data. A flag
   ("Recheck", "Ended") is a word in a small outlined pill, in the alarm colour
   because it is asking for something, and never a second accent. */
.scoped-text.scoped-extra { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.scoped-text.scoped-extra.is-unnamed { color: var(--mute); font-style: italic; }
.scoped-flag {
  font: 500 11px/1 var(--font-body); font-style: normal; color: var(--alarm);
  border: 1px solid color-mix(in srgb, var(--alarm) 55%, transparent);
  border-radius: var(--r-pill); padding: var(--s-half) var(--s-1); white-space: nowrap;
}
.scoped-date { color-scheme: dark; width: 100%; }
/* A USED COUNT YOU CAN PRESS (migration 55): the number, opening the list of
   the work it went into. It reads as the other count columns do until hovered. */
.scoped-used { background: none; border: 0; padding: var(--s-nudge) var(--s-1); border-radius: var(--r-pill); color: var(--ink); font: 500 13px/1.4 var(--font-body); cursor: pointer; }
.scoped-used:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cs-camp-w { font-size: 12px; white-space: nowrap; margin-left: auto; padding-right: var(--s-1); }
:root[data-theme="light"] .scoped-date { color-scheme: light; }

/* THE TAG CELL. A WORD, NOT A COLOUR — which side of a business a rule belongs
   to is the difference between a usable script and one that calls a lawn crew
   licensed applicators, so it is readable at a glance and never a hue somebody
   has to learn. The eight Plutchik colours are for emotion and nothing else,
   and a ninth accent does not exist in this product.
   Quiet when General, because most rows are and the page should not shout it;
   in the accent when narrowed, because THAT is the exception the eye needs. */
/* CENTRED, LABEL AND VALUE BOTH. Franz, 2026-09-16: "make type column centre
   alginement mate label and field items". The cell is centred by `is-center` on
   the column; the button inside it has to agree, or a full-width control goes
   on left-aligning its own text inside a centred cell and only the header
   appears to have moved. */
.scoped-pick {
  width: 100%; max-width: 100%; text-align: center; cursor: pointer;
  border: 0; border-radius: 5px; padding: var(--s-half) var(--s-1);
  background: none; color: var(--mute);
  font: 500 12px/1.5 var(--font-body);
  text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
}
.dt-row:hover .scoped-pick { background: var(--shade); }
.scoped-pick:hover, .scoped-pick:focus-visible { color: var(--ink); background: var(--shade); }
.scoped-pick.is-set { color: var(--accent); }
/* LINES OF BUSINESS: the chosen lines as small pills side by side, General
   when there are none, and a tick beside each line in the menu (2026-10-04). */
.scoped-pick.lob-pick { display: inline-flex; flex-wrap: wrap; justify-content: center; gap: var(--s-half); width: 100%; padding: var(--s-half) var(--s-half); }
.lob-pill {
  display: inline-flex; align-items: center; height: 22px; padding: 0 var(--s-1); border-radius: var(--r-pill);
  font: 500 11.5px/1 var(--font-body); color: var(--accent); white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.lob-none { font: 500 12px/1.5 var(--font-body); color: var(--mute); }
.lob-tick {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 15px; height: 15px; border-radius: 4px; border: 1px solid var(--rule); color: var(--accent-ink);
}
.lob-tick.is-on { background: var(--accent); border-color: var(--accent); }
/* A CATEGORY IS A PILL (2026-10-04): a small rounded tag in its cell, tinted
   when set, quiet when it is a plain Fact. */
.scoped-pick.is-pill {
  width: auto; display: inline-flex; align-items: center; justify-content: center;
  max-width: 100%; height: 24px; padding: 0 var(--s-1); border-radius: var(--r-pill);
  border: 1px solid var(--rule); background: none; font-size: 11.5px;
}
.scoped-pick.is-pill.is-set {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.scoped-pick.is-pill:hover, .scoped-pick.is-pill:focus-visible { border-color: var(--accent); }
.scoped-none { font: 500 12px/1.5 var(--font-body); color: var(--mute); text-align: center; }

/* The add bar, under the table, carrying the same two columns. */
.scoped-addbar { display: flex; gap: var(--s-1); align-items: flex-start; }
.scoped-addbar .lled-new { flex: 1 1 auto; min-width: 0; }
/* The add bar's tag is a slot holding one button, so the border lives on the
   slot and the button inside it stays the same control as the ones in the rows. */
.scoped-pick-new {
  flex: 0 0 auto; align-self: stretch; display: inline-flex; align-items: center;
  max-width: 42%; min-width: 96px;
  border: 1px solid var(--rule); background: var(--shade);
  border-radius: var(--r-sm);
}
.scoped-pick-new .scoped-pick { padding: var(--s-1) var(--s-1); text-align: left; }
.scoped-pick-new:hover .scoped-pick { color: var(--ink); }

/* A TAG MENU IS A SHORT MENU. It opens under the tag it belongs to, inside the
   column, and it is never wider than it needs to be. */
.dt-pop.is-tag { min-width: 180px; }

/* The one line of that card worth keeping: the sentence under a document's name
   in its sheet, and in the Dossier's own tooltips. */
.dpack-say { font-size: 12px; color: var(--mute); }

/* A row for a document that does not exist yet is still a row — quieter, but
   present, because the dossier's job is to say what a client's context is MADE
   of before any of it is there. */
.doc-icon.is-empty { opacity: .35; }
.dt-title.is-empty { color: var(--mute); }
.doc-state.is-missing { color: var(--mute); border-color: var(--rule); }
.doc-state.is-accepted { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--rule)); }
.doc-state.is-legacy { color: var(--mute); border-style: solid; }
.doc-size { font-variant-numeric: tabular-nums; color: var(--mute); }
.doc-size.is-have { color: var(--ink-soft); }
.doc-sheet-go { margin: var(--s-half) 0 var(--s-nudge); }

/* --- the dossier as cards -------------------------------------------------- */
/* Shaped like the media tiles beside them — a bordered box, its caption at the
   bottom — but taller, because a document has to carry the sentence saying what
   it is FOR. That sentence is why cards beat the table here: "only things that
   are true and checkable" tells somebody which document to write next, and a
   table has nowhere to put it. */
/* SMALLER, AND SCROLLING INSIDE ITSELF. Franz, 2026-09-16: "i didnt want the
   length of the dossier section to increase ... these internal cards need to be
   smaller too to accomodate and then use internal samrt scroll".
   Seven cards at 148px tall made the dossier the longest thing on the page and
   dragged the paste box beside it to the same height. They are shorter, denser
   and two-up in a narrower column, and the grid scrolls in its own box past a
   point — SMART, in that a client with two documents never scrolls at all. */
.dpack-grid {
  display: grid; gap: var(--s-1); margin: var(--s-1) 0 0;
  grid-template-columns: repeat(auto-fill, minmax(min(196px, 100%), 1fr));
  /* ROWS SIZED BY THEIR CONTENT, SAID OUT LOUD.
     The grid sits in a box of a fixed height, and an implicit `auto` row inside
     a definite-height grid gets SHARED the leftover space — five rows each came
     out at exactly 76px, which is the box divided by five, and every card was
     squashed to a third of itself. `min-content` says what was meant: a row is
     as tall as the tallest card in it, and the box scrolls. */
  grid-auto-rows: min-content;
  max-height: 368px; overflow-y: auto; padding-right: var(--s-nudge);
}
.dpack-card {
  display: flex; flex-direction: column; gap: var(--s-half);
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--shade); padding: var(--s-1) var(--s-1) var(--s-1);
  /* A floor, so a card can never be squeezed below the five things it carries:
     the badge row, the name, two lines of purpose, the count and the verbs. */
  min-height: 146px;
}
.dpack-card.is-have { background: var(--card); border-color: var(--rule); }
.dpack-card:hover { border-color: var(--ink-soft); }
.dpack-card-top { display: flex; align-items: center; justify-content: space-between; gap: var(--s-1); }
.dpack-card-name { font: 600 12.5px/1.3 var(--font-display); color: var(--ink); }
.dpack-card-name.is-empty { color: var(--mute); }
/* Two lines and then an ellipsis: the sentence says what the document is for,
   and a third line of it is never the difference. */
/* `flex: none`, NOT `1 1 auto`. It grew to fill the card while the card was
   free to be any height; the moment the card became fixed and the grid a flex
   child, the same rule let it be SQUEEZED to nothing and the sentence vanished
   from every tile — the one thing a card has that a table row does not. Two
   lines, always, whatever the box does. */
.dpack-card-says {
  font-size: 11px; line-height: 1.4; color: var(--mute); flex: none;
  min-height: 2.8em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* `align-content: start` so the rows sit at the top of the scroller instead of
   spreading down it — but NOT `align-self: start` on the card.
   That one line collapsed every row: a flex column with `min-height: 0` that is
   told not to stretch reports an automatic minimum of almost nothing, so the
   grid sized its rows at 76px while the cards were 149px and each row overlapped
   the one below it. Stretched, the row is the card's height and a row of three
   ends level, which is what a grid of tiles should do anyway. */
.dpack-grid { align-content: start; }
/* A search that matches nothing still needs to say so, across the whole grid
   rather than in the width of one tile. */
.dpack-empty { grid-column: 1 / -1; margin: var(--s-nudge) 0 0; }
.dpack-card-meta { display: flex; align-items: baseline; gap: var(--s-1); min-width: 0; }
.dpack-card-size { font-size: 10.5px; color: var(--mute); font-variant-numeric: tabular-nums; flex: none; }
/* Which side of the business this document is for. Only drawn when it has one,
   so a client with a single line never sees the word General on a tile. */
.dpack-card-line {
  font-size: 10.5px; color: var(--accent); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dpack-card-size.is-have { color: var(--accent); }
.dpack-card-go {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-1);
  margin-top: var(--s-half); padding-top: var(--s-1); border-top: 1px solid var(--rule-faint);
}
.dpack-card-go .dt-ics { gap: var(--s-half); }
.dpack-card.is-legacy { min-height: 118px; }
.dpack-card.is-legacy .dpack-card-go { justify-content: flex-end; }

/* The prompt is a labelled button, not a glyph: it is the first thing anybody
   needs and the only verb about a document that does not exist yet. */
/* THE TWO GREENS, AND THEY ARE NOT THE SAME GREEN. Franz, 2026-09-16: "ahve
   the copy prompt and accepted pills or buttons in this green bg too nicely".
   Copy Prompt is the ACTION — solid accent with dark ink, the same weight as
   Create in the header, because it is the one thing a person came here to press.
   `accepted` is a STATE, and seven solid green pills on one card would shout
   over the seven buttons beside them; it takes a tint of the same accent, which
   is the same green doing a quieter job. */
/* The second way in, and it reads as the second way in: an outline chip beside
   the solid one, not another solid button competing with it. */
.dpack-paste { font-size: 11.5px; padding: var(--s-half) var(--s-1h); }
.dpack-prompt {
  font-size: 11.5px; padding: var(--s-half) var(--s-1); white-space: nowrap;
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  font-weight: 500;
}
.dpack-prompt:hover { filter: brightness(1.08); }
.dpack-prompt.is-small { font-size: 11px; padding: var(--s-nudge) var(--s-1); }
.dpack-prompt.is-done { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.doc-state.is-accepted {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 34%, var(--rule));
  color: var(--accent);
}
/* SMALLER ON A CARD THAN IN A ROW. Franz, 2026-09-16: "reduce the accepted pill
   size mate nicely strategicaly in the cards". On a 196px card a full-size chip
   was as wide as the name under it and read as the loudest thing on the tile,
   which a state should never be — the name and the action are what matter. */
.dpack-card .doc-state {
  font-size: 9.5px; padding: var(--s-px) var(--s-1); letter-spacing: .02em; border-radius: var(--r-pill);
}
.dpack-card .doc-icon { font-size: 9px; padding: var(--s-px) var(--s-half); }

/* A verb that is available but has nothing to do — the refresh on a card that
   is already level with its document. Lit means there is something new. */
.dt-ic.is-dim { opacity: .3; }
.dt-ic.is-dim:hover { opacity: .7; }

/* --- what a document offers a card ---------------------------------------- */
.ticks .tick-rows {
  display: flex; flex-direction: column; gap: var(--s-px);
  max-height: 52vh; overflow: auto; margin: var(--s-half) 0 0;
}
.tick-row {
  display: flex; align-items: flex-start; gap: var(--s-1); cursor: pointer;
  padding: var(--s-1) var(--s-1); border-radius: var(--r-sm); background: var(--shade);
}
.tick-row:hover { background: var(--card); }
/* Already on the card: shown, ticked and inert. Hiding them would make the list
   look like the document had less in it than it does. */
.tick-row.is-on { opacity: .45; cursor: default; }
.tick-text { flex: 1 1 auto; min-width: 0; font: 400 13px/1.5 var(--font-body); }
.tick-where { flex: none; font-size: 11px; color: var(--mute); padding-top: var(--s-nudge); }

/* Naming a new line in the cell the picker was in: same box, same size. */
.scoped-newline { width: 100%; font-size: 12px; padding: var(--s-nudge) var(--s-1); }

/* The Line of business picker on the Create form. */
.line-field .pick-line { min-width: 260px; }

/* A MENU THAT ENDS WHERE THE PAGE ENDS. The Settings menu carries two headed
   groups now and needs the room to say what is in each; at the default width
   the second line of every row wrapped. */
.dt-pop.is-wide { min-width: 300px; max-width: min(360px, calc(100vw - 24px)); }
.lled-status { margin: 0; min-height: 14px; }

/* Three fields, no paragraphs. A label and a placeholder are the explanation. */
.ws-form .pf-field { gap: var(--s-1); }

/* --- the Context page: links and media on top, then two columns ------------ */

/* TWO PAIRS, AND EACH PAIR IS A PAIR. Franz, 2026-09-15: "not liking the uneven
   cards here i need these to be smartly equal in length and breadths."
   `align-items: start` let every card hug its own contents, so Links ended
   taller than Images and the two rows read as four unrelated boxes. They
   stretch: equal halves, equal heights, both rows. */
.ctx-top {
  display: grid; gap: var(--s-3); margin: 0 0 var(--s-3);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
}
.ctx-cols {
  display: grid; gap: var(--s-3); margin: 0 0 var(--s-3);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
}
/* A stretched card has to lay its own contents out top-down, or it stretches
   and leaves its content floating in the middle of the space. */
.ctx-top > .pf-field { display: flex; flex-direction: column; margin: 0; }
.ctx-top > .pf-field > .lled { flex: 1 1 auto; }
.ctx-col { min-width: 0; display: flex; }
.ctx-col > * { flex: 1 1 auto; min-width: 0; }
.ctx-col .dump { margin: 0; display: flex; flex-direction: column; }
.ctx-col .dump .dump-form { flex: 1 1 auto; }

/* THE PAGE SCROLLS, NOT A BOX INSIDE IT. Franz, 2026-09-15: "make it easy to
   scroll through the page without only having to do so from the sides or minute
   gaps ... the hooks have disappeared and the audience too."
   Both complaints were one bug. The dossier column was a 520px scroller with
   `overscroll-behavior: contain`, which is an instruction NOT to pass the wheel
   on — so over most of the right-hand side the wheel did nothing, and the cards
   below the fold (Audience, Hooks) could not be reached at all. They had not
   disappeared; the page had stopped scrolling.

   The pair is equal height by stretch now, which is what the inner scroller was
   really for. A tall dossier makes a tall card and the page scrolls, which is
   the behaviour everybody already knows. */
.ctx-col.is-scroll { padding-right: 0; }


/* The dump is a column now, not a full-width strip: its two boxes stack. */
/* IN A COLUMN IT IS A STACK, AND A STACK NEEDS ONE RHYTHM. Franz: "doc upload
   paste spacing not good." The two-column grid's 10px/14px gaps were designed
   for a full-width strip; stacked, the name box, the paste box and the buttons
   sat at three different distances from each other. One gap between all three,
   and the paste box takes whatever height the card has left so the card matches
   the dossier beside it instead of ending early. */
.ctx-col .dump-form {
  display: flex; flex-direction: column; gap: var(--s-1h);
}
.ctx-col .dump-form .dump-area {
  flex: 1 1 auto; min-height: 150px; max-height: none; resize: none;
}
/* The two buttons are one row with one gap, set apart from the box above them
   rather than sitting against it. Franz: "add documents or paste buttons
   spacing too". */
.ctx-col .dump-form .dump-go { margin-top: var(--s-half); gap: var(--s-1); }
.ctx-col .dump-form .dump-go .btn { padding: var(--s-1) var(--s-2); }
.ctx-col .dump-form .dump-pick { padding: var(--s-1) var(--s-2); }

@media (max-width: 900px) {
  .ctx-top, .ctx-cols { grid-template-columns: 1fr; }
  .ctx-col.is-scroll { max-height: none; }
}

/* --- images and media ------------------------------------------------------ */

.media .pf-head { align-items: center; }
/* Nothing of its own but the pointer: it is a .chip, and a chip is a chip. */
.media-add { cursor: pointer; }
/* A LIBRARY, NOT A SHOWCASE. Franz: "there will be a lot of images later mate."
   One 104px tile in a row of its own looked like a mistake and forty of them
   would have made this card taller than the page. Small even squares, as many
   per row as fit, and a ceiling after which the grid scrolls inside its own
   card rather than pushing everything below it down. */
/* THE SAME HEIGHT AS THE DOSSIER, AND FOR THE SAME REASON. Franz, 2026-09-16:
   "images and media needs to be longer like before mate cause there will be a
   lot of images". A card that holds forty tiles cannot be sized by how many it
   holds today — one logo made it the shortest card on the board and forty would
   have made it the longest. It is a fixed box that scrolls, like the dossier
   beside it, so the board's shape never depends on how much somebody uploaded.
   `grid-auto-rows: min-content` for the reason written on the dossier grid: an
   auto row inside a definite-height grid shares the leftover space instead of
   hugging its contents. */
.media .pf-head, .media .dt-toolbar { flex: none; }
.media .dt-toolbar { padding-bottom: var(--s-1); }
.media { height: 486px; display: flex; flex-direction: column; }
.media-grid {
  display: grid; gap: var(--s-1); margin: var(--s-1) 0 0; align-content: start;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  grid-auto-rows: min-content;
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding-right: var(--s-nudge);
}
.media-grid::-webkit-scrollbar { width: 8px; }
.media-tile {
  position: relative; margin: 0; border: 1px solid var(--rule); border-radius: var(--r-md);
  overflow: hidden; background: var(--shade);
}
.media-tile.is-going { opacity: .4; }
.media-tile.is-gone { display: none; }
.media-img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; background: var(--shade); }
.media-cap {
  display: block; padding: var(--s-half) var(--s-1); font-size: 10.5px; color: var(--ink-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.media-x {
  position: absolute; top: 5px; right: 5px; display: inline-flex; padding: var(--s-half);
  border: 0; border-radius: var(--r-sm); background: rgba(0,0,0,.62); color: #fff;
  cursor: pointer; opacity: 0; transition: opacity .12s;
}
.media-x { opacity: 1; }
.media-x:hover { background: var(--alarm); }
.media-say { margin: var(--s-1) 0 0; }
.media-grid > .lled-none { grid-column: 1 / -1; }

/* A growing field, wherever one is: never a scrollbar, never hidden text. */
.textarea.grow { resize: none; overflow: hidden; min-height: 0; }
/* EXCEPT THE TWO IN A NOTE. That zero is right for a field inside a script,
   which shrinks back to one line as it is emptied, and wrong for a box somebody
   is meant to start writing in — a one-line slot reads as a search bar. They
   still grow past this. It sits after the rule it is answering, because that
   rule is the one that would otherwise win. */
.comment-form .textarea.grow { min-height: 74px; }
.lled-new, .lled-name, .lled-url-in, .lled-input { line-height: 1.45; }
.dump-pick { cursor: pointer; font-size: 12.5px; padding: var(--s-1) var(--s-1h); }

/* --- the three numbers, in the title bar ------------------------------------
   A strip between the client's name and the Settings pill: value, then its
   word, then a hairline, then the next. Stacked at display size they were a
   dashboard; inline they are a caption, which is all they ever were. */

.client-head .client-stats {
  display: flex; align-items: baseline; gap: 0;
  margin: 0 0 0 var(--s-nudge); padding-left: var(--s-2);
  border-left: 1px solid var(--rule);
}
.client-head .stat {
  position: relative; flex-direction: row; align-items: baseline;
  gap: var(--s-1); padding: 0 var(--s-2);
}
.client-head .stat:first-child { padding-left: 0; }
.client-head .stat + .stat::before {
  content: ''; position: absolute; left: 0; top: var(--s-px); bottom: var(--s-px);
  width: 1px; background: var(--rule);
}
.client-head .stat-v { font-size: 15px; }
.client-head .stat-l { font-size: 12px; color: var(--mute); }

@media (max-width: 860px) {
  /* Nothing to the right to sit beside, so the strip drops under the name
     rather than squeezing the title into two words a line. */
  .client-head .client-stats {
    flex-basis: 100%; order: 3; margin: var(--s-nudge) 0 0; padding-left: 0; border-left: 0;
  }
  .client-head .stat:first-child { padding-left: 0; }
}

/* --- the workspace picker, as a control rather than an outline --------------

   Franz, 2026-09-15: "the workspace picker column should be same type from
   outside as the create in the top right like with bg colour". Same height,
   same radius, same weight as the Create button — a filled pill, not a hollow
   one. It is filled in a SURFACE, not the accent: the accent names the one
   primary action on a screen, and two green pills in one bar is two primary
   actions, which is none. */

.ws-switch {
  /* THE SAME PILL AS CREATE, INCLUDING ITS BACKGROUND. Franz, 2026-09-15,
     twice: "the workspace picker column should be same type from outside as
     the create in the top right like with bg colour", then "this pill bg needs
     to be like create pill bg mate".
     I had kept it a surface fill on the argument that the accent names the one
     primary action on a screen. He has asked for the accent twice; it is his
     product and the two controls do sit at opposite ends of the bar. */
  padding: var(--s-1) var(--s-2); border-radius: var(--r-pill);
  background: var(--accent); border: 1px solid transparent;
  font: 600 13.5px/1 var(--font-body); color: var(--accent-ink);
  gap: var(--s-1);
}
.ws-switch:hover, .ws-switch[aria-expanded="true"] { filter: brightness(1.06); }
.ws-switch .ws-chev { border-color: var(--accent-ink); }

/* --- the panel: room to breathe, and folders that stay folded -------------- */

.switcher-list { padding: var(--s-1) var(--s-1) 0; overflow: auto; }
.switcher-row {
  display: flex; align-items: stretch; gap: var(--s-nudge);
  border-radius: var(--r-md); margin: var(--s-px) 0;
}
.switcher-row:hover { background: var(--shade); }
.switcher-row.is-current { background: var(--shade); }
.switcher-row .switcher-ws {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-1) var(--s-half) var(--s-1) var(--s-1h); background: none;
}
.switcher-row .switcher-ws:hover { background: none; }
.switcher-ws-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.switcher-fold {
  flex: none; width: 26px; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer; border-radius: var(--r-md);
}
.switcher-fold:disabled { cursor: default; opacity: .25; }
.switcher-fold:hover:not(:disabled) { color: var(--accent); }
.switcher-chev {
  width: 6px; height: 6px;
  border-right: 1.5px solid var(--mute); border-bottom: 1.5px solid var(--mute);
  transform: rotate(-45deg) translate(-1px, 1px);
  transition: transform .12s ease-out;
}
.switcher-chev.is-open { transform: rotate(45deg) translate(-1px, -1px); }

.switcher-clients { padding: 0 0 var(--s-1); }
/* THE CLIENTS, NO THREAD (Franz, 2026-11-03: "a better ui/ux here no line and
   better margining and with logos for each small ones"): each its own small
   logo, the logo's left edge on the workspace name's, the whole row the hover. */
.switcher-client {
  display: flex; align-items: center; gap: var(--s-1h);
  padding: var(--s-1) var(--s-1) var(--s-1) var(--s-1h); border-radius: var(--r-md); margin: var(--s-px) 0;
  color: var(--ink); font-size: 13.5px;
}
.switcher-client::before { content: none; }
.switcher-mark { flex: none; display: inline-flex; }
.switcher-mark .mark { width: var(--s-3); height: var(--s-3); font-size: 9.5px; margin: 0; }
.switcher-client-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The two ways to get another workspace: two equal buttons at the foot, the
   list's own air above them and no rule. */
.switcher-foot {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-1);
  padding: var(--s-1) var(--s-half) var(--s-1h); margin-top: var(--s-half);
}
.switcher-foot .switcher-btn { justify-content: center; width: 100%; white-space: nowrap; }
.switcher-foot .switcher-plus { margin-right: var(--s-1); font-weight: 600; }

/* --- a mini pill: a count, and a sentence on hover ------------------------- */

.mini-pill {
  flex: none; margin-left: auto;
  min-width: 17px; padding: var(--s-px) var(--s-half); border-radius: 9px;
  font: 600 10.5px/1.5 var(--font-body); font-variant-numeric: tabular-nums;
  text-align: center; cursor: help;
  background: var(--accent); color: var(--accent-ink);
}
.switcher-client .mini-pill { background: var(--shade); color: var(--accent); border: 1px solid var(--accent); }

/* --- Create: two big tabs, and the run's context at the top ---------------- */

/* SMALLER, AND THE SAME AIR ABOVE AS BELOW. Franz, 2026-09-16: "the main video
   script and static ad pills on top should be a bit smaller and more on brand
   ... up and down spacing from it should be uniform".
   They were 16px display type in a 44px pill — the size of a page heading, for
   a question that is answered once and forgotten. They are set like the rest of
   the product's chips now: 13px body type, the same height as a Length pill,
   and 18px of gutter on both sides of the row. */
.create-kinds {
  display: flex; gap: var(--s-1); margin: var(--s-2) 0;
}
.create-kind {
  padding: var(--s-1) var(--s-2); border-radius: var(--r-pill); cursor: pointer;
  background: var(--card); border: 1px solid var(--rule); color: var(--mute);
  font: 500 13px/1.4 var(--font-body); letter-spacing: 0;
}
.create-kind:hover { color: var(--ink); border-color: var(--ink-soft); }
.create-kind[aria-selected="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

.chip-add { cursor: pointer; }

/* A tick and the sentence it turns on. */
.tickrow { display: flex; align-items: center; gap: var(--s-1); }
.tickrow label { font-size: 13.5px; color: var(--ink); cursor: pointer; }
.tick { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }

/* --- the hook picker ------------------------------------------------------- */

.hookpick { display: flex; flex-direction: column; gap: var(--s-1); }
.hookpick-own { font-size: 13px; flex: 1 1 auto; min-width: 0; }
.hookpick-new { display: flex; align-items: flex-start; gap: var(--s-1); }
.hookpick-add { flex: none; padding: var(--s-1) var(--s-1h); font-size: 12.5px; white-space: nowrap; }
.hookpick-shown {
  margin: 0; padding: var(--s-1) var(--s-1h); border-radius: var(--r-md);
  background: var(--shade); border-left: 2px solid var(--accent);
  font-size: 13.5px; line-height: 1.5; color: var(--ink);
}
.hookpick-say { margin: 0; }

/* --- Controls, as a block rather than a folder ----------------------------- */

.controls {
  margin: var(--s-half) 0 0; padding: var(--s-2) var(--s-2) var(--s-1);
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
}
.controls-h { font: var(--t-section); letter-spacing: var(--tr-section); margin: 0 0 var(--s-half); }
.controls > .hint:first-of-type { margin: 0 0 var(--s-2); }

/* One switch, its name, and what it does. No heading above it: the control
   says more than a heading could. */
.switch {
  display: flex; align-items: flex-start; gap: var(--s-1h); cursor: pointer;
  margin: var(--s-half) 0 var(--s-2); padding: var(--s-1h) var(--s-1h);
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--shade);
}
.switch:hover { border-color: var(--ink-soft); }
.switch:has(.tick:checked) { border-color: var(--accent); }
.switch .tick { margin-top: var(--s-nudge); }
.switch-body { display: flex; flex-direction: column; gap: var(--s-half); }
.switch-name { font: 600 13.5px/1.2 var(--font-body); color: var(--ink); }
.switch-say { font-size: 11.5px; line-height: 1.5; color: var(--mute); }

/* --- two halves of a row, and the tiles in them ---------------------------- */

.ctx-pair {
  display: grid; gap: var(--s-2) var(--s-3); margin: 0 0 var(--s-2);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.ctx-pair > .field { margin: 0; min-width: 0; }
@media (max-width: 760px) { .ctx-pair { grid-template-columns: 1fr; } }

/* A HALF-WIDTH MENU FITS ITS HALF. The template and the line of business sit in
   the Ad card's columns now, not in a row of their own, so a fixed minimum wide
   enough to push a column open is a minimum that wraps the row. */
.create-halves .pick-line, .create-halves .pick-tpl { min-width: 0; width: 100%; }

/* ROOM FOR A REAL LIBRARY. Franz, 2026-09-16: "this needs to be longer cause
   there could be many docs and media attached ... there already space deep in
   the column below". Both lists take the height the card has spare and scroll
   inside it, so twenty documents is a scroll rather than a card twice as tall
   as the one beside it. */
.runctx-docs, .runctx-media {
  display: grid; gap: var(--s-1); margin: var(--s-nudge) 0 0;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  align-content: start;
  min-height: 190px; max-height: 300px; overflow-y: auto;
  /* The page scrolls when this one is at its end — the lesson from the Context
     page's dossier column, which contained the wheel and lost two cards. */
  overscroll-behavior: auto;
}
.tile {
  position: relative; display: flex; flex-direction: column; justify-content: flex-end;
  aspect-ratio: 1; padding: var(--s-1); overflow: hidden; cursor: pointer;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--shade); color: var(--ink-soft); text-align: left;
}
.tile:hover { border-color: var(--ink-soft); }
.tile[aria-pressed="true"] { border-color: var(--accent); }
.tile[aria-pressed="true"]::after {
  content: ''; position: absolute; inset: 0;
  box-shadow: inset 0 0 0 2px var(--accent); border-radius: inherit; pointer-events: none;
}
.tile-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.tile-kind {
  position: absolute; top: 6px; left: 6px;
  font: 600 9.5px/1 var(--font-body); letter-spacing: .06em;
  color: var(--accent); border: 1px solid var(--accent); border-radius: 3px; padding: var(--s-nudge) var(--s-half);
}
.tile-name {
  position: relative; font-size: 10.5px; line-height: 1.3; max-height: 2.6em; overflow: hidden;
}
.tile-media .tile-name {
  padding: var(--s-half) var(--s-half); border-radius: 4px; background: rgb(0 0 0 / .62); color: #fff;
  white-space: nowrap; text-overflow: ellipsis;
}
.tile-add {
  align-items: center; justify-content: center; gap: var(--s-half); color: var(--mute);
  border-style: solid;
}
.tile-add:hover { color: var(--accent); border-color: var(--accent); }
.tile-add .tile-name { font-size: 11px; }
/* An empty library says so across the whole row, not down one 78px column. */
.runctx-docs > .hint, .runctx-media > .hint { grid-column: 1 / -1; margin: 0; }

/* EXISTING, ONE GRID THAT WRAPS (Franz, 2026-10-27: "scrolling so many images
   horizontally is a ui/ux nightmare"). The documents, then the pictures, as one
   grid: the two rows inside it are `display: contents`, so their tiles are its
   cells. Each tile is a square picture with its name under it, in full on two
   lines. foldGrid (dom.js) shows two rows and puts the rest behind Show All. */
.reflane {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(144px, 1fr));
  gap: var(--s-2) var(--s-1h); padding-top: var(--s-nudge);
}
.reflane > .runctx-docs, .reflane > .runctx-media { display: contents; }
.reflane .hint { grid-column: 1 / -1; margin: 0; }
.reflane .ref-tile {
  aspect-ratio: auto; padding: 0; border: 0; border-radius: 0; background: none; overflow: visible;
  justify-content: flex-start; gap: var(--s-1); min-width: 0;
}
.reflane .ref-tile[hidden] { display: none; }
.reflane .ref-tile[aria-pressed="true"]::after { content: none; }
.ref-pic {
  position: relative; display: grid; place-items: center; aspect-ratio: 1; overflow: hidden;
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--shade);
}
.ref-tile:hover .ref-pic, .ref-tile:focus-visible .ref-pic { border-color: var(--ink-soft); }
.ref-tile[aria-pressed="true"] .ref-pic { border-color: var(--accent); }
.ref-tile[aria-pressed="true"] .ref-pic::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}
.ref-pic .tile-kind { position: static; font: 600 13px/1 var(--font-body); padding: var(--s-half) var(--s-1); }
.reflane .ref-tile .tile-name {
  position: static; max-height: none; padding: 0; border-radius: 0; background: none;
  font-size: 13px; line-height: 1.35; color: var(--ink-soft); white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.reflane .ref-tile[aria-pressed="true"] .tile-name { color: var(--ink); }
.grid-more { margin-top: var(--s-1h); }
.grid-more[hidden] { display: none; }
/* Adding is a row of verbs, not a tile in the middle of what already exists,
   and a mild line says the two halves are two questions. */
.refadd { margin-top: var(--s-2); padding-top: var(--s-2); border-top: 1px solid var(--rule-faint); }
.refnew { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.refbtn { display: inline-flex; align-items: center; cursor: pointer; }
.refbtn:hover { border-color: var(--accent); color: var(--ink); }
/* THE SHAPE OF A FULL ROW, WHILE IT IS EMPTY. A dashed square says a document
   goes here without a sentence explaining it. */
.tile-ghost {
  border-style: solid; border-color: var(--rule-faint); background: none;
  pointer-events: none;
}

/* SIX LENGTHS, ONE ROW, EQUAL WIDTHS. Franz, 2026-09-16: "timing pills should be
   justified and in one row". They were a wrapping flex, so 90s dropped onto a
   line of its own and the row looked broken rather than full. */
.c-card .durations {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--s-1);
}
.c-card .durations .chip { width: 100%; padding: var(--s-half) 0; text-align: center; }

/* A select, wearing the same clothes as every other field. */
.pick {
  font: inherit; font-size: 13px; padding: var(--s-1) var(--s-1); cursor: pointer;
  max-width: 100%; color: var(--ink);
}
.pick-hook, .pick-camp, .pick-tpl { min-width: 220px; }
.pick-suggest { max-width: var(--w-pop); font-size: 12.5px; padding: var(--s-1) var(--s-1); }
.field-row { display: flex; align-items: center; gap: var(--s-1h); }
.field-row .label { margin: 0; flex: none; }
.field-head { display: flex; align-items: center; gap: var(--s-1h); flex-wrap: wrap; margin: 0 0 var(--s-1); }
.field-head .label { margin: 0; }

/* --- help and suggestions inside a profile card ---------------------------- */

.pf-field .pick-suggest { margin: var(--s-1) 0 0; width: 100%; max-width: none; }

/* The copy button on a link row. */
.lled-copy {
  flex: 0 0 auto; display: inline-flex; padding: var(--s-half); border: 0; background: none;
  color: var(--mute); border-radius: 5px; cursor: pointer; opacity: 0; transition: opacity .12s;
}
.lled-row:hover .lled-copy, .lled-copy:focus-visible { opacity: 1; }
.lled-copy:hover { color: var(--accent); background: var(--shade); }
.lled-copy.is-done { opacity: 1; color: var(--accent); }

/* --- Home: one workspace, its numbers, and what to do next ----------------- */

.page-head-right { display: flex; align-items: center; gap: var(--s-2); }
.page-head h1 { display: flex; align-items: center; gap: var(--s-1h); }

.home-stats {
  display: grid; gap: var(--s-1); margin: 0 0 var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}
.home-stat {
  display: flex; flex-direction: column; gap: var(--s-half); text-align: left;
  padding: var(--s-1h) var(--s-2); cursor: pointer;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  color: var(--ink);
}
.home-stat:hover { border-color: var(--ink-soft); }
.home-stat .stat-v { font: 800 26px/1 var(--font-poster); font-variant-numeric: tabular-nums; }
.home-stat .stat-l { font-size: 12px; color: var(--mute); }
/* A live count is the accent; a zero is not, because a zero is good news and
   should not look like a thing to do. */
.home-stat.is-live .stat-v { color: var(--accent); }
.home-stat.is-open { border-color: var(--accent); }

.home-next { margin: 0 0 var(--s-3); }
.next-box {
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
  overflow: hidden;
}
.next-row {
  display: flex; align-items: baseline; gap: var(--s-1h); width: 100%; text-align: left;
  padding: var(--s-1h) var(--s-2); background: none; border: 0; cursor: pointer;
  border-top: 1px solid var(--rule); color: var(--ink);
}
.next-row:first-child { border-top: 0; }
.next-row.is-fresh { align-items: center; }
.next-row.is-fresh > .btn { flex: none; margin-left: var(--s-1); }
.next-row:hover { background: var(--shade); }
.next-who { flex: none; font-size: 12px; color: var(--mute); min-width: 80px; }
.next-title { flex: none; max-width: 40%; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.next-note {
  flex: 1 1 auto; min-width: 0; font-size: 12.5px; color: var(--mute);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.next-row .c-when { flex: none; margin-left: auto; font-size: 11.5px; color: var(--mute); }
.next-box .hint { margin: 0; padding: var(--s-1h) var(--s-2); }

/* Tagging somebody in a note. */
/* `.comment-tag` used to live here: the row of "Tag · Frazer Dsa" chips above
   the note box. Typing @ opens the same list where the caret already is, so the
   strip was a second control teaching a habit people arrive with — and it cost
   the note box a third of its height in a card this narrow. */
.chip-tag { font-size: 11.5px; padding: var(--s-half) var(--s-1); }

/* How much there is, when the list is showing a slice of it. */
.home-more { margin: var(--s-1) 0 0; display: flex; align-items: baseline; gap: var(--s-1); flex-wrap: wrap; }

/* A client in the list a tile opens: their mark, their name, what they sell. */
.next-row.is-client { align-items: center; gap: var(--s-1h); }
.next-row.is-client .next-title { font-weight: 500; max-width: 30%; }
.next-row.is-client .mark { flex: none; }

/* A client's name, which is also how it is renamed. */
.client-name {
  background: none; border: 0; padding: 0; margin: 0; color: inherit;
  font: inherit; letter-spacing: inherit; text-align: left;
}
.client-name.is-editable { cursor: text; border-bottom: 1px solid transparent; }
.client-name.is-editable:hover { border-bottom-color: var(--rule); }
.client-name-input {
  font: inherit; letter-spacing: inherit; padding: var(--s-nudge) var(--s-1); min-width: 260px;
}

/* New workspace, in the app header. Quiet, because it is rare — Create is the
   button people press all day and there is only one of those. */
/* THE SLOTS METER (app.js, wireHeadSlots): "Slots 2/2 · 8 queued", just left
   of Create. A quiet pill; full turns its count to the accent. */
.head-slots {
  display: inline-flex; align-items: center; gap: var(--s-1); height: var(--h-md); padding: 0 var(--pad-chip-x);
  border: 1px solid var(--rule); border-radius: var(--r-pill); background: transparent;
  color: var(--mute); font: inherit; font-size: 13px; white-space: nowrap; cursor: pointer;
}
.head-slots[hidden] { display: none; }
.head-slots:hover { color: var(--ink); border-color: var(--mute); }
.head-slots > strong { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.head-slots.is-full > strong { color: var(--accent); }
/* THE BAR (2026-10-30): a segment a slot, the used ones filling left to right
   when it is drawn, the locked ones faint; several clients, one bar by share. */
.head-slots .hs-bar { display: inline-flex; align-items: center; gap: var(--s-half); }
.head-slots .hs-seg { position: relative; display: block; width: var(--s-2); height: var(--s-1); border-radius: var(--r-pill); background: var(--shade); box-shadow: inset 0 0 0 var(--s-px) var(--rule); overflow: hidden; }
.head-slots .hs-seg.is-locked { opacity: .45; }
.head-slots .hs-seg.is-used::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; background: var(--accent);
  transform-origin: left; animation: hs-fill .6s cubic-bezier(.22, .61, .36, 1) var(--d, 0ms) both;
}
.head-slots .hs-seg.is-run { width: calc(var(--s-6) + var(--s-2)); }
.head-slots .hs-seg.is-run > b { position: absolute; inset: 0 auto 0 0; border-radius: inherit; background: var(--accent); transform-origin: left; animation: hs-fill .8s cubic-bezier(.22, .61, .36, 1) both; }
@keyframes hs-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) { .head-slots .hs-seg.is-used::after, .head-slots .hs-seg.is-run > b { animation: none; } }
/* A phone keeps the bar and the count: no word, no queue. */
@media (max-width: 639px) { .head-slots > .hs-word, .head-slots > .hs-queue { display: none; } }
/* At 390 the team's first row is full (Home, Create, the bell, day and night,
   the avatar), so the meter ends the pickers' row instead, outside the part
   that scrolls. A client's first row has no Home and keeps it beside Create. */
@media (max-width: 419px) {
  body:not(.is-client) .top-right > .head-slots { order: 6; margin-left: 0; align-self: flex-start; height: var(--h-lg); line-height: calc(var(--h-lg) - 2px); }
  body:not(.is-client) .top-right > .head-slots + .btn-create { margin-left: auto; }
}

/* Something that happened since you last looked. */
.notif-item.is-fresh { border-left: 2px solid var(--accent); }
.notif-item.is-fresh .notif-where { color: var(--ink); }

/* The bell is a square button now that the word is gone, and the count rides
   its corner rather than sitting beside it. */
.notif { position: relative; padding: 0; gap: 0; width: var(--h-md); height: var(--h-md); justify-content: center; border-radius: 50%; }
.notif .notif-count {
  position: absolute; top: -6px; right: -6px;
  min-width: 17px; height: 17px; line-height: 17px; font-size: 10.5px;
  box-shadow: 0 0 0 2px var(--bg);
}

/* --- every dropdown, with room at both ends --------------------------------

   Franz, 2026-09-15: "the spacing needs to be proper not so close to the end
   everywhere". A native select draws its own arrow hard against the border, a
   pixel or two from the corner radius, and no amount of padding moves it —
   padding pushes the TEXT and leaves the arrow where it was. So the browser's
   arrow is turned off and the same chevron every other control in the product
   uses is drawn in its place, with a real gutter it cannot lose.

   The colour is written out rather than taken from --mute: a `url()` cannot
   read a custom property, so the light theme redefines the whole image below.
   Two lines to keep one glyph honest in both themes. */

select.input {
  appearance: none; -webkit-appearance: none;
  padding-right: var(--s-4);
  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='%238A918A' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 12px 12px;
}
select.input:hover, select.input:focus {
  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='%23BFC5BE' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) select.input {
    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='%23767C77' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  }
  :root:not([data-theme="dark"]) select.input:hover, :root:not([data-theme="dark"]) select.input:focus {
    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='%233D433F' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  }
}
/* The same, picked by hand with the day/night switch. */
:root[data-theme="light"] select.input {
  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='%23767C77' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
:root[data-theme="light"] select.input:hover, :root[data-theme="light"] select.input:focus {
  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='%233D433F' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* The compact ones keep the same proportion rather than the same numbers: a
   6px-padded select with a 34px right gutter is mostly gutter. */
.pick-suggest, .lled-name, .pick.lled-url-in {
  padding-right: var(--s-4); background-position: right 11px center;
}

/* --- Everything, across every workspace ------------------------------------ */

.wordmark-btn {
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--ink);
  font: 600 17px/1 var(--font-body); letter-spacing: -.01em; text-decoration: none;
}
.wordmark-btn:hover { color: var(--accent); }

.ov-list { display: flex; flex-direction: column; gap: var(--s-4); }

/* One workspace, drawn exactly as its own Home is: a heading, the tiles, what
   is waiting, then the latest thirty. */
.ov-section { position: relative; }
.ov-head {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin: 0 0 var(--s-2); padding-bottom: var(--s-1); border-bottom: 1px solid var(--rule);
}
.ov-head h2 {
  display: flex; align-items: center; gap: var(--s-1h); flex-wrap: wrap; margin: 0;
  font: var(--t-title); letter-spacing: var(--tr-title);
}
.ov-name-btn {
  background: none; border: 0; padding: 0; color: inherit; font: inherit;
  letter-spacing: inherit; cursor: pointer;
}
.ov-name-btn:hover { color: var(--accent); }
.ov-head-right { display: flex; align-items: center; gap: var(--s-1h); margin-left: auto; }
.ov-section.is-current .ov-head { border-bottom-color: var(--accent); }

/* Up and down, side by side at the end of the heading row. Reordering is a
   thing you do once and never think about again, so it does not get the width
   of a control you use. */
.ov-move { display: flex; align-items: center; gap: var(--s-nudge); }
.ov-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border: 1px solid var(--rule);
  border-radius: var(--r-md); background: none; color: var(--mute); cursor: pointer;
}
.ov-arrow:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.ov-arrow:disabled { opacity: .22; cursor: default; }
.ov-chev {
  width: 7px; height: 7px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
}
.ov-chev.is-up { transform: rotate(-135deg) translate(-1px, -1px); }

/* The plus and its label are two words, set apart. */
.switcher-plus { display: inline-block; margin-right: var(--s-1); }

/* --- the account menu ------------------------------------------------------ */

.acct-who { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-nudge) var(--s-1) var(--s-1); }
.acct-who-text { min-width: 0; }
.acct-mark {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--shade); border: 1px solid var(--rule);
  font: 600 12px/1 var(--font-body); color: var(--ink-soft);
}
.acct-who .acct-name { margin: 0; }
.acct-who .acct-sub { margin: var(--s-nudge) 0 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A row with a second line saying what it is for. */
.dt-pop-row.is-said { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-nudge); }
.pop-row-name { font-size: 13.5px; }
.pop-row-say { font-size: 11.5px; color: var(--mute); line-height: 1.4; }

/* The dossier is a panel, like the box it sits beside. */
/* THE DOSSIER BOX IS ONE SIZE, IN BOTH VIEWS, FOR EVER.
   Franz, 2026-09-16: "the dossier box size can never change and has to be the
   rigt size to acocmmodate both with scroll doesnt matter and we can never have
   a gap in theese cards like that hard rule".
   Switching to the list made the card four hundred pixels taller, which shoved
   the board it sits in and left a hand's depth of nothing under the card beside
   it. A card whose height depends on which way you are looking at it is a card
   that rearranges the page every time you glance at it differently. It is
   fixed; whichever view is on scrolls inside it. */
.dossier-card {
  padding: var(--s-2) var(--s-2) var(--s-2);
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
  display: flex; flex-direction: column;
  height: 486px;
}
.dossier-card > div { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
/* Whatever is inside takes the slack and scrolls in its own box, never the
   card's. The toolbar stays put at the top of both. */
.dossier-card .dpack-grid { flex: 1 1 auto; max-height: none; min-height: 0; }
.dossier-card .dt { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.dossier-card .dt-scroll { flex: 1 1 auto; min-height: 0; max-height: none; }
/* ROOM UNDER THE STRIP, AND NOTHING CLIPPED ABOVE THE HEADER.
   Franz, 2026-09-16: "the toolbar bottom spacing is not proper and top is cut
   off in dossier". The strip's 8px was set for a row of plain text; it now
   carries a search box, two chips and a segmented control, all taller than the
   text it replaced, so they sat against the table's header with the top edge of
   that header lost behind them. Twelve under the strip, and the scroller keeps
   its own rounded top so the sticky header has an edge to sit on. */
.dossier-card .dt-toolbar { padding-bottom: var(--s-1h); }
/* AND IT MAY NEVER BE SQUEEZED. Franz, 2026-09-16: "the top part of toolbar is
   still broken".
   The card is a flex column of a fixed height and the grid inside it asks for
   all the room, so the strip — a flex item with the default `shrink: 1` — was
   handed 27 pixels to fit 28-pixel chips in. A flex item that is too short does
   not clip, it squashes: the Paste chip, the view toggle and the search box all
   stuck out of the top of their own strip and the row read as half a toolbar.
   `flex: none` means the strip is as tall as what it holds, and the grid takes
   what is left — which is what the scroller is for. */
.dossier-card .dt-toolbar { flex: none; }
.dossier-card .dt-scroll { border-top-left-radius: var(--r-md); border-top-right-radius: var(--r-md); }

/* PINNED HERE TOO (Franz, 2026-10-30: "utility columns in every card or every
   place should be locked on the right"; it was unpinned on these cards since
   2026-09-16). The pinned cell keeps the row's own background so nothing
   slides under it unseen. */
.profile-form .dt-h-cell.dt-util { background: var(--shade); }
.dossier-card .dossier-head { margin: 0 0 var(--s-1); }
/* Its own rows already carry a border; inside a card that is one line too many
   at the edges, so they lose theirs and keep the separators between them. */
.dossier-card .dossier { border: 0; border-radius: 0; }
.dossier-card .doc-row { padding-left: 0; padding-right: 0; }
.ctx-col.is-scroll .dossier-card { height: 100%; }

/* Editing a link: its name and its address, side by side, both live. */
.lled-edit { display: flex; gap: var(--s-1); width: 100%; }
.lled-edit-name { flex: 0 0 34%; }
.lled-edit-url { flex: 1 1 auto; min-width: 0; font-size: 12.5px; }


/* A newer build is out. Bottom left, out of the way of everything: it is news,
   not an interruption, and it never reloads by itself. */
/* BOTTOM LEFT, ON THE PAGE'S OWN EDGE (Franz, 2026-10-19: "the same as
   before in the bottom left, just starting from where the cards start"). The
   compact box it always was, its left edge on the page margin the cards and
   the title share, and never wider than the window. */
.newbuild {
  position: fixed; left: var(--pad-page, 16px); bottom: 18px; z-index: 60;
  max-width: calc(100vw - 2 * var(--pad-page, 16px));
  display: flex; align-items: center; gap: var(--s-1h);
  padding: var(--s-1) var(--s-1h) var(--s-1) var(--s-2); font-size: 13px; color: var(--ink);
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  /* QUIET (Franz, 2026-10-29: "has a lot of shadow"): a card's edge and the
     faintest lift, no green ring, the same on the site's bar. */
  box-shadow: 0 2px 8px rgb(0 0 0 / .12);
}
.newbuild .btn-sm { padding: var(--s-1) var(--s-1h); margin-left: 0; }

/* --- links and the dossier, as tables -------------------------------------- */

.linktable { display: flex; flex-direction: column; gap: var(--s-1); }
.linktable .dt-toolbar { padding-left: 0; padding-right: 0; }
.dt-title.is-bad { color: var(--mute); text-decoration: line-through; }
/* `.doc-namecell` was the badge and the name in one cell. They are two columns
   now, so the name is a plain title like every other pinned first column. */
.dt-row .dt-title.is-empty, .doc-namecell .dt-title {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* THE FORMAT, AS A TAG. The chip shape every "what this row IS" column uses, in
   the accent when there is a file and quiet when there is not — the same
   distinction `doc-state` makes one column over, said the same way. */
.doc-format {
  font: 500 10.5px/1 var(--font-body); letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent); border-color: var(--accent);
}

/* One document, over the page. Bounded, scrolling in its own box — inline it
   made a card four thousand pixels long and took the page with it. */
.doc-sheet { display: flex; flex-direction: column; gap: var(--s-1h); }
.doc-sheet-head { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.doc-sheet-name { font: 600 15px/1.2 var(--font-display); }
.doc-sheet .doc-open { display: block; }
.doc-sheet .doc-md {
  max-height: 52vh; overflow: auto; margin: 0;
  background: var(--shade); border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: var(--s-1h) var(--s-2); font: 12.5px/1.6 ui-monospace, Menlo, monospace; white-space: pre-wrap;
}

.who-pick { display: inline-flex; }
.who-pick .pick-suggest { min-width: 240px; }

/* A SECTION'S NAME, INSIDE THE STRIP IT BELONGS TO. The same size and weight as
   every other card title — a card's title is a card's title whether the card
   holds a list or a table. Links and Dossier carry theirs inside a toolbar
   strip rather than above the content, and that is the only difference between
   them and the cards beside them. */
.sec-label {
  display: inline-flex; align-items: baseline; gap: var(--s-1); flex: none;
  font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink);
}
.sec-label .dossier-count { font-size: 11.5px; color: var(--accent); }
.dossier-head { margin: 0 0 var(--s-1); }

/* THE MENU OPENS UNDER THE CONTROL, NOT OVER IT. Franz, 2026-09-16: "i want
   the dropdown to open properly and below the labels". A native select paints
   its list wherever the platform likes — over the label, over the field, in the
   middle of the screen on some machines — and it cannot be styled. `base-select`
   hands the popup back to the page: it is anchored under the control, it wears
   the product's own card, rule and accent, and it is still a real <select>, so
   every caller, every keyboard and every screen reader is unchanged.
   A browser that does not support it keeps the native menu and loses nothing. */
@supports (appearance: base-select) {
  select.pick, select.pick::picker(select) { appearance: base-select; }
  /* THE SAME CHEVRON A CARD FOLDS WITH. Franz, 2026-09-16: "i want the collapse
     style icon for dropdown icons mate". base-select draws its own triangle,
     which was the one glyph in the product nothing else used; it is hidden and
     the product's own chevron — the one on every foldable card — is painted
     where it always was. */
  select.pick::picker-icon { display: none; }
  select.pick { padding-right: var(--s-4); }
  .pick::picker-icon { color: var(--mute); transition: transform 140ms ease; }
  .pick:open::picker-icon { transform: rotate(180deg); }
  .pick::picker(select) {
    /* DOWNWARDS, UNDER ITS OWN FIELD. Franz, 2026-09-16: "why is audience and
       hook drop boxes opening upwards mate". Left to itself the picker takes
       whichever side of the control has more room, so a field low on a long
       page opened upwards over the field above it. Anchored to the bottom edge,
       and it flips only when there is genuinely no room below. */
    position-area: block-end span-inline-end;
    /* NO FLIP. Franz, 2026-09-16: "i want all dropdowns to open down use scroll
       if necessary i dont want them opening upwards at all". `flip-block` was
       the one thing still allowed to put a menu above its field, on exactly the
       fields low enough on the page for it to matter. Without it the picker
       stays under the control and takes the room below it — a percentage here
       is of the area the anchor leaves, so this is "320px, or whatever is left,
       whichever is smaller" — and scrolls inside itself for the rest. */
    position-try-fallbacks: none;
    margin-top: var(--s-half); padding: var(--s-half); max-height: min(320px, 100%); overflow-y: auto;
    border: 1px solid var(--rule); border-radius: var(--r-md);
    background: var(--card); box-shadow: 0 14px 34px rgb(0 0 0 / .45);
  }
  .pick option {
    display: flex; align-items: center; gap: var(--s-1);
    padding: var(--s-1) var(--s-1); border-radius: var(--r-sm);
    font-size: 13px; color: var(--ink-soft); background: none;
  }
  .pick option:hover, .pick option:focus { background: var(--shade); color: var(--ink); }
  .pick option:checked { color: var(--ink); }
  .pick option::checkmark { color: var(--accent); }
  .pick option:disabled { color: var(--mute); opacity: .55; }
  .pick optgroup { padding: var(--s-half) 0; color: var(--mute); font-size: 11.5px; }
}

/* An aside, not an instruction: the one hint that is a note about the files
   rather than a label on a control. Franz, 2026-09-16. */
.hint.is-aside { font-style: italic; }

/* --- the static ad form ----------------------------------------------------- */
/* TWO EQUALS, NOT SIXTY / FORTY. Franz, 2026-09-17: "copy on the left and
   creative on the right". `.create-top` is 6fr/4fr because the script form's
   left card asks eleven questions and its right one holds a strip of tiles.
   Here both sides are the work — one writes the words, the other decides the
   frame they land in — so a pair is a pair. */
/* EACH CARD ITS OWN HEIGHT. `stretch` is right for the script form, where the
   two cards are close in length; here the creative side carries a preview and
   the copy side does not, so stretching left a third of a screen of empty
   card under the flow. A card is as tall as what is in it. */
.create-pair { display: grid; gap: var(--s-3); align-items: start; grid-template-columns: minmax(0, 1fr); }
.create-pair > .c-card { margin: 0; display: flex; flex-direction: column; }
.create-pair.create-under { margin-top: var(--s-3); align-items: start; }
.create-pair.create-under > .sheet-go { margin: 0; }

@media (min-width: 1100px) {
  .create-pair { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* CREATIVE ONLY drops the copy column's tone, CTA and flow, so its brief card
   is narrower than the creative beside it rather than half-empty. */
@media (min-width: 1100px) {
  .is-creative-only .create-pair { grid-template-columns: minmax(0, 4fr) minmax(0, 6fr); }
}

/* THE SHAPE ROW, under the placement's own two rows and reading as a third
   step of the same decision: platform, ad type, shape. */
.shape-row { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.shape-say { margin: var(--s-1) 0 0; }

/* WHAT GOES ON IT. The same tick menu the audience picker opens, because it is
   the same kind of question and the workspace has one answer to it. */
/* A ROW THAT FILLS ITS WIDTH. Franz, 2026-09-17: "what goes on it should be
   like nice pills justify style but smart". A menu was one click too many for
   eight yes-or-no answers, and it hid the answers behind a button that could
   only say how many there were. Auto-fit columns so eight pills make an even
   block at any card width rather than a ragged wrap. */
.onc-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(134px, 1fr)); gap: var(--s-1); }
.onc-pill {
  display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-1) var(--s-1); border: 1px solid var(--rule); border-radius: var(--r-pill);
  background: transparent; color: var(--ink-soft); cursor: pointer;
  font: 500 13px/1.2 var(--font-body); text-align: left;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.onc-pill:hover { border-color: var(--mute); color: var(--ink); }
.onc-pill.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
/* SMART MEANS IT SAYS SO. Not greyed into silence: a pill nothing on this
   shape can carry keeps its name, loses its edge, and explains itself when
   tapped rather than doing nothing. */
.onc-pill.is-dead { color: var(--mute); border-style: solid; }
.onc-pill.is-dead.is-on { background: transparent; color: var(--mute); }
.onc-tick {
  width: 14px; height: 14px; flex: 0 0 14px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid currentColor; border-radius: 4px; opacity: .75;
}
.onc-pill.is-on .onc-tick { opacity: 1; }
.onc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.onc-say:not(:empty) { margin: var(--s-1) 0 0; }

/* ONE OF FIVE IS A ROW OF PILLS, NOT A MENU. Franz, 2026-09-17: "the ad style
   should be pill selection style too". A closed menu hides four of the five
   answers behind a click, and this is a choice worth seeing all of. */
.stylerow { display: flex; flex-wrap: wrap; gap: var(--s-1); }

/* THE LAYOUT PICKER. A drawing of the slots and the name under it — never the
   drawing alone, SPEC-UI-V1 §8. The thumbnail is what makes the name mean
   something; it is not a replacement for it. */
/* A DEFINITE MINIMUM, OR THERE IS ONLY EVER ONE COLUMN. `auto-fit` cannot work
   out how many tracks fit from a minimum of zero, so it makes one — which is
   how the layout picker became a single tile the width of the card. */
.layout-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--s-1); }
.layout-tile {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s-1); min-width: 0;
  padding: var(--s-1) var(--s-1) var(--s-half); border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--card); color: var(--ink-soft); cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.layout-tile:hover { border-color: var(--mute); color: var(--ink); }
.layout-tile.is-on { border-color: var(--accent); color: var(--ink); }
.layout-thumb { display: block; width: 100%; height: auto; border-radius: var(--r-sm); }
.layout-name { font: var(--t-label); letter-spacing: var(--tr-label); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.layout-say { margin: var(--s-1) 0 0; }

/* THE PREVIEW. It is the ad, drawn by the same compositor that draws the
   finished one, with a placeholder where the picture will go. Nothing here
   shimmers or blurs: a placeholder that looks like a result is how somebody
   ships a placeholder. */
.preview-frame {
  display: flex; align-items: center; justify-content: center;
  min-height: 200px; padding: var(--s-2); border: 1px solid var(--rule-faint); border-radius: var(--r-lg);
  background: var(--shade);
}
/* THE PREVIEW IS A VIEW, NOT THE PAGE. Capped in both directions so a 9:16
   does not make the card twice as tall as the one beside it. */
.ad-preview { display: block; max-width: 100%; max-height: 300px; width: auto; height: auto; border-radius: var(--r-sm); }
.preview-say { margin: var(--s-1) 0 0; }
.preview-field { margin-top: 0; }

/* THE TOOLBAR HOLDS STATION. Franz, 2026-09-17: "i also need a sticky tool bar
   in create mode too". At --header-h and under z-index 10, which is
   SPEC-UI-V1 §6 in full. The spacing is PADDING, not margin: a sticky
   element's margin is transparent and the form would scroll through the gap. */
.kinds-bar.is-stuck-bar {
  position: sticky; top: var(--header-h); z-index: 9;
  background: var(--paper);
  /* THE SAME AIR ABOVE THE PILLS AS BELOW THEM, and less of it.
     Above = the tabs' 12px margin plus this padding-top; below = this
     padding-bottom plus this margin. Both land on 22, which is why the two
     numbers here are not the same as each other. */
  margin: 0 0 var(--s-1);
  padding: var(--s-1) 0;
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease;
}
.kinds-bar.is-stuck-bar.is-stuck { border-bottom-color: var(--rule-faint); }

/* --- the ad card, and the stack under it ------------------------------------ */
/* THE AD, THEN THE COPY, THEN THE CREATIVE BESIDE BOTH. The left column is a
   stack of two cards rather than one, because what the ad IS and what it SAYS
   are answered at different moments and the first one governs the second. */
.create-stack { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
.create-stack > .c-card { margin: 0; }

/* A LABEL SITS OVER WHAT IT NAMES. `.c-card .field > .label` is indented 11px
   to line up with the text inside an input — right for a box, wrong for a row
   of pills, which start at the card's own edge. The label was hanging 25px to
   the right of the chips it named, on every pill row in the creative column. */
.c-card .field > .label.label-flush { padding-left: 0; }

/* --- the composer: a brief you can attach to and talk into ------------------ */
/* THE SAME BOX EVERY OTHER BRIEF ON THIS PAGE IS. Franz, 2026-09-17: "the
   creative brief section sucks everything wrong and offset check the similar
   boxes in other tabs like video to see how it should be done."
   He is right. The first version wrapped the textarea in a second bordered
   panel with a row of buttons under it, which is a different kind of object
   from every other field in the product — a box inside a box, with its own
   edge, its own radius and its own row. The script form's brief is one plain
   textarea with the count sitting INSIDE its bottom corner, and that is the
   convention.
   So this is `.counted` with one more thing in it: the count keeps the right
   corner and the verbs take the left, both inside the box, neither costing a
   line of height. Nothing new to learn and nothing that sits a pixel off. */
.composer { position: relative; display: block; }
.composer > .textarea { display: block; width: 100%; padding-bottom: var(--s-4); }
.composer.is-over > .textarea,
.composer.is-listening > .textarea { border-color: var(--accent); }
.composer-floor {
  position: absolute; left: var(--s-1); bottom: 5px;
  display: flex; align-items: center; gap: var(--s-nudge);
}
/* A SHAPE, NOT A PILL WITH A WORD IN IT. Square-ish, quiet until hovered, and
   the same 26px box for both so the two sit as a pair rather than as two
   buttons of different lengths. */
.composer-verb {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--mute); cursor: pointer;
  transition: color .15s ease, background .15s ease, border-color .15s ease;
}
.composer-verb:hover { color: var(--ink); background: var(--shade); border-color: var(--rule); }
.composer-verb.is-live { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.composer-count { position: absolute; right: var(--s-1h); bottom: 11px; margin: 0; }
/* The main brief carries a resize grip in that corner, so its count steps
   aside for it rather than sitting under it — the same allowance the script
   form's own brief makes. */
.composer > .brief-main ~ .composer-count { right: 22px; }
.composer-say:not(:empty) { margin: var(--s-1) 0 0; }
.composer-files { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: var(--s-1) 0 0; }

/* AN ATTACHMENT SAYS WHAT IT IS ON ITS OWN FACE. Never behind a hover: what a
   file is FOR is the whole of its usefulness to the thing that reads it. */
.ref-chip { display: inline-flex; align-items: center; gap: var(--s-1); max-width: 280px; padding: var(--s-half) var(--s-1) var(--s-half) var(--s-half); }
.ref-thumb { width: 20px; height: 20px; object-fit: cover; border-radius: var(--r-sm); display: block; }
.ref-kind { font: var(--t-label); letter-spacing: var(--tr-label); color: var(--mute); padding: 0 var(--s-half); }
/* A FILE NAME IS SOMEBODY ELSE'S TEXT. `.chip` capitalises, which is right for
   a label this product wrote and wrong for a name it was handed: BRAND.md is
   flat that a client's own words are never re-cased, and "Sprinkler-Head.Png"
   is that rule being broken on a file off their desktop. */
.ref-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px; text-transform: none; }
.ref-kind, .ref-role { text-transform: none; }
.ref-role {
  font: var(--t-label); letter-spacing: var(--tr-label);
  color: var(--accent); border: 1px solid var(--rule); border-radius: var(--r-pill);
  padding: var(--s-px) var(--s-1); background: transparent; cursor: pointer;
}
.ref-role:hover { border-color: var(--accent); }

/* --- the design picker ------------------------------------------------------ */
/* Each tile is the real compositor at tile size, so what is shown IS what is
   drawn. A row that scrolls rather than wraps: five designs are one decision
   and stacking them into two rows made it read as ten. */
/* ALL FIVE ON SCREEN. A row that scrolled sideways hid two of the five designs
   behind an edge, which is a choice somebody cannot make. The tiles share the
   width instead and the drawing scales with them. */
.design-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--s-1); }
.design-tile {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s-1); min-width: 0;
  padding: var(--s-1) var(--s-1) var(--s-half); border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--card); color: var(--ink-soft); cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.design-tile:hover { border-color: var(--mute); color: var(--ink); }
.design-tile.is-on { border-color: var(--accent); color: var(--ink); }
.design-thumb { display: block; width: 100%; height: auto; border-radius: var(--r-sm); }
.design-name { font: var(--t-label); letter-spacing: var(--tr-label); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The kind picker's rules went with the picker: what kind of ad this is, is
   read from the brief and the attachments now, and CREATIVE_KINDS lives on the
   Worker as the writer's vocabulary. */

/* --- the four questions the reading already answered ------------------------ */
/* Shut by default. A form that opens on every control it owns is the thing
   Franz called overwhelming, and the chevron is on the right where
   SPEC-UI-V1 §3 puts every collapse control in this product. */
.create-more { margin-top: var(--s-3); border-top: 1px solid var(--rule-faint); padding-top: var(--s-2); }
.create-more > summary {
  list-style: none; display: flex; align-items: center; justify-content: space-between;
  cursor: pointer; padding: var(--s-nudge) 0;
}
.create-more > summary::-webkit-details-marker { display: none; }
.create-more > summary .chev { color: var(--mute); display: inline-flex; transition: transform .18s ease; }
.create-more[open] > summary .chev { transform: rotate(180deg); }
.create-more > summary:hover .chev { color: var(--ink); }
.create-more-body { padding-top: var(--s-2); }
.create-more-body > .field + .field { margin-top: var(--s-2); }

/* --- a section that folds ---------------------------------------------------- */
/* Chevron on the RIGHT and no native marker, SPEC-UI-V1 §3 — the rule every
   other collapse in this product already follows. A folded row still carries
   its answer: hiding the control must never hide what it says. */
.fold + .fold { margin-top: var(--s-nudge); }
.create-creative > .fold { margin-top: var(--s-2); }
.fold > summary {
  list-style: none; display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-1h); cursor: pointer; padding: var(--s-1) 0;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold-head { display: flex; align-items: baseline; gap: var(--s-1); min-width: 0; }
.fold-head > .label { padding-left: 0; }
.fold-value {
  font: 400 12.5px/1.2 var(--font-body); color: var(--mute);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: opacity .15s ease;
}
.fold[open] .fold-value { opacity: 0; }
.fold > summary .chev { color: var(--mute); display: inline-flex; flex: 0 0 auto; transition: transform .18s ease; }
.fold[open] > summary .chev { transform: rotate(180deg); }
.fold > summary:hover .chev, .fold > summary:hover .fold-value { color: var(--ink); }
.fold-body { padding: var(--s-half) 0 var(--s-1h); }
.fold-body > .field { margin: 0; }
/* The fold's own summary already names it, so the control inside does not. */
.fold-body > .field > .label:first-child { display: none; }

/* THE PANEL KEEPS ITS OWN HEIGHT. Four sections opened in place pushed
   everything under them off the bottom of the screen. */
.create-more-body { max-height: 56vh; overflow-y: auto; padding-right: var(--s-half); }

/* --- what this client already has ------------------------------------------- */
.made-row { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.tile-made { text-align: left; }
/* A fixed square, like the reference lane. Without it a `.tile` in a wrapping
   flex row takes its title's full width and `aspect-ratio: 1` turns that into
   a box as tall as it is wide: two empty panels 220px high. */
.made-row .tile { width: 96px; flex: none; }

/* THE CHEVRON GOES TO THE FAR RIGHT, and it gets there by the head taking the
   room rather than by space-between, which a stray third flex child can defeat
   without anybody noticing. SPEC-UI-V1 §3. */
.fold-head { flex: 1 1 auto; }
.create-more > summary > .label { flex: 1 1 auto; }

/* Tags on the form, not only in the table afterwards. The same chips the
   audience picker uses, because it is the same kind of answer. */
.tagrow-chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); }
.tagrow-add { border-style: solid; color: var(--mute); }
.tagrow-add:hover { border-style: solid; color: var(--ink); }

/* Chrome still paints a marker for a flex summary, and it lands at the end —
   right beside the chevron this product draws itself. Two chevrons on one row
   is the native one, and this is how it goes. */
.fold > summary::marker,
.create-more > summary::marker { content: ''; }
.fold > summary::after,
.create-more > summary::after { content: none; }

/* --- three columns ----------------------------------------------------------- */
/* THE AD AND THE CREATIVE ARE THE SAME SIZE, and the preview is the narrow one
   beside them. Franz, 2026-09-17: "lets make three columns ... the ad and the
   creative should be same size and put preview on the right neatly".
   Two equals and a rail, collapsing to two and then to one rather than
   squeezing three columns onto a laptop. */
.create-trio { display: grid; gap: var(--s-3); align-items: start; grid-template-columns: minmax(0, 1fr); }
.create-trio > .c-card { margin: 0; }

@media (min-width: 1000px) {
  .create-trio { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* Until there is room for a third column the preview runs under both. */
  .create-preview { grid-column: 1 / -1; }
}
/* A RAIL, NOT A THIRD COLUMN. Franz, 2026-09-17: "i need preview column to be
   slimmer cause then otherwise this happens" — the flow's preset chips were
   breaking "Aspiration → Belonging" over two lines because a third of the page
   had gone to a picture that does not need it.
   A fixed width is the right shape and it is the shape this product already
   uses: the script view's activity rail is 350px beside `minmax(0, 1fr)`. The
   two working columns take everything else and stay equal. */
@media (min-width: 1420px) {
  .create-trio { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 290px; }
  .create-preview { grid-column: auto; }
}
@media (min-width: 1750px) {
  .create-trio { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 330px; }
}

/* IT FOLLOWS YOU DOWN. Under the app header and under the toolbar that is
   already sticky beneath it — SPEC-UI-V1 §6: sticky things stack, they never
   overlap, and everything starts at --header-h and sits below z-index 10. */
/* The whole rail follows you down, not only the picture in it: what is still
   missing is as useful beside the fields as the preview is. */
.create-rail {
  position: sticky; top: calc(var(--header-h) + 62px); z-index: 7;
  max-height: calc(100vh - var(--header-h) - 92px); overflow: auto;
}
.create-rail > .c-card { margin: 0; }
.create-preview > .fold { margin-top: 0; }
.create-preview .preview-frame { min-height: 0; }
.create-preview .ad-preview { max-height: 52vh; }

/* The creative-only form drops its copy column, so the first two stay equal. */


/* A FOLD THAT IS A CARD'S HEAD READS AT A CARD'S RANK. The preview sits beside
   The Ad and The Creative, so its heading is theirs — `.pf-head label`'s own
   size, face and colour, not a field label's. Four sizes, SPEC-UI-V1 §1. */
.fold.is-cardhead > summary { padding: 0 0 var(--s-nudge); }
.fold.is-cardhead > summary .label {
  font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink);
}
.fold.is-cardhead > summary .chev { color: var(--mute); }
.fold.is-cardhead .fold-body { padding: var(--s-2) 0 0; }

/* --- a finished static ad ---------------------------------------------------- */
.ad-final { display: block; max-width: 100%; max-height: 62vh; width: auto; height: auto; border-radius: var(--r-sm); }
.static-facts { display: flex; flex-direction: column; gap: var(--s-1); }
.fact-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1h); }
/* The Design row is a control, not a reading. It sits on the value side of
   the fact row and keeps that column's right edge. */
.fact-pickwrap {
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--s-nudge);
  margin-left: auto; min-width: 0;
}
.fact-pick {
  max-width: 100%; padding: var(--s-half) var(--s-1);
  font-size: 13px; text-align: right; text-align-last: right;
}
.ad-from { font-size: 11px; color: var(--mute); }

.fact-value { font-size: 13.5px; color: var(--ink-soft); text-align: right; }
.need-line { margin: var(--s-half) 0 0; font-size: 13px; color: var(--alarm); }
.is-saved { margin-left: var(--s-1); font-size: 11.5px; color: var(--mute); }
.problems { margin-top: var(--s-2); }
.problems > summary { list-style: none; cursor: pointer; }
.problems > summary::-webkit-details-marker { display: none; }

/* A BOX THE SIZE OF THE ANSWER IT WANTS. Franz, 2026-09-18: "make the cretive
   brief box longer mate so it looks better".
   `rows` does nothing here: `.textarea.grow` carries `field-sizing: content`,
   so an empty field is one line high whatever the attribute says and the box
   snapped back the moment it was rebuilt. A floor is the thing that actually
   holds — and it still grows past it, which is the point of `grow`.
   Describing a photograph — the light, the place, what is in frame — is three
   or four lines of writing, and a one-line slot asks for one line back. */
.textarea.grow.art-brief { min-height: 108px; }
.textarea.grow.brief-main { min-height: 132px; }

/* --- a choice whose options explain themselves ------------------------------- */
/* Five bare pills reading "Product Hero", "Statement", "Catalogue" asked
   somebody to choose between five things they had no way to tell apart. Every
   row carries the sentence that makes it a choice, and the first row is the
   honest default: leave it to the brief. */
.chooser { display: flex; flex-direction: column; gap: var(--s-nudge); }
.chooser-row {
  display: flex; align-items: flex-start; gap: var(--s-1); width: 100%;
  padding: var(--s-1) var(--s-1h); border: 1px solid transparent; border-radius: var(--r-md);
  background: transparent; color: var(--ink-soft); cursor: pointer; text-align: left;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.chooser-row:hover { background: var(--shade); color: var(--ink); }
.chooser-row.is-on { border-color: var(--accent); background: var(--shade); color: var(--ink); }
.chooser-dot {
  width: 15px; height: 15px; flex: 0 0 15px; margin-top: var(--s-px);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--rule); border-radius: 50%; color: var(--accent);
}
.chooser-row.is-on .chooser-dot { border-color: var(--accent); }
.chooser-body { display: flex; flex-direction: column; gap: var(--s-nudge); min-width: 0; }
.chooser-name { font: var(--t-label); letter-spacing: var(--tr-label); }
.chooser-says { font-size: 12.5px; line-height: 1.45; color: var(--mute); }

/* ── the brand kit, as a form ────────────────────────────────────────────────
   Franz, 2026-09-18: "we dont need a column there ... like fields to add stuff
   mate". Every other card on the profile holds rows of ONE kind, and a column
   is right for those. This one holds a hex code, a typeface, a phone number and
   a star rating, and the only question anybody asks a brand kit is which slot
   is still empty — which a column cannot answer and a form answers by looking.

   Two columns at desk width, one on a phone. The groups are labelled because
   the four of them are read at different moments: the colours when a creative
   is drawn, the contact when a call to action is written. */
.brand-card { gap: 0; }
.brand-group + .brand-group { margin-top: var(--s-2); }
.brand-group-name { display: block; margin-bottom: var(--s-1); }
.brand-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-1) var(--s-2);
}
@media (max-width: 720px) { .brand-grid { grid-template-columns: minmax(0, 1fr); } }
.brand-slot { min-width: 0; }
.brand-slot .label { font-size: 11.5px; }
.brand-slot .input { width: 100%; }

/* A COLOUR IS A SWATCH AND A HEX, SIDE BY SIDE. The picker is how somebody
   chooses one; the text is how somebody pastes the one their client emailed,
   which is what actually happens. An unset swatch reads as an empty well
   rather than as black, because black is a real answer and "nothing yet" is
   not the same answer. */
.brand-colour { display: flex; gap: var(--s-1); align-items: center; }
.brand-swatch {
  flex: none; width: 34px; height: var(--h-md); padding: 0; cursor: pointer;
  background: none; border: 1px solid var(--rule); border-radius: var(--r-sm);
}
.brand-swatch::-webkit-color-swatch-wrapper { padding: var(--s-half); }
.brand-swatch::-webkit-color-swatch { border: 0; border-radius: 4px; }
.brand-swatch.is-unset { background: repeating-linear-gradient(45deg, var(--shade), var(--shade) 4px, var(--card) 4px, var(--card) 8px); }
.brand-swatch.is-unset::-webkit-color-swatch { opacity: 0; }
.brand-hex { flex: 1 1 auto; min-width: 0; font-variant-numeric: tabular-nums; }

/* A slot that takes several — credentials come in twos and threes. Same row as
   every other list on this page, so they are recognisably the same control. */
.brand-rows { display: flex; flex-direction: column; gap: var(--s-px); margin-bottom: var(--s-1); }
.brand-row {
  display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-half) var(--s-1); border-radius: var(--r-sm); background: var(--shade);
}
.brand-row:hover { background: var(--card); }
.brand-row-txt {
  flex: 1 1 auto; min-width: 0; color: var(--ink);
  font: 400 12.5px/1.45 var(--font-body);
}
.brand-row .lled-x {
  flex: none; background: none; border: 0; cursor: pointer; padding: var(--s-nudge);
  color: var(--mute); line-height: 0;
}
.brand-row .lled-x:hover { color: var(--alarm); }

/* ── the product catalog ─────────────────────────────────────────────────────
   Migration 35. The tiles were 84px because a tile was a thumbnail and nothing
   else. A catalog tile carries what the photograph is FOR and what it is OF, so
   it is wide enough to say both without either being clipped — a tag nobody can
   read is a tag nobody will file by. */
.media-grid { grid-template-columns: repeat(auto-fill, minmax(146px, 1fr)); }
.media-tile .media-role {
  width: calc(100% - 12px); margin: 0 var(--s-1); font-size: 12px; padding: var(--s-half) var(--s-1);
}
/* THE TAGS ARE ONE LINE OF WORDS, not a stack of pills (Franz, 2026-10-16).
   Clicking the line opens the list to tick them on and off. */
.media-tagline {
  display: block; width: calc(100% - 12px); margin: 0 var(--s-1) var(--s-1); padding: var(--s-nudge) 0;
  border: 0; background: none; text-align: left; cursor: pointer;
  font: inherit; font-size: 12px; line-height: 1.4; color: var(--ink-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.media-tagline:hover { color: var(--ink); }
.media-tagline.is-empty { color: var(--mute); font-style: italic; }
.media-tagline.media-who { margin-bottom: 0; color: var(--ink); font-weight: 600; }
p.media-tagline { cursor: default; }
.media-tags {
  display: flex; flex-wrap: wrap; gap: var(--s-half); align-items: center;
  padding: 0 var(--s-1) var(--s-1); min-height: 20px;
}
/* The tag's cross is always there on a tile, unlike in a table row: a tile is
   already a thing you are looking at one of, and a control that appears on
   hover inside a grid of ninety is a control nobody finds. */
.media-tags .tag.is-live {
  display: inline-flex; align-items: center; gap: var(--s-half); cursor: pointer;
  font-size: 10.5px; padding: var(--s-nudge) var(--s-1);
}
.media-tags .tag.is-live:hover { border-color: var(--alarm); color: var(--alarm); }
.media-tags .tag-x { color: var(--mute); font-size: 12px; line-height: 1; }
.media-tags .tag.is-live:hover .tag-x { color: var(--alarm); }
.media-tagadd { font-size: 10.5px; padding: var(--s-nudge) var(--s-1); }
.media-novocab { margin: 0; font-size: 10.5px; }
/* The tag filter sits beside the card's name, not out with the search: it is
   part of what is being shown, the way a column filter is. */
.media-filter { display: inline-flex; margin-left: var(--s-1); }
.media-filter .pick-suggest { font-size: 11.5px; padding: var(--s-half) var(--s-1); }

/* ── THE PAGE A CLIENT OPENS ─────────────────────────────────────────────────
   A share link is often the first and only thing somebody outside the team
   ever sees of this product, and it is read on a phone as often as not. So it
   is one column, generous line length, and nothing to operate except the one
   box they might type in. No sidebar, no switcher, no navigation at all: there
   is nowhere else for them to go, and offering somewhere is how a guest ends
   up at a sign-in screen wondering what they did wrong. */
body.is-guest { background: var(--bg); }
.guest {
  max-width: 760px; margin: 0 auto; padding: var(--s-5) var(--s-2) var(--s-12);
  display: flex; flex-direction: column; gap: var(--s-2);
}
.guest-head { display: flex; flex-direction: column; gap: var(--s-1); }
.guest-mark { margin: 0 0 var(--s-1); font: var(--t-label); letter-spacing: var(--tr-label); color: var(--mute); }
.guest-mark .wordmark-mark { width: 16px; height: 16px; border-radius: 4px; }
.guest-title { margin: 0; font: var(--t-h1); line-height: 1.2; }
.guest-meta, .guest-role { margin: 0; font-size: 13px; color: var(--mute); }
.guest-role { color: var(--accent); }

/* Asked once, above the first place they can type, never as a gate. */
.guest-who { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-1); }
.guest-who-row { display: flex; gap: var(--s-1); align-items: center; flex-wrap: wrap; }
.guest-who-row .input { max-width: 260px; }

.guest-card {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: var(--s-2); display: flex; flex-direction: column; gap: var(--s-2);
}
.guest-card h2 { margin: 0; font: var(--t-h3); }
.guest-field { display: flex; flex-direction: column; gap: var(--s-half); }
.guest-field .lbl { font: var(--t-label); letter-spacing: var(--tr-label); color: var(--mute); }
.guest-field p { margin: 0; line-height: 1.55; }
/* The one line that is spoken, so it reads as speech and not as a field. */
.guest-vo { font-size: 16px; line-height: 1.5; }

.guest-scene-top { display: flex; align-items: baseline; gap: var(--s-1); flex-wrap: wrap; }
.guest-n { font: var(--t-label); letter-spacing: var(--tr-label); color: var(--accent); }
.guest-scene-title { font-weight: 600; }
.guest-time { margin-left: auto; font-size: 12px; color: var(--mute); }

.guest-thread { display: flex; flex-direction: column; gap: var(--s-1); }
.guest-note { border-left: 2px solid var(--rule); padding-left: var(--s-1); }
.guest-note-by { margin: 0 0 var(--s-nudge); display: flex; align-items: baseline; gap: var(--s-1); font-size: 12px; color: var(--mute); }
.guest-note-by > span:first-child { color: var(--ink); font-weight: 500; }
/* A guest name is a CLAIM, not an identity — anybody with the link can type
   any name — so it is labelled every time it is shown. */
.guest-tag {
  font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
  border: 1px solid var(--rule); border-radius: 3px; padding: 0 var(--s-half); color: var(--mute);
}
.guest-note-at { margin-left: auto; }
.guest-note-text { margin: 0; line-height: 1.5; white-space: pre-wrap; }

.guest-form { display: flex; flex-direction: column; gap: var(--s-1); }
.guest-form-go { display: flex; align-items: center; gap: var(--s-1h); }
.guest-gone { max-width: 520px; margin: 18vh auto; text-align: center; display: flex; flex-direction: column; gap: var(--s-1); }
.guest-gone h1 { margin: 0; font: var(--t-h2); }

@media (max-width: 560px) {
  .guest { padding: var(--s-3) var(--s-2) 72px; }
  .guest-card { padding: var(--s-2); }
}

/* The share panel: what is already out there, then what you can make. */
.share-row {
  display: flex; align-items: center; gap: var(--s-1h); justify-content: space-between;
  padding: var(--s-1) var(--s-1); border-radius: 5px;
}
.share-row:hover { background: var(--shade); }
.share-what { display: flex; flex-direction: column; gap: var(--s-px); min-width: 0; }
.share-role { font-size: 12.5px; color: var(--ink); }
.share-say { font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.share-verbs { display: flex; gap: var(--s-1); flex: none; }
.share-make {
  display: flex; gap: var(--s-1); flex-wrap: wrap;
  margin-top: var(--s-1); padding-top: var(--s-1); border-top: 1px solid var(--rule-faint);
}

/* The script's name, edited where it is read — same affordance as a client's. */
.script-title.is-editable { cursor: text; border-radius: 4px; }
.script-title.is-editable:hover { background: var(--shade); box-shadow: 0 0 0 6px var(--shade); }
.script-title.is-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.script-title-input {
  font: var(--t-title); letter-spacing: var(--tr-title);
  background: var(--shade); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 5px; padding: var(--s-nudge) var(--s-1);
  width: 100%; max-width: var(--w-modal);
}
@media (prefers-reduced-motion: reduce) {
  .scene-rail, .live-rail { transition: none; }
}

/* ── A LEAD FORM, READ AS THE THING IT BECOMES ───────────────────────────────
   A form is pasted field by field into Meta's or Google's builder, so the two
   jobs on this screen are "is this line short enough" and "give me that line".
   Everything here serves one of those: the count, and the copy button. The
   numbered rows and mono labels are the reference build Franz gave, and they
   earn their place — a form is a sequence, and the numbers are how two people
   on a call say which field they mean. */
.fm-card {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  overflow: hidden; margin: var(--s-2) 0 var(--s-3);
}
.fm-screen {
  display: flex; align-items: baseline; gap: var(--s-1h);
  padding: var(--s-1h) var(--s-2); background: var(--shade);
  border-top: 1px solid var(--rule-faint); border-bottom: 1px solid var(--rule-faint);
  font: var(--t-label); letter-spacing: var(--tr-label); color: var(--accent);
}
.fm-screen:first-child { border-top: 0; }
.fm-screen em {
  font: 400 11.5px/1.4 var(--font-body); letter-spacing: 0;
  color: var(--mute); font-style: normal;
}
.fm-f { position: relative; display: flex; gap: 0; padding: var(--s-1h) var(--s-12) var(--s-1h) var(--s-2); border-bottom: 1px solid var(--rule-faint); }
.fm-f:hover { background: var(--shade); }
.fm-n { flex: none; min-width: 38px; font-size: 16px; color: var(--accent); line-height: 1.3; }
.fm-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s-1); }
/* EVERY LABEL ON THIS SCREEN READS AT ONE RANK. Franz, 2026-09-23: "just as
   the feedback label is now, style and weight and size, needs to be elsewhere
   too, like greeting headline, greeting description, question 1, ending
   headline and all of it". Feedback was `--t-section` and every field label
   was `--t-label`, so the rail's heading outranked the things it was about. */
.fm-lab {
  display: flex; align-items: baseline; gap: var(--s-1);
  font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink);
}
/* THE COUNT SAYS WHOSE LIMIT IT IS ON HOVER, because calling AdEQ's cap a
   platform limit is the one thing this screen must never do. */
.fm-cnt { font-size: 11px; color: var(--mute); }
.fm-cnt.is-over { color: var(--alarm); font-weight: 600; }
.fm-ed {
  width: 100%; max-width: 720px; resize: none; overflow: hidden;
  background: none; border: 1px solid transparent; border-radius: 5px;
  padding: var(--s-half) var(--s-1); margin: -3px -6px;
  font: inherit; color: var(--ink); line-height: 1.5;
}
.fm-ed:hover { border-color: var(--rule-faint); }
.fm-ed:focus { outline: none; border-color: var(--accent); background: var(--paper); }
.fm-ed.is-tall { line-height: 1.55; }
.fm-copy {
  position: absolute; top: 11px; right: 14px;
  background: none; border: 1px solid transparent; border-radius: 5px;
  padding: var(--s-half) var(--s-1); font-size: 11px; color: var(--mute); cursor: pointer; opacity: 0;
}
.fm-f:hover .fm-copy, .fm-copy:focus-visible { opacity: 1; }
.fm-copy:hover { color: var(--accent); border-color: var(--rule); }

.fm-why { margin: 0; padding: 0 var(--s-2) var(--s-1) 56px; font-size: 11.5px; color: var(--mute); }
.fm-opts { list-style: none; margin: 0; padding: var(--s-nudge) var(--s-2) var(--s-1h) 56px; display: flex; flex-direction: column; gap: var(--s-half); }
.fm-opt {
  display: flex; align-items: flex-start; gap: var(--s-1);
  border: 1px solid var(--rule-faint); border-radius: 7px; padding: var(--s-half) var(--s-1); max-width: 560px;
}
.fm-radio { width: 13px; height: 13px; border: 1.5px solid var(--mute); border-radius: 50%; flex: none; margin-top: var(--s-half); }
/* A short answer is not an option somebody picks, so it does not look like one. */
.fm-opt.is-short { border-style: solid; color: var(--mute); font-style: italic; }
.fm-input {
  border: 1px solid var(--rule-faint); border-radius: 7px;
  padding: var(--s-1) var(--s-1); max-width: 380px; font-size: 13.5px; color: var(--mute);
}
.fm-cta {
  margin: var(--s-1) var(--s-2) var(--s-2); max-width: 380px; padding: var(--s-1);
  border-radius: 8px; background: var(--accent); color: var(--accent-ink);
  text-align: center; font-weight: 600;
}
/* The stuck version, and its spacing, live in screen-form.css. */
.fm-bar { display: flex; align-items: center; gap: var(--s-1); }
.fm-problems { display: flex; flex-direction: column; gap: var(--s-half); margin: 0 0 var(--s-1); }
.fm-problems-h { margin: var(--s-1) 0 0; font: var(--t-label); letter-spacing: var(--tr-label); color: var(--alarm); }
.fm-problem { margin: 0; font-size: 12.5px; color: var(--ink-soft); }
.fm-notes { margin-top: var(--s-3); }
.fm-notes h2 { font: var(--t-h3); margin: 0 0 var(--s-1); }
.fm-log { margin-top: var(--s-2); }
/* The form and the conversation about it, side by side — the same split and
   the same 350px rail the script page uses, because it is the same job. */
.fm-layout { display: grid; grid-template-columns: minmax(0, 1fr) calc(320px + (var(--s-3) - var(--gap-rail)) / 2); column-gap: var(--gap-rail); row-gap: var(--s-3); align-items: start; }
.fm-main { min-width: 0; }
.fm-rail {
  position: sticky; top: var(--stick-top);
  max-height: calc(100vh - var(--stick-top) - var(--pad-page-end)); overflow: auto;
  overscroll-behavior: contain;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: var(--stick-pad) var(--s-1h);
}
@media (max-width: 1100px) {
  .fm-layout { grid-template-columns: minmax(0, 1fr); }
  .fm-rail { position: static; max-height: none; }
}
.fm-note-form { display: flex; flex-direction: column; gap: var(--s-1); max-width: 620px; }
.fm-note-go { display: flex; align-items: center; gap: var(--s-1h); }

/* A SECTION IS A SCENE, so it is laid out like one: one header across the
   card, then the writing on the left and what people have said about it on
   the right. Same grid as `.scene` — writing capped at 68ch, the rest to the
   conversation — because a comment three screens from the line it is about is
   a comment nobody connects to anything. */
.fm-sec {
  border: 1px solid var(--rule); border-radius: var(--r-lg);
  background: var(--card); margin: 0 0 var(--s-2); overflow: hidden;
  scroll-margin-top: 130px;
}
/* ONE HEADER, FULL WIDTH. Inside the left column its tint stopped at the
   divider and left a ragged step halfway across every card. */
.fm-sec-head {
  display: flex; align-items: baseline; gap: var(--s-1h);
  padding: var(--s-1h) var(--s-2); background: var(--shade);
  border-bottom: 1px solid var(--rule-faint);
}
/* The section's own name matches them. It keeps the accent and the tinted
   band, which is what separates a section from a field, rather than being
   three points smaller than everything under it. */
.fm-sec-title { font: var(--t-section); letter-spacing: var(--tr-section); color: var(--accent); }
.fm-sec-hint { font: 400 11.5px/1.4 var(--font-body); color: var(--mute); font-style: normal; }
.fm-sec-body {
  /* STRETCH, NOT START. With `align-items: start` the side column is only as
     tall as its own contents, so its left border stopped partway down and the
     card looked torn in half. Both halves now run the full height of the
     section and the divider runs with them. */
  display: grid; grid-template-columns: minmax(0, 68ch) minmax(240px, 1fr);
}
/* The two halves end level. Without this the shorter column left the card
   with a tall empty foot, which is what "stuff ends too low" was. */
.fm-sec-main { min-width: 0; padding: var(--s-half) 0 var(--s-1); }
.fm-sec-side {
  min-width: 0; height: 100%;
  border-left: 1px solid var(--rule-faint);
  padding: var(--s-2) var(--s-2) var(--s-2); display: flex; flex-direction: column; gap: var(--s-1);
}
.fm-verbs { margin-left: auto; display: inline-flex; gap: var(--s-1); flex: none; }
/* Nothing in the side column may push past its own column. */
.fm-sec-side .textarea, .fm-sec-side .input, .fm-sec-side select { max-width: 100%; box-sizing: border-box; }
.fm-sec .fm-f:last-of-type { border-bottom: 0; }
.fm-sec .fm-opts:last-child { padding-bottom: var(--s-1); }
@media (max-width: 1000px) {
  .fm-sec-body { grid-template-columns: minmax(0, 1fr); }
  .fm-sec-side { border-left: 0; border-top: 1px solid var(--rule-faint); height: auto; }
}
}

/* THE CLIENTS HEADING, AND THE TWO CONTROLS THAT BELONG TO IT.
   Franz, 2026-09-23: "only place it in within the client page where franzer is
   currently written in light text next to clients header in the page and then
   the add client should be on its right".
   The picker sits where its name was already being printed, and Add a client
   sits after it — the order he asked for, and the order of the sentence: these
   clients, in this workspace, and one more. */

/* THIRTY A PAGE, AND THE PAGE AFTER IT. Right-aligned under the list, because
   it is about the list rather than about any row in it, and the count is read
   before the two buttons are pressed. */
.home-pager {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--s-1);
  margin: var(--s-2) 0 0;
}
.home-pager .hint { margin: 0; }
.home-pager .chip:disabled { opacity: .4; cursor: not-allowed; }

/* --- MOVING THE TYPE BY HAND -------------------------------------------------
   Franz, 2026-09-23: "turning the system semi-manual so we can edit the final
   creative a bit". The handles are DOM over the canvas, never drawn into it:
   anything drawn into the composite would be in the downloaded PNG, and the
   PNG is the ad.

   The stage exists because `.preview-frame` centres a capped canvas inside its
   own padding, so the frame's corners are not the ad's corners. The stage
   shrinks to the canvas and is the only box a handle can trust. */
/* THE STAGE BAR SITS ON THE CARDS' OWN RHYTHM. `.page-head` gives 18px above
   it and `.create-trio` puts 20px between the cards, so the bar takes the same
   20px underneath rather than resting on the first card. Scoped to this page:
   `.fm-bar` is shared with the form screen, which spaces it from its own
   layout. */
.ad-stagebar { margin: 0 0 var(--s-3); }

/* --- the ad page's own column shape ----------------------------------------
   Franz, 2026-09-24: "the on the creative and in facebook needs to be slimmer
   mate and the creative and activity needs to be wider cause it main".

   Create's three columns are two equals and a rail, because there the form IS
   the work. On an ad the picture is the work: the words beside it are short
   lines being checked, and the facts on the right are read once. So the middle
   takes everything the other two do not need, rather than a third. */
@media (min-width: 1000px) {
  .ad-trio { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); }
}
@media (min-width: 1420px) {
  .ad-trio { grid-template-columns: minmax(0, 330px) minmax(0, 1fr) 300px; }
}
@media (min-width: 1750px) {
  .ad-trio { grid-template-columns: minmax(0, 360px) minmax(0, 1fr) 320px; }
}
/* The canvas may use the room the middle column just gained. It was capped at
   62vh for a narrow column and would otherwise sit in the middle of a wide
   one with white space either side. */
@media (min-width: 1420px) {
  .ad-trio .ad-final { max-height: 74vh; }
}

/* THE CARD'S OWN CONTROLS, top right. `.rail-spacer` pushes them over, which
   is the pattern the form screen's bar already uses. */
.ad-head { align-items: center; gap: var(--s-1); }
.ad-head .btn { padding: var(--s-half) var(--s-1h); font-size: 13px; }

/* The renderer's own sentence about where it put the words. Quiet, because it
   is an explanation and not a warning. */
.ad-why { margin: var(--s-1) 0 0; color: var(--mute); }
.ad-why[hidden] { display: none; }
/* Thin contrast on a colour somebody picked on purpose. Said in the alarm
   colour because it is worth seeing, and said once rather than enforced. */
.ad-thin { margin: var(--s-1) 0 0; color: var(--alarm); }
.ad-thin[hidden] { display: none; }

/* A FOLD WEARING A CARD. The card keeps its own padding; the fold inside it
   must not add a second inset, or a closed section sits in a box twice its
   own height. */
.is-foldcard { padding-top: 0; padding-bottom: 0; }
.is-foldcard > .fold > summary { padding: var(--s-2) 0; }
.is-foldcard > .fold[open] > summary { padding-bottom: var(--s-1); }
.is-foldcard > .fold > .fold-body { padding-bottom: var(--s-2); }

/* THE LOG SCROLLS RATHER THAN RUNS. An ad picks up a row for every word
   changed and every block moved, so a week of work is a column taller than
   the screen and the card under it gets pushed out of reach. Capped at a
   sane depth with its own scroll; the Refresh button below it stays put. */
.ad-log { max-height: 320px; overflow-y: auto; overscroll-behavior: contain; }

.ad-stage { position: relative; display: inline-block; max-width: 100%; line-height: 0; }
.ad-handles { position: absolute; inset: 0; }
.ad-handles[hidden] { display: none; }

.ad-handle {
  position: absolute; box-sizing: border-box; cursor: grab;
  border: 1px solid color-mix(in srgb, var(--accent) 70%, transparent);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  touch-action: none;
  transition: background .12s ease, border-color .12s ease;
}
.ad-handle:hover,
.ad-handle:focus-visible {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border-color: var(--accent);
}
/* The focus ring sits OUTSIDE the box so it never covers the type underneath,
   which is the thing being positioned. */
.ad-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ad-handle:active { cursor: grabbing; }
/* A slot that has been moved reads as moved without being opened, so a person
   can see at a glance what is theirs and what is the layout's. */
.ad-handle.is-moved { border-style: solid; }
/* PINNED BY ITS TEMPLATE: type into it, but it does not move. Quieter than a
   movable block and with a text cursor rather than a grab, so the difference
   is legible before you try it rather than after. */
.ad-handle.is-fixed {
  cursor: text;
  border-style: solid;
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.ad-handle.is-fixed .ad-handle-grip { display: none; }

.ad-handle-tag {
  position: absolute; top: -9px; left: var(--s-half); padding: var(--s-px) var(--s-1);
  font: var(--t-label); font-size: 10.5px; line-height: 1.5;
  letter-spacing: var(--tr-label);
  color: var(--accent-ink); background: var(--accent); border-radius: var(--r-pill);
  white-space: nowrap; pointer-events: none;
  /* A NAME IS NEVER CUT SHORT. Franz, 2026-09-24: "the price label is cutoff,
     that should never happen". It used to truncate so a tag could not write
     over its neighbour, which traded one unreadable thing for another —
     "Pri…" names nothing. The tag keeps its whole word and is allowed out of
     its box; the selected block's tag is lifted above the rest, so the one
     you are working on is always the legible one. */
  z-index: 1;
  box-sizing: border-box;
}
.ad-handle.is-picked > .ad-handle-tag { z-index: 3; }
.ad-handle:hover > .ad-handle-tag { z-index: 2; }
/* THE GRIP IS THE CONTROL AND NO LABEL WOULD FIT, so it carries the handle's
   own title and aria-label. SPEC-UI-V1: an icon that IS the control is allowed;
   a glyph in front of a word it already says is not. */
/* Four of them, one per corner. Bigger than they look: the visible square is
   10px but the hit area is padded out to 22px, because a 10px target at the
   corner of a block is a target you miss. */
.ad-handle-grip {
  position: absolute; width: 10px; height: 10px;
  background: var(--accent); border: 1.5px solid var(--accent-ink);
  border-radius: 2px; box-sizing: border-box;
}
.ad-handle-grip::after { content: ''; position: absolute; inset: -6px; }
.ad-handle-grip.is-nw { left: -5px; top: -5px; cursor: nwse-resize; }
.ad-handle-grip.is-ne { right: -5px; top: -5px; cursor: nesw-resize; }
.ad-handle-grip.is-sw { left: -5px; bottom: -5px; cursor: nesw-resize; }
.ad-handle-grip.is-se { right: -5px; bottom: -5px; cursor: nwse-resize; }

/* THE EDITOR IS INVISIBLE ON PURPOSE. Styling it to look like the type would
   put a second, slightly wrong rendering of the headline on top of the real
   one — different face, different fit, different line breaks. It is a
   transparent caret over the block, and the canvas underneath is the preview.
   The caret takes the accent so it can be found on any photograph. */
.ad-edit {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; padding: 0; border: 0; resize: none; overflow: hidden;
  background: transparent; color: transparent; caret-color: var(--accent);
  font: inherit; outline: none;
}
.ad-edit::selection { background: color-mix(in srgb, var(--accent) 45%, transparent); }
.ad-handle:has(.ad-edit) { border-style: solid; cursor: text; }

/* THE SELECTED BLOCK, and it has to be findable at a glance while the
   inspector is talking about it. */
.ad-handle.is-picked {
  border-style: solid; border-width: 2px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* THE INSPECTOR. One row under the canvas rather than a panel floating over
   the ad: the ad is the thing being judged and nothing should sit on top of
   it. Wraps rather than scrolls, so nothing is ever out of reach. */
.ad-inspect {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-2);
  margin: var(--s-1h) 0 0; padding: var(--s-1h) var(--s-2);
  border: 1px solid var(--rule-faint); border-radius: var(--r-lg);
  background: var(--shade);
}
.ad-inspect[hidden] { display: none; }
.ad-ins-who {
  font: var(--t-label); letter-spacing: var(--tr-label); color: var(--accent);
  align-self: center;
}
.ad-ins-grp { display: flex; flex-direction: column; gap: var(--s-half); min-width: 0; }
.ad-ins-lab { font-size: 11px; color: var(--mute); }
.ad-ins-row { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.ad-ins-val { min-width: 42px; text-align: center; font-size: 13px; color: var(--ink); }

.ad-chip {
  padding: var(--s-half) var(--s-1); font-size: 12.5px; line-height: 1.4;
  color: var(--ink-soft); background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--r-pill); cursor: pointer;
}
.ad-chip:hover { border-color: var(--accent); color: var(--ink); }
/* The one that is currently true. Without it every chip looks equally likely
   and the row says nothing about the block it is pointed at. */
.ad-chip.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.ad-swatch {
  width: 22px; height: 22px; padding: 0; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 50%;
}
.ad-swatch:hover { border-color: var(--accent); }
.ad-swatch.is-on { box-shadow: 0 0 0 2px var(--accent); border-color: transparent; }

/* ANOTHER LAYOUT, SHOWN RATHER THAN NAMED. Franz, 2026-09-25: "they choose by
   looking instead of guessing". Five names in a dropdown ask somebody to
   picture five layouts; four thumbnails of their own ad ask them to point. */
.ad-tries {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-1);
  margin: var(--s-2) 0 0; padding-top: var(--s-2); border-top: 1px solid var(--rule-faint);
}
.ad-tries-h {
  flex: 1 0 100%; margin-bottom: var(--s-nudge);
  font-size: 11px; color: var(--mute);
}
.ad-try {
  display: flex; flex-direction: column; align-items: center; gap: var(--s-half);
  padding: var(--s-1); cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: var(--r-md);
}
.ad-try:hover { border-color: var(--rule); background: var(--shade); }
.ad-try-c { display: block; width: 74px; height: auto; border-radius: 3px; }
.ad-try-n { font-size: 11px; color: var(--ink-soft); }
/* The one in use is shown, not hidden: the row answers "what else could this
   be" and "what is it now" in one glance. */
.ad-try.is-on { border-color: var(--accent); background: var(--shade); cursor: default; }
.ad-try.is-on .ad-try-n { color: var(--accent); }
.ad-try[disabled] { opacity: .5; cursor: progress; }

/* Undo and redo sit with the other controls, not floating over the ad. */
.ad-steps { display: inline-flex; gap: var(--s-1); }
.ad-steps[hidden] { display: none; }


/* ── the header's two pickers, and the one button ───────────────────────────
   Franz, 2026-09-23: "i want the workspace picker pill and a new client picker
   pill next to in the header ... and also add client can be on the right of the
   client picker".

   NO MARGIN OF ITS OWN. Franz: "spacing on either side of the home button from
   the adeq and the worksapce pill should be equal and uniform". The header is a
   flex row with a 26px gap; a 4px margin here made the right-hand side of Home
   30px and the left 26px, which is exactly the kind of four-pixel lie that
   reads as wrong without being nameable. The gap does the spacing. */
.top-pickers {
  display: flex; align-items: center; gap: var(--s-1); flex: none;
}

/* SHORTER THAN THEY WERE. Franz: "make add cleint and pill with less height".
   The header is 52px and three 34px controls in it left almost no air above or
   below. 28px is the same height as the account avatar at the other end of the
   bar, so the row has one height in it rather than three. */
.top-pickers .ws-switch,
.top-pickers .top-add-client {
  height: 28px;
}
/* NO CAP, AND NOTHING CUT OFF. Franz, 2026-09-30: "the names in the pill above
   should never ever be hidden mate". A picker whose whole job is to say which
   workspace and which client you are looking at, saying "R&S Lawn & Spri…", is
   the one control in the header that has failed at the only thing it does.
   `.top-right` carries `margin-left: auto`, so these two take exactly the width
   of their names and the right-hand group is pushed along ahead of them. */
.top-pickers .ws-switch { padding: 0 var(--s-2); }
.top-add-client { flex: none; white-space: nowrap; padding: 0 var(--s-1h); }

/* ── the client pill ────────────────────────────────────────────────────────
   Franz, 2026-09-24: "this shoudl just be a picker and no highlgiht like this
   at all and clicking it just opens the dropdown".

   A DIFFERENT GROUND FROM THE WORKSPACE PILL, which is the filled accent one
   next to it. Two accent pills side by side is two primary controls, which is
   none — and the first version made that literal: accent text on an accent
   fill, so the pill rendered as a green blob with the client's name invisible
   inside it. This one is a surface fill with a rule around it, which is what
   every other secondary control in the product wears.

   NO FOCUS RING ON THE POINTER PATH. `:focus-visible` is the right selector
   for this: a keyboard tab still gets a visible ring, and a mouse click does
   not leave a blue box sitting on the header afterwards. The split button this
   replaced had two focusable halves, so clicking it drew a ring around one of
   them — which is the highlight he is pointing at. */
.client-switch {
  background: var(--card); border: 1px solid var(--rule); color: var(--ink-soft);
  font: 600 13px/1 var(--font-body);
}
.client-switch:hover { border-color: var(--ink-soft); color: var(--ink); filter: none; }
.client-switch:focus { outline: none; }
.client-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.client-switch .ws-chev { border-color: var(--mute); }
/* One line, never an ellipsis, for BOTH pills — `.ws-name` carries the
   truncation globally and the switcher menu still wants it, so this is scoped
   to the header rather than unset at the source. */
.top-pickers .ws-name { overflow: visible; text-overflow: clip; white-space: nowrap; }

/* A CLIENT IS NAMED, rather than "all of them" — said in INK, with no colour
   at all. Franz, 2026-09-23: "remove that green brodering from the clicnt
   picker". */
.client-switch.is-on { color: var(--ink); }

/* Below the width where the header has to choose, the gap closes rather than
   the names. They were capped at 116 and 150 here and half a client name is
   still a hidden name — the rule above has no exceptions. */
@media (max-width: 860px) {
  .top-pickers { gap: var(--s-1); }
}

/* ── THE HEADER BELOW DESKTOP: TWO ROWS, NOTHING CUT, NOTHING SIDEWAYS ────────
   The screenshot check (SPEC-LAYOUT-V1 §6), 2026-10-16: on every screen the
   header made the page 774px wide in a 390px phone and 882px in a 768px tablet,
   so the whole app scrolled sideways under the thumb. One row cannot hold the
   wordmark, Home, two named pills, Add a client, New workspace, Create, the
   bell, and the avatar at those widths, and the rule above says the names are
   never shortened.

   So below 1024 the header is two rows, always, so its height is known:
     row 1   the wordmark and Home, then Create, the bell, and the avatar
     row 2   the workspace pill, the client pill, and Add a client
   Row 2 scrolls sideways inside itself when the names are long, and the page
   never does. `.top-right` steps aside (display: contents) so its children can
   be placed in either row. --header-h is the header's height, so every sticky
   thing, the drawer, and the bell's panel move down with it. */
@media (max-width: 1023px) {
  /* --header-h grows to two rows here: web/layout.css, below the tokens. */
  .top {
    flex-wrap: wrap; align-content: flex-start; row-gap: 0; column-gap: var(--s-2);
    height: var(--header-h); min-height: 0; padding: 0 var(--s-2);
  }
  .top > .wordmark, .top > .tabs { height: 52px; }
  .top > .tabs .tab { height: 52px; padding: 0; }
  .top-right { display: contents; }
  .top-right > .head-slots { order: 1; margin-left: auto; align-self: center; }
  .top-right > .head-slots:not([hidden]) + .btn-create { margin-left: 0; }
  .top-right > .btn-create { order: 1; margin-left: auto; align-self: center; }
  .top-right > .notif { order: 2; align-self: center; }
  .top-right > .avatar { order: 3; align-self: center; }
  /* The break between the rows: a zero-height item a whole row wide. */
  .top::after { content: ''; order: 4; flex: 0 0 100%; height: 0; }
  .top-pickers {
    order: 5; flex: 1 1 0; min-width: 0; height: calc(var(--h-lg) + var(--s-1)); padding-bottom: var(--s-1);
    gap: var(--gap-inline); overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  }
  /* The pills keep their whole names; the ROW scrolls, never a pill. */
  .top-pickers > * { flex: none; }
  /* The 760px cap on the workspace pill ("max-width: 130px") hid half a name. */
  .top-pickers .ws-switch { max-width: none; }
  .top-pickers::-webkit-scrollbar { display: none; }
}

/* A SUGGESTION WITH NOTHING TO COMPARE IT AGAINST, shown rather than folded.
   See activity.js: a folded single line is the reviewer's words hidden behind
   a disclosure that opens onto one sentence. */
.comment-sugg {
  margin: var(--s-1) 0 0; padding: var(--s-1) var(--s-1);
  background: var(--shade); border-left: 2px solid var(--accent); border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.comment-sugg-h {
  display: block; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--mute); margin: 0 0 var(--s-half);
}
.comment-sugg-t { margin: 0; font-size: 13px; color: var(--ink); white-space: pre-wrap; }

/* A CLIENT'S MARK IN A MENU ROW, and on the pill that names one.
   Franz, 2026-09-24: "add mini logo of the clients too".

   18px in the menu: big enough to recognise a logo, small enough that the row
   keeps the height every other menu row in the product has. The row becomes a
   flex line so the mark and the words share a baseline instead of the mark
   sitting on the text's descender. */
.dt-pop-row.has-lead {
  display: flex; align-items: center; gap: var(--s-1);
}
.dt-pop-row.has-lead .mark { width: 18px; height: 18px; font-size: 8px; flex: none; }
.dt-pop-row-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* On the pill it is smaller again, because the pill is 28px tall and a mark
   that fills it leaves the name nowhere to sit. Hidden entirely on "All
   clients", where there is no one client to stand for. */
.client-pick-mark { display: inline-flex; flex: none; }
.client-pick-mark[hidden] { display: none; }
.client-pick-mark .mark { width: 17px; height: 17px; font-size: 7.5px; }

/* ── a client's own settings ────────────────────────────────────────────────
   Franz, 2026-09-24: a fifth tab, "and this is the client settings basiaclly
   and has to be sperate from the workspace settings". See client-settings.js
   for where the line between the two is drawn and why. */
/* TWO UP, NOT FOUR ACROSS. Franz, 2026-09-24, put the palette in the first
   card — four colours and two fonts, in pairs — and a quarter of the page is
   not wide enough for a swatch beside a hex. Four cards over two columns is
   also the one arrangement with no hole in it: at four across the fifth unit
   wrapped and left a card stranded alone on a second row. */
/* ONE BOARD, THE PROFILE'S. `.cs-board` was a plain grid with `align-items:
   start`, which is what left a card-shaped hole under every short card and
   pushed the tall ones onto rows of their own. It is `.profile-form` now — the
   same masonry the Profile uses — and this class only carries what is left
   over. See client-settings.js. */
.cs-form { margin: 0; }
.cs-row { display: flex; align-items: center; gap: var(--s-1h); margin: 0 0 var(--s-1); }
.cs-form > * > :last-child { margin-bottom: 0; }
.cs-row.is-end { justify-content: flex-start; margin-top: var(--s-half); }
/* A fixed label column, so three rows of different controls still read as a
   form rather than as three unrelated lines. */
.cs-lab { flex: none; width: 92px; font-size: 12.5px; color: var(--mute); }
.cs-row .input { flex: 1; min-width: 0; }
.cs-val { font-size: 13px; color: var(--ink); }
.cs-mark { display: inline-flex; }

/* LOGOS: the main one, every saved version, and Add, in one compact row
   (2026-10-04). Each cell is a 44px tile with its name underneath, so a white
   logo and a black one can be told apart without opening anything. */
.cs-logos { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: flex-start; flex: 1; min-width: 0; }
.cs-logo-cell { display: flex; flex-direction: column; align-items: center; gap: var(--s-half); width: 56px; }
.cs-logo-cell .mark-btn .mark { width: 44px; height: 44px; }
.cs-logo {
  width: 44px; height: 44px; padding: var(--s-half); border-radius: 8px; cursor: pointer;
  border: 1px solid var(--rule); background: var(--shade);
  display: inline-flex; align-items: center; justify-content: center;
}
.cs-logo:hover, .cs-logo:focus-visible { border-color: var(--accent); }
.cs-logo-img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.cs-logo-add { color: var(--mute); border-style: solid; background: none; }
.cs-logo-name {
  width: 100%; font-size: 11px; color: var(--mute); text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cs-logos-say { flex-basis: 100%; font-size: 12px; color: var(--mute); }
.cs-logos-say:empty { display: none; }
/* BUSINESS HOURS, AS A WEEK (2026-10-04). One framed control the same width,
   border, and radius as the inputs around it, so it lines up with them; seven
   rows of identical height whether the day is open or closed; and the line it
   saves at the foot, inside the frame. */
.brand-wide { grid-column: 1 / -1; }
.hours {
  display: flex; flex-direction: column;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: var(--s-half) var(--s-1h); background: var(--card);
}
.hours-row {
  /* The two times give way on a phone; 72 + 132 + 20 + 132 was wider than its card. */
  display: grid; grid-template-columns: 72px minmax(0, 132px) 20px minmax(0, 132px); justify-content: start;
  align-items: center; column-gap: var(--s-1); min-height: 42px;
  border-bottom: 1px solid var(--rule-faint);
}
.hours-day { display: inline-flex; align-items: center; gap: var(--s-1); font-size: 13px; color: var(--ink); cursor: pointer; }
.hours-on { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; }
.hours-time { height: 32px; padding: var(--s-half) var(--s-1); font-size: 13px; }
.hours-to { font-size: 12px; color: var(--mute); text-align: center; }
.hours-closed { display: none; font-size: 13px; color: var(--mute); grid-column: 2 / -1; }
.hours-row.is-closed .hours-time, .hours-row.is-closed .hours-to { display: none; }
.hours-row.is-closed .hours-closed { display: block; }
.hours-row.is-closed .hours-day { color: var(--mute); }
.hours-say { margin: 0; padding: var(--s-1) 0 var(--s-1); font-size: 12.5px; color: var(--mute); }
.hours-old { margin: var(--s-1) 0; }
/* In business since: the year, and the count it means, underneath. */
.brand-since { display: block; margin-top: var(--s-1); padding-left: 0; font-size: 12px; color: var(--mute); }
.brand-since:empty { display: none; }

/* A font and its weight, as one row. */
.brand-font { display: flex; gap: var(--s-1); align-items: center; }
.brand-font > input { flex: 1 1 auto; width: auto; min-width: 0; }
/* The app swaps every <select> for its own dropdown button, which then took
   the whole row; the weight gets a fixed slot beside the name instead. */
.brand-font > select, .brand-font > button { flex: 0 0 124px; width: 124px; min-width: 0; }

.cs-colours { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.cs-dot {
  width: 20px; height: 20px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--rule); padding: 0; background: var(--shade);
}
.cs-dot.is-on { outline: 2px solid var(--accent); outline-offset: 2px; }
.cs-dot:disabled { cursor: default; opacity: .5; }
/* "None" is a ring rather than a colour, because an empty swatch and a dark
   swatch look the same on this background. */
.cs-dot.is-none { background: none; border-style: solid; }

/* The lines this client works in: a column of ticks, not a table. The table is
   the workspace's, where the list is maintained; here it is only a choice. */
.cs-lines { display: flex; flex-direction: column; gap: var(--s-1); margin: 0 0 var(--s-1); }
.cs-line { display: flex; align-items: center; gap: var(--s-1); font-size: 13px; color: var(--ink-soft); cursor: pointer; }
.cs-line:hover { color: var(--ink); }
.cs-line input { accent-color: var(--accent); }

/* Archiving is not deleting, so it is not red until it is pointed at. */
.chip.is-danger-quiet:hover { color: var(--alarm); border-color: var(--alarm); }

/* SOURCE MATERIAL, under the client's own settings.
   Franz, 2026-09-24: Profile is "the exact place where the writer would" look;
   this is everything the brief was built FROM. A heading and a line, because
   four cards appearing under three unrelated ones needs to say what they are. */
/* `.cs-source`, `.cs-source-h` and `.cs-source-say` went with the two section
   headings on 2026-09-30: one board has nothing to head. */

/* HALF AND HALF, NOT FOUR FULL-WIDTH BANDS. Franz, 2026-09-24: "all the cards
   dont need to be full iwdth do it smartly mate half an fhalf for a few are
   finw too".
   A links table and a dossier are both lists of short rows, so a full-width
   card spends two thirds of its width on nothing and makes the page scroll for
   no reason. Two up, and each one still packs to its own height — below 900px
   they stack, because half of 900 is narrower than a URL. */
/* AND THE ROW GAP STAYS ZERO. cardBoard packs this board by giving the grid
   one-pixel rows and telling each card to span its own height plus twenty — so
   a row gap is counted once per PIXEL of card, not once per card. `gap: var(--s-2)`
   here turned a 1,600px board into a 13,771px one with an empty screen and a
   half of nothing under it, and hid Media and Templates below the fold. Only
   the column gap is ours to set. */
/* `.cs-source-board` and `.cs-vocab-board` merged into the one board on
   2026-09-30. The warning above is why `.cs-form` sets no gap of its own:
   `.profile-form` already carries `column-gap: var(--s-3); row-gap: 0`, and a row
   gap on a cardBoard grid is counted once per PIXEL of card. */
/* Team and Clients, two up over the settings board. Same masonry rules as
   every other board on this page, and the same reason the row gap is zero. */
.ws-top-board { column-gap: var(--gap-card); row-gap: 0; margin: 0 0 var(--s-3); }
/* An invited row says so and carries its code beside it. */
.ws-invited { display: inline-flex; align-items: center; gap: var(--s-1); min-width: 0; }

/* ── the campaigns card ─────────────────────────────────────────────────────
   A list of short names, so it is a list and not a table: four columns of
   chrome around one word each would be more furniture than fact. It scrolls
   at ten rows rather than growing the card past its neighbours, because this
   board packs by height and one long card drags the whole row down with it. */
.cs-camps .cs-camp-list { max-height: 232px; overflow: auto; margin: 0 0 var(--s-1); }
.cs-camp {
  display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-half) 0; border-bottom: 1px solid var(--rule);
}
.cs-camp:last-of-type { border-bottom: 0; }
.cs-camp-n {
  flex: 1; min-width: 0; font-size: 13px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cs-camp-v { display: inline-flex; align-items: center; gap: var(--s-half); flex: none; }
.cs-camp-put {
  border: 0; background: none; padding: var(--s-nudge) var(--s-half); cursor: pointer;
  font: 500 12px/1 var(--font-body); color: var(--mute);
}
.cs-camp-put:hover { color: var(--ink); }
/* A put-away campaign is still readable — it is dimmed, not hidden, because
   the only reason to look at this card is to find one and bring it back. */
.cs-camp.is-off .cs-camp-n { color: var(--mute); text-decoration: line-through; }
.cs-camp.is-editing { padding: var(--s-nudge) 0; }
.cs-camp-note { margin: var(--s-1) 0 0; }
.cs-line-new { margin: var(--s-1) 0 0; }
/* The palette inside "This client": it is a continuation of the three rows
   above it, so it is separated by a rule rather than by a heading. */
.cs-look { margin: var(--s-1h) 0 0; padding: var(--s-1h) 0 0; border-top: 1px solid var(--rule); }
.cs-look .brand-group { margin: 0 0 var(--s-1); }
.cs-look .brand-group:last-of-type { margin-bottom: 0; }
/* TWO ACROSS, AND THE CARD IS WIDENED TO AFFORD IT. Six stacked fields made
   "This client" two and a half times the height of the three cards beside it;
   a swatch and a hex will not fit in a quarter of the page, so the card takes
   half and the pairs stay pairs. */
/* Nothing spans now — two columns is the whole layout. */


/* ── a toolbar in the Create bar's clothes ──────────────────────────────────
   Worn by the client's Work tab and by Home's Latest work. Franz, 2026-09-25:
   "the home page tool bar is old style now we need it as same look at the one
   in work for each client". They are the same strip doing the same job over
   the same rows, so they are one set of clothes with one name — `.work-body`
   was that name and it only ever fitted one of them.

   Franz, 2026-09-24: "using this create bar as a new style tool bar in the new
   unified work tab now ... and the search bar and camapgin picker and wahtever
   else types are there as roundd pills in create bar".

   The Create bar is three rounded pills and a dropdown on a quiet strip, and
   it is the thing somebody's eye already knows on a client page. This makes
   Work's strip the same object: the type segments become individual pills
   rather than one joined bar, and the search becomes a pill instead of a
   bordered field.

   ONE RULE, NOT A COPY OF THE CREATE BAR'S. The two strips are built by
   different functions (cardToolbar here, a hand-rolled row there), so what is
   shared is the LOOK, stated once, under the screen that uses it. */
/* THE SAME BAR, SO THE SAME SPACING AND THE SAME BEHAVIOUR. Franz,
   2026-09-24: "the create page tool bar or create is should the bar and up ad
   down spacing should be for the one in work too". Every number below is
   copied from `.kinds-bar.is-stuck-bar` and must stay copied from it: two bars
   that look alike and are spaced 3px apart look broken in a way nobody can
   name. Padding rather than margin above, because a sticky element's margin is
   transparent and the rows would scroll through the gap. */
.bar-toolbar .dt-toolbar {
  position: sticky; top: var(--header-h); z-index: 9;
  background: var(--paper);
  gap: var(--s-1); margin: 0 0 var(--s-1); padding: var(--s-1) 0; flex-wrap: wrap;
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease;
}
/* Only once it is actually holding station, and never on a page short enough
   not to scroll — Franz, 2026-09-24: "i dont want this line below the tool bar
   mate". */
.bar-toolbar .dt-toolbar.is-stuck { border-bottom-color: var(--rule-faint); }
/* A STICKY BAR PINS ONLY INSIDE ITS PARENT (Franz, 2026-11-02: "i hope the
   sticky scroll is proper including the toolbar tab in all"). The Briefs bar
   sat in .tk-head and the Profile, Files, and Settings bars in their own
   .bar-toolbar, each as tall as the bar, so each scrolled away with the page.
   Those wrappers step aside and the bar pins against the whole tab. */
.tk-page > .tk-head,
.cm-tabs > .bar-toolbar { display: contents; }

/* AND THE OTHER TWO TABS START WHERE THOSE TWO DO. Franz, 2026-09-24: "same
   starting spacing from the top for settings and profile mate".
   Create and Work both open with a bar that pads itself by 9px so its sticky
   background has something to be; Profile and Settings open with a card, which
   pads itself by nothing — so all four measured the same and only two of them
   LOOKED it. The 9px is the bars', spelled again here rather than shared,
   because on those two it is ordinary page air and on these it is the inside
   of a bar. */
[data-screen="client"] .cs-form,
[data-screen="client"] .ctx-board { padding-top: var(--s-1); }
/* The joined segmented bar becomes separate pills, the way the Create bar's
   are: same height, same radius, same accent when chosen. */
.bar-toolbar .dt-toolbar .bar {
  display: flex; gap: var(--s-1); background: none; border: 0; padding: 0;
}
.bar-toolbar .dt-toolbar .bar-seg {
  height: var(--h-md); padding: 0 var(--s-2); border-radius: var(--r-pill);
  border: 1px solid var(--rule); background: var(--card); color: var(--ink-soft);
  font: 500 13px/1 var(--font-body); display: inline-flex; align-items: center;
}
.bar-toolbar .dt-toolbar .bar-seg:hover { border-color: var(--ink-soft); color: var(--ink); }
.bar-toolbar .dt-toolbar .bar-seg.is-on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.bar-toolbar .dt-toolbar .pill,
.bar-toolbar .dt-toolbar .chip {
  height: var(--h-md); border-radius: var(--r-pill); display: inline-flex; align-items: center;
}
/* The search is a pill too, and it grows into whatever room is left rather
   than sitting at a fixed width with a gap after it. */
.bar-toolbar .dt-toolbar .dt-search {
  height: var(--h-md); border-radius: var(--r-pill); padding: 0 var(--s-1h);
  flex: 1 1 200px; max-width: var(--w-pop); min-width: 150px;
}
.bar-toolbar .dt-toolbar .dt-search-in { font-size: 13px; }

/* The Creative Brief's "Write It For Me" and the line that says what it did. */
.art-write-row { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; margin: calc(-1 * var(--s-half)) 0 var(--s-1); }
.art-write-row .art-say { margin: 0; }

/* THE ZING. Where a notification lands (dom.js zingTo). A ring in the accent
   that flares out and settles twice, over a wash that fades — enough to catch
   the eye mid-scroll, gone in under three seconds, never left behind. Outline
   rather than border so nothing it lands on changes size. */
@keyframes zing {
  0%   { outline-color: color-mix(in srgb, var(--accent) 0%, transparent); outline-offset: 10px;
         box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  18%  { outline-color: var(--accent); outline-offset: 3px;
         box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
  36%  { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  56%  { box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
  80%  { outline-color: var(--accent); outline-offset: 3px; }
  100% { outline-color: color-mix(in srgb, var(--accent) 0%, transparent); outline-offset: 3px; }
}
@keyframes zing-wash {
  0%, 30% { background-color: color-mix(in srgb, var(--accent) 16%, transparent); }
  100%    { background-color: transparent; }
}
.zing {
  outline: 2px solid transparent;
  animation: zing 2.4s cubic-bezier(.2,.7,.2,1) both;
}
/* A note or a reply gets the wash too; a whole section or card is too big a
   thing to tint and the ring alone reads as "here". */
.comment.zing, .comment-reply.zing { border-radius: var(--r-sm); animation: zing 2.4s cubic-bezier(.2,.7,.2,1) both, zing-wash 2.4s ease-out both; }
@media (prefers-reduced-motion: reduce) {
  .zing, .comment.zing, .comment-reply.zing { animation: none; outline: 2px solid var(--accent); outline-offset: 3px; }
}

/* ── Still Needs (web/needs-card.js) ─────────────────────────────────────────
   Franz, 2026-10-03: a card where Activity is, with Activity under it, on
   every piece of work. It is a card INSIDE the rail on scripts and forms and a
   card in the stack on a static, so it carries its own frame either way. The
   accent edge is the one thing that marks it as waiting on somebody; the rest
   is the rail's own type so it reads as part of the same column. */
.rail-card.nd-card { border-color: var(--accent); }
.rail-card.nd-card.is-empty { border-color: var(--rule); }
.nd-none { margin: 0; }
.nd-card[hidden] { display: none; }
.nd-count { font: 500 12px/1 var(--font-body); color: var(--accent); }
.nd-item { padding: var(--s-1) 0 var(--s-1); border-top: 1px solid var(--rule); }
.nd-item:first-child { border-top: 0; padding-top: var(--s-nudge); }
.nd-text { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink); }
.nd-where { display: block; margin-top: var(--s-half); font-size: 11.5px; color: var(--mute); overflow-wrap: anywhere; }
.nd-acts { display: flex; align-items: center; gap: var(--s-1h); margin-top: var(--s-1); }
.nd-acts .btn { padding: var(--s-half) var(--s-1h); font-size: 12.5px; }
.nd-acts .linkbtn { font-size: 12.5px; color: var(--ink-soft); }
.nd-fill { display: flex; gap: var(--s-1); margin-top: var(--s-1); }
.nd-fill[hidden] { display: none; }
.nd-fill .input { flex: 1; min-width: 0; padding: var(--s-1) var(--s-1); font-size: 13px; }
.nd-fill .btn { padding: var(--s-half) var(--s-1h); font-size: 12.5px; white-space: nowrap; }
/* On a static the card sits in the stack beside other cards, not in a rail. */
.create-stack .rail-card { margin-bottom: 0; padding: var(--s-2) var(--s-2); }  /* the stack's own gap spaces it */

/* ── rolling names (2026-10-03) ───────────────────────────────────────────
   dom.js rollText(). A name that does not fit slides left as a seamless
   loop — the text, a thin muted bar, the text again — instead of ending in
   an ellipsis. Only an overflowing name moves: `.is-always` rolls all the
   time, `.is-over` alone rolls while pointed at. The fran7esque player's
   MarqueeText, ported. */
/* CLIP, AND IT HAS TO WIN. Most rows a name sits in already say
   `text-overflow: ellipsis` at screen specificity, and an ellipsis applied to
   an atomic inline box swaps the WHOLE moving track for "…". */
.roll { display: block; overflow: hidden; white-space: nowrap; min-width: 0; text-overflow: clip !important; }
.roll-track { display: inline-flex; align-items: center; column-gap: var(--s-2); will-change: auto; }
.roll-sep { flex: none; width: 10px; display: inline-flex; justify-content: center; }
.roll-sep::before { content: ''; display: block; width: 2px; height: .85em; border-radius: 1px; background: var(--mute); opacity: .55; }
@keyframes adeq-roll { from { transform: translateX(0); } to { transform: translateX(var(--roll-shift, -100%)); } }
.roll.is-always .roll-track,
.roll.is-over:hover .roll-track,
.roll.is-over:focus-visible .roll-track { animation: adeq-roll var(--roll-dur, 10s) linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .roll .roll-track { animation: none !important; }
  .roll .roll-sep, .roll .roll-text + .roll-sep + .roll-text { display: none; }
  .roll .roll-track { display: inline; }
  .roll { text-overflow: ellipsis !important; }
}

/* ── a card in the rail (web/rail-card.js) ───────────────────────────────────
   Franz, 2026-10-03: Still Needs and Activity are "two separate cards and
   collapsible each". The rail stops being one framed box: `.is-cards` takes its
   frame off and keeps everything that made it work — sticky, its own scroll,
   the overscroll guard — so each card inside carries the frame instead. */
.fm-rail.is-cards, .live-rail.is-cards {
  background: none; border: 0; border-radius: 0; padding: 0;
}
.rail-card {
  margin: 0 0 var(--s-1h); padding: var(--s-2) var(--s-2);
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
}
.rail-card:last-child { margin-bottom: 0; }
.rail-card > summary.rc-head { min-height: 20px; }
.rail-card[open] > summary.rc-head { margin-bottom: var(--s-1h); }
/* THE PROFILE'S CARD TITLE. Franz, 2026-10-03: "the headings of still needs
   and activity should be like the profile card labels". The same token
   `.pf-head label` and `.sec-label` use, so the three cannot drift apart. */
.rc-title { font: var(--t-section); letter-spacing: var(--tr-section); color: var(--ink); }
.nd-count { font-size: 11.5px; }
.rc-extra { display: inline-flex; align-items: center; gap: var(--s-1); margin-left: auto; }
/* The chevron follows the extras rather than pushing them off the edge. */
.rail-card > summary.rc-head::after { margin-left: var(--s-half); }
.rc-body .fm-log, .rc-body .live-body { margin-top: 0; }
/* THE CARD'S ICON, LEFT OF ITS TITLE (Franz, 2026-11-04). The same glyph is
   the card's button when the column folds (rail-dock.js), so it is there to be
   learnt, not decoration. Quiet ink; the title stays the loudest thing. */
.rail-card > summary.rc-head { display: flex; align-items: center; gap: var(--s-1); }
.rc-ic { display: inline-flex; flex: none; width: 18px; justify-content: center; color: var(--ink-soft); }

/* ── the right column folds to a bar (web/rail-dock.js, 2026-11-04) ──────────
   The fold button sits on top of the rail, level with the tab row. Shut,
   the rail is a 52px bar, one button a card. The
   left column takes the room. Below 1024px the rail is under the page, so
   there is nothing to fold and the button goes. */
.rd-bar { display: none; }
/* The fold button's own row on top of the rail: on the right margin open, centred over the bar shut. */
.rd-head { display: flex; justify-content: flex-end; margin-bottom: var(--s-1); }
.rd-toggle {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--s-4); height: var(--s-4); padding: 0;
  border: 0; border-radius: var(--r-pill); background: none; color: var(--ink-soft); cursor: pointer;
}
.rd-toggle:hover, .rd-btn:hover { background: var(--shade); color: var(--ink); }
.rd-toggle:focus-visible, .rd-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 1023px) { .rd-head { display: none; } }
@media (min-width: 1024px) {
  .script-layout.is-rail-shut, .fm-layout.is-rail-shut, .wk-layout.is-rail-shut { grid-template-columns: minmax(0, 1fr) 52px; }
  .rd-rail.is-shut { padding: 0; background: none; border: 0; border-radius: 0; overflow: visible; max-height: none; }
  .rd-rail.rd-rail.rd-rail.is-shut > :not(.rd-bar):not(.rd-head) { display: none; }
  .rd-rail.is-shut > .rd-head { justify-content: center; }
  .rd-rail.is-shut > .rd-bar {
    display: flex; flex-direction: column; align-items: center; gap: var(--s-half);
    padding: var(--s-1) 0; border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
  }
}
.rd-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0; border: 0; border-radius: var(--r-pill);
  background: none; color: var(--ink-soft); cursor: pointer;
}
.rd-count {
  position: absolute; top: var(--s-nudge); right: 0; min-width: var(--s-2); height: var(--s-2); padding: 0 var(--s-half);
  border-radius: var(--r-pill); background: var(--accent); color: var(--accent-ink);
  font: 700 10px/16px var(--font-body); text-align: center;
}

/* ── every dropdown is the product's own menu (2026-10-03) ──────────────────
   dom.js enhanceSelect(). Franz: "garbage dropdown boxes ... fix everywhere".
   The real <select> stays in the page as the truth and is hidden here; the
   button in front of it wears whatever classes the select wore (`input`,
   `pick`, `pick-line`), so every field keeps its size and place, and opens the
   same popover every other menu uses. */
.menu-native {
  position: absolute !important; width: 1px !important; height: 1px !important;
  margin: 0 !important; padding: 0 !important; border: 0 !important;
  opacity: 0 !important; pointer-events: none !important; overflow: hidden; clip-path: inset(50%);
}
.menu-btn {
  display: inline-flex; align-items: center; justify-content: space-between; gap: var(--s-1);
  text-align: left; cursor: pointer; background-image: none !important; padding-right: var(--s-1h); min-width: 0;
}
.menu-btn:not(.input) {
  border: 1px solid var(--rule-faint); border-radius: var(--r-md); background: var(--paper);
  padding: var(--s-1) var(--s-1); font-size: 13px; color: var(--ink);
}
.menu-btn:not(.input):hover { border-color: var(--rule); }
.menu-btn-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-btn-chev { flex: none; display: inline-flex; color: var(--mute); transition: transform 140ms ease; }
.menu-btn:hover .menu-btn-chev { color: var(--ink-soft); }
.menu-btn.is-placeholder .menu-btn-t { color: var(--mute); font-style: italic; }
.menu-btn:disabled { cursor: default; opacity: .6; }
.filters .menu-btn.input { flex: 0 0 auto; min-width: 150px; }
.newclient .menu-btn.input { flex: none; width: auto; min-width: 210px; }
.menu-pop .menu-list { display: flex; flex-direction: column; max-height: 320px; overflow-y: auto; }
.menu-pop .dt-pop-row.is-placeholder .dt-pop-row-t { color: var(--mute); font-style: italic; }
.menu-pop .dt-pop-row[hidden], .menu-pop .dt-pop-h[hidden] { display: none; }

/* ── a pointer on everything you can press (2026-10-03) ─────────────────────
   Franz: "no cursor on hover mate". Zero specificity on purpose (`:where`), so
   any control that already chose a cursor — a card you drag, a design you
   zoom into — keeps its own. */
:where(button, [role="button"], summary, a[href], label[for], select,
  input[type="checkbox"], input[type="radio"], input[type="file"], input[type="date"],
  .chip, .seg, .tab, .dt-pop-row, .linkbtn):not(:disabled):not([aria-disabled="true"]) { cursor: pointer; }

/* ── a side panel you can widen (2026-10-03) ────────────────────────────────
   dom.js panelResize(). A hairline on the panel's left edge that thickens to
   the accent when pointed at or dragged; the cursor says what it does. */
.panel-grip {
  position: fixed; top: var(--header-h); bottom: 0; z-index: 10; width: 8px;
  cursor: col-resize; touch-action: none; background: none;
}
.panel-grip::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 3px; width: 2px;
  background: transparent; transition: background .12s ease;
}
.panel-grip:hover::after, .panel-grip.is-dragging::after, .panel-grip:focus-visible::after { background: var(--accent); }
.panel-grip:focus-visible { outline: none; }
html.is-resizing, html.is-resizing * { cursor: col-resize !important; user-select: none !important; }
html.is-resizing .tk-panel { transition: none !important; }

/* ── EVERY BOX ON A CARD IS ROUND, 36 TALL, AND ITS WORDS SIT IN THE MIDDLE ──
   Franz, 2026-10-06: "triple check that all help text is aligned properly in
   boxes, and I want rounded style, not squareish". The boxes on the boards
   were five sizes (24, 30, 37, 37, 41) on three corners (5, 6, 10), and a
   textarea's placeholder sat a pixel above the line the picker beside it
   centred on. Now: every add box, picker, and one-line input on a Profile or
   Settings card is 36px, fully rounded while it is one line, with its text on
   the same centre. A box that grows to several lines keeps 18px corners, so
   it is a rounded rectangle rather than a stretched pill. Inputs inside a
   table row are left alone: those are edited in place at the row's height. */
.profile-form .lled-new, .profile-form .lled-name, .profile-form .lled-url-in {
  box-sizing: border-box; min-height: 36px;
  border-radius: 18px;
  /* 7 + 20 + 7 + the border is exactly 36, so the line is the box's centre
     (the old box was 37 on a 21px line beside a 30px picker). */
  padding: var(--s-1) var(--s-2); font-size: 13px; line-height: 20px;
}
.profile-form .scoped-addbar, .profile-form .lled-linknew { align-items: flex-start; }
.profile-form .scoped-pick-new {
  align-self: flex-start; box-sizing: border-box; height: 36px;
  border-radius: var(--r-pill); padding: 0 var(--s-half);
}
.profile-form .scoped-pick-new .scoped-pick {
  height: 34px; padding: 0 var(--s-1h); align-items: center; align-content: center;
  border: 0; background: none; border-radius: var(--r-pill);
  font-size: 12.5px;
}
.profile-form .input:not(textarea):not(.dt-scroll *):not(.dt-row *):not(.dt-toolbar *):not(.pf-head *) {
  box-sizing: border-box; height: 36px; min-height: 36px;
  border-radius: var(--r-pill); padding-left: var(--s-2); padding-right: var(--s-2);
}
.profile-form textarea.textarea:not(.grow):not(.dt-scroll *) { border-radius: var(--r-lg); }
/* The colour well beside a round hex box is a circle, not a square. */
.profile-form .brand-swatch { width: 36px; height: 36px; border-radius: 50%; }
.profile-form .brand-swatch::-webkit-color-swatch { border-radius: 50%; }

/* NO FOCUS RINGS, ANYWHERE (Franz, 2026-10-10: "I never want to see such
   highlight or selection stuff ever anywhere across the whole site, even on
   pills or anything else"). The browser's blue ring after a click on a pill,
   a fold heading or a menu is gone site-wide, last in the cascade so no rule
   brings it back. A text field still shows it is being edited by its own
   border colour; that is the field, not a ring. */
*:focus, *:focus-visible, summary:focus, summary:focus-visible { outline: none !important; }
* { -webkit-tap-highlight-color: transparent; }
::-moz-focus-inner { border: 0; }

/* THE WORK BOARD, WIDE LANES THAT SCROLL SIDEWAYS (Franz, 2026-10-10: "hate
   these kanbans in work, they need to be wider and horizontally scrollable").
   Every lane is a fixed 320px instead of a seventh of the row, so a card has
   room for its picture and a title on two lines; the row scrolls sideways,
   snapping lane by lane, with a soft fade at the edge while more is off-screen. */
[data-screen="client"] .tk-board.wb-board {
  display: flex; gap: var(--s-2); align-items: flex-start;
  overflow-x: auto; overflow-y: visible; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scroll-padding-left: var(--s-nudge);
  padding: var(--s-nudge) var(--s-nudge) var(--s-2); scrollbar-width: thin;
}
[data-screen="client"] .tk-board.wb-board > .wb-col { flex: 0 0 320px; width: var(--w-col); scroll-snap-align: start; padding: var(--s-1h) var(--s-1) var(--s-2); }
[data-screen="client"] .tk-board.wb-board::-webkit-scrollbar { height: 8px; }
[data-screen="client"] .tk-board.wb-board::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 4px; }
[data-screen="client"] .wb-col .wb-img { max-height: 220px; }
@media (max-width: 560px) { [data-screen="client"] .tk-board.wb-board > .wb-col { flex-basis: 84vw; width: 84vw; } }

/* A BULK SET ON THE WORK BOARD: every static as a tile, three across, in a
   4:5 frame. Past nine tiles the grid scrolls inside the card (three rows show),
   with a soft fade at the bottom saying there is more. */
.wb-thumbs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-1); grid-auto-rows: max-content; align-content: start; margin: calc(-1 * var(--s-nudge)) calc(-1 * var(--s-half)) 0; padding: var(--s-1); border-radius: 8px; background: var(--shade); }
.wb-thumbs.is-scroll { max-height: calc(3 * (var(--wb-th, 106px)) + 2 * 6px + 12px); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(180deg, #000 calc(100% - 28px), transparent); }
.wb-thumb { position: relative; aspect-ratio: 4 / 5; padding: 0; border: 0; border-radius: var(--r-sm); overflow: hidden; background: var(--card); cursor: pointer; display: block; }
.wb-thumb:hover { box-shadow: 0 0 0 2px var(--accent); }
.wb-thumb-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wb-thumb-live { position: absolute; inset: 0; overflow: hidden; }
.wb-thumb-n { position: absolute; left: var(--s-half); top: var(--s-half); z-index: 2; min-width: 18px; height: 18px; padding: 0 var(--s-half); border-radius: var(--r-pill); display: grid; place-items: center; background: rgb(0 0 0 / .62); color: #fff; font: 700 10.5px/1 var(--font-body); font-variant-numeric: tabular-nums; }
.wb-thumb.is-empty { display: grid; place-items: center; border: 1px solid var(--rule); background: none; }
.wb-thumb-none { font-size: 10px; color: var(--mute); text-align: center; padding: var(--s-half); }

/* LIVE TAGS (2026-10-14). Offers, facts, avoid and the task brief's four lists.
   A tag is drawn whole: it wraps onto as many lines as its words need and is
   never cut with an ellipsis. Enter or a comma makes one; the cross removes it. */
.tagwrap { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.tagbox {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-1);
  min-height: 40px; padding: var(--s-1); min-width: 0;
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--paper); cursor: text;
}
.tagbox:focus-within { border-color: var(--ink-soft); }
.tg {
  display: inline-flex; align-items: flex-start; gap: var(--s-nudge); max-width: 100%; min-width: 0;
  padding: var(--s-half) var(--s-half) var(--s-half) var(--s-1); border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: var(--shade); color: var(--ink); font: 500 13px/1.45 var(--font-body);
}
.tg-txt {
  display: inline; min-width: 0; padding: var(--s-px) 0; border: 0; background: none; color: inherit;
  font: inherit; text-align: left; cursor: text; white-space: normal; overflow-wrap: anywhere;
}
.tg-txt > span + span { margin-left: var(--s-1); }
.tg-name { font-weight: 650; }
.tg-kind, .tg-meta { color: var(--mute); font-size: 12px; }
.tg-kind { color: var(--accent); }
.tg-flag { color: var(--alarm); font-size: 12px; font-weight: 600; }
.tg .lob-pill { display: inline-block; vertical-align: 1px; }
.tg-x, .tg-ok {
  flex: none; width: 22px; height: 22px; display: grid; place-items: center;
  border: 0; border-radius: 4px; background: none; color: var(--mute); cursor: pointer;
}
.tg-x:hover { color: var(--alarm); background: color-mix(in srgb, var(--alarm) 12%, transparent); }
.tg-ok:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.tg.is-editing { flex: 1 1 100%; align-items: center; gap: var(--s-1); padding: var(--s-half); background: var(--card); border-color: var(--ink-soft); }
.tg.is-editing .tag-edit { flex: 1 1 auto; min-width: 0; border: 0; background: none; padding: var(--s-nudge) var(--s-1); font: 500 13px/1.45 var(--font-body); }
.tag-input {
  flex: 1 1 180px; min-width: 140px; min-height: 26px; padding: var(--s-half) var(--s-half); margin: 0;
  border: 0; outline: none; background: none; resize: none; overflow: hidden;
  color: var(--ink); font: 13px/1.45 var(--font-body);
}
.tag-input::placeholder { color: var(--mute); }
.tag-sugs { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.tag-sugs[hidden] { display: none; }
.tg.is-sug {
  align-items: flex-start; gap: var(--s-half); padding: var(--s-half) var(--s-1) var(--s-half) var(--s-1); cursor: pointer; text-align: left;
  background: none; border-style: solid; color: var(--ink-soft); white-space: normal; overflow-wrap: anywhere;
}
.tg.is-sug:hover { color: var(--ink); border-color: var(--accent); }
.tg-plus { flex: none; display: grid; place-items: center; height: 19px; color: var(--accent); }
.tg.is-read { padding: var(--s-half) var(--s-1); white-space: normal; overflow-wrap: anywhere; }
.tg-read { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.tg-filters { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: 0 0 var(--s-1); }
.tg-filter { display: inline-flex; align-items: center; gap: var(--s-1); }
.tg-filter .tg-n { font-size: 11.5px; opacity: .7; font-variant-numeric: tabular-nums; }
.tg-filter.is-new { border-style: solid; color: var(--mute); }
.scopedtable .tg-strip { padding-left: 0; padding-right: 0; }
.scoped-addbar[hidden] { display: none; }

/* Suggestions as short paragraphs, one per source (2026-10-14). */
.tag-sugs { flex-direction: column; gap: var(--s-1); }
.tag-sug-group { display: grid; grid-template-columns: 118px 1fr; gap: var(--s-1); align-items: start; }
.tag-sug-group.is-bare { grid-template-columns: 1fr; }
.tag-sug-h { font: 600 11.5px/1.35 var(--font-body); color: var(--mute); padding-top: var(--s-half); overflow-wrap: anywhere; }
.tag-sug-set { display: flex; flex-wrap: wrap; gap: var(--s-half); min-width: 0; }
@media (max-width: 640px) { .tag-sug-group { grid-template-columns: 1fr; gap: var(--s-half); } .tag-sug-h { padding-top: 0; } }
.tag-sugs .tg.is-sug { font-size: 12.5px; padding: var(--s-nudge) var(--s-1) var(--s-nudge) var(--s-half); }

/* The tag editor, opened up across the card. */
.tg-editor {
  flex: 1 1 100%; display: flex; flex-direction: column; gap: var(--s-1); padding: var(--s-1);
  border: 1px solid var(--ink-soft); border-radius: var(--r-md); background: var(--card);
}
.tg-editor .tag-edit { width: 100%; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--paper); padding: var(--s-1) var(--s-1); font: 500 14px/1.45 var(--font-body); }
.tg-editor-foot { display: flex; align-items: center; gap: var(--s-1); }
.scope-pills { display: flex; flex-direction: column; gap: var(--s-1); }
.scope-row { display: grid; grid-template-columns: 128px 1fr; gap: var(--s-1); align-items: start; }
.scope-h { font: 600 12px/1.3 var(--font-body); color: var(--mute); padding-top: var(--s-1); }
.scope-set { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.scope-pill { display: inline-flex; align-items: center; gap: var(--s-half); font-size: 12.5px; padding: var(--s-half) var(--s-1h); }
.scope-pill.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.scope-pill.is-new { border-style: solid; color: var(--mute); }
.scope-tick { display: grid; place-items: center; }
.scope-say { margin: 0; font-size: 12px; color: var(--mute); }
@media (max-width: 640px) { .scope-row { grid-template-columns: 1fr; gap: var(--s-half); } .scope-h { padding-top: 0; } }

/* THE ADD ROW, ENTER ONLY (2026-10-14). Full width at the foot of the tags,
   so a sentence is typed in the open; an Add key shows the ↵ it stands for. */
.tagbox .tag-add {
  flex: 1 1 100%; display: flex; align-items: flex-start; gap: var(--s-1); min-width: 0;
  margin-top: var(--s-nudge); padding: var(--s-half) var(--s-half) var(--s-half) var(--s-1); border-radius: var(--r-sm);
  border: 1px solid transparent; transition: border-color .15s, background-color .15s;
}
.tagbox .tag-add:first-child { margin-top: 0; }
.tagbox:focus-within .tag-add,
.tagbox .tag-add.is-typing { border-color: var(--rule); background: var(--card); }
.tag-add .tag-input { flex: 1 1 auto; min-width: 0; padding: var(--s-half) 0; }
.tag-add-note { flex: none; align-self: center; font-size: 12px; color: var(--alarm); }
.tag-add-note:empty { display: none; }
.tag-add-key {
  flex: none; display: inline-flex; align-items: center; gap: var(--s-1); height: 26px; padding: 0 var(--s-1) 0 var(--s-1);
  border: 1px solid var(--accent); border-radius: var(--r-sm); background: var(--accent); color: var(--accent-ink);
  font: 600 12px/1 var(--font-body); cursor: pointer;
}
.tag-add-key[hidden] { display: none; }
.tag-add-key kbd {
  display: grid; place-items: center; min-width: 18px; height: 18px; padding: 0 var(--s-half); border-radius: 4px;
  background: color-mix(in srgb, var(--accent-ink) 18%, transparent); font: 600 12px/1 var(--font-body);
}
.tag-add-key:hover { filter: brightness(1.08); }
.tag-add.is-dupe { animation: tag-nudge .3s ease; }
@keyframes tag-nudge { 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
.tg.is-new { animation: tag-glow 1.4s ease-out; }
@keyframes tag-glow {
  0% { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent); }
  100% { border-color: var(--rule); box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) { .tg.is-new, .tag-add.is-dupe { animation: none; } }

/* TAGS, TIDIED (2026-10-14). Franz: "unreadable ... packed up". The words and
   their pills sit on one centred line and wrap as a group; an offer is an
   entry with its name, what they get, and its terms each on a line. */
.tg.is-row { align-items: center; padding-top: var(--s-half); padding-bottom: var(--s-half); }
.tg.is-row .tg-txt { display: inline-flex; flex-wrap: wrap; align-items: center; column-gap: var(--s-1); row-gap: var(--s-half); }
.tg.is-row .tg-txt > span + span { margin-left: 0; }
.tg-lines { display: inline-flex; flex-wrap: wrap; gap: var(--s-half); }
.tg-lines .lob-pill { vertical-align: 0; line-height: 1; padding: var(--s-half) var(--s-1); font-size: 11px; }
.tg-entry {
  flex: 1 1 100%; display: grid; grid-template-columns: 1fr auto; align-items: start;
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--shade);
}
.tg-entry-body {
  display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; padding: var(--s-1h) var(--s-half) var(--s-1h) var(--s-2);
  border: 0; background: none; color: var(--ink); text-align: left; font: inherit; cursor: text;
}
.tg-entry-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-1) var(--s-1h); }
.tg-entry-name { font: 600 14.5px/1.3 var(--font-display); letter-spacing: -.005em; }
.tg-entry-said { margin: 0; font: 400 13.5px/1.55 var(--font-body); color: var(--ink-soft); overflow-wrap: anywhere; max-width: 72ch; }
.tg-term { display: flex; gap: var(--s-1); align-items: baseline; font-size: 12.5px; line-height: 1.5; color: var(--mute); }
.tg-term-k { flex: none; font: 600 10.5px/1.5 var(--font-body); text-transform: uppercase; letter-spacing: .06em; }
.tg-term-v { overflow-wrap: anywhere; }
.tg-entry > .tg-x { margin: var(--s-1) var(--s-1) 0 0; }
.tg-entry:hover { border-color: var(--ink-soft); }
.tg-entry.is-new { animation: tag-glow 1.4s ease-out; }
.tagbox.is-scoped { gap: var(--s-1); padding: var(--s-1); }
@media (prefers-reduced-motion: reduce) { .tg-entry.is-new { animation: none; } }
.tg-group { flex: 1 1 100%; display: flex; flex-direction: column; gap: var(--s-1); padding: var(--s-nudge) 0 var(--s-1); }
.tg-group + .tg-group { border-top: 1px solid var(--rule-faint); padding-top: var(--s-1); }
.tg-group-h { font: 600 11px/1.3 var(--font-body); text-transform: uppercase; letter-spacing: .07em; color: var(--accent); }
.tg-group-set { display: flex; flex-wrap: wrap; gap: var(--s-1); }

/* ===== PROFILE LISTS, REDRAWN AS ROWS (2026-10-14) =====================
   Franz: "still dont like it ... make it really better". The pills read as a
   wall of grey boxes inside a box inside a card. A fact is a sentence, so it
   is set as one: a row per fact, full width, 14px, a hairline between rows,
   its lines of business quiet at the right, the cross at the end. No inner
   border — the card is already the box. Sections are plain headings with a
   count. Offers are the same rows with the name over what they get. */
.tagbox.is-scoped { display: block; padding: 0; border: 0; background: none; min-height: 0; }
.tagbox.is-scoped:focus-within { border: 0; }
.tagbox.is-scoped .tg-group { display: block; padding: 0; }
.tagbox.is-scoped .tg-group + .tg-group { border-top: 0; padding-top: 0; margin-top: var(--s-2); }
.tagbox.is-scoped .tg-group-h {
  display: flex; align-items: center; gap: var(--s-1); margin: 0 0 var(--s-nudge); padding: 0 var(--s-nudge) var(--s-1);
  font: 600 12.5px/1.3 var(--font-body); text-transform: none; letter-spacing: 0; color: var(--ink-soft);
  border-bottom: 1px solid var(--rule);
}
.tg-group-n { font-weight: 500; color: var(--mute); font-variant-numeric: tabular-nums; }
.tagbox.is-scoped .tg-group-set { display: block; }

.tagbox.is-scoped .tg.is-row,
.tagbox.is-scoped .tg-entry {
  display: flex; align-items: flex-start; gap: var(--s-1); width: 100%; max-width: none;
  margin: 0; padding: var(--s-1) var(--s-nudge); border: 0; border-bottom: 1px solid var(--rule-faint); border-radius: 0;
  background: none; font: 400 14px/1.5 var(--font-body);
}
.tagbox.is-scoped .tg.is-row:hover,
.tagbox.is-scoped .tg-entry:hover { background: color-mix(in srgb, var(--ink) 3%, transparent); }
.tagbox.is-scoped .tg.is-row .tg-txt {
  display: block; flex: 1 1 auto; min-width: 0; padding: 0; text-align: left; color: var(--ink);
}
.tagbox.is-scoped .tg-kind {
  display: inline-block; margin-right: var(--s-1); padding: var(--s-px) var(--s-1); border-radius: 4px; vertical-align: 1px;
  font: 600 11px/1.5 var(--font-body); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.tagbox.is-scoped .tg-meta, .tagbox.is-scoped .tg-flag { margin-left: var(--s-1); }
.tagbox.is-scoped .tg-lines { flex: none; justify-content: flex-end; max-width: 45%; padding-top: var(--s-nudge); }
.tagbox.is-scoped .tg-x { flex: none; width: 24px; height: 24px; margin: 0 0 0 var(--s-nudge); color: var(--mute); opacity: .7; }
.tagbox.is-scoped .tg-x:hover { opacity: 1; }

/* Offers: the name, then what they get, then terms. */
.tagbox.is-scoped .tg-entry { padding: var(--s-1h) var(--s-nudge); }
.tagbox.is-scoped .tg-entry-body { flex: 1 1 auto; padding: 0; gap: var(--s-half); }
.tagbox.is-scoped .tg-entry-head { justify-content: space-between; }
.tagbox.is-scoped .tg-entry-name { font: 600 14.5px/1.35 var(--font-body); letter-spacing: 0; }
.tagbox.is-scoped .tg-entry-said { font-size: 14px; line-height: 1.5; color: var(--ink-soft); max-width: none; }
.tagbox.is-scoped .tg-term { margin-top: var(--s-half); font-size: 13px; }
.tagbox.is-scoped .tg-term-k { font-size: 10.5px; color: var(--mute); }
.tagbox.is-scoped .tg-entry > .tg-x { margin: 0 0 0 var(--s-nudge); }

/* The add row: a quiet "+ Add" line at the foot, lit when it has focus. */
.tagbox.is-scoped .tag-add {
  margin-top: var(--s-1); padding: var(--s-1) var(--s-1) var(--s-1) var(--s-half); border: 1px solid transparent; border-radius: var(--r-sm); background: none;
}
.tagbox.is-scoped .tag-add:focus-within,
.tagbox.is-scoped .tag-add.is-typing { border-color: var(--rule); background: var(--paper); }
.tag-add-plus { flex: none; display: grid; place-items: center; width: 20px; height: 24px; color: var(--accent); }
.tagbox.is-scoped .tag-add .tag-input { font-size: 14px; line-height: 1.5; padding: var(--s-px) 0; }
.tagbox.is-scoped .tg-editor { margin: var(--s-1) 0; }

/* The filters: a quieter segmented row, not a second set of pills. */
.tg-filters { gap: var(--s-half); margin: 0 0 var(--s-2); }
.tg-filter.chip { padding: var(--s-half) var(--s-1h); font-size: 12.5px; border-color: transparent; background: none; }
.tg-filter.chip:hover { background: var(--shade); }
.tg-filter.chip[aria-pressed="true"] { background: var(--shade); border-color: var(--rule); color: var(--ink); }
.tg-filter.chip.is-new { border: 1px solid var(--rule); }
@media (max-width: 640px) {
  .tagbox.is-scoped .tg.is-row { flex-wrap: wrap; }
  .tagbox.is-scoped .tg-lines { max-width: none; order: 3; flex-basis: 100%; justify-content: flex-start; padding-left: 0; }
}

/* ── the way back, in the margin and on screen ───────────────────────────────
   Franz, 2026-10-16/17: the arrow went missing when the app went full width
   (it hung a full gutter plus 18px out, past the edge of the screen), and then
   "the back button is in the margin off the page contents mate bring the title
   margin back". So the title stays on the page margin, lined up with
   everything under it, and the arrow sits in the gutter to its left, sized to
   fit the gutter and centred in it: 32px in the 48px gutter from 1440, 24px in
   the 32px gutter below that. Under 1024 the gutter is 16–24px, too narrow for
   anything you can press, so the arrow joins the title's line there. */
.page-back {
  --back: 32px;
  width: var(--back); height: var(--back);
  /* JUST THE ARROW (Franz, 2026-10-19: "lets not have a circle bg fill
     around the back button"): no fill, no ring; it turns green on hover. */
  color: var(--ink); background: none; border-color: transparent;
}
.page-back:hover { border-color: transparent; color: var(--accent); }
.page-head, .l-head, .client-head { position: relative; }
@media (min-width: 1024px) {
  .page-back { --back: 24px; }
  .page-head > .page-back, .l-head > .page-back, .client-head h1 > .page-back {
    position: absolute; margin: 0;
    left: calc(var(--pad-page) * -1 + (var(--pad-page) - var(--back)) / 2);
    top: 50%; transform: translateY(-50%);
  }
}
@media (min-width: 1440px) {
  .page-back { --back: 32px; }
}

/* View As Client sits with Settings at the top right of a client's page. */
.client-viewas { display: inline-flex; align-items: center; margin-left: auto; flex: none; text-decoration: none; }
.client-viewas[hidden] { display: none; }
.client-viewas:not([hidden]) + .client-set { margin-left: 0; }

/* A client's Settings is two planned columns at every width that has room for
   two (Franz, 2026-10-17), never the three or four a full-width board grows to:
   the left is the account, the right is the lists Create picks from. */
@media (min-width: 640px) {
  .profile-form.cs-form, .profile-form.ctx-board { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  .profile-form.cs-form, .profile-form.ctx-board { grid-template-columns: minmax(0, 1fr); }
}

/* THE DAY/NIGHT SWITCH, between the bell and the avatar. A round button the
   avatar's size; it shows the sun at night and the moon by day, the place a
   press takes you. */
.daynight {
  width: var(--h-md); height: var(--h-md); border-radius: 50%; flex: none; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  background: none; border: 1px solid var(--rule); color: var(--ink-soft);
}
.daynight:hover { border-color: var(--accent); color: var(--ink); }
.daynight .theme-moon, .daynight[data-now="light"] .theme-sun { display: none; }
.daynight[data-now="light"] .theme-moon { display: block; }
/* On two-row headers the four buttons at the right sit 8px apart, not the
   row's 16, so the avatar still fits on row one at 390. */
@media (max-width: 1023px) {
  .top-right > .daynight { order: 3; align-self: center; }
  .top-right > .notif, .top-right > .daynight, .top-right > .avatar { margin-left: calc(var(--s-1) * -1); }
}

/* EVERY PAGE STARTS ON ONE LINE (Franz, 2026-10-19: "each and every page has
   to start from the same exact place"). The title row is 32px on every page,
   16px under the app header, so the title, the back arrow, and the first
   control all share one centre line. A client's logo is 40px and hangs 4px
   over the row each way rather than making the row taller, which is what had
   pushed client titles 15px lower than every other page's. */
main :is(.page-head, .client-head, .l-head) > h1 { min-height: var(--h-md); align-items: center; }
main .client-head .mark-wrap { margin: calc(var(--s-1) / -2) 0; }
main .client-head .mark-btn .mark { width: 40px; height: 40px; font-size: 14px; }
/* And the layer under the title sits 16px below it, as on every other page. */
main .client-head { margin-bottom: var(--s-2); }
/* On a phone the back arrow shares the title's row on every page, as it does
   on a client's: the title takes the rest of the row and wraps inside it. */
@media (max-width: 1023px) {
  main .page-head > .page-back { align-self: flex-start; }
  main .page-head > .page-back + h1 { flex: 1 1 calc(100% - var(--h-md) - var(--s-3)); min-width: 0; }
}
/* EVERY CLIENT TAB'S FIRST CARD STARTS AT ONE DEPTH (Franz, 2026-10-19: "the
   spacing ... should be same as how the cards start in the profile and
   settings pages"). Those boards carried 9px of their own on top; the space
   now belongs to the tab body, so Work, Create, and Tasks start there too. */
main .client-body { padding-top: var(--s-1); }
main .client-body > .profile-form { padding-top: 0; }
/* AND A TOOLBAR SITS IN THAT RHYTHM AS IF IT WERE A CARD (Franz, 2026-10-19:
   "create and task elements including above and below the toolbar should
   have the same spacing as the profile ... and settings"). Profile sets the
   rhythm: the first card 18px under the tabs, 20px between cards. A pinned
   toolbar pads itself 9px top and bottom so its paper covers what slides
   under it; at rest that padding read as extra space above the controls, so
   the bar is pulled up through it. The controls start 18px under the tabs on
   Create and Tasks, 20px under the tiles on Work, and the next card starts
   20px under the controls, as it already did. Where it pins is unchanged. */
/* (Its padding, plus the 1px the rail mark above it leaves; Tasks' status
   pill stands 1px taller than its neighbours, so it is pulled 1px more.) */
main .client-body .create-page > .kinds-bar.is-stuck-bar { margin-top: calc((var(--s-1) + var(--s-px) * 2) * -1); }
/* BRIEFS AND WORK, ONE BAR, TOOLBAR UP AND CARDS DOWN (Franz, 2026-11-03:
   "cards down and tool bar up ... fix spacing up and down of the toolbar"):
   on both tabs, board or list, the controls sit 20px under the tabs and the
   slots or tiles 20px under the controls. Measured on live. */
main .client-body .bar-toolbar > .dt-toolbar,
main .client-body .bar-toolbar > .dt > .dt-toolbar,
main .client-body .bar-toolbar > .tk-view > .dt > .dt-toolbar,
main .client-body .tk-head > .tk-toolbar { margin-top: calc((var(--s-half) + var(--s-px)) * -1); margin-bottom: calc(var(--s-1h) - var(--s-px)); }
/* Profile's and Settings' tab bars, and Files' (its pane starts a pixel lower),
   measured on live to the same 20 and 20. */
main .client-body .cm-tabs > .bar-toolbar > .dt-toolbar { margin-top: calc((var(--s-half) + var(--s-px)) * -1); margin-bottom: calc(var(--s-1h) - var(--s-px) * 2); }
main .client-body .cf-page > .bar-toolbar > .dt-toolbar { margin-top: calc((var(--s-half) + var(--s-px) * 2) * -1); margin-bottom: calc(var(--s-1h) - var(--s-px) * 3); }
/* The slot tiles sit under Briefs' bar where Work's tiles sit under its own. */
/* CREATE A BRIEF STARTS WHERE ALL BRIEFS & WORK'S BAR DOES (Franz,
   2026-10-29: "take all elements ... higher up ... in sync with spacing from
   tool like all brief/work"): Brief Name's label stands where that bar's
   controls do, and Your Brief's top edge on the same line. The pinned head
   keeps a bar's top padding for when it is stuck, so the grid is pulled up
   through it, never over the tabs' rule, and the side card is pushed down by
   the same amount. */
main .client-body .bf-one:not(.is-read) .bf-one-grid { margin-top: calc(var(--s-3) * -1); }
main .client-body .bf-one:not(.is-read) .bf-side { padding-top: calc(var(--s-1h) + var(--s-px) * 2); }
/* Stuck, Your Brief stops where the pinned head does, its top on the switch's line. */
@container (min-width: 1100px) {
  main .client-body .bf-one:not(.is-read) .bf-side { top: var(--header-h); }
  /* WIDER, AS THE PAGE IS (Franz, 2026-10-30: "increase the width of the right
     brief thing"): a third of the page on a big screen, never under 416px. */
  main .client-body .bf-one:not(.is-read) .bf-one-grid {
    grid-template-columns: minmax(0, 1fr) minmax(calc(var(--w-col) + var(--s-2) + (var(--gap-section) - var(--gap-rail)) / 2), max(calc(var(--w-col) + var(--s-12) + (var(--gap-section) - var(--gap-rail)) / 2), 32%));
    column-gap: var(--gap-rail);
  }
}
/* ONE RHYTHM UNDER THE TOOLBAR (2026-11-03): slots, Queue, Paused, and the
   board each 20px apart, on Briefs as its own tab (the rule was scoped to the
   old Briefs & Work tab, and the gap fell back to 8px). */
main .client-body .sl-strip.qs { gap: calc(var(--s-2) + var(--s-half)); margin: 0 0 calc(var(--s-2) + var(--s-half)); }
main .client-body .wk-sum-box { margin-bottom: calc(var(--s-2) + var(--s-half)); }

/* ── Feedback in two tabs, Notes and Suggest (2026-10-19) ── */
.cf-tabs { display: inline-flex; gap: var(--s-nudge); padding: var(--s-half); margin: 0 0 var(--s-1); border-radius: var(--r-pill); background: var(--shade); border: 1px solid var(--rule-faint); }
.cf-tab { height: 28px; padding: 0 var(--s-2); border: 0; border-radius: var(--r-pill); background: none; color: var(--mute); font: 600 13px/1 var(--font-body); cursor: pointer; }
.cf-tab:hover { color: var(--ink); }
.cf-tab[aria-selected="true"] { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px rgb(0 0 0 / .18); }
.comment-form > .cf-pane { margin-bottom: var(--s-1); }
.comment-form > .cf-pane[hidden] { display: none; }
.comment-form .suggest-body { display: grid; gap: var(--s-1); }

/* ── The Create toolbar at Work's height (Franz, 2026-10-19: "the toolbar
   spacing is a bit extra ... compared to work"). Every control in it is the
   32px every other toolbar uses, so the row is 32 tall and the air above and
   below it is exactly the rhythm's 18 and 20. */
main .client-body .kinds-bar .create-kind,
main .client-body .kinds-bar .pick-tpl,
main .client-body .kinds-bar .namebox,
main .client-body .kinds-bar .pick-versions,
main .client-body .kinds-bar .savetpl,
main .client-body .kinds-bar .menu-btn {
  height: var(--h-md); min-height: 0; box-sizing: border-box;
  padding-top: 0; padding-bottom: 0; display: inline-flex; align-items: center; line-height: 1;
}
main .client-body .kinds-bar .namebox { display: block; line-height: calc(var(--h-md) - 2px); }
main .client-body .kinds-bar .kinds-hint, main .client-body .kinds-bar .hint { line-height: 1; }
main .client-body .create-page > .kinds-bar.is-stuck-bar { margin-bottom: var(--s-1); }

/* ── The template library: Create's fourth thing (2026-10-19) ──
   A bar (Statics, Videos, Briefs; search; Upload), then shelves of cards. Each
   card has one fixed stage the template is fitted into, in whichever of the
   four sizes is picked, so a row of cards stays one height whatever the size. */
.create-kinds-div { width: 1px; height: 20px; background: var(--rule); margin: 0 var(--s-half); align-self: center; flex: none; }
.create-kind.is-tool { color: var(--mute); }
.tl { display: grid; gap: var(--s-3); }
.tl-bar { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.tl-bar .seg-group { height: var(--h-md); }
.tl-find { flex: 1 1 240px; max-width: 420px; height: var(--h-md); display: flex; align-items: center; gap: var(--s-1); padding: 0 var(--s-1h); border-radius: var(--r-pill); border: 1px solid var(--rule); background: var(--paper); color: var(--mute); }
.tl-find:focus-within { border-color: var(--accent); }
.tl-find-in { flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; background: none; color: var(--ink); font: 500 13px/1 var(--font-body); }
.tl-bar .btn { margin-left: auto; }
.tl-body { display: grid; gap: var(--s-2); }
.tl-shelf-h { display: flex; align-items: center; gap: var(--s-1); margin-top: var(--s-half); }
.tl-shelf-h h3 { margin: 0; font: 700 15px/1.2 var(--font-display); color: var(--ink); }
.tl-shelf-n { min-width: 22px; height: 20px; padding: 0 var(--s-1); border-radius: var(--r-pill); display: inline-grid; place-items: center; background: var(--shade); color: var(--ink-soft); font: 700 11.5px/1 var(--font-body); }
.tl-grid { display: grid; gap: var(--s-2); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.tl-card { display: flex; flex-direction: column; border: 1px solid var(--rule-faint); border-radius: var(--r-lg); background: var(--card); overflow: hidden; min-width: 0; }
.tl-stage { height: 340px; display: grid; place-items: center; padding: var(--s-2); background: var(--shade); border-bottom: 1px solid var(--rule-faint); }
.tl-frame { position: relative; aspect-ratio: var(--ar); border-radius: 8px; overflow: hidden; background: #000; box-shadow: 0 4px 18px rgb(0 0 0 / .28); }
/* FITTED, NEVER SQUASHED: as wide as the stage's height allows for this
   shape, and never wider than the card. */
.tl-stage { --stage-h: 312px; }
.tl-stage .tl-frame { width: min(100%, calc(var(--stage-h) * var(--arn))); height: auto; }
.tl-frame.is-gone, .tl-frame.is-none { background: repeating-linear-gradient(45deg, var(--shade), var(--shade) 8px, var(--card) 8px, var(--card) 16px); }
.tl-img, .tl-vid { width: 100%; height: 100%; object-fit: cover; display: block; }
.tl-card-b { display: grid; gap: var(--s-1); padding: var(--s-2) var(--s-2) var(--s-2); }
.tl-card-h { display: flex; align-items: baseline; gap: var(--s-1); min-width: 0; }
.tl-card-h h4 { margin: 0; flex: 1; min-width: 0; font: 700 15px/1.25 var(--font-display); color: var(--ink); }
.tl-used { flex: none; color: var(--mute); font: 600 12px/1 var(--font-body); }
.tl-about { margin: 0; color: var(--ink-soft); font-size: 13px; line-height: 1.45; }
.tl-ctl { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.tl-sizes { display: inline-flex; border: 1px solid var(--rule); border-radius: var(--r-md); overflow: hidden; background: var(--shade); }
.tl-size { height: 28px; padding: 0 var(--s-1); border: 0; background: none; cursor: pointer; color: var(--ink-soft); font: 600 12px/1 var(--font-body); font-variant-numeric: tabular-nums; }
.tl-size + .tl-size { border-left: 1px solid var(--rule); }
.tl-size:hover:not(:disabled) { color: var(--ink); }
.tl-size.is-on { background: var(--accent); color: var(--accent-ink); }
.tl-size.is-none { color: var(--mute); opacity: .45; cursor: default; }
.tl-look { width: auto; height: 30px; min-height: 0; padding: 0 var(--s-4) 0 var(--s-1h); border-radius: var(--r-pill); font-size: 12.5px; }
.tl-acts { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.tl-acts .btn-sm { margin-left: 0; }
.tl-del { margin-left: auto; }
.tl-tags { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.tl-tag { font-size: 11.5px; }
.tl-empty { margin: var(--s-1) 0; color: var(--mute); font-size: 13.5px; }
.tl-briefs { display: grid; gap: var(--s-1); }
.tl-brief { display: flex; align-items: center; gap: var(--s-1h); padding: var(--s-1) var(--s-2); border: 1px solid var(--rule-faint); border-radius: var(--r-md); background: var(--card); }
.tl-brief-t { flex: 1; min-width: 0; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-brief-k { color: var(--mute); font-size: 12.5px; }
.tl-all-wrap { overflow-x: auto; }
.tl-all { display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-end; gap: var(--s-2) var(--s-2); padding: var(--s-half) var(--s-nudge) var(--s-1); }
.tl-all-c { margin: 0; display: grid; gap: var(--s-1); justify-items: center; }
.tl-all-c .tl-frame { height: 260px; width: calc(260px * var(--arn)); max-width: 100%; }
.tl-all-c figcaption { color: var(--ink-soft); font: 600 12.5px/1 var(--font-body); }
.tl-up-list { display: grid; gap: var(--s-1); }
.tl-up-row { display: flex; justify-content: space-between; gap: var(--s-1h); padding: var(--s-1) var(--s-1); border-radius: var(--r-sm); background: var(--shade); font-size: 12.5px; }
.tl-up-row.is-bad .tl-up-z { color: var(--alarm); }
.tl-up-z { color: var(--accent); font-weight: 600; white-space: nowrap; }
@media (max-width: 639px) { .tl-stage { height: 280px; --stage-h: 252px; } .tl-all-c .tl-frame { height: 220px; width: calc(220px * var(--arn)); } }

/* ── The template shuffler (2026-10-19): this static's copy in every layout ── */
.sheet-card:has(> .shf), .sheet-card:has(> .tl-all-wrap) { width: min(1240px, calc(100vw - 32px)); max-width: none; }
.shf { display: grid; gap: var(--s-2); }
.shf-top { display: flex; align-items: center; gap: var(--s-1h); flex-wrap: wrap; }
.shf-say { margin: 0; flex: 1; min-width: 0; color: var(--ink-soft); font-size: 13.5px; }
.shf-sizes { height: var(--h-md); }
.shf-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--s-1h); max-height: min(64vh, 760px); overflow-y: auto; padding: var(--s-nudge); }
.shf-card { display: grid; gap: var(--s-1); padding: var(--s-1); border-radius: var(--r-md); border: 1px solid var(--rule-faint); background: var(--card); cursor: pointer; text-align: left; color: var(--ink); }
.shf-card:hover { border-color: var(--ink-soft); }
.shf-card.is-picked { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 45%, transparent); }
.shf-stage { height: 230px; padding: var(--s-1); display: grid; place-items: center; background: var(--shade); border-radius: 8px; overflow: hidden; }
.shf-frame { aspect-ratio: var(--ar); width: min(100%, calc(214px * var(--arn))); position: relative; border-radius: var(--r-sm); overflow: hidden; background: #000; }
.shf-frame.is-gone { background: var(--shade); }
.shf-name { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-1); padding: 0 var(--s-nudge); }
.shf-name b { font: 700 13.5px/1.2 var(--font-display); }
.shf-now { color: var(--accent); font: 700 11.5px/1 var(--font-body); }
.shf-add { color: var(--mute); font-size: 11.5px; text-align: right; }
.shf-foot { display: flex; align-items: center; gap: var(--s-1h); flex-wrap: wrap; padding-top: var(--s-1h); border-top: 1px solid var(--rule-faint); min-height: 46px; }
.shf-hint { color: var(--mute); font-size: 13px; }
.shf-picked { font: 700 15px/1 var(--font-display); }
.shf-l { display: inline-flex; align-items: center; gap: var(--s-1); color: var(--ink-soft); font-size: 12.5px; font-weight: 600; }
.shf-sel { width: auto; height: 32px; min-height: 0; padding: 0 var(--s-4) 0 var(--s-1h); border-radius: var(--r-pill); font-size: 12.5px; }
.bk-creative-pair { display: inline-flex; gap: var(--s-1); flex-wrap: wrap; }
.tl-stage-wrap { position: relative; }
.tl-mode { position: absolute; top: 10px; left: 10px; display: inline-flex; padding: var(--s-half); border-radius: var(--r-pill); background: color-mix(in srgb, var(--paper) 88%, transparent); border: 1px solid var(--rule); backdrop-filter: blur(6px); }
.tl-mode-b { height: 26px; padding: 0 var(--s-1h); border: 0; border-radius: var(--r-pill); background: none; color: var(--mute); font: 600 12px/1 var(--font-body); cursor: pointer; }
.tl-mode-b.is-on { background: var(--accent); color: var(--accent-ink); }

/* The Form setup on Create (form-setup.js, 2026-10-20). Sentences as rows,
   the client's offers as chips under the brief. */
.fs-rows { display: grid; gap: var(--s-1); }
.fs-row-list { display: grid; border: var(--s-px) solid var(--rule); border-radius: var(--r-md); }
.fs-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content;
  align-items: center;
  gap: var(--s-1);
  min-height: var(--h-md);
  padding: 0 var(--s-1) 0 var(--s-1h);
  border-top: var(--s-px) solid var(--rule-faint);
}
.fs-row:first-child { border-top: 0; }
.fs-row-text { overflow-wrap: anywhere; }
.fs-offers { display: grid; gap: var(--s-1); }
.fs-offers:empty { display: none; }
.fs-offer { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fs-page .pick-task, .fs-page .pick-lead { width: 100%; }

/* What one request holds (web/fit.js, 2026-10-21): a label, then a row of chips. */
.fit { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: var(--s-1) var(--s-2); }
.fit-row { display: contents; }
.fit-k { font: var(--t-label); color: var(--mute); }
.fit-chips { display: flex; flex-wrap: wrap; gap: var(--s-half); }
.fit-auto { padding: 0 var(--s-half); border-radius: var(--r-pill); background: var(--accent-soft); color: var(--ink); font: var(--t-label); }
.chip[aria-pressed="true"] .fit-auto { background: var(--accent-ink); color: var(--accent); }
.fit-say { grid-column: 2; margin: 0; }
/* A length over a minute: shown with a lock, never picked (web/fit.js). */
.chip.fit-locked { gap: var(--s-half); opacity: .55; cursor: not-allowed; }
.chip.fit-locked[aria-pressed="true"] { opacity: 1; }
.fit-lock { display: inline-grid; place-items: center; }
.fit-say.is-warn { color: var(--alarm); }
@media (max-width: 639px) { .fit { grid-template-columns: minmax(0, 1fr); } .fit-say { grid-column: 1; } }
.count.is-warn { color: var(--alarm); }

/* A settled thread folds to its first line and a summary (2026-10-22). The
   chevron sits top right like every fold, down when shut, up when open. */
.comment-fold { flex: none; margin-left: auto; width: 24px; height: 24px; display: inline-grid; place-items: center; border: 0; border-radius: var(--r-sm); background: none; color: var(--ink-soft); cursor: pointer; }
.comment-fold:hover { color: var(--ink); background: var(--shade); }
.comment-fold svg { transition: transform .15s; }
.comment-fold[aria-expanded="true"] svg { transform: rotate(180deg); }
.comment-fold-sum { display: none; margin: var(--s-half) 0 0; font-size: 12px; color: var(--mute); }
.comment.is-folded { cursor: pointer; }
.comment.is-folded > :not(.comment-head):not(.comment-note):not(.comment-fold-sum) { display: none; }
.comment.is-folded > .comment-fold-sum { display: block; }
.comment.is-folded > .comment-note { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.comment-more { display: block; margin: var(--s-1) 0 var(--s-nudge); padding: 0; border: 0; background: none; color: var(--accent); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; }
.comment-more:hover { text-decoration: underline; }

/* LIVE (2026-10-22): a card somebody else just changed glows once and settles. */
@keyframes live-glow { 0% { box-shadow: 0 0 0 2px var(--accent); } 100% { box-shadow: 0 0 0 2px transparent; } }
.is-live-new { animation: live-glow 2.2s ease-out; }

/* ── CLIENT MODE (SPEC-PORTAL-V3 §1.2) ───────────────────────────────────────
   A client signs in to this same app. The header is the team's, less what a
   client has no use for: Home, Add a client, and New workspace. Looks only;
   the Worker's lens refuses a client everything else. Create stays (Franz,
   2026-10-26) and opens the picked client's Create tab. */
.is-client .top > .tabs { display: none; }
/* A client adds another of their businesses too (2026-11-02); View As draws none. */
/* Create a Brief, or on a phone, Brief (app.js). */
.btn-create > .cr-short { display: none; }
@media (max-width: 639px) {
  .btn-create > .cr-long { display: none; }
  .btn-create > .cr-short { display: inline; }
}
/* View As Client: one band under the header, the brand's own colour, so it
   is never mistaken for the client's real session. */
.viewas-band {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--s-half) var(--s-2);
  margin: 0; padding: var(--s-1) var(--s-2);
  background: var(--accent); color: var(--accent-ink); font: var(--t-label); text-align: center;
}
.viewas-band .linkbtn { color: inherit; text-decoration: underline; font: inherit; }
/* The work board's team verbs: no lane +, no upload, no rename, no drag, and
   a stage pill that only says the stage. The lens refuses them anyway. */
.is-client .wk-add,
.is-client .wk-upload,
.is-client .wk-rename-btn,
.is-client .wk-grip,
.is-client .wb-stage-chev { display: none; }
.is-client .wb-stage { pointer-events: none; }
/* No AdEQ'lizer or Templates lane on a client's board: they hold team work. */
.is-client .wk-lane:is([data-lane="analysis"], [data-lane="template"]) { display: none; }
/* The brief builder on a client's Create tab starts where every tab's first
   card starts. */
[data-screen="client"] .client-brief { padding-top: var(--s-1); }
/* A client's Settings: how many slots run at once, said once above the strip. */
.cs-slots { display: grid; gap: var(--s-1); }
.cs-slots-n { margin: 0; font: var(--t-section); }

/* ── LANDING PAGE TEMPLATES in the fit picker (fit.js templateRow) ──────────
   One card per template for the request's goal, the one picked ringed in the
   accent, and a Preview link that opens it live in the chosen feeling. */
.fit-tpls { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(calc(var(--w-col) * 0.625), 100%), 1fr)); gap: var(--s-1); min-width: 0; }
.fit-tpl { position: relative; border: var(--s-px) solid var(--rule); border-radius: var(--r-md); background: var(--card); min-width: 0; }
.fit-tpl:hover { border-color: var(--ink-soft); }
.fit-tpl.is-on { border-color: var(--accent); box-shadow: 0 0 0 var(--s-px) var(--accent); }
.fit-tpl-pick {
  display: grid; gap: var(--s-half); width: 100%; padding: var(--s-1h); padding-right: calc(var(--s-8) + var(--s-1));
  border: 0; background: none; color: var(--ink); text-align: left; cursor: pointer; font: inherit;
}
.fit-tpl-pick b { font-weight: 600; }
/* A template's own picture on top of its card (web/fit.js). */
.fit-tpl-thumb { display: grid; place-items: center; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; object-position: top; border-radius: var(--r-sm); background: var(--shade); border: var(--s-px) solid var(--rule-faint); margin-bottom: var(--s-half); color: var(--mute); font-size: 12.5px; }
.fit-tpl-pick span { color: var(--mute); font-size: 12.5px; line-height: 1.4; }
.fit-tpl-look { position: absolute; top: var(--s-1h); right: var(--s-1h); font-size: 12.5px; color: var(--accent); }

/* ── THE VISUAL WORK LIST (web/columns.js workListCell, Franz 2026-10-26) ────
   A client's All Work as a list you can see: a 56px picture in the piece's
   own shape, a short name in bold with one muted line under it, a stage that
   shows progress, and the campaign as a chip. Scoped to .dt.is-visual. */
.dt.is-visual .dt-row { min-height: calc(var(--s-8) + var(--s-1)); }
.dt.is-visual .dt-cell { border-right: 0; }
.dt.is-visual .dt-cell-in { overflow: visible; }
.dt.is-visual .dt-row:hover .wl-name { color: var(--accent); }
.wl-item { display: flex; align-items: center; gap: var(--s-1h); min-width: 0; width: 100%; }
.wl-slot { flex: none; width: calc(var(--s-6) + var(--s-1)); height: calc(var(--s-6) + var(--s-1)); display: grid; place-items: center; }
.wl-thumb {
  position: relative; display: grid; place-items: center; overflow: hidden;
  aspect-ratio: var(--ar, 1 / 1); max-width: 100%; max-height: 100%; height: 100%;
  border-radius: var(--r-sm); background: var(--shade); border: var(--s-px) solid var(--rule-faint);
}
.wl-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wl-thumb.is-mosaic { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: var(--s-px); height: 100%; aspect-ratio: 1 / 1; }
.wl-tile { overflow: hidden; min-width: 0; min-height: 0; background: var(--card); }
.wl-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wl-glyph { display: grid; place-items: center; gap: 0; color: var(--ink-soft); text-align: center; line-height: 1.1; padding: var(--s-half); }
.wl-glyph b { font: 600 16px/1 var(--font-display); color: var(--ink); }
.wl-glyph span { font-size: 10px; color: var(--mute); }
.wl-badge {
  position: absolute; right: var(--s-nudge); bottom: var(--s-nudge); padding: 0 var(--s-half); border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--paper) 80%, transparent); color: var(--ink); font: 600 10px/16px var(--font-body); font-variant-numeric: tabular-nums;
}
.wl-words { display: flex; flex-direction: column; gap: var(--s-nudge); min-width: 0; }
.wl-name {
  border: 0; background: none; padding: 0; text-align: left; cursor: pointer; color: var(--ink);
  font: 600 14px/1.3 var(--font-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.wl-meta { color: var(--mute); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wl-progress { display: flex; flex-direction: column; gap: var(--s-half); min-width: 0; width: 100%; }
.wl-bar { display: block; height: var(--s-half); border-radius: var(--r-pill); background: var(--shade); overflow: hidden; max-width: calc(var(--w-col) * .45); }
.wl-bar i { display: block; height: 100%; background: var(--accent); border-radius: inherit; }
.wl-progress-t { color: var(--ink-soft); font-size: 12.5px; }
.wl-chip {
  display: inline-block; max-width: 100%; padding: 0 var(--s-1); border: var(--s-px) solid var(--rule); border-radius: var(--r-pill);
  background: var(--card); color: var(--ink-soft); font: 500 12px/22px var(--font-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wl-chip.is-none { border-style: solid; color: var(--mute); opacity: 0; }
/* No campaign is offered on the row being pointed at, not printed on all of them. */
.dt.is-visual .dt-row:hover .wl-chip.is-none, .wl-chip.is-none:focus-visible { opacity: 1; }
.dt-pill.is-read { cursor: default; }
button.wl-chip { cursor: pointer; }
button.wl-chip:hover { border-color: var(--ink-soft); color: var(--ink); }
.wl-when { color: var(--ink-soft); }

/* A PHONE: each row a compact card (picture, name, line, stage). The table's
   columns give way; their inline widths are the table's, so they are set
   aside here on purpose. */
@media (max-width: 639px) {
  .dt.is-visual .dt-head { display: none; }
  /* The table writes its widths inline. On a phone its inner box steps aside
     (display: contents), so the minimum width it carries sizes nothing, and
     each card's cells size by flex, which an inline width does not override. */
  .dt.is-visual .dt-inner { display: contents; }
  .dt.is-visual .dt-row { flex-wrap: wrap; align-items: center; padding: var(--s-1) 0; row-gap: var(--s-half); }
  .dt.is-visual .dt-cell { flex: 0 1 auto; max-width: 100%; padding: 0 var(--s-1); }
  .dt.is-visual .dt-cell[data-col="title"] { flex: 1 1 100%; }
  .dt.is-visual .dt-cell[data-col="state"] { padding-left: calc(calc(var(--s-6) + var(--s-1)) + var(--s-1h) + var(--s-1)); }
  .dt.is-visual .dt-cell:not([data-col="title"]):not([data-col="state"]),
  .dt.is-visual .dt-util, .dt.is-visual .dt-select { display: none; }
}

/* THE BRIEFS AND TASKS FILTER BAR (filter-bar.js, Franz 2026-10-26): pills
   that tick several things, each row with its count; the picks as chips under
   the bar, then Clear All. */
.fb-rows { display: flex; flex-direction: column; }
.dt-pop-row.fb-row { align-items: center; }
.fb-row-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fb-row-n { flex: none; font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums; }
.dt-pop-row.fb-row.is-none .fb-row-t { color: var(--mute); }
/* A pill sets line-height 1, so a box that clips its text cuts g and y at the line's edge. The padding gives the tails
   room to show and the margin hands it straight back, so the pill stays its height. */
.fb-pill-t { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-block: .2em; margin-block: -.2em; }
.tk-chips[hidden] { display: none; }
.fb-chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); padding: var(--s-1) 0; }
.fb-chip { display: inline-flex; align-items: center; gap: var(--s-1h); max-width: 100%; }
.fb-chip > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fb-chip-x { display: inline-flex; color: var(--mute); }
.fb-chip:hover .fb-chip-x { color: var(--ink); }
.fb-clear { margin-left: var(--s-1); }
/* THE FILES BAR'S PILLS (2026-10-31): their holder takes no box, so each pill
   sits in the bar's own row and gap, as Briefs' do. */
.media-pills { display: contents; }
/* The chips row sits on the grid as Docs' sits on its cards: one 8px step. */
.media-chips + .media-grid { margin-top: 0; }
body.is-client .cf-page .media > .media-chips { margin-bottom: calc(-1 * var(--s-2)); }
/* The table counts the page it holds; the bar's count is the server's total. */
.tk-page .dt-toolbar > .dt-count:not(.tk-total) { display: none; }
.tk-list-more { display: block; margin: var(--s-3) auto; }

/* THE STATUS PILL: a colour dot and a word. */
.tk-spill {
  display: inline-flex; align-items: center; gap: var(--s-1h); height: 24px; padding: 0 var(--s-2);
  border: 1px solid var(--rule); border-radius: var(--r-pill); font-size: 12.5px; color: var(--ink); white-space: nowrap;
}
.tk-spill-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--ink-soft); }
.tk-spill.is-draft { border-style: solid; color: var(--ink-soft); }
.tk-spill.is-draft .tk-spill-dot { background: transparent; border: 1.5px solid var(--mute); }
.tk-spill.is-doing .tk-spill-dot { background: var(--c-sea); }
.tk-spill.is-review .tk-spill-dot { background: var(--c-sand); }
.tk-spill.is-done .tk-spill-dot { background: var(--accent); }
.tk-spill.is-paused { color: var(--ink-soft); }
.tk-spill.is-paused .tk-spill-dot { background: var(--mute); }
.tk-from { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── CREATE ON A PHONE FITS ITS SCREEN (2026-10-27, adeq-9d: the page was 84px
   wider than a 390 phone). The kinds are a two-column grid, AdEQ'lizer across
   the bottom, and the header's Add a Client chip is a round "+" (the words stay
   its accessible name, as text the screen reader still reads). */
@media (max-width: 639px) {
  .kinds-bar .create-kinds { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
  .kinds-bar .create-kinds-div { display: none; }
  main .client-body .kinds-bar .create-kind { justify-content: center; min-width: 0; }
  .kinds-bar .create-kind.is-tool { grid-column: 1 / -1; }
  .top-pickers .top-add-client {
    width: 28px; padding: 0; font-size: 0; display: inline-grid; place-items: center; border-radius: var(--r-pill);
  }
  .top-pickers .top-add-client::before { content: '+'; font: 500 16px/1 var(--font-body); }
}

/* A RATING, 1 TO 5 (web/stars.js, Franz 2026-11-02): five dots in the one
   accent, fran7esque-sounds' RatingDots in AdEQ's colours. Unrated is five
   faint rings, so it reads as "rate me" without shouting. */
.stars { display: inline-flex; align-items: center; gap: calc(var(--s-half) + var(--s-px)); padding: var(--s-half); flex: none; vertical-align: middle; }
.stars:focus-visible { outline: var(--s-px) solid var(--accent); outline-offset: var(--s-half); border-radius: var(--r-sm); }
.stars { --dot: var(--accent); }
.stars[data-n="1"] { --dot: var(--c-clay); }
.stars[data-n="2"] { --dot: var(--c-sand); }
.stars[data-n="3"] { --dot: var(--c-sea); }
/* UNRATED IS STILL VISIBLE (Franz, 2026-11-06): five soft filled dots that read as "rate me" on any card, in either theme. */
.stars-dot { width: 12px; height: 12px; padding: 0; border: var(--s-px) solid color-mix(in srgb, var(--ink) 34%, transparent); border-radius: 50%; background: color-mix(in srgb, var(--ink) 16%, transparent); cursor: pointer; transition: transform .12s, background-color .12s, border-color .12s, box-shadow .12s; }
.stars-dot.is-on { background: var(--dot); border-color: var(--dot); }
.stars[data-n="5"] .stars-dot.is-on { box-shadow: 0 0 0 var(--s-px) color-mix(in srgb, var(--dot) 35%, transparent); }
/* What a click would set: the same colour, held back until it is chosen. */
.stars.is-preview .stars-dot.is-on { background: color-mix(in srgb, var(--dot) 55%, transparent); }
.stars.is-pop .stars-dot.is-on { animation: stars-pop .28s ease-out both; animation-delay: calc(var(--i) * 45ms); }
@keyframes stars-pop { 40% { transform: scale(1.35); } }
@media (prefers-reduced-motion: reduce) { .stars.is-pop .stars-dot.is-on { animation: none; } }
.stars:not(.is-read) .stars-dot:hover { transform: scale(1.2); }
.stars.is-read .stars-dot { cursor: default; }
.stars.is-read.is-none { display: none; }

/* A LIST TO TICK SEVERAL FROM (web/multi-pick.js, 2026-11-02): a search on
   top, the rows scrolling inside, how many are picked and Done at the foot.
   Long sentences wrap; the ticked ones sit first under Picked. */
.mp {
  position: fixed; z-index: var(--z-pop); display: flex; flex-direction: column; min-height: 0;
  border: var(--s-px) solid var(--rule); border-radius: var(--r-lg); background: var(--paper);
  box-shadow: 0 var(--s-1) var(--s-4) color-mix(in srgb, var(--ink) 16%, transparent); overflow: hidden;
}
.mp-head { padding: var(--s-1h); border-bottom: var(--s-px) solid var(--rule-faint); }
.mp-find { width: 100%; }
/* scroll: a menu list over the page; at its ends the wheel must not move the page and carry the field away (multi-pick.js). It hides nothing below. */
.mp-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--s-1); }
.mp-group { margin: var(--s-1) var(--s-1) var(--s-half); font: 600 12px/1.4 var(--font-body); color: var(--mute); }
.mp-row {
  display: grid; grid-template-columns: var(--s-2) minmax(0, 1fr); align-items: start; gap: var(--s-1h);
  width: 100%; padding: var(--s-1) var(--s-1h); border: 0; border-radius: var(--r-md); background: none;
  font: inherit; color: var(--ink); text-align: left; cursor: pointer;
}
.mp-row:hover, .mp-row.is-active { background: var(--shade); }
.mp-text { font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }
.mp-tick {
  display: grid; place-items: center; width: var(--s-2); height: var(--s-2); margin-top: var(--s-nudge);
  border: var(--s-px) solid var(--rule); border-radius: var(--r-sm); background: var(--paper); color: var(--accent-ink);
}
.mp-row.is-on .mp-tick { background: var(--accent); border-color: var(--accent); }
.mp-add .mp-plus { display: grid; place-items: center; width: var(--s-2); height: var(--s-2); font-weight: 700; color: var(--accent); }
.mp-add .mp-text { color: var(--accent); font-weight: 600; }
.mp-none { margin: var(--s-2); color: var(--mute); font-size: 13px; }
.mp-foot { display: flex; align-items: center; gap: var(--s-1h); padding: var(--s-1) var(--s-1h); border-top: var(--s-px) solid var(--rule-faint); }
.mp-n { font-size: 13px; color: var(--mute); }
/* The client's own rating beside the team's (migration 95), on the team's side. */
.stars-pair { display: inline-flex; align-items: center; gap: var(--s-1); flex: none; }
.stars-client { font: 600 12px/1 var(--font-body); color: var(--mute); white-space: nowrap; }
.stars-client.is-saved { color: var(--accent); }
/* THE CLIENT'S SAVE PILL (stars.js, migration 98): AdEQ's small quiet pill, green once saved. */
.save-pill {
  height: var(--h-xs, 24px); padding: 0 var(--s-1h); border-radius: var(--r-pill); cursor: pointer;
  border: var(--s-px) solid var(--rule); background: none; color: var(--ink-soft); font: 600 12px/1 var(--font-body); white-space: nowrap;
}
.save-pill:hover { border-color: var(--ink-soft); color: var(--ink); }
.save-pill.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.save-pill:disabled { cursor: default; opacity: .7; }
/* ONE LOOK FOR BRIEFS AND WORK (Franz, 2026-11-02: "i need something in the
   middle ... cohesion"): both lists are the visual list, a brief's cover in
   the same square slot a piece's picture takes; plain cells read quietly. */
.wl-text { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-soft); font-size: 13px; }
.dt.is-visual .tk-row-cover { aspect-ratio: 1 / 1; }
.dt.is-visual .dt-row:hover .tk-row-title { color: var(--accent); }
.dt.is-visual .dt-row { cursor: pointer; }

/* VOICES TO LISTEN TO AND TICK (web/voice-list.js, 2026-11-02): a video
   brief's Voices and both Settings' Voices card. */
.vl { display: flex; flex-direction: column; gap: var(--s-half); }
.vl-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; gap: var(--s-1h); padding: var(--s-1) var(--s-1h); border: var(--s-px) solid var(--rule-faint); border-radius: var(--r-md); background: var(--paper); }
.vl-row.is-on { border-color: var(--accent); }
.vl-what { display: flex; flex-direction: column; min-width: 0; }
.vl-name { font-weight: 600; font-size: 14px; }
.vl-meta { font-size: 12.5px; color: var(--mute); }
.vl-fav { font: 600 12px/1 var(--font-body); color: var(--accent); }
.vl-nosample { font-size: 12px; color: var(--mute); }
.vl-pick.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.vl-say:empty { display: none; }
.bf-voices { margin-top: var(--s-2); }
/* THE VIDEO STUDIO'S HOLDER (web/studio-page.js): the editor takes the page under the header. */
.studio-mount { margin: 0 calc(var(--pad-page, var(--s-2)) * -1); }
/* THE VIDEO TAB'S STUDIO (script-view.js videoPanel, 2026-11-02): Studio or
   The Cut, and the editor at the tab's full width. */
.sc-vid-modebar { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); margin-bottom: var(--s-1h); }
.sc-vid-modebar[hidden] { display: none; }
.sc-studio { min-height: calc(var(--s-12) * 6); }
.sc-studio .adeq-studio { height: calc(100dvh - var(--header-h, 52px) - var(--s-12)); padding: 0; }

/* A WORKSPACE'S TAG (2026-11-03): Pro on Forgefire Creative, Personal on yours. */
.ws-tag {
  display: inline-flex; align-items: center; height: var(--s-2h, 20px); padding: 0 var(--s-1); margin-left: var(--s-1);
  border-radius: var(--r-pill); background: var(--accent-ink); color: var(--accent);
  font: 700 11px/1 var(--font-body); letter-spacing: .02em; text-transform: none;
}
.ws-tag[hidden] { display: none; }
.ws-tag.is-personal { background: var(--shade); color: var(--ink-soft); }
.switcher-ws-name + .ws-tag { margin-left: var(--s-1); background: var(--accent); color: var(--accent-ink); }
.switcher-ws-name + .ws-tag.is-personal { background: var(--shade); color: var(--ink-soft); }
/* In the pill, the tag sits on the pill's own 8px rhythm: name, tag, chevron
   evenly apart, and the pill a little wider so the name is not cut for it. */
.ws-switch .ws-tag { margin-left: 0; }
.ws-switch:has(> .ws-tag:not([hidden])) { max-width: calc(var(--w-col) - var(--s-1)); }
.switcher-ws .ws-tag { margin-left: var(--s-half); }

/* THE TEMPLATE LIBRARY (web/kit-lib.js, Franz 2026-11-04): every template as
   its preview, what it needs, and Use. The same cards on Create → Templates
   and, compact, in the Work board's Templates column. */
.kl { display: grid; gap: var(--s-2); }
.kl-bar { display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.kl-pick { width: auto; height: 34px; min-height: 0; padding: 0 var(--s-4) 0 var(--s-1h); border-radius: var(--r-pill); font-size: 13px; }
.kl-find { flex: 1 1 200px; height: 34px; min-height: 0; border-radius: var(--r-pill); font-size: 13px; }
.kl-count { margin-left: auto; color: var(--mute); font: 600 12.5px/1 var(--font-body); font-variant-numeric: tabular-nums; }
.kl-grid { display: grid; gap: var(--s-2); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.kl-card { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--rule-faint); border-radius: var(--r-lg); background: var(--card); overflow: hidden; }
.kl-shot { aspect-ratio: 4 / 5; background: var(--shade); border-bottom: 1px solid var(--rule-faint); }
.kl-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.kl-body { display: grid; gap: var(--s-1); padding: var(--s-1h) var(--s-2) var(--s-2); }
.kl-head { display: flex; align-items: baseline; gap: var(--s-1); min-width: 0; }
.kl-name { margin: 0; flex: 1; min-width: 0; font: 700 14.5px/1.25 var(--font-display); color: var(--ink); }
.kl-need { flex: none; color: var(--mute); font: 600 11.5px/1 var(--font-body); }
.kl-about { margin: 0; color: var(--ink-soft); font-size: 12.5px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.kl-foot { display: flex; align-items: center; gap: var(--s-1); min-width: 0; }
.kl-ind { flex: 1; min-width: 0; color: var(--mute); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kl-use { flex: none; }
.kl-empty { margin: var(--s-1) 0; color: var(--mute); font-size: 13.5px; }
.kl.is-compact .kl-bar .kl-pick { flex: 1; min-width: 0; }
.kl-grid.is-compact { grid-template-columns: 1fr 1fr; gap: var(--s-1); }
.kl-card.is-compact .kl-body { padding: var(--s-1) var(--s-1h) var(--s-1h); }
.kl-card.is-compact .kl-name { font-size: 13px; }
.kl-card.is-compact .kl-foot .kl-ind { display: none; }

/* ── THE PAGE'S END (Franz, 2026-11-04: "the same padding or spacing below as up and down the tool bar") ──
   `main` ends every page at --pad-page-end, so nothing inside may add to it. Each rule here takes away the
   trailing space of something that is last on a page, and of nothing that has a neighbour under it. */
.pf-status:empty { display: none; }
.client-body > div:has(> .pf-status:empty:only-child) { display: none; }
.missing:has(+ .pf-status:empty), .missing:last-child { margin-bottom: 0; }
.sheet-form.is-page .sheet-go:last-child { margin-bottom: 0; }
.work-cards:last-child > .work-card:last-child { margin-bottom: 0; }
.missing p:last-child { margin-bottom: 0; }
.scene-insert-row:last-child, .scene-insert-row.bk-insert:last-child { padding-bottom: 0; }
[data-screen="form"] .scene-insert-row:last-child, [data-screen="form"] .fm-sec:not(:has(~ :not([hidden]))) { margin-bottom: 0; }
/* A card board's rows each carry the 16px under their card, the last row too. */
.profile-form:has(> [data-card]):is(:last-child, :has(+ div > .pf-status:empty:only-child)) { margin-bottom: calc(var(--s-2) * -1); }

/* ── THE PAGE'S OWN ACTIONS, ON THE NAME'S ROW (Franz, 2026-11-04) ──
   Every kind of work: what you do to the whole piece (Share, Export, Download, Studio | The Cut) sits right of its
   name, with Other Videos or Other Sets last. The toolbar under it keeps what works inside the piece. */
.head-acts { display: inline-flex; align-items: center; gap: var(--s-1); flex: none; margin-left: auto; margin-right: var(--head-end, 0px); }
.head-acts > * { flex: none; }
.head-acts > .btn, .head-acts > .chip { height: var(--h-md); }
.head-acts .sc-switch, .head-acts .bk-switch { margin-left: 0; }
.head-part { display: contents; }
.head-part[hidden] { display: none; }
/* A phone gives the actions a row of their own, and they wrap inside it. */
@media (max-width: 639px) { .head-acts { flex: 1 1 100%; flex-wrap: wrap; margin: 0; row-gap: var(--s-1); } }
/* TIGHT (dom.js fitHead): the row would wrap, so the small print steps aside and a long name shortens with an ellipsis. */
@media (min-width: 640px) {
  .page-head.is-tight { flex-wrap: nowrap; }
  .page-head.is-tight > .count { display: none; }
  .page-head.is-tight > h1, .page-head.is-tight > .script-title { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }
}

/* EVERY COLLAPSE WEARS THE PENCIL'S HOVER (Franz, 2026-11-06): point at a fold and its chevron stands on the same small dark
   square, the same size at every level. The square never moves anything: its margins hand back what the box adds.
   A drawn chevron is a rotated corner, so a square behind it would turn to a diamond; these draw the chevron as a picture on
   an upright box, which only ever turns by quarters. :where keeps the website's own FAQ chevrons out of this. */
:where([data-screen]) details > summary::after, .sec-chev, .wg-sec .wg-chev, .wp-fold-chev {
  flex: none; box-sizing: border-box; width: var(--fold-w); height: var(--fold-w);
  margin-block: calc((var(--fold-w) - var(--s-1h)) / -2); margin-right: calc(var(--s-half) * -1 - var(--s-nudge));
  border: 0; border-radius: var(--r-sm); background: var(--chev-rest) center / 20px no-repeat;
  transform: none; rotate: none; translate: none; transition: transform .12s ease, background-color .12s ease;
}
:where([data-screen]) details:not([open]) > summary::after, .sec:not([open]) .sec-chev, .wg-sec.is-shut .wg-chev { transform: rotate(-90deg); }
:where([data-screen]) details > summary:hover::after, .sec-head:hover .sec-chev, .wg-head:hover .wg-chev.wg-chev, .wp-fold > summary:hover .wp-fold-chev, .wp-row-head:hover .wp-fold-chev { background-color: var(--fold-tile); background-image: var(--chev-on); }
/* The ones drawn from the icon set: a small box around the icon, handing back the room it adds. */
.fold > summary .chev, .create-more > summary .chev, .sp-chev, .wg-list .wg-chev { box-sizing: border-box; align-items: center; justify-content: center; width: var(--fold-w); height: var(--fold-w); border-radius: var(--r-sm); }
.fold > summary .chev, .create-more > summary .chev { margin: calc((var(--fold-w) - var(--s-1h)) / -2); }
.card-fold:hover, .comment-fold:hover, .tk-card-more:hover, .fold > summary:hover .chev, .create-more > summary:hover .chev, .sp-client-head:hover .sp-chev, .wg-list .wg-head:hover .wg-chev { background: var(--fold-tile); color: var(--ink); }
.rd-toggle:hover { background: var(--fold-tile); }
.switcher-fold { height: var(--fold-w); align-self: center; border-radius: var(--r-sm); }
.switcher-fold:hover:not(:disabled) { background: var(--fold-tile); }
.switcher-fold:hover:not(:disabled) .switcher-chev { border-color: var(--ink); }

/* THE HEADER'S PILLS STAND ONE HEIGHT (Franz, 2026-11-06): workspace, client, and Slots alike; Add client a step smaller beside them. */
.top-pickers .ws-switch.ws-switch { box-sizing: border-box; height: var(--h-md); padding-block: 0; }
.top-pickers .top-add-client.top-add-client { box-sizing: border-box; height: calc(var(--h-sm) - var(--s-nudge)); min-height: 0; padding-block: 0; padding-inline: var(--s-1h); font-size: 12.5px; }

/* The header's pills wear the All pill's double edge too (Franz, 2026-11-07). */
.top-pickers .ws-switch.ws-switch:not(.client-switch), .head-slots.head-slots { border-style: double; border-width: var(--s-half); background-clip: padding-box; }
.top-pickers .ws-switch:not(.client-switch) { border-color: var(--accent); }
.head-slots.head-slots { border-color: color-mix(in srgb, var(--ink) 30%, transparent); }

/* THE CLIENT PILL: THE WORKSPACE PILL'S SIZE AND WORDS, ONE LIGHT BORDER, AND ITS LOGO CLEAR OF THE EDGE (Franz, 2026-11-07). */
.top-pickers .client-switch.client-switch { border: var(--s-px) solid color-mix(in srgb, var(--ink) 30%, transparent); font: 600 13.5px/1 var(--font-body); }
.top-pickers .client-switch.client-switch:has(.client-pick-mark:not([hidden])) { padding-left: var(--s-half); }
.top-pickers .client-pick-mark .mark { width: calc(var(--s-3) - var(--s-nudge)); height: calc(var(--s-3) - var(--s-nudge)); }
