/* ============================================================
 * TIDE v0.1 — 29Court design system · COMPONENTS (CSS)   DRAFT
 * Needs tokens.css first. Plain CSS, @layer, no build step.
 *
 * Original 29Court code, except the parts marked [MIT-PORT],
 * whose behaviour/geometry is ported from Bencho free blocks
 * (slide-confirm, toast-stack, time-scrubber, one-time-code,
 * checklist, dynamic-island, slosh):
 *   MIT License — Copyright (c) 2026 Lorenzo Cabra
 *   Permission is hereby granted, free of charge, to any person
 *   obtaining a copy of this software ... (full text in
 *   THIRD-PARTY.md, which ships with this file).
 * Pro-licensed patterns (swipe deck, inline confirm, feedback bar,
 * pay button, access request, activity stack, AI chat input,
 * copy field, thinking, skeleton morph, shimmer) are clean-room
 * rebuilds from behaviour specs — no Bencho Pro code.
 * ============================================================ */

@layer c29.reset {
  *, *::before, *::after { box-sizing: border-box; }
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  body { margin: 0; }
  /* Axis lock (Rev 1, 2026-10-17): the page never scrolls sideways.
     Gesture components may paint past the viewport mid-flight; that
     travel must never become page scroll. clip (not hidden), so
     position:sticky keeps working; hidden is the legacy fallback. */
  html, body { overflow-x: hidden; overflow-x: clip; }
  button, input, textarea, select { font: inherit; color: inherit; }
  img, svg { display: block; max-width: 100%; }
  dialog { padding: 0; border: 0; color: inherit; }
  [hidden] { display: none !important; }
}

/* Derived tokens are re-declared on every component root so a knob
   set on the component (or any ancestor) recomputes them there. */
@layer c29.tokens {
  :root, .tc, [data-tune] {
    --d1: calc(160ms / var(--speed)); --d2: calc(220ms / var(--speed));
    --d3: calc(320ms / var(--speed)); --d4: calc(420ms / var(--speed)); --d5: calc(480ms / var(--speed));
    --ease-give: cubic-bezier(.24, calc(1 + .5 * var(--bounce)), .38, 1);
    --s1:calc(4px * var(--density)); --s2:calc(8px * var(--density)); --s3:calc(12px * var(--density));
    --s4:calc(16px * var(--density)); --s5:calc(20px * var(--density)); --s6:calc(24px * var(--density));
    --s8:calc(32px * var(--density)); --s12:calc(48px * var(--density));
    --accent-soft: color-mix(in srgb, var(--accent) 14%, var(--surface));
  }
}

@layer c29.base {
  html { background: var(--bg); color: var(--ink); font-family: var(--font-ui);
    font-size: var(--fs-body); line-height: var(--lh-body); -webkit-font-smoothing: antialiased; }
  body { background: var(--bg); position: relative; min-height: 100dvh; }
  h1, h2, h3 { font-family: var(--font-display); letter-spacing: var(--track-head); line-height: 1.15; margin: 0; text-wrap: balance; font-weight: 750; }
  p { margin: 0; text-wrap: pretty; }
  a { color: var(--accent-text); }
  :focus-visible { outline: none; box-shadow: var(--focus); border-radius: 8px; }
  /* FOCUS LAW (2026-10-19, Principal field defect): the ring belongs to
     INTERACTIVE elements only. Programmatic focus targets — dialogs,
     sheet/dialog containers, headings, landmarks and other tabindex="-1"
     waypoints that exist so focus has somewhere accessibility-correct to
     LAND — never paint a ring: focus there is an announcement, not a
     selection. (sheet() focuses its <h2> on open; the unscoped rule above
     drew the sea-green box around the title on iOS. The 2026-10-17 grab-only
     suppression below is this law's first, too-narrow instance.)
     Real controls are excluded from the tabindex clause by element/role:
     a roving-tabindex control (c29-tabs) moves tabindex to 0 before focus
     lands, so no keyboard user loses a cue. */
  :is(dialog, [role="dialog"], [role="alertdialog"]):focus-visible,
  :is(h1, h2, h3, h4, h5, h6):focus-visible,
  [tabindex="-1"]:not(button, a, input, select, textarea, summary,
    [role="button"], [role="tab"], [role="menuitem"], [role="menuitemcheckbox"],
    [role="menuitemradio"], [role="option"], [role="link"], [role="checkbox"],
    [role="radio"], [role="switch"], [role="slider"], [role="spinbutton"],
    [role="combobox"], [role="listbox"], [role="textbox"],
    [contenteditable]):focus-visible { outline: none; box-shadow: none; }
  /* THEME SWAP LAW (2026-10-19): while <html> wears .c29-swapping (set by
     theme.set() in tide.js for the swap frames), nothing transitions — a
     theme change is an instant token change, never 60 staggered colour
     animations under a view-transition snapshot (the reported toggle
     jitter). Component-owned deliberate transitions resume untouched the
     moment the class lifts. */
  html.c29-swapping *, html.c29-swapping *::before, html.c29-swapping *::after { transition: none !important; }
  ::selection { background: color-mix(in srgb, var(--accent) 26%, transparent); }
  .tnum { font-variant-numeric: tabular-nums; }
  @view-transition { navigation: auto; }
}

@layer c29.components {

/* ---------- page frame ---------- */
.t-page { max-width: 560px; margin: 0 auto; padding: calc(var(--s4) + var(--safe-t)) var(--s4) calc(var(--s12) + var(--safe-b)); }
.t-sky { position: fixed; inset: 0; background: var(--glow); pointer-events: none; z-index: 0; }
.t-page > * { position: relative; z-index: 1; }
.t-cap { font-size: var(--fs-cap); letter-spacing: var(--track-cap); text-transform: uppercase; font-weight: 650; color: var(--ink-mute); }
.t-muted { color: var(--ink-mute); }
.t-small { font-size: var(--fs-sm); }
.t-stack { display: grid; gap: var(--s3); grid-template-columns: minmax(0, 1fr); }
.t-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.t-spacer { flex: 1; }
.t-sim { display: inline-flex; align-items: center; gap: 4px; font: 650 11px/1 var(--font-ui); letter-spacing: .08em;
  text-transform: uppercase; color: var(--warn-text); background: var(--warn-bg); padding: 4px 7px; border-radius: 99px; white-space: nowrap; }

/* ---------- card / pane ---------- */
.t-card { background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: var(--s5); }
.t-pane { background: var(--pane); backdrop-filter: blur(var(--pane-blur)) saturate(1.2); -webkit-backdrop-filter: blur(var(--pane-blur)) saturate(1.2);
  border-radius: var(--corner); box-shadow: 0 0 0 1px var(--pane-edge), var(--lift); background-image: var(--sheen); }

/* ---------- buttons ---------- */
.t-btn { --b-bg: var(--surface); --b-ink: var(--ink); --b-edge: var(--edge);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: var(--tap); min-width: var(--tap);
  padding: 0 calc(var(--s4) + 2px); border: 0; border-radius: var(--r-pill); background: var(--b-bg); color: var(--b-ink);
  box-shadow: 0 0 0 1px var(--b-edge); font-weight: 600; font-size: 15px; cursor: pointer; text-decoration: none;
  transition: transform var(--d1) var(--ease), background var(--d2) var(--ease), box-shadow var(--d2) var(--ease);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation; white-space: nowrap; }
.t-btn:hover { background: color-mix(in srgb, var(--b-bg) 92%, var(--ink)); }
.t-btn:active { transform: scale(.97); }
.t-btn[disabled] { opacity: .45; pointer-events: none; }
.t-btn.is-accent { --b-bg: var(--accent); --b-ink: var(--on-accent); --b-edge: transparent; }
.t-btn.is-money { --b-bg: var(--money); --b-ink: var(--on-money); --b-edge: transparent; }
.t-btn.is-ghost { --b-bg: transparent; --b-edge: transparent; }
.t-btn.is-quiet { --b-bg: var(--wash); --b-edge: transparent; }
.t-btn.is-sm { min-height: 36px; padding: 0 14px; font-size: 14px; }
.t-btn.is-icon { padding: 0; width: var(--tap); }
.t-btn svg { width: 18px; height: 18px; flex: none; }
.t-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 14px; border-radius: 99px; border: 0;
  background: var(--wash); color: var(--ink-soft); font-size: 14px; font-weight: 550; cursor: pointer; transition: background var(--d2) var(--ease), color var(--d2) var(--ease), transform var(--d1) var(--ease); }
.t-chip[aria-pressed="true"], .t-chip.is-on { background: var(--accent); color: var(--on-accent); }
.t-chip:active { transform: scale(.96); }
.t-icon { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ---------- inputs ---------- */
.t-field { display: grid; gap: 6px; }
.t-field label { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.t-input { width: 100%; min-height: 48px; padding: 0 16px; border-radius: var(--r-md); border: 0; background: var(--raised);
  box-shadow: 0 0 0 1px var(--edge); font-size: 17px; color: var(--ink); transition: box-shadow var(--d2) var(--ease); }
.t-input:focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 5px color-mix(in srgb, var(--accent) 16%, transparent); }
.t-input.is-bad { box-shadow: 0 0 0 1.5px var(--bad); animation: t-shake var(--d4) var(--ease); }
/* Rev 5 (2026-10-18): plain .t-input placeholders had no themed colour and
   fell back to UA grey at 3.10:1 on the dark raised surface. --ink-mute is
   theme-aware: 5.64:1 on light raised, 4.68:1 on dark raised. opacity:1
   stops engines that dim ::placeholder by default from undoing it. */
.t-input::placeholder { color: var(--ink-mute); opacity: 1; }
/* Rev 7 (2026-10-18): the dark placeholder was the system's weak spot —
   4.68:1 on the dark raised surface cleared the bar but sat under the
   4.7 aim every other Rev 5 fix met. Pinned one step lighter in dark
   only (#9AABB3, ≈6.0:1 on dark raised, measured in the Rev 7 render
   pass), the .pg-pct pin pattern; opacity:1 rides along so engines
   that dim ::placeholder cannot undo it on the composer either.
   Light is untouched: 5.64:1 already clears the aim. */
[data-theme="dark"] .t-input::placeholder,
[data-theme="dark"] c29-composer textarea::placeholder { color: #9AABB3; opacity: 1; }
@keyframes t-shake { 20%{translate:-6px} 45%{translate:5px} 70%{translate:-3px} 100%{translate:0} }
.t-unfold { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--d4) var(--ease); }
.t-unfold > * { overflow: hidden; }
.t-unfold.is-open { grid-template-rows: 1fr; }

/* ---------- segmented / switch ---------- */
.t-seg { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; padding: 3px; border-radius: 99px; background: var(--wash); position: relative; isolation: isolate; }
.t-seg button { min-height: 38px; padding: 0 14px; border: 0; background: none; border-radius: 99px; font-size: 14px; font-weight: 600; color: var(--ink-mute); cursor: pointer; transition: color var(--d2) var(--ease); white-space: nowrap; }
.t-seg button[aria-pressed="true"] { color: var(--ink); }
.t-seg .t-seg-thumb { position: absolute; z-index: -1; top: 3px; bottom: 3px; border-radius: 99px; background: var(--raised); box-shadow: var(--lift);
  transition: left var(--d3) var(--ease-give), width var(--d3) var(--ease-give); }
.t-switch { appearance: none; width: 50px; height: 30px; border-radius: 99px; background: var(--wash-2); position: relative; cursor: pointer; flex: none; margin: 7px 0;
  transition: background var(--d2) var(--ease); }
.t-switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%; background: #fff;
  box-shadow: 0 2px 6px rgba(0,0,0,.18); transition: translate var(--d3) var(--ease-give); }
.t-switch:checked { background: var(--accent); }
.t-switch:checked::after { translate: 20px 0; }

/* ---------- theme toggle ---------- */
.t-theme { position: relative; overflow: hidden; }
.t-theme .sun, .t-theme .moon { position: absolute; transition: transform var(--d4) var(--ease-give), opacity var(--d2) var(--ease); }
[data-theme="dark"] .t-theme .sun { transform: translateY(26px) rotate(60deg); opacity: 0; }
:root:not([data-theme="dark"]) .t-theme .moon { transform: translateY(-26px) rotate(-40deg); opacity: 0; }

/* ======================================================
   SWIPE DECK  <c29-deck>   (clean-room, spec: swipe-deck)
   ====================================================== */
