/* Layout, components and chart patterns. Tokens live in tokens.css. */
*, *::before, *::after { box-sizing: border-box; }
html { color-scheme: light dark; scroll-behavior: smooth; }
body {
  margin: 0; background: var(--canvas); color: var(--body); font-family: var(--font-sans);
  font-size: 16px; line-height: 1.55; -webkit-font-smoothing: antialiased;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
/* Scrollbars take the ladder's warm gray, never the platform's black-on-white bar. Firefox reads
   scrollbar-color from body; Chromium and WebKit need the pseudo-elements. */
*::-webkit-scrollbar { width: 10px; height: 10px; background: transparent; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: var(--r-pill); border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-corner { background: transparent; }
h1, h2, h3 { color: var(--ink); margin: 0 0 var(--s-3); display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
h1 { font-family: var(--font-display); font-weight: 400; font-size: 28px; letter-spacing: -0.3px; line-height: 1.2; }
h2 { font-size: 22px; font-weight: 500; line-height: 1.3; }
h3 { font-size: 18px; font-weight: 500; line-height: 1.4; }
p { margin: 0 0 var(--s-3); }
a { color: var(--text-accent); }
button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; }
:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }
/* A heading focused on a route change: the ring wraps the words with a button's breathing
   room, not the heading's page-wide block box. Headings are flex, so fit-content is honored. */
:is(h1, h2)[tabindex="-1"] { width: fit-content; max-width: 100%; padding: 2px 8px; margin-left: -8px; border-radius: var(--r-md); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip-link { position: absolute; left: -999px; top: var(--s-2); background: var(--ink); color: var(--canvas); padding: var(--s-2) var(--s-3); border-radius: var(--r-md); z-index: 100; }
.skip-link:focus { left: var(--s-3); }
.icon { width: 18px; height: 18px; flex: none; vertical-align: middle; }
.icon.lg { width: 28px; height: 28px; }

/* app frame */
.app-header { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--hairline); flex-wrap: wrap; }
.app-header .brand { font-family: var(--font-display); font-size: 20px; color: var(--ink); text-decoration: none; letter-spacing: -0.2px; }
.app-header nav { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.app-header nav a { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: var(--body); padding: var(--s-1) var(--s-3); border-radius: var(--r-pill); font-size: 14px; font-weight: 500; }
.app-header nav a[aria-current="page"] { background: var(--surface-card); color: var(--ink); }
.app-header .spacer { flex: 1; }
@media (max-width: 899px) {
  /* Brand and theme toggle on one row, then the nav as small chips that wrap: every route stays
     visible at 320 with nothing to discover behind a hidden horizontal scroll. */
  .app-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-2) var(--s-3); padding: var(--s-2) var(--s-3); }
  .app-header .brand { grid-row: 1; grid-column: 1; font-size: 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .app-header > .btn { grid-row: 1; grid-column: 2; }
  .app-header .spacer { display: none; }
  .app-header nav { grid-row: 2; grid-column: 1 / -1; flex-wrap: wrap; gap: var(--s-1); }
  .app-header nav a { flex: none; white-space: nowrap; padding: 6px 10px; font-size: 13px; gap: 4px; }
}
@media (max-width: 399px) { .app-header nav a svg { display: none; } }
main { padding: var(--s-4); max-width: 1500px; margin: 0 auto; }
@media (min-width: 900px) { main { padding: var(--s-5) var(--s-6); } }

/* status pill */
.status-pill { position: relative; }
.status-pill > button { display: inline-flex; align-items: center; gap: var(--s-1); padding: var(--s-1) var(--s-3); border-radius: var(--r-pill); border: 1px solid var(--hairline); background: var(--surface-soft); font-size: 13px; font-weight: 500; }
.status-pill > button.warn { background: var(--bg-warning); color: var(--text-warning); }
.status-pill .panel { position: absolute; right: 0; top: calc(100% + 6px); background: var(--canvas); border: 1px solid var(--hairline); border-radius: var(--r-lg); box-shadow: var(--shadow-md); padding: var(--s-3) var(--s-4); min-width: 280px; z-index: 30; font-size: 14px; }
.status-pill .panel dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: var(--s-1) var(--s-3); }
.status-pill .panel dt { color: var(--muted); }
.banner { display: flex; gap: var(--s-2); align-items: flex-start; padding: var(--s-2) var(--s-3); border-radius: var(--r-md); background: var(--bg-warning); color: var(--text-warning); font-size: 14px; margin-bottom: var(--s-3); }
.banner.info { background: var(--bg-accent); color: var(--text-accent); }

/* cards and buttons */
.card { background: var(--surface-card); border-radius: var(--r-lg); padding: var(--s-4) var(--s-5); }
.card.soft { background: var(--surface-soft); }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2); padding: 8px 16px; border-radius: var(--r-md); border: 1px solid var(--border-strong); background: var(--control-bg); color: var(--ink); font-size: 14px; font-weight: 500; min-height: 38px; text-decoration: none; transition: background var(--dur-base) var(--ease-out), transform var(--dur-snap) var(--ease-snap); }
.btn:hover { background: var(--surface-card); }
.btn:active { transform: scale(0.98); }
.btn.primary { background: var(--primary-active); color: #fff; border-color: transparent; }
/* Hover darkens the one coral toward ink: the same token as the result card's stripe. */
.btn.primary:hover { background: color-mix(in srgb, var(--primary-active) 85%, var(--ink)); }
.btn.ghost { border-color: transparent; background: transparent; }
.btn.small { padding: 5px 12px; min-height: 32px; font-size: 13px; }
.btn.icon-only { padding: 5px; width: 32px; min-height: 32px; }
.btn.fixed { min-width: 96px; }
.btn[aria-pressed="true"] { background: var(--surface-strong); color: var(--ink); border-color: var(--ink); }
.btn[aria-pressed="mixed"] { background: var(--surface-strong); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-row { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; }

/* badges */
.badge { display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border-radius: var(--r-pill); font-size: 12px; font-weight: 500; letter-spacing: 0.2px; background: var(--bg-neutral); color: var(--body); border: 1px solid var(--border); line-height: 1.5; }
.badge .icon { width: 12px; height: 12px; }
.badge.beta { background: var(--bg-accent); color: var(--text-accent); }
.badge.new { background: var(--bg-warning); color: var(--text-warning); }
.badge.deprecated { text-decoration: line-through; }
.badge.measured { background: var(--bg-success); color: var(--text-success); }
.badge.estimated { background: var(--bg-warning); color: var(--text-warning); border-style: dashed; }
.badge.stale { background: var(--bg-danger); color: var(--text-danger); }
.badge.later { background: var(--bg-neutral); color: var(--muted); }

/* zippy and glossary */
details.zippy { margin-top: var(--s-1); font-size: 14px; }
details.zippy summary { cursor: pointer; color: var(--text-accent); font-weight: 500; list-style: none; display: inline-flex; align-items: center; gap: 4px; }
details.zippy summary::-webkit-details-marker { display: none; }
details.zippy summary::before { content: ""; width: 0; height: 0; border-left: 5px solid currentColor; border-top: 4px solid transparent; border-bottom: 4px solid transparent; transition: transform var(--dur-base) var(--ease-out); }
details.zippy[open] summary::before { transform: rotate(90deg); }
details.zippy .zippy-body { padding: var(--s-2) 0 0 var(--s-3); color: var(--body); max-width: 70ch; }
.answer { margin-top: var(--s-3); }
.answer .eyebrow { margin-bottom: var(--s-1); }
.answer .headline .change { font-weight: 400; }
.answer .reason { font-size: 15px; color: var(--body); margin: 0 0 var(--s-1); max-width: 70ch; }
/* "Go there" split over a line end is two tap targets that look like one word each. */
.answer .reason a { white-space: nowrap; }
/* The arrival card is the first thing read; its two sentences take the card's width. */
.answer.arrival .reason { max-width: none; }
details.zippy .zippy-body dl { margin: 0; }
details.zippy .zippy-body dt { font-weight: 500; color: var(--ink); margin-top: var(--s-1); }
details.zippy .zippy-body dd { margin: 0 0 var(--s-1); }
.term { background: none; border: none; padding: 0; color: inherit; text-decoration: underline dashed; text-underline-offset: 3px; text-decoration-color: var(--muted); cursor: help; font: inherit; }
.tooltip { position: fixed; z-index: 60; max-width: 320px; background: var(--tooltip-bg); color: var(--tooltip-fg); padding: var(--s-2) var(--s-3); border-radius: var(--r-md); font-size: 14px; line-height: 1.45; box-shadow: var(--shadow-md); pointer-events: none; }
.tooltip a { color: inherit; }
.deep-link { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--text-accent); text-decoration: none; }
.deep-link:hover { text-decoration: underline; }

/* home */
.entry-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); margin: var(--s-5) 0; }
.entry-card { display: flex; flex-direction: column; gap: var(--s-2); text-decoration: none; color: var(--ink); background: var(--surface-card); border-radius: var(--r-lg); padding: var(--s-5); min-height: 160px; transition: transform var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out); }
.entry-card:hover { transform: translateY(-2px); background: var(--surface-strong); }
.entry-card .time { color: var(--muted); font-size: 13px; margin-top: auto; }
.privacy { color: var(--muted); font-size: 13px; }

