/* Design tokens — Track & Zone (dark default, auto light). Fonts load from base.html. */

/* TRACK & ZONE PRIMITIVES (dark) */
:root {
	--tz-ink-950: #0b0c0e;
	--tz-ink-875: #1c1f24;
	--tz-ink-900: #15171b;
	--tz-ink-850: #1f2227;
	--tz-ink-800: #262930;
	--tz-ink-700: #33373f;
	--tz-fog-400: #9aa0aa;
	--tz-fog-100: #e6e8eb;
	--tz-paper: #f3f3ef;
	--tz-volt-dark: #bfe03c;
	--tz-volt-light: #d4ff3f;
	--tz-gold: #c8b468;
	--tz-recovery-dark: #05675a;
	--tz-recovery-line-dark: #0fd4bb;
	--tz-easy-dark: #37458e;
	--tz-easy-line-dark: #8ea5fe;
	--tz-moderate-dark: #935f05;
	--tz-moderate-line-dark: #f5a939;
	--tz-high-dark: #8b3720;
	--tz-high-line-dark: #ec7859;
	--tz-strength-dark: #74326d;
	--tz-strength-line-dark: #da7fce;
	--e-deep-ink: #ffffff;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SEMANTIC TOKENS (dark)
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
	/* Surfaces */
	--surface: var(--tz-ink-950);
	--surface-raised: var(--tz-ink-900);
	--surface-raised-hover: var(--tz-ink-850);
	--surface-sunken: var(--tz-ink-950);
	--surface-overlay: var(--tz-ink-850);
	--surface-bubble: var(--tz-ink-800);
	--data-bar: var(--tz-ink-700);
	--pane: var(--surface-raised);
	--pane-ring: none;
	--chat-ground: var(--tz-ink-875);

	/* Text */
	--text: var(--tz-fog-100);
	--text-secondary: var(--tz-fog-400);
	--text-muted: var(--tz-fog-400);
	--text-faint: var(--tz-fog-400);
	--text-bright: var(--tz-fog-100);

	/* Borders */
	--border: var(--tz-ink-800);
	--border-strong: var(--tz-ink-700);

	/* Neutral interactive */
	--neutral: var(--tz-fog-400);

	--action: var(--tz-volt-dark);
	--action-ink: var(--tz-ink-950);
	--action-ring: none;
	--wash-ink: #ffffff;
	--moment: var(--tz-gold);
	--moment-ink: var(--tz-ink-950);
	--moment-glyph: var(--moment);
	--moment-text: var(--moment);
	--moment-wash: color-mix(in srgb, var(--moment) 10%, transparent);
	--selected: color-mix(in srgb, currentColor 14%, transparent);
	--danger: #b07878;
	--danger-text: var(--danger);
	--danger-faint: color-mix(in srgb, var(--danger) 20%, transparent);
	--danger-strong: #905050;
	--danger-strong-hover: #744040;
	--danger-ink: #ffffff;

	/* Shadow */
	--shadow: rgba(0, 0, 0, 0.5);

	--effort-recovery: var(--tz-recovery-dark);
	--effort-recovery-ink: #ffffff;
	--effort-recovery-line: var(--tz-recovery-line-dark);
	--effort-recovery-wash: var(--effort-recovery);
	--effort-recovery-strong: var(--effort-recovery-ink);
	--effort-easy: var(--tz-easy-dark);
	--effort-easy-ink: #ffffff;
	--effort-easy-line: var(--tz-easy-line-dark);
	--effort-easy-wash: var(--effort-easy);
	--effort-easy-strong: var(--effort-easy-ink);
	--effort-moderate: var(--tz-moderate-dark);
	--effort-moderate-ink: #ffffff;
	--effort-moderate-line: var(--tz-moderate-line-dark);
	--effort-moderate-wash: var(--effort-moderate);
	--effort-moderate-strong: var(--effort-moderate-ink);
	--effort-high: var(--tz-high-dark);
	--effort-high-ink: #ffffff;
	--effort-high-line: var(--tz-high-line-dark);
	--effort-high-wash: var(--effort-high);
	--effort-high-strong: var(--effort-high-ink);
	--effort-strength: var(--tz-strength-dark);
	--effort-strength-ink: #ffffff;
	--effort-strength-line: var(--tz-strength-line-dark);
	--effort-strength-wash: var(--effort-strength);
	--effort-strength-strong: var(--effort-strength-ink);

	color-scheme: dark;

	/* ── Font tokens ── */
	--font-display: "Big Shoulders Display", system-ui, sans-serif;
	--font-heading: "Big Shoulders Display", system-ui, sans-serif;
	--font-body: "Instrument Sans", system-ui, sans-serif;
	--font-coach: "Instrument Sans", system-ui, sans-serif;
	--font-mono: "IBM Plex Mono", ui-monospace, monospace;
	--font-input: "Instrument Sans", system-ui, sans-serif;

	/* Typography adjustments */
	--body-letter-spacing: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   LAYOUT / TYPOGRAPHY / MOTION TOKENS
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
	/* Layout */
	--content-max-width: 800px;

	/* Stacking: the sticky nav, and the one-column node sheet over it. */
	--z-nav: 100;
	--z-sheet: 110;

	--space-xs: 0.25rem;
	--space-sm: 0.5rem;
	--space-md: 1rem;
	--space-lg: 1.5rem;
	--space-xl: 2rem;
	--space-2xl: 3rem;
	--pane-pad-inline: 13px;

	/* ── Fluid horizontal insets (viewport-based, 320px→800px) ──
     Use for ALL horizontal padding and horizontal gaps.
     --space-* remain for vertical rhythm and margin-bottom.
     Slope unchanged from the original 360→800 design; floors lowered
     so iPhone-SE-class widths (320px) still resolve via the formula
     instead of clamping above the available gutter. */
	--inset-sm: clamp(0.25rem, 0.2rem + 0.8vw, 0.5rem);
	--inset: clamp(0.375rem, 0.1rem + 1.8vw, 1rem);
	--inset-lg: clamp(0.5rem, 0.1rem + 2.9vw, 1.5rem);

	--text-2xs: calc(12rem / 15);
	--text-xs: calc(13rem / 15);
	--text-sm: 0.9rem;
	--text-base: 1.0rem;
	--text-lg: 1.2rem;
	--text-xl: 1.6rem;
	--text-2xl: 2.0rem;
	--text-3xl: 2.5rem;

	--radius-xs: 0.125rem;
	--radius-sm: 0.375rem;
	--radius-md: 0.5rem;
	--radius-lg: 0.75rem;
	--radius-full: 9999px;
	--radius-hero: 28px;
	--radius-sheet: 26px;
	--radius-card: 16px;
	--radius-chip: 14px;
	--radius-pill: 999px;

	/* Focus ring thickness. */
	--rule-focus: 2px;

	--tracking-tight: -0.01em;
	--tracking-wide: 0.04em;
	--tracking-wider: 0.08em;

	--transition-fast: 150ms ease;
	--transition-normal: 300ms ease;
	--transition-elegant: 400ms cubic-bezier(0.16, 1, 0.3, 1);
	--transition-spring: 500ms cubic-bezier(0.34, 1.56, 0.64, 1);

	--motion-arrive: 300ms cubic-bezier(0.16, 1, 0.3, 1);
	--motion-zoom: 340ms;
	--motion-zoom-cut: 120ms;
	--motion-zoom-ease: cubic-bezier(0.16, 1, 0.3, 1);

	--shadow-lg: 0 10px 15px var(--shadow);

	--weight-normal: 400;
	--weight-medium: 500;
	--weight-semibold: 600;
	--weight-bold: 700;

	--overlay-bg: rgba(0, 0, 0, 0.75);

	/* P07 — Editorial Rhythm */
	--space-section: clamp(2rem, 8vh, 3rem);
}

/* ═══════════════════════════════════════════════════════════════════════════
   COACH LAYOUT — geometry
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
	/* ── Scheme-independent: layout + geometry ── */
	/* Chat prose stops at a readable measure however wide the column gets —
	   cards and stat grids still use the full width. */
	--coach-prose-measure: 72ch;
	--reading-measure: 56ch;
	--form-column: 440px;
	--form-wide: 640px;
	/* A workout or goal card in the chat: the wide sheet must not stretch a log line. */
	--chat-card-measure: 40rem;
	--coach-header-h: 63px;
	--tap-target: 44px;
	--ink-quiet: 70%;
	--composer-h: 50px;
	--chat-asymmetry: 3.6rem;
	/* The coach surface's own prose size, off the --text-* scale on purpose:
	   re-tuning it after the device pass must be ONE edit. iOS twin:
	   EmberText.chat. */
	--chat-prose-size: 16px;
}

body.coach-ledger {
	/* The mockup drew 1100px, which is the CHAT's comfortable measure, not the
	   page's: at 1100 the rail is squeezed to 300px (labels like
	   "Milestone: 10.0km 5:40/km" truncate) and a wide monitor is mostly
	   gutter. The shell takes the room when it exists; the prose keeps its own
	   measure below, so the extra width goes to the rail and the cards. */
	--content-max-width: 1400px;
}

body.coach-ledger {
	--coach-header-h: 56px;
	--screen-inset: 13px;
}

@media (min-width: 1000.02px) {
	body.coach-ledger {
		--screen-inset: 22px;
	}
}

body:has(#insights[data-intensity="recovery"]) { --ground: var(--effort-recovery-wash); }
body:has(#insights[data-intensity="easy"]) { --ground: var(--effort-easy-wash); }
body:has(#insights[data-intensity="moderate"]) { --ground: var(--effort-moderate-wash); }
body:has(#insights[data-intensity="high"]) { --ground: var(--effort-high-wash); }
body:has(#insights[data-intensity="strength"]) { --ground: var(--effort-strength-wash); }

[data-intensity="recovery"] {
	--e: var(--effort-recovery);
	--e-line: var(--effort-recovery-line);
	--e-wash: var(--effort-recovery-wash);
	--e-strong: var(--effort-recovery-strong);
	--e-deep: var(--tz-recovery-dark);
}

[data-intensity="easy"] {
	--e: var(--effort-easy);
	--e-line: var(--effort-easy-line);
	--e-wash: var(--effort-easy-wash);
	--e-strong: var(--effort-easy-strong);
	--e-deep: var(--tz-easy-dark);
}

[data-intensity="moderate"] {
	--e: var(--effort-moderate);
	--e-line: var(--effort-moderate-line);
	--e-wash: var(--effort-moderate-wash);
	--e-strong: var(--effort-moderate-strong);
	--e-deep: var(--tz-moderate-dark);
}

[data-intensity="high"] {
	--e: var(--effort-high);
	--e-line: var(--effort-high-line);
	--e-wash: var(--effort-high-wash);
	--e-strong: var(--effort-high-strong);
	--e-deep: var(--tz-high-dark);
}

[data-intensity="strength"] {
	--e: var(--effort-strength);
	--e-line: var(--effort-strength-line);
	--e-wash: var(--effort-strength-wash);
	--e-strong: var(--effort-strength-strong);
	--e-deep: var(--tz-strength-dark);
}

[data-intensity] {
	--pane: color-mix(in srgb, var(--tz-ink-950) 60%, var(--e));
	--e-tint: color-mix(in srgb, var(--e-line) 10%, transparent);
}

@media (prefers-color-scheme: light) {
	:root {
		color-scheme: light;
		--moment-wash: color-mix(in srgb, var(--moment) 8%, transparent);

		/* Surfaces */
		--surface: #f3f3ef;
		--surface-raised: #ffffff;
		--surface-raised-hover: #ecece7;
		--surface-sunken: #f3f3ef;
		--surface-overlay: #ffffff;
		--surface-bubble: #ffffff;
		--data-bar: #d2d2cc;
		--pane: var(--surface-raised);
		--pane-ring: inset 0 0 0 1px var(--border);
		--chat-ground: #e4e4de;

		/* Text */
		--text: #0c0d0f;
		--text-secondary: #5b5f66;
		--text-muted: #5b5f66;
		--text-faint: #5b5f66;
		--text-bright: #0c0d0f;

		/* Borders */
		--border: #dcdcd6;
		--border-strong: #d2d2cc;

		/* Neutral */
		--neutral: #5b5f66;

		--action: var(--tz-volt-light);
		--action-ink: #0c0d0f;
		--action-ring: inset 0 0 0 1px rgba(0, 0, 0, 0.14);
		--wash-ink: var(--text);
		--moment-ink: #0c0d0f;
		/* 3.8:1 on a pane (#ffffff), 3:1 on --chat-ground; --moment measures 2.1:1. */
		--moment-glyph: #958135;
		--moment-text: #8a6a10;
		--danger-text: #905050;

		/* Shadow */
		--shadow: rgba(120, 90, 50, 0.15);

		--effort-recovery: #037465;
		--effort-recovery-ink: #ffffff;
		--effort-recovery-line: #008171;
		--effort-recovery-strong: #066357;
		--effort-recovery-wash: color-mix(in srgb, var(--effort-recovery) 16%, var(--tz-paper));
		--effort-easy: #3f50ad;
		--effort-easy-ink: #ffffff;
		--effort-easy-line: #485ac0;
		--effort-easy-strong: #344298;
		--effort-easy-wash: color-mix(in srgb, var(--effort-easy) 16%, var(--tz-paper));
		--effort-moderate: #976205;
		--effort-moderate-ink: #ffffff;
		--effort-moderate-line: #a76c01;
		--effort-moderate-strong: #845502;
		--effort-moderate-wash: color-mix(in srgb, var(--effort-moderate) 16%, var(--tz-paper));
		--effort-high: #962905;
		--effort-high-ink: #ffffff;
		--effort-high-line: #a7320c;
		--effort-high-strong: #812001;
		--effort-high-wash: color-mix(in srgb, var(--effort-high) 16%, var(--tz-paper));
		--effort-strength: #8f3985;
		--effort-strength-ink: #ffffff;
		--effort-strength-line: #9e4194;
		--effort-strength-strong: #7c2e74;
		--effort-strength-wash: color-mix(in srgb, var(--effort-strength) 16%, var(--tz-paper));
	}

	[data-intensity] {
		--pane: var(--surface-raised);
	}
}

*,
*::before,
*::after {
	box-sizing: border-box;
}

/* `!important` so the hidden attribute beats author `display` rules
   (e.g. a `display: grid` on the same element would otherwise win). */
[hidden] {
	display: none !important;
}

html {
	/* Root font-size scales every rem-based token in tokens.css uniformly.
     14px (~87% of browser default) lands body text at the readability
     floor — fits more content per viewport on a 13" MacBook Air at
     100% zoom. Users who want larger can browser-zoom on top. */
	font-size: 15px;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

body {
	margin: 0;
	padding: 0;
	font-family: var(--font-body);
	background-color: var(--surface);
	color: var(--text);
	line-height: var(--body-line-height, 1.6);
	letter-spacing: var(--body-letter-spacing, 0);
	font-weight: var(--body-font-weight, 400);
	min-height: 100vh;
	transition:
		background-color 0.3s ease,
		color 0.3s ease;
	font-feature-settings:
		"liga" 1,
		"calt" 1;
	font-variant-numeric: tabular-nums;
}

.container {
	width: 100%;
	max-width: var(--content-max-width);
	margin: 0 auto;
	padding-inline: var(--inset-lg);
}

h1,
h2,
h3,
h4,
h5,
h6,
/* Title/heading classes that render visually as headings but use <div>/<span> tags.
   Consolidated here so swapping --font-heading (typography combo) cascades app-wide. */
.section-title,
.message-board-title {
	font-family: var(--font-heading);
	font-weight: var(--weight-semibold);
	line-height: 1.2;
	letter-spacing: var(--tracking-tight);
}

h1,
h2,
h3,
h4 {
	margin: 0 0 var(--space-md) 0;
	color: var(--text-bright);
	line-height: 1.3;
}

h1 {
	font-size: var(--text-3xl);
}

h2 {
	font-size: var(--text-2xl);
}

h3 {
	font-size: var(--text-xl);
}

h4 {
	font-size: var(--text-lg);
}

/* Heading sizes — all derive from the --text-* token scale in tokens.css
   so the app has a single typographic source of truth. */
.section-title {
	font-size: var(--text-lg);
}

.message-board-title {
	font-size: var(--text-base);
}

.empty-state-title {
	font-size: var(--text-xl);
}


p {
	margin: 0 0 var(--space-md) 0;
	color: var(--text-secondary);
}

.text-muted {
	color: var(--text-muted);
}

.text-danger {
	color: var(--danger-text);
}

a {
	font-family: inherit;
	color: var(--text);
	text-decoration: none;
	transition: color var(--transition-fast);
}

.section-title {
	font-size: var(--text-lg);
	color: var(--text-secondary);
	margin: 0;
}

.site-footer {
	margin-top: var(--space-lg);
	padding: var(--space-md) var(--inset-lg);
	border-top: 1px solid var(--surface-overlay);
}

.footer-content {
	max-width: var(--content-max-width);
	margin: 0 auto;
	text-align: center;
	display: flex;
	flex-direction: column;
	gap: var(--space-md);
}

.footer-quote {
	font-size: var(--text-xs);
	font-style: italic;
	color: var(--text-secondary);
	line-height: 1.6;
	max-width: var(--content-max-width);
	margin: 0 auto;
}

.footer-links {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: var(--space-sm) var(--space-md);
	flex-wrap: wrap;
}

.footer-links a {
	font-size: var(--text-sm);
	color: var(--text-muted);
	text-decoration: none;
}

.footer-links a:hover {
	color: var(--text-bright);
}

.footer-copy {
	font-size: var(--text-sm);
	color: var(--text-muted);
}

.footer-brand {
	display: inline;
}

.brand-wordmark {
	display: inline-block;
	font-family: var(--font-display);
	font-weight: var(--weight-semibold);
	font-size: var(--text-lg);
	line-height: 1;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: currentColor;
	vertical-align: middle;
}

/* Context-specific sizing */
.nav-brand .brand-wordmark {
	font-size: 20px;
}

.footer-brand .brand-wordmark {
	font-size: 1.1em;
}

.footer-alpha {
	text-transform: uppercase;
	font-size: var(--text-2xs);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-wider);
}

.footer-alpha-link {
	color: var(--text);
}

/* =============================================================================
   CARDS - Card components
   ============================================================================= */

.card {
	background: var(--pane);
	border: 0;
	box-shadow: var(--pane-ring);
	border-radius: var(--radius-card);
	padding: var(--space-lg) var(--inset-lg);
	margin-bottom: var(--space-lg);
	font-family: var(--font-body);
	transition: background-color 0.3s ease;
}


/* =============================================================================
   ACTION-PILL.CSS - Card-level inline action buttons

   Used on node detail panels and chat cards. Pill-shaped with optional
   primary and muted variants. Distinct from
   .chat-chip (the composer's state chips) — these are literal actions
   attached to a card.
   ============================================================================= */

.action-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.4em 0.9em;
  border-radius: var(--radius-full);
  border: 1px solid color-mix(in srgb, var(--text-muted) 25%, transparent);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  font-family: inherit;
  cursor: pointer;
  transition:
    color var(--transition-fast),
    background var(--transition-fast),
    border-color var(--transition-fast);
}

.action-pill:hover {
  background: color-mix(in srgb, var(--text-muted) 10%, transparent);
  color: var(--text);
  border-color: var(--text-secondary);
}

.action-pill:active {
  transform: scale(0.97);
}

.action-pill-icon {
  font-size: 1.05em;
  line-height: 1;
}

.action-pill--primary {
  border-color: var(--border-strong);
  color: var(--text-bright);
}

.action-pill--primary:hover {
  background: var(--surface-raised-hover);
  border-color: var(--text-secondary);
}

.action-pill--muted {
  color: var(--text-muted);
}

.action-pill--muted:hover {
  color: var(--text-secondary);
}

/* =============================================================================
   ALERTS - Alert, toast, and status banner components
   ============================================================================= */

.alert {
  padding: var(--space-md);
  background: var(--pane);
  box-shadow: var(--pane-ring);
  border-radius: var(--radius-card);
  font-size: var(--text-base);
}

.alert-caution {
  background: var(--surface-raised-hover);
  box-shadow: none;
  color: var(--text);
}

.alert-danger {
  background: var(--danger-faint);
  box-shadow: inset 0 0 0 1px var(--danger);
  color: var(--danger-text);
}

/* =============================================================================
   TOASTS - Notification popups
   ============================================================================= */

.toast-container {
  position: fixed;
  bottom: var(--space-lg);
  right: var(--space-md);
  left: var(--space-md);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  pointer-events: none;
}

.toast {
  padding: var(--space-md);
  border-radius: var(--radius-card);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
  animation: slideIn var(--transition-normal) ease-out;
  pointer-events: auto;
}

/* Auto-dismiss toasts fade out after 4 seconds */
.toast[data-auto-dismiss] {
  animation: slideIn var(--transition-normal) ease-out, toastFadeOut 0.3s ease-out 4s forwards;
}

@keyframes toastFadeOut {
  to { opacity: 0; transform: translateY(-10px); pointer-events: none; }
}

.toast-error {
  box-shadow: inset 0 0 0 1px var(--danger), var(--shadow-lg);
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateX(100%);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}


/* =============================================================================
   AVATAR.CSS - Initial-letter circle avatars

   Three sizes: default (sized per-context, no fixed width), sm (32px),
   md (48px). A neutral raised disc with bright text — never volt.
   ============================================================================= */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface-raised-hover);
  color: var(--text-bright);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  letter-spacing: -0.02em;
  text-transform: uppercase;
  flex-shrink: 0;
}

.avatar-sm {
  width: 32px;
  height: 32px;
  min-width: 32px;
  min-height: 32px;
  font-size: var(--text-sm);
}

/* =============================================================================
   BUTTONS - Button components including FAB
   ============================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-lg);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  line-height: normal;
  border: none;
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--border);
  cursor: pointer;
  transition: color var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast), opacity var(--transition-fast);
  text-decoration: none;
  white-space: nowrap;
  -webkit-appearance: none;
  appearance: none;
  background: var(--surface-raised);
  color: var(--text);
}

.btn:active:not(:disabled) {
  transform: scale(0.97);
}

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

.btn-danger {
  background: var(--danger-strong);
  box-shadow: none;
  color: var(--danger-ink);
}

.btn-danger:hover:not(:disabled) {
  background: var(--danger-strong-hover);
}

.btn-ghost {
  background: transparent;
  box-shadow: none;
  color: var(--text-secondary);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--surface-raised-hover);
  color: var(--text);
}

.btn-block {
  width: 100%;
}

.btn-sm {
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--text-sm);
}

/* Icon-only button variant */
.btn-icon {
  padding: var(--space-sm);
  min-width: 2.5rem;
  min-height: 2.5rem;
  font-size: var(--text-lg);
  line-height: 1;
}

.btn-icon.btn-sm {
  min-width: 2rem;
  min-height: 2rem;
}

/* =============================================================================
   ACTION PILLS — these also style the empty-state CTA.
   ============================================================================= */

.btn-primary {
  padding: 9px 20px;
  background: var(--action);
  box-shadow: var(--action-ring);
  color: var(--action-ink);
  font-size: 15px;
  font-weight: var(--weight-semibold);
}

.btn-primary:disabled:not(.htmx-request *, .htmx-request) {
  background: var(--surface-raised-hover);
  box-shadow: none;
  color: var(--text-muted);
}

.btn-primary:disabled {
  opacity: 1;
}

.btn-outline {
  padding: 9px 20px;
  border: 0;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--border-strong);
  color: var(--text);
  font-size: 15px;
  font-weight: var(--weight-semibold);
}

.btn-outline:hover:not(:disabled) {
  background: var(--surface-raised-hover);
  color: var(--text-bright);
}

@media (max-width: 760px) {
  .btn,
  .btn-icon.btn-sm {
    min-height: 44px;
  }
}

/* =============================================================================
   CARD-SECTIONS.CSS — the card vocabulary's section blocks

   Chrome (border, radius, padding) comes from .chat-card; this file styles
   only what lives INSIDE a card. One block per section kind, plus the
   fallback core every card degrades to. Sections stack vertically —
   horizontal layout never escapes the kind that owns it. Phase 0 is
   motion-free: motion is defined per surface later.
   ============================================================================= */

/* `card` marks a CARD BODY: the element whose direct children are sections.
   render_card emits it, and the two wrappers that render a bare section list
   (journey node detail, standalone chat card) declare it too, so
   section rhythm has ONE owner instead of each wrapper's incidental gap.
   base.css owns a legacy `.card` block (own surface + bottom margin) — inside
   the vocabulary the wrapper's own chrome is authoritative. */
.chat-card.card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 0;
}

/* chat-cards.css tightens the journey detail to --space-xs — a value tuned when
   the card held a poster plus its pills. The body's own rhythm is the one
   owner, at every surface it renders on. */
.timeline-detail > .chat-card.card {
  gap: 10px;
}

/* A labelled prose/list/segments/route after the first section OPENS A
   GROUP: a hairline rule with 18 above (the 10 gap plus 8) and 16 below — the
   same split iOS draws (CardSectionListView's groupLabel). The label is
   emitted only when it is non-empty, so `:has(> .card-section__label)` already
   reads "has a real label". A TONED prose is excluded: `_prose.html` puts
   `card-prose--info/--caution/--success` on the section element itself, and
   those carry their own border and padding — a callout is already a boundary,
   and the group rule would replace the box it draws. */
.chat-card.card
  > .card-section
  + :is(.card-section--prose, .card-section--list, .card-section--segments, .card-section--route):not(
      .card-prose--info,
      .card-prose--caution,
      .card-prose--success
    ):has(> .card-section__label) {
  margin-top: 8px;
  padding-top: 16px;
  border-top: 1px solid var(--card-rule);
}

.chat-card.card[data-variant="poster"]
  > .card-section
  + :is(.card-section--prose, .card-section--list, .card-section--segments, .card-section--route):not(
      .card-prose--info,
      .card-prose--caution,
      .card-prose--success
    ):has(> .card-section__label) {
  margin-inline: var(--poster-inset);
  padding-inline: 0;
}

/* .card-actions predates the card body and spaces itself; inside one the gap
   already separates it, so its own top margin would double it. */
.card > .card-actions {
  margin-top: 0;
}

/* Server-composed copy is unbounded: long tokens wrap instead of widening
   the card. */