c29-deck { display: block; position: relative; --deck-h: 360px; --fan: 1; outline: none; --deck-on-sun: #3b2a12; }
/* on-sun word colour (deck-pass, measured): light #3b2a12 on sun-300 is
   8.85:1; on the dark theme's deeper sun (#C08442) it drops to 4.34:1,
   so the night beach deepens the word to the same brown family (5.8:1). */
[data-theme="dark"] c29-deck { --deck-on-sun: #1d1406; }
c29-deck .deck-stage { position: relative; height: var(--deck-h); touch-action: none; user-select: none; -webkit-user-select: none; }
c29-deck .deck-card { position: absolute; inset: 0; border-radius: var(--corner); background: var(--sheen), var(--raised); box-shadow: var(--lift);
  padding: var(--s5); display: flex; flex-direction: column; gap: var(--s3); overflow: hidden; will-change: transform;
  transition: transform var(--d5) var(--ease-give), opacity var(--d3) var(--ease), box-shadow var(--d3) var(--ease); cursor: grab; }
c29-deck .deck-card.is-held { transition: none; cursor: grabbing; box-shadow: var(--lift-hi); }
/* while a drag runs, the whole stack is JS-posed every move (the
   anticipation rise) — CSS transitions would lag the hand. */
c29-deck .deck-stage.is-dragging .deck-card { transition: none; }
c29-deck .deck-card.is-gone { pointer-events: none; }
c29-deck .deck-card:not(.is-top) { pointer-events: none; }
c29-deck .deck-card:not(.is-top) > :not(.deck-tint) { opacity: .0; transition: opacity var(--d3) var(--ease); }
c29-deck .deck-card.is-top > * { opacity: 1; }
c29-deck .deck-tint { position: absolute; inset: 0; pointer-events: none; opacity: 0; border-radius: inherit; }
/* The ONE indicator: a full-stage layer clipped (per move, in JS) to
   exactly the strip the dragged card has vacated on the trailing edge —
   it never overlays card content. Its colour is NOT in this stylesheet:
   the gesture machine paints background-color + the word's colour
   inline on every move from the drag scalar p (soft suggestion tint →
   fully saturated at p = 1, melting across hues on a sector change).
   Any CSS colour or transition here would reintroduce the snap the
   Slide-Cards campaign exists to kill. */
c29-deck .deck-reveal { position: absolute; inset: 0; z-index: 60; pointer-events: none; opacity: 0; clip-path: inset(0 100% 0 0); }
c29-deck .deck-reveal.is-on { opacity: 1; }
c29-deck .deck-reveal-in { position: absolute; display: flex; align-items: center; gap: 6px; color: var(--ink); }
c29-deck .deck-reveal[data-dir="right"] .deck-reveal-in { left: 16px; top: 50%; translate: 0 -50%; flex-direction: column; text-align: center; }
c29-deck .deck-reveal[data-dir="left"] .deck-reveal-in { right: 16px; top: 50%; translate: 0 -50%; flex-direction: column; text-align: center; }
c29-deck .deck-reveal[data-dir="down"] .deck-reveal-in { top: 14px; left: 50%; translate: -50% 0; }
c29-deck .deck-reveal[data-dir="up"] .deck-reveal-in { bottom: 14px; left: 50%; translate: -50% 0; }
c29-deck .deck-reveal-ic { display: grid; place-items: center; will-change: transform; }
c29-deck .deck-reveal-ic .t-icon { width: 26px; height: 26px; }
c29-deck .deck-reveal-word { font: 800 15px/1 var(--font-display); letter-spacing: .12em; text-transform: uppercase; white-space: nowrap; }
c29-deck .deck-reveal-sub { font-size: 11px; font-weight: 650; letter-spacing: .04em; opacity: .75; white-space: nowrap; }
c29-deck .deck-empty { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; padding: var(--s6);
  border-radius: var(--corner); box-shadow: inset 0 0 0 1.5px var(--edge); border: 0; color: var(--ink-mute); opacity: 0; transition: opacity var(--d4) var(--ease) var(--d2); }
c29-deck[data-empty] .deck-empty { opacity: 1; }
c29-deck .deck-bar { display: grid; grid-template-columns: repeat(var(--deck-actions, 4), 1fr); gap: var(--s2); margin-top: calc(var(--s4) + 28px); }
c29-deck .deck-bar button { min-height: 56px; border: 0; border-radius: var(--r-md); background: var(--surface); box-shadow: var(--hair); cursor: pointer;
  display: grid; place-items: center; gap: 2px; padding: 6px 2px; font-size: 12px; font-weight: 600; color: var(--ink-soft);
  transition: transform var(--d1) var(--ease), background var(--d2) var(--ease); }
c29-deck .deck-bar button:active { transform: scale(.95); }
c29-deck .deck-bar button[data-primary="true"] { background: var(--act-c, var(--accent)); color: var(--act-on, var(--on-accent)); box-shadow: none; }
c29-deck .deck-bar .t-icon { width: 20px; height: 20px; }
/* the direction chip: a little badge riding the button's icon, tinted
   with the action's own colour — the button row teaches the gesture.
   Decorative (aria-hidden in the markup); the direction is named in
   the button's accessible label. */
c29-deck .deck-btn-ic { position: relative; display: grid; place-items: center; }
c29-deck .deck-arrow { position: absolute; top: -7px; right: -12px; min-width: 15px; height: 15px; padding: 0 3px; border-radius: 99px;
  background: color-mix(in srgb, var(--act-c, var(--ink)) 18%, var(--surface)); color: color-mix(in srgb, var(--act-c, var(--ink)) 72%, var(--ink));
  font-size: 10px; font-weight: 800; line-height: 15px; text-align: center; box-shadow: var(--hair); pointer-events: none; }
c29-deck .deck-bar button[data-primary="true"] .deck-arrow { background: color-mix(in srgb, var(--act-on, var(--on-accent)) 22%, transparent); color: var(--act-on, var(--on-accent)); box-shadow: none; }
c29-deck .deck-hint { margin-top: var(--s2); text-align: center; font-size: 12px; color: var(--ink-mute); }
c29-deck .deck-count { position: absolute; top: -12px; right: 12px; z-index: 50; }

/* item anatomy (used in deck + inbox) */
.t-item-kind { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 650; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-mute); }
.t-item-kind i { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.t-item-kind i.money { background: var(--money); } .t-item-kind i.warn { background: var(--sun-500); }
.t-item-title { font: 750 var(--fs-xl)/1.2 var(--font-display); letter-spacing: var(--track-head); }
.t-item-body { color: var(--ink-soft); font-size: 15px; }
.t-item-foot { margin-top: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; color: var(--ink-mute); }
.t-amount { font: 750 var(--fs-2xl)/1 var(--font-display); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.t-tag { white-space: nowrap; display: inline-flex; align-items: center; min-height: 24px; padding: 0 9px; border-radius: 99px; background: var(--wash); font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.t-tag.is-accent { background: var(--accent-soft); color: var(--accent-text); }
.t-tag.is-money { background: color-mix(in srgb, var(--money) 14%, var(--surface)); color: var(--money-text); }
/* status tones (2026-10-19, Desk composition rebuild) — the designed
   ok / warn / bad token pairs; text rides on its paired soft ground */
.t-tag.is-ok { background: var(--ok-bg); color: var(--ok-text); }
.t-tag.is-warn { background: var(--warn-bg); color: var(--warn-text); }
.t-tag.is-bad { background: var(--bad-bg); color: var(--bad-text); }

/* ======================================================
   SLIDE TO CONFIRM  <c29-slide>   [MIT-PORT slide-confirm]
   The handle follows the finger 1:1; only the end of the
   track commits; on commit the handle unfurls leftward and
   fills the track (right edge pinned), arrow becomes check.
   ====================================================== */
c29-slide { --h: 60px; --pad: 4px; display: block; }
c29-slide .sl-track { position: relative; height: var(--h); border-radius: min(var(--corner), calc(var(--h) / 2)); background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--edge-soft); overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none;
  transition: scale var(--d3) var(--ease); }
c29-slide .sl-wash { position: absolute; inset: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--sl-c, var(--accent)) 22%, transparent), transparent); opacity: 0; }
c29-slide .sl-label { position: absolute; inset: 0; display: grid; place-items: center; padding-left: calc(var(--h) + 4px); padding-right: 16px;
  font-weight: 650; font-size: 15px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
c29-slide .sl-label span { background: linear-gradient(90deg, var(--ink-soft) 0 40%, var(--ink-mute) 50%, var(--ink-soft) 60%) 0 0/250% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent; animation: t-sweep var(--shimmer-period) linear infinite; }
c29-slide .sl-grip { position: absolute; top: var(--pad); left: var(--pad); height: calc(var(--h) - 2 * var(--pad)); width: calc(var(--h) - 2 * var(--pad));
  border-radius: max(0px, calc(min(var(--corner), calc(var(--h) / 2)) - var(--pad))); background: var(--sl-c, var(--accent)); color: var(--on-accent);
  display: flex; align-items: center; justify-content: flex-end; padding-right: calc((var(--h) - 2 * var(--pad) - 22px) / 2);
  box-shadow: 0 4px 12px -4px color-mix(in srgb, var(--sl-c, var(--accent)) 60%, transparent); cursor: grab; border: 0; }
c29-slide .sl-grip:focus-visible { box-shadow: var(--focus); }
c29-slide .sl-grip svg { width: 22px; height: 22px; transition: opacity var(--d2) var(--ease), transform var(--d3) var(--ease-give); }
c29-slide .sl-grip .ok { position: absolute; right: calc((var(--h) - 2 * var(--pad) - 22px) / 2); opacity: 0; transform: scale(.4); }
c29-slide .sl-grip .ok path { stroke-dasharray: 24; stroke-dashoffset: 24; transition: stroke-dashoffset var(--d4) var(--ease) var(--d2); }
c29-slide .sl-done { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; color: var(--on-accent); font-weight: 700; font-size: 15px; opacity: 0; pointer-events: none;
  transition: opacity var(--d3) var(--ease) var(--d3); }
c29-slide[data-state="done"] .sl-grip .go { opacity: 0; transform: translateX(8px); }
c29-slide[data-state="done"] .sl-grip .ok { opacity: 1; transform: none; }
c29-slide[data-state="done"] .sl-grip .ok path { stroke-dashoffset: 0; }
c29-slide[data-state="done"] .sl-done { opacity: 1; }
c29-slide[data-state="done"] .sl-label { opacity: 0; }
c29-slide[tone="money"] { --sl-c: var(--money); }
c29-slide[tone="money"] .sl-done, c29-slide[tone="money"] .sl-grip { color: var(--on-money); }
@keyframes t-sweep { from { background-position: 100% 0; } to { background-position: -150% 0; } }

/* ======================================================
   TOASTS  <c29-toasts>   [MIT-PORT toast-stack: every place is its depth]
   ====================================================== */
c29-toasts { position: fixed; z-index: 90; left: 50%; bottom: calc(16px + var(--safe-b)); width: min(420px, calc(100vw - 24px)); translate: -50% 0;
  height: 0; pointer-events: none; }
c29-toasts .toast { position: absolute; left: 0; right: 0; bottom: 0; pointer-events: auto; display: flex; align-items: center; gap: 10px;
  min-height: 56px; padding: 8px 8px 8px 16px; border-radius: min(var(--corner), 22px); overflow: hidden;
  background: var(--pane-strong); backdrop-filter: blur(16px) saturate(1.3); -webkit-backdrop-filter: blur(16px) saturate(1.3);
  box-shadow: 0 0 0 1px var(--pane-edge), var(--lift-hi); transform-origin: 50% 100%;
  transition: transform var(--d5) var(--ease-give), opacity var(--d3) var(--ease); }
c29-toasts .toast .tx { flex: 1; min-width: 0; font-size: 15px; font-weight: 550; line-height: 1.3; }
c29-toasts .toast .tx small { display: block; font-weight: 450; color: var(--ink-mute); font-size: 13px; }
c29-toasts .toast .dot { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; flex: none; background: var(--accent); color: var(--on-accent); }
c29-toasts .toast .dot svg { width: 15px; height: 15px; }
c29-toasts .toast[data-tone="neutral"] .dot { background: var(--ink); color: var(--bg); }
c29-toasts .toast[data-tone="sun"] .dot { background: var(--sun-300); color: #3b2a12; }
c29-toasts .toast[data-tone="bad"] .dot { background: var(--bad); color: var(--on-bad); }
c29-toasts .toast .drain { position: absolute; left: 0; bottom: 0; height: 2.5px; width: 100%; background: var(--accent); transform-origin: 0 50%; opacity: .7; }
c29-toasts .toast .t-btn { min-height: 40px; }
c29-toasts:hover .toast, c29-toasts:focus-within .toast { transition-duration: var(--d4); }

/* ======================================================
   BOTTOM SHEET  <c29-sheet> wraps native <dialog>
   ====================================================== */
dialog.t-sheet { position: fixed; inset: auto 0 0 0; margin: 0 auto; width: min(560px, 100%); max-width: 100%; max-height: min(88dvh, 760px);
  border-radius: calc(var(--corner) + 6px) calc(var(--corner) + 6px) 0 0; background: var(--raised); box-shadow: var(--lift-hi);
  padding: 0 var(--s5) calc(var(--s6) + var(--safe-b)); overflow: auto; overscroll-behavior: contain;
  transform: translateY(100%); transition: transform var(--d5) var(--ease-sheet), display var(--d5) allow-discrete, overlay var(--d5) allow-discrete; }
dialog.t-sheet[open] { transform: translateY(0); }
@starting-style { dialog.t-sheet[open] { transform: translateY(100%); } }
dialog.t-sheet::backdrop { background: color-mix(in srgb, #0b1622 34%, transparent); backdrop-filter: blur(2px); opacity: 0;
  transition: opacity var(--d4) var(--ease), display var(--d4) allow-discrete, overlay var(--d4) allow-discrete; }
dialog.t-sheet[open]::backdrop { opacity: 1; }
@starting-style { dialog.t-sheet[open]::backdrop { opacity: 0; } }
dialog.t-sheet.is-held { transition: none; }
.t-sheet .grab { display: block; margin: 0 auto; width: 100%; height: 28px; border: 0; background: var(--raised); cursor: grab; touch-action: none; position: sticky; top: 0; z-index: 2; }
/* The grab strip is a pointer affordance, not a focus destination.
   showModal's initial focus lands on it (first focusable element in the
   sheet) and the global :focus-visible ring painted a sea-green outline
   around the full-width strip — the "green border" defect (Rev 1,
   2026-10-17). Sheet focus lives on the dialog itself (tide.js sheet()).
   The grab also paints the sheet's own background so content scrolling
   beneath the sticky strip no longer ghosts through it. */
.t-sheet .grab:focus-visible { box-shadow: none; border-radius: 0; }
.t-sheet .grab::before { content: ""; display: block; margin: 10px auto 0; width: 40px; height: 5px; border-radius: 99px; background: var(--grab-pill); }
.t-sheet h2 { font-size: var(--fs-xl); margin-bottom: var(--s2); }
.t-sheet .sheet-body { display: grid; gap: var(--s4); grid-template-columns: minmax(0, 1fr); }

/* ======================================================
   SNOOZE  <c29-snooze>   presets + [MIT-PORT time-scrubber] ruler
   ====================================================== */
c29-snooze { display: grid; gap: var(--s4); }
c29-snooze .sz-presets { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
c29-snooze .sz-presets button { min-height: 64px; border: 0; border-radius: var(--r-md); background: var(--surface); box-shadow: var(--hair); text-align: left;
  padding: 10px 14px; cursor: pointer; display: grid; gap: 2px; transition: background var(--d2) var(--ease), box-shadow var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-snooze .sz-presets button b { font-size: 15px; font-weight: 650; }
c29-snooze .sz-presets button span { font-size: 13px; color: var(--ink-mute); }
c29-snooze .sz-presets button[aria-pressed="true"] { background: var(--accent-soft); box-shadow: inset 0 0 0 1.5px var(--accent); }
/* Rev 5 (2026-10-18): the selected preset sits on the --accent-soft tint,
   where the sublabel's --ink-mute fell to 3.99:1 in dark. On the tint the
   sublabel steps up one ink level: --ink-soft measures 6.71:1 on the dark
   tint and stays clear on the light tint too. Unselected presets keep
   --ink-mute on --surface, which passes in both themes. */
c29-snooze .sz-presets button[aria-pressed="true"] span { color: var(--ink-soft); }
c29-snooze .sz-presets button:active { transform: scale(.97); }
c29-snooze .sz-read { text-align: center; }
c29-snooze .sz-time { font: 750 var(--fs-3xl)/1.1 var(--font-display); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
c29-snooze .sz-day { font-size: 14px; color: var(--ink-mute); }
c29-snooze .sz-ruler { position: relative; height: 64px; border-radius: var(--r-md); background: var(--sunken); overflow: hidden; touch-action: none; cursor: ew-resize;
  -webkit-mask: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent); mask: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent); }
c29-snooze .sz-ruler:focus-visible { box-shadow: var(--focus); }
c29-snooze .sz-ticks { position: absolute; inset: 0; }
c29-snooze .sz-ticks i { position: absolute; bottom: 14px; width: 2px; border-radius: 2px; background: var(--ink); translate: -1px 0; }
c29-snooze .sz-ticks em { position: absolute; bottom: 0; font: 600 10px/1 var(--font-ui); font-style: normal; color: var(--ink-mute); translate: -50% 0; white-space: nowrap; }
c29-snooze .sz-mark { position: absolute; left: 50%; top: 6px; bottom: 10px; width: 3px; translate: -1.5px 0; border-radius: 3px; background: var(--accent); }

/* ======================================================
   COMPOSER  <c29-composer>   (clean-room, spec: ai-chat-input)
   pill → box on one springy curve; radius never changes
   ====================================================== */
c29-composer { display: block; --c-h: 52px; --c-open: 120px; }
c29-composer .cp-box { position: relative; min-height: var(--c-h); height: var(--c-h); border-radius: 26px; background: var(--raised);
  box-shadow: 0 0 0 1px var(--edge), var(--lift); transition: height var(--d4) var(--ease-give), box-shadow var(--d3) var(--ease); overflow: hidden;
  display: flex; flex-direction: column; }
c29-composer.is-open textarea { padding-bottom: 48px; }
c29-composer.is-open .cp-box { height: var(--c-open); box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--accent) 60%, transparent), var(--lift-hi); }
c29-composer textarea { flex: 1; width: 100%; resize: none; border: 0; background: none; outline: none; padding: 14px 56px 12px 20px; font-size: 16px; line-height: 1.45; color: var(--ink); min-height: 0; }
c29-composer:not(.is-open) textarea { white-space: nowrap; overflow: hidden; }
c29-composer textarea::placeholder { color: var(--ink-mute); }
c29-composer .cp-tools { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 6px; padding: 0 12px 10px; opacity: 0; transform: translateY(6px);
  transition: opacity var(--d2) var(--ease), transform var(--d3) var(--ease); pointer-events: none; overflow-x: auto; scrollbar-width: none; margin-right: 54px; }
c29-composer.is-open .cp-tools { opacity: 1; transform: none; pointer-events: auto; transition-delay: var(--d1); }
c29-composer .cp-tools .t-chip { min-height: 32px; padding: 0 12px; font-size: 13px; flex: none; }
c29-composer .cp-send { position: absolute; right: 6px; bottom: 6px; width: 40px; height: 40px; border-radius: 50%; border: 0; display: grid; place-items: center; cursor: pointer;
  background: transparent; color: var(--ink-mute); transition: background var(--d3) var(--ease), color var(--d3) var(--ease), transform var(--d1) var(--ease); }
c29-composer .cp-send svg { position: absolute; width: 20px; height: 20px; transition: opacity var(--d2) var(--ease), transform var(--d3) var(--ease-give); }
c29-composer .cp-send .send { opacity: 0; transform: translateY(8px) scale(.6); }
c29-composer.has-text .cp-send { background: var(--ink); color: var(--bg); }
c29-composer.has-text .cp-send .mic { opacity: 0; transform: scale(.6); }
c29-composer.has-text .cp-send .send { opacity: 1; transform: none; }
c29-composer.is-sent .cp-send { transform: translateY(-4px) scale(.92); }
c29-composer .cp-ctx { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 13px; color: var(--ink-mute); min-height: 20px; }
c29-composer .cp-ctx:empty { display: none; }

/* ---------- voice (D3 variant: absorbs c29-voicebar) ----------
   The voice strip lives inside the box, above the tools: state
   word, ONE level meter painted from the single host-set value,
   and the interim words as plain visible text. Nothing types
   itself; text appears only when the host's recognizer reports
   it, and final text lands in the textarea (see tide.js). */
c29-composer .cp-voice { display: flex; align-items: center; gap: 8px; margin: 0 64px 0 12px; padding: 8px 12px; border-radius: var(--r-md);
  background: var(--accent-soft); color: var(--accent-text); font-size: 13.5px; position: relative; z-index: 1; }
c29-composer .cp-voice[hidden] { display: none; }
c29-composer.is-open .cp-voice { margin-bottom: 44px; }
c29-composer .cp-voice-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }
c29-composer[data-voice="listening"] .cp-voice-dot { animation: cp-pulse 1.2s var(--ease) infinite; }
@keyframes cp-pulse { 50% { opacity: .35; } }
html[data-motion="reduced"] c29-composer[data-voice="listening"] .cp-voice-dot { animation: none; }
c29-composer .cp-voice-lab { font-weight: 650; flex: none; }
c29-composer .cp-voice-meter { flex: none; width: 56px; height: 6px; border-radius: 99px; background: var(--wash-2); overflow: hidden; }
c29-composer .cp-voice-meter i { display: block; height: 100%; width: 0; background: var(--accent); border-radius: 99px; transition: width var(--d1) linear; }
c29-composer .cp-voice-live { flex: 1; min-width: 0; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
c29-composer .cp-voice-x { flex: none; width: 32px; height: 32px; border: 0; border-radius: 50%; background: none; color: inherit; display: grid; place-items: center; cursor: pointer; }
c29-composer .cp-voice-x .t-icon { width: 15px; height: 15px; }
c29-composer[data-voice="error"] .cp-voice { background: var(--bad-bg); color: var(--bad-text); }
c29-composer[data-voice="error"] .cp-voice-dot { background: var(--bad); animation: none; }
c29-composer[data-voice="error"] .cp-voice-lab { font-weight: 600; }
c29-composer[data-voice="transcribing"] .cp-voice-dot { background: var(--ink-mute); animation: none; }
c29-composer .cp-send .stopic { opacity: 0; transform: scale(.6); }
c29-composer[data-voice="listening"] .cp-send, c29-composer[data-voice="transcribing"] .cp-send { background: var(--bad); color: var(--on-bad); }
c29-composer[data-voice="listening"] .cp-send .mic, c29-composer[data-voice="transcribing"] .cp-send .mic,
c29-composer[data-voice="listening"] .cp-send .send, c29-composer[data-voice="transcribing"] .cp-send .send { opacity: 0; }
c29-composer[data-voice="listening"] .cp-send .stopic, c29-composer[data-voice="transcribing"] .cp-send .stopic { opacity: 1; transform: none; }
c29-composer .cp-tools .cp-dictate .t-icon { width: 15px; height: 15px; }

/* ======================================================
   FEEDBACK BAR  <c29-feedback>   (clean-room, spec: feedback-bar)
   one bar changing shape; width eases to measured content
   ====================================================== */
c29-feedback { display: inline-block; max-width: 100%; }
c29-feedback .fb { display: inline-flex; align-items: center; overflow: hidden; border-radius: 26px; background: var(--surface); box-shadow: var(--hair);
  min-height: 52px; transition: width var(--d4) var(--ease), height var(--d4) var(--ease); max-width: 100%; }
c29-feedback .fb-in { display: flex; align-items: center; gap: 6px; padding: 4px 6px 4px 16px; flex-wrap: wrap; animation: t-in var(--d3) var(--ease) both; }
c29-feedback .fb-in > span { font-size: 15px; font-weight: 550; margin-right: 4px; }
c29-feedback .fb-thumb { width: 44px; height: 44px; border: 0; border-radius: 50%; background: transparent; cursor: pointer; display: grid; place-items: center; color: var(--ink-soft);
  transition: background var(--d2) var(--ease), transform var(--d3) var(--ease-give); }
c29-feedback .fb-thumb:hover { background: var(--wash); }
c29-feedback .fb-thumb.is-on { background: var(--accent); color: var(--on-accent); animation: t-pop var(--d4) var(--ease-give); }
c29-feedback .fb-thumb.is-shake { background: var(--ink); color: var(--bg); animation: t-shake var(--d4) var(--ease); }
c29-feedback .fb-reasons { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 6px; width: 100%; }
@keyframes t-in { from { opacity: 0; filter: blur(4px); transform: translateY(4px); } }
@keyframes t-pop { 40% { transform: scale(1.18); } }

/* ======================================================
   DECISION INBOX  <c29-inbox>   (clean-room, specs: activity-stack + access-request)
   stacked by depth; press opens into the list; rows approve/dismiss
   ====================================================== */
c29-inbox { display: block; --peek: 10px; }
c29-inbox .ib-pile { position: relative; display: block; width: 100%; border: 0; padding: 0; background: none; text-align: left; cursor: pointer; color: inherit;
  height: calc(92px + 2 * var(--peek)); transition: height var(--d4) var(--ease); }
c29-inbox .ib-card { position: absolute; left: 0; right: 0; bottom: 0; height: 92px; border-radius: var(--corner); background: var(--raised); box-shadow: var(--lift);
  padding: 14px 16px; display: flex; gap: 12px; align-items: center; transform-origin: 50% 0;
  transition: transform var(--d5) var(--ease-give), opacity var(--d4) var(--ease); }
c29-inbox .ib-card .ib-av { width: 44px; height: 44px; border-radius: 14px; display: grid; place-items: center; flex: none; background: var(--accent-soft); color: var(--accent); }
c29-inbox .ib-card .ib-av.money { background: color-mix(in srgb, var(--money) 14%, var(--surface)); color: var(--money); }
c29-inbox .ib-card .ib-t { min-width: 0; flex: 1; }
c29-inbox .ib-card .ib-t b { display: block; font-size: 15px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
c29-inbox .ib-card .ib-t span { display: block; font-size: 13px; color: var(--ink-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
c29-inbox .ib-badge { position: absolute; z-index: 9; top: -6px; right: 10px; min-width: 26px; height: 26px; padding: 0 8px; border-radius: 99px; background: var(--money); color: var(--on-money);
  font: 700 13px/26px var(--font-ui); text-align: center; font-variant-numeric: tabular-nums; box-shadow: 0 0 0 3px var(--bg); transition: transform var(--d3) var(--ease-give); }
c29-inbox .ib-badge.bump { transform: scale(1.25); }
c29-inbox.is-open .ib-pile { height: 64px; }
c29-inbox .ib-card.ib-fold { height: 64px; background: var(--accent-soft); box-shadow: var(--hair); }
c29-inbox .ib-list { display: grid; gap: 8px; margin-top: var(--s3); }
c29-inbox .ib-row { display: grid; grid-template-columns: 40px 1fr; gap: 6px 12px; align-items: center; padding: 12px 12px 12px 14px; border-radius: var(--r-md); background: var(--surface); box-shadow: var(--hair);
  transition: opacity var(--d3) var(--ease), transform var(--d4) var(--ease), grid-template-rows var(--d4) var(--ease); }
c29-inbox .ib-row .ib-av { width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center; background: var(--accent-soft); color: var(--accent); }
c29-inbox .ib-row .ib-av.money { background: color-mix(in srgb, var(--money) 14%, var(--surface)); color: var(--money); }
c29-inbox .ib-row .ib-t b { display: block; font-size: 15px; font-weight: 650; }
c29-inbox .ib-row .ib-t span { display: block; font-size: 13px; color: var(--ink-mute); }
c29-inbox .ib-row .ib-acts { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
c29-inbox .ib-row.is-out { opacity: 0; transform: translateX(40px) scale(.98); }
c29-inbox .ib-row.is-out-l { opacity: 0; transform: translateX(-40px) scale(.98); }
c29-inbox .ib-empty { text-align: center; padding: var(--s6); color: var(--ink-mute); border-radius: var(--corner); box-shadow: inset 0 0 0 1.5px var(--edge); }

/* ======================================================
   ROW LIST  <c29-list>  +  STAR  <c29-star>
   (2026-10-19, Desk composition rebuild — rows that open;
   interiors are composed by the host from t-* primitives)
   ====================================================== */
c29-list { display: grid; gap: var(--s2); list-style: none; }
/* Row chrome + bullet suppression are PURE CSS on the <li> itself —
   never a side effect of the JS-added .ls-row class. Post-mortem
   2026-10-19 (Desk iOS render failure): a fresh page paired with a
   stale cached tide.js (no c29-list definition) left rows un-adopted,
   and every protection the component had rode on adoption — native
   bullets rendered and the row interior fell back to inline flow.
   Canon law from that day: a component's layout must hold with its
   JS dead. .ls-row stays as the adoption marker for compat. */
c29-list > li { list-style: none; margin: 0; padding: 0; }
c29-list > li::marker { content: none; }
c29-list > li, c29-list .ls-row { display: flex; align-items: stretch; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); overflow: hidden; }
c29-list [data-open] { flex: 1 1 auto; min-width: 0; display: grid; gap: 3px; align-content: center; text-align: left; padding: var(--s3) var(--s4); font: inherit; color: inherit; background: transparent; border: 0; cursor: pointer; }
c29-list [data-open]:active { background: var(--wash); }
c29-list .ls-row > c29-star { align-self: center; margin-right: var(--s2); }
c29-list .ls-empty { text-align: center; padding: var(--s6); color: var(--ink-mute); border-radius: var(--corner); box-shadow: inset 0 0 0 1.5px var(--edge); }

c29-star { display: inline-flex; flex: none; }
c29-star .st-btn { width: var(--tap); height: var(--tap); display: grid; place-items: center; border: 0; border-radius: var(--r-md); background: transparent; color: var(--ink-mute); cursor: pointer; transition: color var(--d2) var(--ease), background var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-star .st-btn:hover { background: var(--wash); }
c29-star .st-btn:active { transform: scale(.92); }
c29-star .st-btn svg { width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
c29-star[pressed] .st-btn { color: var(--29d-amber-600); }
c29-star[pressed] .st-btn svg { fill: currentColor; }

/* ======================================================
   BUY  <c29-buy>   (clean-room, spec: pay-button) — one shape the whole way
   ====================================================== */
c29-buy { display: block; }
c29-buy .by { position: relative; display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%; height: 56px; border: 0; border-radius: 28px;
  background: var(--money); color: var(--on-money); font-weight: 700; font-size: 16px; cursor: pointer; overflow: hidden;
  transition: width var(--d4) var(--ease-sheet), background var(--d3) var(--ease), transform var(--d1) var(--ease); }
c29-buy .by:active { transform: scale(.98); }
c29-buy .by-l { display: flex; align-items: center; gap: 8px; white-space: nowrap; transition: opacity var(--d2) var(--ease), filter var(--d2) var(--ease); }
c29-buy .by-l svg { width: 16px; height: 16px; }
c29-buy .by-price { font-variant-numeric: tabular-nums; padding-left: 8px; margin-left: 2px; border-left: 1px solid color-mix(in srgb, currentColor 35%, transparent); }
c29-buy .by-track { position: absolute; left: 22px; right: 22px; top: 50%; height: 3px; translate: 0 -50%; border-radius: 3px; background: color-mix(in srgb, currentColor 25%, transparent); opacity: 0; overflow: hidden; }
c29-buy .by-track::after { content: ""; position: absolute; inset: 0; width: 40%; border-radius: 3px; background: currentColor; translate: -100% 0; }
c29-buy .by-ok { position: absolute; display: flex; align-items: center; gap: 8px; opacity: 0; white-space: nowrap; }
c29-buy .by-ok path { stroke-dasharray: 24; stroke-dashoffset: 24; transition: stroke-dashoffset var(--d4) var(--ease) var(--d3); }
c29-buy[data-state="processing"] .by { width: 128px; }
c29-buy[data-state="processing"] .by-l { opacity: 0; filter: blur(3px); }
c29-buy[data-state="processing"] .by-track { opacity: 1; transition: opacity var(--d2) var(--ease) var(--d3); }
c29-buy[data-state="processing"] .by-track::after { animation: t-run calc(1100ms / var(--speed)) var(--ease) infinite; }
c29-buy[data-state="success"] .by { background: var(--ok); color: var(--on-ok); }
c29-buy[data-state="success"] .by-l { opacity: 0; }
c29-buy[data-state="success"] .by-ok { opacity: 1; transition: opacity var(--d3) var(--ease) var(--d3); }
c29-buy[data-state="success"] .by-ok path { stroke-dashoffset: 0; }
c29-buy .by-meta { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 10px; font-size: 13px; color: var(--ink-mute); flex-wrap: wrap; text-align: center; }
c29-buy .by-receipt { margin-top: 10px; }
@keyframes t-run { to { translate: 260% 0; } }

/* ======================================================
   UPGRADE / RESET / LAPSED  <c29-upgrade>
   ====================================================== */
c29-upgrade { display: grid; position: relative; border-radius: var(--corner); overflow: hidden; box-shadow: var(--hair); background: var(--surface); }
c29-upgrade .up-preview, c29-upgrade .up-gate { grid-area: 1 / 1; }
c29-upgrade .up-preview { padding: var(--s5); filter: blur(5px) saturate(.8); opacity: .55; pointer-events: none; user-select: none; }
c29-upgrade[data-unlocked] .up-preview { filter: none; opacity: 1; pointer-events: auto; transition: filter var(--d5) var(--ease), opacity var(--d5) var(--ease); }
c29-upgrade .up-gate { display: grid; place-items: center; padding: var(--s4); transition: opacity var(--d3) var(--ease); }
c29-upgrade[data-unlocked] .up-gate { opacity: 0; pointer-events: none; }
c29-upgrade .up-card { max-width: 320px; width: 100%; text-align: center; display: grid; gap: 10px; justify-items: center; padding: var(--s5); }
c29-upgrade .up-card .t-pane { padding: 0; }
c29-upgrade .up-ic { width: 44px; height: 44px; border-radius: 14px; display: grid; place-items: center; background: var(--sun-100); color: #8a5a17; }
[data-theme="dark"] c29-upgrade .up-ic { background: #3a2a14; color: var(--sun-300); }
c29-upgrade h3 { font-size: var(--fs-lg); }
c29-upgrade p { font-size: 14px; color: var(--ink-soft); }

/* ======================================================
   ACCOUNT  <c29-account>
   ====================================================== */
c29-account { display: inline-block; position: relative; flex: none; }
c29-account > .t-btn { padding: 0 14px; }
c29-account .ac-av { width: 44px; height: 44px; border-radius: 50%; border: 0; cursor: pointer; padding: 3px; background: conic-gradient(from 200deg, var(--sea-300), var(--sun-300), var(--sea-300)); }
c29-account .ac-av span { display: grid; place-items: center; width: 100%; height: 100%; border-radius: 50%; background: var(--raised); font-weight: 750; font-size: 15px; color: var(--ink); }
c29-account [popover] { position: fixed; inset: auto; margin: 0; border: 0; padding: 8px; min-width: 240px; border-radius: 18px; background: var(--pane-strong);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); box-shadow: 0 0 0 1px var(--pane-edge), var(--lift-hi); color: var(--ink);
  opacity: 0; transform: translateY(-6px) scale(.97); transform-origin: 100% 0;
  transition: opacity var(--d2) var(--ease), transform var(--d3) var(--ease-give), display var(--d3) allow-discrete, overlay var(--d3) allow-discrete; }
c29-account [popover]:popover-open { opacity: 1; transform: none; }
@starting-style { c29-account [popover]:popover-open { opacity: 0; transform: translateY(-6px) scale(.97); } }
c29-account .ac-who { padding: 10px 12px 12px; border-bottom: 1px solid var(--edge-soft); margin-bottom: 6px; }
c29-account .ac-who b { display: block; font-size: 15px; } c29-account .ac-who span { font-size: 13px; color: var(--ink-mute); }
c29-account .ac-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 0 12px; border: 0; border-radius: 12px; background: none; cursor: pointer; font-size: 15px; text-align: left; color: var(--ink); }
c29-account .ac-item:hover { background: var(--wash); }
.c29-if-member, .c29-if-anon { display: contents; }
html[data-session="anon"] .c29-if-member, html:not([data-session="anon"]) .c29-if-anon { display: none; }

/* ======================================================
   ONE-TIME CODE  <c29-otp>   [MIT-PORT one-time-code: one real input under six drawn cells]
   ====================================================== */
c29-otp { display: block; position: relative; max-width: 360px; }
c29-otp input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; font-size: 17px; letter-spacing: 2em; caret-color: transparent; border: 0; z-index: 2; }
c29-otp .otp-cells { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
c29-otp .otp-cells i { height: 58px; border-radius: 14px; background: var(--raised); box-shadow: 0 0 0 1px var(--edge); display: grid; place-items: center; font: 700 24px/1 var(--font-display); font-style: normal;
  font-variant-numeric: tabular-nums; transition: box-shadow var(--d2) var(--ease), transform var(--d3) var(--ease-give), border-radius var(--d4) var(--ease), background var(--d3) var(--ease); }
c29-otp .otp-cells i.is-cur { box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 5px color-mix(in srgb, var(--accent) 15%, transparent); }
c29-otp .otp-cells i.is-cur::after { content: ""; width: 2px; height: 24px; background: var(--accent); animation: t-blink 1s steps(1) infinite; }
c29-otp .otp-cells i.pop { transform: scale(1.08); }
c29-otp[data-state="ok"] .otp-cells { gap: 0; transition: gap var(--d4) var(--ease); }
c29-otp[data-state="ok"] .otp-cells i { border-radius: 0; background: var(--accent-soft); box-shadow: none; color: var(--accent-text); }
c29-otp[data-state="ok"] .otp-cells i:first-child { border-radius: 14px 0 0 14px; } c29-otp[data-state="ok"] .otp-cells i:last-child { border-radius: 0 14px 14px 0; }
c29-otp[data-state="bad"] .otp-cells { animation: t-shake var(--d4) var(--ease); }
c29-otp[data-state="bad"] .otp-cells i { box-shadow: 0 0 0 1.5px var(--bad); }
@keyframes t-blink { 50% { opacity: 0; } }

/* ======================================================
   COPY FIELD  <c29-copy>   (clean-room, spec: copy-field)
   ====================================================== */
c29-copy { min-width: 0; display: flex; align-items: center; gap: 4px; min-height: 52px; padding: 4px 4px 4px 16px; border-radius: var(--r-md); background: var(--sunken); box-shadow: inset 0 0 0 1px var(--edge-soft); max-width: 100%; }
c29-copy code { flex: 1; min-width: 0; font: 500 14px/1.3 var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: relative;
  background: linear-gradient(90deg, var(--ink) 0 40%, var(--accent) 50%, var(--ink) 60%) 100% 0/250% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; }
c29-copy.is-copied code { animation: t-sweep calc(700ms / var(--speed)) var(--ease) 1 both; }
c29-copy button { position: relative; width: 44px; height: 44px; border: 0; border-radius: 12px; background: var(--raised); box-shadow: var(--hair); cursor: pointer; display: grid; place-items: center; flex: none; color: var(--ink-soft); }
c29-copy button svg { position: absolute; width: 18px; height: 18px; transition: opacity var(--d2) var(--ease), transform var(--d2) var(--ease); }
c29-copy button .pg-b { transform: translate(2px, -2px); }
c29-copy button .ok { opacity: 0; color: var(--accent); }
c29-copy button .ok path { stroke-dasharray: 24; stroke-dashoffset: 24; transition: stroke-dashoffset var(--d3) var(--ease) var(--d2); }
c29-copy.is-copied .pg-b { transform: none; opacity: 0; } c29-copy.is-copied .pg-f { opacity: 0; transition-delay: var(--d1); }
c29-copy.is-copied .ok { opacity: 1; transition-delay: var(--d1); } c29-copy.is-copied .ok path { stroke-dashoffset: 0; }

/* ======================================================
   CONNECT  <c29-connect>
   ====================================================== */
c29-connect { display: grid; grid-template-columns: 44px 1fr auto; align-items: center; gap: 12px; padding: 12px 12px 12px 14px; border-radius: var(--r-md); background: var(--surface); box-shadow: var(--hair); position: relative; overflow: hidden; }
c29-connect .cn-logo { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; background: var(--sunken); font-weight: 800; font-size: 16px; color: var(--ink-soft); }
c29-connect .cn-t b { display: block; font-size: 15px; font-weight: 650; }
c29-connect .cn-t span { display: block; font-size: 13px; color: var(--ink-mute); min-height: 18px; }
c29-connect .cn-bar { position: absolute; left: 0; bottom: 0; height: 2.5px; width: 100%; background: var(--accent); transform: scaleX(0); transform-origin: 0 50%; transition: transform var(--d5) var(--ease); }
c29-connect[data-state="syncing"] .cn-bar { transform: scaleX(.7); transition-duration: calc(1400ms / var(--speed)); }
c29-connect[data-state="on"] .cn-bar { transform: scaleX(1); opacity: 0; transition: transform var(--d3) var(--ease), opacity var(--d4) var(--ease) var(--d3); }
c29-connect[data-state="on"] .cn-logo { background: var(--accent-soft); color: var(--accent); }

/* ---------- recover / reset prompt ---------- */
c29-recover { display: block; }

/* ======================================================
   SKELETON / SHIMMER (clean-room, specs: skeleton-morph, shimmer-text)
   ====================================================== */
.t-skel { position: relative; overflow: hidden; border-radius: 10px; background: var(--sunken); min-height: 14px; }
.t-skel::after { content: ""; position: absolute; inset: 0; translate: -100% 0;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--sun-100) 55%, transparent), transparent); animation: t-wave calc(var(--shimmer-period) / var(--speed)) var(--ease) infinite; }
[data-theme="dark"] .t-skel::after { background: linear-gradient(90deg, transparent, rgba(220,240,255,.06), transparent); }
@keyframes t-wave { 60%, 100% { translate: 100% 0; } }
.t-shimmer { background: linear-gradient(90deg, var(--ink-mute) 0 35%, var(--ink) 50%, var(--ink-mute) 65%) 0 0/250% 100%; -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: t-sweep calc(var(--shimmer-period) / var(--speed)) linear infinite; }
.t-morph > * { animation: t-rise var(--d5) var(--ease) both; }
.t-morph > *:nth-child(2) { animation-delay: calc(80ms / var(--speed)); } .t-morph > *:nth-child(3) { animation-delay: calc(160ms / var(--speed)); }
.t-morph > *:nth-child(4) { animation-delay: calc(240ms / var(--speed)); }
@keyframes t-rise { from { opacity: 0; transform: translateY(8px); filter: blur(5px); } }

/* ======================================================
   TRACE / TIMELINE  <c29-trace>   (clean-room, spec: thinking) + [MIT-PORT step-player dots]
   ====================================================== */
c29-trace { display: block; }
c29-trace .tr-head { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; border: 0; background: none; padding: 0; cursor: pointer; text-align: left; font-size: 15px; font-weight: 650; color: var(--ink); }
c29-trace .tr-head svg { transition: transform var(--d3) var(--ease); }
c29-trace[data-folded] .tr-head svg.chev { transform: rotate(-90deg); }
c29-trace .tr-body { display: grid; grid-template-rows: 1fr; transition: grid-template-rows var(--d5) var(--ease); }
c29-trace[data-folded] .tr-body { grid-template-rows: 0fr; }
c29-trace .tr-body > ol { overflow: hidden; margin: 0; padding: 0; list-style: none; }
c29-trace li { position: relative; padding: 0 0 18px 34px; animation: t-rise var(--d4) var(--ease) both; }
c29-trace li::before { content: ""; position: absolute; left: 10px; top: 22px; bottom: -2px; width: 2px; border-radius: 2px; background: var(--edge); }
c29-trace li:last-child::before { display: none; }
c29-trace li .dot { position: absolute; left: 3px; top: 3px; width: 16px; height: 16px; border-radius: 50%; background: var(--raised); box-shadow: inset 0 0 0 2px var(--accent); }
c29-trace li[data-s="done"] .dot { background: var(--accent); box-shadow: none; }
c29-trace li[data-s="done"] .dot::after { content: ""; position: absolute; left: 5px; top: 3px; width: 4px; height: 7px; border: solid var(--on-accent); border-width: 0 2px 2px 0; rotate: 45deg; }
c29-trace li[data-s="run"] .dot { animation: t-breathe 1.6s var(--ease) infinite; }
c29-trace li[data-s="wait"] .dot { box-shadow: inset 0 0 0 2px var(--sun-500); background: var(--sun-100); }
c29-trace li b { display: block; font-size: 15px; font-weight: 600; }
c29-trace li span { display: block; font-size: 13px; color: var(--ink-mute); }
c29-trace li .tr-acts { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
c29-trace li .tr-note { margin-top: 8px; padding: 10px 12px; border-radius: 12px; background: var(--accent-soft); font-size: 14px; color: var(--ink-soft); animation: t-rise var(--d4) var(--ease) both; }
@keyframes t-breathe { 50% { box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 6px color-mix(in srgb, var(--accent) 18%, transparent); } }

/* ---------- checklist  [MIT-PORT checklist: box fills, tick draws, words strike] ---------- */
.t-check { display: flex; align-items: center; gap: 12px; min-height: 44px; cursor: pointer; font-size: 15px; }
.t-check input { appearance: none; width: 24px; height: 24px; border-radius: 8px; box-shadow: inset 0 0 0 1.5px var(--edge); margin: 0; flex: none; display: grid; place-items: center; cursor: pointer;
  transition: background var(--d2) var(--ease), box-shadow var(--d2) var(--ease); }
.t-check input::after { content: ""; width: 6px; height: 11px; border: solid var(--on-accent); border-width: 0 2.2px 2.2px 0; rotate: 45deg; translate: 0 -1px; scale: 0; transition: scale var(--d3) var(--ease-give); }
.t-check input:checked { background: var(--accent); box-shadow: none; }
.t-check input:checked::after { scale: 1; }
.t-check span { background: linear-gradient(currentColor, currentColor) 0 55%/0% 1.5px no-repeat; transition: background-size var(--d4) var(--ease), color var(--d3) var(--ease); }
.t-check input:checked + span { background-size: 100% 1.5px; color: var(--ink-mute); }

/* ---------- tune panel ---------- */
.t-tune { display: grid; gap: var(--s4); }
.t-tune .tn-row { display: grid; grid-template-columns: 76px 1fr 46px; align-items: center; gap: 10px; font-size: 14px; }
.t-tune .tn-row output { font-variant-numeric: tabular-nums; color: var(--ink-mute); text-align: right; font-size: 13px; }
.t-tune input[type=range] { width: 100%; accent-color: var(--accent); height: 44px; margin: 0; }
.t-tune .tn-sw { display: flex; gap: 8px; flex-wrap: wrap; }
.t-tune .tn-sw button { width: 40px; height: 40px; border-radius: 50%; border: 0; cursor: pointer; box-shadow: inset 0 0 0 2px rgba(255,255,255,.4); }
.t-tune .tn-sw button[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink); }


/* ---------- TASTE WAVE 1 (2026-10-19) ---------- */
/* ============================================================
 * LANE A / A1 — c29-showcode + c29-countdown CSS FRAGMENT
 * Integration: paste inside the @layer c29.components block of
 * tide.css (the fragment ships wrapped in that layer so it also
 * loads standalone after tide.css in the lane harness).
 * Tokens only — no colour literals anywhere in this file.
 * ============================================================ */

/* ---------- SHOWCODE  <c29-showcode> ---------- */
c29-showcode { display: grid; gap: var(--s2); justify-items: center; text-align: center; min-width: 0; }
c29-showcode .sc-label { display: block; }
c29-showcode .sc-code {
  font: 800 clamp(30px, 11vw, 44px)/1.15 var(--font-display);
  letter-spacing: .14em; color: var(--ink);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere; user-select: text; -webkit-user-select: text;
  max-width: 100%; padding: 0 var(--s2);
}
c29-showcode .sc-code:focus-visible { box-shadow: var(--focus); border-radius: 8px; outline: none; }
c29-showcode .sc-url, c29-showcode .sc-urltext {
  font: 500 var(--fs-sm)/1.4 var(--font-mono); color: var(--accent-text);
  overflow-wrap: anywhere; max-width: 100%;
}
c29-showcode .sc-urltext { color: var(--ink-mute); }
c29-showcode .sc-expiry { display: flex; justify-content: center; }
c29-showcode .sc-actions { display: grid; gap: var(--s2); justify-items: center; margin-top: var(--s1); }
c29-showcode .sc-copy { min-width: 168px; }
c29-showcode .sc-copy[disabled] { opacity: .45; pointer-events: none; }
/* The status line is ALWAYS in the layout (min-height) so an
   answer never shifts the page — and failure is a sentence in
   --bad-text, not a colour alone. */
c29-showcode .sc-status { min-height: 22px; font-size: var(--fs-sm); font-weight: 600; color: var(--accent-text); max-width: 34ch; }
c29-showcode[data-state="copied"] .sc-status { color: var(--ok-text); }
c29-showcode[data-state="copy-failed"] .sc-status { color: var(--bad-text); }
c29-showcode[data-state="empty"] .sc-code { color: var(--ink-faint); }
c29-showcode[data-state="empty"] .sc-status { color: var(--ink-mute); font-weight: 550; }

/* ---------- COUNTDOWN  <c29-countdown> ---------- */
c29-countdown { display: inline-flex; align-items: baseline; gap: var(--s2); font-variant-numeric: tabular-nums; }
c29-countdown .cd-label { font-size: var(--fs-sm); color: var(--ink-mute); font-weight: 550; }
c29-countdown .cd-num { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); min-width: 5ch; }
c29-countdown[data-state="expired"] .cd-num { color: var(--bad-text); }
c29-countdown[data-state="expired"] .cd-label { color: var(--bad-text); font-weight: 650; }
c29-countdown[data-state="unset"] .cd-num { color: var(--ink-faint); }
c29-countdown[data-state="paused"] .cd-num { color: var(--ink-mute); }
/* ============================================================
 * LANE A / A3 — c29-log CSS FRAGMENT
 * Integration: paste inside the @layer c29.components block of
 * tide.css. TOKENS ONLY: this file contains no hex / rgb / hsl
 * colour literal of any kind — grep it. The sunken pair
 * (--sunken background, --ink text) resolves per theme from
 * tokens.css, so a dark-on-light rendering cannot be written.
 * ============================================================ */

c29-log { display: block; min-width: 0; }
c29-log .lg-lines {
  list-style: none; margin: 0; padding: var(--s3) var(--s4);
  background: var(--sunken); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--edge-soft);
  border-radius: calc(var(--corner) - 6px);
  font: 500 13px/1.65 var(--font-mono);
  max-height: 264px; overflow-y: auto; overflow-x: hidden;
  overflow-wrap: anywhere; word-break: break-word;
}
c29-log .lg-line { padding: 1px 0 1px var(--s3); border-left: 2px solid transparent; min-width: 0; }
/* Tones are a border + a token text colour — never colour alone:
   the tone word is the line's own text, written by the host. */
c29-log .lg-line[data-tone="ok"] { border-left-color: var(--ok); }
c29-log .lg-line[data-tone="warn"] { border-left-color: var(--warn); }
c29-log .lg-line[data-tone="bad"] { border-left-color: var(--bad); color: var(--bad-text); }
c29-log .lg-empty {
  padding: var(--s4); text-align: center;
  background: var(--sunken); color: var(--ink-mute);
  box-shadow: inset 0 0 0 1px var(--edge-soft);
  border-radius: calc(var(--corner) - 6px);
  font-size: var(--fs-sm);
}
/* ============================================================
 * LANE A / A4 — c29-steps finish-parity CSS FRAGMENT
 * The variant reuses the canon .wv-steps-* rules UNCHANGED.
 * The only addition: the latched finished state reads as
 * settled, not merely done — the status line takes the ok
 * token and the advance button (disabled by the latch) keeps
 * its label legible. Tokens only.
 * Integration: append inside @layer c29.components in tide.css,
 * after the canon c29-steps block.
 * ============================================================ */

c29-steps[data-state="finished"] .wv-steps-status { color: var(--ok-text); }
c29-steps[data-state="finished"] .wv-steps-ctl .t-btn[data-act="advance"][disabled] { opacity: .75; }
/* ============================================================
 * FIELD  <c29-field>   (Taste Fleet Wave 1, Lane B — B1)
 * Label + control + hint + error composed in place, as ONE
 * pattern. The host supplies the control (input / textarea /
 * select) and the words; the component owns the wiring:
 * label-for, aria-describedby (hint + error), aria-invalid,
 * and an error that is kind and specific — a soft bad-bg line
 * with an icon and the host's exact words, never a bare red
 * shout and never a shake. Tokens only; works Tide-only and
 * loaded the bridge way (ds-tide-bridge.css order), both themes.
 * Integration: append inside @layer c29.components in tide.css.
 * ============================================================ */
c29-field { display: grid; gap: 6px; grid-template-columns: minmax(0, 1fr); text-align: left; }
c29-field .fld-label { font-size: 13px; font-weight: 650; color: var(--ink-soft); display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
c29-field .fld-req { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--accent-text); }
c29-field .fld-opt { font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-mute); }
/* The control: the .t-input picture, owned here so a plain
   host <input> composes without extra classes. */
c29-field :is(input:not([type="checkbox"]):not([type="radio"]), textarea, select) {
  width: 100%; min-height: 48px; padding: 0 16px; border-radius: var(--r-md); border: 0;
  background: var(--raised); box-shadow: 0 0 0 1px var(--edge);
  font-size: 17px; color: var(--ink); transition: box-shadow var(--d2) var(--ease); }
c29-field textarea { min-height: 96px; padding: 12px 16px; line-height: 1.45; resize: vertical; }
c29-field select { appearance: none; }
c29-field :is(input, textarea, select):focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 5px color-mix(in srgb, var(--accent) 16%, transparent); }
c29-field :is(input, textarea, select)::placeholder { color: var(--ink-mute); opacity: 1; }
[data-theme="dark"] c29-field :is(input, textarea, select)::placeholder { color: #9AABB3; opacity: 1; }
c29-field .fld-hint { font-size: var(--fs-sm); line-height: 1.45; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-field .fld-hint:empty { display: none; }
/* Error: specific words on the soft bad ground, bad-text ink
   (the AA token pair), an icon so colour is never the only
   signal. No shake, no all-caps, no exclamation styling. */
c29-field .fld-error { display: flex; gap: 8px; align-items: flex-start; font-size: var(--fs-sm); line-height: 1.45;
  color: var(--bad-text); background: var(--bad-bg); border-radius: var(--r-md); padding: var(--s2) var(--s3); overflow-wrap: anywhere; }
c29-field .fld-error[hidden] { display: none; }
c29-field .fld-error .t-icon { width: 17px; height: 17px; flex: none; margin-top: 1px; }
c29-field[data-state="error"] :is(input, textarea, select) { box-shadow: 0 0 0 1.5px var(--bad); }
c29-field[data-state="error"] :is(input, textarea, select):focus { box-shadow: 0 0 0 1.5px var(--bad), 0 0 0 5px color-mix(in srgb, var(--bad) 16%, transparent); }
c29-field[data-state="ok"] .fld-ok { display: flex; }
c29-field .fld-ok { display: none; gap: 8px; align-items: flex-start; font-size: var(--fs-sm); line-height: 1.45; color: var(--ok-text); }
c29-field .fld-ok .t-icon { width: 17px; height: 17px; flex: none; margin-top: 1px; }
c29-field[data-disabled] { opacity: .55; }
c29-field[data-disabled] :is(input, textarea, select) { pointer-events: none; }
/* Honeypot: present in the form, absent from every human
   sense — off-screen and clipped, NOT display:none (the
   house intake's bot trap; see the showcase composition). */
c29-field[honeypot] { position: absolute !important; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; opacity: 0; pointer-events: none; }
/* ============================================================
 * OFFER  <c29-offer>   (Taste Fleet Wave 1, Lane B — B2)
 * One calm card: what it is, what it costs, what happens
 * next, one CTA. The component renders price STRUCTURES the
 * host passes in the price slot (.of-price-row / .of-amount /
 * .of-per / .of-arrow / .of-note) — pilot→ongoing and
 * install+monthly are both just two rows — and it never
 * hardcodes, parses, or restyles a number off-token: figures
 * ride the display face, tabular, in --ink; the money colour
 * appears once, on the CTA the host supplies.
 * Integration: append inside @layer c29.components in tide.css.
 * ============================================================ */
c29-offer { display: grid; gap: var(--s3); align-content: start; grid-template-columns: minmax(0, 1fr);
  background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: var(--s5); text-align: left; }
c29-offer .of-head { display: grid; gap: var(--s1); }
c29-offer .of-kicker { font-size: var(--fs-cap); letter-spacing: var(--track-cap); text-transform: uppercase; font-weight: 650; color: var(--ink-mute); }
c29-offer .of-name { font-family: var(--font-display); font-weight: 750; font-size: var(--fs-xl); letter-spacing: var(--track-head); line-height: 1.15; text-wrap: balance; overflow-wrap: anywhere; }
c29-offer .of-tagrow { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }
c29-offer .of-what { color: var(--ink-soft); font-size: 15px; line-height: 1.5; overflow-wrap: anywhere; }
c29-offer .of-what:empty { display: none; }
c29-offer .of-price { display: grid; gap: var(--s2); padding: var(--s3) 0; border-top: 1px solid var(--edge-soft); border-bottom: 1px solid var(--edge-soft); }
c29-offer .of-price:empty { display: none; }
c29-offer .of-price-row { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
c29-offer .of-amount { font: 750 var(--fs-2xl)/1.05 var(--font-display); font-variant-numeric: tabular-nums; letter-spacing: -.02em; color: var(--ink); white-space: nowrap; }
c29-offer .of-amount.is-small { font-size: var(--fs-xl); }
c29-offer .of-per { font-size: var(--fs-sm); color: var(--ink-mute); overflow-wrap: anywhere; }
c29-offer .of-arrow { color: var(--ink-mute); font-weight: 650; }
c29-offer .of-note { font-size: var(--fs-sm); color: var(--ink-mute); line-height: 1.45; overflow-wrap: anywhere; flex-basis: 100%; }
c29-offer .of-next { font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-soft); overflow-wrap: anywhere; }
c29-offer .of-next:empty { display: none; }
c29-offer .of-next b { font-weight: 650; color: var(--ink); }
c29-offer .of-cta { margin-top: auto; padding-top: var(--s2); display: grid; }
c29-offer .of-cta .t-btn { width: 100%; }
c29-offer[data-cta-extra] .of-cta-extra-note { font-size: var(--fs-cap); color: var(--warn-text); }
/* ============================================================
 * FAQ  <c29-faq>   (Taste Fleet Wave 1, Lane B — B3)
 * Questions open like a drawer, not a performance: the panel
 * travels its MEASURED height on the house curve (--ease,
 * --d3 / --speed via JS) and is instant under the motion
 * gate. The closed state is the considered state — hairline
 * rows, a quiet chevron, generous tap rows — in both themes.
 * Real <button> semantics, aria-expanded honest, focus
 * visible via the global :focus-visible ring.
 * Integration: append inside @layer c29.components in tide.css.
 * ============================================================ */
c29-faq { display: block; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: 0 var(--s5); text-align: left; }
c29-faq .faq-item + .faq-item { border-top: 1px solid var(--edge-soft); }
c29-faq .faq-qh { margin: 0; }
c29-faq .faq-btn { display: flex; align-items: center; gap: var(--s3); width: 100%; min-height: 56px; padding: var(--s3) 0;
  border: 0; background: none; cursor: pointer; text-align: left;
  font: 650 15.5px/1.35 var(--font-ui); color: var(--ink); -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
c29-faq .faq-btn:hover { color: var(--accent-text); }
c29-faq .faq-q { flex: 1; min-width: 0; overflow-wrap: anywhere; }
c29-faq .faq-chev { flex: none; width: 30px; height: 30px; border-radius: 50%; background: var(--wash); display: grid; place-items: center; color: var(--ink-soft);
  transition: transform var(--d3) var(--ease), background var(--d2) var(--ease); }
c29-faq .faq-chev .t-icon { width: 16px; height: 16px; }
c29-faq .faq-btn[aria-expanded="true"] .faq-chev { transform: rotate(180deg); background: var(--accent-soft); color: var(--accent-text); }
c29-faq .faq-panel { overflow: hidden; }
c29-faq .faq-panel[hidden] { display: none; }
c29-faq .faq-a { padding: 0 0 var(--s5); color: var(--ink-soft); font-size: 15px; line-height: 1.55; display: grid; gap: var(--s2); overflow-wrap: anywhere; }
c29-faq .faq-a a { color: var(--accent-text); }
/* ============================================================
 * BANNER  <c29-banner>   (Taste Fleet Wave 1, Lane B — B4)
 * The persistent sibling of c29-toasts: an inline, toned
 * state line that stays until the underlying state resolves.
 * It has NO timer, NO entrance travel, NO fixed positioning —
 * it never borrows a toast's urgency or a modal's
 * interruption. Tone is carried by the designed token pairs
 * (bg + text + dot) AND a glyph AND a word (the tone label),
 * never colour alone. One action max, supplied by the host.
 * Dismiss exists only where the host declares `dismissible` —
 * a state that must be faced gets no ×.
 * Integration: append inside @layer c29.components in tide.css.
 * ============================================================ */
c29-banner { display: flex; gap: var(--s3); align-items: flex-start; text-align: left;
  background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair);
  padding: var(--s4); grid-template-columns: none; }
c29-banner[hidden] { display: none; }
c29-banner .bn-dot { flex: none; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: var(--wash); color: var(--ink-soft); }
c29-banner .bn-dot .t-icon { width: 16px; height: 16px; }
c29-banner[tone="accent"] .bn-dot { background: var(--accent); color: var(--on-accent); }
c29-banner[tone="ok"] { background: var(--ok-bg); } c29-banner[tone="ok"] .bn-dot { background: var(--ok); color: var(--on-ok); }
c29-banner[tone="warn"] { background: var(--warn-bg); } c29-banner[tone="warn"] .bn-dot { background: var(--warn); color: var(--surface); }
c29-banner[tone="bad"] { background: var(--bad-bg); } c29-banner[tone="bad"] .bn-dot { background: var(--bad); color: var(--on-bad); }
c29-banner[tone="ok"] .bn-title, c29-banner[tone="ok"] .bn-text { color: var(--ok-text); }
c29-banner[tone="warn"] .bn-title, c29-banner[tone="warn"] .bn-text { color: var(--warn-text); }
c29-banner[tone="bad"] .bn-title, c29-banner[tone="bad"] .bn-text { color: var(--bad-text); }
c29-banner .bn-main { flex: 1; min-width: 0; display: grid; gap: 2px; }
c29-banner .bn-title { font-weight: 650; font-size: 15px; line-height: 1.4; overflow-wrap: anywhere; }
c29-banner .bn-text { font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-soft); overflow-wrap: anywhere; }
c29-banner .bn-text:empty { display: none; }
c29-banner .bn-actions { flex: none; display: flex; align-items: center; gap: var(--s1); }
c29-banner .bn-x { width: 36px; height: 36px; min-width: 36px; padding: 0; }
c29-banner .bn-x .t-icon { width: 16px; height: 16px; }
@media (max-width: 400px) {
  /* Phone width: the words get the full row — dot + text on the
     first line, the action wraps under the text instead of
     squeezing it into a column. */
  c29-banner { flex-wrap: wrap; }
  c29-banner .bn-main { flex: 1 1 calc(100% - 44px); }
  c29-banner .bn-actions { margin-left: 44px; }
}
/* ============================================================
 * TIDE — LAUNCHER TILE  <c29-tile>  +  .tl-grid
 * Wave 1 Lane C (C1), Taste Fleet 2026-10-19.
 *
 * Provenance: census item 3 / CG-2 — the Hub launcher tile
 * (.hub-tile, a fork of .ds-card-clickable with grey monogram
 * squares, no lift, no art slot) and the hand-rolled apps-shelf
 * grid are the same shape invented twice. This is the one
 * canonical shape for both.
 *
 * Construction law (canon): the tile's layout and chrome are
 * PURE CSS on the host-authored anchor — the tile works, links
 * and reads finished with tide.js / tile.js dead. JS only adds
 * the `open` event and the attribute-form adoption. Tokens
 * only; no literal colours except the on-* pairs canon itself
 * pins. Motion: a whisper of lift on fine-pointer hover only;
 * touch gets a wash, never travel; the global motion gate
 * (tokens.css duration zeroing) collapses what little moves.
 * INTEGRATION: append inside @layer c29.components in tide.css.
 * ============================================================ */

/* ---------- the shelf: group-grid layout ---------- */
.tl-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s2); }
@media (max-width: 340px) { .tl-grid { grid-template-columns: minmax(0, 1fr); } }