/* reference (look it up) */
.wizard { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
/* An even split at every desktop width: the questions' prose keeps 45 characters a line or
   more, and the chip rows stay whole. A 5:7 split from 1100 px gave the answer column width
   its one card never used while "Can't tell" wrapped alone onto a second row (R10-D-06); the
   card is capped below so the wider column does not stretch it. */
@media (min-width: 900px) { .wizard { grid-template-columns: minmax(380px, 1fr) minmax(320px, 1fr); align-items: start; } }
@media (min-width: 1100px) { .wizard { grid-template-columns: minmax(300px, 1fr) minmax(320px, 1fr); } }
.wizard .question { margin-bottom: var(--s-4); }
.wizard .question h3 { font-size: 16px; }
.wizard .question h3 .q-text { flex: 1; min-width: 0; }
.wizard .jump-strip { display: none; }
@media (max-width: 899px) {
  /* The answer list is a screen below the chips: the count follows until the person gets there. */
  /* Fixed rather than sticky: a sticky strip keeps a blank slot in the column after it has done its job. */
  .wizard .jump-strip { display: flex; gap: var(--s-3); align-items: center; position: fixed; bottom: var(--s-2); left: var(--s-3); right: var(--s-3); margin: 0; padding: var(--s-2) var(--s-3); background: var(--surface-strong); color: var(--ink); border: 1px solid var(--border-strong); border-radius: var(--r-md); font-size: 14px; box-shadow: var(--shadow-md); z-index: 20; }
  .wizard .jump-strip[hidden] { display: none; }
  /* While the strip is up, the column ends a strip's height early, so the last chip row scrolls clear of it. */
  .wizard:has(.jump-strip:not([hidden])) { padding-bottom: 104px; }
  .wizard .jump-strip .text { flex: 1 1 auto; min-width: 0; }
  /* Two actions of different kinds: See rows is the strip's one button, Dismiss a 32 px close
     at its corner; both inherit the strip's inverted colors. */
  .wizard .jump-strip .btn { flex: none; color: inherit; background: transparent; border-color: currentColor; }
  .wizard .jump-strip .btn.icon-only { border-color: transparent; }
}
/* Under 400 px the message gets the strip's whole width and the buttons a second row. A sentence
   that overruns wraps to a second line rather than clipping mid-word (R9-P-03); the full
   sentence is announced. */
@media (max-width: 399px) {
  .wizard .jump-strip, .workspace-root .jump-strip { flex-wrap: wrap; gap: 2px var(--s-3); padding-top: 4px; padding-bottom: 6px; }
  .wizard .jump-strip .text, .workspace-root .jump-strip .text { flex-basis: 100%; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .wizard .jump-strip .btn:not(.icon-only), .workspace-root .jump-strip .btn:not(.icon-only) { margin-left: auto; }
}
.chip-row { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: var(--r-pill); border: 1px solid var(--border-strong); background: var(--control-bg); font-size: 14px; min-height: 36px; transition: background var(--dur-base) var(--ease-out); }
.chip.small { padding: 3px 10px; font-size: 13px; min-height: 28px; }
.chip[aria-pressed="true"], .chip[aria-checked="true"] { background: var(--surface-strong); color: var(--ink); border-color: var(--ink); }
.row-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-3); counter-reset: row; }
.row-list > li { display: grid; grid-template-columns: 32px 1fr; gap: var(--s-3); align-items: start; }
.row-list > li::before { counter-increment: row; content: counter(row); width: 32px; height: 32px; border-radius: 50%; background: var(--primary-active); color: var(--canvas); display: inline-flex; align-items: center; justify-content: center; font-weight: 500; font-size: 14px; margin-top: 4px; }
/* One coral for the accent role: the stripe is the button's color, so a card shows one rust, not two. */
.result-card { border-left: 4px solid var(--primary-active); }
.result-card .figure { font-size: 15px; }
.row-list .result-card { max-width: 720px; }
.row-list .result-card p { max-width: 62ch; }
/* A 24 px touch target with a clear gap to the Try it button. */
.result-card details.zippy { margin: var(--s-2) 0 var(--s-3); }
.result-card details.zippy summary { min-height: 24px; }
.row-list .result-card h3 { flex-wrap: nowrap; align-items: flex-start; }
.row-list .result-card h3 .icon { flex: none; margin-top: 2px; }
.row-list .result-card h3 .q-text { flex: 1; min-width: 0; }

/* browse */
.tag-groups { display: flex; flex-direction: column; gap: var(--s-2); margin-bottom: var(--s-5); }
.tag-group { display: block; font-size: 13px; }
.tag-group summary { cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: var(--s-2); }
.tag-group summary::-webkit-details-marker { display: none; }
.tag-group summary::before { content: ""; width: 0; height: 0; border-left: 5px solid currentColor; border-top: 4px solid transparent; border-bottom: 4px solid transparent; color: var(--muted); transition: transform var(--dur-base) var(--ease-out); }
.tag-group[open] summary::before { transform: rotate(90deg); }
.tag-group .tag-label { color: var(--muted); font-weight: 500; }
.tag-group .n { color: var(--muted); font-size: 12px; }
.tag-group .tag-chips { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-2); }
.count { color: var(--muted); font-size: 14px; margin: 0 0 var(--s-3); }
/* Tiles stretch to the row: a shorter quote leaves room inside its tile, not a hole under it. */
.card-grid { display: grid; gap: var(--s-4); grid-template-columns: 1fr; align-items: stretch; }
@media (min-width: 720px) { .card-grid { grid-template-columns: repeat(2, 1fr); } }
.browse .count { display: flex; gap: var(--s-3); align-items: center; flex-wrap: wrap; }
.browse .count .btn[hidden] { display: none; }
.link-card { text-decoration: none; color: inherit; display: block; transition: transform var(--dur-base) var(--ease-out); }
.link-card:hover { transform: translateY(-2px); }
.link-card h3 { font-size: 16px; margin-bottom: 4px; }
/* Title and chip on one line, the title wrapping under itself. */
.link-card h3 { display: flex; align-items: flex-start; gap: var(--s-2); flex-wrap: nowrap; color: var(--ink); margin: 0 0 4px; font-weight: 500; line-height: 1.4; }
.link-card h3 .q-text { flex: 1; min-width: 0; }
/* The tile's title reads as the link and its heading row ends in Open, the trap cards' idiom:
   one look for every card that opens something. The mark is decorative and sits on the
   title's first line. */
