/*
 * Fonts — contract §6.9.
 *
 * IBM Plex Sans (400/500/600/700) and IBM Plex Mono (400/500/600),
 * self-hosted as woff2 under packages/ui/fonts/, served from our own origin.
 * Do not add a <link> to fonts.googleapis.com anywhere that imports this file.
 *
 * font-display: swap is an invented convention — the contract does not
 * specify a loading strategy. Swap was chosen as the smallest reasonable
 * choice that avoids invisible text while the woff2 downloads.
 */

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/ibm-plex-sans-400-a2c8606cdfd70cfe.woff2") format('woff2');
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/ibm-plex-sans-500-9fb99bacc82fdf20.woff2") format('woff2');
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/ibm-plex-sans-600-f9ae023bf8c1b92b.woff2") format('woff2');
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/ibm-plex-sans-700-823e2efeae7dccdd.woff2") format('woff2');
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/ibm-plex-mono-400-1afad13b8fe04a38.woff2") format('woff2');
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/ibm-plex-mono-500-e627b3db6d8b2b1f.woff2") format('woff2');
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/ibm-plex-mono-600-48f9df33ad49932a.woff2") format('woff2');
}

/*
 * Design tokens — contract §6.2.
 *
 * One token set for the whole product (consumer, technician, CMS, public site).
 * Dark is the default theme. `[data-theme="light"]` overrides to light.
 * The CMS opens light: the `:root:not([data-theme='dark'])` block below
 * restates the light values as its default. The guide editor's phone
 * preview (`.cms-consumer-screen`) restates the consumer dark values.
 *
 * Every colour token carries an `-rgb` twin with the same colour, for the
 * dialog scrim and the hover ring at 50% (§6.2). `--hair` is already a
 * colour with alpha and has no twin; `--scrim-rgb` is rgb already.
 */

:root,
[data-theme='dark'] {
  --bg: #0f241c;
  --bg-rgb: 15, 36, 28;
  --tile: #1b3a2f;
  --tile-rgb: 27, 58, 47;
  --ink: #f1e9d6;
  --ink-rgb: 241, 233, 214;
  --muted-1: #cfd9cc;
  --muted-1-rgb: 207, 217, 204;
  --muted-2: #a9b6a8;
  --muted-2-rgb: 169, 182, 168;
  --muted-3: #8a9a8c;
  --muted-3-rgb: 138, 154, 140;
  --link: #c9b482;
  --link-rgb: 201, 180, 130;
  --hair: rgba(241, 233, 214, 0.18);
  --primary: #7a5a17;
  --primary-rgb: 122, 90, 23;
  --on-primary: #f6efdc;
  --on-primary-rgb: 246, 239, 220;
  --scrim-rgb: 6, 20, 16;
}

[data-theme='light'] {
  --bg: #f1e9d6;
  --bg-rgb: 241, 233, 214;
  --tile: #faf6ec;
  --tile-rgb: 250, 246, 236;
  --ink: #10261e;
  --ink-rgb: 16, 38, 30;
  --muted-1: #2f4a3e;
  --muted-1-rgb: 47, 74, 62;
  --muted-2: #4f6157;
  --muted-2-rgb: 79, 97, 87;
  --muted-3: #6b7a70;
  --muted-3-rgb: 107, 122, 112;
  --link: #7a5a17;
  --link-rgb: 122, 90, 23;
  --hair: rgba(16, 38, 30, 0.16);
  --primary: #7a5a17;
  --primary-rgb: 122, 90, 23;
  --on-primary: #f6efdc;
  --on-primary-rgb: 246, 239, 220;
  --scrim-rgb: 6, 20, 16;
}

/*
 * CMS light default (contract §6.10): the CMS opens light. This restates
 * the §6.2 light values so the CMS opens light without beating
 * tokens.css's dark block after the theme link switches; the dark theme
 * falls through to the dark block above. The trailing `:not(:has(.site))`
 * keeps this off the public site: site pages never set `data-theme` (they
 * follow the device in CSS alone), so a bare `:root:not([data-theme])`
 * rule would pin them light in dark mode.
 */
:root:not([data-theme='dark']):not(:has(.site)) {
  --bg: #f1e9d6;
  --bg-rgb: 241, 233, 214;
  --tile: #faf6ec;
  --tile-rgb: 250, 246, 236;
  --ink: #10261e;
  --ink-rgb: 16, 38, 30;
  --muted-1: #2f4a3e;
  --muted-1-rgb: 47, 74, 62;
  --muted-2: #4f6157;
  --muted-2-rgb: 79, 97, 87;
  --muted-3: #6b7a70;
  --muted-3-rgb: 107, 122, 112;
  --link: #7a5a17;
  --link-rgb: 122, 90, 23;
  --hair: rgba(16, 38, 30, 0.16);
  --primary: #7a5a17;
  --primary-rgb: 122, 90, 23;
  --on-primary: #f6efdc;
  --on-primary-rgb: 246, 239, 220;
  --scrim-rgb: 6, 20, 16;
}

/*
 * The guide editor's phone preview (§6.10) shows the real consumer screen
 * in the consumer tokens at 375px, so it restates every §6.2 dark value.
 */
.cms-consumer-screen[data-theme='dark'] {
  --bg: #0f241c;
  --bg-rgb: 15, 36, 28;
  --tile: #1b3a2f;
  --tile-rgb: 27, 58, 47;
  --ink: #f1e9d6;
  --ink-rgb: 241, 233, 214;
  --muted-1: #cfd9cc;
  --muted-1-rgb: 207, 217, 204;
  --muted-2: #a9b6a8;
  --muted-2-rgb: 169, 182, 168;
  --muted-3: #8a9a8c;
  --muted-3-rgb: 138, 154, 140;
  --link: #c9b482;
  --link-rgb: 201, 180, 130;
  --hair: rgba(241, 233, 214, 0.18);
  --primary: #7a5a17;
  --primary-rgb: 122, 90, 23;
  --on-primary: #f6efdc;
  --on-primary-rgb: 246, 239, 220;
  --scrim-rgb: 6, 20, 16;
}

/*
 * Type scale — contract §6.6, plus the Större-text multiplier from §6.11.
 *
 * `--ui-text-scale` is a root-level multiplier (default 1) set by
 * `src/text-size.ts`. Every role's font-size is expressed as
 * `calc(<base> * var(--ui-text-scale, 1))` so the whole ramp scales together.
 * Line heights are unitless ratios and scale automatically with the
 * font-size they apply to — no separate handling needed.
 *
 * A component names a role via one of these classes and MUST NOT set a raw
 * `font-size` (§6.6). `packages/ui` does not know about React; components
 * apply these class names directly or compose them into their own
 * component classes.
 *
 * Only §6.6 names tracking: display −0.025em, statement −0.015em. The other
 * roles set no letter-spacing.
 */

:root {
  --ui-text-scale: 1;

  --ui-font-sans:
    'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --ui-font-mono:
    'IBM Plex Mono', ui-monospace, 'SFMono-Regular', 'Consolas', monospace;
}