.card-section,
.card-fallback {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* ─── Fallback core ─── */

.card-fallback {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* The heading is the control when the fallback carries an action. */
.card-fallback__action {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.card-fallback__action:hover {
  color: var(--card-accent-text);
}

/* ─── header ─── */

.card-section--header {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.card-eyebrow {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-md);
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--card-accent-text);
}

/* `kind` is an OPEN styling hint (http/api/card.rs): a segment whose kind has
   no rule here simply renders as plain eyebrow text — the documented
   degradation. `was` is the one kind that styles itself: the title this
   session replaced, struck out and stepped back. */
.card-eyebrow__seg--was {
  text-decoration: line-through;
  color: var(--text-faint);
}

/* `tag` closes the stamped line with the session's own word ("tempo"), so it
   steps back from the accent the dated segments carry. */
.card-eyebrow__seg--tag {
  color: var(--text-muted);
}

.card-heading {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  font-size: 28px;
  line-height: 1.05;
  letter-spacing: 0.01em;
  color: var(--text-bright);
}

.card-heading__icon {
  width: 24px;
  height: 24px;
  color: var(--card-accent-text);
  align-self: flex-start;
  margin-top: calc((1lh - 24px) / 2);
}

/* A subtitle is a date range — data, not voice. The coach register belongs to
   .card-prose; size and colour carry the distinction here. */
.card-subtitle {
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--text-muted);
}

/* ─── stat_row ─── */

/* Both rules live on the CELLS, not here: at a narrow column the row wraps, and
   a section-level top rule leaves line 2 unruled while a right-hand divider on
   the last cell of line 1 runs into empty space. `overflow: hidden` is what
   clips the divider each row's first cell hangs at -1px. */
.card-section--stat_row {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 0;
  margin: 0;
  overflow: hidden;
}

.card-section--stat_row:has(> .card-stat:nth-child(4):last-child) {
  --stat-basis: max(5.5rem, min(40%, calc((5.5rem - 25%) * 999)));
}

/* column-reverse: the value reads above its label, dt still precedes dd.
   A real basis, not `auto`: flex wraps on the CONTENT width before it shrinks
   anything, so at max-content the stats page's three labels break the row onto
   two lines. 5.5rem is the narrowest legible cell — below it the row wraps,
   which is what flex-wrap is for.

   NO `min-width: 0` — the flex incantation is wrong here. The value is
   `white-space: nowrap`, so a cell floored at zero is a cell that overflows:
   a pace RANGE ("4:50–5:05 /km") is twice any other reading, and on a phone it
   either ran over its neighbour or was eaten by the row's `overflow: hidden`
   (live defect, 2026-09-19). The automatic minimum IS the content, which is
   what makes the wrap above fire instead. */
.card-stat {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 2px;
  flex: 1 1 var(--stat-basis, 5.5rem);
  padding: 10px 6px 4px;
}

.card-stat__value {
  margin: 0;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  font-size: var(--text-xl);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  color: var(--text);
}

.card-stat--prose .card-stat__value {
  white-space: normal;
}

.card-stat__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* A second run after the value, never part of it — the wire splits the unit
   once, server-side, and no renderer re-parses the value to find it. */
.card-stat__unit {
  font-size: 12px;
  font-weight: var(--weight-medium);
  color: var(--text-muted);
  letter-spacing: 0;
}

/* Emphasis tints the whole reading, unit included (RULE C). */
.card-stat--emphasis .card-stat__unit,
.card-feel__target--emphasis .card-stat__unit {
  color: inherit;
}

.card-stat--emphasis .card-stat__value {
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  color: var(--card-emphasis, var(--card-accent-text));
}

/* The tint goes on the arrow, never on the value (DESIGN §1.2) — a trend with
   no tone stays neutral, same muted ink the stat label already uses. */
.card-stat__trend {
  position: relative;
  bottom: 0.8em;
  margin-left: 2px;
  font-size: 0.5em;
  color: var(--text-muted);
}

.card-stat__trend--good {
  color: var(--text-bright);
}

.card-stat__trend--neutral {
  color: var(--text-faint);
}

/* ─── meter (pure CSS: no JS, no chart library) ─── */

.card-section--meter {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.card-section--meter:has(> .card-meter__display) {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
}

.card-section--meter > .card-meter__caption {
  grid-column: 1 / -1;
}

.card-meter--bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.card-meter__track {
  height: var(--space-sm);
  border-radius: var(--radius-full);
  background: var(--data-bar);
  overflow: hidden;
}

/* width comes from the normalized point, inline. */
.card-meter__bar {
  height: 100%;
  border-radius: inherit;
  background: var(--card-emphasis, var(--text-bright));
}

.card-meter--spark {
  display: flex;
  align-items: flex-end;
  gap: var(--space-xs);
  height: var(--space-xl);
}

.card-meter__spark {
  flex: 1 1 0;
  min-height: var(--radius-xs);
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
  background: var(--card-emphasis, var(--text-bright));
}

/* --card-meter-fill is the inline sweep angle; the mask punches the centre so
   the ring reads on any card tint. */
.card-meter--ring {
  --card-meter-fill: 0deg;
  width: var(--space-2xl);
  height: var(--space-2xl);
  border-radius: var(--radius-full);
  background: conic-gradient(
    var(--card-emphasis, var(--text-bright)) var(--card-meter-fill),
    var(--data-bar) 0
  );
  -webkit-mask: radial-gradient(closest-side, transparent 64%, black 65%);
  mask: radial-gradient(closest-side, transparent 64%, black 65%);
}

/* The formatted value ("48 / 60 km") sits in normal flow after the shape, at
   full size, for every style. It used to overlay the ring — a 48px hole is
   not a text box, and the value wrapped onto its own stroke. */
.card-meter__display {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text);
}

/* Up to three segment labels under the bars (SPEC §4.2), spread across the
   full width — `justify-content` alone does the spacing so two labels sit at
   the ends and three land with one centred, no gap needed. */
.card-meter__labels {
  display: flex;
  justify-content: space-between;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ─── prose ─── */

/* Prose is the coach talking — same register as .scribble-text and iOS's
   EmberFont.coach. Italic stays reserved for *em* inside the markdown. */
/* The coach register: same family and size as the chat scribble
   (`chat-marginalia.css` `.scribble-text`) — a card is the same voice as the
   stream, not a denser one. The subtitle above is deliberately NOT this: a
   date range is data. */
.card-prose {
  font-family: var(--font-coach);
  /* The scribble's own size, so a card in the stream cannot end up a size apart
     from the reply above it. iOS says the same thing structurally — its card
     prose renders through the very `coachAttributed` facility the scribble
     uses, so it cannot drift at all. */
  font-size: var(--chat-prose-size);
  /* Tighter than the stream: a card's prose sits between ruled groups rather
     than running on. */
  line-height: 1.5;
  color: var(--text);
}

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

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

.card-prose p {
  margin: 0 0 var(--space-sm);
  color: inherit;
}

.card-prose ul,
.card-prose ol {
  margin: 0 0 var(--space-sm);
  padding-left: var(--space-lg);
}

.card-prose strong {
  font-weight: var(--weight-semibold);
  color: var(--text-bright);
}

.card-prose code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* A coach callout: a hairline + inset, never a left bar. A caution takes the
   stronger hairline so a warning never reads like an on-track note. */
.card-prose--info,
.card-prose--caution,
.card-prose--success {
  border: 1px solid;
  border-radius: var(--radius-sm);
  padding: var(--space-sm);
}

.card-prose--info,
.card-prose--success {
  border-color: var(--border);
}

.card-prose--caution {
  border-color: var(--text-muted);
}

/* Optional micro-label above prose/list/segments blocks (detail surfaces); an <li> in list/segments contexts. */
.card-section__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  list-style: none;
}

/* Anything carrying an entity taps through to the journey node detail — a
   whole card, or a single list row inside one. Mirrors streaming.js's
   delegate selector exactly, so the cursor never disagrees with what is
   actually clickable. Interactive children keep their own cursors via their
   own rules. */
[data-open-node] {
  cursor: pointer;
}

/* A stub card taps too: its fill, not navigation. Keyed on the attribute
   autoFillChatStub removes the moment a fill starts, so the cursor heals itself. */
.chat-card[data-detail-pending="true"] {
  cursor: pointer;
}

/* role="button" tabindex="0" (card.html): a keyboard runner landing here is
   told nothing without a ring. */
.chat-card[data-detail-pending="true"]:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* ─── list ─── */

.card-section--list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
}

.card-list__row {
  display: flex;
  color: var(--text);
}

/* The row stays a listitem; its tap target is this child, stretched to the
   whole row so the cursor and the focus ring cover it. */
.card-list__hook {
  display: flex;
  flex: 1;
}

/* Numbered steps: the count steps down in size only — its ink is the row's,
   --card-accent or whatever status the row carries. */
.card-list__glyph {
  flex: none;
  min-width: 1.5em;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--card-accent-text);
}

/* Reserved per LIST (`ListData::any_kicker`), like the glyph beside it: the day
   stamp is a column, so a row without one still holds its cell. */
.card-list__kicker {
  flex: 0 0 var(--space-2xl);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

/* `align-self: center`, not baseline: an <svg> has no baseline, so a
   flex item wrapping one contributes its BOTTOM edge instead — parking a 20px
   box's bottom on the text baseline and leaving the mark's mid-box ink floating
   ~6px above the label. `vertical-align` cannot fix it; it does not apply to
   flex items. */
.card-list__icon {
  width: 20px;
  height: 20px;
  align-self: center;
  color: var(--card-accent-text);
}

.card-list__value {
  flex: none;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* Shared column for label + its optional second line, so .card-list__value
   keeps reading from the right and the glyph/icon columns stay untouched. A
   row with no detail renders byte-identically to before this wrapper. */
.card-list__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.card-list__detail {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* Done is not struck out: the session HAPPENED, and a strike reads as
   cancelled. Full ink (the row's own) plus a check, the way a log is ticked
   off. The check is presentation, alt text "" — the class carries the state. */
.card-list__row--done .card-list__label::after {
  content: "✓" / "";
  margin-left: 6px;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text);
}

.card-list__row--done .card-list__glyph,
.card-list__row--done .card-list__icon {
  color: var(--text);
}

.card-list__row--active .card-list__label {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-weight: var(--weight-medium);
  color: var(--text-bright);
}

.card-list__row--active .card-list__glyph,
.card-list__row--active .card-list__icon {
  color: var(--text-bright);
}

/* Announced, unlike the "·" and "✓": nothing else on the row says which
   session is next. iOS reads its own word from copy — PLAN open question 4
   holds the trade-off. */
.card-list__row--active .card-list__label::after {
  content: "next";
  flex: none;
  padding: 0 var(--space-sm);
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--border-strong);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-normal);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-bright);
}

.card-list__row--pending .card-list__label,
.card-list__row--pending .card-list__kicker,
.card-list__row--pending .card-list__value,
.card-list__row--pending .card-list__glyph,
.card-list__row--pending .card-list__icon {
  color: var(--text-faint);
}

/* A session that came and went. */
.card-list__row--missed .card-list__label,
.card-list__row--missed .card-list__glyph {
  color: var(--danger-text);
}

.card-list__row--missed .card-list__label {
  text-decoration: line-through;
  text-decoration-color: var(--border);
}

.card-list__row--missed .card-list__icon {
  color: var(--danger);
}

/* A placeholder session: the coach has claimed the day but not written it
   yet, so every metric on the row is an estimate. */
.card-list__row--stub {
  opacity: 0.72;
}

/* ─── chip_row (chips are .chat-chip: quoted utterances, never pills) ─── */

.card-section--chip_row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* Navigate/open_url chips are anchors; they must read exactly like the button
   chips beside them. */
.card-section--chip_row a.chat-chip {
  text-decoration: none;
}

/* On the detail poster the chip is METADATA with an edit affordance (the shoe
   line), not an utterance — it joins the poster's mono metadata family instead
   of wearing the composer follow-up pill `.chat-chip` would give it. The
   dotted underline is the whole affordance. Three classes: it outranks
   `.chat-chip` and its :hover (chat-marginalia.css, later in the bundle). */
.timeline-detail .card-section--chip_row .chat-chip {
  position: relative;
  padding: 0;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-style: normal;
  font-weight: var(--weight-medium);
  color: var(--text-muted);
  text-decoration: underline dotted;
  text-decoration-color: color-mix(in srgb, var(--text-muted) 55%, transparent);
  text-underline-offset: 4px;
}

/* With the pill's padding gone, ::after is the whole 44px hit area (the same
   invisible overlay `.chat-chip` arms on mobile, here at every width because
   the bare line box is ~15px tall everywhere). */
.timeline-detail .card-section--chip_row .chat-chip::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: var(--tap-target);
  transform: translateY(-50%);
}

.timeline-detail .card-section--chip_row .chat-chip:hover {
  color: var(--text-bright);
  /* The dotted underline turns solid, in the link ink. */
  text-decoration-line: underline;
  text-decoration-style: solid;
  text-decoration-color: currentColor;
}

/* ─── cta_row (rows of quiet text, never buttons — this is not an app about
   buttons; the CTAs answer in the card's own register, like the chips above
   them, and the only chrome is a hairline BETWEEN two answers) ─── */

.card-section--cta_row {
  display: flex;
  flex-direction: column;
}

/* The whole row is the target, so the 44px floor and the full width live on
   the macro's element — there is no wrapper to hang them on. */
.card-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: var(--tap-target);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  transform: none;
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* The cue that this line answers. Presentational (alt text ""), and it never
   moves on hover — the underline is the whole of that feedback. */
.card-cta::after {
  content: "→" / "";
  font-family: var(--font-mono);
  font-size: 14px;
}

/* Between rows only: a rule under the last answer would read as a box side. */
.card-cta + .card-cta {
  border-top: 1px solid var(--border);
}

/* The ink IS the hierarchy: the primary answer is the brightest text. */
.card-cta--primary {
  color: var(--text-bright);
}

.card-cta--secondary {
  color: var(--text-secondary);
}

/* The quieter answer's cue is quieter still; the label carries the weight. */
.card-cta--secondary::after {
  color: var(--text-faint);
}

/* The cue inherits: a danger answer's arrow is the same red as its label. */
.card-cta--danger {
  color: var(--danger-text);
}

/* Underline, never a fill — a fill is the pill coming back. The tint survives
   the hover, so a danger answer stays red while it is being pressed. */
.card-cta:hover,
.card-cta:focus-visible,
.card-cta:active {
  text-decoration: underline;
  text-underline-offset: 4px;
}

.card-cta:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ─── segments ─── */

/* One column: a label is the section's eyebrow on its own line, the rows
   full-width below it. */
.card-section--segments {
  display: grid;
  grid-template-columns: 1fr;
  row-gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
}

.card-segment {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 10px;
}

.card-segment__type {
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* All three trailing runs are a reading that never shrinks. */
.card-segment__pace,
.card-segment__hr,
.card-segment__meta {
  flex: none;
}

/* ─── feel ─── */

.card-section--feel {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-lg);
  margin: 0;
}

.card-feel__target {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  min-width: 0;
}

/* Whole words: a sentence-long value beside it wraps instead of breaking
   "TARGET HR" at any character. */
.card-feel__label {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.card-feel__value {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text);
}

.card-feel__target--emphasis .card-feel__value {
  font-weight: var(--weight-medium);
  color: var(--card-emphasis, var(--card-accent-text));
}

/* ─── delta ─── */

/* One mono line per metric, read left to right: LABEL planned → actual, and
   the difference falls out at the right margin. */
.card-section--delta {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
}

.card-delta__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
}

/* A fixed column, not the slack: the planned figures have to stack. */
.card-delta__label {
  flex: 0 0 76px;
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Shrinkable: the label is a fixed column, so in a narrow poster column the
   figures are the only thing left that can give way. */
.card-delta__cell {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 12px;
}

.card-delta__cell--planned {
  color: var(--text-faint);
}

.card-delta__arrow {
  color: var(--text-faint);
}

.card-delta__cell--actual {
  color: var(--text-bright);
}

/* Pushed to the right margin (the label is a fixed column now, so nothing else
   takes the slack). Direction is not wire-encoded, so the delta is set apart by
   weight, never by a tint that would read as a verdict (iOS matches). */
.card-delta__cell--delta {
  margin-left: auto;
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
}

/* Words, not figures: the row stacks, planned over actual, in body type. */
.card-delta__row--prose {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.card-delta__row--prose > .card-delta__label {
  flex: none;
}

.card-delta__row--prose > .card-delta__arrow {
  display: none;
}

.card-delta__row--prose > :is(.card-delta__cell--planned, .card-delta__cell--actual) {
  font-family: var(--font-body);
  font-size: var(--text-sm);
}

.card-delta__row--prose > .card-delta__cell--delta {
  margin-left: 0;
}

/* ─── chart (server-rendered SVG: no JS, no chart library, no motion) ─────────

   The plot is a 0..100 × 0..100 viewBox stretched into this box, so nothing
   inside it may be a circle or a scaling stroke: bars are fills, the line
   carries vector-effect. Labels live OUTSIDE the SVG in grid rows that share
   the plot's slots, which is why they keep the page's type scale.
   ------------------------------------------------------------------------- */

.card-section--chart {
  --card-chart-slots: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
}

.card-chart__values,
.card-chart__labels {
  display: grid;
  grid-template-columns: repeat(var(--card-chart-slots), minmax(0, 1fr));
  align-items: end;
}

/* Held open whether or not the slots kept a display: the series is thinned
   upstream, and a row that collapses would move the plot under it. */
.card-chart__values {
  height: 16px;
}

/* One line each, overflowing its cell rather than wrapping: the row is 16px
   tall and a second line lands in the plot. */
.card-chart__value,
.card-chart__label {
  min-width: 0;
  font-family: var(--font-mono);
  text-align: center;
  white-space: nowrap;
}

/* Text never wears the series color — the mark under it carries identity.
   `now` is the exception: it is the reading the chart exists for. */
.card-chart__value {
  font-size: var(--text-2xs);
  color: var(--text-secondary);
}

.card-chart__value--last {
  font-weight: var(--weight-medium);
  color: var(--text-bright);
}

.card-chart__label {
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* `now` again — the one axis label that is read rather than scanned. Keyed on
   the mark's flag, not :last-child: a status series has no `now`. */
.card-chart__label--last {
  color: var(--text-muted);
}

/* The box the viewBox stretches into; the border IS the baseline rule. */
.card-chart {
  display: block;
  width: 100%;
  height: 72px;
  border-bottom: 1.5px solid var(--border);
}

/* ONE series, one ink, weighted by how much the slot has to say — a thinned
   bucket .55, the peak .75, `now` solid and brightest. The target is the
   strong border. The ramp is the HISTORY series only: a bar carrying a status
   states that status at full strength (below). */
.card-chart__bar {
  fill: var(--text-secondary);
  fill-opacity: 0.55;
}

.card-chart__bar--emphasis {
  fill-opacity: 0.75;
}

.card-chart__bar--last {
  fill: var(--text-bright);
  fill-opacity: 1;
}

.card-chart__bar--target {
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
}

/* plan_week speaks the status vocabulary the list rows already speak, at a
   FIXED weight — the history ramp above never reaches these. Pending is faint
   as well as neutral, so state never rests on hue alone. */
.card-chart__bar--done {
  fill-opacity: 1;
}

.card-chart__bar--active {
  fill: var(--text-bright);
  fill-opacity: 1;
}

.card-chart__bar--pending {
  fill: var(--neutral);
  fill-opacity: 0.45;
}

.card-chart__bar--missed {
  fill: var(--danger);
  fill-opacity: 1;
}

/* An empty bucket: a tick on the baseline, quieter than any drawn session —
   including the faintest of them, the pending bar above. Last, so it wins the
   cascade on a tick that also carries a status. */
.card-chart__bar--tick {
  fill: var(--text-faint);
  fill-opacity: 0.6;
}

/* The line keeps its 2px whatever the box does to the viewBox — the element
   carries vector-effect as an attribute, so this is only its colour + weight. */
.card-chart__line {
  fill: none;
  stroke: var(--text-bright);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Dashed: the target stays legible as a reference under any color vision. */
.card-chart__line--target {
  stroke: var(--text-secondary);
  stroke-dasharray: 4 3;
}

/* A sentence about the plot, not a stamp over it. */
.card-meter__caption,
.card-chart__caption {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ROUTE — a run's trace, server-projected into a box of its own aspect. */
.card-section--route {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
}

.card-route {
  --route-aspect: 1;
  position: relative;
  width: 100%;
  max-width: calc(240px * var(--route-aspect));
  aspect-ratio: var(--route-aspect);
  margin-inline: auto;
}

.card-route__plot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.card-route__line {
  fill: none;
  stroke: var(--text-bright);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.card-route__start,
.card-route__finish {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  box-sizing: border-box;
  transform: translate(-50%, -50%);
}

.card-route__start {
  background: var(--text-secondary);
}

.card-route__finish {
  border: 2px solid var(--text-secondary);
}

/* ─── hero (celebration; the card's `moment` accent carries the chrome half of
   the treatment — chat-cards.css) ─── */

.card-section--hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 0;
  text-align: center;
}

/* A disc of the card's own accent, sized to the glyph — no image, no confetti,
   nothing that moves. --card-accent: the same read iOS's HeroSectionView takes,
   so the disc is gold on a moment card and neutral on a bare hero. */
.card-hero__glyph {
  margin: 0;
  padding: 0;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 24px;
  line-height: 1;
  background: color-mix(in srgb, var(--card-accent) 22%, transparent);
}

.card-hero__headline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  font-size: 20px;
  line-height: 1.15;
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
  color: var(--text);
}

.card-hero__stat {
  margin: 0;
}

/* Value over label, centred: the base column-reverse grammar, tightened — not
   the ruled cell `.card-stat` draws (flex-start, 2px, its own padding); a
   celebration number is not a cell. */
.card-section--hero .card-hero__stat .card-stat {
  align-items: center;
  gap: 4px;
  padding: 0;
}

/* THE number of the celebration — it leads, the headline names it. Later in
   the file than `.card-stat--emphasis .card-stat__value` and at the same
   specificity, so it wins on both the size and the accent an emphasized hero
   stat would otherwise take at --text-2xl. */
.card-hero__stat .card-stat__value {
  font-size: 48px;
  font-weight: var(--weight-medium);
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--card-accent-text);
}

/* Not the cell's 10px faint stamp (`.card-stat__label`). */
.card-section--hero .card-hero__stat .card-stat__label {
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.card-hero__stat .card-stat__unit {
  font-size: 16px;
}

/* ─── choices (option rows with a sentence each — the shape chips cannot
   carry, DESIGN §1.1) ─── */

.card-section--choices {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Grid, not a flex overlay: the macro's element (the real, focusable tap
   target) and the detail text are explicitly placed, so an absent glyph or
   detail never depends on DOM order. */
.card-choices__row {
  display: grid;
  grid-template-columns: 0 minmax(0, 1fr);
  align-items: center;
  column-gap: 0;
  row-gap: 2px;
  min-height: var(--tap-target);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: border-color var(--transition-fast);
}

.card-section--choices:has(.card-choices__glyph) .card-choices__row {
  grid-template-columns: var(--space-lg) minmax(0, 1fr);
  column-gap: var(--space-sm);
}

.card-choices__row:hover {
  border-color: var(--border-strong);
}

.card-choices__glyph {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--card-emphasis, var(--card-accent-text));
}

.card-choices__option {
  grid-column: 2;
  grid-row: 1;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-bright);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.card-choices__option:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

.card-choices__detail {
  grid-column: 2;
  grid-row: 2;
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ─── table (flat strings, ≤4 columns — scrolls inside the card via the
   .scroll-row utility, never the page) ─── */

.card-section--table {
  margin: 0;
}

.card-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

/* The caption names the table, so it heads it, as a section label does. */
.card-table__caption {
  caption-side: top;
  padding-bottom: var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: left;
}

.card-table__head {
  padding: 0 var(--space-md) var(--space-xs) 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  text-align: left;
  white-space: nowrap;
  overflow-wrap: normal;
  border-bottom: 1px solid var(--border);
}

/* Values are numbers: the same mono face .card-list__value and
   .card-delta__cell already use for data, so a table cell reads like every
   other value in this card vocabulary. */
.card-table__cell {
  padding: var(--space-xs) var(--space-md) var(--space-xs) 0;
  font-family: var(--font-mono);
  color: var(--text);
  white-space: nowrap;
  overflow-wrap: normal;
  border-bottom: 1px solid var(--border);
}

.card-table__row:last-child .card-table__cell {
  border-bottom: 0;
}

/* Same status vocabulary as the list rows above (card-sections.css:400-431) —
   a done row here and a done row in a list read the same tokens. */
.card-table__row--done .card-table__cell {
  color: var(--text);
}

.card-table__row--active .card-table__cell {
  font-weight: var(--weight-medium);
  color: var(--text-bright);
}

.card-table__row--pending .card-table__cell {
  color: var(--text-faint);
}

.card-table__row--missed .card-table__cell {
  color: var(--danger-text);
}

/* The row that matters: brightness + weight, not a background slab. AFTER the
   status tints on purpose — same specificity, so source order decides, and
   "this is the row" must beat "this row is pending", which would otherwise
   render the one emphasised row the faintest of all. */
.card-table__row--emphasis .card-table__cell {
  font-weight: var(--weight-medium);
  color: var(--text-bright);
}

.card-table__row--missed .card-table__cell:first-child {
  text-decoration: line-through;
  text-decoration-color: var(--border);
}

/* =============================================================================
   Card foot — byline + actions row BELOW the section stack. A sibling of the
   sections, not one of them, so each wrapper (chat stream, journey detail,
   timeline) can swap the action set without disturbing the card body.
   ============================================================================= */

.chat-card__foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--border);
}

.chat-card__byline {
  font-family: var(--font-coach);
  font-style: italic;
  color: var(--text-faint);
  font-size: var(--text-sm);
}

.chat-card__acts {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
}

.chat-card__acts a,
.chat-card__acts button {
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  padding: 0;
}

.chat-card__acts a:hover,
.chat-card__acts button:hover {
  color: var(--text);
}

/* =============================================================================
   Live stub fill — streaming.js owns every class below: it builds the header
   (buildStreamingHeader), marks the card `.card--streaming`, and adds
   --enter/--in per streamed section (openDetailFillStream).
   ============================================================================= */

/* A fill's progress sits where its finished card lands something — an action
   pill's slot on a detail card, the coach's note on a poster — so nothing
   moves when it arrives. */
.card-fill-indicator {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-normal);
}

.card-fill-indicator .typing-indicator {
  padding: 0;
}

/* A pill like its neighbours, only not pressable yet. */
.action-pill--pending {
  pointer-events: none;
}

.action-pill--pending .card-fill-indicator {
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
}

/* Each section fades + rises on its FIRST appearance only; enrichment (a
   section re-rendering as more fields arrive) swaps silently. */
.card--streaming .card-section--enter {
  opacity: 0;
  transform: translateY(var(--space-xs));
}

.card--streaming .card-section--in {
  opacity: 1;
  transform: none;
  transition: opacity var(--transition-normal), transform var(--transition-normal);
}

@media (prefers-reduced-motion: reduce) {
  .card--streaming .card-section--enter,
  .card--streaming .card-section--in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* =============================================================================
   RULED DETAIL — the stat row's cells, the list and segment rows, and the
   eyebrow's separators.
   ============================================================================= */

/* ─── stat_row: ruled cells under a solid top rule ─── */

/* The cell's own rules — scoped to the row, so the hero's single stat (which
   reuses this vocabulary) stays unruled. The -1px overlaps each divider onto
   its neighbour's edge and hangs the first cell of every row outside the
   section, where the clip above eats it. */
.card-section--stat_row .card-stat {
  margin-left: -1px;
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
}

/* Scoped to the row: a hero's stat is the celebration's number and keeps its
   display type (.card-hero__stat .card-stat__value). Emphasis inside the row
   needs no rule of its own — it inherits this size and the base rule's accent,
   and a taller cell would break the rule grid anyway. */
.card-section--stat_row .card-stat__value {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}

.card-section--stat_row .card-stat--emphasis .card-stat__value {
  font-weight: var(--weight-semibold);
}

/* ─── list: one line per session, tight enough to read as a week ─── */

/* `center`, not baseline: the row carries a kicker stamp, a 20px mark and a
   label at three different sizes. */
.card-list__row,
.card-list__hook {
  align-items: center;
  gap: var(--space-sm);
  min-height: 22px;
}

.card-list__label {
  font-size: 14px;
  line-height: 1.15;
}

/* The label reads a half-step up from the weekly summary's, scoped by the glyph
   COLUMN, so only a list that reserves one (ListData::any_glyph) takes the
   larger size. */
.card-section--list:has(> li > .card-list__hook > .card-list__glyph)
  .card-list__label {
  font-size: 14.5px;
}

/* ─── segments: the session's structure, as data rows ─── */

.card-segment__distance {
  color: var(--text);
}

/* =============================================================================
   THE POSTER — a session as a hero in its own tier (SPEC §5.10): the header and
   the stat row the wire composes right after it are the filled hero, everything
   else sits on the card surface, in wire order. The scope is
   `data-variant="poster"`, stated on the wire; the tier is the card's
   `data-intensity`, absent → neutral; a moment card fills with --moment.
   ============================================================================= */

/* A stub that will land as the poster: its mark and eyebrow already sit where the
   fill will put them (`stub_lands_as_poster`). A compact card is the poster's hero
   one size down, so opening it scales one card rather than swapping two. */
.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) {
  /* The eyebrow's line and the mark that rides it. */
  --poster-mark: var(--text-lg);
  /* Wider than a word space, or eyebrow facts read as one phrase. */
  --poster-gap: calc(var(--space-sm) + var(--space-xs));
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) {
  --hero: var(--surface-raised);
  --hero-ink: var(--text-bright);
  --hero-mark: var(--hero-ink);
  --hero-eyebrow: var(--text-muted);
  /* A mark, not a track: --data-bar is the empty bar behind a fill, and drawn
     alone it barely reads on the pane. */
  --hero-bar: var(--text-muted);
  --hero-line: var(--border);
  --hero-tint: transparent;
}

.chat-card[data-variant="poster"] {
  --poster-inset: var(--space-md);
  --poster-hero-end: calc(var(--poster-inset) + var(--space-xs));
  /* Where the body's first ink sits below the hero. */
  --poster-body-start: calc(var(--poster-inset) + var(--space-sm));
  /* Holds a completed record's longest label. */
  --poster-stat-cell: calc(var(--space-2xl) + var(--space-lg));
  /* Type sized to the card: a long title stays on two lines, a pace range and
     its unit stay inside a third of the row. */
  --poster-title-fit: 10.5cqi;
  --poster-stat-fit: 6cqi;
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub])[data-intensity] {
  --hero: var(--e-deep);
  --hero-ink: var(--e-deep-ink);
  --hero-eyebrow: var(--e-deep-ink);
  --hero-bar: var(--e-line);
  --hero-line: var(--e-line);
  --hero-tint: var(--e-tint);
}

