@charset "UTF-8";
/* ═══════════════════════════════════════════════════════════════════════════
   AiMY SALES — THE BDR BUILD, WHAT IT ADDS TO THE SHEET

   THIS FILE IS AN APPENDIX, NOT A STYLESHEET. The shell, the components, the
   background, the rail, the briefing card, the queue row, the toast and the
   canvas are `sales.css` and are used unchanged — the BDR build is a new
   PROCESS over the existing design, not a new design. If something here
   starts redefining what sales.css already says, that is the mistake this
   header exists to name, and the rule is to delete the rule and use the one
   that is already there.

   Loaded after sales.css and before aimy-responsive.css, so the responsive
   layer still supersedes both.

   Two things in this product genuinely did not exist before:

     THE WINDOWED LIST. A campaign holds thousands of people and the book
     holds twenty thousand calls. `.s-queue` renders every row it is given,
     which is right for the six the V3 briefing showed and impossible for six
     thousand. `.b-vlist` positions rows by arithmetic so only what is on
     screen exists. Its CHILDREN are still `.s-qrow` and friends — the
     windowing is a positioning wrapper, not a second row design.

     THE CHECKPOINT LADDER. Where one lead stands with a BDR, which the V3
     build did not have because it derived status rather than storing a step.

   Everything else here is a small variant of something in sales.css and says
   which one.

   ── AND EVERY WIDTH BREAKPOINT HERE IS MULTIPLIED BY 0.85 ───────────────

   Same contract as sales.css's, which states the derivation in full at its
   own breakpoint block — the shell carries `zoom` on <body> and a media query
   reads the REAL viewport, so a threshold reasoned about in LAYOUT px has to
   be converted into the viewport width that produces that layout. Below a
   real 1306 the scale is exactly 0.85, so the conversion is one exact
   multiplication and every threshold in this file lands inside that region.

   THIS FILE DID NOT FOLLOW IT, AND HALF OF IT SHOWED. Two thresholds were
   converted (1087.98 and 611.98) and eight were written against the real
   viewport, which fired each of those eight about eighteen percent early:
   `max-width: 720px` on `.b-grid` dropped the queue to one column at an
   847px layout, where two columns of 380 still fit, and the single card it
   left was 773px wide. Both spellings of 720 were in the file at once.

   The trailing comment on each query is the number that was reasoned about;
   the number in the parentheses is machinery. The .98 clears the fractional
   viewport Windows reports at 125% display scaling.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ══ 1. THE WINDOWED LIST ══════════════════════════════════════════════════
   The host is as tall as the whole list; the rows inside it are positioned
   by index. About thirty exist at a time whatever the list holds.

   POSITIONING ONLY. No display, no font, no colour — the row element carries
   its own class from sales.css (`.s-qrow`, `.s-camp-row`) and that decides
   what it looks like. A `display` here would fight the grid those rules set
   up, which is how an appendix turns into a second design system. */
.b-vlist { position: relative; width: 100%; }
.b-vrow {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  box-sizing: border-box;
  padding: 12px 0;
}

/* THE HAIRLINE IS ON EVERY ROW, not on `.s-qrow + .s-qrow` as sales.css:4934
   has it. Under windowing the first row in the DOM is not the first row in
   the list — it is whichever one the scroll position reached — so an
   adjacent-sibling rule leaves exactly one row without a separator and moves
   which one that is as you scroll. */
.b-vrow + .b-vrow,
.b-vlist > .b-vrow:first-child { border-top: 1px solid var(--hairline); }
.b-vlist > .b-vrow:first-child { border-top: 0; }

.b-vfoot { padding: 12px 0 0; font-size: var(--fs-xs); color: var(--ink-muted); }

/* ══ 2. THE CHECKPOINT LADDER ══════════════════════════════════════════════
   Where one lead stands, as eight bars and one sentence. The bars carry the
   position; the sentence carries the meaning — eight labels along a track is
   eight things to read to answer one question, and at this width each would
   be four characters.

   ONE ACCENT FOR WHERE IT IS NOW, neutral otherwise. A colour per step would
   be eight colours to learn, and the product already spends its semantic
   colours on status. */

/* ══ 3. THE CHIP ROW OVER A QUEUE ══════════════════════════════════════════
   Which part of the queue you are looking at. `.filter-chip` is the library's
   and is what the float bar's tray already uses, so this is a layout wrapper
   around existing chips rather than a new control. The count rides inside the
   chip because a chip that says "Due" and a number beside it that says 12 are
   two things to read for one fact. */
.b-cuts { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
/* The row's margin is on the BOTTOM, which is right where it belongs — above
   a grid of results on a page. In an answer it is the other way round: the
   chips follow a sentence and were sitting on it, with twelve pixels of
   nothing under them where the answer ends. `:not(.b-cuts-row)` is the
   answer rows and only those; the page's own cut row keeps what it had.

   This is why every answer had to be made to use the row. The rule reaches
   an answer in the card and the same answer in the thread, and it cannot
   reach a chip that was appended to the sentence as a word. */
.b-cuts:not(.b-cuts-row) { margin-top: 10px; }
.b-cuts:not(.b-cuts-row):last-child { margin-bottom: 0; }
/* The 0.65 here was how this number was "set quiet on purpose", and it put
   it at 3.26:1 against its ground where AA asks 4.5. Opacity is the same
   fault as a dim ink token, just spelled differently — it moves the
   composited colour without anything in the palette recording that it did.
   Quiet is --ink-secondary, which IS the quiet floor and passes. */
.b-cut-n {
  font-variant-numeric: tabular-nums; margin-left: 4px;
  color: var(--ink-secondary);
}

/* §4 held a campaign row. Campaigns are cards in the same grid as people
   now, so the row and its five rules are gone with it. */

/* ══ 5. THE PAGER ══════════════════════════════════════════════════════════
   Where you are in the queue, and the two ways to move. Never "load more":
   a caller has to be able to go back to the fifteen they were just on. */
.b-pager {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 12px;
}
.b-pager-go { display: flex; gap: 8px; }

/* §6 styled an outcome row in the call rail. Logging is a conversation
   again — AiMY proposes what it heard and you agree or correct it — so the
   row and its rules are gone with it.

   Skip was styled here as a word among the round handset shapes. It has gone
   back to the canvas with the rest of the run's controls, so the rule goes
   with it — a run's rail and a single call's rail are now the same column. */

/* ══ 7. WHERE A CAMPAIGN'S PEOPLE STAND ════════════════════════════════════
   A row of counts, stated rather than linked. These are STEPS, and the queue
   under them cuts by what is owed — so a door here would open a filter that
   does not exist, which is worse than no door. */

/* §8 held a labelled chip row per axis. The builder asks in the composer
   now, the way the V3 build does — you say who you are after and the page
   shows what it heard, with AiMY offering the rest. So the rows are gone. */

/* The keyboard cursor. j and k move it, o opens the row it is on. Tinted
   rather than outlined: an outline on an absolutely positioned row of fixed
   height would be clipped by its own neighbours. */
.b-vrow.is-cursor { background: var(--accent-dim); }

/* ══ 9. ONE LIBRARY DEFECT, OVERRIDDEN HERE AND FLAGGED ════════════════════
   `aimy-ds.css:627` sets `.evidence-pill .val { color: #fff }` — a hard-coded
   white, which the design system's own Level 1 gate bans. In light mode the
   pill's ground is near-white, so the NUMBER on every rail card is white on
   white: the figure that makes the card's sentence believable is the one part
   of it nobody can read. Measured: computed colour rgb(255,255,255) on
   "1,066 to call".

   This is not new and not caused by this build — the V3 build under /old/ has
   the same line and the same defect. It is fixed HERE rather than THERE
   because the library is an extracted copy and stays a faithful one, and it
   is written down rather than quietly patched because it belongs in the
   library's own gap register. `--text-strong` is the token that already means
   "the strongest text on this surface" and it flips with the theme. */
.evidence-pill .val { color: var(--text-strong); }

/* ══ 10. THE HEADING IS THE SWITCHER ═══════════════════════════════════════
   Not a control above the heading and not a column beside it: the title of
   the block IS the choice. The one you are on reads as the heading it would
   have been anyway; the other two sit beside it, quiet, and press to become
   it. A product with three surfaces does not need a row of chrome to say so.

   An h2 with buttons inside, so the page keeps a real heading in its outline
   whichever surface is showing. The type is `.s-block-h`'s, because the one
   you are on IS that heading and must not be a different size from the one
   it replaces. */
/* An <h2> with no font-size takes the user agent's 1.5em, which is a size
   derived from the body rather than chosen — 21px against a 14px body, 24
   against 16. Every child sets its own size, so the h2's is only ever
   inherited by accident. Named, so it stops moving when the body does. */
.b-switch {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 16px;
  margin: 0; font-size: var(--t-body);
}
/* == THE ONE YOU ARE ON IS THE HEADING; THE REST ARE NOT ==================
   The README has described this block since it was built -- "the one you are
   on at heading weight, the other two quiet beside it and pressable" -- and
   the CSS gave all five extrabold and separated the current one by ink
   alone. Four tabs at 800 read as four headings, and the strip stopped
   looking like a heading with alternatives and started looking like a menu
   bar shouting.

   Ink alone is also one axis, which is the thing this build stopped
   accepting everywhere else: a rank differs from its neighbour in at least
   two of {size, weight, ink}. Now it differs in two. */
.b-switch-btn {
  appearance: none; border: 0; background: transparent; padding: 0;
  cursor: pointer; font-family: var(--font-display);
  font-size: var(--fs-lg); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-tight); color: var(--ink-muted);
  transition: color var(--t-fast), font-weight var(--t-fast);
}
.b-switch-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
/* ══ ONE STEP IS ENOUGH, AND THE COUNT COMES WITH IT ═════════════
   The note above says a rank differs from its neighbour in at least two of
   {size, weight, ink}, and reads the on-state as differing in two. It
   differs in three: the ink moves --ink-muted to --d50, the weight moves,
   and .b-switch-bar slides under it. Two of those were already enough, so
   the weight did not need to jump 500 to 700 — two whole steps, landing on
   --fw-extrabold, which is the top of the ladder.

   That register should be rare or it stops meaning anything, and a tab is
   not the loudest thing on a page that also has a page title. One step to
   --fw-bold keeps the rank and gives the top back to what it is for.

   THE COUNT. There was a rule here whose whole job was to hold the count at
   --ink-muted while its tab lit up, so the one number on the active tab was
   the one part of it that did not respond. The intent was right — the count
   must not compete with the name — but muted is not the only way to be
   subordinate: it is already four points smaller and a weight lighter.

   So it takes the accent instead, which is the same language the filter
   chips underneath already use for "this is the one selected". --accent-text
   rather than --accent, because the raw hue is 3.26:1 against this ground
   and the text-safe one is documented at 5.04. */
.b-switch-btn.is-on { color: var(--d50); font-weight: var(--fw-bold); }
.b-switch-n {
  margin-left: 6px; font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums; color: var(--ink-muted);
}
.b-switch-btn.is-on .b-switch-n { color: var(--accent-text); }
/* ══ WHICH BOOK THE ROW IS COUNTING, ABOVE THE ROW ═════════════════
   A client's desk carries one more control than anybody else's: which of the
   things they bought every tab in the row is about. It is not a fifth tab,
   so it does not sit in the row — it changes what the row COUNTS, and a
   control that changes the meaning of what is under it goes above it.

   `.s-camp-list-head` is a baseline flex row whose first child takes
   `margin-right: auto`, which is what makes a heading read as a heading
   rather than as one of the controls beside it. `flex-basis: 100%` hands the
   chips the whole line above, and the auto-margin has to move with them or a
   find box at the far end of the tab row lands next to the tabs instead. */
/* ══════════════ ON THE TABS' OWN ROW, AT ITS FAR END ══════════════
   It had a line of its own for a commit, and the line was the right answer
   to the wrong problem: what made the row a mess was three controls of
   three shapes with a caption floating loose between two of them, not the
   row. The caption is gone and this is one pill now — a tab strip at the
   left and a control at the right is a toolbar, which is what it always
   should have been.

   `order`, not a move in the markup: `switcher` emits this before the tab
   row because that is the reading order a screen reader wants — which
   book, then the tabs of it — and the eye wants the opposite. Flex is
   where that difference belongs.

   `margin-right: 0` because `.s-camp-list-head > :first-child` hands its
   auto to whatever is first in the DOM, which is still this: left alone it
   takes the free space on its own right and sits short of the edge.

   `align-self: center` against a row that aligns on the baseline. A pill
   has no baseline worth sharing with an 18px tab — sat on one it hangs
   below the strip by the depth of its own padding. On the Campaigns tab,
   where a find box is on the row too, three controls still wrap to a
   second line under about 1200px, and that is the row running out of room
   rather than a shape nobody chose. */
.s-camp-list-head > .s-engpick {
  order: 99; margin-left: auto; margin-right: 0; align-self: center;
}
/* ══════════════ EXCEPT WHERE A FIND BOX IS ALREADY ON IT ══════════════
   Campaigns is the one tab that carries a search field, and a tab strip, a
   field and a control is where the row stops being a toolbar and starts
   being three things in a line. It also wraps on its own under about
   1200px, which puts the scope on a second line at some widths and not
   others — a layout that changes shape with the window is worse than
   either shape.

   So where there is a find box the scope takes the row above it, at every
   width, and the tabs and the field keep theirs. `:has` is how this head
   already branches on the same condition three rules down.

   `order: 0` puts it back where the markup has it — first, which is the
   line above — and `justify-content` does what the auto margin did, because
   an item that is already the full width has no free space for a margin to
   absorb. */
.s-camp-list-head:has(.b-find) > .s-engpick {
  order: 0; flex-basis: 100%; justify-content: flex-end; margin-left: 0;
}
.s-camp-list-head > .s-engpick + .b-switch { margin-right: auto; }
/* And the same end of the report's header. `.s-exec-top` is
   `space-between` and its own margin says the control that changes what is
   read sits opposite the title — but the title block takes the full
   measure, so the control wraps to a line of its own and `space-between`
   has nothing left to push it against. An auto margin holds it at the
   edge on that second line, which is where the header meant it to be. */
.s-exec-top > .s-engpick { margin-left: auto; }
.s-engpick { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.s-engpick-btn { display: inline-flex; align-items: center; gap: 8px; }
/* The longest label here is a product's whole name, and `.b-menu`'s 216 is
   shared by every menu in the build — so "Managed customer support" broke
   over two lines among three that take one, and a list read in one glance
   looked ragged for want of eleven pixels. 216 is a MINIMUM: an item that
   refuses to wrap widens this menu to its widest name and leaves every
   other menu alone. */
.s-engpick-menu .b-menu-item { white-space: nowrap; }
/* Quiet, and it grows nothing on hover: the caret is a statement about the
   control rather than a second thing to look at. */
.s-engpick-btn svg { width: 14px; height: 14px; opacity: 0.6; flex: none; }
/* Which one you are reading, inside the list as well as on the button — a
   menu opened to change scope should say where you already are. */
.b-menu-item[aria-current] .b-menu-name { color: var(--accent-text); }

/* ══ 11. THE QUEUE, AS CARDS IN THREE COLUMNS ══════════════════════════════
   A row had space for a name, a line and a button — enough to be ranked by
   and not enough to prepare with, so every call began by opening the record
   to find out who this was. A card carries what a caller wants before the
   call, and three of them fit across the page.

   `repeat(3, …)` with a floor, not `auto-fill`: the shell's own `.s-grid`
   fills by width and would give two columns here and four on a wide monitor.
   Three is the number the card was written for. It drops to two and then one
   where three would stop fitting.
*/
/* ══ THE COLUMN COUNT IS THE GRID'S TO WORK OUT ════════════════════════════
   This was three columns, then two under 1280 and one under 720, and the
   trouble with counting columns at thresholds is the width just above each
   one. At 1281 layout the queue still held three, which at that width is
   288px a card — measured, and below the 324 the note under this rule calls
   the point where a card stops holding what it is for. Every threshold has a
   worst case on its wide side, and picking more thresholds only makes more of
   them.

   `auto-fill` has no worst case. The track floor IS the 324 that was measured
   here, so the grid fits as many columns as can be 324 or wider and drops one
   the moment they cannot be — at every width, including the ones nobody
   thought to write a threshold for. Three columns survive to 1440 layout and
   the reference build is unchanged at 1536; 1200 gets two at 400px where it
   used to get three at 333.

   auto-FILL rather than auto-fit: a fit collapses the tracks it has not
   filled, so a queue cut with one person left in it would stretch that card
   across the whole row. Fill keeps the track and the card keeps the width its
   neighbours would have had.

   min(324px, 100%) rather than 324px, so a container narrower than a card —
   the drawer, a narrow phone — gets one column that fits rather than one
   column that overflows. */
.b-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(324px, 100%), 1fr));
  gap: 12px;
  align-items: stretch;
}

/* ── NO FIXED HEIGHT, OR A TALL CARD LOSES ITS BOTTOM ─────────────────────
   This was `height: 100%`, which pins the card to the height the grid row
   already has — and `.type-card` carries `overflow: hidden` for its rounded
   corners, so any card whose content ran past that had the rest cut off.
   Measured on a three-line AiMY note: the block's tint, the basis line and
   part of the number were simply gone.

   The grid's `align-items: stretch` was already doing the job the height was
   added for. Without the height the row sizes to its tallest card and every
   card stretches to match it, which is the same result and cannot clip. */
.b-qcard { gap: 0; height: auto; }
.b-qcard .tc-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.b-qcard .tc-type { margin-left: auto; text-align: right; }

/* ── ONE LINE, OR THE HEAD STOPS BEING A ROW ─────────────────────────────
   The head wraps, so a campaign with a long name took a second line and the
   two halves of the strip — the state on the left, the campaign on the right
   — stopped being one row. Two cards beside each other then disagreed about
   where the name beneath them started, on nothing more than how many words
   a campaign was given, and the taller head pushed its whole card down.

   The name is a label, not a sentence: the first two thirds of it already
   says which campaign this is, and the card opens to the rest. So the head
   holds its one line and the name gives up its tail. The tag never shrinks
   — it is two words and the fixed half of the pair — and the mark is
   already `flex: none`, so every pixel of the shrinking lands on the words
   that can afford to lose one.

   Scoped to the two cards whose head is a PAIR — a person's and a company's,
   both `[data-card]`, both a state on the left and one fact on the right. The
   campaign card's head is three parts and its third is the product being
   sold, which sits close enough to its limit that one line would cut a word
   in half to save twenty pixels. That is a worse trade and it keeps the
   wrap. */
.b-qcard[data-card] .tc-head { flex-wrap: nowrap; }
.b-qcard[data-card] .tc-head > .tag { flex: none; }
.b-qcard[data-card] .tc-type > span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── THE CARD'S OWN PADDING IS ON ITS CHILDREN ────────────────────────────
   `.type-card` is `padding: 0` and every `.tc-*` part carries `… 14px` of its
   own. Two of the parts here are this build's and had none, so the reason
   line and the note ran flush to the card's left edge while the name and the
   company above them were indented — a ragged margin down the middle of every
   card. The 14px is the library's number, not a new one. */
/* ══ AND IT IS STILL A CARD'S LINE, ON TWO OTHER CARDS ═════════════════════
   `.b-qcard-note` is gone: it was the evidence for a sentence the card was
   already saying. `.b-qcard-why` came off the PERSON card for the same kind
   of reason — the why was an insight drawn outside the insight — and the
   rule went with it, which broke two cards nobody had looked at. A campaign
   card says how many of its people are left to call and a list card says how
   many of its people are callable; neither is an insight and both need the
   text column the name and the description above them stand in. */
.b-qcard-why {
  padding: 8px 14px 0;
  font-size: var(--fs-base); font-weight: var(--fw-semibold);
  line-height: var(--lh-snug); color: var(--ink-primary);
}
.b-qcard-why b { font-weight: var(--fw-semibold); }

/* ── AND THE QUOTE'S RULE WAS ON THE CARD'S BORDER ────────────────────────
   The fix above moved the note's TEXT off the edge and left its rule behind.
   `.tc-quote` is `padding-left: 9px; border-left: 2px solid var(--card-border)`
   — a rule at the block's left edge with the words nine px clear of it — and
   it is written for a card that carries its own padding, where that edge IS
   the text column. Here the block spans the whole card, so the rule landed at
   x=28 against the card's own border at 27.2: two hairlines touching, which
   reads as a thick left edge on one card in the row and nothing else.

   The rule goes rather than moving to the column. Against the card it sits on
   it measures 1.23:1 in both themes — visible only as the reason for an
   indent, and an indent is what it was costing. Nothing else on this card is
   ruled; the italic and the drop to --d300 already say the words are somebody
   else's. So the note joins the one column every other line here keeps. */


/* ── THE RAMP, AND ALL THREE AXES MOVE THE SAME WAY ───────────────────────
   The first cut ranked by ink alone and everything was a different grey, with
   the number a caller was about to dial the faintest thing on the card. The
   second overcorrected: nearly everything at full white, so the name at 16/800
   and the reason at 14/600 were both shouting and neither led.

   Size, weight and ink descend together, which is what makes a hierarchy read
   at a glance rather than on inspection.

   THE THIRD CUT: THE FLOOR TOOK THE SIZE AXIS AWAY. The ranks above spent
   their differences on five steps inside six pixels — 16 · 13 · 13 · 12 · 12
   · 12 · 10 — and a 12px floor on even steps leaves 12 · 14 · 16 · 18. Four
   of those five collapsed onto 16, the ink band folded d300 and d400 into
   --ink-muted, and eleven of the seventeen runs on this card came out at
   16/500-800 in three greys a hair apart. The reasoning survived; the numbers
   stopped obeying it.

   So the differences move onto the axis that did not change. Weight was
   carrying almost nothing: three ranks shared d50 and four shared one size.
   EVERY RANK NOW DIFFERS FROM ITS NEIGHBOUR IN AT LEAST TWO OF {size, weight,
   ink} — which is what the paragraph above always claimed and the table never
   did.

     18 / 800 / primary     the name       what you are choosing between
     16 / 700 / primary     why it is here two steps under the name, so it reads second
     16 / 700 / secondary   the figure     the amount, or the number you are about to dial
     14 / 600 / secondary   the role       identity
     14 / 500 / muted       the company    context you read once
     14 / 400 / muted       what was said  somebody else's sentence, italic
     14 / 700 / muted       the campaign   a label, not a sentence

   The name is the only 18 on the card, so L1 is unambiguous without being
   loud. The figure shares the why-line's size and weight and steps down one
   in ink: it sits in the foot beside a button, so nothing is competing for
   the same place in the reading order. Everything below is 14, separated by
   weight and ink alone, because that is what "context" means — a band you
   scan rather than a ladder you climb.

   Scoped to `.b-qcard`, so no other card in the product moves. */
.b-qcard .tc-type {
  font-size: var(--t-small); font-weight: var(--fw-semibold); color: var(--ink-muted);
}
.b-qcard .tc-title {
  font-size: var(--fs-lg); font-weight: var(--fw-bold);
  line-height: var(--lh-tight); color: var(--d50);
}
.b-qcard .b-qcard-role {
  padding: 6px 14px 0; font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  line-height: var(--lh-snug); color: var(--ink-secondary);
}
/* ══ THE FACTS ROW IS A MINIMAL COMPONENT, NOT A SENTENCE ══════════
   "Wenside Works · Manufacturing · Antwerp · 40 staff" is four chips of one
   or two words. The scale calls 12 the minimal size — "a tag, a work state,
   a timestamp — recognised, not read" — and this is exactly that; it sat at
   14 only because --fs-xs was the reflex token for anything small.

   The cost of that reflex, counted on the desk: 188 of 260 text runs came
   out at 14px, against 29 at body and 15 at heading. But only ELEVEN of
   those 188 were prose. The rest were labels, so the answer was never to
   push them up to body — it was to let the minimal ones sit at minimal, and
   give the 14px step back to its actual job: the supporting line under a
   name, which here is .tc-summary carrying the role.

   That also buys the card a second axis where it had one. Role against
   facts was 14/500 against 14/400 — one weight step, same ink, which the
   type doctrine does not count as a rank. It is now 14/500 against 12/400:
   size and weight, two axes, visible at a glance.

   Poppins is what makes 12 safe here. Its x-height runs 7.8% above
   Urbanist's, so this line has the x-height Urbanist would have needed
   12.9px to reach — the step down costs less legibility than it would
   have under the old face. */
.b-qcard-where {
  margin: 0; padding: 2px 14px 0;
  font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  line-height: var(--lh-snug); color: var(--ink-muted);
}
/* EMPHASIS INSIDE A LINE NEEDS THE LINE NOT TO BE BOLD ALREADY. This ran at
   700 with its <b> runs inheriting `bolder` on top, so the date and the
   overdue clause were 800 inside a line that was already 700 white — two
   weights doing the work of none, on the longest run on the card. Two lines
   of that out-massed an 18px name by area alone, which put the card’s L1 and
   L2 in the wrong order however the sizes read.

   600 for the sentence, 800 for the two things in it that are the reason the
   card is here. */


/* The number and the one action, pushed to the foot of the card so every card
   in a row ends on the same line.

   `.b-qcard .b-qcard-foot`, not `.b-qcard-foot`: the foot also carries
   `.tc-gov`, and sales.css:2550 sets `.s-card .tc-gov { margin-top: 16px }` at
   (0,2,0) — which beat a bare class and left the footers 21px out of line on
   cards that were themselves exactly the same height. Measured at 612 · 633 ·
   628 across one row of three. */
.b-qcard .b-qcard-foot {
  margin-top: auto; display: flex; align-items: center;
  justify-content: space-between; gap: 8px; flex-wrap: wrap;
}
/* With the amount off the manager's card the foot holds one thing, and
   space-between puts a lone child on the left — under a card whose every
   other row is left-aligned, which made the verb read as another line of
   text. It stays where it has always been. */
.b-qcard .b-qcard-foot > :only-child { margin-left: auto; }
/* ══ THE BODY'S LAST LINE DOES NOT ALWAYS END ITSELF ═══════════════
   On a card with a reading on it, `.b-aimy` closes the body with its own
   `margin-bottom: 14px`, and that is the space between the last block and
   the rule `.tc-gov` draws along its top. A campaign nobody has started has
   no reading — there is nothing to read off one with no calls on it — so
   `.b-qcard-why` becomes the last block in the body and it has no bottom of
   its own. Measured at 0px from the words to the rule: the line saying who
   asked for this and the line saying whose desk it is on read as one
   paragraph with a hairline through them.

   ══ AND THE FIRST FIX PUT THE SPACE ON THE WRONG SIDE OF THE RULE ═══
   It was `padding-top: 23px` on the foot, which is INSIDE the border: the
   foot's contents moved down 14px, the rule stayed exactly where it was
   against the words above it, and the card grew for nothing. Measured text
   to text it read as fixed — 27px against 13 — and the thing anybody could
   actually see had not moved at all. Space meant to separate a block from a
   BORDERED one below it has to sit above that border: padding under the
   block, or a margin, and never padding inside the bordered box.

   `margin-top: auto` cannot do it either. That is what pushes the foot to
   the card's bottom, and on the tallest card in a row there is no slack for
   it to take, so it computes to 0.

   So it goes under the line itself, and `is-last` is written by the card,
   which already knows the answer: the same `isDraft` that decides whether
   to draw the reading. A class rather than `:has(+ .b-qcard-foot)`, which
   says the same thing in a feature that can be absent, and which the audit
   cannot pair with anything. 14px, so a draft and a running campaign close
   their bodies the same distance from the rule. */
.b-qcard .b-qcard-why.is-last { padding-bottom: 14px; }
.b-qcard-num {
  font-size: var(--fs-base); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums; color: var(--ink-secondary);
}

/* ── A NAMED FACT, AT THE PREP SHEET'S RELATIONSHIP ───────────────────────
   The campaign's goal and the list's search, each led by the word for what
   it is. One step of ink between the name and the sentence — the same step
   `.b-prep-refp b` takes over its own body — is enough to read as a label
   without becoming a second heading on a card that already has one.

   `text-wrap: pretty` because these run two and three lines in a 340px card
   and the last line is where a lone word lands. */
/* ── AND THE WORD FOR IT IS A MARK NOW ──────────────────────
   Both cards that use this ran a caption in front of the sentence — Goal,
   Who — and a caption in front of a sentence is read as its first word
   whatever its size, weight and ink. The list card dropped its label; the
   campaign card's became the target from the icon set, drawn through
   `.b-fact` like every other icon-then-fact row in the build.

   So the `> b:first-child` recipe that sat here — twelve semibold in
   --d400, tuned against the sentence over three passes — has nothing left
   to style. It goes with the words it was for. */
/* The mark belongs to the first line, not to the block. `.b-fact` centres its
   glyph, which is right for a one-line fact and wrong the moment the sentence
   wraps: on a two-line goal the target floated into the gap between the lines,
   pointing at neither. Top-aligned, and nudged onto the cap height of the line
   it opens. */
.b-qcard-what.b-fact > svg { align-self: flex-start; margin-top: 6px; }
.b-qcard-what {
  text-wrap: pretty;
  font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--d200);
}

/* The keyboard cursor on a card. A call rather than a tint: a card already
   has a ground of its own, and tinting it would fight the status tag on it. */
.b-qcard.is-cursor { border-color: var(--accent); }

/* ══ 12. WHAT AiMY KNOWS ABOUT THIS ONE ════════════════════════════════════
   One line per card, under the mark, with the record it came from named
   underneath it. The mark is what makes it AiMY's rather than the product's,
   and the basis line is what makes it a reading rather than an assertion —
   an insight that cannot say where it came from is one a caller has to take
   on trust, and the first wrong one costs the rest.

   Tinted rather than bordered: it belongs to the card, not beside it. */
/* ── THE BLOCK IS THE CARD'S FLEXIBLE REGION ──────────────────────────────
   It had no bottom margin and the foot above it took `margin-top: auto`, so
   the free space in a card landed BETWEEN them — which meant the tallest card
   in a row had no space at all and its block's rounded bottom sat flush on
   the footer rule. Measured across nine cards: gaps of 28, 11, 0, 0, 51, 51,
   0, 0, 0. Zero reads as clipped, and 51 next to 0 reads as broken.

   So the block absorbs the slack instead of the gap doing it. `flex: 1 1 auto`
   lets it grow to whatever the row's tallest card left over, its text stays
   at the top, and every card's block now ends the same distance above the
   foot. The blocks differ in height and agree on where they finish, which is
   the opposite of what they did before. */
.b-aimy {
  flex: 1 1 auto;
  display: flex; gap: 8px; align-items: flex-start;
  margin: 10px 14px 14px; padding: 10px 12px;
  background: var(--accent-dim);
  border-radius: var(--r-md);
}
/* On a card, the note is EVIDENCE and evidence is read last. Everything else
   on the card is type on the card’s own ground; this was the one filled
   rectangle, which made it a figure rather than a passage no matter what its
   type did. Half the ground, and the mark keeps saying whose sentence it is —
   which is the job the fill was doubling. Scoped, so the note keeps its full
   ground everywhere it is the point of the block rather than the footnote. */
.b-qcard .b-aimy { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.b-aimy-mark { width: 13px; height: 15px; flex-shrink: 0; margin-top: 1px; }
.b-aimy-say {
  min-width: 0; font-size: var(--fs-xs); font-weight: var(--fw-medium);
  line-height: var(--lh-snug); color: var(--ink-secondary);
}
.b-aimy-say b { font-weight: var(--fw-semibold); }
.b-aimy-from {
  display: block; margin-top: 4px;
  font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--ink-muted);
}
/* ── THE LAST LINE OF A READING CAN BE A VERB ─────────────────────────────
   One control, under the sentence and under its source, where the reading
   names something the reader can fix from where they are standing — a list
   card saying six of them came back without a number, with the thing that
   goes and gets them.

   `display: block` on a span inside a span, which is what lets it take its
   own line without making `.b-aimy-say` a flex column: the text above it is
   a run of inline content and a column would break every `<b>` in it onto a
   line of its own. The 8px is one step over the 4px the source line takes,
   because a thing you press needs more air above it than a caption does. */
.b-aimy-act { display: block; margin-top: 8px; }

/* ══ 13. THE BUILDER'S HEADING TAKES ITS OWN WIDTH ═════════════════════════
   `.s-sheet-head` is a flex row and `.s-sheet-head-main` is only `min-width:
   0` — it has no grow, so it is sized by its content. Its content is a text
   input at `width: 100%`, which resolves against a parent sized by that same
   input: circular, so the browser falls back to an input's default width and
   the list name reads "Software · 200 to 1,000 · Nethe".

   In the V3 build the head sits in a sheet that constrains it; here it sits
   in the page grid, which does not. One `flex: 1 1 auto`. */
.s-home > .s-sheet-head > .s-sheet-head-main { flex: 1 1 auto; }

/* §14 styled the conversation options. The shell already has them —
   .s-cb-opts, .s-cb-opt and .s-cb-hint — so this build renders those and
   defines nothing. */


/* ══ 15. THE THIRD RANK ON A RECORD HEADER ════════════════════════════════
   A company header carries three ranks of fact, and the shell has two: the
   heading, and `.s-block-sub` at `--d500` for everything under it. The third
   is our record OF the company rather than a fact about it — the domain, the
   region, which campaigns of ours it sits on — and none of it changes who
   you call next, so at `--d500` beside the headcount it competed with the one
   line the block exists for.

   `--d600`, which is the next step down the ink ladder the library already
   ships. Barely muted, on purpose: quiet enough to rank below the size and
   the industry, loud enough that the domain is still readable, because a
   fact nobody can read is a fact that may as well not be drawn. */

/* ══ 16. A CARD THAT OPENS SOMETHING LOOKS LIKE IT ═════════════════════════
   The library already gives `.type-card` a hover state — the border lifts and
   a shadow arrives — which is a promise that pressing it does something. It
   did not: only the title inside was a door, so the hover was a lie the card
   told about ninety per cent of its own surface.

   The pointer is the other half of that promise, and it is all that is added:
   the border and the shadow already say the card is live, and a second
   emphasis on top of them would make a grid of fifteen cards flash. Controls
   inside keep their own cursors, and the router matches them first. */
.b-qcard[data-open] { cursor: pointer; }

/* ══ 17. THE SEARCH BOX ════════════════════════════════════════════════════
   The shell has one search field, `.dd-search`, and it is built to sit at the
   top of an open dropdown: sticky, negative margins, a hairline under it and
   the panel's own ground behind. Dropped into a page header it drew a full-
   width bar with a rule under it across the whole block.

   So this is that field's anatomy — an icon, a bare input, no chrome on the
   input itself — in a shape that sits in a flex header row beside the
   switcher. It borrows the card's border and ground rather than inventing
   its own, because it is a control on a page and not a panel of its own.

   `align-self: center` because `.s-camp-list-head` aligns on the baseline,
   which puts a bordered box's text baseline level with a heading's and hangs
   the box's top edge above the line. */
.b-find {
  display: flex; align-items: center; gap: 6px; align-self: center;
  padding: 6px 10px; border-radius: var(--r-lg);
  border: 1px solid var(--card-border); background: var(--card-bg);
  min-width: 220px; max-width: 320px; flex: 0 1 280px;
  transition: border-color var(--t-fast);
}
.b-find:focus-within { border-color: var(--accent); }
.b-find svg { color: var(--ink-muted); flex-shrink: 0; }
.b-find input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none;
  font-family: var(--font-sans); font-size: var(--fs-xs); color: var(--d50);
}
.b-find input::placeholder { color: var(--ink-placeholder); }
/* A way out that does not need the keyboard. Only drawn when there is
   something to clear, so it is never a control that does nothing. */
.b-find-x {
  border: 0; background: none; cursor: pointer; padding: 0 2px;
  font-size: var(--fs-sm); line-height: 1; color: var(--ink-muted);
  transition: color var(--t-fast);
}
.b-find-x:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }


/* ══ 18. THE ACTIONS BELOW THE TITLE ROW ═══════════════════════════════════
   `Call these 15` and `Let AiMY call 15` are drawn from what the search
   matched, so they come and go while you type. Sharing a flex row with the
   search box meant the box moved every time they did — sliding sideways under
   the cursor mid-word, which is the one thing a search field must never do.

   So they live here, under a title row that holds only what cannot change:
   the switcher, whose counts are of the whole book, and the box. Pulled up
   into the head's own bottom margin so the two rows still read as one header
   rather than as a header and a stray row of links. */
.b-acts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  margin: -8px 0 16px;
}

/* ══ 19. THE LADDER AS A NARROWING ═════════════════════════════════════════
   Ten counts in one flat run put `9 meeting set` and `2 do not call` at the
   same rank and hid the one shape a campaign has: it narrows. Two hundred
   called, forty answered, nine with a meeting.

   Three columns, and the middle one is the finding. Every bar is drawn
   against the WHOLE roster rather than against the largest called — scaled to
   the biggest bar, every campaign looks the same shape and the narrowing
   disappears. The name and the number are at full ink because they are the
   content; the track behind the bar is the only quiet thing here. */
.b-funnel {
  display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content;
  gap: 8px 14px; align-items: center; margin: 4px 0 4px;
}
.b-fn-name { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--d100); }
.b-fn-bar {
  height: 8px; border-radius: var(--r-pill);
  background: var(--card-bg-raised); border: 1px solid var(--card-border);
  overflow: hidden;
}
.b-fn-fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--d400); }
/* ══ AND THE FUNNEL DRAWS ITSELF ═══════════════════════════════════════════
   The attainment bar two surfaces away wipes in from the left and this one,
   which is the thing a campaign page is actually about, appeared at full
   length. It takes the same keyframe rather than a new one: `s-att-wipe` is
   `from { scaleX(0) }` with no `to`, so it runs to whatever transform the
   element already has and leaves nothing behind it.

   THE WIDTH STAYS THE WIDTH. Scaling the bar instead of sizing it would mean
   carrying scaleX at rest, and `--r-pill` on an 8px bar is a 4px semicircle
   at each end — squashed to under two pixels by a 0.42 scale, so every bar
   would end with a differently-shaped cap according to its own value. The
   percentage still sets the length, which is a write per render and not a
   thing being animated; the scale is only how it arrives. Nothing here
   animates a width.

   NO STAGGER, AND THAT IS THE POINT. Five bars arriving one after another
   are read one after another. They arrive together so the eye gets the
   profile. sales.css puts it plainly where it draws its own funnel — the
   narrowing IS the finding — and a narrowing is a shape rather than a
   sequence. */
.b-fn-fill { transform-origin: left center; }
#wbStage.is-arriving .b-fn-fill {
  animation: s-att-wipe var(--t-emphasis) var(--ease-out) backwards;
}
@media (prefers-reduced-motion: reduce) {
  #wbStage.is-arriving .b-fn-fill { animation: none; }
}
.b-fn-fill.tone-ok { background: var(--ok-text); }
.b-fn-fill.tone-warn { background: var(--warn-text); }
.b-fn-fill.tone-neutral { background: var(--d300); }
/* Right, like every other column of figures on the page. It carried
   `tabular-nums` and left alignment, which is the two halves of the job
   pulling opposite ways: the digits were all one width and then set from the
   left, so 35 over 9 put the tens of one over the units of the other and the
   column read ragged down its right edge — the edge the eye compares on. The
   counts in What is in the way two sections below were already right. It is
   the last column now, so it is also the row's right edge. The header cell inherits it, so the
   caption sits over the edge its column is set against. */
.b-fn-n {
  font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--d50);
  font-variant-numeric: tabular-nums; text-align: right;
}
/* The ways off the ladder. A sentence, not a fourth bar: three counts that
   share one fact — these people are no longer callable — read as a sentence,
   and drawing them as steps would put leaving on the same scale as climbing. */
.b-tally-out { margin-top: 10px; font-size: var(--fs-xs); color: var(--d200); }

/* ══ 20. WHAT TO SAY ═══════════════════════════════════════════════════════
   Four kinds of preparation at one weight, in the block a caller scans in the
   ten seconds before a call connects. It ran the product names together in a
   paragraph, gave the pitch the same step, drew the objections with the
   Remember stripe — a treatment meant for a durable fact about one person —
   and finished with grey tags.

   Now each part is captioned and the opener is the loudest thing in it,
   because the opener is the only line here you say out loud. */
/* What comes back at you and the answer agreed to it. It was a section on
   the campaign page and it is a part of the brief now — the caption is the
   brief's, the pair underneath is the one this build already drew. */
/* ══ PROXIMITY IS THE AXIS NOBODY SPENT ═══════════════════════
   Hierarchy is size, weight, tone AND space. The first three were tuned
   over several passes and the fourth was never touched, which is why the
   call-prep panel still read as one undifferentiated column of text.

   Measured, top-level gaps down that panel against the gaps INSIDE its
   groups:

     between sections   14  12  15  15   9  10  36
     inside a group     10 (answer rows)   6 (shelf lines)

   A section boundary and a row boundary were the same distance apart, so
   nothing grouped and everything read as one list. This heading was the
   clearest case: 15px above it and 9px below, very nearly equidistant
   between the block it ENDS and the block it INTRODUCES. And the largest
   gap on the panel, 36px, sat in front of the shelf — the least important
   thing there.

   Three tiers now, at roughly 3:1, which is the point where proximity
   starts doing work: 26px between sections, 12-14px inside a section, and
   6px from a heading to the thing it introduces. A heading belongs to what
   follows it, so it sits close to that and far from what it ends. */
/* RANK A. The chip is declared once beside .b-back-k; this adds the two
   things that lift a section head above the rows it contains — a wash
   behind it, and capitals. It keeps the tracking capitals need.

   TWO TRAPS SPRUNG HERE, BOTH MEASURED IN PLACE, BOTH RECORDED SO THE NEXT
   PERSON DOES NOT PAY THEM AGAIN.

   The wash was var(--wash-7) and it rendered transparent. --wash-3/6/7 are
   declared on .ntf-anchor, not on :root, so outside the notification anchor
   they do not resolve and background silently falls back. Nothing warns.
   --card-bg-raised is no good either: it is #1c2630, which is exactly the
   message bubble this panel sits in, so the chip would be invisible. What a
   chip needs is not a colour but a LIFT off whatever is behind it, so it
   mixes 8% of the primary ink into transparent — white-ish on the dark
   theme, near-black on the light one, correct in both without a fork.
   color-mix is already the idiom here; .b-prep-due.is-late uses it.

   And it stretched the full 492px instead of hugging its words. .b-prep is
   a flex column, which blockifies inline-block on its children and then
   stretches them. align-self is what stops that; the other two chips are
   not direct flex children and were already hugging. */
.b-brief-cap {
  margin: 26px 0 6px;
  align-self: flex-start;
  background: color-mix(in srgb, var(--ink-primary) 8%, transparent);
  letter-spacing: 0.08em; text-transform: uppercase;
}
/* ══ 21. THE QUIET STEPS WERE TOO QUIET ════════════════════════════════════
   `--d500` is #637280 and `--d600` is #4a5b6e. Against this ground they are
   for chrome — a placeholder, a disabled control — and the shell spends them
   on content: `.s-block-sub` is the sentence under every heading in this
   build, `.b-vfoot` is how many of what you are looking at, `.s-rec-kind` is
   what kind of thing you have open. All three are things the reader is meant
   to read, drawn in the colour of things they are not.

   Raised, and the hierarchy comes from size and weight where it already did:
   `.s-block-sub` is 13px against a 20px heading, `.b-vfoot` is 13px under a
   card grid. Nothing here changes a size — it only stops the smaller step
   from also being the dimmer one. */
.s-block-sub { color: var(--d200); }
.b-vfoot { color: var(--d200); }
.s-rec-kind { color: var(--ink-muted); }
.b-aimy-from { color: var(--ink-muted); }
.s-callsum-cap { color: var(--ink-muted); }
.s-callsum-val { color: var(--d50); }
.s-call-by { color: var(--d200); }
.s-call-ago { color: var(--ink-muted); }
.s-block-say { color: var(--d200); }
/* The way back is a navigation control, not a footnote. At --d400 it was the
   dimmest thing on a record page, under a masthead at full ink. */
.s-back { color: var(--d200); }

/* ══ AND THE SAME SWEEP OVER FINANCIALS ════════════════════════════════════
   The section above did this for the surfaces a caller reads and stopped at
   the manager's. Counted on the money page: 160 text runs at `--d300` or
   `--d400` across 21 classes — the unit under every figure, the state on
   every campaign, the hours and rates on every crew row, the legend under
   the attainment bar, every section's eyebrow and every note under one. The
   densest page in the build was also the dimmest.

   Every one of them is content. "11.1 hours at €58.23 an hour" is the
   resourcing decision; "of €300k" is the denominator of the biggest figure
   on the page; "Where you should be today" is what a mark on the bar means.
   None of it is chrome and none of it should be read at chrome's ink.

   NOT ONE SIZE CHANGES. The ranks on this page are already carried by size,
   weight and case — 24px figures over 18px names over 14px meta over 12px
   caps — which is where a rank belongs. Dimness was doing the job a second
   time, badly, and it is the axis that costs the reader their eyes. */
/* `.s-pan-restitle` was missed when this list was written, and it is the
   only sublabel on the page still at `--d400` — the same 12px, 600, uppercase
   and 1.2px tracking as `.s-exec-eyebrow`, `.s-odds-cap` and `.s-pan-unit`
   three lines up, two steps dimmer for no reason anybody recorded. It labels
   the two halves of a client's promise ledger, either side of the handover,
   which makes it the most load-bearing sublabel here and was the quietest. */
.s-pan-facts span, .s-pan-meta, .s-pan-unit, .s-pan-state,
.s-exec-eyebrow, .s-exec-note, .s-af-cap, .s-af-sub,
.s-cost-note, .s-odds-cap, .s-odds-n, .s-odds-note, .s-pan-restitle,
.s-att-of, .s-att-key, .s-asks-cap { color: var(--d200); }
/* "Nothing" is a state a campaign is IN, not a value that failed to arrive.
   Dimming it said the second. The word carries the meaning, which is also
   the rule about never leaving colour to do it alone. */
.s-pan-total.is-none { color: var(--d200); }

/* ══ THE CAMPAIGN NAME OPENS THE CAMPAIGN ══════════════════════════════════
   It inherits the name's type exactly — the panel heading is the door, not a
   link dressed up beside it — and adds only what says it can be pressed. The
   accent arrives on the cursor, the way every other quiet door in this build
   announces itself, so a column of ten does not read as ten links. */
.s-pan-go {
  font: inherit; color: inherit; background: none; border: 0; padding: 0;
  text-align: left; cursor: pointer;
  transition: color var(--t-fast);
}
.s-pan-go:hover { color: var(--accent-text); }
.s-pan-go:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }

/* ══ THE MARKS WERE UNDER THE FILLS, NOT BEHIND THEM ═══════════════════════
   "Where you should be today" is in the legend and is not on the bar. It
   renders — measured at 61.1%, two pixels wide, twelve tall, in the right
   place — and `document.elementFromPoint` at its own centre returns
   `.s-att-booked`. The fill is on top of it.

   `.s-att-booked` and `.s-att-fcast` carry `z-index: 1`. `.s-att-track`
   holds them and is `position: relative` with `z-index: auto`, so it does
   not open a stacking context of its own — the fills' z-index competes at
   the level of the bar, against two marks sitting at `auto`. The fills win
   and paint over both.

   The target survives only because it overhangs: `top: -4px; bottom: -4px`
   puts eight pixels of it outside the track, and those two stubs are the
   entire mark anybody has ever seen. The pace mark is inset — `top: 3px;
   bottom: 3px`, wholly inside — so none of it is left.

   This is the second half of a bug the sheet already has a paragraph about.
   That note moved the marks OUT of the track so `overflow: hidden` would
   stop clipping them, and clipping was never the whole of it; the fills were
   painting over the part that was not clipped. Raising the marks over the
   fills is the rest of that fix, and it is what makes the shape difference
   the note describes — a line that crosses, a tick that sits inside —
   readable for the first time. */
.s-att-target, .s-att-pace { z-index: 2; }
/* ══ ONE SHAPE FOR BOTH LISTS ══════════════════════════════════════════════
   What we sell and what they push back on are the same shape — a name and a
   line about it — and they were drawn two different ways. The first ran bold
   name into plain blurb on one line with an 8px gap, so where the name ended
   was a thing you worked out rather than saw. The second sat in a 96px RIGHT-
   aligned gutter, which put "Something else" on two lines and left every
   label a different distance from the answer it belongs to.

   One grid. Labels LEFT-aligned, because a column you scan down has to start
   at the same place every time, and a right-aligned gutter only aligns the
   end nobody reads from. 140px holds every label in this lexicon on one line.

   And the answers are capped at 58ch. They ran the full block — measured at
   800px, about 130 characters — which is roughly twice the width anybody
   tracks a line back across. */
@media (max-width: 719.98px) { /* 720 layout px */
  /* Below this the label column costs more than the alignment is worth. */
}

/* ══ 22. THE POLISH PASS ═══════════════════════════════════════════════════
   Everything below is the aggregate of details nobody will consciously
   notice, which is the point: a press that answers, a hover that does not
   fire under a thumb, a count that does not jitter, an edge that catches the
   light. None of it changes what a surface says. All of it changes whether
   the surface feels like it was made.

   ONE CURVE, ONE SPEED. The library's transitions run on the default `ease`
   at 150ms. Everything this build adds runs on a strong ease-out — starts
   fast, settles clean — at 120–160ms, because a press seen two hundred times
   a day has to answer instantly and get out of the way. */
:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  /* THE SETTLE BEAT HAD NO NAME. 220ms is the most-used duration in this
     build — the toast rise, the figure tick, and every one of the five
     arrivals — and it was written out by hand at each of them. A number
     spelled seven times is a number that drifts: the swap step below already
     drifted once, when the CSS was lengthened and the millisecond count in
     bdr.js was not. It sits a rung above --t-base and a rung under --t-open
     on purpose — longer than a state changing, shorter than a thing opening. */
  --t-settle: 220ms;
  /* THE PIN ASKED FOR A CURVE NOBODY DECLARED. `.pipe-pin` transitions its
     scale on var(--ease-pin), and --ease-pin was never defined anywhere in
     the build. An undeclared custom property is invalid at computed-value
     time, so the whole timing function fell back to the initial value: the
     browser's `ease`, which is slow in and slow out and is the one curve
     this file spends its opening paragraph arguing against. It was never
     meant to be the exception — it is the same ease-out as everything else,
     and it is named only so the pin can be retuned without touching the
     forty other things that read --ease-out. */
  --ease-pin: cubic-bezier(0.23, 1, 0.32, 1);
  /* HOW LONG A SURFACE STANDS BEHIND ITS SKELETON. Nothing in this build
     waits on data, so this is not the length of a fetch — it is a wait we
     choose, and the only honest way to pick it was to look. Four dwells were
     built as one page and compared against each other with the real cards at
     their real size: 200ms is a flash, which reads worse than showing
     nothing; 550ms is a desk that has got slower to use. 350 is where the
     skeleton is long enough to be a state rather than a stutter, and short
     enough that the cards are still an answer to the press.

     bdr.js reads this back rather than keeping a copy, so the dwell and the
     arrival that follows it cannot drift apart. */
  --t-skel: 350ms;
  /* The blur an arriving card resolves through, chosen the same way against
     none, 2, 4 and 6. The build already had 2, 3, 4 and 6 written by hand in
     four different keyframes; this is the one an arriving surface uses. */
  --blur-arrive: 4px;
}

/* [1] EVERY PRESSABLE ANSWERS THE PRESS. Zero of these had an :active state.
   scale(0.97) is the number Linear ships; it is felt rather than seen, which
   is exactly the register a work tool wants. The transition list restates
   the colour properties the shell already animates on these, because a
   later `transition` replaces the earlier one rather than adding to it. */
.s-insight-lnk, .s-inline-btn, .s-cb-opt, .call-end, .call-tool,
.b-switch-btn, .ntf-row-cta, .s-back, .b-find-x, .filter-chip,
.overlay-sugg-chip, .b-pager-go button, .ntf-clear, .ntf-foot-btn {
  transition:
    transform 160ms var(--ease-out),
    color var(--t-fast), background-color var(--t-fast), border-color var(--t-fast);
}
.s-insight-lnk:active, .s-inline-btn:active, .s-cb-opt:active, .call-end:active,
.call-tool:active, .b-switch-btn:active, .ntf-row-cta:active, .s-back:active,
.b-find-x:active, .filter-chip:active, .overlay-sugg-chip:active,
.b-pager-go button:active, .ntf-clear:active, .ntf-foot-btn:active {
  transform: scale(0.97);
}
/* [8] The card door, barely. A card is two hundred pixels wide; 0.97 on it
   is a lurch. 0.995 is the same answer at the size of the thing pressed. */
.b-qcard[data-open] {
  transition: transform 160ms var(--ease-out), border-color var(--t-fast), box-shadow var(--t-fast);
}
.b-qcard[data-open]:active { transform: scale(0.995); }
/* ══ AND THE CHANGE TINT HAS TO OUTRANK THAT LIST ══════════════════════════
   sales.css gives `.s-changed` a background-color transition over 1400ms —
   the marker that says WHERE a write landed. The rule above transitions
   transform, border-color and box-shadow, and a later `transition` REPLACES
   an earlier one rather than adding to it, which is the same trap §1 records
   for the press cluster. So on a card the tint had no transition at all and
   went straight to transparent the instant it was told to settle: a write
   reporting itself by doing nothing visible. Measured before this rule
   existed — the marked card's transition-property read `transform,
   border-color, box-shadow`, with background-color nowhere in it.

   The list is restated here with the tint at the front. The 1400ms and its
   200ms delay are sales.css's numbers rather than new ones; that rule is
   where they are decided, and this is the same values written somewhere the
   cascade can reach a card.

   AND IT IS RESTATED ON `.is-settling` ONLY, WHICH IS THE WHOLE TRICK. The
   tint must arrive at once and leave slowly. Put the background transition
   on `.s-changed` itself and the opposite happens: the card is already
   painted by the time postPaint marks it — `mountLists` reads a rect on the
   way past, which flushes style — so adding the class transitions the card
   INTO the tint over a second and a half, which is a highlight that turns up
   long after the thing it was highlighting. Measured: the marked card
   computed its ordinary ground, not the tint.

   Introducing the transition in the same change that retargets the colour is
   enough for it to run, because a transition is started from the style AFTER
   the change. So a card wearing only `.s-changed` has no background
   transition and takes the tint instantly; adding `.is-settling` brings the
   transition and the new target in together, and it fades. */
.b-qcard.s-changed.is-settling {
  transition: background-color 1400ms var(--ease-out) 200ms,
              transform 160ms var(--ease-out),
              border-color var(--t-fast), box-shadow var(--t-fast);
}
/* Under reduced motion sales.css holds both states at the same tint, so
   there is nothing for a transition to do; it is taken off so the card keeps
   the one the shell gives it. */
@media (prefers-reduced-motion: reduce) {
  .b-qcard.s-changed.is-settling {
    transition: transform 160ms var(--ease-out),
                border-color var(--t-fast), box-shadow var(--t-fast);
  }
}

/* [2] HOVER ONLY WHERE THERE IS A HOVER. A touch fires :hover on tap and
   leaves it lit until the next tap somewhere else — a switcher tab that
   stays half-highlighted after you leave it. */
@media (hover: hover) and (pointer: fine) {
  .b-switch-btn:hover { color: var(--d200); }
  .b-find-x:hover { color: var(--d100); }
}

/* [3] [7] NO STRIPES. The hint under a question and the Remember line both
   wore a 2px accent stripe down the left as their only treatment — the one
   decoration every UI kit of a certain vintage reached for. The hint is a
   sentence and reads as one. Remember is the durable fact on a record, and a
   raised surface behind it says "different from the call above" without
   drawing a line to say so. */
.s-cb-hint { border-left: 0; padding-left: 0; color: var(--d200); }
.s-callsum-mem {
  border-left: 0; padding: 8px 10px; border-radius: var(--r-md);
  background: var(--card-bg-raised); color: var(--d100);
}

/* [4] AN EYEBROW IS A SECTION MARKER, NOT A ROW LABEL. Nine roles here were
   uppercase-and-tracked, so a brief's "Who" and a card's campaign tag shouted
   at the same pitch as the section they sat in. The section captions keep
   it — .s-rec-kind, .b-sell-cap, .s-af-cap, .s-lead-mark — and the labels
   inside rows drop to sentence case at semibold, where weight and position do
   the work the tracking was doing badly. */
.s-callsum-cap, .s-callp b, .b-aimy-from, .tc-type {
  text-transform: none; letter-spacing: normal;
  font-weight: var(--fw-semibold);
}
.s-callsum-cap { font-size: var(--fs-xs); }
.s-callp b { font-size: var(--fs-xs); color: var(--ink-muted); }
.b-aimy-from { font-size: var(--fs-2xs); }
/* Shares .tc-head with the state tag, which is 12px. At 14 it outranked the
   tag beside it while saying less, and collided with .tc-summary below at
   the same size. At 12 the strip reads as one row and the role line under
   the name gets the 14px step to itself. */
.tc-type { font-size: var(--t-micro); color: var(--ink-muted); }

/* [5] COUNTS DO NOT JITTER. Proportional digits change width as the value
   changes, so the switcher shifted sideways every time a search narrowed it.
   Set once on the containers, so every count inside inherits it. */
.b-switch-n, .b-vfoot, .b-pager, .b-tally-out, .b-qcard, .s-block-say {
  font-variant-numeric: tabular-nums;
}

/* T1 — ELEVATION BY LIGHT. On a dark ground a shadow is invisible; what
   separates a raised surface is the edge that catches light. One pixel of
   white at 4% along the top, and cards, the lead block, the search box and
   the read-back card read as material rather than as outlines. */
.b-qcard, .s-insight.is-lead, .b-find, .s-callsum-in-turn, .s-call, .b-aimy {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

/* T4 — OPTICAL TEXT. Balanced headings so a two-line title does not leave
   one word orphaned; pretty paragraphs so the last line is never a single
   word. Both are free, and both are the difference between set and typed. */
.s-block-h, .s-rec-name, .s-rec-cap { text-wrap: balance; }
.s-block-sub, .b-aimy-say, .s-lead-say, .b-back-v, .s-callsum-val, .ntf-row-body { text-wrap: pretty; }

/* T5 — THE CURRENT SURFACE IS UNDERLINED. Colour alone marked which of
   Calls / Campaigns / Lists you were on, and on the calls surface all three
   sat at nearly the same ink. A two-pixel rule under the current one, in ink
   rather than accent, because it is a position and not an action. The rule
   is one element that slides between positions (§32); the page itself
   never waits for it. */
.b-switch-btn { padding-bottom: 4px; }

/* T6 — THE SEARCH BOX SAYS IT HAS FOCUS. The border already turns accent;
   a 3px call at 18% is the standard input focus and reads at a glance from
   across the page. Low alpha on purpose — a call, not a glow. */
.b-find:focus-within {
  border-color: var(--accent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 0 0 3px rgba(var(--accent-rgb), 0.18);
}

/* Reduced motion means fewer and gentler, not none. The colour transitions
   stay because they aid comprehension; the transforms go. */
@media (prefers-reduced-motion: reduce) {
  .s-insight-lnk:active, .s-inline-btn:active, .s-cb-opt:active, .call-end:active,
  .call-tool:active, .b-switch-btn:active, .ntf-row-cta:active, .s-back:active,
  .b-find-x:active, .filter-chip:active, .overlay-sugg-chip:active,
  .b-pager-go button:active, .ntf-clear:active, .ntf-foot-btn:active,
  .b-qcard[data-open]:active { transform: none; }
}

/* ══ 23. THE RECORD, AS A JOURNEY ══════════════════════════════════════════
   The lead block on a person is the campaign's lead without the figure: one
   reading at the deck step and a door. The campaign's 24px padding and 32px
   margins are for a block that opens a page; this one sits between a masthead
   and the ladder, and at that size read as a second header. */
.s-insight.is-lead.b-lead-slim { padding: 16px 18px; margin: 4px 0 8px; }
.b-lead-slim .s-lead-deck { margin-bottom: 12px; font-size: var(--ty-heading); }
.b-lead-slim .s-lead-mark { margin-bottom: 8px; }

/* What is owed and the ways to move it, on one line. The sentence leads and
   the chips follow at baseline, so "Call them back due tomorrow" reads as a
   fact with three answers beside it rather than as a label over a row. */

/* The month over a run of touchpoints. Small, at full ink, with the space
   above it doing the separating — a rule would compete with the cards. */
.b-month {
  margin: 14px 0 6px; font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--ink-muted);
}
.s-calls > .b-month:first-child { margin-top: 0; }

/* The way out, at the end of the action row and pushed to its far side so it
   reads as an exit and not as a fourth thing to do to this person. */
.b-next { margin-left: auto; }
/* Remember on the record wears the same raised tint the touchpoint cards use,
   and the name of who noted it sits inside the sentence rather than beside
   it as a second line. */
.s-block .s-callsum-mem { margin-top: 4px; }

/* ══ 24. THE CAMPAIGN, AS A JOURNEY ════════════════════════════════════════
   ONE VERTICAL RHYTHM. The sections on this page arrived with three different
   gaps between them — the masthead's 24, the lead block's 32, the figure
   strip's 32 and whatever the block before it left. Set once here, on the
   page's own children, so a reader's eye learns one interval and stops
   measuring. The back link and the masthead are one unit and sit closer. */
/* Measured: .s-home is a grid with a 32px gap, and .s-block then adds a 24px
   margin AND a 24px padding above its hairline, so two blocks in a row sat
   80px apart while the masthead and the lead block sat 32. One interval —
   the grid gap — and the hairline block keeps only its own 24px above the
   rule, which is what makes the rule read as belonging to the block below. */
.s-home > .s-back { margin-bottom: 0; }
.s-home > .s-rec-head { margin-bottom: 0; }
.s-home > .s-block + .s-block { margin-top: 0; }
.s-home > .s-insight.is-lead { margin: 0; }

/* THE HEADER'S THREE ROWS STEP BY SIZE AND WEIGHT. The goal is the sentence
   the campaign exists for and was set at the same size as the date range
   under it. Now: the goal at the small step in medium, the counts under it a
   step down, and the name above both at the masthead. */
.s-rec-facts > div:first-child {
  font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--d100);
  max-width: 62ch; text-wrap: pretty;
}
.s-rec-facts > div + div { font-size: var(--fs-xs); color: var(--d200); margin-top: 2px; }
.s-rec-facts > div + div b { color: var(--d50); }

/* Today's run, under the deck. A fact line, not a second deck. */
.b-lead-today {
  margin: -8px 0 16px; font-size: var(--fs-xs); color: var(--d200);
  font-variant-numeric: tabular-nums;
}
.b-lead-today b { color: var(--d50); font-weight: var(--fw-semibold); }

/* THE FIGURE STRIP, TIGHTER, WITH THE FUNNEL BREATHING UNDER IT. The four
   figures had 24px between columns and 32 below, then the bars started 4px
   later — the strip floated and the funnel was crowded. */
.s-afs { gap: 12px 24px; margin-bottom: 20px; }
.b-funnel { margin: 0 0 6px; }

/* A tile that opens a cut. It keeps the strip's anatomy and adds only what
   says it can be pressed: the arrow in its sub-line, the caption turning to
   accent on hover, and a press that answers. */
.b-af-door {
  font: inherit; color: inherit; background: none; text-align: left;
  border: 0; border-top: 1px solid var(--card-border); cursor: pointer;
  padding: 16px 0 0; display: flex; flex-direction: column; gap: 4px;
  transition: transform 160ms var(--ease-out), border-color var(--t-fast);
}
.b-af-door .s-af-sub { color: var(--accent-text); font-weight: var(--fw-semibold); }
.b-af-door:active { transform: scale(0.985); }
.b-af-door:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 4px; }
@media (hover: hover) and (pointer: fine) {
  .b-af-door:hover { border-top-color: var(--accent); }
}

/* ══ THE READINGS, ON THE INSIGHT SURFACE ═════════════════════════════════
   One mark, then a line each with its basis under it. This was `.is-quote` —
   the raised grey step — and the argument for it was crowding: three accent
   panels under one funnel all claiming to matter most. What it cost is the
   doctrine's own rule, written into sales.css: AiMY's work is never rendered
   as the product's plain output. Four derived readings on a grey card is the
   product printing.

   Base `.s-insight` carries the accent ground and border; all that is needed
   here is the layout `.is-quote` used to supply — a block rather than a flex
   row — and full width, because the bars directly above are full width and a
   62ch block under them reads as a different column. */
.s-insight.b-readings {
  display: block; max-width: none;
  margin: 16px 0 0; padding: 16px;
  border-radius: var(--r-lg);
}
.s-insight.b-readings .s-insight-mark { width: 14px; height: 14px; }
.b-readings .s-lead-mark { color: var(--ink-muted); }
.b-reading-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.b-reading {
  font-size: var(--fs-base); line-height: var(--lh-base); color: var(--d100);
  max-width: 70ch; text-wrap: pretty;
}
.b-reading b { color: var(--d50); font-weight: var(--fw-semibold); }
/* The one reading here that has something you can do about it: the sentence,
   then the door under it — the shell's own order for an insight.

   `display: block` with a content width, not `inline-block`. The basis line
   that used to sit between them was a block and was doing the breaking; with
   it gone an inline-block door ran straight on from the full stop —
   "…1 wrong number.Find more for this campaign" — and a link welded to the
   end of a sentence reads as a word in it. */
.b-reading-go { display: block; width: fit-content; margin-top: 6px; }

/* The folded opener sits after the title and wraps under it on a narrow
   row, at the small step so the title stays the loud thing. */

/* The feed as a plain column. The shell's row is a grid with no horizontal
   padding, so it already shares the cards' left edge — nothing to correct. */
.b-feed { display: flex; flex-direction: column; gap: 10px; }
.b-feed-row { padding: 0; }
.b-feed > .b-month { margin: 14px 0 6px; }
.b-feed > .b-month:first-child { margin-top: 0; }
@media (prefers-reduced-motion: reduce) { .b-af-door:active { transform: none; } }

/* ══ 25. THE COMPANY, AS A JOURNEY ═════════════════════════════════════════
   The campaign chips were a sentence over a row of chips in the header, above
   the people they act on. They sit under the people now, as one labelled row:
   a caption in sentence case at the small step — not another uppercase eyebrow
   — and the chips after it on the same baseline, so the row reads as one
   action with five answers rather than as a heading and a list. */
.b-camps-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--card-border);
}

/* ══ 26. ONE SCALE, EVERYWHERE ═════════════════════════════════════════════
   Measured before this pass: a record's name at 20px and everything under it
   at 13, with 0px between the kind line and the name, 0px between the name
   and the goal, and 0px between the counts and the actions. The kind line
   was 13px bold — the same size as the facts it labels. The lead figure (24)
   outranked the page title (20), and the lead deck, the section headings, the
   switcher and every card title were all 16/800: one size for four jobs.

   Seven steps, each with one job, set here once and applied to every surface.
   Nothing changes position; only sizes, weights and the gaps between rows. */
:root {
  --t-title: 26px;   /* the name of the thing you have open; the Today heading */
  --t-figure: 24px;  /* the lead block's number — below the title, above all else */
  --t-deck: 18px;    /* the consequence sentence; the pitch opener */
  --t-lead: 16px;    /* the goal under a title; the sentence beside a figure */
  --t-body: 16px;    /* readings, brief lines, feed notes */
  --t-ui: 14px;      /* buttons, chips, facts rows, row text */
  --t-small: 14px;   /* foot lines */
  /* ══ AND SENTENCE CASE STARTS AT TWELVE ═════════════════════
     This read "Capitals and tracked, or not at all", which is the rule
     §26b states for ELEVEN — "eleven is for capitals, which have no
     descenders to lose and get the tracking that makes them legible" — and
     the same paragraph says in as many words that sentence case starts at
     twelve. The gloss was one step out and this step is where the caps
     components happen to live, not a ban on prose at its size. A dozen
     rules already set sentence case at the same twelve through `--fs-2xs`.

     So: minimal components, and the sub-line under a section heading. */
  --t-micro: 12px;
}

/* ══ 26b. THE FLOOR, AND THE BOTTOM OF THE INK RAMP ════════════════════════
   Two laws this build already wrote down and then broke in eleven places.

   `sales.css:207` says of the smallest step: "FLOOR. Caps and tracked, or
   not at all." Eleven px of untracked sentence case is not a caption, it is
   body copy set too small to read — "weeks · closes 21 Oct" under a field,
   the kind line on a card, a whole notification panel at ten and a half.
   Sentence case starts at twelve; eleven is for capitals, which have no
   descenders to lose and get the tracking that makes them legible.

   `sales.css:231-237` names five ink roles and stops at `--ink-faint`
   (`--d400`) — "ambient: labels, captions, counts". `--d500` is not a role
   and `--d600` is labelled NON-TEXT, HAIRLINES AND DOTS, NEVER TYPE. Both
   were carrying type: the resting switcher tab, the search placeholder and
   its icon, every second line of the bell.

   Nothing below moves position or changes what anything says. Sizes come up
   to the nearest legal step and inks come up to the bottom of the ramp
   rather than below it. */

/* [1] THE STEPS. `.s-home .x` outranks the shell's single-class media rules
   that were setting the masthead to 20 and the figure to 24 at this width. */
.s-home .s-rec-name { font-size: var(--t-title); line-height: 1.1; letter-spacing: -0.02em; }
.slv-head .slv-title, .slv-title { font-size: var(--t-title); }
.s-home .s-lead-n { font-size: var(--t-figure); line-height: 1; }
/* The weight moved to §87, where the argument for it is. */
.s-home .s-lead-deck, .b-lead-slim .s-lead-deck {
  font-size: var(--t-body); font-weight: var(--fw-medium); line-height: var(--lh-base);
  margin-bottom: 16px;
}
.s-home .s-lead-say { font-size: var(--t-lead); }
.s-home .s-rec-kind { font-size: var(--t-micro); }
/* `.b-qcard .s-card-title` was here at --t-lead, setting the same element the
   card's own `.b-qcard .tc-title` sets at --fs-lg — same specificity, later in
   the file, so it won. The name rendered 16 while the block that documents it
   said 18, and the doctrine comment above that block was describing a card
   that had not existed for some time. One element, one rule. */
.b-reading { font-size: var(--t-body); }
/* ══ THE LINE UNDER A HEADING IS NOT A SECOND HEADING ═════════════
   It shared `--t-small` with `.b-vfoot` and so came out at the same size as
   the row text under it: "4 companies you have a way into · worth the most
   first" set as loudly as the rows it was introducing. It is the quietest
   thing on its row by job — the heading says what the block is and this says
   how it is ordered — and it was only one step below the heading on size
   with none on weight.

   Twelve, which §26b's own paragraph makes legal for sentence case. Against
   the heading that is now two steps of size and a step of weight, which is
   the rule this build applies to any two ranks. `.b-vfoot` keeps fourteen:
   a foot line closes a list rather than introducing one, and it is the last
   sentence on the surface rather than the smallest. */
.s-block-say { font-size: var(--t-micro); font-weight: var(--fw-medium); }
.b-vfoot { font-size: var(--t-small); }

/* [2] THE HEADER'S ROWS HAVE ROOM. A flex column so every gap is stated
   rather than inherited from line boxes: kind → name 6, name → goal 10,
   goal → counts 6, counts → actions 16. The goal moves to the Lead step —
   it is the sentence the campaign exists for, and it was 13px grey. */
.s-home > .s-rec-head { display: flex; flex-direction: column; }
.s-home .s-rec-kind { margin-bottom: 6px; }
.s-home .s-rec-title { margin-bottom: 14px; }
.s-home .s-rec-facts { gap: 0; }
.s-home .s-rec-facts > div:first-child { font-size: var(--t-lead); font-weight: var(--fw-medium); }
/* == ONE STEP IS NOT A RANK, IT IS A WOBBLE ===============================
   Measured on the record: 16/500/#d8e0e8 over 14/400/#c8d2dc, both carrying
   the same 13px mark. Four distinctions -- size, weight, ink, position --
   every one of them turned down so far that not one arrives. Four weak
   signals do not add up to a strong one. They add up to a reader who cannot
   tell whether this is one band or two, and settles for seven things.

   Micro: two steps and 4px, with the mark dropping the same amount beside
   it. Rank one is now a sentence and rank two a strip of utilities, and
   nobody has to compare them to see which is which. Eight between them
   stands: they are a tight group, and the space that matters is below. */
.s-home .s-rec-facts > div + div { font-size: var(--t-micro); margin-top: 8px; }
/* ══ TWO CONTROL ROWS ARE ONE GROUP ═══════════════════════════════════════
   Measured with the eyebrow still in: 5px to the name, 8 to the facts, 14 to
   the actions, 11 to the moves — four gaps with no order in them, so the two
   rows that are both controls sat further apart than the facts and the first
   of them. Tight groups, generous separation.

   Then 18 was measured against what it has to separate, and it was not
   enough. The gaps ran 11, 8, 15, 8 -- four numbers inside seven pixels of
   each other, which is a block of text with some spaces in it rather than a
   masthead with parts. Space only reads as space when it is unequal: 8
   inside a group, 13 at the identity break, 25 where the reference ends and
   the things you can DO begin. Three sizes of gap, three groups, and a
   reader who counts three rather than eighteen. */
.s-home .s-rec-actions { margin-top: 28px; }
/* Ten held these two rows as one group on the grounds that both are
   controls. They are not one job. The row above is what to do with this
   person NEXT -- call them, get ready to; this row is what already happened,
   and pressing one of these moves the deal rather than starting anything.
   Twenty, which lands between the 13 that breaks identity from reference
   and the 28 that breaks reference from doing, so the order still reads
   downward and the biggest gap in the masthead is still the one that
   matters most. */
.s-home .b-ask { margin-top: 20px; }
/* Four ghosts and a caption wrap on a phone, and a pill row wrapping on its
   baseline leaves the caption stranded on the line above. Centre them. */
.s-home .b-ask { align-items: center; }

/* [3] INSIDE A SECTION. Heading → first row 12, action row → chips 12, chips
   → grid 16, grid → foot 12. The action row had been pulled up into the
   heading's margin by 8px; the chips sat 10 from the grid. */
.s-camp-list-head { margin-bottom: 12px; }
.b-acts { margin: 0 0 12px; }
.b-cuts + .b-grid { margin-top: 4px; }

/* [4] THE LAST MUTED INK. Smaller is allowed; dimmer is not — this lifted
   the count UP onto the --d200 floor rather than flattening it, which is why
   it lives here and not beside the component.

   It used to name `.b-switch-btn.is-on .b-switch-n` in the same group, at
   the same specificity as the on-state rule beside the component and later
   in the file — so it won every time and the active tab’s count could never
   respond to being active. A floor belongs on the base selector. The
   on-state sets its own ink and is allowed to.

   It also carried the weight, which is why the count computed 500 while the
   rule beside the component declared 600. Same value now, stated once. */
.b-switch-n { color: var(--ink-muted); font-weight: var(--fw-semibold); }
.s-starts-cap { color: var(--ink-muted); }

/* [5] THE LEAD BLOCK'S PROPORTIONS. Figure 24 over sentence 15, deck 17, the
   today-line at UI size, and 16 to the actions — measured at 56 before with
   the today-line stacked inside two margins. */
.s-home .s-lead-line { margin-bottom: 12px; }
.b-lead-today { margin: -6px 0 14px; font-size: var(--t-ui); }

/* ══ 26c. A PLACEHOLDER IS NOT A VALUE, AND NOW IT DIFFERS ON THREE ════════
   §83 fixed this on one axis and wrote down the rule it was fixing: "the rule
   this build adopted for every other pair of ranks is that they differ in at
   least two of {size, weight, ink}, and the two things on this page that must
   never be confused differed on one axis by its smallest increment." The ink
   was corrected then. The size and the weight were not, so the pair still
   differed on one axis — a better one.

   Measured on the campaign draft, where a page of empty fields is the normal
   state: the placeholder and the value it will be replaced by were both
   16px/400. Elsewhere on the product they were 14px/400, and the composer and
   the canvas bar 13.5px/400. Three sizes for one meaning, and in no case a
   weight of its own.

   One step and one weight, everywhere: 14px, which is `--t-ui`, the step this
   build already sets every control in; and `--fw-regular`, which is the
   lightest weight the family loads and is used by nothing else on the
   product, so a placeholder cannot be mistaken for any other rank either. The
   two shell inputs at 13.5 come UP to it, which is §26b's own rule about the
   steps: the product's sizes are 12, 14, 16 and 13.5 is not one of them.

   IT IS DELIBERATELY UNDER THE FLOOR, AND SO IS ITS INK. `aimy-ds.css:307`
   says `--fw-regular` is "deliberately unused: the type floor is 400 or
   heavier below 18px" — measured, because Poppins 300 sets 8% lighter than
   the colour the ladder targets. The same stylesheet makes exactly one
   exception on the ink ramp, thirty lines above it: `--ink-placeholder` is
   "the ONE exception — must not read as a value". This is that same
   exception, on the axis next to it, for that same reason. A placeholder is
   the one piece of type on the product whose job is to NOT read as content,
   and it is the only thing in this build set under 400.

   The two classes from the shell are named rather than left to
   `input::placeholder`, because `aimy-ds.css` sets `font-weight: 400` on
   both at class specificity and would have won — this sheet loads last, so
   naming them at the same specificity is what makes the rule true at runtime
   rather than only in the file. `.b-draft-none` is the hand-drawn one: a
   `<span>` reading "Choose" in the slot a value will occupy, and §83 already
   argues it takes the placeholder rank for the same reason the inputs do. */
input::placeholder,
textarea::placeholder,
.b-draft-none {
  font-size: var(--t-ui);
  font-weight: var(--fw-regular);
}

/* ══ AND ONE FIELD KEEPS ITS SIZE ═════════════════════════════════
   A campaign's name is set at `--t-title` because it is the name of the
   record you have open, and dropping its placeholder to 14px left the
   masthead of an empty draft with nothing in it. True — the campaign has no
   name yet — and not what the page is for: the reader has to see where the
   name goes.

   It keeps the size and takes the weight, which was the better half of the
   rule here anyway. `aimy-ds.css:307` puts the 400 floor "below 18px" and
   says `--fw-regular` "is here for display sizes, where 300 is legal"; 26px
   is one of those. So this is the one placeholder on the product that is
   light without being an exception to anything, and against the value that
   will replace it — 26px, `--fw-extrabold`, `--d50` — it differs on weight
   and on ink, which is the two axes the rank rule asks for. */
.b-draft-name::placeholder { font-size: var(--t-title); font-weight: var(--fw-regular); }

/* The two the shell sets at class specificity, named so this sheet wins, and
   brought onto the placeholder ink while they are here. Measured in dark:
   `--d500` on the bar’s ground is 3.18:1, which is under AA before this pass
   and thinner after it, since a lighter weight is less ink over the same
   area. §26b named this exact case — "`--d500` is not a role ... both were
   carrying type: the resting switcher tab, the search placeholder and its
   icon" — and could not reach it, because a bare element selector loses to a
   class. On `--ink-placeholder` it is 5.6:1 there and 6.1 on the page ground,
   which is the rank every other placeholder on the product now reads at. */
.aimy-float-input::placeholder,
.overlay-input::placeholder {
  font-size: var(--t-ui);
  font-weight: var(--fw-regular);
  color: var(--ink-placeholder);
}

/* ══ 27. THE JOURNEY ON A RAIL ═════════════════════════════════════════════
   A 2px rail down the left, a node per touchpoint on it in the tone of how
   the call went, a larger accent call where a step was climbed, and the step
   it reached on the line. The cards are the same cards; the rail is drawn
   in the column the section already reserves for it. Metrics set once:
   10px dot, 14px milestone call, 26px gutter, rail at 7px. */
.b-tl { position: relative; padding-left: 26px; display: flex; flex-direction: column; gap: 10px; }
.b-tl::before {
  content: ''; position: absolute; left: 6px; top: 10px; bottom: 10px; width: 2px;
  background: var(--card-border); border-radius: 1px;
}
.b-tl-month { margin: 6px 0 0; }
.b-tl > .b-tl-month:first-child { margin-top: 0; }
.b-tl-item { position: relative; transition: border-color var(--t-base); }
/* ── A ROW YOU CAN PRESS ANSWERS THE POINTER ──────────────────────────────
   Seven of these, each one a door onto what was said, each with
   `cursor: pointer` from the shell and no visual answer of any kind: the
   pointer changed and the row did not. The card grid two screens away lifts
   its border and takes a shadow on hover, and the reason a reader trusts
   that grid is that everything in it responds.

   The same token, at a row's weight rather than a card's: the border alone,
   no shadow — a shadow on one row of a stack of seven lifts it off a surface
   it is not sitting on. The chevron comes up a step with it, because the
   thing being promised is that this opens. */
.b-tl-item:hover { border-color: var(--card-border-hover); }
.b-tl-item:hover > .s-call-sum::before { color: var(--ink-muted); }
.b-tl-dot {
  position: absolute; left: -24px; top: 17px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--d400); box-shadow: 0 0 0 3px var(--body-bg);
}
.b-tl-dot.tone-ok { background: var(--ok-text); }
.b-tl-dot.tone-warn { background: var(--warn-text); }
.b-tl-dot.tone-err { background: var(--err-text); }
.b-tl-dot.tone-neutral { background: var(--d400); }
/* A step climbed: the call is the milestone, in accent, and the node sits
   on the rail a shade larger so the eye catches it scanning down. */
.b-tl-item.is-milestone .b-tl-dot {
  width: 14px; height: 14px; left: -26px; top: 15px;
  background: var(--body-bg); border: 3px solid var(--accent);
}
.b-tl-item.is-milestone .b-tl-dot.tone-err { border-color: var(--err-text); }
.b-tl-move {
  font-size: var(--t-small); font-weight: var(--fw-bold); color: var(--accent-text);
  white-space: nowrap;
}
.b-tl-move.is-out { color: var(--err-text); }
/* Where it began. */
.b-tl-end {
  position: relative; padding: 4px 0 2px;
  font-size: var(--t-small); font-weight: var(--fw-medium); color: var(--d200);
  font-variant-numeric: tabular-nums;
}
.b-tl-dot.is-end { top: 8px; background: var(--d300); }

/* ══ 28. THE NOTIFICATIONS PANEL — AiMY QA'S CSS, VERBATIM ═════════════════
   Copied out of ../QA/index.html by line range. QA names its tokens
   differently; the bridge below maps them onto this shell's so the rules
   themselves are untouched. The wash values are QA's own, dark and light. */
.ntf-anchor {
  --qa-accent: var(--accent); --qa-accent-rgb: var(--accent-rgb);
  /* The panel is QA's, byte for byte, and these aliases are how it is dressed
     in this product's colour without editing it. It needed a third: its row
     verb sets type in `--qa-accent` ON a 12% tint of the same hue, which came
     out 3.33:1 in dark — the worst of the three, because the tint lifts the
     ground toward the ink. The fill keeps `--qa-accent-rgb`; only the reading
     moves. */
  --qa-accent-text: var(--accent-text);
  --wash-3: rgba(255,255,255,0.04); --wash-6: rgba(255,255,255,0.07); --wash-7: rgba(255,255,255,0.08);
  --transition-fast: 150ms;
}
:root[data-theme="light"] .ntf-anchor {
  --wash-3: rgba(16,24,40,0.05); --wash-6: rgba(16,24,40,0.09); --wash-7: rgba(16,24,40,0.1);
}

.ntf-anchor { position: relative; display: flex; align-items: center; flex-shrink: 0; }
.topnav-bell:focus-visible { outline: 2px solid var(--qa-accent); outline-offset: 2px; }
.bell-dot[hidden] { display: none; }

.ntf-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 300;
  width: 360px;
  /* ══ --vp-w/--vp-h, NOT vw/vh, AND THE DIFFERENCE IS A FACTOR ═══════════
     The shell carries `zoom` on <body> (assets/aimy-responsive.css), and
     inside a zoomed subtree `100vw` resolves to the raw viewport in LAYOUT px
     and is then multiplied by the scale on the way to the screen — so this cap
     was --ui-scale times wrong at every width that is not the 1536 anchor.
     Above the anchor it never bit at all: at a scale of 1.667 `calc(100vw -
     32px)` was 1.667 viewports wide. The tokens are the same expression with
     the scale divided back out; they are declared on :root in the responsive
     sheet for exactly this. */
  max-width: calc(var(--vp-w) - 32px);
  max-height: min(calc(var(--vp-h) * 0.7), 520px);
  overflow-y: auto;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 14px;
  padding: 6px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(0, 0, 0, 0.2);
  /* Was hardcoded "Urbanist" and so survived two font changes without
     moving; when the document stopped requesting that family the panel
     quietly fell to a system fallback. Name the token, not the face. */
  font-family: var(--font-sans);
  text-align: left;
}
.ntf-panel[hidden] { display: none; }
/* ══ AND IT ARRIVES AND LEAVES LIKE THE MENUS DO ═══════════════════════════
   This panel appeared and vanished on `hidden`, which is the same hard cut
   §47 argues against for a menu, on a bigger surface hung off the same
   topnav. It takes the menus' own pair rather than a new one — the open at
   --t-open and the close a rung faster at --t-fast, for the reason written
   at §47: an open is an invitation and a close gets out of the way.

   THE ORIGIN IS THE OTHER CORNER. `.b-menu` grows from `top left` because it
   hangs off the left edge of its button; this is `right: 0` under the bell
   at the end of the topnav, so it grows from `top right` and appears to come
   out of the bell rather than sliding across from beside it. Same keyframes,
   different anchor. */
.ntf-panel {
  transform-origin: top right;
  animation: b-menu-in var(--t-open) var(--ease-out) both;
}
.ntf-panel.is-closing { animation: b-menu-out var(--t-fast) var(--ease-out) forwards; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .ntf-panel, .ntf-panel.is-closing { animation: none; }
}

/* ══ AND BELOW 520 LAYOUT PX IT IS PINNED TO THE WINDOW ═════════════════
   The bell sits about 90px in from the window edge, so a 360px card hung from
   it runs off the LEFT before `max-width` — which measures from the other
   side — is narrow enough to catch it. Below that width the card stops being
   a dropdown and becomes a sheet under the masthead, which is the only
   position that can hold 360px of text on a 459px layout.

   IT IS HERE AND NOT IN sales.css, WHICH IS WHERE IT WAS. That file defines
   .ntf-panel too, at this same (0,1,0) and earlier in the document, so its
   copy of this block lost to the four positional declarations above — a media
   query carries no extra specificity. All that got through was `left: 12px`,
   resolved against the `position: relative` .ntf-anchor rather than the
   window, which pushed the panel 255px off the right edge of a 390px phone
   with every line cut mid-word. TWO DEFINITIONS OF ONE COMPONENT IS THE
   DEFECT; this block is the half that has to sit with the winner until they
   are one. */
@media (max-width: 519.98px) { /* 520 layout px */
  .ntf-panel {
    position: fixed;
    top: calc(var(--topbar-height) + 4px);
    right: 12px; left: 12px;
    width: auto; max-width: none;
  }
}

/* Scrollbar, matching .dashboard-scroll / .overlay-thread rather than
   leaving the browser default sitting inside a 14px-radius card.
   --wash-6 and --card-border are the only wash/border tokens declared in
   all six files' dark roots; --wash-8/--wash-11 used elsewhere are not. */
.ntf-panel { scrollbar-width: thin; scrollbar-color: var(--wash-6) transparent; }
.ntf-panel::-webkit-scrollbar { width: 6px; }
.ntf-panel::-webkit-scrollbar-track { background: transparent; }
.ntf-panel::-webkit-scrollbar-thumb { background: var(--wash-6); border-radius: 4px; }
.ntf-panel::-webkit-scrollbar-thumb:hover { background: var(--card-border); }

.ntf-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px 10px; }
.ntf-head b { font-size: var(--t-ui); font-weight: var(--fw-bold); color: var(--d50); letter-spacing: 0.01em; }
.ntf-count {
  font-size: var(--t-micro); font-weight: var(--fw-bold); padding: 1px 7px; border-radius: 9999px;
  background: rgba(var(--qa-accent-rgb), 0.16); color: var(--qa-accent);
  font-variant-numeric: tabular-nums;
}
.ntf-count[hidden] { display: none; }
.ntf-clear {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  font-family: inherit; font-size: var(--t-small); font-weight: var(--fw-semibold); color: var(--ink-muted);
  padding: 2px 4px; border-radius: 6px;
  transition: color var(--transition-fast);
}
.ntf-clear:hover { color: var(--d100); }
.ntf-clear:focus-visible { outline: 2px solid var(--qa-accent); outline-offset: 1px; }

.ntf-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ntf-row {
  display: flex; gap: 9px; padding: 10px; border-radius: 10px;
  transition: background var(--transition-fast), opacity var(--transition-fast);
}
.ntf-row:hover { background: var(--wash-3); }
.ntf-row.is-read { opacity: 0.45; }
.ntf-sev { width: 6px; height: 6px; border-radius: 50%; margin-top: 6px; flex-shrink: 0; background: var(--d500); }
/* One row in this panel is about something that went wrong; the other two
   are about things that have not happened yet, which is not the same claim.
   The order already carries the priority. */
.ntf-sev.p1 { background: var(--err); }
.ntf-sev.p2, .ntf-sev.p3 { background: var(--d500); }
.ntf-row-main { flex: 1; min-width: 0; }
.ntf-row-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; }
.ntf-row-type { font-size: var(--t-ui); font-weight: var(--fw-semibold); color: var(--d50); }
.ntf-row-when { margin-left: auto; font-size: var(--t-small); color: var(--ink-muted); flex-shrink: 0; }
/* The sentence the row is for. It was the smallest and faintest thing in the
   panel, under a label that was neither. */
.ntf-row-body { margin: 0 0 7px; font-size: var(--t-ui); line-height: 1.5; color: var(--d200); }
.ntf-row-cta {
  font-family: inherit; font-size: var(--t-small); font-weight: var(--fw-bold); cursor: pointer;
  padding: 4px 10px; border-radius: 9999px;
  background: rgba(var(--qa-accent-rgb), 0.12); color: var(--qa-accent-text);
  border: 1px solid rgba(var(--qa-accent-rgb), 0.26);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.ntf-row-cta:hover { background: rgba(var(--qa-accent-rgb), 0.2); border-color: rgba(var(--qa-accent-rgb), 0.4); }
.ntf-row-cta:focus-visible { outline: 2px solid var(--qa-accent); outline-offset: 2px; }

.ntf-foot { padding: 8px 4px 4px; border-top: 1px solid var(--wash-6); margin-top: 6px; }
.ntf-foot-btn {
  width: 100%; font-family: inherit; font-size: var(--t-ui); font-weight: var(--fw-bold); cursor: pointer;
  padding: 8px; border-radius: 9px; background: var(--wash-3);
  border: 1px solid var(--wash-6); color: var(--ink-muted);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.ntf-foot-btn:hover { background: var(--wash-6); color: var(--d50); }
.ntf-foot-btn:focus-visible { outline: 2px solid var(--qa-accent); outline-offset: 2px; }
.ntf-empty { padding: 22px 12px; text-align: center; font-size: var(--t-ui); color: var(--ink-muted); }

@media (prefers-reduced-motion: reduce) {
  .ntf-row, .ntf-row-cta, .ntf-foot-btn, .ntf-clear { transition: none; }
}

/* ══ 29. THE RUN, AS A PIPELINE ════════════════════════════════════════════
   The deploy-pipeline component's anatomy — track, stage tiles on connectors,
   a step list, a log well, a footer — in this shell's material: the card's
   ground and border, the accent for what is running, the ok green for what
   is done, the mono face for the log. No light gradients; elevation by the
   lit top edge the rest of the product uses. */
.pipe { display: flex; justify-content: center; }
/* ══ CAPPED, BECAUSE A COLUMN DOES NOT WANT A THOUSAND PIXELS ════════════
   `width: 100%` was right while the rail was in here — four tiles and three
   connectors spread to fill whatever they were given. A step row is a mark, a
   few words and a clock, and at a 1412px layout this card was 1023 wide with
   about 300 of that used: a short label on the left, a time on the far right,
   and two thirds of nothing between them.

   Every reference for this pattern is narrow — Rox centres about 340px in a
   full page, Lindy's sits in a chat column, Profound's in a side panel — and
   none of them stretches the steps across the window. `.pipe` is already
   `justify-content: center`, so the cap is the whole change. */
.pipe-card {
  width: 100%; max-width: 640px; padding: 14px;
  border-radius: 20px; background: var(--card-bg);
  border: 1px solid var(--card-border);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 18px 40px -20px rgba(0,0,0,0.5);
}
.pipe-badge, .pipe-elapsed {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}

/* header */
.pipe-head { padding: 6px 8px 16px; }
/* ══ IT WRAPS RATHER THAN OVERLAPS ═════════════════════════════
   `justify-content: space-between` with one side `flex: none` puts the two
   groups at the ends of a row and then lets them run into each other when the
   row is shorter than both — measured on a phone, `Companies · LinkedIn Sales
   Navigator` was drawn straight through `Running · Ask the suppliers`, two
   sentences on the same pixels. Wrapping gives the status its own line at the
   width where it needs one and changes nothing at the width where it does not.

   `.pipe-title` IS GONE. It printed buildName() inside the card, forty pixels
   under `.s-rec-name`, which is buildName() as the page's own heading — the
   same string twice, and the copy in here was the one being cut off at the
   card's edge. */
.pipe-head-row { display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; flex-wrap: wrap; }
.pipe-head-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.pipe-badge {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 5px 8px; border-radius: var(--r-md); font-size: var(--t-ui); line-height: 1;
  color: var(--d200); background: var(--card-bg-raised); border: 1px solid var(--card-border);
}
.pipe-badge-dot { color: var(--ink-muted); }
.pipe-head-state { display: flex; align-items: center; gap: 8px; flex: none; }
/* Both halves of the head give way before the row does. The badge is a fact
   about the run and the status is what it is doing; neither is worth an
   ellipsis until the other has none left to give. */
.pipe-head-main, .pipe-badge, .pipe-status { min-width: 0; }
.pipe-badge, .pipe-status { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pipe-status { font-size: var(--t-ui); font-weight: var(--fw-semibold); color: var(--accent-text); }
.pipe-status.done { color: var(--ok-text); }
.pipe-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15); }
.pipe-live-dot.done { background: var(--ok-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent); }

/* ══ THE RIBBON ════════════════════════════════════════════════

   THE HEIGHT COMES FROM THE STEPS, AND SO DOES THE SPACING. Each step is
   `flex-grow: <its duration>` down a column of fixed height with a zero
   basis, so the room a step gets IS how long it takes: the gap under `Ask the
   suppliers` is bigger than the gap under `Read the criteria` because the
   step is. A list of four evenly spaced rows cannot say that, and it was the
   one thing the run knew and never showed.

   NOTHING THAT MOVES PER FRAME TOUCHES LAYOUT, which is the whole reason this
   shape is drawn the way it is rather than the obvious way. The fill is a
   full-height bar SCALED from its top edge, not a height that is animated;
   the head is a glow TRANSLATED down the same axis, not a top that is moved.
   Both are compositor work. Verified on the standalone before this landed:
   across 121 synthetic frames the only inline properties that changed were
   `transform` on the fill and `transform`/`opacity` on the head, and
   `getBoundingClientRect` was called zero times inside the loop — the track's
   height is read once at mount and again only on resize, because reading it
   per frame is a forced synchronous reflow sixty times a second.

   THE HORIZONTAL TRACK ABOVE IT IS GONE. This IS the progress bar; a second
   one across the head of the same card was the same claim twice. So is the
   predicted finish — the foot carries the clock and nothing else. */
.pipe-rib { position: relative; display: flex; flex-direction: column; height: 250px; margin: 16px 0 4px; }

.pipe-rib-track {
  position: absolute; left: 5px; top: 8px; bottom: 16px; width: 2px;
  border-radius: 2px; background: var(--card-border); overflow: hidden;
}
.pipe-rib-fill {
  position: absolute; inset: 0; border-radius: 2px; background: var(--accent);
  transform-origin: top center; transform: scaleY(0);
}
/* The leading edge, riding down with the fill. */
.pipe-rib-head {
  position: absolute; left: 6px; top: 8px; width: 0; height: 0;
  transform: translateY(0);
  transition: opacity 420ms var(--ease-out); pointer-events: none;
}
/* ══ THE LAYER IS HELD FOR THE RUN, NOT FOR THE PAGE ══════════════════════
   These three carried `will-change` in their own rules, which promotes each
   to its own compositor layer the moment the card mounts and keeps it there
   for as long as the card is on screen — including the beat it spends
   finished, and the whole of a run somebody has walked away from. A promoted
   layer costs GPU memory whether or not anything is moving on it, and the
   hint is only worth paying for in the window where the frame clock is
   actually writing transforms.

   `done` goes on the card the moment the run ends, which is the same signal
   the status line and the dot already read, so the layers are handed back at
   the end of the run rather than at the end of the surface. */
#pipeCard:not(.done) .pipe-rib-fill,
#pipeCard:not(.done) .pipe-pin { will-change: transform; }
#pipeCard:not(.done) .pipe-rib-head { will-change: transform, opacity; }
.pipe-rib-head::before {
  content: ''; position: absolute; left: -11px; top: -11px; width: 22px; height: 22px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--accent-rgb),0.6) 0%, rgba(var(--accent-rgb),0) 68%);
}

/* flex-grow is written inline per step, because it is data — the duration of
   that stage — and not a design constant. Basis 0 so grow distributes the
   whole column and the shares are exact. */
.pipe-step {
  position: relative; flex: 1 1 0; min-height: 46px;
  display: flex; align-items: flex-start; gap: 14px;
}
.pipe-pin {
  position: relative; z-index: 1; flex: none;
  width: 12px; height: 12px; margin-top: 2px; border-radius: 50%;
  background: var(--card-bg); border: 1.5px solid var(--card-border);
  transform: scale(1);
  transition: background-color 400ms var(--ease-out),
              border-color 400ms var(--ease-out),
              transform 460ms var(--ease-pin),
              box-shadow 400ms var(--ease-out);
}
.pipe-step.is-live .pipe-pin {
  background: var(--accent); border-color: var(--accent); transform: scale(1.22);
  box-shadow: 0 0 0 5px rgba(var(--accent-rgb),0.14);
}
.pipe-step.is-done .pipe-pin {
  background: var(--ok-text); border-color: var(--ok-text); transform: scale(1);
  box-shadow: 0 0 0 0 rgba(var(--accent-rgb),0);
}
/* ══ AND THE STEP SIGNS ITSELF ═════════════════════════════════════════════
   A step finishing turned its dot from blue to green and that was the whole
   report. Colour alone is the weakest thing a state change can be — it is
   the one channel a reader may not have, and on a 12px dot at the edge of a
   card it is a change you have to be looking at to catch. The tick draws
   itself instead: the stroke arrives left to right in the time the eye takes
   to get there, so the step says `done` in a shape as well as a hue.

   NOTHING IS MEASURED TO DO THIS. The path carries pathLength="1", which
   renormalises its length to a single unit whatever the geometry actually
   is, so the dash is 1 and the offset runs 1 → 0 with no getTotalLength and
   no reading of the DOM — which matters because the state this hangs off is
   set on the frame clock, and a measurement between two style writes is a
   forced synchronous reflow.

   This is a paint rather than a composite, which the rest of the run is
   careful not to be. It is affordable here and only here: nine pixels, four
   times in a seven-second run, against a ribbon and a glow that are written
   sixty times a second. `.score-ring-fill` in the library draws the same
   way. */
.pipe-pin > svg {
  position: absolute; inset: 0; margin: auto; width: 9px; height: 9px;
  color: var(--card-bg); opacity: 0;
  stroke-dasharray: 1; stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--t-open) var(--ease-out),
              opacity var(--t-fast) var(--ease-out);
}
.pipe-step.is-done .pipe-pin > svg { stroke-dashoffset: 0; opacity: 1; }
/* Frozen, not removed: the tick is what the step now says, so it is drawn
   in full at once rather than taken away. */
@media (prefers-reduced-motion: reduce) { .pipe-pin > svg { transition: none; } }

.pipe-step-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.pipe-step-row { display: flex; align-items: baseline; gap: 12px; }
.pipe-step-label {
  flex: 1; min-width: 0; font-size: var(--t-body); font-weight: var(--fw-medium);
  line-height: 1.25; color: var(--ink-muted);
  transition: color 460ms var(--ease-out);
}
.pipe-step.is-live .pipe-step-label { color: var(--d50); font-weight: var(--fw-bold); }
.pipe-step.is-done .pipe-step-label { color: var(--d200); }
/* The figure and the sentence a finished step ends on. They arrive with it
   rather than being drawn empty and waiting — opacity and transform, so the
   arrival costs nothing the compositor cannot do. */
.pipe-step-time {
  flex: none; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--t-small); color: var(--ink-muted);
  opacity: 0; transform: translateY(-3px);
  transition: opacity 400ms var(--ease-out), transform 400ms var(--ease-out);
}
.pipe-step.is-done .pipe-step-time { opacity: 1; transform: none; }
.pipe-step-said {
  font-family: var(--font-mono); font-size: var(--t-small); color: var(--ink-muted);
  line-height: 1.4;
  opacity: 0; transform: translateY(-4px);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
}
.pipe-step.is-done .pipe-step-said { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .pipe-pin, .pipe-step-label, .pipe-step-time, .pipe-step-said, .pipe-rib-head {
    transition-duration: 1ms;
  }
}

/* footer — the clock, and nothing beside it. `.pipe-foot-act` and its chip
   went with the stage name they carried: it repeated the status at the top of
   the same card and was written once and never updated, so it named the first
   stage for the length of the run. */
.pipe-foot { display: flex; align-items: center; gap: 12px; padding: 16px 8px 4px; }
.pipe-elapsed { font-size: var(--t-ui); color: var(--ink-muted); }

/* text that changes state goes through a blur — swapText() */
/* ══ A NUMBER CHANGING IS NOT A BUTTON BEING PRESSED ══════════════
   90ms is --t-press, and it was here by proximity rather than by argument:
   a press is short because the finger is already up, and nothing about that
   applies to a figure being replaced. Four pixels of blur crossed in 90ms is
   not a softening, it is a flash of something unreadable. The swap step is
   --t-fast over --blur-small's two pixels — a shorter distance to travel,
   held long enough to read as one value becoming another. */
/* ══ AND IT CROSSES ITSELF RATHER THAN BLINKING ════════════════════
   Everything above argued about how long ONE span should take to fade out,
   change its own characters and fade back in. However well that is timed it
   is a blink: for the length of the fade there is nothing on the line, and an
   empty line beside a live dot reads as the thing having broken rather than
   having changed. Timing it correctly only made it an honest blink — measured
   at exactly zero opacity on all four swaps, and still wrong.

   Two spans instead, overlapping. The incoming starts at 90ms while the
   outgoing has until 260, so for 170ms both are on the line — one leaving
   upward, one arriving from below — and on no frame is there nothing there.
   The arriving one is longer and slower than the departing one, which is the
   asymmetry every good swap has: a thing leaving should get out of the way,
   a thing arriving is what you are meant to read.

   OPACITY AND TRANSFORM, NOTHING ELSE. The blur that used to soften this was
   a full repaint on every frame of the fade and buys nothing once two layers
   are crossing — the overlap IS the softening. 6px of travel: far enough to
   carry a direction, not far enough to be a movement of its own.

   The outgoing span is anchored RIGHT, because `.pipe-head-state` sits at the
   end of a `space-between` row: the dot holds still and the text's left edge
   is what moves when the wording changes length. Anchored left, the departing
   words would slide sideways as the incoming ones sized the box. */
.pipe-status { position: relative; display: inline-block; text-align: right; }
.pipe-say { display: block; white-space: nowrap; }
.pipe-say.is-in { animation: pipe-say-in 300ms var(--ease-out) 90ms both; }
.pipe-say.is-out {
  position: absolute; right: 0; top: 0;
  animation: pipe-say-out 260ms var(--ease-out) forwards;
  pointer-events: none;
}
@keyframes pipe-say-in  { from { opacity: 0; transform: translateY(6px); } }
@keyframes pipe-say-out { to   { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) {
  .pipe-say.is-in { animation: none; }
  /* Gone the instant it is replaced, rather than held for an animation that
     is not going to run. */
  .pipe-say.is-out { display: none; }
}
/* The rail's own narrow tier went with the rail. A column of rows needs no
   width tier: the mark is 30px at every width and the label beside it takes
   whatever is left. */

/* ══ 30. THE ROSTER ════════════════════════════════════════════════════════
   The shell's row, at this build's scale. V3 set the row's name at the title
   step in the display face because its rows sat one to a screen in a sheet;
   fifteen of them under a masthead want the lead step and the body face, with
   the verb centred on the row rather than opening a beat under it. */
/* The ladder down the left of a row: name 15/800, what they do 13/500, where
   and how to reach them 12, the addresses 12 in the mono face, AiMY's line 12
   with the mark. The right column ranks the same way — the size at the lead
   step because it is compared between rows, the fit and the tag under it —
   and the verb closes the column. Rank by size and weight; nothing dims
   below --d300. */


/* The results head sat 24px in from the page edge: .s-sheet-head is a sheet
   component with its own padding, and on this page it is a grid child like
   every other header. Flush left, like the masthead on the other pages. */
.s-home > .s-sheet-head { padding-left: 0; padding-right: 0; }

/* The leaving gate: the lead block's anatomy in the warn register — a
   decision, not a finding — with a note under the deck that says what each
   way out does. Inline at the top of the result, never a modal. */
.s-insight.is-lead.b-gate {
  background: color-mix(in srgb, var(--warn) 6%, transparent); border-color: color-mix(in srgb, var(--warn) 22%, transparent);
}
.b-gate-note { margin: -6px 0 14px; font-size: var(--t-ui); color: var(--d200); max-width: 62ch; text-wrap: pretty; }
.b-gate .s-lead-acts { align-items: center; gap: 8px 10px; }

/* ══ 31. THE TOAST ARRIVES AND LEAVES THE SAME WAY ═════════════════════════
   The library gives the toast a 220ms rise, and it never ran here: the toast
   was inserted already .visible, so there was no first frame to rise from,
   and it left by innerHTML = '', which is no way to leave. @starting-style
   gives the insertion its first frame — 8px up, through 4px of blur, from
   0.97 — and .is-leaving takes it out along the same axis, faster, because a
   thing leaving is a thing you have stopped looking at. transitions.dev's
   toast, on the library's toast. */
.s-toast {
  transition: opacity var(--t-settle) var(--ease-out), transform var(--t-settle) var(--ease-out), filter var(--t-settle) var(--ease-out);
}
@starting-style {
  .s-toast.visible { opacity: 0; transform: translateX(-50%) translateY(8px) scale(0.97); filter: blur(4px); }
}
.s-toast.visible.is-leaving {
  opacity: 0; transform: translateX(-50%) translateY(8px) scale(0.97); filter: blur(4px);
  pointer-events: none; transition-duration: 160ms;
}
@media (prefers-reduced-motion: reduce) { .s-toast { transition: none; } }

/* ══ 31b. A RECEIPT IS NOT A HEADING ══════════════════════════════
   Measured on "Pitch drafted — it is yours to edit": a 360x74 box in which
   the sentence had 222px — the other 138 went on padding, a 30px icon tile,
   a divider and Undo — and at 16px/600 that sentence wants about 280, so it
   wrapped onto two lines. A big box and cramped text are the same fault
   here, not two: the type was set at a rank that does not fit the room the
   component gives it.

   16px is `--t-body` and 600 is the weight this build puts on a card title.
   A toast is neither. It is one line saying what just happened, and it
   belongs on `--t-ui`, the step every control and every row on this product
   is already set in. At 14 the same sentence fits on one line and the box
   comes down from 74 to 48, because nothing else in it was ever taller than
   the icon.

   THE SUB KEEPS ITS SIZE. Title and sub then differ on weight and on ink
   rather than on size, which is the rank rule this build keeps everywhere
   and the right pair of axes here, because both lines are the same kind of
   sentence about the same event. Two pixels between them rather than one:
   `gap: 1px` is not a gap, it is a rounding error.

   The icon comes 30 → 26 and the padding 12/16 → 11/14, which is where the
   rest of the slack was. Nothing moves position and nothing changes what
   anything says — §26b's rule, applied to the one component the whole
   product uses to report that it did something. It is corrected here rather
   than in the library, because the library's block says every value in it is
   quoted from the published anatomy table and that is a document this build
   does not own. */
.aimy-toast { min-width: 264px; padding: 11px 14px; gap: 10px; }
.aimy-toast-icon { width: 26px; height: 26px; border-radius: 8px; }
.aimy-toast-body { gap: 2px; }
.aimy-toast-title {
  font-size: var(--t-ui); font-weight: var(--fw-bold); line-height: var(--lh-snug);
}
.aimy-toast-sub { font-size: var(--t-ui); font-weight: var(--fw-medium); line-height: var(--lh-snug); }
.aimy-toast-divider { height: 24px; }

/* ══ 32. THE RULE SLIDES, AND A CHANGED FIGURE TICKS ═══════════════════════
   The page is rebuilt from a string, so nothing on it knows where it was a
   frame ago. Two things want to. The rule under Calls · Campaigns · Lists is
   one bar placed by transform; before a repaint its place is read, after it
   the bar is put back there and let go — 160ms, ease-in-out, because it is
   movement on screen and not an entrance. And a figure a write just changed
   — a chip's count, the roster figures on a campaign, the three counts in the
   switcher — ticks once, the new number rising through 4px of blur. Never on
   load, never on scroll, never for navigating: only for what you just did. */
.b-switch { position: relative; }
/* ══ TOP, NOT BOTTOM, BECAUSE THE STRIP WRAPS ════════════════════════
   `.b-switch` is `flex-wrap: wrap`, and the manager's desk has five entries:
   at a 459px layout they take two rows. `bottom: 0` is the bottom of the
   whole wrapped block, so with Diary lit on the first row the bar drew 34px
   BELOW the second one — a rule floating under Lists, marking nothing.
   Anchored to the top, the bar's place is a function of the button alone and
   placeSwitchBar() carries the y it was already computing the x for. */
.b-switch-bar {
  position: absolute; left: 0; top: 0; width: 1px; height: 2px; background: var(--d50);
  /* SURFACE MOTION TAKES THE SURFACE CURVE. This was
     `cubic-bezier(0.77, 0, 0.175, 1)` — a symmetrical ease-IN-out, which on
     a bar that slides between tabs means it spends its first frames barely
     moving. A thing changing position should leave at once and arrive
     gently, which is what --ease-out is and why every other moving surface
     in this build already uses it. */
  transform-origin: left center; transition: transform 160ms var(--ease-out);
  pointer-events: none;
}
/* The blur is split off and given no fill, for the reason §67 sets out: a
   `forwards` fill holds the INTERPOLATED end of a filter, and interpolating
   toward `none` means interpolating toward blur(0px) — which is a composited
   layer and grey-edged text kept for as long as the element lives. Ending
   with no fill lets it fall back to having no filter at all. */
.b-tick { display: inline-block;
  animation: b-tick var(--t-settle) var(--ease-out) both,
             b-tick-blur var(--t-settle) var(--ease-out); }
/* 4px and 2px: a mark appearing in place travels the swap distance, not the
   slide distance, and softens by the swap's blur. 5 and 4 were off every
   step of both scales. */
@keyframes b-tick { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes b-tick-blur { from { filter: blur(2px); } }
@media (prefers-reduced-motion: reduce) { .b-switch-bar { transition: none; } .b-tick { animation: none; } }

/* ══ 33. A NEW TURN ARRIVES ════════════════════════════════════════════════
   The thread is rebuilt when it grows, and the turn that grew it appeared
   already in place. It comes up 6px through 4px of blur now, 200ms, and
   nothing already read moves. The read-back card is a turn, so it arrives
   the same way. */
/* Split for the same reason as §67 and the tick above: the rise fills, the
   blur does not, so a turn that has arrived is not left holding a layer for
   the life of the conversation it is in. */
.chat-msg.b-arrive { animation: b-arrive 200ms var(--ease-out) both,
                                b-arrive-blur 200ms var(--ease-out); }
@keyframes b-arrive { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes b-arrive-blur { from { filter: blur(4px); } }
@media (prefers-reduced-motion: reduce) { .chat-msg.b-arrive { animation: none; } }

:root {
  /* Lucide chevron-right, the set the rest of the build draws from, carried
     as a mask so `currentColor` still decides what colour it is. */
  --chevron-right: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>');
}

/* ══ 34. A DISCLOSURE OPENS; IT DOES NOT SNAP ══════════════════════════════
   A call on the timeline and a transcript are <details>, and a <details>
   snaps open. ::details-content with interpolate-size lets the
   height run 200ms out; a browser without it keeps the snap. The chevron
   turns a quarter instead of being swapped for a different glyph, which is
   the one place a glyph swap read as a flicker. transitions.dev's accordion,
   on the platform's own element. */
/* `.s-crew` JOINS THEM. The financial report's campaign panels fold their
   crew behind the same native <details> the trace uses, and it was the one
   disclosure in the build that still snapped — the rule listed the two that
   existed when it was written. A component's behaviour belongs to the
   component, not to the surfaces that happened to use it first. */
.s-call, .s-trace, .s-crew, .b-nmo { interpolate-size: allow-keywords; }
.s-call::details-content, .s-trace::details-content, .s-crew::details-content,
.b-nmo::details-content {
  height: 0; overflow: clip;
  transition: height 200ms var(--ease-out), content-visibility 200ms allow-discrete;
}
.s-call[open]::details-content, .s-trace[open]::details-content,
.s-crew[open]::details-content, .b-nmo[open]::details-content { height: auto; }
/* ── AND THE MARK IS DRAWN, NOT TYPED ─────────────────────────────────────
   Every other mark in this product is a Lucide path at 2px in a 24 viewBox,
   set by `chIcon`. This one was U+25B8, a solid triangle out of whatever
   face the system hands over — filled where the others are stroked, and a
   different weight on every platform. It is the last typed mark in the
   build.

   The same chevron the icon set ships, as a mask rather than an image, so it
   takes the `color` the shell already sets on this pseudo-element and turns
   with the theme instead of needing a second copy of itself.

   Two properties to sit it on the line, and each summary ignores the one the
   other needs. The call's is a baseline flex row, so `align-self` centres it
   there and `vertical-align` is dead; the transcript's is a plain block, where
   `align-self` is dead and a 12px box rests its bottom edge on the baseline —
   two px above where a glyph read. A `translateY` would do both and then have
   to be carried through the rotation below, so neither is one. */
.s-call-sum::before, .s-trace-sum::before, .b-nmo-sum::before {
  content: ""; display: inline-block; flex: none; align-self: center;
  width: 12px; height: 12px; margin-right: 0.35em; vertical-align: -2px;
  background: currentColor;
  -webkit-mask: var(--chevron-right) center / 12px 12px no-repeat;
  mask: var(--chevron-right) center / 12px 12px no-repeat;
  transition: transform 160ms var(--ease-out);
}
.s-call[open] .s-call-sum::before, .s-trace[open] .s-trace-sum::before,
.b-nmo[open] .b-nmo-sum::before {
  content: ""; transform: rotate(90deg);
}

/* A month of notes, folded. No rule between them: a closed month is already
   one line with a chevron on it, and a hairline above each one draws a
   boundary the summaries were drawing for themselves — two marks for one
   division, on a page whose whole job is a quiet list. */
.b-nmo-sum {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 11px 10px 11px 0; list-style: none;
}
.b-nmo-sum::-webkit-details-marker { display: none; }
.b-nmo-sum:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-nmo-name {
  font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--ink-primary);
}
/* Its own month's, right where the eye already stops. */
.b-nmo-n { margin-left: auto; font-size: var(--fs-2xs); color: var(--ink-quiet); }

/* ══ 35. A KEY IS DRAWN AS A KEY ═══════════════════════════════════════════
   The read-back's hint names the digits that set an outcome. A keycap: the
   mono face, a hairline, the raised ground — coss's Kbd, at this scale. */
.b-kbd {
  display: inline-block; font-family: var(--font-mono); font-size: var(--t-micro); line-height: 1;
  padding: 2px 5px 3px; border-radius: 4px; color: var(--d100);
  background: var(--card-bg-raised); border: 1px solid var(--card-border);
  box-shadow: inset 0 -1px 0 var(--card-border); vertical-align: 1px;
}

/* ══ 36. THREE SMALL SWAPS ═════════════════════════════════════════════════
   The theme toggle showed the sun in both themes, by display. Both icons
   sit in one cell now and the one that is not for this theme is scaled
   down, turned and blurred out — an icon swap, 160ms, the way
   transitions.dev draws it. The keep-tick on a found row is a native box
   that flipped; it is drawn now, the check revealed left to right in 140ms
   on the accent ground. And the leaving gate, which is an answer to a door
   you pressed, arrives the way a turn arrives. */
.ds-theme-toggle { display: inline-grid; place-items: center; }
.ds-theme-toggle svg { grid-area: 1 / 1; transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out), filter 160ms var(--ease-out); }
.ds-theme-toggle .icon-sun, .ds-theme-toggle .icon-moon { display: block; }
.ds-theme-toggle .icon-moon { opacity: 0; transform: scale(0.6) rotate(-40deg); filter: blur(2px); }
:root[data-theme="light"] .ds-theme-toggle .icon-sun { opacity: 0; transform: scale(0.6) rotate(40deg); filter: blur(2px); }
:root[data-theme="light"] .ds-theme-toggle .icon-moon { opacity: 1; transform: none; filter: none; }

.s-pick-tick .s-tick {
  appearance: none; -webkit-appearance: none; margin: 0; width: 16px; height: 16px; border-radius: 5px;
  border: 1.5px solid var(--d400); background: var(--card-bg); display: grid; place-items: center;
  transition: opacity var(--t-fast), background-color 120ms var(--ease-out), border-color 120ms var(--ease-out);
}
.s-pick-tick .s-tick::after {
  content: ""; width: 10px; height: 10px; background: #fff;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 12.5l4.5 4.5L19 7' fill='none' stroke='black' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'/></svg>") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 12.5l4.5 4.5L19 7' fill='none' stroke='black' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'/></svg>") center / contain no-repeat;
  clip-path: inset(0 100% 0 0); transition: clip-path 140ms var(--ease-out);
}
.s-pick-tick .s-tick:checked { background: var(--accent); border-color: var(--accent); }
.s-pick-tick .s-tick:checked::after { clip-path: inset(0 0 0 0); }
.s-pick-tick .s-tick:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.b-gate { animation: b-arrive 200ms var(--ease-out) both,
                     b-arrive-blur 200ms var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
  .s-call::details-content, .s-trace::details-content,
  .s-crew::details-content, .b-nmo::details-content,
  .s-call-sum::before, .s-trace-sum::before, .b-nmo-sum::before,
  .ds-theme-toggle svg, .s-pick-tick .s-tick, .s-pick-tick .s-tick::after { transition: none; }
  .b-gate { animation: none; }
}

/* ══ 37. THE DECISION ON THE CARD ══════════════════════════════════════════
   On the After-meeting cut a card ends in two answers instead of one verb. */
.b-qcard-decide { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* §38 styled the old column — a caption, a row, and an `is-on` state for the
   one row that could not be pressed — and defined two of its three rules a
   second time further down the file, which is the shape a redesign leaves
   behind when the old rules are kept "just in case". None of it is here and
   none of it is in §91 either: the column is AiMY Knowledge's now, styled
   in sales.css under Knowledge's own names. The ellipsis went with it, for
   the reason that file gives — a title that truncates is a question you
   cannot tell from another question. */


/* ══ 40. THE CHOOSER ═══════════════════════════════════════════════════════
   A row of every campaign was the whole answer laid out before the question
   was asked. The button asks; this is where the answer is given — a filter
   box, the options as chips that hold their state, and one confirm that
   says how many are chosen. It opens where it stands, inside the actions
   row it belongs to, so nothing covers the record you are deciding about. */
/* The chooser panel is gone — one menu puts things on a campaign now. Its
   search box is the part worth keeping, because the menus use it. */
.b-pick-find {
  width: 100%; font-family: inherit; font-size: var(--t-ui); color: var(--d50);
  padding: 8px 12px; border-radius: 10px;
  background: var(--card-bg); border: 1px solid var(--card-border);
}
.b-pick-find::placeholder { color: var(--ink-placeholder); }
.b-pick-find:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18);
}

/* ══ 41. THE RUN SITS WITH THE CUTS IT RUNS ════════════════════════════════
   "Call these 15" had a row of its own above the cuts, which read as a
   second heading over the list. It ends the row of cuts instead: the chips
   say which fifteen, and the verb is at the far end of the same line. */
.b-cuts-row { align-items: center; }
.b-cuts-go { margin-left: auto; }

/* ══ AND ON A NARROW ONE IT IS NOT THE SAME LINE ════════════════════
   The rule above rests on there being ONE line. The row wraps, and
   `margin-left: auto` then pushes the verb to the far end of whichever line
   it lands on — measured at a 459px layout, four chips took the first line
   and `Call them` sat on the second with a 39px gap after `After meeting 3`,
   reading as that chip's own button rather than the set's.

   THE SPACER IS A PSEUDO-ELEMENT OF THE FLEX CONTAINER, which is a flex item
   like any other. Full basis, no height, ordered between the chips and the
   verb: it fills out whatever line the chips ended on and puts the verb on a
   line of its own, left with them, where it reads as the verb for all of
   them. `flex-basis: 100%` on the button itself cannot do this — the
   `max-width: max-content` needed to keep it from stretching clamps the
   hypothetical size the line-break is decided from, so it simply sits back
   down beside the last chip; measured, twice.

   Nothing here applies above 720 layout px, where the row is one line and the
   rule above is right as it stands. */
@media (max-width: 719.98px) { /* 720 layout px */
  .b-cuts-row::after { content: ''; flex: 0 0 100%; height: 0; order: 1; }
  .b-cuts-row .b-cuts-go { order: 2; margin-left: 0; margin-top: 2px; }
}

/* ══ 42. THE WAY OUT IS ITS OWN DECISION ═══════════════════════════════════
   Ending a lead sat in the row of ordinary verbs at the same weight as
   "Call". It is under a rule of its own now, quiet until pressed, and it
   says what it does before it does it. */
/* ══ THE WAY OUT IS A CONTROL, NOT A CAPTION ═══════════════════════════════
   Both of these were text links, and the trigger was one with its accent
   removed — grey words with no border, no ground and no colour, which is a
   fact on a page rather than a button. Its hover set a border-colour on an
   element that had no border to colour.

   A bordered pill with a cancel mark, the shape every other quiet control
   here takes, carrying the destructive tone on hover and press rather than
   at rest: it is read far more often than it is used, and a red word sitting
   in the masthead all day is an alarm nobody hears by the afternoon. */
.b-end { margin-left: auto; flex: none; }
/* ══ THE GHOST ═════════════════════════════════════════════════════════════
   No fill at rest, so it sits on the page rather than on top of it; the
   border is what is left saying 'press me', and the colour arrives with the
   cursor. The shape is written once — three buttons wear it and only one of
   them is destructive, which is a difference of tone and nothing else.

   The leading is taken off deliberately: the line box is fifteen pixels of
   font metrics around thirteen of type, and centring a mark against that is
   centring it against air. */
.b-ghost {
  display: inline-flex; align-items: center; gap: 7px; line-height: 1;
  font-family: inherit; font-size: var(--t-ui); font-weight: var(--fw-semibold);
  cursor: pointer; border-radius: var(--r-pill);
  /* THE PRIMARY'S OWN BOX. A ghost and a filled button standing in the same
     row are the same control at two weights, and these were 28px against 32
     with 13px of side padding against 16 — near enough to look like a
     mistake rather than a decision. The box is the primary's; what stays
     different is the fill and the weight, which is the hierarchy.

     min-height rather than vertical padding, so a mark a pixel taller than
     the type beside it cannot make one ghost taller than another. */
  min-height: 32px; padding: 0 16px;
  color: var(--d200); background: none; border: 1px solid var(--card-border);
  transition: color var(--t-fast), background-color var(--t-fast),
    border-color var(--t-fast), transform 160ms var(--ease-out);
}
.b-ghost:hover { color: var(--d50); border-color: var(--card-border-hover); }
/* A MARK ON A GHOST IS SIZED HERE, NOT BY WHOEVER DRAWS ONE. The two that
   existed carried width and height attributes in their own markup, so a
   ghost built with `chIcon` — which emits neither — grew a mark to the
   height of the button and then some, the failure `aimyBlock` documents.
   Stated once for the class, with `.b-end-mark`'s own `flex: none`, so the
   next ghost to take an icon does not have to know. */
.b-ghost > svg { width: 14px; height: 14px; flex: none; }

/* ── AND EVERY QUIET ACTION IS ONE ────────────────────────────────────────
   The actions row renders its first verb as the filled primary and the rest
   as `.s-inline-btn`, which is the link treatment: accent-coloured text, no
   ground, no edge. On a record that put "Call Henry" in the same clothes as
   "fenborgsoftware.nl" four lines above it, and on the branches where there
   is no primary at all — a handed-over lead seen by the BDR who handed it —
   the entire row was coloured text.

   A verb in the actions row is a control, so it takes the control's edge.
   The primary keeps the fill and stays the one thing that leads.

   NOT scoped away from `.s-rec-facts`: that row is facts that happen to be
   doors, and a phone number is a value you read rather than a button you
   press. It keeps the link. */
.s-rec-actions .s-inline-btn {
  display: inline-flex; align-items: center; gap: 7px; line-height: 1;
  min-height: 32px; padding: 0 16px;
  border-radius: var(--r-pill);
  color: var(--ink-muted); background: none;
  border: 1px solid var(--card-border);
  transition: color var(--t-fast), background-color var(--t-fast),
    border-color var(--t-fast), transform 160ms var(--ease-out);
}
.s-rec-actions .s-inline-btn:hover {
  color: var(--ink-primary); border-color: var(--card-border-hover);
  text-decoration: none;
}
.s-rec-actions .s-inline-btn:active { transform: scale(0.97); }
/* The underline this class carries on hover is a link's affordance and the
   box has replaced it. */
.s-rec-actions .s-inline-btn::after { content: none; }
@media (prefers-reduced-motion: reduce) {
  .s-rec-actions .s-inline-btn { transition: none; }
  .s-rec-actions .s-inline-btn:active { transform: none; }
}
.b-ghost:active { transform: scale(0.97); }
.b-ghost:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* Ending a lead is the one that gets the tone, and it gets it on the cursor
   rather than at rest: it is read far more often than it is used, and a red
   word sitting in the masthead all day is an alarm nobody hears by the
   afternoon. */
.b-end-open:hover {
  color: var(--err-text); background: var(--err-bg); border-color: color-mix(in srgb, var(--err) 45%, transparent);
}
.b-end-mark { flex: none; opacity: 0.7; }
.b-end-open:hover .b-end-mark { opacity: 1; }
/* The question, on the menus' own machinery: a click anywhere else or Escape
   is the way back, and it hangs from the right because the button does. */
.b-end-pop { left: auto; right: 0; transform-origin: top right; width: 296px; max-width: calc(var(--vp-w) * 0.84); }
.b-end-say {
  margin: 2px 10px 10px; font-size: var(--t-body); color: var(--d100);
  line-height: var(--lh-base); text-wrap: pretty;
}
.b-end-acts { display: flex; align-items: center; gap: 10px; padding: 0 10px 4px; }
/* The confirm wears the tone at rest; the way back stays a quiet word,
   because the quiet one should be the easy one. */
.b-end-go {
  color: var(--err-text); background: var(--err-bg); border-color: color-mix(in srgb, var(--err) 45%, transparent);
}
.b-end-go:hover { color: var(--err-text); border-color: var(--err-text); background: color-mix(in srgb, var(--err) 18%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .b-ghost { transition: none; }
  .b-ghost:active { transform: none; }
}

/* Who has it now — a fact about the record, not a thing to press. */
.b-managed b { color: var(--d50); font-weight: var(--fw-semibold); }


/* ══ 39. THE STORY, AS A STORY ═════════════════════════════════════════════
   Five sentences of prose is a paragraph to read. Where they stand now is
   the deck line, the moments that got them there are a chain read left to
   right, and how it ends is one quiet line under it. The chain wraps rather
   than scrolls: a horizontal scroller inside a page that scrolls is two
   scrollbars arguing. */
.b-story { padding-top: 4px; }
/* IT WAS SET AT ITS OWN HEADING’S SIZE. --t-deck is 18 and .s-block-h above
   it is 18, so the section title and the sentence under it were the same
   step, with the <b> inside the sentence matching the title’s weight as
   well. A heading that shares both axes with its body is not a heading.
   --t-body puts the sentence one step under the thing that introduces it. */
.b-story-now {
  margin: 0 0 16px; max-width: 62ch; text-wrap: pretty;
  font-size: var(--t-body); font-weight: var(--fw-medium); line-height: var(--lh-base); color: var(--d100);
}
.b-story-now b { font-weight: var(--fw-semibold); color: var(--d50); }
.b-story-now b.tone-ok { color: var(--ok-text); }
.b-story-now b.tone-warn { color: var(--warn-text); }
.b-story-now b.tone-err { color: var(--err-text); }
.b-story-now b.tone-neutral { color: var(--d50); }
.b-story-line {
  list-style: none; margin: 0 0 14px; padding: 0;
  display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px 0;
}
.b-story-step {
  position: relative; display: flex; flex-direction: column; gap: 2px;
  padding: 0 22px 0 0; margin-right: 14px;
}
/* ══ THE CHAIN IS TRAVELLED ════════════════════════════════════════════════
   Every moment on this line has already happened, and they were drawn as
   chrome-grey dots joined by the hairline this build uses for the edge of a
   card — a stepper on which nothing had been stepped, ending on the only
   thing with any colour in it. Read at a glance it said the opposite of what
   it holds.

   What has passed is filled and joined by a line you can see. The tip is
   open — a call in the step's own tone with the page showing through, which
   is the shape everything from a Stepper to a git log uses for the point
   still moving. And an elided stretch is dashed on both sides, because "3
   steps more" is a piece of the walk nobody is claiming to draw. */
.b-story-step::after {
  content: ""; position: absolute; left: 14px; right: 4px; top: 5px; height: 2px;
  border-radius: 2px; background: var(--d400);
}
.b-story-step.is-gap::after,
.b-story-step:has(+ .b-story-step.is-gap)::after {
  background: repeating-linear-gradient(90deg, var(--d400) 0 3px, transparent 3px 7px);
}
.b-story-step:last-child { padding-right: 0; margin-right: 0; }
.b-story-step:last-child::after { display: none; }
.b-story-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--d200);
  border: 2px solid var(--card-bg); box-shadow: 0 0 0 1px var(--card-border);
}
.b-story-dot.tone-ok { background: var(--ok-text); box-shadow: 0 0 0 1px color-mix(in srgb, var(--ok) 40%, transparent); }
.b-story-dot.tone-warn { background: var(--warn-text); box-shadow: 0 0 0 1px color-mix(in srgb, var(--warn) 40%, transparent); }
.b-story-dot.tone-err { background: var(--err-text); box-shadow: 0 0 0 1px color-mix(in srgb, var(--err) 40%, transparent); }
.b-story-k {
  margin-top: 6px; font-size: var(--t-small); font-weight: var(--fw-bold);
  color: var(--d100); white-space: nowrap;
}
.b-story-t { font-size: var(--t-small); color: var(--ink-muted); white-space: nowrap; }
.b-story-step.is-now .b-story-k { color: var(--d50); }
/* The tip: the tone becomes the call and the ground shows through it. Four
   rules rather than a variable, because the tone arrives as a class and a
   class is the only thing the audit can pair with a rule. */
.b-story-step.is-now .b-story-dot {
  background: var(--card-bg);
  box-shadow: 0 0 0 2px var(--d200), 0 0 0 6px rgba(var(--accent-rgb), 0.14);
}
.b-story-step.is-now .b-story-dot.tone-ok {
  background: var(--card-bg);
  box-shadow: 0 0 0 2px var(--ok-text), 0 0 0 6px color-mix(in srgb, var(--ok) 18%, transparent);
}
.b-story-step.is-now .b-story-dot.tone-warn {
  background: var(--card-bg);
  box-shadow: 0 0 0 2px var(--warn-text), 0 0 0 6px color-mix(in srgb, var(--warn) 18%, transparent);
}
.b-story-step.is-now .b-story-dot.tone-err {
  background: var(--card-bg);
  box-shadow: 0 0 0 2px var(--err-text), 0 0 0 6px color-mix(in srgb, var(--err) 18%, transparent);
}

.b-story-step.is-gap .b-story-dot { background: transparent; box-shadow: none; border-color: transparent; }
.b-story-step.is-gap .b-story-k { color: var(--ink-muted); letter-spacing: 0.08em; }

/* ══ 43. THE FUNNEL SHOWS THE DROP ═════════════════════════════════════════
   Four columns on one grid: the step, the bar, how many ever got that far,
   and what share of the step above that is. The last column is the reason
   the block exists, so it is set in the mono face and reads down its own
   edge. */
/* ══ AND THE LABEL COLUMN HAD A CEILING IT COULD NOT MEET ══
   `minmax(96px, 150px) 1fr` was the ladder's: six steps with names like
   `Reachable` and `Handed to you`, none of which reaches 150. Two other
   surfaces draw on this grid now and one of them takes its labels from a
   contract — `Contacts answered a week`, `Time to first resolution` —
   which want 175 and got 150. So ONE row of the four wrapped, stood at
   41px against its neighbours' 20, and left its bar floating against the
   middle of a two-line label. A table where one row is double height is
   read as two tables.

   And the 389px beside it was the argument: the bar had two and a half
   times the label column to draw a picture of a figure printed twice to
   its right, while the row's own subject folded. `max-content` gives the
   label what it asks for and the bar keeps the rest — 191 and 348 here,
   every row one line.

   A CEILING AND A FLOOR, BECAUSE THIS LABEL IS NOT ALWAYS A STEP. The
   campaign funnel's first row takes an account's own name, which is
   unbounded: stress-tested at a 376px layout with a 51-character one,
   uncapped the column asked for 331px inside a 308px block and took the
   table off the card. `22ch` is 191px here, which is wider than every step
   in the ladder and wider than every metric on a floor, so nothing
   ordinary is touched by it. And `minmax(96px, ...)` rather than a bare
   `max-content` so the track can still give ground when the viewport has
   none; rigid is how a four-column table walks off a phone.

   THREE COLUMNS NOW. The fourth held the rate from one step to the next,
   which the count and the bar beside it already said; it went, with its
   rules, and the grid lost the track it sat in — a row is `display:
   contents`, so a fourth track with no cell for it would pull the next
   row's name up into the gap. */
.b-funnel {
  display: grid; grid-template-columns: minmax(96px, max-content) minmax(40px, 1fr) auto;
  gap: 8px 14px; align-items: center;
}
.b-fn-name { max-width: 22ch; }
.b-fn-row { display: contents; }
.b-fn-head { display: contents; }
.b-fn-head .b-fn-name, .b-fn-head .b-fn-n {
  font-size: var(--t-micro); font-weight: var(--fw-bold); letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-muted); padding-bottom: 2px;
}

/* ══ 44. WHAT TO SAY, AS SPEECH ════════════════════════════════════════════
   The opener is the one thing in the block you say out loud, so it is set
   as speech: the accent ground and the deck step. It had a 3px accent bar
   down its left edge too — a tint and a stripe saying one thing twice, and
   the stripe is the oldest tell in the book. What comes back is a pair — their words tagged in the warning
   tone, your answer in full ink under it. What we sell and what you can
   send are the quiet ends. */
.b-back { display: flex; flex-direction: column; gap: 14px; margin-bottom: 26px; }
.b-back:last-child { margin-bottom: 0; }
/* ══ THE LABEL SITS ABOVE ITS ANSWER, AND STOPS SHOUTING ═══════════
   Two faults, one row.

   It wore `tag tag-warn`, deliberately — the note below still describes
   that as tagging their words in the warning tone. But the panel put four
   amber pills on screen at once: Callback, Services, Pricing, Features. Only
   ONE thing in that panel is actually urgent, .b-prep-due.is-late, and it is
   amber too, so the colour that means "late" was being outvoted three to one
   by colour that means "category". The loudest treatment in the build was
   being spent on the word Pricing. These are neutral now and amber means
   late again.

   It cannot simply borrow .b-brief-cap's eyebrow either. That is 12px bold
   uppercase and tracked, and so is .b-prep-line b — the panel already sets
   two different levels in one treatment, and a third would make the section
   heading "If they push back" identical to the three labels under it. So
   this one separates on case and tracking instead: same 12px step, bold,
   but sentence case and untracked. Against the heading that is three axes;
   against its own answer at 16/400/--d50 it is size, weight and ink.

   ── and the measure, which is why the row moved in the first place ──

   It was two columns, `minmax(96px, max-content) 1fr`, stacking at a
   VIEWPORT breakpoint of 720px. It lives in the canvas answer body, about
   330px wide on a 1250px screen, so the breakpoint never fired and the wide
   layout ran inside a narrow box. Measured there, reproducing it at 329px:

     grid resolved to   95.99px + 227.86px, for a tag needing 68px
     .b-back-v got      26 characters per line
     same panel, rest   35 to 44
     its max-width      62ch, resolving to 622px against a 221px column

   26 characters is under half a comfortable measure, and it is why the
   block read as a wall rather than as three answers. Stacking settles it at
   every width without a media query or a container query: the answer always
   has the full column, 62ch caps it on a wide surface, and nothing depends
   on the window's width to describe a box the window knows nothing about. */
/* ══ THE ROW LABEL BECOMES A CHIP, BECAUSE FORM IS WHAT IS LEFT ═════
   Three ranks in this panel were wearing one treatment: .b-brief-cap heads
   a section, .b-back-k names a row inside that section, and .b-prep-line b
   and .b-prep-refp b label a value. All three sat at 12px bold, separated
   only by case and tracking, which is not enough to carry three levels.

   Size and weight are both spent at this end of the scale — 12px is the
   floor and 400 is the weight floor below 18px — so the axis left is FORM.
   A chip has an outline and a shape that a run of text does not, and that
   reads as a different KIND of thing rather than a louder one. Three
   devices for three ranks now:

     section   IF THEY PUSH BACK   uppercase, tracked, weight 400
     row       [ Services ]        a chip, outlined, 12px
     field     Last time           plain bold, sentence case

   It borrows the neutral treatment from .b-prep-state .tag-warn a few
   sections down, so the panel has one idea of what a quiet chip looks like
   rather than two. Amber stays reserved for late. The gap under it goes to
   6px: a chip carries its own padding, so 2px read as collision. */
.b-back-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
}
/* ══ THE LABELS ARE ONE FAMILY IN THREE RANKS ══════════════════
   Four labels arrived as four unrelated treatments, and the first attempt
   at fixing that made them one — which was worse, because they are not
   peers and flattening them said they were. Worse in a specific direction:
   an outlined chip is visually STRONGER than plain text, so making the row
   labels chips while the section head stayed a plain eyebrow left the rows
   outranking the section that contains them.

   What they actually are, by how much each one introduces:

     A  .b-brief-cap     heads a group: a paragraph AND three rows
     B  .b-back-k        labels one row
        .b-prep-line b   labels one value
     C  .b-prep-refp b   the shelf — reference, read last if at all

   Ranked on FORM, because size, weight and ink are all spent down here:
   12px is the size floor, 400 is the weight floor below 18px, and --d200 is
   the ink floor. Form still has room, and it has three clear steps:

     filled chip   >   outlined chip   >   plain text

   So A takes a wash behind the same border, B keeps the outline, and C
   drops the chrome entirely. That last one is not a demotion invented here:
   .b-prep-ref a few rules down already says the shelf earns its step down
   "without a rule above it or a box around it", and a chip is a box around
   it. The rule and the comment agree again. */
.b-back-k,
.b-brief-cap,
.b-prep-line b {
  display: inline-block;
  padding: 2px 9px; border-radius: var(--r-pill);
  border: 1px solid var(--card-border);
  font-size: var(--t-micro); font-weight: var(--fw-bold);
  line-height: var(--lh-snug); color: var(--ink-muted);
}
.b-back-v {
  margin: 0; max-width: 62ch; text-wrap: pretty;
  font-size: var(--t-body); font-weight: var(--fw-medium); line-height: var(--lh-base); color: var(--d50);
}
/* ══ AND THE BREAKPOINT THAT FIXED THIS ONCE IS GONE ══
   Both of its rules are in the default tier now, where they always
   belonged, and its own comment is why they were not: "max-content is what
   the default tier above already gives this column" — which was not true
   and had not been. The default was `minmax(96px, 150px)`, eight hundred
   lines further up, and a pass that fixed this column at 720 and wrote
   down that the wide tier was already right left the wide tier wrong for
   another year. A comment asserting what a rule elsewhere does is a claim,
   and it goes stale exactly like a number does.
   The reading it recorded stands and has moved up with the rules: the
   label is the row's SUBJECT, not a share of it, and a bar that is given
   twice its label's width says nothing the figures at the end of the same
   row do not. */

/* ══ AND BELOW 560 THE BAR IS THE COLUMN THAT GOES ═════════════════════
   Four columns on a 459px layout leave the label 80 of them, which is not a
   label column: `Got this far` folded to two lines in the header and `On the
   record here` to two in the row under it, so a three-row table stood five
   rows tall with every cell straddling a line it did not belong to.

   THE BAR IS THE ONE THAT CAN LEAVE, because it is the only cell here that
   says nothing the row does not already say in words — it is `of the one
   above` drawn, and `of the one above` is still there in figures at the end
   of the same row. Dropping the count or the conversion would cost a fact;
   this costs a picture of a fact. The label then takes what the bar was
   holding and every cell is one line again. */
/* ══ AND THERE IS NO THIRD TIER, WHICH TOOK FOUR TO FIND OUT ══════════════
   A pass through here dropped the bar below 560 and rebuilt the block three
   columns wide, on the reasoning that the bar is the only cell saying nothing
   the row does not also say in figures. It read well and it was the wrong
   move: a component that runs out of room should change its form, not leave.

   Three forms were built and looked at on a 459px layout, in both themes:
   the bar as the row's own ground with the label over it, the bar as a rule
   under the row, and the bar simply gone. The ground form is invisible in
   LIGHT — --d300 and --ink-muted at the alpha that reads on a dark card are
   nothing against a near-white one, which is the flattener every colour
   change here has to be checked for. The rule form folded `of the one above`
   into four lines. And then the arithmetic said the question was wrong:
   `max-content minmax(40px, 1fr) auto auto` is 117 + 65 + 46 + 120 at a 459px
   layout and 117 + 40 + 46 + 63 at 376, so the bar FITS at every width this
   build renders — 65 real pixels of it, filled to its share, which is a bar.

   The tier above is the whole answer. This block is the note that a third one
   was tried and is not needed, so the next pass does not spend the four
   rounds again. */

/* ══ 45. NO RULES BETWEEN SECTIONS ═════════════════════════════════════════
   The shell separates one block from the next with a hairline, and once a
   page carries six blocks that is six rules down a screen doing what a gap
   already does. Proximity is the separator: the boundary between two
   sections is 64px of air — twice the 32px the grid puts between the rows
   inside one — and the heading does the rest. Rules that survive are the
   ones that are not separators: a row inside a list, and the notifications
   panel, which is QA's byte for byte. */
.s-home > .s-block { border-top: 0; padding-top: 32px; }
.s-home > .s-block:first-of-type { padding-top: 0; }

/* The four figures had a rule over each of them and 24px between: the rule
   was reading as a table header nobody wrote. The caption over the figure
   is the label, and a door says it is a door by its own sub-line. */
.s-home .s-af, .s-home .b-af-door { border-top: 0; padding-top: 0; }
.s-home .s-afs { margin-bottom: 24px; }
@media (hover: hover) and (pointer: fine) {
  .b-af-door:hover .s-af-cap { color: var(--accent-text); }
}

/* Two more rules that were separating rather than containing. */
.b-camps-row { margin-top: 20px; padding-top: 0; border-top: 0; }

/* The briefing's own rule, above the ways to start: inside one card, and
   still a rule doing what the gap under a caption already does. */
.s-home .s-starts-wrap { margin-top: 28px; padding-top: 0; border-top: 0; }


/* ══ 47. A MENU ON ITS BUTTON ══════════════════════════════════════════════
   Five managers under a search box, a multiple choice and a confirm was a
   form for a question with one answer. The verb opens a menu under itself:
   it scales up from the button's own corner rather than out of nothing —
   140ms, no overshoot — and a name is the whole interaction. */
.b-menu-wrap { position: relative; display: inline-flex; }
.b-menu {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 40; min-width: 216px;
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: var(--card-bg-raised); border: 1px solid var(--card-border-hover); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.42), 0 0 0 1px rgba(255,255,255,0.03);
  transform-origin: top left;
  /* ══ IT OPENED FASTER THAN A MENU IS SUPPOSED TO CLOSE ══════════
     140ms, which is under the 150 a DISMISSAL gets. Opening and closing are
     not the same event: a close should get out of the way, an open is an
     invitation and has to be legible while it happens, or the menu is simply
     there and the eye has to find out where from. --t-open, and the 0.97 a
     menu starts from rather than the 0.96 that belongs to something the size
     of a modal. */
  animation: b-menu-in var(--t-open) var(--ease-out) both;
}
@keyframes b-menu-in { from { opacity: 0; transform: scale(0.97) translateY(-4px); } to { opacity: 1; transform: none; } }
.b-menu[hidden] { display: none; }
/* ══ AND IT LEAVES. A MENU THAT VANISHES WAS NEVER THERE ══════════════════
   The open above is argued at --t-open and the close was `hidden = true`:
   one frame the menu, the next frame nothing. A surface that takes 250ms to
   arrive and no time at all to go does not read as the same object leaving;
   it reads as the page having dropped it, which is the same defect as a
   status line blinking, in a bigger box.

   ONE RUNG FASTER AND A SHORTER MOVE. --t-fast against the open's --t-open,
   for the reason written above it: a close gets out of the way, an open is
   an invitation. And 0.99 against the open's 0.97, because the eye is being
   released here rather than led — the exit only has to say `gone on
   purpose`, and anything larger asks to be watched. The menu that opened
   upwards leaves downwards, back the way it came.

   No duration is written in bdr.js for this. The class starts the exit, the
   length lives here, and `hidden` goes on when the animation reports that it
   is over. */
.b-menu.is-closing { animation: b-menu-out var(--t-fast) var(--ease-out) forwards; pointer-events: none; }
.b-menu.is-up.is-closing { animation-name: b-menu-up-out; }
@keyframes b-menu-out { to { opacity: 0; transform: scale(0.99) translateY(-2px); } }
@keyframes b-menu-up-out { to { opacity: 0; transform: scale(0.99) translateY(2px); } }
.b-menu-cap {
  padding: 4px 10px 6px;
  font-size: var(--t-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-muted);
}
.b-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  font-family: inherit; font-size: var(--t-ui); font-weight: var(--fw-medium); color: var(--d100);
  text-align: left; padding: 8px 10px; border: 0; border-radius: 8px;
  background: transparent; cursor: pointer;
  transition: background-color var(--t-fast), color var(--t-fast);
}
.b-menu-item:hover { background: rgba(var(--accent-rgb), 0.14); color: var(--d50); }
.b-menu-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
/* ══ AND THE WAY OUT TO V1, ONLY WHILE ITS PILL IS GONE ════════════════════
   sales.css hides `.ver-link` under 1000 layout px, where the topnav runs out
   of room before anything else does. It was the one control this build let a
   breakpoint delete rather than move: the tab strip becomes the product
   chevron, the rail becomes a drawer, the call column becomes its minimised
   self, and this just went.

   It is in the desk menu now, under a hairline so it reads as a different
   kind of thing from the three people above it. Shown at exactly the width
   the pill is not, so the two are never both there and never both missing —
   one number, stated in two rules, and sales.css owns it.

   An anchor brings a browser underline and an inherited link colour, so it
   sets both for itself; the same note `.rail-console` carries. */
.b-menu-ver {
  display: none;
  margin-top: 4px; padding-top: 10px;
  border-top: 1px solid var(--card-border);
  text-decoration: none; color: var(--d100);
}
.b-menu-ver-ico { width: 13px; height: 13px; flex: none; opacity: 0.75; }
@media (max-width: 999.98px) { /* 1000 layout px */ .b-menu-ver { display: flex; } }
@media (prefers-reduced-motion: reduce) { .b-menu, .b-menu.is-closing { animation: none; } }
.b-menu-open[aria-expanded] { color: var(--d50); }
.b-menu-wrap:has(.b-menu:not([hidden])) .b-menu-open { color: var(--d50); border-color: var(--accent); }
.b-menu.is-right { left: auto; right: 0; transform-origin: top right; }
/* Hung above its button when there is no room beneath it. The origin and the
   4px the open animation travels both turn over with it, or the menu would
   grow downwards out of a corner it is not in. */
.b-menu.is-up { top: auto; bottom: calc(100% + 8px); transform-origin: bottom left; }
.b-menu.is-up.is-right { transform-origin: bottom right; }
.b-menu.is-up { animation-name: b-menu-up-in; }
@keyframes b-menu-up-in { from { opacity: 0; transform: scale(0.97) translateY(4px); } to { opacity: 1; transform: none; } }

/* ══ 46. THE THING TO DO, AND THE NOTE SOMEBODY LEFT ═══════════════════════
   Two captioned rows of prose was a wireframe of this. They are different
   kinds of thing: one is a task — a mark, the sentence, and the date it is
   owed as a chip that turns amber once it has passed — and the other is a
   note, which reads the way a note from a colleague reads anywhere else:
   their initials, their sentence, their name. */
.b-nm { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
.b-nm-do, .b-nm-note {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; align-items: start;
  padding: 14px 16px; border-radius: 14px;
}
.b-nm-do { background: rgba(var(--accent-rgb), 0.08); border: 1px solid rgba(var(--accent-rgb), 0.2); }
.b-nm-do.is-done { background: var(--ok-bg); border-color: color-mix(in srgb, var(--ok) 28%, transparent); }
.b-nm-note { background: var(--card-bg-raised); border: 1px solid var(--card-border); }
/* THE TILE IS 28px AND THE GLYPH IS NOT. The svg inside carried no width or
   height, so it filled the tile edge to edge — a 28px checkmark beside a 12px
   pill and a 16px sentence, which is the loudest thing in the block and says
   the least. §76 of this file already states the rule for marks on a card: a
   mark that outsizes the line it sits on is the wrong way round. 16px matches
   the sentence it marks and leaves the tile its padding. */
.b-nm-mark {
  /* The FALLBACK mark, drawn only when there is no due chip to carry it.
     Shrinking the glyph alone left a 28px tile holding 16px of icon beside a
     16px sentence — the box was still the largest thing in the row. Tile and
     glyph come down together: 22px around 14px keeps the same padding ratio
     and stops the mark outsizing the line it marks. */
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 7px; margin-top: 1px;
  color: var(--accent-text); background: rgba(var(--accent-rgb), 0.18);
}
.b-nm-mark > svg { width: 14px; height: 14px; }
.b-nm-do.is-done .b-nm-mark { color: var(--ok-text); background: color-mix(in srgb, var(--ok) 18%, transparent); }
.b-nm-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.b-nm-say {
  font-size: var(--t-lead); font-weight: var(--fw-semibold); line-height: var(--lh-base);
  color: var(--d50); max-width: 60ch; text-wrap: pretty;
}
.b-nm-due {
  align-self: flex-start; margin-bottom: 5px; white-space: nowrap;
  padding: 2px 9px; border-radius: 99px;
  /* A pill on the product surface, so it takes the tag step. It was
     --t-small at 14 and the sweep that found the others missed it: that
     one walked five list routes and no detail page, and this lives on a
     customer record.

     IT CARRIES THE MARK NOW, and the mark’s colours with it — the accent
     tint the tile used to wear, one step deeper than the block behind it so
     the same hue still separates them. The glyph sits at 12px, the size of
     the words beside it: §76 of this file says a 13px mark beside 12px type
     is the loudest thing in the corner, and a leading glyph in a chip has
     even less licence than that. */
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--t-micro); font-weight: var(--fw-bold); letter-spacing: 0.01em;
  /* --accent-label, not --accent-text. The tile this replaced held an ICON,
     which AA judges at 3:1; the chip holds WORDS, judged at 4.5, and putting
     --accent-text on an 0.18 tint of its own hue measured 3.94 — the tint
     lifts the ground toward the ink, which aimy-ds.css already records for
     --qa-accent a few hundred lines away. --accent-label is the lighter
     violet kept for exactly this, and .tag-qa already pairs it with an accent
     ground. 4.61 against the composited ground. */
  color: var(--accent-label); background: rgba(var(--accent-rgb), 0.18);
  border: 1px solid transparent;
}
.b-nm-due > svg { width: 12px; height: 12px; flex: none; }
.b-nm-do.is-done .b-nm-due { color: var(--ok-text); background: color-mix(in srgb, var(--ok) 18%, transparent); }
.b-nm-due.is-late { color: var(--warn-text); background: var(--warn-bg); border-color: color-mix(in srgb, var(--warn) 32%, transparent); }
.b-nm-then { font-size: var(--t-ui); color: var(--d200); max-width: 66ch; text-wrap: pretty; }
.b-nm-then b { color: var(--d50); font-weight: var(--fw-semibold); }
.b-nm-said {
  font-size: var(--t-body); font-weight: var(--fw-medium); line-height: var(--lh-base);
  color: var(--d50); max-width: 62ch; text-wrap: pretty;
}
.b-nm-by { font-size: var(--t-small); color: var(--ink-muted); }

/* ══ 47. WHERE A DEAL STANDS, IN ONE BLOCK ═════════════════════════════════
   `b-nm-do`'s drawing, because it is the same kind of thing: one statement,
   a mark, and the sentence that says what it is — so the mark keeps
   `b-nm-mark`'s square rather than a second class that would draw it again.

   Two blocks used to sit in this slot. The unwritten meeting was a bordered
   card and the ended deal was a bold line of text with nothing around it,
   and they never appear together — a deal is either running and owes a
   write-up, or it has stopped. One question, two drawings, which is the
   duplicate this build keeps removing. One drawing now, four tones.

   The tone is the pole where there is one: ok signed, err said no. Ask
   takes the accent, because it is a thing to do rather than a thing that
   happened. Rescheduled takes the card's own ground and no colour at all —
   nothing went well or badly there, a date moved.

   In a section rather than in the masthead, because it is not always there.
   A prompt on every record all the time is furniture, and furniture is not
   read. */
.b-state {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px;
  align-items: start; padding: 14px 16px; border-radius: 14px;
}
/* Centred on the sentence, which is not the same as centred on the block.
   `align-items: start` stays, because the ask has a row of four buttons
   under its line and centring against the pair would put the mark beside
   the gap between them -- the same fault the facts' marks had. It takes the
   sentence's own type metrics so `1lh` below means the line the mark has to
   sit on rather than whatever leading it inherited, and it lands on that
   line whether the sentence runs to one or three. */
.b-state-mark {
  display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: 9px; flex: none;
  font-size: var(--t-lead); line-height: var(--lh-base);
  margin-top: calc((var(--t-lead) * var(--lh-base) - 28px) / 2);
  margin-top: calc((1lh - 28px) / 2);
}
.b-state-mark > svg { width: 15px; height: 15px; }
.b-state-text { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.b-state-say {
  margin: 0; font-size: var(--t-lead); font-weight: var(--fw-medium);
  line-height: var(--lh-base); color: var(--d50); max-width: 62ch; text-wrap: pretty;
}
.b-state-say b { font-weight: var(--fw-semibold); }
.b-state-moves { display: flex; flex-wrap: wrap; gap: 8px; }

.b-state.is-ask { background: rgba(var(--accent-rgb), 0.08); border: 1px solid rgba(var(--accent-rgb), 0.2); }
.b-state.is-ask .b-state-mark { color: var(--accent-text); background: rgba(var(--accent-rgb), 0.18); }
.b-state.is-won { background: var(--ok-bg); border: 1px solid color-mix(in srgb, var(--ok) 28%, transparent); }
.b-state.is-won .b-state-mark { color: var(--ok-text); background: color-mix(in srgb, var(--ok) 18%, transparent); }
.b-state.is-lost { background: var(--err-bg); border: 1px solid color-mix(in srgb, var(--err) 28%, transparent); }
.b-state.is-lost .b-state-mark { color: var(--err-text); background: color-mix(in srgb, var(--err) 18%, transparent); }
.b-state.is-later { background: var(--card-bg-raised); border: 1px solid var(--card-border); }
.b-state.is-later .b-state-mark { color: var(--ink-quiet); background: var(--card-bg); }


/* ══ 47c. A CONTROL AiMY PERFORMS ══════════════════════════════════════════
   "Prepare me for 10:30" was drawn as a link -- accent text, no edge --
   under a block of six meetings. That is the treatment this system reserves
   for something that only goes somewhere, and this goes nowhere: it reads
   the record, the campaign and everything ever said into it, and writes a
   sheet. A control has an edge, so it takes one.

   The ghost's box exactly, because the two stand in rows together and a
   near-miss on height or padding reads as a mistake rather than a decision.
   What is added is the mark and the accent, and they are added for a reason
   the reader can use: every other button on the page does what its verb
   says, and this one hands the work to AiMY. The surfaces that already do
   that carry the same mark at the head of the block.

   Two of the three `data-prep` controls are not here. Both sit in rows that
   set their own weight from position -- the primary of a record's action
   row, the verb under a card's reading -- and taking that away from them to
   make three look alike would cost more than it bought. */
.b-ai { color: var(--accent-text); border-color: rgba(var(--accent-rgb), 0.35); }
.b-ai:hover {
  color: var(--accent-text); background: rgba(var(--accent-rgb), 0.10);
  border-color: rgba(var(--accent-rgb), 0.5);
}
.b-ai > svg { width: 13px; height: 15px; flex: none; }

/* Off-screen but in the tree: read aloud, never drawn, and never a focus
   stop of its own. `clip-path` rather than the old 1px-clip trick, and a
   negative margin so it cannot add a stray pixel to a grid row. */
.b-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

/* ══ THE PARTS NOBODY DRAWS ════════════════════════════════════════════════
   Text selection shipped as the browser's own blue on every surface of this
   build — the one piece of chrome that is visible on every page and belongs
   to no design system in it. It is the accent at low alpha now, which is
   what the rest of the ink is picked against, and the foreground is left
   alone so contrast cannot be lost behind it.

   Build-wide rather than scoped to one card: a selection colour that changes
   depending on which box the words are in is worse than the default it
   replaced. */
::selection { background: rgba(var(--accent-rgb), 0.28); }

/* ══ 47d. THE ANSWER, BEFORE THE CANVAS ════════════════════════════════════
   Anchored to the bar the way the scope tray is — `bottom: calc(100% + 8px)`
   rather than a fixed offset that assumes a bar height — and the same 760px
   the bar itself is, so the card and the input it came from are one column.

   The tray steps aside while it is up. They want the same eight pixels, and
   an answer you just asked for outranks the filters you did not.

   Clipped rather than scrolled. A scroller inside a card above a text field
   is a second place to put the wheel, and the whole point of this card is
   that it is a glance: what fits, a fade, and a door to the rest. */
.b-peek {
  position: absolute; bottom: calc(100% + 6px); left: 0; right: 0;
  display: flex; justify-content: center; pointer-events: none;
  /* ══ 47e. AND IT CAN BE PUT AWAY, WHICH MEANS SOMEWHERE TO PUT IT ═════
     Under the bar, not over it. The card painted at z-index 2 against a bar
     that declares none, which is the one arrangement that cannot be
     collapsed: a thing in front of the composer has nowhere to go. One is
     enough to clear the page and stay under the bar's three.

     THE CLIP IS WHAT MAKES IT A DRAWER. Shutting it is a translate, and a
     translate does not change the box — so without this the card would
     slide down out of itself and go on painting over the rows below. The
     clip ends the element at its own bottom edge, so the part that has gone
     down is not drawn and what is left above is exactly the lip. `inset()`
     with negative sides because the shadow lives outside the box and
     clipping it at the border would cost the lift on three sides to gain it
     on one. */
  z-index: 1;
  /* How far past its own bottom edge the clip reaches — how far into the
     seam the drawer is still drawn. It has to be in the travel as well as in
     the clip: the translate lands the card so exactly --peek-lip of it is
     inside the window, and a window 14px taller than the box leaves 14px of
     the answer showing under the handle. Measured; it read as a line of text
     trapped in the seam. */
  --peek-tuck: 14px;
  /* MEASURED FROM THE BOTTOM OF THE WINDOW, NOT FROM THE BAR. The window
     reaches --peek-tuck past the seam and the seam is 6, so the lip carries
     both before any of it is above the composer: 40 is the card's 1px
     border, its 5 of padding, the 26px strip itself, and the 8 that goes
     behind the bar. Set to the strip's own height it landed the handle four
     pixels off the bar — drawn, and pressed against it.
     IT DID NOT MOVE WHEN THE OPEN CARD SHRANK, and the arithmetic says
     why: `100%` of the translate is the card's own height, so this number
     alone decides what stands above the seam. Taken to 28 to match the
     open card's 14px strip it took the tucked tab down with it, which is a
     part of the component nobody asked to change. The shut lip is 26 again
     two hundred lines down, and this is the 40 that goes with it. */
  --peek-lip: 40px;
  clip-path: inset(-64px -64px calc(-1 * var(--peek-tuck)) -64px);
}
/* The composer is the drawer front, so it has to be in front. The shell
   gives this bar `position: relative; isolation: isolate` and no z-index,
   which put it and the card in painting order — later wins, and the card is
   later. Three, so there is room under it for the drawer at one. */
.aimy-float-bar { z-index: 3; }
/* `display: flex` above outranks the shell's `[hidden] { display: none }` --
   same specificity, later in the cascade -- so the card sat on screen from
   boot with nothing in it. Every element this build hides with the attribute
   and styles with a display is one line away from the same bug. */
.b-peek[hidden] { display: none; }
.aimy-float-wrap.has-peek .filter-tray { display: none; }
/* ══ THE COMPOSER'S OWN SKIN ═══════════════════════════════════════════════
   The pass before this built it as a card: the raised card ground, a wash
   down it, the card border, a card shadow tinted to the page. Every one of
   those is right for a card and wrong for this, because this is not a card.
   It is what the input said back, and dressed as a card it read as a
   notification that had floated in over the page and happened to land there.

   Nothing says "connected" as directly as being made of the same material.
   The bar is #1e2428 with a nine-percent edge, an inner top hairline at
   five, and a two-part shadow — one wide and soft for the lift, one
   hairline for the contact. This is that, exactly, so the two read as one
   object with a seam across it rather than as two things at two heights.
   The gap closes to six: near enough to be that seam, far enough to be two.

   TOKENISED BECAUSE TWO RULES NEED IT NOW. The shell hard-codes the literal
   in `.aimy-float-bar`, which is why the value is written here rather than
   read from a token that never existed. That rule still carries its own
   copy: until the shell adopts these, the composer's material has to be
   changed in both places. */
:root { --composer-skin: #1e2428; --composer-edge: rgba(255, 255, 255, 0.09); }
/* Off-white for the reason the design system's copy of this gives: white is
   the brightest thing light can make, and a composer wearing it is a slab
   rather than a control. #fafcfe sits above the page and below white, which
   is the only band where a raised surface can be both. */
:root[data-theme="light"] { --composer-skin: #fafcfe; --composer-edge: rgba(16, 24, 40, 0.14); }
/* The prose and the chips stack; the dismiss sits beside both. Two boxes
   rather than three flex children, so the chips line up under the sentence
   instead of beside it. */
.b-peek-main { flex: 1 1 auto; min-width: 0; }
.b-peek-card {
  pointer-events: all; position: relative;
  width: 100%; max-width: 760px;
  /* A column now: the lip you pull, then the answer. It was a row — one
     answer beside one dismiss — which is the arrangement with nowhere to put
     a handle that stays reachable once the rest of it is down. */
  display: flex; flex-direction: column;
  padding: 5px; border-radius: 24px;
  /* The slide. --t-panel is what every other panel in this build opens on,
     and a drawer is a panel that happens to travel downwards. The width
     travels with it — see the shut rule — on the shorter step, so it is
     finished while the card is still on its way down. */
  transition: transform var(--t-panel) var(--ease-out),
              max-width var(--t-base) var(--ease-out);
  background: var(--composer-skin);
  border: 1px solid var(--composer-edge);
  /* The bar's shadow, at a third. Taking its material was right and taking
     its LIFT was not: that shadow is the weight of the composer against the
     page, and the card is not a second thing floating over the same page —
     it is the top of the one object. Cast at full strength it read as
     stacked, which is the thing this pass set out to stop.

     Enough is left to hold it off the rows scrolling under it, and the
     contact hairline and the inner edge stay untouched, because those are
     what make it the same material rather than what makes it float. */
  box-shadow:
    0 4px 16px rgba(0, 0, 0, 0.34),
    0 0 0 1px rgba(0, 0, 0, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
  /* Overshoot rather than ease-out. The card is arriving, not fading in, and
     a thing that arrives settles. */
  /* A panel arriving: --t-panel, and the build's own curve rather than a
     hand-rolled one that was within a rounding error of it anyway. */
  animation: b-peek-in var(--t-panel) var(--ease-out);
}
/* The shell's own light values for the same two shadows, and no inner
   hairline: on white there is no light for an edge to catch. */
:root[data-theme="light"] .b-peek-card {
  box-shadow:
    0 4px 16px rgba(16, 24, 40, 0.09),
    0 0 0 1px rgba(16, 24, 40, 0.03);
}
/* One authored moment, and it reaches past transform and opacity. The card
   resolves out of the blur the bar behind it is already carrying, which is
   the same material arriving rather than a box sliding in — 6px is small
   enough to stay on the compositor and short enough that nothing waits on
   it. `filter` and not `backdrop-filter`: animating the backdrop re-samples
   everything under the card every frame. */
@keyframes b-peek-in {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(0.985); filter: blur(6px); }
  to { opacity: 1; transform: none; filter: blur(0); }
}
/* ══ THE LIP ═══════════════════════════════════════════════════════════════════════
   The one part of the drawer that is never behind the bar. Shut, this strip
   IS the drawer — a rounded top edge above the composer with a short line
   across the middle of it, which is the whole of what Miro shows and the
   whole of what is needed: something is down there, and it comes up when you
   pull it.

   Both controls live up here, because a dismiss that goes down with the
   drawer is a dismiss you have to open the drawer to reach. */
/* ══════════════ THE LIP COSTS NO HEIGHT ══════════════
   It was a 26px row of its own above the answer, and 26 is what the × in
   it measures — so the card spent 26px on a 4px mark and a button, then
   another 28 on the sentence's own padding, to show 24px of text. Ninety
   pixels of card for one line, and three quarters of it chrome.

   Out of flow, then: the strip is drawn ACROSS the top of the card rather
   than above it, over the padding the sentence already had, and the ×
   moves to the corner. The card is 58px and reads as one object rather
   than a handle with an answer under it.

   WHAT IT COSTS IS THE TARGET, AND THAT IS THE WHOLE OF WHAT IT COSTS.
   The margin under `.b-peek-grip` argues for a row you cannot miss over a
   line you have to hit, and it is right — this is the trade taken
   deliberately, not an oversight. Two things pay it back: the press band
   is still the full width of the card, and shut, where there is nothing
   else on the sliver to press, the strip grows to the whole of it. */
.b-peek-lip {
  position: absolute; top: 0; left: 0; right: 0; height: 14px;
  z-index: 1;
}
/* ══════════════ AND SHUT, IT IS THE ROW IT ALWAYS WAS ══════════════
   The strip above is the OPEN card's arrangement. Tucked, the sliver is
   the only part of this component anybody sees, and it was already the
   right shape: a 26px row inside the card's padding, the bar at its
   centre, the × at its end. Nothing about saving height in the open card
   is an argument for changing the shut one, and a 14px strip down there
   drew the × half behind the composer — a chevron with its bottom sheared
   off.

   So the geometry comes back exactly: inset by the card's own 5, 26 tall,
   the × at the end of it. `--peek-lip` is the travel and it is untouched,
   so what stands above the seam is what always stood above the seam — the
   same numbers, not similar ones. It is the same control in two forms,
   which is what a component does when the room changes. */
.b-peek.is-shut .b-peek-lip {
  top: 5px; left: 5px; right: 5px; height: 26px;
  display: flex; align-items: center; justify-content: flex-end;
}
/* Inset by the padding again, so the handle follows the card's inner curve
   rather than its outer one. */
.b-peek.is-shut .b-peek-grip { border-radius: 19px 19px 0 0; }
/* Back to a flex child of that row: `top: 0` is the row's own top and the
   2px is the gap it always had from the card's right padding. */
.b-peek.is-shut .b-peek-x { top: 0; right: 2px; }
/* THE GRIP IS THE ROW, NOT THE MARK IN IT. A 36×4 bar is four pixels tall
   and a target is forty-four; drawn as the button it would be a line you
   have to hit. So the button is the width of the drawer and the height of
   the lip, and the line is what it paints at its centre. Nothing here is
   draggable — a drawer whose only way open is a drag is a drawer that is
   shut for anyone who does not know it is there. It is a press. */
.b-peek-grip {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  /* The card's 24px less its 1px border: the strip sits on the inside of
     that edge now rather than inside the 5px padding, so it follows the
     outer curve and not the inner one. */
  padding: 0; border: 0; border-radius: 23px 23px 0 0;
  background: none; cursor: pointer;
  transition: background-color var(--t-fast);
}
/* Pulled as well as pressed. `touch-action: none` is what makes that true on
   a touch screen: without it the browser claims a vertical drag for the page
   scroll and the handle never sees the move. */
.b-peek-grip { cursor: grab; touch-action: none; }
.b-peek.is-dragging .b-peek-grip { cursor: grabbing; }
.b-peek-grip:hover { background: rgba(var(--accent-rgb), 0.07); }
.b-peek-grip:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
/* --d400 and not a type token: this is a mark, not a word, and it is the one
   thing in the component that is allowed to be quiet. It answers the cursor
   by growing rather than by lighting up, which is the gesture it stands for. */
.b-peek-grip-bar {
  width: 36px; height: 4px; border-radius: 2px;
  background: var(--d400);
  transition: width var(--t-fast) var(--ease-out), background-color var(--t-fast);
}
.b-peek-grip:hover .b-peek-grip-bar { width: 48px; background: var(--d300); }
@media (prefers-reduced-motion: reduce) {
  .b-peek-grip-bar { transition: background-color var(--t-fast); }
}
/* Over the grip, which is `inset: 0` across the whole lip and would otherwise
   take the press meant for the dismiss. */
.b-peek-x { z-index: 1; }

/* Under the hand, nothing is animating and nothing is narrow: the card is at
   the position the pointer put it, at the width it is read at. Both come back
   under their own transitions the moment it is let go. */
.b-peek.is-dragging .b-peek-card {
  transition: none;
  max-width: 760px;
}
.b-peek.is-dragging .b-peek-main { opacity: 1; transition: none; }

/* ══ SHUT ═════════════════════════════════════════════════════════════════════════
   Down by its own height less the lip, which lands the lip where the card's
   top edge was and puts the rest under the clip. `100%` of a translate is the
   element's own height, so this is one declaration for an answer of any
   length — one line or the full three. */
.b-peek.is-shut .b-peek-card {
  transform: translate3d(0, calc(100% - var(--peek-lip) + var(--peek-tuck)), 0);
  /* ══ AND NARROWER THAN THE THING IT IS BEHIND ═══════════════════
     At the composer's own 760 the shut drawer is a full-width slab with a
     line on it, which reads as a second bar that has failed to load rather
     than as something tucked away. Narrower, it is a tab: the composer's
     edges are outside it on both sides, so the eye puts it behind.

     Four fifths of the composer, and written twice because the composer's
     own width is itself a min(): 608 is 80% of its 760 cap, and the bare 80%
     is what applies below the width where that cap stops binding. A single
     number is right at exactly one size — 360 was, and it took a quarter off
     the bar rather than a fifth. Only the shut card is narrowed; open, it is
     the composer's own 760, because that is the width the answer is read at. */
  max-width: min(608px, 80%);
}
/* The answer rewraps from 760 to 360 as the card narrows, and for the first
   moment of the collapse the top of it is still above the seam — so the
   reflow is on screen, and text rewrapping mid-animation reads as a fault.
   It goes out first and comes back after the width has settled. Cheap:
   `inert` has already taken this out of reach while it is down. */
.b-peek-main { transition: opacity var(--t-fast) var(--ease-out) 160ms; }
.b-peek.is-shut .b-peek-main { opacity: 0; transition-delay: 0s; }

/* The whole card is the press, so the text inside it is left-aligned rather
   than centred the way a button's label would be — and there is no label
   under it saying so. "Open the canvas" was a caption explaining a control
   that is already the size of the sentence it holds; a card that is entirely
   pressable does not need a second, smaller thing to press.

   `transform` and `opacity` only, and the push is a pixel down rather than a
   scale: a 760px card that scales on press wobbles at its corners, where the
   same card moving one pixel reads as a key going down. */
.b-peek-open {
  display: grid; width: 100%;
  /* Right padding clears the × now sitting over this corner — the
     sentence must never run under it, and a three-line answer at 760 is
     the case that would. */
  padding: 12px 44px 12px 16px; border: 0; border-radius: 20px;
  background: none; font-family: inherit; text-align: left; cursor: pointer;
  transition: background-color var(--t-fast), transform 120ms var(--ease-out);
}
.b-peek-open:hover { background: rgba(var(--accent-rgb), 0.07); }
.b-peek-open:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) { .b-peek-open:active { transform: none; } }
.b-peek-open:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-peek-body {
  position: relative; display: block; overflow: hidden;
  /* Three. A peek is a glance and four lines is a paragraph — and with the
     chips lifted out of this box, three lines of prose is all it ever holds,
     so the cut lands on reading rather than on something to press. */
  max-height: calc(var(--t-body) * var(--lh-base) * 3);
  font-size: var(--t-body); line-height: var(--lh-base);
  color: var(--d100); text-wrap: pretty;
}
.b-peek-body b { color: var(--d50); font-weight: var(--fw-semibold); }
/* Outside the cap and outside the press, so it is never cut and never
   opens the canvas; gone entirely when the answer carries no chips, so it
   costs no space of its own. The padding is the button's own, minus the
   bottom, so the chips sit under the sentence on the same left edge. */
.b-peek-acts { display: block; padding: 0 16px 12px; }
.b-peek-acts:empty { display: none; }
/* The button keeps its full box when it is alone and gives up its bottom
   padding when the chips follow, so the gap between them is the chip row's
   own ten and not that plus fourteen. */
.b-peek-open:has(+ .b-peek-acts:not(:empty)) { padding-bottom: 0; }
/* Knowledge's own chip entrance, shape for shape: 420ms on the shared ease,
   rising 0.375rem from nothing, staggered off `--i`. It arrives after the
   sentence rather than with it, so the stagger reads as the answer handing
   you what to do about it — which is the order the two things happen in.

   NINETY, NOT FORTY. Both numbers are that file's: 40ms staggers the gate's
   suggestion chips, which are a wall of them and want to arrive as one
   surface, and 90ms staggers its skeleton rows, which want to be counted.
   Four chips at 40 is 120ms end to end — one movement. At 90 the last one
   lands 270ms after the first, which is slow enough to read as four things
   arriving and still finished before the eye leaves the card. It carries on
   from the words: the sentence types itself in, then the chips come one at
   a time behind it. */
.b-peek-acts .s-insight-lnk {
  opacity: 0;
  /* ══ AND THE LAST CHIP WAS NOT WAITING A RHYTHM, IT WAS JUST WAITING ══
     90ms apart with NO CAP on the index — the one stagger in this file that
     forgot the `min()` every other one has. Five chips put the last one at
     450ms before it starts and 870ms before it settles, on a row of controls
     beside an answer you have already read. 40ms, capped at the sixth, is
     240ms end to end: the same total the queue cards were tuned to, reached
     the same way. */
  animation: b-chip-in var(--t-panel) var(--ease-out) forwards;
  animation-delay: calc(min(var(--i, 0), 6) * var(--t-stagger));
}
@keyframes b-chip-in {
  from { opacity: 0; transform: translate3d(0, 0.375rem, 0); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .b-peek-acts .s-insight-lnk { animation: none; opacity: 1; }
}
/* Only where there is more, and now it is the ONLY thing that says so —
   the caption under it is gone. That is the right amount: a sentence cut
   mid-air says there is more of it, and a reader who wants the rest presses
   the thing they are already reading.

   Drawn on the card's own ground, which is a wash rather than a flat colour
   now, so the fade ends on the colour the bottom of the card actually is
   rather than on the one the top of it started as. */
.b-peek.is-clipped .b-peek-body::after {
  /* One line's worth. Over a three-line box a 2.4em fade covered most of
     what it was meant to be revealing. */
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1.7em;
  background: linear-gradient(to bottom, transparent, var(--composer-skin));
  pointer-events: none;
}
/* 32px of button under a 14px mark, and a thumb wants 44. The target grows
   and the button does not: an invisible box centred on it takes the press,
   so the card keeps its proportions and the finger keeps its margin. */
/* ══════════════ AND THE × IS WHY THE ROW WAS 26 ══════════════
   Nothing about a dismiss wants a row of its own; it was in one because
   the lip was one, and the lip was 26 because this is. In the corner it
   costs the card nothing, and it keeps the property the lip was giving it:
   it is outside `.b-peek-main`, so it does not go down with the drawer and
   is still there to press when the answer is tucked away. */
.b-peek-x {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  display: grid; place-items: center; width: 26px; height: 26px;
  border: 0; border-radius: 50%; background: none; color: var(--ink-muted);
  cursor: pointer; transition: color var(--t-fast), background-color var(--t-fast);
}
.b-peek-x::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.b-peek-x:hover { color: var(--ink-primary); background: var(--card-bg); }
.b-peek-x:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* ══ THE MARK IT THINKS WITH ═══════════════════════════════════════════════
   `.ai-thinking` and `.ai-thinking-label` are already in this shell's design
   system, so the row and its label need nothing here. `.think-mark` is not,
   and it arrives as Knowledge writes it, to the character:

     width: 1.625rem; height: 1.625rem;
     flex-shrink: 0;
     color: var(--ai-text);
     vertical-align: middle;

   The `color` is load-bearing rather than decorative — the canvas reads its
   own floor colour off it, which is why the mark is painted in one place and
   not two. Nothing here guards reduced motion: `startThinking` asks the
   media query itself and draws the mark once, at rest, because the state is
   information and only the movement is decoration.

   The clip comes off while it thinks. There is nothing to fade to when what
   is in the box is a mark and four words. */
.b-peek.is-thinking .b-peek-body { max-height: none; }
/* ══ THE PILL, ONE SIZE DOWN ═══════════════════════════════════════════════
   The design system's `.ai-thinking` is built for a chat bubble, where it
   stands in for a whole message and is sized to hold that place. Here it sits
   in a card whose own padding already frames it, so it was carrying a second
   frame at a message's scale — 9/15 of padding, a 16px label and a 26px mark
   inside a box that is only ever one line tall.

   Down a step and no further: the mark to 22, the label to the `--t-small`
   this build uses for a line that is not the reading, and the padding to
   7/13. Scoped to the card, so the bubble's own version is untouched. */
/* No pill. The card is the container and a second rounded box inside it,
   with its own ground and its own border, was a container drawn around a
   mark and four words that were already contained. What is left is the
   thing happening and what it is doing, which is all the state was ever
   saying. */
.b-peek .ai-thinking {
  gap: 9px; padding: 0; background: none; border: 0; border-radius: 0;
}
.b-peek .ai-thinking-label { font-size: var(--t-small); }
.b-peek .think-mark { width: 1.375rem; height: 1.375rem; }
.think-mark {
  width: 1.625rem; height: 1.625rem;
  flex-shrink: 0;
  color: var(--ai-text);
  vertical-align: middle;
}
@media (prefers-reduced-motion: reduce) { .b-peek-card { animation: none; transition: none; } }

/* ══ 48. A DOCUMENT IS A DOOR ══════════════════════════════════════════════
   They were grey uppercase tags — a status treatment on a thing you open,
   set in 10px capitals so a document's name read as a label rather than a
   title. A chip you can press: sentence case at the small step, the open
   mark at the end, and a border that answers the cursor. */
.b-docs { display: flex; flex-wrap: wrap; gap: 8px; }
/* The tag's own colour and shape — it was already right — with the press,
   the open mark and a hover that says it is a door. */
.b-doc {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-family: inherit; font-size: var(--t-micro); font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide); text-transform: uppercase; text-align: left;
  padding: 3px 9px; border-radius: var(--r-pill);
  color: var(--d200); background: var(--wash-3); border: 1px solid var(--card-border);
  transition: border-color var(--t-fast), color var(--t-fast), background-color var(--t-fast), transform 160ms var(--ease-out);
}
.b-doc:hover { color: var(--d50); border-color: var(--accent); background: var(--wash-6); }
.b-doc:active { transform: scale(0.97); }
.b-doc:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.b-doc-out { flex: none; color: var(--ink-muted); }
.b-doc:hover .b-doc-out { color: var(--accent-text); }
@media (prefers-reduced-motion: reduce) { .b-doc { transition: none; } .b-doc:active { transform: none; } }

/* ══ 50. A ROW'S STATE IS NOT A BUTTON ═════════════════════════════════════
   The step sat in the roster's right column as a bordered pill, directly
   above the Call pill, and the two read as a pair of buttons. A dot in the
   called's own tone and the word beside it: a state you read, over the one
   thing on the row you press. */
.b-rstate {
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  font-size: var(--t-micro); font-weight: var(--fw-bold); letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-muted);
}
.b-rstate-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--d400); flex: none; }
.b-rstate-dot.tone-ok { background: var(--ok-text); }
.b-rstate-dot.tone-warn { background: var(--warn-text); }
.b-rstate-dot.tone-err { background: var(--err-text); }
.b-rstate-dot.tone-neutral { background: var(--d400); }

/* The one figure the chips add up to, under the heading rather than at the
   far end of its row, where a section's actions live. */
.b-tocall { margin: 0 0 12px; font-size: var(--t-ui); color: var(--d200); }
.b-tocall b { color: var(--d50); font-weight: var(--fw-semibold); }

/* A menu row that names a person: the step as a dot, the name, what they do. */
.b-menu-line { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.b-menu-name { font-size: var(--t-ui); font-weight: var(--fw-semibold); color: inherit; }
.b-menu-sub { font-size: var(--t-small); color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.b-menu-more { padding-top: 2px; }
.b-menu-item.is-foot { color: var(--d200); font-weight: var(--fw-semibold); margin-top: 2px; }

/* ══ 51. THE MAP ═══════════════════════════════════════════════════════════
   A root, a rail, and what hangs off it. The rail is the drawing: one line
   down from the company card and an elbow into each limb, which is what
   makes a set of cards read as branches of one thing instead of another
   grid. The last limb's line stops at its own elbow, so the rail ends where
   the map ends rather than running into the white space below it. */
.b-map { margin-top: 8px; }
/* THE ROOT AND THE FIRST BRANCH WERE TOUCHING. The limb draws its own
   elbow from its own top edge, so with the body flush against the card the
   corner was drawn across the first node and the stem had nowhere to be.
   The body steps down and the stem is drawn into that step, which is the
   only place a rail can start. */
.b-map-body { position: relative; margin-left: 22px; margin-top: 16px; }
.b-map-body::before {
  content: ""; position: absolute; left: 0; top: -16px; width: 1px; height: 16px;
  background: var(--card-border-hover);
}
.b-limb { position: relative; padding: 0 0 10px 26px; }
.b-limb:last-child { padding-bottom: 0; }
/* The elbow is one rounded corner, not a vertical meeting a horizontal at a
   right angle — a drawn join reads as a join, and a crossing reads as two
   lines that happen to touch. The trunk between limbs is the second rule,
   and the last limb has none, so the rail ends at the last branch. */
.b-limb::before {
  content: ""; position: absolute; left: 0; top: 0; width: 17px; height: 25px;
  border-left: 1px solid var(--card-border-hover);
  border-bottom: 1px solid var(--card-border-hover);
  border-bottom-left-radius: 9px;
}
.b-limb:not(:last-child)::after {
  content: ""; position: absolute; left: 0; top: 25px; bottom: 0; width: 1px;
  background: var(--card-border-hover);
}
.b-map-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* A node is a door with one fact on it: for a person, the step as a dot. */
.b-node {
  display: flex; flex-direction: column; gap: 3px; align-items: flex-start; cursor: pointer;
  font-family: inherit; text-align: left; min-width: 172px; max-width: 260px;
  padding: 10px 14px; border-radius: 12px;
  background: var(--card-bg); border: 1px solid var(--card-border);
  transition: border-color var(--t-fast), background-color var(--t-fast), transform 160ms var(--ease-out);
}
.b-node:hover { border-color: var(--accent); background: var(--card-bg-raised); }
.b-node:active { transform: scale(0.985); }
.b-node:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.b-node.is-root {
  max-width: 340px; padding: 12px 16px;
  background: var(--card-bg-raised); border-color: var(--card-border-hover);
}
.b-node.is-here { border-color: rgba(var(--accent-rgb), 0.42); background: rgba(var(--accent-rgb), 0.08); }
/* THE NAME WRAPS, IT DOES NOT TRUNCATE. "Finance and back office — …" is
   not a campaign you can choose between; two lines are. The dot and the mark
   hold the first line while it does. */
.b-node-top { display: flex; align-items: flex-start; gap: 7px; min-width: 0; max-width: 100%; }
.b-node-top .b-rstate-dot { margin-top: 6px; }
.b-node-name {
  min-width: 0; font-size: var(--t-ui); font-weight: var(--fw-bold); color: var(--d50);
  line-height: 1.35; text-wrap: pretty;
}
.b-node.is-root .b-node-name { font-size: var(--t-lead); letter-spacing: -0.01em; }
.b-node-sub {
  font-size: var(--t-small); color: var(--ink-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.b-node-mark {
  flex: none; margin-top: 2px; padding: 1px 6px; border-radius: var(--r-pill);
  font-size: var(--t-micro); font-weight: var(--fw-bold); letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--accent-text); background: rgba(var(--accent-rgb), 0.16);
}

/* A limb that names its own kind of join. The margin above it is what puts
   its middle on the elbow: the rail points at the caption, not past it. */
.b-branch-head {
  display: inline-flex; align-items: baseline; gap: 10px;
  margin: 16px 0 10px; font-family: inherit; text-align: left;
}
.b-branch-head.is-flat { cursor: default; }
.b-branch-name { font-size: var(--t-ui); font-weight: var(--fw-bold); color: var(--d50); }
.b-branch-n { font-size: var(--t-small); color: var(--ink-muted); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .b-node { transition: none; }
  .b-node:active { transform: none; }
}

/* ══ 52. A FACE ════════════════════════════════════════════════════════════
   A photograph over a drawing — see the argument in bdr.js. The image is
   absolutely placed so that when the request fails it collapses to nothing
   and the drawing behind it is simply what you see; alt is empty because a
   face that does not load must not leave a word behind. The rings are on the
   wrapper: an inner one so a pale photograph does not cut a hole in a dark
   surface, an outer one to sit it on whatever ground it lands on. */
.b-face {
  position: relative; display: block; flex: none; overflow: hidden; border-radius: 50%;
  box-shadow: 0 0 0 1px var(--card-border);
}
.b-face > svg { display: block; width: 100%; height: 100%; }
.b-face-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: 50%;
}
.b-face::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
}
/* The masthead and the canvas both held a monogram in a tinted circle; the
   face brings its own ground, so theirs would only show as a rim. */
.topnav-avatar { background: none; padding: 0; overflow: hidden; }
.msg-avatar.user-av { background: none; border: 0; border-radius: 50%; }
.b-nm-note .b-face { margin-top: 1px; }

/* ══ 53. WHAT A CAMPAIGN IS ════════════════════════════════════════════════
   Three parts across the masthead's width, in the order a caller asks them:
   what am I trying to get, what am I selling, whose is it. They are a
   definition list, not a card grid — no borders between them, because the
   caption above each is already the separator and a rule would make three
   facts read as three objects. */
.b-cmeta {
  /* ══ THE 232 GIVES WAY WHEN THERE IS NOT 232 ═══════════════════════
     `minmax(232px, 1fr)` reads like a preference and behaves like a floor:
     the track cannot go under 232 whatever the container does, so a grid of N
     parts reports a min-content of N x 232 and the block sizes the PAGE
     instead of the page sizing the block. `.b-draft-meta` below hit this and
     was answered by counting columns per breakpoint; this is the same defect
     at its source, and it showed on the manager's campaign, where four parts
     made 792px of min-content inside a 391px layout and took the back link,
     the title, the team and every block below them off the right of the
     screen by 380px.

     `min(232px, 100%)` is the same preference written so it can lose. The
     column still asks for 232 and auto-fit still decides how many fit; when
     the container is narrower than one column, the floor becomes the
     container and the grid folds to one part per row instead of overflowing.
     No breakpoint, and it answers every width rather than the ones somebody
     thought to write down. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(232px, 100%), 1fr));
  column-gap: 48px; row-gap: 24px; margin: 22px 0 0; max-width: 1080px;
}
.b-cmeta-part { min-width: 0; }
.b-cmeta-cap {
  display: block; margin: 0 0 8px;
  font-size: var(--t-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-muted);
}
.b-cmeta-say { font-size: var(--t-body); color: var(--d100); }
.b-cmeta-p { margin: 0 0 10px; line-height: var(--lh-base); max-width: 44ch; text-wrap: pretty; }
.b-cmeta-p:last-child { margin-bottom: 0; }
.b-cmeta-p b { color: var(--d50); font-weight: var(--fw-semibold); }
.b-cmeta-p .tag { vertical-align: 1px; margin-left: 6px; }

/* ══ 54. THE TEAM ══════════════════════════════════════════════════════════
   The people calling the same two hundred numbers as you. A face, a name and
   what they are on it for — the knowledge graph's node, at the size a person
   is drawn rather than the size a company is. */
/* No card around each of them. A pill with a border and a ground made three
   colleagues read as three controls; the face is the object here, and the
   space between them says where one person ends. */
.b-team { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 32px; margin-top: 26px; }
.b-team-cap { flex-basis: 100%; margin: 0 0 2px; }
.b-mate { display: flex; align-items: center; gap: 10px; }
.b-mate-t { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.b-mate-name { font-size: var(--t-ui); font-weight: var(--fw-bold); color: var(--d50); white-space: nowrap; }
.b-mate-role { font-size: var(--t-small); color: var(--ink-muted); white-space: nowrap; }

/* ══ 55. WHAT IS IN THE WAY ════════════════════════════════════════════════
   One block. It was five findings, each with a 24px figure, a bar, a blurb,
   a caption over the remedy and a chip under it — five sections wearing the
   clothes of a page, for something read between two calls.

   A sentence, then a line each: the count, what it is, what beats it. The
   bars went because three of them measured against three different totals,
   which is the one thing a bar cannot do — the proportion is in words beside
   the name now. Amber marks the obstacle nobody has written an answer to,
   and nothing else, so it means one thing on this block. */
/* ══ MEASURED BEFORE IT WAS TOUCHED ════════════════════════════════════════
   `.b-way-n` was `--t-lead`, `.b-way-beat` was `--t-body`, and both of those
   tokens are 16px. `.b-way-name` was `--t-ui`, which is 14. So the count was
   the same size as the prose about it, and the NAME of each obstacle was the
   smallest thing in its own row. Two sizes across the whole block, the larger
   one spent on the paragraph. No amount of cutting words fixes that; the
   scale is what was missing.

   The scale below is not invented. It is `.s-af` — the figure strip forty
   pixels up this same page — read off sales.css and applied here: a 24px
   tabular figure with negative tracking, its denominator small and faint
   beneath it, and rows DIVIDED, NEVER BOXED, which is that component's own
   stated rule. */
.b-way-lead {
  margin: 0 0 22px; font-size: var(--fs-lg); color: var(--d100);
  line-height: var(--lh-snug, 1.35); max-width: 62ch; text-wrap: pretty;
}
.b-way-lead b { color: var(--d50); font-weight: var(--fw-semibold); }

/* Two populations, two groups. The caption carries the ground its figures
   are counted against, so "of 8 reasons" under a number has been explained
   before it is read. */
/* ══ THE SMALLER STEP IS NOT ALSO THE DIMMER ONE ═══════════════════════════
   §11 of this file already lifted `.s-block-sub`, `.b-vfoot` and `.s-back`
   off the faint inks for this reason, and every class added here went
   straight back to them: the caption, the denominator and the footnote were
   all `--ink-faint`, which is `--d400`. Nothing in this block is below
   `--d200` now. The ranks are carried by size, weight and case, which is
   where they should have been carried in the first place — a rank made out
   of dimness is a rank that costs the reader their eyes. */
.b-way-group + .b-way-group { margin-top: 26px; }
.b-way-cap {
  margin: 0 0 2px; font-family: var(--font-sans); font-size: var(--fs-2xs);
  font-weight: var(--fw-medium); text-transform: uppercase;
  letter-spacing: var(--ls-wide); color: var(--d200);
}

/* ══ THE FIGURE COLUMN IS SIZED TO THE FIGURE ══════════════════════════════
   sales.css measured this on its own obstacle rows: 40px of column and a
   16px gap put 56px between a one-digit figure and its own sentence, so half
   of every row was the space between its two halves. These counts run 32,
   11, 2, 2 — two digits at most. 34 and 14. */
.b-ways { display: flex; flex-direction: column; }
/* ONE RIGHT EDGE FOR THE ROW. The measure is capped on the row, the way
   `.s-reason` and `.s-straight` cap theirs, rather than on the paragraph
   inside it — a filled band that stops half way across a column reads as a
   box somebody cut short, where the same width shared by the name, the line
   under it and the band reads as a column. */
.b-way {
  display: grid; grid-template-columns: 34px minmax(0, 1fr);
  column-gap: 14px; align-items: start; max-width: 72ch;
  padding-top: 14px; margin-top: 14px;
  border-top: 1px solid var(--hairline);
}
/* The first row sits under its caption, which is already a separator. */
.b-way:first-child { margin-top: 6px; }

.b-way-n {
  font-family: var(--font-sans); font-size: var(--fs-2xl);
  font-weight: var(--fw-bold); letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums; line-height: var(--lh-none);
  color: var(--d50); padding-top: 2px;
}

.b-way-what { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* The name and what it is counted of, on one line. The denominator is not
   the figure: it reads with the name, in a quieter ink, so the count to the
   left stays the thing the eye lands on. */
.b-way-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
/* ══ A RANK DIFFERS FROM ITS NEIGHBOUR ON TWO AXES, NOT ONE ═══════════════
   At 18/600 this was one weight step from the block's own `<h2>` at 18/800
   in the same ink — a row name claiming the size of the section heading over
   it. 16/700 against the heading is size and weight, against the beat at
   16/400 is weight and ink, and against the 24px figure beside it is the
   pairing `.s-reason` already uses: 24 over 16, 700 over 400, two inks. */
.b-way-name {
  font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--d50);
  line-height: var(--lh-snug);
}
.b-way-of {
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  color: var(--d100); line-height: var(--lh-snug);
}
.b-way-sub {
  font-size: var(--fs-xs); color: var(--d200);
  line-height: var(--lh-base); max-width: 58ch; text-wrap: pretty;
}
/* ══ THE BEAT IS AiMY, SO IT IS `.b-aimy` ══════════════════════════════════
   A rule down the left of every beat would have been a stripe carrying
   nothing on three rows out of four, which is the decoration the doctrine
   names; a neutral card ground would have been a second way of drawing what
   `.b-aimy` already draws. These sentences are AiMY's read of the record —
   the same thing it says on a card, on the rail and on a lead block — so
   they are the same component, reused whole. All that is declared here is
   where it sits: `.b-aimy` carries a card's margins and a card's `flex`,
   and inside a row it wants neither. */
.b-way-beat.b-aimy { flex: 0 1 auto; margin: 8px 0 0; }
/* Tone tints the ground, never the figure. Amber where nobody ever agreed an
   answer, which is the one thing amber means on this block. */
.b-way.is-gap .b-way-beat.b-aimy { background: var(--warn-bg); }
.b-way-beat .b-doc, .b-way-beat .s-inline-btn { vertical-align: 1px; margin-left: 2px; }

/* What the group left out, and the way to the rest of it. `.s-sec-ask` is the
   whole control — the accent pill, the mark, the hover — so all this adds is
   where it sits and an ink at the type floor rather than the pill's default
   muted one. It is a door, so it does not read as a fifth row: a pill under a
   column of rows is plainly not another row. */
.b-way-rest.s-sec-ask {
  margin: 12px 0 0; font-weight: var(--fw-semibold); color: var(--d200);
}

@media (max-width: 719.98px) { /* 720 layout px */
  .b-way { grid-template-columns: 30px minmax(0, 1fr); column-gap: 12px; }
  .b-way-n { font-size: var(--fs-xl); }
}
/* `.b-way-thin` stood here. The sentence it styled — "too few to call a
   pattern" — is a statement about the block rather than a row in it, so it
   moved into the lead where this block's other statements about itself are
   made, and the rule had nothing left to style. */

/* ══ 56. THE BRIEF, IN THREE STEPS ═════════════════════════════════════════
   Nine label-and-value pairs at one size, one weight and one colour. A brief
   has three jobs and they are not equal: what you act on now, what shapes
   the words, and the campaign's material — identical on every call in it and
   therefore reference, not instruction.

   The steps are typographic, not boxed. One card inside a card inside the
   canvas is three borders around one idea; a size and a colour do it with
   none. */
.b-prep { display: flex; flex-direction: column; }
.b-prep-id {
  margin: -2px 0 10px; font-size: var(--t-ui); color: var(--d200);
  line-height: var(--lh-base); text-wrap: pretty;
}

/* 1. Where they stand and what is owed — the fact that decides what this
   call is for, on one line, at the top, where it is looked for. */
.b-prep-state { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin-bottom: 26px; }
/* TWO PILLS, ONE COLOUR, SIDE BY SIDE. .tag-warn and .b-prep-due.is-late
   render identically — both rgb(247,201,92) on a 13% wash inside a 32%
   border, same radius, 12px/700 against 14px/600. So the state chip and
   the overdue warning were the same object twice, and the one that is
   actually urgent had nothing to stand out from. Amber belongs to late.
   Scoped here rather than on .tag-warn itself, because on a queue card
   the state chip is the only amber thing and it works there. */
.b-prep-state .tag-warn {
  color: var(--ink-muted); background: transparent; border-color: var(--card-border);
}
.b-prep-owed { font-size: var(--t-ui); color: var(--d100); }
.b-prep-due {
  padding: 2px 9px; border-radius: var(--r-pill); white-space: nowrap;
  font-size: var(--t-small); font-weight: var(--fw-bold);
  color: var(--d100); background: var(--wash-3); border: 1px solid var(--card-border);
}
.b-prep-due.is-late { color: var(--warn-text); background: var(--warn-bg); border-color: color-mix(in srgb, var(--warn) 32%, transparent); }

/* 2. The one line here anybody says out loud, so it is set as speech. */
.b-open {
  margin: 0 0 26px; padding: 14px 16px;
  background: rgba(var(--accent-rgb), 0.07); border: 1px solid rgba(var(--accent-rgb), 0.18);
  border-radius: 14px;
}
.b-open-cap {
  display: block; margin-bottom: 6px;
  font-size: var(--t-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--accent-text);
}
.b-open-say {
  margin: 0; max-width: 58ch; text-wrap: pretty;
  font-size: var(--t-lead); font-weight: var(--fw-medium);
  line-height: var(--lh-base); color: var(--d50);
}

/* 3. What shapes those words. Body ink, because it is read; the label is the
   only bold thing on the line, as it is everywhere else in this build. */
.b-prep-know { display: flex; flex-direction: column; gap: 8px; }
.b-prep-line {
  margin: 0; font-size: var(--t-body); color: var(--d100);
  line-height: var(--lh-base); max-width: 64ch; text-wrap: pretty;
}
/* RANK B, and it has to sit ABOVE its value rather than beside it. The
   shared chip sets inline-block, which put this one on the same line as the
   sentence it labels. `display: table` is the one value that does both
   things at once: it shrink-wraps to the words like inline-block, and it
   forms a block box, so the value starts on the next line. */
.b-prep-line b { display: table; margin: 0 0 6px; }

/* 4. What arrives mid-call: which one they actually raise, then the script. */
.b-prep-most {
  margin: 0 0 12px; font-size: var(--t-small); color: var(--d200);
  line-height: var(--lh-base); max-width: 64ch; text-wrap: pretty;
}

/* 5. The shelf. A step down in size and ink says reference without a rule
   above it. It does now carry a box around its LABELS — see rank C above —
   because a rank that opts out of the family stops reading as the bottom of
   it and starts reading as unstyled. The block itself still takes no rule,
   no border and no heading, which is the part this note was really about. */
/* Each entry is a label over a value now rather than one line, so the gap
   BETWEEN entries has to beat the 4px gap inside one or the six lines read
   as one list. 12 against 4 is the same 3:1 the rest of the panel uses. */
.b-prep-ref { display: flex; flex-direction: column; gap: 12px; margin-top: 26px; }
/* .b-prep is a flex column, so adjacent margins do NOT collapse. .b-back
   already carries 26px below it and this adds 26 above, which measured 50px
   in place — making the shelf, the least important block on the panel, the
   most separated thing on it. The pair is spaced once. */
.b-back + .b-prep-ref { margin-top: 0; }
.b-prep-refp {
  margin: 0; font-size: var(--t-small); color: var(--ink-muted);
  line-height: var(--lh-base); max-width: 66ch; text-wrap: pretty;
}
/* RANK C, AND IT IS IN THE FAMILY AFTER ALL ════════════════════
   This was left as plain bold on the argument below that the shelf earns
   its step down without a box around it. Two things were wrong with that.

   It made the QUIETEST rank the largest label on the panel: 14px, inherited
   from .b-prep-refp, against 12px for the two ranks above it. A ladder that
   runs 12 / 12 / 14 downward is not a ladder.

   And "no treatment" is not the bottom of a scale, it is the absence of
   one. A rank reads as ranked when it is the same KIND of thing as its
   neighbours, held quieter — not when it opts out. So the shelf takes the
   softest member of the chip family instead of leaving it:

     A  filled + bordered + CAPS   the loudest edge there is here
     B  bordered, no fill          a crisp edge, no weight behind it
     C  filled, no border          weight without an edge: the quietest
                                   thing that is still an object

   A border is a hard boundary and reads stronger than a diffuse wash, which
   is what puts C under B while keeping both in the same family. The fill is
   5% against A's 8%, and the padding is 1px against 2px, so it sits inside
   the line rather than interrupting it. */
.b-prep-refp b {
  /* Above its value, not beside it — the same as every other label on this
     panel. `display: table` is what does both jobs at once: it shrink-wraps
     to the words the way inline-block does, and it forms a block box, so
     the value starts on the next line. inline-block alone would keep the
     chip in the flow and the sentence would run on beside it. */
  display: table;
  padding: 1px 8px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--ink-primary) 5%, transparent);
  font-size: var(--t-micro); font-weight: var(--fw-semibold);
  line-height: var(--lh-snug); color: var(--ink-muted);
  margin: 0 0 4px;
}
.b-prep-refp .b-docs { display: inline-flex; vertical-align: -2px; gap: 10px; }
/* ── A CHIP THAT WRAPS IS NOT A CHIP ANY MORE ──────────────────────
   §48 sets 3px of vertical padding, which is the right amount for what a
   doc chip is everywhere else: one short line inside a pill, sitting in a
   sentence. In the brief it is neither. A document's real name runs past the
   panel's 329px column, so the chip takes the full width and the name wraps —
   and 3px above and below 36px of text is two lines pressed against the top
   and bottom of their own pill.

   The padding is set against the BLOCK here rather than against the line:
   half a line of air top and bottom, 14px at the sides so the first and last
   lines clear the stadium's curve, and 10px between the chips so two of them
   stacked read as two doors and not as a four-line paragraph. Scoped to the
   brief's two document lists — "You can send" and "On paper"; the chip in a
   beat's sentence stays the size of the words around it. */
.b-prep-refp .b-doc { padding: 9px 14px; gap: 10px; }

/* ══ 57. WHERE THE NUMBERS COME FROM ═══════════════════════════════════════
   Three chips with one lit, beside a sentence naming the lit one — a control
   for picking a supplier on a page where nobody picks a supplier. It is a
   listing now: what each of them fills, and which of them has stopped
   answering. Under the sentence it explains, left of the button, because it
   is that sentence's working and not another thing to press. */
.b-src-side { flex: 1 1 26ch; min-width: 0; display: flex; flex-direction: column; gap: 24px; }
.b-src-side > .s-build-total { flex: none; }
/* ONE RHYTHM DOWN THE BLOCK. The caption sat 2px off its list while the
   rows sat 5px apart, and what each supplier actually did was the palest
   thing in it. Every row is one step apart, and the sentence is read at the
   ink everything else on this page is read at. */
.b-srcs { display: flex; flex-direction: column; gap: 8px; }
.b-srcs-cap {
  margin-bottom: 4px;
  font-size: var(--t-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--d200);
}
.b-src { display: flex; align-items: baseline; gap: 10px; font-size: var(--t-body); }
.b-src .b-rstate-dot { align-self: center; }
.b-src-n { font-weight: var(--fw-bold); color: var(--d50); min-width: 104px; }
.b-src-v { color: var(--d100); }
/* A supplier that is not answering is a fact about today, not a fault of
   yours — stated in the warning tone and left in the list, because a source
   removed from the list is a source you stop expecting back. */
.b-src.is-off .b-src-v { color: var(--warn-text); }

/* ══ 58. THE WAY INTO THE BAR ══════════════════════════════════════════════
   The empty state told you where to type and left you to remember the shape
   of the sentence. The line and the press that fills it sit together, so the
   instruction and the way to follow it are one thing. */
.b-empty { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }
.b-empty .s-said { margin: 0; }

/* ══ 59. WHAT TO EXPECT ════════════════════════════════════════════════════
   Three rows on one grid so the estimates line up and can be read down the
   middle column without reading the reasons. The reason is at the small step
   because it is why the estimate is a guess, and a guess whose working is
   hidden is a count wearing a hedge. */
/* One sentence under one caption. It was a two-row grid of labels and
   paragraphs to say the two things a caller reads in a second. */
.b-expect { display: flex; flex-direction: column; }
.b-exp-say {
  margin: 0; font-size: var(--t-body); color: var(--d100);
  line-height: var(--lh-base); max-width: 68ch; text-wrap: pretty;
}
.b-exp-say b { color: var(--d50); font-weight: var(--fw-semibold); }

/* A rule above the foot is the section separator this build took out
   everywhere else; the space under the block above it says the same thing. */
.s-build-foot { border-top: 0; padding-top: 0; margin-top: 26px; }

/* ══ 60. THE RUN FITS THE SCREEN ═══════════════════════════════════════════
   Four stage tiles, four log rows and a foot, on a card that grew past the
   bottom of a short viewport — and a loading state you have to scroll is a
   loading state that has stopped telling you anything. The card is capped at
   the height it has, and the log scrolls inside it, which is the part that
   grows. svh rather than vh, so a mobile browser's own bar is counted. */
.pipe-card { max-height: calc(var(--vp-h) - 190px); display: flex; flex-direction: column; }
/* Nothing is pressed here any more, so the foot is one line of elapsed. */
.pipe-foot { padding: 12px 8px 2px; }
@media (max-height: 611.98px) { /* 720 layout px tall */
  .pipe-card { max-height: calc(var(--vp-h) - 150px); }
}

/* A tick in a menu row: on for what is chosen, a held place for what is not,
   so the rows do not shift as you pick. */
.b-menu-tick {
  width: 14px; height: 14px; flex: none; border-radius: 5px;
  border: 1px solid var(--card-border-hover); background: var(--card-bg);
  position: relative;
}
.b-menu-tick.is-on { background: var(--accent); border-color: var(--accent); }
.b-menu-tick.is-on::after {
  content: ""; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(42deg);
}

/* The run is started from the top of the page it is the point of, so the
   masthead carries it on the right the way every other masthead here does. */
.b-sheet-act { margin-left: auto; flex: none; align-self: center; }
.b-sheet-act .s-build-go[disabled] { opacity: 0.45; cursor: not-allowed; }

/* The primary opens a menu, so the menu hangs off a filled button rather
   than a quiet one; it is the same wrapper either way. */
.b-menu-wrap .s-build-go { white-space: nowrap; }

/* A menu that searches. The box is the chooser's own, narrowed to the menu's
   width and set between the caption and the rows it filters. */
.b-menu-find { margin: 2px 0 6px; padding: 7px 10px; font-size: var(--t-ui); }
.b-menu-item[hidden] { display: none !important; }
/* The one action on a surface goes at the end of its row, where "Call these
   15" is on the queue and every other surface's action is. */
.b-acts-end { justify-content: flex-end; }

/* ══ 61. A MENU IS FIVE ROWS AND A SCROLLBAR ═══════════════════════════════
   Fourteen campaigns ran a menu off the bottom of the screen and under the
   composer, so the last of them could not be reached at all. Five rows and
   then it scrolls; the caption and the search box stay put, because a search
   that scrolls away is a search you cannot correct. */
.b-menu { max-height: 328px; overflow-y: auto; overscroll-behavior: contain; }
.b-menu > .b-menu-cap:first-child, .b-menu > .b-menu-find {
  position: sticky; z-index: 1; background: var(--card-bg-raised);
}
.b-menu > .b-menu-cap:first-child { top: 0; }
.b-menu > .b-menu-find { top: 22px; }
/* ══ AND 22px IS THE HEIGHT OF A CAPTION THAT MAY NOT BE THERE ════════════
   The offset above is the caption's, because every menu with a search had
   one over it. The crew menu does not — the button that opens it and the
   box's own placeholder already say what the list is — so the box stuck 22
   pixels down from the top of a menu it was the first thing in, with a dead
   strip above it for rows to scroll into and the first name pressed hard
   against its underside.

   First child, no phantom. The margin goes with it: `2px 0 6px` was spacing
   against the caption's own 6px of padding-bottom, and with nothing above it
   the box wants the menu's padding and no more. */
.b-menu > .b-menu-find:first-child { top: 0; margin-top: 0; margin-bottom: 8px; }

/* ══ 62. EVERY PRESSABLE ANSWERS THE HAND ══════════════════════════════════
   Twenty kinds of control here pressed down under the cursor and seven did
   not: the two ways into a list, a suggestion's door, a chat's opener, a
   criterion chip, a filter chip's call. A button that does not move under a
   press is a button that might not have heard you, and the second press is
   the one that runs twice. Same 160ms and the same curve as the rest; 0.97
   for a button, 0.985 for a card, because a card is a bigger thing to move.

   THE CHAT'S OPENER IS NO LONGER ONE OF THEM. It was `.b-chat-item`, and the
   column that held it is now AiMY Knowledge's, lifted whole — where the row
   transitions its ground and its ink and does not press. Listing it here
   would override that transition from a later file and put a transform on a
   control whose own stylesheet says it has none, which is exactly the kind
   of quiet divergence a whole-component lift exists to refuse. */
.s-way, .s-start, .s-finding-go, .chip {
  transition: transform 160ms var(--ease-out), border-color var(--t-fast),
    background-color var(--t-fast), color var(--t-fast);
}
.s-finding-go:active, .chip:active { transform: scale(0.97); }
.s-way:active, .s-start:active { transform: scale(0.985); }
.chip:focus-visible, .filter-chip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* The find box focuses the way the menus' search does — a call, not only a
   changed border — so the two boxes that do the same job look the same lit. */
.b-find:focus-within { box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18); }
.b-find { transition: border-color var(--t-fast), box-shadow var(--t-fast); }
/* A row that opens says so to the cursor, like everything else that opens. */
.s-call { transition: border-color var(--t-fast); }
.s-call:hover { border-color: var(--card-border-hover); }
@media (prefers-reduced-motion: reduce) {
  .s-way, .s-start, .s-finding-go, .chip { transition: none; }
  .s-finding-go:active, .chip:active, .s-way:active, .s-start:active { transform: none; }
}

/* ══ 62b. THE ONES THE HAND STILL GOT NOTHING FROM ════════════════════════
   §62 covered this build's own controls and sales.css §21 covered the
   shell's, and between them eight pressables fell down the gap. Measured
   against every :active rule that actually applies: sixty-eight clickables
   on the caller's desk, and these were the ones no rule reached.

   THE BELL PRESSES AND THE RAIL TOGGLE BESIDE IT DOES NOT. One rule in
   sales.css styles both their hovers on the same line; only the bell is in
   the press list. The same is true of the chats toggle, the prototype
   toggle, the mic and the version pill — shell chrome whose neighbours
   answer the hand and which does not. That is not a component with its own
   opinion about pressing, the way the chat row lifted from Knowledge is; it
   is a control that was missed, and the cure is the shell's own value at the
   shell's own speed so the two feel identical rather than merely similar.

   0.97 IS THE SHELL'S NUMBER, NOT A DERIVED ONE. Working back from the
   rules already here, a press moves an edge about 1.1px, which at 29px
   would ask for 0.93 — and the bell, at 29px, presses at 0.97 like
   everything else. A control that sits next to the bell should feel like
   the bell, so the convention wins over the arithmetic. The two genuinely
   argued exceptions in this file stay as they are: the ring's buttons at
   0.94 and the diary's at 0.96.

   THREE THAT LOOK BARE AND MUST STAY BARE. A queue card's title reports no
   press rule of its own, and needs none: :active matches ancestors, so
   pressing the title already presses the card around it, and giving the
   title its own transform would scale it inside a card scaling with it.
   `.prod-chev` carries transform: rotate(180deg) while its menu is open —
   a press scale on the same property would throw the arrow's rotation away
   at the moment it is most meaningful. And `.slv-n` is a figure set in the
   middle of the rail's sentence: it is a button, but scaling it moves type
   inside a line of prose, and a word that jumps while it is being read is
   worse than a word that does not answer at all.

   `.rail-console` belongs to this set and is not in the rule below it. Its
   own rule sits further down the file, and a transition written here would
   be thrown away by the one there, so its press is declared at that rule. */
.b-menu-item, .b-peek-x, .voice-btn,
.rail-toggle, .ov-chats-toggle, .proto-toggle, .ver-link {
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.b-menu-item:active, .b-peek-x:active, .voice-btn:active,
.rail-toggle:active, .ov-chats-toggle:active, .proto-toggle:active,
.ver-link:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
  .b-menu-item, .b-peek-x, .voice-btn,
  .rail-toggle, .ov-chats-toggle, .proto-toggle, .ver-link { transition: none; }
  .b-menu-item:active, .b-peek-x:active, .voice-btn:active,
  .rail-toggle:active, .ov-chats-toggle:active, .proto-toggle:active,
  .ver-link:active { transform: none; }
}

/* ══ 63. ONE SHADOW FOR WHAT FLOATS ════════════════════════════════════════
   Four surfaces sit above the page — a menu, a toast, the bell's panel, the
   run's card — and each carried its own recipe: 12/32 at .42, 18/40 at .5,
   the library's xl, the library's lg. One token, layered the way a lifted
   surface actually shades: a hairline where it meets the air, a short close
   shadow and a long soft one, and on a light ground the same shape in the
   page's own ink. Flat cards stay flat; on a dark ground a border says
   structure and a shadow says lift, and the queue is structure. */
:root {
  --float-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(255, 255, 255, 0.04),
    0 2px 6px -2px rgba(0, 0, 0, 0.5), 0 16px 40px -12px rgba(0, 0, 0, 0.6);
}
:root[data-theme="light"] {
  --float-shadow: 0 0 0 1px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(16, 24, 40, 0.12),
    0 16px 40px -12px rgba(16, 24, 40, 0.2);
}
.b-menu, .aimy-toast, .ntf-panel, .pipe-card { box-shadow: var(--float-shadow); }

/* CONCENTRIC. Outer radius is inner radius plus the padding between them,
   or the corners of a nested surface visibly disagree with the corners of
   the one holding it — the most common thing that makes a surface feel
   slightly wrong without saying why. The menu is 8px rows inside 6px of
   padding, so 14. The run's panels sit 14px inside a 20px card, so 8. A
   card's reading sits 14px inside a 12px card, so it is the smallest step. */
.b-menu { border-radius: 14px; }
.b-aimy { border-radius: var(--r-sm); }

/* A photograph's edge takes its call from the ground it sits on: white at a
   tenth on a dark page, black at a tenth on a light one. A white call on a
   light page is a call that is not there. */
:root[data-theme="light"] .b-face::after { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }

/* ══ 64. FIGURES LINE UP ═══════════════════════════════════════════════════
   Every number a caller compares by eye — a lead figure, a tile, a count
   after a bold — is set in tabular figures, so a 1 takes the room of a 7
   and two of them stacked read as a column. */
.s-lead-n, .s-lead-of, .s-af-val, .b-way-n, .b-exp-say b, .b-tocall b, .b-way-lead b,
.s-block-say b, .b-vfoot b, .b-branch-n, .b-src-v, .pipe-elapsed, .b-menu-sub {
  font-variant-numeric: tabular-nums;
}

/* ══ 65. A NEW SURFACE ARRIVES ═════════════════════════════════════════════
   Opacity and six pixels of rise, 200ms on the ease-out everything else here
   enters on. No blur: the toast blurs in because it is small; a whole surface
   through a filter is a repaint of everything on it on every frame. The class
   is put on the stage by go() when the surface changed and by nothing else,
   so a write's repaint and a page of the queue turning stay still. */
@keyframes b-surface-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
#wbStage.is-arriving > .s-home { animation: b-surface-in 200ms var(--ease-out) both; }
@media (prefers-reduced-motion: reduce) { #wbStage.is-arriving > .s-home { animation: none; } }

/* ══ 66. CARDS SETTLE IN ORDER, ONCE ═══════════════════════════════════════
   Under the same arrival the surface makes, the cards of a queue come in
   one after another, 30ms apart — the list reads as a list arriving rather
   than a block appearing, and the order the cards settle in is the order
   they are ranked in. Capped at the eighth: fifteen steps would hold the
   last card a quarter of a second, which is a wait, not a rhythm. Nothing
   here runs on a repaint or a page of the queue turning, because the class
   that starts it is only put on for a change of surface. */
/* ══ ONE BOX, TWO STATES ═══════════════════════════════════════════════════
   The slot is what the grid lays out now, and the card sits inside it. It is
   a grid rather than a block on purpose: `align-items: stretch` still sizes
   the row to its tallest card and still stretches every card to match, which
   is what §1 established, and the card is never given a height of its own —
   the bug that clipped a three-line AiMY note stays fixed.

   The skeleton is pinned to the slot, so it is the card's own rectangle to
   the pixel with nothing measured to make it so. That is the whole reason
   for the wrapper: a placeholder that is a different size from the thing it
   stands in for announces itself when they trade places. */
.b-slot { position: relative; display: grid; }
.b-skel { position: absolute; inset: 0; z-index: 2; margin: 0; pointer-events: none; }
/* ══ THE SKELETON LEAVES WHILE THE CARDS ARRIVE ════════════════════════════
   Not after them. Four handoffs were compared and this is the one that was
   chosen: the placeholder is still going as the first cards are already
   coming, so there is never a frame with nothing on the surface — which is
   the same argument §29 makes about two words passing each other, at the
   size of a page.

   It fades on --t-fast against the cards' --t-settle, because the thing
   leaving should be gone before the thing arriving has finished; a skeleton
   still legible under a settled card is a placeholder that outstayed it. */
.b-skel { opacity: 1; transition: opacity var(--t-fast) var(--ease-out); }
#wbStage[data-load="done"] .b-skel { opacity: 0; }
/* ══ AND THE CARDS RESOLVE INTO IT ═════════════════════════════════════════
   The card holds still and arrives in place: six pixels up, through four of
   blur, 30ms apart and capped at eight so the last one is a rhythm behind
   the first rather than merely late. Blur is a paint, which this build spends
   carefully — but this runs once per surface on cards that are already being
   composited for the rise, and it is what turns a card appearing into a card
   resolving. The keyframe lands on `filter: none` rather than interpolating
   toward it, so nothing is left holding a composited layer and grey-edged
   text for the life of the page.

   NONE OF THIS RUNS ON A REPAINT. `data-load` is only written by arrive(),
   which only runs when the surface key changed — so a write and a page of
   the queue turning draw their cards plain, exactly as before. */
#wbStage[data-load] .b-qcard { opacity: 0; }
/* ══ THE BLUR IS ITS OWN ANIMATION, AND IT DOES NOT FILL ═══════════════════
   Writing `filter: none` in the last frame is not the same as ending with no
   filter. To interpolate from blur(4px) the browser reads that `none` as
   blur(0px), and a `forwards` fill then holds the INTERPOLATED value — so
   every card finished the arrival still carrying blur(0px). Measured here
   before it was split: the computed filter on a settled card was blur(0px),
   not none.

   A zero blur is not free. It keeps the card on its own composited layer and
   its text on grey antialiasing for as long as the card lives, which on this
   surface is fifteen cards held that way for the life of the page — for an
   effect that finished in a fifth of a second.

   So the rise and the blur are two animations. The rise fills both ways,
   because the card has to be down and invisible through its stagger delay
   and settled after it. The blur fills neither way, so the moment it is over
   the card falls back to the filter its own rule gives it, which is none at
   all. Knowledge's entry animation is split for the same reason and says so
   at knowledge.css:1270. */
#wbStage[data-load="done"] .b-qcard {
  animation: b-card-in var(--t-settle) var(--ease-out) both,
             b-card-blur var(--t-settle) var(--ease-out);
  animation-delay: calc(min(var(--i, 0), 8) * 30ms);
}
@keyframes b-card-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
/* No `to`, on purpose: the end is whatever the card already is. */
@keyframes b-card-blur {
  from { filter: blur(var(--blur-arrive)); }
}
/* Frozen, not removed. The skeleton is a state rather than a motion, so it
   still stands and still hands over — it simply does not fade, and the cards
   are there rather than arriving. */
@media (prefers-reduced-motion: reduce) {
  #wbStage[data-load="done"] .b-qcard { animation: none; opacity: 1; }
  .b-skel { transition: none; }
}

/* OPTICAL, AND THE OPTICS CHANGED UNDER IT. This pulled the mark up 2px
   because the chip was the first line and the mark sat three pixels below
   its middle. The mark lives INSIDE the chip now, so the tile only ever
   renders when there is no chip at all — and then the sentence is the first
   line again, which is the case this correction was written to undo.

   Measured with it still on: the tile centred 3.28px ABOVE the first line.
   Without it the base margin of 1px lands within 0.3px of that line’s middle,
   which is where an icon labelling a paragraph belongs — on the first line,
   not floating between two of them. */

/* The two lines in the call panel set in chrome-grey — "Not recording" and
   the THEM/YOU label over each line — are read, not decoration, and read
   at the ink the panel's other words are read at. Them stays a step under
   you, which is a hierarchy and not a fade. */
.call-none { color: var(--ink-muted); }
.call-who { color: var(--ink-muted); }

/* The way back and the way out at opposite edges of one line. The back
   button carries its own bottom margin for the pages that use it bare; in
   here the row owns the spacing. */
.b-topbar { display: flex; align-items: center; gap: 12px; }
.b-topbar .s-back { margin-bottom: 0; }
/* The other edge. `.b-end` is the same two declarations and is spoken for —
   it is the gate that ends a lead, not a position — so the record's own door
   carries them under its own name rather than borrowing a word that means
   something else three sections away. */
.b-rec-edit { margin-left: auto; flex: none; }

/* CENTRED ON THE LETTERS, WHICH HERE IS THE SAME AS CENTRED ON THE BOX.
   Measured rather than nudged: in Urbanist at this size and weight the cap
   band centre and the em box centre both sit 4.5px above the baseline, so
   they coincide and a hand correction would only move it off.

   That measurement was taken in Urbanist, which this build no longer sets.
   It has NOT been re-taken in Poppins, so treat the coincidence as unproven
   rather than as a reason not to correct by hand. The rest of this note is
   face-independent and still holds. What the box
   does need is its leading taken away — the line box is fifteen pixels of
   font metrics around thirteen of type, and centring the mark against that
   is centring it against air. */
.b-end-word { display: inline-block; line-height: 1; }

/* ══ 67. WHAT HAPPENED IS A QUESTION, NOT A VERB ═══════════════════════════
   The steps stood in the row of verbs at the same weight as Call, and which
   of them led swapped the moment a meeting date passed. They are a different
   kind of thing — a fact to record rather than an action to take — so they
   sit under the row on a line of their own, behind the question they answer.
   Baseline-aligned to the answers, because a label and the words it governs
   sit on one line or the eye reads them as two rows. */
.b-ask {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 14px;
  margin-top: 12px;
}
.b-ask-cap {
  font-size: var(--t-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-muted); margin-right: 2px;
}

/* ══ 68. THE DESK YOU ARE AT, IN A MENU THAT SWITCHES ══════════════════════
   The people menu wore the tick the multi-select assign menu wears, which
   says "choose as many as you like" about a control where you are exactly
   one person. Lit like the switcher's current tab instead: the ground says
   where you are, and the hover still says where you could go. */
.b-menu-item.is-on {
  background: rgba(var(--accent-rgb), 0.16);
  color: var(--d50);
}
.b-menu-item.is-on:hover { background: rgba(var(--accent-rgb), 0.22); }

/* ══ 69. THE BOARD ═════════════════════════════════════════════════════════
   Gone. Seven columns each scrolling inside itself, each with a card
   component of its own, and what the whole apparatus bought was the stage
   — which is a filter chip on every other surface in this build. The
   manager's deals are a grid of cards over a row of cuts now, which is
   what the caller's queue has always been, and one card draws both.

   The number stays with the gap in it. A section that was here and is not
   is worth more in the sequence than a renumbering that makes the file
   look as though it never had a board. */

/* ══ 70. THE DIARY ═════════════════════════════════════════════════════════
   A month of dots over one day in full. The reference this is built from is
   a light-only component with its own type scale, its own greys and a
   four-layer drop shadow; what is kept is the SHAPE — a panel inside a card,
   a 7×6 grid of pressable days each carrying up to three dots, a hairline,
   then the day's agenda — and everything that made it look like somebody
   else's product is replaced with ours.

   The one colour decision: five kinds of thing land in a diary and the dot
   is the only place they are told apart, so this is the one surface in the
   build that spends five hues. They are the tones the product already owns —
   the same five that tag anything else — rather than a new palette. */
.b-cal { margin-top: 16px; max-width: 720px; }
/* Everything from here to the end of this block belongs to the diary page.
   The stacked month at 720 sat in a 1000px column with 280 of it empty, and
   filling that by stretching the same seven cells only made a date and three
   dots adrift in a wider box: 140 by 54 is not a day, it is a row. The width
   goes to a SECOND COLUMN instead — the day beside the month rather than
   under it — which is the shape the pop-out had and the reason it read well.
   Capped, because at 1440 uncapped the cells stretch again. */
/* Inside the card now, so it is separated from the month the way the month
   is separated from the day beside it: an inset rule, not an edge-to-edge
   one — this build draws the dividers between the parts of a panel inside
   the panel's own padding. */
/* `wrap` and `min-width: 0` are both inert until the row cannot hold the
   month and its controls on one line, which at this type size is a 376px
   layout — a 320px phone. Measured there: 120 of month plus 150 of nav plus
   the gap is 280 in a 278px box, three pixels of a panel hanging over its own
   card. Nothing above that width moves, because nothing above it overflows. */
.b-cal-head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px; padding: 2px 2px 12px; margin-bottom: 10px;
  border-bottom: 1px solid var(--card-border);
}
.b-cal-month { min-width: 0; }
.b-cal-month {
  display: flex; align-items: center; gap: 10px; margin: 0;
  font-size: var(--t-h3); font-weight: var(--fw-bold); color: var(--d50);
  letter-spacing: -0.02em;
}
.b-cal-count {
  min-width: 24px; text-align: center; padding: 3px 8px; border-radius: var(--r-pill);
  font-size: var(--t-micro); font-weight: var(--fw-semibold); color: var(--d200);
  background: var(--card-bg); border: 1px solid var(--card-border);
  font-variant-numeric: tabular-nums;
}
.b-cal-nav { display: flex; align-items: center; gap: 8px; }
.b-cal-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex: none;
  border-radius: var(--r-pill); cursor: pointer;
  color: var(--d200); background: none; border: 1px solid var(--card-border);
  font-family: inherit; font-size: var(--t-ui); font-weight: var(--fw-semibold);
  transition: color var(--t-fast), border-color var(--t-fast),
    background-color var(--t-fast), transform 160ms var(--ease-out);
}
.b-cal-btn.is-word { width: auto; padding: 0 14px; }
.b-cal-btn svg { width: 16px; height: 16px; }
.b-cal-btn:hover { color: var(--d50); border-color: var(--card-border-hover); }
.b-cal-btn:active { transform: scale(0.96); }
.b-cal-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.b-cal-panel {
  padding: 8px; border-radius: var(--r-lg);
  background: var(--card-bg); border: 1px solid var(--card-border);
}
.b-cal-week { display: grid; grid-template-columns: repeat(7, 1fr); padding: 2px 0 4px; }
.b-cal-wd {
  text-align: center; font-size: var(--t-micro); font-weight: var(--fw-semibold);
  color: var(--d200);
}
.b-cal-wd.is-end { color: var(--ink-muted); }
.b-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }

/* A day is a stack: the ground that lights, the number, and the dots. The
   ground is a child rather than the button's own background so it can scale
   from 0.9 without taking the number with it. */
.b-cal-day {
  position: relative; height: 54px; padding: 0; border: 0; background: none;
  cursor: pointer; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 5px; border-radius: var(--r-md);
  font-family: inherit;
}
.b-cal-day:focus { outline: none; }
.b-cal-bg {
  position: absolute; inset: 2px 4px; border-radius: var(--r-md);
  opacity: 0; transform: scale(0.9);
  background: rgba(var(--accent-rgb), 0.12);
  transition: opacity 160ms var(--ease-out), transform 200ms var(--ease-out);
}
.b-cal-day:hover .b-cal-bg { opacity: 1; transform: none; }
.b-cal-day.is-sel .b-cal-bg,
.b-cal-day.is-sel:hover .b-cal-bg {
  opacity: 1; transform: none; background: rgba(var(--accent-rgb), 0.22);
}
.b-cal-day:focus-visible .b-cal-bg {
  opacity: 1; transform: none; background: none;
  box-shadow: inset 0 0 0 2px var(--brand);
}
.b-cal-num {
  position: relative; font-size: var(--t-ui); font-weight: var(--fw-medium);
  color: var(--d100); font-variant-numeric: tabular-nums;
}
.b-cal-day.is-out .b-cal-num { color: var(--ink-muted); }
.b-cal-day.is-end .b-cal-num { color: var(--d200); }
.b-cal-day.is-sel .b-cal-num { color: var(--d50); font-weight: var(--fw-bold); }
/* Today is stated in a call rather than a fill, because the fill means "the
   day you are reading" and two fills would be two answers to one question. */
.b-cal-day.is-today .b-cal-num::after {
  content: ""; position: absolute; left: 50%; bottom: -7px; width: 3px; height: 3px;
  margin-left: -1.5px; border-radius: var(--r-pill); background: var(--accent);
}
.b-cal-day.is-today.is-sel .b-cal-num::after { display: none; }
.b-cal-dots { position: relative; display: flex; gap: 3px; height: 5px; align-items: center; }
.b-cal-dot { width: 5px; height: 5px; border-radius: var(--r-pill); flex: none; }
.b-cal-day.is-out .b-cal-dot { opacity: 0.4; }
/* The kinds come back to the month, at the label family's chroma. Five hues
   on five dots was a legend nobody was given while they were a purple, a
   cyan and an amber at full strength; at a quarter of that they are what a
   dot is for — which of the five, without reading. Owed keeps the neutral
   because it is the absence of a kind. */
.k-meeting { background: var(--accent-label); }
.k-demo { background: var(--info); }
.k-dinner { background: var(--warn); }
.k-held { background: var(--ok); }
.k-owed { background: var(--d400); }

.b-cal-cap {
  margin: 0; padding: 16px 8px 8px;
  font-size: var(--t-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-muted);
}
.b-cal-agenda { display: flex; flex-direction: column; }
.b-cal-ev {
  display: flex; flex-direction: column; align-items: stretch; gap: 3px;
  width: 100%; text-align: left;
  padding: 9px 8px; border: 0; border-radius: var(--r-md);
  background: none; cursor: pointer; font-family: inherit;
  transition: background-color var(--t-fast);
  animation: b-surface-in var(--t-settle) var(--ease-out) both;
  animation-delay: calc(min(var(--i, 0), 8) * 40ms);
}
.b-cal-ev:hover { background: rgba(var(--accent-rgb), 0.10); }
.b-cal-ev:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-cal-ev .b-cal-dot { width: 8px; height: 8px; }
/* The hour and the kind of thing it is, together as one mark. */
.b-cal-evtop { display: flex; align-items: center; gap: 8px; }
.b-cal-evtop .b-kind { flex: none; }

/* ══ WHAT KIND OF THING THIS IS ════════════════════════════════════════════
   A tag says where a record STANDS. Five other things were wearing one — a
   meeting's kind, an offering's kind, a campaign's clock, a list's
   attachment, a tally — so the component meant five things and a reader
   could not learn it once.

   The four that are not states land here. It is a word, so it takes none of
   the three treatments the system spends: a label has a fill, a control has
   an edge, a link has neither and is coloured. This has none of them — the
   minimal-component size, the ambient step, and no ground at all, which is
   what leaves the tag beside it meaning something. */
.b-kind {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  color: var(--ink-faint); white-space: nowrap;
}

/* ══ WHAT AN ACCOUNT IS WORTH TO US ══════════════════════════
   A medal, which is the one place in this build where colour is allowed to
   be the name of something. `ok` and `err` are a verdict on a thing that
   happened; gold against silver is a verdict on nothing — it is what the
   team already calls these accounts out loud, and a reader told an account
   is gold and shown a grey mark has two facts to reconcile.

   Bench is the shield with no fill: not a third metal, but the one that is
   not on the field. That also leaves the three differing in something other
   than hue, which is what holds them apart for a reader who cannot separate
   gold from silver. */
.b-tier {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-2xs); white-space: nowrap;
}
.b-tier-shield {
  /* Height only: the viewBox carries the aspect, so one number sets the
     mark and the box can never disagree with the shape inside it. */
  height: 16px; width: auto; flex: none;
  filter: drop-shadow(0 1px 1.5px var(--tier-cast));
}

/* The metals. Each is three values rather than one — the face, a rim a
   shade under it, and the cast the badge throws — and each pair is picked
   against its own ground: a gold that reads on a dark page is a pale one,
   and that same value on white is a yellow nobody would call gold. */
:root {
  --tier-gold: #d9a533;   --tier-gold-rim: #8f6a11;
  --tier-silver: #aab6c3; --tier-silver-rim: #6b7783;
  --tier-cast: rgba(0, 0, 0, 0.55);
  --tier-lit: rgba(255, 255, 255, 0.34);
}
:root[data-theme="light"] {
  --tier-gold: #c08f16;   --tier-gold-rim: #7d5c07;
  --tier-silver: #939fab; --tier-silver-rim: #5d6873;
  --tier-cast: rgba(16, 24, 40, 0.3);
  --tier-lit: rgba(255, 255, 255, 0.42);
}

/* `paint-order` lays the fill down before the stroke, so the rim straddles
   the outline and only its inner half survives. That is half a pixel of
   dark all the way round, which is a bevel; without it the badge is a flat
   shape with a shadow underneath. */
.b-tier-face { stroke-width: 1; paint-order: fill stroke; stroke-linejoin: round; }
.b-tier.is-gold .b-tier-face { fill: var(--tier-gold); stroke: var(--tier-gold-rim); }
.b-tier.is-silver .b-tier-face { fill: var(--tier-silver); stroke: var(--tier-silver-rim); }

/* One light source, upper left, where every struck thing a person has held
   has its light. The point is not that anybody notices the highlight — it
   is that without one the shape has no thickness. */
.b-tier-lit { fill: var(--tier-lit); stroke: none; }

/* Bench keeps none of it. It is not a third metal but the one that is not
   on the field, so it takes the outline, no wash and no lift, and stays
   the quietest thing in a column of fifteen. */
.b-tier.is-bench .b-tier-shield { filter: none; }
.b-tier.is-bench .b-tier-face { fill: none; stroke: var(--ink-rule); stroke-width: 2; }
.b-tier.is-bench .b-tier-lit { fill: none; }

/* The word keeps the ink and weight steps it always had. Only the shield
   is coloured: gold TEXT at twelve pixels is a contrast problem on both
   grounds, and the hierarchy on the line it lands in is the page's. */
.b-tier.is-gold { color: var(--ink-primary); font-weight: var(--fw-bold); }
.b-tier.is-silver { color: var(--ink-secondary); font-weight: var(--fw-semibold); }
.b-tier.is-bench { color: var(--ink-quiet); font-weight: var(--fw-medium); }

/* The name's own row, on a card and on a record, with the mark hard against
   the right so a column of them can be scanned straight down. The card's
   padding lives on its children, so the wrapper carries the right inset
   the title already had on its left. */
.b-qcard-top { display: flex; align-items: baseline; gap: 8px; padding-right: 14px; }
.b-qcard-top .b-tier,
.s-rec-title .b-tier { margin-left: auto; }

.b-cal-etime {
  flex: none; font-size: var(--t-small); font-weight: var(--fw-semibold);
  color: var(--d200); font-variant-numeric: tabular-nums;
}
.b-cal-ename {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
  font-size: var(--t-ui); font-weight: var(--fw-semibold); color: var(--d50);
}
.b-cal-ewhat { font-size: var(--t-small); font-weight: var(--fw-medium); color: var(--d200); }
.b-cal-ev .tag { flex: none; }
.b-cal-none { margin: 0; padding: 4px 8px 8px; font-size: var(--t-body); color: var(--d200); }

.b-cal-add {
  display: flex; align-items: center; gap: 11px; width: 100%; margin-top: 4px;
  padding: 9px 8px; border: 0; background: none; border-radius: var(--r-md);
  cursor: pointer; font-family: inherit;
  font-size: var(--t-ui); font-weight: var(--fw-medium); color: var(--d200);
  transition: background-color var(--t-fast), color var(--t-fast);
}
.b-cal-add:hover { background: rgba(var(--accent-rgb), 0.10); color: var(--d50); }
.b-cal-add:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-cal-plus {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 24px; height: 24px; border-radius: var(--r-pill);
  border: 1px solid var(--card-border); color: inherit;
  transition: transform 200ms var(--ease-out), border-color var(--t-fast);
}
.b-cal-plus svg { width: 13px; height: 13px; }
.b-cal-add:hover .b-cal-plus { transform: rotate(90deg); border-color: var(--card-border-hover); }

/* What is coming, under the day you are on. Its own rule, because it is a
   different question from the one the column opens with — and the caption
   sits tight to that rule rather than carrying the day heading's air. */
.b-cal-next {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--card-border);
}
.b-cal-next .b-cal-cap { padding-top: 0; }
/* One line: when, who, and the colour of the thing. A step under the day's
   own rows on size and on ink, because it is about a day you are not on. */
.b-cal-nrow {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 7px 8px; border: 0; border-radius: var(--r-md);
  background: none; cursor: pointer; font-family: inherit;
  transition: background-color var(--t-fast);
  animation: b-surface-in var(--t-settle) var(--ease-out) both;
  animation-delay: calc(min(var(--i, 0), 8) * 40ms);
}
.b-cal-nrow:hover { background: rgba(var(--accent-rgb), 0.10); }
.b-cal-nrow:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-cal-nrow.is-plain { cursor: default; }
.b-cal-nrow.is-plain:hover { background: none; }
.b-cal-nwhen {
  flex: none; font-size: var(--t-small); font-weight: var(--fw-semibold);
  color: var(--ink-muted); font-variant-numeric: tabular-nums;
}
.b-cal-nwho {
  flex: 1; min-width: 0; font-size: var(--t-ui); font-weight: var(--fw-semibold);
  color: var(--d100); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (prefers-reduced-motion: reduce) { .b-cal-nrow { animation: none; } }

@media (max-width: 559.98px) { /* 560 layout px */
  .b-cal-day { height: 48px; }
  /* THE COLUMN EXISTS SO THE KINDS LINE UP, and 42 was measured against the
     times alone. A diary row's when is `11:30` or `16:30` — 29 and 33px — or
     it is `all day`, which is 46.3 and wrapped, taking that row to two lines
     and dropping `Owed` half a line below the `Demo` above it.

     min-width rather than width, and nowrap beside it: at 48 every value this
     column holds is one line and they share an edge, and a longer one later
     takes the room it needs instead of folding. A fixed width can only fail
     by wrapping; this can only fail by not aligning. */
  .b-cal-etime { min-width: 48px; white-space: nowrap; }
}
@media (prefers-reduced-motion: reduce) {
  .b-cal-bg, .b-cal-plus, .b-cal-btn { transition: none; }
  .b-cal-ev { animation: none; }
  .b-cal-btn:active { transform: none; }
  .b-cal-add:hover .b-cal-plus { transform: none; }
}

/* ══ 71. THE LOOP NOBODY CLOSED ════════════════════════════════════════════
   Under the diary, and deliberately not another card: it is the same
   surface's unfinished business, so it reads as the bottom of the diary
   rather than as a second thing to do. */
/* ══ THE MEASURE BELONGS TO THE PROSE, NOT TO THE SECTION ═════════════════
   560px sat on the whole block, so the rule that separates this from the
   diary stopped two-thirds across, the caption's row ended there, and the
   door on it landed in the middle of the page with the page's own right
   edge a long way past it. A section spans its block; the thing that
   needs a measure is the sentence.

   Which also gives the section one right edge: the door on the caption's
   row and the verb at the end of every row under it now line up on it. */
.b-loop { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--card-border); }
/* The caption and the door on one row: what has not been written down, and
   the way to what has. The caption keeps its own type — a control beside a
   micro capital is the pair this row is for, not a mismatch to correct. */
.b-loop-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 6px;
  /* The rows below carry 10px of their own so the hover ground clears their
     text. This row is not pressable and has no ground to protect, so without
     the same inset its door stood nine pixels outside the column of verbs it
     is the head of. One edge for the section. */
  padding-right: 10px;
}
.b-loop-cap {
  margin: 0; font-size: var(--t-micro); font-weight: var(--fw-medium);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-muted);
}
/* The sentence under the caption is an `.b-aimy` now, so the measure it was
   carrying moves onto that — a reading is prose and prose wants a line
   length, whoever is saying it. */
.b-loop .b-aimy { max-width: 560px; margin-bottom: 10px; }
/* ══ WHAT IS OWED, ACROSS EVERY SURFACE ════════════════════════
   The briefing's own list. A row is pressable end to end and carries no
   edge of its own — the block is already a surface, and eight bordered
   rows inside it is a table drawn by hand. The verb on the right is a
   link: no fill, no edge, coloured, which is what colour is for here. */
.b-owed { display: flex; flex-direction: column; }
/* ══ IT WRAPS ON THE WIDTH IT HAS, NOT ON THE WIDTH OF THE WINDOW ════
   The breakpoint at the foot of this block says what these rows become on a
   phone, and a breakpoint is the wrong instrument for the half of this that
   is not about phones: a row is squeezed whenever its OWN width runs out,
   which also happens in a narrow pane, inside the canvas, and at any window
   size the query does not name. A query reads the window; a floor on the
   text column reads the row.

   So the wrap is unconditional and the floor is what fires it — when the
   sentence cannot have its 200px the verb takes the next line, instead of
   both of them getting half of what they need. On a wide row it never
   triggers and nothing moves.

   `--ui-scale` is the second reason to prefer it. `zoom` on <body> means a
   media query reads the real viewport in real px while the rules inside it
   apply to a layout of a different size, which is why every query in this
   file is keyed in LAYOUT px and says so. A `min-width` is inside the zoom
   with the row and measures the pixels the row is laid out in. */
.b-owed-row {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px;
  width: 100%; text-align: left;
  padding: 11px 12px; border: 0; border-radius: var(--r-md);
  background: none; cursor: pointer; font-family: inherit;
  transition: background-color var(--t-fast);
  animation: b-surface-in var(--t-settle) var(--ease-out) both;
  animation-delay: calc(min(var(--i, 0), 8) * 40ms);
}
.b-owed-row:hover { background: rgba(var(--accent-rgb), 0.10); }
.b-owed-row:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
@media (prefers-reduced-motion: reduce) { .b-owed-row { animation: none; } }
/* Two poles and nothing else, matching the bell exactly: p1 is a thing
   that went wrong, p2 and p3 are things that have not happened yet, and
   the order of the rows is what separates those two. */
.b-owed-sev {
  width: 6px; height: 6px; border-radius: 50%;
  margin-top: 7px; flex: none; background: var(--d500);
}
.b-owed-sev.p1 { background: var(--err); }
/* 200 is where wrapping the sentence starts costing more than moving the
   verb: under it this column breaks into two-word lines. `min()` so the
   floor can never be wider than the row and make the thing it is preventing.
   It was `min-width: 0`, which is the right answer to "may this shrink" and
   no answer at all to "how far". */
.b-owed-main { flex: 1; min-width: min(200px, 100%); }
/* Wrapping at every width, not only the narrow ones. The two halves are a
   name and the line that places them, and unwrapped they share one line by
   squeezing BOTH: at 360px "Mia Foster" came out as two words stacked in a
   column four characters wide beside a title in another. There is nothing to
   decide here — when they fit they sit together, and when they do not the
   name takes the line. */
.b-owed-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
.b-owed-type {
  font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--ink-primary);
}
.b-owed-when { font-size: var(--fs-2xs); color: var(--ink-quiet); }
/* ══ A ROW'S SENTENCE IS STILL A SENTENCE ═══════════════════════
   It was a clause when this row was built — "7 deals with the price on the
   table" — and the rows it carries now are two and three of them, with two
   names and a job title in the middle. At that length it was a paragraph
   set to the full width of the column and leaded like a caption: it ran to
   the verb on the right and the lines sat on top of each other.

   `62ch` is this build's measure, used a dozen times in this file and
   always with `text-wrap: pretty` beside it. It is the cap and not the
   width: the column stays `flex: 1` so the verbs go on lining up down the
   right-hand edge, and on a narrow pane the cap never binds. What it stops
   is the long line on a wide screen, which is where the crowding was.

   `--lh-snug` is 1.4, which is right for a clause and tight for a
   paragraph; `--lh-base` is the body step and the one this build gives
   anything anybody reads a whole sentence of. 19.6px to 21. */
.b-owed-body {
  display: block; margin-top: 2px;
  font-size: var(--fs-xs); color: var(--ink-secondary); line-height: var(--lh-base);
  max-width: 62ch; text-wrap: pretty;
}
/* ══ THE VERB IS AN ACTION, SO IT TAKES THE INK ACTIONS TAKE ════════
   `--accent` measured 3.77:1 here at 12px/600 and fails AA — and it failed in
   DARK ONLY, where it came back 7.15:1 in light, which is why three surfaces
   carried it for this long. A change checked in the theme it happens to pass
   in is a change nobody caught.

   sales.css has carried the answer all along, with the reason written beside
   it: `--accent-text`, “5.04:1 — an action, not a status”. Measured after, on
   the ground each one actually sits on: this verb and the diary's 5.09:1 dark
   and 6.01:1 light, the bell's 4.51:1 and 5.32:1. The bell clears AA by one
   hundredth and it is the tint under it that costs the rest — dropping that
   fill below 0.12 is the lever if the margin ever needs to be real.

   The FILLS stay `--accent`. A non-text graphic answers to 3:1 and the tint
   under the bell's row verb is doing no reading. */
/* Pushed right whichever line it lands on. Inert while the row is one line
   — `.b-owed-main` is `flex: 1` and has already taken the slack — and on a
   wrapped line it keeps the verb under the right-hand edge it sits at
   everywhere else, rather than starting it under the severity dot. The phone
   block overrides it, because a stacked row reads left. */
.b-owed-go {
  flex: none; align-self: center; margin-left: auto; white-space: nowrap;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--accent-text);
}

/* The same rule and the same reason as `.b-owed-row` above: this one runs
   out sooner than any row in the build, because the date is a fixed 84 and
   the verb about a hundred before the name has asked for anything. */
.b-loop-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px;
  width: 100%; text-align: left;
  padding: 9px 10px; border: 0; border-radius: var(--r-md);
  background: none; cursor: pointer; font-family: inherit;
  transition: background-color var(--t-fast);
  animation: b-surface-in var(--t-settle) var(--ease-out) both;
  animation-delay: calc(min(var(--i, 0), 8) * 40ms);
}
.b-loop-row:hover { background: rgba(var(--accent-rgb), 0.10); }
.b-loop-row:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-loop-when { width: 84px; flex: none; font-size: var(--t-small); color: var(--d200); }
.b-loop-who {
  flex: 1; min-width: min(180px, 100%); display: flex; flex-direction: column; gap: 2px;
  font-size: var(--t-ui); font-weight: var(--fw-semibold); color: var(--d50);
}
.b-loop-what { font-size: var(--t-small); font-weight: var(--fw-medium); color: var(--d200); }
/* The diary's verb: the same rule, the same defect, 3.77:1 before. Fixing two
   of the three would have read as deliberate to whoever found the third. */
.b-loop-go {
  flex: none; margin-left: auto;
  font-size: var(--t-ui); font-weight: var(--fw-semibold); color: var(--accent-text);
}
/* Today's three sit in a block rather than in the diary's panel, so they
   carry the panel's padding themselves. */
@media (prefers-reduced-motion: reduce) { .b-loop-row { animation: none; } }

/* ══ A ROW OF COLUMNS IS NOT A ROW ON A PHONE ═══════════════════
   Both of these are one line of facts with the verb at the far end, which is
   the right shape while there is room for it and falls apart without one. A
   diary row spends 84 fixed pixels on the date and about a hundred on "Say
   how it went", so at 360 the middle got 130 and put a name over two lines
   and its sentence over three — five lines of two-word columns for one
   meeting. The connections row does the same with a bridge's job title.

   THE COMPONENT CHANGES FORM; NOTHING IS DELETED. The verb is the whole
   point of the row and a breakpoint that hides it takes the capability with
   it. So the row stops being columns and becomes what it is on a narrow
   screen: the facts down the page in the order you read them, and the verb
   under them on a line of its own, where a stacked row's action goes.

   560 is this file's own mobile step and the width these two stop fitting
   at, measured rather than picked: the diary row's fixed 84 plus its verb
   leave the middle column under half the line from there down. */
@media (max-width: 559.98px) { /* 560 layout px */
  .b-loop-row { flex-direction: column; align-items: stretch; gap: 4px; }
  /* The date stops being a column and becomes the line above the name. */
  .b-loop-when { width: auto; }
  /* An auto margin outranks `align-self`, so the base rule's push to the
     right has to be taken back by name rather than argued with. */
  .b-loop-go { align-self: flex-start; margin-left: 0; }

  /* The dot stays on the line it marks — it is the row's severity and it
     belongs beside the first words, not on a line of its own. Only the verb
     wraps, and it lands in the text column rather than under the dot: 18px
     is the dot's 6 and the row's 12 of gap. */
  .b-owed-row { flex-wrap: wrap; }
  .b-owed-go { width: 100%; margin-left: 18px; margin-top: 6px; align-self: flex-start; }
}

/* ══ 72. THE BAR TAKES DICTATION ═══════════════════════════════════════════
   The control ships in the library and nothing had ever used it. What it
   does not ship is where the wave and the clock sit once the input has got
   out of the way, which is the bar's own business. */
.voice-btn { flex: none; width: 30px; height: 30px; }
.voice-btn svg { width: 15px; height: 15px; }
/* `display` on a class beats the browser's own [hidden], so a wave that
   says it is hidden would be drawn anyway. */
.voice-wave[hidden], .voice-timer[hidden], .aimy-float-input[hidden] { display: none !important; }
.voice-wave { flex: 1; justify-content: center; }
.voice-timer { flex: none; }
.overlay-input[hidden] { display: none !important; }

/* ══ 73. THE PROOF GOES FIRST ══════════════════════════════════════════════
   Two sentences somebody is going to say out loud in a room, so they are set
   as sentences rather than as fields: the customer's name over the claim,
   at reading size, with nothing else competing on the line. */
.b-story { display: flex; flex-direction: column; gap: 12px; margin: 8px 0 18px; }
.b-story-row {
  padding: 12px 14px; border-radius: var(--r-md);
  background: rgba(var(--accent-rgb), 0.08);
  border: 1px solid rgba(var(--accent-rgb), 0.16);
}
/* ══ THE FOURTH ONE, AND THE WORST ═════════════════════════
   3.07:1 in dark at 12px/700, against the canvas's raised ground — lower than
   the three verbs, because this sits on a panel rather than on the page.
   6.77:1 in light, like the others: the whole family failed in one theme.

   `--accent-text` reads “an action, not a status” and this is a caption,
   which is neither. The distinction that comment is drawing is against
   `--ok-text` and `--err-text` — the accent does not MEAN anything here, it
   is the product's colour on a piece of type, and the token is that colour at
   a contrast type can be set in. Measured after: 4.15:1 dark, 5.70:1 light.

   4.15 does not clear AA for body copy and this is not body copy: 12px at
   700 with 0.06em tracking, four words, naming the company a quote belongs
   to. It clears the 3:1 that a non-body caption answers to with room, and
   pushing it further means leaving the accent, which is the only thing on
   the card saying whose voice the sentence under it is. */
.b-story-who {
  display: block; margin-bottom: 4px;
  font-size: var(--t-micro); font-weight: var(--fw-bold);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent-text);
}
.b-story-say {
  margin: 0; font-size: var(--t-body); color: var(--d50);
  line-height: var(--lh-base); text-wrap: pretty;
}

/* §74 styled "What else fits" — a block of its own under the reading on a
   customer's page, naming the service that goes with what they run. It
   answered the same question the reading answers, from the other end, with
   its own heading and its own verb; the two named different products often
   enough that a caption had to sit between them explaining the difference.
   Two reasons for one decision are two lines of one reading. Folded into
   `.b-lead-then` at §88, and the six rules go with the block. */

/* ══ 75. A MARK PER KIND OF FACT ═══════════════════════════════════════════
   The mark sits with the fact rather than in a column of its own, so a row
   of facts wraps as a row of facts and nothing has to line up. It is quieter
   than the words it marks — one step down the ink scale — because it is
   there to be recognised at a glance and not to be read.

   The middot separator goes wherever a mark arrives: two separators for one
   job is one too many, and the gap does the rest. */
.b-fact { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
/* == CENTRED ON THE FIRST LINE, WHICH IS BOTH ANSWERS AT ONCE =============
   `align-self: center` centres the mark on the whole box, so a fact of one
   line got it level with the words and a fact that wrapped got it level with
   the gap between them -- floating beside the middle of a sentence with no
   line to belong to.

   Centred on the FIRST line instead. Where there is only one line that is
   the same thing as centred, and where there are two it sits against the
   line the sentence starts on. One rule, both behaviours, and no counting
   of lines: `1lh` is this element's own line box, so it follows whatever
   size the row is set at without being told.

   The mark's size moves to `--mark` so the sum can read it. Every rule that
   used to set width and height sets that instead, and each still gets its
   own centring for free. */
.b-fact > svg {
  --mark: 13px;
  width: var(--mark); height: var(--mark); flex: none; color: var(--ink-muted);
  align-self: flex-start;
  /* For anything without `lh`: the row's own leading, in ems. */
  margin-top: calc((var(--lh-base, 1.5) * 1em - var(--mark)) / 2);
  margin-top: calc((1lh - var(--mark)) / 2);
}
/* == THE MARK IS THE SEPARATOR, ON EVERY RANK =============================
   Rank one lost its marks for four commits and took the middot instead. The
   argument was that a campaign name, a job title and a company name say
   which kind of thing they are without a drawing in front of them -- true of
   those three, and no longer true of the rank, because what we sell them
   joined it. "Logistics, Southern Europe · Data annotation" is a campaign
   beside a product, and neither announces which it is. The flag and the box
   said so; a middot says nothing.

   And the marks were never what fixed this masthead. Two ranks that read as
   one were 16/500 over 14/400 with the SAME 13px mark, and what separated
   them was dropping rank two to micro with a mark to match -- two steps of
   size, which stands whether or not rank one is drawn with marks. Taking
   them off was a second measure on top of a fix that had already landed,
   and it was the one that cost information.

   So the marks are back, and with them the rule this file has always held:
   two separators for one job is one too many. Where a mark arrives the
   middot goes -- on every rank now rather than only on rank two, because
   `:has` hangs the rule off the fact BEFORE the gap and does not care which
   row it is in. A bare fact beside a marked one still takes a dot; it is
   the only thing it has. */
.s-rec-facts .b-fact:has(+ .b-fact)::after { content: none; }
.s-rec-facts > div + div .b-fact > svg { --mark: 11px; }
.s-rec-facts > div { gap: 4px 16px; }
.b-kinds { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; }
/* On a card the mark is smaller again: the line it sits on is 11px, and a
   13px mark beside it reads as the loudest thing in the corner. */
.tc-type.b-fact > svg { --mark: 10px; }
.b-node-name.b-fact > svg { --mark: 12px; }

/* ══ 76. THE MARKS ON A CARD ═══════════════════════════════════════════════
   Smaller than on a record, because the line they sit on is smaller: a
   13px mark beside 12px type is the loudest thing in the corner of a card,
   which is the opposite of what a mark is for. The facts line becomes a
   wrapping row so a long company name takes its mark with it. */
.b-qcard-where { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; }
.b-qcard-where .b-fact > svg { --mark: 10px; }
.b-qcard-num.b-fact > svg { --mark: 12px; }

/* The campaign's team block carries its own caption and the top margin that
   goes with sitting inside the metadata grid. On a record it is a section
   with a heading of its own, so it takes neither. */
.b-team-rec { margin-top: 12px; }

/* ══ 78. A HEAD THAT HOLDS A SEARCH FIELD ALIGNS ON CENTRES ════════════════
   `.s-camp-list-head` is `align-items: baseline`, which is right for what it
   was written for — a heading with a caption beside it, two runs of text that
   should sit on the same line of writing. Two of these heads hold something
   else: a bordered search pill, on the queue beside the tab strip and on a
   campaign beside "To call". A pill is read as a box, not as the text inside
   it, so lining up the text left the box hanging. Measured: 4.5px below the
   tabs' optical centre, 5.1px below the heading's.

   `:has(.b-find)` rather than a blanket change, because every other head is
   the heading-and-caption case and baseline is right there.

   The negative margin is the tab strip's underline gutter. `.b-switch-btn`
   carries `padding-bottom: 4px` to reserve room for `.b-switch-bar`, so the
   strip's box is 4px taller than its type and centring the box would centre
   the type 2px high. Discounting the gutter says what is meant: line up the
   writing, not the runway under it. 4.5px to 0.6px on the queue, 5.1px to
   nothing on the campaign, and the sliding bar lands level with the bottom of
   the pill. */
.s-camp-list-head:has(.b-find) { align-items: center; }
.s-camp-list-head:has(.b-find) > .b-switch { margin-bottom: -4px; }
/* And the heading's own bottom margin, which centring would otherwise centre
   along with it: flex aligns the MARGIN box, so `.s-block-h`'s 12px underneath
   pushed the type 5px up inside a row that was now correctly centred. The
   margin is redundant here anyway — the head's `row-gap` already spaces a
   wrapped second line — and the shell zeroes it the same way, on the same
   class, in `.s-camp-rows-head`. */
.s-camp-list-head:has(.b-find) > .s-block-h { margin-bottom: 0; }

/* ══ 79. THE MARK STANDS BESIDE THE TAG, NOT UNDER IT ══════════════════════
   14px against a pill that is 24px of line and padding: the mark read as a
   speck put next to a label rather than as the speaker of it. 18px is about
   three quarters of the pill, which is where a logo sits beside a word — big
   enough to be the thing talking, small enough that an 11px label still leads.
   Scoped past the shell's `.s-insight.is-lead` rule rather than editing it,
   since the same mark at 14 is right where it appears without a tag. */
.s-insight.is-lead .s-lead-mark .s-insight-mark { width: 18px; height: 18px; }

/* ══ 80. THE OUTCOME CARRIES ITS OWN WEIGHT AND TONE ═══════════════════════
   Argued at `campTouchRow`. `.tone-ok` and its siblings are colour-only
   utilities from the shell, so the weight lives here and the neutral case is
   the absence of a tone class rather than `tone-neutral`, whose --d400 would
   have sunk an event the tones simply have nothing to say about. */
.b-feed-out { font-weight: var(--fw-semibold); color: var(--d100); }
/* The tones have to outrank the neutral above them. `.tone-ok` and its
   siblings are single-class rules in sales.css and this file loads after it,
   so at equal specificity the LATER rule won and --d100 quietly painted over
   every tone in the feed — Connected and Resolution came out the same grey as
   Moved by hand. Two classes beats one. */
.b-feed-out.tone-ok { color: var(--ok-text); }
.b-feed-out.tone-warn { color: var(--warn-text); }
.b-feed-out.tone-err { color: var(--err-text); }
/* And the bookkeeping behind it. Who logged it and at what minute is true and
   worth keeping, and nobody has ever scanned a feed looking for it.

   THIS RULE STOPPED DOING ANYTHING. It demotes to --ink-muted, and --ink-muted
   is --d200, which is what `.s-qrow-sub` already sets on the line this sits
   in: written to push the attribution down a step, and after the ink band
   narrowed, pushing it to exactly where it already was. Seventeen of the
   forty-two runs on an account page came out identical because of it — the
   note, the outcome and the bookkeeping in one grey.

   An attribution and a timestamp beside something else is the textbook case
   for the minimal-component size, so it takes that instead of a shade of
   grey that no longer exists. */
.b-feed-meta {
  font-size: var(--fs-2xs); font-weight: var(--fw-medium); color: var(--ink-muted);
}
/* What happened leads its own line; the note under it is the sentence you
   read, and it is not the same rank as who typed it. */
.b-feed-out { font-weight: var(--fw-semibold); }
.s-qrow-because { color: var(--ink-secondary); }

/* ══ 81. TWO DOORS THAT ARE SPECIMENS OF WHAT IS BEHIND THEM ═══════════════
   Argued at `dayRail`. Two cards, apart, each one a card in its own right:
   joined into one split surface they read as a control tray with a divider
   down it, which is the thing they were drawn not to be.

   The material is the system's own `.bcard`, which is where this product
   keeps the idea of a surface worth pressing: the larger radius, a shadow
   with a real offset and a soft blur rather than a halo, and the lift on
   hover. The inset hairline along the top edge is the one thing added — a
   single pixel of light where a raised surface would catch it, which is what
   separates a card from a rectangle with a border. */
/* One under the other in a 282px column, which is the shape the rail has
   spare — and the reason these two could carry a figure at 32px on a page
   is the reason they still can here: there is nothing beside them. */
.rail-doors { display: grid; gap: 10px; margin-top: 12px; }
/* ── AND IT MUST BE ALLOWED TO BE NARROWER THAN ITS CONTENTS ─────────────
   `width: 100%` says what it wants; `min-width: auto` is what a grid and a
   flex item get by default and it says the opposite — never smaller than
   the widest thing inside. In the rail's 282px track that made the card
   284.3, so both doors hung two pixels over the edge and the rail grew a
   horizontal scrollbar. Not the diary card's fault: Financials did it too,
   which is how you know it is the box and not the sentence.

   `min-width: 0` hands the sizing back to the track. Everything inside can
   wrap, which is the other half of this fix. */
.b-door {
  display: flex; flex-direction: column; gap: 12px; width: 100%; height: 100%;
  min-width: 0;
  text-align: left; padding: 18px 20px 20px;
  border: 1px solid var(--card-border); border-radius: var(--r-2xl);
  background: var(--card-bg);
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.045);
  cursor: pointer;
  transition: border-color var(--t-base), transform var(--t-base), box-shadow var(--t-base);
}
.b-door:hover {
  border-color: var(--card-border-hover); transform: translateY(-1px);
  box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
.b-door:active { transform: translateY(0); }
.b-door:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .b-door:hover, .b-door:active { transform: none; }
}
.b-door-cap {
  font-size: var(--t-small); font-weight: var(--fw-bold); color: var(--ink-muted);
}
.b-door-say { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--d200); }
/* ── the half of the line that is an alarm ───────────────────────────
   `--err-text` rather than `--err` literally, because the -text pair is what
   a product colours INK with and the two diverge in light, where the fill's
   red is 3.82:1 on a white card. Measured where this one actually sits — on
   `--card-bg`, not on the page — it is 6.59:1 in dark and 7.58:1 in light.

   `--fw-bold`, which is 600 here, against the line's own 400. Two steps, not
   the one `--fw-semibold` would give: colour is never the only difference
   between two clauses, and at `--fs-sm` a single step is a difference you
   have to go looking for. */
.b-door-miss { color: var(--err-text); font-weight: var(--fw-bold); }

/* ── who the time belongs to ────────────────────────────────────────────
   The name sits under the clock with the kind of thing it is beside it, so
   the card answers when, who and what in three lines and stops. */
.b-door-who {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--d100);
}
/* The tuck belongs to the figure, not to the row. `-4px` closes the gap a
   three-line-height figure leaves under itself; on the clear diary, which
   has no figure at all now, it pulled the sentence up under the caption
   instead and took 4px off a gap that was already the card's smallest. */
.b-door-fig + .b-door-who { margin-top: -4px; }
/* ── A CLEAR DAY IS TWO SENTENCES, NOT A NAME AND A WORD ──────────────────
   The row was built for "Evie Reid" beside "Demo" — two short things that
   sit on one line at any width the rail has. The empty day puts "Nothing is
   in the diary" in the name's slot and "9 things in the fortnight ahead" in
   the kind's, and `.b-kind` is `white-space: nowrap`, so the sentence could
   not break and the name was crushed into three ragged lines beside it:
   Nothing / is in the / diary. Measured at 70px of row for two facts.

   The row wraps. Where both halves fit they stay on one line and the booked
   day is untouched; where they do not, the second takes a line of its own,
   which is what two sentences want anyway. 48px, and both read straight.

   `white-space: normal` only HERE. The nowrap is right everywhere else
   `.b-kind` is used, where it holds one or two words and a break inside it
   would read as two facts. */
.b-door-who { flex-wrap: wrap; }
.b-door-who .b-kind { white-space: normal; }

/* ── the book, as a figure ─────────────────────────────────────────────── */
.b-door-fig {
  font-size: var(--fs-3xl); font-weight: var(--fw-extrabold); color: var(--d50);
  line-height: var(--lh-tight); letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;
}
/* "Clear" is a good day, not a big number: it takes the size so the two cards
   still line up, and the ink of something that is not a figure. */
.b-door-fig.is-quiet { color: var(--ink-muted); }
/* ── and a count of missed calls is not a good day ───────────────────
   The one figure on any of these cards that is somebody waiting rather than a
   fact about the day. Same token as `.b-door-miss` on the book desk's line,
   for the same fact, so one red means missed calls across both desks — and
   the same reasoning about ink: `--err-text`, never `--err`. The weight is
   already extrabold from `.b-door-fig`, so the colour is the only thing this
   rule adds and the figure is legible without it. */
.b-door-fig.is-miss { color: var(--err-text); }
/* ── WHAT THE FIGURE IS ──
   A number this size with no noun on it is a card asking a question. The
   report's own headline solves it the same way — `.s-att-of` after
   `.s-att-lead` — and this is that pair at the size a rail card can spare:
   the word drops three steps and loses the weight, so it reads as the
   figure's unit rather than as a second figure beside it. */
.b-door-of {
  font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--ink-quiet);
  letter-spacing: 0; margin-left: 6px;
}
/* ── The book against its target, in six pixels ──
   `overflow: hidden` clipped the target mark whenever the book had passed
   it, so the one frame this bar exists to draw — the moment the quarter is
   made — was the one it could not show. `position: relative` because the
   mark is placed along the track rather than laid in the flex row. */
.b-door-bar {
  display: flex; position: relative; height: 6px; border-radius: var(--r-pill);
  background: var(--card-border); margin-top: -2px;
}
.b-door-seg { display: block; height: 100%; border-radius: var(--r-pill); }
/* Signed is the one thing on this bar that has been decided, so it is the
   one thing with a colour. */
.b-door-seg.is-open { background: var(--d500); }
.b-door-seg.is-won { background: var(--ok-text); }
/* WHERE THE QUARTER IS MEANT TO REACH. The same object the report's own
   track carries, at the width a rail card can spare — a rule across the
   bar, not a segment of it, because a target is a line rather than an
   amount of anything. */
.b-door-mark {
  position: absolute; top: -2px; bottom: -2px; width: 2px;
  background: var(--d50); border-radius: var(--r-pill);
  /* Centred on the position rather than starting at it, or a target the book
     has exactly reached draws two pixels past the end of its own track. */
  transform: translateX(-1px);
}

/* A grid inside a grid track needs telling that it may be narrower than its
   content, or the seven columns push the track wider than it was given. */
.b-cal-mo { min-width: 0; }
/* ── the day under the month, which is the default ──────────────────────
   The side column had the pop-out's treatment unscoped — a left rule and a
   left indent — so on the diary page it drew a vertical hairline down the
   left of a block with nothing beside it. Stacked is what a page does and
   what a phone does; beside is what the pop-out does, and the rule between
   two things turns with them. */
.b-cal-side {
  min-width: 0; padding-top: 12px; margin-top: 12px;
  border-top: 1px solid var(--card-border);
}
.b-cal-side .b-cal-cap { margin-top: 0; }
@media (prefers-reduced-motion: reduce) { .b-gate:active { transform: none; } }

/* The promise each door keeps, in the ink this product uses for a text action
   everywhere else. `margin-top: auto` puts it on the floor of the card, so the
   two sit on one line however tall their middles run; the chevron steps
   forward on hover, which is the only motion either card has. */
.b-door-go {
  display: inline-flex; align-items: center; gap: 5px; margin-top: auto; padding-top: 2px;
  font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--accent-text);
}
.b-door-go svg {
  width: 13px; height: 13px; transition: transform 160ms var(--ease-out);
}
.b-door:hover .b-door-go svg { transform: translateX(2px); }
@media (prefers-reduced-motion: reduce) {
  .b-door:hover .b-door-go svg { transform: none; }
}

/* The clock sits on the figure's own line, sized off it so the two scale
   together, and a step down in ink so the number still leads. `0.62em` is the
   digits' cap height at this weight — a mark that matches the glyphs beside it
   rather than the box they sit in. */
.b-door-clock {
  display: inline-flex; align-items: center; margin-right: 0.3em;
  vertical-align: baseline; color: var(--ink-muted);
}
.b-door-clock svg { width: 0.62em; height: 0.62em; }

/* An entry the calendar holds for its own sake: the same row, no door. */
.b-cal-ev.is-plain { cursor: default; }
.b-cal-ev.is-plain:hover { background: none; }

/* ══ 83. A DRAFT CAMPAIGN'S OWN PAGE ═══════════════════════════════════════
   The fields are the record's own type, not a form's. A name that becomes an
   input should still look like the name: same size, same weight, no box
   around it until you are in it — a page of grey rectangles announces that
   somebody is being made to fill in a form, and this is somebody writing
   down a campaign they have already decided on. */
/* The name of a record is `--t-title` on every other page in this build
   (§26), and this is a record. At 32px it outranked every masthead in the
   product, which read as a form with one enormous field rather than as the
   campaign's own page. */
.b-draft-name {
  display: block; width: 100%; margin: 4px 0;
  font-family: var(--font-display); font-size: var(--t-title); font-weight: var(--fw-extrabold);
  letter-spacing: -0.02em; line-height: 1.1; color: var(--d50);
  /* Transparent at rest, and it can stay that way because the placeholder
     is still set at the size of a name: "Name this campaign" IS the
     affordance, which is the whole reason §26c exempts this one field from
     the 14px rank. An underline was added here while it was 14px and is
     gone with the reason for it. */
  background: none; border: 0; border-bottom: 1px solid transparent; padding: 2px 0;
  transition: border-color var(--t-base);
}
/* ══ AN ABSENCE MUST NOT READ AS A VALUE ═══════════════════════════════════
   Measured on the campaign draft, where a page of empty fields is the normal
   state: "Choose" rendered #c8d2dc and "FlairsTech" — a real answer beside it
   — rendered #d8e0e8. One step on the ramp, same size, same weight. The rule
   this build adopted for every other pair of ranks is that they differ in at
   least two of {size, weight, ink}, and the two things on this page that must
   never be confused differed on one axis by its smallest increment.

   `--ink-placeholder` has been in the shell the whole time, declared as "the
   ONE exception — must not read as a value" and pointed at by nothing. Every
   placeholder in this product asked for `--ink-muted`, which the same block
   calls "the floor for TYPE. Slightly quiet, still read" — a value.

   It is legible, not invisible: 6.1:1 on the page ground, well past AA. The
   point is not that a placeholder is hard to read, it is that it cannot be
   mistaken for an answer. */
.b-draft-name::placeholder { color: var(--ink-placeholder); }
.b-draft-name:hover { border-bottom-color: var(--card-border); }
.b-draft-name:focus { outline: none; border-bottom-color: var(--accent); }
.b-draft-in {
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--d100);
  background: none; border: 0; border-bottom: 1px solid var(--card-border); padding: 3px 0;
  min-width: 0; width: 100%; max-width: 34ch;
  transition: border-color var(--t-base);
}
.b-draft-in::placeholder { color: var(--ink-placeholder); }
.b-draft-in:hover { border-bottom-color: var(--card-border-hover); }
.b-draft-in:focus { outline: none; border-bottom-color: var(--accent); }
.b-draft-pick {
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-medium);
  color: var(--d100); background: none; border: 0; cursor: pointer;
  border-bottom: 1px dashed var(--card-border-hover); padding: 3px 0; text-align: left;
}
.b-draft-pick:hover { color: var(--accent-text); border-bottom-color: var(--accent); }
.b-draft-pick:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* "Choose" is not a value, it is the absence of one, and it sits in the
   slot a value will occupy. It takes the placeholder rank for the same
   reason the inputs do — it IS a placeholder, drawn by hand because the slot
   is a menu button rather than an input. */
.b-draft-none { color: var(--ink-placeholder); }
/* When it closes is a fact on the record, not a footnote about one. And a
   field that holds one or two digits is sized for one or two digits: at the
   full 34ch it took the whole cell and pushed its own unit onto a second
   line, so "6" and "weeks" read as two unrelated things. */
.b-draft-unit { margin-left: 8px; font-size: var(--t-ui); color: var(--ink-muted); }
.b-draft-in[data-cfield="weeks"] { width: 3ch; text-align: right; }
/* What is still missing, in the words of the thing missing — never a greyed
   button that will not say why it is grey. */
.b-draft-miss { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--warn-text); }
.b-draft-saved { font-size: var(--t-ui); color: var(--ink-muted); margin-left: auto; }

/* The record's metadata grid is `auto-fit` over `minmax(232px, 1fr)`, which is
   right for the four facts a campaign has and wrong for the eight a draft
   asks: eight resolved to four 232px tracks, the track they sit in is sized by
   its content, and the whole page grew to 1072px and scrolled sideways.
   Counted columns and `minmax(0, 1fr)` so the fields fit the page rather than
   the page fitting the fields. */
.b-draft-meta { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 100%; }
@media (max-width: 1279.98px) { /* 1280 layout px */ .b-draft-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 719.98px) { /* 720 layout px */ .b-draft-meta { grid-template-columns: minmax(0, 1fr); } }

/* Greyed, and the reason is the sentence beside it rather than a tooltip
   nobody hovers. */
.s-rec-actions .s-insight-lnk[disabled] { opacity: 0.45; cursor: not-allowed; }
.b-draft-empty { display: block; padding: 4px 10px 6px; max-width: 30ch; }
/* Throwing it away is not a peer of running it: last, and out of the way of
   the two things you actually came to press. */
.b-draft-bin { margin-left: auto; color: var(--ink-muted); }
.b-draft-bin:hover { color: var(--err-text); }

/* The kind line on the left, the two decisions on the right, on one line. */
.b-draft-top { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
/* Three verbs now rather than two, and the third is a sentence. They fit on
   one row at 375 layout px with their labels wrapping inside the pills; the
   wrap is here for the widths under that and for a page zoomed past 150%,
   where a button pushed off the right is a capability nobody can reach. */
.b-draft-acts { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ══ 83b. THE HALF OF A CAMPAIGN THAT IS SENTENCES ═══════════════════
   The eight facts above this block are a word or a name each and sit three to
   a row. These three are prose — a line of job titles, the angle somebody
   says out loud, and whatever the manager knows that the book does not — and
   a paragraph in a 1fr track of a three-column grid is a column of single
   words. One column, and the measure comes from the type rather than from the
   grid. 68ch because a pitch is read once, fast, by somebody about to speak.

   It is the record's own `.b-cmeta` either way, so the block reads as part of
   the record on a running campaign and as part of the form on a draft without
   either of them learning a second layout. */
.b-said { grid-template-columns: minmax(0, 1fr); max-width: 100%; row-gap: 20px; }
.b-said .b-draft-in, .b-said .b-cmeta-p { max-width: 68ch; }

/* The caption and the verb on one row — `.b-team-head`'s shape, which is what
   this build uses wherever a section has exactly one thing you can do to it.
   `.b-cmeta-cap` keeps its own 8px underneath, so the field below sits where
   it sits on every other part whether or not the verb is drawn. */
.b-draft-caprow { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }

/* ══ ASKING AiMY IS NOT A PEER OF ANSWERING ═════════════════════════
   A filled button beside a field would make writing the field yourself the
   second option, on a page whose whole argument is that you already know what
   this campaign is. This is the weight of a word with the mark in front of
   it: on screen the whole time, never louder than the caption beside it, and
   it takes the accent on hover where every other quiet verb here does.

   `--d200` and not below it. It is a control, and the ramp's floor for type
   is the floor for a control that is type. */
.b-draft-ai {
  display: inline-flex; align-items: center; gap: 5px; padding: 0;
  font-family: inherit; font-size: var(--t-ui); font-weight: var(--fw-medium);
  color: var(--d200); background: none; border: 0; cursor: pointer;
  transition: color var(--t-fast);
}
.b-draft-ai:hover { color: var(--accent-text); }
.b-draft-ai:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

/* The mark's own class. `.b-aimy-mark` belongs to a card's note and carries
   that block's size and its one-pixel optical nudge, and `.b-ghost > svg` in
   this same sheet is 14px square and outranks it — so the same markup drew at
   two sizes depending on what it had been put inside. */
.b-spark { flex: none; width: 11px; height: 13px; }
.b-draft-fill > .b-spark { width: 13px; height: 15px; }

/* A field whose answer is a paragraph. The underline is the one every other
   field on the page wears; what changes is the box, because a sentence needs
   a second line and a 34ch input scrolls sideways rather than giving it one.

   NO HANDLE. It had `resize: vertical`, defended at the time as an addition
   to a box that already sizes itself rather than the only way to see what you
   wrote — and that defence is the argument against it: the drag did nothing
   the typing had not already done, and a corner grab with no press behind it
   is the one shape this build does not ship. What it cost was a grey wedge in
   the corner of all three fields, on a page whose whole job is to look like
   the record rather than like a form.

   Nothing is hidden by taking it away. Where `field-sizing` exists the
   browser measures the box; where it does not, `draftArea` sets the rows from
   the value on every repaint and the `input` handler grows it on every
   keystroke, which is the half a repaint cannot cover because this build
   deliberately does not repaint a field while it is being typed in. */
.b-draft-area {
  display: block; width: 100%; max-width: 68ch;
  line-height: var(--lh-base); resize: none;
  /* The browser measures its own text better than any character count can.
     Where it is supported this is exact at every width and every zoom; where
     it is not, `rows` is computed from the value and lands within a line.
     Both are needed: one of them is right and the other one always runs. */
  /* No min-height. It was 2lh, and it made the persona — one line of job
     titles, sized by its own rows to one line — stand in a two-line box with
     its underline floating an empty line below the words. The row count and
     the browser's own measurement both already know how tall this is. */
  field-sizing: content;
}

/* On a page the two doors sit in a column 282px wide, so the figure and its
   bar have the width of the card rather than half of it. Nothing else about
   them changes: same card, same steps, same promise on the floor of it. */
/* ══ A RAIL CARD IS A GLANCE, NOT A PAGE ═══════════════════════════════════
   The doors were built at the page's own proportions — 18px of padding, a
   24px figure, 12px between the parts — and dropped into a 272px column
   where two of them plus the reading ran 975px inside an 829px rail. The
   rail scrolled, which is the one thing a rail must not do to reach its own
   foot.

   Everything comes down one step and nothing changes rank: the figure is
   still the loudest thing in its card, the caption still the quietest, and
   the gap between them still reads as a gap. 145px of the overflow goes. */
.rail-doors .b-door { padding: 13px 14px 14px; gap: 8px; }
.rail-doors .b-door-fig { font-size: var(--fs-xl); }
.rail-doors .b-door-say, .rail-doors .b-door-go { font-size: var(--t-small); }

/* ══ THE FOOT, AND THE QUIETER WAY INTO THE CONSOLE ════════════════════════
   Knowledge's shape, in this build's units: a hairline, an icon, a name and
   a line saying what is behind it. `margin-top: auto` sits it at the bottom
   of a short rail and after the last card on a long one — a gate pinned over
   content that scrolls under it is a second thing to read past. */
/* No rule above it. Knowledge's gate sits under a list of chats that runs to
   the foot of its rail, where a hairline is what stops the last row and the
   door reading as one thing. This rail ends in a card with its own border and
   then eighty pixels of ground, so the rule was drawing a line across empty
   space to separate two things already separated. */
.rail-foot { margin-top: auto; padding-top: 10px; flex-shrink: 0; }
.rail-console {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: var(--r-md);
  /* An anchor now, so it brings a browser underline and an inherited link
     colour that this row sets for itself two lines down. */
  background: none; font-family: inherit; text-align: left; text-decoration: none;
  color: var(--ink-quiet); cursor: pointer;
  /* The press lives here rather than in §62b, because this rule is later in
     the file and a transition set there would be thrown away by this one. */
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-fast), color var(--t-fast);
}
.rail-console:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
  .rail-console { transition: none; }
  .rail-console:active { transform: none; }
}
.rail-console:hover { background: var(--card-bg-raised); color: var(--ink-primary); }
.rail-console:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.rail-console > svg { width: 15px; height: 15px; flex: none; opacity: 0.75; }
/* One line, not two — the subline and its wrapper are gone. The truncation
   moves onto the name, which is what is left to truncate. */
.rail-console-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--t-ui); font-weight: var(--fw-semibold);
}

/* The read-back's card. The record's own metadata grid, at two columns
   because a canvas turn is half the width of a page. */
.b-cb-card {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: 12px; max-width: 100%;
}
@media (max-width: 719.98px) { /* 720 layout px */ .b-cb-card { grid-template-columns: minmax(0, 1fr); } }
/* Eight of the card's cells are a word or a name and pair up across two
   columns. The pitch is three sentences, and three sentences in a 1fr track
   of a half-width turn is a column of single words. It takes the row, last,
   where the card reads as a record with a paragraph at the foot of it rather
   than as a grid with one cell that will not fit. */
.b-cb-wide { grid-column: 1 / -1; }
.b-cb-wide .b-cmeta-say { line-height: var(--lh-base); text-wrap: pretty; }

/* ══ 84. THE DIARY, AS A PAGE ══════════════════════════════════════════════
   The same month the pop-out draws, standing on a surface instead of hanging
   off a door: no card of its own, because `.b-cal-panel` already is one, and
   the loop under it is the block it has always been. */
.b-diary { margin-bottom: 4px; }
.b-diary .b-cal { margin-top: 4px; max-width: 1080px; }
.b-diary .b-cal-panel {
  display: grid; grid-template-columns: minmax(0, 1fr) 340px; align-items: start;
  padding: 12px 14px 14px;
}
/* Across both columns: the month it names is one of them and the day it
   steps is the other. */
.b-diary .b-cal-head { grid-column: 1 / -1; }
.b-diary .b-cal-side {
  padding-left: 18px; margin-left: 18px; padding-top: 0; margin-top: 0;
  border-left: 1px solid var(--card-border); border-top: 0;
}
/* ══ AND SIZED FOR THE ROOM IT IS IN ═══════════════════════════════════
   The month's type was set for a 296px column inside a panel that was
   fighting for height: 54px cells, a 13px date, five-pixel dots. In seven
   hundred it reads as a wireframe of a calendar — the marks are correct and
   they are all too small for the box around them, which is the same fault
   as a photograph printed at four times its resolution.

   So the cell grows to 72 and everything in it grows with it. The weekday
   row comes off the eleven-pixel floor while it is here, which it had no
   business sitting on: "Mon" is not capitals. */
.b-diary .b-cal-day { height: 72px; gap: 7px; }
.b-diary .b-cal-num { font-size: var(--t-deck); }
.b-diary .b-cal-dots { gap: 4px; height: 7px; }
.b-diary .b-cal-dot { width: 7px; height: 7px; }
.b-diary .b-cal-week { padding: 4px 0 8px; }
.b-diary .b-cal-wd { font-size: var(--t-ui); }
.b-diary .b-cal-day.is-today .b-cal-num::after {
  bottom: -9px; width: 4px; height: 4px; margin-left: -2px;
}
/* The day beside it is a column of its own now rather than a strip under a
   month, so its rows read at the size the rest of the product reads at. */
.b-diary .b-cal-cap { padding-top: 0; }
.b-diary .b-cal-etime, .b-diary .b-cal-ewhat { font-size: var(--t-ui); }
/* Under the fold for a second column: the month goes back over the day and
   the rule between them turns with them, which is the stacked default. */
@media (max-width: 1023.98px) { /* 1024 layout px */
  .b-diary .b-cal-panel { grid-template-columns: minmax(0, 1fr); }
  .b-diary .b-cal-side {
    padding-left: 0; margin-left: 0; padding-top: 12px; margin-top: 12px;
    border-left: 0; border-top: 1px solid var(--card-border);
  }
  .b-diary .b-cal-day { height: 60px; }
}

/* ══ 85. THE NUMBER THAT IS NEWS ═══════════════════════════════════════════
   `.b-cut-n` is how many are in a cut. It is a size, it is the same next
   week, and it is set quiet on purpose. This is the other kind of number a
   chip can carry: how many of them something happened to since you last
   looked — which is the only figure in that row worth crossing a room for.

   It is the bell's badge, rule for rule. A reader who has learnt what an
   accent pill at the top of the screen means has already learnt this, and
   a second treatment for the same idea would be a second thing to learn.
   Only the weight differs: `.ntf-count` sits in a panel head with room
   around it, and this one rides inside a 30px chip beside a dimmer number,
   so it takes the tighter padding a pill needs at that size.

   NOT DIMMED, which is the whole of it. The count beside it is at 0.65 and
   should be; a badge at 0.65 is a badge nobody sees, and one nobody sees
   is worse than none — it takes the room and pays nothing back. */
/* ══ AND A BORROWED TOKEN IS ONLY DECLARED WHERE IT WAS BORROWED ═════════
   Written first as `var(--qa-accent)` over `rgba(var(--qa-accent-rgb), .16)`,
   copying §28 — the bell's own count, which is what this badge is meant to
   look like. §28 is AiMY QA's CSS taken verbatim, and it works because the
   bridge at the top of it maps QA's token names onto this shell's. That
   bridge is declared on `.ntf-anchor`. A chip in the page is not inside it.

   So the two names resolved to NOTHING here, and a custom property with no
   value does not fall back to anything sensible: `color` went invalid at
   computed-value time and INHERITED, taking the chip's own ink, while
   `background` was simply dropped. The rule applied. The class was defined
   and used, so the audit passed. And the badge rendered as a bare number in
   the same colour as the count beside it.

   The bell is untouched — it is correct where it stands, and §28 says in
   its own first line that the rules in it are not to be edited. What was
   wrong was borrowing its names outside the scope that defines them. This
   uses the shell's own, and they were read out of the browser rather than
   out of the file, because the failure mode of a missing token is a rule
   that looks exactly like one that worked. */
.b-cut-new {
  margin-left: 6px; padding: 1px 6px; border-radius: 9999px;
  font-size: var(--t-micro); font-weight: var(--fw-bold); font-variant-numeric: tabular-nums;
  /* ══ FILLED, IN BOTH STATES ═════════════════════════════════════════
     The first cut of this gave the badge a tinted ground and took the
     ground away on the active chip, reasoning that an accent pill inside
     an accent-tinted chip would dissolve. On screen it did worse than
     dissolve: with no ground it is a number in accent ink sitting beside a
     number in the same accent ink at 0.65, and "Customers 31 9" reads as
     one figure somebody typed twice.

     Solid, always. A filled pill against a 15% ground is a different
     OBJECT rather than a different shade, which is the distinction that
     survives being glanced at — and it is what a badge is everywhere
     else, including the bell forty pixels above it. */
  background: var(--accent); color: #fff;
  /* The chip's own text sets the line box; without this the pill grows the
     chip by two pixels and the one chip carrying news sits taller than the
     six beside it. */
  line-height: 1.35;
}

/* ══ 86. THE CHIP THAT IS NOT A FILTER ═════════════════════════════════════
   Six chips narrow the forty-eight deals and sum to All. This one is
   thirty-one companies that are not among the forty-eight at all — it does
   not narrow the row's set, it replaces it — and drawn identically it was
   the row telling a reader that a customer is a place a deal stands.

   A GROUND WAS THE FIRST IDEA AND IT WAS ALREADY TAKEN. Written as "the
   chips are outlined and empty until you stand on one, so a fill says this
   one is different" — measured, `.filter-chip` at rest is a 5% white wash
   with a 10% edge. It has a fill. The ghost's own note beside it says as
   much, calling that fill the reason a ghost cannot sit in this row.

   So the difference is the one the doctrine actually ranks with: weight and
   ink. The six sit at `--d200` at the row's own weight; this sits at `--d50`
   at bold — a step of ink and a step of weight, which reads before any of
   the seven words do and survives being glanced at. Plus a mark, which is
   the glyph this build puts against a company on every other surface, and
   a rule that says where the arithmetic stops. */
/* `--card-border` was the first value and it is a 7% white: measured, a
   0.9px line at 7% beside a chip with a 10% edge of its own is not a rule,
   it is nothing. `currentColor` takes the page's ink — light on the dark
   theme, dark on the light one — so one value reads in both, and 0.18 of it
   is the weight of a divider rather than of a border. */
.b-cut-split {
  width: 1px; align-self: stretch; margin: 0 6px;
  background: currentColor; opacity: 0.18;
}
.b-cut-book { gap: 6px; color: var(--d50); font-weight: var(--fw-bold); }
/* The mark takes the row's own size rather than the 24 it is drawn in: an
   `<svg>` with no width and no rule reaching it fills its container, which
   on a chip is the whole chip. */
.b-cut-book svg { width: 14px; height: 14px; flex-shrink: 0; }
/* Standing on it, the library's active state owns the ink — two classes
   against one — and should: a selected chip is a selected chip whatever
   else it is. The weight and the mark carry the difference from there. */

/* ══ 87. EVERYTHING BOLD IS NOTHING BOLD ═══════════════════════════════════
   The deck under every AiMY figure on this desk was set at 700 — 800 in
   `sales.css`, pulled to 700 by §26b — for the whole paragraph. Every
   sentence that lands in it is written with the figures marked up:

     <b>€710k</b> of it sits in 14 deals with the price already on the
     table, <b>5</b> are past the date they should have landed and
     <b>2</b> have been handed to you and never warm-called.

   Three `<b>` in that one, and not one of them did anything, because bold
   inside bold is bold. The markup has been there since the sentence was
   written; the rule was quietly cancelling it on every surface — the deals
   take, the customer book, a campaign, a company, a person, the unsaved-list
   gate. A reader gets a wall at one weight and has to read all of it to find
   the two numbers it is about.

   The deck is not loud because it is heavy. It is loud because it is 18px in
   a page set at 14 and 15, and that is untouched. Dropping the prose to 400
   leaves the size doing the ranking it was always doing and gives the
   figures three hundred units of contrast to be found in — which is what
   `<b>` is for, and the only thing it can be for in a block that has already
   spent size on the heading above it.

   400 rather than 500: measured at 18px, a 500-to-700 step reads as a
   slightly firmer word, and the point of these marks is that somebody
   glancing at the block sees the numbers without reading the sentence. */
.s-lead-deck { font-weight: var(--fw-medium); }
/* Reaching the child directly, so §26b and the shell's own rule on the
   paragraph cannot inherit over it. No colour: the ink is already at the
   top of the ramp, and a mark that changes two things at once is a second
   rank nobody asked for. */
.s-lead-deck b, .s-lead-deck strong { font-weight: var(--fw-semibold); }

/* ══ 88. THE DECK WAS BALANCING ITSELF INTO A COLUMN ═══════════════════════
   `text-wrap: balance` on the deck, from the shell. It is a HEADING rule:
   the browser evens out the line lengths of a short block so the last line
   is not a stub, and the way it does that is by pulling the whole block in
   from its own max-width.

   Measured on a customer's reading — six lines at 18px, `max-width` 557px,
   rendering at 474. Eighty-three pixels of measure given back to nothing,
   inside a section 642px wide, so the paragraph sat as a narrow even-edged
   column with a quarter of the block empty beside it. That is the "max
   width" complaint, and the max-width was never the cause.

   `pretty` is the body-copy one: it fixes orphans and leaves the measure
   alone. And 58ch rather than 52, because at 18px in a 642px block the
   shorter measure was leaving a gutter wide enough to read as a mistake.

   THE LEADING WITH IT. 1.3 at 18px is a heading's leading, and it is what
   the multi-line decks were set in — six lines of 18px at 1.3 is the
   "cramped" half of the same complaint. `--lh-base` is 1.45 and is what
   every other paragraph in the build reads at. */
.s-lead-deck { text-wrap: pretty; }
.s-home .s-lead-deck, .b-lead-slim .s-lead-deck { max-width: 58ch; line-height: var(--lh-base); }

/* ══ THE CONCLUSION IS NOT THE SETUP ═══════════════════════════════════════
   A customer's reading is a finding — what happened and what it means — and
   then what of ours follows from it, sometimes two things. As one paragraph
   the conclusion arrived as the fifth line of six in the same ink at the
   same weight as the setup, and the only part that says what to DO was the
   hardest part to find.

   FIRST ATTEMPT RANKED ON ONE AXIS AND IT WAS NOT ENOUGH. Deck at 400,
   conclusion at 500, both at 18px: a hundred units of weight at one size,
   which at a glance is three slabs of the same text separated by rules. The
   doctrine ranks on size, weight and case, and only one of the three was
   doing anything.

   The build already has this exact relationship and it ranks on three.
   `.b-nm-say` is the instruction in the relationship block — 16px, semibold,
   `--d50` — and `.b-nm-then` is what follows it: `--t-ui`, `--d200`, with
   `b` at `--d50` and bold. Four points of size, a step of ink, and three
   hundred units of weight between the prose and the names in it.

   So this follows that, which is also why it needs no new argument: a
   reader who has met one of these has met both. The finding keeps the deck
   at 18. What follows drops to 14 and one step down the ink ramp — not
   below it: `--d200` is the floor for type and this sits on it. The product
   names are the thing to come away with, so they take the bold AND the
   brightest ink, which makes them the only marked thing in the lower half
   of the card and findable without reading a word around them.

   NOT COLOUR, on any of it. An offer is not a verdict, and this build
   spends its two hues on things that went well or badly. */
.b-lead-thens {
  margin: 0 0 16px; padding-top: 13px;
  border-top: 1px solid var(--card-border);
  display: flex; flex-direction: column; gap: 9px;
}
.b-lead-then {
  display: flex; align-items: flex-start; gap: 9px;
  max-width: 74ch; margin: 0;
  font-size: var(--t-ui); font-weight: var(--fw-medium);
  line-height: var(--lh-base); color: var(--d200); text-wrap: pretty;
}
/* 74ch rather than the deck's 58: a measure is a count of characters and
   these are four points smaller, so the same 58 would have set them in a
   column two thirds the width of the paragraph above and reintroduced the
   ragged gutter §88 exists to remove. */
.b-lead-then b { font-weight: var(--fw-semibold); color: var(--d50); }
/* The glyph is drawn on a 24 grid and has no size of its own; without this
   it fills the flex item, which is the width of the line. It follows the
   text down a size, and `margin-top` optically centres it on the first line
   rather than on the box. */
.b-lead-then svg { width: 15px; height: 15px; flex-shrink: 0; margin-top: 3px; }

/* ══ 89. THE CLOCK IN A ROW OF FACTS, WHICH HAS NEVER BEEN DRAWN ═══════════
   `.b-due` is written onto the check-in on an account's masthead and onto
   the renewal beside it, and no sheet has ever defined it. Measured on a
   prospect: the element renders at the ink and weight of the row it sits
   in, which is to say it renders as the row it sits in. "Check-in overdue
   by 9 days" has never once been amber.

   The audit could not see it, and the reason is its own documented blind
   spot one rung along: the only usage was inside a CONCATENATED attribute —
   `class="b-due' + (late ? ' is-late' : '') + '"` — which the scan cannot
   read as a class list. Writing a second, static one exposed the first. A
   class assembled at runtime is invisible to a check that reads source.

   Quieter than `.b-nm-due`, which is the same idea as a chip inside the
   relationship block: this one sits in a row of plain facts separated by
   middots, so a pill here would be the only boxed thing on the line and
   would read as a control. The word carries it — weight at rest, and the
   warn ink when the date has gone. `--warn-text` rather than `--warn`,
   which is the token the build uses for a warn hue set as TYPE. */
.b-due { font-weight: var(--fw-semibold); }
.b-due.is-late { color: var(--warn-text); }

/* ══ 90. THE REST OF THE TEAM, AS ONE THING ════════════════════════════════
   `.b-team` lays a face, a name and a job flat and wraps. Measured on a crew
   of seven it is three rows deep and 108px tall, and the block saying who
   works this campaign became the tallest thing on the page after the
   reading. Three, then the rest as a stack.

   THE OVERLAP IS THE ARGUMENT, not the decoration. Faces set apart are a
   list of people; faces set over each other are one object, a group — which
   is exactly what the hidden remainder is, and why every product that has
   ever had assignees arrives at this shape. `margin-left` negative by a
   third of the face, and each one carries a ring in the ground colour so
   the edges stay separate where they cross. `direction: rtl` on the row
   with `ltr` back on each face: it makes the FIRST face sit on top without
   a z-index per child, which is what stacking order should look like when
   the eye reads left to right.

   IT IS A CONTROL AT REST, so it takes the chip's hairline and ground —
   `.filter-chip` is the shape this build uses for a small pressable thing,
   and a bare stack of faces beside three named colleagues reads as more
   colleagues rather than as a way in. */
.b-stack-wrap { display: inline-flex; }
.b-stack {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 3px 8px 3px 5px; border-radius: 9999px;
  background: rgba(255, 255, 255, 0.05); border: 1px solid var(--card-border);
  cursor: pointer; color: var(--d200);
  transition: background-color var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
:root[data-theme="light"] .b-stack { background: rgba(16, 24, 40, 0.05); }
.b-stack:hover { color: var(--d50); border-color: rgba(var(--accent-rgb), 0.4); }
.b-stack:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.b-stack-faces { display: inline-flex; align-items: center; direction: rtl; }
.b-stack-face {
  direction: ltr; display: inline-grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  /* The ring is the page behind the stack rather than a colour of its own,
     so a face overlapping another is cut out of it cleanly in both themes.
     `--page-bg` was the name it was written with and this build has no such
     token, so every ring fell to the hard-coded dark and stayed dark on the
     light theme — a black edge round each face on a white ground. Read out
     of the browser: `--body-bg`, #171a1c against #f4f6f9. */
  box-shadow: 0 0 0 2px var(--body-bg);
  overflow: hidden; flex-shrink: 0;
}
.b-stack-face + .b-stack-face { margin-right: -9px; }
.b-stack-face svg, .b-stack-face img { display: block; width: 100%; height: 100%; }
/* The count and the plus are the two faces this stack does not have: same
   circle, same ring, a ground and a figure instead of a person. */
/* The count is the one face this stack does not have: same circle, same
   ring, a ground and a figure instead of a person. There was a plus here
   too, for the case where the stack hid nobody and was still the way to
   change the crew — the stack does not do that any more, so a stack with
   nothing in it is not drawn at all. */
.b-stack-n {
  background: var(--card-bg-raised, rgba(255, 255, 255, 0.09));
  border: 1px solid var(--card-border);
  font-size: var(--t-micro); font-weight: var(--fw-bold); color: var(--d100);
  font-variant-numeric: tabular-nums;
}
.b-stack-chev { flex-shrink: 0; opacity: 0.7; }

/* ══ AND WHERE THERE ARE NO FACES IT IS A NODE ═════════════════════════════
   The lozenge above is right among `.b-mate` — a face and two lines, no box
   around them. The account draws this in a row of `.b-node`, which are
   cards, and a flex row stretches its children: measured, the pill came out
   61 wide by 64 tall at a 9999px radius, which is not a pill. It is a blob
   with a small circle and a chevron rattling inside it.

   So it is a node. The node's radius, ground, border and padding, hovering
   the way a node hovers, with the count as its whole content — one more
   door in a row of doors, which is what it is. Stretching is right here and
   was only ever wrong at a pill radius: the row sets every node to the
   height of the tallest, and this one joins them. */
.b-stack.is-count {
  gap: 7px; padding: 10px 14px; border-radius: 12px;
  background: var(--card-bg); border: 1px solid var(--card-border);
  color: var(--d100); font-size: var(--t-ui); font-weight: var(--fw-semibold);
}
.b-stack.is-count:hover {
  border-color: var(--accent); background: var(--card-bg-raised); color: var(--d50);
}
.b-stack.is-count:active { transform: scale(0.985); }
.b-stack-count { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ══ AND THE ONE THAT TAKES SOMEBODY OFF ══════════════════════════════════
   Beside the name rather than in a list of its own, which is the whole
   argument: the team is already drawn, so a second list of who is on it
   exists only to hang a verb from. Quiet until the row is under the hand —
   a cross per colleague, lit, on a block whose job is to say who works
   this, would read as a row of things to dismiss.

   It is not hidden, which is the other half of that. `opacity: 0` on a
   control is a control a reader cannot find and a keyboard cannot skip
   sensibly; this one sits one step down the ink ramp and comes up on
   hover, on focus, and whenever the row it is on is hovered. */
.b-crew-x {
  display: inline-grid; place-items: center; flex-shrink: 0;
  /* ══ AND THEY LINE UP ══════════════════════════════════════════════════
     `margin-left: 2px` put each cross immediately after the name block, so
     seven of them landed at seven different x positions — one per name
     length — and the column that should have read as a single edge read as
     a ragged one. `auto` takes the rest of the row, which is what makes
     them a column: the rows are all the same width, so the end of every row
     is the same place. */
  width: 20px; height: 20px; margin-left: auto; border-radius: 6px;
  border: 0; padding: 0; background: none; cursor: pointer;
  color: var(--d300);
  transition: color var(--t-fast), background-color var(--t-fast);
}
.b-crew-x svg { width: 13px; height: 13px; }
.b-mate:hover .b-crew-x, .b-menu-row:hover .b-crew-x { color: var(--d100); }
.b-crew-x:hover { color: var(--err-text); background: var(--err-bg); }
.b-crew-x:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* The row it sits in is a `div` of two text lines; the cross has to sit
   with them rather than under them. */
.b-mate { position: relative; }

/* ══ THE CAPTION AND THE VERB SHARE A ROW ══════════════════════════════════
   `.b-team` is a wrapping flex row and the caption takes the whole first
   line of it. The verb that changes the crew belongs beside the caption
   rather than out among the faces, where it would read as a colleague — so
   the two share a line of their own and the faces start under it. */
.b-team-head {
  flex-basis: 100%; display: flex; align-items: baseline;
  justify-content: space-between; gap: 12px; margin-bottom: 2px;
}
.b-team-head .b-team-cap { margin-bottom: 0; }
/* The verb is two words and it broke across them: `.b-team-head` inherits
   the shrink a flex child has by default, so at `space-between` across the
   block's full width the button was the thing that gave. A control never
   wraps — the row it is on can. */
.b-team-head .b-menu-wrap { flex-shrink: 0; }
.b-team-head .b-menu-open { white-space: nowrap; }

/* A row in a menu that is a disclosure rather than a chooser. `.b-menu-item`
   carries the hover and the cursor because everything else in a menu here
   does something; these name the people the stack could not fit, and this
   build has no page for a colleague to open. */
.b-menu-row { cursor: default; }
.b-menu-row:hover { background: transparent; color: var(--d100); }
/* The face in a menu row, at the size the stack draws it. */
.b-team-menu .b-menu-item > svg, .b-team-menu .b-menu-item > img,
.b-menu-row > svg, .b-menu-row > img {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
}

/* ══ 91. THE CHAT COLUMN IS NOT HERE ═════════════════════════════
   It was, briefly, and under this build’s own prefix — a second
   implementation of a component sales.css already carried under the names
   AiMY Knowledge gives it, one cut behind. Two families styling one column
   is how a stylesheet ends up with a winner nobody chose.

   So the column went back to where it lives, beside the region that sets
   its width, and came up to Knowledge’s current cut while it was there:
   sales.css, THE CHAT COLUMN IS AiMY KNOWLEDGE’S, LIFTED WHOLE. Nothing
   about it is this build’s to override, so this file has no rule for it.
   §38 was the caption-and-row pair that stood before either. */

/* ══ 92. THINKING HAS NO FRAME, FROM AiMY KNOWLEDGE ═══════════════════
   The canvas had no wait at all: asking inside it skipped the card, and what
   stood in for it was 720ms of nothing. It has one now, as a turn, and these
   are Knowledge's four rules for it — character for character, comments and
   all, the way §28 and the chat column take theirs. Everything they hang on
   is the shell's: `.chat-msg`, `.msg-bubble`, `.msg-avatar`, `.ai-thinking`
   and its label are in aimy-ds.css, and `.think-mark` is §47 above.

   Not taken: `.chat-msg.aimy:has(.ai-thinking) .msg-acts`. A turn here has no
   actions row to hide. */

/* ══ THINKING HAS NO FRAME ═════════════════════════════════
   No bubble and no avatar while there is nothing to put in them. The classes
   stay on the same elements throughout — only this rule comes off — so the
   frame appears around an answer that is already streaming rather than the
   message being rebuilt to gain one. */
.chat-msg.is-thinking .msg-avatar { display: none; }
/* ══ UNDRESSING THE BUBBLE TAKES TWO SELECTORS ═════════════════
   The bubble is dressed TWICE in the library: once as `.chat-msg.aimy
   .msg-bubble` for the default dark theme, and again as `:root[data-theme=
   "light"] .chat-msg.aimy .msg-bubble` for light, which is five class-level
   selectors deep.

   The single rule here was three deep, so it beat the dark one and lost to
   the light one — and lost only PARTLY, which is why this was easy to miss.
   Padding and shadow are not set by the light rule, so those cleared; the
   background and border are, so a white pill with a hairline survived on a
   state that is meant to have no container at all. Dark mode was correct the
   whole time and light mode was not, from one rule that half-applied.

   So the light theme is answered on its own terms. Not `!important`, which
   would win here and then have to be fought later, and not a bare `:root`
   prefix to pad the count — the second selector says which rule it exists to
   override, which is the thing a reader needs to know. */
.chat-msg.is-thinking .msg-bubble,
:root[data-theme="light"] .chat-msg.aimy.is-thinking .msg-bubble {
  background: none;
  border-color: transparent;
  padding: 0;
  box-shadow: none;
}
/* The frame fades up rather than snapping on, so the first words are not
   interrupted by a box drawing itself around them.

   `:not(.is-thinking)` is doing real work here. A transition belongs to the
   state being entered, so declaring it on both states animates the frame OUT
   as well as in — and since `push` reads layout before the class is applied,
   the browser resolves the framed style first and then fades it away. The
   frame appeared for an instant and dissolved, which is the opposite of the
   intent. Declared only on the resting state, entering the thinking state is
   instant and leaving it fades in. */
.chat-msg.aimy:not(.is-thinking) .msg-bubble,
.chat-msg.aimy:not(.is-thinking) .msg-avatar {
  transition: background-color var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease-out),
              opacity var(--t-base) var(--ease-out);
}
.chat-msg.is-thinking .msg-avatar { opacity: 0; }
/* Same lift, same omission. The frame still appears and the avatar still
   comes back — only the fading does not, which is the whole of what the
   preference asks for. */
@media (prefers-reduced-motion: reduce) {
  .chat-msg.aimy:not(.is-thinking) .msg-bubble,
  .chat-msg.aimy:not(.is-thinking) .msg-avatar { transition: none; }
}

/* ══ 93. THE PHONE RINGING ═══════════════════════════════════════════
   The one surface in this build that the user did not ask for and cannot
   defer. Everything else here appears because somebody pressed something;
   this appears because somebody else did, and it has a deadline.

   SO IT IS THE BANNER AND NOT THE PANEL. A ringing call is answered from
   wherever you are — mid-canvas, mid-scroll, three rows into a queue — so
   it cannot be a region that reflows the shell, and it cannot wait for a
   surface to close. It is fixed, it is centred on the WINDOW the way the
   composer and the toast are, and it sits under the topnav rather than over
   it: covering the chrome would take the mark, the tabs and the bell away
   for the length of a ring, and nothing about answering a phone needs them
   gone.

   THE LAYER. 970, one above the toast's 960 and two above the prototype
   panel's 950. A receipt for something that already happened must not cover
   a phone that is still ringing, and a prototype control never covers
   anything. */
.b-ring-host {
  position: fixed;
  top: calc(var(--topbar-height) + 12px);
  left: 50%;
  z-index: 970;
  /* The host holds the centring and the card holds the motion, so the
     entrance's transform is never fighting a translate that positions it. */
  transform: translateX(-50%);
  /* Empty most of the time, and an empty fixed box across the top of the
     window would eat the first click on everything under it. */
  pointer-events: none;
}
.b-ring-host:empty { display: none; }

.b-ring {
  pointer-events: auto;
  width: 384px; max-width: calc(var(--vp-w) - 32px);
  border-radius: var(--r-2xl);
  background: var(--composer-skin);
  border: 1px solid var(--composer-edge);
  /* The panel elevation, because this is further off the page than anything
     else on it — plus the inner top hairline the composer uses, which is
     what makes the two read as the same material rather than as two boxes
     of the same colour. */
  box-shadow:
    0 16px 48px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(0, 0, 0, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
  overflow: hidden;
}
:root[data-theme="light"] .b-ring {
  box-shadow:
    0 16px 48px rgba(16, 24, 40, 0.16),
    0 0 0 1px rgba(16, 24, 40, 0.04);
}

.b-ring-main { display: flex; align-items: center; gap: 12px; padding: 14px 16px; }

/* ── Who it is ──
   Initials, because there is not a photograph anywhere in this product and
   a grey silhouette is a photograph that failed. The unknown caller gets a
   question mark on a flat ground rather than the avatar gradient: the
   gradient means we hold a person, and we do not. */
.b-ring-av {
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--grad-avatar); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-sm); font-weight: var(--fw-bold);
  flex-shrink: 0; position: relative;
}
.b-ring-av.is-unknown {
  background: var(--d700); color: var(--d200);
  font-size: var(--fs-lg); font-weight: var(--fw-bold);
}
/* THE RING IS NOT A DECORATIVE PULSE. The doctrine's Never list names
   pulsing decorative dots, and this is the other kind: `.call-live` in
   sales.css already pulses for exactly this reason, with the argument that
   it is the one thing on the surface true RIGHT NOW rather than true as of
   the last paint. A phone that is ringing is the same claim, and it stops
   the moment the claim does. */
.b-ring[data-state="ringing"] .b-ring-av::after {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid var(--brand); opacity: 0.55;
  animation: b-ring-pulse 1.6s var(--ease-out) infinite;
}
@keyframes b-ring-pulse {
  from { transform: scale(0.86); opacity: 0.55; }
  to { transform: scale(1.5); opacity: 0; }
}

.b-ring-id { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.b-ring-name {
  font-size: var(--t-lead); font-weight: var(--fw-bold); color: var(--d50);
  line-height: var(--lh-tight);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A number is a number: tabular, so two callers' numbers do not wobble
   against each other across two rings. */
.b-ring-sub {
  font-size: var(--t-micro); color: var(--d200); margin-top: 3px;
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* `--d200` and not below it. This line says which of the two states the
   card is in, and a state set at caption weight under its own subject is
   the footnote problem the briefing already argued its way out of. */
.b-ring-state {
  font-size: var(--t-micro); font-weight: var(--fw-semibold);
  color: var(--d200); margin-top: 2px;
  text-transform: uppercase; letter-spacing: var(--ls-wide);
}

/* ── Answer and decline ──
   Green answers and red refuses because every phone anybody has held for
   twenty years does — the same Jakob's Law argument this component already
   makes for the handset glyphs themselves, and the one part of the old note
   here that survives.

   THEY WERE SATURATED FILLS WITH A WHITE GLYPH, in a green and a red this
   product named for itself because `--ok` is a mint and `--err` a salmon and
   neither can carry white: they measure 1.83:1 and 2.63:1 against it, where
   a glyph owes 3:1. The fills cleared that floor and the argument for them
   was entirely that floor.

   Tinted, the measurement runs the other way — the hue against the card
   rather than white against the hue — and the family's own values clear 3:1
   there in both themes. So these are `--ok` and `--err` now, as glyph and
   edge over a 16% wash of themselves, which is the treatment this build
   already uses for a tool that is doing something. Two literals and a
   product-local name went with them.

   What is lost is loudness: a tinted circle is quieter than a fill, and
   these are still the two most consequential presses in the build. What is
   gained is that they theme, and that answer and decline are the same green
   and red as every other verdict on the surface. `.call-go` in the rail is
   still brand and still not green — dialling somebody you picked off a queue
   is not a phone going off in front of you. */
.b-ring-acts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.b-ring-btn {
  width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid transparent;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-fast) var(--ease-out);
}
.b-ring-btn svg { width: 19px; height: 19px; display: block; }
.b-ring-btn.is-no {
  color: var(--err); border-color: var(--err);
  background: color-mix(in srgb, var(--err) 16%, transparent);
}
/* The handset turned down, which is what declining looks like on every
   phone anybody has held. Rotating the same glyph rather than drawing a
   second one keeps the two buttons visibly one pair. */
.b-ring-btn.is-no svg { transform: rotate(134deg); }
.b-ring-btn.is-yes {
  color: var(--ok); border-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 16%, transparent);
}
/* Both deepen their own wash. There is no fill left to darken, and raising
   the tint keeps the hue — which is the whole of what tells these two
   buttons apart — at full strength on the glyph while the circle gains
   weight under the cursor. */
.b-ring-btn.is-no:hover { background: color-mix(in srgb, var(--err) 30%, transparent); }
.b-ring-btn.is-yes:hover { background: color-mix(in srgb, var(--ok) 30%, transparent); }
.b-ring-btn:active { transform: scale(0.94); }
.b-ring-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ── What AiMY thinks they want ──
   The five seconds before you decide to pick up. `callPrep` already writes a
   full brief, but it does that AFTER you answer, and by then the question
   has changed from "why is this person ringing me" to "what do I say". This
   is the first question, and it has to be answerable at a glance.

   It is drawn only when there is something true to say. A strip that fills
   itself with a hedge on a contact with no history is a strip people learn
   to skip, and then the one time it knows something it is skipped too. */
.b-ring-read {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 16px 12px;
  border-top: 1px solid var(--hairline);
}
.b-ring-mark { width: 13px; height: 15px; flex-shrink: 0; margin-top: 1px; }
/* Not muted. This is the payload of the card — the conclusion AiMY reached,
   which is the thing the whole strip exists to carry — and setting the
   conclusion lighter than the phone number above it would rank the reading
   below the fact it was read from. Two lines is the cap: past that it is a
   brief, and a brief has a surface of its own. */
.b-ring-said {
  font-size: var(--t-small); line-height: var(--lh-snug);
  color: var(--d100); font-weight: var(--fw-medium);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
/* The step, the obstacle or the offering the sentence is about. It is the
   one word a caller scans this strip for, and the rest of the build already
   sets a next step in bold inside its own sentence. */
.b-ring-said b { font-weight: var(--fw-semibold); color: var(--d50); }

/* ── Answered, on the way out ──
   The card does not vanish the instant it is pressed. It becomes the thing
   it is handing off to — name and a running clock, the shape the call rail
   is about to draw at full size — holds that for a beat so the handoff is
   something you saw rather than something you inferred, and leaves. */
.b-ring[data-state="live"] .b-ring-acts,
.b-ring[data-state="live"] .b-ring-read,
.b-ring[data-state="live"] .b-ring-state { display: none; }
.b-ring[data-state="live"] .b-ring-sub { color: var(--d100); }

/* ── Arriving, and leaving ──
   Down from above, because that is where it came from and where dismissing
   sends it. `--t-panel` in and `--t-open` out: a thing that interrupts you
   should arrive deliberately enough to be read before it is acted on, and
   get out of the way faster than it came.

   From 0.97 and not from 0 — an element that scales up from nothing has no
   shape to come from. The blur is on `filter` and not `backdrop-filter`:
   animating the backdrop re-samples the whole shell every frame. */
.b-ring {
  animation: b-ring-in var(--t-panel) var(--ease-out);
}
@keyframes b-ring-in {
  from { opacity: 0; transform: translate3d(0, -14px, 0) scale(0.97); filter: blur(3px); }
  to { opacity: 1; transform: none; filter: blur(0); }
}
/* ══ AND THE CALL COLUMN ARRIVES FROM THE EDGE IT CAME FROM ════════════════
   Pressing Call put a 300px column into the flex row and the page was simply
   narrower on the next frame. The column itself arrives now — eight pixels
   in from the right, 200ms, on the same curve as everything else. From the
   right rather than from below, for the reason the ring comes down from the
   top: it entered at that edge, and an entrance that ignores where a thing
   came from is a fade with extra steps.

   ONLY THE COLUMN, AND ONLY ONCE. The page narrowing around it is a layout
   change — a flex sibling appearing and a composer recomputing its width off
   --fb-reserve — and there is no honest way to tween that. Animating a width
   is layout on every frame, and FLIPping it means scaling live text
   horizontally for a quarter of a second. It is a cut, and it should be.

   The canvas behind it does not move either, though `body.is-calling` shifts
   it 300px. `.aimy-overlay` carries backdrop-filter: blur(28px) over the
   whole viewport, and §93 already says why that cannot be animated: moving
   it re-samples the entire shell every frame. A jump is cheaper than that,
   and the canvas is on its way out of the reader's attention anyway.

   `is-opening` is put on for the paint that mounts the panel and taken off
   by the next one, so the four state changes a call goes through — ready,
   connecting, live, logging — rebuild it without it arriving again. It goes
   on `#callPanel` itself, which IS the column: the host carries `.app-call`
   rather than containing it, so this is one element and not a descendant. */
.app-call.is-opening {
  animation: b-call-in var(--t-base) var(--ease-out) both;
}
@keyframes b-call-in { from { opacity: 0; transform: translateX(8px); } }
@media (prefers-reduced-motion: reduce) {
  .app-call.is-opening { animation: none; }
}
.b-ring.is-leaving {
  animation: b-ring-out var(--t-open) var(--ease-out) forwards;
  pointer-events: none;
}
@keyframes b-ring-out {
  from { opacity: 1; transform: none; filter: blur(0); }
  to { opacity: 0; transform: translate3d(0, -10px, 0) scale(0.98); filter: blur(2px); }
}

/* The preference asks for less motion, not for less information. The card
   still arrives and still leaves — instantly — and the ring around the
   avatar HOLDS rather than going away: it is the only thing on the card
   saying the phone is ringing right now, and removing it would answer the
   preference by deleting the state. */
@media (prefers-reduced-motion: reduce) {
  .b-ring, .b-ring.is-leaving { animation: none; }
  .b-ring-btn { transition: none; }
  .b-ring[data-state="ringing"] .b-ring-av::after {
    animation: none; opacity: 0.55; transform: scale(1.12);
  }
}

/* The phone is 384px and the narrow shell is not, so it takes the gutter the
   rest of the build takes and nothing else changes: the same card, the same
   two buttons, at the width there is. */
@media (max-width: 459.98px) { /* 460 layout px */
  .b-ring-main { padding: 12px 12px; gap: 10px; }
  .b-ring-read { padding: 8px 12px 10px; }
}

/* ══ WHICH WAY THE CALL WENT ═══════════════════════════════
   Inside the outcome's own span on both timelines, so it takes the tone of
   the row it belongs to rather than carrying a second colour of its own, and
   so it can never wrap away from the word it qualifies.

   `em` rather than px: the two rows set the outcome at different sizes and a
   fixed 12px glyph was a third of a line tall on one and half of it on the
   other. Optical, not geometric — the arrow's mass sits low in its box, so a
   baseline alignment floats it. */
.b-dir {
  display: inline-flex; align-items: center;
  margin-right: 4px; vertical-align: -0.1em;
}
.b-dir svg { width: 1em; height: 1em; stroke-width: 2.25; }

/* ══ THE ROW THE DOOR STOOD ON IS GONE WITH IT ═══════════════════
   `.s-starts-head` was a flex row holding the Start caption and, at its right
   edge, the door onto the call log — plus a reserved 28px so the four opener
   cards did not jump when the last missed call was dealt with. The log stands
   in the rail now, so the row holds one caption, and a flex container around
   a single child is a container with nothing to arrange. The caption is back
   on its own `margin-bottom` in sales.css, which is the gap the row borrowed.

   Both rules are deleted rather than left standing: a rule that matches
   nothing is the kind of dead CSS this file cannot see from the inside. */

/* ══ THE COUNT IS THE PAYLOAD, SO IT IS NOT SET IN THE QUIET INK ═════
   The bell beside it carries a 6px dot, which says THERE IS SOMETHING. This
   says how many, because how many is the whole of what the control is for,
   and a number is no use at `--ink-muted` next to a label that outweighs it.

   A filled pill rather than coloured text: `--err` as ink measures 4.37:1 in
   dark and 3.82:1 in light and fails AA at both, which is the reason
   `--err-text` exists. On a fill the contrast is the fill's to answer for — and
   the ink is the PAGE, not white. `--err` resolves to a light coral in dark
   theme and a deep red in light, so no one fixed ink can serve both: white
   measured 2.63:1 on the dark theme's fill, which fails AA outright, and the
   first draft of this rule shipped it. `--body-bg` inverts with the theme the
   fill does — the trick `.bell-dot` already uses for its ring — and measures
   6.65:1 dark, 7.0:1 light. Tabular figures so 1 and 11 are the same
   width — the badge is redrawn on every paint and a number that changes
   width nudges the label under the cursor. */
/* The gap between the words and the count, on whichever button is carrying
   them: the pill in the briefing had it, and the diary's nav button — which
   centres its content and declares no gap of its own — needs the same one.
   The nav button is 32px against the pill's 28, so the badge gets the 4px of
   right padding back that a 14px inset would otherwise put beyond it. */
.b-missed-door { gap: 7px; }
.b-cal-btn.b-missed-door { padding-right: 10px; }
.b-missed-n {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 5px;
  /* --badge-ground, not --err: --err is #ff7282 on this theme and white on
     it is 2.64:1. The ground has to come down for the ink to come up, and
     the reasoning and the two bounds are at --badge-ground in sales.css. */
  border-radius: var(--r-pill); background: var(--badge-ground); color: #ffffff;
  font-size: var(--fs-2xs); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums; line-height: 1;
}

/* ══ THE CALL LOG ═════════════════════════════════════
   A row is pressable end to end and carries no edge of its own — the block is
   already a surface, and seventeen bordered rows inside it is a table drawn by
   hand. `.b-owed` next door makes the same call for the same reason; this is
   not that component because the anatomy is different: a glyph chip instead of
   a severity dot, and three lines instead of two.

   `flex-start` on the row, not `center`. The chip belongs beside the NAME —
   the way a phone draws it — and centring it against a three-line body floats
   it halfway down the meta. The verb is the exception and centres, because it
   is the row's own action rather than a part of what the row says. */
.b-log { display: flex; flex-direction: column; }
/* The day headings are flex items of the list they head. Same margins the
   feed gives them, and the same nought on the first, or the list opens with
   fourteen pixels of nothing under a section head that already left twelve. */
.b-log > .b-month { margin: 14px 0 6px; }
.b-log > .b-month:first-child { margin-top: 0; }
/* The entrance belongs to the ITEM, not the row: a voicemail strip arriving
   flat under a row that is still fading in is one event drawn as two. */
.b-log-item {
  animation: b-surface-in var(--t-settle) var(--ease-out) both;
  animation-delay: calc(min(var(--i, 0), 8) * 40ms);
}
@media (prefers-reduced-motion: reduce) { .b-log-item { animation: none; } }
.b-log-row {
  display: flex; align-items: flex-start; gap: 12px; width: 100%; text-align: left;
  padding: 10px 12px; border: 0; border-radius: var(--r-md);
  background: none; cursor: pointer; font-family: inherit;
  transition: background-color var(--t-fast);
}
.b-log-row:hover { background: rgba(var(--accent-rgb), 0.10); }
.b-log-row:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* ══ THE GLYPH CARRIES THE TONE BECAUSE THE WORD CANNOT ════════════
   `--err` measures 4.37:1 in dark and 3.82:1 in light and fails AA as type —
   which is the whole reason `--err-text` exists and why no status word in this
   build is tinted. A non-text graphic answers to 3:1 instead, so the colour
   goes on the chip and "Missed" is set in plain ink beside it. The ground is
   `--err-bg` / `--warn-bg`, which the design system already defines per theme,
   rather than an alpha invented here.

   34px: the chip is a marker, not an avatar. `.b-ring-av` is 44 because a
   ringing phone has one subject and this has seventeen. */
.b-log-ico {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  margin-top: 1px;
}
.b-log-ico svg { width: 16px; height: 16px; }
.b-log-ico.is-missed { background: var(--err-bg); color: var(--err); }
.b-log-ico.is-meet { background: var(--warn-bg); color: var(--warn); }

.b-log-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.b-log-name {
  font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--ink-primary);
}
/* The number, the direction and the hour, at the floor for type and no lower.
   Tabular figures because a column of phone numbers and clock times that do
   not line up is a column nobody can scan — which is the one thing a log is
   for. */
.b-log-meta {
  margin-top: 1px; font-size: var(--fs-2xs); color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
/* What AiMY makes of it, and the reason this is not just a phone's list. Set
   ABOVE the meta, not below it: the meta is chrome every row has and this is
   the payload only some rows have. */
/* ══ THE READING SAYS WHOSE IT IS ═══════════════════════════
   `flex-start` and a nudge down, the way `.b-aimy-mark` sits: the mark aligns
   to the first line's cap height rather than floating in the middle of a
   sentence that wraps to two. `currentColor` so it takes the line's own ink
   instead of introducing a third colour into a three-line row. */
.b-log-say {
  display: flex; align-items: flex-start; gap: 6px;
  margin-top: 4px; font-size: var(--fs-xs); color: var(--ink-secondary);
  line-height: var(--lh-snug);
}
.b-log-mark { flex: none; margin-top: 3px; color: currentColor; opacity: 0.9; }
/* ══ THE VERB IS A CONTROL, SO IT LOOKS LIKE ONE ═════════════════
   It was accent-coloured text, which is this build's TERTIARY tier — right at
   the end of a sentence, wrong at the end of a row that is itself the press.
   Seventeen of them down a page read as seventeen links to seventeen places
   rather than as the one thing each row does.

   A pill with its glyph: the shape of the secondary control the rest of the
   build uses, at a weight that does not compete with a real one — tinted
   ground rather than the filled `--brand`, because the row is not the primary
   action on the surface, it is one of seventeen.

   It lights on the ROW's hover, not its own. Its own would be a second hover
   target inside a single control and would say, wrongly, that the pill is the
   part you have to hit.

   `--accent-text`, not `--accent`. Measured over this pill’s own composited
   ground, `--accent` came back 3.52:1 and fails AA as type — which is the
   whole reason sales.css carries a second accent whose comment reads “5.04:1
   — an action, not a status”. Measured on this pill: 4.76:1 dark, 5.27:1
   light. The GLYPH beside it could have kept the raw accent, since a
   non-text graphic answers to 3:1 — but one control speaking in two purples
   could not. */
.b-log-go {
  flex: none; align-self: center; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 28px; padding: 0 11px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--accent-rgb), 0.28);
  background: rgba(var(--accent-rgb), 0.08);
  font-size: var(--fs-2xs); font-weight: var(--fw-bold); color: var(--accent-text);
  transition: background-color var(--t-fast), border-color var(--t-fast);
}
.b-log-go svg { width: 13px; height: 13px; flex: none; }
.b-log-row:hover .b-log-go {
  background: rgba(var(--accent-rgb), 0.18);
  border-color: rgba(var(--accent-rgb), 0.45);
}
@media (prefers-reduced-motion: reduce) { .b-log-go { transition: none; } }
/* The tabs sit between the section head and the reading, and the head already
   carries its own 12 underneath. */
.b-log-tabs { margin-bottom: 14px; }

/* ══ AND ON A PHONE THE VERB GOES UNDER THE ROW, NOT OFF THE EDGE ════
   Three lines of body plus a verb on the right is four columns of content in
   a 360px window. The row stacks instead of truncating: the rule this build
   holds is that when space runs out the component changes form, and a verb
   clipped at the right edge is a capability deleted. */
@media (max-width: 619.98px) { /* 620 layout px, the width `.s-starts` reflows at */
  .b-log-row { flex-wrap: wrap; }
  /* ══ WRAPPING IS NOT WHAT MAKES IT WRAP ════════════════════
     `flex-wrap` alone changed nothing: the body carries `min-width: 0` so it
     can shrink to nothing, and all three children went on fitting one line —
     measured at 375px, a 51px body against a 127px pill, every sentence
     breaking to one word per line. The body has to CLAIM the first line, and
     46px is the chip plus the gap it sits in. */
  .b-log-main { flex-basis: calc(100% - 46px); }
  /* Indented to the body column by a margin, not by padding — padding would
     stretch the pill itself across the row. */
  .b-log-go { align-self: flex-start; margin: 8px 0 0 46px; }
}

/* ══ THE MESSAGE THEY LEFT ══════════════════════════════
   Indented to the body column — 46px is the glyph chip plus its gap — so it
   hangs off the call it belongs to rather than starting a second list. Its own
   press, its own hover, and a quieter ground than the row above it, because it
   is a thing attached to that row and not a peer of it.

   `max-width` as well as a share: a voice note is a fixed-size object and a
   waveform stretched across a 1400px desk is a decorative rule with a button
   on the end. Every messaging app caps it for the same reason. */
.b-log-vm {
  display: flex; align-items: center; gap: 10px;
  width: calc(100% - 46px); max-width: 340px; margin: 3px 0 0 46px;
  padding: 7px 12px 7px 8px; border: 0; border-radius: var(--r-pill);
  background: rgba(var(--accent-rgb), 0.06);
  cursor: pointer; font-family: inherit; text-align: left;
  transition: background-color var(--t-fast);
}
.b-log-vm:hover { background: rgba(var(--accent-rgb), 0.12); }
.b-log-vm:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.b-log-vm-go {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent-text); color: var(--body-bg);
}
/* The triangle is optically left-heavy in its own box, so it is nudged back
   onto the circle's centre. The pause bars are symmetrical and are not. */
.b-log-vm-go svg { width: 11px; height: 11px; fill: currentColor; stroke: none; margin-left: 1px; }
.b-log-vm.is-playing .b-log-vm-go svg { margin-left: 0; }

/* ══ THE WAVEFORM, AND THE PLAYED HALF OF IT ══════════════════
   Two identical stacks of bars, the second clipped from the left as it plays.
   `clip-path` rather than an animated width: a clipped copy whose own width
   is animating re-lays out its twenty-eight flex children on every frame, and
   the bars visibly slide against the ones underneath them. `clip-path` is a
   paint-time operation — the two stacks stay in register by construction. */
.b-log-vm-wave { position: relative; flex: 1; min-width: 0; height: 22px; }
.b-log-vm-bars {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: 2px;
}
.b-log-vm-bars i {
  flex: 1; min-width: 0; border-radius: var(--r-pill);
  background: rgba(var(--accent-rgb), 0.30);
}
:root[data-theme="light"] .b-log-vm-bars i { background: rgba(var(--accent-rgb), 0.26); }
.b-log-vm-on { clip-path: inset(0 100% 0 0); }
.b-log-vm-on i { background: var(--accent-text); }
.b-log-vm.is-playing .b-log-vm-on { animation: b-vm-run linear both; }
@keyframes b-vm-run { to { clip-path: inset(0 0 0 0); } }

/* What is LEFT, counting down, which is the half of a duration that changes.
   Tabular so 0:09 and 0:10 do not shift the pill's right edge under the
   cursor a dozen times a message. */
.b-log-vm-len {
  flex: none; min-width: 30px; text-align: right;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  color: var(--ink-muted); font-variant-numeric: tabular-nums;
}

/* Reduced motion does not mean the message stops playing — the clock is the
   message's own length and still counts down, which is the fact. The fill
   states it rather than travelling to it, the pattern every other indicator
   in this build follows. */
@media (prefers-reduced-motion: reduce) {
  .b-log-vm.is-playing .b-log-vm-on { animation: none; clip-path: inset(0 0 0 0); }
  .b-log-vm { transition: none; }
}

/* ══ THE CANVAS SCROLLS ON THE WINDOW, NOT ON THE COLUMN ══════════
   `.overlay-thread` is the scroller AND the measure — 720px wide, centred by
   the flex parent — so its scrollbar was drawn against the right edge of the
   prose, four hundred pixels in from the window, with empty canvas either
   side of it. It read as a rule down the middle of the surface rather than as
   the edge of a scrolling region, and it had no relationship to the close
   button it should have sat under.

   THE MEASURE MOVES TO THE PADDING AND NOTHING ELSE CHANGES. The obvious
   version — drop the max-width, centre the children with `align-items` —
   rewrites how every child of this thread is laid out, and the thread holds
   rows that set their own `align-self`. Padding leaves all of that alone: the
   element is full width, so the scrollbar is at the window's edge, and the
   content box is still 720 in the middle of it.

   `max()` keeps the 24px gutter the design system set, for the widths where
   there is less than 720 to give. Below that the two agree and the rule is
   inert. */
.overlay-thread {
  max-width: none;
  /* 672, not 720. The rule this replaces was `max-width: 720px` on a
     border-box element carrying 24px of its own side padding, so the measure
     the prose actually had was 672 — and a padding calc written off 720 gave
     it 43px more and moved every line. The column is identical to what it
     was; only the bar it is scrolled by has moved. */
  padding-left: max(24px, calc((100% - 672px) / 2));
  padding-right: max(24px, calc((100% - 672px) / 2));
}

/* Its APPEARANCE is left alone on purpose. `.ntf-panel`'s treatment was the
   obvious thing to copy across and it is wrong here: that panel is an opaque
   card and its thumb at 6% white reads against it, where the canvas ground is
   a dark glass and the same value all but disappears. The ask was to move the
   bar, not to restyle it, and moving it is the whole of what this does. */

/* ══ INSIDE A CARD, NOTHING OUTRANKS THE CARD’S OWN TITLE ════════════
   The queue card sets its title at 18/600 and then set three things beneath
   it at 600 as well: the phone at 16, the action at 14, the state chip at
   12. Size held them apart from each other and nothing held them under the
   name at the top — four things at the same weight in a box the size of a
   postcard.

   The phone is the worst of the three because it is the closest: 16 against
   18 is one step, so weight was the only axis left and it was spent. It is
   also plain data rather than a control, which is the case where a tie
   reads as a mistake rather than as emphasis.

   All three drop to --fw-semibold. The ladder inside a card is then 600 for
   the name, 500 for anything that acts or states, 400 for the sentences —
   which is the same three-rank ladder the rest of the build now uses, and
   leaves --fw-extrabold for the page name outside the card entirely.

   Declared here, at the end, because bdr.css is the last stylesheet and all
   three of these are set earlier: .b-qcard-num above in this file, .tag and
   .s-insight-lnk in sales.css. Equal specificity, so the last one wins. */
.tag, .s-insight-lnk, .b-qcard-num, .tc-type { font-weight: var(--fw-semibold); }

/* ══ THE NOTES ROW ═════════════════════════════════════
   Two ranks, and the note is the loud one: body step against minimal, 400
   against 500, --d100 against --ink-muted. The gap inside a row is 2px and
   .b-feed puts 10 between rows, so a note and its provenance read as one
   thing and the next note reads as another. */
.b-note-say {
  margin: 0 0 2px; max-width: 70ch; text-wrap: pretty;
  font-size: var(--t-body); font-weight: var(--fw-medium);
  line-height: var(--lh-base); color: var(--d100);
}
.b-note-who {
  margin: 0; font-size: var(--t-micro); font-weight: var(--fw-medium);
  line-height: var(--lh-snug); color: var(--ink-muted);
}
.b-note-name {
  appearance: none; border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: var(--fw-semibold); color: var(--ink-secondary);
  transition: color var(--t-fast);
}
.b-note-name:hover { color: var(--d50); }
.b-note-name:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* ══ THE DRAFT LIST'S NAME IS THE MASTHEAD, TYPEABLE ═══════════════════
   `.s-rec-title` is a flex row: the name, then the state chip beside it.
   `.s-build-name` is `width: 100%` — written for `.s-sheet-head`, where the
   heading is a block and full width is what it wants — and inside a flex row
   that made the heading grow to eat the whole line, which pushed the chip to
   the far right edge and broke the one thing this page was made to match.

   So here the field is sized by its own content: `size` sets the width
   before CSS arrives and is the floor, `field-sizing: content` grows it as
   you type, and `max-width` stops a long name from running past the column.
   The heading shrinks to fit and the chip sits where it sits on a saved list
   — next to the name, not across the page from it.

   `field-sizing` is Chromium-only today. Everywhere else the `size`
   attribute alone answers, which is the same layout one repaint behind: the
   field is redrawn at the right width the next time the page paints. */
.s-rec-title > .s-rec-name.is-field { min-width: 0; margin-top: 0; }
.s-rec-title .s-build-name {
  width: auto; max-width: 100%;
  field-sizing: content;
}
/* ══ THE CANDIDATE CARD ═══════════════════════════════════════════════
   `netCard` is `qcard`'s anatomy with three slots doing different work, and
   these are the rules for those three. Everything else on it — the head, the
   title, the two fact lines, the AiMY block, the foot — is the card every
   other surface in this build draws, unstyled again here on purpose. */

/* ══ THE TWO PLACES YOU CAN GO AND LOOK ══════════════════════════════
   A stranger off an index is the one record in this product you cannot open,
   so the only way to check them before you keep them is to go outside. The
   row carried both addresses as bare text and they were the quietest thing
   on it; here they are links and read as links, one step under the facts.

   `min-width: 0` on the anchor because a domain does not break: without it
   a long one pushes the card wider than its column and the grid stops being
   a grid. */
/* ══ AND IT SITS IN THE CARD'S GUTTER LIKE EVERY OTHER ROW ═══════════
   This had no horizontal padding, so its marks began at the card's edge
   while the tag, the name, the sentence, the facts and the foot all begin
   14px in. One row out of seven breaking the left margin is the kind of
   thing you see before you can say what it is. Measured: 353 against 367.

   AND IT GETS THE BREAK THE BLOCK IT REPLACED HAD — BOTH HALVES OF IT.
   `.b-aimy` sat in this slot on a queue card and it carries `10px 0 14px`:
   ten to separate the card's FACTS from the thing under them, fourteen to
   keep the foot's hairline off it. This went in at 2, then at 10, and both
   times with nothing underneath — so the rule sat flush against the links,
   measured 168.1 to 168.1, with every other row on the card spaced and
   this one pinned to a line.

   Lopsided is worse than tight. Ten above and nothing below reads as the
   links belonging to the foot rather than to the card, which is backwards:
   they are the last of what the supplier returned, and the foot is what
   you do about it.

   THE NUMBER IS COPIED, NOT CHOSEN. It is the one the block this replaced
   already used in the same slot, so a candidate card and the roster card
   it becomes put their hairline the same distance under the last thing
   above it. */
.b-net-links {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px;
  padding: 0 14px; margin: 10px 0 14px; min-width: 0;
}
.b-net-link {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
  font-size: var(--t-micro); font-weight: var(--fw-semibold);
  color: var(--accent-label); text-decoration: none;
  transition: color var(--t-fast);
}
.b-net-link > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 10px, which is what `.b-qcard-where` gives its marks. Three stacked
   rows of mark-then-text whose marks are 10, 11 and 12 wide start their
   text at three different places — 383, 370 and 385 — and a column that
   is nearly aligned reads worse than one that is plainly not. */
.b-net-link > svg { flex: none; width: 10px; height: 10px; }
.b-net-link:hover { color: var(--accent); text-decoration: underline; }
.b-net-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ══ THE FOOT'S VERB IS A DECISION ABOUT THE CARD ═════════════════════
   Every other card in this build ends in something to do to that person.
   This one ends in whether they survive, so the tick sits where the verb
   sits and carries the word with it: a 14px box on its own is the smallest
   target on the surface, and `<label>` makes the word part of it.

   IT DOES NOT FADE. `.s-tick` is 0.35 until the card is hovered, which is
   right on a grid where picking is an occasional thing you do to one card.
   It is wrong here: every card on this page is asking the same question,
   the answer is already set on all of them, and a page of ticks you cannot
   see until you reach for them is a page that does not look answerable. */
.b-net-keep {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: var(--t-ui); font-weight: var(--fw-semibold); color: var(--d100);
  transition: color var(--t-fast);
}
.b-net-keep .s-tick { opacity: 1; width: 15px; height: 15px; }
.b-net-keep:hover { color: var(--d50); }

/* ══ AND ONE YOU HAVE LEFT OUT STAYS ON THE PAGE ══════════════════════
   Unticking does not remove the card, and it should not: the count in the
   masthead has already dropped, and a row that vanishes when you untick it
   makes changing your mind a search. So it goes quiet instead — a treatment
   rather than a re-inking, so nothing inside it has to hold a second colour.

   THE WAY BACK KEEPS FULL INK, AND THAT IS WHY THIS IS NOT ONE RULE ON THE
   CARD. `opacity` on a parent cannot be undone by a child: it composites
   the subtree as one layer, so `.b-net-keep { opacity: 1 }` under a card at
   0.45 is a rule that reads as an exception and has no effect whatsoever.
   It was written that way first and it took a screenshot to catch — the
   audit cannot see a rule that applies cleanly and does nothing.

   So the dimming is applied to the card's CONTENT instead, every child but
   the foot, and inside the foot to the fact and not to the control. The one
   press that gives you this person back is the one thing on the card still
   at full strength, which is what you want on the card you are least sure
   about. */
.b-netcard.is-dropped > *:not(.b-qcard-foot) { opacity: 0.45; }
/* The foot's own fact was dimmed by name here. It has not carried a
   `.b-qcard-num` since the reach pills went in, so this rule has been
   applying to nothing — the second kind of dead rule the audit cannot
   see, after the one whose effect is cancelled by its parent. The foot
   holds only the tick now, and the tick is what must stay bright. */
.b-netcard.is-dropped:hover > *:not(.b-qcard-foot),
.b-netcard.is-dropped:focus-within > *:not(.b-qcard-foot) { opacity: 0.85; }
@media (prefers-reduced-motion: no-preference) {
  .b-netcard > *, .b-netcard .b-qcard-num { transition: opacity var(--t-fast); }
}
/* ══ THE CONTACT BLOCK, UNDER THE FIRMOGRAPHICS ══════════════════════
   The number and the address moved out of the foot and into the card's
   facts, where they are the value rather than a word about the value. They
   are a different KIND of fact from the two lines above them — where the
   place is and how big it is describe the company; these are how you reach
   somebody — so the block starts with the same 8px the facts take under the
   name, and the lines inside it keep `.b-qcard-where`'s own 2px because they
   are a pair in exactly the way the two firmographic lines are.

   On a company card the 8px falls on the contact's name instead, because
   there the block is who-and-how rather than just how. */
.b-netcard .b-net-contact { padding-top: 8px; }
/* ══ THE CARD'S OWN RHYTHM, WHICH WAS ALL ONE INTERVAL ════════════════
   `.b-qcard-where` carries `padding-top: 2px` and there are two of them, so
   that one value was doing two different jobs. Between the two fact lines it
   is right and deliberate: the organisation on one, where and how big on the
   next, 2px because they are ONE block and a bigger gap would make them read
   as two. Above the first one it is not separating a pair, it is separating
   the facts from the line that names who this is — and there it left the job
   title sitting on top of the first fact with nothing between them.

   ON `.b-qcard`, NOT ON `.b-netcard`. This was written scoped to the
   candidate card, because that is the card somebody was looking at when it
   was noticed. But the queue card and the roster card are the same component
   with the same two rows and the same 2px, and scoping the fix to one of the
   three would have given a person 10px on the draft and 2px on the list
   thirty seconds later — a difference between two pages, introduced by a rule
   whose whole argument is that they should match. Every card grid gets it.

   ONE VALUE FOR BOTH KINDS OF LINE. A first cut gave prose 10 and a job
   title 8, on the argument that a sentence at `--fs-sm` needs more air under
   it than three words at `--fs-xs`. That is true and it is worth two pixels,
   which is not worth a second rule and a second thing to keep in step.

   `.tc-summary + .b-qcard-where`, not `:first-of-type`: the summary is a `<p>`
   too, so first-of-type is the summary and the rule lands on nothing. */
.b-qcard .tc-summary + .b-qcard-where { padding-top: 8px; }

/* ══ WHAT YOUR FLOOR DID, AS FOUR WIDGETS ════════════════════════════════
   Each metric is its own card — `.s-pan`, the surface "What you bought"
   already uses on this page — so the four read as four answers rather than
   four rows of one table. The argument for drawing the weeks is at
   `floorWidget`.

   The rank inside a card is built from size and weight, never dimness: the
   figure at the figure step, the name at meta and semibold, the sub-line and
   the axis at meta and 2xs in `--d200`, which is the floor for type here. */
/* The bottom margin is the one the paragraph under the grid used to give the
   page: without it the next block sat 10px under the widgets. */
.b-wids {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px; margin: 16px 0 24px;
}
@media (max-width: 619.98px) { /* 620 layout px, where `.s-starts` goes to one */
  .b-wids { grid-template-columns: minmax(0, 1fr); }
}
.b-wid { padding: 16px 20px 14px; }
.b-wid-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.b-wid-name { font-size: var(--ty-meta); font-weight: var(--fw-semibold); color: var(--d100); min-width: 0; }
.b-wid-head .s-pan-state { margin-top: 0; flex: none; }
.b-wid-fig {
  margin-top: 6px;
  font-family: var(--font-display); font-size: var(--t-figure); font-weight: var(--fw-bold);
  line-height: 1.1; letter-spacing: var(--ls-tight); color: var(--d50);
}
.b-wid-sub { margin-top: 4px; font-size: var(--ty-meta); line-height: var(--lh-base); color: var(--d200); }

/* The plot is a fixed height whatever the column width, so four widgets in a
   grid line up. The strokes do not scale with the stretch; everything that
   has to stay round or legible is HTML over it, placed in percentages. */
.b-wid-plot { position: relative; height: 88px; margin-top: 16px; cursor: crosshair; }
.b-wid-plot svg { display: block; width: 100%; height: 100%; overflow: visible; }
.b-wid-line, .b-wid-now {
  fill: none; stroke-width: 2; vector-effect: non-scaling-stroke;
  stroke-linejoin: round; stroke-linecap: round;
}
.b-wid-line { stroke: var(--d300); }
.b-wid-now { stroke: var(--accent); stroke-width: 2.5; }
.b-wid-goal {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px dashed var(--d200); pointer-events: none;
}
/* Semibold, because it names a MARK and the axis under it names a position:
   at one weight the two read as one kind of label, and a widget built of
   12px regular was most of what failed the flatness test. */
.b-wid-goal-tag {
  position: absolute; left: 0; transform: translateY(calc(-100% - 3px));
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); line-height: 1;
  color: var(--d200); pointer-events: none;
}
.b-wid-goal-tag.is-below { transform: translateY(4px); }
.b-wid-dot, .b-wid-hdot {
  position: absolute; width: 10px; height: 10px; border-radius: 50%;
  transform: translate(-50%, -50%); pointer-events: none;
  box-shadow: 0 0 0 2px var(--card-bg);
}
.b-wid-dot { background: var(--accent); }
.b-wid-hdot { background: var(--d50); }
.b-wid-cross {
  position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 1px solid var(--d300); pointer-events: none;
}
.b-wid-tip {
  position: absolute; bottom: calc(100% + 6px); transform: translateX(-50%);
  padding: 4px 8px; border-radius: var(--r-md);
  background: var(--card-bg-raised); border: 1px solid var(--card-border);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); line-height: 1.3;
  color: var(--d50); white-space: nowrap; pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.b-wid-tip.is-start { transform: none; }
.b-wid-tip.is-end { transform: translateX(-100%); }
.b-wid-cross[hidden], .b-wid-hdot[hidden], .b-wid-tip[hidden] { display: none; }
.b-wid-axis {
  display: flex; justify-content: space-between; gap: 12px; margin-top: 10px;
  font-size: var(--fs-2xs); color: var(--d200);
}
/* The key: a swatch of the stroke it explains, in the stroke's own colour
   and weight, and the words in ink — text never wears the series colour. */
.b-wid-key { display: inline-flex; align-items: center; gap: 6px; text-align: right; }
.b-wid-key::before {
  content: ''; flex: none; width: 12px; height: 2.5px; border-radius: 2px;
  background: var(--accent);
}
/* The widget's one reading is AiMY's, so it is `.b-aimy` — the mark and the
   sentence on the accent ground, the way AiMY speaks on every other surface.
   What the component brings from a queue card is a card's margins and a
   `flex: 1` that stretches it to fill the row; here it sits at the foot of
   the widget at its own height, with the widget's own gap above it. */
/* `margin-top: auto` sends it to the foot of the widget, so across a row of
   widgets of different heights the four sentences line up; the space above
   it is the block above it's own bottom margin, which auto cannot remove. */
.b-wid .b-aimy { flex: none; margin: auto 0 0; }
.b-wid-axis, .b-steps { margin-bottom: 14px; }

/* A step's stages: name, bar, count. Each bar's track is the count the step
   started with — said in words on the sub-line above — and the fill is how
   many of them got that far. */
.b-steps {
  display: grid; grid-template-columns: max-content minmax(40px, 1fr) auto;
  gap: 10px 12px; align-items: center; margin-top: 14px;
}
.b-step-name { font-size: var(--ty-meta); color: var(--d100); }
.b-step-n {
  font-size: var(--ty-meta); font-weight: var(--fw-bold); color: var(--d50);
  font-variant-numeric: tabular-nums; text-align: right;
}

/* ══ 94. ASSIGN IT TO A MANAGER ════════════════════════════════════════════
   The CEO's one verb, drawn wherever there is something to assign: the
   Assign menu (the hand-over menu's own component) and, beside it, AiMY's
   suggestion as a sentence ending in its own press, all on one line that
   wraps. No existing rule is a control with a sentence at its end, which is
   why this is here rather than borrowed.

   Under a `.b-owed` row it starts at the row's text column — 12px of row
   padding, the 6px dot and the 12px gap — so the verbs sit under the thing
   they act on rather than under the dot. The reason is set at the body's
   size and ink, never quieter: it is the sentence that decides the press. */
/* `.b-join` is the same strip holding the one verb Join it, under a deal
   on "Where you are needed" — the same place under the row, for the same
   reason: the row opens the deal and the verb acts on it. */
.b-give, .b-join {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  margin: 0 0 12px;
}
.b-owed .b-give, .b-owed .b-join { margin: -2px 0 10px 30px; }
/* In a row of buttons the row spaces them, and a bottom margin on one flex
   item in a centred row lifts it: Reassign sat 6px above Join it. */
.s-rec-actions .b-give, .b-draft-acts .b-give { margin: 0; }
/* A line of its own, above the Assign button, and running text rather
   than a flex row: the press is the sentence's last words, so it has to
   wrap with the words around it instead of dropping to a line as a block. */
.b-give-why {
  display: block; flex: 0 0 100%; min-width: 0;
  font-size: var(--fs-xs); color: var(--ink-secondary); line-height: var(--lh-snug);
}
.b-give-why svg { vertical-align: -2px; margin-right: 6px; }
/* A link in the sentence: the product's link ink and weight (`.b-net-link`),
   underlined so it reads as pressable in the middle of prose, where colour
   alone is too little to go on. */
.b-give-lnk {
  display: inline; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-weight: var(--fw-semibold); color: var(--accent-label);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px;
  transition: color var(--t-fast);
}
.b-give-lnk:hover { color: var(--accent); }
.b-give-lnk:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 2px; }
.b-give-lnk[disabled] { cursor: not-allowed; text-decoration: none; }

/* ══ 95. A MANAGER'S QUARTER, IN A ROW ══════════════════════════════════════
   The rail card's bar — gained against a mark at the target — at the width
   a row can give it. Capped, because a bar the width of the page reads as a
   progress bar for the page rather than a figure for one person, and the
   three of them are compared by position, which needs them the same length. */
.b-mgr-bar { display: block; max-width: 360px; margin: 10px 0 4px; }

/* ══ 96. CONNECT LINKEDIN ══════════════════════════════════════════════════
   The ghost button with LinkedIn's mark in front of the words, so the one
   press that asks for an outside account says which account at a glance.
   The mark is the line icon every LinkedIn link here already uses. */
.b-li-btn { display: inline-flex; align-items: center; gap: 8px; }
.b-li-btn svg { flex: none; }
/* One line, Nour's call: the block's own reading measure is for paragraphs,
   and this is a single sentence under a header as wide as the page. */
.b-li-sub { max-width: none; }
