/* ─────────────────────────────────────────────────────────────
   Cotrugli — Colors & Type (design tokens)
   One brand. Two themes. Components reference tokens, never raw hex.
   Dark is default. Light is the inverted alternative. The ladder is the
   approved Ink and Paper pair (brand revision 2.1): no hue tint on the
   neutrals, and every contrast ratio here is measured in
   test/cotrugli_web/design/.

   THE one token sheet: every surface (the gate at /, auth, marketing,
   the console) loads this file through the ds_root layout. There is no
   second copy anywhere; if a value changes, it changes here.
   @font-face paths point at /fonts/ (served from priv/static/fonts by
   the Phoenix endpoint).
   ───────────────────────────────────────────────────────────── */

/* ─── Brand fonts — variable, self-hosted ─── */
@font-face {
  font-family: "Inter Tight";
  src: url("/fonts/InterTight-VariableFont_wght-3cdfc5dab0621f61ef1f9ace2ac1a3a9.ttf?vsn=d") format("truetype-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter Tight";
  src: url("/fonts/InterTight-Italic-VariableFont_wght-b943a16806dfc4772ed712a38b79a4a6.ttf?vsn=d") format("truetype-variations");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/JetBrainsMono-VariableFont_wght-514bb1da4b5d654b134572435ffcfea7.ttf?vsn=d") format("truetype-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/JetBrainsMono-Italic-VariableFont_wght-85cc70f51b722cc2618ab156b5bd5e5d.ttf?vsn=d") format("truetype-variations");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Source Serif 4";
  src: url("/fonts/SourceSerif4-Variable-c0afa3622c49cdf60735c0b4227d7191.ttf?vsn=d") format("truetype-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

/* .light .mkt-invert: the marketing inversion band. A .mkt-invert subtree
   carries the OPPOSITE theme's tokens; custom properties set directly on
   the band element beat the ones inherited from <html>, so the flip is
   total. Selector lists only — the values are the one palette, unchanged. */
:root,
.light .mkt-invert {
  /* ── Type families ── */
  --font-sans: "Inter Tight", "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  /* The serif carries the signature and welcome lines only (DESIGN.md, Typography).
     --font-sans stays the interface family and is never redirected to it. */
  --font-serif: "Source Serif 4", Georgia, "Times New Roman", serif;

  /* ── Type roles (font shorthand: weight size/line-height family) ── */
  --t-display:    500 48px/1.05 var(--font-sans);  /* tracking -0.03em  */
  --t-h1:         500 32px/1.10 var(--font-sans);  /* tracking -0.025em */
  --t-h2:         500 22px/1.20 var(--font-sans);  /* tracking -0.02em  */
  --t-h3:         500 18px/1.30 var(--font-sans);  /* tracking -0.015em */
  --t-body:       400 15px/1.55 var(--font-sans);
  --t-body-sm:    400 13px/1.50 var(--font-sans);
  /* One step up from body, for prose that is SPOKEN rather than tabulated:
     the room's bubbles. 15px is the register's size - dense, scannable, a
     column of rows - and a conversation set in it reads as a log of one. */
  --t-body-lg:    400 16px/1.55 var(--font-sans);
  --t-mono-data:  500 13px/1.50 var(--font-mono);
  --t-mono-label: 500 10px/1.40 var(--font-mono);  /* 0.12em UPPER     */
  --t-micro:      400 11px/1.40 var(--font-sans);
  /* The composer that IS the page. On Practice's front door the writing box
     is not a control sitting on a surface, it is the surface's whole purpose
     - the greeting says what is waiting, the agent says it in a sentence, and
     then there is nothing to do but write. So it takes a size of its own:
     one step above the room's spoken prose, at body's weight rather than a
     heading's, because it is a place to write and not a thing to read. This
     role belongs to that box and to nothing else. */
  --t-compose:    400 18px/1.55 var(--font-sans);
  /* The touch body role. DESIGN.md has documented body-touch since the scale
     was written; it never had a token. 16px is not a taste here, it is the
     floor under which iOS scales the page on focus - and this shell cannot
     pan back - so every field takes it on a coarse pointer. */
  --t-body-touch: 400 16px/1.5 var(--font-sans);
  /* The one serif role inside product, for a greeting or welcome line
     (Client "Let's catch up", Practice front door), never a heading,
     control or dense text. Consumers apply `font: var(--t-welcome)` plus
     `letter-spacing: var(--tr-welcome)`; F2d migrates the first consumer. */
  --t-welcome:    500 28px/1.2 var(--font-serif);
  /* The greeting's phone step: same role, one size down under 559.98px. */
  --t-welcome-sm: 500 24px/1.2 var(--font-serif);

  /* The presentation ramp for public and investor pages (DESIGN.md, Brand
     and product scales): landing statements, deck covers and section
     openings. Product surfaces never take these - the product ladder
     above is the one inventory a console or Practice screen reaches for. */
  --t-brand-display: 500 clamp(40px, 6.5vw, 80px)/1.08 var(--font-serif);
  --t-brand-heading: 500 clamp(32px, 4.2vw, 48px)/1.08 var(--font-serif);
  --t-brand-subhead: 550 clamp(24px, 2.8vw, 32px)/1.2 var(--font-sans);
  --t-brand-lead:    400 clamp(20px, 2vw, 24px)/1.4 var(--font-sans);
  --t-brand-body:    400 18px/1.5 var(--font-sans);
  --t-brand-label:   400 16px/1.4 var(--font-sans);
  --t-brand-data:    400 16px/1.5 var(--font-mono);

  /* ── Tracking helpers (apply alongside the role) ── */
  --tr-display: -0.03em;
  --tr-h1:      -0.025em;
  --tr-h2:      -0.02em;
  --tr-h3:      -0.015em;
  --tr-welcome: -0.01em;
  --tr-brand-display: -0.025em;
  --tr-brand-heading: -0.02em;
  --tr-label:    0.12em;

  /* ── Spacing scale (4px base) ── */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;
  --sp-20: 80px;

  /* ── Corner radius (tight — system feeling) ── */
  --r-1: 2px;
  --r-2: 4px;
  --r-3: 6px;
  --r-4: 8px;

  /* ── Layout ── */
  --topbar-h: 56px;   /* the console topbar; the chat drawer hangs beneath it */
  --control-h: 32px;  /* one height for every chrome control (topbar, filters) */
  --tabbar-h: 56px;   /* the phone's bottom tab bar, above the home indicator */

  /* ── Safe area — the notch, the island, the home indicator ──
     ds_root opts into viewport-fit=cover and the manifest is standalone, so
     these are real on every installed console. Named once here: a component
     that writes env() inline is a component that will miss one of the four. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);

  /* ── Touch ──
     44px is the floor, which is what marketing.css's own coarse-pointer
     rules already use; the console shipped 40 and two doctrines in one
     system is one too many. --tap-dense is the ONE exception: a control
     inside a scrolling register row, where 44 would double the row's height.
     Never for a destructive verb. --tap-slop expands an inline word-target
     through an ::after inset without growing its box. */
  --tap-min:   44px;
  --tap-dense: 36px;
  --tap-slop:  10px;   /* 16px of line + 2x10 = the --tap-dense floor */

  /* ── Gutters — two shared left edges ──
     Panels (head rows, table edge cells) sit on one; card interiors on the
     other. A head 2px off its first column is the wobble that reads homemade.
     --pad-view is the page gutter: a token so the phone step moves it once
     instead of every surface overriding it. */
  --pad-panel: 16px;  /* panel heads, register edge cells */
  --pad-card:  20px;  /* card interiors */
  --pad-view:  32px;  /* page gutter: .page-header, .view-body */

  /* ── Tracking (two jobs, two tokens) ── */
  --tr-wide: 0.08em;  /* inline chips/badges; --tr-label (0.12em) is 10px labels */

  /* ── Layer scale — the one z-index vocabulary ──
     In-flow surfaces stack at field/raised; sticky headers hold their rows;
     floating chrome climbs drawer → menu → chrome → backdrop → scrim →
     sidebar. If a layer needs a number outside this scale, the layer is
     wrong, not the scale. */
  --z-field: 0;      /* canvas fields, in-flow base layers */
  --z-raised: 1;     /* raised in-flow elements */
  --z-sticky: 2;     /* sticky table headers */
  --z-meter: 6;      /* in-row meter bars */
  --z-nav: 30;       /* the marketing nav pill */
  --z-drawer: 60;    /* the chat panel drawer */
  --z-menu: 70;      /* menus and popovers (user menu, chat history) */
  --z-tip: 90;       /* tooltips */
  --z-chrome: 100;   /* skip link, flash stack */
  --z-backdrop: 200; /* command palette backdrop */
  --z-scrim: 290;    /* mobile sidebar scrim */
  --z-sidebar: 300;  /* mobile sidebar */

  /* ── Motion ──
     Durations are a ramp by element size; easing is a contract:
     movement (transform, size, position) rides --ease-out; pure linear is
     reserved for opacity/color fades and progress loops. Linear movement is
     the single biggest machine-made tell. */
  --easing: linear;            /* fades and color shifts only */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);  /* movement: enter, press, nudge */
  --t-quick: 120ms;   /* press feedback, micro shifts */
  --t-base:  160ms;   /* hovers, small pops */
  --t-menu:  200ms;   /* dropdowns, popovers */
  --t-panel: 280ms;   /* side panels, large surfaces */
  --t-slow:  240ms;
  /* The navigation column's own three. `pt` was "practice transition": the
     column was Practice's and these lived in room.css, an exception to
     the one-sheet rule that survived because only one host read them. Both
     staff hosts wear the column now, so the exception ends here - but the
     PREFIX stays, because renaming is ~80 uses across product.css and
     room.css and a missed one is an unresolved var(), which drops the
     whole declaration without saying so. Read `pt` as "the column", not as
     "Practice". --pt-fast is --t-base's value and --pt-ease is --ease-out's;
     they are held apart because the column's ramp is its own to retune. */
  --pt-fast: 160ms;   /* the column's hovers, its menu opening */
  --pt-move: 220ms;   /* the collapse: width, padding, the phone overlay */
  --pt-ease: cubic-bezier(0.16, 1, 0.3, 1);  /* the collapse rides --ease-out */

  /* ─────────── DARK THEME — default ─────────── */
  /* The surface ladder is the approved Ink ramp (revision 2.1), flat hex -
     no hue tint on the neutrals; depth = one step up the ladder + an
     alpha-white hairline, never a shadow (in-flow). Floating chrome is
     different physics: --surface-overlay + --shadow-overlay. */
  --surface:        #191d26;   /* mono ink — page bg */
  --surface-elev:   #242830;   /* one step off the page: a selected row, a chip, a band, a zebra stripe */
  /* An OBJECT on the page - a card, a panel, a bubble the agent hands you -
     is the step toward the light in BOTH themes (the owner, 2026-09-13: on
     paper the cards sat a tier BELOW the page, so every bubble read as a
     recessed, disabled panel while the empty composer was the one thing
     standing on the paper). In the dark the object step and the off-page
     step are the same tier, so nothing here changes; on paper they part. */
  --surface-card:   #242830;
  --surface-overlay: #2d3038;  /* floating chrome only */
  --surface-rule:   rgba(245, 242, 234, 0.10);  /* hairline — works on every step */
  --line-strong:    rgba(245, 242, 234, 0.28);  /* interactive/emphasized edges */

  /* Ledger-paper ruling inks (marketing field bands, the gate's canvas):
     verticals faintest, horizontals a touch more present. The solid ink is
     read by page-js for the canvas field, where alpha lines multiply into
     nothing; warm, tinted with the paper ink rather than the old hue axis. */
  --field-v:          rgba(245, 242, 234, 0.045);
  --field-h:          rgba(245, 242, 234, 0.07);
  --field-rule-solid: #2d3038;

  /* State overlays: dark-UI hovers LIGHTEN, never darken. */
  --surface-hover:  rgba(245, 242, 234, 0.05);
  --surface-active: rgba(245, 242, 234, 0.08);

  /* ── The conversation's two sides (I16) ──
     The room is the one product surface where two people take turns, and the
     mockup's whole figure/ground is that the operator's bubble is TINTED and
     every agent's is the raised white. --you-tint is one step up the Ink
     ladder (the same value as --surface-elev); --you-rule is the same
     paper-ink hairline as --surface-rule. Dark is its twin one step off the
     page, not a wash of the same grey.
     --shadow-bubble was the room's own named carve-out from the borders-not-
     boxes rule (DESIGN.md 4): 1px of ambient contact, paper only, so a bubble
     read as handed over rather than printed on the page. COT-234b (BR-A2b)
     retired the lift from both consumers (the thread card, the chat bubble):
     DESIGN.md's Elevation rules out shadows outright now, flat and ruled
     surfaces only. The token stays, named and pinned by RoomColumnTest, but
     it is UNCONSUMED until some surface earns a carve-out of its own. */
  --you-tint:       #242830;
  --you-rule:       rgba(245, 242, 234, 0.10);
  --shadow-bubble:  none;

  /* Anna's hue (I16). Beno speaks in the signal ink his marks have always
     worn - a mark IS an agent, which is what the Signal Rule reserves the
     colour for - and a second agent answering under the same roof was the same
     mark with a different letter in it. Deep teal-blue: off the neutral ramp
     entirely and not --success (a state), not --info (a notice), not
     --warning. Computed as a letter on --surface-elev, the darkest ground a
     mark sits on: 7.88:1 dark, 5.22:1 light. */
  --agent-people:    oklch(0.79 0.105 218);

  /* Mira's hue (CS3). The third agent needs a third mark, and it is read
     BESIDE Anna's in the same conversation, so it is a full 67 degrees off
     her teal-blue rather than a neighbour of it: indigo, on the same
     lightness axis and the same chroma discipline. Off the neutral ramp like
     Anna's, and not --success, --info or --warning. Computed as a letter on
     --surface-elev, the darkest ground a mark sits on: 7.48:1 dark,
     5.76:1 light. */
  --agent-seo:       oklch(0.79 0.105 285);

  /* AV-02 avatar fills are a presentation-only token family. The source
     geometry stays fixed; leaf green, terracotta and muted lavender carry
     identity alongside Ink/Paper. These accents never indicate work state. */
  --avatar-beno-lit:   #a2be7d;
  --avatar-beno-shade: #42663f;
  --avatar-beno-flat:  #42663f;
  --avatar-people-lit: #deb09a;
  --avatar-people-shade: #a35e49;
  --avatar-people-flat: #a35e49;
  --avatar-seo-lit:    #c8bdd8;
  --avatar-seo-shade:  #78658e;
  --avatar-seo-flat:   #78658e;
  --avatar-eye:        #ffffff;
  --avatar-highlight:  #ffffff;

  /* The one sanctioned shadow: floating layers only (palette, menus,
     flashes, tooltips), always paired with a hairline border. */
  --shadow-overlay: 0 1px 1px rgba(0, 0, 0, 0.3),
                    0 8px 24px -6px rgba(0, 0, 0, 0.5),
                    0 24px 48px -12px rgba(0, 0, 0, 0.5),
                    inset 0 1px 0 rgba(255, 255, 255, 0.04);

  /* The ground under a cover: the drawer's scrim and the palette's backdrop
     are the same physics, so they are one token. DESIGN.md has named it
     since the palette shipped; the two overlays each hardcoded their own. */
  --scrim:          rgba(0, 0, 0, 0.5);

  /* Human focus is neutral (chartreuse focus would claim the agent's voice). */
  --focus-ring:     #f5f2ea;

  --text:           #f5f2ea;  /* primary text — Paper */
  --text-hover:     #e6e3dc;  /* primary-button hover: one step off paper, never a dim */
  /* The muted ramp holds WCAG AA (4.5:1+) for small text on both --surface
     and --surface-elev; hierarchy below --text-muted leans on size and case,
     not on dropping below readable contrast. Flat hex on the approved
     Ink/Paper ramp, not an oklch retint. */
  --text-muted:     #b7b6b3;  /* de-emphasized */
  --text-subtle:    #9d9d9c;  /* labels, captions */
  --text-faint:     #929292;  /* micro hints */

  /* The Signal Rule (DESIGN.md): Action marks evidenced agent activity or
     authorship. A proposal remains a proposal until the required authority
     and execution exist; selection, navigation, a person's name, a human
     approval button and decoration do not imply agent action. A completed
     record can be neutral with a check and explicit label - it need not
     have a bright fill. */
  --signal:         #c4f000;  /* chartreuse — AGENT ONLY */
  --signal-text:    #c4f000;  /* signal as TEXT on a surface (light theme darkens it) */
  --signal-soft:    #d8f566;  /* hover on signal */
  --signal-ink:     #191d26;  /* text on signal fill */
  /* The true chartreuse, only ever on an Ink ground: a glyph on the dark
     surface, or the recorded mark on an Ink tile. Never a field on Paper -
     that job is --signal (light theme darkens the fill for that reason). */
  --signal-mark:    #c4f000;

  /* Money coming in / money going out, the pair the Reporting trend already
     uses, validated for colour-vision separation and 3:1 on both grounds.
     Text never wears a series colour. */
  --series-in:      #5f8fe2;
  --series-out:     #bb8330;

  --success:        #5fd38a;
  --danger:         #f47c7c;
  --warning:        #f2b233;
  --info:           #7fb2f5;
  /* Danger and info get the same TYPE split as warning and success, for the
     same reason - it is only that dark spends nothing on it, as today: the
     fill is already at-contrast as text, so ink and fill are one value. */
  --danger-text:    #f47c7c;
  --info-text:      #7fb2f5;
  /* Warning set as TYPE rather than as a fill, the --signal-text split for
     the same reason: on paper the fill amber reads 4.49:1 on --surface-elev,
     which is a FAIL at every size a status word is set in. On dark the fill
     is already 11.35:1 as text, so the two are one value here. */
  --warning-text:   #f2b233;
  /* Success set as TYPE, same split, same reason: on paper the fill green
     reads 4.48:1 on --surface-elev, a FAIL at the 10px a badge is set in.
     On dark the fill is already 10.88:1 as text, so the two are one value
     here too. Computed in WarningContrastTest, every badge token, both themes. */
  --success-text:   #5fd38a;
}

/* ─────────── LIGHT THEME — inverted ─────────── */
/* .mkt-invert alone (dark theme): the band renders as paper. Inside a
   .light page the dark block's .light .mkt-invert selector outranks this. */
.light,
.mkt-invert {
  --surface:        #f5f2ea;
  /* The paper band step: one neutral tier off --surface, no hue tint. Band
     architecture on paper leans on the hairline edges elevated bands carry
     (marketing.css), not on this step alone. */
  --surface-elev:   #ebe8e1;
  /* The object step on paper: a warm near-white a clear tier ABOVE the
     page, under the floating chrome's pure white. A card is a sheet laid
     on the desk, not a patch worn into it. Every text ink clears 4.5:1 on
     it by a wider margin than on --surface-elev (InkPaperRampTest). */
  --surface-card:   #fcfbf8;
  --surface-overlay: #ffffff;  /* floating chrome only */
  --surface-rule:   rgba(25, 29, 38, 0.12);
  --line-strong:    rgba(25, 29, 38, 0.30);

  --field-v:          rgba(25, 29, 38, 0.045);
  --field-h:          rgba(25, 29, 38, 0.07);
  --field-rule-solid: #dcd9d1;

  --surface-hover:  rgba(25, 29, 38, 0.04);
  --surface-active: rgba(25, 29, 38, 0.07);

  --shadow-overlay: 0 1px 1px rgba(25, 29, 38, 0.04),
                    0 8px 16px -4px rgba(25, 29, 38, 0.10),
                    0 24px 32px -8px rgba(25, 29, 38, 0.10);

  /* The conversation's two sides on paper (I16). --you-tint is one step off
     the page (the same value as --surface-elev), and since 2026-09-13 that
     is what tells the two sides apart: the agent's bubble and card stand
     on --surface-card, a tier above the paper, and the operator's own sit
     in the page's tint. --you-rule is the same paper-ink hairline as
     --surface-rule. Paper used to be where the
     bubble needed the contact shadow: on white, a hairline around white
     read as a shape, not an object anybody handed you. COT-234b (BR-A2b)
     retired that lift with the rest of the room's shadows (DESIGN.md's
     Elevation), so the hairline alone now does the work on both themes. */
  --you-tint:       #ebe8e1;
  --you-rule:       rgba(25, 29, 38, 0.12);
  --shadow-bubble:  0 1px 2px rgba(0, 0, 0, 0.045);

  /* Anna's hue on paper: the same hue, taken down until a letter at 11px
     clears AA on the elevated step (5.22:1 there). */
  --agent-people:    oklch(0.47 0.105 218);

  /* Mira's hue on paper: the same hue, taken down until a letter at 11px
     clears AA on the elevated step (5.76:1 there). */
  --agent-seo:       oklch(0.47 0.105 285);

  --avatar-beno-lit:   #a2be7d;
  --avatar-beno-shade: #42663f;
  --avatar-beno-flat:  #42663f;
  --avatar-people-lit: #deb09a;
  --avatar-people-shade: #a35e49;
  --avatar-people-flat: #a35e49;
  --avatar-seo-lit:    #c8bdd8;
  --avatar-seo-shade:  #78658e;
  --avatar-seo-flat:   #78658e;
  --avatar-eye:        #ffffff;
  --avatar-highlight:  #ffffff;

  /* Paper's cover is lighter: a 50% black over white reads as a bruise. */
  --scrim:          rgba(25, 29, 38, 0.40);

  --focus-ring:     #191d26;

  --text:           #191d26;
  --text-hover:     #2d3038;
  --text-muted:     #57595d;
  --text-subtle:    #5f6165;
  --text-faint:     #646569;

  /* signal-deep: darker chartreuse so the signal survives on paper. The
     Signal Rule (DESIGN.md): Action marks evidenced agent activity or
     authorship, never a human control or decoration - so this fill is
     reserved for the agent's own marks (the composer's send-to-agent
     control, a recorded/executed state), and Ink on it holds >= 4.5:1 on
     both fills (6.38:1 rest, 7.45:1 hover). --signal-mark (below, both
     themes) is the true chartreuse reserved for an Ink ground; this fill
     is the paper-safe deepening for the same hue on Paper. */
  --signal:         #8aac00;
  --signal-soft:    #96ba05;
  --signal-ink:     #191d26;
  /* The true chartreuse, only ever on an Ink ground: a glyph on the dark
     surface, or the recorded mark on an Ink tile. Never a field on Paper -
     --signal (above) is the paper-safe fill for that job, which is why the
     value here is unchanged from dark theme. */
  --signal-mark:    #c4f000;
  /* Signal as text needs more depth than the fill: the raw fill reads
     2.35:1 on paper, under the 3:1 non-text floor; this holds 5.81:1.
     Fills keep --signal; prose keeps this. */
  --signal-text:    #4f6600;

  /* Money coming in / money going out, the pair the Reporting trend already
     uses, validated for colour-vision separation and 3:1 on both grounds.
     Text never wears a series colour. */
  --series-in:      #3560a8;
  --series-out:     #bd7a2a;

  --success:        #15803d;
  --danger:         #b91c1c;
  --warning:        #b45309;
  --info:           #1d4ed8;
  /* Every `-text` step below is the same hue and chroma as its fill, taken
     down the lightness axis until it clears AA on the ELEVATED step (a
     status badge paints its own --surface-elev, so that is the pair that
     governs). warning_contrast_test.exs pins the hue AND chroma hold
     (0.01) for all four, the same way the original warning/success fix
     did.
     Warning: #aa4a00, dh 1.2 dc 0.002 off the fill - 4.65:1 elevated,
     5.09:1 on paper. Success: #007735, dh 0.1 dc 0.001 off the fill -
     4.65:1 elevated, 5.09:1 on paper. Danger and info spend nothing: their
     fills already clear 5.29:1 and 5.48:1 on the elevated step, so light
     needs no split either, same as dark. */
  --success-text:   #007735;
  --danger-text:    #b91c1c;
  --warning-text:   #aa4a00;
  --info-text:      #1d4ed8;
}

/* ── Breakpoint ladder — THREE steps, max-width, desktop-first ──
   Custom properties do not resolve inside a media query and there is no
   build step, so the ladder is a contract stated here and enforced in
   review, not a token.

     sm   560px   phone portrait. One column, one register per screen.
     md   900px   phone landscape + tablet portrait. The shell's drawer and
                  tab-bar boundary: below md the sidebar is off-canvas.
     lg  1200px   desktop. Sidebar docked, chat panel docked beside content.

   Nothing else. A rule reaching for 820 or 1100 is a rule that has not
   decided which of the three steps it belongs to. */

/* ── The coarse-pointer step ──
   Deliberately not a width query: a 900px phone in landscape needs thumb-
   sized chrome and a 900px laptop window does not. Stepping --control-h
   here lifts every control already reading the token (the icon buttons, the
   theme toggle, the register filters, the onboarding fields) in one move,
   and the type roles step with it so a phone stops speaking in 10px. */
@media (pointer: coarse) {
  :root,
  .light .mkt-invert {
    --t-body:       400 16px/1.55 var(--font-sans);
    --t-body-sm:    400 14px/1.50 var(--font-sans);
    --t-mono-data:  500 14px/1.50 var(--font-mono);
    --t-mono-label: 500 12px/1.40 var(--font-mono);
    --t-micro:      400 13px/1.40 var(--font-sans);

    /* 0.12em is a display-caps value. On 10px it was 1.2px of air between
       letters on the app's DEFAULT label face, which is most of the text on
       a phone: legible as a texture, not as words. Caps still need more than
       zero, so it halves rather than going away. */
    --tr-label: 0.06em;

    --control-h: var(--tap-min);
    --pad-card:  16px;
    --pad-view:  16px;
  }
}