/* A session still ahead has no ground yet: only the line and the tint, in the
   page's ink. Finishing it fills the hero. */
.chat-card:is([data-variant="poster"], [data-poster-stub])[data-intensity]:not(.chat-card--success) {
  --hero: transparent;
  --hero-ink: var(--text-bright);
  --hero-mark: var(--e-line);
  --hero-eyebrow: color-mix(in srgb, var(--hero-ink) var(--ink-quiet), transparent);
  --poster-hero-end: 0;
}

@media (prefers-color-scheme: light) {
  .chat-card[data-variant="poster"][data-intensity] {
    --hero-bar: var(--e);
  }
}

.chat-card:is([data-variant="poster"], [data-variant="compact"]):is(.chat-card--moment, .chat-card--success:has(> .card-section--header .steev-icon__badge use[href="#i-personal_best"])) {
  --hero: var(--moment);
  --hero-ink: var(--moment-ink);
  --hero-eyebrow: var(--moment-ink);
  --hero-bar: var(--moment);
  --hero-line: var(--moment-glyph);
  --hero-tint: var(--moment-wash);
}

.chat-card.card[data-variant="poster"] {
  gap: 0;
}

/* A spark meter's bars already stand that far down inside their own box; a
   callout keeps the box it draws. */
.chat-card.card[data-variant="poster"] > :is(.card-section--header, .card-section--stat_row) + .card-section:not(.card-section--stat_row, .card-section--meter, .card-prose--info, .card-prose--caution, .card-prose--success) {
  margin-top: 0;
  border-top: 0;
  padding-top: var(--poster-body-start);
}

.chat-card[data-variant="poster"] > .card-section {
  padding: var(--space-md) var(--poster-inset) 0;
}

.chat-card[data-variant="poster"] > .card-section:last-child {
  padding-bottom: var(--poster-inset);
}

.chat-card[data-variant="poster"] > .card-actions {
  margin: var(--space-md) var(--poster-inset) var(--poster-inset);
}

.chat-card[data-variant="poster"] > .card-section--header,
.chat-card[data-variant="poster"] > .card-section--stat_row {
  --icon-knockout: var(--hero);
  background: var(--hero);
  color: var(--hero-ink);
}

.chat-card[data-variant="poster"] > .card-section--header {
  container-type: inline-size;
  padding: var(--poster-inset) var(--poster-inset) var(--poster-gap);
}

/* The hero's two sections are one ground: at a fractional zoom their shared edge
   lands between device pixels and the surface shows through as a hairline. */
.chat-card[data-variant="poster"] > .card-section--header:has(+ .card-section--stat_row) {
  box-shadow: 0 1px 0 var(--hero);
}

/* One column system for every session: thirds, with a completed four-stat record
   on one row wherever its labels fit and two by two where they do not, so one
   never sits alone. Only a record's values are known to be short: a plan's pace
   range or effort sentence needs the third. */
.chat-card[data-variant="poster"] > .card-section--stat_row {
  container-type: inline-size;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: var(--poster-gap) var(--space-sm);
  padding: var(--space-xs) var(--poster-inset) var(--poster-hero-end);
}

.chat-card--success[data-variant="poster"] > .card-section--stat_row:has(> .card-stat:nth-child(4):last-child) {
  grid-template-columns: repeat(auto-fit, minmax(max(var(--poster-stat-cell), min(50% - var(--space-sm), (var(--poster-stat-cell) + var(--space-sm) * 3 / 4 - 25%) * 99999)), 1fr));
}

.chat-card[data-variant="poster"]:not(:has(> .card-section--stat_row)) > .card-section--header {
  padding-bottom: var(--poster-hero-end);
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) .card-heading,
.chat-card[data-variant="poster"] .card-subtitle {
  color: inherit;
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) .card-heading__icon {
  color: var(--hero-mark);
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) .card-eyebrow {
  font-size: var(--text-2xs);
  color: var(--hero-eyebrow);
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) .card-eyebrow__seg {
  color: inherit;
}

/* A finished session, stamped in one place on every size of the card. */
.chat-card:is([data-variant="poster"], [data-variant="compact"]) .card-eyebrow__seg--done {
  padding-inline: var(--space-xs);
  border-radius: var(--radius-xs);
  background: var(--hero-ink);
  color: var(--hero);
  font-weight: var(--weight-semibold);
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) > .card-section--header > .card-eyebrow > .card-eyebrow__seg {
  display: inline-block;
  text-indent: 0;
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) > .card-section--header > .card-eyebrow > .card-eyebrow__seg:not(:last-child) {
  margin-inline-end: var(--poster-gap);
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) .card-heading {
  align-items: flex-start;
  font-size: min(var(--text-3xl), var(--poster-title-fit));
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  text-wrap: balance;
}

/* The mark rides the eyebrow's line, so mark, title and stats share one left
   edge and the title gets the whole measure. It lives inside the heading, hence
   absolute at the header's padding; with no eyebrow it stays beside the title.
   The eyebrow indents only its first line past the mark: a wrapped line returns
   to the shared edge. */
.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) > .card-section--header:has(> .card-eyebrow) {
  position: relative;
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) > .card-section--header:has(> .card-eyebrow) > .card-eyebrow {
  display: block;
  line-height: var(--poster-mark);
  text-indent: calc(var(--poster-mark) + var(--space-sm));
  text-wrap: balance;
}

.chat-card:is([data-variant="poster"], [data-variant="compact"], [data-poster-stub]) > .card-section--header:has(> .card-eyebrow) .card-heading__icon {
  position: absolute;
  inset-block-start: var(--poster-inset, 0);
  inset-inline-start: var(--poster-inset, 0);
  width: var(--poster-mark);
  height: var(--poster-mark);
  margin: 0;
}

.chat-card[data-variant="poster"] .card-stat {
  flex: 0 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 0;
  margin: 0;
  border: 0;
}

.chat-card[data-variant="poster"] .card-stat__label {
  overflow-wrap: normal;
  color: var(--hero-eyebrow);
}

.chat-card[data-variant="poster"] .card-section--stat_row .card-stat__value {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs);
  font-family: var(--font-display);
  font-size: clamp(var(--text-base), var(--poster-stat-fit), var(--text-xl));
  font-weight: var(--weight-medium);
  line-height: 1;
  letter-spacing: 0.01em;
  color: inherit;
  overflow-wrap: anywhere;
}

/* The value never breaks; a unit that carries words does, or the row clips it. */
.chat-card[data-variant="poster"] .card-stat__unit {
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  font-variant-numeric: normal;
  white-space: normal;
  color: inherit;
}

.chat-card[data-variant="poster"] .card-section--stat_row .card-stat--prose .card-stat__value {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: 1.35;
  letter-spacing: 0;
}

.chat-card[data-variant="poster"] .card-stat--prose .card-stat__unit {
  font-family: inherit;
  font-size: inherit;
}

.chat-card[data-variant="poster"] > .card-section--stat_row > .card-stat--prose {
  grid-column: 1 / -1;
}

#plan-pane .timeline-detail > .chat-card[data-variant="poster"] {
  max-width: none;
  padding: 0;
  box-shadow: none;
}

/* The close sits on the hero wherever a poster opens (the plan pane, the
   narrow sheet, the chat thread), so it wears the hero's ink there. */
.timeline-detail > .chat-card[data-variant="poster"] > .detail-close {
  top: calc(var(--poster-inset) + var(--poster-mark) / 2);
  right: var(--poster-gap);
  translate: 0 -50%;
  color: var(--hero-ink);
}

.timeline-detail > .chat-card[data-variant="poster"] > .detail-close:hover:not(:disabled) {
  color: var(--hero-ink);
  background: color-mix(in srgb, var(--hero-ink) 14%, transparent);
}

/* A long eyebrow wraps clear of the close, which sits on its line. */
.timeline-detail > .chat-card[data-variant="poster"] > .card-section--header > .card-eyebrow {
  padding-inline-end: var(--space-lg);
}

/* The pane gives its chip row the right edge to scroll into, so the detail
   restores the inset the left edge already has. The sheet below 1000px pads
   both sides itself. */
@media (width > 1000px) {
  #plan-pane .timeline-detail > .chat-card[data-variant="poster"] {
    margin-inline-end: var(--pane-pad-inline);
  }
}

/* ─── the effort profile ─── */

.chat-card[data-variant="poster"] .card-meter--spark {
  height: 44px;
  gap: 2px;
}

.chat-card[data-variant="poster"] .card-meter__spark {
  border-radius: 2px 2px 0 0;
  background: var(--hero-bar);
  transform-origin: bottom;
  animation-name: meter-bar-rise;
  animation-duration: 500ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  animation-fill-mode: backwards;
}

/* The plan draws itself left to right, 30ms apart — SPEC §7. Written out
   because CSS has no index to compute a delay from, and left UNSCOPED for the
   same reason: twelve copies of the poster selector to carry one number each.
   A delay with no animation-name is inert, so every other spark meter ignores
   these. */
@keyframes meter-bar-rise {
  from {
    transform: scaleY(0);
  }
  to {
    transform: scaleY(1);
  }
}

.card-meter__spark:nth-child(1) { animation-delay: 0ms; }
.card-meter__spark:nth-child(2) { animation-delay: 30ms; }
.card-meter__spark:nth-child(3) { animation-delay: 60ms; }
.card-meter__spark:nth-child(4) { animation-delay: 90ms; }
.card-meter__spark:nth-child(5) { animation-delay: 120ms; }
.card-meter__spark:nth-child(6) { animation-delay: 150ms; }
.card-meter__spark:nth-child(7) { animation-delay: 180ms; }
.card-meter__spark:nth-child(8) { animation-delay: 210ms; }
.card-meter__spark:nth-child(9) { animation-delay: 240ms; }
.card-meter__spark:nth-child(10) { animation-delay: 270ms; }
.card-meter__spark:nth-child(11) { animation-delay: 300ms; }
.card-meter__spark:nth-child(12) { animation-delay: 330ms; }

/* =============================================================================
   THE COMPACT CARD — a finished session, collapsed: the poster's hero one size
   down, in the poster's ground, ink and faces. Shell is chat-cards.css.
   ============================================================================= */

/* WHOLE WORDS. `.card-section`'s `min-width: 0; overflow-wrap: anywhere` (top
   of this file) was written for a COLUMN stack, where a long token would widen
   the card; laid out in a ROW it lets a sibling squeeze the title to zero and
   break it at any character — "Sh / ort / Ea / sy / Ru / n". The floor is the
   longest word, and the break stays at the spaces. */
.chat-card[data-variant="compact"] > .card-section {
  flex: 0 1 auto;
  min-width: min-content;
  overflow-wrap: normal;
}

.chat-card[data-variant="compact"] .card-section--header {
  flex-basis: 100%;
}

.chat-card:is([data-variant="compact"], [data-poster-stub]:not([data-variant])) .card-heading {
  font-size: var(--text-xl);
}

.chat-card[data-variant="compact"] .card-section--stat_row {
  align-items: baseline;
  gap: 0 var(--space-md);
}

.chat-card[data-variant="compact"] .card-stat {
  flex: 0 0 auto;
  flex-direction: row;
  align-items: baseline;
  gap: var(--space-xs);
  padding: 0;
  margin-left: 0;
  border: none;
}

/* One line, VALUES only. The server composes the compact stat row so every
   value speaks for itself — a scale reads with its number ("7/10") — so
   this file needs neither a position rule (a session with no distance leads
   with Time) nor the label text (that is copy). */
.chat-card[data-variant="compact"] .card-stat__label,
.chat-card[data-variant="compact"] .card-feel__label {
  display: none;
}

.chat-card[data-variant="compact"] .card-section--stat_row .card-stat__value {
  font-size: var(--text-lg);
  color: inherit;
}

.chat-card[data-variant="compact"] .card-stat__unit {
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  font-weight: inherit;
  letter-spacing: 0;
  color: inherit;
}

.chat-card[data-variant="compact"] .card-section--feel {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-normal);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: inherit;
}

.chat-card[data-variant="compact"] .card-feel__value {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

/* Each part scales as itself: a full-width box would only cross-fade. */
.chat-card:is([data-variant="poster"], [data-variant="compact"]) :is(.card-heading, .card-stat__value) {
  width: fit-content;
}

@media (prefers-reduced-motion: reduce) {
  .chat-card[data-variant="poster"] .card-meter__spark {
    animation: none;
  }
}

/* =============================================================================
   CHAT-CARDS.CSS — Unified card system for chat-area structured content

   Cards are structured chat content (workout posters, goal cards, summaries)
   on one flat shell: surface, hairline, one radius. Each card type sets
   --card-accent, which colours eyebrows, glyphs, meters and marks; the surface
   and the border stay neutral. Voice positioning is handled by [data-voice] in
   chat-marginalia.css.
   ============================================================================= */

/* ─── Base ─── */

.chat-card {
  --card-accent: var(--text-muted);
  --card-accent-text: var(--text-muted);
  --card-rule: var(--border);
  position: relative;
  padding: var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface-raised);
  box-shadow: none;
  align-self: flex-start;
  max-width: 100%;
  font-size: var(--text-sm);
}

/* ─── Accent variants ─── */

/* Column-flex stacks the body text and workout poster as direct children. */
.chat-card--coach {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Every card kind is neutral, and an emphasized stat reads by brightness,
   never a colour. A card that names its session's effort tier
   (`data-intensity`) takes that tier's line — text in the light scheme takes
   the tier's `strong` ink — and, in the chat, a small mark hanging from its
   top edge (never a side bar; a poster's hero is already the tier). A moment
   card — the moment kind, or a personal best — names no tier: it wears
   --moment. */
:is(.chat-card--coach, .chat-card--action, .chat-card--success, .chat-card--proposed, .chat-card--goal, .chat-card--milestone) {
  --card-emphasis: var(--text-bright);
}

.chat-card[data-intensity]:not(.chat-card--moment) {
  --card-accent: var(--e-line);
  --card-accent-text: var(--e-line);
  --card-emphasis: var(--card-accent-text);
}

@media (prefers-color-scheme: light) {
  .chat-card[data-intensity]:not(.chat-card--moment) {
    --card-accent-text: var(--e-strong);
  }
}

.chat-card:is(.chat-card--moment, .chat-card--success:has(> .card-section--header .steev-icon__badge use[href="#i-personal_best"])) {
  --card-accent: var(--moment-glyph);
  --card-accent-text: var(--moment-text);
  --card-emphasis: var(--card-accent-text);
  --card-rule: var(--border-strong);
  border-color: var(--moment-glyph);
  background: linear-gradient(var(--moment-wash), var(--moment-wash)),
    var(--surface-raised);
}

/* When the capture form is hosted inside the global dialog (the leaderboard
   "Log workout" button reusing the coach page's form), center it on the page
   instead of pinning it right like it would in the chat stream. The form is
   wrapped in .exchange-group > .user-workout-card to put it on the user side
   of the chat — those wrappers exist inside the dialog too, so we override
   their alignment + width. */
dialog#modal-container .exchange-group,
dialog#modal-container .user-workout-card {
  align-self: center;
  justify-content: center;
  max-width: 100%;
  width: 100%;
}

/* ─── Entrance animation ─── */

.chat-card--animated {
  animation: card-enter 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes card-enter {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Fill reconcile: the streamed preview already showed this content, so the
   canonical card must not replay its entrance animation when it swaps in —
   that re-pop is what made the end of streaming jump. */
.detail--reconciling .chat-card--animated {
  animation: none;
}

/* ─── Card actions ─── */

.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  padding-top: var(--space-sm);
  border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
}

/* ─── Timeline detail context ─── */

.timeline-detail > .chat-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.timeline-detail > .chat-card > .detail-close {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  z-index: 1;
}

.timeline-detail > .chat-card--goal {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* Compact card hidden while its in-place poster (#chat-node-poster) stands in
   for it; streaming.js applies and removes it. Two classes to beat
   `.chat-card.card` (card-sections.css, same weight, imported earlier); the
   variant-bearing form, three, to beat the `.chat-card[data-variant="…"]`
   display rules later in this file — or a compact card keeps its row and the
   poster lands UNDER it. */
.chat-card.chat-card--poster-hidden,
.chat-card[data-variant].chat-card--poster-hidden {
  display: none;
}

/* ─── Size variants ─── */

/* After `.chat-card` at the same weight, so a summary pill stays a pill and a
   form card stays a form card. */
.chat-card--compact {
  padding: var(--space-xs) var(--inset-sm);
}

/* The celebration card, deliberately bigger than its siblings; its moment
   border and wash are the moment rule's above. */
.chat-card.chat-card--moment {
  padding: 26px 30px 22px;
}

/* ─── The proposed-workout POSTER ─── */

/* The shell is the base card's; the hero inside it is card-sections.css.
   `data-variant="poster"` is stated on the wire (`http/api/card.rs`). */
.chat-card[data-poster-stub],
.chat-card[data-variant="poster"],
.chat-card[data-variant="poster"]:is(.chat-card--moment, .chat-card--success:has(> .card-section--header .steev-icon__badge use[href="#i-personal_best"])) {
  border-color: var(--hero-line);
  background: linear-gradient(var(--hero-tint), var(--hero-tint)), var(--surface-raised);
}

.chat-card[data-variant="poster"] {
  padding: 0;
  overflow: hidden;
}

/* The chat's one volt: only the newest still-Proposed poster carries answers
   (older posters lose theirs server-side), so its primary answer is the
   action fill and the other answers are surface pills beside it. */
:where(#chat-messages) .chat-card[data-variant="poster"] .card-section--cta_row {
  flex-direction: row;
  gap: var(--space-sm);
}

:where(#chat-messages) .chat-card[data-variant="poster"] .card-cta {
  flex: 1 1 auto;
  width: auto;
  justify-content: center;
  padding: 0 var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--text);
}

:where(#chat-messages) .chat-card[data-variant="poster"] .card-cta + .card-cta {
  border-top: 0;
}

:where(#chat-messages) .chat-card[data-variant="poster"] .card-cta::after {
  content: none;
}

:where(#chat-messages) .chat-card[data-variant="poster"] .card-cta--primary {
  background: var(--action);
  box-shadow: var(--action-ring);
  color: var(--action-ink);
}

#chat-messages:has(.form-pane) .chat-card[data-variant="poster"] .card-cta--primary {
  background: var(--surface-raised);
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--text);
}

:where(#chat-messages) .chat-card[data-variant="poster"] .card-cta--danger {
  color: var(--danger-text);
}

/* ─── The ledger's completed card ─── */

.chat-card[data-variant="compact"],
.chat-card[data-variant="compact"]:is(.chat-card--moment, .chat-card--success:has(> .card-section--header .steev-icon__badge use[href="#i-personal_best"])) {
  --icon-knockout: var(--hero);
  border-color: var(--hero);
  background: var(--hero);
  color: var(--hero-ink);
}

.chat-card[data-variant="compact"] {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  /* Wrap, not overflow: the sections refuse to shrink below their content
     (card-sections.css re-arms the min-content floor the row layout removes),
     so a long title beside a six-stat line drops the stats to a second full-
     width line instead of pushing them past the card's edge. */
  flex-wrap: wrap;
  gap: var(--space-md);
  padding: var(--space-md);
}

/* ─── A workout in the thread ─── */

/* One width in every state (stub, compact line, poster, the poster opened in
   place, and the fill streaming into it), so a tap only changes the height. */
#chat-messages .chat-card[id^="workout-"],
#chat-messages .chat-card:has(.card-list__hook[data-open-node]),
.chat-node-poster[data-voice],
.chat-node-poster > .chat-card {
  width: 100%;
  max-width: var(--chat-card-measure);
}

/* ─── The zoom ─── */

/* Named parts paint above the page and past every clip, so the page stays live
   behind them and the overlay is cut to the chat (streaming.js sets the inset). */
:root.card-zooming {
  view-transition-name: none;
}

::view-transition {
  clip-path: var(--zoom-clip);
}

::view-transition-group(*) {
  animation-duration: var(--motion-zoom);
  animation-timing-function: var(--motion-zoom-ease);
}

/* The two grounds cut quickly; the type is what travels. */
::view-transition-old(zoom-card),
::view-transition-new(zoom-card) {
  animation-duration: var(--motion-zoom-cut);
}

/* Labels arrive once the numbers have moved. */
::view-transition-new(.zoom-label) {
  animation: chat-fade var(--motion-zoom-cut) var(--motion-zoom-cut) both;
}

/* ─── Reduced motion ─── */

@media (prefers-reduced-motion: reduce) {
  .chat-card--animated {
    animation: none;
  }
}

/* ─── Live card (render_card streamed while the turn writes) ─── */

/* Open but not yet filled: the frame arrives before its first section, and an
   empty bordered box under the bubble reads as a broken card. */
.chat-card.card--streaming:empty {
  display: none;
}

#chat-loading-stub:not([hidden]) {
  display: contents;
}

/* =============================================================================
   CHAT-ERROR.CSS - Error bubble + expandable diagnostic details

   Typographic only — no heavy background/border pill. The user-visible
   message sits inline with a details button (⚠︎); diagnostic keys/values
   expand via native <details> beneath a hairline separator.

   The bubble is partials/chat_error.html, rendered by
   `llm_pipeline::render_web_error`.
   ============================================================================= */

.scribble-text.chat-error {
  color: var(--danger-text);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs);
}

.chat-error-message {
  flex: 1 1 auto;
  min-width: 0;
}

.chat-error-details {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.chat-error-details > summary {
  list-style: none;
}

.chat-error-details > summary::-webkit-details-marker,
.chat-error-details > summary::marker {
  display: none;
}

.chat-error-info-btn {
  cursor: pointer;
  padding: 0 var(--space-xs);
  font-size: var(--text-base);
  line-height: 1;
  color: var(--text-muted);
  opacity: 0.7;
  user-select: none;
  transition: opacity var(--transition-fast) ease;
}

.chat-error-info-btn:hover,
.chat-error-details[open] .chat-error-info-btn {
  opacity: 1;
  color: var(--danger-text);
}

.chat-error-body {
  flex: 1 1 100%;
  margin-top: var(--space-xs);
  padding-top: var(--space-xs);
  border-top: 1px solid var(--border-strong);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.chat-error-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.chat-error-key {
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.chat-error-val {
  color: var(--text);
  word-break: break-word;
}

/* =============================================================================
   CHAT-MARGINALIA.CSS

   The chat inside the floating window (SPEC §5.9): a centred day separator,
   the runner's neutral bubbles on the right, the coach's unboxed prose under
   the face that heads each of his turns. Page-level items (separators, empty
   state) span the full width.

   Layout lives on [data-voice], not on .exchange-group. .exchange-group
   is a thin shell carrying mood class + entrance animation; one group can
   contain BOTH a user entry AND a coach scribble, so it does not itself
   carry a voice.
   ============================================================================= */

.coach-inline {
  --chat-face-size: 22px;
}

.exchange-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* A widow group must not go on eating a stream gap. */
.exchange-group:empty {
  display: none;
}

/* ─── Entry (runner's note) ────────────────────────────────────────── */

.entry-body {
  margin: 0;
}

/* ─── Scribble (coach prose) ───────────────────────────────────────── */

/* Positioned: the one-shot face moment anchors on the landed bubble. */
.scribble {
  position: relative;
}

/* Prose stops at a readable measure; cards and stat grids still spend the
   whole column. Mood never tints it: the face at the head of the turn carries
   the mood. */
.scribble-text {
  max-width: var(--coach-prose-measure);
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--chat-prose-size);
  line-height: 1.5;
  color: var(--text);
}

.scribble--fallback .scribble-text {
  color: var(--text-muted);
  cursor: help;
}

/* ─── The face at the head of a coach turn ─────────────────────────────
   Glyph only, in the chat's own ink; its expression is the turn's mood
   (steev-face.css). The negative inline margin puts the glyph — not the pad —
   on the prose's left edge. */

.coach-face {
  display: flex;
  align-self: flex-start;
  padding: var(--space-xs);
  margin-inline-start: calc(-1 * var(--space-xs));
  border-radius: var(--radius-pill);
}

#chat-messages > .exchange-group:has(~ .exchange-group:not([hidden]) > .coach-face) .coach-face,
#chat-messages > .exchange-group:has(~ .exchange-group > #chat-loading-stub:not([hidden]) > .coach-face) .coach-face,
#chat-messages > .exchange-group:not(.exchange-arriving):has(~ #goal-card-slot #goal-card) .coach-face {
  display: none;
}

/* The goal card's ask is the newest words until a turn lands above it. */
#chat-messages:has(> .exchange-group.exchange-arriving:not([hidden]) > .coach-face, > .exchange-group > #chat-loading-stub:not([hidden]) > .coach-face) #goal-card > .coach-face {
  display: none;
}

/* ─── Receipt rows ──────────────────────────────────────────────────
   The server's own account of what a turn changed where the reply and its
   card show nothing (a moved/updated/removed/added calendar item). This is
   NOT the coach speaking — muted ink only. Quiet rows, no buttons: a hairline
   between two lines and nothing else. Sits beside `.scribble`, not inside it,
   so it renders even when the reply text is empty and the scribble itself is
   absent. */

.receipt-rows {
  display: flex;
  flex-direction: column;
}

.receipt-row {
  margin: 0;
  padding: var(--space-xs) 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.4;
}

.receipt-row + .receipt-row {
  border-top: 1px solid var(--border-strong);
}

/* ─── Coach reply markdown ──────────────────────────────────────────
   The reply is rendered from the model's markdown by the `md` filter, so the
   bubble (a <div class="scribble-text">) holds block children — paragraphs, the
   odd list — plus inline strong/em, which stay emphasis. Collapse the outer
   margins to keep the bubble tight. */
.scribble-text > :first-child {
  margin-top: 0;
}
.scribble-text > :last-child {
  margin-bottom: 0;
}
.scribble-text p {
  margin: 0 0 var(--space-sm) 0;
}
/* Markdown children keep the prose colour: global element rules (base.css
   `p` → --text-secondary, headings → --text-bright) would otherwise split one
   reply into two inks. */
