/* static/console.css -- design/prototype/prototype.css, served to the app.
 *
 * A COPY, and deliberately so. The app must not load its stylesheet out of
 * design/prototype/: that directory is the proof that the design was
 * reproduced, and a running application that depends on it makes the proof
 * circular -- compare.mjs would then be diffing a page against its own
 * stylesheet and every computed value would agree by construction.
 *
 * The copy is exact except for the @font-face URLs, which are rewritten from
 * the prototype's relative `fonts/` to the app's `/fonts/`. tests/test_console.py
 * re-applies that one rewrite to the prototype's file and fails if the result
 * is not this file byte for byte, so the two cannot drift apart quietly.
 *
 * DO NOT EDIT. Edit design/prototype/prototype.css (which is off-limits) or,
 * far more likely, edit nothing at all.
 */
/* ============================================================================
   Slate prototype — shared design system
   ============================================================================

   This is a faithful port of a three-screen Claude Design prototype. It is a
   visual reference artifact, not application code: nothing here is wired to
   the real app, and every number was read off the rendered original rather
   than chosen.

   HOW THE ORIGINAL WAS STRUCTURED, because it explains this file's shape.
   The React bundle carried no stylesheet classes at all. Every visual lived
   in one of 597 inline `style` attributes, and the only shared CSS was a
   small base layer that each component injected into <head> as its own
   "helmet" <style> block. There were four such helmets: a Root shell plus one
   per screen. A standalone export of a single screen therefore loaded exactly
   two of them — the Root shell, then that screen's own — which is the model
   this file reproduces.

   That detail is load-bearing. The four helmets DISAGREE with one another, so
   there is no single correct value for several properties; there is only a
   correct value per screen. In the running prototype the helmets accumulate
   in <head> as you navigate, which means the same screen renders differently
   depending on which screens you visited first. That is a bug in the original,
   not a design intent, and it is why the per-screen values below are scoped to
   a screen class rather than averaged into one global value.

   Every screen root element must therefore carry its screen class:
       <body class="screen-signin">   <body class="screen-console">
       <body class="screen-triage">   <body class="screen-index">

   Verified divergences between the four helmets are marked "PER-SCREEN" and
   each carries the evidence for its value. Do not unify them.
   ============================================================================ */


/* ----------------------------------------------------------------------------
   1. FONTS
   ---------------------------------------------------------------------------- */

/* Self-hosted so the prototype renders in its real typefaces with no network
   request at runtime, which the fixture forbids. The original referenced these
   by an opaque bundler UUID that resolves nowhere outside that tool, so the
   captured page reported both families "unloaded" and silently rendered in
   system-ui and Menlo. Every line-break in that capture is a fallback artifact,
   not the design. These are the Google Fonts originals, latin subset only: no
   glyph on any of the three screens falls outside that range. */

/* Instrument Sans ships as one variable font spanning wght 400-700, which is
   why Google serves a byte-identical file for 400, 500 and 600. Declaring the
   range once renders identically to three pinned faces at the three weights in
   use, and additionally stops a stray 700 from picking up synthetic bold if
   anyone extends this fixture later. */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: block;
  src: url('/fonts/instrument-sans-latin-var.woff2') format('woff2');
}

/* DM Mono is static, so 400 and 500 are genuinely different outlines and both
   files are needed. Dropping the 500 would not shift the layout — monospace
   advance widths are identical — it would just render the "Deciding" and
   "To reach" eyebrows a shade too light, which is the class of error nobody
   catches by eye. */
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('/fonts/dm-mono-400-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('/fonts/dm-mono-500-latin.woff2') format('woff2');
}

/* `font-display: block` where the original said `swap`. The original's value
   was inert because its fonts never loaded at all. For a reference artifact
   that gets screenshotted and diffed, `swap` can paint one frame in system-ui
   and poison the diff; `block` cannot. The files are local, so the block
   period is effectively zero. */


/* ----------------------------------------------------------------------------
   2. TOKENS
   ---------------------------------------------------------------------------- */

/* The 20 tokens of the Root shell helmet, which the sign-in and triage helmets
   also declare identically. Console is the sole dissenter and is handled just
   below rather than here.

   `--hair` is the workhorse: this is a flat, hairline-delimited design with
   only three box-shadows in the entire system, so a 1px `--hair` rule is what
   separates most things from most other things.

   `--ink2` is the dominant text colour, not `--ink` — it carries 146 of the
   247 colour declarations across the three screens. `--ink` is reserved for
   headings, metric values and other things meant to sit forward. */
:root {
  --page: #f3f2f0;
  --card: #fdfcfb;
  --sunk: #e7e5e1;
  --hair: #dcd9d4;
  --line: #898680;

  --ink: #17181c;
  --ink2: #575d66;

  --accent: #2f56d9;
  --accent-wash: #e4e8fb;
  --on-accent: #fdfcfb;

  --amber: #8a5a0b;
  --amber-wash: #f7ecd6;

  --moss: #1f6b4c;
  --moss-wash: #e0efe7;

  --plum: #7b3f9d;
  --plum-wash: #f2e8f8;

  --r: 8px;
  --r-sm: 5px;

  --ui: 'Instrument Sans', system-ui, sans-serif;
  --num: 'DM Mono', ui-monospace, monospace;

  color-scheme: light;
}

/* PER-SCREEN — console's helmet is the only one that sets these.
   It rounds tighter than the other three screens (6/4 against 8/5) and adds a
   sixth accent hue. Verified by counting token references against rendered
   radii on the live page: console has 19 `var(--r-sm)` references and renders
   19 elements at 4px; sign-in has 4 and renders 4 at 5px; triage has 6 and
   renders 6 at 5px. So this is a genuine per-screen value, not a cascade
   accident to be tidied away.

   Beware the trap that produced the conflicting reports about these numbers:
   in the running SPA, console renders at 6/4 on first visit but at 8/5 if you
   reach it after triage, because triage's helmet gets appended after console's
   and overrides it. 6/4 is what console was authored as and what it shows
   standalone, which is what this port reproduces. */
