/* Svanzen — built from design_system/ by scripts/build_ds_css.py.
   DO NOT EDIT: every rule below is copied from the file its banner names, and
   the ONE transformation applied is noted where it happens. Edit the named
   file and rebuild. The pinned design-system revision and the sync-back
   runbook are in docs/design/DS_SYNC.md. */
/* ==== design_system/tokens/fonts.css ==== */
/* 3 face declaration(s) removed here by scripts/build_ds_css.py.
   This application declares its own, against absolute /static/fonts/ URLs
   that its allowlist route actually serves — ui_tokens.SVANZEN_FONT_HTML and
   webhook.py's _FONT_FILES. The commentary around this point argues for faces
   that are declared THERE, not here. */
/* ONE TYPEFACE, ONE FILE, ONE REQUEST. Figtree is a variable font with a
   wght axis; `font-weight: 400 800` is a RANGE, not a typo. The upstream axis
   runs 300-900 — 400 800 is what we DECLARE, so a stray 900 clamps to 800
   rather than quietly opening a ninth weight into the ramp.
   Self-hosted: a same-origin fetch has no third party to trust. */
/* THE TWO INDIC FACES, NOW SELF-HOSTED LIKE EVERY OTHER FACE HERE — a public
   register page carries no third-party font request, which was the whole
   argument for self-hosting Figtree and applies identically to these.
   VARIABLE, WITH TWO AXES. Both files carry wdth as well as wght; `400 700`
   is what we DECLARE on the weight axis, so a stray 800 clamps rather than
   quietly opening a weight the Latin ramp does not have. The width axis is
   left at its default: a condensed Kannada is a different typographic
   decision, not a fallback.
   TTF, NOT WOFF2, and that is honest rather than ideal: these are the upstream
   binaries from google/fonts, ~640 KB each against Figtree's 27 KB. Subset and
   convert them before production — the family names do not change when you do,
   so nothing else in this system moves. */
:root{
  --sv-font: Figtree, system-ui, -apple-system, sans-serif;
  /* NAMED INDIC STACKS, NOT A SYSTEM FALLBACK. A script that resolves to
     whatever the OS happens to carry is not multilingual support. Set the
     stack AND the 1.4 line-height floor (--sv-lh-indic) on the element. */
  --sv-font-kannada: 'Noto Sans Kannada', Figtree, system-ui, sans-serif;
  --sv-font-devanagari: 'Noto Sans Devanagari', Figtree, system-ui, sans-serif;
  --sv-lh-indic: 1.4; /* @kind font */
}
/* ==== design_system/tokens/colors.css ==== */
/* GROUNDS, RAMP, BOUNDARIES, ACCENT — and the rules that are not
   negotiable are written at the token that carries them.
   No tinted page colour anywhere: photographs carry all the colour. */
:root{
  /* ---- grounds ---- */
  --sv-ground:#FFFFFF;        /* the canvas */
  --sv-card:#FFFFFF;          /* anything raised off it. SAME hex on purpose:
                                 a card is separated by a hairline and a 16px
                                 radius, never by a fill step or a shadow. */
  --sv-well:#F7F7F7;          /* recessed — search rest, segment track, plate
                                 tiles, empty photo slots */
  --sv-band:#1A1A1A;          /* the dark compositional band. NOT A THEME —
                                 dark mode is explicitly unsupported. */

  /* ---- the text ramp. Ratios are (white / #F7F7F7 well). ---- */
  --sv-ink:#222222;           /* display type (15.91 / 14.85) */
  /* THE CANONICAL NAME, AND WHY IT CHANGED. `--sv-body` sat one keystroke from
     `--sv-body-size` in a different file, so an author autocompleting
     `--sv-body…` got a colour where they expected a type size. Renamed while no
     external consumer exists; the old name stays as an alias below, because
     direct edits made in the editor may already reference it. */
  --sv-text-body:#484848;     /* body prose — anything actually read (9.15 / 8.54) */
  --sv-body:var(--sv-text-body); /* @kind color */ /* deprecated alias — prefer --sv-text-body */
  --sv-quiet:#6F6F6F;         /* the quiet tier: meta, field names (5.02 / 4.69) */
  --sv-quiet-page:#6B6B6B;    /* the quiet tier where the ground is only ever
                                 white — also the band's border colour */
  /* NOT A TEXT COLOUR AT ANY SIZE. 2.75:1 on white. It cannot be a
     placeholder, a plate id, a pill label, an icon stroke or a date.
     Non-text fills only: a disabled control, an empty tile, a rule. */
  --sv-tint:#9C9C9C;

  /* ---- boundaries. A BORDER IS A BOUNDARY, NEVER A WORD. ---- */
  --sv-border-control:#8A8A8A;   /* 3:1 floor. Every text control's outline,
                                    every PRESSABLE chip, dashed absence
                                    edges — anything a border must be seen to
                                    bound. */
  --sv-border-decorative:#DDDDDD;/* 1.36:1 — decoration beside a word or glyph
                                    that carries the meaning. Static chips
                                    only; never a pressable one. */
  --sv-border-soft:#EBEBEB;      /* hairlines: card edges, section rules, rows */
  --sv-border-empty:#E0E0E0;     /* the lightest dashed edge, and its one user:
                                    an upload cell with nothing asked of it */
  --sv-border-on-band:#6B6B6B;   /* borders on the band */

  /* ---- THE ACCENT, SPLIT INTO THREE SEMANTIC TOKENS. All three resolve to
     one hex today and MUST be able to diverge — that is why they are three
     names and not one. Never body prose, a hairline, a section heading or an
     ordinary link. ---- */
  --sv-cobalt:#2C50C8;            /* the raw value. Prefer a role below. */
  --sv-brand-accent:#2C50C8;      /* the wordmark. Nothing else. */
  --sv-action-primary:#2C50C8;    /* CTA fill, links, focus */
  /* THE PRODUCT'S OWN VOICE (external review F1). A vow is never pressed,
     never navigates and never takes focus, so it is not an action — it read
     --sv-action-primary only because both were cobalt on the day it was
     written, which is exactly the collapse these names exist to prevent. The
     day action.primary moves for a CTA reason, the vows must not move with it. */
  --sv-voice:#2C50C8;
  --sv-evidence-attested:#2C50C8; /* a claim WE verified, with a named
                                     predicate. Never a general shield. */
  --sv-on-cobalt:#FFFFFF;
  /* ON THE BAND cobalt measures 2.56:1 — illegal. The primary inverts to a
     white pill with ink text; the accent does not appear on the band. */
  --sv-on-band:#FFFFFF;
  /* Band prose (external review F4): 12.21:1 on the band. Was the one colour
     in the system with no name — nothing could reference it and no card could
     document it. */
  --sv-on-band-prose:#D8D8D8;
  --sv-band-action-bg:#FFFFFF;
  --sv-band-action-fg:#222222;

  /* ---- the four translucent inks. Painted ON TOP of a photograph, so they
     cannot be flat greys: what is underneath moves. ---- */
  --sv-burnt:rgba(34,34,34,.78);       /* a label burnt onto a generated image;
                                          a clip's duration badge */
  --sv-scrim-film:rgba(34,34,34,.55);  /* play disc on GENERATED footage */
  --sv-scrim:rgba(34,34,34,.42);       /* behind the bottom sheet */
  --sv-overlay:rgba(255,255,255,.94);  /* play disc on REAL footage; the save
                                          button on a card's photograph */

  /* ---- focus. 2px RING at 2px offset, COBALT since 4.2 (9 Sep 2026).
     Colour is never the sole carrier of meaning, so focus is a RING and not a
     colour change — that is why the ring can take the accent without the
     accent becoming the signal. Cobalt measures 6.80:1 on white, the same
     pair this system already computed for the count chip.

     THE WIDTH DID NOT MOVE, AND THE KIT ASSUMED IT WOULD. The cobalt kit's
     own tokens.json declares a 3px ring at 3px offset and computed its 64
     contrast pairs against that ring; the handoff ships the colour alone at
     2px/2px (§A2c). What ships is therefore this system's ring in the kit's
     colour, and the kit's verification describes neither. Logged in
     github.md. ---- */
  --sv-focus:#2C50C8;
  --sv-focus-on-band:#FFFFFF;
  /* The filled primary is a saturated cobalt ground, and since 4.2 the ring
     is cobalt too — so an outside ring is no longer merely optically close to
     the button's edge, it is the SAME COLOUR as the fill. What was a judgement
     call under the ink ring is now structural: the ring must go inside.
     White drawn INSIDE the fill measures 6.80:1 on cobalt. */
  --sv-focus-on-cobalt:#FFFFFF;
}
/* ==== design_system/tokens/typography.css ==== */
/* THE TYPE RAMP — 4.2, THE COBALT REFRESH (9 Sep 2026). TEN ROLES, EACH WITH
   ONE JOB, cut from the portal
   screens the system now dresses (explorations/upgrade-2026-09, 2 Sep 2026).
   Six roles were declared before this and only five were distinguishable;
   4.0 re-spaces the ramp so display and title are 4px apart on a phone and
   6px past 900, and names the four sizes the record card actually needed
   and had been improvising: name, lede, control, voice.

   ONE DISPLAY PER SCREEN. Every prose sentence takes body — including the
   honesty lines, which are the sentences most likely to be shrunk.
   WEIGHT 600 IS THE CEILING; 800 is retired except the wordmark.
   MICRO IS A FLOOR, not a scale point. Nothing in this system is smaller.

   EVERY ROLE NAME STILL RESOLVES; 4.2 moved values, never names.

   WHAT 4.2 DID, AND WHAT IT COSTS. The cobalt kit's ramp raises the phone
   sizes to roughly 4.0's desktop sizes and loosens every heading's leading:
   body 15→16, meta 13→14, micro 12→13, lede 16→17, title 24→27, display
   28→32, and display-lh 1.06→1.15, title-lh 1.1→1.2, name-lh 1.15→1.3.
   It is the second re-cut of this ramp in a week and it runs opposite to the
   first: 4.0 (2 Sep, founder-approved) had tightened body, meta and title
   DOWN. Adopted 9 Sep 2026 per COBALT_KIT_HANDOFF_20260909 §A2b.

   TEN ROLES, EIGHT SIZES. Two pairs now share a size and are told apart by
   something other than scale: figure and display both 32 (letter-spacing and
   tabular numerals separate them), control and meta both 14 (weight 500 vs
   400, ink vs quiet). Before 4.2 every role had its own step.

   THE MICRO FLOOR MOVED, 12→13. Nothing in this system is smaller than 13px
   now. The console's ratified 10–11.5px divergence (github.md) is therefore
   one step further from the floor than the day it was ratified.

   THE VOW IS THE OPEN QUESTION. Voice stays 18 while lede rises to 17 and
   body to 16, so the vow's own step is 1px over the lede and 2px over body —
   it is now carried by cobalt and weight 600, not by size. The pin
   (voice ≥ lede ≥ body) still passes; the reason the step exists no longer
   shows. Raising voice was not in the handoff's table, so it is NOT done
   here: it is logged open in github.md for a founder ruling. */
:root{
  /*      role        phone    ·  what it is for */
  --sv-display:32px;   /* @kind font */ /* Your questions · Saved · a vendor's name on their page */
  --sv-display-lh:1.15; /* @kind font */
  --sv-display-ls:-.03em; /* @kind font */
  --sv-title:27px;     /* @kind font */ /* the question as a page heading · a quoted amount */
  --sv-title-lh:1.2;   /* @kind font */
  --sv-title-ls:-.025em; /* @kind font */
  --sv-name:21px;      /* @kind font */ /* a maker's name on a card */
  --sv-name-lh:1.3;   /* @kind font */
  --sv-name-ls:-.015em; /* @kind font */
  --sv-section:21px;   /* @kind font */ /* section heads share the name step */
  --sv-section-lh:1.3; /* @kind font */
  --sv-section-ls:-.01em; /* @kind font */
  --sv-voice-size:18px; /* @kind font */ /* the vow, and only the vow */
  --sv-voice-lh:1.4;   /* @kind font */
  --sv-lede:17px;      /* @kind font */ /* a question in the list · the ask field · the lede under a display */
  --sv-lede-lh:1.5;    /* @kind font */
  --sv-body-size:16px; /* @kind font */ /* anything read as a sentence */
  --sv-body-lh:1.55;    /* @kind font */
  --sv-control-size:14px; /* @kind font */ /* tabs · chips · contact tiles. NOT buttons: `patterns/controls.css` keeps `.sv-btn` at body — 16 at every width since 4.2 — the bundle follows the pattern, and the products follow the bundle. Corrected 8 Sep 2026; body value restated 9 Sep 2026. NOTE: control and meta are the same 14px now, so this step is told from meta by WEIGHT (500 vs 400) and COLOUR (ink vs quiet), not size. */
  --sv-control-lh:1.35; /* @kind font */
  --sv-meta-size:14px; /* @kind font */ /* kind · place · facts · dates beside a claim */
  --sv-meta-lh:1.5;   /* @kind font */
  --sv-micro:13px;     /* @kind font */ /* question numbers · timeline dates · state-rail labels. THE FLOOR, raised from 12 by 4.2. */
  --sv-micro-lh:1.4;   /* @kind font */
  --sv-figure:32px;    /* @kind font */ /* one string a human copies by eye: the login code. Class: .sv-code (.sv-figure is the media frame). Same 32px as display on a phone since 4.2 — the letter-spacing (.14em) and tabular numerals are what separate them, not the size. */
  --sv-figure-ls:.14em; /* @kind font */
  --sv-w-regular:400;  /* @kind font */
  --sv-w-medium:500;   /* @kind font */
  /* 600 is the ceiling for every role; 800 survives for the wordmark alone. */
  --sv-w-semi:600;     /* @kind font */
  --sv-w-wordmark:800; /* @kind font */
  --sv-wordmark-size:22.8px; /* @kind font */
  --sv-mark-size:40px; /* @kind spacing */
}
@media (min-width:900px){
  :root{
    --sv-display:38px;
    --sv-title:31px;
    /* THE RAMP STOPS STEPPING BELOW TITLE. name, section, lede, body, meta,
       control, voice, micro and figure are one size at every width now: the
       phone ramp came up to meet the desktop one, so seven of the ten roles
       have nothing left to step to. Past 900px only display and title grow,
       and that is the whole of the desktop difference. */
  }
}
.sv-display{font-size:var(--sv-display);line-height:var(--sv-display-lh);letter-spacing:var(--sv-display-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0;text-wrap:pretty}
.sv-title{font-size:var(--sv-title);line-height:var(--sv-title-lh);letter-spacing:var(--sv-title-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0;text-wrap:pretty}
.sv-name{font-size:var(--sv-name);line-height:var(--sv-name-lh);letter-spacing:var(--sv-name-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0}
.sv-section-t{font-size:var(--sv-section);line-height:var(--sv-section-lh);letter-spacing:var(--sv-section-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0}
.sv-lede{font-size:var(--sv-lede);line-height:var(--sv-lede-lh);font-weight:var(--sv-w-regular);color:var(--sv-text-body);margin:0;text-wrap:pretty}
.sv-prose{font-size:var(--sv-body-size);line-height:var(--sv-body-lh);font-weight:var(--sv-w-regular);color:var(--sv-text-body);margin:0;text-wrap:pretty}
.sv-control-t{font-size:var(--sv-control-size);line-height:var(--sv-control-lh);font-weight:var(--sv-w-medium);color:var(--sv-ink);margin:0}
.sv-meta{font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);font-weight:var(--sv-w-regular);color:var(--sv-quiet);margin:0}
.sv-micro-t{font-size:var(--sv-micro);line-height:var(--sv-micro-lh);font-weight:var(--sv-w-semi);color:var(--sv-quiet);margin:0}
.sv-code{font-size:var(--sv-figure);letter-spacing:var(--sv-figure-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);font-variant-numeric:tabular-nums;margin:0}
/* THE PRODUCT SPEAKING IN ITS OWN VOICE — a promise about what this product
   refuses to do. 4.0 gives it its own step (18): larger than body, because a
   vow is the one sentence a screen should not let a reader skim past, and
   never fine print. */
.sv-vow{font-size:var(--sv-voice-size);line-height:var(--sv-voice-lh);letter-spacing:-.01em;font-weight:var(--sv-w-semi);color:var(--sv-voice);margin:0;text-wrap:pretty}
.sv-num{font-variant-numeric:tabular-nums}
.sv-kannada{font-family:var(--sv-font-kannada);line-height:var(--sv-lh-indic)}
.sv-devanagari{font-family:var(--sv-font-devanagari);line-height:var(--sv-lh-indic)}
/* Visually hidden, still announced. */
.sv-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* ==== design_system/tokens/radii.css ==== */
/* THREE VALUES UNDER SIX NAMES — 4.2, the cobalt refresh (9 Sep 2026).
   Until 9 Sep this was SIX STEPS and the note here said a seventh would be a
   decision, not a nudge. The refresh did something the note did not anticipate:
   it halved the scale rather than extending it. Three pairs now resolve to the
   same number — input and pill at 4, card and grid at 8, sheet and frame at 12.

   EVERY NAME IS KEPT ON PURPOSE. Nothing that reads a radius token has to
   change, and the pairs must be able to diverge again — the day a sheet needs
   a different corner from a photographic frame, the name is already there and
   only its value moves. That is the same argument the three cobalt tokens rest
   on in `tokens/colors.css`.

   WHAT THAT COSTS, STATED PLAINLY. Two distinctions this system used to draw
   with shape are now drawn only in documentation: a native field is no longer
   a different shape from a pressable control, and a bottom sheet is no longer
   a different shape from a photograph's frame. If either distinction turns out
   to matter, it needs a value, not a comment.

   "PILL" IS NOW A NAME, NOT A SHAPE. It carries buttons, chips, segmented
   toggles, the search row, the doors, the OTP boxes and the sheet's grab
   handle — seventeen readers across `patterns/` — and at 4px none of them is a
   pill. Renaming it would break every one of those readers for no drawn
   difference, so the name stays and this paragraph is the correction. */
:root{
  --sv-r-input:4px;   /* text controls and every native input */
  --sv-r-pill:4px;    /* buttons, chips, segmented toggles, the search field —
                         same value as input since 4.2, still its own name */
  --sv-r-card:8px;    /* a card, and a photo grid's wrapper */
  --sv-r-grid:8px;    /* the wrapper around a mosaic — same step as a card */
  --sv-r-sheet:12px;  /* the bottom sheet's TOP corners, and only that */
  --sv-r-frame:12px;  /* a photographic frame standing on its own */
  --sv-r-round:50%;   /* icon buttons — a circle, not a rounded square. THE ONE
                         STEP 4.2 DID NOT TOUCH: a circle is geometry, not a
                         point on a scale. */
}
/* ==== design_system/tokens/elevation.css ==== */
/* FOUR USES, AND NO FIFTH. A shadow says "this floats", not "this is
   evidence": cards, media and evidence rows are FLAT. Layered or transient
   surfaces only, never media. */
:root{
  --sv-el-search:0 1px 6px rgba(32,33,36,.18);                        /* search field at rest */
  --sv-el-search-open:0 1px 6px rgba(32,33,36,.28),0 4px 20px rgba(32,33,36,.13); /* open / focused */
  --sv-el-knob:0 1px 4px rgba(0,0,0,.12);                             /* selected segment knob */
  --sv-el-sheet:0 -10px 40px rgba(0,0,0,.18);                         /* bottom sheet */
  /* pressed button, inset */
  --sv-el-pressed:inset 0 2px 6px rgba(32,33,36,.22); /* @kind shadow */
}
/* ==== design_system/tokens/motion.css ==== */
/* FIVE DURATIONS, ONE CURVE. */
:root{
  /* screen enter */
  --sv-t-screen:280ms;  /* @kind other */
  /* shared-element expand; the sheet rising */
  --sv-t-expand:380ms;  /* @kind other */
  /* search-to-pill: the field arriving as a card */
  --sv-t-pill:300ms;    /* @kind other */
  /* hover, and the press */
  --sv-t-hover:160ms;   /* @kind other */
  /* ease-out */
  --sv-ease:cubic-bezier(0,0,.2,1); /* @kind other */
  --sv-press-scale:.97;   /* @kind other */
  --sv-press-opacity:.92; /* @kind other */
}
@keyframes svScreenIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes svHeroExpand{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}
@keyframes svPillMorph{from{opacity:0;transform:scaleX(1.06) translateY(-6px)}to{opacity:1;transform:none}}
@keyframes svSheetUp{from{transform:translateY(101%)}to{transform:none}}
@keyframes svScrimIn{from{opacity:0}to{opacity:1}}
/* .sv-anim-screen / -expand / -pill were declared here from 20 Aug to 2 Sep 2026
   and never bound by anything upstream or here; retired 2 Sep (owner: fix all).
   The keyframes stay: .sv-refcard, .sv-sheet and the screen roots bind them
   directly. */
/* prefers-reduced-motion is not a weakening of the design; it is the design,
   for the reader who asked. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:1ms!important;animation-iteration-count:1!important;transition-duration:1ms!important}
}

/* THE THROBBER, ANIMATED FROM OUTSIDE. The asset store strips <style> and
   SMIL from an .svg on save, so assets/svanzen-loading.svg is a static frame
   and these rules animate it when it is INLINED (the Throbber pattern and the
   brand card do). An <img> cannot be reached by them AND cannot fetch the
   linked swan, so the static file is inline-only; the self-contained SMIL
   version for <img src> is assets/svanzen-loading.animated.svg.txt. */
.sv-throbber .ring-cw{transform-origin:200px 200px;animation:svThrobCw 1.6s linear infinite}
.sv-throbber .ring-ccw{transform-origin:200px 200px;animation:svThrobCcw 9s linear infinite}
.sv-throbber .ripple{transform-origin:200px 205px;opacity:0;animation:svThrobRipple 3.2s cubic-bezier(.2,.7,.3,1) infinite}
.sv-throbber .r2{animation-delay:.8s}.sv-throbber .r3{animation-delay:1.6s}.sv-throbber .r4{animation-delay:2.4s}
.sv-throbber .swan-group{animation:svThrobBob 2.4s ease-in-out infinite}
.sv-throbber .swan-shadow{transform-origin:200px 268px;animation:svThrobShadow 2.4s ease-in-out infinite}
.sv-throbber .dot{animation:svThrobDot 1.2s ease-in-out infinite}.sv-throbber .d2{animation-delay:.2s}.sv-throbber .d3{animation-delay:.4s}
@keyframes svThrobCw{to{transform:rotate(360deg)}}
@keyframes svThrobCcw{to{transform:rotate(-360deg)}}
@keyframes svThrobRipple{0%{transform:scale(.55);opacity:0}20%{opacity:.9}100%{transform:scale(1.9);opacity:0}}
@keyframes svThrobBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
@keyframes svThrobShadow{0%,100%{transform:scaleX(1)}50%{transform:scaleX(.86)}}
@keyframes svThrobDot{0%,100%{opacity:.25}50%{opacity:1}}
@media (prefers-reduced-motion:reduce){.sv-throbber *{animation:none!important}.sv-throbber .ripple{opacity:.5;transform:scale(1.2)}}
/* ==== design_system/tokens/layout.css ==== */
/* NO FIXED-WIDTH CANVASES. Two gutters, one phone cap, one desktop column,
   one breakpoint — and the app bar publishes its own height because CSS
   cannot ask a sibling how tall it is. */
:root{
  --sv-gutter:22px;        /* brand / shop / product / stone blocks */
  --sv-gutter-wide:26px;   /* profile and claim title blocks */
  --sv-phone:440px;        /* the phone column's cap */
  --sv-wide:760px;         /* the desktop reading column */
  --sv-bp-wide:900px;      /* @kind spacing */ /* a media query cannot read a var */
  --sv-tap:44px;           /* the tap floor. A HEIGHT, never a width. */
  /* the arithmetic, not the answer */
  --sv-app-h:calc(6px + 44px + 8px); /* @kind spacing */
  --sv-stack:10px;         /* the rhythm between stacked blocks */
  --sv-stack-loose:30px;   /* between result cards */
}
@media (min-width:900px){
  :root{--sv-gutter:32px;--sv-gutter-wide:36px}
}
/* ==== design_system/base/elements.css ==== */
/* Element defaults, the radius application, and the focus ring.
   THE RADIUS RESET IS EXPLICIT, NOT ABSENT: every browser paints its own
   3-6px round on native controls, different in each engine. Naming the step
   is what stops that being a shape nobody chose — and since 4.2 put the
   named step at 4px, INSIDE that range, naming it matters more rather than
   less: the shape is now close enough to each engine's default that an
   unnamed control would look almost right and be wrong. */
*{box-sizing:border-box}
html{background:var(--sv-ground)}
body{margin:0;background:var(--sv-ground);color:var(--sv-ink);font:var(--sv-w-regular) var(--sv-body-size)/var(--sv-body-lh) var(--sv-font);-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased}
input,select,textarea{border-radius:var(--sv-r-input)}
/* a radio is a circle by definition and a checkbox's box is drawn by the UA:
   these two are handed back DELIBERATELY */
input[type="checkbox"],input[type="radio"]{border-radius:revert}
/* LINKS ARE INK, AND THE SPLIT IS WRITTEN DOWN RATHER THAN SPLIT THE
   DIFFERENCE. Two of this system's own texts disagreed: the accent's role
   names "CTA, links, focus", and the colour discipline says cobalt is never an
   ordinary link. Both are right about different objects, so:

     an ORDINARY LINK — a website on a record, a term in a sentence, a
     footer link — is INK with an underline. That is what every evidence
     link in patterns/blocks.css already draws (.sv-kv .v.link), and a
     cobalt one there would read as provenance.

     a STANDALONE ACTION LINK — one that performs or begins something,
     drawn as a control — takes --sv-action-primary, and it takes it by
     wearing .sv-btn or .sv-link-action rather than by being an <a>.

   The element default is therefore the ordinary case, because that is what an
   author adding a link later actually means. Defined even where a design has
   no links yet, so one added in the editor is never browser-default blue. */
a{color:var(--sv-ink);text-decoration:underline;text-underline-offset:3px}
a:hover{color:var(--sv-quiet)}
/* The action treatment, opted into. Cobalt, and pressable-looking. */
.sv-link-action{color:var(--sv-action-primary);font-weight:var(--sv-w-semi);text-decoration:underline;text-underline-offset:3px}
.sv-link-action:hover{color:var(--sv-action-primary);opacity:.86}
/* FOCUS IS A 2px RING AT 2px OFFSET. Cobalt on light grounds since 4.2, white
   on the band. The width is unchanged — only the colour moved.
   Inset on fields so the halo does not collide with the control beside it. */
a:focus-visible,button:focus-visible,textarea:focus-visible,input:focus-visible,summary:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--sv-focus);outline-offset:2px}
textarea:focus-visible,input:focus-visible{outline-offset:-2px}
.sv-band a:focus-visible,.sv-band button:focus-visible{outline-color:var(--sv-focus-on-band)}
/* THE FILLED PRIMARY TAKES ITS RING INSIDE, AND 4.2 MADE THAT MANDATORY. The
   ring and the fill are now the same cobalt, so an outside ring would be
   invisible against the button rather than merely close to its edge. White at
   -4px lands on the fill itself at 6.80:1. The ghost half keeps the outside
   ring: its ground is the page and its own border is ink, so a cobalt ring
   there still reads as a ring. */
.sv-btn:not(.ghost):focus-visible{outline-color:var(--sv-focus-on-cobalt);outline-offset:-4px}
/* The band's action is white, not cobalt, so it keeps the band's own ring. */
.sv-band .sv-btn:not(.ghost):focus-visible{outline-color:var(--sv-focus-on-band);outline-offset:2px}
/* THE BAND. A compositional device: a dark full-bleed passage inside a white
   page. Cobalt is 2.56:1 here, so the accent does not appear on it. */
.sv-band{background:var(--sv-band);color:var(--sv-on-band)}
.sv-band .sv-prose,.sv-band .sv-meta{color:var(--sv-on-band-prose)}
/* THE TWO CLASSES THE BAND HAD NO GUARD FOR (external review F1). Cobalt is
   2.56:1 on the band and quiet is 3.46:1 — both fail. The vow keeps its
   weight and gives up its colour: on the band the ground is already doing the
   emphasis, and this is a one-accent system — a band-only lightened cobalt
   would be the two-blues contradiction with a media query around it. The cost
   is real and drawn in the band-voice card: on the band a vow and a section
   heading differ only by size. */
.sv-band .sv-vow{color:var(--sv-on-band)}
.sv-band .sv-micro-t{color:var(--sv-on-band-prose)}
.sv-band .sv-display,.sv-band .sv-title,.sv-band .sv-section-t{color:var(--sv-on-band)}
/* ==== design_system/patterns/chrome.css ==== */
/* The screen frame, the masthead, the back row, the 38px circle, the title
   block, the sticky bar, the footer. */

/* A COLUMN AS TALL AS THE VIEWPORT: a page with little on it must show ALL of
   itself — footer and terms link included — without a scroll. 100dvh is the
   height a phone actually has right now; the 100% above it is the fallback and
   is not dead code. Horizontal padding is ZERO at the root and paid by each
   block, which is what lets the sticky bar and its hairline run edge to edge. */
.sv-screen{position:relative;max-width:var(--sv-phone);margin:0 auto;display:flex;flex-direction:column;min-height:100%;min-height:100dvh;background:var(--sv-ground);padding:env(safe-area-inset-top,0px) 0 calc(24px + env(safe-area-inset-bottom,0px));animation:svScreenIn var(--sv-t-screen) var(--sv-ease) both}
.sv-screen > .sv-foot{margin-top:auto}
.sv-pad{padding-left:var(--sv-gutter);padding-right:var(--sv-gutter)}
.sv-pad.wide{padding-left:var(--sv-gutter-wide);padding-right:var(--sv-gutter-wide)}
.sv-stack{display:flex;flex-direction:column;gap:var(--sv-stack)}
.sv-stack.loose{gap:var(--sv-stack-loose)}
/* Rows share one hairline system: no gap, each row draws its own border-top,
   so a list can be cut anywhere without leaving a rule hanging. */
.sv-rows{display:flex;flex-direction:column}

/* THE FOLD'S FRAME (§3.1). The contract drew the home fold inside a device
   frame, and the frame has to be an ELEMENT because a viewport-tall box is the
   only thing that can centre the column against the visitor's first
   screenful — a margin cannot. It lived in the register kit's own <style> for
   one pass, which meant a consumer building the home screen from this system
   got nothing; it is part of the system. */
.sv-first{display:flex;flex:1;flex-direction:column;justify-content:center;gap:18px;padding:0 var(--sv-gutter) 8px}
.sv-fold{display:flex;flex-direction:column;gap:18px}
/* The example queries: a column, left-set, so a wrapped phrase does not centre
   itself against its neighbours. */
.sv-examples{display:flex;flex-direction:column;align-items:flex-start;gap:8px}

/* C1 — the persistent app header. 6 + 44 + 8 = --sv-app-h. */
.sv-app{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px var(--sv-gutter) 8px}
.sv-brand{display:inline-flex;align-items:center;gap:8px;min-height:var(--sv-tap);text-decoration:none;color:var(--sv-ink)}
.sv-mark{display:block;width:var(--sv-mark-size);height:var(--sv-mark-size);flex:none}
/* THE ONE SURVIVING WEIGHT 800, and the only use of --sv-brand-accent.
   The line-height is load-bearing: unstated, this line box would push the
   lockup past the 44px tap floor --sv-app-h is written against. */
.sv-word{font-size:var(--sv-wordmark-size);font-weight:var(--sv-w-wordmark);line-height:1.2;letter-spacing:-.01em;color:var(--sv-brand-accent)}
.sv-nav{display:flex;gap:4px;flex-wrap:wrap}
.sv-nav a,.sv-auth{display:inline-flex;align-items:center;min-height:var(--sv-tap);padding:0 7px;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-text-body);text-decoration:none;white-space:nowrap}
/* The lockup never shrinks below the mark plus one word — the bar wraps its
   nav below the brand instead, which is the shape this bar has always had. */