/* The Start hero headline only. */
.ui-type-display {
  font-family: var(--ui-font-sans);
  font-size: calc(40px * var(--ui-text-scale, 1));
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: #f1e9d6;
}

/* One per screen: the question or the finding. */
.ui-type-statement {
  font-family: var(--ui-font-sans);
  font-size: calc(26px * var(--ui-text-scale, 1));
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--ink);
}

@media (min-width: 720px) {
  .ui-type-display {
    font-size: calc(68px * var(--ui-text-scale, 1));
  }

  .ui-type-statement {
    font-size: calc(34px * var(--ui-text-scale, 1));
  }
}

/* Names a group of several items. Never coloured, never uppercase. */
.ui-type-title {
  font-family: var(--ui-font-sans);
  font-size: calc(16px * var(--ui-text-scale, 1));
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}

/* All flowing text, and a value beside a label. */
.ui-type-body {
  font-family: var(--ui-font-sans);
  font-size: calc(16px * var(--ui-text-scale, 1));
  font-weight: 400;
  line-height: 1.5;
  color: var(--muted-1);
}

/* The label of anything tappable. Links underline in --link. */
.ui-type-control {
  font-family: var(--ui-font-sans);
  font-size: calc(16px * var(--ui-text-scale, 1));
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink);
}

/* Only a timestamp, the brand·model line, or a section label. */
.ui-type-meta {
  font-family: var(--ui-font-sans);
  font-size: calc(14px * var(--ui-text-scale, 1));
  font-weight: 400;
  line-height: 1.4;
  color: var(--muted-2);
}

/*
 * Invariants — contract §6.5.
 *
 * Tokens for the values, plus the small set of utility classes needed to
 * apply the compound invariants (sticky header, app background) consistently.
 * These are not components — C2–C4 compose them.
 */

:root {
  --ui-radius: 3px;

  --ui-focus-ring-width: 2px;
  --ui-focus-ring-offset: 2px;

  /* No shadows anywhere (§6.5) except the dialog scrim. */
  --ui-shadow-sm: none;

  --ui-tap-target: 44px;
  --ui-tap-target-mobile-header: 46px;

  --ui-sticky-blur: 14px;
  /* rgba(var(--bg-rgb), .92) per §6.5 — bg-rgb is theme-aware from tokens.css */
  --ui-sticky-bg: rgba(var(--bg-rgb), 0.92);
}

/*
 * Focus ring — "Never remove it" (§6.5). Applied as a global default on
 * every focusable element via :focus-visible so no component has to opt in,
 * and no component may opt out without contract review.
 */
:focus-visible {
  outline: var(--ui-focus-ring-width) solid var(--link);
  outline-offset: var(--ui-focus-ring-offset);
}

/*
 * Minimum tap target. Apply to any interactive element; does not shrink at
 * any Större-text setting because it is independent of --ui-text-scale.
 */
.ui-tap-target {
  min-width: var(--ui-tap-target);
  min-height: var(--ui-tap-target);
}

.ui-tap-target--mobile-header {
  min-width: var(--ui-tap-target-mobile-header);
  min-height: var(--ui-tap-target-mobile-header);
}

/* Sticky header treatment. */
.ui-sticky-header {
  position: sticky;
  top: 0;
  background: var(--ui-sticky-bg);
  backdrop-filter: blur(var(--ui-sticky-blur));
  -webkit-backdrop-filter: blur(var(--ui-sticky-blur));
}

/* App page background: flat `var(--bg)` (calm-ui, contract §6.5). */
.ui-app-background {
  background: var(--bg);
}

/*
 * Motion — contract §6.8.
 *
 * Keyframes ported verbatim from the <style> blocks in the design files
 * (extracted with a balanced-brace parse of the minified CSS, not
 * approximated). Only the animations §6.8 names are ported; C2-C4 apply
 * `animation-name` / `animation-duration` etc. to the elements that use
 * them, since no components exist in this task.
 *
 * Source-file note on `sheetUp`: §6.8 says "the CMS uses its own set
 * including cmsRise and sheetUp", implying both live in the CMS design.
 * `cmsRise` does. `sheetUp` does not — it is absent from the CMS design and
 * only defined in the technician one (which this task was not asked
 * to read). This is reported as a contract defect in the executor report;
 * the keyframe below is ported from the technician design because it does
 * exist verbatim there, which is more faithful to "port, do not invent"
 * than filing a false "missing keyframe" deviation would have been.
 *
 * The CMS design also declares `cmsFade` and `cmsSpin`, which §6.8 does not
 * name. They are not ported here — see the executor report.
 *
 * Calm (owner, 2026-10-02, card *CalmUI integrated*): the decorative loops
 * (`felsokPulse`, `felsokBreathe`, `webBreathe`) are removed with their uses;
 * `webBar` stays as the `AudioPlayer`'s playback feedback (§7.2) and the
 * spinner rotates. Every remaining animation MUST respect
 * `prefers-reduced-motion: reduce` (below).
 */