.screen-console {
  --teal: #17646d;
  --teal-wash: #e0f0f2;
  --r: 6px;
  --r-sm: 4px;
}

/* Ported verbatim from the original even though nothing in the three screens
   toggles it and no `prefers-color-scheme` query ever activates it. It is
   fully specified, costs two rules, and deleting a complete theme because the
   fixture happens not to exercise it would be a real regression. Opt in with
   `<html data-theme="dark">`. */
[data-theme="dark"] {
  --page: #14161a;
  --card: #1c1f25;
  --sunk: #101216;
  --hair: #2f343c;
  --line: #6a717c;

  --ink: #f1f2f4;
  --ink2: #a3a9b4;

  --accent: #93a9ff;
  --accent-wash: #1e2440;
  --on-accent: #14161a;

  --amber: #e0ad55;
  --amber-wash: #2c2411;

  --moss: #79c8a4;
  --moss-wash: #14261f;

  --plum: #c9a6ee;
  --plum-wash: #241a33;

  color-scheme: dark;
}

[data-theme="dark"] .screen-console {
  --teal: #7fd3dd;
  --teal-wash: #10262b;
}

/* PER-SCREEN — the Mac app's helmet defines its own teal, and it is a
   different hue from console's: #0f6b68 against #17646d, #6cc5c0 against
   #7fd3dd. Nothing in the nine panel states references it (`grep -c teal` over
   all nine captured DOMs returns 0), so this is carried for the same reason the
   dark theme above is carried — a complete token set that the fixture happens
   not to exercise is not dead code, it is the part of the design that has not
   been drawn yet, and dropping it is a silent decision to diverge later.

   Note what is deliberately ABSENT: `--r` and `--r-sm`. The Mac app rounds at
   the base 8px/5px, not console's 6px/4px. That is why the panel gets its own
   screen class instead of borrowing `screen-console` for the teal — reusing
   console's class would silently retune every `var(--r-sm)` control in the
   footer and the four state buttons. */
.screen-macapp {
  --teal: #0f6b68;
  --teal-wash: #dfefee;
}

[data-theme="dark"] .screen-macapp {
  --teal: #6cc5c0;
  --teal-wash: #102827;
}


/* ----------------------------------------------------------------------------
   3. BASE / RESET
   ---------------------------------------------------------------------------- */

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--ui);
  -webkit-font-smoothing: antialiased;
}

/* `height: 100%` is not decoration and not dead scaffolding. It pins <body> to
   the viewport (900px at the reference size) while the screen root, which is
   `min-height: 100vh`, overflows past it. Drop it and <body> instead stretches
   to its content — 1368.91px on the console, 1603.34px on triage — which is a
   real computed-style divergence from the capture even though no descendant
   reads it today. It is also what makes a future `body { display: flex }` or a
   percentage-height child behave the way the original would. */

/* PER-SCREEN — and the one place the rule above does not hold. The panel is a
   menu-bar popover drawn over a simulated macOS desktop, so three of the four
   declarations it inherits are wrong for it:
     - `height: 100%` would pin <body> to 760px, but the panel's card is
       content-sized and three of its eight states (needs-sign-in, usage-limit,
       can't-reach-Slate) grow past the 420x760 preview and scroll. Measured on
       the original: 760px, 797.77px and 837.66px. A pinned body reports 760px
       for all three, so the two tall states would compare as a 77.66px lie.
     - the backdrop is desktop grey, not `--page`. #8f9199 is not a token and
       must not become one: it is not a step on the design's ramp, it is the
       wallpaper the panel is photographed against.
     - the Mac app's helmet declares no `color` at all, so <body> falls through
       to CanvasText. That is not the same as `var(--ink)`: CanvasText resolves
       to rgb(0,0,0) light and rgb(255,255,255) dark, where --ink is #17181c and
       #f1f2f4. Writing the token here would miss by a measurable amount in both
       themes. `unset` does not work either — `color` inherits, so it would
       re-inherit --ink from the rule above.

   In dark mode only <body> repaints; <html> stays grey. That asymmetry is in
   the original and it is what shows when you overscroll past the panel. */
html:has(> body.screen-macapp) {
  background: #8f9199;
}

body.screen-macapp {
  height: auto;
  background: #8f9199;
  color: CanvasText;
}

[data-theme="dark"] body.screen-macapp {
  background: #22242a;
}

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

button {
  font-family: inherit;
  cursor: pointer;
}

button:active {
  transform: translateY(1px);
}

/* PER-SCREEN — only the console and triage helmets declare this transition, so
   sign-in's buttons snap to the pressed offset with no easing. Visible only
   while a button is held down, but it is a real difference and costs one rule
   to keep. */
.screen-console button,
.screen-triage button,
.screen-index button {
  transition: transform 120ms ease;
}

/* The Mac app's helmet declares the same transition 10ms faster. Ten
   milliseconds is not a value anyone would defend, but it is what the panel was
   authored with, and folding it into the 120ms rule above would be the fixture
   deciding the design was wrong about its own numbers. */
.screen-macapp button {
  transition: transform 110ms ease;
}

/* Sign-in's helmet says `input`, console's says `input, textarea`. Applied
   together here because sign-in has no textarea, so the wider selector cannot
   change what sign-in renders. */
input,
textarea {
  font-family: inherit;
}

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

a:hover {
  text-decoration: underline;
}

/* Worth knowing when writing the markup: this underline is why the top-bar nav
   must not be built from <a>. Ignore the accessibility argument for a moment —
   a link there would need `text-decoration: none` plus a `:hover` override just
   to look right, and it still would not match, for the box reasons spelled out
   at `.navbtn`. There are no links in the rendered screens at all; this rule
   exists because the capture's stylesheet has it, not because anything uses it. */

:focus {
  outline: none;
}

/* The Root shell and triage helmets both specify 3px/3px, so that is the base. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

/* PER-SCREEN — sign-in tightens the offset, console tightens both. Each screen
   overrides only what its own helmet actually changed. */