.scribble-text :is(p, li, h1, h2, h3, h4, h5, h6) {
  color: inherit;
}
.scribble-text ul,
.scribble-text ol {
  margin: var(--space-sm) 0;
  padding-left: var(--space-lg);
}
.scribble-text li + li {
  margin-top: var(--space-xs);
}
/* Defensive: the coach shouldn't reach for headings, but if one slips through
   keep it at bubble scale rather than blowing up the layout. */
.scribble-text :is(h1, h2, h3, h4, h5, h6) {
  font-size: inherit;
  font-weight: 600;
  margin: 0 0 var(--space-sm) 0;
}

/* ─── Exchange entrance (page load + HTMX-appended) ────────────────── */

@keyframes entry-arrive {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.exchange-group {
  animation: entry-arrive var(--motion-arrive) both;
}

.exchange-group:nth-child(1) { animation-delay:   0ms; }
.exchange-group:nth-child(2) { animation-delay:  50ms; }
.exchange-group:nth-child(3) { animation-delay: 100ms; }
.exchange-group:nth-child(4) { animation-delay: 150ms; }
.exchange-group:nth-child(5) { animation-delay: 200ms; }
.exchange-group:nth-child(6) { animation-delay: 250ms; }
.exchange-group:nth-child(7) { animation-delay: 300ms; }
.exchange-group:nth-child(8) { animation-delay: 350ms; }
.exchange-group:nth-child(n+9) { animation-delay: 400ms; }

.exchange-group.exchange-arriving {
  animation: entry-arrive var(--motion-arrive) both;
  animation-delay: 0ms;
}

@media (prefers-reduced-motion: reduce) {
  .exchange-group,
  .exchange-group.exchange-arriving { animation: none; }
}

/* ─── Typing indicator (streaming) ─────────────────────────────────── */

.typing-indicator {
  display: flex;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--inset-sm);
}

.typing-indicator span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-faint);
  animation: typing-bounce 1.8s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }

@keyframes typing-bounce {
  0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
  40%           { transform: scale(1);   opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .typing-indicator span { animation: none; opacity: 0.5; }
}

/* ─── Tool status (under streaming scribble) ───────────────────────── */

.tool-status {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding-block: var(--space-xs);
  font-size: var(--text-xs);
  color: var(--text-muted);
  animation: fadeIn 0.2s ease-in;
}

/* ─── Day separator ──────────────────────────────────────────────────────
   A centred mono line between days, a sibling of the groups in the flat
   stream. `data-date` is what the history-batch dedup reads. */

.chat-day {
  margin: var(--space-sm) 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  color: var(--text-muted);
}

/* ─── System line (budget notes, wall notice) ──────────────────────
   System voice, not coach voice: muted mono, full width — the same
   register as the date separator, left-aligned. */
.system-line {
  padding: var(--space-xs) 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-muted);
  letter-spacing: var(--tracking-wide);
}

.system-line-link {
  color: var(--text-secondary);
  text-decoration: underline;
}

/* ─── Composer (.entry-line) ───────────────────────────────────────── */

.entry-line {
  padding: var(--space-xs) 0 0;
  transition: opacity var(--motion-arrive), translate var(--motion-arrive);
}

/* @starting-style is the before-change style for the first render, so on a
   cold load the band fades and lifts on the same curve as .exchange-group. */
@starting-style {
  .entry-line {
    opacity: 0;
    translate: 0 var(--space-sm);
  }
}

@media (prefers-reduced-motion: reduce) {
  .entry-line {
    transition: none;
  }
}

.entry-line-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  height: var(--composer-h);
  padding: 0 var(--space-xs) 0 var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
}

/* 16px is the floor below which mobile Safari zooms a focused <input>, and this
   is a real one (chat_area.html, composer_walled.html) — NOT the prose size that
   happens to share the number today. Never point it at --chat-prose-size: the
   next chat re-tune would silently re-arm the zoom. */
.entry-line-input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  outline: none;
  text-overflow: ellipsis;
}

.entry-line-input::placeholder {
  color: var(--text-muted);
}

.entry-line-send {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: calc(var(--composer-h) - 2 * var(--space-xs) - 2px);
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--action);
  box-shadow: var(--action-ring);
  color: var(--action-ink);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  line-height: 1;
  cursor: pointer;
}

.entry-line-send::after {
  content: "";
  position: absolute;
  inset: calc((100% - var(--tap-target)) / 2);
}

.entry-line-send svg {
  width: 50%;
  height: 50%;
}

.entry-line-send:is(:disabled, [aria-disabled="true"]) {
  background: var(--surface-bubble);
  box-shadow: none;
  color: var(--text-muted);
  cursor: not-allowed;
}

.entry-line-send[data-offer] {
  background: var(--chat-ground);
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--text);
}

.entry-line--disabled .entry-line-input {
  cursor: not-allowed;
}

/* ─── Seam action group ─────────────────────────────────────────────── */

.coach-actions-group {
  display: contents;
}

/* The groups generate no box, so the pills are the flex items — without this
   they shrink and break their labels over two lines instead of scrolling. */
.coach-actions-group > * {
  flex: none;
}

/* Follow-up chips: hairline-ringed chips (the handoff's call, over quotes). The Log /
   Goal / Plan action items share the pill so the row is one height. */
.chat-chip,
.coach-action-item {
  padding: 8px 16px;
  border: 0;
  box-shadow: inset 0 0 0 1px var(--border);
  border-radius: var(--radius-chip);
  background: transparent;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.15s;
}

/* The get-the-app link + its dismiss, riding together inside their seam slot —
   the web twin of ComposerView's HStack. The slot outside them is what
   app-promo.js un-hides. */
.app-promo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

/* An <a>, unlike its <button> siblings, arrives underlined. */
.app-promo .coach-action-item {
  text-decoration: none;
}

.app-promo-dismiss {
  background: transparent;
  border: 0;
  padding: var(--space-xs);
  color: var(--text-muted);
  font-family: inherit;
  font-size: var(--text-xs);
  line-height: 1;
  cursor: pointer;
}

.app-promo-dismiss:hover {
  color: var(--text);
}

/* ─── Runner's entry: a neutral bubble on the right ─── */

/* Only the note itself and the runner's logged-workout card take the right
   placement. The FORM cards that also carry data-voice="user" (log-workout
   capture) keep full width. */
.entry[data-voice="user"],
.user-workout-card {
  align-self: flex-end;
  max-width: calc(100% - var(--chat-asymmetry));
}

.entry[data-voice="user"] .entry-body {
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-card) var(--radius-card) 4px var(--radius-card);
  background: var(--surface-bubble);
  color: var(--text);
  font-size: var(--text-base);
  line-height: 1.4;
}

/* ─── Composer ─── */

/* THE DOCK owns sticky, not the composer. The seam line is the dock's last
   child, so it rides along instead of sitting in normal flow below a sticky
   composer — where it was reachable only at the very bottom of the scroll, and
   where the composer visibly shifted by its height as it settled out of sticky.
   One band, one position, both fixed.

   The ground (the chat window's own surface) fades in over a fixed 12px: the conversation still passes under a
   soft edge, and the fade never reaches the field's text whatever the dock's
   height. */
/* The bottom pad carries three things at once, which is why it is one
   expression and not three rules. `env(safe-area-inset-bottom)` is 0 wherever
   there is no inset, so `max()` is already the value at every width — the
   narrow duplicate this replaced was never a different number.
   `--keyboard-inset` is the part behind the keys: sticky pins the dock's bottom
   edge to the LAYOUT viewport's, which the keyboard never moves, so without it
   the field sits behind them. Padding does two jobs there — it lifts the field
   onto the keyboard, and it grows the document by the same amount so the newest
   message can still scroll clear. It is 0 with no keyboard and 0 without
   viewport.js, which is exactly the resting pad. */
.entry-dock {
  position: sticky;
  bottom: 0;
  z-index: 1;
  padding: 0 28px
    calc(max(var(--space-lg), env(safe-area-inset-bottom)) + var(--keyboard-inset, 0px));
  background: linear-gradient(transparent, var(--surface-raised) 12px);
}

.entry-line-glyph {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1;
  color: var(--text-muted);
}

/* ─── Follow-up chips ─── */

/* A chip sends AS the runner, so it wears the runner's slant — the same italic
   their typed message and their landed note carry. `.coach-action-item` shares
   the pill so the row stays one height, but it opens a form rather than
   speaking, so it stays upright. Voice, not decoration. */
.chat-chip {
  font-style: italic;
}

.chat-chip:hover,
.coach-action-item:hover {
  box-shadow: inset 0 0 0 1px var(--border-strong);
  color: var(--text-bright);
  text-decoration: none;
}

/* 44 is the TAP target, not the pill. `min-height` PAINTS it — the pill's own
   background and border fill the taller box — which inflates every chip by ~9px
   and, stacked, is what made the native strip unusable. An invisible overlay
   carries the hit area instead, so the pill keeps its designed height. iOS
   corrects the same mistake on these same three controls with a frame outside
   the painted background; this is the CSS twin. */
@media (max-width: 760px) {
  .chat-chip,
  .coach-action-item {
    position: relative;
  }

  .chat-chip::after,
  .coach-action-item::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: var(--tap-target);
    transform: translateY(-50%);
  }
}

/* ─── Seam line (SPEC §5.9a) ───────────────────────────────────────────
   ONE line that scrolls, never a wrapping block (owner ruling 2026-09-03):
   the markup carries `.scroll-row .edge-fade--trailing`, so the overflow, the
   hidden scrollbar and drag-to-scroll are shared with the other strips. The
   first visible slot takes the ink (sibling CSS only, so app-promo.js
   un-hiding the get-app slot moves it without a script); the methodology
   link is the group's trailing sibling and never takes it. */
.entry-seam {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: var(--weight-semibold);
}

.entry-seam > * {
  flex: none;
  white-space: nowrap;
}

.entry-seam__link,
.entry-seam .coach-action-item {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  background: none;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
}

.entry-seam__link:hover,
.entry-seam__link:focus-visible,
.entry-seam .coach-action-item:hover,
.entry-seam .coach-action-item:focus-visible {
  color: var(--text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.entry-seam .seam-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--text);
}

.entry-seam .seam-item[hidden] {
  display: none;
}

/* The seam scrolls sideways and would clip a popover; a modal <dialog> sits
   in the top layer. It inherits the seam's semibold, so it resets it. */
.watch-choice {
  width: min(22rem, calc(100vw - 2 * var(--space-lg)));
  padding: var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sheet);
  background: var(--surface-raised);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--weight-normal);
  white-space: normal;
}

.watch-choice::backdrop {
  background: var(--shadow);
}

.watch-choice__title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-heading);
  font-weight: 600;
}

.watch-choice__option {
  margin-top: var(--space-sm);
}

.entry-seam .seam-item:not([hidden]) ~ .seam-item {
  color: inherit;
}

/* ─── Citation footnote ───────────────────────────────────────────────────
   A raised mono marker on the end of the reply's last line, the way a printed
   page references a source — only on the replies the coach actually cited. */

/* Two block siblings can never share a line box, so when a marker follows the
   reply the reply's box goes inline — and so does a trailing PARAGRAPH, but only
   a paragraph. A trailing list or heading stays the block it is: inlining a <ul>
   leaves its display: list-item children block-level, which breaks the inline box
   around them and strands the marker anyway. The prose measure moves up to the
   body for the inline case — an inline box ignores max-width. */
.scribble-body:has(> .scribble-cite) {
  max-width: var(--coach-prose-measure);
}

.scribble-body:has(> .scribble-cite) > .scribble-text,
.scribble-body:has(> .scribble-cite) > .scribble-text > p:last-child {
  display: inline;
}

/* Raised the way `sup` is normalized — zero line-height plus a relative
   offset — and NOT with vertical-align: super, which grows the line box of
   the very line the marker joins and would re-space the reply's last line. */
.scribble-cite {
  position: relative;
  top: -0.5em;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  line-height: 0;
  vertical-align: baseline;
  text-decoration: none;
  cursor: pointer;
}

.scribble-cite:hover,
.scribble-cite:focus-visible {
  color: var(--text);
}

/* An unstyled ::backdrop is transparent, which is what a footnote popup wants:
   the conversation stays visible behind it. */
.cite-pop {
  max-width: 320px;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface-raised);
  color: var(--text);
}

.cite-pop__rule {
  font-size: 14px;
  line-height: 1.55;
}

/* Same "SOURCE" label convention the methodology page uses, one step below
   its own trigger. */
.cite-pop__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.cite-pop__source {
  font-size: 13px;
  color: var(--text-secondary);
}

.cite-pop__link,
.cite-pop__all {
  display: block;
  color: var(--text);
  font-size: 12.5px;
  text-decoration: underline;
  overflow-wrap: anywhere;
}

/* The marker is a few px in BOTH axes, so the tap target is an invisible
   overlay. Painting it (min-height) would push the marker out of the line it
   belongs to. The marker is already `position: relative` for its raise, so the
   overlay has its anchor. The popup never exceeds the viewport minus the page
   gutters. */
@media (max-width: 760px) {
  .scribble-cite::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--tap-target);
    height: var(--tap-target);
    transform: translate(-50%, -50%);
  }

  .cite-pop {
    max-width: min(320px, calc(100vw - 32px));
  }
}

/* =============================================================================
   CHAT-WINDOW.CSS — the floating chat over the insights (SPEC §5.2)

   One window, two states on `.coach-layout[data-chat]` (streaming.js). Narrow
   it is a bottom sheet whose straight top edge sits under the header band;
   wide it is a window on the right. Collapsed, both shrink to the peek plus
   the composer row. --chat-sheet-top and --chat-window-h are measured by
   coach.html's inline script before first paint.
   ============================================================================= */

.coach-layout {
  --chat-window-w: 420px;
  --chat-peek-w: 360px;
}

.coach-layout[data-chat="collapsed"] {
  padding-bottom: calc(var(--chat-window-h, 0px) + var(--space-md));
}

.chat-window {
  position: fixed;
  z-index: calc(var(--z-nav) - 1);
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0 var(--space-md);
  background: var(--chat-ground);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}

.coach-layout[data-chat="collapsed"] .chat-window {
  top: auto;
}

.coach-layout[data-chat="open"] .chat-peek,
.coach-layout[data-chat="collapsed"] .chat-window__toggle,
.coach-layout[data-chat="collapsed"] #chat-messages,
.coach-layout[data-chat="collapsed"] .entry-seam {
  display: none;
}

/* ─── The bar: grab handle (touch drag) and the collapse chevron ─── */

.chat-window__bar {
  position: relative;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  min-height: var(--space-lg);
  touch-action: none;
}

.chat-window__grab {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  width: var(--space-xl);
  height: var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--border-strong);
}

.chat-window__toggle {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  display: grid;
  place-items: start center;
  width: var(--tap-target);
  height: var(--tap-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

.chat-window__toggle:hover {
  color: var(--text);
}

.chat-window__toggle svg {
  width: calc(var(--tap-target) / 2);
  height: calc(var(--tap-target) / 2);
}

/* ─── The peek: Steev's newest message, two lines ─── */

.chat-peek {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-xs) 0 var(--space-sm);
  border: 0;
  background: none;
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.45;
  text-align: left;
  cursor: pointer;
  touch-action: none;
}

.chat-peek__face {
  position: relative;
  flex: none;
  width: calc(var(--tap-target) / 2);
  height: calc(var(--tap-target) / 2);
}

.chat-peek__face .steev-face {
  width: 100%;
  height: 100%;
  color: currentColor;
}

.chat-peek__text {
  flex: 1;
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.chat-peek--unread .chat-peek__face::after {
  content: "";
  position: absolute;
  top: calc(var(--space-xs) * -1);
  right: calc(var(--space-xs) * -1);
  width: var(--space-sm);
  height: var(--space-sm);
  border-radius: var(--radius-pill);
  background: var(--text);
  box-shadow: 0 0 0 2px var(--chat-ground);
}

/* ─── The chat inside: the stream scrolls, the composer stays ─── */

.chat-window .message-stream {
  flex: 1;
  min-height: 0;
  padding-bottom: var(--space-lg);
  overflow-y: auto;
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
}

.chat-window .tool-status .typing-indicator {
  padding-inline: 0;
}

.chat-window .entry-dock {
  position: static;
  padding-inline: 0;
  background: none;
}

.coach-layout[data-chat="open"]:has(#chat-messages .chat-card[data-variant="poster"] .card-cta--primary) .entry-line-send:not(:disabled),
.coach-layout[data-chat="open"]:has(#chat-messages :is(.goal-card__confirm-btn:not(:disabled), .goal-card__confirm.htmx-request)) .entry-line-send:not(:disabled),
.coach-layout[data-chat="open"]:has(#chat-messages .form-pane) .entry-line-send:not(:disabled) {
  background: var(--chat-ground);
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--text);
}

/* ─── System states inside the window ─── */

.chat-window .entry-line--disabled .system-line-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  margin-inline-start: var(--space-sm);
  padding: 0 var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--action);
  box-shadow: var(--action-ring);
  color: var(--action-ink);
  text-decoration: none;
}

.chat-window .empty-state-card {
  border: 0;
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--border);
}

.chat-window .empty-state-title {
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
}

.coach-layout:has(.entry-line--disabled .system-line-link) #chat-messages :is(.chat-card[data-variant="poster"] .card-cta--primary, .goal-card__confirm-btn),
.coach-layout:has(#chat-messages .goal-card__confirm-btn:not(:disabled)) #chat-messages .chat-card[data-variant="poster"] .card-cta--primary {
  background: var(--surface-raised);
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--text);
}

/* ─── Narrow: a bottom sheet ─── */

@media (max-width: 1000px) {
  .chat-window {
    top: var(--chat-sheet-top, var(--coach-header-h));
    right: 0;
    bottom: 0;
    left: 0;
    border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  }

  .coach-layout[data-chat="open"][data-composing] .chat-window {
    top: var(--coach-header-h);
  }
}

/* ─── Wide: a floating window on the right ─── */

@media (min-width: 1000.02px) {
  .chat-window {
    top: calc(var(--coach-header-h) + var(--space-md));
    right: var(--screen-inset);
    bottom: var(--space-md);
    width: var(--chat-window-w);
    border-radius: var(--radius-hero);
  }

  .coach-layout[data-chat="collapsed"] .chat-window {
    width: var(--chat-peek-w);
  }

  .coach-layout[data-chat="open"] > .site-footer {
    margin-right: calc(var(--chat-window-w) + var(--screen-inset));
  }

  .chat-window__grab {
    display: none;
  }
}

/* ─── Motion: a short slide when the runner changes the state ─── */

.coach-layout[data-chat-moved][data-chat="open"] .chat-window {
  animation: chat-window-open var(--motion-arrive);
}

.coach-layout[data-chat-moved][data-chat="collapsed"] .chat-window {
  animation: chat-window-collapse var(--motion-arrive);
}

.chat-peek--unread .chat-peek__face::after {
  animation: chat-peek-dot var(--motion-arrive);
}

@keyframes chat-window-open {
  from { transform: translateY(var(--space-lg)); }
}

@keyframes chat-window-collapse {
  from { transform: translateY(calc(var(--space-lg) * -1)); }
}

@keyframes chat-peek-dot {
  from { transform: scale(0); }
}

@keyframes chat-fade {
  from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .coach-layout[data-chat-moved][data-chat] .chat-window,
  .chat-peek--unread .chat-peek__face::after {
    animation: chat-fade var(--transition-fast);
  }
}

/* ========================================================================
   P03 — Voice Breaks Chrome + P08 — Emphasis

   P03: Steev's significant sentences render without bubble chrome.
        --font-coach italic, poster-scale, 3-line drop-cap in --font-display.
        Applied to: design-kit specimen, poster reasoning paragraphs. Chat celebrations stay in flow as a regular
        scribble.

   P08: Inline emphasis. Neutral underline; preserves readability. .coach-emph is
        the standalone class for static specimens / openers; chat prose keeps
        markdown <strong> as plain bold.
   ======================================================================== */

.coach-voice-opener {
  font-family: var(--font-coach);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.75rem, 3.5vw, 3.375rem);
  line-height: 1.12;
  color: var(--text);
  letter-spacing: -0.012em;
  max-width: 22ch;
  margin: 0;
}

.coach-voice-opener .drop-cap {
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 900;
  font-size: 1.8em;
  line-height: 0.78;
  float: left;
  margin: 0.12em 0.18em -0.16em 0;
  color: var(--text-bright);
}

.coach-voice-opener__signature {
  margin-top: var(--space-md);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* P08 — Inline emphasis within static coach text (voice-openers, specimens). */
.coach-emph {
  border-bottom: 2px solid var(--border-strong);
  padding-bottom: 1px;
  /* Intentionally no text color shift — underline alone preserves readability
     and avoids a confetti-sprinkle effect when multiple phrases are marked. */
}

/* ========================================================================
   P06 — Embodied Steev state animations

   States are set as data-state on .steev-face. CSS drives the visuals.
   Respects prefers-reduced-motion (except sleeping/waking poses, which
   are static).
   ======================================================================== */

.steev-face .sv-el,
.steev-face .sv-er { transition: transform 140ms ease; transform-origin: center; transform-box: fill-box; }
.steev-face[data-state="blinking"] .sv-el,
.steev-face[data-state="blinking"] .sv-er { transform: scaleY(0.1); }

.steev-face .sv-m { transform-origin: 50% 50%; transform-box: fill-box; }
.steev-face[data-state="writing"] .sv-m {
  animation: steev-wiggle 200ms ease-in-out infinite alternate;
}
@keyframes steev-wiggle {
  from { transform: rotate(-0.5deg); }
  to   { transform: rotate(0.5deg); }
}

.steev-face[data-state="sleeping"] .sv-el,
.steev-face[data-state="sleeping"] .sv-er { transform: scaleY(0.3); }
.steev-face[data-state="sleeping"] .sv-m { filter: blur(0.3px); opacity: 0.85; }

@media (prefers-reduced-motion: reduce) {
  .steev-face[data-state="writing"] .sv-m { animation: none !important; }
  .steev-face[data-state="blinking"] .sv-el,
  .steev-face[data-state="blinking"] .sv-er { transform: none; }
}

/* =============================================================================
   GENERIC DETAILS - Base styles for <details>/<summary> elements
   ============================================================================= */

details summary {
  cursor: pointer;
  list-style: none;
}

details summary::-webkit-details-marker {
  display: none;
}

details summary::marker {
  display: none;
}

details[open] summary {
  margin-bottom: var(--space-md);
}

details summary:hover {
  color: var(--text);
}

/* ========================================================================
   P07 — Editorial Rhythm (page composition)

   Long-form pages are vertical compositions, not continuous feeds.
   Each section has:
     - Hairline top rule (1px, var(--border))  -- EXCEPT .composition-section--no-rule
     - Mono micro-label above the content
     - --space-section vertical padding (clamp 5rem..8rem)
     - No shared container background — each section breathes on --surface

   Usage:
     <section class="composition-section">
       <div class="composition-section__label">NEXT UP</div>
       <!-- section content -->
     </section>

   For the first section (no top rule needed) OR the Journey zone
   (memory rule: never a top rule above the Arc):
     <section class="composition-section composition-section--no-rule">...</section>
   ======================================================================== */

.composition-section {
  padding-block: var(--space-section);
}

.composition-section--no-rule {
  border-top: none;
}

.composition-section__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-sm);
}

/* =============================================================================
   CREDITS - the gift-code readout and Store's gift-code rows
   ============================================================================= */

.code-readout {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-md);
	padding: var(--space-sm) var(--space-sm) var(--space-sm) var(--space-md);
	border-radius: var(--radius-chip);
	background: var(--surface-sunken);
	box-shadow: inset 0 0 0 1px var(--border);
}

.code-readout code {
	font-family: var(--font-mono);
	font-size: var(--text-lg);
	letter-spacing: var(--tracking-wide);
	color: var(--text-bright);
	overflow-wrap: anywhere;
}

.pane-sub {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	margin-top: var(--space-md);
}

.pane-list .row-note {
	display: block;
	margin-top: var(--space-xs);
	font-family: var(--font-body);
	font-size: var(--text-sm);
	letter-spacing: 0;
	text-transform: none;
	color: var(--text-muted);
}

.pane-list .v small {
	margin-inline-start: var(--space-xs);
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	color: var(--text-muted);
}

.pane-list .credit-code-value {
	font-family: var(--font-mono);
	font-size: var(--text-sm);
	letter-spacing: var(--tracking-wide);
	color: var(--text-bright);
}

/* =============================================================================
   EMPTY STATES

   Rendered when a zone has no content yet (chat with no exchanges, leaderboard
   with no entries, etc.). Brutalist-editorial treatment: tracked mono label,
   condensed display heading, direct subtitle, optional CTA.
   ============================================================================= */

.chat-empty-state {
  display: flex;
  justify-content: center;
  padding: var(--space-xl) var(--inset);
}

/* A row appended live — a coach turn that spoke, the runner's own send —
   starts the conversation the card stood in for. */
.message-stream:has(> .exchange-group.exchange-arriving:not([hidden])) > .chat-empty-state {
  display: none;
}

.empty-state-card {
  max-width: 440px;
  width: 100%;
  padding: var(--space-xl) var(--inset-lg);
  background: var(--pane);
  border: 0;
  border-radius: var(--radius-card);
  box-shadow: var(--pane-ring);
  text-align: center;
}

.empty-state-label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wider);
  color: var(--text-faint);
  margin-bottom: var(--space-md);
}

.empty-state-title {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  line-height: 1.1;
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-sm);
  color: var(--text-bright);
}

.empty-state-subtitle {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--text-secondary);
  margin: 0 auto var(--space-lg);
  max-width: 36ch;
}


/* =============================================================================
   FORM PAGE - the page name as the big title, the fields in one flat pane,
   one volt, the quiet other ways under the pane; a message page is the title,
   one line and one way on.
   ============================================================================= */

.form-page {
	display: flex;
	flex-direction: column;
	gap: var(--space-md);
	width: 100%;
	max-width: var(--form-column);
	margin-inline: auto;
	padding-block: var(--space-xl) var(--space-2xl);
}

.form-page--message {
	padding-top: var(--space-2xl);
}

.form-page > .header-band {
	padding-bottom: var(--space-xs);
}

.form-page .header-band__title,
.form-pane__title {
	color: var(--text-bright);
}

.form-page > .notice-strip {
	margin: 0;
}

.form-page > .btn {
	align-self: stretch;
	margin-top: var(--space-sm);
}

.form-page--message > .btn {
	align-self: flex-start;
	min-width: 12rem;
}

.form-code {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wider);
	text-transform: uppercase;
	color: var(--text-muted);
}

.form-line {
	margin: 0;
	font-size: var(--text-lg);
	line-height: 1.45;
	color: var(--text);
	text-wrap: pretty;
}

.form-line a,
.form-quiet a,
.login-footer a {
	color: var(--text-bright);
	font-weight: var(--weight-semibold);
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.form-pane {
	display: flex;
	flex-direction: column;
	gap: var(--space-md);
	padding: var(--space-lg) var(--space-md);
	border-radius: var(--radius-card);
	background: var(--pane);
	box-shadow: var(--pane-ring);
	color: var(--text);
}

@media (min-width: 600px) {
	.form-pane {
		padding-inline: var(--space-lg);
	}
}

.form-pane > form {
	gap: var(--space-md);
}

.form-pane .form-group {
	margin: 0;
}

.form-pane .btn-block {
	margin-top: var(--space-sm);
}

.form-pane__title {
	margin: 0;
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: 900;
	line-height: 1;
	letter-spacing: 0.01em;
	text-transform: uppercase;
}

.form-pane__section {
	display: flex;
	flex-direction: column;
	gap: var(--space-sm);
	padding-top: var(--space-md);
	border-top: 1px solid var(--border);
}

.form-pane__label {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-wider);
	text-transform: uppercase;
	color: var(--text-secondary);
}

