/* =====================================================================================================
   PolyJacks Mission Control - ds/components.css (S0b-T0; contract S0bc D20 + 5.2)
   The shared components every screen extends: the owner pages, the Kyle app, the walkthrough and the
   customer's signing shell. Built ONLY on ds/tokens.css - a colour is always a --c-* token, never raw hex -
   so every screen inherits one look and one set of contrast proofs (the figures live in tokens.css).
   LAWS (owner ruling 2026-10-02: screens MIX navy frames and light work surfaces - "a nice mix in of
   brighter sections and darker sections ... dark, light, dark, light"): the LIGHT SCOPE below maps every
   --c-* name to the light family (white cards, dark ink) and the NAVY SCOPE maps it to the navy family with
   bright ink, so one component reads correctly on either surface by itself; a page lays its content out in
   BANDS (.swath-navy for the frame - header, hero, summary; .swath-light for anything text-heavy). System
   faces only, never a fixed-width face; every amount, count, time and reading in tabular figures (.num);
   390 px first - every control is a 44 px target on a phone or a coarse pointer; every state is drawn
   (hover, focus, pressed, disabled, loading, empty, error); motion runs on the token durations, which drop
   to 0 under reduced motion (tokens.css), and no animation carries meaning on its own.
   LOAD ORDER: tokens.css -> components.css -> app.css (the S0a shell) -> the page's own css. A name the board
   already uses (.btn, .field, .input, .card, .chip, .skeleton, .sk, .toast) is styled here so that the S0a
   rule, loading later, still wins on the board without a visual change; a page sheet (sign.css, kyle.css)
   extends a component by adding its own class beside the DS one (.paper.sign-paper), never by renaming it.
   No inline style anywhere (the CSP allows none): a per-instance value is a class, an attribute or an SVG
   presentation attribute (the ring's arc), or a custom property set from script through the CSSOM.
   ===================================================================================================== */

/* ---------- base: the reset app.css also carries, so a shell that loads only the DS (the signing page, the
   Kyle app, the style guide) gets it too - identical values, so the two never disagree while both load ---------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

[hidden] {
  display: none !important; /* author display rules must never resurrect a hidden node */
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--font-ui);
  font-size: var(--fs-2);
  line-height: var(--lh-body);
  font-variant-numeric: tabular-nums;
  color: var(--c-text);
  background: var(--c-canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4,
p,
ul,
ol,
dl,
dd,
figure {
  margin: 0;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 650;
  line-height: var(--lh-tight);
  letter-spacing: -.005em;
}

ul[role="list"],
ol[role="list"] {
  list-style: none;
  padding: 0;
}

button,
input,
select,
textarea {
  font: inherit; /* Chrome's default textarea face is fixed-width - never in this product */
  color: inherit;
  letter-spacing: inherit;
}

a {
  color: var(--c-link);
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-thickness: 2px;
}

summary {
  cursor: pointer;
}

/* One focus ring everywhere: 2 px of --c-focus with a 2 px gap, so it reads on any fill (amber 6.69:1 or
   better on every dark ground; inside .paper it is the paper link colour, 5.18:1 on white). */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}

::selection {
  background: rgba(176, 77, 30, .28);
}

.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The first Tab on a DS shell goes straight to the content, past the bar (WCAG 2.4.1). */
.skip-link {
  position: absolute;
  top: var(--sp-2);
  left: var(--sp-2);
  z-index: var(--z-toast);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-sm);
  background: var(--c-primary);
  color: var(--c-on-primary); /* 5.00:1 */
  font-weight: 700;
  text-decoration: none;
  translate: 0 -200%;
  transition: translate var(--dur-2) var(--ease);
}

.skip-link:focus-visible {
  translate: 0 0;
}

/* ---------- SURFACES (owner ruling 2026-10-02). Two scopes map the semantic --c-* names to a family from
   tokens.css, so every component below reads right on either ground without knowing which it is on.
   THE LIGHT SCOPE: the main column of every app page (#main - never the public shell's main.pub-main,
   find-c14-1), the Kyle app, the signing stage, any
   .swath-light / .on-light, the paper, the board's cards (ivory cards in navy columns) and the strip's
   tiles. Ink is navy (palette A); body text 12.48:1 and better, the quietest caption 6.15:1.
   THE NAVY SCOPE: .on-navy, the top bar, the app bar, the drawer head and the toasts - navy is the ACCENT
   (owner 2026-10-05: "a little bit more balance with the other colors"); the header swaths (.swath-navy,
   kept as a class name) and the tab bar are light now. The rest of the navy scope:
   toasts - the S0a navy grounds with the --n-* ink, so nothing on navy is ever gray. ---------- */
#main:not(.pub-main), .kyle, .sign-main, .swath-light, .on-light, .paper, .board .card, .card-ghost, .strip-link, .swath-navy, .tabbar {
  --c-canvas: var(--l-canvas);
  --c-surface: var(--l-surface);
  --c-surface-2: var(--l-surface-2);
  --c-column: var(--l-column);
  --c-column-line: var(--l-column-line);
  --c-line: var(--l-line);
  --c-line-strong: var(--l-line-strong);
  --c-hover: var(--l-hover);
  --c-text: var(--l-text);
  --c-text-2: var(--l-text-2);
  --c-text-3: var(--l-text-3);
  --c-placeholder: var(--l-placeholder);
  --c-link: var(--l-link);
  --c-focus: var(--l-focus);
  --c-accent-line: var(--l-accent-line);
  --c-good-text: var(--l-good-text);
  --c-good-fill: var(--l-good-fill);
  --c-good-bg: var(--l-good-bg);
  --c-good-line: var(--l-good-line);
  --c-warn-text: var(--l-warn-text);
  --c-warn-fill: var(--l-warn-fill);
  --c-warn-bg: var(--l-warn-bg);
  --c-warn-line: var(--l-warn-line);
  --c-danger-text: var(--l-danger-text);
  --c-danger-fill: var(--l-danger-fill);
  --c-danger-bg: var(--l-danger-bg);
  --c-danger-line: var(--l-danger-line);
  --c-info-text: var(--l-info-text);
  --c-info-bg: var(--l-info-bg);
  --c-info-line: var(--l-info-line);
  --c-on-danger: #faf7f0; /* 6.01:1 on the light danger fill */
  --c-chip: var(--l-chip);
  --c-chip-text: var(--l-chip-text);
  /* the job tag's own colour (owner 2026-10-15) - one pair per tag name, the same pair on every light surface */
  --c-tag-slate-bg: var(--l-tag-slate-bg);
  --c-tag-slate-ink: var(--l-tag-slate-ink);
  --c-tag-amber-bg: var(--l-tag-amber-bg);
  --c-tag-amber-ink: var(--l-tag-amber-ink);
  --c-tag-green-bg: var(--l-tag-green-bg);
  --c-tag-green-ink: var(--l-tag-green-ink);
  --c-tag-blue-bg: var(--l-tag-blue-bg);
  --c-tag-blue-ink: var(--l-tag-blue-ink);
  --c-tag-red-bg: var(--l-tag-red-bg);
  --c-tag-red-ink: var(--l-tag-red-ink);
  --c-tag-violet-bg: var(--l-tag-violet-bg);
  --c-tag-violet-ink: var(--l-tag-violet-ink);
  --c-scrim: var(--l-scrim);
  --c-hatch: var(--l-hatch);
  --chevron: var(--l-chevron);
  --icon-alert: var(--l-icon-alert);
  --stage-new: var(--l-stage-new);
  --stage-contacted: var(--l-stage-contacted);
  --stage-quoted: var(--l-stage-quoted);
  --stage-signed: var(--l-stage-signed);
  --stage-rig: var(--l-stage-rig);
  --stage-done: var(--l-stage-done);
  --stage-visit: var(--l-stage-visit);
  --stage-abandoned: var(--l-stage-abandoned);
  --stage-follow: var(--l-stage-follow);
  --stage-lost: var(--l-stage-lost);
  --stage-nofit: var(--l-stage-nofit);
  --shadow-1: var(--l-shadow-1);
  --shadow-2: var(--l-shadow-2);
  --shadow-3: var(--l-shadow-3);
  --shadow-paper: var(--l-shadow-2);
  color: var(--c-text);
  color-scheme: light;
}