.screen-signin :focus-visible {
  outline-offset: 2px;
}

.screen-console :focus-visible {
  outline-offset: 2px;
  border-radius: 2px;
}

/* The Mac app takes console's tighter offset but keeps the base 3px radius —
   a combination neither of the other screens uses, so it needs its own rule
   rather than a shared one. Only the offset is overridden; inheriting the
   radius is the point. */
.screen-macapp :focus-visible {
  outline-offset: 2px;
}

::placeholder {
  color: var(--ink2);
  opacity: 1;
}

::selection {
  background: var(--accent-wash);
}

/* PER-SCREEN — scrollbar styling belongs to console's helmet alone, so a
   standalone sign-in or triage page shows the platform default. Chromium and
   WebKit only; Firefox has no equivalent that matches, and the original has
   the same gap. */
.screen-console ::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

.screen-console ::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 99px;
}

.screen-console ::-webkit-scrollbar-track {
  background: transparent;
}

/* REDUCED MOTION.

   Was the blanket `animation-duration: 0.01ms` kill. That rule is safe in this
   sheet, but only by accident, and the accident is worth writing down: EVERY
   animation here rests at the element's natural style. riseIn, riseInSignIn,
   riseInMacApp, fadeIn, markIn and popIn all start at opacity 0 and end at the
   element as authored; fillUp, barGrow and growX scale from 0 to 1; glow dips
   to 0.35 at its midpoint and returns to opacity 1 at both ends. So running
   any of them for 0.01ms lands on the same pixels as never running them.

   That is a property of today's keyframes, not of the rule, and nothing states
   it. The first animation with `fill-mode: forwards` ending somewhere other
   than the resting style, or a looping indicator whose 0% frame is its "off"
   state, would be frozen at the wrong frame with no warning. `animation: none`
   cannot fail that way: it removes the animation and leaves the element as
   authored, which is the outcome the 0.01ms version was reaching for.

   Checked before switching: no state in this app is carried by motion alone.
   The collector dot's `glow` marks "online", and online is also the dot's hue,
   the word beside it and the "· pause" suffix -- so a reader who never sees it
   move still has three ways to know. Motion is decoration here, and decoration
   is what reduced-motion is allowed to drop.

   The two transitions are both `transform` (the toggle knob and the slider
   thumb) and both end at the state they were moving toward, so removing their
   duration keeps the state change and drops only the travel. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: 0s !important;
  }
}


/* ----------------------------------------------------------------------------
   4. MOTION
   ----------------------------------------------------------------------------
   Seven keyframes are referenced by the three original screens. Four more were
   defined in the original and referenced by nothing (`spin`, `tick`, `shake`,
   `shimmer`, all confirmed at zero references) and are dropped.

   `glow` used to be on that dropped list and has come off it. It was genuinely
   unreferenced by the three screens ported first, but the console's Sourcing
   page — which none of them reached — puts it on the Collector card's status
   dot. Counting references on the pages you happen to have ported is not the
   same as counting them on the design, and this is what that costs.

   Every animation in the design uses the same easing, `cubic-bezier(.16,1,.3,1)`,
   except five `fadeIn`s on plain `ease`. Durations run 320-720ms and the bar
   fills stagger on a 22ms step. Those values live on the elements, since they
   vary per instance.
   ---------------------------------------------------------------------------- */

/* ---- the deciding board's own motion -------------------------------------

   Ryan: "beed more animations". TWO, not the four this started as: `chairIn`
   and `cardBack` were written and referenced by nothing, and this file already
   dropped four unreferenced keyframes once for exactly that reason. An
   animation nobody plays is a claim about the design that is not true.

   Both of these mark a CHANGE OF STATE rather than decorating a static one --
   a screen worked several hundred times a week cannot afford movement that
   means nothing.

   All on the design's easing and inside its 320-720ms band, and all covered by
   the prefers-reduced-motion rule above, which is why they are here rather
   than inline: an inline `animation` on an element is not reachable by that
   media query's `!important` in every engine, and the one reader who needs it
   off is the one who would be worst served by getting it anyway. */

/* A card arriving in a column. Short, and from BELOW the column rather than
   from the side: a decided card leaves to the left or the right, and an
   arrival sharing that axis reads as an undo. */