.sv-app{flex-wrap:wrap}
.sv-brand{flex:none}

/* C3 — the 38px circle. Every one of them: back, menu, share, save.
   38 is the VISIBLE edge; the row around it supplies the 44px height. */
.sv-round{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex:none;padding:0;border:1px solid var(--sv-border-control);border-radius:var(--sv-r-round);background:transparent;color:var(--sv-ink);cursor:pointer;transition:background var(--sv-t-hover) var(--sv-ease)}
.sv-round:hover{background:var(--sv-well)}
.sv-round svg{display:block}

/* C2 — the back row, in its three variants. */
.sv-backrow{display:flex;align-items:center;gap:12px;padding:8px var(--sv-gutter) 16px}
.sv-backrow.split{justify-content:space-between}
.sv-backrow.wide{padding-left:var(--sv-gutter-wide);padding-right:var(--sv-gutter-wide)}
.sv-backrow .lead{display:flex;align-items:center;gap:12px;min-width:0}
.sv-back{display:inline-flex;align-items:center;gap:12px;min-height:var(--sv-tap);font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-text-body);text-decoration:none}
.sv-backmeta{font-size:var(--sv-micro);color:var(--sv-quiet);flex:none;font-variant-numeric:tabular-nums}
.sv-backicons{display:flex;gap:8px;flex:none}

/* C4 — the title block. align-items:flex-start is the GUARD: a stretched
   child is invisible for a paragraph and a bug the moment a child draws its
   own edge (a lone dashed chip the full width of the phone). */
.sv-titles{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:0 var(--sv-gutter) 18px}
.sv-titles.wide{padding-left:var(--sv-gutter-wide);padding-right:var(--sv-gutter-wide)}
/* An eyebrow is quiet; .attested is the one that is not — "Identified", this
   product asserting it recognised something. */
.sv-eyebrow{font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:var(--sv-micro-lh);color:var(--sv-quiet);margin:0}
.sv-eyebrow.attested{color:var(--sv-evidence-attested)}

/* C20 — the sticky CTA bar. Edge to edge, its own hairline, the safe-area
   inset added so the button clears a home indicator. */
.sv-sticky{position:sticky;bottom:0;z-index:6;margin-top:18px;display:flex;flex-direction:column;gap:10px;padding:12px var(--sv-gutter) calc(16px + env(safe-area-inset-bottom,0px));background:var(--sv-card);border-top:1px solid var(--sv-border-soft)}
.sv-sticky .sv-btn{width:100%}

.sv-foot{padding:36px var(--sv-gutter) 0;text-align:center}
.sv-foot p{font-size:var(--sv-micro);line-height:1.6;color:var(--sv-quiet);margin:0}
.sv-foot a{display:inline-flex;align-items:center;min-height:var(--sv-tap);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);color:var(--sv-text-body)}

/* The throbber's box, and only its box: svanzen-loading.svg carries its own
   keyframes inline. prefers-reduced-motion cannot walk into the file to still
   them, so a reader who asked for less motion gets it REMOVED — the line
   beside it says the same thing in words either way. */
.sv-throb{display:block;width:96px;height:96px;margin:0 auto 14px}
@media (prefers-reduced-motion:reduce){.sv-throb{display:none}}
/* ==== design_system/patterns/chips.css ==== */
/* THE EVIDENCE CHIP FAMILY — six kinds, six treatments, one ladder.
   Every chip carries its DATE. That is a rule about the caller's copy, and it
   is restated here because this is the file a person reads when they add a
   seventh kind.
   A PRESSABLE CHIP TAKES --sv-border-control (3:1). Only a STATIC chip may
   take --sv-border-decorative. One chip pattern never sometimes filters and
   sometimes navigates. */
.sv-chip{display:inline-flex;align-items:center;gap:5px;font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.35;padding:5px 10px;border:1px solid var(--sv-border-decorative);border-radius:var(--sv-r-pill);background:transparent;color:var(--sv-ink);white-space:nowrap}
.sv-chip svg{flex:none}
/* attested — the business itself told us, or we checked it. The one filled
   chip and the accent's clearest job on the page. */
.sv-chip.attested{background:var(--sv-evidence-attested);border-color:var(--sv-evidence-attested);color:var(--sv-on-cobalt)}
/* read — we observed it in public. */
.sv-chip.read{color:var(--sv-ink);border-color:var(--sv-border-decorative)}
/* drawn — we traced it, by hand, from a photograph on file. NOT the same
   claim as "read", which is why it has its own glyph. */
.sv-chip.drawn{color:var(--sv-ink);border-color:var(--sv-border-decorative)}
/* generated — synthetic, and burnt onto the image it labels rather than
   sitting beside it: its fill is translucent ink because it has to read on
   top of a photograph. */
.sv-chip.generated{background:var(--sv-burnt);border-color:transparent;color:var(--sv-on-cobalt)}
/* claims-only — asserted to us, unconfirmed. Dashed, at the 3:1 boundary. */
.sv-chip.claims{color:var(--sv-quiet);border-style:dashed;border-color:var(--sv-border-control);background:transparent}
/* not-on-file — absence, said honestly, and deliberately quieter than an
   unconfirmed claim because it asserts even less. */
.sv-chip.notonfile{color:var(--sv-quiet);font-weight:var(--sv-w-regular);border-style:dashed;border-color:var(--sv-border-control)}
/* an EMPTY cell, bounding nothing that has been said — the lightest edge. */
.sv-chip.empty{color:var(--sv-quiet);border-style:dashed;border-color:var(--sv-border-empty)}
/* sizes. A size is not a kind: a compact chip on a dealer card and a standard
   chip in a title block are the same claim at two scales. */
.sv-chip.compact{font-size:var(--sv-micro);padding:5px 11px}
.sv-chip.tag{font-size:var(--sv-meta-size);padding:7px 13px;color:var(--sv-text-body)}
.sv-chip.choice{font-size:var(--sv-meta-size);padding:9px 16px;color:var(--sv-text-body);border-color:var(--sv-border-control);cursor:pointer}
/* A SELECTED CHIP IS INK-FILLED, NOT COBALT — one line from .attested on
   purpose. .attested is a claim about where a fact came from; .on is a control
   a designer has pressed. */
.sv-chip.on{background:var(--sv-ink);border-color:var(--sv-ink);color:var(--sv-card)}
.sv-chips{display:flex;flex-wrap:wrap;gap:6px}
/* ==== design_system/patterns/controls.css ==== */
/* Buttons, the text control, the search field, the segmented toggle,
   the coverage row, claim progress, upload cells. */

/* C19 — TWO SHAPES AND NO SIXTH. Filled, or outlined. A styled span is never
   a button. 4px corners since 4.2 (it was a pill until 9 Sep 2026), 50px
   tall, weight 600 — the ceiling. The HEIGHT is what makes a button a button
   now that the radius no longer does. */
.sv-btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;min-height:50px;padding:10px 22px;font:inherit;font-size:var(--sv-body-size);font-weight:var(--sv-w-semi);line-height:1.2;text-align:center;text-decoration:none;cursor:pointer;border:1px solid var(--sv-action-primary);border-radius:var(--sv-r-pill);background:var(--sv-action-primary);color:var(--sv-on-cobalt);transition:transform var(--sv-t-hover) var(--sv-ease),opacity var(--sv-t-hover) var(--sv-ease),box-shadow var(--sv-t-hover) var(--sv-ease)}
.sv-btn svg{flex:none}
.sv-btn.ghost{background:transparent;border-color:var(--sv-ink);color:var(--sv-ink)}
.sv-btn:hover{text-decoration:none;opacity:.94}
/* THE PRESS IS A STATE, NOT A POINTER EVENT — .pressed sits in the same
   declaration as :active because a reader who submits with Enter never
   touches the button. Scale alone reads as a shrink; the inset shadow is the
   surface going down. */
.sv-btn:active,.sv-btn.pressed{transform:scale(var(--sv-press-scale));opacity:var(--sv-press-opacity);box-shadow:var(--sv-el-pressed)}
.sv-btn.sm{min-height:46px;padding:0 22px}
.sv-btn.full{width:100%}
/* ON THE BAND cobalt is 2.56:1, so the primary INVERTS to a white pill. */
.sv-band .sv-btn{background:var(--sv-band-action-bg);border-color:var(--sv-band-action-bg);color:var(--sv-band-action-fg)}
.sv-band .sv-btn.ghost{background:transparent;border-color:var(--sv-border-on-band);color:var(--sv-on-band)}
/* The third tier: a text control. Underlined, because that is what says it is
   pressable when it has no box. */
.sv-link{display:inline-block;width:auto;padding:0;border:0;border-radius:0;background:none;font:inherit;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-ink);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.sv-link.quiet{color:var(--sv-quiet)}
.sv-link.dotted{font-weight:var(--sv-w-regular);color:var(--sv-text-body);text-decoration:underline dotted}
/* The circle-free icon control inside a field row. */
.sv-iconbtn{display:inline-flex;align-items:center;justify-content:center;min-width:var(--sv-tap);min-height:var(--sv-tap);padding:0;border:0;background:none;color:var(--sv-text-body);cursor:pointer}

/* THE SEARCH FIELD. THE BOX IS THE ROW, NOT THE FIELD: one bordered,
   rounded, shadowed container holding the magnifier, the field and the
   trailing controls, with the textarea inside it wearing no chrome at all.
   Drawn the other way round, the glyphs sit outside the box.
   White fill is allowed because the hairline is --sv-border-control (3:1) —
   a box legible only by its shadow is not legible with reduced transparency
   on. ONE SHAPE, TWO STATES: the radius never moves. */
.sv-searchrow{display:flex;align-items:center;gap:12px;min-height:54px;padding:0 18px;border:1px solid var(--sv-border-control);border-radius:var(--sv-r-pill);background:var(--sv-card);box-shadow:var(--sv-el-search);transition:box-shadow 200ms var(--sv-ease),border-color 200ms var(--sv-ease)}
.sv-searchrow:focus-within,.sv-searchrow.open{border-color:var(--sv-ink);box-shadow:var(--sv-el-search-open)}
/* THE PADDING STEP OUTLIVED ITS REASON, AND STANDS ON A NEW ONE. It was here
   because a 999px radius on a grown four-row box resolved to half the box,
   putting an 18px glyph on the curve. At 4px there is no curve to fall off.
   The step stays because an open row is a different object from a resting
   one — more of the screen, more text, and the extra 8px of inset is what
   keeps the glyphs from crowding a full-width field. */
.sv-searchrow.open{padding:0 26px}
/* THE GLYPHS CENTRE AGAINST THE FIELD (founder, 24 Aug). They were pinned at
   `margin-top:17px` under `align-items:flex-start`, which put a 44px tap
   target's centre at 39px against a first line centred at 28px — eleven
   pixels low, and the reason they read as sitting below the words.
   CENTRING RATHER THAN FIRST-LINE ALIGNMENT is the right answer now that the
   resting placeholder is two lines: aligning to line one would park the
   glyphs a quarter of the way down the box. The curve argument that used to
   sit here died with the pill in 4.2 — centring now rests on the tap-target
   geometry alone, which was always the stronger half. */
.sv-searchrow .lead,.sv-searchrow .trail{flex:none;color:var(--sv-text-body)}

/* THE 'i' AND WHAT IT OPENS — verification detail a reader asks for rather
   than reads past. Deferred, never hidden: it stays in the document and in
   the accessibility tree. Nothing that BOUNDS a decision goes in here; an
   authorization limit or a stock caveat is a fact a reader must not have to
   ask for. The summary's only visible content is a glyph, so its accessible
   name comes from aria-label, and it holds the same 44px tap floor as every
   other control in this file. */