.form-pane__body {
	margin: 0;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--text);
}

.form-pane .pane-form__foot {
	margin-top: var(--space-sm);
}

.form-pane .pane-form__foot .btn {
	min-width: 7rem;
}

.form-pane--wide {
	max-width: var(--form-wide);
}

dialog#modal-container .form-pane {
	width: min(var(--form-column), 100%);
	max-height: 90vh;
	overflow-y: auto;
}

dialog#modal-container .form-pane--wide {
	width: min(var(--form-wide), 100%);
}

.form-quiet,
.login-footer {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-xs);
	margin: 0;
	padding: 0;
	border: 0;
	font-size: var(--text-sm);
	color: var(--text-secondary);
}

.form-quiet p,
.login-footer p {
	margin: 0;
}

.form-quiet .pane-link,
.login-footer .pane-link {
	min-height: var(--tap-target);
}

.form-quiet--row {
	flex-direction: row;
	flex-wrap: wrap;
	align-items: center;
	gap: 0 var(--space-sm);
}

.form-check {
	display: flex;
	align-items: flex-start;
	gap: var(--space-sm);
	font-size: var(--text-sm);
	line-height: 1.45;
	color: var(--text);
	cursor: pointer;
}

.form-check input {
	margin-top: 0.2em;
	flex-shrink: 0;
}

/* =============================================================================
   FORMS - Form components and inputs
   ============================================================================= */

/* =============================================================================
   FORM RHYTHM — unified two-gap system carried on bare <form> and on
   .form--labels-below, so non-form containers (a div of form-groups driven by
   hx-include buttons) opt into the identical rhythm with the grammar class.
   ----------------------------------------------------------------------------
   Tight default 8px gap between adjacent children; section-break classes
   get an additional 16px margin-top, producing a loose 24px gap effective
   (parent gap + child margin-top stack in flex). Inline forms opt out via
   .form-inline (defined at the end of this file).

   Labels-below containers widen the default gap to 16px: the flipped caption
   sits 4px under its own field, and the next group must start clearly farther
   away than that for the label to read upward, never downward.
   ============================================================================= */

form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.form--labels-below {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

form > * + button[type="submit"] {
  margin-top: var(--space-md);
}

.form-label {
  display: block;
  margin-bottom: var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-muted);
}

input:is([type="radio"], [type="checkbox"]) {
  accent-color: var(--text-bright);
}

/* Date inputs - neutral placeholder color (avoids theme-specific muted colors) */
input[type="date"]:not(:valid) {
  color: var(--text-secondary);
}
input[type="date"]::-webkit-datetime-edit-text,
input[type="date"]::-webkit-datetime-edit-month-field,
input[type="date"]::-webkit-datetime-edit-day-field,
input[type="date"]::-webkit-datetime-edit-year-field {
  color: inherit;
}

.form-error {
  color: var(--danger-text);
  font-size: var(--text-sm);
  margin-top: var(--space-xs);
}

.form-success {
  color: var(--text);
  font-size: var(--text-sm);
}

.form-success::before {
  content: "✓" / "";
  margin-inline-end: var(--space-xs);
}

.form-caution {
  color: var(--text);
  font-size: var(--text-sm);
}

.form-hint {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-muted);
  margin-top: var(--space-xs);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}

/* =============================================================================
   PASSWORD INPUT
   ============================================================================= */

.password-input-wrapper {
  position: relative;
  display: flex;
  align-items: center;
}

.password-input-wrapper .input--filled {
  padding-right: 44px;
}

.password-toggle {
  position: absolute;
  right: var(--space-sm);
  background: transparent;
  border: none;
  padding: var(--space-xs);
  cursor: pointer;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
}

.password-toggle:hover {
  color: var(--text);
}

.eye-icon {
  width: 20px;
  height: 20px;
}

/* =============================================================================
   TIME INPUT
   ============================================================================= */

.time-input-group {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

/* `.input` (below) also sets `width: 100%` at equal specificity and wins on
   order, so this has to name both classes — a bare `.time-input` here is dead
   and every mm/ss field stretches container-wide. Width is intrinsic: two
   digits plus the padding it carries. */
.input.time-input {
  width: calc(3ch + 2 * var(--space-sm));
  text-align: center;
  padding-inline: var(--space-sm);
}

.input.time-input::-webkit-inner-spin-button,
.input.time-input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.time-input-sep {
  font-size: var(--text-xl);
  color: var(--text-muted);
  font-weight: var(--weight-semibold);
}

/* =============================================================================
   DISTANCE INPUT
   ============================================================================= */

.distance-input-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.distance-select {
  flex: 1;
}

.custom-distance-input {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

/* Sized to its content — a distance is at most a few digits and a decimal —
   so the field never stretches to fill whatever container it lands in. */
.custom-distance-input .input--filled {
  width: calc(6ch + 2 * var(--inset));
}

.input-suffix {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ─── Toggle Switch ─────────────────────────────────────────────────── */

.toggle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  cursor: pointer;
}

.toggle-label {
  flex: 1;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text);
}

.toggle-hint {
  width: 100%;
  font-size: var(--text-xs);
  color: var(--text-muted);
  order: 3;
}

.toggle-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: relative;
  width: 40px;
  height: 22px;
  background: var(--border-strong);
  border-radius: var(--radius-pill);
  transition: background 0.2s;
  flex-shrink: 0;
}

.toggle-slider::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  background: var(--text-secondary);
  border-radius: 50%;
  transition: transform 0.2s;
}

.toggle-input:checked + .toggle-slider {
  background: var(--text-bright);
}

.toggle-input:checked + .toggle-slider::after {
  background: var(--surface);
  transform: translateX(18px);
}

.push-time-select {
  max-width: 160px;
}

/* ─── Send-workout triggers (quiet typographic actions) ─────────────── */

.send-workout-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}

.send-workout-btn {
  background: transparent;
  border: none;
  padding: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--transition-fast);
}

.send-workout-btn::after {
  content: " →";
  color: var(--text-muted);
  transition: color var(--transition-fast);
}

.send-workout-btn:hover:not(:disabled),
.send-workout-btn:hover:not(:disabled)::after {
  color: var(--text-bright);
}

.send-workout-btn:disabled {
  opacity: 0.5;
  cursor: progress;
}

.send-workout-status {
  flex: 1;
  font-size: var(--text-sm);
}

/* =============================================================================
   SEGMENTS - Interval entry inside the unified workout-capture form
   ============================================================================= */

/* Both modes share the same row anatomy: a flex container with a number
   gutter on the left (auto-numbered via CSS counter) and a data box on
   the right. The mode-specific selectors mostly exist for the rows
   *containers* and inner row-line variants; the row + cell rules below
   apply uniformly. */

.segment-rows,
.segment-splits-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  counter-reset: segment;
}

.segment-row,
.segment-splits-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  counter-increment: segment;
}

/* Numeric badge ("1.", "2.") rendered in the gutter beside each row.
   Uses a CSS counter so add/remove/reorder works without JS bookkeeping. */
.segment-row-number {
  /* `3ch` covers "99." comfortably; sized by character count, not magic em. */
  flex: 0 0 3ch;
  text-align: end;
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.segment-row-number::before {
  content: counter(segment) ".";
}

.segment-row-data {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-xs) var(--space-sm);
  /* Baseline border-bottom per row — same input-style baseline rule;
     replaces the previous surface-sunken card-around-row that visually
     fragmented rows from their parent form. */
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}

/* Last row in the rows container drops the baseline rule — the row count
   is variable and the trailing rule reads as a "more rows expected" hint
   that we don't want at the visual end of the section. */
.segment-row:last-child .segment-row-data,
.segment-splits-row:last-child .segment-row-data {
  border-bottom: none;
}

.segment-row-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

.segment-row-line--elev {
  padding-inline-start: var(--inset-sm);
}

/* Inputs intrinsic-size from their `size` attribute. Override
   the default width so each input takes only the width it needs. */
.segment-row-line input,
.segment-splits-row input {
  width: auto;
  flex: 0 0 auto;
}

.segment-input-mins,
.segment-input-secs,
.segment-input-hr,
.segment-input-dist,
.segment-input-up,
.segment-input-down {
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.segment-input-type {
  font-size: var(--text-sm);
  /* Flat-line select: strip chevron background, no extra right padding. */
  appearance: none;
  background-image: none;
  padding-right: 0;
}

.segment-colon {
  color: var(--text-muted);
}

/* One "mins : secs" reading — the row wraps around it, never through it. */
.segment-time {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  flex: 0 0 auto;
}

/* The row's last column, level with its first line whatever the row wraps
   to: the data box's top padding plus a line box the inputs' size, pinned to
   the top so the mobile touch-target height only extends it downward. */
.segment-remove-btn {
  flex: 0 0 auto;
  align-items: flex-start;
  padding: var(--space-xs) var(--space-xs) 0;
  font-size: var(--text-lg);
  color: var(--text-muted);
}

.segment-add-btn {
  align-self: flex-start;
  margin-top: var(--space-xs);
}

/* The form's wrapper chain (.exchange-group > .user-workout-card > .form-pane)
   normally lets each child auto-size to content, which means opening the
   <details> for extras makes the whole pane widen and closing it makes it
   shrink — jarring. Force the user-workout-card and its pane to full width
   (the pane's own max-width still caps it). Make the wrapper a flex row that
   end-justifies its child — the parent's `align-self: flex-end` no longer
   has spare space to do that work once we go to 100% width. Scoped via
   :has() so logged-workout cards keep their natural shrink-wrap. */
.user-workout-card:has([data-capture-intervals-section]) {
  width: 100%;
  max-width: 100%;
  display: flex;
  justify-content: flex-end;
}

.user-workout-card:has([data-capture-intervals-section]) > .form-pane {
  width: 100%;
}

[data-capture-intervals-section]:has([name="segments_mode"][value="custom"]:checked) [data-segment-pane="splits"],
[data-capture-intervals-section]:has([name="segments_mode"][value="splits"]:checked) [data-segment-pane="custom"] {
  display: none;
}

/* ----- Splits mode --------------------------------------------------------
   Splits rows share most styling with custom rows (see merged rules above);
   only the residual-partial label is splits-only. */

/* Only shown for the residual partial split (e.g. "+0.3km") — full-stride
   rows omit this label entirely since the row number conveys position. */
.segment-splits-label {
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* Splits rows are flat: all inputs (mins/sec/bpm/↑m/↓m) sit as direct
   children of the data box on a single line, wrapping individually when
   horizontal space runs out. Custom rows keep the stacked column layout
   from .segment-row-data because they carry more fields per line. */
.segment-splits-row .segment-row-data {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
}

/* HR + elev are off by default in splits mode; the toggle reveals them.
   Nothing to hide in custom mode — both stay visible. */
[data-capture-intervals-section]:not(:has([data-splits-extras-toggle]:checked)) .segment-splits-row :is(.segment-input-hr, .segment-input-up, .segment-input-down) {
  display: none;
}

/* =============================================================================
   FORM RHYTHM — opt-out for inline forms (search, comment, micro-action)
   ============================================================================= */

.form-inline {
  display: inline-flex;
  flex-direction: row;
  align-items: baseline;
  gap: var(--inset-sm);
}


/* =============================================================================
   INPUT PRIMITIVE — one canonical .input + opt-in variants
   ----------------------------------------------------------------------------
   Base carries shared mechanical concerns (width, transitions, browser reset).
   Variants opt in to the visual contract:
     .input--filled  — page form field
     .input--voice   — chat composer (defined in coach-input.css)
     .input--card    — card field
     .input--sm      — density modifier; combine with a variant
                        (e.g. .input--card.input--sm) — each variant
                        defines what --sm means in its context
   ============================================================================= */

.input {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  transition:
    border-color var(--transition-fast),
    background-color var(--transition-fast),
    box-shadow var(--transition-fast);
}

.input:focus {
  outline: none;
}

select.input {
  appearance: none;
}

input[type="number"].input {
  appearance: textfield;
  -webkit-appearance: textfield;
}

/* ─── .input--filled — page form aesthetic ─────────────────────────────── */
.input--filled {
  font-family: var(--font-input);
  background-color: var(--surface-sunken);
  border: 0;
  box-shadow: inset 0 0 0 1px var(--border);
  border-radius: var(--radius-chip);
  padding: 0.85em var(--inset);
  font-size: var(--text-base);
  color: var(--text-bright);
}

.input--filled:focus {
  box-shadow: inset 0 0 0 var(--rule-focus) var(--text-secondary);
}

.input--filled::placeholder {
  color: var(--text-muted);
  font-family: var(--font-input);
}

select.input--filled {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position:
    calc(100% - var(--space-md) - 5px) 50%,
    calc(100% - var(--space-md)) 50%;
  background-size: 5px 5px;
  background-repeat: no-repeat;
  padding-right: var(--space-2xl);
}

textarea.input--filled {
  min-height: 100px;
  resize: vertical;
  padding: 0.75em var(--inset);
}

/* ─── .input--card — card field ─────────────────────────────────────────── */
.input--card {
  background: var(--surface-sunken);
  border: 0;
  box-shadow: inset 0 0 0 1px var(--border);
  border-radius: var(--radius-chip);
  padding: var(--space-sm);
  font-size: var(--text-base);
  color: var(--text);
  font-family: inherit;
}

.input--card:focus {
  box-shadow: inset 0 0 0 var(--rule-focus) var(--text-secondary);
}

.input--card::placeholder {
  color: var(--text-muted);
}

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

/* ─── .input--sm — density modifier; per-variant rule (see header) ─────── */
.input--card.input--sm {
  border-radius: var(--radius-chip);
  padding: var(--space-xs) var(--inset-sm);
  font-size: var(--text-sm);
}

/* =============================================================================
   FIELD ERROR SLOT

   Present in the markup at all times so a failure response can rewrite every
   slot of a form out-of-band; collapses to nothing while empty.
   ============================================================================= */

.field-error {
  color: var(--danger-text);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  margin-top: var(--space-xs);
}

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

.form-group:has(> .field-error:not(:empty)) .input--filled {
  box-shadow: inset 0 0 0 var(--rule-focus) var(--danger);
}

/* =============================================================================
   LABELS-BELOW GRAMMAR

   Value / rule / label: the field reads first, its label sits under it as a
   caption. `<label for>` stays first in the DOM (a11y, click target) and CSS
   does the flipping. Rank order inside a group:
     -2  the field itself (and any picker chrome that follows it)
     -1  the field's error slot
      0  the label, then hints and secondary actions in DOM order

   Composed by `.form--labels-below`.

   Proximity: the label sits one 4px step under its own field — far tighter
   than the 16px rhythm gap before the next group — so it reads as a caption
   of the field above it, never of the field below.
   ============================================================================= */

.form--labels-below .form-group:has(> .form-label) {
  display: flex;
  flex-direction: column;
}

.form--labels-below .form-group:has(> .form-label) > .form-label {
  margin: var(--space-xs) 0 0;
}

.form--labels-below .form-group:has(> .form-label) > .form-label + * {
  order: -2;
}

.form--labels-below .form-group:has(> .form-label) > .field-error {
  order: -1;
}

/* =============================================================================
   CAPTURE FORM (log / mark complete / edit)
   ============================================================================= */

.form-prefix {
  position: relative;
}

.form-prefix > .capture-type-mark {
  position: absolute;
  top: 50%;
  left: var(--space-md);
  width: 20px;
  height: 20px;
  margin: 0;
  transform: translateY(-50%);
  color: var(--text-secondary);
  pointer-events: none;
}

.form-prefix > select {
  width: 100%;
  padding-left: calc(var(--space-md) + 20px + var(--space-sm));
}

.capture-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md);
}

.capture-grid .time-input-group {
  width: 100%;
}

.capture-grid .time-input-field {
  flex: 1;
}

.capture-grid .input.time-input {
  width: 100%;
}

.form-pane input[type="number"]::-webkit-inner-spin-button,
.form-pane input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.form-more {
  border-top: 1px solid var(--border);
}

.form-more > summary {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--tap-target);
  padding-right: var(--space-xl);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-bright);
}

.form-more > summary > .pane-cue {
  top: 50%;
  right: 0;
  transform: translateY(-50%);
}

.form-more[open] > summary > .pane-cue {
  transform: translateY(-50%) rotate(90deg);
}

.form-more__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding-bottom: var(--space-sm);
}

form:has([name="workout_type"] option:checked[data-hide~="custom_type"]) [data-capture-custom-type],
form:has([name="workout_type"] option:checked[data-hide~="metrics"]) :is([data-capture-metrics-section], [data-capture-extras-section]),
form:has([name="workout_type"] option:checked[data-hide~="distance"]) [data-capture-distance-fields],
form:has([name="workout_type"] option:checked[data-hide~="cadence_elevation"]) :is([data-capture-cadence-field], [data-capture-elevation-row]),
form:has([name="workout_type"] option:checked[data-hide~="gear"]) :is([data-capture-gear-field], [data-extras-summary="gear"]),
form:has([name="workout_type"] option:checked[data-hide~="intervals"]) :is([data-capture-intervals-section], [data-extras-summary="intervals"]),
form:has([name="workout_type"] option:checked:not([data-hide~="gear"])) :is([data-extras-summary="intervals"], [data-extras-summary="metrics"]),
form:has([name="workout_type"] option:checked:not([data-hide~="intervals"])) [data-extras-summary="metrics"] {
  display: none;
}

/* =============================================================================
   GOAL-CARD.CSS — the one-card onboarding: write, read back, confirm

   The coach asks in its own voice (a .scribble), then hands the runner an
   index card (.chat-card's chrome). What the runner writes wears their slant,
   the read-back is data, and a gap is a note to fix, never an error.
   ============================================================================= */

#goal-card-slot:not(:has(#goal-card)) {
  display: none;
}

.goal-card[data-voice="coach"] {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: min(100%, var(--chat-card-measure));
}

/* A coach turn's face-to-words spacing (.exchange-group), not the card's. */
.goal-card > .coach-face {
  margin-bottom: calc(var(--space-sm) - var(--space-md));
}

.goal-card__sheet {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface-raised);
  font-size: var(--text-sm);
}

/* ── Write ── */

.goal-card__write {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.goal-card__field {
  position: relative;
}

/* The runner's own words, so the runner's slant — the composer's rule. Room at
   the foot for Send, which sits inside the field like the composer's. */
textarea.goal-card__text {
  display: block;
  resize: none;
  min-height: 7.5em;
  padding-bottom: calc(var(--tap-target) + var(--space-xs));
  font-style: italic;
  line-height: 1.5;
}

.goal-card__text::placeholder {
  font-style: italic;
}

/* forms.css keys the error state on a .form-group; the card's field is not one. */
.goal-card__write:has(> #field-error-text:not(:empty)) .goal-card__text {
  box-shadow: inset 0 0 0 var(--rule-focus) var(--danger);
}

/* The composer's Send: ink only, no fill. */
.goal-card__send {
  position: absolute;
  right: var(--space-xs);
  bottom: var(--space-xs);
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  padding: 0 var(--space-sm);
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-bright);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  cursor: pointer;
}

.goal-card__send:hover:not(:disabled) {
  text-decoration: underline;
}

.goal-card__send:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

.goal-card__send:disabled {
  color: var(--text-faint);
  cursor: progress;
}

.goal-card__hint,
.goal-card__retry {
  margin: 0;
  text-wrap: pretty;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* What a runner might write, so the runner's slant again. */
.goal-card__examples {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: var(--space-xs) 0;
}

.goal-card__example-list {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--text-secondary);
  text-wrap: pretty;
}

/* One quote per row, split like the read-back rows that replace them. */
.goal-card__example-list li {
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-sm) 0;
}

.goal-card__example-list li:first-child {
  padding-top: 0;
}

.goal-card__example-list li:last-child {
  padding-bottom: 0;
}

.goal-card__example-list li + li {
  border-top: 1px solid var(--border);
}

/* Decorative, so silent to a screen reader. */
.goal-card__example-list li::before {
  content: "\201C";
  content: "\201C" / "";
  flex: none;
  width: 0.6em;
  font-style: normal;
  font-weight: var(--weight-bold);
  font-size: var(--text-xl);
  line-height: 0.9;
  color: var(--text-muted);
}

/* utilities.css shows an indicator as flex; this one keeps its own line. */
.goal-card__reading.htmx-request {
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.goal-card__reading .typing-indicator {
  padding: 0;
}

/* While a read is out, the old read-back is about to go: step it back. */
.goal-card:has(.goal-card__reading.htmx-request) :is(.goal-card__block, .goal-card__gaps, .goal-card__confirm, .goal-card__retry) {
  filter: saturate(0.4);
  pointer-events: none;
}

.goal-card__write:has(.goal-card__reading.htmx-request) .goal-card__hint {
  display: none;
}

/* ── Read back ── */

.goal-card__block {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border);
}

.goal-card__block + .goal-card__block {
  padding-top: 0;
  border-top: 0;
}

.goal-card__label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-muted);
}

.goal-card__rows {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.goal-card__row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: baseline;
  padding: var(--space-sm) 0;
}

.goal-card__row + .goal-card__row {
  border-top: 1px solid var(--border);
}

.goal-card__row:first-child {
  padding-top: 0;
}

/* The role is a word: the primary in bright ink, a secondary in plain ink.
   The rows share one column for it (subgrid), so every title starts on the
   same line. */
.goal-card__role {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}

.goal-card__role--primary {
  color: var(--text-bright);
  font-weight: var(--weight-semibold);
}

.goal-card__role--secondary {
  color: var(--text-secondary);
  font-weight: var(--weight-normal);
}

.goal-card__goal {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  overflow-wrap: anywhere;
}

.goal-card__title {
  font-weight: var(--weight-semibold);
  color: var(--text-bright);
}

.goal-card__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
  margin-top: var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* A value the runner can change wears a pill. Its tap target overlays the line
   rather than growing it, so a row of pills stays a line of facts. */
.goal-card__tap {
  position: relative;
  padding: 2px var(--space-sm);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface-sunken);
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.goal-card__tap::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: var(--tap-target);
  transform: translateY(-50%);
}

@media (hover: hover) {
  .goal-card__tap:hover {
    border-color: var(--text-muted);
    color: var(--text-bright);
  }
}

.goal-card__missing {
  border-color: currentColor;
  color: var(--danger-text);
  font-weight: var(--weight-semibold);
}

/* ── Editing one value ── */

/* A form's own display rule outranks the UA rule that hides a closed popover,
   so the card says both states itself. */
.goal-card__edit {
  width: min(22rem, calc(100vw - 2 * var(--space-lg)));
  padding: var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sheet);
  background: var(--surface-raised);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}

.goal-card__edit:not(:popover-open) {
  display: none;
}

.goal-card__edit:popover-open {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.goal-card__edit::backdrop {
  background: var(--shadow);
}

.goal-card__edit .goal-card__label,
.goal-card__edit .form-hint {
  margin: 0;
}

.goal-card__edit-ask {
  margin: 0;
  color: var(--text-bright);
  font-weight: var(--weight-semibold);
}

.goal-card__edit-distance {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.goal-card__edit-distance .input {
  flex: 1;
  min-width: 0;
}

.goal-card__edit-unit {
  color: var(--text-secondary);
}

.goal-card__edit-date {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.goal-card__edit-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.goal-card__fitness {
  margin: 0;
  color: var(--text);
}

/* ── Gaps ── */

/* A sunken note with a hairline and full-ink text: the dot marks what is
   missing, the words say what to add without raising their voice. */
.goal-card__gaps {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  list-style: none;
}

.goal-card__gap {
  position: relative;
  padding-left: var(--space-md);
  color: var(--text);
  line-height: 1.45;
}

.goal-card__gap::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--danger);
}

/* ── Confirm ── */

.goal-card__confirm {
  margin: 0;
}

.header-band {
	grid-column: 1 / -1;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 3px;
	width: 100%;
	min-width: 0;
	padding: 4px 0 6px;
}

.header-band__eyebrow {
	font-family: var(--font-display);
	font-size: 15px;
	font-weight: 800;
	line-height: 1;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--e-strong, currentColor);
}

.header-band__meta {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	font-weight: 400;
	line-height: 1.35;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	opacity: 0.85;
}

.header-band__title {
	max-width: 100%;
	overflow: hidden;
	padding-block: 0.15em;
	margin-block: -0.15em;
	font-family: var(--font-display);
	font-size: 40px;
	font-weight: 900;
	line-height: 1;
	letter-spacing: 0.01em;
	text-transform: uppercase;
	overflow-wrap: anywhere;
}

.header-band__mantra {
	margin-top: 3px;
	font-family: var(--font-body);
	font-size: 12px;
	font-style: italic;
	line-height: 1.4;
	opacity: 0.85;
}

@media (min-width: 1000.02px) {
	.header-band__title {
		font-size: 52px;
	}
}

/* Activity marks — composed base + role badge. Geometry per icons/ICONS.md:
   24x24 grid, fixed 2px stroke, badge at 60% bottom-right knocked out of the
   surface. Size comes from the caller's class; colour from currentColor. */

.steev-icon-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.steev-icon {
  width: 100%;
  height: 100%;
  display: block;
}

.steev-icon__badge {
  position: absolute;
  right: -10%;
  bottom: -10%;
  width: 60%;
  height: 60%;
  background: var(--icon-knockout, var(--surface-raised));
  border-radius: 50%;
}

.insights {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	align-content: start;
	gap: 9px;
	padding: 9px 13px 13px;
	background: var(--e-wash, var(--surface));
	color: var(--text);
	font-family: var(--font-body);
}

.insights[data-intensity] {
	color: var(--wash-ink);
}

.insight-pane,
.insight-tile {
	min-width: 0;
	border-radius: var(--radius-card);
	background: var(--pane);
	box-shadow: var(--pane-ring);
}

.insight-pane {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin: 0;
	padding: 12px var(--pane-pad-inline);
	border: 0;
	color: inherit;
	font: inherit;
	text-align: start;
}

.insight-pane--plan {
	padding-inline-end: 0;
}

button.insight-pane,
.insight-session,
.insight-tile__face,
.insight-tile__collapse,
.insight-bars__latest-hit,
.insight-window__current,
.insight-window__pill {
	cursor: pointer;
}

.insight-pane:focus-visible,
.insight-session:focus-visible,
.insight-tile__face:focus-visible,
.insight-tile__collapse:focus-visible,
.insight-bars__latest-hit:focus-visible,
.insight-window__current:focus-visible,
.insight-window__pill:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

.insight-pane__row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 8px;
}