@keyframes cardIn {
  from { opacity: 0; transform: translateY(6px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* A decision landing. The card does not slide out -- it is spliced from the
   column it was in and drawn in the one it went to, which is instant -- so
   what animates is the COUNT it changed, which is the thing a reviewer is
   watching to know the press registered. */
@keyframes countBump {
  0%   { transform: none; }
  40%  { transform: translateY(-2px) scale(1.08); }
  100% { transform: none; }
}

/* THIN SCROLLBARS ON THE BOARD'S COLUMNS.
   Ryan: "the thing is cooked like why is the scroll bar looin like that, if
   anythung make it suoer thin".

   macOS draws an overlay scrollbar that appears on scroll and fades -- but only
   while "Show scroll bars: when scrolling" is set. On "Always", and on any
   trackpad-less setup, the same element gets a 15px opaque gutter drawn INSIDE
   the scroller, and on this screen that lands on top of the cards: the column
   is a fixed width holding fixed-width cards, so the gutter has nowhere to
   come from except the content.

   `scrollbar-width: thin` is the standard property and Chromium honours it, so
   the column keeps its cards and the bar becomes a hairline. The colour pair
   is track-then-thumb; both are tokens, so it is right in either theme.

   Scoped to the board's own scrollers rather than set globally: the console's
   long pages are read by dragging a real scrollbar and making that hard to
   grab everywhere would be fixing one screen by damaging three. */
[data-scroll-id] {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
/* The WebKit half, for the same elements. Chromium reads both and the
   pseudo-elements win, so the two have to agree -- 8px here against `thin`
   above, which is what Chromium resolves `thin` to. */
[data-scroll-id]::-webkit-scrollbar { width: 8px; height: 8px; }
[data-scroll-id]::-webkit-scrollbar-track { background: transparent; }
[data-scroll-id]::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: 99px;
}
[data-scroll-id]::-webkit-scrollbar-thumb:hover { background: var(--ink2); }

/* The record drawer arriving. From the edge it is attached to, so it reads as
   having come from the board rather than landed on top of it. */
@keyframes drawerIn {
  from { opacity: 0; transform: translateX(14px); }
  to   { opacity: 1; transform: none; }
}

/* The drawer's close control, under the pointer. It is a borderless label in
   the drawer's own quiet voice rather than a keycap -- see deciding.js for why
   the keycap had to go -- and a borderless label needs SOMETHING to say it is
   pressable. Darkening to --ink is the whole of it, and it cannot be an inline
   style because :hover is not expressible as one. */
[data-close]:hover { color: var(--ink) !important; }

@keyframes riseIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* PER-SCREEN — sign-in's helmet authors `riseIn` with a 14px rise rather than
   10px. Keyframes are global and cannot be scoped by selector, so sign-in's
   variant needs its own name; sign-in's markup references THIS name instead of
   `riseIn`. Renders identically to the original, which is the bar a rename has
   to clear.

   Do not "fix" sign-in back to `riseIn`. The 10px value only appears on
   sign-in in the running SPA after console has been visited and its helmet has
   overridden sign-in's. 14px is what sign-in shows on a clean load. */
@keyframes riseInSignIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes fillUp {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}

@keyframes markIn {
  from { opacity: 0; transform: scale(0.9); }
  to   { opacity: 1; transform: none; }
}

/* `barGrow` and `growX` have identical bodies. Both names are kept because
   sign-in's markup references `barGrow` and console's and triage's reference
   `growX`; collapsing them would force edits to the markup for no visual gain. */
@keyframes barGrow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes growX {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes popIn {
  0%   { opacity: 0; transform: scale(0.82); }
  60%  { transform: scale(1.07); }
  100% { opacity: 1; transform: scale(1); }
}

/* One reference: the live dot on the console's Collector card, on the Sourcing
   page. Note it dips to 0.35 and returns, where the Mac app's `pulseMacApp`
   dips to 0.4 — near enough to look like a rounding slip and far enough apart
   that unifying them would change both screens. They are separate names because
   they are separate animations. */
@keyframes glow {
  0%   { opacity: 1; }
  50%  { opacity: 0.35; }
  100% { opacity: 1; }
}

/* --- Mac app ---------------------------------------------------------------

   All three are suffixed, and the suffix is doing real work in each case.

   `riseInMacApp` is the `riseInSignIn` situation again: the panel's helmet
   authors `riseIn` as a 6px drop from ABOVE, where every other screen rises
   10px from below. Keyframes are global and cannot be scoped by selector, so
   the variant needs its own name and the Mac app's markup references it.

   `pulseMacApp` and `creepMacApp` are a subtler case, and the reason they are
   renamed is the opposite of the usual one — it is not that two screens
   disagree about the body, it is that ONE screen defines the name and ANOTHER
   references it while the original leaves it undefined.

   The console's Sourcing page puts `animation-name: pulse` on the dot of every
   running work order, and there is no `@keyframes pulse` anywhere in the
   console's original: its sheets define sc-shine, glow, fadeIn, riseIn, popIn,
   growX, shimmer and spin, and that is the complete list. The declaration is
   inert there. The dot is static, and a screenshot of it is a still image.

   The Mac app's original DOES define `pulse`, and its status dots really do
   breathe. So a single global `@keyframes pulse` cannot serve both: defining it
   would start the console's six work-order dots animating against an original
   where they never move, and the first thing to notice would be that the
   byte-identical screenshots had stopped being byte-identical.

   Renaming the Mac app's copy keeps both true at once. The console's markup
   still writes the literal `pulse` it was authored with and still resolves to
   nothing; the panel writes `pulseMacApp` and animates. What differs from the
   original is one identifier that no rendering can observe — which is the same
   bar `riseInSignIn` had to clear. It is also why `animation-name` is the one
   animation property compare.mjs deliberately does not compare; see the note
   beside PROPS.motion there. */
@keyframes riseInMacApp {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes pulseMacApp {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

/* Scales the progress fill from 90% to full and back, on `alternate`. It is the
   design's way of saying "this number is still moving" without moving the
   number, which would make it unreadable. */
@keyframes creepMacApp {
  from { transform: scaleX(0.9); }
  to   { transform: scaleX(1); }
}


/* ----------------------------------------------------------------------------
   5. TYPOGRAPHY
   ----------------------------------------------------------------------------
   The original has no type scale. 27 distinct sizes across 281 declarations,
   hand-tuned in 0.5px steps, and no `h1`-`h6`, `p`, or base `font-size` rule
   anywhere. Sizes therefore stay on the elements that use them; only the
   genuinely repeated typographic devices are named here.

   Two rules that matter more than any size:

   NUMERALS. `var(--num)` carries 173 of the 176 font-family declarations, with
   `font-variant-numeric: tabular-nums` on 72 elements and `text-align: right`
   on 41. Every metric is monospaced, tabular and right-aligned. This is the
   strongest signature in the design — get it wrong and no other fidelity
   matters.

   WEIGHTS. Only 400, 500 and 600 appear, and only in 27 explicit declarations;
   everything else inherits normal. There is no 700 and no italic anywhere.
   ---------------------------------------------------------------------------- */

/* The original wrote <div> where this port writes <h1>, <h2> and <table>. UA
   stylesheets would give those `font-weight: bold` (700, a weight the design
   never uses and the font files would have to synthesise) plus block margins
   the original never had. Neutralising them here is what keeps the semantic
   markup invisible in the render; each heading then sets its own size and
   margin inline, exactly as the reference did. */
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
}

/* Same idea for the lists. Every <ul> here carries `list-style: none` inline so
   no marker is painted either way, but a bare <li> still computes to
   `display: list-item` where the capture's <div> computed `block`, and the
   list-item box keeps a marker slot in the layout that a plain block does not.
   Forcing `block` is what makes div -> ul/li a pure markup change. The rule is
   safe against the flex and grid list items on triage because those set their
   own `display` inline, which wins on specificity of origin order. */
li {
  display: block;
}

table {
  border-spacing: 0;
  border-collapse: collapse;
}

th {
  font-weight: inherit;
  text-align: left;
}

/* Uppercase micro-labels are the recurring typographic device: `var(--num)` at
   9.5-12px, wide tracking, `--ink2`. Tracking varies per instance (0.04em
   through 0.16em) so it is not baked in here; these two cover the common pair.
   Display type goes the other way, tightening to -0.01/-0.02/-0.03em as it
   grows, which is why the large headings set letter-spacing inline. */
.eyebrow {
  font-family: var(--num);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink2);
}

.eyebrow-lg {
  font-family: var(--num);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink2);
}

/* Non-uppercase mono meta text, the second most repeated text treatment. */
.meta {
  font-family: var(--num);
  font-size: 11px;
  color: var(--ink2);
}

/* Every numeric readout in the design. Named because the tabular-nums is the
   part that gets forgotten, and without it columns of figures stop lining up. */
.num {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
}

/* There is deliberately no `.sr-only` here. The one place the reference markup
   is genuinely deficient is the password input, which has no label — and that
   is fixed with `aria-label` on the input rather than a visually-hidden
   <label>, because a label is an element and the field row is `display: flex`,
   where an extra element is an extra item. A visually-hidden class invites
   exactly the addition this fixture cannot afford, so it is not offered. */


/* ----------------------------------------------------------------------------
   6. LAYOUT
   ---------------------------------------------------------------------------- */

/* Screen root. Identical in all three captures. */
.shell {
  min-height: 100vh;
  background: var(--page);
  color: var(--ink);
  font-family: var(--ui);
}

/* The truncation idiom, used 66 times. `min-width: 0` is the load-bearing half:
   without it a flex item refuses to shrink below its content width and the
   ellipsis never appears, which is the single most common way this design
   breaks when someone re-implements it. */
.grow {
  flex: 1 1 0%;
  min-width: 0;
}

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Console's two-column page: fixed sidebar, fluid main. */
.console-page {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  background: var(--page);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.6;
}

.sidebar {
  border-right: 1px solid var(--hair);
  background: var(--card);
  display: flex;
  flex-direction: column;
  padding: 18px 0 14px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: auto;
}

/* PER-SCREEN — console and triage were authored independently and their
   two-column splits drifted apart. Kept separate deliberately. */
.two-col {
  display: grid;
  gap: 34px;
}

/* THE PAGE'S MEASURE.
 *
 * Was a flat 1080px, which on a 1512px laptop left about 430px of empty page
 * down the right of every screen -- and on Sourcing, which is a form beside a
 * work-order list beside a collector dashboard, that is a lot of room given
 * up for a column width only prose needs.
 *
 * 1560 rather than unbounded: past it the Recruiter Lite form's three columns
 * stretch far enough apart that a label and its field stop reading as a pair.
 *
 * AND THAT IS THE ONLY PAGE IT IS TRUE OF, which is why the cap has moved off
 * the page shell and onto the outlet of the screen that needs it. As a blanket
 * rule it charged every other page for Sourcing's problem: on a wide monitor
 * Deciding ended at 1560 and left a dead column down the right with the
 * Settings control floating alone in it, while the queue rail -- the thing
 * Ryan actually reads down -- stopped short of the edge. His words: "just so
 * more of that dashboard is used up ya know".
 *
 * Nothing else needs it. The prose on these screens already carries its own
 * measure where it matters (the deciding card's headline is capped at 24ch and
 * its role line at 48ch), so widening the shell widens the LISTS and the rail,
 * which are the parts that get better with room.
 */
/* "spend", not "sourcing": that is the key in the PAGES table, and the outlet
   carries the key. A selector written from the screen's NAME rather than its
   key matches nothing, which would have removed the cap from the one page it
   was protecting -- silently, and only visible on a monitor wide enough to
   pull the form apart. */
.screen-console .console-page [data-page="spend"] { max-width: 1560px; }

/* Deciding used to be the exception at 1180px, on a reading-measure argument:
   its card is prose and a list, and both get harder to read the wider they go.
   That argument was about a card of one column. The card has been two columns
   for a while -- the summary on the left, what the screen said on the right --
   so the measure is set by the COLUMN, not by the page, and capping the page
   only left 700px of empty grey beside it on a 1980px screen. Ryan: "why is it
   taking up like half the screen this is jsut dumb". */

.screen-console .two-col {
  grid-template-columns: minmax(0, 1.45fr) minmax(240px, 1fr);
  margin-top: 26px;
  padding-top: 24px;
  border-top: 1px solid var(--hair);
}

.screen-triage .two-col {
  grid-template-columns: minmax(0, 1.4fr) minmax(220px, 1fr);
  margin-top: 30px;
}

.screen-console .card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 24px;
}

/* NARROW WINDOWS.
 *
 * The console had exactly one @media rule and it was `min-width: 24px` on an
 * unrelated element -- so every width from a 1512px laptop down to a phone got
 * the same fixed 236px rail beside a column that could shrink to nothing. The
 * shell never overflowed, because `minmax(0, 1fr)` always yields, so this was
 * never a horizontal-scroll bug; it was the content column quietly starving
 * while a navigation rail that does not need to be 236px wide kept every pixel
 * it had. At 900px the rail is a quarter of the window. On a 390px phone it is
 * 60% of it.
 *
 * 1000px is where the two-column body stops working rather than where the
 * shell breaks. `.two-col` is `minmax(0, 1.45fr) minmax(240px, 1fr)` with a
 * 34px gap, so under 1000 the left column -- the one holding the form or the
 * record -- falls below about 430px while the right keeps its 240 floor. That
 * is the point at which the narrower column should stop being a column.
 *
 * Below it the rail becomes a band across the top: sticky and 100vh are both
 * wrong once it is not a column, and its right border becomes a bottom one, so
 * the same hairline keeps separating it from the content. Nothing is hidden --
 * on a two-person tool the pipeline counts and the day's budget are the reason
 * the rail exists, and a disclosure that hides them would cost more than the
 * scroll it saves.
 *
 * Matches triage.css, which already reflowed at 1100/1400; this is the same
 * idea one screen over, and the two numbers differ because the layouts do.
 *
 * THIS BLOCK IS HALF THE FIX. The rail's own height and order are overridden
 * in static/recruiter/console.html, because that is where `.screen-console .sidebar`
 * (0,2,0) lives and it beats the `.sidebar` (0,1,0) below -- shipping only
 * this half left the stacked rail claiming `height: 100dvh`, which pushed the
 * content a full viewport down. The two halves share the 1000px number and
 * must be changed together. */
@media (max-width: 1000px) {
  .console-page {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: static;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--hair);
    padding-bottom: 12px;
  }

  .screen-console .two-col {
    grid-template-columns: minmax(0, 1fr);
    gap: 22px;
  }
}

.screen-triage .card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 26px;
  margin-top: 26px;
}