.sv-info{display:inline-block;vertical-align:middle}
/* CHIP-SIZED, NOT TAP-SIZED, and deliberately. This control is drawn INSIDE
   other rows — a chip row, a credential line — and a 44px box is taller than
   everything beside it there: it wrapped a two-chip row onto a second line
   for one glyph, and stretched a 20px credential line to 44. At 25px square
   it still clears the 24x24 minimum for a target of this kind, and the rows
   it sits in keep their height. The 44px floor stands everywhere it means a
   standalone control. */
.sv-info>summary{list-style:none;cursor:pointer;color:var(--sv-quiet);display:inline-flex;align-items:center;justify-content:center;padding:5px 7px}
.sv-info>summary::-webkit-details-marker{display:none}
.sv-info-body{display:block;font-size:var(--sv-micro);line-height:var(--sv-micro-lh);color:var(--sv-quiet);padding:2px 0 6px}
.sv-searchrow .trail{display:flex;align-items:center;gap:13px}
.sv-text{font:inherit;font-size:var(--sv-body-size);line-height:1.5;width:100%;flex:1;min-width:0;padding:16px 0;resize:none;border:0;border-radius:0;outline:none;background:none;box-shadow:none;color:var(--sv-ink)}
.sv-text::placeholder{color:var(--sv-quiet)}
/* THE ONE FIELD THE INSET RING CANNOT HAVE: .sv-text's box begins where its
   first character begins, so an inset ring is drawn through the letter. The
   ring belongs to the ROW, which is the control a reader sees anyway. Both
   declarations sit behind :has() so a browser that cannot parse it drops BOTH
   and keeps the field's own inset ring — a slightly wrong ring, never no ring.
   THE ROW'S RING READS --sv-focus AS OF 4.2. It was --sv-border-control, a
   grey substitute chosen when the system's ring was ink and close enough. Now
   that focus is cobalt everywhere else, a grey ring here would have left the
   home search row — the most-focused control in the product — the one place
   focus did not turn cobalt. One line beyond the handoff's table, and the
   reason is its own §A5 sweep: it reads focus colour on .sv-searchrow and
   expects cobalt. */
.sv-searchrow:has(.sv-text:focus-visible) .sv-text:focus-visible{outline:none}
.sv-searchrow:has(.sv-text:focus-visible){outline:2px solid var(--sv-focus);outline-offset:2px}

/* C7 — the segmented toggle. The knob is one of the four elevated things. */
.sv-seg{display:inline-flex;flex:none;padding:4px;background:var(--sv-well);border-radius:var(--sv-r-pill)}
/* MIN-HEIGHT, NOT HEIGHT. This is the one box in the sweep that holds WORDS
   at a fixed height: a longer label, a script with taller metrics, or a
   reader's zoom would have clipped it. The knob still measures 34px at every
   size the product uses; it can now grow rather than crop. */
.sv-seg > *{display:inline-flex;align-items:center;min-height:34px;padding:0 16px;border:0;border-radius:var(--sv-r-pill);background:transparent;color:var(--sv-quiet);font:inherit;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1;white-space:nowrap;text-decoration:none;cursor:pointer;transition:background var(--sv-t-hover) var(--sv-ease),color var(--sv-t-hover) var(--sv-ease)}
.sv-seg > .on{background:var(--sv-card);color:var(--sv-ink);box-shadow:var(--sv-el-knob)}

/* C8 — the coverage row: the toggle, and the count string it is a control
   for, right-aligned on the same line. */
.sv-coverage{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 var(--sv-gutter) 12px}
.sv-coverage .meta{font-size:var(--sv-micro);line-height:var(--sv-micro-lh);color:var(--sv-quiet);text-align:right}

/* C22 — claim progress. role="img" with a label at the caller: three
   unlabelled divs are three unlabelled divs to a screen reader. */
/* THE ONE LITERAL RADIUS THAT MUST STAY A LITERAL. 99px on a 3px-tall bar is
   a semicircular cap, correct at any scale and at any token value. Pointing
   it at --sv-r-pill would have squared it to 4px the day 4.2 landed, which is
   the trap in the handoff's §A3 rule that every surviving literal becomes a
   token read. A cap is geometry, not a step in the scale. */
.sv-prog{display:flex;gap:5px;padding:0 var(--sv-gutter-wide) 24px}
.sv-prog i{flex:1;height:3px;border-radius:99px;background:var(--sv-border-soft)}
.sv-prog i.on{background:var(--sv-action-primary)}

/* An upload target. .empty is the cell with nothing asked of it. */
.sv-drop{display:flex;align-items:center;justify-content:center;aspect-ratio:4/3;padding:8px;text-align:center;border:1px dashed var(--sv-border-control);border-radius:var(--sv-r-input);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.3;color:var(--sv-quiet)}
.sv-drop.sq{aspect-ratio:1/1}
.sv-drop.empty{border-color:var(--sv-border-empty)}
/* ==== design_system/patterns/media.css ==== */
/* Photographs, plates, film. THE WRAPPER TAKES THE RADIUS AND THE CLIP and
   the tiles inside it are butted squares — rounding a tile inside a rounded
   wrapper puts a notch at every internal corner of the block.
   ASPECT RATIOS ARE UTILITIES, NOT COMPONENTS: which one a tile takes is a
   property of the SLOT, not of the grid. */
.sv-grid{display:grid;gap:4px;grid-template-columns:repeat(2,minmax(0,1fr));border-radius:var(--sv-r-grid);overflow:hidden;animation:svHeroExpand var(--sv-t-expand) var(--sv-ease) both}
.sv-grid.c3{grid-template-columns:repeat(3,minmax(0,1fr))}
.sv-grid.c5{grid-template-columns:repeat(5,minmax(0,1fr))}
.sv-tile{position:relative;overflow:hidden;min-width:0;aspect-ratio:1/1;background:var(--sv-well)}
.sv-tile img{display:block;width:100%;height:100%;object-fit:cover}
/* A frame that stands ALONE rounds itself, at the photographic step. */
.sv-figure{position:relative;overflow:hidden;min-width:0;aspect-ratio:1/1;background:var(--sv-well);border-radius:var(--sv-r-frame)}
.sv-figure img{display:block;width:100%;height:100%;object-fit:cover}
.sv-figure.card-r{border-radius:var(--sv-r-card)}
.a-1x1{aspect-ratio:1/1}.a-4x5{aspect-ratio:4/5}.a-3x2{aspect-ratio:3/2}
.a-4x3{aspect-ratio:4/3}.a-3x4{aspect-ratio:3/4}.a-5x3{aspect-ratio:5/3}
.a-16x9{aspect-ratio:16/9}.a-16x10{aspect-ratio:16/10}.a-9x16{aspect-ratio:9/16}
/* THE EMPTY SLOT IS DRAWN, NOT BLANK. Never a stock image, never a bare grey
   box: an empty tile that says nothing reads as a broken one, and a
   photograph we do not have is not a defect. pointer-events:none because it
   is not a control — whatever wraps it owns the tap. */
.sv-slot{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:8px;text-align:center;background:var(--sv-well);color:var(--sv-quiet);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.35;pointer-events:none}
/* C10 — the plate tile: a 1:1 line drawing at stroke 1.1 on the well, 10px of
   padding, the plate's id in the corner. NO aspect-ratio — the square SVG
   plus its padding IS the height. */
.sv-plates{display:grid;gap:4px;grid-template-columns:repeat(3,minmax(0,1fr));border-radius:var(--sv-r-grid);overflow:hidden;animation:svHeroExpand var(--sv-t-expand) var(--sv-ease) both}
.sv-plate{position:relative;display:block;width:100%;padding:10px;border:0;border-radius:0;background:var(--sv-well);color:var(--sv-ink);cursor:pointer}
.sv-plate svg{display:block;width:100%}
.sv-plate .id{position:absolute;left:9px;top:8px;font-size:var(--sv-micro);font-weight:var(--sv-w-semi);color:var(--sv-quiet);font-variant-numeric:tabular-nums}
.sv-drawing svg{display:block;width:100%}
/* C11 — a work that exists as a RECORD with no photograph. Dashed, because
   that is this system's mark for absence, and the words sit at the BOTTOM so
   a row of them reads as a caption line rather than four floating labels. */
.sv-unsent{aspect-ratio:1/1;display:flex;align-items:flex-end;justify-content:center;padding:9px;text-align:center;border:1px dashed var(--sv-border-control);border-radius:var(--sv-r-card);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.3;color:var(--sv-quiet)}
/* C12 — what the grid above adds up to, and where it came from. */
.sv-gridcap{display:flex;justify-content:space-between;gap:12px;padding-top:10px;font-size:var(--sv-micro);line-height:var(--sv-micro-lh);color:var(--sv-quiet)}
.sv-gridcap > :last-child{text-align:right}
/* C21 — TWO DISCS, AND THE DIFFERENCE IS THE POINT: real footage gets the
   white disc with an ink triangle; a GENERATED clip gets the dark translucent
   disc with a white one, so the two never read alike at a glance. */
.sv-play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none}
.sv-play i{display:flex;align-items:center;justify-content:center;width:54px;height:54px;border-radius:var(--sv-r-round);background:var(--sv-overlay);color:var(--sv-ink)}
.sv-play.generated i{background:var(--sv-scrim-film);color:var(--sv-on-cobalt)}
.sv-burnt{position:absolute;left:9px;bottom:9px;padding:5px 10px;border-radius:var(--sv-r-pill);background:var(--sv-burnt);color:var(--sv-on-cobalt);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.2;white-space:nowrap;pointer-events:none}
.sv-dur{position:absolute;right:6px;bottom:6px;padding:3px 8px;border-radius:var(--sv-r-pill);background:var(--sv-burnt);color:var(--sv-on-cobalt);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.2;white-space:nowrap;pointer-events:none;font-variant-numeric:tabular-nums}
/* THE PLACEHOLDER MARK. A test image is synthetic media, so it wears the same
   burnt pill a generated frame does — the label is not a courtesy, it is the
   difference between a mock and a claim. Applied by lib/placeholder-photos.js
   through the .sv-ph class; nothing in components/ sets it. */
/* TOP-LEFT, NOT BOTTOM-LEFT. It shared a corner with `.sv-burnt` (the burnt
   "Generated · date" label), so a generated frame carrying a placeholder image
   stacked the two and garbled the one label that most needs reading. The other
   floaters are audited: `.sv-onphoto` top-right, `.sv-burnt` bottom-left,
   `.sv-dur` bottom-right — top-left was free. */
.sv-ph::after{content:"Placeholder";position:absolute;left:9px;top:9px;padding:5px 10px;border-radius:var(--sv-r-pill);background:var(--sv-burnt);color:var(--sv-on-cobalt);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.2;white-space:nowrap;pointer-events:none;z-index:2}
/* The only other thing that floats on an image: a save affordance. */
.sv-onphoto{position:absolute;top:10px;right:10px;width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:var(--sv-r-round);background:var(--sv-overlay);color:var(--sv-ink);cursor:pointer}
/* ==== design_system/patterns/blocks.css ==== */
/* Sections, cards, rows, the sheet, and the minor repeats — too small for a
   component, too shared to leave to each screen. */

/* C13 — ONE BORDER-TOP, NEVER TWO. The page's only divider. */
.sv-sect{display:flex;flex-direction:column;gap:12px;margin:24px var(--sv-gutter) 0;padding-top:20px;border-top:1px solid var(--sv-border-soft)}
.sv-sect.tight{gap:4px}
/* THE SECTION'S GUTTER IS PAID ONCE: a card list inside a section that
   already pays the gutter ends up 44px in a side. The INNER padding is
   cancelled, so the hairline and the heading keep their inset. */
.sv-sect.flush > .sv-pad{padding-left:0;padding-right:0}
.sv-secthead{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.sv-secthead .meta{font-size:var(--sv-micro);color:var(--sv-quiet);flex:none}

/* C14 — a hairline, a 16px radius, and NO SHADOW. Cards are flat.
   AND THAT IS AN ARGUMENT, NOT A TASTE. 2.x separated a card from the page
   with a two-part shadow (a 1px contact shadow and a 30px lift). Here the
   ground and the card are the SAME white, so the edge is doing all the work,
   and a shadow under an edge that is already legible is decoration on a page
   whose whole argument is that it does not decorate.
   ELEVATION SURVIVES IN EXACTLY THREE PLACES, and tokens/elevation.css is the
   list: the search field at rest and open (--sv-el-search/-open), a segmented
   toggle's knob (--sv-el-knob), and a bottom sheet (--sv-el-sheet). A card is
   none of them, which is why reaching for a fourth shadow here means either
   the reach is wrong or the token file owes a new name and a reason. */
.sv-card{background:var(--sv-card);border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);padding:18px}
.sv-card.tight{padding:16px}
.sv-card.roomy{padding:20px}
.sv-card.flush{padding:0;overflow:hidden}
/* THE DASHED CARD IS NOT A STYLE, IT IS A CLAIM: everything inside a dashed
   edge is generated, unconfirmed or absent. That is why they look related. */
.sv-card.dashed{border:1px dashed var(--sv-border-control)}

/* C15 — the evidence row: what we hold, how we got it, and when. Flat. */
.sv-eve{display:flex;align-items:center;gap:12px;padding:13px 0;border-top:1px solid var(--sv-border-soft)}
.sv-eve.first{border-top:0}
.sv-eve .ico{display:inline-flex;flex:none;color:var(--sv-ink)}
.sv-eve .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.sv-eve .t{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-eve .s{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}
.sv-eve > .sv-chip{flex:none}
/* THE DIMMED VARIANT, AND AN HONEST NOTE. Dimming a claims-only row to 65%
   says "weaker evidence" in the one dimension nobody misreads, and it COSTS
   CONTRAST: composited on white the title still clears AA, the sub lands near
   2.6:1 and does not. No scanner reports it (both read an element's own
   colour, not an ancestor's opacity), so this comment is the record. The
   dimming is never the message — the row's own words and its dashed chip say
   it outright. If revisited, dim the ROW CHROME and leave the sentence. */
.sv-eve.dim{opacity:.65}

/* C16 — the label/value row. ABSENCE IS A VALUE, and it renders. The row is
   never dropped: a missing row reads as a fact nobody asked about, a printed
   absence reads as one we did. */
.sv-kv{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:11px 0;border-top:1px solid var(--sv-border-soft)}
.sv-kv .k{font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);color:var(--sv-quiet);flex:none}
.sv-kv .v{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:var(--sv-meta-lh);color:var(--sv-ink);text-align:right;overflow-wrap:anywhere}
.sv-kv .v.absent,.sv-kv .v.text{font-weight:var(--sv-w-regular);color:var(--sv-quiet)}
/* A website that COULD be linked: ink and underlined — tappable and honest
   about it — never the accent, which is provenance. */
.sv-kv .v.link{font-weight:var(--sv-w-regular);color:var(--sv-ink);text-decoration:underline;text-underline-offset:3px}

/* C17 — the option card: a whole card that is one tap. */
.sv-opt{display:flex;align-items:center;justify-content:space-between;gap:14px;width:100%;padding:18px;text-align:left;background:var(--sv-card);border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);color:inherit;font:inherit;text-decoration:none;cursor:pointer;transition:border-color var(--sv-t-hover) var(--sv-ease),transform var(--sv-t-hover) var(--sv-ease)}
.sv-opt:hover{border-color:var(--sv-border-decorative)}
.sv-opt:active{transform:scale(.985)}
.sv-opt .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.sv-opt .t{font-size:var(--sv-body-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-opt .s{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}
.sv-opt .go{display:inline-flex;flex:none;color:var(--sv-quiet)}

/* C18 — the download row. "Get" is a text control, not a button: the file is
   the object and the row is what describes it. */
.sv-dl{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 0;border-top:1px solid var(--sv-border-soft)}
.sv-dl .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.sv-dl .t{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-dl .s{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}
.sv-dl .sv-link{flex:none}

/* C23 — the bottom sheet. The last elevated thing, and the only 20px radius.
   ABSOLUTE, NOT FIXED: it belongs to the screen it rose out of, which is what
   lets the scrim cover exactly that screen. */
.sv-scrim{position:absolute;inset:0;z-index:20;width:100%;padding:0;border:0;border-radius:0;background:var(--sv-scrim);cursor:pointer;animation:svScrimIn 200ms var(--sv-ease) both}
.sv-sheet{position:absolute;left:0;right:0;bottom:0;z-index:21;display:flex;flex-direction:column;gap:14px;padding:10px var(--sv-gutter) calc(24px + env(safe-area-inset-bottom,0px));background:var(--sv-card);border-radius:var(--sv-r-sheet) var(--sv-r-sheet) 0 0;box-shadow:var(--sv-el-sheet);animation:svSheetUp var(--sv-t-expand) var(--sv-ease) both}
.sv-grab{width:38px;height:4px;align-self:center;border-radius:var(--sv-r-pill);background:var(--sv-border-decorative)}
.sv-sheet .head{display:flex;flex-direction:column;gap:5px;padding-top:4px}
.sv-sheet .credit{display:flex;flex-direction:column;gap:8px;padding:14px 0;border-top:1px solid var(--sv-border-soft);border-bottom:1px solid var(--sv-border-soft)}
.sv-sheet .acts{display:flex;flex-direction:column;gap:9px}

/* ---- the minor repeats ---- */
/* An avatar disc. INITIALS ONLY — this system holds no photographs of people
   and is not about to start. */
.sv-avatar{display:flex;align-items:center;justify-content:center;width:36px;height:36px;flex:none;border-radius:var(--sv-r-round);background:var(--sv-well);font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-text-body)}
.sv-person{display:flex;align-items:center;gap:11px;font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);color:var(--sv-ink)}
/* A numbered step. The numeral takes the accent. */
.sv-n{font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.5;color:var(--sv-action-primary);flex:none;font-variant-numeric:tabular-nums}
.sv-n.big{font-size:var(--sv-section);width:28px}
.sv-step{display:flex;align-items:baseline;gap:10px;padding:4px 0}
.sv-step.wide{gap:14px;padding:13px 0;border-top:1px solid var(--sv-border-soft)}
.sv-step.wide.first{border-top:0}
.sv-step .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.sv-step .t{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-step .s{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}
/* A stat pair, divided by the same hairline as everything else. Tabular
   figures so two counts of different lengths line up under their labels. */
.sv-stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
.sv-stat{display:flex;flex-direction:column;gap:3px;padding:16px 18px}
.sv-stat + .sv-stat{border-left:1px solid var(--sv-border-soft)}
.sv-stat .n{font-size:var(--sv-title);font-weight:var(--sv-w-semi);letter-spacing:-.01em;line-height:1.15;color:var(--sv-ink);font-variant-numeric:tabular-nums}
.sv-stat .k{font-size:var(--sv-micro);line-height:var(--sv-micro-lh);color:var(--sv-quiet)}
/* An icon disc — 44px over an empty state, 64px beside a fidelity claim. The
   large one takes the accent because what it marks IS a check we performed. */
.sv-disc{display:flex;align-items:center;justify-content:center;width:44px;height:44px;flex:none;border-radius:var(--sv-r-round);background:var(--sv-well);color:var(--sv-quiet)}
.sv-disc.lg{width:64px;height:64px;border-radius:var(--sv-r-card);color:var(--sv-evidence-attested)}
/* The reference card — the query a result set answers. Its own name because
   it is the one card that animates: it is the search pill from the previous
   screen, arriving as a card. */
.sv-refcard{display:flex;align-items:center;gap:13px;padding:12px;background:var(--sv-card);border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);animation:svPillMorph var(--sv-t-pill) var(--sv-ease) both}
.sv-refcard .thumb{position:relative;width:64px;height:64px;flex:none;overflow:hidden;border-radius:var(--sv-r-card)}
.sv-refcard .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
/* C5 — the credential line. Icon plus a dated fact, and the two variants are
   a colour apart because they are different claims: attested is ours (the
   accent, with the shield), plain is somebody else's. */
.sv-creds{display:flex;flex-direction:column;gap:5px;padding-top:6px}
.sv-cred{display:flex;align-items:center;gap:6px;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.4;color:var(--sv-text-body)}
.sv-cred.attested{color:var(--sv-evidence-attested)}
.sv-cred svg{flex:none}
/* THE DESIGNED ABSENCE. A disc, a heading, the sentences that say what IS on
   file, and the vow. Centred, and every line capped to a measure. */
.sv-state{display:flex;flex-direction:column;align-items:center;gap:12px;padding:28px var(--sv-gutter);text-align:center}
.sv-state.grow{flex:1;justify-content:center}
.sv-measure{max-width:280px}
/* ==== design_system/patterns/evidence.css ==== */
/* THE EVIDENCE LEDGER. The first implementation slice: EvidenceSummary and
   EvidenceDisclosure. Relationships are conveyed through SEMANTIC MARKUP —
   a <dl> of property to propositions — never through visual position alone,
   because a ledger read down a column by eye is a ledger a screen reader
   cannot read at all. */
.sv-ev{display:flex;flex-direction:column;gap:10px;padding:16px 0;border-top:1px solid var(--sv-border-soft)}
.sv-ev.first{border-top:0}
.sv-ev > .prop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.sv-ev .property{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:var(--sv-meta-lh);color:var(--sv-quiet)}
/* THE RESOLVED VALUE, and it only renders when there IS one. A disputed
   property has no headline — see .sv-conflict. */
.sv-ev .value{font-size:var(--sv-body-size);font-weight:var(--sv-w-semi);line-height:1.4;color:var(--sv-ink);text-align:right;overflow-wrap:anywhere}
/* A PROPOSITION IS A VALUE PLUS WHO SAID IT. Two differing values are two
   propositions, never one with a footnote — so they are siblings at the same
   weight, and neither is drawn as the answer. */
.sv-props{display:flex;flex-direction:column;gap:0;margin:0;padding:0}
.sv-prop{display:flex;flex-direction:column;gap:3px;padding:11px 13px;border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);background:var(--sv-card)}
.sv-prop + .sv-prop{margin-top:8px}
.sv-prop .v{font-size:var(--sv-body-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-prop .who{font-size:var(--sv-micro);line-height:1.45;color:var(--sv-quiet)}
/* UNRESOLVED CONFLICT IS PERSISTENT EMPHASIS, because it changes what the
   reader should do next. It is NOT a colour: the word "unresolved" carries it,
   the dashed edge repeats it, and both survive a greyscale print. */
.sv-conflict{display:flex;flex-direction:column;gap:8px;padding:13px 14px;border:1px dashed var(--sv-border-control);border-radius:var(--sv-r-card);background:var(--sv-well)}
.sv-conflict .head{display:flex;align-items:center;gap:7px;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-conflict .note{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet);margin:0}
/* Bounded absence, stated as a sentence with a named corpus and an as-of
   date. Never "Not found". */
.sv-absence{display:flex;align-items:flex-start;gap:8px;font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);color:var(--sv-quiet)}
.sv-absence svg{flex:none;margin-top:2px}
/* Freshness. Passive when it does not change the decision, persistent when it
   does — which is a prop on the component, not a second style. */
.sv-stale{display:inline-flex;align-items:center;gap:6px;font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-quiet)}
.sv-stale.acting{padding:5px 10px;border:1px dashed var(--sv-border-control);border-radius:var(--sv-r-pill);color:var(--sv-ink)}
/* THE DISCLOSURE IS A REAL BUTTON. aria-expanded lives on it, the chevron is
   ONE glyph rotated, and focus returns to it when the panel closes. */