.insight-pane__eyebrow,
.insight-pane__caption,
.insight-pane__footer,
.insight-pane__delta,
.insight-move,
.insight-session__previous,
.progress-call__written,
.insight-tile__label,
.insight-tile__caption,
.insight-tile__detail,
.insight-figure__caption,
.insight-window__current,
.insight-window__pill {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	font-weight: 400;
	line-height: 1.35;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.insight-pane__eyebrow,
.insight-pane__caption,
.insight-pane__footer,
.insight-pane__delta,
.insight-move,
.insight-tile__label,
.insight-figure__caption {
	opacity: 0.75;
}

.insight-pane__time,
.insight-pane__headline,
.insight-tile__value,
.insight-figure__value,
.progress-call__status {
	font-family: var(--font-display);
	font-weight: 500;
	line-height: 1;
	letter-spacing: 0.01em;
}

.insight-pane__time,
.insight-figure__value {
	font-size: 26px;
}

.insight-pane__time {
	margin-top: 4px;
}

.insight-pane__headline,
.insight-tile__value {
	font-size: 22px;
}

.insight-pane__headline {
	flex-shrink: 0;
	white-space: nowrap;
}

.insight-pane__row .insight-pane__caption {
	text-align: right;
}

.insight-pane__delta {
	margin-inline-start: 8px;
}

.insight-meter {
	height: 4px;
	margin-top: 4px;
	border-radius: var(--radius-pill);
	background: color-mix(in srgb, var(--wash-ink) 18%, transparent);
}

.insight-meter__fill {
	display: block;
	height: 100%;
	border-radius: inherit;
	width: calc(var(--fill) * 100%);
	background: color-mix(in srgb, var(--wash-ink) 70%, transparent);
}

.insight-moves {
	display: flex;
	flex-direction: column;
	gap: 2px;
	margin: 4px 0 0;
	padding: 0;
	list-style: none;
}

.insight-move {
	display: flex;
	align-items: baseline;
	gap: 8px;
}

.insight-move > :first-child {
	white-space: nowrap;
}

.insight-move__label {
	min-width: 0;
}

.insight-move__delta {
	margin-inline-start: auto;
	white-space: nowrap;
}

.insight-phase {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
	padding-inline: var(--pane-pad-inline);
}

.insight-pane__basis {
	font-size: 12px;
	line-height: 1.45;
	opacity: 0.8;
}

.insight-pane__footer {
	margin: 0;
}

.progress-call {
	--call-first-line: calc(12.5px * 1.45);
	flex-direction: row;
	align-items: flex-start;
	gap: 10px;
}

.progress-call:has(.progress-call__status) {
	--call-first-line: 24px;
}

.progress-call__face {
	flex: none;
	width: 22px;
	height: 22px;
	margin-top: calc((var(--call-first-line) - 22px) / 2);
}

.progress-call__face .steev-face {
	width: 22px;
	height: 22px;
	color: currentColor;
}

.progress-call__body {
	display: flex;
	flex-direction: column;
	gap: 5px;
	min-width: 0;
}

.progress-call__status {
	font-size: 24px;
	letter-spacing: 0.02em;
	text-transform: uppercase;
}

.progress-call__read {
	font-size: 12.5px;
	line-height: 1.45;
}

.progress-call__written {
	margin-top: 2px;
	opacity: 0.6;
}

.insight-bars {
	display: block;
	width: 100%;
	height: 36px;
	margin: 8px 0 12px;
	overflow: visible;
}

.insight-bars-wrap {
	position: relative;
}

.insight-bars__latest {
	fill: color-mix(in srgb, var(--wash-ink) 70%, transparent);
}

.insight-bars__missed {
	fill: none;
	stroke: var(--danger);
	stroke-width: 5px;
	stroke-linecap: round;
	vector-effect: non-scaling-stroke;
}

.insight-bars__latest-hit {
	position: absolute;
	right: 0;
	bottom: 0;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 0;
	background: transparent;
}

.insight-bars__bar {
	fill: color-mix(in srgb, var(--wash-ink) 28%, transparent);
}

.insight-bars__tick,
.insight-bars__planned {
	fill: none;
	stroke: color-mix(in srgb, var(--wash-ink) 45%, transparent);
	stroke-width: 1.5px;
	vector-effect: non-scaling-stroke;
}

.insight-spark {
	display: block;
	width: 100%;
	height: 20px;
	margin-top: 6px;
	overflow: visible;
}

.insight-spark__line {
	fill: none;
	stroke: color-mix(in srgb, var(--wash-ink) 70%, transparent);
	stroke-width: 1.5px;
	stroke-linejoin: round;
	stroke-linecap: round;
}

.insight-pane--sessions {
	gap: 0;
	padding-block: 2px;
}

.insight-session {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 8px;
	width: 100%;
	min-height: 44px;
	margin: 0;
	padding: 0;
	border: 0;
	border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent);
	background: none;
	color: inherit;
	font: inherit;
	font-size: var(--text-2xs);
	text-align: start;
}

.insight-session:first-child {
	border-top: 0;
}

.insight-session__title {
	min-width: 0;
}

.insight-session__target {
	margin-inline-start: 6px;
	opacity: 0.7;
}

.insight-session__previous {
	display: block;
	margin-top: 2px;
	opacity: 0.6;
}

.insight-session__actual {
	font-weight: 500;
	white-space: nowrap;
}

.insight-window {
	margin: -12px calc(-1 * var(--pane-pad-inline)) -10px;
}

.insight-window--tiles {
	grid-column: 1 / -1;
	margin: 0;
}

.insight-window__current {
	display: flex;
	justify-content: flex-end;
	align-items: center;
	min-height: 44px;
	padding: 0 var(--pane-pad-inline);
	list-style: none;
	opacity: 0.75;
}

.insight-window__current::-webkit-details-marker {
	display: none;
}

.insight-window__pills {
	display: flex;
	justify-content: flex-end;
	gap: 4px;
	padding: 0 9px 10px;
}

.insight-window__pill {
	min-width: 44px;
	min-height: 44px;
	padding: 0 8px;
	border: 0;
	border-radius: var(--radius-pill);
	background: none;
	color: inherit;
}

.insight-window__pill[aria-current] {
	background: color-mix(in srgb, currentColor 14%, transparent);
}

.insight-figures {
	display: flex;
	gap: 20px;
}

.insight-figure {
	display: flex;
	flex: 1 1 0;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.insight-figure__delta {
	margin-inline-start: 2px;
	font-size: 0.5em;
	vertical-align: 0.45em;
	opacity: 0.85;
}

.insight-tiles {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 9px;
}

.insight-tile--open {
	grid-column: span 2;
}

.insights-packed .insight-tiles {
	grid-auto-flow: row dense;
	grid-auto-rows: 4px;
	row-gap: 0;
	align-items: start;
	margin-bottom: -9px;
}

.insights-packed .insight-tiles > * {
	grid-row: span var(--rows, 1);
}

.insight-tile__face {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 4px;
	width: 100%;
	min-height: 44px;
	margin: 0;
	padding: 10px 12px;
	border: 0;
	border-radius: inherit;
	background: none;
	color: inherit;
	font: inherit;
	text-align: start;
}

.insight-tile--open .insight-tile__face {
	display: none;
}

.insight-tile__caption,
.insight-tile__detail {
	opacity: 0.6;
}

.insight-tile--moment:not(.insight-tile--open) {
	outline: 1.5px solid var(--moment);
	outline-offset: -1.5px;
}

.insight-tile--moment .insight-tile__value {
	color: var(--moment-text);
}

.insight-tile__card {
	display: flex;
	flex-direction: column;
	padding: 4px;
}

.insight-tile__card > .chat-card {
	align-self: stretch;
	max-width: none;
	padding: 8px 8px 0;
	border: 0;
	border-radius: 0;
	background: none;
	box-shadow: none;
}

.insight-tile__card[hidden] {
	display: none;
}

.insight-tile__collapse {
	align-self: center;
	display: grid;
	place-items: center;
	width: 44px;
	height: 32px;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
}

.insight-tile__collapse svg {
	width: 18px;
	height: 18px;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.insight-empty {
	grid-column: 1 / -1;
}

.insight-tile[data-moved].insight-tile--open .insight-tile__card,
.insight-tile[data-moved]:not(.insight-tile--open) .insight-tile__face {
	animation: insight-tile-arrive var(--motion-arrive);
}

@keyframes insight-tile-arrive {
	from {
		opacity: 0;
		transform: translateY(var(--space-sm));
	}
}

@media (prefers-reduced-motion: reduce) {
	.insight-tile[data-moved].insight-tile--open .insight-tile__card,
	.insight-tile[data-moved]:not(.insight-tile--open) .insight-tile__face {
		animation: chat-fade var(--transition-fast);
	}
}

@media (min-width: 1000.02px) {
	.insights {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		align-items: start;
		gap: 12px;
		padding: 12px 22px 22px;
	}

	.insights-packed .insights {
		grid-auto-flow: row dense;
		grid-auto-rows: 4px;
		row-gap: 0;
		padding-bottom: 10px;
	}

	.insights-packed .insights > * {
		grid-row: span var(--rows, 1);
	}

	.coach-layout[data-chat="open"] .insights {
		margin-right: calc(var(--chat-window-w) + 22px);
	}

	.insight-pane--call {
		grid-column: span 2;
	}

	.insight-pane--plan,
	.insight-phase {
		grid-column: 1 / -1;
	}

	.insight-tiles {
		display: contents;
	}
}

/* =============================================================================
   MESSAGE-BOARD.CSS - Race chat (leaderboard-scoped message board)

   Panel + header + guidelines callout + input form + empty/error notices
   + scrollable list of messages with hover-revealed delete. Rate-limited
   via config/app.toml (max messages/day/user). Visibility gated on
   leaderboard.visible = true server-side.
   ============================================================================= */

.message-board-panel {
  margin-top: var(--space-md);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border);
}

.message-board-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-md);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--surface-sunken);
}

.message-board-title {
  margin: 0;
  font-size: var(--text-lg);
}

.message-board-race {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.message-board-guidelines {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  padding: var(--space-sm) var(--inset);
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  border-left: 2px solid var(--text-muted);
}

.message-board-form {
  margin-bottom: var(--space-lg);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--surface-sunken);
}

.message-board-input-row {
  display: flex;
  gap: var(--space-sm);
  align-items: flex-end;
}


.message-board-error {
  background: var(--danger-faint);
  color: var(--danger-text);
  padding: var(--space-sm) var(--inset);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-md);
  font-size: var(--text-sm);
}

.message-board-notice {
  text-align: center;
  padding: var(--space-md) var(--inset);
  margin-bottom: var(--space-md);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

.message-board-notice p {
  margin-bottom: var(--space-sm);
  color: var(--text-muted);
}

.message-board-empty {
  text-align: center;
  padding: var(--space-xl) var(--inset-lg);
  color: var(--text-secondary);
}

.message-board-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-height: 400px;
  overflow-y: auto;
}

.message-board-message {
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-sm);
  background: transparent;
  border-bottom: 1px solid var(--border);
  position: relative;
}

.message-board-message:last-child {
  border-bottom: none;
}

.message-board-message:hover {
  background: var(--surface-sunken);
}

.message-board-message .avatar {
  flex-shrink: 0;
}

.message-content {
  flex: 1;
  min-width: 0;
}

.message-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: var(--space-xs);
}

.message-author {
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
}

.message-time {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.message-text {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  word-wrap: break-word;
  white-space: pre-wrap;
}

.message-delete-btn {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: var(--space-xs);
  border-radius: var(--radius-sm);
  opacity: 0;
  transition:
    opacity var(--transition-fast),
    color var(--transition-fast);
}

.message-board-message:hover .message-delete-btn {
  opacity: 1;
}

.message-delete-btn:hover {
  color: var(--danger-text);
  background: var(--danger-faint);
}

/* =============================================================================
   MODALS - Modal overlay and dialog components
   ============================================================================= */

/* Native dialog modal — replaces JS-driven .modal-overlay */
dialog#modal-container {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  background: transparent;
  border: none;
  padding: var(--space-lg) var(--inset-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}

dialog#modal-container:not([open]) {
  display: none;
}

dialog#modal-container::backdrop {
  background: var(--overlay-bg);
  animation: fadeIn 0.15s ease-out;
}

/* =============================================================================
   NAVIGATION - Inline priority nav + avatar-dropdown fallback
   ============================================================================= */

/* Frosted bar over the page's own surface (--surface is the body background in
   both schemes). */
.nav-minimal {
	position: sticky;
	top: 0;
	z-index: var(--z-nav);
	background: color-mix(in srgb, var(--surface) 94%, transparent);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
}

/* Height CONTRACT: total rendered header = --coach-header-h. The rail's sticky
   offsets read that token, so the inner box IS the token. */
.nav-inner {
	display: flex;
	align-items: center;
	gap: var(--space-md);
	max-width: var(--content-max-width);
	margin: 0 auto;
	padding: var(--space-sm) var(--inset-lg);
	height: var(--coach-header-h);
	container-type: inline-size;
	container-name: navbar;
}

/* Brand/Logo */
.nav-brand {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 11px;
	text-decoration: none;
	color: var(--text-bright);
	transition: color var(--transition-elegant);
}

/* Inline nav items cluster against .nav-right (margin-left: auto pushes
   the group to the right edge). Avoids the dead gap that appeared when
   .nav-items sat flush after the brand and .nav-right floated alone to
   the far edge. */
.nav-items {
	display: flex;
	align-items: center;
	gap: var(--space-sm);
	flex: 0 1 auto;
	min-width: 0;
	margin-left: auto;
}

.nav-item {
	flex-shrink: 0;
	padding: 8px 13px;
	border-bottom: 2.5px solid transparent;
	font-family: var(--font-mono);
	font-size: 12.5px;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: lowercase;
	color: var(--text-muted);
	text-decoration: none;
	white-space: nowrap;
	transition: color var(--transition-elegant);
}

/* The runner's own name keeps its casing and is the item that yields width. */
.nav-item--name {
	flex-shrink: 1;
	min-width: 0;
	max-width: 20ch;
	overflow: hidden;
	text-overflow: ellipsis;
	text-transform: none;
}

/* Active: ink with its own underline. Block, not flex: text-overflow only
   ellipsizes a block container. */
.nav-item.active {
	color: var(--text-bright);
	border-bottom-color: currentColor;
	display: block !important;
}

/* Priority-based inline hiding driven by the nav's own width (not viewport).
   Items hidden here appear in the avatar dropdown's overflow section.
   Thresholds are set so items only move once they actually no longer fit. */
@container navbar (max-width: 760px) {
	.nav-minimal .nav-item[data-priority="5"] {
		display: none;
	}
}

@container navbar (max-width: 600px) {
	.nav-minimal .nav-item[data-priority="4"] {
		display: none;
	}
}

@container navbar (max-width: 480px) {

	.nav-minimal .nav-item[data-priority="2"],
	.nav-minimal .nav-item[data-priority="3"] {
		display: none;
	}
}

/* Below the last breakpoint only the active item remains. Let it ellipsize
   and trim nav padding so the avatar can never be pushed off-screen. */
@container navbar (max-width: 420px) {
	.nav-minimal .nav-item.active {
		flex-shrink: 1;
		min-width: 0;
		overflow: hidden;
		text-overflow: ellipsis;
	}
}

@media (max-width: 420px) {
	.nav-inner {
		padding-left: var(--space-sm);
		padding-right: var(--space-sm);
	}
}

/* Right side container — sits adjacent to .nav-items (which carries the
   margin-left: auto that pushes the whole group right). */
.nav-right {
	display: flex;
	align-items: center;
	gap: var(--space-sm);
	position: relative;
	flex-shrink: 0;
}

/* Credit badge - only shown when low/exhausted.
   A hidden placeholder reserves the same slot when absent, so nav breakpoints
   don't shift as credit state changes. */
.nav-credit-badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 22px;
	height: 22px;
	padding: 0 var(--space-xs);
	border-radius: var(--radius-full);
	font-size: var(--text-2xs);
	font-weight: var(--weight-semibold);
	text-decoration: none;
	transition: transform var(--transition-spring);
}

.nav-credit-badge:hover {
	transform: scale(1.08);
}

.nav-credit-badge--placeholder {
	visibility: hidden;
	pointer-events: none;
}

.nav-credit-badge--placeholder:hover {
	transform: none;
}

.nav-credit-badge.low {
	background: var(--surface-raised-hover);
	color: var(--text);
}

.nav-credit-badge.exhausted {
	background: var(--danger-faint);
	color: var(--danger-text);
}

.nav-credit-badge.admin {
	background: var(--surface-raised-hover);
	color: var(--text-bright);
	font-weight: var(--weight-bold);
}

/* Details-based dropdown */
.nav-dropdown {
	position: relative;
}

.nav-dropdown::details-content {
	display: block;
	overflow: visible;
}

/* Avatar trigger button (now a <summary>) */
.nav-user-trigger {
	display: flex;
	padding: 0;
	background: transparent;
	border: 2px solid transparent;
	border-radius: var(--radius-full);
	cursor: pointer;
	transition: border-color var(--transition-elegant);
}

.nav-user-trigger:hover {
	border-color: var(--border-strong);
}

.nav-nick {
	display: none;
}

.nav-dropdown[open]>.nav-user-trigger {
	margin-bottom: 0;
}

/* Vertical 3-dot kebab — the user-menu trigger. Reads as "secondary
   options" so it yields visual hierarchy to the nav-brand wordmark on the
   left. Three explicit child spans (rather than an SVG or Unicode glyph)
   make hover/open color shifts easy to express. */
.nav-menu-dots {
	width: 28px;
	height: 28px;
	border-radius: var(--radius-full);
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3px;
}

.nav-menu-dots>span {
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var(--text-muted);
	transition: background var(--transition-elegant);
}

.nav-user-trigger:hover .nav-menu-dots>span,
.nav-dropdown[open]>.nav-user-trigger .nav-menu-dots>span {
	background: var(--text-bright);
}

/* Dropdown menu */
.nav-user-menu {
	position: absolute;
	top: calc(100% + var(--space-xs));
	right: 0;
	min-width: 200px;
	background: var(--surface-raised);
	border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-lg);
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transform: translateY(-8px);
	transition: opacity var(--transition-elegant), transform var(--transition-elegant);
	z-index: 2;
}

.nav-dropdown[open] .nav-user-menu {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transform: translateY(0);
}

/* User header in dropdown */
.nav-user-header {
	padding: var(--space-md);
}

.nav-user-name {
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
	display: block;
}

.nav-user-tier {
	font-family: var(--font-mono);
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--text-muted);
}

/* Menu items */
.nav-menu-item {
	display: block;
	padding: var(--space-sm) var(--space-md);
	color: var(--text-secondary);
	text-decoration: none;
	font-size: var(--text-sm);
	background: none;
	border: none;
	width: 100%;
	text-align: left;
	cursor: pointer;
	font-family: inherit;
	transition:
		color var(--transition-elegant),
		background var(--transition-elegant);
}

.nav-menu-item:hover {
	color: var(--text);
	background: var(--surface-sunken);
}

.nav-menu-item.active {
	color: var(--text-bright);
	background: var(--surface-sunken);
}

/* Divider */
.nav-menu-divider {
	height: 1px;
	background: var(--border);
	margin: var(--space-xs) 0;
}

/* Logout styling */
.nav-logout:hover {
	color: var(--danger-text);
	background: var(--danger-faint);
}

/* ── Public nav auth CTAs ── */
.nav-auth-ctas {
	display: flex;
	align-items: center;
	gap: var(--space-sm);
}

.nav-auth-btn {
	display: inline-flex;
	align-items: center;
	min-height: var(--tap-target);
	padding: 0 var(--space-sm);
	font-family: var(--font-body);
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
	white-space: nowrap;
}

.nav-auth-btn:hover,
.nav-auth-btn:focus-visible {
	text-decoration: underline;
	text-underline-offset: 3px;
}

.nav-auth-btn:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 2px;
	border-radius: var(--radius-xs);
}

/* ── Dropdown overflow section (mirrors inline nav items) ──
   Every nav item is rendered here but hidden by default. Container queries
   reveal a dropdown twin only when its inline counterpart is hidden, so each
   item is reachable in exactly one place. */
.nav-overflow-section {
	display: none;
}

.nav-overflow-item {
	display: none;
}

@container navbar (max-width: 760px) {
	.nav-overflow-section {
		display: block;
	}

	.nav-overflow-item[data-priority="5"] {
		display: block;
	}
}

@container navbar (max-width: 600px) {
	.nav-overflow-item[data-priority="4"] {
		display: block;
	}
}

@container navbar (max-width: 480px) {

	.nav-overflow-item[data-priority="2"],
	.nav-overflow-item[data-priority="3"] {
		display: block;
	}
}

/* Active page is always inline; never duplicate it in the dropdown. */
.nav-overflow-section .nav-overflow-item.active {
	display: none !important;
}

.coach-ledger .nav-item {
	font-size: 12px;
}

body.coach-ledger .nav-minimal {
	background: color-mix(in srgb, var(--ground, var(--surface)) 94%, transparent);
}

body.coach-ledger .nav-inner {
	max-width: none;
	padding-inline: var(--screen-inset);
}

.nav-item:not(.active):hover {
	color: var(--text);
	border-bottom-color: var(--border-strong);
}

@media (max-width: 760px) {
	.nav-item {
		padding: 8px 10px;
	}
}

@media (max-width: 699px) {
	.nav-minimal .nav-items {
		display: none;
	}

	.nav-minimal .nav-right {
		margin-inline-start: auto;
	}

	.nav-nick {
		display: inline-block;
		max-width: 40vw;
		overflow: hidden;
		padding: 8px 10px;
		font-family: var(--font-mono);
		font-size: 12.5px;
		font-weight: 600;
		letter-spacing: 0.04em;
		color: var(--text-bright);
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.nav-user-trigger {
		min-height: var(--tap-target);
		align-items: center;
	}

	.nav-user-trigger .nav-menu-dots {
		display: none;
	}

	.nav-overflow-section,
	.nav-overflow-section .nav-overflow-item,
	.nav-overflow-section .nav-overflow-item.active {
		display: block !important;
	}
}

.app-page {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	grid-auto-flow: row dense;
	gap: 9px;
	align-items: start;
	padding-top: var(--space-md);
	padding-bottom: var(--space-2xl);
}

@media (min-width: 700px) {
	.app-page {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.app-page > .header-band {
	padding-bottom: var(--space-sm);
}

.app-page .header-band__title {
	color: var(--text-bright);
}

.span-2 {
	grid-column: span 2;
}

.span-all,
.app-pane[open] {
	grid-column: 1 / -1;
}

.app-pane {
	color: var(--text);
}

.app-pane > .insight-tile__face {
	position: relative;
	gap: 6px;
	padding: 12px var(--pane-pad-inline) 14px;
}

.app-pane[open] > summary {
	margin-bottom: 0;
}

.app-pane[open] > summary > :not(.insight-tile__label, .pane-cue) {
	display: none;
}

div.insight-tile__face {
	cursor: auto;
}

a.insight-tile__face {
	text-decoration: none;
}

.pane-cue {
	position: absolute;
	top: 12px;
	right: 12px;
	width: 14px;
	height: 14px;
	color: var(--text-muted);
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.app-pane[open] > summary > .pane-cue {
	transform: rotate(-90deg);
}

.app-pane .insight-tile__value {
	font-size: 26px;
	color: var(--text-bright);
}

.app-pane .insight-tile__value small {
	margin-inline-start: 3px;
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.pane-meta {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
	line-height: 1.4;
}

.pane-prose {
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--text);
	display: -webkit-box;
	-webkit-line-clamp: 4;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.pane-prose--voice {
	font-style: italic;
}

.pane-list {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto auto;
	gap: 0;
	width: 100%;
	margin: 2px 0 0;
	padding: 0;
	list-style: none;
}

.pane-list > * {
	grid-column: 1 / -1;
	display: grid;
	grid-template-columns: subgrid;
	align-items: baseline;
	padding: 8px 0;
	border-top: 1px solid var(--border);
}

.pane-list > :first-child {
	border-top: 0;
	padding-top: 2px;
}

.pane-list.pane-list--names .k {
	font-family: var(--font-body);
	font-size: var(--text-sm);
	letter-spacing: 0;
	text-transform: none;
	color: var(--text);
}

.pane-group {
	grid-column: 1 / -1;
	display: grid;
	grid-template-columns: inherit;
	grid-auto-flow: row dense;
	gap: 9px;
	align-items: start;
}

.choice-row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-sm);
}

.choice {
	position: relative;
	display: inline-flex;
	align-items: center;
	text-decoration: none;
	min-height: var(--tap-target);
	padding: 0 var(--space-md);
	border: 0;
	border-radius: var(--radius-pill);
	background: none;
	box-shadow: inset 0 0 0 1px var(--border-strong);
	font: inherit;
	font-size: var(--text-sm);
	color: var(--text);
	cursor: pointer;
}

.choice:is([aria-checked="true"], [aria-pressed="true"], [aria-current="page"], :has(input:checked)) {
	background: var(--selected);
	box-shadow: none;
	color: var(--text-bright);
	font-weight: var(--weight-semibold);
}

.choice input {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}

.choice:focus-visible,
.choice:has(input:focus-visible) {
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

.pane-bullets {
	margin: 0;
	padding-inline-start: 1.1em;
	font-size: var(--text-sm);
	line-height: 1.6;
	color: var(--text);
}

.pane-list .k {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-secondary);
	align-self: baseline;
}

.pane-list .v {
	font-family: var(--font-display);
	font-size: 20px;
	font-weight: 500;
	line-height: 1;
	color: var(--text-bright);
	text-align: end;
	align-self: baseline;
}

.pane-list .v,
.pane-list .d {
	padding-inline-start: var(--space-md);
}

.pane-check {
	font-family: var(--font-body);
	text-transform: none;
}

.pane-list .d {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	color: var(--text-muted);
	text-align: end;
	align-self: baseline;
}

.pane-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-sm) var(--space-md);
	align-items: center;
	margin-top: 4px;
}

.pane-link {
	min-height: var(--tap-target);
	display: inline-flex;
	align-items: center;
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
	text-decoration: underline;
	text-underline-offset: 0.2em;
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
}

.pane-link--danger {
	color: var(--danger-text);
}

.pane-form {
	display: flex;
	flex-direction: column;
	gap: var(--space-md);
	padding: 0 var(--pane-pad-inline) 14px;
}

.pane-form .form-group {
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.pane-fields {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: var(--space-md);
}

.pane-form__foot {
	display: flex;
	justify-content: flex-end;
	align-items: center;
	gap: var(--space-sm);
}

.pane-result {
	margin-inline-end: auto;
	font-size: var(--text-sm);
}

.section-title {
	grid-column: 1 / -1;
	margin: var(--space-lg) 0 0;
	font-family: var(--font-display);
	font-size: 22px;
	font-weight: 800;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--text-bright);
}

.quiet-row {
	grid-column: 1 / -1;
	display: flex;
	flex-wrap: wrap;
	gap: 0 var(--space-lg);
	margin-top: var(--space-lg);
	padding-top: var(--space-sm);
	border-top: 1px solid var(--border);
}

.pane-section {
	padding-top: var(--space-md);
	border-top: 1px solid var(--border);
}

.notice-strip {
	grid-column: 1 / -1;
	padding: 10px var(--pane-pad-inline);
	border-radius: var(--radius-card);
	box-shadow: inset 0 0 0 1px var(--border-strong);
	font-size: var(--text-sm);
	color: var(--text);
}

.meter {
	width: 100%;
	height: 4px;
	border-radius: var(--radius-pill);
	background: var(--surface-raised-hover);
	overflow: hidden;
}

.meter > span {
	display: block;
	height: 100%;
	background: var(--text-secondary);
}

.pane-title {
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
}

.pane-retired .insight-tile__value,
.pane-retired .pane-title {
	color: var(--text-secondary);
}

.pane-model {
	font-size: var(--text-sm);
	color: var(--text-secondary);
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.pane-prose--full {
	display: block;
	-webkit-line-clamp: unset;
	white-space: pre-wrap;
}

.pane-actions--row {
	margin-top: 0;
}

.pane-list--buy .d {
	align-self: center;
}

.pane-list--buy .d > .btn {
	margin-top: 0;
	white-space: nowrap;
}


/* =============================================================================
   PLAN-STRIP.CSS - the plan pane's chip row (SPEC §5.7): journey_zone.html,
   journey_strip.html and _rail_node.html.
   ============================================================================= */

#plan-pane {
  --plan-chip-w: 120px;
  --plan-chip-ground: color-mix(in srgb, var(--text) 6%, transparent);
  --plan-eyebrow-size: var(--text-2xs);
  --plan-mark-size: 14px;
  --plan-ring: 1.5px;
  --plan-fade: 36px;
  --plan-hatch: color-mix(in srgb, var(--text) 10%, transparent);
  --plan-hatch-step: 6px;
}

/* Positioned: centreOnToday reads a chip's offsetLeft against this row. */
#plan-pane .track-nodes {
  --edge-fade-size: var(--plan-fade);
  position: relative;
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--space-sm);
  min-width: 0;
  padding: var(--space-xs) var(--plan-fade) var(--space-xs) 0;
}