.link-card .q-text { color: var(--text-accent); text-decoration: underline transparent; text-underline-offset: 3px; transition: text-decoration-color var(--dur-base) var(--ease-out); }
.link-card:hover .q-text, .link-card:focus-visible .q-text { text-decoration-color: currentColor; }
.link-card .open { flex: none; color: var(--muted); margin: -5px -8px 0 0; }
.intro { max-width: 70ch; }
.daily > section.card { margin-bottom: var(--s-4); }
/* The reveal's zippy belongs to the sentence it expands: 4 px under the feel line, a row clear of the buttons. */
.daily section.card details.zippy + .btn-row { margin-top: var(--s-3); }
.app-footer { max-width: 1500px; margin: var(--s-7) auto 0; padding: var(--s-4) var(--s-4) var(--s-6); border-top: 1px solid var(--hairline); font-size: 13px; color: var(--muted); }
@media (min-width: 900px) { .app-footer { padding: var(--s-4) var(--s-6) var(--s-6); } }

/* daily card */
/* Four options in one column, two by two, or one row: never three and an orphan. The four-across
   row starts where each option still gets 25 characters a line. */
.daily .options { display: grid; gap: var(--s-2); grid-template-columns: 1fr; margin: var(--s-3) 0; }
@media (min-width: 560px) { .daily .options { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .daily .options { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.daily .stage.pair { display: grid; gap: var(--s-4); grid-template-columns: 1fr; }
/* Stretched, not start-aligned: the two figures end on one y, so the shared legend under them
   is the same distance from both, not a footer hung off the shorter one (R10-D-03). */
@media (min-width: 900px) { .daily .stage.pair { grid-template-columns: 1fr 1fr; align-items: stretch; } }
/* The legend and hint are shared by the pair, so they span it under both figures, hung from
   the card's own left edge: the figures share the card's fill, so their inline padding is
   dropped and their eyebrow, title and legend hang from the same x as the sibling cards'
   content, and the notes with them (R8-D-06, R9-D-06); the single-figure card the same, since it
   stacks with the same siblings. The pair's grid gap keeps the two figures apart; the notes
   pull back that gap and the figures' bottom padding, so the legend sits under the longer
   note at the note's own margin, the distance it keeps inside a single figure (R10-D-03). The
   figures share the card's fill, so the overlap with their padding is invisible. */
.daily .stage figure.chart { padding-inline: 0; }
.daily .stage.pair > .pair-notes { grid-column: 1 / -1; padding: 0; margin-top: calc(0px - var(--s-3) - var(--s-4)); }
.daily .stage.pair > .pair-notes > :first-child { margin-top: 0; }
/* An eyebrow, not a pill: the ink pill is the selected chip on the routes before this one, and a
   static state name in that costume reads as a toggle. Its own block above the caption, so both
   figures' captions are one line and the pair's hairlines share a y. */
.daily .state-label { display: inline-block; margin: 0 0 var(--s-1); background: transparent; color: var(--muted); border: 1px solid var(--hairline); border-radius: var(--r-sm); padding: 0 6px; font-size: 12px; letter-spacing: 0.02em; text-transform: uppercase; white-space: nowrap; }
/* The other options applied to the Clean state, drawn small under the pair (X1-09): the trap
   card's mini idiom, three across, the pair's scale. */
.daily .stage > .pick-shapes { grid-column: 1 / -1; }
.daily .pick-shapes .mini { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-2); align-items: start; }
.daily .pick-shapes .mini svg { width: 100%; height: 44px; display: block; overflow: visible; }
.daily .pick-shapes .mini span { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.daily .pick-shapes .mini .value { color: var(--ink); font-variant-numeric: tabular-nums; }
@media (max-width: 719px) { .daily .pick-shapes .mini { grid-template-columns: 1fr; } }
/* Title and toggle stay on one row; the title wraps under itself. */
.daily figure.chart figcaption { flex-wrap: nowrap; align-items: flex-start; }
.daily figure.chart figcaption .chart-title { flex: 1; min-width: 0; }
.daily figure.chart figcaption .spacer { display: none; }
.daily figure.chart figcaption .btn { flex: none; }
/* Under 400 px the title gets the room: the Table button keeps its icon and its name, not its word. */
.daily .lock-note { display: flex; align-items: flex-start; max-width: 70ch; }
.daily .lock-note .icon { flex: none; margin-top: 3px; }
.option { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-3); border-radius: var(--r-md); border: 1px solid var(--border-strong); background: var(--control-bg); text-align: left; min-height: 56px; transition: background var(--dur-base) var(--ease-out); }
.option[aria-pressed="true"] { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
.option.correct { border-color: var(--outcome-solved); background: var(--bg-success); }
.option.wrong { border-color: var(--outcome-failed); background: var(--bg-danger); }
/* After the commit the unchosen options are dead, and look it, so nobody taps one expecting a second try. */
.option[disabled]:not(.correct):not(.wrong) { opacity: .55; cursor: default; }
.lock-note { display: inline-flex; gap: 6px; align-items: center; color: var(--muted); font-size: 14px; }
/* The Go there link flows as the note's last words and never splits over two lines. */
.lock-note .lock-text { min-width: 0; }
.lock-note .lock-text a { white-space: nowrap; }
.headline { font-family: var(--font-display); font-size: 22px; color: var(--ink); line-height: 1.25; letter-spacing: -0.2px; margin: 0 0 var(--s-2); display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }
.headline .delta { font-family: var(--font-sans); font-size: 14px; font-weight: 500; color: var(--muted); }
.headline .delta.better { color: var(--text-success); }
.headline .delta.worse { color: var(--text-danger); }
/* The reveal's headline is an h2 for heading navigation (R9-S-04), bold as the takeaway is on
   the trap card and detail: it is the lesson restated after the experiment's answer. */
.daily h2.headline { font-weight: 600; }
.customer { color: var(--body); }
/* One eyebrow recipe for every small-caps label on the workspace: the customer line, the answer
   card's heading and the KPI tile labels. `.customer .who` is listed so the daily page's line
   shares it without carrying the class. No trailing colon on any of them. */
.eyebrow, .customer .who { font-size: 12px; font-weight: 500; letter-spacing: 0.8px; text-transform: uppercase; color: var(--muted); line-height: 1.3; margin: 0; }
.customer .who { display: block; margin-bottom: 2px; }
.customer q { font-style: italic; }
.customer q::before, .customer q::after { content: ""; }

/* workspace */
.workspace { display: grid; gap: var(--s-4); grid-template-columns: minmax(0, 1fr); }
.ws-header { margin-bottom: var(--s-4); }
/* The goals row is shared with the daily card (R9-D-03). */
.ws-header .goals, .daily .goals { display: flex; gap: var(--s-4); flex-wrap: wrap; font-size: 14px; color: var(--muted); margin-top: var(--s-2); }
.ws-header .goals span, .daily .goals span { display: inline-flex; align-items: center; gap: 6px; }
.tabs { display: flex; gap: var(--s-1); border-bottom: 1px solid var(--hairline); margin-bottom: var(--s-3); flex-wrap: wrap; }
.tabs [role="tab"] { background: none; border: none; padding: var(--s-2) var(--s-3); border-bottom: 2px solid transparent; font-weight: 500; color: var(--muted); display: inline-flex; gap: 6px; align-items: center; white-space: nowrap; transition: color var(--dur-base) var(--ease-out); }
.tabs [role="tab"][aria-selected="true"] { color: var(--ink); border-bottom-color: var(--ink); }
.tabs [role="tab"]:disabled { opacity: 0.55; cursor: not-allowed; }
/* The same strip in the narrow bar: three tabs share the width, each centered, the strip's
   own hairline dropped since the bar draws one. */
.mode-toggle .tabs { margin-bottom: 0; flex-wrap: nowrap; gap: 0; border-bottom: none; }
.mode-toggle .tabs [role="tab"] { flex: 1; justify-content: center; }
@media (min-width: 720px) {
  /* DOM order is levers, timeline, results; the grid puts the timeline under both columns. */
  .workspace { grid-template-columns: minmax(440px, 11fr) minmax(360px, 9fr); grid-template-areas: "levers results" "timeline timeline"; align-items: start; }
  .workspace .tabs-panel { grid-area: levers; }
  .workspace .outputs { grid-area: results; }
  .workspace .timeline { grid-area: timeline; }
  /* Height from where the panel starts (set by the workspace), leaving the timeline bar clear.
     Never under 40vh: a tall answer card (Why open) lets the page scroll, with the timeline
     sticky at the bottom, rather than squeeze both working panes to a few rows. */
  .workspace .tabs-panel, .workspace .outputs { max-height: max(40vh, calc(100vh - var(--ws-top, 246px) - 56px)); overflow-y: auto; padding-right: var(--s-2); position: sticky; top: var(--s-3); }
  /* A panel with more below its edge says so at its foot: a hairline and a word that stick to
     the edge whatever content lands there (a fade painted canvas across a grid gap). The
     workspace drops .more at the end. */
  /* The cue is set apart from a lever's caption (12px sentence case, the same muted color,
     right-aligned): an arrow, small caps and letterspacing, and a taller top pad, so a
     caption clipped at the hairline and the cue under it never read as one two-line sentence. */
  .workspace .tabs-panel::after, .workspace .outputs::after { content: "\2193 more below" / ""; display: block; position: sticky; bottom: 0; visibility: hidden; text-align: right; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1.3; color: var(--muted); padding: 6px var(--s-2) 0; border-top: 1px solid var(--hairline); background: var(--canvas); }
  .workspace .tabs-panel.more::after, .workspace .outputs.more::after { visibility: visible; }
  /* At the 40vh floor the panes run past the viewport, so they stop scrolling inside it: they
     grow to their content and the page carries them. The bar stays stuck to the viewport's
     foot, where it overlays only rows the page can scroll past; at the end of the page it sits
     in its own grid row under both panes. */
  .workspace.tall .tabs-panel, .workspace.tall .outputs { max-height: none; overflow: visible; position: static; }
  .workspace.tall .tabs-panel::after, .workspace.tall .outputs::after { display: none; }
  /* The Levers/Code strip stays at the pane's top while the rows scroll under it, so a Go
     there that lands deep in the Tradeoffs group leaves the tab in sight and Code in reach.
     Its own hairline is the top edge; the word at its right end says rows are clipped above,
     as the foot says rows are clipped below. The strip's spacing moves onto the panel under
     it, since a sticky box's margin is a gap rows would show through. */
  .workspace .tabs-panel > .tabs { position: sticky; top: 0; z-index: 2; background: var(--canvas); margin-bottom: 0; }
  .workspace .tabs-panel > .tabs + * { padding-top: var(--s-3); }
  .workspace .tabs-panel > .tabs::after { content: "\2191 more above" / ""; margin-left: auto; align-self: flex-end; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1.3; color: var(--muted); padding: 0 var(--s-2) 3px; visibility: hidden; }
  .workspace .tabs-panel.more-above > .tabs::after { visibility: visible; }
  .workspace.tall .tabs-panel > .tabs { position: static; }
  .narrow-bar, .mode-toggle { display: none; }
}
/* Side by side from 720: between 720 and 899 the page gutter, the card padding and the grid gap
   step down to 12 px and the controls column gives up its slack, so two columns fit without
   shrinking the text (14 px) or the chart labels (12 px). Only empty card space is spent. */
@media (min-width: 720px) and (max-width: 899px) {
  main:has(.workspace-root) { padding: 12px; }
  .workspace { gap: 12px; grid-template-columns: minmax(300px, 5fr) minmax(280px, 6fr); }
  .workspace-root .answer, .workspace figure.chart, .workspace .kpi-card, .workspace .kpi { padding: 12px; }
  .workspace .outputs { --chart-inset: 24px; }
  .workspace .tabs-panel, .workspace .outputs { padding-right: 4px; }
}
/* The footer's own top margin would leave a second gap under the timeline bar, at every width:
   main's padding (larger wide) plus the footer's margin, cut back to one spacing step. */
.workspace-root { margin-bottom: calc(var(--s-4) - var(--s-7)); }
/* Wide shows both panels, so the results strip has nowhere to point. */
.workspace-root .jump-strip { display: none; }
@media (min-width: 720px) { .workspace-root { margin-bottom: calc(var(--s-4) - var(--s-7) - var(--s-5)); } }

@media (max-width: 719px) {
  .workspace[data-mode="controls"] .outputs { display: none; }
  .workspace[data-mode="results"] .tabs-panel { display: none; }
  /* A sticky bar cannot leave its container, and on a phone the workspace starts below the
     fold, so on arrival it sat across the first control. Fixed to the viewport instead, with
     its height reserved on the container so the last block is never under it. */
  .workspace { padding-bottom: 56px; }
  .narrow-bar .timeline { position: fixed; left: 0; right: 0; bottom: 0; z-index: 25; transition: transform var(--dur-base) var(--ease-out); }
  /* Until the first change the bar has nothing to press; the levers get the whole viewport.
     The reserved padding stays so nothing jumps when it slides in. */
  .narrow-bar .timeline:not(:has(.step)) { transform: translateY(100%); }
  /* A zippy that opens under the fixed bar scrolls its body into view; this is the bar's height,
     so "nearest" stops above it and the first sentence is read, not the chrome. */
  .workspace-root .zippy-body { scroll-margin-bottom: 64px; }
  /* The bar shrinks when it sticks and the content moves up by the difference, as a collapsing
     header does. Scroll anchoring would undo that move to hold the row under the bar still,
     which brings the sentinel back into view, unsticks the bar, and loops. */
  .workspace-root { overflow-anchor: none; }
  .stick-sentinel { height: 1px; margin-bottom: -1px; }
  .narrow-bar { position: sticky; top: 0; z-index: 24; background: var(--canvas); border-bottom: 1px solid var(--hairline); margin-bottom: var(--s-3); }
  /* A change that landed behind the Controls or Code tab: its one line and the way to the
     card, fixed just above the timeline bar on Look it up's answer strip recipe. While it is
     up the workspace ends a strip earlier, so the last row scrolls clear of both bars. */
  .workspace-root .jump-strip { display: flex; gap: var(--s-3); align-items: center; position: fixed; bottom: 64px; left: var(--s-3); right: var(--s-3); margin: 0; padding: var(--s-2) var(--s-3); background: var(--surface-strong); color: var(--ink); border: 1px solid var(--border-strong); border-radius: var(--r-md); font-size: 14px; box-shadow: var(--shadow-md); z-index: 25; }
  .workspace-root .jump-strip[hidden] { display: none; }
  .workspace-root:has(.jump-strip:not([hidden])) .workspace { padding-bottom: 120px; }
  /* Longhands, not the flex shorthand: the shorthand would reset the flex-basis: 100% the
     under-400 rule above sets, and the Dismiss button would wrap alone onto a second row. */
  .workspace-root .jump-strip .text { flex-grow: 1; flex-shrink: 1; min-width: 0; }
  .workspace-root .jump-strip .btn { flex: none; color: inherit; background: transparent; border-color: currentColor; }
  .workspace-root .jump-strip .btn.icon-only { border-color: transparent; }
  .mode-toggle { display: block; }
  .bill-strip { display: flex; gap: var(--s-3); align-items: center; flex-wrap: wrap; padding: var(--s-2) 0 0; }
  .bill-strip .bill-item { display: inline-flex; align-items: baseline; gap: 6px; }
  .bill-strip .kpi-label { font-size: 12px; color: var(--muted); }
  .bill-strip .kpi-value { font-size: 18px; color: var(--ink); font-weight: 500; }
  .bill-strip .kpi-figure { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
  .bill-strip .kpi-delta { font-size: 12px; display: inline-flex; align-items: center; gap: 2px; }
  /* The sticky strip is the bill in results mode; the tile grid starts at Cost per solved task
     beside Pass rate and ends on Latency, two to a row. */
  .workspace[data-mode="results"] .kpis .kpi.primary.kpi-headline { display: none; }
  .bill-strip .bill-item { flex-wrap: wrap; }
  .bill-strip .kpi-note { font-size: 12px; color: var(--muted); flex-basis: 100%; }
  /* While the answer card is on screen it carries the bill; the unstuck strip shows what the
     card does not. Once stuck, the card has scrolled away and the strip takes the bill over. */
  .workspace-root.has-answer .narrow-bar:not(.stuck) .bill-strip .bill-item[aria-label="Monthly bill"] { display: none; }
}
@media (max-width: 532px) {
  /* The strip stacks in one order at every state, so the basis badge has one home beside the
     per-task number; once the bar sticks, only the bill and the toggle stay, under 20% of a phone. */
  .bill-strip { flex-direction: column; align-items: stretch; gap: var(--s-1); }
  .narrow-bar.stuck .bill-strip .bill-item:not(:first-child) { display: none; }
  /* Stuck chrome under 20% of a 667 px phone: one bill line (the target distance is on the
     answer card and the tiles) and a one-line toggle. */
  .narrow-bar.stuck .bill-strip .kpi-note { display: none; }
  .mode-toggle .tabs [role="tab"] { white-space: nowrap; font-size: 13px; padding-inline: 6px; }
}
.group { margin-bottom: var(--s-3); }
.group > summary { cursor: pointer; font-weight: 500; color: var(--ink); font-size: 16px; padding: var(--s-2) 0; list-style: none; display: flex; align-items: center; gap: var(--s-2); }
.group > summary::-webkit-details-marker { display: none; }
.group > summary::before { content: ""; width: 0; height: 0; border-left: 6px solid currentColor; border-top: 5px solid transparent; border-bottom: 5px solid transparent; transition: transform var(--dur-base) var(--ease-out); }
.group[open] > summary::before { transform: rotate(90deg); }
.group > summary .spacer { flex: 1; }
.group > summary h2 { font-size: 16px; font-weight: 500; margin: 0; display: inline; }
.switch-label { display: inline-flex; align-items: center; gap: var(--s-2); font-size: 14px; color: var(--ink); font-weight: 500; cursor: pointer; }
.group .group-body { padding-left: var(--s-4); border-left: 2px solid var(--hairline-soft); margin-left: 4px; }
/* The group switch sits in the same column as the row switches it drives. */
.group-toolbar { padding: var(--s-1) 0 var(--s-2); }
/* Label and count lead; the control keeps the right edge and drops to its own line in a
   column too narrow for all three (the 720 to 899 range), rather than wrap the count into four. */
.group-toolbar .switch-label { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-3); align-items: center; }
/* Off, Some, All at one fixed width each, so the thumb moves and nothing else does; Some is a
   state the rows set, shown selected and never pressable, so it keeps full strength when it is
   the selected position and dims like any unpressable segment otherwise. */
.group-toolbar .segmented.free-wins { flex-wrap: nowrap; margin-left: auto; }
.group-toolbar .segmented.free-wins button { width: 52px; justify-content: center; padding-inline: 0; }
.group-toolbar .segmented.free-wins button[aria-checked="true"]:disabled { opacity: 1; cursor: default; }
.group-toolbar .count { margin: 0; flex: 1 1 12em; }
.control-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-1) var(--s-3); align-items: center; padding: var(--s-2) 0; border-top: 1px solid var(--hairline-soft); }
.control-row .label { display: flex; flex-direction: column; gap: 2px; }
.control-row .label .name { color: var(--ink); font-weight: 500; }
.control-row .badges { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.control-row .control { justify-self: end; display: flex; align-items: center; gap: var(--s-2); }
/* A cue stacked beside its switch sized the auto column to the cue's width and left the title
   a third of the row at every desktop width. The cell dissolves into the row grid instead: the
   switch keeps column 2 of row 1 beside a title that keeps minmax(0, 1fr), and the cue takes
   its own row under both, right-aligned under the switch, so it never sizes the column. */
.control-row .control:has(.polarity) { display: contents; }
/* Any control with a cue under it: a switch, or the select or radio group a trap's fix names. */
.control-row .control:has(.polarity) > :not(.polarity) { grid-column: 2; grid-row: 1; justify-self: end; }
.control-row .polarity { grid-column: 1 / -1; grid-row: 2; justify-self: end; font-size: 12px; text-align: right; }
.control-row .zippy-cell { grid-column: 1 / -1; }
.control-row.locked .control { opacity: 0.55; }
.control-row.deprecated .label .name { text-decoration: line-through; color: var(--muted); }
.disclaimer { font-size: 13px; color: var(--muted); border-top: 1px solid var(--hairline); padding-top: var(--s-3); margin-top: var(--s-3); }

/* controls */
/* The off track reaches 3:1 on the canvas (tokens.css) and the edge carries the knob, which
   is 2.6:1 on that track: the control's extent is visible before it is touched. */
.switch { position: relative; width: 40px; height: 22px; border-radius: var(--r-pill); background: var(--switch-track); border: 1px solid var(--border-strong); padding: 0; transition: background var(--dur-base) var(--ease-out); flex: none; }
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--switch-knob); box-shadow: var(--shadow-sm); transition: transform var(--dur-snap) var(--ease-snap); }
.switch[aria-checked="true"] { background: var(--switch-track-on); border-color: var(--switch-track-on); }
.switch[aria-checked="true"]::after { transform: translateX(18px); }
.switch:disabled { opacity: 0.5; cursor: not-allowed; }
.segmented { display: inline-flex; background: var(--segment-track); border-radius: var(--r-md); padding: 2px; gap: 2px; flex-wrap: wrap; }
.segmented button { border: none; background: transparent; padding: 4px 10px; border-radius: var(--r-sm); font-size: 13px; font-weight: 500; color: var(--body); min-height: 28px; display: inline-flex; gap: 4px; align-items: center; transition: background var(--dur-base) var(--ease-out); }
.segmented button[aria-checked="true"] { background: var(--segment-thumb); color: var(--ink); box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--border-strong); }
.segmented button:disabled { opacity: 0.45; cursor: not-allowed; }
.stepper { display: inline-flex; align-items: center; gap: 3px; }
.stepper input { width: 58px; padding: 3px 6px; border-radius: var(--r-sm); border: 1px solid var(--border-strong); background: var(--control-bg); font-size: 13px; min-height: 28px; text-align: center; }
.stepper button { width: 28px; height: 28px; border-radius: var(--r-sm); border: 1px solid var(--border-strong); background: var(--control-bg); }
.unit-field { display: inline-flex; align-items: center; gap: 4px; }
.unit-field .unit { font-size: 13px; color: var(--muted); }
.num { width: 88px; padding: 4px 8px; border-radius: var(--r-sm); border: 1px solid var(--border-strong); background: var(--control-bg); font-size: 13px; min-height: 28px; }
/* The select, the effort dots and the results-card ring are in styles/controls.css. */