.on-navy, .topbar, .appbar, .drawer-head, .toast {
  --c-canvas: #16213a;
  --c-surface: #1c2944;
  --c-surface-2: #1c2944;
  --c-column: #1c2944;
  --c-column-line: #33415e;
  --c-line: #33415e;
  --c-line-strong: #6c7892;
  --c-hover: var(--n-hover);
  --c-text: var(--n-text);
  --c-text-2: var(--n-text-2);
  --c-text-3: var(--n-text-3);
  --c-placeholder: #c2bcb0;
  --c-link: #e9a97f;
  --c-focus: #e9a97f;
  --c-accent-line: var(--c-brand);
  --c-good-text: #9fd3ae;
  --c-good-fill: #9fd3ae;
  --c-good-bg: #1b3236;
  --c-good-line: #2f5a4a;
  /* palette A's status hues lifted for navy (the S0a values): text >= 7.25:1 on its opaque tint and
     6.65:1 on the #24324f chip; the fills clear 3:1 on every navy ground (warn 3.30, danger 4.48 at worst) */
  --c-warn-text: #e7c398;
  --c-warn-fill: #b2742a;
  --c-warn-bg: #3c2f20;
  --c-warn-line: #725737;
  --c-danger-text: #e7ada6;
  --c-danger-fill: #db8076;
  --c-danger-bg: #3e2623;
  --c-danger-line: #73403a;
  --c-info-text: #e8b49c;
  --c-info-bg: #3c2920;
  --c-info-line: #724937;
  --c-on-danger: #121c32; /* the bar navy on the danger fill, 5.95:1 */
  --c-chip: #24324f;
  --c-chip-text: var(--n-chip-text);
  --c-scrim: rgba(2, 6, 23, .72);
  --c-hatch: rgba(255, 255, 255, .03);
  --chevron: var(--n-chevron);
  --icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e7c398' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.5v5M12 16v.5'/%3E%3C/svg%3E");
  --stage-new: #5b85b0;
  --stage-contacted: #7878ad;
  --stage-quoted: #c8622d;
  --stage-signed: #5b916c;
  --stage-rig: #4a8a96;
  --stage-done: #8c8477;
  --stage-visit: #9272a8;
  --stage-abandoned: #83878c;
  --stage-follow: #a8822e;
  --stage-lost: #c45f62;
  --stage-nofit: #747b8a;
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .4), 0 2px 6px rgba(0, 0, 0, .22);
  --shadow-2: 0 2px 6px rgba(0, 0, 0, .35), 0 18px 40px -12px rgba(0, 0, 0, .6);
  --shadow-3: 0 10px 20px rgba(0, 0, 0, .4), 0 30px 60px -18px rgba(0, 0, 0, .72);
  --shadow-paper: 0 1px 2px rgba(0, 0, 0, .35), 0 24px 48px -16px rgba(0, 0, 0, .75), 0 0 0 1px rgba(255, 255, 255, .06);
  color: var(--c-text);
  color-scheme: dark;
}

/* ---------- swaths: the page rhythm. A page is a stack of full-bleed swaths - navy for the frame (the
   header with its title, a hero or a summary), light for the work - each with one centred inner column
   (.swath-inner, --page-w wide). Two swaths of one colour merge; the last light swath fills to the foot. ---------- */
#main:not(.pub-main) {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: calc(100vh - var(--topbar-h));
  min-height: calc(100dvh - var(--topbar-h));
  background: var(--c-canvas);
}

.swath {
  position: relative;
  width: 100%;
  min-width: 0;
  padding-block: var(--swath-y);
}

.swath-inner {
  width: 100%;
  max-width: var(--page-w);
  margin-inline: auto;
  padding-inline: max(var(--gutter), env(safe-area-inset-left)) max(var(--gutter), env(safe-area-inset-right));
  min-width: 0;
}

.swath-light {
  background: var(--c-canvas);
}

.swath-light:last-child {
  flex: 1 0 auto;
}

.swath-light + .swath-light {
  padding-top: 0;
}

.swath-navy {
  background-color: var(--l-surface);
  border-bottom: 1px solid var(--l-line);
}

.swath-navy + .swath-navy {
  padding-top: 0;
}

/* The header swath under the top bar: a touch tighter on top (the bar's own hairline separates them). */
.swath-hero {
  padding-block: var(--sp-6) var(--swath-y);
}

/* A summary swath in the middle of a long page (the ledger's figures, the quote's price) is a little calmer. */
.swath-summary {
  padding-block: var(--sp-6);
}

/* ---------- hero: the page's name and its one-line purpose, in the navy header swath, with the page's
   top-level actions on the right (stacked under the title on a phone). ---------- */
.hero {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-6);
  min-width: 0;
}

.hero-copy {
  display: grid;
  gap: var(--sp-2);
  flex: 1 1 320px;
  min-width: 0;
  max-width: 72ch;
}

/* UI_SPEC 3.2 names THIS rule by line ("`.hero-eyebrow` (352-358): drop uppercase and tracking; 13 px / 600 /
   --c-text-2") and UI_SPEC 1.4 says an eyebrow is 13 px / 600 in the secondary ink. U1 edited .appbar-title
   instead and left this one caps in the old accent ink (RVW-U1 MAJOR 5). It is on 8+ pages (ledger, reports,
   timeline, desk, schedule, the funnel pages, the style guide). */
.hero-eyebrow {
  color: var(--c-text-2); /* 7.58:1 on a card, 7.01:1 on the ground */
  font-size: var(--fs-2);
  font-weight: 600;
  letter-spacing: 0;
}

.hero-title {
  color: var(--c-text);
  font-family: var(--font-display);
  font-size: var(--fs-7);
  font-weight: 700; /* UI_SPEC 1.4: 700 only for a page title - 750 was an off-scale weight */
  line-height: var(--lh-display);
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
}

.hero-sub {
  max-width: 64ch;
  color: var(--c-text-2); /* 14.89:1 */
  font-size: var(--fs-3);
  line-height: var(--lh-body);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

/* A quiet back link above the hero title. */
.hero-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  justify-self: start;
  min-height: var(--sp-6);
  /* a 44 px thumb target (the text line + 12 + 12) that takes no more room in the layout than the line it draws */
  padding-block: 12px;
  margin-block: -12px;
  color: var(--c-text-2);
  font-size: var(--fs-2);
  font-weight: 600;
  text-decoration: none;
}

.hero-back:hover {
  color: var(--c-text);
  text-decoration: underline;
}

.hero-back .i {
  --i-size: var(--sp-4);
}

/* ---------- layout: one page frame, one rhythm (a 4 px grid; sections 40 px apart) ---------- */
.page {
  width: 100%;
  max-width: var(--page-w);
  margin: 0 auto;
  padding: var(--sp-6) max(var(--gutter), env(safe-area-inset-right)) var(--sp-10) max(var(--gutter), env(safe-area-inset-left));
}

.page.is-narrow {
  max-width: 760px;
}

/* A shell with a bottom tab bar keeps its last line above the bar. */
body:has(> .tabbar) .page {
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--sp-6));
}

.page-head {
  display: grid;
  gap: var(--sp-2);
  margin-bottom: var(--sp-8);
}

.page-title {
  font-family: var(--font-display); /* Sora: a page title is one of the five display slots (UI_SPEC 1.3) */
  font-size: var(--fs-6);
  font-weight: 700;
  letter-spacing: -.01em;
}

.page-sub {
  max-width: 68ch;
  color: var(--c-text-2);
  font-size: var(--fs-3);
}

/* UI_SPEC 1.4: an eyebrow is 13 px / 600 in the secondary ink - no caps, no tracking. */
.eyebrow {
  color: var(--c-text-2);
  font-size: var(--fs-2);
  font-weight: 600;
  letter-spacing: 0;
}

.section {
  display: grid;
  gap: var(--sp-4);
  scroll-margin-top: calc(var(--topbar-h) + var(--sp-4));
}

.section + .section {
  margin-top: var(--sp-10);
}

.section-h {
  font-size: var(--fs-4);
  font-weight: 700;
  letter-spacing: -.01em;
}

.section-sub {
  max-width: 68ch;
  color: var(--c-text-3); /* 7.57:1 on the canvas */
}

.stack {
  display: grid;
  align-content: start;
  gap: var(--sp-4);
  min-width: 0;
}

.stack-lg {
  gap: var(--sp-6);
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
  gap: var(--sp-4);
}

.split {
  display: grid;
  align-items: start;
  gap: var(--sp-6);
}

@media (min-width: 768px) {
  .page-title {
    font-size: var(--fs-6);
    letter-spacing: -.02em;
  }

  .section-h {
    font-size: var(--fs-5);
  }
}

@media (min-width: 1024px) {
  .split {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  }
}

/* ---------- icons: a glyph token painted in currentColor, so an icon always has its text's contrast ---------- */
.ico {
  display: inline-block;
  flex-shrink: 0;
  width: 1.25em;
  height: 1.25em;
  background-color: currentColor;
  vertical-align: -.25em;
  -webkit-mask: var(--ico) center / contain no-repeat;
  mask: var(--ico) center / contain no-repeat;
}

.ico-check { --ico: var(--glyph-check); }
.ico-x { --ico: var(--glyph-x); }
.ico-alert { --ico: var(--glyph-alert); }
.ico-info { --ico: var(--glyph-info); }
.ico-offline { --ico: var(--glyph-offline); }
.ico-eye { --ico: var(--glyph-eye); }
.ico-pause { --ico: var(--glyph-pause); }
.ico-chevron { --ico: var(--glyph-chevron); }
.ico-arrow { --ico: var(--glyph-arrow); }
.ico-external { --ico: var(--glyph-external); }
.ico-inbox { --ico: var(--glyph-inbox); }
.ico-clock { --ico: var(--glyph-clock); }
.ico-calendar { --ico: var(--glyph-calendar); }
.ico-pin { --ico: var(--glyph-pin); }
.ico-phone { --ico: var(--glyph-phone); }
.ico-camera { --ico: var(--glyph-camera); }
.ico-sun { --ico: var(--glyph-sun); }
.ico-layers { --ico: var(--glyph-layers); }
.ico-wallet { --ico: var(--glyph-wallet); }
.ico-user { --ico: var(--glyph-user); }
.ico-plus { --ico: var(--glyph-plus); }
.ico-lock { --ico: var(--glyph-lock); }
.ico-tag { --ico: var(--glyph-tag); }
.ico-gear { --ico: var(--glyph-gear); }
.ico-chart { --ico: var(--glyph-chart); }