.sv-disc-btn{display:inline-flex;align-items:center;gap:7px;min-height:var(--sv-tap);padding:0;border:0;background:none;font:inherit;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-ink);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.sv-disc-btn svg{transition:transform var(--sv-t-hover) var(--sv-ease)}
.sv-disc-btn[aria-expanded="false"] svg{transform:rotate(-90deg)}
.sv-disc-panel{display:flex;flex-direction:column;gap:12px;padding:2px 0 6px}
/* The ledger itself. A definition list, so the pairing is in the markup. */
.sv-ledger{display:flex;flex-direction:column;gap:0;margin:0}
.sv-ledger > div{display:flex;align-items:baseline;justify-content:space-between;gap:14px;padding:9px 0;border-top:1px solid var(--sv-border-soft)}
.sv-ledger dt{font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.45;color:var(--sv-quiet);flex:none;margin:0}
.sv-ledger dd{font-size:var(--sv-micro);line-height:1.45;color:var(--sv-ink);text-align:right;margin:0;overflow-wrap:anywhere}
/* A CANDIDATE IS NOT A MAKER. The card says so in words, above the name, and
   the dashed edge says it again for anyone scanning. */
.sv-candidate{display:flex;flex-direction:column;gap:10px;padding:16px;border:1px dashed var(--sv-border-control);border-radius:var(--sv-r-card);background:var(--sv-card)}
.sv-candidate .kind{font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-quiet)}
/* The object trail — where the reader is on the boundary, and what is behind
   them. Never a decorative breadcrumb: each step is the object it names. */
.sv-trail{display:flex;align-items:center;flex-wrap:wrap;gap:4px 6px;padding:0 var(--sv-gutter) 12px;font-size:var(--sv-micro);line-height:1.4}
.sv-trail a{color:var(--sv-text-body);text-decoration:underline;text-underline-offset:2px;font-weight:var(--sv-w-semi)}
.sv-trail .sep{color:var(--sv-tint)}
.sv-trail .now{color:var(--sv-ink);font-weight:var(--sv-w-semi)}
.sv-trail .ahead{color:var(--sv-quiet)}
/* A transformation is stated, not implied: input, output, activity, actor. */
.sv-transform{display:flex;align-items:flex-start;gap:8px;font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}
.sv-transform svg{flex:none;margin-top:2px}
/* ==== design_system/patterns/feedback.css ==== */
/* FEEDBACK IS LOCAL, EXPLICIT AND PROPORTIONAL. It appears beside the object
   it affects; it never interrupts unrelated work; and its interruption level
   matches the consequence. There is no global spinner in this system. */

/* Inline progress, beside the affected results, with the existing content
   left usable underneath. Determinate where a count is known. */
.sv-progress{display:flex;align-items:center;gap:10px;padding:11px 13px;border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);background:var(--sv-well);font-size:var(--sv-meta-size);line-height:1.4;color:var(--sv-text-body)}
.sv-progress .bar{flex:1;min-width:60px;height:3px;border-radius:99px;background:var(--sv-border-decorative);overflow:hidden}
.sv-progress .bar i{display:block;height:100%;border-radius:99px;background:var(--sv-action-primary);transition:width 300ms var(--sv-ease)}
.sv-progress .bar.indeterminate i{width:38%;animation:svSlide 1400ms var(--sv-ease) infinite}
@keyframes svSlide{0%{transform:translateX(-100%)}100%{transform:translateX(264%)}}
@media (prefers-reduced-motion:reduce){.sv-progress .bar.indeterminate i{animation:none;width:100%;opacity:.4}}

/* AN ERROR PRESERVES THE WORK. The message sits beside the control that
   failed, names what happened, and offers the recovery the operation's actual
   state allows — Retry when the request is gone, Edit when the input is
   wrong. The glyph is not the message: the words are. */
.sv-alert{display:flex;flex-direction:column;gap:8px;padding:14px;border:1px solid var(--sv-border-control);border-radius:var(--sv-r-card);background:var(--sv-card)}
.sv-alert .head{display:flex;align-items:flex-start;gap:8px}
.sv-alert .head svg{flex:none;margin-top:2px;color:var(--sv-ink)}
.sv-alert .t{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-alert .s{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet);margin:0}
.sv-alert .acts{display:flex;flex-wrap:wrap;gap:14px}
/* Persistent, because it changes what the reader should do next. */
.sv-alert.acting{border-style:dashed;background:var(--sv-well)}

/* THE QUIET CONFIRMATION. A control-state change is the primary feedback; this
   is the optional second line, and it carries Undo rather than an OK. It is
   not a toast that steals focus — it is a status region beside the object. */
.sv-quiet{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 13px;border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-pill);background:var(--sv-well);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-text-body)}

/* THE FOUR SUBMISSION STATES, DRAWN AS FOUR. Submitted / Accepted for
   delivery / Responded / Committed. Each row states the actor and the time;
   the ones that have not happened are printed too, in the quiet tier, so a
   reader can see how far this is from a commitment. */
.sv-receipt{display:flex;flex-direction:column;gap:0;margin:0}
.sv-receipt > li{display:flex;align-items:flex-start;gap:11px;padding:11px 0;border-top:1px solid var(--sv-border-soft);list-style:none}
.sv-receipt > li:first-child{border-top:0}
.sv-receipt .mark{display:flex;align-items:center;justify-content:center;width:20px;height:20px;flex:none;margin-top:1px;border-radius:var(--sv-r-round);border:1px solid var(--sv-border-control);color:var(--sv-quiet)}
.sv-receipt li.done .mark{background:var(--sv-action-primary);border-color:var(--sv-action-primary);color:var(--sv-on-cobalt)}
.sv-receipt .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.sv-receipt .t{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.35;color:var(--sv-ink)}
.sv-receipt li.pending .t{font-weight:var(--sv-w-regular);color:var(--sv-quiet)}
.sv-receipt .s{font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}

/* A MODAL CONFIRMATION IS FOR AN UNEXPECTED IRREVERSIBLE ACTION AND NOTHING
   ELSE. Routine reversible actions get the quiet line above. */
.sv-confirm{position:absolute;inset:0;z-index:30;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--sv-scrim);animation:svScrimIn 200ms var(--sv-ease) both}
.sv-confirm .box{display:flex;flex-direction:column;gap:12px;width:100%;max-width:360px;padding:20px;border-radius:var(--sv-r-card);background:var(--sv-card);box-shadow:var(--sv-el-sheet)}
.sv-confirm .acts{display:flex;flex-direction:column;gap:9px}
/* ==== design_system/patterns/navigation.css ==== */
/* THE TAB BAR — the funnel's own navigation, ABOVE the page's own heading,
   and the ONLY tab set on a page. A second, unlabelled navigation landmark
   inside the same shell is what axe's landmark-unique rule exists to catch.
   A TAB CHANGES ITS PANEL; a link navigates. These are links between routes,
   so they are anchors with aria-current, not tabs — the name is kept because
   that is what the product calls the strip.
   IT SAT AT THE FOOT UNTIL 24 AUG, sticky to the bottom of the viewport with
   a safe-area pad and 92px of clearance reserved by the shell above it. The
   founder moved it to the top, which is why the rule is now a border-BOTTOM:
   a strip above its content is underlined, and the clearance it used to need
   became the space that anchors the footer on a short page. */
.sv-tabs{display:flex;align-items:stretch;background:var(--sv-card);border-bottom:1px solid var(--sv-border-soft)}
.sv-tabs a{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:var(--sv-tap);padding:8px 4px;font-size:var(--sv-micro);font-weight:var(--sv-w-semi);line-height:1.2;color:var(--sv-quiet);text-decoration:none}
/* The selected tab takes the ACTION accent, and it also takes a word in ink
   plus aria-current — colour is never the sole carrier. */
.sv-tabs a[aria-current]{color:var(--sv-action-primary)}
.sv-tabs a[aria-current] .dot{width:4px;height:4px;border-radius:99px;background:var(--sv-action-primary)}
.sv-tabs .dot{width:4px;height:4px;border-radius:99px;background:transparent}

/* The desktop app bar. Same lockup, same 44px floor; the nav sits BESIDE the
   brand past the breakpoint because the column is wide enough for it. */
.sv-deskbar{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:14px var(--sv-gutter);border-bottom:1px solid var(--sv-border-soft)}
.sv-deskbar .sv-nav{gap:2px}
/* ==== design_system/patterns/record.css ==== */
/* THE RECORD — 4.0. The class layer for the designer portal and the vendor
   funnel: the location line, the two doors, the question row, the record
   card and its fixed-slot contact row, the recorded-state chips, the state
   rail, the timeline and the quote. Cut from explorations/upgrade-2026-09
   (2 Sep 2026) against portal/me.py, candidates.py, vendor_memory.py.
   ADDITIVE: nothing in the older sheets is redefined here. */

/* THE LOCATION LINE — founder ask 17 Aug, on every signed-in screen. A 44px
   row, not a 12.5px disclosure: the origin of every distance on the page is
   a fact a reader must be able to see and change without hunting. */
/* Any text control drawn inside a record meta line clears the 44px floor. */
.sv-record .sv-link,.sv-link.dotted{min-height:var(--sv-tap);display:inline-flex;align-items:center}
.sv-loc{display:flex;align-items:center;gap:6px;min-height:var(--sv-tap);padding:0 var(--sv-gutter);border-top:1px solid var(--sv-border-soft);border-bottom:1px solid var(--sv-border-soft);font-size:var(--sv-meta-size);color:var(--sv-quiet)}
.sv-loc > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sv-loc b{font-weight:var(--sv-w-semi);color:var(--sv-ink)}
.sv-loc svg{color:var(--sv-action-primary)}
.sv-loc .sv-link{margin-left:auto;min-height:var(--sv-tap);display:inline-flex;align-items:center;font-size:var(--sv-meta-size)}

/* THE TWO DOORS. Create, never narrow — the filters below them are a rule
   and a label so a cobalt Search never reads as a third door. */
.sv-doors{display:flex;gap:8px}
.sv-door{flex:1;display:inline-flex;align-items:center;gap:10px;min-height:52px;padding:0 14px;border:1px solid var(--sv-border-control);border-radius:var(--sv-r-pill);background:var(--sv-card);color:var(--sv-ink);font:inherit;font-size:var(--sv-control-size);font-weight:var(--sv-w-semi);text-decoration:none;cursor:pointer}
.sv-door svg{color:var(--sv-action-primary)}

/* THE QUESTION ROW (/me). Threaded by indent and a rule — nothing else says
   "follow-up". The waiting chip is ink, not an accent: whose turn it is
   is a state, not an action. */
.sv-qrow{display:flex;flex-direction:column;gap:6px;padding:16px 0;border-bottom:1px solid var(--sv-border-soft)}
.sv-qrow .r1{display:flex;gap:10px;align-items:baseline}
.sv-qrow .qnum{font-size:var(--sv-micro);color:var(--sv-quiet);flex:none;font-variant-numeric:tabular-nums}
.sv-qrow .qtext{flex:1;font-size:var(--sv-lede);font-weight:var(--sv-w-semi);line-height:1.3;color:var(--sv-ink)}
.sv-qrow .r2{display:flex;justify-content:space-between;gap:12px;font-size:var(--sv-meta-size);color:var(--sv-quiet)}
.sv-qrow .r3{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin:-8px 0}
.sv-qrow .r3 .sv-link,.sv-qrow .r3 .sv-meta{min-height:var(--sv-tap);display:inline-flex;align-items:center}
.sv-qrow.sub{margin-left:22px;padding-left:14px;border-left:1px solid var(--sv-border-soft);border-bottom:0}
.sv-wait{display:inline-flex;align-items:center;gap:6px;min-height:28px;padding:0 10px;border-radius:var(--sv-r-pill);background:var(--sv-ink);color:var(--sv-on-band);font-size:var(--sv-micro);font-weight:var(--sv-w-semi);white-space:nowrap}

/* THE GROUP HEAD — "Sent to you · 3", "Also looked at · 8". An ink rule under
   it because the partition is ours to state; the count is quiet. */
.sv-group{display:flex;justify-content:space-between;align-items:baseline;padding-bottom:8px;border-bottom:1px solid var(--sv-ink);font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-ink)}
.sv-group span{color:var(--sv-quiet);font-weight:var(--sv-w-medium)}

/* THE RECORD CARD. FLAT, FULL-BLEED, RULED — a card separated from the next
   by a hairline and nothing else. Everything under the name indents to the
   rank column so the eye reads one left edge down the whole page. */
.sv-record{padding:16px 0 18px;border-bottom:1px solid var(--sv-border-soft)}
.sv-record .r1{display:flex;gap:10px;align-items:baseline}
.sv-record .rank{flex:none;width:18px;font-size:var(--sv-control-size);font-weight:var(--sv-w-semi);color:var(--sv-quiet);font-variant-numeric:tabular-nums}
.sv-record .sv-name{flex:1}
.sv-record .kind{margin:3px 0 0 28px;font-size:var(--sv-meta-size);font-weight:var(--sv-w-medium);color:var(--sv-text-body)}
.sv-record .place{margin:2px 0 0 28px;font-size:var(--sv-meta-size);color:var(--sv-quiet)}
.sv-record .sv-chip{margin-left:auto}
.sv-record > .sv-facts,.sv-record > .sv-says,.sv-record > .sv-contact,.sv-record > .sv-tracked,.sv-record > .sv-callback{margin-left:28px}

/* FACTS, IN A STRICT ORDER: claim status → what we considered → saved-by.
   One glyph, one sentence, one date each. The shield is cobalt only for a
   claim WE verified — evidence.attested, never decoration. */
.sv-facts{margin-top:12px;display:flex;flex-direction:column;gap:6px}
.sv-fact{display:flex;gap:7px;align-items:flex-start;font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);color:var(--sv-text-body)}
.sv-fact svg{flex:none;margin-top:2px;color:var(--sv-quiet)}
.sv-fact.attested svg{color:var(--sv-evidence-attested)}
.sv-fact.attested b{color:var(--sv-ink);font-weight:var(--sv-w-semi)}
.sv-fact i{font-style:normal;color:var(--sv-quiet)}

/* THE BUSINESS SAYS. A quotation, in their words, with the attribution and
   date beneath — an ink rule down the side, never a box, never cobalt:
   this is somebody else's claim. */
.sv-says{margin-top:10px;padding:10px 12px;border-left:2px solid var(--sv-ink);font-size:var(--sv-body-size);line-height:var(--sv-body-lh);color:var(--sv-ink)}
.sv-says .k{font-size:var(--sv-micro);color:var(--sv-quiet);margin-bottom:3px}
.sv-says i{font-style:normal;font-size:var(--sv-meta-size);color:var(--sv-quiet)}

/* THE CONTACT ROW — FOUR FIXED SLOTS, ALWAYS FOUR, ALWAYS IN THIS ORDER:
   Call · Message · Map · Site. A slot with nothing behind it keeps its box
   and is never dropped, so every card's Call sits under the last card's Call
   (founder, 2 Sep). Call is the primary; it is ink, not cobalt, because the
   accent is for what WE did.

   THE UNAVAILABLE SLOT, CORRECTED 9 Sep 2026 — pulled down from the runtime.
   It used to be dashed with its word struck. The consuming repo overrode that
   in its own layer, and an external review of the 4.1 candidate confirmed the runtime's
   treatment as the right one: a SOLID quiet border, NO strikethrough, the slot
   labelled as unavailable, and a non-interactive element. Struck text reads as
   a cancelled action rather than a channel we do not hold, and a dashed edge in
   this system means generated-or-unconfirmed, which an absent phone number is
   not. Shipped behaviour comes down (github.md `flow`), so this is now the
   system's own rule and the open item is closed.

   THE BASE RULE MUST NAME THE SPAN. An unavailable slot is a <span> — that is
   what makes it unfocusable and unclickable by element choice rather than by a
   cursor or an aria-disabled. The base geometry used to select `a, button`
   only, so the span inherited NO box: no 54px min-height, no radius, and a 3px
   medium dashed border from `border-style` alone. Four slots that must line up
   across every card, and one of them was a different object. */
.sv-contact{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-top:14px}
.sv-contact>a,.sv-contact>button,.sv-contact>span{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;min-height:54px;padding:0;border:1px solid var(--sv-border-decorative);border-radius:var(--sv-r-card);background:var(--sv-card);color:var(--sv-ink);font:inherit;font-size:var(--sv-micro);font-weight:var(--sv-w-semi);text-decoration:none;cursor:pointer}
.sv-contact .primary{background:var(--sv-ink);border-color:var(--sv-ink);color:var(--sv-on-band)}
.sv-contact .absent{color:var(--sv-quiet);cursor:default}
/* Colour and the word carry it. The border stays solid and the same hairline as
   every other slot, so the four boxes read as one row and not three plus one. */

/* YOUR RECORD — the tracked actions, as chips that STAY CHECKED. The product
   records six actions and a callback; after a POST the page used to
   re-collapse the disclosure and print a receipt line, which reads as
   "nothing happened". A recorded action is cobalt with a check and a date,
   and it does not go away. */
.sv-tracked{margin-top:12px;display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.sv-tracked .sv-chip{min-height:var(--sv-tap);padding:0 14px;font-size:var(--sv-meta-size);font-weight:var(--sv-w-medium);white-space:nowrap}
.sv-tracked .sv-chip.on{font-weight:var(--sv-w-semi)}
.sv-tracked .sv-chip.on svg{width:13px;height:13px;stroke-width:2.2}
.sv-tracked .sv-chip small{font-size:var(--sv-micro);font-weight:var(--sv-w-medium);opacity:.8}
.sv-tracked .more{margin-left:auto;min-height:var(--sv-tap);display:inline-flex;align-items:center;font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);color:var(--sv-text-body);text-decoration:underline;text-underline-offset:3px;background:none;border:0;padding:0;font-family:inherit;cursor:pointer}
.sv-callback{margin-top:10px;font-size:var(--sv-meta-size);color:var(--sv-quiet)}
/* The callback sentence belongs UNDER the chips, not after them. Whatever wraps
   it is the flex item — in the product a real POST form — so the rule has to
   name the wrapper, not the paragraph (repo Step 5, 3 Sep). */
.sv-tracked > form,.sv-tracked > .sv-callback{flex-basis:100%}

/* THE FOUR SUBMISSION STATES, AS A RAIL. Submitted · Accepted · Responded ·
   Committed — never merged. Done is ink, now is cobalt, ahead is quiet. */
.sv-states{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px}
.sv-states > *{font-size:var(--sv-micro);color:var(--sv-quiet);text-align:center;padding-top:8px;border-top:2px solid var(--sv-border-soft)}
.sv-states > .done{color:var(--sv-ink);border-color:var(--sv-ink)}
.sv-states > .now{color:var(--sv-action-primary);border-color:var(--sv-action-primary);font-weight:var(--sv-w-semi)}

/* THE TIMELINE — who did what, when. Our own events take the cobalt dot;
   theirs are ink outlines. */
.sv-timeline{list-style:none;border-left:1px solid var(--sv-border-decorative);margin-left:5px;padding-left:16px;display:flex;flex-direction:column;gap:14px}
.sv-event{position:relative;font-size:var(--sv-body-size);line-height:var(--sv-body-lh);color:var(--sv-ink)}
.sv-event::before{content:"";position:absolute;left:-20.5px;top:6px;width:8px;height:8px;border-radius:var(--sv-r-round);background:var(--sv-card);border:1.5px solid var(--sv-ink)}
.sv-event.us::before{background:var(--sv-action-primary);border-color:var(--sv-action-primary)}
.sv-event .d{display:block;font-size:var(--sv-micro);color:var(--sv-quiet);margin-top:1px}

/* FIVE LITERAL RADII BECAME TOKEN READS AT 4.2 (9 Sep 2026). .sv-contact,
   .sv-quote-card and .sv-mynote were 12px, .sv-spec 16px, .sv-codecard 20px —
   every one of them a card-family step chosen when 16 and 20 were IN the
   scale. After 4.2 they are not: 16 and 20 name nothing, and 12 is the
   photographic frame. All five now read --sv-r-card, which is what each of
   them meant. Handoff §A3: a surviving literal becomes a token read, never a
   new literal. */