/* Triage's header centres its middle column between two equal fluid gutters. */
.centered-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 18px;
  padding: 14px 28px 12px;
}


/* ----------------------------------------------------------------------------
   7. COMPONENTS
   ----------------------------------------------------------------------------
   Reconstructed from the inline styles, since the original had no classes to
   inherit. Anything that differs between console and triage is scoped by
   screen: the two screens were authored separately and drifted by about a
   pixel in a dozen places. Triage is consistently the larger. A shared class
   that averages them would silently change both renders, which is exactly the
   failure this file is meant to prevent.
   ---------------------------------------------------------------------------- */

/* --- Top bar -------------------------------------------------------------- */

/* The dark sticky nav that wraps every screen, including the index. `z-index`
   is 200 in the original — high enough to clear the console sidebar, which is
   itself sticky and would otherwise slide over it. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 9px 20px;
  background: var(--ink);
  color: var(--page);
  flex-wrap: wrap;
}

.topbar-brand {
  font-family: var(--num);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.72;
}

.topbar-nav {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.topbar-note {
  font-family: var(--num);
  font-size: 11px;
  opacity: 0.6;
}

/* These are <button>s, exactly as the capture has them, and the rule sets no
   `display` on purpose. A <button> is `inline-block` in the UA sheet, which the
   flex parent then blockifies to `block` — that is why the capture computes
   `display: block; align-items: normal` here. An earlier cut used <a> plus
   `display: inline-flex; align-items: center` to get the box model back; the
   box came out identical at 66x28, but the label did not. Chromium's button
   layout centres a button's anonymous content in its content box: measured on
   this pill, the 16px label sits 6px below the top and 6px above the bottom.
   Re-tag it as `<a>` with `display: block` and the same label goes to 1px above
   and 11px below — 5px high. The centring is UA behaviour bound to the element,
   and no box-model property puts it back, so this has to be a button. */