#plan-pane .track-item {
  display: flex;
  flex: none;
}

/* ─── The chip ───
   The card's smallest step: a finished session wears the collapsed card's
   ground, one still ahead the opened card's line and tint. */

#plan-pane .track-node {
  --plan-ground: var(--plan-chip-ground);
  --plan-ink: var(--text);
  --plan-eyebrow-ink: color-mix(in srgb, var(--plan-ink) var(--ink-quiet), transparent);
  --plan-line: var(--border);
  --plan-tier: var(--e-line, var(--data-bar));
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: center;
  align-content: center;
  column-gap: var(--space-xs);
  row-gap: var(--space-xs);
  inline-size: var(--plan-chip-w);
  min-block-size: var(--tap-target);
  padding: calc(var(--space-sm) + var(--space-xs));
  border: 0;
  border-radius: var(--radius-chip);
  background: var(--plan-ground);
  box-shadow: inset 0 0 0 1px var(--plan-line);
  color: var(--plan-ink);
  font-family: inherit;
  text-align: start;
  cursor: pointer;
  transition: transform var(--transition-fast);
}

#plan-pane .track-node:hover {
  transform: translateY(-1px);
}

#plan-pane .track-node:active {
  transform: scale(0.96);
}

#plan-pane .track-node:focus-visible {
  outline: var(--rule-focus) solid var(--text);
  outline-offset: var(--rule-focus);
}

#plan-pane .track-node:is(.track-node--milestone, .track-node--goal) {
  --plan-tier: var(--e-line, var(--effort-high-line));
}

#plan-pane .track-node[data-external] {
  --plan-tier: var(--data-bar);
}

#plan-pane .track-node[data-moment] {
  --plan-tier: var(--moment);
}

#plan-pane .track-node[data-status="proposed"][data-intensity] {
  --plan-ground: var(--e-tint);
  --plan-line: var(--e-line);
}

#plan-pane .track-node[data-status="completed"] {
  --plan-ground: var(--surface-raised);
  --plan-ink: var(--text-bright);
  --plan-eyebrow-ink: var(--plan-ink);
  --plan-tier: var(--plan-ink);
}

#plan-pane .track-node[data-status="completed"][data-intensity]:not([data-external], [data-moment]) {
  --plan-ground: var(--e-deep);
  --plan-ink: var(--e-deep-ink);
  --plan-line: var(--e-deep);
}

#plan-pane .track-node[data-status="completed"][data-moment] {
  --plan-ground: var(--moment);
  --plan-ink: var(--moment-ink);
  --plan-line: var(--moment);
}

/* ─── Eyebrow: mark · date · ✓ ─── */

#plan-pane .track-date,
#plan-pane .track-node[data-status="completed"]::after {
  font-family: var(--font-mono);
  font-size: var(--plan-eyebrow-size);
  font-weight: var(--weight-normal);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  line-height: 1;
  color: var(--plan-eyebrow-ink);
}

#plan-pane .track-date {
  grid-area: 1 / 2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#plan-pane .track-node[data-status="completed"]::after {
  content: "\2713";
  grid-area: 1 / 3;
  padding-inline: var(--space-xs);
  border-radius: var(--radius-xs);
  background: var(--plan-ink);
  color: var(--plan-ground);
  font-weight: var(--weight-semibold);
  letter-spacing: normal;
}

/* The session's type in its tier's ink; a milestone or goal shows its role. */
#plan-pane .track-mark {
  display: inline-flex;
  grid-area: 1 / 1;
  inline-size: var(--plan-mark-size);
  block-size: var(--plan-mark-size);
  color: var(--plan-tier);
}

#plan-pane .track-mark .steev-icon__badge,
#plan-pane :is(.track-node--milestone, .track-node--goal) .track-mark .steev-icon {
  display: none;
}

#plan-pane :is(.track-node--milestone, .track-node--goal) .track-mark .steev-icon__badge {
  display: block;
  position: static;
  inline-size: 100%;
  block-size: 100%;
  background: none;
}

/* ─── Label ─── */

#plan-pane .track-label {
  grid-area: 2 / 1 / 3 / -1;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: 1.25;
  text-transform: uppercase;
}

/* ─── Missed: a half-faded mark over a hatch ─── */

#plan-pane .track-node[data-status="missed"] {
  background-image: repeating-linear-gradient(
    135deg,
    var(--plan-hatch) 0 1px,
    transparent 1px var(--plan-hatch-step)
  );
}

#plan-pane .track-node[data-status="missed"] .track-mark {
  opacity: 0.5;
}

/* ─── Today: ringed in its own ink, its date bold ─── */

#plan-pane :is(.track-node--today, .track-node--today-only) {
  --plan-ground: var(--surface-raised);
  box-shadow: inset 0 0 0 var(--plan-ring) var(--plan-ink);
}

#plan-pane :is(.track-node--today, .track-node--today-only) .track-date {
  font-weight: var(--weight-bold);
  color: var(--plan-ink);
}

#plan-pane .track-node--today-only .track-date {
  grid-column: 1 / 3;
}

#plan-pane .track-node--today-only {
  inline-size: auto;
  min-inline-size: var(--tap-target);
  pointer-events: none;
}

/* ─── Older (+N) and the add affordances ─── */

#plan-pane .track-node--older {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  place-items: center;
  align-self: center;
  inline-size: var(--tap-target);
  block-size: var(--tap-target);
  min-block-size: 0;
  padding: 0;
  border-radius: var(--radius-pill);
}

/* A strip with nothing on it (`data-bare`: only the synthetic today marker)
   hides its pane until a refresh brings rows. */
#plan-pane:has([data-bare]) {
  display: none;
}

/* A secondary goal is a detour: it keeps the goal mark, its label drops to
   secondary ink. Status wins over role. */
#plan-pane .track-node--secondary:not([data-status="completed"], [data-status="missed"]) .track-label {
  color: var(--text-secondary);
}

#plan-pane .track-node--older .track-label {
  grid-area: 1 / 1;
  overflow: visible;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-normal);
  color: var(--text-muted);
}

/* ─── Open poster ─── */

.timeline-detail:empty {
  display: none;
}

#plan-pane .track-node--active {
  box-shadow: inset 0 0 0 var(--plan-ring) var(--plan-tier);
}

#plan-pane .timeline-detail:not(:empty) {
  margin-top: var(--space-md);
}

@media (max-width: 1000px) {
  #plan-pane .timeline-detail:not(:empty) {
    position: fixed;
    inset: auto 0 0;
    z-index: var(--z-sheet);
    max-block-size: calc(100dvh - var(--coach-header-h));
    margin: 0;
    padding: var(--space-md);
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
    background: var(--surface-raised);
    box-shadow: var(--shadow-lg);
  }
}

@media (prefers-color-scheme: light) {
  #plan-pane {
    --plan-chip-ground: var(--surface);
  }
}

@media (prefers-reduced-motion: reduce) {
  #plan-pane .track-node {
    transition: none;
  }
}

/* ── Page column ── */

.reading {
	display: flex;
	flex-direction: column;
	gap: var(--space-lg);
	padding-top: var(--space-sm);
	padding-bottom: var(--space-2xl);
}

.reading .reading {
	padding: 0;
}

.reading-back {
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	min-height: var(--tap-target);
	margin-bottom: calc(-1 * var(--space-md));
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.reading-back:hover,
.reading-back:focus-visible {
	color: var(--text-bright);
}

/* ── Head: kicker, the page name as the big title, the lede ── */

.reading-head {
	gap: var(--space-sm);
	padding: 0;
}

.reading-title {
	margin: 0;
	color: var(--text-bright);
	overflow: visible;
	line-height: 0.95;
	overflow-wrap: break-word;
	text-wrap: balance;
}

.reading-kicker {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-xs) var(--space-sm);
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	line-height: 1.35;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.reading-lede {
	max-width: var(--reading-measure);
	margin: var(--space-xs) 0 0;
	font-size: var(--text-lg);
	line-height: 1.45;
	color: var(--text);
	text-wrap: pretty;
}

/* ── Prose: Instrument Sans at the coach's prose size, Big Shoulders subheads ── */

.prose {
	max-width: var(--reading-measure);
	font-size: var(--chat-prose-size);
	line-height: 1.65;
	color: var(--text);
}

.prose > * {
	margin: 0;
}

.prose > * + * {
	margin-top: var(--space-md);
}

.prose p {
	color: inherit;
}

.prose h2 {
	margin-top: var(--space-xl);
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: 800;
	line-height: 1.05;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
	text-wrap: balance;
}

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

.prose > h2 + * {
	margin-top: var(--space-sm);
}

.prose strong {
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
}

.prose a,
.reading-refs a,
.reading-foot a,
.source-line a,
.fact-lines a {
	color: var(--text-bright);
	text-decoration: underline;
	text-decoration-color: var(--text-muted);
	text-decoration-thickness: 1px;
	text-underline-offset: 0.2em;
	overflow-wrap: anywhere;
}

.prose a:hover,
.reading-refs a:hover,
.reading-foot a:hover,
.source-line a:hover,
.fact-lines a:hover {
	text-decoration-color: currentColor;
}

.prose code {
	padding: 0 var(--space-xs);
	border-radius: var(--radius-xs);
	background: var(--surface-raised-hover);
	font-family: var(--font-mono);
	font-size: var(--text-sm);
}

.prose ul,
.prose ol {
	padding: 0;
	list-style: none;
}

.prose ol {
	counter-reset: step;
}

.prose li + li {
	margin-top: var(--space-sm);
}

.prose ul > li {
	padding-inline-start: var(--space-lg);
}

.prose ol > li {
	counter-increment: step;
	padding-inline-start: var(--space-xl);
}

/* Markers sit inline on the first line, so they share its baseline. */
.prose ul > li::before {
	content: "–";
	display: inline-block;
	width: var(--space-lg);
	margin-inline-start: calc(-1 * var(--space-lg));
	color: var(--text-muted);
}

.prose ol > li::before,
.reading-refs > li::before,
.doctrine-rule::before {
	display: inline-block;
	width: var(--space-xl);
	margin-inline-start: calc(-1 * var(--space-xl));
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	color: var(--text-muted);
}

.prose ol > li::before {
	content: counter(step, decimal-leading-zero);
}

/* A paragraph that is all emphasis: the dated update note, the trademark line. */
.prose .prose-note {
	font-size: var(--text-sm);
	font-style: italic;
	line-height: 1.55;
	color: var(--text-muted);
}

.prose--lede {
	font-size: var(--text-lg);
	line-height: 1.45;
}

/* ── Sections after the article ── */

.reading-block {
	display: flex;
	flex-direction: column;
	gap: var(--space-sm);
	margin-top: var(--space-md);
}

.reading-block > .section-title {
	margin: 0;
}

.reading-chapter {
	margin: var(--space-md) 0 0;
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: 900;
	line-height: 1;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
	text-wrap: balance;
}

/* ── Index lists: rows on the ground, hairline between them ── */

.reading-list {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

/* The rule is per row, so an odd last cell in two columns draws no line under the empty one. */
.reading-list > :first-child {
	border-top: 1px solid var(--border);
}

.reading-row {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	padding: var(--space-md) var(--space-xl) var(--space-md) 0;
	border-bottom: 1px solid var(--border);
	color: var(--text);
	text-decoration: none;
}

.reading-row__title {
	margin: 0;
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: 800;
	line-height: 1.02;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
	text-wrap: balance;
}

.reading-row__desc {
	max-width: var(--reading-measure);
	margin: 0;
	font-size: var(--text-base);
	line-height: 1.5;
	color: var(--text-secondary);
	text-wrap: pretty;
}

.reading-row > .pane-cue,
.link-pane > .pane-cue {
	top: calc(var(--space-md) + var(--space-xs));
	right: 0;
}

.link-pane > .pane-cue {
	right: var(--pane-pad-inline);
}

.reading-row:hover .reading-row__title,
.reading-row:focus-visible .reading-row__title,
.race-row:hover .race-row__name,
.race-row:focus-visible .race-row__name {
	text-decoration: underline;
	text-decoration-thickness: from-font;
	text-underline-offset: 0.12em;
}

.reading-row:hover .pane-cue,
.link-pane:hover .pane-cue {
	color: var(--text-bright);
}

.reading-row:focus-visible,
.race-row:focus-visible,
.link-pane:focus-visible {
	outline: var(--rule-focus) solid currentColor;
	outline-offset: var(--rule-focus);
}

@media (min-width: 700px) {
	.reading-list--cols {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--space-xl);
	}

	.reading-list--cols > :nth-child(2) {
		border-top: 1px solid var(--border);
	}

	.reading-list--cols .reading-row__title {
		font-size: var(--text-lg);
	}
}

.reading-empty {
	margin: 0;
	padding: var(--space-xl) 0;
	border-top: 1px solid var(--border);
	color: var(--text-muted);
}

/* ── Races: a dense list on one aligned grid ── */

.race-rows {
	display: grid;
	grid-template-columns: minmax(0, 1fr) max-content;
	border-bottom: 1px solid var(--border);
}

.race-row {
	grid-column: 1 / -1;
	display: grid;
	grid-template-columns: subgrid;
	column-gap: var(--space-md);
	row-gap: var(--space-xs);
	align-items: baseline;
	padding: var(--space-sm) 0;
	border-top: 1px solid var(--border);
	color: var(--text);
	text-decoration: none;
}

.race-row__when {
	grid-column: 1;
	grid-row: 1;
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.race-row__dist {
	grid-column: 2;
	grid-row: 1;
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text);
	text-align: end;
}

.race-row__name {
	grid-column: 1 / -1;
	grid-row: 2;
	margin: 0;
	font-family: var(--font-display);
	font-size: var(--text-lg);
	font-weight: 800;
	line-height: 1.1;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
}

.race-row__place {
	grid-column: 1 / -1;
	grid-row: 3;
	font-size: var(--text-sm);
	color: var(--text-secondary);
}

.race-row--past .race-row__name {
	color: var(--text-secondary);
}

@media (min-width: 700px) {
	.race-rows {
		grid-template-columns: max-content minmax(0, 1fr) max-content;
	}

	.race-row {
		column-gap: var(--space-lg);
		padding: var(--space-md) 0;
	}

	.race-row__when {
		grid-row: 1 / span 2;
		display: flex;
		flex-direction: column;
		gap: var(--space-xs);
	}

	.race-row__dist {
		grid-column: 3;
	}

	.race-row__name {
		grid-column: 2;
		grid-row: 1;
	}

	.race-row__place {
		grid-column: 2;
		grid-row: 2;
	}
}

.race-row__status {
	color: var(--text-bright);
}

@media (max-width: 699px) {
	.race-row__status::before {
		content: "\2002\2002";
	}
}

/* ── Filters: step 2's .choice chips, plus the country select ── */

.reading-filters {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-sm) var(--space-md);
}

.reading-select {
	flex-direction: row;
	display: flex;
	align-items: center;
	gap: var(--space-sm);
	margin: 0;
}

@media (min-width: 700px) {
	.reading-select {
		margin-inline-start: auto;
	}
}

.reading-select label {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.reading-select .input {
	width: auto;
	min-height: var(--tap-target);
}

/* ── Side facts: one flat pane each ── */

.fact-pane {
	display: flex;
	flex-direction: column;
	gap: var(--space-md);
	padding: var(--space-md) var(--pane-pad-inline);
}

.fact-pane > h2 {
	margin: 0;
	color: inherit;
}

.fact-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-lg) var(--space-md);
	margin: 0;
}

.fact-grid--specs {
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--space-md) var(--space-sm);
}

@media (min-width: 700px) {
	.fact-grid--race {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}

	.fact-grid--specs {
		grid-template-columns: repeat(5, minmax(0, 1fr));
	}
}

.fact {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	min-width: 0;
}

.fact dd {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	margin: 0;
}

.fact .insight-figure__value,
.fact .insight-tile__value {
	color: var(--text-bright);
	overflow-wrap: anywhere;
}

.fact .insight-figure__value small,
.fact .insight-tile__value small {
	margin-inline-start: var(--space-xs);
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.fact .meter {
	margin-top: var(--space-xs);
}

.fact a {
	color: inherit;
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.15em;
}

.fact-lines {
	display: grid;
	grid-template-columns: max-content minmax(0, 1fr);
	gap: var(--space-sm) var(--space-md);
	align-items: baseline;
	margin: 0;
	padding-top: var(--space-md);
	border-top: 1px solid var(--border);
}

.fact-lines dt {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.fact-lines dd {
	margin: 0;
	font-size: var(--text-sm);
	color: var(--text);
}

.fact-note {
	margin: 0;
	font-size: var(--text-xs);
	line-height: 1.5;
	color: var(--text-muted);
}

/* An in-pane head: the label face, set apart from the labels under it by ink, weight and a rule. */
.fact-pane > .pane-head {
	margin: 0;
	padding-bottom: var(--space-sm);
	border-bottom: 1px solid var(--border);
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-bright);
}

.fact-pane > .pane-head:not(:first-child) {
	margin-top: var(--space-sm);
}

.reading-uses {
	margin: 0;
	color: var(--text);
}

/* The at-a-glance strip: lead, name, detail per cell; neutral ink. */
.glance {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(calc(var(--space-2xl) * 2), 1fr));
	gap: var(--space-md) var(--space-sm);
	margin: 0;
	padding: 0;
	list-style: none;
}

.glance > li {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	padding-top: var(--space-sm);
	border-top: 1px solid var(--border-strong);
}

.glance .insight-tile__value {
	color: var(--text-bright);
}

/* ── Link panes: related posts, guides, races ── */

.link-panes {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--space-sm);
}

@media (min-width: 700px) {
	.link-panes {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		align-items: start;
	}
}

.link-pane {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	padding: var(--space-md) var(--space-xl) var(--space-md) var(--pane-pad-inline);
	color: var(--text);
	text-decoration: none;
	transition: background-color var(--transition-fast);
}

.link-pane:hover {
	background: var(--surface-raised-hover);
}

.link-pane__title {
	font-family: var(--font-display);
	font-size: var(--text-lg);
	font-weight: 800;
	line-height: 1.1;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
}

.link-pane .pane-prose {
	-webkit-line-clamp: 3;
}

/* ── Leaderboard inside its pane ── */

.fact-pane .choice-row {
	gap: var(--space-xs);
}

.fact-pane .pane-list .k {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.fact-pane .pane-actions {
	justify-content: space-between;
	margin-top: 0;
}

/* ── References, quiet footers, the one action ── */

.reading-refs {
	max-width: var(--reading-measure);
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: ref;
	font-size: var(--text-sm);
	line-height: 1.55;
	color: var(--text-secondary);
}

.reading-refs > li {
	counter-increment: ref;
	padding-inline-start: var(--space-xl);
	overflow-wrap: anywhere;
}

.reading-refs > li + li {
	margin-top: var(--space-sm);
}

.reading-refs > li::before {
	content: counter(ref, decimal-leading-zero);
}

.reading-refs a {
	color: inherit;
}

.reading-foot {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	max-width: var(--reading-measure);
	margin-top: var(--space-md);
	padding-top: var(--space-md);
	border-top: 1px solid var(--border);
	font-size: var(--text-sm);
	line-height: 1.55;
	color: var(--text-secondary);
}

.reading-foot p,
.reading-foot h2 {
	margin: 0;
	color: inherit;
}

.reading-foot h2.reading-label {
	font-weight: 400;
	color: var(--text-muted);
}

.reading-label {
	margin-inline-end: var(--space-sm);
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.reading-cta {
	align-items: center;
	gap: var(--space-sm);
	display: flex;
	margin-top: var(--space-md);
}

/* ── Methodology: models and doctrine on the ground ── */

.reading .notice-strip {
	max-width: var(--reading-measure);
	line-height: 1.55;
}

.reading .notice-strip a {
	display: inline-block;
	margin-top: var(--space-xs);
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.model {
	display: flex;
	flex-direction: column;
	gap: var(--space-sm);
	padding-top: var(--space-lg);
	border-top: 1px solid var(--border);
}

.model__name {
	margin: 0;
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: 800;
	line-height: 1.05;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
	text-wrap: balance;
}

.equations {
	display: flex;
	flex-direction: column;
	gap: var(--space-md);
	max-width: 100%;
	min-width: 0;
	margin: var(--space-xs) 0;
	padding: var(--space-md) var(--pane-pad-inline);
	border-radius: var(--radius-card);
	background: var(--pane);
	box-shadow: var(--pane-ring);
}

.equation {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	min-width: 0;
}

.equation code {
	display: block;
	font-family: var(--font-mono);
	font-size: var(--text-sm);
	line-height: 1.5;
	white-space: pre-wrap;
	color: var(--text-bright);
}

.equation__note {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	line-height: 1.45;
	color: var(--text-muted);
}

.source-line {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-xs) var(--space-sm);
	max-width: var(--reading-measure);
	margin: 0;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--text-secondary);
}

.source-line .reading-label {
	margin: 0;
}

.methodology-section {
	display: flex;
	flex-direction: column;
	gap: var(--space-sm);
}

/* Deep links land clear of the sticky nav; scoped here, never on html (the bundle is global). */
.methodology-section,
.doctrine-item {
	scroll-margin-top: calc(var(--coach-header-h) + var(--space-md));
}

.methodology-section > .reading-chapter {
	margin-bottom: var(--space-xs);
}

.doctrine {
	max-width: var(--reading-measure);
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: rule;
}

.doctrine-item {
	counter-increment: rule;
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	padding: var(--space-md) 0 var(--space-md) var(--space-xl);
	border-top: 1px solid var(--border);
}

.doctrine-item:target {
	border-radius: var(--radius-xs);
	background: var(--selected);
	box-shadow: 0 0 0 var(--space-sm) var(--selected);
}

.doctrine-rule {
	margin: 0;
	font-size: var(--chat-prose-size);
	line-height: 1.6;
	color: var(--text);
}

.doctrine-rule::before {
	content: counter(rule, decimal-leading-zero);
}

/* ── Credits: rows on the ground; the logos are brand marks and keep their colours ── */

.credit-list {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

.credit-list > :first-child {
	border-top: 1px solid var(--border);
}

.credit {
	display: flex;
	align-items: flex-start;
	gap: var(--space-md);
	padding: var(--space-lg) 0;
	border-bottom: 1px solid var(--border);
}

.credit--sm {
	gap: var(--space-sm);
	padding: var(--space-md) 0;
}

.credit-logo {
	flex-shrink: 0;
	width: var(--space-2xl);
	height: var(--space-2xl);
	object-fit: contain;
}

.credit-logo--sm {
	width: var(--space-lg);
	height: var(--space-lg);
}

.credit-logo--round {
	border-radius: var(--radius-pill);
}

.credit-logo--invert-dark {
	filter: invert(1);
}

@media (prefers-color-scheme: light) {
	.credit-logo--invert-dark {
		filter: none;
	}
}

/* A block, so the <source> beside the img never becomes a flex item (and an extra gap). */
.credit picture {
	display: block;
	flex-shrink: 0;
}

.credit picture img {
	display: block;
}

.credit__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	min-width: 0;
	max-width: var(--reading-measure);
}

.credit__title {
	margin: 0;
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: 800;
	line-height: 1.05;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--text-bright);
	text-wrap: balance;
}

.credit__name {
	margin: 0;
	font-weight: var(--weight-semibold);
	color: var(--text-bright);
}

.credit__name a {
	color: inherit;
	text-decoration: underline;
	text-decoration-color: var(--text-muted);
	text-decoration-thickness: 1px;
	text-underline-offset: 0.2em;
}

.credit__name a:hover {
	text-decoration-color: currentColor;
}

.credit__name .reading-label {
	margin-inline-start: var(--space-xs);
	font-weight: 400;
	text-transform: none;
}

.credit__desc {
	margin: 0;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--text-secondary);
}

@media (min-width: 700px) {
	.credit-list--cols {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--space-xl);
	}

	.credit-list--cols > :nth-child(2) {
		border-top: 1px solid var(--border);
	}
}

/* =============================================================================
   STEEV-FACE.CSS - The canonical face system

   This file owns the complete visual state machine for the Steev face:
   - Base box (size, color, SVG element transform-box setup)
   - Idle choreography (breathe + 20s brow/eye/mustache cycle)
   - Sleeping pose (data-state="sleeping") + waking transition (data-state="waking")
   - Writing / Celebrating transitions (data-state="writing|celebrating")
   - Mood system (neutral, proud, concerned, curious, amused, tired)
   - Outbursts (laugh, gasp) opening the mouth
   - Face-scoped reduced-motion overrides

   Identity envelope: mustache + brows never disappear. Amplitudes bounded
   (brow rotate ≤9°, scale-y ≥0.5, mustache rotate ≤1°, whole-face gaze turn ≤2°).
   ============================================================================= */

/* ─── Base box ─── */

.steev-face {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--text);

  /* Mouth shade var — the OPEN mouth: a deeper shade of the face's own ink, reads as "aperture" not
     "ink mark". The resting mouth is face ink; only outbursts use this.
     Locally scoped so the face remains portable. */
  --mouth-shade: color-mix(in oklab, currentColor 55%, black 45%);

  /* JS-driven gaze turn: standalone rotate composes with the transform-based
     state keyframes (see the mood-system header) — zero keyframe edits. */
  rotate: var(--face-turn, 0deg);
  transition: rotate 80ms ease-out;
}

.steev-face svg {
  width: 100%;
  height: 100%;
  display: block;
  /* Celebrate/think animations translate brows up to 2px past the viewBox.
     Visible spill is desirable; clipping would lose identity (brows). */
  overflow: visible;
}

/* SVG sub-elements: fill-box so transforms are relative to each shape */
.steev-face svg * {
  transform-box: fill-box;
  transform-origin: center;
}


/* Gaze tracking — eyes follow cursor.
   The `translate` property is separate from `transform`, so it composes
   with the `scaleY` blink keyframes below without overriding them. Older
   (sleeping) faces never receive `--eye-x/y`, so the 0 fallback keeps
   them still. */
.steev-face .sv-el,
.steev-face .sv-er {
  translate: var(--eye-x, 0) var(--eye-y, 0);
  transition: translate 80ms ease-out, transform 80ms ease-out;
}

/* Mouth base state: a resting feature, drawn in the face's own ink — so it
   follows the face onto any ground (any ground it is placed on) for free.
   Mood reshapes it through the standalone `scale`/`translate`/`rotate`
   properties (untransitioned, like every other mood pose on this face);
   `transform` and `fill` are left free for the state/outburst layers, which
   is what this transition covers. */
.steev-face .sv-mouth {
  fill: currentColor;
  transition: fill 180ms ease-out, transform 180ms ease-out;
}

/* Nose: inherit stroke color and subtle by default. */
.steev-face .sv-n {
  transition: opacity 180ms ease-out;
}

/* ─── Breathing: ambient halo pulse + 20s idle cycle ───
   Driven by data-state="breathing" set by the P06 state machine in
   steev-face.js. An unpinned face spends most of its time in this
   state when the user is not actively interacting. */

.steev-face[data-state="breathing"] {
  animation: sv-breathe 4s ease-in-out infinite;
}

@keyframes sv-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}

.steev-face[data-state="breathing"] .sv-bl { animation: sv-idle-bl 20s ease-in-out infinite; }
.steev-face[data-state="breathing"] .sv-br { animation: sv-idle-br 20s ease-in-out infinite; }
.steev-face[data-state="breathing"] .sv-el { animation: sv-idle-eye 20s ease-in-out infinite; }
.steev-face[data-state="breathing"] .sv-er { animation: sv-idle-eye 20s ease-in-out 0.04s infinite; }
.steev-face[data-state="breathing"] .sv-m  { animation: sv-idle-m 20s ease-in-out infinite; }