/* THE QUOTE. The amount at title size; the boundary sentence always under it. */
.sv-quote-card{padding:12px 14px;border:1px solid var(--sv-border-decorative);border-radius:var(--sv-r-card)}
.sv-quote-card .amt{font-size:var(--sv-title);font-weight:var(--sv-w-semi);letter-spacing:var(--sv-title-ls);line-height:1.1;color:var(--sv-ink);font-variant-numeric:tabular-nums}
.sv-quote-card .amt small{font-size:var(--sv-meta-size);font-weight:var(--sv-w-medium);color:var(--sv-quiet);letter-spacing:0;margin-left:4px}
.sv-quote-card .who{font-size:var(--sv-meta-size);color:var(--sv-quiet);margin-top:2px}
.sv-quote-card .boundary{margin-top:10px;padding-top:10px;border-top:1px solid var(--sv-border-soft);font-size:var(--sv-micro);line-height:1.5;color:var(--sv-quiet)}

/* THE ASK FIELD — a 17px textarea in a frame that takes the card step, the
   two attachments as 44px discs, the one cobalt button. The frame was a
   literal 16px, which WAS the card step; 4.2 moved the card to 8px, so the
   literal is now a token read (handoff §A3) and the frame follows the card
   again instead of standing 8px off it. */
.sv-askbox{border:1px solid var(--sv-border-control);border-radius:var(--sv-r-card);padding:14px 16px;min-height:120px;font-size:var(--sv-lede);line-height:var(--sv-lede-lh);color:var(--sv-ink)}
.sv-askbox::placeholder{color:var(--sv-quiet)}
.sv-askrow{display:flex;justify-content:space-between;align-items:center;margin-top:10px}
.sv-askrow .attach{display:flex;gap:6px}
.sv-askrow .attach > *{display:inline-flex;align-items:center;justify-content:center;width:var(--sv-tap);height:var(--sv-tap);border:1px solid var(--sv-border-decorative);border-radius:var(--sv-r-round);background:var(--sv-card);color:var(--sv-text-body);cursor:pointer}

/* THE LOGIN CODE. One card, one code, one action, the waiting line. */
.sv-codecard{padding:28px 22px;border:1px solid var(--sv-border-decorative);border-radius:var(--sv-r-card);text-align:center}
.sv-codecard .sv-code{margin:20px 0}
.sv-codecard .waiting{margin-top:16px;display:flex;align-items:center;justify-content:center;gap:8px;font-size:var(--sv-meta-size);color:var(--sv-quiet)}
.sv-codecard .waiting i{width:8px;height:8px;border-radius:var(--sv-r-round);background:var(--sv-action-primary);display:inline-block}

/* A private note on a saved vendor. A well, not a card; never read by a prompt. */
.sv-mynote{margin-top:12px;padding:12px 14px;background:var(--sv-well);border-radius:var(--sv-r-card);font-size:var(--sv-body-size);line-height:var(--sv-body-lh);color:var(--sv-ink)}
.sv-mynote .k{display:flex;justify-content:space-between;align-items:center;font-size:var(--sv-micro);color:var(--sv-quiet);margin-bottom:3px}
.sv-mynote .k .sv-link{min-height:var(--sv-tap);display:inline-flex;align-items:center;margin:-12px 0 -10px;font-size:var(--sv-meta-size)}

/* A specification the vendor quotes against: label · value rows in a frame. */
.sv-spec{border:1px solid var(--sv-border-decorative);border-radius:var(--sv-r-card);overflow:hidden}
.sv-spec .r{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border-top:1px solid var(--sv-border-soft);font-size:var(--sv-body-size)}
.sv-spec .r:first-child{border-top:0}
.sv-spec .r span{color:var(--sv-quiet)}
.sv-spec .r b{font-weight:var(--sv-w-semi);text-align:right}

@media (min-width:900px){
  /* THE TABS TAKE CONTROL SIZE PAST 900 (founder, 2 Sep): 12px labels in a
     44px strip read as an afterthought on a laptop. */
  .sv-tabs a{font-size:var(--sv-control-size)}
  .sv-record{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);column-gap:32px}
  .sv-record .r1,.sv-record .kind,.sv-record .place{grid-column:1}
  .sv-record > .sv-facts,.sv-record > .sv-says{grid-column:1}
  .sv-record > .sv-contact,.sv-record > .sv-tracked,.sv-record > .sv-callback{grid-column:2;margin-left:0}
  /* Column two starts at the NAME's line. Without the row, auto-placement
     drops the contact tiles one line down (repo Step 5, 3 Sep). */
  .sv-record > .sv-contact{grid-row:1;margin-top:0}
}
/* ==== scripts/ds_extras.css ==== */
/* THE REPO'S OWN LAYER, AND THE ONE FILE IN THE BUNDLE THAT IS NOT VENDORED.

   design_system/ is a verbatim copy of the claude.ai Design project and must
   stay that way — docs/design/DS_SYNC.md pushes it back up, so a rule written
   in there to fix a repo surface would be pushed into the design system as if
   a designer had drawn it. This file is where a repo-owned rule goes instead.

   ITS POSITION IS THE POINT. scripts/build_ds_css.py concatenates it AFTER the
   pattern layer and BEFORE the trailing wide-viewport restatement, which is
   exactly the slot `extra` occupies in ui_tokens.svanzen_screens_css: the
   desktop pass restates mobile geometry at equal specificity and wins by
   ORDER, so a rule written here beats the phone geometry and loses to the
   desktop pass. A rule that must beat the desktop pass says so with its own
   @media, in this file.

   WHAT IT IS FOR, CONCRETELY — and this paragraph is a correction (21
   Aug): it used to call this file "a BRIDGE, temporary by design" that
   "PR-8 renames the markup and deletes". PR-8 shipped, deleted the
   inline sheets, and deliberately KEPT this layer — #148 even grew it —
   because what it holds turned out not to be bridge material at all:
   the alias tokens above (names this repository's markup speaks that
   the design system spells differently), the screen-local rules the
   flips would otherwise have orphaned (#148's six restored groups), and
   the landing surfaces' own geometry (PR-5). DS_SYNC.md describes the
   settled role: the one non-vendored file in the bundle, where a
   repo-owned rule goes so that design_system/ stays a verbatim vendor
   tree the sync-back can push up unmixed. */

/* ---------------------------------------------------------------- tokens --
   SIX NAMES THIS REPOSITORY USES THAT THE DESIGN SYSTEM SPELLS DIFFERENTLY.
   An undefined custom property does not fail loudly — the declaration is
   invalid at computed-value time, the element quietly inherits, and the page
   looks like a styling bug rather than a build error. Every one of these was
   matched to its counterpart BY VALUE, not by name, except where the design
   system deliberately changed the value; those three are called out because
   adopting them is the point of the port rather than a side effect. */
:root{
  /* Same value, different spelling — verified hex-for-hex. */
  --sv-ink-soft:var(--sv-text-body);          /* #484848 */
  --sv-muted:var(--sv-quiet);                 /* #6F6F6F */
  --sv-line:var(--sv-border-decorative);      /* #DDDDDD */
  --sv-line-soft:var(--sv-border-soft);       /* #EBEBEB */
  --sv-line-empty:var(--sv-border-empty);     /* #E0E0E0 */

  /* THE ONE RATIFIED DIVERGENCE LEFT. The value CHANGES here, and it is
     one of the seven the design system carries deliberately: control border
     #949494 -> #8A8A8A, which is the 3:1 the review asked for on a white
     ground rather than the 2.6:1 it was.

     THE TWO RADIUS ALIASES ARE GONE (2 Sep 2026, DS 4.0 Step 2). They
     carried the same two decisions — every tappable radius 10px -> a pill,
     a standalone photographic frame 10px -> 24px — under this repository's
     old spelling. As of Step 2 every rule in this repository speaks
     `--sv-r-pill` and `--sv-r-frame` by the system's own names, so the
     aliases left with their last reader; the decisions did not change. */
  --sv-control:var(--sv-border-control);      /* #8A8A8A, was #949494 */

  /* THE SIDE SHEET'S STEP, WHICH THE SYSTEM DOES NOT CARRY YET (10 Sep
     2026, port train 1). The drawer's mock names `--sv-t-sheet` and falls
     back to 240ms; the design system has --sv-t-expand (380ms, the BOTTOM
     sheet's svSheetUp) and nothing between it and --sv-t-hover. A `var()`
     on an undefined name is not free here — it is invalid at computed-value
     time, and tests/test_ds_bundle.py fails the build for it — so the name
     is defined once, in the repo's own layer, at the value the mock was
     reviewed at. A FINDING for the design system: a panel that enters from
     the side is a shorter move than one that rises from the bottom, and the
     motion scale has no step for it. */
  --sv-t-sheet:240ms;
}

/* ------------------------------------------------------------- the ramp --
   SIX ROLES, AND THE MICRO-VARIANTS COLLAPSE INTO THEM. This repository grew
   a heading set at 25/24/22px — three sizes inside three points of each
   other, which is what a ramp with no rule about itself produces. The design
   system's answer is six roles and a 600 ceiling, and a variant that wants a
   seventh size is a variant the ramp declines. So `.sv-h1` and all three of
   its step-downs resolve to one role.

   NO PER-CLASS DESKTOP RESTATEMENT ANYWHERE BELOW, deliberately: the design
   system steps the ramp in tokens/typography.css at >=900px, and `var()`
   resolves at use time, so every rule here inherits the desktop step for
   free. That is why the ramp lives in tokens there rather than in the
   desktop pass, and it is the reason this file needs no @media of its own. */
.sv-h1{font-size:var(--sv-title);line-height:var(--sv-title-lh);letter-spacing:var(--sv-title-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0;text-wrap:pretty}
.sv-h1.hero{font-size:var(--sv-display);line-height:var(--sv-display-lh);letter-spacing:var(--sv-display-ls)}
.sv-h2{font-size:var(--sv-section);line-height:var(--sv-section-lh);letter-spacing:var(--sv-section-ls);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0}
.sv-h3{font-size:var(--sv-body-size);line-height:var(--sv-body-lh);font-weight:var(--sv-w-semi);color:var(--sv-ink);margin:0}
.sv-sub{font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);color:var(--sv-quiet);margin:0}
.sv-note{font-size:var(--sv-meta-size);line-height:var(--sv-meta-lh);color:var(--sv-quiet);margin:0}
.sv-fine{font-size:var(--sv-micro);line-height:var(--sv-micro-lh);color:var(--sv-quiet);margin:0}
/* A rule block's own sentence is the page qualifying itself, and reads one
   tier up from anything set finer under it. */
.sv-rule > .sv-note{color:var(--sv-text-body)}
/* A BARE `<b>` OR `<strong>` RENDERS AT THE BROWSER'S OWN 700, and 4.0's
   ceiling is 600 for every role but the wordmark — so both read the token. */
b, strong { font-weight: var(--sv-w-semi); }
/* PROSE KEEPS A MEASURE — 66ch is where a line stops needing a finger to find
   its way back. The design system states the same rule for .sv-prose in its
   desktop pass; these are the repo's own paragraph roles saying it too. */
@media (min-width:900px){
  .sv-sect > .sv-lede,.sv-sect > .sv-note,.sv-sect > .sv-fine,
  .sv-stack > .sv-lede,.sv-stack > .sv-note,
  .sv-titles > .sv-sub{max-width:66ch}
}

/* -------------------------------------------------- the claim steps 3.8-3.11
   A labelled field that is a STATEMENT rather than an input. The claim flow
   takes no typing on the web — the proof arrives over WhatsApp — so the
   mock's two form rows are drawn as what they are: values, under labels. */
.sv-field{display:flex;flex-direction:column;gap:8px}
.sv-field .k{font-size:var(--sv-micro);line-height:var(--sv-micro-lh);font-weight:var(--sv-w-semi);color:var(--sv-quiet)}
/* The number being asked about, with a rule under it because something IS
   being asked of it. */
.sv-number{display:flex;align-items:baseline;gap:10px;padding-bottom:9px;font-size:var(--sv-section);font-weight:var(--sv-w-semi);letter-spacing:.04em;line-height:var(--sv-section-lh);color:var(--sv-ink);border-bottom:1.5px solid var(--sv-ink);font-variant-numeric:tabular-nums}
.sv-number .cc{font-size:var(--sv-section);font-weight:var(--sv-w-medium);color:var(--sv-quiet)}
/* The number that is no longer the number: quieter, and no rule under it,
   because nothing is being asked of it. */
.sv-number.past{padding-bottom:0;border-bottom:0;font-weight:var(--sv-w-medium);color:var(--sv-quiet)}
/* A PLACEHOLDER MUST NEVER READ AS A REAL VALUE at a glance. There is no
   amber in this palette to shout in, so it is marked by a dashed underline
   and by dropping a size below the values around it. */
.ph{font-size:var(--sv-micro);font-weight:var(--sv-w-regular);color:var(--sv-text-body);border-bottom:1px dashed var(--sv-border-control)}
/* The two dashed targets 3.9 draws. A pair, not a mosaic. */
.sv-pair{display:grid;gap:6px;grid-template-columns:repeat(2,minmax(0,1fr))}
.sv-dash{display:flex;flex-direction:column;gap:12px}

/* ------------------------------------------------------------- spacing ----
   Three one-line utilities with no design-system counterpart, because they
   are about where a block sits in a column rather than what it looks like. */
.sv-blockpad{padding-top:20px}
.sv-centre{text-align:center}
/* THE LAST LINE ON A SCREEN, AND THE 8px IS ITS ONLY AIR. Every caller is a
   top-level .sv-pad block sitting directly in .sv-screen, a column that
   declares no gap — without this the footnote lands on the block above it. */
.sv-tail{padding-top:8px}

/* ---------------------------------------------------------- the one foot --
   THE TAP FLOOR APPLIES TO A FOOTER TOO. Quiet type is still a control a
   thumb has to hit, so every item carries the 44px height the bar's controls
   do — as min-height on an inline-flex box, which adds air around the text
   without moving the text itself. */
.sv-foot .row{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:4px 8px}
.sv-foot .row form{margin:0}
.sv-foot .row a,.sv-foot .row button{display:inline-flex;align-items:center;min-height:var(--sv-tap);padding:0 2px;font:inherit;font-size:var(--sv-micro);line-height:var(--sv-micro-lh);color:var(--sv-quiet);text-decoration:underline;background:none;border:0;cursor:pointer}
.sv-foot .row a:hover,.sv-foot .row button:hover{color:var(--sv-ink)}

/* ------------------------------------------------------ the public profile
   /v's own two, neither of which the design system carries: the contact
   strip is this product's shape, and the tappable telephone line under it
   is the repo's.

   THE VOW IS NOT ONE OF THEM, AND NEITHER IS THE LEDE ANY MORE. This
   repository set the vow a hair smaller than the lede it punctuates (13/600
   against 13.5/400) and carried the difference on weight; 4.0 took both
   decisions out of this layer's hands. The vow has its own step,
   `--sv-voice-size` (18px, "the vow, and only the vow", unstepped), and the
   lede has its own, `--sv-lede` (16px, 17px past 900). This file sets
   NEITHER: both come from the bundle, and the anti-drift guarantee the old
   override was written for is now RANK — voice >= lede >= body at both
   widths — pinned on the tokens themselves in tests/test_svanzen_pages.py.
   A `.sv-state .sv-lede` rule would reintroduce exactly the per-surface
   size the rank test forbids; there is none, and there must not be one. */
/* THE CONTACT STRIP, first fold. The one block on this page that prints a
   phone number, so the number and the link that acts on it sit on the same
   line at the same size — only one of the two happens to be pressable.
   RENAMED `.sv-contactline` ON 2 SEP 2026. DS 4.0 gives `.sv-contact` to the
   record card's four-slot grid, and this layer is spliced AFTER the pattern
   layer — so this flex strip was silently winning over the system's grid on
   every record. DS_SYNC.md's rule for a name the system has taken for a
   different object is that the REPO renames (its precedents: `.sv-logincode`,
   `.sv-metarow`, `.sv-whybox`, `.sv-vpreview`). Declarations unchanged. */
.sv-contactline{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:14px;padding-top:6px}
.sv-contactline > a{font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi)}
.sv-contactline .sv-mono{font-size:var(--sv-meta-size);font-weight:var(--sv-w-regular);color:var(--sv-quiet)}
/* A chip row pushes its own edge off the screen if it justifies to the ends. */
.sv-contactline.chips{justify-content:flex-start;gap:8px}
.sv-contactline.chips .chip{min-height:var(--sv-tap);padding:0 13px;max-width:100%;border-color:var(--sv-border-control);text-decoration:none}
/* A TAPPABLE LINE CARRIES THE 44px FLOOR even when it reads as text. */
.sv-tel{display:inline-flex;align-items:center;min-height:var(--sv-tap);font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);line-height:1.4;color:var(--sv-ink)}

/* ============================================ the landing surfaces (PR-5) ==
   THE FINAL `_EXTRA_CSS` DISPOSITION, and it was decided by comparing
   SELECTORS rather than class names. A rule was dropped only where the
   bundle already carries the identical selector; everything else is below,
   verbatim, with the reasoning its author wrote beside it.

   That distinction is not pedantry. `.sv-column` IS in the design system --
   as a desktop max-width -- while `.sv-column[hidden]` is not, and that rule
   is what hides the question column when the fold swaps to the wait state.
   A class-level comparison would have called it covered and deleted a
   functional rule that no assertion in the required tier would have missed.
   Four `[hidden]` rules travel for exactly that reason.

   Twenty-five rules died here: the fold, the column, the state block, the
   feedback box, the board, the contact strip and the claim-step rules that
   PR-3 and PR-4 had already brought across. Each was checked against the
   bundle by selector, not by name. */

/* ---- 3.1 home: the line that shows what a question looks like ---- */
/* THE HEADLINE NAMES THE PRODUCT AND THIS LINE ASKS THE QUESTION. The
   fold used to do both jobs with one sentence; a name at the top and a
   worked example under it is what tells a stranger what shape of thing
   this box takes, which is the whole reason the line rotates.
   TWO SPANS, ONE SENTENCE. The visible one is `aria-hidden` because it
   MUTATES — a live region that retypes itself every few seconds is a
   screen reader talking over its own reader — and the clipped one is
   the same phrase, static, which is what is actually announced.
   THE HEIGHT IS RESERVED FOR THE LONGEST PHRASE (two lines at this
   size). A line that grows from one row to two mid-rotation moves the
   search pill under it, and a control that walks up the page while a
   reader is aiming at it is worse than a little white space. */
.sv-rotate { min-height: 45px; }
.sv-rotate .live { transition: opacity 200ms ease-out; }
.sv-rotate.fading .live { opacity: 0; }
/* ---- 3.1 home: the chosen photo, before it is sent ---- */
/* WHAT THE READER PICKED, SHOWN BACK TO THEM. The file controls are
   clipped (below), so without this the only evidence a photo was
   attached is that a phone's picker closed. `hidden` is the resting
   state and the rule below it is why that works at all: a `display`
   declaration BEATS the `[hidden]` UA style, so a block styled as a
   flex row is visible with the attribute still on it unless the
   attribute is given its own rule. This is that rule.
   IT IS A CONFIRMATION, NOT A SUBMISSION, AND IT CARRIES NO SEND OF ITS
   OWN. Choosing a file changes nothing but this block; the fold's one
   `Search` is what sends it (the script points that button at the photo
   form while a file is waiting), and `Remove` is the way back out. A
   second filled action beside the thumbnail asked a reader looking at
   their own photograph to pick between two buttons that do one thing. */
.sv-preview { display: flex; align-items: center; justify-content: center;
              gap: 14px; }
.sv-preview[hidden] { display: none; }
.sv-preview .thumb { width: 72px; height: 72px; object-fit: cover;
                     border-radius: var(--sv-r-frame);
                     border: 1px solid var(--sv-line); }
/* THE THUMB HIDES ON ITS OWN NOW, because there are two ways to say "it
   is attached" and only one of them is a picture — see `.sv-attached` in
   the shared sheet, and `SVANZEN_THUMBNAIL_JS` for when each is drawn.
   `<img>` has no `display` of its own here, so the UA's `[hidden]` would
   have sufficed; it is stated because the block around it needed the
   same rule and a reader comparing the two should find them alike. */
.sv-preview .thumb[hidden] { display: none; }
.sv-preview .acts { display: flex; align-items: center; gap: 14px; }
.sv-preview .sv-attached { max-width: 210px; }
/* 16px, not 8: the 8px read as the button being attached to the field above
   it rather than following it (founder, 24 Aug). The front-door no-scroll
   contract pays for this out of the retuned display role. */
.sv-actions { display: flex; gap: 10px; justify-content: center;
              flex-wrap: wrap; padding-top: 16px; }
/* ---- 3.1 home: the wait, once a question is on its way ---- */
/* THE FOLD IS ITS OWN LOADING SCREEN, because there is nowhere else for
   one to be: both questions leave as a full-page POST, and until the
   server answers — an ask can take the better part of a minute — the
   browser keeps painting the page it already has. Left alone that is a
   live field and a filled button with no evidence anything happened,
   which is a page a reader presses a second time. So the column steps
   out and this block takes the space it was centred in.
   BOTH HALVES SWAP BY `hidden`, the same mechanism and the same UA-style
   lesson as `.sv-preview` above: a `display` declaration beats the
   `[hidden]` style, so each one needs its own attribute rule before the
   attribute means anything.
   THE MARK IS `.sv-throb` OUT OF THE ROOT SHEET, unchanged — /login and
   /claim wait on the same kind of pause and draw the same 96px file, and
   a reader who asked for less motion has it removed there and here,
   which is why the line underneath has to say the same thing in words.
   IT IS ALSO WHY THE `<img>` SHIPS INSIDE THE HIDDEN BLOCK RATHER THAN
   BEING WRITTEN ON DEMAND: a hidden element is still a fetched one, so
   the file is in the cache before the press and the mark is painted on
   the same frame the attribute flips, not after a round trip.
   `.sv-note` AND NOT THE CLAIM CARD'S `.sv-fine`: there it is the small
   print under a code, here it is the only sentence on the screen. */