/* Windows high contrast repaints backgrounds; the glyph keeps the system text colour instead of vanishing. */
@media (forced-colors: active) {
  .ico {
    background-color: CanvasText;
    forced-color-adjust: none;
  }
}

/* ---------- app bar: navy with the live site's topo texture and the LIGHT logo (text on navy 15.07:1,
   the accent label 8.44:1). Sticky; the safe-area inset keeps it clear of a phone's status bar. ---------- */
.appbar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: calc(var(--topbar-h) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) max(var(--gutter), env(safe-area-inset-right)) 0 max(var(--gutter), env(safe-area-inset-left));
  border-bottom: 1px solid var(--n-rule);
  background-color: var(--navy);
  background-image: none; /* UI_SPEC 3.2: the app bar is FLAT navy; C adds the neon inset line */
  color: var(--c-text);
}

.appbar-brand {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  min-height: var(--tap);
  border-radius: var(--r-xs);
}

.appbar-logo {
  display: block;
  width: auto;
  height: 28px;
}

.appbar-title {
  min-width: 0;
  padding-left: var(--sp-3);
  overflow: hidden;
  border-left: 1px solid var(--n-rule);
  color: var(--c-text-2); /* UI_SPEC 1.4: an eyebrow is 13 px / 600 in the secondary ink - never caps */
  font-size: var(--fs-2);
  font-weight: 600;
  letter-spacing: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.appbar-end {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
}

/* ---------- buttons: one shape, four voices. The S0a metrics exactly (40 px, 44 on touch), plus every state:
   hover, pressed (1 px down, deeper fill), focus (the ring), disabled (dimmed, no pointer) and LOADING -
   aria-busy keeps the label's width (no layout jump) and draws a spinner in the button's own ink. ---------- */
.btn {
  --btn-ink: var(--c-text);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 40px;
  padding: 0 var(--sp-4);
  border: 1px solid transparent;
  border-radius: var(--r-ctl, var(--r-sm)); /* UI_SPEC 3.2; the fallback keeps the base radius if tokens2 is not linked yet (RVW-U1 MINOR 7) */
  font-size: var(--fs-2);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease),
    color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease), transform var(--dur-1) var(--ease);
}

.btn:active {
  transform: translateY(1px);
}

.btn .ico {
  width: 18px;
  height: 18px;
}

/* The ONE primary action of a screen: ivory on rust with the accent glow (5.00:1; hover and pressed 6.09). */
.btn-primary {
  --btn-ink: var(--c-on-primary);
  background: var(--c-primary);
  box-shadow: var(--shadow-glow-sm);
  color: var(--c-on-primary);
}

.btn-primary:hover {
  background: var(--c-primary-hover);
  box-shadow: var(--shadow-glow);
}

.btn-primary:active {
  background: var(--c-primary-press);
  box-shadow: var(--shadow-glow-sm);
}

.btn-secondary {
  border-color: var(--c-line-strong);
  background: var(--c-surface);
  color: var(--c-text);
}

.btn-secondary:hover {
  border-color: var(--c-text-3);
  background: var(--c-surface-2);
}

.btn-secondary:active {
  background: var(--c-surface);
}

/* owner 2026-10-05: a quiet button must still READ as a button on a phone - a hairline pill, not bare text */
.btn-ghost {
  --btn-ink: var(--c-text-2);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-ctl, var(--r-sm)); /* UI_SPEC 3.2: every .btn carries the SAME radius - no pill in A/C */
  background: transparent;
  color: var(--c-text-2);
}

.btn-ghost:hover {
  background: var(--c-hover);
  color: var(--c-text);
}

/* Destructive (PAUSE ALL, revoke, cancel a contract): the ink on the danger fill is the bar navy on the
   navy set's #db8076 (5.95:1) and ivory on the light set's #a8382c (6.01:1) - --c-on-danger follows the scope. */
.btn-danger {
  --btn-ink: var(--c-on-danger);
  background: var(--c-danger-fill);
  color: var(--c-on-danger);
}

.btn-danger:hover {
  filter: brightness(1.08);
}

.btn-danger:active {
  filter: none;
}

.btn-block {
  display: flex;
  width: 100%;
}

/* The big thumb target: Sign, ACCEPT / DECLINE. */
.btn-lg {
  min-height: 52px;
  padding: 0 var(--sp-6);
  border-radius: var(--r-md);
  font-size: var(--fs-3);
}

.btn-sm {
  min-height: 32px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-1);
}

.btn-icon {
  min-width: 40px;
  padding: 0;
}

.btn-sm.btn-icon {
  min-width: 32px;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}

a.btn[aria-disabled="true"] {
  pointer-events: none;
}

.btn[aria-busy="true"] {
  color: transparent;
  cursor: progress;
  pointer-events: none;
}

.btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 18px;
  height: 18px;
  margin: auto;
  border: 2px solid var(--btn-ink);
  border-right-color: transparent;
  border-radius: 50%;
  animation: ds-spin 700ms linear infinite;
}

/* ---------- fields: the label above, the control, then .help and .error - both tied to the control by
   aria-describedby. An empty .error takes no room, so a page fills it (and announces it) when it needs to. ---------- */
.field {
  display: grid;
  align-content: start; /* a field stretched by its grid row keeps a 40 px input (NEO-336) */
  gap: 6px;
  min-width: 0;
}

.field-label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  color: var(--c-text-2);
  font-size: var(--fs-1);
  font-weight: 600;
}

.field-tag {
  color: var(--c-warn-text);
  font-size: var(--fs-1);
  font-weight: 500;
}

.field-tag.is-optional {
  color: var(--c-text-3);
}

.field .help {
  color: var(--c-text-3);
  font-size: var(--fs-1);
}

.field .error {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  color: var(--c-danger-text); /* 7.78:1 on the light card, 7.53:1 on the navy surface */
  font-size: var(--fs-1);
  font-weight: 600;
}

.field .error::before {
  content: "";
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  background: currentColor;
  -webkit-mask: var(--glyph-alert) center / contain no-repeat;
  mask: var(--glyph-alert) center / contain no-repeat;
}

.field .error:empty {
  display: none;
}

.input {
  display: block;
  width: 100%;
  min-height: 42px;
  padding: 9px 12px;
  border: 1px solid var(--c-line-strong); /* 3.42:1 on the surface */
  border-radius: var(--r-sm);
  background-color: var(--c-surface);
  color: var(--c-text);
  font-size: var(--fs-2);
  line-height: var(--lh-snug);
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}

.input::placeholder {
  color: var(--c-placeholder);
  opacity: 1;
}

.input:hover {
  border-color: var(--c-text-3);
}

.input:focus-visible {
  border-color: var(--c-focus);
  outline-offset: 1px;
}

.input[aria-invalid="true"] {
  border-color: var(--c-danger-fill);
  box-shadow: 0 0 0 3px var(--c-danger-bg);
}

.input:disabled {
  opacity: .55;
  cursor: not-allowed;
}

.input[readonly] {
  border-style: solid; /* UI_SPEC 4: no dashed outline anywhere but the Add zone and the hold hatch */
  background-color: transparent;
}

textarea.input {
  min-height: 84px;
  resize: vertical;
}

select.input {
  padding-right: 38px;
  background-image: var(--chevron);
  background-position: right 12px center;
  background-repeat: no-repeat;
  background-size: 16px 16px;
  cursor: pointer;
  appearance: none;
}

/* A checkbox or radio row (consent boxes, 8A, decline reasons): the WHOLE row is the target, 44 px tall,
   the native control drawn in the brand amber. Checked rows take an amber edge as well as the tick. */
.check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  color: var(--c-text);
  line-height: var(--lh-snug);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease), background-color var(--dur-2) var(--ease);
}

.check:hover {
  border-color: var(--c-line-strong);
}

.check:has(:checked) {
  border-color: var(--c-accent-line);
  background-color: var(--c-info-bg); /* text on it 12.52:1 light, 12.19:1 navy */
}

.check:has(:disabled) {
  opacity: .55;
  cursor: not-allowed;
}

.check > input {
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--c-primary);
  cursor: inherit;
}