/* -- public site rise -- */
@keyframes webRise {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

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

/*
 * `webBar` stays: it is not decoration but the `AudioPlayer`'s playback
 * feedback (§7.2) — the bars bounce only while the recording plays, and
 * under `prefers-reduced-motion` they stay still and played bars take the
 * accent colour. The decorative loops (`felsokPulse`, `felsokBreathe`,
 * `webBreathe`) are gone (calm-ui, contract §6.8); the spinner reports
 * work by rotating instead.
 */
@keyframes webBar {
  0%,
  100% {
    transform: scaleY(0.4);
  }
  50% {
    transform: scaleY(1);
  }
}

/* -- nav transitions (shared with technician) -- */
@keyframes navNextA {
  from {
    opacity: 0;
    transform: translateX(48px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes navNextB {
  from {
    opacity: 0;
    transform: translateX(48px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes navPrevA {
  from {
    opacity: 0;
    transform: translateX(-48px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes navPrevB {
  from {
    opacity: 0;
    transform: translateX(-48px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* -- consumer app rise -- */
@keyframes felsokRise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes felsokSlideLeft {
  from {
    opacity: 0;
    transform: translateX(-18px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* -- CMS rise -- */
@keyframes cmsRise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* -- sheet rise (see file-header note on sheetUp's source) -- */
@keyframes sheetUp {
  from {
    transform: translateY(102%);
  }
  to {
    transform: none;
  }
}

/*
 * Every animation collapses under prefers-reduced-motion: reduce.
 * A universal duration/iteration clamp is used rather than per-animation
 * reduced variants, because no component exists yet to know which element
 * plays which animation — see the executor report for this as an invented
 * convention. This still satisfies §6.8: motion collapses to (effectively)
 * nothing, and any transform/opacity end-state resolves immediately.
 * The delay is cleared too: a kept stagger (`.ui-in`) turns the screen
 * entrance into a top-to-bottom pop-in, which is still motion.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/*
 * Component styles — contract §6.3, §6.5, §6.6, §6.7, §6.12 and §7.
 *
 * Rules this file obeys (guarded by test/components-css.test.ts):
 * - No hex colour. Every colour is a `var(--*)` token or a
 *   `rgba(var(--*-rgb), …)` composite. The only exception is the dialog
 *   scrim, which composites the `--scrim-rgb` token (§6.2 defines it as
 *   rgb precisely for this).
 * - No `font-size`. Type comes from the `ui-type-*` role classes in
 *   `type-scale.css` (§6.6), applied in markup. Weight here is only
 *   400/500/600: 700 is not used (§6.6).
 * - No hardcoded radius. Every corner is `var(--ui-radius)` (§6.5: 3px),
 *   `50%` for the spinner ring and the list-item dots, or `0`.
 * - No shadows, no blur, no translucency except the dialog scrim (§6.5).
 * - No hover lift, no `brightness()` (§6.12): hover is the ring at 50%
 *   or an underline.
 * - No tint or alpha fills for depth (§6.3): a tile is a fill, a list is
 *   hairlines, selection is the inset 1.5px `--link` ring.
 * - Focus ring: every interactive element keeps the global `:focus-visible`
 *   rule from `invariants.css` (`2px solid var(--link)`, offset `2px`).
 *   This file never sets `outline: none` — the guard test fails on it.
 * - Tap targets: every control is at least `44px` (§6.5).
 */

/* Buttons — §6.7: primary, link, back. */

.ui-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  border-radius: var(--ui-radius);
  font-weight: 500;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
}

/* `size` is gone from the look (§7.1): every size renders the 52px
   primary. The classes stay so callers passing `size` keep working. */
.ui-button--md,
.ui-button--lg,
.ui-button--hero {
  min-height: 52px;
  padding: 0 26px;
}

.ui-button--full-width {
  width: 100%;
}

.ui-button--primary {
  min-height: 52px;
  padding: 0 26px;
  border: 0;
  background: var(--primary);
  color: var(--on-primary);
  font-weight: 600;
}

.ui-button--primary:hover {
  box-shadow: 0 0 0 1.5px rgba(var(--link-rgb), 0.5);
}

/*
 * A primary whose label is wider than its slot (card #77: `"Jag har inte
 * provat något"` at 320px) wraps its label instead of pushing the screen
 * sideways. Labels that fit render exactly as before.
 */
@media (max-width: 719px) {
  .ui-button--primary {
    white-space: normal;
    min-width: 0;
  }
}

.ui-button--link {
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--link);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  white-space: normal;
}

.ui-button--back {
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--muted-2);
  font-weight: 500;
}

/* A destructive link is ink (§6.7). Kept underlined: it is still a link. */
.ui-button--ink {
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  white-space: normal;
}

.ui-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: none;
}

.ui-button__icon {
  display: inline-flex;
  align-items: center;
  flex: none;
}

/*
 * ActionRow — §6.7. One line: Back left, the primary right, one link
 * between them when it fits at 375px. DOM order is visual order — no
 * `order` reordering, so keyboard focus follows what is seen (WCAG 2.4.3).
 * `ActionRow` measures the row and moves a link that does not fit onto
 * its own left-aligned line under the row (`.ui-action-row__overflow`).
 * On phones the spacer hides and the primary takes the remaining width.
 */
.ui-action-row {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  column-gap: 16px;
  row-gap: 8px;
  padding-top: 4px;
}

.ui-action-row > :first-child:not(.ui-action-row__spacer) {
  flex: none;
}

.ui-action-row__spacer {
  flex: 1 1 8px;
  min-width: 0;
}

/*
 * No primary (§6.7, owner 2026-10-06): with Back and no primary, a link
 * sits right after Back at its own width — the spacer stays out, so on a
 * computer nothing pushes the link into the primary's far-right slot.
 * Rows with a primary, and rows without Back, are untouched.
 */
.ui-action-row:has(.ui-button--back):not(:has(.ui-button--primary)) .ui-action-row__spacer {
  display: none;
}

/*
 * The overflowing link's own line (§6.7): left-aligned under the row,
 * several side by side 24px apart. `nowrap` keeps `scrollWidth` an exact
 * measure of the links, which `ActionRow` uses to move them back inline
 * once they fit again — but only because the slot shrink-wraps its
 * content (`width: max-content`, capped at the row): a block-level slot
 * would report the full row width instead and the link would never move
 * back. `max-width: 100%` keeps a longer-than-the-row link capped, so
 * `scrollWidth` stays its true width.
 */
.ui-action-row__overflow {
  display: flex;
  flex-wrap: nowrap;
  justify-content: flex-start;
  gap: 8px 24px;
  margin-top: 8px;
  width: max-content;
  max-width: 100%;
}

/*
 * Row links keep their labels on one line and never shrink below their
 * content (`flex: none`): a link that does not fit overflows the row, so
 * `ActionRow` can measure it (`scrollWidth`) and move it onto its own
 * left-aligned line under the row. The primary keeps the base `nowrap`.
 */
.ui-action-row .ui-button--link,
.ui-action-row .ui-button--ink {
  flex: none;
  white-space: nowrap;
  text-align: left;
}

@media (max-width: 719px) {
  .ui-action-row__spacer {
    display: none;
  }

  .ui-action-row > :last-child:not(:first-child) {
    flex: 1 1 auto;
  }

  /*
   * No primary (§6.7, owner 2026-10-06): on a phone the link keeps its
   * own width next to Back instead of taking the primary's
   * remaining-width growth. Rows with a primary are untouched.
   */
  .ui-action-row:has(.ui-button--back):not(:has(.ui-button--primary)) > :last-child:not(:first-child) {
    flex: none;
  }
}

.ui-icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  flex: none;
  border: 0;
  border-radius: var(--ui-radius);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.ui-icon-button--sm {
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
}

.ui-icon-button--md {
  width: 46px;
  height: 46px;
  min-width: 46px;
  min-height: 46px;
}

.ui-icon-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* PenButton — §6.7: the 20px line pen in --link, 44px hit area. */
.ui-pen-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  flex: none;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--ui-radius);
  background: transparent;
  color: var(--link);
  cursor: pointer;
}

.ui-pen-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Surfaces and cards — §6.3: one depth step, a tile fill, no border. */
.ui-surface {
  background: var(--tile);
}

.ui-card {
  display: block;
  box-sizing: border-box;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--tile);
  padding: 16px;
}

.ui-card--interactive {
  cursor: pointer;
  text-align: left;
  width: 100%;
  color: inherit;
}

.ui-card--interactive:hover {
  box-shadow: inset 0 0 0 1.5px rgba(var(--link-rgb), 0.5);
}

.ui-card--selected {
  box-shadow: inset 0 0 0 1.5px var(--link);
}

/* Fields and inputs — §6.7: tile fill, no border, ring on focus. */
.ui-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.ui-field__label {
  color: var(--ink);
}

.ui-field__required {
  color: var(--ink);
}

.ui-field__hint {
  color: var(--muted-2);
}

.ui-field__error {
  color: var(--ink);
}

.ui-input,
.ui-select,
.ui-text-field,
.ui-text-area-field {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--tile);
  color: var(--ink);
}

.ui-input:focus,
.ui-select:focus,
.ui-text-field:focus,
.ui-text-area-field:focus {
  box-shadow: inset 0 0 0 1.5px var(--link);
}

.ui-input,
.ui-select,
.ui-text-field {
  min-height: 52px;
  padding: 0 16px;
}

.ui-text-area-field {
  min-height: 52px;
  padding: 14px 16px;
  resize: vertical;
}

.ui-input::placeholder,
.ui-text-field::placeholder,
.ui-text-area-field::placeholder {
  color: var(--muted-2);
  opacity: 1;
}

.ui-input--invalid,
.ui-text-field--invalid {
  box-shadow: inset 0 0 0 1.5px var(--ink);
}

/* A field on a `--tile` surface (a dialog, a sheet): the `--bg` fill, so
   it reads exactly like a field on the page. */
.ui-text-field--on-tile {
  background: var(--bg);
}

.ui-input:disabled,
.ui-select:disabled,
.ui-text-field:disabled,
.ui-text-area-field:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* In-place editing in the CMS phone and answers strip: a tile like
   every other field, so text stays readable on both themes. */
.ui-editable__pen {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  border: 0;
  border-radius: var(--ui-radius);
  background: transparent;
  color: var(--link);
  cursor: pointer;
}

.ui-editable__field {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--tile);
  color: var(--ink);
  min-height: 52px;
  padding: 12px 16px;
  resize: vertical;
}

.ui-editable__field:focus {
  box-shadow: inset 0 0 0 1.5px var(--link);
}

.ui-editable__field::placeholder {
  color: var(--muted-2);
  opacity: 1;
}

.ui-checkbox {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  color: var(--ink);
  cursor: pointer;
}

.ui-checkbox__input {
  flex: none;
  width: 22px;
  height: 22px;
  accent-color: var(--link);
  cursor: pointer;
}

.ui-checkbox--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Spinner — §6.7: a 2px hairline ring with a brass quarter. */
.ui-spinner {
  display: inline-block;
  flex: none;
  box-sizing: border-box;
  border: 2px solid var(--hair);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: ui-spin-rotate 0.9s linear infinite;
}

@keyframes ui-spin-rotate {
  to {
    transform: rotate(360deg);
  }
}

.ui-spinner--sm {
  width: 20px;
  height: 20px;
}

.ui-spinner--md {
  width: 28px;
  height: 28px;
}

.ui-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px 24px;
  text-align: center;
}

.ui-empty__title {
  color: var(--ink);
  margin: 0;
}

.ui-empty__body {
  color: var(--muted-2);
  margin: 0;
}

.ui-empty__action {
  margin-top: 8px;
}

.ui-sheet__close {
  flex: none;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  margin-top: -10px;
  margin-right: -2px;
  border: 0;
  border-radius: var(--ui-radius);
  background: transparent;
  color: var(--muted-1);
  cursor: pointer;
}

.ui-sheet__overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(var(--scrim-rgb), 0.6);
}

/* Sheets and dialogs are a --tile surface on the scrim (§6.5): no
   border, no shadow. */
.ui-sheet__panel {
  box-sizing: border-box;
  width: 100%;
  max-height: 86vh;
  overflow: auto;
  border: 0;
  border-radius: var(--ui-radius) var(--ui-radius) 0 0;
  background: var(--tile);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  animation: sheetUp 0.2s ease both;
}

.ui-sheet__header {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.ui-sheet__title {
  flex: 1;
  min-width: 0;
  color: var(--ink);
  margin: 0;
}

/* The sheet's hairline action rows (§6.7 list item sheet). */
.ui-sheet__actions {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--hair);
}

.ui-sheet__action {
  display: flex;
  align-items: center;
  min-height: 52px;
  width: 100%;
  box-sizing: border-box;
  padding: 14px 0;
  border: 0;
  border-bottom: 1px solid var(--hair);
  background: transparent;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.ui-sheet__action--destructive {
  color: var(--ink);
}

.ui-sheet__action:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--hair);
}

.ui-tabs__tab {
  min-height: 44px;
  padding: 0 16px;
  margin-bottom: -1px;
  border: 0;
  border-radius: var(--ui-radius);
  background: transparent;
  color: var(--muted-2);
  cursor: pointer;
  white-space: nowrap;
}

.ui-tabs__tab--selected {
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 0 0 1.5px var(--link);
}

.ui-tabs__tab:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Tables — §6.7: hairline rows, no box. */
.ui-table {
  width: 100%;
  border-collapse: collapse;
}

.ui-table__header-cell {
  padding: 10px 12px;
  border-bottom: 1px solid var(--hair);
  color: var(--muted-2);
  text-align: left;
}

.ui-table__header-cell--center {
  text-align: center;
}

.ui-table__header-cell--right {
  text-align: right;
}

.ui-table__sort-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.ui-table__sort-arrow {
  color: var(--link);
}

.ui-table__cell {
  padding: 12px;
  border-bottom: 1px solid var(--hair);
  color: var(--ink);
  text-align: left;
  vertical-align: top;
}

.ui-table__cell--center {
  text-align: center;
}

.ui-table__cell--right {
  text-align: right;
}

.ui-table__row--clickable {
  cursor: pointer;
}

.ui-table__empty {
  color: var(--muted-2);
  padding: 24px 12px;
  text-align: center;
}

/*
 * Selected and pressed tiles (§6.7): the 600 must reach the label. The
 * label sits in a `ui-type-control` span whose own 500 beats the
 * inherited weight — and unlayered CSS beats Tailwind utilities, so
 * `font-semibold` on the button alone never reaches it. The 600 is
 * stated here, where it wins on specificity.
 */
.font-semibold > .ui-type-control,
.font-medium:active > .ui-type-control {
  font-weight: 600;
}

/*
 * Hazard — §6.3: a card holding only the message text, its border ink
 * (1.5px for a warning, 2px for danger) and plain ink text; no fill,
 * no icon, no severity label. The buttons sit outside the card, in the
 * ordinary action row under it.
 */
.ui-hazard {
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-sizing: border-box;
  border: 0 solid var(--ink);
  border-radius: var(--ui-radius);
  background: transparent;
  padding: 22px;
}

/* Plain CSS, so a real media query — a Tailwind variant in here reaches the
   browser verbatim and is dropped. */
@media (max-width: 720px) {
  .ui-hazard {
    padding: 16px;
  }
}

.ui-hazard--warning {
  border-width: 1.5px;
}

.ui-hazard--danger {
  border-width: 2px;
}

.ui-hazard__message {
  color: var(--ink);
  margin: 0;
  text-wrap: pretty;
}

/*
 * The hazard row is the ordinary `ActionRow`, which carries the layout —
 * including moving an overflowing abort link onto its own line. What is
 * scoped here: Back keeps its width, abort links keep one line like every
 * other row link, and only a primary that cannot fit even beside Back
 * alone wraps inside its own button at spaces.
 */
.ui-hazard__actions > :first-child:not(.ui-action-row__spacer) {
  flex: none;
}

/* A long acknowledge label wraps inside a phone-width banner instead of overflowing it. */
.ui-hazard__actions .ui-button--primary {
  max-width: 100%;
  white-space: normal;
}

/* AudioPlayer — §6.12: 30 bars in a tile, played ink, unplayed
   --muted-2, a 44px ink-square play control. `webBar` kept. */
.ui-audio {
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-sizing: border-box;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--tile);
  padding: 16px 18px;
}

.ui-audio__row {
  display: flex;
  align-items: center;
  gap: 16px;
}

.ui-audio__play {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--ink);
  color: var(--bg);
  cursor: pointer;
}

.ui-audio__play:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-audio__waveform {
  display: flex;
  align-items: center;
  gap: 3px;
  flex: 1;
  min-width: 0;
  height: 44px;
}

.ui-audio__waveform > span {
  flex: 1 1 0;
  min-width: 0;
}

/*
 * The equaliser bounce (§6.12). Applied only while the recording plays,
 * and never under `prefers-reduced-motion` — the component then carries
 * progress as colour instead. `animation-duration` and `animation-delay`
 * are deliberately absent here: each bar's own pace is set inline, so the
 * shorthand below contributes only the name, timing function and
 * iteration count.
 */
.ui-audio__bar {
  animation-name: webBar;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

.ui-audio__element {
  flex: 1;
  min-width: 0;
}

.ui-audio__caption {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: var(--muted-1);
  margin: 0;
}

/* PendingBadge — §6.7: a quiet line, no box, no tint. */
.ui-pending {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--muted-2);
}

.ui-pending--pending,
.ui-pending--retrying,
.ui-pending--failed {
  border: 0;
  background: transparent;
  color: var(--muted-2);
}

button.ui-pending {
  min-height: 44px;
  padding: 0 4px;
  color: var(--link);
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
}

/* Notices — plain text, no tint, no border, no dot. */
.ui-tried,
.ui-tenant,
.ui-known,
.ui-microwave {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  box-sizing: border-box;
  border: 0;
  background: transparent;
  padding: 0;
}

.ui-tried__dot,
.ui-tenant__dot,
.ui-known__dot {
  display: none;
}

.ui-tried__label,
.ui-tenant__label,
.ui-known__label,
.ui-microwave__title,
.ui-microwave__body {
  flex: 1;
  min-width: 0;
  color: var(--ink);
  margin: 0;
}

.ui-known__action {
  flex: none;
  align-self: center;
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--link);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
  white-space: nowrap;
}

/* LinkRow — §7.1: a hairline row with a trailing arrow in --link. */
.ui-link-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  box-sizing: border-box;
  width: 100%;
  min-height: 44px;
  padding: 14px 0;
  border: 0;
  border-top: 1px solid var(--hair);
  background: transparent;
  color: var(--ink);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.ui-link-row:last-child {
  border-bottom: 1px solid var(--hair);
}

.ui-link-row:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-link-row__texts {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 1;
}

.ui-link-row__label {
  color: var(--ink);
}

.ui-link-row__hint {
  color: var(--muted-2);
}

.ui-link-row__arrow {
  flex: none;
}

/*
 * ListItem — §6.7: title, meta, any body detail, then the main action as
 * a link on the left and three 4px ink dots (44px hit area) on the right.
 * Items stack as hairline rows with no box around the list.
 */
.ui-list-item {
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-sizing: border-box;
  padding: 16px 0;
  border: 0;
  border-bottom: 1px solid var(--hair);
  background: transparent;
}

.ui-list-item__texts {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ui-list-item__title {
  color: var(--ink);
  margin: 0;
}

.ui-list-item__meta {
  color: var(--muted-2);
  margin: 0;
}

.ui-list-item__detail {
  color: var(--muted-1);
  margin: 0;
}

.ui-list-item__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ui-list-item__action {
  min-height: 44px;
  padding: 0 4px 0 0;
  border: 0;
  background: transparent;
  color: var(--link);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
  text-align: left;
}

.ui-list-item__action:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-list-item__more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex: none;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  margin: -12px -12px -12px 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.ui-list-item__more:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-list-item__dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
}

@media (min-width: 768px) {
  .ui-sheet__overlay {
    align-items: center;
    padding: 24px;
    box-sizing: border-box;
  }

  .ui-sheet__panel {
    width: min(560px, 100%);
    border-radius: var(--ui-radius);
    animation: webRise 0.18s ease both;
  }
}

/*
 * §7.1 (card #82): on phones, a dialog that holds a text field opens at
 * the top of the viewport, so the on-screen keyboard never covers it.
 * Pure CSS: the panel matches `:has(input, textarea)` and the viewport
 * pins it to the top with the normal 16px phone gutter (§6.5). Dialogs
 * without a text field keep the centred overlay, as does every dialog
 * at and above the breakpoint.
 */
@media (max-width: 719px) {
  .ui-dialog__viewport:has(input, textarea) {
    align-items: flex-start;
    padding: 16px;
  }
}

/*
 * §7.3 result/summary and §7.4 identification/account.
 *
 * `.ui-mono` is the one addition outside those two rows: a font-family-only
 * modifier (no font-size) so `PlateCandidateList` can render in IBM Plex Mono per §6.6 without setting a
 * raw size — the `ui-type-*` class next to it still owns that.
 */
.ui-mono {
  font-family: var(--ui-font-mono);
}

/* Numerals take the exact style of the line they introduce (§6.6):
   no brass, no tint, no discs. */
.ui-cause-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ui-cause-card__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.ui-cause-card__ordinal {
  flex: none;
  color: var(--ink);
}

.ui-cause-card__title {
  flex: 1;
  min-width: 0;
  color: var(--ink);
  margin: 0;
}

.ui-cause-card__body {
  color: var(--ink);
  margin: 0;
}

.ui-cause-card__reasoning {
  color: var(--muted-2);
  margin: 0;
}

.ui-cause-card__steps {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui-cause-card__step {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}

.ui-cause-card__step-num {
  flex: none;
  color: var(--ink);
}

.ui-cause-card__note {
  color: var(--muted-2);
  margin: 0;
}

.ui-plate-candidates {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.ui-plate-candidates__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui-plate-candidate {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
}

.ui-plate-candidate__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ui-plate-candidate__brand {
  color: var(--ink);
}

.ui-plate-candidate__model {
  color: var(--muted-2);
}

.ui-plate-candidate__warning {
  flex: 1 1 100%;
  order: 4;
  color: var(--muted-2);
  margin: 0;
}

/* Text-only notices: no tint, no border. */
.ui-unsupported,
.ui-unreadable {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  box-sizing: border-box;
  border: 0;
  background: transparent;
  padding: 0;
}

.ui-unsupported__title,
.ui-unreadable__title {
  color: var(--ink);
  margin: 0;
}

.ui-unsupported__message,
.ui-unreadable__message {
  color: var(--ink);
  margin: 0;
}

.ui-unsupported__model {
  color: var(--muted-2);
  margin: 0;
}

.ui-unreadable__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.ui-otp {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.ui-otp__digits {
  display: flex;
  gap: 10px;
  /* Six 44px digits and five 10px gaps are 314px: past that the digits
     keep their size, below it they share the row instead of pushing the
     screen sideways (card #77: no sideways scroll from 320px). */
  max-width: 314px;
}

/* Tile fill, no border at rest, the link ring on focus, ink ring invalid. */
.ui-otp__digit {
  box-sizing: border-box;
  flex: 1 1 0;
  min-width: 0;
  max-width: 44px;
  height: 56px;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--tile);
  color: var(--ink);
  text-align: center;
  font-family: var(--ui-font-mono);
}

.ui-otp__digit:focus {
  box-shadow: inset 0 0 0 1.5px var(--link);
}

.ui-otp__digit--invalid {
  box-shadow: inset 0 0 0 1.5px var(--ink);
}

.ui-otp__digit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-otp__resend {
  align-self: flex-start;
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--link);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
}

.ui-otp__resend:disabled {
  color: var(--muted-3);
  text-decoration: none;
  cursor: not-allowed;
}

/*
 * CMS components — contract §7.5. Same token rules as above: colours
 * are `var(--*)` or `rgba(var(--*-rgb), …)`, type comes from `ui-type-*`
 * classes in markup, corners are `var(--ui-radius)` / `50%` / `0`, every
 * control clears `44px`, and focus stays on the global `:focus-visible`
 * ring from `invariants.css`.
 */

.ui-target-picker {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ui-media-upload {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ui-media-upload__current {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.ui-media-upload__key {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

.ui-media-upload__label {
  display: flex;
  flex-direction: column;
  gap: 8px;
  box-sizing: border-box;
  min-height: 44px;
  padding: 14px 16px;
  border: 0;
  border-radius: var(--ui-radius);
  background: var(--tile);
  cursor: pointer;
}

.ui-media-upload__label--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-media-upload__prompt {
  color: var(--link);
}

.ui-media-upload__input {
  min-height: 44px;
  color: var(--ink);
}

.ui-media-upload__hint {
  color: var(--muted-2);
}

.ui-media-upload__error {
  color: var(--ink);
}

.ui-retarget {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ui-retarget__intro {
  margin: 0;
}

.ui-retarget__list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui-retarget__row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ui-retarget__label {
  color: var(--ink);
}

.ui-retarget__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.ui-issue-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.ui-issue-list__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ui-issue-list__heading {
  color: var(--muted-2);
  margin: 0;
}

.ui-issue-list__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* CMS lists are hairline rows (§6.10); state is said in words. */
.ui-issue-list__item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 10px 0;
  border: 0;
  border-bottom: 1px solid var(--hair);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}

.ui-issue-list__item:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ui-issue-list__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ui-issue-list__meta {
  color: var(--muted-2);
}

.ui-draft-toolbar {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.ui-draft-toolbar__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.ui-draft-toolbar__version {
  color: var(--muted-2);
}

.ui-draft-toolbar__issues {
  color: var(--muted-2);
}

/* The conflict state reads in ink with an ink border, like a warning. */
.ui-draft-toolbar__conflict {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
  border: 1.5px solid var(--ink);
  border-radius: var(--ui-radius);
  background: transparent;
}

.ui-draft-toolbar__conflict-title {
  color: var(--ink);
  margin: 0;
}

.ui-draft-toolbar__conflict-body {
  color: var(--ink);
  margin: 0;
}

.ui-draft-toolbar__conflict-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.ui-draft-toolbar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/*
 * SideNav — the CMS section sidebar (§6.10, §8.3). A 220px column of
 * tiles; the current section carries the ring. The queue count is words,
 * not a pill.
 */

.ui-side-nav {
  flex: 1 1 210px;
  max-width: 250px;
  min-width: 200px;
  align-self: stretch;
  box-sizing: border-box;
  padding: 24px 16px;
  background: transparent;
  border-right: 1px solid var(--hair);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ui-side-nav__list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui-side-nav__row {
  margin: 0;
  padding: 0;
}

.ui-side-nav__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 11px;
  box-sizing: border-box;
  width: 100%;
  min-height: 48px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--ui-radius);
  background: transparent;
  color: var(--ink);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.ui-side-nav__item:hover {
  box-shadow: inset 0 0 0 1.5px rgba(var(--link-rgb), 0.5);
}

.ui-side-nav__item--active {
  background: var(--tile);
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 0 0 1.5px var(--link);
}

.ui-side-nav__dot {
  display: none;
}

.ui-side-nav__label {
  flex: 1;
  min-width: 0;
}

.ui-side-nav__badge {
  flex: none;
  color: var(--muted-2);
  font-weight: 400;
  text-align: center;
}

/*
 * Quiet scrollbars (owner, 2026-10-03, card *CalmUI integrated*): one
 * global rule for the app, the CMS and the site — every page and list
 * scrolls with a thin bar in the theme's own colours. Track transparent,
 * thumb ink at rest and darker on hover; `scrollbar-width`/`scrollbar-color`
 * for Firefox and the `::-webkit-scrollbar` equivalents for Chromium and
 * Safari. Ink comes from the theme token, so light and dark both work.
 */

* {
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--ink-rgb), 0.25) transparent;
}

*::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background: rgba(var(--ink-rgb), 0.25);
  border-radius: var(--ui-radius);
}

*::-webkit-scrollbar-thumb:hover {
  background: rgba(var(--ink-rgb), 0.4);
}

/*
 * Document base — the `body`/control rules every design file declares in
 * its own <style> block (`body{margin:0;background:var(--bg);font-family:
 * "IBM Plex Sans";color:var(--ink)}`, `a{color:var(--link)}`, controls
 * inherit the family). Without it the browser's 8px body margin shows as a
 * white frame and unstyled text falls back to the UA serif. Layered as
 * `base` so every Tailwind utility (layer `utilities`) still wins.
 */

@layer base {
  /* Borders are opt-in, as in the design's inline styles: a side named by a
     utility (`border-b`) must not bring the UA's medium width on the others. */
  *,
  ::before,
  ::after {
    border-width: 0;
    border-style: solid;
  }

  html,
  body {
    margin: 0;
    padding: 0;
    /* Pages scroll only vertically (cards 38/39): anything wider than the
       screen is clipped at the page instead of letting it be dragged
       sideways. `clip`, not `hidden`, so nothing becomes a scroll container
       and sticky positioning keeps working. */
    overflow-x: clip;
  }

  html {
    background: var(--bg);
  }

  body {
    min-height: 100vh;
    /* The page is a flat colour in both themes (§6.3): no texture. */
    background: var(--bg);
    color: var(--ink);
    font-family: var(--ui-font-sans);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    -webkit-text-size-adjust: 100%;
  }

  a {
    color: var(--link);
  }

  a:hover {
    color: var(--ink);
  }

  button,
  input,
  select,
  textarea {
    font-family: inherit;
    color: inherit;
  }

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

  img {
    max-width: 100%;
  }
}

/*
 * Screen entrance — calm-ui (contract §6.8): a short fade (≤200 ms) when a
 * screen opens, no slide, no stagger. `enter()` still passes each block's
 * index as `--i`; it no longer delays anything, so every call site keeps
 * working unchanged.
 */
.ui-in {
  animation: webFade 0.16s ease both;
}

[data-nav-dir='back'] .ui-in {
  animation-name: webFade;
}

.ui-fade {
  animation: webFade 0.16s ease both;
}

.ui-rise {
  animation: webRise 0.2s ease both;
}

/* The menu overlay entrance (Pf `menu`): shortened to the
   calm 200 ms ceiling; the slide itself is a functional open transition. */
.ui-slide-left {
  animation: felsokSlideLeft 0.2s ease both;
}

.ui-spin {
  animation: cmsSpin 1s linear infinite;
}

@keyframes cmsSpin {
  to {
    transform: rotate(360deg);
  }
}

  /* The site follows the device in CSS alone (§6.1): no data-theme attribute,
     no script, no button. Dark tokens come from tokens.css `:root`; the
     light values below are that file's `[data-theme='light']` row (§6.2),
     scoped to the site so nothing else is touched. `color-scheme` lives on
     `.site` and on `:root:has(.site)` — never a bare `:root` — so the root
     follows the page into dark mode on www (classic scrollbars and other
     root-level UA chrome read the root), while importing this stylesheet
     into the app (the shared front page, which never renders `.site`)
     changes nothing there. */
  :root:has(.site) {
    color-scheme: light dark;
  }
  .site {
    color-scheme: light dark;
  }
  @media (prefers-color-scheme: light) {
    .site {
      color-scheme: light;
      --bg: #f1e9d6;
      --bg-rgb: 241, 233, 214;
      --tile: #faf6ec;
      --tile-rgb: 250, 246, 236;
      --ink: #10261e;
      --ink-rgb: 16, 38, 30;
      --muted-1: #2f4a3e;
      --muted-1-rgb: 47, 74, 62;
      --muted-2: #4f6157;
      --muted-2-rgb: 79, 97, 87;
      --muted-3: #6b7a70;
      --muted-3-rgb: 107, 122, 112;
      --link: #7a5a17;
      --link-rgb: 122, 90, 23;
      --hair: rgba(16, 38, 30, 0.16);
      --primary: #7a5a17;
      --primary-rgb: 122, 90, 23;
      --on-primary: #f6efdc;
      --on-primary-rgb: 246, 239, 220;
      --scrim-rgb: 6, 20, 16;
    }
  }
  .site {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background: var(--bg);
    color: var(--ink);
  }

  /* The app's home-page top bar (app-header on `start`): no bar of its own
     — no fill, border or blur — the wordmark centred, the flag button on
     the right, and nothing on the left (no menu button, no placeholder).
     It paints above the hero photo, whose negative top margin pulls the
     photo up behind it. */
  .site-topbar {
    position: relative;
    z-index: 2;
    flex: none;
    padding: 16px 24px 0;
  }
  .site-topbar-row {
    position: relative;
    height: 46px;
  }
  .site-wordmark {
    position: absolute;
    left: 50%;
    top: 0;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 46px;
    padding: 0;
    border: 0;
    background: transparent;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.2;
    white-space: nowrap;
    color: var(--ink);
    text-decoration: none;
  }
  .site-wordmark:hover {
    color: var(--ink);
  }
  /* The flag button shows the current language (§17): its flag and code as
     text, no box (§6.8); a native details/summary keeps it working without
     a framework. The list below is only as wide as its text and carries no
     active-dot marker — the current row is 600 weight, with no tint fill
     (§6.3). Hover is an underline (§6.12). */
  .site-lang {
    position: absolute;
    right: 0;
    top: 0;
  }
  .site-flag {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: 8px;
    height: 46px;
    padding: 0 4px;
    border: 0;
    background: transparent;
    color: var(--ink);
    font-size: 15px;
    font-weight: 600;
    text-transform: uppercase;
    cursor: pointer;
    list-style: none;
  }
  .site-flag::-webkit-details-marker {
    display: none;
  }
  .site-flag::marker {
    content: '';
  }
  .site-flag:hover {
    text-decoration: underline;
    text-underline-offset: 4px;
  }
  .site-flag svg {
    display: block;
    border-radius: 2px;
  }
  .site-lang-list {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    z-index: 31;
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: max-content;
    padding: 6px;
    border-radius: var(--ui-radius);
    border: 1px solid var(--hair);
    background: var(--tile);
  }
  .site-lang-item {
    font-weight: 500;
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    padding: 0 12px;
    border-radius: var(--ui-radius);
    color: var(--ink);
    font-size: 15px;
    line-height: 1.5;
    text-decoration: none;
    white-space: nowrap;
  }
  .site-lang-item:hover {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 4px;
  }
  .site-lang-item svg {
    display: block;
    border-radius: 2px;
  }
  .site-lang-item--current {
    font-weight: 600;
  }
  /* The search link (§17): like the flag button, no box, on the left where
     the app's menu button would sit. Normal case (§17); at least 44px
     wide (§6.5). */
  .site-search-link {
    position: absolute;
    left: 0;
    top: 0;
    display: flex;
    align-items: center;
    min-width: 44px;
    min-height: 46px;
    padding: 0 4px;
    color: var(--ink);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
  }
  .site-search-link:hover {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 4px;
  }
  /* Over the hero photo the top bar keeps the dark pairing in both themes:
     the photo is the same dark wall in light and dark alike. */
  .site:has(.site-hero) .site-wordmark,
  .site:has(.site-hero) .site-wordmark:hover,
  .site:has(.site-hero) .site-flag,
  .site:has(.site-hero) .site-search-link,
  .site:has(.site-hero) .site-search-link:hover {
    color: #f1e9d6;
  }
  @media (max-width: 719px) {
    .site-topbar {
      padding: 10px 16px 0;
    }
  }

  /* The app's centred column: 880px of content like the app shell's inner
     column, with the same 24px gutters outside it (928px border-box). */
  .site-main {
    box-sizing: border-box;
    width: 100%;
    max-width: 928px;
    margin: 0 auto;
    padding: 26px 24px 72px;
    display: flex;
    flex-direction: column;
    gap: 22px;
    flex: 1;
  }
  @media (max-width: 719px) {
    .site-main {
      padding: 18px 16px 44px;
    }
  }

  /* The home hero (§6.7, design `Screen` `start` dir `a`): the photo runs
     from the top edge under the header — the negative top margin pulls it
     up behind the top bar (top bar plus column padding: 88px wide, 74px on
     phones below 720px, the same geometry as the app shell, so the shared
     front page lands under the app header too) — and fades into `--bg`
     over its last 120px. The photo is the same dark wall in both themes,
     so the headline keeps the cream `display` pairing in light mode too.
     The pull switches below 720px like the paddings it cancels. */
  .site-start {
    display: flex;
    flex-direction: column;
    gap: 28px;
  }
  .site-hero {
    position: relative;
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-top: -88px;
    overflow: clip;
  }
  .site-hero-media {
    position: absolute;
    inset: 0;
    background: var(--bg);
  }
  .site-hero-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* The machine fills the source's right third; the headline sits on the
       dark wall to the left, so the crop anchors right. */
    object-position: right center;
    display: block;
  }
  .site-hero-scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(100deg, rgba(10, 28, 22, 0.78), rgba(10, 28, 22, 0.15) 64%);
    pointer-events: none;
  }
  .site-hero-fade {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 120px;
    background: linear-gradient(to bottom, transparent, var(--bg));
    pointer-events: none;
  }
  .site-hero-inner {
    position: relative;
    z-index: 1;
    box-sizing: border-box;
    width: 100%;
    max-width: 928px;
    margin: 0 auto;
    padding: 150px 24px 110px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 24px;
    min-height: 640px;
  }
  /* The `display` role comes from the `ui-type-display` class in the
     markup; only the column width lives here. */
  .site-hero-title {
    margin: 0;
    max-width: min(560px, 40vw);
    text-wrap: pretty;
  }
  .site-hero-lead {
    margin: 0;
    font-size: 16px;
    line-height: 1.5;
    color: #f1e9d6;
    max-width: min(560px, 40vw);
    text-wrap: pretty;
  }
  .site-cta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding-top: 4px;
  }
  .site-cta-row--loose {
    gap: 16px;
    padding-top: 8px;
  }
  @media (max-width: 719px) {
    .site-hero {
      margin-top: -74px;
    }
  }
  @media (max-width: 760px) {
    .site-hero-inner {
      padding: 150px 16px 64px;
      min-height: 600px;
      justify-content: flex-end;
      gap: 16px;
    }
    .site-hero-scrim {
      background: linear-gradient(to top, rgba(10, 28, 22, 0.96) 0%, rgba(10, 28, 22, 0.72) 50%, rgba(10, 28, 22, 0.35) 100%);
    }
    .site-hero-title,
    .site-hero-lead {
      max-width: none;
    }
  }

  /* Three numbered steps (§6, design `Screen` `start` dir `a`): hairline
     rows, the number beside a stacked title and text. */
  .site-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0 40px;
    border-top: 1px solid var(--hair);
  }
  .site-step {
    display: flex;
    gap: 16px;
    padding: 20px 0;
    border-bottom: 1px solid var(--hair);
    min-width: 0;
  }
  .site-step-number {
    flex: none;
    min-width: 24px;
    font-size: 16px;
    line-height: 1.4;
    font-weight: 600;
    color: var(--ink);
  }
  .site-step-body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
  }
  .site-step-body h2,
  .site-step-body p {
    margin: 0;
  }
  @media (max-width: 760px) {
    .site-steps {
      grid-template-columns: 1fr;
    }
  }

  /* The account card beside the types card: plain tiles (§6.3, §6.5), no
     border. Stacked below 760px. */
  .site-cards {
    display: grid;
    grid-template-columns: 5fr 6fr;
    gap: 12px;
    align-items: stretch;
  }
  .site-account-card,
  .site-types-card {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 22px;
    border-radius: var(--ui-radius);
    background: var(--tile);
  }
  .site-account-card h2,
  .site-types-card h2,
  .site-account-card p,
  .site-types-card p {
    margin: 0;
  }
  @media (max-width: 760px) {
    .site-cards {
      grid-template-columns: 1fr;
    }
  }

  /* Type (§6.6): one `statement` per page — the role class lives in the
     markup, so only the measure lives here. The home hero keeps its own
     `display` style and is excluded here. */
  .site-main h1:not(.site-hero-title) {
    margin-top: 0;
    margin-bottom: 0;
    text-wrap: pretty;
  }
  .site-lead,
  .site-lead p {
    max-width: 58ch;
    font-size: 15px;
    line-height: 1.5;
    color: var(--muted-1);
    text-wrap: pretty;
  }
  .site-eyebrow {
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-size: 15px;
    line-height: 1.4;
    color: var(--muted-2);
    margin-top: 12px;
  }
  /* The article's category link under the title (§6.4): a 44px tap target
     like the eyebrow above it. The focus ring is untouched. */
  .site-category a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* Quiet rows — the symptom, category and blog lists. No box around
     each line: title at full contrast, the second line muted, 1px `--hair`
     lines between rows (§6.5); hover is an underline (§6.12). */
  .site-rows {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
  }
  .site-rows > li + li {
    border-top: 1px solid var(--hair);
  }
  .site-row {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 44px;
    justify-content: center;
    padding: 12px 0;
    color: var(--ink);
    text-decoration: none;
  }
  a.site-row:hover {
    color: var(--ink);
  }
  a.site-row:hover .site-row-title,
  a.site-row:hover .ui-type-control {
    text-decoration: underline;
    text-underline-offset: 4px;
  }
  .site-row-title {
    font-weight: 500;
    font-size: 15px;
    line-height: 1.5;
    text-wrap: pretty;
  }
  .site-row-text {
    font-size: 15px;
    line-height: 1.5;
    color: var(--muted-2);
    text-wrap: pretty;
  }
  .site-arrow::after {
    content: ' →';
    color: var(--link);
  }

  /* The search page (§17): the label, the shared field and the quiet rows
     stack with the field full width. */
  .site-search {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-width: 66ch;
  }
  .site-search .ui-type-body {
    margin: 0;
  }

  /* The front page's fault rows (§6, design `Screen` `start` dir `a`): one
     line, title and arrow apart, hairlines above and below the list. */
  .site-faults {
    border-top: 1px solid var(--hair);
  }
  .site-faults > li:last-child {
    border-bottom: 1px solid var(--hair);
  }
  .site-row.site-fault {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 0;
  }

  /* Plain symptom sections — one title and running text, no boxes. The
     `title` role class lives in the markup. */
  .site-cause {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 66ch;
  }
  .site-cause h2 {
    margin: 0;
  }
  .site-cause p,
  .site-cause li {
    margin: 0;
    font-size: 15px;
    line-height: 1.5;
    color: var(--muted-1);
    text-wrap: pretty;
  }
  .site-cause ul {
    margin: 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }

  .site-codes {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 12px;
    padding: 0;
    list-style: none;
  }
  .site-codes li {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-family: var(--ui-font-mono);
    font-size: 15px;
    line-height: 1.5;
    letter-spacing: 0.06em;
    color: var(--muted-1);
  }
  .site-note p {
    font-size: 15px;
    line-height: 1.5;
    color: var(--muted-2);
  }

  /* Blog body — rendered Markdown. */
  .site-prose {
    max-width: 66ch;
    font-size: 15px;
    line-height: 1.5;
    color: var(--muted-1);
  }
  .site-prose [data-body] > * + * {
    margin-top: 14px;
  }
  /* A Markdown `# heading` inside the body must stay body text on the
     live page too: `.site-main .site-prose [data-body] h1` is more
     specific than `.site-main h1:not(.site-hero-title)` above, so it wins
     there. The bare selectors stay for the CMS preview,
     which renders this component outside `.site-main`. */
  .site-main .site-prose [data-body] h1,
  .site-main .site-prose [data-body] h2,
  .site-main .site-prose [data-body] h3,
  .site-prose [data-body] h1,
  .site-prose [data-body] h2,
  .site-prose [data-body] h3 {
    color: var(--ink);
    font-size: 15px;
    font-weight: 400;
    line-height: 1.5;
    margin-top: 26px;
  }
  /* Markdown `**bold**` renders `<strong>` (700 by default): body text has no other bold (§6.6). */
  .site-prose strong {
    font-weight: 400;
  }
  .site-prose a {
    color: var(--link);
  }
  .site-prose ul,
  .site-prose ol { padding-left: 22px; }