.sv-column[hidden] { display: none; }
/* RENAMED `.sv-waiting` ON 2 SEP 2026: DS 4.0 gives `.sv-wait` to the record
   layer's ink waiting PILL ("Waiting on you · 2h"), and this column block was
   winning over it from below. The id stays `sv-wait` — scripts and the browser
   tier reach this block by id, and an id is not a class. */
.sv-waiting { display: flex; flex-direction: column; align-items: center;
           justify-content: center; padding: 8px 0 20px; }
.sv-waiting[hidden] { display: none; }
/* THE QUESTION ON ITS WAY, IN THE READER'S OWN WORDS (founder item 7).
   The wait used to leave the rotating EXAMPLES cycling above it; what
   belongs on a loading screen is the thing being loaded. Set a step
   above the status line under it — this is what the reader is waiting
   for and that is the machine reporting — and centred with it. Written
   into by the fold script through `textContent`, so it holds text and
   can hold nothing else; `[hidden]` needs its own rule for the reason
   `.sv-preview` above does. */
.sv-asked { max-width: 320px; margin: 0 0 8px;
            font-size: var(--sv-body-size); font-weight: 600;
            line-height: 1.45; color: var(--sv-ink);
            text-wrap: pretty; }
.sv-asked[hidden] { display: none; }
/* THE FILE CONTROL IS CLIPPED, NOT HIDDEN, and the camera glyph in the
   field is its label. `display: none` would take it out of the tab
   order; this keeps it focusable, and the rule below draws the ring on
   the label, where a reader can see it. (One door since #146 — the
   gallery control's selector was parked here awaiting the next rebuild,
   which is this one.)

   `.sv-sr` IS THE DESIGN SYSTEM'S OWN (tokens/typography.css) and is
   deliberately NOT restated here — a re-vendor that changes it must not
   find a half-shadowing copy in the repo layer. Only `.sv-file`, the
   repo's control, carries the extra `clip-path`. */
.sv-file { position: absolute; width: 1px; height: 1px;
           margin: -1px; padding: 0; overflow: hidden;
           clip: rect(0 0 0 0); clip-path: inset(50%);
           white-space: nowrap; border: 0; }
/* THE RING IS DRAWN ON THE LABEL AND THE INPUT IS NOT ITS SIBLING: the
   clipped input sits in the photo form and its glyph sits in the ask
   form's drawn row, which is what `form=` associations cost. `:has()` on
   the column that holds both is the one selector that can still reach
   across it. Under the ink ring, ink and focus were one colour; under
   DS 4.2 they are not. */
.sv-column:has(#photo:focus-visible) [for="photo"] {
                   outline: 2px solid var(--sv-focus); outline-offset: 2px; }
/* ---- 3.2 results: the reference card and the result card ---- */
.sv-refline { font-size: var(--sv-control-size);
              font-weight: var(--sv-w-semi); line-height: 1.4;
              color: var(--sv-ink); margin: 0; overflow-wrap: anywhere; }
.sv-result { display: flex; flex-direction: column; gap: 11px;
             color: inherit; text-decoration: none; }
.sv-result .text { display: flex; flex-direction: column; gap: 2px; }
/* SCOPED TO THE RESULT CARD ON 2 SEP 2026. DS 4.0's `tokens/typography.css`
   gives `.sv-name` the 20px name step — a maker's name on a record — and this
   16px rule, spliced after it, was taking every record's name back down.
   Both emitters of this one (screens.result_card, screens.design_card) sit
   inside `.sv-result`, so the rule says so.
   AND ON 2 SEP IT TOOK THE STEP ITSELF (Step 4). Scoping only stopped the
   collision; what sits inside `.sv-result` is the very object the name step
   is FOR — a maker's name on a card — so the rule reads `--sv-name` with the
   step's own leading and tracking. 16px at 1.35 everywhere becomes 20px on a
   phone and 22px past 900, at 1.15 and -.015em. */
.sv-result .sv-name { font-size: var(--sv-name);
           line-height: var(--sv-name-lh);
           letter-spacing: var(--sv-name-ls);
           font-weight: var(--sv-w-semi);
           color: var(--sv-ink); margin: 0; text-wrap: pretty; }
/* The rank a card carries in an answer ("1." before the name). INK,
   never cobalt: an ordering is ours to state, provenance it is not. */
.sv-rank { color: var(--sv-ink); font-variant-numeric: tabular-nums; }
.sv-why { margin: 4px 0 0; padding-left: 18px;
          font-size: var(--sv-meta-size);
          line-height: 1.55; color: var(--sv-ink-soft); }
.sv-warnline { margin: 4px 0 0; font-size: var(--sv-meta-size);
               line-height: 1.55; color: var(--sv-ink); }
.sv-state.bare { padding-bottom: 0; gap: 0; }
.sv-emptyref { width: 104px; }
/* THE REFERENCE CARD PAYS THE AIR UNDER IT, and it has to pay it itself
   because of where it sits: `.sv-titles` carries 18px BELOW its heading
   and nothing above, on the reasoning that whatever precedes a title
   block owns the gap — which is true of `.sv-backrow` (16px) and was
   true of nothing else until this card was slid between the two. Left
   alone, a filled card with a hairline edge ended on the same pixel the
   25px heading's line box began, on `/ask` and `/lookup` at every phone
   width. 16px, the back row's own number, because the question this
   answers is the same one: what does a block above a title block owe
   it? */
.sv-refzone { padding-bottom: 16px; }
/* A poster frame beside text (§3.11 [D]). `flex: none` is the whole
   rule: in a flex row a fixed width is only a suggestion, and without
   it the frame is squeezed to whatever the sentence beside it leaves. */
.sv-poster { width: 112px; flex: none; }
.sv-optzone { padding-top: 34px; }
/* ---- 3.14-3.16 the product screens ---- */
/* THE 2-UP, AND WHY THE GAP IS 2px AND NOT 4px. Everywhere else in this
   system a pair of frames is a MOSAIC of related things at a 4px gap
   (`.sv-grid`). This is not a mosaic — it is a comparison, two halves of
   one statement, and the hairline between them reads as a seam rather
   than as a gutter. The mock draws exactly that difference. */
.sv-match { display: grid; gap: 2px;
            grid-template-columns: repeat(2, minmax(0, 1fr)); }
.sv-matchbody { display: flex; flex-direction: column; gap: 9px;
                padding: 16px 18px 18px; }
.sv-matchname { font-size: var(--sv-name);
                font-weight: var(--sv-w-semi); line-height: 1.28;
                color: var(--sv-ink); margin: 0; text-wrap: pretty; }
/* A LABEL/VALUE ROW WHOSE VALUE IS A SENTENCE. Catalog `specs` are the
   catalog's own wording, so a key like "Recommendation" arrives with a
   forty-word paragraph behind it — and forty words right-aligned in a
   half-width column is a ragged left edge nobody can read. The row
   stacks instead: label, then value, both left. Short values keep the
   two-column shape the mock draws. */
.sv-kv.stack { flex-direction: column; align-items: flex-start; gap: 4px; }
.sv-kv.stack .v { text-align: left; font-weight: 400;
                  line-height: 1.55; }
/* The film frame is 186px wide and centred, the mock's own figure — a
   9:16 poster in a column this narrow is the one image that must not
   run to the gutters. */
.sv-film { width: 186px; margin: 0 auto; }
/* §3.16 [D]'s scene rows. The label column is fixed at the mock's 52px
   so two labels of different lengths still start their prose on one
   line — a scene list whose left edge moves reads as two paragraphs. */
.sv-scenes { display: flex; flex-direction: column; }
.sv-scene { display: flex; gap: 12px; align-items: baseline; padding: 11px 0;
            border-top: 1px solid var(--sv-line-soft); }
.sv-scene:last-child { border-bottom: 1px solid var(--sv-line-soft); }
.sv-scene .n { width: 52px; flex: none; font-size: var(--sv-micro);
               font-weight: var(--sv-w-semi);
               line-height: 1.4; color: var(--sv-cobalt); }
.sv-scene .s { font-size: var(--sv-meta-size); line-height: 1.5;
               color: var(--sv-ink-soft); }
.sv-fid { display: flex; gap: 14px; align-items: center; }
.sv-fid .body { display: flex; flex-direction: column; gap: 4px; }
.sv-fid .t { font-size: var(--sv-control-size);
             font-weight: var(--sv-w-semi); line-height: 1.35;
             color: var(--sv-ink); }
/* ---- 3.12 brand ---- */
.sv-strip { display: flex; gap: 12px; overflow-x: auto;
            padding: 14px var(--sv-gutter, 22px) 4px; }
.sv-catalog { width: 138px; flex: none; display: flex;
              flex-direction: column; gap: 9px; color: inherit;
              text-decoration: none; }
.sv-catalog .n { font-size: var(--sv-control-size);
                 font-weight: var(--sv-w-semi); line-height: 1.3;
                 color: var(--sv-ink); }
/* ---- `/terms`: the statutory block ---- */
/* THE INDEX ROWS AND THE PRINCIPLE TICKS WENT WITH THEIR SECTIONS on
   15 Aug (founder items 16 and 18 — landing.py carries the note on what
   was removed and what is still true). `.sv-idx`, `.sv-idx-name`,
   `.sv-idx-n`, `.sv-ticks` and `.sv-tick` had exactly one caller each
   and are deleted with it: a rule with no markup left is the thing the
   next reader spends twenty minutes looking for the caller of. */
/* ONE OBLIGATION PER ROW — the label above the fact, hairline-separated,
   which is the shape the block has always drawn and never had a rule
   for: `.sv-obl` and `.sv-k` were written into `svanzen_home_css` and
   that sheet stopped being the one `/terms` renders against when the
   3.0 screens landed. So the statutory table has been running on
   default block layout since. Restated here, in the sheet that actually
   serves it, at the same numbers — all but the label's, which was 13.5px, a
   size no ramp in this product carries, and reads `--sv-meta-size` (13, 14
   past 900) as of DS 4.0 Step 4. */
.sv-obl { padding: 14px 0; border-top: 1px solid var(--sv-line-soft); }
.sv-obl:first-child { border-top: 0; }
.sv-k { font-size: var(--sv-meta-size); color: var(--sv-muted);
        margin-bottom: 8px; }
.sv-feedback textarea { font: inherit; font-size: var(--sv-control-size);
                        line-height: 1.5; width: 100%;
                        padding: 11px 12px; resize: vertical;
                        border: 1px solid var(--sv-control);
                        border-radius: var(--sv-r-pill);
                        background: var(--sv-well); color: var(--sv-ink); }
.sv-feedback textarea::placeholder { color: var(--sv-muted); }
.sv-strong { font-size: var(--sv-body-size);
             font-weight: var(--sv-w-semi); line-height: 1.45;
             color: var(--sv-ink); overflow-wrap: anywhere; }
.sv-addr { white-space: pre-line; font-size: var(--sv-meta-size);
           line-height: 1.55; color: var(--sv-ink); }
/* One family here too: what this marks is a run of digits (a CIN, a
   GSTIN, a phone, a vendor's website), and what it needs is tabular
   figures and a break point — not a second typeface chosen by the
   reader's OS. */
.sv-mono { font-variant-numeric: tabular-nums; letter-spacing: .01em;
           overflow-wrap: anywhere; }
/* `.sv-foot` LEFT THIS BLOCK for `ui_tokens.SVANZEN_FOOT_CSS` (founder
   item 5, 15 Aug evening): the footer is on every shell in the product
   now, including the ones served by the funnel sheet, so its rules
   cannot live in the sheet only the public screens render. */
/* ---- 3.4, 3.6, 3.7, 3.13: the profile screens ---- */
/* `.sv-tel` LEFT THIS BLOCK FOR `_SV_TYPE`, at the same 15/600 it has
   always drawn: a size only one surface knows about is a size the ramp
   cannot keep in step, and the number in this fold is the most tapped
   thing on the page. Its 44px floor came with the move.
   `.sv-attrib` IS GONE and its markup says `.sv-fine` now. The two
   were the same declaration written twice — 12px, muted, the quiet
   tier — differing only in a `font-weight: 400` that existed to escape
   the type `.sv-contact` used to set on its container, and that
   container no longer sets any. A parallel spelling of a ramp role is
   the thing the next reader greps for and finds two of. */
/* The one sentence that sits between the first fold and the first
   section — "Built from public record. Nobody from X has confirmed this
   page.", or the save count on a claimed one. It is a rule with words
   on it rather than a card, because it qualifies everything under it
   rather than saying anything of its own. */
.sv-rule { margin: 16px var(--sv-gutter, 22px) 0; padding-top: 16px;
           border-top: 1px solid var(--sv-line-soft); }
/* A run of cells that is NOT a mosaic: the claim grid's ten, where four
   are photographs and six are dashed outlines of a photograph nobody
   has sent. Its own class rather than `.sv-grid`, which clips its
   children to one rounded block — a dashed cell inside that clip loses
   two of its own edges. */
.sv-cells { display: grid; gap: 4px;
            grid-template-columns: repeat(5, minmax(0, 1fr)); }
.sv-cells.c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.sv-cells .sv-tile, .sv-cells .sv-figure { border-radius: var(--sv-r-frame); }
/* RENAMED `.sv-otp` ON 2 SEP 2026. DS 4.0 gives `.sv-code` to the login-code
   STRING at the figure step (32px, `tokens/typography.css`, "one string a
   human copies by eye"), and these six drawn CELLS are this repository's own
   object under the same name — an OUTPUT shaped like an OTP, which is what
   `screens.code_boxes` calls them. DS_SYNC.md's rule for a name the system
   has taken for a different object is that the REPO renames (its precedents:
   `.sv-logincode`, `.sv-metarow`, `.sv-whybox`, `.sv-vpreview`). Geometry
   unchanged; the one declaration that moved is the character's, from a 20px
   literal to the name step it was already sitting on, at the 600 ceiling. */
.sv-otp { display: flex; gap: 7px; }
.sv-otp span { flex: 1; display: flex; align-items: center;
               justify-content: center; aspect-ratio: 3 / 4;
               border: 1.5px solid var(--sv-ink);
               border-radius: var(--sv-r-pill);
               font-size: var(--sv-name); font-weight: var(--sv-w-semi);
               line-height: 1; color: var(--sv-ink);
               font-variant-numeric: tabular-nums; }
/* ---- 3.5: the plate's source column ---- */
/* The 64px source photograph and the dimension rows beside it, under
   the drawing and over its own hairline (§3.5 [C]). */
.sv-src { display: flex; gap: 12px; align-items: flex-start;
          margin-top: 13px; padding-top: 13px;
          border-top: 1px solid var(--sv-line-soft); }
.sv-src .thumb { width: 64px; flex: none; display: flex;
                 flex-direction: column; gap: 5px; }
.sv-src .body { flex: 1; min-width: 0; }
/* ---- 3.17, 3.18: stone ---- */
.sv-slabs { display: grid; gap: 10px;
            grid-template-columns: repeat(2, minmax(0, 1fr)); }
.sv-slab { display: flex; flex-direction: column; justify-content:
           space-between; gap: 10px; min-width: 0; min-height: 104px;
           box-sizing: border-box; padding: 14px;
           border: 1px solid var(--sv-line-soft);
           border-radius: var(--sv-r-grid); }
/* A slab that exists as a line in a listing and as nothing else. The
   dashed edge is this system's mark for absence everywhere else and it
   is the same mark here. */
.sv-slab.absent { border-style: dashed; border-color: var(--sv-control); }
.sv-slab .lot { font-size: var(--sv-meta-size);
                font-weight: var(--sv-w-semi); line-height: 1.35;
                color: var(--sv-ink); }
/* 11px WAS UNDER THE FLOOR. 4.0 calls micro a floor and not a scale point —
   "nothing in this system is smaller" — so a lot's dimensions and its state
   read `--sv-micro` (12, unstepped) rather than a point below it. */
.sv-slab .size { font-size: var(--sv-micro); line-height: 1.45;
                 color: var(--sv-muted); }
.sv-slab .state { font-size: var(--sv-micro); line-height: 1.4;
                  color: var(--sv-muted); }
/* A CHOICE THAT IS DRAWN AND NOT OFFERED. `.sv-chip.choice` carries a
   pointer cursor because on the mock it is a control; where the answer
   has nowhere to be stored the chips are `<span>`s, and a hand cursor
   over something that cannot be pressed is the smallest possible lie a
   screen can tell. See portal/claim.py's consent block for the whole
   argument and for what happens the day there is a column to write to. */
.sv-chips.inert .sv-chip { cursor: default; }
/* THE CHIP ROW UNDER A TITLE IS THE TITLE BLOCK'S LAST LINE, so it owes
   the block below it what `.sv-titles` owes: 18px. `.sv-chips` on its
   own is only "lay these out in a wrapping row" and pays no vertical
   rhythm at all, which on `/plate/1` put a chip's outlined pill edge
   flat against the drawing card's outlined edge — two drawn borders on
   one pixel, at every width. Scoped to `.sv-pad.sv-chips`, the STANDALONE
   row: a chip row inside a card body (`.sv-matchbody`, `/design`) is
   spaced by the card's own gap and must not gain a second one. */
.sv-pad.sv-chips { padding-bottom: 18px; }
.banner { background: var(--sv-card); border-bottom: 1px solid var(--sv-ink);
          padding: 14px 22px; font-size: var(--sv-meta-size);
          line-height: 1.55; color: var(--sv-ink); }
.banner code { font-family: inherit; font-weight: 600;
               font-size: var(--sv-micro); overflow-wrap: anywhere; }
@media (min-width:900px){

 /* `minmax(0, 1fr)` frees the TRACK to shrink; a grid item's automatic
    minimum size is its content, so a long unbroken catalog value would
    still push its column out without this. */
 .sv-boardcol { min-width: 0; }
 .sv-board .sv-sect { margin-left: 0; margin-right: 0; }
 /* `.sv-pad.wide` is spelled out rather than left to source order: it
    is two classes, so at one class this rule would only beat it by
    being written later in the sheet, and that is not a thing the next
    edit should have to know. */
 .sv-board .sv-pad, .sv-board .sv-pad.wide { padding-left: 0;
                                             padding-right: 0; }
}

/* ------------------------------------------------- the search row, at 390px
   THE PILL GROWS RATHER THAN CLIPPING, and the numbers behind that are worth
   writing down because they moved twice.

   The design system gives every `.sv-iconbtn` `min-width: var(--sv-tap)` —
   the 44px floor, which is right and which this repository's own icon buttons
   did not have. With THREE of them in the row (a magnifier and two photo
   doors) the field had ~139px at 390px, about sixteen characters, and the
   design system's own default placeholder wrapped and was cut off by the
   pill's edge. Collapsing the two photo doors into one (screens.py's
   `home_fold` carries that argument) returned ~57px, so the field is ~196px
   now — about twenty-three characters against a twenty-seven character
   placeholder. Better, and still one line short.

   So the field takes two lines' worth of height below 430px and the pill
   follows it: "a grown pill is mostly cap" is the design system's own note
   beside the `.open` state, and a wrapped placeholder that can be READ is the
   point. THE PADDING IS HALF THE ARITHMETIC — `.sv-text` is border-box with
   `padding:16px 0`, so one line measures 24 + 32 = 56px and two need
   48 + 32 = 80px. A min-height counting only the lines is SMALLER than the
   height already there and does nothing, which is what the first attempt did.

   A placeholder of about twenty-two characters would remove the need for any
   of this. That is a copy decision and it is the founder's.

   24 AUG — THAT COPY DECISION WAS TAKEN, AND THIS RULE OUTLIVED HALF ITS
   REASON. The placeholder is "What do you need made?", twenty-two
   characters, measuring 184px in Figtree at 16px. Measured field widths are
   166px at 360, 181px at 375, 184px at 378 and 196px at 390, so it wraps at
   or below 377px and fits above it. Holding two lines' height at a width
   where the placeholder needs one is what put the text at the TOP of an 80px
   box instead of the middle of a 56px one — the founder's "not vertically
   centred". The breakpoint therefore sits at the measured boundary, with a
   few pixels of margin for font-rendering variance rather than 378 exactly.
   Below it the accommodation still earns its keep; above it the row centres
   itself. (The 33-character "Interiors Product & Vendor Finder" briefly
   lived here before moving to the h1 where it belonged; it needed 239px and
   wrapped on every phone, which is the case this breakpoint would have had
   to cover instead.) */
@media (max-width:383px){
  .sv-searchrow .sv-text{min-height:calc(2 * 1.5em + 32px)}
}

/* ------------------------------------------------- what the flips left here
   SIX RULE GROUPS THAT WERE RENDERING AND THEN WERE NOT, and every one of
   them for the same reason: they lived ONLY in the inline sheet
   `ui_tokens.svanzen_screens_css` composes, and PR-3/PR-4/PR-5 moved their
   surfaces onto the bundle without bringing them along. The plan called that
   step the `_EXTRA_CSS` disposition pass; on the claim flip it was genuinely
   empty (claim draws 26 classes and none of them were defined there), that
   emptiness was written down as a finding, and the pass was not re-run when
   landing flipped — where it was not empty.

   NOTHING FAILED. An element whose class has no rule renders; it just renders
   as the page's defaults, which is why every one of these survived a green
   suite. The AI disclaimer at the foot of every public answer was 15px ink
   instead of 12px muted — fine print set as body text — from the day `/`
   flipped until this file.

   WHAT KEPT IT INVISIBLE is worth more than the rules. The two assertions
   that pin `.sv-ai-note` and `.sv-prose-list` read `svanzen_screens_css()`,
   and no surface has rendered that sheet since PR-5. They were true and they
   were about nothing. `tests/test_brand_sweep.py` now asks the question the
   other way round — of the bytes a reader is actually served. */

/* The AI disclaimer, under every composed answer on every web surface
   (ui_tokens.ai_disclaimer_html). Fine print by design: a standing property
   of every answer rather than news about this one. */
.sv-ai-note{font-size:var(--sv-micro);line-height:1.6;
            color:var(--sv-muted);margin:18px 0 0}