.choices {
  display: grid;
  gap: var(--sp-2);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.choices > legend {
  margin-bottom: var(--sp-1);
  padding: 0;
  color: var(--c-text-2);
  font-size: var(--fs-1);
  font-weight: 600;
}

/* The combobox's suggestion list (Door 3 address): role=listbox under its input, the active option marked by
   aria-selected (an amber edge, never colour alone - the option also gets the tint). */
.combo {
  position: relative;
}

.listbox {
  position: absolute;
  top: calc(100% + var(--sp-1));
  right: 0;
  left: 0;
  z-index: var(--z-pop);
  max-height: 288px;
  margin: 0;
  padding: var(--sp-1);
  overflow: auto;
  list-style: none;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  box-shadow: var(--shadow-3);
  animation: ds-pop var(--dur-2) var(--ease);
}

.option {
  display: grid;
  gap: 2px;
  min-height: var(--tap);
  padding: 10px 12px;
  border-radius: var(--r-xs);
  color: var(--c-text);
  cursor: pointer;
}

.option:hover {
  background: var(--c-hover);
}

.option[aria-selected="true"] {
  background: var(--c-info-bg); /* text on it 12.52:1 light, 12.19:1 navy */
  box-shadow: inset 3px 0 0 var(--c-primary);
}

.option-sub {
  color: var(--c-text-3);
  font-size: var(--fs-1);
}

/* ---------- chips: stage, door, source, state, and the good / warn / danger tones. Meaning is always in the
   words; a stage adds a coloured dot (the stage accents are dots and bars, never text). ---------- */
[data-accent="new"] { --accent: var(--stage-new); }
[data-accent="contacted"] { --accent: var(--stage-contacted); }
[data-accent="quoted"] { --accent: var(--stage-quoted); }
[data-accent="signed"] { --accent: var(--stage-signed); }
[data-accent="rig_scheduled"] { --accent: var(--stage-rig); }
[data-accent="done_paid"] { --accent: var(--stage-done); }
[data-accent="sales_visit"] { --accent: var(--stage-visit); }
[data-accent="abandoned"] { --accent: var(--stage-abandoned); }
[data-accent="follow_up"] { --accent: var(--stage-follow); }
[data-accent="lost"] { --accent: var(--stage-lost); }
[data-accent="not_a_fit"] { --accent: var(--stage-nofit); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 0 8px;
  overflow: hidden;
  border-radius: var(--r-pill);
  background: var(--c-chip);
  color: var(--c-chip-text); /* 9.68:1 */
  font-size: var(--fs-1);
  font-weight: 600;
  line-height: 20px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* UI_SPEC 2.1: no dot on a chip or a tag. The stage is a WORD; the accent colour stays available as a
   boundary (a column head's top rule) but never as a bullet. */
.chip-stage::before {
  content: none;
}

/* B4-06 (0915): a filled pill, no inner ring - the 1px inset read as the same "outline with lines" the owner
   named on the tag. The fill alone says "this came from somewhere". */
.chip-source {
  border: 1px solid var(--c-line-strong); /* UI_SPEC 2.1; --line2 #cbd5e1 measures 1.48:1 on white, so the
                                             source outline uses the boundary token (3.66:1) - RVW-U1 MAJOR 4 */
  background: transparent;
  color: var(--c-text-2);
}

.chip.is-missing {
  padding: 0 8px;
  border: 1px solid var(--c-line-strong); /* UI_SPEC 4: no dashed border anywhere */
  background: transparent;
  box-shadow: none;
  color: var(--c-text-3);
  line-height: 18px;
}

.chip-good {
  background: var(--c-good-bg);
  box-shadow: inset 0 0 0 1px var(--c-good-line);
  color: var(--c-good-text);
}

.chip-warn {
  background: var(--c-warn-bg);
  box-shadow: inset 0 0 0 1px var(--c-warn-line);
  color: var(--c-warn-text);
}

.chip-danger {
  background: var(--c-danger-bg);
  box-shadow: inset 0 0 0 1px var(--c-danger-line);
  color: var(--c-danger-text);
}

/* A chip that is a CHOICE (surfaces, slot times, a payment method): a button with aria-pressed or
   role=radio + aria-checked, or a .chip-check label around a hidden native input. Chosen = amber fill. */
.chip-pick {
  min-height: 36px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-ctl, var(--r-sm)); /* a chip that is a CHOICE is a control, not a tag; fallback for an unlinked tokens2 (RVW-U1 MINOR 7) */
  background: var(--c-surface);
  color: var(--c-text);
  line-height: var(--lh-tight);
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease),
    color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}

.chip-pick:hover {
  border-color: var(--c-text-3);
  background: var(--c-surface-2);
}

.chip-pick[aria-pressed="true"],
.chip-pick[aria-checked="true"],
.chip-check > :checked + .chip-pick {
  border-color: var(--c-primary);
  background: var(--c-primary);
  box-shadow: var(--shadow-glow-sm);
  color: var(--c-on-primary); /* 5.00:1 */
}

.chip-pick:disabled {
  opacity: .55;
  cursor: not-allowed;
}

.chip-check {
  position: relative;
  display: inline-flex;
  cursor: pointer;
}

.chip-check > input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.chip-check > :focus-visible + .chip-pick {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}

/* ---------- the tag component (UI_SPEC 2.1-2.3) ----------
   ONE tag, four kinds. A tag is a WORD in a shape: 24 px tall (28 px .lg), 12 px / 600, radius --r-tag
   (6 px in A, a pill in B, 4 px in C), never a ::before dot, never a dashed border. The hue comes from
   [data-hue]; the KIND decides what that hue is allowed to mean:
     stage  - the stage's fixed hue; the column head carries it, a card inside the column does not repeat it
     source - ALWAYS the neutral outline, in every style: a source never competes with a person or a stage
     flag   - amber (attention): Out of area, Needs assigning, Hold, Stopped at photos
     person - THE PERSON'S OWN HUE; label "<small>Company ·</small> <b>First name</b>"
   Max one tag row per card, capped at three plus "+N" (board.js TAG_CAP). */
.tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  height: 24px;
  padding: 0 8px;
  overflow: hidden;
  border: 1px solid transparent;
  border-radius: var(--r-tag, var(--r-sm)); /* 6 px in A; fallback for an unlinked tokens2 (RVW-U1 MINOR 7) */
  background: var(--t, var(--c-column));
  color: var(--i, var(--c-text-2));
  font-size: var(--fs-1);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tag.lg {
  height: 28px;
  padding: 0 10px;
  font-size: var(--fs-2);
}

/* THE DEFAULT IS STYLE 2 - SOLID (owner_pick_night_shift.webp: Tags = "2 solid"). A person's colour reads
   from across the room. The bare rule makes it the system default; the .theme-night copies below keep a
   page that re-binds :root on the same style. [data-kind="source"] still wins (0,2,0) and stays an outline. */
.tag {
  background: var(--s, var(--t, var(--c-column)));
  color: var(--si, var(--i, var(--c-text-2)));
}

.tag[data-hue="red"] { --t: var(--hue-red-t); --i: var(--hue-red-i); --s: var(--hue-red-s); --si: var(--hue-red-si); --nt: var(--hue-red-nt); --ni: var(--hue-red-ni); --nb: var(--hue-red-nb); }
.tag[data-hue="amber"] { --t: var(--hue-amber-t); --i: var(--hue-amber-i); --s: var(--hue-amber-s); --si: var(--hue-amber-si); --nt: var(--hue-amber-nt); --ni: var(--hue-amber-ni); --nb: var(--hue-amber-nb); }
.tag[data-hue="lime"] { --t: var(--hue-lime-t); --i: var(--hue-lime-i); --s: var(--hue-lime-s); --si: var(--hue-lime-si); --nt: var(--hue-lime-nt); --ni: var(--hue-lime-ni); --nb: var(--hue-lime-nb); }
.tag[data-hue="green"] { --t: var(--hue-green-t); --i: var(--hue-green-i); --s: var(--hue-green-s); --si: var(--hue-green-si); --nt: var(--hue-green-nt); --ni: var(--hue-green-ni); --nb: var(--hue-green-nb); }
.tag[data-hue="teal"] { --t: var(--hue-teal-t); --i: var(--hue-teal-i); --s: var(--hue-teal-s); --si: var(--hue-teal-si); --nt: var(--hue-teal-nt); --ni: var(--hue-teal-ni); --nb: var(--hue-teal-nb); }
.tag[data-hue="sky"] { --t: var(--hue-sky-t); --i: var(--hue-sky-i); --s: var(--hue-sky-s); --si: var(--hue-sky-si); --nt: var(--hue-sky-nt); --ni: var(--hue-sky-ni); --nb: var(--hue-sky-nb); }
.tag[data-hue="blue"] { --t: var(--hue-blue-t); --i: var(--hue-blue-i); --s: var(--hue-blue-s); --si: var(--hue-blue-si); --nt: var(--hue-blue-nt); --ni: var(--hue-blue-ni); --nb: var(--hue-blue-nb); }
.tag[data-hue="violet"] { --t: var(--hue-violet-t); --i: var(--hue-violet-i); --s: var(--hue-violet-s); --si: var(--hue-violet-si); --nt: var(--hue-violet-nt); --ni: var(--hue-violet-ni); --nb: var(--hue-violet-nb); }
.tag[data-hue="plum"] { --t: var(--hue-plum-t); --i: var(--hue-plum-i); --s: var(--hue-plum-s); --si: var(--hue-plum-si); --nt: var(--hue-plum-nt); --ni: var(--hue-plum-ni); --nb: var(--hue-plum-nb); }
.tag[data-hue="slate"] { --t: var(--hue-slate-t); --i: var(--hue-slate-i); --s: var(--hue-slate-s); --si: var(--hue-slate-si); --nt: var(--hue-slate-nt); --ni: var(--hue-slate-ni); --nb: var(--hue-slate-nb); }

/* Style 2 - solid: a person's colour reads from across the room. The default in C (Night Shift) and on an
   already-tinted schedule block. Style 1 (tint) is the default everywhere else in A.

   C1 BUG FIX: these two rules used a BARE `var(--s)` / `var(--si)`. A tag that carries no [data-hue] - the
   legacy emitters (board.js:415/422/481/1439, job.js) still write `tag tag--<name>` and let shell.css pick the
   colour - has neither, and a bare var() is invalid at computed-value time: the declaration is dropped and the
   tag painted TRANSPARENT with the inherited ink. The fallback chain is the same one the base .tag rule uses. */
.theme-night .tag {
  background: var(--s, var(--t, var(--c-column)));
  color: var(--si, var(--i, var(--c-text-2)));
}

/* On navy: the translucent tint, the light ink and a hairline (style 1); the solid fill in style 2.
   --nb only exists when the tag carries a [data-hue] (the ten rules above), so the fallback matters: with a
   bare `var(--nb)` a hue-less tag (a source tag, or a flag before its hue is set) dropped the declaration at
   computed-value time and drew its border in its own text colour instead of the hairline (RVW-U1b MINOR 7). */
.on-navy .tag,
.appbar .tag,
.tabbar .tag,
.swath-navy .tag {
  background: var(--nt, transparent);
  border-color: var(--nb, transparent);
  color: var(--ni, var(--nink2));
}

.theme-night .tag,
.theme-night .on-navy .tag,
.theme-night .appbar .tag,
.theme-night .tabbar .tag,
.theme-night .swath-navy .tag {
  background: var(--s, var(--t, var(--c-column)));
  border-color: transparent;
  color: var(--si, var(--i, var(--c-text-2)));
}

/* A SOURCE is always the neutral outline, whatever the style and whatever the ground. */
.tag[data-kind="source"],
.theme-night .tag[data-kind="source"],
.on-navy .tag[data-kind="source"],
.appbar .tag[data-kind="source"],
.tabbar .tag[data-kind="source"],
.swath-navy .tag[data-kind="source"] {
  background: transparent;
  border: 1px solid var(--c-line-strong); /* 3.66:1 on a card - --line2 #cbd5e1 is 1.48:1 (RVW-U1 MAJOR 4) */
  color: var(--c-text-2);
}

.on-navy .tag[data-kind="source"],
.appbar .tag[data-kind="source"],
.tabbar .tag[data-kind="source"],
.swath-navy .tag[data-kind="source"] {
  border-color: var(--nline2, var(--n-rule-strong));
  color: var(--nink2, var(--c-text-2));
}

/* A person: the company is 500 at 85%, the first name 700. */
.tag[data-kind="person"] small {
  font-size: var(--fs-1);
  font-weight: 500;
  opacity: .85;
}

.tag[data-kind="person"] b {
  font-weight: 700;
}

.tag.lg[data-kind="person"] small {
  font-size: var(--fs-2);
}

/* C only: the 1 px neon line on the bottom edge of a navy band. */
.theme-night .appbar,
.theme-night .swath-navy,
.theme-night .sheet-head {
  box-shadow: var(--neon-line);
}

/* THE SHEET'S HEAD IS A NAVY FRAME (UI_SPEC 1.3 / 3.6: navy frames - the app bar, the tab bar, the next-up
   bands, the sheets' heads - with the neon line on the bottom edge). The body and the foot stay white. */
.theme-night .sheet-head {
  border-bottom: 0;
  background: var(--navy);
  color: var(--nink);
}

/* The close button in a navy head keeps a pale ink (it is on the navy now, not on the white body). */
.theme-night .sheet-head .btn-quiet {
  color: var(--nink2);
}

/* ---------- cards: the dark surface every section sits on ---------- */
.card {
  position: relative;
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: var(--card-bd, 1px solid var(--c-line));
  border-radius: var(--r-card, var(--r-md)); /* UI_SPEC 3.2; fallback for an unlinked tokens2 (RVW-U1 MINOR 7) */
  background: var(--c-surface);
  box-shadow: var(--card-sh, none); /* UI_SPEC 3.2: a border OR a shadow, never both */
}

/* A stage- or kind-coloured edge (the job's stage, a visit's kind). */
.card[data-accent]::before {
  content: "";
  position: absolute;
  top: 14px;
  bottom: 14px;
  left: -1px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}

a.card {
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}

a.card:hover {
  border-color: var(--c-line-strong);
  box-shadow: var(--shadow-2);
  transform: translateY(-1px);
}

.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-3);
  min-width: 0;
}