/* ---------- the tile ---------- */
c29-tile { display: block; min-width: 0; height: 100%; }
c29-tile .tl-hit { display: flex; align-items: center; gap: var(--s3); height: 100%;
  /* Pinned to the full name+sub+gate stack (71px: 20 name + 18 sub
     + 24 gate + 4 of grid gaps + 4 gate margin + rounding) + padding,
     so a gated and an ungated tile are the same height in the shelf —
     and c29-skeleton shape=tile stands in at this same metric. */
  min-height: calc(71px + 2 * var(--s4));
  padding: var(--s4); border-radius: var(--corner);
  background: var(--surface); box-shadow: var(--hair);
  color: inherit; text-decoration: none; -webkit-tap-highlight-color: transparent;
  transition: transform var(--d2) var(--ease), box-shadow var(--d2) var(--ease), background var(--d2) var(--ease); }
c29-tile .tl-hit:active { background: var(--wash); }
/* The whisper: fine pointers only, 2px, no scale theatre. */
@media (hover: hover) and (pointer: fine) {
  c29-tile .tl-hit:hover { transform: translateY(-2px); box-shadow: var(--lift); }
}
c29-tile .tl-hit:focus-visible { outline: none; box-shadow: var(--focus); }

/* Art slot: a real image, an inline SVG, or a monogram letter.
   One ground, one radius (concentric: corner − padding step). */
c29-tile .tl-art { flex: none; width: 52px; height: 52px; border-radius: calc(var(--corner) - 6px);
  display: grid; place-items: center; overflow: hidden;
  background: var(--accent-soft); color: var(--accent-text);
  font: 800 21px/1 var(--font-display); letter-spacing: var(--track-head); }
c29-tile .tl-art img { width: 100%; height: 100%; object-fit: cover; }
c29-tile .tl-art svg { width: 26px; height: 26px; }
c29-tile .tl-art.is-money { background: color-mix(in srgb, var(--money) 12%, var(--surface)); color: var(--money-text); }
c29-tile .tl-art.is-sun { background: var(--sun-100); color: var(--warn-text); }
[data-theme="dark"] c29-tile .tl-art.is-sun { background: var(--warn-bg); }

c29-tile .tl-main { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; align-content: center; }
c29-tile .tl-name { font: 700 15.5px/1.3 var(--font-display); letter-spacing: var(--track-head);
  overflow-wrap: anywhere; }
c29-tile .tl-sub { font-size: 13px; line-height: 1.4; color: var(--ink-mute); overflow-wrap: anywhere; }

/* Gate tag: sits under the words, never crowding the art. It is
   a fact about the door (Gated / Open / Pro), set in the house
   tag voice — word, not colour alone. */
c29-tile .tl-gate { justify-self: start; margin-top: var(--s1); }

/* Disabled: the door exists but is not openable today. JS
   removes it from the tab order; CSS carries the look alone. */
c29-tile[disabled] .tl-hit { opacity: .55; cursor: default; }
c29-tile[disabled] .tl-hit:active { background: var(--surface); }
@media (hover: hover) and (pointer: fine) {
  c29-tile[disabled] .tl-hit:hover { transform: none; box-shadow: var(--hair); }
}
/* ============================================================
 * TIDE — EMPTY STATE  <c29-empty>
 * Wave 1 Lane C (C2, states package with c29-skeleton).
 *
 * Provenance: census item 6 — emptiness in the Hub was forked
 * as .f2-absence at 5+ call sites plus .hub-empty because a
 * page-level absence had no Tide carrier (list/deck `empty`
 * attributes are list states, not a view with nothing in it).
 * Board bar: title + ONE honest sentence + AT MOST ONE action.
 *
 * Design ruling: an empty view is open sky, not a grey apology
 * box — no dashed ring, no inset border (the .ib-empty /
 * .ls-empty look this replaces). A soft sea-glass medallion,
 * a display line, one sentence, one action, generous air.
 * Chrome is pure CSS on host-authored slots (works JS-dead);
 * JS only adopts the attribute form. Tokens only.
 * INTEGRATION: append inside @layer c29.components in tide.css.
 * ============================================================ */

c29-empty { display: grid; justify-items: center; text-align: center;
  gap: var(--s2); padding: var(--s8) var(--s5); }
c29-empty .em-mark { width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center; margin-bottom: var(--s1);
  background: var(--accent-soft); color: var(--accent-text); }
c29-empty .em-mark svg { width: 26px; height: 26px; }
c29-empty .em-mark .em-mono { font: 800 22px/1 var(--font-display); letter-spacing: var(--track-head); }
c29-empty [slot="title"], c29-empty .em-title { font: 750 var(--fs-xl)/1.2 var(--font-display);
  letter-spacing: var(--track-head); text-wrap: balance; }
c29-empty [slot="body"], c29-empty .em-body { font-size: 15px; line-height: 1.55;
  color: var(--ink-soft); max-width: 36ch; text-wrap: pretty; }
c29-empty [slot="action"], c29-empty .em-action { margin-top: var(--s2); }
/* ============================================================
 * TIDE — SKELETON  <c29-skeleton>
 * Wave 1 Lane C (C2, states package with c29-empty).
 *
 * Provenance: census item 7 — the .t-skel shimmer existed as
 * an unregistered gallery demo while the Hub shipped ZERO
 * skeletons (loading was bare text: "Checking this machine.").
 * This registers the primitive with shapes that stand in for
 * real canon shapes, at the SAME metrics, so the swap to loaded
 * content causes NO layout jump:
 *   tile      103px = c29-tile min-height calc(71px + 2*--s4)
 *   row       65px  = the standard two-line c29-list row
 *             (20px title line + 3px gap + 18px sub + 24px pad)
 *   settings  60px  = c29-settings row min-height, in its card
 *   checklist 64px  = c29-checklist row min-height, in its card
 *   card      --sk-h (default 132px), text = body-rhythm lines
 * The blocks are canon .t-skel (sunken ground + the slow wave);
 * shimmer period is the token --shimmer-period (3s, slow and
 * low-contrast). Under the motion gate the canon collapse
 * zeroes the wave; the extra rule below pins it fully static
 * so a reduced-motion skeleton is a still shape, not a flicker.
 * JS-dead posture: an unrendered <c29-skeleton> still holds its
 * shape's height in sunken ground — the space is reserved even
 * if the detailed blocks never stamp.
 * INTEGRATION: append inside @layer c29.components in tide.css.
 * ============================================================ */