/* The list a composed answer draws (ui_tokens.prose_html). The markers are
   REAL — a browser's own `1.`/`•` — because the order in a composed answer is
   the model's statement and not our decoration, and a reader copying it needs
   the numerals to come with it. */
.sv-prose-list{margin:2px 0;padding-left:22px;font-size:var(--sv-meta-size);
               line-height:1.55;color:var(--sv-ink-soft)}
.sv-prose-list li{margin:0 0 6px}
.sv-prose-list li:last-child{margin-bottom:0}
.sv-prose-list::marker,.sv-prose-list li::marker{color:var(--sv-muted)}

/* The chosen photo, shown back before it is sent (ui_tokens.SVANZEN_THUMBNAIL_JS
   fails over to this chip when a browser cannot decode the file). The design
   system carries `.sv-preview .sv-attached { max-width }` and nothing else, so
   without these the chip lost its row, its size and its truncation.
   `[hidden]` NEEDS ITS OWN RULE because a `display` declaration beats the UA's
   `[hidden]` style — the lesson screens.py paid for on the public fold. */
.sv-attached{display:inline-flex;align-items:center;gap:7px;min-width:0;
             font-size:var(--sv-meta-size);font-weight:var(--sv-w-semi);
             line-height:1.35;color:var(--sv-ink)}
.sv-attached[hidden]{display:none}
.sv-attached svg{flex:none;color:var(--sv-cobalt)}
.sv-attached .name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* The third state of a chosen photo: one we will not take. Weight, not colour,
   and NOT fine print either way — a reader who cannot see why Search stopped
   working just presses it again. */
.sv-toobig{display:block;font-size:var(--sv-meta-size);
           font-weight:var(--sv-w-semi);line-height:1.4;
           color:var(--sv-ink);margin:8px 0 0}
.sv-toobig[hidden]{display:none}

/* THE TITLE MODIFIERS, RE-DECIDED ON THE WRITTEN RECORD (21 Aug). What
   stood here was a self-cancelling pair — `.sv-h1.screen{24px}` followed
   at equal specificity by `.sv-h1.screen,.sv-h1.long{28px}` — #148's
   restoration of the pre-flip sizes, pasted WITHOUT the @media wrapper
   the second line lived in, so the 24px rule was dead on arrival and
   both modifiers computed 28px at every width. That state matched no
   written intention: not #148's (24 phone / 28 desktop), and not the
   claim flip's, whose commit ratified the RAMP as "the point of the
   port" ("the title goes 24px/800 -> 28px/600 (36px on desktop)").

   `.screen` GOES TO THE RAMP — no rule here at all. The flip ratified
   28/36 for exactly these titles, and the convergence note ("the public
   screens take the six roles") counts it done; a modifier that restates
   the role it sits on is the drift, not the design.

   `.long` AND `.state` KEPT THEIR DRAWN NUANCE AT THE PRE-PORT VALUES,
   each with an `@media` of its own — the two rules this file's own header
   says are not here — until DS 4.0 Step 4 (2 Sep 2026). The ramp moved
   underneath them and settled both without a restatement.

   `.long` KEEPS ONLY ITS LOOSER LEADING. The title step is 24px on a phone
   now, which is precisely the size this modifier was reaching down for, and
   30px past 900 instead of the 28 it restated. The long-name case is still
   real — "Mohammed Ghouse Sahib Furniture & Joinery Works" wraps to three
   lines, and a notch looser is what keeps three lines readable rather than
   tight (portal/public_listing.py still promises exactly this above its
   emitter) — and `text-wrap: pretty` on `.sv-h1` does the rest of it.

   `.state` IS THE SECTION STEP, said in the ramp's own words. The
   drawn-absence heading (`state_block`) is still the mock's quieter size —
   an empty-state card wearing the full title role reads as a headline about
   nothing — but quieter than a title is a role this ramp names: 20px, 22
   past 900, with the step's own leading, rather than a 22px literal and a
   25px restatement of it. */
.sv-h1.long{line-height:1.18}
.sv-h1.state{font-size:var(--sv-section);line-height:var(--sv-section-lh)}

/* ==================================== the funnel's page head (DS 4.0 3a) ==
   MOVED OUT OF `ui_tokens.svanzen_funnel_css` ON 2 SEP 2026, and the move is
   the point rather than the rule. /me and /me/saved stopped drawing this row
   in Step 3a — they open with `.sv-display` and a meta count, the way the kit
   does — but /vendor, /vendor/enquiries, /vendor/help, /claim and /me/profile
   still draw it, and those are not this train's files. A rule inlined into
   every funnel document for four pages that no longer want it is a rule in
   the wrong layer: it is shared, so it belongs in the shared sheet, where the
   pages that draw it are served it through the LINKED bundle and the pages
   that do not draw it are served none of it. Declarations verbatim. */
.sv-pagehead { justify-content: space-between; align-items: baseline;
               margin-bottom: 16px; }

/* ============================== the record's own repairs (DS 4.0 Step 5) ==
   FOUR RULES THE RECORD LAYER NEEDS AND THE KIT DOES NOT GIVE IT. Three are
   a base `patterns/record.css` assumes and the bundle does not carry; the
   fourth is a place where the kit's drawing and this product's reading of it
   disagree, and by this file's own rule the REPO overrides and the
   disagreement is logged rather than edited into the vendored tree. Six
   portal routes were scored against `design_system/guidelines/critic-rubric.md`
   on the merged tree and every record screen showed the same three. */

/* An ABSENT slot keeps its shape and loses contrast. DS 4.2 pulled down
   the repo's solid quiet border and unstruck label (9 Sep 2026); only the
   radius moves here, onto the card token shared by all four slots. */
.sv-contact .absent { display: flex; flex-direction: column; align-items: center;
                      justify-content: center; gap: 4px; min-height: 54px; padding: 0;
                      border-style: solid; border-color: var(--sv-border-soft);
                      border-radius: var(--sv-r-card); font-size: var(--sv-micro);
                      font-weight: var(--sv-w-semi); color: var(--sv-quiet); }
.sv-contact .absent span { text-decoration: none; }
.sv-contact .absent svg { opacity: .55; }

/* The kit's timeline assumes a list reset the bundle does not carry: without
   it the <ol> draws "1." under the marker. */
.sv-timeline { list-style: none; margin: 0 0 0 5px; padding-left: 16px; }

/* The callback sentence is a full line UNDER the tracked row's controls,
   not a flex item beside them. */
.sv-tracked .sv-callback-form { flex-basis: 100%; }
.sv-tracked .sv-callback { flex-basis: 100%; margin: 4px 0 0; }

/* GOOGLE'S ATTRIBUTION, the policy's text form (3 Sep 2026): 400 weight,
   the micro step (12px, inside the policy's 12–16), the quiet colour that
   passed the contrast walk, inside the record it qualifies.

   WRITTEN CLOSED UP (`.sv-attrib{`) and that is load-bearing, not style:
   tests/test_svanzen_pages.py pins the SPACED selector out of the served
   bytes, because `.sv-attrib` was once a /v-only alias for `.sv-fine` and
   that one-off must not come back under its old spelling. This is the
   design system's own minified form and a different rule entirely. */
.sv-attrib{margin:8px 0 0;font-size:var(--sv-micro);line-height:var(--sv-micro-lh);font-weight:400;color:var(--sv-quiet);white-space:nowrap}
.sv-record > .sv-attrib{margin-left:28px}   /* the rank gutter, like every line under the name */
.sv-saved > .sv-attrib{margin-left:0}
/* Inside the /ask credential — a flex ROW — the attribution sits on the
   dated line's baseline, a gap to its left, not a line under it. */
.sv-cred .sv-attrib{margin:0 0 0 8px;align-self:baseline}        /* a saved record has no rank */

@media (min-width:900px){
  /* The kit sets no grid-row, so auto-placement drops the contact row one
     row under the name; beside the name is the intent. */
  .sv-record > .sv-contact { grid-row: 1; }
  /* The record becomes two columns here; the attribution belongs with the
     facts it qualifies, never in the contact column. */
  .sv-record > .sv-attrib{grid-column:1}
}

/* ================================== the shell: drawer and motion (port 1) ==
   FOUNDER, 10 Sep 2026 11:12 — "left side hamburger for navigation, desktop
   and phone" — and 12:0x — "workspace tabs move into the drawer, tab strip
   removed". The markup is ui_tokens.svanzen_drawer_html, emitted by BOTH top
   bars; this is its whole stylesheet, plus the system-level half of the
   motion inventory the same mock pass specifies.

   TOKENS ONLY. No literal hex and no new type size: the scrim's paint is the
   bundle's own (--sv-scrim, rgba(34,34,34,.42)) and the focus ring is the
   bundle's global `button:focus-visible` (2px --sv-focus at 2px offset), so
   neither is restated. A DRAWER IS A SHEET — it takes the sheet's elevation
   step (--sv-el-sheet) and the sheet's z-index (21) over the scrim's 20. */

/* THE TWO BUTTONS. `.sv-iconbtn` is the bundle's own 44x44 / no border / no
   fill control and both buttons carry it, which is what the mock's own
   finding asked the port to do rather than redraw the geometry here. What is
   left is the ink (the icon button paints --sv-text-body; a navigation
   opener beside the wordmark is ink) and the exact box, because `.sv-iconbtn`
   sets MIN width and height and a flex row may still stretch it. */
.sv-menu-btn,.sv-drawer-close{flex:none;width:var(--sv-tap);height:var(--sv-tap);color:var(--sv-ink)}
/* The bar is pinned to ONE line and the wordmark keeps the left edge it has
   today: with a third child in the row, `space-between` would centre it.
   `header.sv-top` beats both sheets that set `flex-wrap:wrap` on this element
   (the bundle's `.sv-app` and the funnel's own inline `.sv-top`) on
   specificity rather than on order, which is the only thing that reaches
   across a <style> that follows the <link>. */
header.sv-top{flex-wrap:nowrap}
header.sv-top > .sv-brand{margin-right:auto}

/* THE SHIPPED SCREEN ROOT IS A FIXED-POSITIONING CONTAINING BLOCK, and the
   drawer lives inside it. `.sv-screen` runs svScreenIn with fill `both`, so
   the finished `to` step stays applied — and `transform:none` inside a
   keyframe computes to the IDENTITY MATRIX, which is still a transform.
   Every position:fixed descendant would be laid out against the 440/1240px
   screen column rather than the viewport, and would scroll away with the
   page. `backwards` fills only BEFORE the active phase: the entry animation
   plays identically and leaves the base `transform:none` behind it. A
   FINDING FOR THE DESIGN SYSTEM, recorded where it is fixed — as shipped, no
   overlay inside .sv-screen could be fixed to the viewport. */
main.sv-screen{animation-fill-mode:backwards}

/* THE DRAWER. `hidden` is display:none, and a transition cannot run off a
   display change, so the slide is an ANIMATION — the same device the bundle
   uses for the sheet (svSheetUp) and for this scrim (svScrimIn). */
.sv-drawer{position:fixed;left:0;top:0;z-index:21;display:flex;flex-direction:column;width:min(320px,86vw);height:100dvh;padding:8px 0;overflow-y:auto;background:var(--sv-card);box-shadow:var(--sv-el-sheet);animation:svDrawerIn var(--sv-t-sheet) var(--sv-ease) both}
.sv-drawer[hidden]{display:none}
@keyframes svDrawerIn{from{transform:translateX(-100%)}to{transform:none}}
.sv-drawer-close{align-self:flex-end;margin-right:8px}
/* A hairline between GROUPS only — never between two items of one group, and
   never emitted as an element: the workspace's four sections draw the rule
   under themselves by being a group with a group after them. */
.sv-drawer-group + .sv-drawer-group{margin-top:8px;padding-top:8px;border-top:1px solid var(--sv-border-soft)}
.sv-drawer .sv-micro-t{padding:8px 16px}
.sv-drawer ul{list-style:none;margin:0;padding:0}
.sv-drawer li{margin:0}
.sv-drawer a{display:flex;align-items:center;min-height:var(--sv-tap);padding:0 16px;font-size:var(--sv-body-size);font-weight:var(--sv-w-semi);color:var(--sv-ink);text-decoration:none}
/* THE SCRIM is the bundle's own, moved from the sheet's absolute box to the
   viewport — and SCOPED TO THE DRAWER'S, which is the one deviation from the
   mock's text. `svanzen_sheet` renders a second `.sv-scrim` on /plate/<id>
   that is deliberately absolute inside `.sv-screen`; an unscoped
   `position:fixed` would tear that overlay off its own sheet. */
#sv-drawer + .sv-scrim{position:fixed}
#sv-drawer + .sv-scrim[hidden]{display:none}

/* THE HEADER SEARCH SLOT'S GEOMETRY, and nothing that fills it: the markup
   and the script that move the shipped `.sv-column` into the bar past 900px
   are the next train's. The slot is drawn nowhere yet, so every rule below
   is inert today and lands with the markup rather than after it. */
.sv-app-search{display:none}
@media (min-width:900px){
  /* ONE 72px ROW, AND ONLY WHERE THE SLOT IS ACTUALLY DRAWN: --sv-app-h
     restated for >=900 as 6 + 58 + 8, so a 54px search row and the 44px
     lockup both centre in the 58px it opens up. A bar with no slot keeps the
     58px it has always had. */
  header.sv-top:has(.sv-app-search){--sv-app-h:calc(6px + 58px + 8px);min-height:var(--sv-app-h)}
  .sv-app-search{display:flex;justify-content:center;flex:1 1 auto;min-width:0}
  .sv-app-search .sv-column{width:100%;max-width:640px;margin:0}
  .sv-app-search #sv-ask .sv-searchrow{padding:0 18px;min-height:54px;transition:min-height var(--sv-t-hover) var(--sv-ease),box-shadow 200ms var(--sv-ease),border-color 200ms var(--sv-ease)}
  /* AFTER 80px OF SCROLL the bar's vertical padding halves (6/8 -> 3/4) and
     the row's min-height goes 54 -> 44. THE ROW PAINTS 48, NOT 44, and the
     token says why: the trail's Photo pill and the inline Search are 44px tap
     targets inside a 1px hairline (46), and the row adds its own hairline on
     top (48). A 44px painted row would mean a control under the tap floor
     --sv-tap pins. The class is toggled at every width by one rAF-coalesced
     scroll listener; only these rules act on it. */
  header.sv-top{transition:min-height var(--sv-t-hover) var(--sv-ease),padding-top var(--sv-t-hover) var(--sv-ease),padding-bottom var(--sv-t-hover) var(--sv-ease)}
  header.sv-top.compact{padding-top:3px;padding-bottom:4px}
  header.sv-top.compact:has(.sv-app-search){--sv-app-h:calc(3px + 48px + 4px)}
  header.sv-top.compact .sv-app-search #sv-ask .sv-searchrow{min-height:44px}
}

/* EVERY CONTENT IMAGE FADES IN. The well it sits in (.sv-figure, .sv-tile) is
   the skeleton beneath it, so a slow photograph shows the well rather than a
   hole. @starting-style is the whole mechanism: a browser without it renders
   the image at opacity 1 and no script is involved. */
.sv-figure img,.sv-tile img{opacity:1;transition:opacity var(--sv-t-hover) var(--sv-ease)}
@starting-style{.sv-figure img,.sv-tile img{opacity:0}}

/* PRESSED FEEDBACK on the things that DO something. The bundle already
   presses `.sv-btn`, so that is not restated; these are the ones the system
   had not pressed yet. Never on a printed fact — no .sv-meta, .sv-facts or
   .sv-says in the selector.

   `transition` IS A SHORTHAND, which is why the drawer item names all three
   properties in ONE declaration instead of taking the press rule and the
   hover rule below: the second of two shorthands wins outright and the first
   one's property is silently dropped. */
.sv-platecard,.sv-contact>a,.sv-contact>button,.sv-contact>span{transition:transform var(--sv-t-hover) var(--sv-ease)}
.sv-drawer a{transition:transform var(--sv-t-hover) var(--sv-ease),text-underline-offset var(--sv-t-hover) var(--sv-ease),color var(--sv-t-hover) var(--sv-ease);text-underline-offset:3px}
.sv-platecard:active,.sv-drawer a:active,.sv-contact>a:active,.sv-contact>button:active,.sv-contact>span:active{transform:scale(var(--sv-press-scale))}

/* SECTION REVEAL, armed by the script that reveals it: `sv-motion` on <html>
   is added by the same code that adds `.in`, so a reader with no script and a
   browser with no IntersectionObserver both meet every section visible. */
.sv-motion .sv-reveal{opacity:0;transform:translateY(8px);transition:opacity var(--sv-t-expand) var(--sv-ease),transform var(--sv-t-expand) var(--sv-ease)}
.sv-motion .sv-reveal.in{opacity:1;transform:none}

/* LINK HOVER: the underline steps away from the word and the word goes to
   ink. The offset is the bundle's own resting 3px, named here so there is
   something to transition from. */
.sv-link{text-underline-offset:3px;transition:text-underline-offset var(--sv-t-hover) var(--sv-ease),color var(--sv-t-hover) var(--sv-ease)}
.sv-link:hover,.sv-drawer a:hover{text-underline-offset:5px;color:var(--sv-ink)}

/* A COMPOSITION OF TWO IS NOT A CONTACT SHEET. `.sv-grid.c3` opens to four
   columns past 900 (design_system/patterns/desktop.css), which is right for a
   run of thumbnails and leaves a mosaic's fourth column empty. A real
   modifier, three at every width; restated inside the wide query because this
   layer is spliced BEFORE that pass and would otherwise lose on order. */
.sv-grid.mosaic{grid-template-columns:repeat(3,minmax(0,1fr))}

/* THE BAND'S MISSING COLOUR GUARDS. `.sv-band` already recolours .sv-prose,
   .sv-meta, .sv-vow, .sv-micro-t and the three heading steps; `.sv-step .t`
   is --sv-ink and `.sv-n` is --sv-action-primary, and a numbered promise in
   either is unreadable on --sv-band. Written in the design system's own
   idiom, here rather than in the vendored tree, and logged as a finding. */
.sv-band .sv-step .t{color:var(--sv-on-band)}
.sv-band .sv-n{color:var(--sv-on-band-prose)}

/* AND NONE OF IT FOR A READER WHO ASKED FOR LESS. Every property above is
   turned OFF rather than shortened. The bundle's own global step — 1ms
   !important on every duration, under a comment calling reduced motion "the
   design, for the reader who asked" — is left exactly alone; these rules sit
   under it, not over it. */
@media (prefers-reduced-motion:reduce){
  .sv-drawer{animation:none}
  #sv-drawer + .sv-scrim{animation:none}
  .sv-figure img,.sv-tile img{transition:none}
  .sv-platecard,.sv-drawer a,.sv-contact>a,.sv-contact>button,.sv-contact>span{transition:none}
  .sv-platecard:active,.sv-drawer a:active,.sv-contact>a:active,.sv-contact>button:active,.sv-contact>span:active{transform:none}
  header.sv-top{transition:none}
  .sv-app-search #sv-ask .sv-searchrow{transition:none}
  .sv-motion .sv-reveal{opacity:1;transform:none;transition:none}
  .sv-link{transition:none}
  .sv-link:hover,.sv-drawer a:hover{text-underline-offset:3px}
}