.card-title {
  font-size: var(--fs-4);
  font-weight: 650;
}

@media (min-width: 768px) {
  .card {
    padding: var(--sp-5);
  }
}

/* ---------- key-value rows (a <dl>): label left, value right in tabular figures, hairlines between ---------- */
.kv {
  display: grid;
  margin: 0;
}

.kv > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-1) var(--sp-4);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--c-line);
}

.kv > div:last-child {
  border-bottom: 0;
}

.kv dt {
  color: var(--c-text-3);
}

.kv dd {
  margin: 0;
  color: var(--c-text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  overflow-wrap: anywhere;
}

/* The bottom line (Total, What to pay): a strong rule above, a step up in size. */
.kv > .is-total {
  margin-top: var(--sp-1);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--c-line-strong);
  font-size: var(--fs-3);
}

.kv > .is-total dt {
  color: var(--c-text);
  font-weight: 650;
}

/* ---------- lists: rows with a main column and an end column; a row that is a link or a button gets a hover
   and a chevron ---------- */
.list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.list-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 52px;
  padding: var(--sp-2) 0;
  color: inherit;
  text-decoration: none;
}

/* One hairline under every row but the last, whether the row is the <li>, a link inside the <li>, or a
   plain child of the list - the line always rides the list's own child. */
.list > li,
.list-row {
  border-bottom: 1px solid var(--c-line);
}

.list > li:last-child,
.list-row:last-child,
.list > li > .list-row {
  border-bottom: 0;
}

a.list-row,
button.list-row {
  width: 100%;
  margin: 0;
  padding-inline: var(--sp-2);
  border-top: 0;
  border-right: 0;
  border-left: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease);
}

a.list-row:hover,
button.list-row:hover {
  background: var(--c-hover);
}

a.list-row::after,
button.list-row::after {
  content: "";
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  background: var(--c-text-3);
  -webkit-mask: var(--glyph-chevron) center / contain no-repeat;
  mask: var(--glyph-chevron) center / contain no-repeat;
}

.list-main {
  display: grid;
  flex: 1;
  gap: 2px;
  min-width: 0;
}

.list-title {
  color: var(--c-text);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.list-meta {
  color: var(--c-text-3);
  font-size: var(--fs-1);
}

.list-end {
  flex-shrink: 0;
  color: var(--c-text-2);
  font-weight: 600;
  text-align: right;
}

/* ---------- tables: a white sheet with a quiet column head, hairlines between rows, figures right-aligned.
   The wrapper scrolls sideways on a phone so the page itself never does. ---------- */
.table-wrap {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
  box-shadow: var(--shadow-1);
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-2);
}

.table th {
  padding: 10px 14px;
  border-bottom: 1px solid var(--c-column-line);
  background: var(--c-column);
  color: var(--c-text-2);
  font-size: var(--fs-1);
  font-weight: 600; /* UI_SPEC 3.4: a column head is 600 - and never caps (1.4) */
  letter-spacing: 0;
  text-align: left;
  white-space: nowrap;
}

.table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--c-line);
  color: var(--c-text);
  vertical-align: top;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table tbody tr:hover {
  background: var(--c-hover);
}

.table .num,
.table th.num {
  text-align: right;
}

/* ---------- PAPER: a document shown on a screen, the same white the PDF is printed on. It is a light scope
   (above), so any component placed on it takes light contrast by itself; its own ink is the paper set:
   text 17.85:1, muted 7.58:1, link 5.18:1. On a light swath a hairline edge keeps it a sheet, not a blur. ---------- */
.paper {
  --c-text-2: var(--c-paper-text);
  --c-text-3: var(--c-paper-muted);
  --c-placeholder: var(--c-paper-muted);
  --c-link: var(--c-paper-link);
  --c-focus: var(--c-paper-link);
  --c-line-strong: var(--c-paper-muted);
  position: relative;
  width: 100%;
  max-width: 820px;
  margin-inline: auto;
  padding: var(--sp-6) var(--sp-5);
  border: 1px solid var(--c-paper-line);
  border-radius: var(--r-md);
  background: var(--c-paper);
  box-shadow: var(--shadow-paper);
  color: var(--c-paper-text);
  font-size: 15px;
  line-height: 1.6;
}

.paper h1,
.paper h2,
.paper h3 {
  margin: var(--sp-6) 0 var(--sp-2);
  color: var(--c-paper-text);
  letter-spacing: 0;
}

.paper h1 {
  font-size: var(--fs-5);
}

.paper h2 {
  font-size: var(--fs-4);
}

.paper h3 {
  font-size: var(--fs-3);
}

.paper p,
.paper ul,
.paper ol {
  margin: 0 0 var(--sp-3);
}

.paper ul,
.paper ol {
  padding-left: 1.25em;
}