@keyframes sv-idle-bl {
  0%, 62%, 78%, 100% { transform: translateY(0) rotate(0); }
  /* Amused: left brow raises */
  66% { transform: translateY(-1.5px) rotate(-3deg); }
  74% { transform: translateY(-0.5px) rotate(-1deg); }
}

@keyframes sv-idle-br {
  0%, 26%, 40%, 100% { transform: translateY(0) rotate(0); }
  /* Curious: right brow raises */
  30% { transform: translateY(-1.5px) rotate(4deg); }
  37% { transform: translateY(-0.5px) rotate(1deg); }
}

@keyframes sv-idle-eye {
  0%, 11%, 17%, 49%, 55%, 83%, 89%, 100% { transform: scaleY(1); }
  13%, 15% { transform: scaleY(0.1); }
  51%, 53% { transform: scaleY(0.1); }
  85%, 87% { transform: scaleY(0.1); }
}

@keyframes sv-idle-m {
  0%, 62%, 78%, 100% { transform: translateY(0); }
  /* Amused: mustache shifts up slightly with left brow */
  67% { transform: translateY(-0.4px); }
  74% { transform: translateY(-0.2px); }
}

/* ─── Sleeping: 60s idle or tab hidden ───
   Driven by data-state="sleeping" set by the P06 state machine. */
.steev-face[data-state="sleeping"] {
  animation: sv-sleep 7s ease-in-out infinite;
}

/* Eyes shut while asleep (held blink) — the sleep signal, no dim. */
.steev-face[data-state="sleeping"] .sv-el,
.steev-face[data-state="sleeping"] .sv-er {
  transform: scaleY(0.1);
  transition: transform 300ms ease-out;
}

/* Mouth shuts with the eyes — a sleeping face is closed, not agape. `transform`
   rather than `scale` so it multiplies whatever mood shape is set instead of
   replacing it (the eyes above do the same). */
.steev-face[data-state="sleeping"] .sv-mouth {
  transform: scale(0.8, 0.25);
  transition: transform 300ms ease-out;
}

@keyframes sv-sleep {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.02); }
}

/* ─── Waking: transient state back to breathing (P06 wake handler) ─── */
.steev-face[data-state="waking"] {
  animation: sv-breathe 4s ease-in-out infinite;
}

/* ─── Blinking: momentary eye-close (P06 schedules every 6-10s) ─── */
.steev-face[data-state="blinking"] .sv-el,
.steev-face[data-state="blinking"] .sv-er {
  transform: scaleY(0.1);
  transition: transform 80ms ease-out;
}

/* ─── Writing: LLM is producing output (thinking + streaming combined) ───
   Driven by data-state="writing" set by the P06 state machine in response
   to steev:writing:start events from streaming.js. Folds the previous
   per-bubble "thinking" (typing-indicator visible) and "speaking" (chunks
   streaming) into one state — the runner sees "Steev is working" without
   needing substate fidelity. */
.steev-face[data-state="writing"] svg {
  animation: sv-think 2.5s ease-in-out infinite;
}
.steev-face[data-state="writing"] .sv-bl {
  animation: sv-think-brow 2.5s ease-in-out infinite;
}
.steev-face[data-state="writing"] .sv-br {
  animation: sv-think-brow 2.5s ease-in-out infinite reverse;
}
.steev-face[data-state="writing"] .sv-m {
  animation: sv-think-m 2.5s ease-in-out infinite;
}

@keyframes sv-think {
  0%, 100% { transform: rotate(0) translateY(0); }
  30%      { transform: rotate(-5deg) translateY(-1px); }
  70%      { transform: rotate(5deg) translateY(-1px); }
}

@keyframes sv-think-brow {
  0%, 100% { transform: translateY(0) rotate(0); }
  50%      { transform: translateY(0.8px) rotate(5deg); }
}

@keyframes sv-think-m {
  0%, 100% { transform: scaleY(1); }
  50%      { transform: scaleY(0.94); }
}


/* ─── Celebrating: milestone hit / goal complete ───
   Triggered by data-state="celebrating" set by the P06 state machine in
   response to steev:celebrate events from streaming.js. Auto-reverts to
   data-state="breathing" after the celebration animation completes
   (~2.4s — sv-celebrate iteration: 3). */
.steev-face[data-state="celebrating"] svg {
  animation: sv-celebrate 0.8s ease-in-out 3;
}
.steev-face[data-state="celebrating"] .sv-bl {
  animation: sv-celeb-brow 0.6s ease-in-out 4;
}
.steev-face[data-state="celebrating"] .sv-br {
  animation: sv-celeb-brow 0.6s ease-in-out 0.05s 4;
}
.steev-face[data-state="celebrating"] .sv-m {
  animation: sv-celeb-m 0.8s ease-in-out 3;
}

@keyframes sv-celebrate {
  0%, 100% { transform: scale(1) rotate(0); }
  20%      { transform: scale(1.15) rotate(-6deg); }
  50%      { transform: scale(0.97) rotate(3deg); }
  80%      { transform: scale(1.08) rotate(-1deg); }
}

@keyframes sv-celeb-brow {
  0%, 100% { transform: translateY(0) rotate(0); }
  40%      { transform: translateY(-2px) rotate(-6deg); }
}

@keyframes sv-celeb-m {
  0%, 100% { transform: translateY(0) scaleY(1); }
  35%      { transform: translateY(0.8px) scaleY(1.1); }
  70%      { transform: translateY(-0.3px) scaleY(0.95); }
}

/* =============================================================================
   MOOD SYSTEM — face mood (data-mood) + transient outbursts (data-outburst)

   How this layers with the idle system:
   - `translate:`, `rotate:`, `scale:` (separate CSS properties) compose
     with the `transform:`-based idle/sleep/think keyframes rather than
     fighting them.
   - Mood is data-mood on the face: set live by steev:mood (streaming.js
     dispatches it as each exchange-group lands), and pinned server-side
     on a reloaded turn's head face (data-manual-state, chat.rs coach_face).
   ============================================================================= */

/* ─── Mood baselines ───
   Defaults to neutral (mischievous asymmetric brow). */

.steev-face .sv-bl {
  translate: 0 -0.6px;
  rotate: -6deg;
}
.steev-face .sv-br {
  translate: 0 0.3px;
  rotate: 2deg;
}

/* ─── The mood mouth ───
   An ellipse's honest vocabulary is APERTURE — width, weight, tilt, height on
   the face. Steev's smile is the mustache (which already carries mood); this
   mark sits below it and says how open he is. At the 24px shipping size it is
   ~3.8×1.4 device px, so WIDTH and INK WEIGHT are the only channels that
   survive rasterization — tilt and offset are refinements that resolve at
   40pt+. Four legible levels: open (proud, amused), neutral (neutral,
   curious), thin (concerned, tired), shut (sleeping). `curious` is deliberately
   left at the base shape — its head tilt already swings the mouth with the
   whole face, and any small round mouth it could take collides with `gasp`. */

/* ─── mood-proud: brows lift, eyes widen, mustache tilts ≤1° ─── */
.steev-face[data-mood="proud"] .sv-bl { translate: 0 -1.4px; rotate: -6deg; }
.steev-face[data-mood="proud"] .sv-br { translate: 0 -0.8px; rotate: 2deg; }
.steev-face[data-mood="proud"] .sv-el,
.steev-face[data-mood="proud"] .sv-er { scale: 1.08; }
.steev-face[data-mood="proud"] .sv-m  { rotate: -0.8deg; }
/* broad and level, riding up with the brows */
.steev-face[data-mood="proud"] .sv-mouth { translate: 0 -0.15px; scale: 1.2 1; }

/* ─── mood-concerned: inner brows lift, eyes narrow, nose scrunch ─── */
.steev-face[data-mood="concerned"] .sv-bl { translate: 0 -0.2px; rotate: 4deg; }
.steev-face[data-mood="concerned"] .sv-br { translate: 0 -0.2px; rotate: -2deg; }
.steev-face[data-mood="concerned"] .sv-el,
.steev-face[data-mood="concerned"] .sv-er { scale: 0.92; }
.steev-face[data-mood="concerned"] .sv-m  { translate: 0 0.2px; }
.steev-face[data-mood="concerned"] .sv-n  { opacity: 0.85; }
/* small and pursed — the narrowest mouth in the set */
.steev-face[data-mood="concerned"] .sv-mouth { scale: 0.75 0.7; }

/* ─── mood-curious: amplified asymmetric brow + head-tilt on the face itself ─── */
.steev-face[data-mood="curious"] .sv-bl { translate: 0 -1.5px; rotate: -9deg; }
.steev-face[data-mood="curious"] .sv-br { translate: 0 0.2px; rotate: 2deg; }
.steev-face[data-mood="curious"] { rotate: calc(-1.8deg + var(--face-turn, 0deg)); }

/* ─── mood-amused: brow lift, eyes squint ─── */
.steev-face[data-mood="amused"] .sv-bl { translate: 0 -0.8px; rotate: -8deg; }
.steev-face[data-mood="amused"] .sv-br { translate: 0 0.3px; rotate: 2deg; }
.steev-face[data-mood="amused"] .sv-el,
.steev-face[data-mood="amused"] .sv-er { scale: 1 0.68; }
.steev-face[data-mood="amused"] .sv-m  { rotate: -0.5deg; }
/* widest and lopsided — the smirk. The tilt is the one thing only the mouth
   can do here; the mustache is capped at 1° by the identity envelope. */
.steev-face[data-mood="amused"] .sv-mouth { translate: 0 0.1px; rotate: -8deg; scale: 1.35 0.95; }

/* ─── mood-tired: brows settle, eyes half-closed ─── */
.steev-face[data-mood="tired"] .sv-bl { translate: 0 0.2px; rotate: -3deg; }
.steev-face[data-mood="tired"] .sv-br { translate: 0 0.4px; rotate: 1deg; }
.steev-face[data-mood="tired"] .sv-el { scale: 1 0.5; }
.steev-face[data-mood="tired"] .sv-er { scale: 1 0.55; }
/* slack: a long, thin, dropped line */
.steev-face[data-mood="tired"] .sv-mouth { translate: 0 0.2px; scale: 1.15 0.45; }

/* ─── Outbursts: the mouth OPENS ───
   data-outburst is set by streaming.js when the LLM emits one and deleted
   after 1.8s, letting the mouth ease back to its mood shape.

   An outburst OVERRIDES the mood mouth rather than multiplying it: a gasp is an
   O whatever the mood, and a `tired`-flattened O is not one. iOS's composer
   overwrites the same three channels for the same reason. `--mouth-shade` is
   what makes the reveal read now that the mouth is always present — an open
   mouth is an aperture, deeper than the ink of the lip that was there. */

.steev-face[data-outburst="laugh"] .sv-mouth,
.steev-face[data-outburst="gasp"] .sv-mouth {
  fill: var(--mouth-shade);
  translate: 0;
  rotate: 0;
  scale: 1;
}

.steev-face[data-outburst="laugh"] .sv-mouth {
  transform: scale(1.35) translateY(0.1px);
}

.steev-face[data-outburst="gasp"] .sv-mouth {
  transform: scale(0.9);
  ry: 0.9;  /* taller, smaller — O-shape */
}

/* ─── The chat seat — the face at the head of each coach turn ───
   Glyph only, in the chat's ink; on a celebration the glyph itself takes the
   moment colour (SPEC §3.7: no disc). */
.coach-face .steev-face {
  width: var(--chat-face-size);
  height: var(--chat-face-size);
  color: var(--text);
  --mouth-shade: color-mix(in oklab, currentColor 55%, black 45%);
}

.coach-face:has(+ .scribble--celebration) .steev-face {
  color: var(--moment-glyph);
}

/* ─── Big-moment face (A7) — the outburst / celebration, one-shot ───
   Cloned from #face-moment-template by streaming.js onto the landed coach
   bubble (one per turn) and removed on animationend; never persisted. It
   rises out of the turn's own face seat, just above the bubble, so it stays
   inside the chat window and shifts no layout. */
.face-moment {
  position: absolute;
  left: 0;
  bottom: 100%;
  width: calc(2 * var(--chat-face-size));
  aspect-ratio: 1;
  padding: var(--space-xs);
  margin-inline-start: calc(-1 * var(--space-xs));
  border-radius: var(--radius-pill);
  pointer-events: none;
  transform-origin: 0 100%;
  animation: face-moment-play 2600ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

.face-moment .steev-face {
  width: 100%;
  height: 100%;
  color: var(--text);
  --mouth-shade: color-mix(in oklab, currentColor 55%, black 45%);
}

.scribble--celebration > .face-moment .steev-face {
  color: var(--moment-glyph);
}

@keyframes face-moment-play {
  0%   { opacity: 0; transform: translateY(8px) scale(0.4) rotate(-8deg); }
  12%  { opacity: 1; transform: translateY(0) scale(1.06) rotate(2deg); }
  20%  { transform: none; }
  80%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(3px) scale(0.85); }
}

/* Reduced motion: a pure opacity fade — the moment still lands, without
   spring or travel. animationend still fires, so JS cleanup is shared. */
@keyframes face-moment-fade {
  0%   { opacity: 0; }
  15%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .face-moment {
    animation: face-moment-fade 2600ms ease both;
  }
}

/* ─── Reduced motion: static poses, no transitions ─── */
@media (prefers-reduced-motion: reduce) {
  .steev-face,
  .steev-face svg,
  .steev-face svg * {
    animation: none !important;
  }

  .steev-face,
  .steev-face .sv-el,
  .steev-face .sv-er,
  .steev-face .sv-mouth,
  .steev-face .sv-n {
    transition: none;
  }

  /* The sleeping rules carry their own 300ms transition at a HIGHER
     specificity than the block above, so they escaped this gate. The poses
     themselves are placement and stay; only their easing is motion. */
  .steev-face[data-state="sleeping"] .sv-el,
  .steev-face[data-state="sleeping"] .sv-er,
  .steev-face[data-state="sleeping"] .sv-mouth {
    transition: none;
  }
}

.form-pane--option .form-pane__title {
    font-size: var(--text-xl);
}

.form-pane--option .pane-form__foot .btn {
    flex: 1;
}

.login-create-account {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm) var(--space-md);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.login-create-account p {
  margin: 0;
}

.campaign-offer {
  margin: var(--space-sm) 0 var(--space-md);
  color: var(--text-bright);
  font-weight: var(--weight-semibold);
}

/* Consent checkbox is shown only when the email field has a value.
   Gates a UX moment without JS — see 2026-05-01-email-opt-out-design.md §2. */
.email-consent {
  display: none;
  margin-top: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--text-muted);
  align-items: flex-start;
  gap: var(--space-xs);
  cursor: pointer;
}

.email-consent input[type="checkbox"] {
  margin-top: 0.2em;
  flex-shrink: 0;
}

.form-group:has(input[name="email"]:not(:placeholder-shown)) .email-consent {
  display: flex;
}

.form-pane .email-consent {
  gap: var(--space-sm);
  line-height: 1.45;
  color: var(--text);
}

.form-label .required {
  color: var(--text-secondary);
}

.signup-tos-bullets {
  margin: 0 0 var(--space-sm);
  padding-left: 1.2em;
  display: grid;
  gap: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.signup-tos-links {
  display: flex;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
  font-size: var(--text-sm);
}

.form-pane .signup-tos-bullets {
  margin: 0;
  color: var(--text);
}

.form-pane .signup-tos-links {
  margin: 0;
  flex-wrap: wrap;
}

.form-pane .signup-tos-links a {
  color: var(--text-bright);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.coach-page-content {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.form-pane fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  border: 0;
}

.form-pane legend {
  padding: 0;
  margin-bottom: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--text);
}

.pricing-loading {
  grid-column: 1 / -1;
  text-align: center;
  padding: var(--space-2xl);
  color: var(--text-muted);
}

.pricing-loading .spinner {
  margin-bottom: var(--space-md);
}

/* =============================================================================
   LANDING.CSS — what only the landing has

   The page IS the coach page (pages/landing.html): the ground, the insights, the
   strip, the posters and the floating chat are production and arrive as they are.
   This file adds the marketing bands around them and the replay of the chat. It
   never restyles a shared component: a look that belongs to /coach changes there.
   ============================================================================= */

@keyframes hero-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* The face bobs AND holds the brand's −3° tilt: the rotation lives in the
   keyframe, so the resting transform must not be set on the element too. */
@keyframes face-bob {
  0%, 100% { transform: rotate(-3deg) translateY(0); }
  50%      { transform: rotate(-2deg) translateY(-8px); }
}

@keyframes msg-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes swap-pulse {
  0%   { box-shadow: 0 0 0 0 var(--border-strong); }
  70%  { box-shadow: 0 0 0 14px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

@keyframes demo-window {
  from, to { visibility: visible; }
}

/* ─── The bands: on the coach's ground, beside its chat window ─── */

.landing-hero,
.landing-section {
  --text: var(--wash-ink);
  --text-bright: var(--wash-ink);
  --text-secondary: color-mix(in srgb, var(--wash-ink) 80%, transparent);
  --text-muted: color-mix(in srgb, var(--wash-ink) 80%, transparent);
  --border: color-mix(in srgb, var(--wash-ink) 18%, transparent);
  color: var(--wash-ink);
  padding-inline: var(--screen-inset);
}

.landing-section {
  padding-block: var(--space-2xl);
}

@media (min-width: 1000.02px) {
  .landing-page[data-chat="open"] > :is(.landing-hero, .landing-section) {
    margin-right: calc(var(--chat-window-w) + var(--screen-inset));
  }
}

.landing-section.landing-demo-title {
  padding-block: 0;
  margin-bottom: var(--space-sm);
}

.landing-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  color: var(--text-secondary);
  margin: 0 0 var(--space-md);
}

/* ─── Hero: copy, then the face ─── */

.landing-hero {
  padding-block: clamp(32px, 6vh, 64px) var(--space-2xl);
}

.landing-hero-stage {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xl);
  animation: hero-in 500ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.landing-hero-copy {
  flex: 1 1 400px;
  min-width: 0;
}

.landing-hero-title {
  margin: 0 0 var(--space-lg);
  font-family: var(--font-display);
  font-size: clamp(3.25rem, 6.5vw, 5.75rem);
  font-weight: 900;
  line-height: 0.92;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.landing-hero-pitch {
  font-size: var(--text-lg);
  line-height: 1.55;
  max-width: 52ch;
  margin: 0 0 var(--space-xl);
}

.landing-hero-cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
  margin: 0;
}

.landing-hero-reassure {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.landing-hero-blob {
  width: min(240px, 50vw);
  aspect-ratio: 1;
  margin-inline: auto;
  display: grid;
  place-items: center;
  animation: face-bob 6s ease-in-out infinite;
}

.landing-hero-blob .steev-face {
  width: 72%;
  height: 72%;
}

/* One column: the face heads the copy, small enough that the product still shows
   above the peek. */
@media (max-width: 1000px) {
  .landing-hero-blob {
    order: -1;
    width: 112px;
    margin-inline: 0 auto;
  }

  .landing-hero-stage {
    gap: var(--space-sm);
  }

  .landing-hero {
    padding-block: var(--space-md) var(--space-xl);
  }
}

/* ─── Manifesto: numbered claims ─── */

.landing-stack {
  list-style: none;
  padding: 0;
  margin: 0;
}

.landing-stack-row {
  max-width: 60ch;
  padding-block: var(--space-lg);
}

.landing-stack-num {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  margin-bottom: var(--space-sm);
}

.landing-stack-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  line-height: 1.02;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  margin: 0 0 var(--space-md);
}

.landing-stack-desc {
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0;
}

.landing-stack-marginalia {
  margin: var(--space-xl) 0 0;
  padding: var(--space-md) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* ─── Closing: one display line, the primary door ─── */

.landing-closing {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-lg);
}

.landing-closing-title {
  max-width: 22ch;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 2.75rem);
  font-weight: 900;
  line-height: 0.98;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  text-wrap: balance;
}

.landing-closing-reassure {
  font-size: var(--text-sm);
  color: var(--text-muted);
  margin: 0;
}

.landing-check {
  color: var(--text);
  font-weight: var(--weight-bold);
}

/* ─── The composer is a link here ─── */

.entry-line--door {
  display: block;
  color: inherit;
  text-decoration: none;
}

.entry-line--door .entry-line-input {
  color: var(--text-muted);
}

/* A session the demo strip does not show has no poster to open. */
.landing-page .insight-session[data-node-id=""] {
  pointer-events: none;
}

/* ─── The replay: the finished conversation is the page; each element's rewound
   state lives only in its keyframes, and its `--at` is its moment. Scoped to the
   children of #chat-messages: the day marker, then one group per exchange. ─── */

.landing-page #chat-messages > :nth-child(2) .entry { --at: 0.4s; }
.landing-page #chat-messages > :nth-child(2) [data-demo-typing] { --at: 1s; }
.landing-page #chat-messages > :nth-child(2) :is(.coach-face, .scribble) { --at: 1.9s; }
.landing-page #chat-messages > :nth-child(2) .chat-card { --at: 3.6s; }
.landing-page #chat-messages > :nth-child(3) .entry { --at: 8.1s; }
.landing-page #chat-messages > :nth-child(3) [data-demo-typing] { --at: 8.7s; }
.landing-page #chat-messages > :nth-child(3) :is(.coach-face, .scribble) { --at: 9.6s; }
.landing-page #chat-messages > :nth-child(4) .entry { --at: 11.3s; }
.landing-page #chat-messages > :nth-child(4) [data-demo-typing] { --at: 11.8s; }
.landing-page #chat-messages > :nth-child(4) :is(.coach-face, .scribble) { --at: 12.7s; }
.landing-page #chat-messages > :nth-child(5) .entry { --at: 14.4s; }
.landing-page #chat-messages > :nth-child(5) [data-demo-typing] { --at: 14.9s; }
.landing-page #chat-messages > :nth-child(5) :is(.coach-face, .scribble) { --at: 15.8s; }

/* Typing dots: no footprint when finished, the box is zero-tall and its margin
   cancels the group's gap, so they overlay the reply's slot for their window. */
.landing-page [data-demo-typing] {
  height: 0;
  padding-block: 0;
  align-items: flex-start;
  margin-bottom: calc(-1 * var(--space-sm));
  visibility: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .landing-page #chat-messages > .exchange-group > :is(.entry, .coach-face, .scribble, .chat-card) {
    animation: msg-in var(--motion-arrive) var(--at) backwards;
  }

  .landing-page [data-demo-typing] {
    animation: demo-window 0.9s var(--at);
  }

  /* The moment Steev swaps the session, the strip answers. */
  .landing-page #plan-pane .track-node--today {
    animation: swap-pulse 900ms ease-out 3s;
  }
}

@media (prefers-reduced-motion: reduce) {
  .landing-hero-stage,
  .landing-hero-blob {
    animation: none;
  }

  /* face-bob CARRIES the brand's −3° tilt, so stopping it must not straighten
     the face: every other Steev face on the site is tilted. */
  .landing-hero-blob {
    transform: rotate(-3deg);
  }
}

/* =============================================================================
   COACH-PAGE.CSS - Coach page layout only

   The page is the insights layer (components/insights.css) with the chat
   floating over it (components/chat-window.css). This file owns what the chat
   area needs inside that window.
   ============================================================================= */

body.coach-ledger {
  background: var(--ground, var(--surface));
}

body.coach-ledger > .container {
  max-width: none;
  padding-inline: 0;
}

body.coach-ledger:has(#insights[data-intensity]) :is(.nav-minimal, .site-footer) {
  --text: var(--wash-ink);
  --text-bright: var(--wash-ink);
  --text-secondary: color-mix(in srgb, var(--wash-ink) 80%, transparent);
  --text-muted: color-mix(in srgb, var(--wash-ink) 80%, transparent);
  color: var(--wash-ink);
}

#chat-area {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* Loading state: typing dots need visible height before HTMX loads */
#chat-area > .chat-loading-stub {
  min-height: 4rem;
  display: flex;
  align-items: center;
  background: none;
  padding: var(--space-md);
  border-radius: 0;
}

.coach-inline {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* Stream of bubbles. Past-face hiding (steev-face.css) and awake-tagging
   (steev-face.js) work per-parent inside this stream. */
.message-stream {
  display: flex;
  flex-direction: column;
  gap: 30px;
  padding-bottom: 20px;
  min-width: 0;
}

body.coach-ledger .message-stream {
  gap: var(--space-lg);
}

/* The seam's Calendar slot lands the plan pane under the pinned nav. */
.coach-layout #plan-pane {
  scroll-margin-top: calc(var(--coach-header-h) + var(--space-md));
}

/* =============================================================================
   LEADERBOARD.CSS - The leaderboard page's board width
   ============================================================================= */

.leaderboard-board > .chat-card {
  max-width: none;
  margin: 0;
}

.flex {
  display: flex;
}

.text-sm {
  font-size: var(--text-sm);
}
.text-xs {
  font-size: var(--text-xs);
}

.htmx-indicator {
  display: none;
}
.htmx-request .htmx-indicator {
  display: flex;
}
.htmx-request.htmx-indicator {
  display: flex;
}

.spinner {
  width: 2rem;
  height: 2rem;
  border: 3px solid var(--surface-sunken);
  border-top-color: var(--text-bright);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
.spinner-sm {
  width: 1rem;
  height: 1rem;
  border-width: 2px;
}

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

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* =============================================================================
   SCROLL ROW + EDGE FADE — a horizontal scroller and its overflow cue.

   The fade is unconditional, and that is exactly why it needs no JS: a mask over
   an edge that has no content at it is invisible, so it appears when the row
   overflows and vanishes when it does not. A surface that is not a horizontal
   scroller nulls the mask itself.
   ============================================================================= */

.scroll-row {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.scroll-row::-webkit-scrollbar {
  display: none;
}

/* A mouse has no swipe: streaming.js drags these rows by scrollLeft, and the
   cursor is the only thing announcing it. Fine pointers only — a finger keeps
   native momentum scrolling and needs no cursor. The clickable children carry
   their own `cursor: pointer`, so `grab` reads on the row's own ground; while a
   drag is live the row overrides them, because nothing in it is clickable then. */
@media (pointer: fine) {
  .scroll-row {
    cursor: grab;
  }
  .scroll-row--dragging,
  .scroll-row--dragging * {
    cursor: grabbing;
    user-select: none;
  }
}

.edge-fade,
.edge-fade--trailing {
  --edge-fade-size: 20px;
  mask-image: linear-gradient(
    to right,
    transparent,
    black var(--edge-fade-size),
    black calc(100% - var(--edge-fade-size)),
    transparent
  );
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    black var(--edge-fade-size),
    black calc(100% - var(--edge-fade-size)),
    transparent
  );
}

/* Trailing edge only — for a scroller that always opens at offset 0, where a
   leading fade would permanently dim the first item and read as a bug. */
.edge-fade--trailing {
  mask-image: linear-gradient(
    to right,
    black calc(100% - var(--edge-fade-size)),
    transparent
  );
  -webkit-mask-image: linear-gradient(
    to right,
    black calc(100% - var(--edge-fade-size)),
    transparent
  );
}

/* =============================================================================
   MAIN.CSS - CSS Entry Point

   This file imports all modular CSS files. The order matters:
   1. Foundation - Design tokens, typography, layout primitives
   2. Components - Reusable UI components (alphabetical within block)
   3. Page-specific - Auth, pages, store, public, landing
   4. Feature-specific - Timeline, coach page, leaderboard
   5. Utilities - Utility classes (last for override capability)
   ============================================================================= */

/* ─── Foundation ─── */

/* ─── Components (alphabetical) ─── */

/* ─── Page-specific ─── */

/* ─── Feature-specific ─── */

/* ─── Utilities (last so they can override) ─── */