c29-skeleton { display: block; }
c29-skeleton .sk-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s2); }
@media (max-width: 340px) { c29-skeleton .sk-grid { grid-template-columns: minmax(0, 1fr); } }
c29-skeleton .sk-stack { display: grid; gap: var(--s2); }
c29-skeleton .sk-card { background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); overflow: hidden; }
/* Lines are grids with the bar centered — never margins, which
   collapse out of the fixed-height line and break the metric. */
c29-skeleton .sk-line { height: 60px; display: grid; align-items: center; padding: 0 var(--s4); }
c29-skeleton .sk-line + .sk-line { box-shadow: inset 0 1px 0 var(--edge-soft); }
c29-skeleton .sk-check-line { height: 64px; display: grid; align-items: center; padding: 0 var(--s4); }
c29-skeleton .sk-check-line + .sk-check-line { box-shadow: inset 0 1px 0 var(--edge-soft); }
c29-skeleton .sk-tile { height: calc(71px + 2 * var(--s4)); border-radius: var(--corner); }
c29-skeleton .sk-row { height: 65px; border-radius: var(--corner); }
c29-skeleton .sk-text { display: grid; gap: 10px; padding: 5px 0; }
c29-skeleton .sk-text .t-skel { height: 14px; }

/* Reserved-space fallback before/without JS (see header). */
c29-skeleton[shape="tile"]:empty { min-height: calc(71px + 2 * var(--s4)); border-radius: var(--corner); background: var(--sunken); }
c29-skeleton[shape="row"]:empty { min-height: 65px; border-radius: var(--corner); background: var(--sunken); }
c29-skeleton[shape="card"]:empty { min-height: var(--sk-h, 132px); border-radius: var(--corner); background: var(--sunken); }

/* Reduced motion: the skeleton is a still shape. */
html[data-motion="reduced"] c29-skeleton .t-skel::after { animation: none; }
@media (prefers-reduced-motion: reduce) {
  html:not([data-motion="full"]) c29-skeleton .t-skel::after { animation: none; }
}
/* ============================================================
 * TIDE — SETTINGS ROWS  <c29-settings>
 * Wave 1 Lane C (C3).
 *
 * Provenance: census item 17 — the Hub has NO settings view at
 * all (audit S13/M1): sign-out hides inside Ceremony step 2,
 * theme is a top-bar text button, update state lives in a shim
 * pill. DS v2 carries a full .ds-settings-* family used
 * nowhere; this is Tide's living version.
 *
 * The five row shapes — nothing in the family invents a sixth:
 *   nav row      <a class="st-row">    label + value + chevron
 *   toggle row   <div class="st-row">  label + a real .t-switch
 *   select row   <div class="st-row">  label + a real <select>
 *   action row   <button class="st-row"> label (+ value), no chevron
 *   info row     <div class="st-row">  label + value, not interactive
 * plus two earned modifiers: .is-destructive (sign-out, delete)
 * and a .st-detail second line under a label.
 * Rows sit in .st-group cards under .st-group-title caps; the
 * identity block above them is c29-account's job, not this
 * family's. Chrome is pure CSS (works JS-dead); JS only wires
 * the change/setting events for toggle/select/action rows.
 * Row height is pinned at 60px — c29-skeleton shape=settings
 * stands in at exactly this metric.
 * INTEGRATION: append inside @layer c29.components in tide.css.
 * ============================================================ */

c29-settings { display: grid; gap: var(--s5); }
c29-settings .st-group-title { font-size: var(--fs-cap); letter-spacing: var(--track-cap);
  text-transform: uppercase; font-weight: 650; color: var(--ink-mute);
  margin: 0 0 var(--s2) var(--s1); }
c29-settings .st-group { background: var(--surface); border-radius: var(--corner);
  box-shadow: var(--hair); overflow: hidden; }

c29-settings .st-row { display: flex; align-items: center; gap: var(--s3);
  width: 100%; min-height: 60px; padding: var(--s2) var(--s4);
  font: inherit; color: inherit; text-align: left; text-decoration: none;
  background: none; border: 0; }
c29-settings .st-group .st-row + .st-row { box-shadow: inset 0 1px 0 var(--edge-soft); }
a.st-row, button.st-row { cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background var(--d2) var(--ease); }
a.st-row:active, button.st-row:active { background: var(--wash); }
a.st-row:focus-visible, button.st-row:focus-visible { outline: none; box-shadow: var(--focus); }

c29-settings .st-ic { flex: none; width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center; background: var(--accent-soft); color: var(--accent-text); }
c29-settings .st-ic svg { width: 18px; height: 18px; }
c29-settings .st-main { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
c29-settings .st-label { font-size: 15.5px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
c29-settings .st-detail { font-size: 13px; line-height: 1.4; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-settings .st-value { flex: none; max-width: 46%; font-size: 14px; color: var(--ink-mute);
  text-align: right; overflow-wrap: anywhere; }
c29-settings .st-chev { flex: none; color: var(--ink-mute); display: grid; place-items: center; }
c29-settings .st-chev svg { width: 18px; height: 18px; }

/* Controls keep their own canon shapes — the switch is .t-switch,
   the select is a native select in quiet dress (44px target). */
c29-settings .st-row .t-switch { flex: none; }
c29-settings .st-select { flex: none; max-width: 52%; min-height: 44px;
  border: 0; background: transparent; color: var(--ink-mute);
  font-size: 14px; text-align: right; cursor: pointer; }
c29-settings .st-select:focus-visible { outline: none; box-shadow: var(--focus); border-radius: 8px; }

/* Destructive is a tone, earned by the act — never a red box. */
c29-settings .st-row.is-destructive .st-label { color: var(--bad-text); }
c29-settings .st-row.is-destructive .st-ic { background: var(--bad-bg); color: var(--bad-text); }

/* Disabled row: present, legible, not pressable. */
c29-settings .st-row[disabled], c29-settings .st-row[aria-disabled="true"] { opacity: .55; cursor: default; }
c29-settings .st-row[aria-disabled="true"]:active { background: none; }
/* ============================================================
 * TIDE — STATUS CHECKLIST  <c29-checklist>
 * Wave 1 Lane C (C4).
 *
 * Provenance: census item 10 — the Hub Ceremony's check rows
 * are text-only ("Lives in the desktop app" ×3 in muted text,
 * no difference between checked-fine, not-applicable, and
 * missing) at the exact moment the product claims "we checked
 * before asking". Permit Runner's desk and Learn onboarding
 * hand-composed the same shape. This is its canonical carrier.
 *
 * The law of the row: a state is carried THREE ways at once —
 * SHAPE (filled check disc / slashed hollow disc / warning
 * triangle / hollow ring), WORD (a visible status word in the
 * status's own chip), and TONE. Colour alone never carries it:
 * strip the colour and the rows still read differently.
 *   done     ✓ disc, filled ok        word "Done"
 *   na       ⊘ hollow disc + slash    word "Not needed"
 *   missing  ⚠ triangle, bad tone     word "Missing"
 *   pending  ○ hollow ring            word "Not checked"
 * Row layout is pure CSS on host <li data-status> children —
 * with JS dead the words come from the host's own .cl-word and
 * the tone/shape ring still paints (li::before per status);
 * JS stamps the SVG icon + word only where the host left them
 * out, and computes the optional summary line. Row height
 * pinned at 64px — c29-skeleton shape=checklist matches it.
 * INTEGRATION: append inside @layer c29.components in tide.css.
 * NOTE the existing .t-check (a checkbox row) is a different
 * truth — an input, not a verified state — and stays untouched.
 * ============================================================ */

c29-checklist { display: block; }
c29-checklist .cl-summary { display: flex; align-items: center; gap: var(--s2);
  margin-bottom: var(--s3); font-size: 14px; font-weight: 600; color: var(--ink-soft); }
c29-checklist .cl-card { background: var(--surface); border-radius: var(--corner);
  box-shadow: var(--hair); overflow: hidden; }
c29-checklist > ul, c29-checklist .cl-card > ul { list-style: none; margin: 0; padding: 0; }
c29-checklist li { list-style: none; }
c29-checklist li::marker { content: none; }
c29-checklist li { position: relative; display: flex; align-items: center; gap: var(--s3);
  min-height: 64px; padding: var(--s2) var(--s4); }
c29-checklist li + li { box-shadow: inset 0 1px 0 var(--edge-soft); }

c29-checklist .cl-ic { flex: none; width: 30px; height: 30px; display: grid; place-items: center; }
c29-checklist .cl-ic svg { width: 26px; height: 26px; fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
c29-checklist li[data-status="done"] .cl-ic { color: var(--ok-text); }
c29-checklist li[data-status="done"] .cl-ic .cl-disc { fill: var(--ok); stroke: none; }
c29-checklist li[data-status="done"] .cl-ic .cl-tick { stroke: var(--on-ok); }
c29-checklist li[data-status="na"] .cl-ic { color: var(--ink-mute); }
c29-checklist li[data-status="missing"] .cl-ic { color: var(--bad-text); }
c29-checklist li[data-status="missing"] .cl-ic .cl-tri { fill: var(--bad-bg); }
c29-checklist li[data-status="pending"] .cl-ic { color: var(--ink-mute); }

/* JS-dead shape fallback: a status ring paints per row even
   before the icon stamps (tone + ring weight differ per state). */
c29-checklist li:not(:has(.cl-ic))::before { content: ""; flex: none; width: 26px; height: 26px;
  border-radius: 50%; box-shadow: inset 0 0 0 2px var(--ink-mute); }
c29-checklist li[data-status="done"]:not(:has(.cl-ic))::before { background: var(--ok); box-shadow: none; }
c29-checklist li[data-status="missing"]:not(:has(.cl-ic))::before { border-radius: 6px;
  background: var(--bad-bg); box-shadow: inset 0 0 0 2px var(--bad); }

c29-checklist .cl-main { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
c29-checklist .cl-label { font-size: 15.5px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
c29-checklist .cl-detail { font-size: 13px; line-height: 1.4; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-checklist li[data-status="na"] .cl-label { color: var(--ink-soft); font-weight: 550; }

/* The word chip: the status SAID, in its paired tone tokens. */
c29-checklist .cl-word { flex: none; display: inline-flex; align-items: center; min-height: 24px;
  padding: 0 9px; border-radius: 99px; background: var(--wash); color: var(--ink-soft);
  font-size: 12px; font-weight: 650; white-space: nowrap; }
c29-checklist li[data-status="done"] .cl-word { background: var(--ok-bg); color: var(--ok-text); }
c29-checklist li[data-status="missing"] .cl-word { background: var(--bad-bg); color: var(--bad-text); }
c29-checklist li[data-status="na"] .cl-word { background: var(--wash); color: var(--ink-mute); }

c29-checklist .cl-act { flex: none; }
/* ======================================================
   PROPOSAL  <c29-proposal>   (Lane D · D1, Taste Wave 1)
   A machine-proposed action a calm human can approve in one
   read: what will happen (payload, plainly), why (rationale),
   who proposed it, approve / deny as real actions, and a
   decided state that keeps its result + provenance forever.
   Tokens only; layout is pure CSS on the host element so the
   card holds its shape even before JS adopts it.
   ====================================================== */
c29-proposal { display: block; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: var(--s5); }
c29-proposal .pp-kind { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 650; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-mute); }
c29-proposal .pp-kind i { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; }
c29-proposal .pp-title { margin-top: var(--s2); font: 750 var(--fs-xl)/1.22 var(--font-display); letter-spacing: var(--track-head); overflow-wrap: anywhere; }
c29-proposal .pp-why { margin-top: var(--s2); color: var(--ink-soft); font-size: 15px; line-height: 1.55; overflow-wrap: anywhere; }
c29-proposal .pp-why:empty { display: none; }
/* The payload: what will actually happen, rendered plainly.
   Sunken and quiet — background truth, never an alarm, and
   never raw-JSON-only: hosts compose readable lines here. */
c29-proposal .pp-payload { margin-top: var(--s4); background: var(--sunken); border-radius: var(--r-md); box-shadow: inset 0 0 0 1px var(--edge-soft);
  padding: var(--s3) var(--s4); font-size: 14.5px; line-height: 1.55; color: var(--ink); overflow-wrap: anywhere; }
c29-proposal .pp-payload:empty { display: none; }
c29-proposal .pp-payload-cap { display: block; font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ink-mute); margin-bottom: 4px; }
c29-proposal .pp-empty { margin-top: var(--s4); font-size: 14px; color: var(--ink-mute); border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1.5px var(--edge); padding: var(--s3) var(--s4); }
c29-proposal .pp-meta { margin-top: var(--s3); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; color: var(--ink-mute); }
c29-proposal .pp-acts { margin-top: var(--s4); display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
c29-proposal .pp-acts .t-btn { width: 100%; }
/* Decided state: the card keeps its payload + rationale and
   gains a provenance band. Colour is never the only signal —
   the band states the decision in words. */
c29-proposal .pp-decided { margin-top: var(--s4); display: none; align-items: flex-start; gap: var(--s2);
  border-radius: var(--r-md); padding: var(--s3) var(--s4); font-size: 14px; line-height: 1.5; }
c29-proposal .pp-decided .t-icon { width: 18px; height: 18px; flex: none; margin-top: 2px; }
c29-proposal .pp-decided b { font-weight: 700; }
c29-proposal .pp-decided .pp-result { display: block; color: var(--ink-soft); overflow-wrap: anywhere; }
c29-proposal[data-state="approved"] .pp-decided { display: flex; background: var(--ok-bg); color: var(--ok-text); }
c29-proposal[data-state="denied"] .pp-decided { display: flex; background: var(--bad-bg); color: var(--bad-text); }
c29-proposal[data-state="approved"] .pp-decided .pp-result,
c29-proposal[data-state="denied"] .pp-decided .pp-result { color: inherit; opacity: .85; }
c29-proposal[data-state="approved"] .pp-acts, c29-proposal[data-state="denied"] .pp-acts { display: none; }
c29-proposal[disabled] .pp-acts .t-btn { opacity: .45; pointer-events: none; }
/* ======================================================
   CHECK + CITE  <c29-check> · <c29-cite>   (Lane D · D2)
   The Learn pair. Check: a question whose options are real
   buttons; the verdict + explanation land IN PLACE the instant
   an option is tapped. Cite: a small chip that opens to the
   exact quoted words it cites. Tokens only, both themes.
   ====================================================== */
c29-check { display: block; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: var(--s5); }
c29-check .ck-q { font: 700 var(--fs-lg)/1.3 var(--font-display); letter-spacing: var(--track-head); overflow-wrap: anywhere; }
c29-check .ck-opts { display: grid; gap: var(--s2); margin-top: var(--s4); }
c29-check .ck-opt { display: flex; align-items: center; gap: var(--s3); width: 100%; min-height: 52px; text-align: left;
  padding: var(--s3) var(--s4); border: 0; border-radius: var(--r-md); background: var(--raised);
  box-shadow: 0 0 0 1px var(--edge); font-size: 15.5px; font-weight: 550; color: var(--ink); cursor: pointer;
  transition: background var(--d2) var(--ease), box-shadow var(--d2) var(--ease), transform var(--d1) var(--ease); overflow-wrap: anywhere; }
c29-check .ck-opt:hover { background: var(--wash); }
c29-check .ck-opt:active { transform: scale(.99); }
c29-check .ck-opt .ck-mark { flex: none; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--wash); color: var(--ink-soft); font: 700 13px/1 var(--font-ui); }
  /* Integrator fix (2026-10-19, post-landing render pass): the letter was --ink-mute, which samples 4.02:1 in dark on the wash disc — under the 4.5 bar. --ink-soft samples ≈6.6:1 in dark and stronger in light. */
c29-check .ck-opt .ck-mark .t-icon { width: 15px; height: 15px; }
c29-check .ck-opt[disabled] { cursor: default; }
c29-check[data-state="correct"] .ck-opt.is-picked { background: var(--ok-bg); box-shadow: 0 0 0 1.5px var(--ok); color: var(--ok-text); }
c29-check[data-state="correct"] .ck-opt.is-picked .ck-mark { background: var(--ok); color: var(--on-ok); }
c29-check .ck-opt.is-wrong { background: var(--bad-bg); box-shadow: 0 0 0 1.5px var(--bad); color: var(--bad-text); }
c29-check .ck-opt.is-wrong .ck-mark { background: var(--bad); color: var(--on-bad); }
c29-check .ck-opt.is-dim { opacity: .55; }
c29-check .ck-fb { margin-top: var(--s4); display: none; gap: var(--s2); border-radius: var(--r-md); padding: var(--s3) var(--s4); font-size: 14.5px; line-height: 1.55; }
c29-check .ck-fb .t-icon { width: 18px; height: 18px; flex: none; margin-top: 2px; }
c29-check .ck-fb b { font-weight: 700; }
c29-check .ck-fb .ck-why { display: block; color: inherit; opacity: .88; overflow-wrap: anywhere; }
c29-check[data-state="correct"] .ck-fb { display: flex; background: var(--ok-bg); color: var(--ok-text); }
c29-check[data-state="incorrect"] .ck-fb { display: flex; background: var(--bad-bg); color: var(--bad-text); }
c29-check .ck-loop { margin-top: var(--s3); font-size: 13px; color: var(--ink-mute); }
c29-check .ck-loop:empty { display: none; }

/* ---------- cite ---------- */
c29-cite { display: inline-block; max-width: 100%; vertical-align: baseline; }
c29-cite .ct-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; max-width: 100%; padding: 2px 12px;
  border: 0; border-radius: 99px; background: var(--accent-soft); color: var(--accent-text);
  font-size: 13px; font-weight: 650; cursor: pointer; transition: background var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-cite .ct-chip:active { transform: scale(.97); }
c29-cite .ct-chip .t-icon { width: 14px; height: 14px; flex: none; }
c29-cite .ct-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
c29-cite .ct-chip[disabled] { background: var(--wash); color: var(--ink-mute); cursor: default; }
c29-cite .ct-quote { display: none; margin-top: var(--s2); max-width: min(480px, 88vw); background: var(--sunken);
  border-radius: var(--r-md); box-shadow: inset 0 0 0 1px var(--edge-soft); padding: var(--s3) var(--s4); }
c29-cite.is-open .ct-quote { display: block; }
c29-cite .ct-quote blockquote { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--ink);
  overflow-wrap: anywhere; max-height: 220px; overflow: auto; }
c29-cite .ct-quote blockquote::before { content: "“"; } c29-cite .ct-quote blockquote::after { content: "”"; }
c29-cite .ct-src { margin-top: var(--s2); font-size: 12.5px; color: var(--ink-mute); }

} /* end components */

@layer c29.utilities {
  .vh { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

/* ---------- reduced motion: ONE GLOBAL GATE (Rev 4 item 5) ----------
   Keep states, lose the travel; springs collapse to fades. The
   effective mode is resolved ONCE by tide.js — a stored user
   override (sh29core_motion) wins over the OS in either direction;
   no stored override follows the OS — and published as
   <html data-motion="reduced"|"full">. The media-query block below
   therefore stands down the moment the user has explicitly chosen
   "full", and the [data-motion="reduced"] block applies the same
   collapse when the user chose reduced against a full-motion OS.
   The duration scale itself is zeroed at the token layer
   (tokens.css), and every JS-driven animation consults
   Tide.reduced() — the gate is a system feature, not a per-surface
   negotiation. */
@media (prefers-reduced-motion: reduce) {
  html:not([data-motion="full"]) *, html:not([data-motion="full"]) *::before, html:not([data-motion="full"]) *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important;
    transition-property: opacity, background-color, color, box-shadow !important; transition-duration: 160ms !important; }
  html:not([data-motion="full"]) .t-shimmer, html:not([data-motion="full"]) c29-slide .sl-label span { animation: none !important; color: var(--ink-soft) !important; background: none !important; }
  @view-transition { navigation: none; }
}
html[data-motion="reduced"] *, html[data-motion="reduced"] *::before, html[data-motion="reduced"] *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important;
  transition-property: opacity, background-color, color, box-shadow !important; transition-duration: 160ms !important; }
html[data-motion="reduced"] .t-shimmer, html[data-motion="reduced"] c29-slide .sl-label span { animation: none !important; color: var(--ink-soft) !important; background: none !important; }

/* ============================================================
 * REV 2 — mined from the AccountabilityPals First-Name pages
 * (PALS-INVENTORY-2026-10-17.md). Patterns generalized, costumes
 * left behind; every value a token.
 * ============================================================ */

/* ---------- c29-sweep + c29-sweep-entry: the dated update board ----------
   Rev 4 overhaul (2026-10-18; the Principal's steering: "the sweep is
   shit and needs a full overhaul"). A timeline board: date medallions
   ride a rail, facts read as a spec sheet, and today is the one coral
   day. The date state machine is unchanged — past fades + Done, today
   is coral + Today, an all-past board declares itself stale — only
   the presentation was rebuilt, a full tier up.
   Every value below is a real token. The Rev 2 block named tokens
   that never existed (--hairline, --shadow-pop, --r-lg, a rise/fadein
   keyframe pair), so its borders, shadows and radii silently never
   rendered; that, not taste, is why the trio read flat. */
c29-sweep { display: block; }
c29-sweep .sw-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); }
c29-sweep .sw-head h3 { font: 700 19px/1.2 var(--font-display); letter-spacing: -.01em; }
c29-sweep .sw-range { flex: none; margin-top: 1px; }
c29-sweep .sw-checked { display: flex; align-items: center; gap: 6px; margin-top: var(--s1); font-size: 12.5px; color: var(--ink-mute); }
c29-sweep .sw-checked .t-icon { width: 14px; height: 14px; flex: none; }
c29-sweep .sw-entries { position: relative; margin-top: var(--s4); display: grid; gap: var(--s4); }
c29-sweep .sw-entries::before { content: ""; position: absolute; top: 12px; bottom: 12px; left: 26px; width: 2px; border-radius: 2px;
  background: color-mix(in srgb, var(--ink-1) 9%, transparent); }
c29-sweep-entry { position: relative; display: grid; grid-template-columns: 54px minmax(0, 1fr); gap: var(--s3); }
c29-sweep-entry .se-medal { position: relative; z-index: 1; width: 54px; height: 54px; border-radius: 17px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: var(--sunken); color: var(--ink-soft); box-shadow: inset 0 0 0 1px var(--edge-soft); }
c29-sweep-entry .se-medal b { font: 800 19px/1 var(--font-display); letter-spacing: -.01em; }
c29-sweep-entry .se-medal span { margin-top: 3px; font-size: 9.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
c29-sweep-entry .se-medal .t-icon { width: 22px; height: 22px; }
c29-sweep-entry .se-main { min-width: 0; padding-top: 3px; }
c29-sweep-entry .se-top { display: flex; align-items: baseline; gap: var(--s2); }
c29-sweep-entry .se-title { font-weight: 700; font-size: 15.5px; line-height: 1.3; }
c29-sweep-entry .se-tag { margin-left: auto; flex: none; }
c29-sweep-entry .se-when { margin-top: 1px; font-size: 13px; font-weight: 650; color: var(--ink-soft); }
c29-sweep-entry .se-body { margin-top: 3px; font-size: 14px; line-height: 1.55; color: var(--ink-2); }
c29-sweep-entry .se-kvs { margin-top: var(--s2); }
c29-sweep-entry .se-kv { display: flex; gap: var(--s3); padding: 6px 0; border-top: 1px solid var(--edge-soft); font-size: 13.5px; line-height: 1.45; }
c29-sweep-entry .se-kv:last-child { border-bottom: 1px solid var(--edge-soft); }
c29-sweep-entry .se-kv b { flex: none; min-width: 48px; padding-top: 2px; font-size: 10.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-mute); }
c29-sweep-entry .se-src { margin-top: var(--s2); font-size: 13px; font-weight: 650; }
c29-sweep-entry .se-src a { text-decoration: none; }
c29-sweep-entry[data-state="past"] { opacity: .6; }
c29-sweep-entry[data-state="past"] .se-medal { background: transparent; }
c29-sweep-entry[data-state="today"] .se-medal { background: linear-gradient(155deg, var(--29d-coral-500), var(--money));
  color: var(--on-money); box-shadow: var(--lift); }
c29-sweep-entry[data-state="today"] .se-main { margin: calc(-1 * var(--s2)) calc(-1 * var(--s3)); padding: calc(var(--s2) + 3px) var(--s3) var(--s2);
  border-radius: var(--r-md); background: color-mix(in srgb, var(--money) 6%, transparent);
  box-shadow: inset 3px 0 0 var(--money); }
c29-sweep .sw-cadence { margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--edge-soft);
  font-size: 13px; color: var(--ink-soft); }
c29-sweep[data-state="stale"] .sw-cadence { display: inline-block; padding: 7px 13px; border-top: 0; border-radius: var(--r-pill);
  background: var(--warn-bg); color: var(--warn-text); font-weight: 650; }
c29-sweep .sw-note { margin-top: var(--s3); padding: var(--s3) var(--s4); border-radius: var(--r-md);
  background: var(--sunken); font-size: 13.5px; line-height: 1.55; color: var(--ink-2); }
c29-sweep .sw-note:empty { display: none; }

/* ---------- c29-gift: the present / drawer ----------
   One component, two modes (inline box | announce bar -> sheet
   drawer), one seen-store, presence owned by the host page through
   html[data-gift-open]; the bar stands down while the mini player
   docks. Rev 4 (2026-10-18: "the gift doesn't open or confetti or
   anything") gives the open its moment: ONE master progress value
   (--gp, 0..1) driven by a rAF clock in tide.js flies the lid, swells
   the box and fades the ribbon; at p=.42 a tasteful confetti burst
   fires (canvas, token colors, ~1.2s, sound off, one buzz) and the
   reveal rises in behind it. Reduced motion — or a gift restored as
   already opened — skips the sequence entirely: instant reveal, no
   confetti, at most the static settle of the end state. */
/* Box internals are scoped to .gift-box, never to the element: the
   drawer path MOVES the box into Tide.sheet's <dialog>, out from
   under <c29-gift> — element-scoped rules silently stopped applying
   there (the Rev 2 drawer box rendered naked). The box mirrors the
   component's state + costume on itself for the same reason. */
c29-gift { display: block; }
.gift-box { position: relative; text-align: center; padding: var(--s2) 0; }
.gift-box .gift-kicker { font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--money-text); }
.gift-box .gift-title { margin-top: var(--s2); font: 700 19px/1.25 var(--font-display); letter-spacing: -.01em; }
.gift-box .gift-stage { position: relative; width: 120px; height: 106px; margin: var(--s4) auto var(--s1); }
.gift-box .gift-shadow { position: absolute; left: 50%; bottom: 0; width: 78px; height: 12px; transform: translateX(-50%);
  border-radius: 50%; background: color-mix(in srgb, var(--ink-1) 16%, transparent); filter: blur(5px); }
.gift-box .gift-present { --gp: 0; position: absolute; left: 50%; top: 8px; width: 88px; height: 88px; margin-left: -44px;
  display: grid; place-items: center; border: 0; border-radius: 20px; cursor: pointer; color: var(--money-text);
  background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 26%, var(--surface)), color-mix(in srgb, var(--accent) 12%, var(--surface)));
  box-shadow: inset 0 0 0 1px var(--edge-soft);
  transform: scale(calc(1 + var(--gp) * .07)); }