.navbtn {
  height: 28px;
  padding: 0 12px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 99px;
  background: transparent;
  color: var(--page);
  font-family: inherit;
  font-size: 13px;
  white-space: nowrap;
}

/* The current screen inverts to a solid chip. */
.navbtn.is-active {
  border-color: var(--page);
  background: var(--page);
  color: var(--ink);
}

/* --- Metric row ----------------------------------------------------------- */

/* The core list primitive, 17 uses across the two data screens: a label that
   takes the slack and a right-aligned figure that does not. `align-items:
   baseline` is what keeps the 13.5px label and the mono figure sitting on the
   same line even though their fonts have different metrics. */
.metric-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  border-bottom: 1px solid var(--hair);
}

/* THE LABEL IS THE FIXED SIDE, THE VALUE IS THE VARIABLE ONE, and these two
   used to be the other way round -- which is what made "Location" and
   "Bridgewater, New Jersey, United States" paint on top of each other.

   With `flex: 1 1 0%; min-width: 0` on the label and `flex: 0 0 auto` on the
   value, a long value could not shrink, so the label's box was squeezed to
   nothing -- and a squeezed box does not clip its text, it just lets it
   overflow. The two strings then occupied the same pixels.

   Every label here is short and known ("Location", "Recruiter link"); the
   value is free text off LinkedIn and can be any length. So the label never
   shrinks, and the value takes the slack and WRAPS. Wrapping rather than
   truncating because a location cut off at "Bridgewater, New Jer…" has lost
   the part that distinguishes it. */
.metric-row-label {
  flex: 0 0 auto;
  color: var(--ink2);
}