.paper hr {
  margin: var(--sp-5) 0;
  border: 0;
  border-top: 1px solid var(--c-paper-line);
}

.paper > :first-child {
  margin-top: 0;
}

.paper > :last-child {
  margin-bottom: 0;
}

@media (min-width: 768px) {
  .paper {
    padding: var(--sp-10) 56px;
    font-size: 16px;
  }
}

/* The signature pad: a white pad on the paper card, its boundary in paper-muted (7.58:1 - a control on paper
   is never outlined in the hairline), a baseline to sign above, and Clear. The canvas captures the finger,
   so the page never scrolls under a signature. */
.sigpad {
  position: relative;
  border-radius: var(--r-sm);
  background: var(--c-paper);
  color: var(--c-paper-muted);
}

.sigpad-canvas {
  display: block;
  width: 100%;
  height: 180px;
  border: 2px solid var(--c-paper-muted); /* UI_SPEC 4: only the Add zone and the hold hatch stay dashed (RVW-U1 HARD-RULES note, MINOR) */
  border-radius: inherit;
  cursor: crosshair;
  touch-action: none;
}

.sigpad:focus-within .sigpad-canvas,
.sigpad.is-signed .sigpad-canvas {
  border-style: solid;
}

.sigpad-line {
  position: absolute;
  right: var(--sp-6);
  bottom: 48px;
  left: var(--sp-6);
  border-top: 2px solid var(--c-paper-muted);
  pointer-events: none;
}

.sigpad-hint {
  position: absolute;
  bottom: var(--sp-4);
  left: var(--sp-6);
  font-size: var(--fs-1);
  pointer-events: none;
}

.sigpad.is-signed .sigpad-hint {
  visibility: hidden;
}

.sigpad-clear {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
}

@media (min-width: 768px) {
  .sigpad-canvas {
    height: 200px;
  }
}

/* ---------- money: tabular figures always; the hero number is one big figure with its label above ---------- */
.money {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-weight: 650;
  letter-spacing: -.01em;
  white-space: nowrap;
}

.money.is-credit {
  color: var(--c-good-text);
}

.money-hero {
  font-family: var(--font-display);
  font-size: var(--hero-fs);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.025em;
  white-space: nowrap;
}

.money-label {
  color: var(--c-text-2); /* 7.58:1 on a card */
  font-size: var(--fs-1);
  font-weight: 500; /* UI_SPEC 1.4: 500 for a label; no caps, no tracking */
  letter-spacing: 0;
}

.money-note {
  color: var(--c-text-3);
}

/* The hero card: a warm rust light from the top-left corner behind the number (text over it 11.75:1). */
.money-card {
  background-image: radial-gradient(ellipse 90% 70% at 0% 0%, rgba(176, 77, 30, .16), transparent 70%);
}

/* ---------- stepper: progress through named steps - a dot per step joined by a rail. Done = a green tick,
   the current step (aria-current="step") = an amber ring, upcoming = an open dot. The words carry the state
   too (each done step ends with a visually hidden "done"). ---------- */
.stepper {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.step {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 40px;
  color: var(--c-text-3);
  font-weight: 600;
}

.step + .step::before {
  content: "";
  position: absolute;
  bottom: 50%;
  left: 9px;
  width: 2px;
  height: 100%;
  background: var(--c-line);
}

.step.is-done + .step::before {
  background: var(--c-good-fill);
}

.step-dot {
  position: relative;
  z-index: var(--z-raised);
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 20px;
  height: 20px;
  border: 2px solid var(--c-line-strong);
  border-radius: 50%;
  background: var(--c-surface);
}

.step.is-done {
  color: var(--c-text);
}

.step.is-done .step-dot {
  border-color: var(--c-good-fill);
  background: var(--c-good-fill);
}

.step.is-done .step-dot::after {
  content: "";
  width: 12px;
  height: 12px;
  background: var(--c-on-primary);
  -webkit-mask: var(--glyph-check) center / contain no-repeat;
  mask: var(--glyph-check) center / contain no-repeat;
}

.step[aria-current="step"] {
  color: var(--c-text);
}

.step[aria-current="step"] .step-dot {
  border-color: var(--c-primary);
  box-shadow: 0 0 0 4px rgba(176, 77, 30, .22);
}

.step[aria-current="step"] .step-dot::after {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--c-primary);
}

/* ---------- the accept clock: an SVG ring (pathLength 100, so the arc is plain "minutes left" percent
   without arithmetic in the page) around the minutes AS TEXT. Urgent turns red and breathes; paused
   (Kyle is on the rig) goes quiet; late keeps the words "you can still accept". ---------- */
.ring {
  position: relative;
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: var(--ring-size);
  height: var(--ring-size);
}

.ring-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  rotate: -90deg;
}

.ring-track,
.ring-arc {
  fill: none;
  stroke-width: 8;
}

.ring-track {
  stroke: var(--c-line);
}

.ring-arc {
  stroke: var(--c-primary); /* 4.62:1 on the light canvas */
  stroke-linecap: round;
  filter: drop-shadow(0 0 6px rgba(176, 77, 30, .5));
  transition: stroke-dasharray var(--dur-4) var(--ease);
}

.ring-text {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 2px;
  max-width: 84%;
  text-align: center;
}

.ring-num {
  font-family: var(--font-display);
  font-size: 30px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.02em;
}

.ring-unit {
  color: var(--c-text-3);
  font-size: var(--fs-1);
  font-weight: 600;
  line-height: var(--lh-tight);
}

.ring.is-urgent .ring-arc {
  stroke: var(--c-danger-fill);
  filter: drop-shadow(0 0 6px rgba(168, 56, 44, .55));
  animation: ds-breathe 1600ms var(--ease) infinite;
}

.ring.is-urgent .ring-num {
  color: var(--c-danger-text);
}

.ring.is-paused .ring-arc {
  stroke: var(--c-text-3);
  filter: none;
}

.ring.is-paused .ring-num,
.ring.is-late .ring-num {
  font-size: var(--fs-4);
}

.ring.is-late .ring-track {
  stroke: var(--c-danger-line);
}

.ring.is-late .ring-num {
  color: var(--c-danger-text);
}

/* ---------- the two-state switch (SALES | RIG, Repair | Project, Off | On): a track with two options, the
   chosen one amber. The state is the ARIA attribute itself (aria-pressed, or aria-checked in a radiogroup),
   so what is drawn can never disagree with what a screen reader hears. ---------- */
.switch2 {
  display: inline-grid;
  grid-auto-columns: minmax(0, 1fr);
  grid-auto-flow: column;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-md);
  background: var(--c-column);
}

.switch2 > button {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 2px;
  min-height: 40px;
  padding: var(--sp-2) var(--sp-4);
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--c-text-2);
  font-weight: 700;
  line-height: var(--lh-tight);
  text-align: center;
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease), color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}

.switch2 > button:hover {
  background: var(--c-hover);
  color: var(--c-text);
}

.switch2 > button[aria-pressed="true"],
.switch2 > button[aria-checked="true"] {
  background: var(--c-selected-bg, var(--navy));
  box-shadow: none;
  color: var(--c-selected-ink, var(--nink)); /* A: navy + its ink; C: orange + navy ink */
}

/* The dangerous option (PAUSE ALL) turns red when chosen - its ink is --c-on-danger: ivory on the light
   danger fill (6.01:1), the bar navy on the navy one (5.95:1). */
.switch2 > button.is-danger[aria-pressed="true"],
.switch2 > button.is-danger[aria-checked="true"] {
  background: var(--c-danger-fill);
  box-shadow: none;
  color: var(--c-on-danger);
}

.switch2 > button:disabled {
  opacity: .55;
  cursor: not-allowed;
}

.switch2-sub {
  font-size: var(--fs-1);
  font-weight: 500;
}

/* Kyle's big one: full width, 56 px, capitals. */
.switch2.is-lg {
  display: grid;
  width: 100%;
}

.switch2.is-lg > button {
  min-height: 56px;
  font-size: var(--fs-4);
  letter-spacing: 0; /* UI_SPEC 1.4: no caps on a control label */
}

/* ---------- banners: a sentence about the page's state, with an icon and at most one action. Tones: info
   (default), .warn, .error, .offline (nothing saves until you are back), .sandbox (recorded - not sent) and
   .demo (the walkthrough: nothing is saved, ever - hazard stripes so it is never mistaken for the real app).
   A banner that brings its own icon (.i / .ico) suppresses the built-in one. ---------- */
.banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: 12px 14px;
  border: 1px solid var(--c-info-line);
  border-radius: var(--r-sm);
  background: var(--c-info-bg);
  color: var(--c-info-text); /* 6.00:1 light, 7.48:1 navy */
  line-height: var(--lh-snug);
}

/* The install tip's Dismiss (install.js): a full 44 px thumb target, not the bare 32 px button (NEO-935). */
[data-install-hint-dismiss] {
  min-height: 44px;
}

/* THE INSTALL STRIP IS LIGHT (FABLE_REVIEW 9: the tip used to be a dark brown box drawn with the NAVY
   scope's --c-warn-bg on a light page - "it looks like a rendering bug". The markup is a direct child of
   body, outside both scopes, so it is pinned here by its own hook): one line, the light info tint and ink
   (6.59:1), a control radius, no icon. */