/* outputs */
.outputs { display: flex; flex-direction: column; gap: var(--s-3); }
.kpis { display: grid; gap: var(--s-2); grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* The outputs column is under 480px until about 1040, and a phone is narrower still: two
   columns keep every label on one line. Six tiles fill either grid with no empty cell. */
/* Under 1300 the outputs column is too narrow for "COST PER SOLVED TASK" on one line at three across. */
@media (max-width: 1300px) { .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* The label sits at the top of the tile and the value under it; nothing centers or pushes it. */
/* One card radius for every filled surface in the column; only the padding steps down for a tile. */
.kpi { background: var(--surface-card); border-radius: var(--r-lg); padding: var(--s-2) var(--s-3); display: grid; grid-template-rows: auto auto auto auto; align-content: start; gap: 2px; }
.kpi .kpi-label { align-self: start; }
.kpi .kpi-value { font-size: 20px; color: var(--ink); font-weight: 500; line-height: 1.2; display: flex; align-items: baseline; gap: 6px; transition: opacity var(--dur-base) var(--ease-out); }
.kpi-delta { font-size: 12px; display: inline-flex; align-items: center; gap: 4px; color: var(--body); }
.kpi-delta.better { color: var(--text-success); }
.kpi-delta.worse { color: var(--text-danger); }
.kpi .kpi-note { font-size: 12px; color: var(--muted); }
/* The bill is the number the customer cares about: full row, display type, target beside it. */
.kpi.primary { grid-column: 1 / -1; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: var(--s-3); align-items: baseline; }
.kpi.primary .kpi-label { grid-column: 1 / -1; align-self: auto; }
.kpi.primary .kpi-value { font-size: 32px; font-family: var(--font-display); font-weight: 400; grid-row: 2; }
.kpi.primary .kpi-delta { grid-row: 2; font-size: 14px; }
.kpi.primary .kpi-note { grid-column: 1 / -1; font-size: 14px; color: var(--body); }
.kpi.held .kpi-value { animation: held var(--dur-base) var(--ease-overshoot) 1; }
@keyframes held { 0% { transform: translateY(0); } 40% { transform: translateY(-4px); } 100% { transform: translateY(0); } }
figure.chart { margin: 0; background: var(--surface-card); border-radius: var(--r-lg); padding: var(--s-3) var(--s-4); }
/* Marks (X1-03): pointing at a turn, a task or a sweep point shows its numbers and lights the
   same case on every chart of the column. The body is the one keyboard stop; the SVG stays
   out of the tree, so the tooltip is decoration and the live region carries the words. */
figure.chart .chart-body { position: relative; }
figure.chart svg .mark { cursor: pointer; }
figure.chart svg.has-lit .mark:not(.lit):not(.hot) { opacity: 0.45; }
figure.chart svg .mark.hot { filter: drop-shadow(0 0 3px var(--point)); }
.mark-tip { position: absolute; z-index: 3; pointer-events: none; max-width: 280px; padding: 4px 8px; border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--surface-card); color: var(--ink); font-size: 12px; line-height: 1.35; box-shadow: var(--shadow-sm); }
.mark-tip[hidden] { display: none; }
/* One figure header at every title length and on every page: the title wraps under itself,
   the Table button stays top right. */
figure.chart figcaption { display: flex; align-items: flex-start; gap: var(--s-2); font-size: 14px; font-weight: 500; color: var(--ink); margin-bottom: var(--s-2); flex-wrap: nowrap; }
/* Keyed on the class, not the id: views suffix the title's id per state, and a renamed figure
   must keep its layout. */
figure.chart figcaption .chart-title { flex: 1; min-width: 0; }
figure.chart figcaption .spacer { display: none; }
figure.chart figcaption .btn { flex: none; }
/* The key numbers as a card of the results column, headed like a figure. */
.kpi-card { background: var(--surface-card); border-radius: var(--r-lg); padding: var(--s-3) var(--s-4); }
.kpi-card .card-head { display: flex; align-items: flex-start; gap: var(--s-2); font-size: 14px; font-weight: 500; color: var(--ink); margin-bottom: var(--s-2); }
.kpi-card .card-head .chart-title { flex: 1; min-width: 0; }
.kpi-card .card-body .kpi { background: var(--surface-soft); }
.kpi-card .summary { display: none; font-size: 13px; color: var(--muted); margin: 0; }
/* The handle leads the header; the fold ends it. Both are 28 px targets on a 14 px glyph. */
.card-handle { flex: none; width: 28px; height: 28px; margin: -6px 0 -6px -8px; border: none; background: none; color: var(--muted); border-radius: var(--r-sm); cursor: grab; touch-action: none; display: inline-flex; align-items: center; justify-content: center; }
.card-handle:hover, .card-handle:focus-visible { color: var(--ink); background: var(--surface-soft); }
.card-fold { flex: none; }
.card-fold .icon { transition: transform var(--dur-base) var(--ease-out); transform: rotate(90deg); }
.collapsed .card-fold .icon { transform: rotate(0deg); }
/* Folded, a card is its header and one line: the body, the legends and the hints go. */
figure.chart.collapsed > :not(figcaption):not(.summary), .kpi-card.collapsed > .card-body { display: none; }
figure.chart.collapsed figcaption, .kpi-card.collapsed .card-head { margin-bottom: 2px; }
figure.chart.collapsed figcaption .btn:not(.card-fold) { display: none; }
figure.chart.collapsed .summary, .kpi-card.collapsed .summary { display: block; margin-top: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The card in hand lifts; the column says it is being reordered so nothing under it reacts. */
.outputs .dragging { box-shadow: var(--shadow-md); opacity: 0.92; position: relative; z-index: 3; cursor: grabbing; }
.outputs.reordering { user-select: none; }
.outputs.reordering .card-handle { cursor: grabbing; }
.stack-bar { display: flex; height: 12px; border-radius: var(--r-pill); overflow: hidden; margin: 6px 0; border: 1px solid var(--border); }
.stack-bar i { display: block; height: 100%; }
/* text-wrap: pretty keeps a single word off the last line where the engine supports it. */
figure.chart .summary { font-size: 13px; color: var(--muted); margin-top: var(--s-2); text-wrap: pretty; }
figure.chart .chart-body > svg { width: 100%; height: auto; display: block; overflow: visible; font-family: var(--font-sans); font-size: 12px; }
/* A chart drawn at pixel size keeps it: one unit is one pixel, so its text is 12 px on screen. */
figure.chart .chart-body > svg[width] { width: auto; max-width: 100%; max-height: 360px; }
figure.chart .chart-body > svg text { font-size: inherit; }
.dark-muted-fix { color: inherit; }
figure.chart table { width: 100%; border-collapse: collapse; font-size: 13px; }
figure.chart th, figure.chart td { text-align: right; padding: 4px 6px; border-bottom: 1px solid var(--hairline); }
figure.chart th:first-child, figure.chart td:first-child { text-align: left; }
/* The row label is its own column, so wrapped items line up under the first item, not under it. */
.legend { display: flex; align-items: baseline; gap: var(--s-1) var(--s-3); font-size: 12px; color: var(--body); margin: var(--s-2) 0 0; }
/* One label column for stacked legends: "Context:" is the widest at 12 px, so both rows'
   swatches start on one vertical (R8-D-07). */
.legend .row-label { flex: none; min-width: 4.5em; color: var(--muted); font-weight: 500; }
.legend ul { display: flex; flex: 1 1 0; min-width: 0; gap: var(--s-1) var(--s-3); flex-wrap: wrap; margin: 0; padding: 0; list-style: none; }
.legend li { display: inline-flex; align-items: center; gap: 4px; }
.legend i { width: 14px; height: 10px; display: inline-block; border: 1px solid var(--border-strong); }
figure.chart .legend + .pipeline-hint { margin-top: var(--s-2); }
@media (max-width: 532px) {
  /* The bar fits a phone: the count leaves the eye (the pills are the count) but stays in the
     tree, so a screen reader still hears what the row is for and how long the path is.
     Step-name widths sit with the timeline rules below, ordered so the narrowest width wins. */
  .timeline .count { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .legend { display: block; }
  .legend .row-label { display: block; margin-bottom: 4px; }
  .legend ul { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; }
}
.bar, .block, .dot, .path-line { transition: x var(--dur-morph) var(--ease-out), y var(--dur-morph) var(--ease-out), width var(--dur-morph) var(--ease-out), height var(--dur-morph) var(--ease-out), cx var(--dur-morph) var(--ease-out), cy var(--dur-morph) var(--ease-out), opacity var(--dur-morph) var(--ease-out), d var(--dur-morph) var(--ease-out); }
.flow { stroke: var(--band-output); stroke-width: 1.5; fill: none; stroke-dasharray: 60; stroke-dashoffset: 60; animation: flow var(--dur-flow) var(--ease-out) forwards; }
@keyframes flow { to { stroke-dashoffset: 0; } }
/* The same measure as the chart summaries: under a pair of charts the hint spans both columns
   and ran 210 characters to a line. */
.pipeline-hint { font-size: 12px; color: var(--muted); max-width: 90ch; text-wrap: pretty; }
.meter { height: 14px; border-radius: var(--r-pill); background: var(--bg-neutral); position: relative; margin: var(--s-3) 0 var(--s-2); }
.meter .fill { height: 100%; border-radius: var(--r-pill); background: var(--band-read); background-image: radial-gradient(rgba(0,0,0,0.25) 1px, transparent 1.5px); background-size: 6px 6px; transition: width var(--dur-morph) var(--ease-out); }
.meter .mark { position: absolute; top: -4px; width: 2px; height: 22px; background: var(--chart-reference); }

/* timeline */
/* Translucent so a control row scrolling under the bar still shows through. */
/* Opaque: a row passing under the bar mid-scroll is hidden, not half-read through a blur. */
.timeline { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); background: var(--surface-soft); border-top: 1px solid var(--hairline); position: sticky; bottom: 0; z-index: 20; overflow-x: auto; }
.timeline ol { display: flex; gap: var(--s-1); list-style: none; margin: 0; padding: 0; flex: 1; overflow-x: auto; }
/* .count's bottom margin is for lists; inside align-items: center it lifts the label off the row. */
.timeline .count { white-space: nowrap; margin: 0; }
.timeline .step { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border-radius: var(--r-pill); border: 1px solid var(--border-strong); background: var(--canvas); font-size: 13px; white-space: nowrap; transition: background var(--dur-base) var(--ease-out); }
/* A step name is shown whole when the bar has room; the caps belong to widths where pills
   compete. Same specificity, so the narrowest rule comes last and wins. The value and the cost
   arrow are never cut: past the answer card they are the only record of which way the bill went. */
.timeline .step-name { max-width: 40ch; overflow: hidden; text-overflow: ellipsis; }
/* 16ch holds the longest short label ("Changing prefix") whole down to a 360 px phone. */
@media (max-width: 899px) { .timeline .step-name { max-width: 16ch; } }
.timeline .step-value { margin-left: -4px; }
@media (max-width: 532px) { .timeline .step-value { max-width: 12ch; overflow: hidden; text-overflow: ellipsis; } }
@media (max-width: 359px) { .timeline .step-name { max-width: 10ch; } }
.timeline .step[aria-current="step"] { background: var(--surface-strong); color: var(--ink); border-color: var(--ink); }
.timeline .step.future { opacity: 0.5; }

/* code tab */
.code { background: var(--code-bg); border: 1px solid var(--code-border); color: var(--on-dark); border-radius: var(--r-lg); padding: var(--s-4); font-family: var(--font-mono); font-size: 13px; line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }
/* A wrapped line hangs past any 4-space statement indent, so it reads as a continuation. */
.code .line { display: block; padding-left: 8ch; text-indent: -8ch; }
/* On a phone the model id control needs the width; the hang matches the statement indent. */
@media (max-width: 532px) { .code .line { padding-left: 4ch; text-indent: -4ch; } }
.code .dim { color: var(--on-dark-soft); }
.code .comment { color: #8fbf8f; }
.code .inline { display: inline-flex; align-items: center; gap: 4px; background: var(--surface-dark-elevated); border: 1px solid rgba(255,255,255,0.15); border-radius: var(--r-xs); padding: 0 6px; color: #f9dca4; font: inherit; vertical-align: baseline; min-height: 22px; line-height: 1.4; }
/* An inline control never overhangs the block: the select's longest option is wider than a
   320 px line, so it shrinks to the line and clips its text. */
.code .inline { max-width: 100%; }
.code select.inline { appearance: auto; text-overflow: ellipsis; }
.code .inline:focus-visible { box-shadow: 0 0 0 2px var(--fill-accent); }
.code-toolbar { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; margin-bottom: var(--s-3); }
.cost-line { margin: var(--s-3) 0 0; font-family: var(--font-sans); font-size: 13px; color: var(--muted); }

/* traps */
.trap-list { display: flex; flex-direction: column; gap: var(--s-3); max-width: 1100px; }
.trap-card { position: relative; background: var(--surface-card); border-radius: var(--r-lg); padding: var(--s-4); display: grid; gap: var(--s-2) var(--s-4); grid-template-columns: minmax(0, 1fr) auto; text-align: left; border: 1px solid transparent; transition: border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out); }
.trap-card > div { min-width: 0; }
/* Row one is the heading with Open as the only thing beside it; row two is the takeaway across
   the full card width (owner redline 2: prose beside the heading fought it for the line); the
   experiment question, the minis and the prose sides come under both. */
.trap-card h2 { grid-column: 1; grid-row: 1; }
.trap-card .headline { grid-column: 1 / -1; grid-row: 2; }
.trap-card .experiment { grid-column: 1 / -1; grid-row: 3; }
.trap-card .body { grid-column: 1 / -1; grid-row: 4; }
/* The prose sides are visually desktop-only: on a phone the shapes and their figures carry the
   row, but the measured figures stay in the accessibility tree (sr-only, not display: none). */
.trap-card .sides { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
@media (min-width: 720px) {
  .trap-card { grid-template-columns: minmax(220px, 1fr) minmax(0, 1.2fr) auto; align-items: start; }
  .trap-card h2 { grid-column: 1 / 3; }
  .trap-card .body { grid-column: 1; grid-row: 3 / 5; }
  .trap-card .experiment { grid-column: 2; grid-row: 3; }
  .trap-card .sides { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: normal; display: flex; flex-direction: column; gap: var(--s-2); grid-column: 2; grid-row: 4; }
}
.trap-card .mini { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2); align-items: start; margin-top: var(--s-2); }
.trap-card .mini svg { width: 100%; height: 44px; display: block; overflow: visible; }
.trap-card .mini span { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.trap-card .mini .value { color: var(--ink); font-variant-numeric: tabular-nums; }
.trap-card:hover, .trap-card:focus-within { border-color: var(--border-strong); }
.trap-card:active { background: var(--surface-strong); }
/* The title reads as the link (accent color, underline on hover) because the whole card opens
   the trap; the heading stays a heading and the Open button stays the one control. It outranks
   the serif line under it, larger and heavier, at every width (R8-D-08). */
.trap-card h2 { margin: 0; font-size: 20px; font-weight: 600; color: var(--text-accent); text-decoration: underline transparent; text-underline-offset: 3px; transition: text-decoration-color var(--dur-base) var(--ease-out); }
.trap-card:hover h2, .trap-card:focus-within h2 { text-decoration-color: currentColor; }
.trap-card .mini-key { grid-column: 1 / -1; margin: var(--s-1) 0 0; font-size: 12px; color: var(--muted); }
/* The takeaway keeps .headline's serif on the card, one step under the heading's size, in
   bold: it is the lesson restated, and the grid gives it the full width to run across. */
.trap-card .headline { margin: 4px 0 0; font-size: 19px; font-weight: 600; }
/* The experiment is a question a customer asked, set in the body face so it never reads as a
   second heading or a claim; the word Experiment: in ink names what it is. */
.experiment { margin: 0; font-size: 14px; color: var(--body); max-width: 70ch; }
.experiment strong { color: var(--ink); font-weight: 500; }
.trap-card .pipeline-hint { margin: 0; }
.trap-card .side { font-size: 13px; color: var(--muted); max-width: 70ch; }
.trap-card .side strong { display: block; color: var(--ink); margin-bottom: 2px; font-weight: 500; }
/* One control per card, stretched over it with ::after, so the whole card opens the trap and
   the word Open says so. Pinned to the card's top right so it lands in the same place on every
   row. Its focus ring lands on the card through :focus-within. */
.trap-card .open { grid-column: -2; grid-row: 1; align-self: start; justify-self: end; color: var(--muted); }
.trap-card .open::after { content: ""; position: absolute; inset: 0; border-radius: var(--r-lg); }
.trap-card .open:focus-visible { box-shadow: none; }
.trap-card:focus-within { box-shadow: var(--focus); }
.pair { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-2); }
.pair .side { background: var(--canvas); border-radius: var(--r-md); padding: var(--s-2); font-size: 13px; color: var(--muted); }
.pair .side strong { display: block; color: var(--ink); margin-bottom: 4px; }
/* One cap for the list and its detail, so Traps > Open > All traps keeps one right edge
   (R10-D-04); the prose keeps its own measure below, and the chart keeps its 900 px cap. */
.trap-detail { display: flex; flex-direction: column; gap: var(--s-3); max-width: 1100px; }
.trap-detail .source strong { font-weight: 500; color: var(--ink); }
.trap-detail > p, .trap-detail figure.chart .summary { max-width: 70ch; }
/* Grid's default stretch: the two boxes share a bottom edge whatever the expected shape's line count. */
@media (min-width: 720px) { .trap-detail .pair { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Title, takeaway, aphorism: three sizes, one display face, so the h1 outranks what follows.
   The takeaway is bold because it is the lesson restated under the two states (owner redline 1). */
.trap-detail .headline { display: block; margin: 0; font-weight: 600; }
/* One face for feel wherever a trap's headline is: the detail page and the daily reveal. */
.feel { font-family: var(--font-sans); font-size: 15px; font-style: italic; color: var(--muted); margin: 0; }
.trap-detail .pair .side { background: var(--surface-soft); }
/* The state tabs wear the segmented control's look (the lever controls' idiom) keyed on
   aria-selected, the tab role's attribute: one state on screen at a time, never a blend. */
.state-tabs { display: inline-flex; align-self: start; background: var(--segment-track); border-radius: var(--r-md); padding: 2px; gap: 2px; flex-wrap: wrap; }
.state-tabs [role="tab"] { border: none; background: transparent; padding: 4px 12px; border-radius: var(--r-sm); font-size: 13px; font-weight: 500; color: var(--body); min-height: 28px; transition: background var(--dur-base) var(--ease-out); }
.state-tabs [role="tab"][aria-selected="true"] { background: var(--segment-thumb); color: var(--ink); box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--border-strong); }

/* ghost toggle (G key) */
.outputs.no-ghost rect[stroke-dasharray="2 2"], .outputs.no-ghost path[stroke-dasharray="3 3"], .outputs.no-ghost circle[fill="url(#p-ghost)"] { display: none; }

/* reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .bar, .block, .dot, .path-line, .meter .fill, .entry-card, details summary::before, .switch, .switch::after, .btn, .chip, .option, .timeline, .card-fold .icon { transition: none !important; }
  .flow { animation: none; stroke-dashoffset: 0; }
  .kpi.held .kpi-value { animation: heldFade var(--dur-base) 1; }
  @keyframes heldFade { from { opacity: 0.6; } to { opacity: 1; } }
}
@media print {
  .app-header, .timeline, .tabs, .btn, .skip-link, .mode-toggle { display: none !important; }
  body { background: #fff; color: #000; }
  figure.chart, .card, .kpi { break-inside: avoid; background: #fff; border: 1px solid #ccc; }
}

/* Under 400 px the handle, the changed word and two buttons leave a card title one word per line;
   the title keeps a readable basis and the buttons take a second row. Last so it wins the tie. */
@media (max-width: 399px) {
  figure.chart figcaption, .kpi-card .card-head { flex-wrap: wrap; }
  figure.chart figcaption .chart-title, .kpi-card .card-head .chart-title, .daily figure.chart figcaption .chart-title { flex: 1 1 140px; }
}