.metric-row-value {
  flex: 1 1 0%;
  min-width: 0;
  /* Long single tokens -- a URL, an unspaced company name -- would otherwise
     push the row wider than its column instead of breaking. */
  overflow-wrap: anywhere;
  font-family: var(--num);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* PER-SCREEN — the drift. Triage runs a point larger and a pixel looser. */
.screen-console .metric-row { padding: 7px 0; }
.screen-triage  .metric-row { padding: 8px 0; }

.screen-console .metric-row-label,
.screen-console .metric-row-value { font-size: 13.5px; }
.screen-triage  .metric-row-label,
.screen-triage  .metric-row-value { font-size: 14.5px; }

/* --- Progress bar --------------------------------------------------------- */

/* 10 uses. The fill is scaled rather than sized wherever it animates, so the
   growth runs on the compositor. */
.bar {
  flex: 1 1 0%;
  height: 6px;
  border-radius: 99px;
  background: var(--sunk);
  overflow: hidden;
  display: block;
}

/* This class deliberately does NOT set `transform-origin`, and it used to.
   The reasoning for the old `transform-origin: 0 50%` was that a fill growing
   from the default centre origin would open outward from the middle instead of
   sweeping in from the left — true, but it described elements this class is
   never applied to. Every one of `.bar-fill`'s five users is a triage Skills
   bar, and those do not animate at all (see the note at triage.html's Skills
   list); the fills that really do grow sideways are the triage ladder rungs,
   which set their origin inline because the original does.

   So the declaration changed nothing anyone could see and disagreed with the
   original on all five elements that had it: the original leaves them at the
   initial `50% 50%`, the port forced `0% 50%`. Invisible at rest, which is
   exactly why it survived until `transform-origin` was added to the harness's
   property list. Set the origin on the thing that transforms, not on the family
   it belongs to. */
.bar-fill {
  display: block;
  height: 100%;
}

/* Vertical fills (sign-in) do animate, and they grow from the bottom edge. */
.bar-fill-up {
  transform-origin: 50% 100%;
}

/* --- Count pill ----------------------------------------------------------- */

/* 7 uses, console sidebar. `min-width` plus centred text is what stops a
   one-digit and a two-digit count from rendering at different widths and
   ragging the column. */
.pill {
  flex: 0 0 auto;
  min-width: 24px;
  text-align: center;
  padding: 1px 7px;
  border-radius: 99px;
  background: var(--sunk);
  font-family: var(--num);
  font-size: 13px;
  color: var(--ink2);
  font-variant-numeric: tabular-nums;
}

/* The only colour rule in the entire system: a wash background is always
   paired with its own solid foreground, never mixed across hues. */
.pill-amber  { background: var(--amber-wash);  color: var(--amber); }
.pill-accent { background: var(--accent-wash); color: var(--accent); }
.pill-moss   { background: var(--moss-wash);   color: var(--moss); }
.pill-plum   { background: var(--plum-wash);   color: var(--plum); }
.pill-quiet  { background: transparent;        color: var(--ink2); }

/* --- Status chip ---------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  border-radius: 99px;
  font-family: var(--num);
  line-height: 1.5;
  white-space: nowrap;
}

/* PER-SCREEN — same drift as the metric row: triage's chips are taller and a
   point larger. */
.screen-console .chip { min-height: 22px; padding: 2px 9px;  font-size: 11px; }
.screen-triage  .chip { min-height: 28px; padding: 3px 12px; font-size: 12px; }

.chip-filled {
  background: var(--accent-wash);
  color: var(--ink);
}

.chip-outline {
  border: 1px solid var(--hair);
  color: var(--ink2);
}

/* The dashed border is the design's only dashed edge, and it means "not yet
   settled" — it marks pending states specifically. */
.chip-pending {
  border: 1px dashed var(--amber);
  color: var(--amber);
}

.chip-amber { background: var(--amber-wash); color: var(--amber); }
.chip-moss  { background: var(--moss-wash);  color: var(--moss); }
.chip-plum  { background: var(--plum-wash);  color: var(--plum); }

/* --- Action button -------------------------------------------------------- */

/* Triage's decision controls. */
.action {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  padding: 4px 14px;
  border-radius: 99px;
  font-family: inherit;
  font-size: 14px;
  white-space: nowrap;
}

.action-primary {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

.action-secondary {
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ink2);
}

/* --- Sidebar nav ---------------------------------------------------------- */

/* 4 uses, console. Baseline alignment again, so the label and its trailing
   count pill share a baseline rather than centring against each other. */
.sidenav-btn {
  flex: 1 1 0%;
  min-width: 0;
  text-align: left;
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-bottom: 3px;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
  font-family: inherit;
}

/* --- Timeline ------------------------------------------------------------- */

.tl-dot {
  width: 7px;
  height: 7px;
  border-radius: 99px;
  background: var(--line);
  display: block;
}

/* The connector stretches to fill whatever the adjacent content leaves, which
   is why it takes a flex grow and a minimum rather than a fixed height. */
.tl-line {
  flex: 1 1 0%;
  width: 2px;
  min-height: 10px;
  background: var(--hair);
}

/* --- Avatar --------------------------------------------------------------- */

/* Initials only — there are no images anywhere in this fixture. The original's
   capture showed a spray of empty `background-*` longhands here; those are
   serialisation noise from a `background` shorthand with no image, not design. */
.avatar {
  flex: 0 0 auto;
  border-radius: 99px;
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--num);
  overflow: hidden;
}

/* THE FACE ON THE DECISION SCREEN. Ryan: "bigger photo too". 74px was
   the same size as a list thumbnail two rows further down, on the one
   card whose whole job is a person -- and the name beside it is 34px,
   so the photo read as decoration next to the text rather than as the
   subject. Both sheets carry this scale and both move together: the
   console and the queue show the SAME card to two people, and a face
   that is one size for Ryan and another for Rich is two designs. */
.avatar-lg { width: 104px; height: 104px; font-size: 34px; }
.avatar-md { width: 60px; height: 60px; font-size: 20px; }
.avatar-sm { width: 38px; height: 38px; font-size: 13.5px; }

/* --- Card ----------------------------------------------------------------- */

/* The two shadows below are, with `none`, the only three in the design. They
   are deliberately almost invisible: elevation is carried by the hairline
   border, and the shadow only separates a raised surface from the page. */
.card {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: var(--r);
}

.card-raised {
  box-shadow: rgba(20, 22, 26, 0.08) 0 1px 3px;
}

.card-flat {
  box-shadow: rgba(20, 22, 26, 0.05) 0 1px 2px;
}

/* --- Rules and wells ------------------------------------------------------ */

.hair-top    { border-top: 1px solid var(--hair); }
.hair-bottom { border-bottom: 1px solid var(--hair); }

/* The active tab underline. 2px against the 1px used everywhere else, so it
   reads as a selection rather than as another divider. */
.tab-underline        { border-bottom: 2px solid var(--hair); }
.tab-underline.is-active { border-bottom-color: var(--accent); }

.well {
  background: var(--sunk);
  border-radius: var(--r-sm);
}

/* --- Rich email composer ------------------------------------------------- */