.gift-box .gift-present::before { content: ""; position: absolute; left: -5px; right: -5px; top: -7px; height: 25px; border-radius: 9px;
  background: linear-gradient(160deg, var(--29d-coral-500), var(--money)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink-1) 8%, transparent);
  transform-origin: 10% 90%;
  transform: rotate(calc(var(--gp) * -26deg)) translate(calc(var(--gp) * -16px), calc(var(--gp) * -24px));
  opacity: calc(1 - var(--gp) * .92);
  transition: transform var(--d2) var(--ease), opacity var(--d2) var(--ease); }
.gift-box .gift-present::after { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 15px; margin-left: -7.5px;
  border-radius: 2px; background: linear-gradient(180deg, var(--29d-coral-500), var(--money));
  opacity: calc(1 - var(--gp) * .5); transition: opacity var(--d2) var(--ease); }
.gift-box[data-state="opening"] .gift-present::before, .gift-box[data-state="opening"] .gift-present::after { transition: none; }
.gift-box .gift-present .t-icon { position: relative; z-index: 1; width: 34px; height: 34px; margin-top: 10px; }
.gift-box .gift-present:active { transform: scale(calc(.94 + var(--gp) * .07)); }
/* the waiting life is a breathing shadow, never a moving tap target:
   the present sits still (agents and thumbs alike get a stable hit
   area — Bot Manners rule 76) while its ground shadow breathes. */
.gift-box[data-state="waiting"] .gift-shadow, .gift-box[data-state="panel"] .gift-shadow { animation: gift-bob 3.4s ease-in-out infinite; }
@keyframes gift-bob { 0%, 100% { transform: translateX(-50%) scaleX(1); opacity: 1; } 50% { transform: translateX(-50%) scaleX(.84); opacity: .55; } }
.gift-box .gift-confetti { position: absolute; z-index: 6; pointer-events: none; }
.gift-box .gift-hint { min-height: 20px; margin-top: var(--s2); font-size: 13.5px; font-weight: 600; color: var(--ink-soft); }
.gift-box .gift-reveal { margin-top: var(--s3); text-align: left; }
.gift-box .gift-reveal[hidden] { display: none; }
.gift-box .gift-reveal .t-stack > * { animation: t-rise var(--d3) var(--ease) both; }
.gift-box .gift-actions { display: flex; gap: var(--s2); justify-content: center; margin-top: var(--s4); }
c29-gift.gift-bar { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(var(--s3) + var(--safe-b)); z-index: 75;
  width: min(calc(100% - 2 * var(--s4)), 560px); min-height: 60px;
  display: flex; align-items: center; gap: var(--s3); text-align: left;
  padding: var(--s2) var(--s4) var(--s2) var(--s3); border-radius: var(--corner);
  background: var(--surface); border: 1px solid var(--edge-soft); box-shadow: var(--lift);
  animation: t-rise var(--d2) var(--ease); }
html[data-player="mini"] c29-gift.gift-bar { display: none; } /* stand-down: one floating bar at a time */
c29-gift[hidden], c29-gift.gift-bar[hidden] { display: none; } /* author display rules must never beat [hidden] */
c29-gift.gift-bar .t-icon { width: 26px; height: 26px; color: var(--accent-text); flex: none; }
c29-gift .gb-open { flex: 1; min-width: 0; min-height: 44px; display: flex; align-items: center; gap: var(--s3); text-align: left; border: 0; background: none; cursor: pointer; }
c29-gift .gb-x { flex: none; width: 40px; height: 44px; display: grid; place-items: center; border: 0; background: none; border-radius: 99px; color: var(--ink-soft); cursor: pointer; }
c29-gift .gb-x .t-icon { width: 19px; height: 19px; color: var(--ink-soft); }
c29-gift .gift-bar-tx { flex: 1; min-width: 0; }
c29-gift .gift-bar-tx b { display: block; font-size: 14.5px; font-weight: 700; }
c29-gift .gift-bar-tx span { display: block; font-size: 12.5px; color: var(--ink-mute); }
c29-gift[theme="gameboy"] { --gift-a: #9BBC0F; --gift-b: #0F380F; }
.gift-box[data-costume="gameboy"] { --gift-a: #9BBC0F; --gift-b: #0F380F; }
.gift-box[data-costume="gameboy"] .gift-present { background: var(--gift-a); border: 3px solid var(--gift-b); border-radius: 6px; color: var(--gift-b); box-shadow: none; }
.gift-box[data-costume="gameboy"] .gift-present::before { background: var(--gift-b); box-shadow: none; border-radius: 4px; }
.gift-box[data-costume="gameboy"] .gift-present::after { background: var(--gift-b); }
c29-gift[theme="gameboy"].gift-bar { background: var(--gift-a); border: 3px solid var(--gift-b); border-radius: 6px; box-shadow: none; }

/* ---------- c29-player: the listen player ----------
   Rev 4 visual overhaul (2026-10-18: "looks like ass and not as
   nice as Therese's") to the wc player standard of the pals' pages:
   a real stage card with artwork, a 22px title, bordered skip
   circles, a 58px coral play, and a seek you can SEE — a filled
   track painted from the component's own progress (--p, 0..1) over
   the range input, so the keyboard contract is the native one.
   Behavior is untouched Rev 2 law: Stop is split from Dismiss, the
   html[data-player] attribute + --player-slot-h own presence, the
   component formats chapters, persist="local" never autoplays,
   auto-advance stays OFF. (The Rev 2 CSS below this line also named
   dead tokens and a classless .t-range — the seek rendered as a
   naked browser input. Gone.) */
c29-player { display: block; }
c29-player .pl-stage { position: relative; overflow: hidden; padding: var(--s5);
  background: var(--raised); border-radius: calc(var(--corner) - 4px); box-shadow: var(--hair); }
c29-player .pl-stage::before { content: ""; position: absolute; inset: 0; background: var(--sheen); pointer-events: none; }
c29-player .pl-stage > * { position: relative; }
c29-player .pl-top { display: flex; align-items: center; gap: var(--s3); }
c29-player .pl-art, c29-player .pm-art { flex: none; position: relative; overflow: hidden;
  background: radial-gradient(120% 120% at 30% 20%, var(--sea-200), var(--sea-600) 78%); }
c29-player .pl-art::after, c29-player .pm-art::after { content: ""; position: absolute; inset: 0; background: var(--sheen); }
c29-player .pl-art[data-art="1"], c29-player .pm-art[data-art="1"] { background: radial-gradient(120% 120% at 30% 20%, var(--sun-300), var(--29d-coral-500) 80%); }
c29-player .pl-art[data-art="2"], c29-player .pm-art[data-art="2"] { background: radial-gradient(120% 120% at 70% 15%, var(--sea-300), var(--sea-700) 80%); }
c29-player .pl-art[data-art="3"], c29-player .pm-art[data-art="3"] { background: radial-gradient(120% 120% at 25% 25%, var(--sun-100), var(--sun-500) 82%); }
c29-player .pl-art { width: 52px; height: 52px; border-radius: 14px; box-shadow: var(--hair); }
c29-player .pl-id { flex: 1; min-width: 0; }
c29-player .pl-id .t-kicker { font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--accent-text); }
c29-player .pl-show { margin-top: 1px; font-size: 13px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
c29-player .pl-title { margin-top: var(--s4); font: 750 22px/1.22 var(--font-display); letter-spacing: -.01em; }
c29-player .pl-note { margin-top: 3px; font-size: 14.5px; line-height: 1.55; color: var(--ink-soft); }
c29-player .pl-note:empty { display: none; }
c29-player .pl-seek { margin-top: var(--s4); }
c29-player input[type="range"].pl-range { --p: 0; -webkit-appearance: none; appearance: none; display: block;
  width: 100%; height: 28px; margin: 0; padding: 0; background: transparent; cursor: pointer; }
c29-player .pl-range::-webkit-slider-runnable-track { height: 8px; border-radius: 99px;
  background: linear-gradient(90deg, var(--money) calc(var(--p) * 100%), var(--sunken) calc(var(--p) * 100%)); }
c29-player .pl-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; margin-top: -6px;
  border-radius: 50%; background: var(--raised); border: 1px solid var(--edge); box-shadow: var(--lift); }
c29-player .pl-range::-moz-range-track { height: 8px; border-radius: 99px; background: var(--sunken); }
c29-player .pl-range::-moz-range-progress { height: 8px; border-radius: 99px; background: var(--money); }
c29-player .pl-range::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%;
  background: var(--raised); border: 1px solid var(--edge); box-shadow: var(--lift); }
c29-player .pl-times { display: flex; justify-content: space-between; margin-top: 1px; }
c29-player .pl-t { font-size: 12px; font-weight: 600; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
c29-player .pl-t[data-now] { color: var(--ink-soft); }
/* TRANSPORT CLUSTER LAW (2026-10-19, Principal-spotted on Listen: skips
   spread edge-to-edge "piss me off"): the transport is ONE tight cluster
   centered on the play button — a single even gap (--s3) between every
   pair of controls, no control carries its own side margins, nothing
   stretches or space-betweens. The primary skip pair flanks play; the
   optional outer pair (skip2) wears the same 44px circle with a quieter
   glyph. Every target >= 44px. A transport spread across the bar is a
   defect, whoever renders it. */
c29-player .pl-ctl { display: flex; align-items: center; justify-content: center; gap: var(--s3); margin-top: var(--s3); }
c29-player .pl-ctl-dual { gap: 10px; } /* five controls: the same one-gap rhythm, drawn 2px tighter so the cluster still fits the stage's content box */
c29-player .pl-skip { flex: none; width: 44px; height: 44px; display: grid; place-items: center; border-radius: 50%;
  border: 1px solid var(--edge); background: transparent; color: var(--money-text);
  transition: transform var(--d1) var(--ease-give), background var(--d1) var(--ease); }
c29-player .pl-skip .t-icon { width: 23px; height: 23px; }
c29-player .pl-skip:active { background: var(--wash); transform: scale(.94); }
c29-player .pl-skip2 { color: var(--ink-soft); }
c29-player .pl-skip2 .t-icon { width: 21px; height: 21px; }
c29-player .pl-play { flex: none; width: 58px; height: 58px; display: grid; place-items: center; border: 0; border-radius: 50%; cursor: pointer;
  color: var(--on-money); background: linear-gradient(155deg, var(--29d-coral-500), var(--money)); box-shadow: var(--lift);
  transition: transform var(--d1) var(--ease-give); }
c29-player .pl-play .t-icon { width: 25px; height: 25px; }
c29-player .pl-play:active { transform: scale(.93); }
c29-player .pl-err { margin-top: var(--s3); font-size: 13px; font-weight: 600; color: var(--bad-text); text-align: center; }
c29-player .pl-err[hidden] { display: none; }
c29-player .pl-listlab { margin-top: var(--s5); font-size: 12px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-mute); }
c29-player [data-list], c29-player [data-chaplist] { border-bottom: 1px solid var(--edge-soft); }
c29-player .pl-ep { display: flex; align-items: center; gap: var(--s3); width: 100%; min-height: 52px;
  padding: var(--s3) 2px; border: 0; border-top: 1px solid var(--edge-soft); background: none; text-align: left; cursor: pointer; }