.banner[data-install-hint] {
  align-items: center;
  gap: 8px 12px;
  margin: 0;
  padding: 8px 12px;
  border: 1px solid var(--l-info-line);
  border-radius: var(--r-ctl);
  background: var(--l-info-bg);
  color: var(--l-info-text);
  font-size: var(--fs-2);
  line-height: 1.3;
}

.banner[data-install-hint]::before {
  display: none;
}

.banner[data-install-hint] > p {
  flex: 1 1 auto;
}

.banner::before {
  content: "";
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  background: currentColor;
  -webkit-mask: var(--glyph-info) center / contain no-repeat;
  mask: var(--glyph-info) center / contain no-repeat;
}

.banner:has(> .i, > .ico)::before {
  display: none;
}

.banner > .i,
.banner > .ico {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
}

.banner > p,
.banner-text {
  flex: 1 1 220px;
  min-width: 0;
  font-weight: 550;
}

.banner .btn {
  flex-shrink: 0;
}

.banner.warn {
  border-color: var(--c-warn-line);
  background: var(--c-warn-bg);
  color: var(--c-warn-text); /* 6.72:1 light, 7.82:1 navy */
}

.banner.warn::before {
  -webkit-mask-image: var(--glyph-alert);
  mask-image: var(--glyph-alert);
}

.banner.error {
  border-color: var(--c-danger-line);
  background: var(--c-danger-bg);
  color: var(--c-danger-text); /* 6.40:1 light, 7.25:1 navy */
}

.banner.error::before {
  -webkit-mask-image: var(--glyph-alert);
  mask-image: var(--glyph-alert);
}

.banner.offline {
  border-color: var(--c-warn-line);
  background: var(--c-warn-bg);
  color: var(--c-warn-text);
}

.banner.offline::before {
  -webkit-mask-image: var(--glyph-offline);
  mask-image: var(--glyph-offline);
}

.banner.sandbox {
  border: 1px solid var(--c-line-strong);
  background: var(--c-chip);
  color: var(--c-chip-text); /* 9.68:1 */
}

.banner.sandbox::before {
  -webkit-mask-image: var(--glyph-pause);
  mask-image: var(--glyph-pause);
}

.banner.demo {
  border-color: var(--amber-d);
  background: repeating-linear-gradient(-45deg, var(--c-primary) 0 14px, var(--c-primary-hover) 14px 28px);
  color: var(--c-on-primary); /* ivory on both stripes: 5.00 and 6.09:1 */
  font-weight: 700;
}

.banner.demo::before {
  -webkit-mask-image: var(--glyph-eye);
  mask-image: var(--glyph-eye);
}

/* ---------- toasts: the one live region, bottom centre, above a tab bar when there is one ---------- */
.toasts {
  position: fixed;
  bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  left: 50%;
  z-index: var(--z-toast);
  display: grid;
  gap: var(--sp-2);
  width: min(440px, calc(100% - 32px));
  translate: -50% 0;
  pointer-events: none;
}

body:has(> .tabbar) .toasts {
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--sp-3));
}

/* A toast is navy on every surface (it is in the navy scope): cream ink on --navy, 15.07:1, with a coloured
   edge for its tone. */
.toast {
  display: block;
  padding: 12px 16px;
  border: 1px solid var(--n-rule-strong);
  border-left: 3px solid var(--c-primary);
  border-radius: var(--r-md);
  background: var(--navy);
  box-shadow: var(--shadow-3);
  color: var(--c-text);
  pointer-events: auto;
  animation: ds-toast-in var(--dur-3) var(--ease);
}

.toast--good {
  border-left-color: var(--c-good-fill);
}

.toast--error {
  border-left-color: var(--c-danger-fill);
}

.toast--info {
  border-left-color: var(--c-warn-fill);
}

.toast.is-leaving {
  animation: ds-toast-out var(--dur-2) var(--ease-in) forwards;
}

/* ---------- loading: skeleton bars fade in only after 300 ms (a fast answer never flashes) and shimmer
   until the content lands. An EMPTY .skeleton element is itself a bar; a .skeleton with children is the
   group (role="status" + a hidden "Loading" for assistive tech). ---------- */
.skeleton {
  display: grid;
  gap: 10px;
}

.sk,
.skeleton:empty {
  display: block;
  min-height: 12px;
  border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--c-chip) 0%, var(--c-line) 50%, var(--c-chip) 100%);
  background-size: 200% 100%;
  animation: ds-appear var(--dur-3) var(--ease) 300ms both, ds-shimmer 1200ms ease-in-out infinite;
}

.sk.is-short {
  width: 58%;
}

.sk-h {
  width: 40%;
  min-height: 16px;
  margin-top: var(--sp-1);
}

.sk-block {
  min-height: 56px;
  border-radius: var(--r-sm);
}

.sk-actions {
  min-height: var(--tap);
  border-radius: var(--r-sm);
}

/* Empty: says what will appear here and, when there is one, offers the action that fills it. */
.empty {
  display: grid;
  justify-items: center;
  gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-5);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-md);
  color: var(--c-text-3);
  text-align: center;
}

.empty::before,
.empty::after {
  content: "";
  grid-area: 1 / 1;
}

.empty::before {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--c-chip);
  box-shadow: inset 0 0 0 1px var(--c-line);
}

.empty::after {
  place-self: center;
  width: 22px;
  height: 22px;
  background: var(--c-text-3);
  -webkit-mask: var(--glyph-inbox) center / contain no-repeat;
  mask: var(--glyph-inbox) center / contain no-repeat;
}

.empty-title {
  color: var(--c-text);
  font-size: var(--fs-3);
  font-weight: 650;
}

.empty-text {
  max-width: 40ch;
}

.empty .btn {
  margin-top: var(--sp-2);
}

/* An .empty may name its own glyph (vis-4): the base paints an inbox, these paint the screen's glyph. Each
   re-declares only mask-image, after the base rule, so it wins on source order at equal specificity. */
.empty--lock::after {
  -webkit-mask-image: var(--glyph-lock);
  mask-image: var(--glyph-lock);
}

.empty--wallet::after {
  -webkit-mask-image: var(--glyph-wallet);
  mask-image: var(--glyph-wallet);
}

.empty--user::after {
  -webkit-mask-image: var(--glyph-user);
  mask-image: var(--glyph-user);
}

/* ---------- sheet: a modal <dialog> opened by ds.js sheet() - a bottom sheet on a phone (a grabber, the
   actions stacked full width), a centred dialog from 768 px. The page under it cannot scroll. ---------- */
.sheet {
  width: min(560px, calc(100% - 48px));
  max-width: none;
  max-height: calc(100dvh - 48px);
  margin: auto;
  padding: 0;
  overflow: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  box-shadow: var(--shadow-3);
  color: var(--c-text);
}

.sheet[open] {
  display: flex;
  flex-direction: column;
  animation: ds-pop var(--dur-3) var(--ease);
}

.sheet::backdrop {
  background: var(--c-scrim, rgba(2, 6, 23, .72));
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

.sheet[open]::backdrop {
  animation: ds-appear var(--dur-3) var(--ease);
}

html:has(.sheet[open]) {
  overflow: hidden;
}

.sheet-head {
  position: sticky;
  top: 0;
  z-index: var(--z-raised);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-3) var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--c-line);
  background: var(--c-surface);
}