/* A REAL SHEET, and therefore one of the design's few shadows. The email is
   the only thing on this page that can leave Slate and reach another person;
   putting its exact preview in a fixed layer makes that boundary visible and
   keeps a poll or a role-card reflow from moving the Send button mid-review. */
.rich-mail-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  padding: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 22, 26, 0.56);
}

.rich-mail-sheet {
  width: min(1180px, 100%);
  max-height: calc(100vh - 48px);
  overflow: hidden;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: rgba(20, 22, 26, 0.28) 0 22px 70px;
}

.rich-mail-head {
  padding: 20px 22px 18px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  border-bottom: 1px solid var(--hair);
}

.rich-mail-head h2 {
  margin: 5px 0 0;
  font-size: 22px;
  font-weight: 600;
  line-height: 1.2;
}

.rich-mail-head p {
  margin: 5px 0 0;
  color: var(--ink2);
  font-size: 13.5px;
  line-height: 1.45;
}

.rich-mail-close {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--hair);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink2);
  font: 22px/1 var(--sans);
}

.rich-mail-body {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(300px, 0.78fr) minmax(390px, 1.22fr);
}

.rich-mail-fields {
  min-height: 0;
  overflow: auto;
  padding: 18px 20px 24px;
  border-right: 1px solid var(--hair);
}

.rich-mail-group {
  min-width: 0;
  margin: 0 0 18px;
  padding: 0;
  border: 0;
}

.rich-mail-group legend { padding: 0; }

.rich-mail-templates {
  margin-top: 7px;
  display: grid;
  gap: 6px;
}

.rich-mail-template {
  width: 100%;
  padding: 10px 11px;
  display: grid;
  gap: 3px;
  text-align: left;
  border: 1px solid var(--hair);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
}

.rich-mail-template.is-selected {
  border-color: var(--plum);
  background: var(--plum-wash);
}

.rich-mail-template strong {
  font-size: 13.5px;
  font-weight: 600;
}

.rich-mail-template span {
  color: var(--ink2);
  font-size: 11.5px;
  line-height: 1.35;
}

.rich-mail-roles {
  max-height: 174px;
  margin-top: 7px;
  overflow: auto;
  border: 1px solid var(--hair);
  border-radius: var(--r-sm);
}

.rich-mail-role {
  min-height: 47px;
  padding: 8px 10px;
  display: flex;
  align-items: flex-start;
  gap: 9px;
  border-bottom: 1px solid var(--hair);
}

.rich-mail-role:last-child { border-bottom: 0; }
.rich-mail-role input { margin: 3px 0 0; }
.rich-mail-role span { min-width: 0; display: grid; gap: 2px; }
.rich-mail-role strong { font-size: 13px; font-weight: 600; }
.rich-mail-role small { color: var(--ink2); font-size: 11px; line-height: 1.35; }

.rich-mail-label {
  margin-top: 15px;
  display: grid;
  gap: 6px;
}

.rich-mail-label input,
.rich-mail-label textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--page);
  color: var(--ink);
  font: 14px/1.45 var(--sans);
}

.rich-mail-label input { height: 40px; padding: 0 11px; }
.rich-mail-label textarea { min-height: 106px; padding: 9px 11px; resize: vertical; }

.rich-mail-check {
  min-height: 34px;
  margin-top: 11px;
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--ink);
  font-size: 12.5px;
}

.rich-mail-refresh,
.rich-mail-cancel,
.rich-mail-send {
  height: 36px;
  padding: 0 14px;
  border-radius: var(--r-sm);
  font-size: 13.5px;
  font-weight: 600;
}

.rich-mail-refresh {
  width: 100%;
  margin-top: 14px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
}

.rich-mail-error,
.rich-mail-setup {
  margin-top: 10px;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  font-size: 12px;
  line-height: 1.45;
}

.rich-mail-error { background: var(--amber-wash); color: var(--amber); }
.rich-mail-setup { background: var(--sunk); color: var(--ink2); }

.rich-mail-preview {
  min-width: 0;
  min-height: 0;
  padding: 18px 20px 20px;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  background: var(--page);
}

.rich-mail-preview-head {
  padding: 0 2px 10px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
}

.rich-mail-preview-head > div { display: grid; gap: 3px; }
.rich-mail-preview-head strong { font-size: 12.5px; font-weight: 600; }
.rich-mail-preview-head > span { color: var(--moss); font-family: var(--num); font-size: 10.5px; }
.rich-mail-preview-head > span.is-dirty { color: var(--amber); }

.rich-mail-preview iframe,
.rich-mail-preview-empty {
  width: 100%;
  height: 100%;
  min-height: 440px;
  border: 1px solid var(--hair);
  border-radius: var(--r-sm);
  background: #f3f2f0;
}

.rich-mail-preview-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink2);
  font-size: 13px;
}

.rich-mail-foot {
  min-height: 62px;
  padding: 12px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  border-top: 1px solid var(--hair);
}

.rich-mail-foot > span { color: var(--ink2); font-family: var(--num); font-size: 11px; }
.rich-mail-foot > div { display: flex; gap: 8px; }
.rich-mail-cancel { border: 1px solid var(--line); background: transparent; color: var(--ink); }
.rich-mail-send { border: 1px solid var(--accent); background: var(--accent); color: var(--on-accent); }

.rich-mail-sheet button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

@media (max-width: 760px) {
  .rich-mail-backdrop { padding: 0; align-items: stretch; }
  .rich-mail-sheet { max-height: 100vh; border: 0; border-radius: 0; }
  .rich-mail-head { padding: 16px; }
  .rich-mail-head p { display: none; }
  .rich-mail-body { display: block; overflow: auto; }
  .rich-mail-fields { overflow: visible; border-right: 0; border-bottom: 1px solid var(--hair); }
  .rich-mail-preview { min-height: 560px; }
  .rich-mail-preview iframe { min-height: 500px; }
  .rich-mail-foot { padding: 10px 14px; }
}