@media (min-width:900px){
  .sv-grid.mosaic{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* 15 Sep 2026 (s1b): plates — catalogue crops only, beside workspace v9.
   The frame sets the height; even a small crop fills it edge to edge.
   Keep workspace's square contain plates and the existing DS radii. */
.sv-figure.sv-catplate,.sv-tile.sv-catplate{aspect-ratio:4/3;width:100%;height:auto}
.sv-figure.sv-catplate>img,.sv-tile.sv-catplate>img{position:absolute;inset:0;display:block;width:100%;height:100%;max-height:none;object-fit:cover;object-position:center}
.sv-source-plate{margin:16px 24px}
.sv-source-plate figcaption{margin-top:8px}
.sv-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:16px}
.sv-catalog-grid .sv-catalog{width:auto;min-width:0}
.sv-product-identity>.sv-catplate{width:88px;flex:none}

/* Workspace v9 — 14 Sep 2026. Existing tokens and radii are unchanged. */
.sv-wrap.sv-v9-shell{padding-bottom:88px}
.sv-v9-shell .sv-top{gap:8px}
.sv-v9-shell .sv-v9-ask{margin-left:auto;min-width:44px}
.sv-v9-band{background:#1A1A1A;color:var(--sv-on-band);padding:8px 16px;margin:12px 0;font-size:var(--sv-meta-size);line-height:1.25}
.sv-v9-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:24px}
.sv-v9-card{min-width:0;border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);overflow:hidden;background:var(--sv-card)}
.sv-v9-card>a{display:block;color:var(--sv-ink);text-decoration:none;padding:12px;overflow-wrap:anywhere}
.sv-v9-card h2,.sv-v9-card h3{margin:12px 0 8px}
.sv-v9-card-plate,.sv-v9-hero,.sv-v9-row-plate{position:relative;overflow:hidden;background:var(--sv-well);border-radius:var(--sv-r-card)}
.sv-v9-card-plate,.sv-v9-hero{aspect-ratio:1/1}
.sv-v9-card-plate img,.sv-v9-hero img,.sv-v9-row-plate img{display:block;width:100%;height:100%;object-fit:contain}
.sv-v9-group{margin-top:24px}
.sv-v9-filters{display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin:16px 0}
.sv-v9-filters .sv-seg{display:flex;flex-wrap:wrap;max-width:100%;width:auto;gap:4px}
.sv-v9-filters .sv-seg>a{min-height:44px;white-space:normal;line-height:1.3;padding:8px 12px;flex:none}
.sv-v9-new,.sv-v9-edit{margin:16px 0}
.sv-v9-new summary,.sv-v9-edit summary{cursor:pointer;min-height:44px;padding:10px 0}
.sv-v9-new form,.sv-v9-edit form{display:grid;gap:8px;margin:8px 0}
.sv-v9-wide input,.sv-v9-wide select,.sv-v9-wide textarea{min-width:0;max-width:100%;width:100%;box-sizing:border-box}
.sv-v9-item-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:24px}
.sv-v9-facts{min-width:0;overflow-wrap:anywhere}
.sv-v9-facts .sv-kv{flex-wrap:wrap;gap:8px}
.sv-v9-facts .sv-kv .v{min-width:0;overflow-wrap:anywhere}
.sv-v9-facts .sv-sticky{position:sticky;bottom:64px}
.sv-v9-tree{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.sv-v9-tree .sv-chip{max-width:100%;white-space:normal}
.sv-v9-tree button{min-height:44px}
.sv-v9-marks{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0 20px}
.sv-v9-marks button{min-height:44px;cursor:pointer}
.sv-v9-option{padding:16px 0;border-top:1px solid var(--sv-border-soft)}
.sv-v9-option h3{margin:0 0 8px}
.sv-v9-bottom{position:fixed;inset:auto 0 0;z-index:7;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));background:var(--sv-card);border-top:1px solid var(--sv-border-soft);padding-bottom:env(safe-area-inset-bottom,0px)}
.sv-v9-bottom>a{display:flex;align-items:center;justify-content:center;min-height:60px;padding:8px 4px;text-align:center;color:var(--sv-quiet);font-size:var(--sv-meta-size);line-height:1.25;text-decoration:none}
.sv-v9-bottom>a[aria-current]{color:var(--sv-ink);font-weight:var(--sv-w-semi);box-shadow:inset 0 2px var(--sv-action-primary)}
.sv-v9-row>a{display:grid;grid-template-columns:72px minmax(0,1fr);gap:12px;align-items:center;color:var(--sv-ink);text-decoration:none;padding:8px 0;border-bottom:1px solid var(--sv-border-soft)}
.sv-v9-row-plate{width:72px;height:72px;grid-row:span 2}
.sv-v9-row-plate .sv-slot svg{display:none}
.sv-v9-row-copy{min-width:0}
.sv-v9-row-copy h3,.sv-v9-row-copy p{margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sv-v9-row>a>.sv-chip{justify-self:start}
.sv-v9-project-head{display:flex;align-items:center;justify-content:space-between;gap:16px}
.sv-v9-project-head h1{min-width:0;overflow-wrap:anywhere}
.sv-v9-project-list .sv-v9-room>h2{font-size:var(--sv-body-size)}
@media (min-width:900px){
  .sv-wrap.sv-v9-shell{padding-bottom:0}
  .sv-wrap.sv-v9-wide{max-width:1120px;padding-top:24px}
  .sv-v9-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
  .sv-v9-item-layout{grid-template-columns:minmax(0,7fr) minmax(0,5fr);align-items:start}
  .sv-v9-item-layout>.sv-v9-hero{position:sticky;top:24px}
  .sv-v9-facts .sv-sticky{position:sticky;bottom:0;justify-content:flex-start}
  .sv-v9-bottom{display:none}
  .sv-wrap.sv-v9-project-list{padding-top:8px}
  .sv-v9-project-list .sv-top{margin-bottom:0;min-height:44px;padding-bottom:0}
  .sv-v9-project-list .sv-locbox{margin-bottom:0}
  .sv-v9-project-list .sv-display{margin:0;line-height:1.1}
  .sv-v9-project-list .sv-v9-band{margin:8px 0;padding:6px 16px}
  .sv-v9-project-list .sv-v9-room>h2{margin:0;line-height:24px}
  .sv-v9-project-list .sv-v9-room+.sv-v9-room{margin-top:16px}
  .sv-v9-row>a{grid-template-columns:72px minmax(0,1fr) auto;height:72px;padding:0;border-bottom:0}
  .sv-v9-row-plate{grid-row:auto}
}

/* Workspace v9 — 14 Sep 2026, V3: share controls and the client band. */
.sv-v9-share-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:32px}
.sv-v9-share-layout>section,.sv-v9-share-controls,.sv-v9-client-item>div{min-width:0;overflow-wrap:anywhere}
.sv-v9-share-controls .sv-seg{display:flex;flex-wrap:wrap;width:auto;max-width:100%}
.sv-v9-share-controls .sv-seg>a{min-height:44px;white-space:normal;line-height:1.3;padding:10px 12px;flex:none}
.sv-v9-share-controls form{display:grid;gap:8px;margin:16px 0}
.sv-v9-share-switch{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;border-bottom:1px solid var(--sv-border-soft);padding:8px 0}
.sv-v9-share-switch p{margin:0}
.sv-v9-share-link{overflow-wrap:anywhere}
.sv-v9-share-record{padding:16px 0;border-bottom:1px solid var(--sv-border-soft);overflow-wrap:anywhere}
.sv-v9-public{max-width:1000px;margin:0 auto;padding:24px 20px;color:var(--sv-ink);font-family:var(--sv-font)}
.sv-v9-client{min-width:0;overflow-wrap:anywhere}
.sv-v9-client-head{background:#1A1A1A;color:var(--sv-on-band);padding:16px;margin-bottom:24px}
.sv-v9-client-head .sv-v9-band{margin:0;padding:0}
.sv-v9-client-head h1,.sv-v9-client-head h2{color:inherit;margin:12px 0;font-size:var(--sv-title);line-height:1.2}
.sv-v9-client-head p{margin:0}
.sv-v9-client-item{display:grid;grid-template-columns:minmax(0,1fr);gap:20px;padding:24px 0;border-bottom:1px solid var(--sv-border-soft)}
.sv-v9-client-item h3{margin-top:0}
.sv-v9-client-plate{aspect-ratio:1/1;position:relative;overflow:hidden;background:var(--sv-well);border-radius:var(--sv-r-card)}
.sv-v9-client-plate img{display:block;width:100%;height:100%;object-fit:contain}
.sv-v9-client-option{padding:12px 0;border-top:1px solid var(--sv-border-soft)}
.sv-v9-client .sv-kv{flex-wrap:wrap;gap:8px}
.sv-v9-client .sv-kv .v{min-width:0;overflow-wrap:anywhere}
.sv-v9-client .sv-opt{min-width:0}
.sv-v9-client-decision fieldset{border:0;padding:0;margin:16px 0;min-width:0;display:grid;gap:8px}
.sv-v9-client-decision legend{font-size:var(--sv-meta-size);margin-bottom:8px}
.sv-v9-client-decision input,.sv-v9-client-decision textarea{box-sizing:border-box;min-width:0;max-width:100%;width:100%;font:inherit;color:var(--sv-ink);background:var(--sv-card);border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-input);padding:10px}
.sv-v9-client-decision input{min-height:44px}
@media(min-width:900px){
  .sv-v9-share-layout{grid-template-columns:minmax(0,4fr) minmax(0,6fr)}
  .sv-v9-public .sv-v9-client-item{grid-template-columns:minmax(0,4fr) minmax(0,6fr)}
}

/* Workspace v9 — 14 Sep 2026, V4: promises and the following-up table. */
.sv-v9-follow-columns{display:none}
.sv-v9-follow-row{display:grid;grid-template-columns:72px minmax(0,1fr);gap:12px;padding:20px 0;border-bottom:1px solid var(--sv-border-soft)}
.sv-v9-follow-row>div,.sv-v9-follow-controls,.sv-v9-follow-line{min-width:0;overflow-wrap:anywhere}
.sv-v9-follow-row .sv-v9-row-plate{grid-row:auto}
.sv-v9-follow-row h2{margin:0;font-size:var(--sv-body-size)}
.sv-v9-follow-row p{margin:4px 0}
.sv-v9-follow-promise,.sv-v9-follow-timing,.sv-v9-follow-actions{grid-column:1/-1}
.sv-v9-nudge{grid-column:1/-1;justify-self:start;align-self:start}
.sv-v9-follow-actions,.sv-v9-outcomes{display:flex;flex-wrap:wrap;gap:8px}
.sv-v9-follow-actions form{margin:0}
.sv-v9-following .sv-btn,.sv-v9-follow-controls .sv-btn,.sv-v9-follow-line .sv-btn{flex:none;min-height:44px;max-width:100%;white-space:normal;height:auto}
.sv-v9-follow-controls{display:grid;gap:8px;margin-top:16px}
.sv-v9-follow-controls p,.sv-v9-follow-controls form{margin:0}
.sv-v9-follow-controls summary{min-height:44px;cursor:pointer;padding:10px 0}
.sv-v9-follow-controls details form{display:grid;gap:8px}
.sv-v9-follow-line{padding:12px 0;border-bottom:1px solid var(--sv-border-soft)}
@media(min-width:900px){
  .sv-v9-follow-columns,.sv-v9-follow-row{display:grid;grid-template-columns:72px minmax(0,2fr) minmax(0,2fr) minmax(0,1fr) 88px;gap:16px}
  .sv-v9-follow-columns{padding:8px 0;border-bottom:1px solid var(--sv-border-soft)}
  .sv-v9-follow-promise,.sv-v9-follow-timing,.sv-v9-nudge{grid-column:auto}
  .sv-v9-follow-actions{grid-column:2/-1}
}

/* Workspace v9 — 15 Sep 2026 (V5). Supplier cards and private controls. */
.sv-v9-supplier{display:grid;grid-template-columns:72px minmax(0,1fr);align-items:start;gap:12px;padding:16px;border:1px solid var(--sv-border-soft);border-radius:var(--sv-r-card);min-width:0}
.sv-v9-supplier .sv-v9-row-plate{grid-row:auto}
.sv-v9-supplier h2{font-size:var(--sv-section);margin:0 0 8px}
.sv-v9-supplier p{margin:8px 0}
.sv-v9-suppliers p,.sv-v9-suppliers h1,.sv-v9-suppliers h2,.sv-v9-suppliers h3,.sv-v9-suppliers li{overflow-wrap:anywhere}
.sv-v9-supplier-chips{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:8px 0}
.sv-v9-supplier-chips form{margin:0;max-width:100%}
.sv-v9-suppliers .sv-chip{white-space:normal;max-width:100%;height:auto;line-height:1.3}
.sv-v9-suppliers button.sv-chip,.sv-v9-suppliers a.sv-chip{background:transparent;border:1px solid var(--sv-ink);color:var(--sv-ink);border-radius:var(--sv-r-pill);min-height:44px;padding:8px 12px;font:inherit;cursor:pointer;text-decoration:none}
.sv-v9-suppliers button.sv-chip[aria-pressed=true]{box-shadow:inset 0 0 0 1px var(--sv-ink);font-weight:600}
.sv-v9-supplier-filters{display:flex;flex-wrap:wrap;gap:12px;align-items:end;margin:16px 0}
.sv-v9-supplier-filters label{display:grid;gap:4px;flex:1 1 180px;min-width:0}
.sv-v9-suppliers input,.sv-v9-suppliers select{font:inherit;min-height:44px;width:100%;min-width:0;max-width:100%;box-sizing:border-box}
.sv-v9-suppliers .sv-btn{min-height:44px;height:auto;white-space:normal;flex:none}
.sv-v9-suppliers section{margin-top:24px}

/* 15 Sep 2026 — workspace v9, V7 visual-weight sweep.
   One filled action per action row; the rest keep the DS ink/pill shape.
   Legacy Saved uses the same treatment only inside the lit shell. */
.sv-v9-shell main a.sv-chip,.sv-v9-shell main button.sv-chip,.sv-v9-client a.sv-chip,.sv-v9-client button.sv-chip{display:inline-flex;align-items:center;justify-content:center;flex:none;justify-self:start;width:auto;min-height:44px;height:auto;max-width:100%;padding:8px 12px;box-sizing:border-box;white-space:normal;line-height:1.3;background:transparent;color:var(--sv-ink);border:1px solid var(--sv-ink);border-radius:var(--sv-r-pill);font:inherit;cursor:pointer;text-decoration:none}
.sv-v9-shell main button.sv-chip[aria-pressed=true]{box-shadow:inset 0 0 0 1px var(--sv-ink);font-weight:600}
.sv-v9-shell main .sv-btn,.sv-v9-client .sv-btn{flex:none;justify-self:start;width:auto;max-width:100%;min-height:44px;height:auto;white-space:normal}
.sv-v9-shell .sv-saved .sv-btn{width:auto;flex:none}
.sv-v9-shell .sv-saved .sv-contact>a,.sv-v9-shell .sv-saved .sv-contact>button,.sv-v9-shell .sv-saved .sv-linky{background:transparent;color:var(--sv-ink);border:1px solid var(--sv-ink);border-radius:var(--sv-r-pill);min-height:44px;padding:8px 12px;width:auto;flex:none;white-space:normal;cursor:pointer}
/* The system gives `.sv-mynote .k .sv-link` a negative top margin so a 44px
   tap target does not inflate the label row (patterns/record.css:168). With
   the note editor OPEN the editor wraps onto its own flex line and that pull
   rides the summary back up over "Your note", which axe reports as bgOverlap
   and a reader sees as a label under a link. Inside the lit shell only: drop
   the pull once the editor is open. The closed row is unchanged, and so is
   every dark page — this selector cannot match outside `.sv-v9-shell`. */
.sv-v9-shell .sv-saved .sv-mynote .k details[open]>summary{margin-top:0}
/* 15 Sep 2026 (s2) — workspace v9: Saved editor on the legacy shell.
   Extend V7's open-editor correction outside the lit shell. The closed
   disclosure keeps its negative margins and the label row stays 22px. */
.sv-saved .sv-mynote .k details[open]>summary{margin-top:0}
@media(min-width:900px){
  .sv-v9-suppliers .sv-v9-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* 15 Sep 2026 (s4) — workspace v9: the saved supplier's note editor.
   Preserve the full-width open editor and the s2 label-overlap correction. */
.sv-v9-suppliers .sv-mynote{overflow-wrap:anywhere}
.sv-v9-suppliers .sv-mynote .k{flex-wrap:wrap}
.sv-v9-suppliers .sv-notebox[open]{flex:1 1 100%;min-width:0}
.sv-v9-suppliers .sv-notebox[open]>summary{margin-top:0}
.sv-v9-suppliers .sv-note{margin-top:9px}
.sv-v9-suppliers .sv-note textarea{margin-bottom:8px}

/* 15 Sep 2026 (s5) — workspace v9: legacy signed-in action weight.
   Keep the frozen markup. Existing route/form hooks scope the sweep; the
   vendor console and token reply form have no workspace shell. Do not set
   display on the reply buttons: their selected mode owns visibility. */
.sv-v9-shell:not(.sv-v9-wide) main .sv-btn.ghost,
.sv-v9-shell:not(.sv-v9-wide) main .sv-btn.secondary,
.sv-v9-shell .sv-private-contacts details[open]>summary.sv-btn,
.sv-v9-shell .sv-contact-form>button[formaction$="/replies/draft"],
.sv-v9-shell .sv-card.sv-private-contacts>details~details .sv-btn,
.sv-v9-shell .sv-coverage-row .sv-btn,
.sv-v9-shell .sv-me-rail .sv-door,
.sv-v9-shell .sv-results .sv-contactline a.chip,
.sv-listing .sv-btn.ghost,
.sv-listing .sv-linky,
.sv-business-switch .sv-btn,
.sv-wrap main:has(.sv-ask-form,.sv-thread) .sv-btn.ghost{
  background:transparent;color:var(--sv-ink);border:1px solid var(--sv-ink);
  border-radius:var(--sv-r-pill);min-height:44px;height:auto;padding:8px 12px;
  flex:none;justify-self:start;width:auto;max-width:100%;box-sizing:border-box;
  white-space:normal;line-height:1.3;cursor:pointer;text-decoration:none;
}
.sv-listing .sv-btn,
.sv-business-switch .sv-btn,
.sv-wrap main:has(.sv-ask-form,.sv-thread) .sv-btn{
  flex:none;justify-self:start;width:auto;max-width:100%;min-height:44px;
  height:auto;box-sizing:border-box;white-space:normal;
}
/* The outcome sheet's later inline rule otherwise makes every action grow.
   Coverage contains several independent saves; Add city keeps the accent. */
.sv-v9-shell .sv-outcome .sv-outcome-actions .sv-btn{flex:none;width:auto}
.sv-v9-shell .sv-card.sv-coverage{display:block}
.sv-v9-shell .sv-coverage-row{min-inline-size:0}
/* Record contact tiles retain their order and missing slots. Call is the
   supplier card's primary; a private contact's editor keeps its own Save. */
.sv-v9-shell .sv-results .sv-contact>a,
.sv-v9-shell .sv-results .sv-contact>button,
.sv-v9-shell .sv-private-contact .sv-contact>a,
.sv-v9-shell .sv-private-contact .sv-contact>button{
  background:transparent;color:var(--sv-ink);border:1px solid var(--sv-ink);
  border-radius:var(--sv-r-pill);min-height:44px;height:auto;padding:8px;
  white-space:normal;box-sizing:border-box;
}
.sv-v9-shell .sv-results .sv-contact>.primary{
  background:var(--sv-action-primary);border-color:var(--sv-action-primary);
  color:var(--sv-on-cobalt);
}
/* Index cards have no button classes: the next step is the primary and the
   activity link is a chip. Ordinary source/question links stay text links. */
.sv-v9-shell .sv-enquiry-index-links>a{
  display:inline-flex;align-items:center;justify-content:center;
  background:transparent;color:var(--sv-ink);border:1px solid var(--sv-ink);
  border-radius:var(--sv-r-pill);min-height:44px;height:auto;padding:8px 12px;
  flex:none;width:auto;max-width:100%;box-sizing:border-box;
  white-space:normal;line-height:1.3;text-decoration:none;
}
.sv-v9-shell .sv-enquiry-index-links>a:first-child{
  background:var(--sv-action-primary);border-color:var(--sv-action-primary);
  color:var(--sv-on-cobalt);
}
.sv-listing .sv-removes{flex-wrap:wrap;gap:8px}
.sv-listing .sv-removes form{flex:none;margin:0}
/* ==== design_system/patterns/desktop.css ==== */
/* THE DESKTOP PASS. Every rule is behind one media query and ADDS ONLY: the
   phone must be pixel-identical afterwards. The type ramp already steps in
   tokens/typography.css — what is here is layout.
   NO FIXED-WIDTH CANVASES: this is a widening, not a second design. */
@media (min-width:900px){
  .sv-screen{max-width:var(--sv-wide)}
  /* THE FOLD IS A QUESTION, NOT A BANNER. Stretched to 760px the field is a
     slot and the button floats in the middle of nowhere. */
  .sv-column{width:100%;max-width:560px;margin-left:auto;margin-right:auto}
  /* THE 2-UP. align-items:start so a card with two reason lines does not
     stretch the one beside it; minmax(0,1fr) so a long unbroken shop name
     cannot blow the column out. */
  .sv-stack.loose{display:grid;align-items:start;grid-template-columns:repeat(2,minmax(0,1fr))}
  /* PROSE KEEPS A MEASURE — 66ch is where a line stops needing a finger to
     find its way back. This is the half of a responsive pass usually
     forgotten, and the half a reader actually feels. */
  .sv-sect > .sv-prose,.sv-sect > .sv-meta,.sv-stack > .sv-prose,.sv-titles > .sv-prose{max-width:66ch}
  .sv-state .sv-measure{max-width:60ch}
  .sv-feedback{max-width:520px}
  /* THE BOARD — two records side by side, which is what a desktop actually
     buys: the evidence beside the thing it is evidence about, instead of one
     scroll below it. `align-items:start` so a long ledger does not stretch
     the media column. */
  .sv-board{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:32px;align-items:start}
  .sv-board.rail{grid-template-columns:minmax(0,1fr) 300px}
  .sv-board.even{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* A SIDE RAIL IS NOT A SIDEBAR. It holds the evidence for the record in the
     main column and scrolls with it; it never becomes navigation. */
  .sv-rail{position:sticky;top:24px;display:flex;flex-direction:column;gap:14px}
  /* The desktop shell widens past the reading column ONLY where two columns
     are the point. Prose still caps itself at 66ch inside them. */
  .sv-screen.wide-board{max-width:1120px}
  .sv-plates{grid-template-columns:repeat(4,minmax(0,1fr))}
  .sv-grid.c3{grid-template-columns:repeat(4,minmax(0,1fr))}
  /* The strip's position and rule are no longer a desktop difference — since
     24 Aug it is in flow and underlined at every width. What IS still a
     desktop difference: the anchors stop stretching and take their own
     width. */
  .sv-tabs a{flex:none;padding:10px 16px}
  .sv-sticky{position:static;margin-top:24px;flex-direction:row;align-items:center;border-top:0;padding-left:0;padding-right:0}
  .sv-sticky .sv-btn{width:auto}
  .sv-confirm .box{max-width:420px}
}
/* 320 CSS PIXELS, AND NOTHING BELOW IT SCROLLS SIDEWAYS. Ordinary content
   reflows at 320 without page-level horizontal scrolling: the gutters step
   down, the two-up grids collapse, and nothing in the system carries a fixed
   width for a page to be wider than. Tested, not assumed. */
@media (max-width:359px){
  :root{--sv-gutter:16px;--sv-gutter-wide:18px}
  .sv-stats{grid-template-columns:1fr}
  .sv-stat + .sv-stat{border-left:0;border-top:1px solid var(--sv-border-soft)}
  .sv-kv,.sv-secthead,.sv-gridcap,.sv-ev > .prop,.sv-ledger > div{flex-direction:column;align-items:flex-start;gap:3px}
  .sv-kv .v,.sv-ledger dd,.sv-gridcap > :last-child{text-align:left}
  .sv-coverage{flex-direction:column;align-items:flex-start;gap:8px}
  .sv-coverage .meta{text-align:left}
  .sv-seg{width:100%}
  .sv-seg > *{flex:1;justify-content:center;padding:0 10px}
  .sv-quiet{border-radius:var(--sv-r-card);align-items:flex-start;flex-direction:column;gap:8px}
}