.sheet-title {
  padding-top: 6px;
  font-size: var(--fs-4);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.sheet-body {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
}

.sheet-foot {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-top: auto;
  padding: var(--sp-3) var(--sp-5) max(var(--sp-3), env(safe-area-inset-bottom));
  border-top: 1px solid var(--c-line);
  background: var(--c-surface);
}

@media (max-width: 767px) {
  .sheet {
    width: 100%;
    max-height: calc(100dvh - var(--sp-6));
    margin: auto 0 0;
    border-width: 1px 0 0;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
  }

  .sheet[open] {
    animation: ds-sheet-up var(--dur-4) var(--ease);
  }

  .sheet-head {
    padding-top: var(--sp-6);
  }

  .sheet-head::before {
    content: "";
    position: absolute;
    top: var(--sp-2);
    left: 50%;
    width: 40px;
    height: 4px;
    border-radius: var(--r-pill);
    background: var(--c-line-strong);
    translate: -50% 0;
  }

  .sheet-foot {
    flex-direction: column-reverse;
  }

  .sheet-foot .btn {
    width: 100%;
  }
}

/* ---------- the morning strip (owner, above the board): the day in one row of tiles, each a link (or the
   Alerts toggle) whose text is its whole accessible name. A tile that needs him gets a coloured top edge
   and a coloured figure - amber, or red for Alerts. While loading, the figure is a shimmer. ---------- */
.strip {
  display: grid;
  gap: var(--sp-3);
}

.strip-tiles {
  display: grid;
  grid-auto-columns: minmax(152px, 1fr);
  grid-auto-flow: column;
  gap: var(--sp-3);
  margin: 0;
  padding: 2px 2px var(--sp-2);
  overflow-x: auto;
  list-style: none;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}

.strip-tile {
  display: grid;
  scroll-snap-align: start;
}

.strip-link {
  position: relative;
  display: grid;
  align-content: start;
  gap: 2px;
  width: 100%;
  min-height: 92px;
  margin: 0;
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  overflow: hidden;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
  box-shadow: var(--shadow-1);
  color: var(--c-text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}

.strip-link::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 3px;
}

.strip-link:hover {
  border-color: var(--c-line-strong);
  box-shadow: var(--shadow-2);
  transform: translateY(-1px);
}

.strip-k {
  color: var(--c-text-3); /* 5.43:1 on a card, 5.02:1 on the ground */
  font-size: var(--fs-1);
  font-weight: 500; /* UI_SPEC 1.4: a label is 500 - no caps, no tracking */
  letter-spacing: 0;
}

.strip-v {
  font-family: var(--font-display);
  font-size: 28px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.02em;
}

.strip-v--word {
  font-size: var(--fs-4);
  line-height: var(--lh-snug);
}

.strip-sub {
  color: var(--c-text-3);
  font-size: var(--fs-1);
}

.strip-tile.needs-you .strip-link {
  border-color: var(--c-warn-line);
}

.strip-tile.needs-you .strip-link::before {
  background: var(--c-warn-fill);
}

.strip-tile.needs-you .strip-v {
  color: var(--c-warn-text); /* 7.71:1 on the ivory tile */
}

.strip-tile--alert.needs-you .strip-link {
  border-color: var(--c-danger-line);
}

.strip-tile--alert.needs-you .strip-link::before {
  background: var(--c-danger-fill);
}

.strip-tile--alert.needs-you .strip-v {
  color: var(--c-danger-text); /* 7.78:1 on the ivory tile */
}

.strip-tile.is-loading .strip-v {
  justify-self: start;
  min-width: 3ch;
  border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--c-chip) 0%, var(--c-line) 50%, var(--c-chip) 100%);
  background-size: 200% 100%;
  color: transparent;
  animation: ds-shimmer 1200ms ease-in-out infinite;
}

.strip-allclear {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-good-line);
  border-radius: var(--r-md);
  background: var(--c-good-bg);
  color: var(--c-good-text); /* 6.46:1 light, 7.97:1 navy */
  font-weight: 600;
}

.strip-alert-list {
  display: grid;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.strip-alert-link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-danger-line);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  color: var(--c-text);
  text-decoration: none;
}

.strip-alert-link:hover {
  background: var(--c-surface-2);
}

.strip-alert-words {
  font-weight: 600;
}

.strip-alert-earlier {
  padding-top: var(--sp-2);
  color: var(--c-text-3);
  font-size: var(--fs-1);
  font-weight: 700;
}

.strip-alert.is-seen .strip-alert-link {
  border-color: var(--c-line);
}

.strip-alert.is-seen .strip-alert-words {
  font-weight: 400;
}

.strip-alert-when {
  flex-shrink: 0;
  color: var(--c-text-3);
  font-size: var(--fs-1);
}

@media (min-width: 1180px) {
  .strip-tiles {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    grid-auto-flow: row;
    overflow: visible;
  }
}

/* ---------- the Kyle app's tab bar: Today / Leads / Money / Me, fixed to the bottom over the home
   indicator. The current tab is the accent ink with a bar above it.

   RVW-U1 BLOCKER 2: UI_SPEC 3.2 paints the tab bar navy (`background: var(--navy)` below), but the light
   scope at :159 lists .tabbar, so every label took the LIGHT ink and the bar went dark on near-black
   (#4d5360 on #0b1220 = 2.43:1, hover 1.20:1, far under the 4.5:1 floor). tests/design_contrast_test.py
   pins that selector list verbatim (LIGHT_SCOPE), so .tabbar stays in it and this rule restates the navy
   family on the element itself: same specificity (0,1,0), later in the sheet, so it wins and the bar's
   ink is its own. Measured: --n-text-3 #8c97a8 on --navy #0b1220 = 6.34:1 (the quietest label),
   --n-text-2 10.09:1, --n-text 17.89:1, the current tab --or-n #fb923c = 8.27:1. The fallbacks hold the
   S0a navy ink until tokens2.css is linked (15.07 / 11.74 / 8.98 / 8.44 on the S0a --navy #121c32). ---------- */
.tabbar {
  --c-canvas: var(--navy);
  --c-surface: var(--navy-2, var(--navy));
  --c-surface-2: var(--navy-2, var(--navy));
  --c-column: var(--navy-2, var(--navy));
  --c-line: var(--nline, var(--n-rule));
  --c-line-strong: var(--nline2, var(--n-rule-strong));
  --c-text: var(--nink, var(--n-text));
  --c-text-2: var(--nink2, var(--n-text-2));
  --c-text-3: var(--nink3, var(--n-text-3));
  --c-placeholder: var(--nink3, var(--n-text-3));
  --c-link: var(--or-n, var(--c-brand));
  --c-focus: var(--or-n, var(--c-brand));
  --c-accent-line: var(--or, var(--c-primary));
  color: var(--c-text);
  color-scheme: dark;
}

/* ---------- the Kyle app's tab bar: Today / Leads / Money / Me, fixed to the bottom over the home
   indicator. The current tab is the accent ink with a bar above it; its ink comes from the navy family
   restated just above. ---------- */
.tabbar {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: var(--z-sticky);
  display: grid;
  grid-auto-columns: minmax(0, 1fr);
  grid-auto-flow: column;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  border-top: 1px solid var(--nline, var(--n-rule));
  background: var(--navy); /* UI_SPEC s1.1: the tab bar is navy in A; C adds the neon inset line */
  box-shadow: none;
}

.tabbar > .tab {
  position: relative;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 2px;
  min-height: var(--tap);
  color: var(--c-text-3);
  font-size: var(--fs-1);
  font-weight: 600;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-2) var(--ease);
}

.tabbar > .tab .ico {
  width: 24px;
  height: 24px;
}

.tabbar > .tab:hover {
  color: var(--c-text);
}

.tabbar > .tab[aria-current="page"] {
  color: var(--or-n, var(--c-brand)); /* orange on navy, 8.3:1 */
  font-weight: 600;
}

.tabbar > .tab[aria-current="page"]::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 32px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--or, var(--c-primary));
  translate: -50% 0;
}

.tab-badge {
  position: absolute;
  top: 6px;
  left: calc(50% + 6px);
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--c-danger-fill);
  color: var(--c-on-danger); /* 5.95:1, the bar navy on the tab bar's danger fill */
  font-size: var(--fs-1);
  font-weight: 700;
  line-height: 20px;
  text-align: center;
}

/* A phone-sized frame on a desktop (the walkthrough, the style guide): layout containment makes it the
   containing block of its fixed tab bar, which therefore sits at the frame's foot, not the window's. */
.device {
  position: relative;
  display: grid;
  width: min(100%, 390px);
  height: min(760px, calc(100dvh - 120px));
  margin-inline: auto;
  overflow: hidden;
  border: 1px solid var(--c-line-strong);
  border-radius: 32px;
  background: var(--c-canvas);
  box-shadow: var(--shadow-3);
  contain: layout paint;
}

.device-screen {
  display: grid;
  align-content: start;
  gap: var(--sp-4);
  min-height: 0;
  padding: var(--sp-4) var(--sp-4) calc(var(--tabbar-h) + var(--sp-6));
  overflow: auto;
  overscroll-behavior: contain;
}

/* ---------- touch: every control is a 44 px target on a phone or a coarse pointer, and inputs are 16 px so
   iOS never zooms the page on focus ---------- */
@media (max-width: 900px), (pointer: coarse) {
  .btn,
  .btn-sm,
  .chip-pick,
  .switch,
  .switch2 > button {
    min-height: var(--tap);
  }

  .switch {
    align-items: center;
  }

  .btn-icon,
  .btn-sm.btn-icon {
    min-width: var(--tap);
  }

  .input {
    min-height: var(--tap);
    font-size: 16px;
  }
}

/* ---------- print: paper is white (tokens.css swaps to the light set); the chrome does not print ---------- */
@media print {
  .appbar,
  .tabbar,
  .toasts,
  .skip-link,
  .btn {
    display: none !important;
  }

  .paper {
    max-width: none;
    padding: 0;
    box-shadow: none;
  }

  .card,
  .strip-link {
    box-shadow: none;
    break-inside: avoid;
  }

  .money-card {
    background-image: none;
  }
}

/* ---------- motion (ds- names, so a page's own keyframes can never replace these) ---------- */
@keyframes ds-spin {
  to {
    rotate: 360deg;
  }
}

@keyframes ds-shimmer {
  from {
    background-position: 100% 0;
  }

  to {
    background-position: -100% 0;
  }
}

@keyframes ds-appear {
  from {
    opacity: 0;
  }
}

@keyframes ds-pop {
  from {
    opacity: 0;
    transform: translateY(8px) scale(.98);
  }
}

@keyframes ds-sheet-up {
  from {
    transform: translateY(100%);
  }
}

@keyframes ds-toast-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(.98);
  }
}

@keyframes ds-toast-out {
  to {
    opacity: 0;
    transform: translateY(8px);
  }
}

@keyframes ds-breathe {
  50% {
    opacity: .55;
  }
}