c29-player .pl-ep .dot { flex: none; width: 9px; height: 9px; border-radius: 50%; border: 2px solid var(--accent); opacity: .35; }
c29-player .pl-ep.is-on .dot { background: var(--accent); opacity: 1; }
c29-player .pl-ep .tx { flex: 1; min-width: 0; display: grid; }
c29-player .pl-ep .tx b { font-size: 15px; font-weight: 650; line-height: 1.3; }
c29-player .pl-ep.is-on .tx b { color: var(--accent-text); }
c29-player .pl-ep .tx small { font-size: 13px; color: var(--ink-mute); }
c29-player .pl-ep .pl-dur { flex: none; padding-left: var(--s2); font-size: 12.5px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
c29-player .pl-chaplab { margin-top: var(--s4); font-size: 13px; color: var(--ink-soft); }
c29-player .pl-chap { display: flex; align-items: center; gap: var(--s3); width: 100%; min-height: 44px;
  padding: var(--s2) 2px; border: 0; border-top: 1px solid var(--edge-soft); background: none; text-align: left; font-size: 14.5px; cursor: pointer; }
c29-player .pl-chap .ct { flex: none; padding: 3px 9px; border-radius: 99px; background: var(--accent-soft);
  color: var(--accent-text); font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
c29-player .pl-chap.is-on .ct { background: var(--accent); color: var(--on-accent); }
c29-player .pl-chap.is-on .cl { font-weight: 700; }
c29-player .pl-mini { --p: 0; position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(var(--s2) + var(--safe-b)); z-index: 80;
  width: min(calc(100% - 2 * var(--s3)), 560px); height: calc(var(--player-slot-h) - 8px);
  display: flex; align-items: center; gap: var(--s2); padding: 0 var(--s3) 0 var(--s2); overflow: hidden;
  border-radius: 99px; background: var(--raised); border: 1px solid var(--edge-soft); box-shadow: var(--lift);
  animation: t-rise var(--d2) var(--ease); }
c29-player .pl-mini::after { content: ""; position: absolute; left: 16px; right: 16px; bottom: 4px; height: 3px; border-radius: 99px;
  background: linear-gradient(90deg, var(--money) calc(var(--p) * 100%), var(--wash-2) calc(var(--p) * 100%)); }
c29-player .pm-art { width: 34px; height: 34px; border-radius: 10px; }
c29-player .pl-mini[hidden] { display: none; }
c29-player .pm-play { flex: none; width: 44px; height: 44px; display: grid; place-items: center; border: 0; border-radius: 50%; cursor: pointer;
  color: var(--on-money); background: linear-gradient(155deg, var(--29d-coral-500), var(--money)); }
c29-player .pm-play .t-icon { width: 19px; height: 19px; }
c29-player .pm-words { flex: 1; min-width: 0; display: grid; text-align: left; padding: 2px 0; border: 0; background: none; cursor: pointer; }
c29-player .pm-words b { font-size: 14px; font-weight: 650; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
c29-player .pm-words small { font-size: 12px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
c29-player .pm-btn { flex: none; width: 40px; height: 44px; display: grid; place-items: center; border: 0; background: none; border-radius: 99px; color: var(--ink-soft); cursor: pointer; }
c29-player .pm-btn .t-icon { width: 19px; height: 19px; }
c29-player .pl-resume { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(var(--s3) + var(--safe-b)); z-index: 79;
  display: flex; align-items: center; gap: var(--s2); padding: var(--s2) var(--s4); border-radius: 99px;
  background: var(--surface); border: 1px solid var(--edge-soft); box-shadow: var(--lift);
  font-weight: 650; font-size: 13.5px; animation: t-rise var(--d2) var(--ease); }
c29-player .pl-resume[hidden] { display: none; }

/* ---------- Now-Playing sheet (D4 variant) ----------
   The same player, larger: the sheet face reuses the stage's
   seek + transport classes wholesale, so the cluster law and
   the seek paint hold identically — only the artwork and the
   title grow. The dialog itself is canon's dialog.t-sheet. */
c29-player dialog.pl-sheet { text-align: left; }
c29-player .pl-sheet-in { display: grid; gap: var(--s3); justify-items: stretch; }
c29-player .pl-sheet-art { width: 168px; height: 168px; border-radius: 22px; box-shadow: var(--lift); justify-self: center; margin-top: var(--s2);
  position: relative; overflow: hidden; background: radial-gradient(120% 120% at 30% 20%, var(--sea-200), var(--sea-600) 78%); }
c29-player .pl-sheet-art::after { content: ""; position: absolute; inset: 0; background: var(--sheen); }
c29-player .pl-sheet-art[data-art="1"] { background: radial-gradient(120% 120% at 30% 20%, var(--sun-300), var(--29d-coral-500) 80%); }
c29-player .pl-sheet-art[data-art="2"] { background: radial-gradient(120% 120% at 70% 15%, var(--sea-300), var(--sea-700) 80%); }
c29-player .pl-sheet-art[data-art="3"] { background: radial-gradient(120% 120% at 25% 25%, var(--sun-100), var(--sun-500) 82%); }
c29-player dialog.pl-sheet .t-kicker { text-align: center; font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--accent-text); }
c29-player .pl-sheet-title { font: 750 26px/1.2 var(--font-display); letter-spacing: -.01em; text-align: center; overflow-wrap: anywhere; }
c29-player dialog.pl-sheet .pl-note { text-align: center; margin-top: 0; }
c29-player dialog.pl-sheet .pl-seek { margin-top: var(--s2); }
c29-player dialog.pl-sheet .pl-ctl { margin-top: var(--s2); }
c29-player dialog.pl-sheet [data-schaplist] { border-bottom: 1px solid var(--edge-soft); }
c29-player dialog.pl-sheet [data-sheet-close] { justify-self: center; margin-top: var(--s2); }

/* ============================================================
 * REV 5 — the Bencho steals (WO-TIDE-601/602/603), ported from
 * the tide-lab prototypes P2/P3/P1 in real Tide tokens. Every
 * behavior collapses under the global motion gate: tide.js
 * snaps the springs, glues the fill, and never arms the rise.
 * ============================================================ */

/* ---------- c29-progress: the activity island [MIT-PORT dynamic-island] ----------
   One black pill that IS the activity. Width, height and corner
   are written by tide.js from ONE spring scalar; the faces are
   absolutely placed over the morphing box and crossfade against
   that scalar. The island is a dark surface in both themes (its
   own --pg-bg), so its text colors are pinned locally, exactly
   as the prototype pinned them. */
c29-progress { display: block; }
c29-progress .pg-stage { display: flex; justify-content: center; min-height: 216px; padding-top: var(--s1); }
c29-progress .pg-island { --pg-bg: var(--ink-1); position: relative; flex: none; overflow: hidden; cursor: pointer;
  background: var(--pg-bg); color: #FBF7EF; box-shadow: var(--lift-hi); -webkit-tap-highlight-color: transparent; }
[data-theme="dark"] c29-progress .pg-island { --pg-bg: #050D14; }
c29-progress .pg-face { position: absolute; inset: 0; padding: 0 var(--s4); }
c29-progress .pg-compact { display: flex; align-items: center; gap: var(--s2); }
c29-progress .pg-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--sea-300); flex: none; }
c29-progress[data-state="paused"] .pg-dot { background: var(--sun-300); }
/* THE FIT RULE (2026-10-19): the compact island's width is measured
   from its content in tide.js (_fitCompact) so the value always sits
   inside the padding. These two lines are only the last-resort guard
   for a label longer than the whole stage: the LABEL ellipsizes, the
   value (flex:none) never does. A fixed-width pill with a dynamic
   value is a defect. */
c29-progress .pg-compact .pg-label { font-size: var(--fs-sm); font-weight: 600; white-space: nowrap;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; }
c29-progress .pg-compact .pg-pct { margin-left: auto; font-variant-numeric: tabular-nums; color: #8DCABD;
  font-size: var(--fs-sm); font-weight: 700; white-space: nowrap; flex: none; }
c29-progress .pg-full { padding: var(--s4); display: flex; flex-direction: column; gap: var(--s2); }
c29-progress .pg-full h3 { margin: 0; font-family: var(--font-display); font-size: var(--fs-lg); letter-spacing: var(--track-head); }
c29-progress .pg-detail { margin: 0; font-size: var(--fs-sm); color: #C7D2D6; font-variant-numeric: tabular-nums; }
c29-progress .pg-note { margin: 0; font-size: var(--fs-sm); color: #C7D2D6; }
/* flex-crush guard (WO-TIDE-601 regression, caught by the lab rig):
   the full face is a morphing flex column — without flex:none the
   bar shrinks to 0px tall mid-morph. The bar NEVER shrinks. */
c29-progress .pg-bar { flex: none; height: 6px; min-height: 6px; border-radius: 3px; background: rgba(255,255,255,.16); overflow: hidden; margin-top: var(--s1); }
c29-progress .pg-bar i { display: block; height: 100%; width: 0%; background: var(--sea-300); border-radius: 3px; }
c29-progress .pg-actions { margin-top: auto; display: flex; gap: var(--s2); flex: none; }
c29-progress .pg-btn { appearance: none; border: 0; cursor: pointer; min-height: 38px; padding: 0 var(--s4);
  border-radius: var(--r-pill); background: rgba(255,255,255,.14); color: #fff; font: 650 var(--fs-sm) var(--font-ui); }
c29-progress .pg-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--pg-bg), 0 0 0 4px var(--sea-300); }

/* ---------- .t-range: the range family [MIT-PORT slosh] ----------
   A pill track, a drawn handle, and a fill with weight: tide.js
   rides the handle 1:1 under the finger and chases it with the
   fill on a softer second spring (the skew is the liquid leaning
   into the move, driven by the fill spring's own velocity).
   overflow:hidden keeps a surging fill inside the pill. With a
   nested native input the input is the control (invisible, full
   size, focusable) and the drawn pieces are its picture. */
.t-range { position: relative; display: block; height: 56px; border-radius: var(--r-pill); background: var(--sunken);
  box-shadow: inset 0 0 0 1px var(--edge-soft); overflow: hidden; touch-action: none; cursor: grab; }
.t-range:active { cursor: grabbing; }
.t-range:focus-visible { outline: none; box-shadow: var(--focus); }
.t-range:focus-within { box-shadow: var(--focus); }
.t-range .tr-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; border-radius: var(--r-pill);
  transform-origin: left center; background: linear-gradient(180deg, var(--sea-300), var(--sea-500)); pointer-events: none; }
.t-range .tr-handle { position: absolute; top: 50%; width: 48px; height: 48px; border-radius: 50%;
  background: var(--raised); box-shadow: var(--lift); transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center; gap: 3px; pointer-events: none; }
.t-range .tr-handle i { width: 3px; height: 16px; border-radius: 2px; background: var(--ink-4); }
.t-range input[type="range"] { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: grab; }
.t-range input[type="range"]:active { cursor: grabbing; }

/* ---------- type in motion: [data-rise] ----------
   Words are split by tide.js (never pre-hidden in markup); the
   rise itself is one WAAPI pass armed only under full motion. */
[data-rise] .t-rise-w { display: inline-block; }

/* ============================================================
 * REV 6 — the peel (WO-TIDE-604), CEREMONY CLASS. The ceremony
 * doctrine (the Principal's ruling on Desk card
 * tide-ceremony-doctrine, consumed by buzzsaw R116): ceremony
 * surfaces (gift, front door, pricing) may carry one flourish;
 * work surfaces (approvals, decks, sheets) stay calm. These
 * styles serve c29-peel and c29-gift[reveal=peel] ONLY — both
 * stamp data-surface="ceremony" on the peel root. Geometry and
 * shading are the tide-lab P4 prototype's, in canonical tokens;
 * tide.js drives the fold, and under the motion gate the corner
 * is a plain toggle (JS snaps; nothing here animates).
 * ============================================================ */

/* ---------- the peel: shared geometry [CLEAN-ROOM page-peel] ---------- */
.peel-card { position: relative; border-radius: var(--corner); overflow: hidden; box-shadow: var(--lift);
  touch-action: none; background: var(--sea-100); }
.peel-under { position: absolute; inset: 0; padding: var(--s4);
  background: linear-gradient(160deg, var(--sea-100), var(--sea-200)); }
.peel-face { position: absolute; inset: 0; padding: var(--s5); background: var(--raised); }
.peel-flap { position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(225deg, var(--sand-300), var(--sand-50) 55%, var(--sand-200));
  filter: drop-shadow(-5px 5px 7px rgba(74, 56, 28, .28)); visibility: hidden; }
.peel-hint { position: absolute; top: 0; right: 0; width: 74px; height: 74px; pointer-events: none;
  transition: opacity var(--d2) var(--ease); }
.peel-hint i { position: absolute; top: 0; right: 0; width: 0; height: 0; border-style: solid;
  border-width: 0 34px 34px 0; border-color: transparent var(--sand-200) transparent transparent; }
.peel-hint span { position: absolute; top: 30px; right: 8px; font-size: var(--fs-cap); font-weight: 700;
  letter-spacing: var(--track-cap); text-transform: uppercase; color: var(--ink-soft); } /* ink-soft, not ink-mute: the label must clear 4.5 on the card's sea ground in dark too (render-pass, Rev 6) */
.peel-btn { position: absolute; top: 0; right: 0; width: 84px; height: 84px; z-index: 3;
  appearance: none; border: 0; background: transparent; cursor: pointer; }
.peel-btn:focus-visible { outline: none; box-shadow: var(--focus); border-radius: 0 var(--corner) 0 var(--corner); }
/* face + under typography (hosts may override; the under's key
   lines belong in the corner triangle the fold uncovers, laid
   out as a right-aligned staircase clear of the fold at rest) */
.peel-face h3 { margin: 0 0 var(--s2); font-family: var(--font-display); font-size: var(--fs-xl); letter-spacing: var(--track-head); }
.peel-face p { margin: 0; color: var(--ink-soft); max-width: 26ch; }
.peel-face .peel-price { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-2xl); color: var(--money-text); margin-top: var(--s3); }
.peel-face .peel-price span { font-size: var(--fs-body); color: var(--ink-mute); }
.peel-under h3 { margin: 0 0 var(--s2); font-family: var(--font-display); font-size: var(--fs-lg); letter-spacing: var(--track-head); color: var(--ink-1); }
.peel-under p { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.65; }
.peel-under .peel-note { margin-left: auto; width: 72%; text-align: right; }
.peel-under .peel-note p { line-height: 1.45; } /* tighter staircase pitch: each descending line drops one pitch closer to the 45deg fold — 1.45 keeps equal-width lines clear of it at rest (Rev 6 render-pass: the 1.65 pitch grazed by 2.7px) */
.peel-under .peel-note .nl { display: block; }

/* ---------- c29-peel: the ceremony reveal ---------- */
c29-peel { display: block; }
c29-peel .peel-card { height: var(--peel-h, 244px); }

/* ---------- c29-gift[reveal=peel]: the ceremony opening ----------
   The payload layer is IN FLOW (it sizes the card) under an
   absolute wrapping face that carries the present; the fold
   uncovers the real payload, and the open settle sweeps the
   face fully off. Scoped to .gift-box like every gift internal
   (the drawer path moves the box out from under the element). */
.gift-box .gift-peel { margin-top: var(--s4); text-align: left; background: var(--sunken); }
.gift-box .gift-peel .peel-under { position: relative; inset: auto; padding: var(--s4); background: none; }
.gift-box .gift-peel .gift-reveal { margin-top: 0; }
.gift-box .gift-peel-face { display: flex; align-items: center; justify-content: center; padding: var(--s4); }
.gift-box .gift-peel-face .gift-stage { margin: 0; }

/* ============================================================
 * WAVE TWO (2026-10-19) — twelve new gallery demos.
 * Appended as one block; every selector is scoped to its
 * c29-* element or a unique .wv- class, tokens only.
 * ============================================================ */

/* ---- wave: sort ---- */
/* ============================================================
   SORT RAIL  <c29-sortrail>   (Wave A)
   Every value is a token. Shift and settle ride var(--d2) /
   var(--d3) (so --speed bites) on var(--ease-give) (so --bounce
   bites); row radius derives from var(--corner); row padding
   scales with var(--density).
   ============================================================ */
c29-sortrail { display: block; }
c29-sortrail .wv-sort-list { display: grid; gap: var(--s2); position: relative; }
c29-sortrail .wv-sort-row { display: flex; align-items: center; gap: var(--s3); position: relative;
  padding: calc(8px * var(--density)) calc(10px * var(--density));
  background: var(--surface); border-radius: calc(var(--corner) - 6px); box-shadow: var(--hair);
  transition: transform var(--d2) var(--ease-give), box-shadow var(--d2) var(--ease), background var(--d2) var(--ease); }
c29-sortrail .wv-sort-row.is-drag { transition: none; background: var(--raised); box-shadow: var(--lift-hi); z-index: 3; }
c29-sortrail .wv-sort-list.is-plain .wv-sort-row { transition: none; }
c29-sortrail .wv-sort-handle { width: 44px; height: 44px; flex: none; display: grid; place-items: center;
  border: 0; border-radius: 12px; background: var(--wash); color: var(--ink-mute); cursor: grab; touch-action: none;
  transition: background var(--d2) var(--ease), color var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-sortrail .wv-sort-handle:active { cursor: grabbing; transform: scale(.96); }
c29-sortrail .wv-sort-handle .t-icon { width: 20px; height: 20px; }
c29-sortrail .wv-sort-main { flex: 1; min-width: 0; display: grid; gap: 1px; }
c29-sortrail .wv-sort-name { font-size: 15px; font-weight: 650; line-height: 1.3; }
c29-sortrail .wv-sort-meta { font-size: 13px; line-height: 1.4; color: var(--ink-mute); }
c29-sortrail .wv-sort-pos { flex: none; min-width: 22px; text-align: center; font-size: 13px; font-weight: 650;
  color: var(--ink-mute); font-variant-numeric: tabular-nums; }
c29-sortrail .wv-sort-read { margin-top: var(--s3); font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
c29-sortrail .wv-sort-hint { margin-top: var(--s1); font-size: 13px; line-height: 1.5; color: var(--ink-mute); }
@media (hover: hover) {
  c29-sortrail .wv-sort-handle:hover { background: var(--wash-2); color: var(--ink-soft); }
  c29-sortrail .wv-sort-row:hover { background: color-mix(in srgb, var(--surface) 94%, var(--ink)); }
}

/* ---- wave: pull ---- */
/* ============================================================
   PULL TO REFRESH  <c29-pull>   (Wave A)
   The card clips to var(--corner); the header strip is the only
   drag surface. Fill height and snap ride var(--d3) on
   var(--ease-give) (so --speed and --bounce bite); the wave spin
   runs calc(900ms / var(--speed)); row padding scales with
   var(--density).
   ============================================================ */
c29-pull { display: block; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); overflow: hidden; }
c29-pull .wv-pull-head { position: relative; height: 56px; display: flex; align-items: center; justify-content: center; gap: var(--s2);
  padding: 0 var(--s4); background: var(--sunken); overflow: hidden; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
c29-pull .wv-pull-head:active { cursor: grabbing; }
c29-pull .wv-pull-fill { position: absolute; left: 0; right: 0; bottom: 0; height: 0;
  background: color-mix(in srgb, var(--sea-300) 68%, var(--sea-100));
  transition: height var(--d3) var(--ease-give); }
c29-pull .wv-pull-head.is-drag .wv-pull-fill { transition: none; }
c29-pull .wv-pull-wave { position: relative; z-index: 1; display: none; width: 22px; height: 22px; color: var(--accent-text); }
c29-pull .wv-pull-wave .t-icon { width: 22px; height: 22px; }
c29-pull[data-state="refreshing"] .wv-pull-wave { display: grid; place-items: center; animation: wv-pull-spin calc(900ms / var(--speed)) linear infinite; }
c29-pull .wv-pull-label { position: relative; z-index: 1; font-size: 14px; font-weight: 650; color: var(--ink-soft); }
c29-pull .wv-pull-rows { padding: var(--s1) var(--s5); }
c29-pull .wv-pull-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--s3);
  padding: calc(10px * var(--density)) 0; border-top: 1px solid var(--edge-soft); }
c29-pull .wv-pull-row:first-child { border-top: 0; }
c29-pull .wv-pull-row-main { display: grid; gap: 1px; min-width: 0; }
c29-pull .wv-pull-row-label { font-size: 15px; font-weight: 600; line-height: 1.35; }
c29-pull .wv-pull-row-sub { font-size: 13px; line-height: 1.4; color: var(--ink-mute); }
c29-pull .wv-pull-row-value { font-family: var(--font-display); font-weight: 750; font-size: 17px; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
c29-pull .wv-pull-rows.is-new .wv-pull-row { animation: wv-pull-in var(--d3) var(--ease) both; }
c29-pull .wv-pull-rows.is-new .wv-pull-row:nth-child(2) { animation-delay: calc(50ms / var(--speed)); }
c29-pull .wv-pull-rows.is-new .wv-pull-row:nth-child(3) { animation-delay: calc(100ms / var(--speed)); }
c29-pull .wv-pull-rows.is-new .wv-pull-row:nth-child(4) { animation-delay: calc(150ms / var(--speed)); }
c29-pull .wv-pull-foot { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s3) var(--s5) var(--s4); border-top: 1px solid var(--edge-soft); }
c29-pull .wv-pull-updated { font-size: 13px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
@keyframes wv-pull-spin { to { transform: rotate(360deg); } }
@keyframes wv-pull-in { from { opacity: 0; transform: translateY(4px); } }
@media (hover: hover) {
  c29-pull .wv-pull-head:hover { background: color-mix(in srgb, var(--sunken) 92%, var(--ink)); }
}

/* ---- wave: tabs ---- */
/* ============================================================
   TABS  <c29-tabs>   (Wave A)
   The pill rides left + width on var(--d3) var(--ease-give), so
   --speed sets its pace and --bounce its overshoot. Panels
   crossfade and slide on var(--d3). The bar radius is the pill
   radius; panel padding scales with var(--density).
   ============================================================ */
c29-tabs { display: block; }
c29-tabs .wv-tabs-bar { position: relative; isolation: isolate; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 2px; padding: 3px; border-radius: var(--r-pill); background: var(--wash); }
c29-tabs .wv-tabs-pill { position: absolute; z-index: -1; top: 3px; bottom: 3px; border-radius: var(--r-pill);
  background: var(--raised); box-shadow: var(--lift);
  transition: left var(--d3) var(--ease-give), width var(--d3) var(--ease-give); }
c29-tabs .wv-tabs-tab { min-height: 44px; padding: 0 var(--s3); border: 0; background: none; border-radius: var(--r-pill);
  font-size: 14px; font-weight: 600; color: var(--ink-mute); cursor: pointer; white-space: nowrap;
  transition: color var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-tabs .wv-tabs-tab:active { transform: scale(.97); }
c29-tabs .wv-tabs-tab[aria-selected="true"] { color: var(--ink); }
c29-tabs .wv-tabs-panel { padding: calc(var(--s4) * var(--density)) var(--s1) 0; display: grid; gap: var(--s2); }
c29-tabs .wv-tabs-panel p { font-size: 14px; line-height: 1.55; color: var(--ink-soft); }
c29-tabs .wv-tabs-panel.is-in { animation: wv-tabs-in var(--d3) var(--ease) both; }
c29-tabs .wv-tabs-stat { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
c29-tabs .wv-tabs-num { font-family: var(--font-display); font-weight: 750; font-size: 20px; letter-spacing: -.01em;
  color: var(--ink); font-variant-numeric: tabular-nums; }
@keyframes wv-tabs-in { from { opacity: 0; transform: translateX(6px); } }
@media (hover: hover) {
  c29-tabs .wv-tabs-tab:hover { color: var(--ink-soft); }
  c29-tabs .wv-tabs-tab[aria-selected="true"]:hover { color: var(--ink); }
}

/* ---- wave: chart ---- */
/* ---------- c29-livechart: a working week, two data sets (Wave B) ---------- */
c29-livechart { display: block; }
c29-livechart .wv-chart-ctl { display: grid; gap: var(--s2); }
c29-livechart .wv-chart-row { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
c29-livechart .wv-chart-lab { flex: none; min-width: 44px; font-size: 13px; font-weight: 650; color: var(--ink-mute); }
c29-livechart .wv-chart-unit { margin-top: var(--s3); font-size: 13px; font-weight: 600; color: var(--ink-soft); }
c29-livechart .wv-chart-plot { position: relative; margin-top: var(--s2); background: var(--sunken); border-radius: calc(var(--corner) - 6px); overflow: hidden; }
c29-livechart .wv-chart-plot svg { display: block; width: 100%; height: auto; }
c29-livechart .wv-chart-grid { stroke: var(--edge-soft); stroke-width: 1; }
c29-livechart .wv-chart-base { stroke: var(--edge); stroke-width: 1.5; }
c29-livechart .wv-chart-area { fill: color-mix(in srgb, var(--accent) 14%, transparent); stroke: none; transition: d var(--d3) var(--ease-give), opacity var(--d3) var(--ease); }
c29-livechart .wv-chart-line { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; transition: d var(--d3) var(--ease-give), opacity var(--d3) var(--ease); }
c29-livechart .wv-chart-dot { fill: var(--surface); stroke: var(--accent); stroke-width: 3; cursor: pointer; transition: cy var(--d3) var(--ease-give), cx var(--d3) var(--ease-give), fill var(--d2) var(--ease); }
c29-livechart .wv-chart-dot.is-on { fill: var(--accent); }
c29-livechart .wv-chart-hit { fill: transparent; stroke: none; cursor: pointer; }
c29-livechart .wv-chart-pt { outline: none; }
c29-livechart .wv-chart-pt:focus-visible .wv-chart-dot { fill: var(--accent); stroke: var(--focus-ring); }
c29-livechart .wv-chart-bar { fill: var(--accent); cursor: pointer; transition: y var(--d3) var(--ease-give), height var(--d3) var(--ease-give), opacity var(--d3) var(--ease), fill var(--d2) var(--ease); }
c29-livechart .wv-chart-bar.is-on { fill: var(--sea-500); }
c29-livechart .wv-chart-bar:focus-visible { stroke: var(--focus-ring); stroke-width: 2; outline: none; }
c29-livechart .wv-chart-g-line, c29-livechart .wv-chart-g-points, c29-livechart .wv-chart-g-bars { transition: opacity var(--d3) var(--ease); }
c29-livechart[data-mode="bars"] .wv-chart-g-line, c29-livechart[data-mode="bars"] .wv-chart-g-points { opacity: 0; pointer-events: none; }
c29-livechart[data-mode="line"] .wv-chart-g-bars { opacity: 0; pointer-events: none; }
c29-livechart.is-init .wv-chart-line, c29-livechart.is-init .wv-chart-area, c29-livechart.is-init .wv-chart-dot, c29-livechart.is-init .wv-chart-bar { transition: none; }
c29-livechart .wv-chart-tag { position: absolute; z-index: 2; transform: translate(-50%, -140%); padding: 5px 10px; border-radius: 99px; white-space: nowrap; pointer-events: none;
  background: var(--ink); color: var(--bg); font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums;
  transition: left var(--d3) var(--ease-give), top var(--d3) var(--ease-give), opacity var(--d2) var(--ease); }
c29-livechart .wv-chart-days { display: grid; grid-template-columns: repeat(7, 1fr); margin-top: var(--s2); text-align: center; font-size: 13px; font-weight: 600; color: var(--ink-mute); }
c29-livechart .wv-chart-read { margin-top: var(--s2); font-size: 13px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
c29-livechart .wv-chart-row[hidden] { display: none; }
c29-livechart .wv-chart-ref { margin-top: var(--s3); border-top: 2px dashed var(--edge); padding-top: var(--s2); font-size: 13px; font-weight: 600; color: var(--ink-soft); }
c29-livechart .wv-chart-ref[hidden] { display: none; }
@media (hover: hover) {
  c29-livechart .wv-chart-pt:hover .wv-chart-dot { fill: var(--accent); }
  c29-livechart .wv-chart-bar:hover { fill: var(--sea-500); }
}

/* ---- wave: odo ---- */
/* ---------- c29-odometer: rolling-digit totals (Wave B) ---------- */
c29-odometer { display: block; }
c29-odometer .wv-odo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s3); }
c29-odometer .wv-odo-stat { display: grid; justify-items: center; gap: 2px; padding: var(--s4) var(--s2); text-align: center; background: var(--sunken); border-radius: calc(var(--corner) - 6px); }
c29-odometer .wv-odo-num { display: flex; align-items: flex-start; height: 1em; overflow: hidden; font: 800 32px/1 var(--font-display); letter-spacing: -0.01em; color: var(--ink); font-variant-numeric: tabular-nums; }
c29-odometer .wv-odo-col { display: block; width: 0.62em; height: 1em; overflow: hidden; text-align: center; }
c29-odometer .wv-odo-comma { display: block; width: 0.3em; text-align: center; }
c29-odometer .wv-odo-strip { display: flex; flex-direction: column; will-change: transform; transition: transform var(--d5) var(--ease-give); transition-delay: calc(var(--wv-i, 0) * 70ms / var(--speed)); }
c29-odometer .wv-odo-strip > span { display: block; height: 1em; line-height: 1; }
c29-odometer.is-instant .wv-odo-strip { transition: none; }
c29-odometer .wv-odo-label { margin-top: var(--s2); font-size: 15px; font-weight: 700; color: var(--ink); }
c29-odometer .wv-odo-sub { font-size: 13px; line-height: 1.45; color: var(--ink-mute); }
c29-odometer .wv-odo-foot { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s4); }
c29-odometer .wv-odo-note { font-size: 13px; color: var(--ink-mute); }
@media (max-width: 400px) {
  c29-odometer .wv-odo-num { font-size: 27px; }
  c29-odometer .wv-odo-grid { gap: var(--s2); }
}

/* ---- wave: job ---- */
/* ============================================================
 * LANE A / A2 — c29-jobrun real-data variant CSS FRAGMENT
 * Integration: REPLACE the canon c29-jobrun CSS block (tide.css,
 * the .wv-job-* rules) with these .jr-* rules, inside the
 * @layer c29.components block. Tokens only.
 * ============================================================ */

c29-jobrun { display: block; }
c29-jobrun .jr-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
c29-jobrun .jr-title { font: 750 var(--fs-xl)/1.2 var(--font-display); letter-spacing: var(--track-head); color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
c29-jobrun .jr-note { margin-top: var(--s2); font-size: var(--fs-sm); color: var(--ink-mute); line-height: 1.5; overflow-wrap: anywhere; }
/* The beam exists ONLY when the record carries a measured
   percent (the JS hides the element otherwise). */
c29-jobrun .jr-beam { display: block; height: 5px; border-radius: 99px; background: var(--wash-2); overflow: hidden; margin-top: var(--s4); }
c29-jobrun .jr-beam i { display: block; height: 100%; width: 0; background: var(--accent); border-radius: inherit; transition: width var(--d3) var(--ease); }
c29-jobrun .jr-trace { list-style: none; margin: var(--s4) 0 0; padding: 0; }
c29-jobrun .jr-trace:empty { margin: 0; }
c29-jobrun .jr-trace li { position: relative; padding: 0 0 var(--s4) 34px; }
c29-jobrun .jr-trace li::before { content: ""; position: absolute; left: 10px; top: 22px; bottom: -2px; width: 2px; border-radius: 2px; background: var(--edge); }
c29-jobrun .jr-trace li:last-child { padding-bottom: 0; }
c29-jobrun .jr-trace li:last-child::before { display: none; }
c29-jobrun .jr-dot { position: absolute; left: 3px; top: 3px; width: 16px; height: 16px; border-radius: 50%; background: var(--raised); box-shadow: inset 0 0 0 2px var(--accent); }
c29-jobrun .jr-trace li[data-s="done"] .jr-dot { background: var(--accent); box-shadow: none; }
c29-jobrun .jr-trace li[data-s="done"] .jr-dot::after { content: ""; position: absolute; left: 5px; top: 3px; width: 4px; height: 7px; border: solid var(--on-accent); border-width: 0 2px 2px 0; rotate: 45deg; }
c29-jobrun .jr-trace li[data-s="bad"] .jr-dot { background: var(--bad); box-shadow: none; }
c29-jobrun .jr-trace li[data-s="wait"] .jr-dot { box-shadow: inset 0 0 0 2px var(--sun-500); background: var(--sun-100); }
/* Running breathes ONLY via the canon t-breathe keyframe and
   collapses under the motion gate like every Tide animation.
   Stalled never animates: honest stillness. */
c29-jobrun .jr-trace li[data-s="run"] .jr-dot { animation: t-breathe 1.6s var(--ease) infinite; }
c29-jobrun .jr-trace b { display: block; font-size: 15px; font-weight: 600; overflow-wrap: anywhere; }
c29-jobrun .jr-trace span { display: block; font-size: 13px; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-jobrun .jr-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); margin-top: var(--s4); font-size: var(--fs-sm); color: var(--ink-mute); }
c29-jobrun .jr-meta:empty { margin: 0; }
c29-jobrun[data-state="empty"] .jr-title { color: var(--ink-mute); font-size: var(--fs-lg); }

/* ---- wave: palette ---- */
/* ---------- c29-palette: the command palette (Wave C) ----------
   The dialog rounds on --corner, the flash pulse spends
   --accent, and every duration divides by --speed, so Tune
   reaches the palette and its jump flash alike. The search
   field computes at 17px: iOS never zooms it. */
c29-palette { display: block; }
c29-palette .wv-palette-kbd { display: inline-flex; align-items: center; min-height: 24px; padding: 0 9px;
  border-radius: 99px; background: var(--wash); color: var(--ink-soft);
  font-size: 13px; font-weight: 650; white-space: nowrap; }
c29-palette .wv-palette-hint { margin-top: var(--s2); font-size: 14px; color: var(--ink-mute); line-height: 1.5; }
c29-palette .wv-palette-dialog { width: min(540px, calc(100vw - 32px)); max-height: min(72vh, 580px);
  margin: 10vh auto auto; padding: var(--s3); border: 0; border-radius: var(--corner);
  background: var(--raised); color: var(--ink); box-shadow: var(--lift-hi); overflow: hidden; }
c29-palette .wv-palette-dialog[open] { animation: wv-palette-in var(--d2) var(--ease-give); }
@keyframes wv-palette-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
c29-palette .wv-palette-dialog::backdrop { background: color-mix(in srgb, var(--ink-1) 42%, transparent);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }
c29-palette .wv-palette-input { width: 100%; min-height: 52px; padding: 0 var(--s4); border: 0;
  border-radius: calc(var(--corner) - 8px); background: var(--sunken); color: var(--ink);
  font-size: 17px; box-shadow: inset 0 0 0 1px var(--edge-soft);
  transition: box-shadow var(--d2) var(--ease); }
c29-palette .wv-palette-input::placeholder { color: var(--ink-mute); opacity: 1; }
c29-palette .wv-palette-input:focus { outline: none; box-shadow: var(--focus); }
c29-palette .wv-palette-list { display: grid; gap: 2px; margin-top: var(--s2);
  max-height: 324px; overflow-y: auto; overscroll-behavior: contain; }
c29-palette .wv-palette-row { display: flex; align-items: center; gap: var(--s3); width: 100%;
  min-height: 48px; padding: var(--s2) var(--s3); border: 0; border-radius: var(--r-md);
  background: none; color: var(--ink); text-align: left; cursor: pointer; font-size: 15px;
  transition: background var(--d1) var(--ease); }
c29-palette .wv-palette-row.is-active { background: var(--accent-soft); box-shadow: inset 0 0 0 1.5px var(--accent); }
@media (hover: hover) {
  c29-palette .wv-palette-row:hover { background: var(--wash); }
  c29-palette .wv-palette-row.is-active:hover { background: var(--accent-soft); }
}
c29-palette .wv-palette-name { flex: 1; min-width: 0; font-weight: 550;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
c29-palette .wv-palette-kind { flex: none; padding: 3px 9px; border-radius: 99px; background: var(--wash);
  font-size: 13px; font-weight: 650; color: var(--ink-mute); white-space: nowrap; }
c29-palette .wv-palette-row.is-active .wv-palette-kind { color: var(--accent-text); background: var(--raised); }
c29-palette .wv-palette-empty { padding: var(--s5) var(--s3); text-align: center;
  font-size: 14px; color: var(--ink-mute); }
c29-palette .wv-palette-foot { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
  margin-top: var(--s2); padding: var(--s3) var(--s1) 0; border-top: 1px solid var(--edge-soft);
  font-size: 13px; color: var(--ink-mute); }
.wv-palette-flash > header { border-radius: var(--r-md);
  animation: wv-palette-pulse calc(950ms / var(--speed)) var(--ease) 2; }
@keyframes wv-palette-pulse {
  0% { box-shadow: 0 0 0 0 transparent; }
  30% { box-shadow: 0 0 0 3px var(--accent), 0 0 0 11px color-mix(in srgb, var(--accent) 22%, transparent); }
  100% { box-shadow: 0 0 0 0 transparent; } }

/* ---- wave: steps ---- */
/* ---------- c29-steps: the stepper (Wave C) ----------
   The connector fill and the check draw ride --speed and
   --ease-give, the active node breathes on --speed, and the
   accent knob recolors the whole run. Wide lays the four
   stages across; narrow stacks them and the line turns with
   them, driven by the same --wv-f position value. */
c29-steps { display: block; --wv-f: 0%; }
c29-steps .wv-steps-wrap { position: relative; }
c29-steps .wv-steps-line { position: absolute; top: 22px; left: 12.5%; right: 12.5%; height: 4px;
  border-radius: 99px; background: var(--wash-2); overflow: hidden; }
c29-steps .wv-steps-fill { display: block; height: 100%; width: var(--wv-f);
  background: var(--accent); border-radius: inherit;
  transition: width calc(520ms / var(--speed)) var(--ease-give), height calc(520ms / var(--speed)) var(--ease-give); }
c29-steps .wv-steps-rail { position: relative; z-index: 1; display: flex; list-style: none; margin: 0; padding: 0; }
c29-steps .wv-steps-rail li { flex: 1; min-width: 0; display: grid; justify-items: center; gap: var(--s2); text-align: center; }
c29-steps .wv-steps-node { position: relative; width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center; background: var(--surface);
  box-shadow: inset 0 0 0 2px var(--edge);
  transition: background var(--d2) var(--ease), box-shadow var(--d2) var(--ease); }
c29-steps .wv-steps-num { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink-soft);
  transition: opacity var(--d1) var(--ease); }
c29-steps .wv-steps-check { position: absolute; width: 22px; height: 22px; fill: none;
  stroke: var(--on-accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
c29-steps .wv-steps-check path { stroke-dasharray: 24; stroke-dashoffset: 24;
  transition: stroke-dashoffset var(--d3) var(--ease-give); }
c29-steps .wv-steps-rail li.is-done .wv-steps-node { background: var(--accent); box-shadow: none; }
c29-steps .wv-steps-rail li.is-done .wv-steps-num { opacity: 0; }
c29-steps .wv-steps-rail li.is-done .wv-steps-check path { stroke-dashoffset: 0; }
c29-steps .wv-steps-rail li.is-active .wv-steps-node { background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px var(--accent);
  animation: wv-steps-pulse calc(1800ms / var(--speed)) var(--ease) infinite; }
c29-steps .wv-steps-rail li.is-active .wv-steps-num { color: var(--accent-text); }
@keyframes wv-steps-pulse { 0% { box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 0 color-mix(in srgb, var(--accent) 32%, transparent); }
  70% { box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 10px transparent; }
  100% { box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 0 transparent; } }
c29-steps .wv-steps-lab { display: grid; gap: 1px; }
c29-steps .wv-steps-lab b { font-size: 14px; font-weight: 650; line-height: 1.3; }
c29-steps .wv-steps-state { font-size: 13px; color: var(--ink-mute); }
c29-steps .wv-steps-rail li.is-active .wv-steps-state { color: var(--accent-text); font-weight: 650; }
c29-steps .wv-steps-status { margin-top: var(--s5); font-size: 15px; font-weight: 600; line-height: 1.45; min-height: 22px; }
c29-steps .wv-steps-ctl { margin-top: var(--s3); }
c29-steps .wv-steps-ctl .t-btn[disabled] { opacity: .45; pointer-events: none; }
@media (max-width: 520px) {
  c29-steps .wv-steps-rail { flex-direction: column; align-items: stretch; gap: var(--s4); }
  c29-steps .wv-steps-rail li { grid-template-columns: 44px minmax(0, 1fr); justify-items: start; text-align: left; align-items: center; }
  c29-steps .wv-steps-line { top: 22px; bottom: 22px; left: 20px; right: auto; width: 4px; height: auto; }
  c29-steps .wv-steps-fill { width: 100%; height: var(--wv-f); }
}

/* ---- wave: tuner ---- */
/* ---------- c29-themetuner: the theme tuner (Wave D, Delight) ---------- */
c29-themetuner { display: block; --wv-paper: var(--sand-100); --wv-pane: var(--sea-100); }
c29-themetuner .wv-tuner-root { display: grid; gap: var(--s4); }
c29-themetuner .wv-tuner-row { display: grid; gap: var(--s2); }
c29-themetuner .wv-tuner-label { font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-mute); }
c29-themetuner .wv-tuner-swatches { display: flex; flex-wrap: wrap; gap: var(--s2); }
c29-themetuner .wv-tuner-swatch { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 6px 14px 6px 8px; border: 0; border-radius: var(--r-pill); background: var(--wash); color: var(--ink); font-size: 14px; font-weight: 600; cursor: pointer; transition: background var(--d2) var(--ease), box-shadow var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-themetuner .wv-tuner-swatch i { width: 26px; height: 26px; border-radius: 50%; background: var(--wv-sw); box-shadow: inset 0 0 0 1px var(--edge-soft); flex: none; }
c29-themetuner .wv-tuner-swatch[aria-pressed="true"] { background: var(--surface); box-shadow: 0 0 0 2px var(--ink); }
c29-themetuner .wv-tuner-swatch:active { transform: scale(.97); }
@media (hover: hover) {
  c29-themetuner .wv-tuner-swatch:hover { background: var(--wash-2); }
  c29-themetuner .wv-tuner-swatch[aria-pressed="true"]:hover { background: var(--surface); }
}
c29-themetuner .wv-tuner-actions { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
c29-themetuner .wv-tuner-read { font-size: 14px; color: var(--ink-soft); }
c29-themetuner .wv-tuner-preview { border-radius: var(--corner); background: var(--wv-paper); box-shadow: var(--hair); padding: var(--s4); transition: background-color var(--d3) var(--ease), border-radius var(--d3) var(--ease-give); }
c29-themetuner .wv-tuner-pane { display: flex; align-items: center; gap: var(--s2); padding: var(--s3); border-radius: calc(var(--corner) - 6px); background: var(--wv-pane); transition: background-color var(--d3) var(--ease), border-radius var(--d3) var(--ease-give); }
c29-themetuner .wv-tuner-price { margin-left: auto; font: 750 20px/1 var(--font-display); font-variant-numeric: tabular-nums; }
c29-themetuner .wv-tuner-title { margin-top: var(--s3); font: 750 19px/1.25 var(--font-display); letter-spacing: var(--track-head); }
c29-themetuner .wv-tuner-line { margin-top: 2px; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
c29-themetuner .wv-tuner-buy { margin-top: var(--s3); transition: background var(--d2) var(--ease), color var(--d2) var(--ease), transform var(--d1) var(--ease); }

/* ---- wave: ticker ---- */
/* ---------- c29-ticker: the notice ticker (Wave D, Delight) ---------- */
c29-ticker { display: block; }
c29-ticker .wv-ticker-viewport { overflow: hidden; border-radius: var(--corner); background: var(--sunken); box-shadow: inset 0 0 0 1px var(--edge-soft); transition: border-radius var(--d3) var(--ease-give); }
c29-ticker .wv-ticker-track { display: flex; width: max-content; animation: wv-ticker-scroll calc(30s / var(--speed)) linear infinite; }
c29-ticker .wv-ticker-group { display: flex; align-items: center; gap: var(--s2); padding: var(--s2); flex: none; }
c29-ticker .wv-ticker-list { display: flex; flex-wrap: wrap; gap: var(--s2); padding: var(--s2); }
c29-ticker .wv-ticker-item { flex: none; display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px; border: 0; border-radius: var(--r-pill); background: var(--raised); box-shadow: var(--hair); color: var(--ink); font-size: 14px; font-weight: 550; white-space: nowrap; font-variant-numeric: tabular-nums; }
c29-ticker button.wv-ticker-item { cursor: pointer; transition: background var(--d2) var(--ease), transform var(--d1) var(--ease); }
c29-ticker button.wv-ticker-item:active { transform: scale(.97); }
c29-ticker.is-held .wv-ticker-track, c29-ticker:focus-within .wv-ticker-track { animation-play-state: paused; }
c29-ticker.is-static .wv-ticker-track { animation: none; }
@media (hover: hover) {
  c29-ticker:hover .wv-ticker-track { animation-play-state: paused; }
  c29-ticker button.wv-ticker-item:hover { background: var(--wash); }
}
@keyframes wv-ticker-scroll { to { transform: translateX(-50%); } }
c29-ticker .wv-ticker-pin { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); min-height: 44px; font-size: 14px; }
c29-ticker .wv-ticker-pin-note { color: var(--ink-mute); font-size: 14px; }
c29-ticker .wv-ticker-pin-label { font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--accent-text); }
c29-ticker .wv-ticker-pin-text { font-weight: 600; font-variant-numeric: tabular-nums; }

/* ---- wave: gauge ---- */
/* ---------- c29-gauge: the level gauge (Wave D, Delight) ---------- */
c29-gauge { display: block; }
c29-gauge .wv-gauge-frame { border-radius: var(--corner); background: var(--sunken); box-shadow: inset 0 0 0 1px var(--edge-soft); padding: var(--s4); transition: border-radius var(--d3) var(--ease-give); }
c29-gauge .wv-gauge-svg { width: 100%; height: auto; display: block; }
c29-gauge .wv-gauge-line { stroke: var(--ink); stroke-width: 1; fill: none; stroke-linecap: round; stroke-linejoin: round; }
c29-gauge .wv-gauge-solid { fill: var(--ink); }
c29-gauge .wv-gauge-hit { fill: transparent; }
c29-gauge .wv-gauge-water { transition: transform var(--d4) var(--ease-give); }
c29-gauge .wv-gauge-waterfill { fill: color-mix(in srgb, var(--sea-300) 42%, transparent); }
c29-gauge .wv-gauge-waterline { stroke: var(--sea-700); stroke-width: 1; stroke-linecap: round; }
c29-gauge .wv-gauge-wave { stroke: var(--sea-700); stroke-width: 1; fill: none; stroke-linecap: round; opacity: .65; }
c29-gauge .wv-gauge-beam { fill: var(--sun-300); opacity: 0; transition: opacity var(--d3) var(--ease); }
c29-gauge[data-lit="true"] .wv-gauge-beam { opacity: .48; }
c29-gauge .wv-gauge-lampglass { fill: var(--sun-300); opacity: 0; transition: opacity var(--d2) var(--ease); }
c29-gauge[data-lit="true"] .wv-gauge-lampglass { opacity: 1; }
c29-gauge .wv-gauge-lampbtn { cursor: pointer; }
c29-gauge .wv-gauge-lampbtn:focus-visible { outline: none; }
c29-gauge .wv-gauge-lampbtn:focus-visible .wv-gauge-lampframe { stroke: var(--accent); stroke-width: 2; }
@media (hover: hover) {
  c29-gauge .wv-gauge-lampbtn:hover .wv-gauge-lampframe { stroke: var(--accent-text); }
}
c29-gauge .wv-gauge-read { margin-top: var(--s3); font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
c29-gauge .wv-gauge-ctl { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); }

/* ---------- release cards + wide page (download surfaces) ----------
   Added 2026-10-19 by the download-page run (TIDE FIRST: pattern lands in
   canon with its adversary round BEFORE the page consumes it; round in
   brains projects/download-page/DOWNLOAD-PAGE-2026-10-19.md).
   Anatomy: .t-page.is-wide for marketing-width surfaces; .t-rel-grid of
   .t-rel cards (compose with .t-card). Each card: .t-rel-head (name +
   honest status .t-tag), .t-rel-shot (a REAL screenshot, or .is-empty —
   the honest empty state that says the shot lands with the first build;
   never a mockup), .t-rel-req (requirement line), .t-rel-act (one
   action). Status tones are the canon tag tones: is-ok available,
   is-warn in the workshop, plain tag listed. */
@layer c29.components {
  .t-page.is-wide { max-width: 1120px; }
  .t-rel-grid { display: grid; gap: var(--s4); grid-template-columns: minmax(0, 1fr); }
  @media (min-width: 720px) { .t-rel-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (min-width: 1080px) { .t-rel-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .t-rel { display: grid; gap: var(--s3); align-content: start; }
  .t-rel-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
  .t-rel-name { font-family: var(--font-display); font-weight: 750; font-size: var(--fs-lg); letter-spacing: var(--track-head); }
  .t-rel-shot { border-radius: calc(var(--corner) - var(--s2)); background: var(--wash); overflow: hidden; box-shadow: inset 0 0 0 1px var(--edge); }
  .t-rel-shot img { display: block; width: 100%; height: auto; }
  .t-rel-shot.is-empty { display: grid; place-items: center; padding: var(--s6) var(--s4); text-align: center; }
  .t-rel-req { font-size: var(--fs-sm); color: var(--ink-mute); }
  .t-rel-act { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: auto; padding-top: var(--s2); }
}

/* ---------- device stage — staged product scenes (download surfaces) ----------
   Added 2026-10-19 by the download-page v2 run (TIDE FIRST: pattern lands
   in canon with its adversary round BEFORE the page consumes it; round in
   brains projects/download-page/DOWNLOAD-PAGE-2026-10-19.md).
   Anatomy: .t-stage is the composed scene a device stands in (a calm
   panel; captions sit inside it, under the device). .t-dev is device
   HARDWARE — a bezel the real capture sits inside: .is-mac (laptop,
   with a base foot), .is-phone (handset), .is-browser (a browser bar
   over a web capture; the bar names the real domain the capture came
   from). Frames are presentation only: the pixels inside are the
   untouched capture — never redrawn, never a mockup. .t-dev-crop crops
   a capture's own surround (window-shadow margins, letterbox) in CSS:
   the author measures the real content box of the capture and sets
   --cx/--cy/--cw/--ch (fractions of the ORIGINAL image) plus the
   visible aspect-ratio inline; the frame then shows exactly that
   region of the real pixels. .t-dev-screen.is-empty is the honest
   empty state inside a frame: the slot, labeled, shot pending. */
@layer c29.components {
  .t-stage { position: relative; border-radius: var(--corner); overflow: hidden;
    background: linear-gradient(180deg, color-mix(in srgb, var(--sea-100) 60%, var(--surface)) 0%, var(--surface) 78%);
    box-shadow: inset 0 0 0 1px var(--edge); padding: var(--s8) var(--s4) var(--s5); }
  @media (min-width: 720px) { .t-stage { padding: var(--s12) var(--s8) var(--s6); } }
  .t-stage .t-dev { margin-inline: auto; }
  .t-dev { --dev-bezel: #11161B; position: relative; background: var(--dev-bezel);
    border-radius: 22px; padding: 10px; box-shadow: 0 0 0 1px var(--edge), var(--lift-hi); }
  .t-dev-screen { border-radius: 13px; overflow: hidden; background: var(--wash); }
  .t-dev-screen img { display: block; width: 100%; height: auto; }
  .t-dev-screen.is-empty { display: grid; place-items: center; padding: var(--s6) var(--s4);
    text-align: center; background: var(--sunken); }
  .t-dev.is-mac { margin-bottom: 14px; }
  .t-dev.is-mac::after { content: ""; position: absolute; left: -3%; right: -3%; bottom: -13px; height: 13px;
    background: linear-gradient(180deg, #2B333B, #12161B); border-radius: 3px 3px 12px 12px;
    box-shadow: 0 10px 18px -10px rgba(20, 16, 8, .45); }
  .t-dev.is-phone { border-radius: 42px; padding: 11px; max-width: 330px; }
  .t-dev.is-phone .t-dev-screen { border-radius: 32px; }
  .t-dev.is-phone .t-dev-screen.is-empty { aspect-ratio: 9 / 18.6; padding: var(--s4); }
  .t-dev.is-browser { background: var(--raised); padding: 0; border-radius: 16px; overflow: hidden; }
  .t-dev-bar { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) var(--s3); background: var(--sunken); }
  .t-dev-dots { display: flex; gap: 6px; flex: none; }
  .t-dev-dots i { width: 10px; height: 10px; border-radius: 50%; background: var(--ink-4); }
  .t-dev-url { flex: 1; min-width: 0; text-align: center; font-family: var(--font-mono); font-size: var(--fs-cap);
    color: var(--ink-mute); background: var(--raised); border-radius: var(--r-pill);
    padding: var(--s1) var(--s3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .t-dev.is-browser .t-dev-screen { border-radius: 0; }
  .t-dev-crop { position: relative; overflow: hidden; }
  .t-dev-crop img { position: absolute; max-width: none;
    width: calc(100% / var(--cw)); height: auto;
    left: calc(var(--cx) / var(--cw) * -100%); top: calc(var(--cy) / var(--ch) * -100%); }
}

/* ============================================================
   PRESENCE <c29-presence> · BOARD <c29-board> · LIST REORDER
   (Workbench lane, 2026-10-19 — see tide.js for the contracts)
   ============================================================ */
@layer c29.components {
  c29-presence { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; min-height: 32px; }
  c29-presence .pr-stack { display: inline-flex; align-items: center; }
  c29-presence .pr-av { width: 28px; height: 28px; border-radius: 50%; display: inline-grid; place-items: center;
    font: 700 11px/1 var(--font-ui); color: #fff; box-shadow: 0 0 0 2px var(--bg); margin-left: -6px; flex: none; }
  c29-presence .pr-av:first-child { margin-left: 0; }
  c29-presence .pr-more { background: var(--wash-2); color: var(--ink-soft); font-size: 10px; }
  c29-presence .pr-label { font-size: var(--fs-sm); color: var(--ink-soft); font-weight: 600; }
  c29-presence .pr-note { font-size: var(--fs-sm); color: var(--accent-text); font-weight: 600; }

  c29-board { display: block; position: relative; overflow: hidden; background:
      radial-gradient(circle, var(--edge-soft) 1px, transparent 1.2px) 0 0 / 24px 24px, var(--sunken);
    border-radius: var(--corner); box-shadow: var(--hair); touch-action: none; min-height: 320px; }
  c29-board .bd-world { position: absolute; left: 0; top: 0; width: 6000px; height: 6000px; transform-origin: 0 0; }
  c29-board .bd-links { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
  c29-board .bd-line { stroke: var(--ink-mute); stroke-width: 2; opacity: .55; }
  c29-board .bd-item { position: absolute; min-height: 72px; padding: var(--s3); border-radius: 10px;
    background: var(--sand-50); color: var(--ink-1); box-shadow: 0 1px 2px rgba(30,30,20,.18), 0 8px 20px -12px rgba(30,30,20,.35);
    font-size: var(--fs-sm); line-height: 1.4; cursor: grab; overflow-wrap: anywhere; user-select: none; -webkit-user-select: none; }
  c29-board .bd-item.is-drag { cursor: grabbing; box-shadow: var(--lift-hi); }
  c29-board .bd-item:focus-visible { outline: none; box-shadow: var(--focus); }
  c29-board .bd-item[data-color="sand"] { background: var(--sand-50); }
  c29-board .bd-item[data-color="sun"] { background: var(--sun-100); }
  c29-board .bd-item[data-color="sea"] { background: var(--sea-100); }
  c29-board .bd-item[data-color="coral"] { background: var(--29d-coral-100); }
  c29-board .bd-item[data-color="sky"] { background: #DCEBF7; }
  c29-board .bd-item[data-color="lilac"] { background: #E7E1F5; }
  [data-theme="dark"] c29-board .bd-item { background: var(--raised); color: var(--ink); }
  [data-theme="dark"] c29-board .bd-item[data-color="sun"] { background: #4A3A1E; }
  [data-theme="dark"] c29-board .bd-item[data-color="sea"] { background: #173F39; }
  [data-theme="dark"] c29-board .bd-item[data-color="coral"] { background: #4E2A1E; }
  [data-theme="dark"] c29-board .bd-item[data-color="sky"] { background: #1E3448; }
  [data-theme="dark"] c29-board .bd-item[data-color="lilac"] { background: #322A4E; }
  c29-board .bd-check { margin-right: 6px; }
  c29-board.is-connect .bd-item { cursor: crosshair; }
  c29-board .bd-cursor { position: absolute; pointer-events: none; z-index: 5; transform: translate(-2px, -2px); }
  c29-board .bd-cursor-dot { display: block; width: 12px; height: 12px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); box-shadow: 0 0 0 1.5px #fff; }
  c29-board .bd-cursor-name { position: absolute; left: 12px; top: 10px; color: #fff; font: 650 11px/1 var(--font-ui); padding: 3px 7px; border-radius: 99px; white-space: nowrap; }
  c29-board .bd-tools { position: absolute; right: var(--s3); bottom: calc(var(--s3) + var(--safe-b, 0px)); display: flex; gap: var(--s2); z-index: 6; }
  c29-board .bd-tool { min-width: 44px; height: 44px; padding: 0 var(--s3); border: 0; border-radius: var(--r-pill);
    background: var(--surface); color: var(--ink); box-shadow: var(--hair), var(--lift); font: 700 17px/1 var(--font-ui); cursor: pointer; }
  c29-board .bd-fit { font-size: var(--fs-sm); }

  c29-list .ls-handle { flex: none; width: 40px; min-height: 44px; align-self: stretch; border: 0; background: transparent;
    color: var(--ink-mute); font-size: 17px; cursor: grab; touch-action: none; border-radius: 0; }
  c29-list .ls-handle:active { cursor: grabbing; }
  c29-list > li.is-dragging { box-shadow: var(--lift-hi); background: var(--raised); }
}

/* ============================================================
 * FLEET BOARD FAMILY — integrated 2026-10-20 (Fleet 1).
 * c29-band · c29-agent · c29-routines · c29-factory · c29-runtime
 * Contracts: FLEET-BOARD-CAMPAIGN-2026-10-20.md §2 (as amended
 * by Sacca Round 0, §6). Fragments follow verbatim.
 * ============================================================ */
/* ============================================================
 * TIDE — FLEET BOARD  <c29-band> + <c29-agent>
 * Fleet-Board campaign Round 1 · Lane A (2026-10-20), with the
 * Sacca Round 0 amendments to c29-band folded in (see the band
 * contracts below). The specialist band + the agent card: the
 * poster's bands of named bots, rebuilt as a tool — every count
 * derived, every state carried by dot + word + tone, stale
 * LOOKS stale, and a band with an error or a stale child says
 * so in its header instead of reading calm.
 *
 * INTEGRATION: append to tide.css verbatim (this fragment
 * carries its own @layer c29.components block, the way the
 * presence/board block already rides at the end of tide.css).
 * Tokens only: the only literals are c29-board's sky/lilac tone
 * grounds, reused verbatim (light + dark) for tone parity with
 * .bd-item[data-color]; every other ground is a token whose
 * dark value the token layer already flips.
 * ============================================================ */
@layer c29.components {

/* ---------- c29-band: the specialist band ----------
   Contracts:
   - the count is DERIVED: JS counts element children and
     mirrors data-count; there is no host `count` attribute,
     by design — a count that can drift from the cards under
     it is a lie.
   - data-attention is derived the same way (error + stale
     children) and mirrored; when it is > 0 the header renders
     it as a worded warn tag — the exception truth rides the
     header, never colour alone (c29-checklist law).
   - data-state precedence (Sacca Round 0): attention
     (≥1 error or derived-stale child) > live (≥1 child not
     stale/parked/error/off) > quiet (children, none live)
     > empty.
   - children render severity-first by construction — error,
     stale, working, idle, parked — re-sorted by the component
     for display; host DOM order is not the host's burden.
   - tint is a header wash in the tone ground + a hairline,
     never a filled panel competing with the card grounds.
     Coral is ABSENT from the tone list: money law (RS-F41). */
c29-band { display: block; min-width: 0; background: var(--surface);
  border-radius: var(--corner); box-shadow: var(--hair); overflow: hidden; }
c29-band .bn-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s3) var(--s4); background: var(--sand-50);
  box-shadow: inset 0 -1px 0 var(--edge-soft); }
c29-band[tone="sun"] .bn-head { background: var(--sun-100); }
c29-band[tone="sea"] .bn-head { background: var(--sea-100); }
c29-band[tone="sky"] .bn-head { background: #DCEBF7; }
c29-band[tone="lilac"] .bn-head { background: #E7E1F5; }
[data-theme="dark"] c29-band .bn-head { background: var(--raised); }
[data-theme="dark"] c29-band[tone="sun"] .bn-head { background: var(--warn-bg); }
[data-theme="dark"] c29-band[tone="sea"] .bn-head { background: var(--sea-100); }
[data-theme="dark"] c29-band[tone="sky"] .bn-head { background: #1E3448; }
[data-theme="dark"] c29-band[tone="lilac"] .bn-head { background: #322A4E; }
c29-band .bn-label { font: 700 var(--fs-lg)/1.25 var(--font-display);
  letter-spacing: var(--track-head); color: var(--ink);
  min-width: 0; overflow-wrap: anywhere; }
c29-band .bn-count { flex: none; }
c29-band .bn-attn { flex: none; }
c29-band .bn-attn[hidden] { display: none; }
c29-band .bn-meta { margin-left: auto; text-align: right;
  font-size: var(--fs-sm); color: var(--ink-mute);
  min-width: 0; overflow-wrap: anywhere; }
c29-band .bn-meta[hidden] { display: none; }
c29-band .bn-grid { display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr));
  gap: var(--s3); padding: var(--s4); }
c29-band .bn-grid[hidden] { display: none; }
c29-band .bn-grid > * { min-width: 0; }
c29-band .bn-empty { padding: var(--s4); font-size: var(--fs-sm); color: var(--ink-mute); }
c29-band .bn-empty[hidden] { display: none; }

/* ---------- c29-agent: the agent card ----------
   Contracts:
   - host-fed truth only: the component renders name, role,
     state and seen from attributes and invents nothing; no
     `seen` → no seen line, ever (never a fake "just now").
   - derived stale stamps ONLY over working/idle when `seen`
     is older than `stale-after` (default 3600s); a host-set
     stale/parked/error always wins.
   - state is carried by dot + WORD + tone together, never
     colour alone (c29-checklist law); error is the only
     bad-tone state; parked is calm — a neutral tag, no warn.
   - stale LOOKS stale: the avatar desaturates, the card drops
     its shadow for the flat surface ground, the name mutes.
   - names and roles wrap, never truncate
     (overflow-wrap:anywhere, c29-tile precedent).
   - the avatar is the house medallion (c29-presence
     language): initials on a token tone — identity colour,
     small-area only, never a state signal. Initials ride
     var(--ink-1), which flips correctly in both themes. */
c29-agent { display: flex; gap: var(--s3); align-items: flex-start;
  position: relative; min-width: 0; height: 100%;
  background: var(--raised); border-radius: var(--r-md);
  box-shadow: var(--hair); padding: var(--s3); }
c29-agent .ag-av { flex: none; width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--sand-50); color: var(--ink-1);
  font: 700 13px/1 var(--font-display); letter-spacing: .02em;
  box-shadow: inset 0 0 0 1px var(--edge-soft); }
c29-agent[tone="sun"] .ag-av { background: var(--sun-100); }
c29-agent[tone="sea"] .ag-av { background: var(--sea-100); }
c29-agent[tone="coral"] .ag-av { background: var(--29d-coral-100); }
c29-agent[tone="sky"] .ag-av { background: #DCEBF7; }
c29-agent[tone="lilac"] .ag-av { background: #E7E1F5; }
[data-theme="dark"] c29-agent[tone="sun"] .ag-av { background: var(--warn-bg); }
[data-theme="dark"] c29-agent[tone="sky"] .ag-av { background: #1E3448; }
[data-theme="dark"] c29-agent[tone="lilac"] .ag-av { background: #322A4E; }
c29-agent .ag-main { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; align-content: start; }
c29-agent .ag-name { font-size: var(--fs-body); font-weight: 600; line-height: 1.3;
  color: var(--ink); overflow-wrap: anywhere; }
c29-agent .ag-role { font-size: var(--fs-sm); line-height: 1.4;
  color: var(--ink-soft); overflow-wrap: anywhere; }
c29-agent .ag-role[hidden] { display: none; }
c29-agent .ag-state { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s1); }
c29-agent .ag-dot { flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink-mute); }
c29-agent .ag-word { font-size: var(--fs-cap); font-weight: 650;
  letter-spacing: var(--track-cap); text-transform: uppercase; color: var(--ink-soft); }
c29-agent[data-state="working"] .ag-dot { background: var(--accent); }
c29-agent[data-state="working"] .ag-word { color: var(--accent-text); }
c29-agent[data-state="stale"] .ag-dot { background: var(--warn); }
c29-agent[data-state="stale"] .ag-word { color: var(--warn-text); }
c29-agent[data-state="parked"] .ag-dot { background: var(--ink-mute); }
c29-agent[data-state="parked"] .ag-word { color: var(--ink-mute);
  background: var(--wash); padding: 2px 8px; border-radius: 99px; }
c29-agent[data-state="error"] .ag-dot { background: var(--bad); }
c29-agent[data-state="error"] .ag-word { color: var(--bad-text); }
c29-agent .ag-seen { font-size: var(--fs-cap); color: var(--ink-mute); overflow-wrap: anywhere; }
c29-agent .ag-seen[hidden] { display: none; }
/* Stale, worn on the card's face (contract above). */
c29-agent[data-state="stale"] { background: var(--surface); box-shadow: none; }
c29-agent[data-state="stale"] .ag-av { filter: saturate(.25); }
c29-agent[data-state="stale"] .ag-name { color: var(--ink-mute); }
/* The stretched link (c29-tile pattern): the whole card is
   the tap target; the open event, not the anchor, decides. */
c29-agent .ag-hit { position: absolute; inset: 0; z-index: 1; border-radius: var(--r-md); }
c29-agent .ag-hit:focus-visible { outline: none; box-shadow: var(--focus); }
}

/* ============================================================
 * TIDE — FLEET BOARD PATTERNS  <c29-routines> · <c29-factory>
 * Fleet 1 · Lane B fragment (Fleet-Board campaign, 2026-10-20).
 * INTEGRATION: append inside @layer c29.components in tide.css,
 * after the c29-list block. Pair with routines-factory.js (a
 * tide.js section) + the two entries in registry.json.
 *
 * Both components are host-fed truth only (NO-FABRICATION):
 * the host writes the rows, the component renders them. Neither
 * component invents a routine, a task, an owner, or a status,
 * and neither carries a control — rows are display, not buttons.
 * Tokens only; both themes; layout holds with JS dead (the
 * heading is the one JS-rendered part, as on c29-sweep).
 * ============================================================ */

@layer c29.components {

/* ======================================================
   ROUTINES  <c29-routines>   (Fleet-Board P4: the routines rail)
   The honesty line IS the pattern: "Empty runs stay silent" —
   with the Round 0 amendment (Sacca): silence is an honesty
   rule for EMPTY SUCCESS only, never a hiding place for
   failure. A row may carry the host-fed outcome of its last
   run (li[data-result] = ok | failed | skipped | never; absent
   = not rendered, never defaulted to ok) and the moment it
   last completed (li[data-last], ISO/ms):
     ok      → silence: no tag, no badge, no noise
     skipped → silence: no tag
     failed  → ALWAYS the word "Failed" (.t-tag.is-bad) + the
               "last ran …" line — failure is never silent
     never   → the words "Never ran" (.t-tag.is-warn)
   The "last ran …" line is quiet, in the c29-jobrun elapsed
   style, computed from data-last and nothing else; no
   data-last = no line, never an invented age. Paused keeps
   its word ("Paused", a plain .t-tag) plus the mute — never
   grey alone (c29-checklist law). When any child is failed
   the rail's data-state reads `attention`, so a composition
   can surface it without scraping rows.
   Host shape:
     <c29-routines heading="Routines" note="Empty runs stay silent">
       <li data-when="Weekdays 7:30" data-result="ok"
           data-last="2026-10-20T07:30:00-07:00">Morning brief</li>
       <li data-when="Daily 21:00" data-state="off">Inbox sweep</li>
     </c29-routines>
   Host data-state = rows | empty | attention (derived when
   any child is failed); empty renders the `empty` line
   (default "No routines scheduled.").
   ====================================================== */
c29-routines { display: block; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: var(--s5); }
c29-routines > li { list-style: none; margin: 0; padding: 0; }
c29-routines > li::marker { content: none; }
c29-routines .ru-head { margin-bottom: var(--s2); }
c29-routines .ru-title { font: 700 var(--fs-lg)/1.2 var(--font-display); letter-spacing: var(--track-head); overflow-wrap: anywhere; }
c29-routines .ru-note { margin-top: var(--s1); font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-routines .ru-note:empty { display: none; }
/* The adopted row: chip + name block (+ outcome words when
   the host fed an outcome that must not stay silent). */
c29-routines .ru-row { display: flex; align-items: center; gap: var(--s3); min-height: 52px; padding: var(--s3) 0; }
c29-routines .ru-row + .ru-row { box-shadow: inset 0 1px 0 var(--edge-soft); }
c29-routines .ru-main { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
/* The "last ran …" line: quiet by design — the words and the
   tag carry the failure, this line only says when. */
c29-routines .ru-last { font-size: 13px; line-height: 1.4; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-routines .ru-tags { flex: none; display: flex; align-items: center; gap: var(--s2); }
c29-routines .ru-chip { flex: none; display: inline-flex; align-items: center; min-height: 26px; max-width: 48%;
  padding: 2px 10px; border-radius: var(--r-pill); background: var(--wash-2); color: var(--ink-soft);
  font-family: var(--font-mono); font-size: 12px; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
c29-routines .ru-name { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
c29-routines .ru-row[data-state="off"] .ru-name { color: var(--ink-mute); font-weight: 550; }
c29-routines .ru-row[data-state="off"] .ru-chip { color: var(--ink-mute); }
c29-routines .ru-paused { flex: none; }
/* JS-dead fallback (c29-checklist precedent): before adoption
   the schedule still paints as a chip from the attribute and a
   paused row still says Paused — the words are the host's own
   attribute values, never component inventions. */
c29-routines > li:not(.ru-row) { display: flex; align-items: center; gap: var(--s3); min-height: 52px; padding: var(--s3) 0;
  font-size: 15px; font-weight: 600; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
c29-routines > li:not(.ru-row) + li:not(.ru-row) { box-shadow: inset 0 1px 0 var(--edge-soft); }
c29-routines > li:not(.ru-row)::before { content: attr(data-when); flex: none; display: inline-flex; align-items: center; min-height: 26px; max-width: 48%;
  padding: 2px 10px; border-radius: var(--r-pill); background: var(--wash-2); color: var(--ink-soft);
  font-family: var(--font-mono); font-size: 12px; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
c29-routines > li[data-state="off"]:not(.ru-row) { color: var(--ink-mute); font-weight: 550; }
c29-routines > li[data-state="off"]:not(.ru-row)::before { color: var(--ink-mute); }
c29-routines > li[data-state="off"]:not(.ru-row)::after { content: "Paused"; flex: none; display: inline-flex; align-items: center; min-height: 24px;
  padding: 0 9px; border-radius: 99px; background: var(--wash); color: var(--ink-soft);
  font-family: var(--font-ui); font-size: 12px; font-weight: 600; white-space: nowrap; }
/* JS-dead outcome words: failure and never-ran are words even
   before adoption (the one ::after slot means paused+failed
   together shows the failure word — failure wins the slot;
   JS adoption renders both). The "last ran" line itself is
   JS-rendered: CSS cannot format an elapsed honestly. */
c29-routines > li[data-result="failed"]:not(.ru-row)::after { content: "Failed"; flex: none; display: inline-flex; align-items: center; min-height: 24px;
  padding: 0 9px; border-radius: 99px; background: var(--bad-bg); color: var(--bad-text);
  font-family: var(--font-ui); font-size: 12px; font-weight: 600; white-space: nowrap; }
c29-routines > li[data-result="never"]:not(.ru-row)::after { content: "Never ran"; flex: none; display: inline-flex; align-items: center; min-height: 24px;
  padding: 0 9px; border-radius: 99px; background: var(--warn-bg); color: var(--warn-text);
  font-family: var(--font-ui); font-size: 12px; font-weight: 600; white-space: nowrap; }
/* The empty line: one quiet sentence, no box, no apology panel. */
c29-routines .ru-empty { padding: var(--s3) 0 var(--s1); font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-routines[data-state="empty"] > li { display: none; }

/* ======================================================
   FACTORY  <c29-factory>   (Fleet-Board P8: the task table)
   Task ID / Owner / Status, host truth in a calm table. The
   steal inside the steal is Parked as a first-class status:
   parked is a DECISION, not a failure — it renders as a plain
   tag in the neutral words, and no warn tone ever comes near
   it. Status is carried by word + tone together, never colour
   alone (c29-checklist law):
     queued  → "Queued"  plain .t-tag
     running → "Running" .t-tag.is-accent
     parked  → "Parked"  plain .t-tag (calm)
     done    → "Done"    .t-tag.is-ok
     blocked → "Blocked" .t-tag.is-bad
     failed  → "Failed"  .t-tag.is-bad (same register as
               blocked, a different word: blocked waits,
               failed ran and died — never conflated)
   An unknown data-status renders as queued (c29-jobrun
   precedent) — the component never invents a sixth status.
   Host shape:
     <c29-factory heading="Bot Factory" max="3">
       <li data-task="SHIP-88" data-owner="June Park"
           data-status="done">Optional second line</li>
       …
     </c29-factory>
   The li text (optional) is a second line under the task id.
   The owner is a 24px medallion (initials derived the
   c29-presence way, on the accent-soft ground in accent-text)
   plus the owner's name — both are host truth from data-owner.
   Round 0 amendments (Sacca), all three:
   (1) data-status gains `failed` — bad tone, same register as
   blocked, never conflated with it: blocked = waiting on
   something, failed = it ran and died.
   (2) li[data-since] (ISO/ms, start of the current status)
   renders an elapsed line in the c29-jobrun style on running
   and blocked rows only ("running 2h 14m"); absent = no line,
   never an invented age.
   (3) Overflow honesty + severity-first order: rendered rows
   sort blocked, failed, running, queued, parked, done —
   display only (flex order; host DOM order never moves, and
   the sort holds with JS dead because it reads data-status).
   Past `max`, the hidden rows are the sorted tail, and the
   overflow line breaks out its exceptions — "+N more · B
   blocked · F failed", counts derived from the actual hidden
   children — in the bad register when B+F > 0 (blocked and
   failed both wear bad tone; the words carry it too). With
   no exceptions hidden it stays the plain "+N more tasks".
   Host data-state = rows | empty.
   ====================================================== */
c29-factory { display: flex; flex-direction: column; background: var(--surface); border-radius: var(--corner); box-shadow: var(--hair); padding: var(--s5); }
c29-factory > li { list-style: none; margin: 0; padding: 0; }
c29-factory > li::marker { content: none; }
c29-factory .fc-head { order: 1; margin-bottom: var(--s1); }
c29-factory .fc-title { font: 700 var(--fs-lg)/1.2 var(--font-display); letter-spacing: var(--track-head); overflow-wrap: anywhere; }
c29-factory .fc-note { margin-top: var(--s1); font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-factory .fc-note:empty { display: none; }
/* The column header: caps labels on the rows' own grid, so a
   label can never drift from the column it names. */
c29-factory .fc-cols { order: 2; display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr) auto; gap: var(--s3);
  margin-top: var(--s3); padding-bottom: var(--s2);
  font-size: var(--fs-cap); letter-spacing: var(--track-cap); text-transform: uppercase; font-weight: 650; color: var(--ink-mute); }
c29-factory .fc-cols span:last-child { text-align: right; }
c29-factory[data-state="empty"] .fc-cols { display: none; }
/* Severity-first render order (Round 0): flex order reads the
   host's data-status, so exceptions float and parked/done sink
   with the DOM untouched — and before JS adoption too. Queued
   (and any unknown status, which paints as queued) is the base
   rank. Every row carries its own top hairline, so separators
   stay honest in whatever order the rows land. */
c29-factory > li { order: 13; }
c29-factory > li[data-status="blocked"] { order: 10; }
c29-factory > li[data-status="failed"] { order: 11; }
c29-factory > li[data-status="running"] { order: 12; }
c29-factory > li[data-status="parked"] { order: 14; }
c29-factory > li[data-status="done"] { order: 15; }
/* The adopted row rides the header's grid exactly. */
c29-factory .fc-row { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr) auto; gap: var(--s3); align-items: center; min-width: 0; padding: var(--s3) 0; box-shadow: inset 0 1px 0 var(--edge-soft); }
c29-factory .fc-row.is-over { display: none; }
c29-factory .fc-main { min-width: 0; display: grid; gap: 1px; }
c29-factory .fc-task { font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 600; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
c29-factory .fc-sub { font-size: 13px; line-height: 1.4; color: var(--ink-mute); overflow-wrap: anywhere; }
/* The elapsed line (data-since, running/blocked only): the
   status word repeated small + the computed age — quiet, the
   tag above it carries the state. */
c29-factory .fc-elapsed { font-size: 13px; line-height: 1.4; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-factory .fc-owner { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
c29-factory .fc-med { flex: none; width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-text); font: 700 10px/1 var(--font-ui); letter-spacing: .02em; }
/* The owner name holds ONE line with an honest ellipsis
   (390px fix, 2026-10-20): the owner slot is ~53px of name at
   phone width, where overflow-wrap:anywhere stacked the name
   mid-word ("Alejand/ro", "Delgad/o") and even break-word
   shatters any name whose longest word beats the slot. A
   table cell follows canon's dense-row idiom instead
   (c29-inbox .ib-t, c29-player .pm-words): one line, the
   ellipsis says it is cut, the medallion's initials still
   name the owner, and the JS-dead floor below keeps the
   full name in text. The medallion is flex:none above and
   this cell never wraps, so the pair can never stack. */
c29-factory .fc-owner-name { min-width: 0; font-size: var(--fs-sm); font-weight: 600; line-height: 1.3; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
c29-factory .fc-status { flex: none; justify-self: end; }
/* The overflow line: counts derived from the real hidden
   children. When the tail hides an exception it says so in
   words and takes the bad register (is-alert) — a buried
   blocked row is the board's most important fact and never
   hides inside an unlabelled "+N". */
c29-factory .fc-more { order: 90; margin-top: var(--s1); padding-top: var(--s3); box-shadow: inset 0 1px 0 var(--edge-soft);
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-mute); }
c29-factory .fc-more.is-alert { margin-top: var(--s3); padding: var(--s3) var(--s4); border-radius: var(--r-md);
  background: var(--bad-bg); color: var(--bad-text); box-shadow: none; }
/* JS-dead fallback: before adoption the row still reads — the
   task id in mono from its attribute, then the li's own text,
   then owner · status in the host's verbatim attribute words.
   JS adoption replaces this stack with the grid above. */
c29-factory > li:not(.fc-row) { display: block; padding: var(--s3) 0; font-size: var(--fs-sm); line-height: 1.45; color: var(--ink-mute); overflow-wrap: anywhere; box-shadow: inset 0 1px 0 var(--edge-soft); }
c29-factory > li:not(.fc-row)::before { content: attr(data-task); display: block;
  font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 600; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
c29-factory > li:not(.fc-row)::after { content: attr(data-owner) " · " attr(data-status); display: block; margin-top: 1px; }
/* The empty line, same quiet sentence shape as the rail's. */
c29-factory .fc-empty { order: 91; padding: var(--s3) 0 var(--s1); font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-mute); overflow-wrap: anywhere; }
c29-factory[data-state="empty"] > li { display: none; }

}

/* ============================================================
 * TIDE — <c29-runtime> the runtime bar (Fleet 1 · Lane C,
 * as amended by the Sacca Round 0 verdict, 2026-10-20).
 * Campaign contracts: FLEET-BOARD-CAMPAIGN-2026-10-20.md §2
 * + staging/sacca-round0.md §c29-runtime.
 *
 * One machine, named, with what runs on it — and an honest
 * state. The bar has THREE faces, distinct at a glance:
 *   online   lit: chips on the accent tint, no state word
 *   unknown  un-lit but un-muted: chips fall back to the
 *            neutral wash, the word "Unknown" shows — this
 *            is also the DEFAULT face, because a bar nobody
 *            has fed must not declare the machine online
 *            (default-online was fabrication by default)
 *   offline  muted: the whole bar at .55, chips greyed, the
 *            word "Offline" shows — a dimmer alone is never
 *            the state, the word is
 * Light DOM; tokens only; both themes; no autoplay. No
 * number appears here that the host did not supply: no
 * invented uptime, load, or counts — the one computed thing
 * is the age of the host's own `seen` heartbeat.
 * INTEGRATION: append inside @layer c29.components in tide.css.
 * ============================================================ */

@layer c29.components {

c29-runtime { display: grid; gap: var(--s3); grid-template-columns: minmax(0, 1fr);
  background: var(--raised); border-radius: var(--corner); box-shadow: var(--hair);
  padding: var(--s5); text-align: left; min-width: 0;
  transition: opacity var(--d3) var(--ease); }
c29-runtime[data-state="offline"] { opacity: .55; }
c29-runtime[data-state="offline"] .rt-chip { filter: grayscale(.5); }
/* Unknown is un-lit, not muted: the chips lose the accent
   tint for the neutral wash, so the bar reads present but
   nobody is claiming the machine is alive. */
c29-runtime[data-state="unknown"] .rt-chip { background: var(--wash); }
c29-runtime .rt-top { display: flex; align-items: center; gap: var(--s2);
  flex-wrap: wrap; min-width: 0; }
c29-runtime .rt-name { font: 800 var(--fs-xl)/1.15 var(--font-display);
  letter-spacing: var(--track-head); overflow-wrap: anywhere; min-width: 0; }
c29-runtime .rt-status { margin-left: auto; }
/* The last-seen line: the host's heartbeat, aged by the
   component (c29-agent / c29-jobrun elapsed style). It is
   absent from layout entirely when the host fed no `seen` —
   no line, never a fake "just now". It stays visible when
   the bar derives to unknown: the age IS the evidence. */
c29-runtime .rt-seen { font-size: var(--fs-sm); line-height: 1.45; color: var(--ink-mute); }
c29-runtime .rt-seen[hidden] { display: none; }
/* Chip discipline (390px fix, 2026-10-20): the gap token is
   the only space two chips ever need — chips wrap to the next
   line as whole units, a chip never exceeds the bar, and its
   label/sub wrap at word boundaries (break-word: a break
   lands mid-word only when one word alone is wider than the
   chip). overflow-wrap:anywhere is gone from the chip: it
   collapsed the chip's min-content to a single character, so
   a squeezed chip shattered its text instead of wrapping. */
c29-runtime .rt-chips { display: flex; flex-wrap: wrap; gap: var(--s2); min-width: 0; }
c29-runtime .rt-chips:empty { display: none; }
c29-runtime .rt-chip { display: inline-grid; gap: 1px; padding: var(--s2) var(--s4);
  border-radius: var(--r-pill); background: var(--accent-soft); min-width: 0; max-width: 100%;
  transition: background var(--d2) var(--ease), filter var(--d2) var(--ease); }
c29-runtime .rt-chip-label { font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink); line-height: 1.3; overflow-wrap: break-word; }
c29-runtime .rt-chip-sub { font-size: var(--fs-cap); line-height: 1.35;
  color: var(--ink-soft); overflow-wrap: break-word; }

}

/* ============================================================
 * FLEET BOARD — A-1 / A-2 STYLES + JS-DEAD FALLBACKS (F-2)
 * Coordinator fixes, 2026-10-20, landing Sacca Round 1's
 * binding amendments (campaign §6): A-1 (c29-agent `unknown`
 * state), A-2 (detail / evidence lines), R-1 (the JS-dead
 * runtime fallback must not affirm "Online"), F-2 (the JS-dead
 * floor, revised after the overseer's screenshot read).
 *
 * The fallback rules are gated on :not(.tc) — the class every
 * component latches when its JS builds it — so each vanishes
 * the moment the real render lands. What CSS cannot know, it
 * does not fake: no counts, no derivations, no elapsed lines.
 * In particular there is NO [state="online"] word rule for
 * c29-runtime: with JS dead there is no stale derivation, so
 * there is no affirmation either (R-1). Offline (a host claim
 * of absence) and Unknown (the honest default) still paint.
 * ============================================================ */
@layer c29.components {
  /* A-2 lines + the A-1 unknown word (built cards). */
  c29-agent .ag-detail { font-size: var(--fs-body); line-height: 1.4;
    color: var(--ink-soft); overflow-wrap: anywhere; }
  c29-agent .ag-detail[hidden] { display: none; }
  c29-agent .ag-evidence { font-size: var(--fs-sm); line-height: 1.4;
    color: var(--ink-mute); overflow-wrap: anywhere; }
  c29-agent .ag-evidence[hidden] { display: none; }
  c29-agent[data-state="unknown"] .ag-word { color: var(--ink-mute); }

  /* JS-dead fallbacks (F-2). */
  c29-agent:not(.tc) { display: grid; gap: 2px; align-content: start; height: auto; }
  c29-agent:not(.tc)::before { content: attr(name); font-size: var(--fs-body); font-weight: 600;
    line-height: 1.3; overflow-wrap: anywhere; }
  c29-agent:not(.tc)::after { content: attr(role); font-size: var(--fs-sm); line-height: 1.4;
    color: var(--ink-soft); overflow-wrap: anywhere; }
  c29-agent:not(.tc)[state]::after { content: attr(role) " · " attr(state); }
  c29-agent:not(.tc)[seen]:not([state])::after { content: attr(role) " · idle"; }
  c29-agent:not(.tc):not([seen]):not([state])::after { content: attr(role) " · unknown"; }
  c29-band:not(.tc) { display: grid; gap: var(--s3); padding-bottom: var(--s4); }
  c29-band:not(.tc)::before { content: attr(label); display: block;
    padding: var(--s3) var(--s4) 0; font: 700 var(--fs-lg)/1.25 var(--font-display);
    letter-spacing: var(--track-head); color: var(--ink); }
  c29-band:not(.tc) > c29-agent { margin: 0 var(--s4); }
  c29-runtime:not(.tc)::before { content: attr(name); font: 800 var(--fs-xl)/1.15 var(--font-display);
    overflow-wrap: anywhere; }
  c29-runtime:not(.tc)::after { content: "Unknown"; font-size: var(--fs-sm); font-weight: 650; color: var(--ink-soft); }
  c29-runtime:not(.tc)[state="online"]::after { content: none; }
  c29-runtime:not(.tc)[state="offline"]::after { content: "Offline"; }
  c29-runtime:not(.tc)[state="offline"] { opacity: .55; filter: grayscale(.5); }
  c29-banner:not(.tc)::before { content: attr(title); display: block;
    font-weight: 650; font-size: 15px; line-height: 1.4; overflow-wrap: anywhere;
    margin-bottom: var(--s1); }
}

/* ============================================================
 * TIDE — <c29-rest> THE LUX REMINDER (Posture fleet Lane 3;
 * registered into canon 2026-10-20, Tide backlog item 1).
 * The break + eye-rest ritual surface. Contracts (registry):
 * never a modal; cadence read from the shared RestPrefs object
 * on every scheduling decision, never hard-coded; at most one
 * pending arrival; snooze backs off, never escalates; presence
 * defers by default (waiting pill / quiet dot / allow); exactly
 * one face per state; appearance is pure CSS (Platform Rule 1);
 * motion gate collapses arrival, orb, ring and dot-breathe;
 * text colour is always a measured token pair (tightest: dark
 * mute 4.68:1 on the raised night surface); every target pins
 * --tap. Tokens only: the only literals are the breath-orb
 * gradient stops and switch knob, carried verbatim from the
 * adversary-passed source (decorative paint, never text).
 * INTEGRATION: appended as its own @layer c29.components block
 * at the end of tide.css, the Fleet Board family pattern.
 * ============================================================ */
@layer c29.components {

/* The element owns its width: full width of its container, capped
   at --rest-w. (First build used 100vw for the cap — adversary A7
   caught it overflowing any container narrower than the window,
   e.g. a small desktop window or a split view. 100% of the
   container is the correct measure at every size.) */
c29-rest { display: block; --rest-w: 400px; width: 100%; max-width: var(--rest-w); }
c29-rest[hidden] { display: none !important; }

/* ---------- the arrival card ---------- */
c29-rest .rest-card {
  width: 100%;
  background: var(--sheen), var(--raised);
  border-radius: var(--corner);
  box-shadow: var(--lift-hi);
  padding: calc(var(--s6) + 4px);
  display: grid; gap: var(--s4);
  /* soft arrival: a gentle rise + fade on the house ease.
     Under the motion gate (tokens collapse --d*) this is instant. */
  animation: rest-rise var(--d5) var(--ease) both;
}
@keyframes rest-rise {
  from { opacity: 0; transform: translateY(14px) scale(.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
c29-rest .rest-kicker {
  font-size: var(--fs-cap); letter-spacing: var(--track-cap);
  text-transform: uppercase; font-weight: 650; color: var(--accent-text);
  display: flex; align-items: center; gap: 8px;
}
c29-rest .rest-kicker i { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; }
c29-rest .rest-title {
  font-family: var(--font-display); font-weight: 750;
  font-size: var(--fs-2xl); letter-spacing: var(--track-head); line-height: 1.15;
  text-wrap: balance; margin: 0;
}
c29-rest .rest-sub { color: var(--ink-soft); font-size: 15px; text-wrap: pretty; margin: 0; }
c29-rest .rest-source { color: var(--ink-mute); font-size: 13px; margin: 0; }

/* ---------- the ritual stage ---------- */
c29-rest .rest-stage { display: grid; justify-items: center; gap: var(--s3); padding: var(--s2) 0; }
c29-rest .rest-ring { position: relative; width: 132px; height: 132px; }
c29-rest .rest-ring svg { width: 132px; height: 132px; transform: rotate(-90deg); }
c29-rest .rest-ring .ring-track { fill: none; stroke: var(--wash-2); stroke-width: 5; }
c29-rest .rest-ring .ring-arc {
  fill: none; stroke: var(--accent); stroke-width: 5; stroke-linecap: round;
  stroke-dasharray: 327; stroke-dashoffset: 327;
  transition: stroke-dashoffset 1s linear;
}
c29-rest .rest-orb {
  position: absolute; inset: 26px; border-radius: 50%;
  background: radial-gradient(circle at 38% 32%,
    color-mix(in srgb, var(--sea-200) 90%, #fff), var(--sea-300) 72%);
  box-shadow: inset 0 0 0 1px var(--pane-edge), 0 10px 24px -12px rgba(31, 97, 88, .45);
  transform: scale(.82);
  transition: transform 4s var(--ease);
}
[data-theme="dark"] c29-rest .rest-orb {
  background: radial-gradient(circle at 38% 32%,
    color-mix(in srgb, var(--sea-200) 55%, #123a37), var(--sea-600) 78%);
}
c29-rest .rest-orb.is-in  { transform: scale(1); }
c29-rest .rest-orb.is-out { transform: scale(.86); transition-duration: 5s; }
c29-rest .rest-cue {
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-lg);
  text-align: center; min-height: 1.4em; margin: 0;
}
c29-rest .rest-count { color: var(--ink-mute); font-size: var(--fs-sm); margin: 0; font-variant-numeric: tabular-nums; }
c29-rest .rest-blinks { display: flex; gap: 7px; }
c29-rest .rest-blinks i {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--wash-2); transition: background var(--d3) var(--ease);
}
c29-rest .rest-blinks i.is-done { background: var(--accent); }

/* ---------- actions: every target ≥ 44px (--tap) ---------- */
c29-rest .rest-actions { display: flex; gap: var(--s2); flex-wrap: wrap; }
c29-rest .rest-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); min-width: var(--tap);
  padding: 0 calc(var(--s4) + 2px); border: 0; border-radius: var(--r-pill);
  background: var(--wash); color: var(--ink); box-shadow: none;
  font-weight: 600; font-size: 15px; cursor: pointer; white-space: nowrap;
  transition: transform var(--d1) var(--ease), background var(--d2) var(--ease);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
c29-rest .rest-btn:hover { background: var(--wash-2); }
c29-rest .rest-btn:active { transform: scale(.97); }
c29-rest .rest-btn.is-accent { background: var(--accent); color: var(--on-accent); flex: 1; }
c29-rest .rest-btn:focus-visible { outline: none; box-shadow: var(--focus); }
c29-rest .rest-btn[disabled] { opacity: .45; pointer-events: none; }

/* ---------- the waiting pill (quiet intelligence) ---------- */
c29-rest .rest-wait {
  display: inline-flex; align-items: center; gap: 9px;
  min-height: var(--tap); padding: 0 var(--s4);
  border-radius: var(--r-pill); background: var(--pane);
  backdrop-filter: blur(var(--pane-blur)) saturate(1.2);
  -webkit-backdrop-filter: blur(var(--pane-blur)) saturate(1.2);
  box-shadow: 0 0 0 1px var(--pane-edge), var(--lift);
  color: var(--ink-soft); font-size: 14px; font-weight: 550;
  animation: rest-rise var(--d4) var(--ease) both;
}
c29-rest .rest-wait .wait-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--sun-500); flex: none;
  animation: rest-breathe 4.5s ease-in-out infinite;
}
@keyframes rest-breathe { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
c29-rest .rest-wait button {
  min-height: var(--tap); min-width: var(--tap); border: 0; background: none;
  color: var(--accent-text); font-weight: 650; font-size: 14px; cursor: pointer;
  padding: 0 6px; border-radius: var(--r-pill);
}
c29-rest .rest-wait button:focus-visible { outline: none; box-shadow: var(--focus); }

/* ---------- the quiet dot (meetingSilence: "dot") ---------- */
c29-rest .rest-dot {
  width: var(--tap); height: var(--tap); border-radius: 50%;
  display: grid; place-items: center; border: 0; cursor: pointer;
  background: transparent; padding: 0;
}
c29-rest .rest-dot i { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); opacity: .75; }
c29-rest .rest-dot:focus-visible { outline: none; box-shadow: var(--focus); }

/* ---------- cadence + settings row (lux, small, in-card) ---------- */
c29-rest .rest-settings {
  border-top: 1px solid var(--edge-soft); padding-top: var(--s4);
  display: grid; gap: var(--s3);
}
c29-rest .rest-set-row { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
c29-rest .rest-set-label { font-size: 13px; font-weight: 600; color: var(--ink-soft); min-width: 92px; }
c29-rest .rest-seg {
  display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: 3px; border-radius: 99px; background: var(--wash);
}
c29-rest .rest-seg button {
  min-height: 38px; min-width: var(--tap); padding: 0 12px; border: 0; background: none;
  /* Unpressed labels ride --ink-soft, not --ink-mute: on the seg's
     --wash ground over the dark raised card, --ink-mute renders at
     4.02:1 (canon render pass, 2026-10-20) — under the 4.5 floor.
     --ink-soft holds 7.87:1 on the card pair and stays clear of
     the wash. The lux source used --ink-mute here; this is the
     one deliberate canon deviation (B4 in the lane report). */
  border-radius: 99px; font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
  cursor: pointer; white-space: nowrap;
}
c29-rest .rest-seg button[aria-pressed="true"] {
  color: var(--ink); background: var(--raised); box-shadow: var(--lift);
}
c29-rest .rest-seg button:focus-visible { outline: none; box-shadow: var(--focus); }
c29-rest .rest-switch {
  appearance: none; width: 50px; height: 30px; border-radius: 99px;
  background: var(--wash-2); position: relative; cursor: pointer; flex: none; margin: 0;
  transition: background var(--d2) var(--ease);
}
c29-rest .rest-switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 24px; height: 24px;
  border-radius: 50%; background: #fff; box-shadow: 0 2px 6px rgba(0,0,0,.18);
  transition: translate var(--d3) var(--ease-give);
}
c29-rest .rest-switch:checked { background: var(--accent); }
c29-rest .rest-switch:checked::after { translate: 20px 0; }
c29-rest .rest-switch:focus-visible { outline: none; box-shadow: var(--focus); }

/* ---------- motion gate: keyframe travel collapses ---------- */
html[data-motion="reduced"] c29-rest .rest-card,
html[data-motion="reduced"] c29-rest .rest-wait { animation: none; }
html[data-motion="reduced"] c29-rest .rest-wait .wait-dot { animation: none; opacity: .8; }
html[data-motion="reduced"] c29-rest .rest-orb { transition: none; }
html[data-motion="reduced"] c29-rest .rest-ring .ring-arc { transition: none; }

} /* @layer c29.components */

/* ============================================================
 * TIDE — CAPABILITY GATE (c29-capgate) + DEMO SHELL (c29-demoshell)
 * (Mirror fleet Lane 2, 2026-10-20). The JS lives in tide.js:
 * Tide.caps / Tide.capGate + the capture-phase [data-cap] gate,
 * and Tide.demoShell. This block is paint only: the gate works with
 * these bytes deleted (the toast still fires); the mark is how a
 * person can SEE a control is native before tapping it. Tokens
 * only; both themes ride the bar token pairs (--pane-strong
 * ground, --ink on it, --accent-text for the way home).
 * ============================================================ */
@layer c29.components {

/* ---------- c29-capgate: the native affordance ----------
   A tiny "native" pill riding the control's top-right corner.
   Absolutely positioned, so it never moves the control's layout;
   pointer-events none, so it never eats the tap it announces.
   Inputs are excluded (a switch's ::after is its knob); a gated
   input still gates — it just wears no pill. */
[data-cap] { position: relative; }
[data-cap]:not(input)::after {
  content: "native";
  position: absolute; top: -7px; right: -7px; z-index: 2;
  padding: 1px 6px 2px; border-radius: var(--r-pill);
  background: var(--ink); color: var(--bg);
  box-shadow: 0 0 0 2px var(--bg);
  font-size: 9.5px; font-weight: 700; line-height: 1.5;
  letter-spacing: .07em; text-transform: uppercase; white-space: nowrap;
  pointer-events: none;
}

/* ---------- c29-demoshell: universal demo chrome ----------
   The host's empty [data-demo-shell] div becomes this sticky bar
   when tide.js enhances it: back link, surface name, theme toggle.
   Sticky at top:0 with the safe-area inset folded into its own
   padding, exactly like the gallery's .g-bar; content scrolls
   under it, nothing is reserved or overlapped — the bar is in
   normal flow, so the page's first content starts below it. */
.c29-demoshell, [data-demo-shell] {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--s2);
  min-height: calc(var(--tap) + 16px);
  padding: calc(8px + var(--safe-t)) var(--s4) 8px;
  background: var(--pane-strong);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: 0 1px 0 var(--edge-soft);
}
.c29-demoshell .ds-back {
  display: inline-flex; align-items: center; flex: none;
  min-height: var(--tap); padding: 0 var(--s2) 0 0;
  color: var(--accent-text); font-weight: 650; font-size: 15px;
  text-decoration: none; white-space: nowrap; border-radius: var(--r-pill);
}
.c29-demoshell .ds-back:focus-visible { outline: none; box-shadow: var(--focus); }
.c29-demoshell .ds-title {
  font-family: var(--font-display); font-weight: 750; font-size: 17px;
  letter-spacing: var(--track-head); color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.c29-demoshell .ds-spacer { flex: 1; }
.c29-demoshell c29-theme { flex: none; }

} /* @layer c29.components (capgate + demoshell) */
