/* ---------------------------------------------------------------------------
   Speaking Drills - the practice booth.

   Quiet, low-light, one thing lit at a time. Amber is a signal, not a
   decoration: it appears on the timer and on whatever is currently active,
   nowhere else. Hierarchy is done with weight and space, not with borders
   and not with a card around every block.
--------------------------------------------------------------------------- */

:root {
  --ground: #101317;
  --ground-lift: #171b20;
  --ground-sunk: #0a0c0e;
  --line: #232931;
  --line-soft: #1a1f25;
  /* The ground, thinned, for chrome that content scrolls underneath. */
  --ground-veil: rgba(16, 19, 23, 0.72);

  --ink: #f1ebe1;
  --ink-soft: #a49c90;
  /* 4.6:1 on the ground. It was #6b665e, which is 3.3:1 -- under the
     readable floor, and it carries the footnote, the chart notes and
     the habit notes, which are prose rather than decoration. */
  --ink-faint: #857e73;

  --signal: #f2a93b;
  --signal-sunk: rgba(242, 169, 59, 0.12);
  --signal-edge: rgba(242, 169, 59, 0.35);
  --warn: #e2603f;

  --font: "Inter", "Segoe UI Variable Text", system-ui, -apple-system,
    "Segoe UI", Roboto, "Helvetica Neue", sans-serif;

  --gutter: 20px;
  --measure: 34rem;
  --tap: 44px;
}

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

/* A class that sets `display` beats the browser's own [hidden] rule, and
   several classes here do. Settle it once rather than per element. */
[hidden] {
  display: none !important;
}

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

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1.0625rem;
  font-weight: 400;
  line-height: 1.5;
  font-feature-settings: "tnum" 1, "cv05" 1;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* Numerals never re-flow mid-countdown. */
.num,
time,
.metric {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

a {
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Feedback belongs on the press, not on the release. The phone is the
   design target and a phone has no hover, so without this every control on
   every screen is dead until the server answers.

   `manipulation` takes the double-tap zoom delay off the input path; the
   platform tap highlight goes because we draw our own and two press states
   read as a stutter. */
.btn,
.linkish,
.segmented button,
.session-list a,
.choice,
.table-view summary {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Tracking is size-specific: large text reads too loose as it grows and wants
   negative tracking, small text wants a little positive. The large sizes are
   already tuned one at a time; these are the labels, all at 0.72-0.82rem. */
.prompt-context,
.capture-note,
.session-when,
.session-tail,
.tag,
.fact dt,
.rewrite h2,
.advice h2,
.facts h2,
.dims h2,
.habits h2,
.chart-head h2,
.recent h2,
.setup-group legend,
.score-meta,
.table-view summary,
.axis {
  letter-spacing: 0.01em;
}

/* --- shell ---------------------------------------------------------------- */

.shell {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 var(--gutter);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

/* A floating layer rather than a strip that consumes the top of the screen:
   content scrolls underneath it, so "how do I get out of here" is answered
   the whole way down the sessions list and the progress page instead of only
   at the top. The negative inline margin cancels the shell's gutter so the
   material covers the full width -- it lands exactly on the shell's own
   border box, so it adds no scrollWidth. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 56px;
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
  padding-top: env(safe-area-inset-top, 0);
  background: var(--ground-veil);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
}

/* A scroll edge, not a divider: the fade exists only where floating chrome
   actually overlaps content, and it is invisible at the top of the page. */
.topbar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 14px;
  background: linear-gradient(var(--ground-veil), transparent);
  pointer-events: none;
}

.wordmark {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink-soft);
}

.wordmark b {
  font-weight: 800;
  color: var(--ink);
}

.topbar form {
  margin: 0;
}

.linkish {
  background: none;
  border: 0;
  padding: 10px 10px;
  min-height: var(--tap);
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink-faint);
  cursor: pointer;
}

.linkish:hover {
  color: var(--ink-soft);
}

.linkish:active {
  color: var(--ink);
}

main {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* --- the booth ------------------------------------------------------------ */

.booth {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 8px 0 28px;
}

.drill-head {
  padding-bottom: 20px;
  border-bottom: 1px solid var(--line-soft);
}

.drill-name {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 800;
  letter-spacing: -0.005em;
}

.drill-instruction {
  margin: 4px 0 0;
  font-size: 0.95rem;
  font-weight: 400;
  color: var(--ink-soft);
}

.drill-meta {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 12px;
  font-size: 0.8rem;
  color: var(--ink-faint);
}

.drill-meta .target {
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}

.dot {
  color: var(--line);
}

/* The one lit thing on the page. No card, no border -- just light. */
.prompt {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  padding: 36px 0;
}

/* Light falling on the prompt. Kept inside the element's own box on the
   horizontal axis -- a negative inline inset here grows scrollWidth and
   gives the phone a sideways drag. */
.prompt::before {
  content: "";
  position: absolute;
  inset: -12% 0;
  background: radial-gradient(
    115% 55% at 50% 45%,
    rgba(241, 235, 225, 0.05),
    transparent 70%
  );
  pointer-events: none;
}

.prompt-text {
  position: relative;
  margin: 0;
  font-size: clamp(1.45rem, 6.2vw, 2rem);
  font-weight: 300;
  line-height: 1.28;
  letter-spacing: -0.015em;
  text-wrap: pretty;
}

.prompt-context {
  position: relative;
  margin: 0 0 12px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-faint);
}

.empty {
  color: var(--ink-soft);
  font-weight: 300;
}

/* --- controls ------------------------------------------------------------- */

.controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 20px;
  border-top: 1px solid var(--line-soft);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 12px 20px;
  border: 1px solid transparent;
  border-radius: 8px;
  font: inherit;
  font-size: 0.98rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 180ms ease-out, background-color 120ms ease,
    border-color 120ms ease, color 120ms ease;
}

/* Down fast, back slow. 60ms is under the threshold where a press reads as
   lag; the release is allowed to settle. */
.btn:active {
  transform: scale(0.97);
  transition-duration: 60ms;
}

.btn-primary {
  background: var(--signal);
  color: #16120a;
  font-weight: 700;
}

.btn-primary:hover {
  background: #ffbb4d;
}

.btn-ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--ink-soft);
}

.btn-ghost:hover {
  border-color: var(--ink-faint);
  color: var(--ink);
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* A control that will not act must not answer as though it did. */
.btn[disabled]:active,
.btn[aria-disabled="true"]:active {
  transform: none;
}

/* Segmented language switch. The selected side is "active", so it gets the
   signal colour -- and it is the only place on this screen that does. */
.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  background: var(--ground-sunk);
  border: 1px solid var(--line-soft);
  border-radius: 9px;
}

.segmented button {
  min-height: 38px;
  min-width: 56px;
  padding: 0 14px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-faint);
  cursor: pointer;
}

.segmented button:hover {
  color: var(--ink-soft);
}

.segmented button:active {
  transform: scale(0.96);
  transition-duration: 60ms;
}

.segmented button[aria-pressed="true"] {
  background: var(--signal-sunk);
  box-shadow: inset 0 0 0 1px var(--signal-edge);
  color: var(--signal);
}

.control-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* --- the drill screen ----------------------------------------------------- */

/* The one bold element on the page. Everything else stays flat. */
.timer {
  position: relative;
  display: grid;
  place-items: center;
  margin: 4px auto 0;
  width: min(62vw, 210px);
  aspect-ratio: 1;
}

.ring {
  width: 100%;
  height: 100%;
  overflow: visible;
}

.ring-track,
.ring-arc {
  fill: none;
  stroke-width: 7;
  stroke-linecap: round;
}

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

.ring-arc {
  stroke: var(--signal);
  /* Painted from JS on every frame -- or once a second under
     prefers-reduced-motion -- so there is no transition here to fight. */
  stroke-dashoffset: 0;
}

.is-over .ring-arc {
  stroke: var(--warn);
}

.timer-readout {
  position: absolute;
  margin: 0;
  font-size: clamp(2.6rem, 13vw, 3.4rem);
  font-weight: 200;
  letter-spacing: -0.03em;
  color: var(--ink);
}

.is-over .timer-readout {
  color: var(--warn);
}

.timer-summary {
  margin: 18px 0 0;
  font-size: 0.85rem;
  color: var(--ink-faint);
}

.timer-summary b {
  font-weight: 600;
  color: var(--ink-soft);
}

.timer-summary .over,
.take .over {
  color: var(--warn);
}

.prompt--drill {
  /* Absorbs the slack so the prompt sits centred between the drill head and
     the ring, with the ring anchored above the controls. */
  flex: 1 1 auto;
  min-height: 0;
  padding: 22px 0 18px;
}

.prompt-text--drill {
  font-size: clamp(1.15rem, 4.9vw, 1.4rem);
  font-weight: 400;
  line-height: 1.35;
  /* -0.015em was tuned for the home screen's 1.45-2rem. At this size it
     closes the letters up too far, which is the whole argument against one
     tracking value for every size. */
  letter-spacing: -0.005em;
}

.btn-stop {
  background: transparent;
  border-color: var(--signal-edge);
  color: var(--signal);
}

.btn-stop:hover {
  background: var(--signal-sunk);
}

.btn-compact {
  min-height: var(--tap);
  padding: 10px 14px;
  font-size: 0.85rem;
}

.start-form {
  margin: 0;
  display: contents;
}

/* --- capture -------------------------------------------------------------- */

.capture {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 18px;
}

.capture textarea {
  min-height: 190px;
  min-height: 9lh;
  line-height: 1.55;
  resize: vertical;
  /* Dictated text arrives as one long unpunctuated run; let it wrap and
     break rather than pushing the layout sideways. */
  overflow-wrap: break-word;
}

/* State machine. The drill screen shows exactly one thing at a time. */
.drill[data-state="ready"] .timer-summary,
.drill[data-state="ready"] .capture,
.drill[data-state="ready"] [data-stop],
.drill[data-state="running"] .timer-summary,
.drill[data-state="running"] .capture,
.drill[data-state="running"] [data-start],
.drill[data-state="capture"] .timer,
.drill[data-state="capture"] .controls,
.drill[data-state="saving"] .timer,
.drill[data-state="saving"] .controls {
  display: none;
}

.drill[data-state="capture"] .prompt--drill,
.drill[data-state="saving"] .prompt--drill {
  padding: 14px 0 10px;
}

.drill[data-state="capture"] .prompt-text--drill,
.drill[data-state="saving"] .prompt-text--drill {
  font-size: 1rem;
  color: var(--ink-soft);
}

.drill[data-state="saving"] .capture button {
  opacity: 0.45;
  pointer-events: none;
}

/* --- recording ------------------------------------------------------------
   The microphone is an addition to the textarea, never a replacement, so
   every state here is a line of text next to a box that still works. Amber
   appears once: the dot that says the microphone is live, which is the
   active state.
--------------------------------------------------------------------------- */

/* Arming: the permission dialog is up. Not "ready" -- start has already been
   pressed -- and not "running" either, so it gets its own state instead of a
   flash of the wrong one. */
.drill[data-state="arming"] .timer-summary,
.drill[data-state="arming"] .capture,
.drill[data-state="arming"] [data-start],
.drill[data-state="arming"] [data-stop],
.drill[data-state="arming"] [data-capture-mode],
.drill[data-state="arming"] .capture-note {
  display: none;
}

.arming {
  display: none;
  margin: 0;
  text-align: center;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.drill[data-state="arming"] .arming {
  display: block;
}

[data-capture-mode] {
  justify-content: center;
}

/* How to capture is a decision made before speaking. Hiding the toggle
   mid-take is not cosmetic: switching away from Record while the recorder is
   running is how a microphone gets stranded. */
.drill[data-state="running"] [data-capture-mode],
.drill[data-state="capture"] [data-capture-mode],
.drill[data-state="saving"] [data-capture-mode] {
  display: none;
}

.capture-note {
  margin: 0;
  text-align: center;
  font-size: 0.8rem;
  color: var(--ink-faint);
}

.mic {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* Only while speaking. The JS unhides it; this stops it lingering into the
   capture state if anything ever gets that wrong. */
.drill:not([data-state="running"]) .mic {
  display: none;
}

.mic-dot {
  will-change: opacity;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--signal);
  animation: breathe 2.4s ease-in-out infinite;
}

@keyframes breathe {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

.capture-status {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: 4px;
}

.capture-status-label {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink-soft);
}

.capture-status-label.bad {
  color: var(--warn);
}

.capture-status-detail {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--ink-faint);
}

.capture-status [data-retry] {
  align-self: flex-start;
  padding-left: 0;
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --- sessions list -------------------------------------------------------- */

.session-list {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}

.session-list li + li {
  border-top: 1px solid var(--line-soft);
}

.session-list a {
  display: grid;
  gap: 4px;
  padding: 16px 10px;
  margin-inline: -10px;
  min-height: var(--tap);
  border-radius: 8px;
  text-decoration: none;
  transition: background-color 180ms ease-out;
}

.session-list a:active {
  background: var(--ground-lift);
  transition-duration: 60ms;
}

.session-when {
  font-size: 0.76rem;
  color: var(--ink-faint);
}

.session-drill {
  font-size: 0.95rem;
  font-weight: 700;
}

.session-prompt {
  font-size: 0.88rem;
  font-weight: 300;
  color: var(--ink-soft);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.session-tail {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
  font-size: 0.76rem;
  color: var(--ink-faint);
}

.session-lang {
  margin-left: auto;
}

.tag {
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--ink-faint);
}

.tag-open {
  border-color: var(--signal-edge);
  color: var(--signal);
}

.tag-scored {
  border-color: var(--line);
  color: var(--ink);
}

/* --- session review ------------------------------------------------------- */

.review {
  padding-bottom: 40px;
}

.review-prompt {
  margin: 20px 0 0;
  font-size: 1.05rem;
  font-weight: 300;
  line-height: 1.4;
}

.take {
  padding-top: 22px;
}

.take-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.take-target {
  color: var(--ink-faint);
}

/* The follow-up, above the take that answered it. Set as speech rather than
   as a heading -- it is the other side of the table talking, and it reads
   that way on the page the way it did in the booth. */
.take-question {
  margin: 0 0 10px;
  font-size: 0.95rem;
  font-weight: 300;
  line-height: 1.45;
  color: var(--ink);
  text-wrap: pretty;
}

.take-question::before {
  content: "\201C";
}

.take-question::after {
  content: "\201D";
}

/* The transcript is shown as it was captured: no tidying, and the line
   breaks it arrived with are kept. */
.transcript {
  margin: 0;
  padding: 14px 16px;
  background: var(--ground-sunk);
  border-left: 2px solid var(--line);
  border-radius: 0 6px 6px 0;
  font-size: 0.95rem;
  font-weight: 300;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: var(--ink-soft);
}

.notice {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 24px;
  padding: 16px;
  background: var(--ground-lift);
  border-radius: 10px;
}

.notice p {
  margin: 0;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.notice-quiet {
  background: none;
  padding: 16px 0 0;
  border-top: 1px solid var(--line-soft);
}

.notice-bad {
  box-shadow: inset 0 0 0 1px rgba(226, 96, 63, 0.35);
}

.notice-bad b {
  color: var(--warn);
}

.notice-detail {
  font-size: 0.78rem !important;
  color: var(--ink-faint) !important;
  overflow-wrap: break-word;
}

/* --- scoring: the loading state ------------------------------------------- */

/* Hidden until HTMX marks it in-flight. */
.scoring {
  display: none;
  margin-top: 24px;
  padding: 18px 0 4px;
  border-top: 1px solid var(--line-soft);
}

.scoring.htmx-request {
  display: block;
}

.scoring-label {
  margin: 0 0 12px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink-soft);
}

.scoring-hint {
  margin: 10px 0 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
}

.scoring-bar {
  height: 3px;
  background: var(--line-soft);
  border-radius: 999px;
  overflow: hidden;
}

.scoring-bar span {
  will-change: transform;
  display: block;
  width: 40%;
  height: 100%;
  background: var(--signal);
  border-radius: 999px;
  animation: crawl 1.6s ease-in-out infinite;
}

@keyframes crawl {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(250%);
  }
}

/* --- the result -----------------------------------------------------------
   Ordered by what you can act on. The rewrite is the payload of the whole
   app and gets the biggest type on the page; the bars come last, because
   they describe a take that is already over.
--------------------------------------------------------------------------- */

.score {
  margin-top: 26px;
}

.verdict {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-bottom: 4px;
}

.verdict-score {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
  font-size: 0.8rem;
  color: var(--ink-faint);
}

.verdict-score b {
  font-size: 2.1rem;
  font-weight: 200;
  letter-spacing: -0.03em;
  color: var(--ink);
}

.verdict-weakest {
  margin: 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
}

.verdict-weakest b {
  font-weight: 600;
  color: var(--ink-soft);
}

/* Biggest type on the page, nothing boxed around it competing for it. */
.rewrite {
  padding: 8px 0 26px;
}

.rewrite h2,
.advice h2,
.facts h2,
.dims h2 {
  margin: 0 0 10px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-faint);
}

.rewrite-text {
  margin: 0;
  font-size: clamp(1.3rem, 5.6vw, 1.65rem);
  font-weight: 300;
  line-height: 1.32;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-wrap: pretty;
}

.advice {
  padding: 20px 0;
  border-top: 1px solid var(--line-soft);
}

/* Numbered because they are ranked, not a menu: the first one is the one to
   do. No bullets, no cards -- the ranking is the information. */
.fix-list {
  margin: 0;
  padding-left: 1.3em;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.fix-list li {
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.fix-list li::marker {
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

.fix-first {
  font-size: 1rem !important;
  color: var(--ink) !important;
}

.kept {
  margin: 16px 0 0;
  font-size: 0.88rem;
  font-weight: 300;
  line-height: 1.5;
  color: var(--ink-soft);
}

.kept b {
  font-weight: 600;
  color: var(--ink);
}

/* --- the measured facts ---------------------------------------------------
   Arithmetic, not judgement. Tone is carried by the value's colour alone --
   no icons, no badges, and never amber, which belongs to the timer. */

.facts {
  padding: 20px 0;
  border-top: 1px solid var(--line-soft);
}

.facts dl {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.fact dt {
  font-size: 0.78rem;
  color: var(--ink-faint);
}

.fact dd {
  margin: 2px 0 0;
}

/* Three steps, no new colour: recessive for a neutral reading, full ink for
   one worth noticing, warn for one that cost points. */
.fact-value {
  font-size: 1.02rem;
  font-weight: 500;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.fact--watch .fact-value {
  color: var(--ink);
}

.fact--bad .fact-value {
  color: var(--warn);
}

.fact-note {
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--ink-faint);
  text-wrap: pretty;
}

/* --- the five readings ----------------------------------------------------- */

.dims {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px 0;
  border-top: 1px solid var(--line-soft);
}

.dim-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin: 0 0 6px;
  font-size: 0.82rem;
  color: var(--ink-soft);
}

.dim-head b {
  font-weight: 700;
  color: var(--ink);
}

/* The weakest one is worth finding at a glance; weight does it, so the bars
   stay a single greyscale family. */
.dim--weakest .dim-head,
.dim--weakest .dim-head b {
  color: var(--warn);
}

.dim--weakest .bar span {
  background: var(--warn);
}

/* Greyscale on purpose: amber is the timer and the active state, not a
   decoration for every number on the screen. */
.bar {
  height: 4px;
  background: var(--line-soft);
  border-radius: 999px;
  overflow: hidden;
}

.bar span {
  display: block;
  height: 100%;
  background: var(--ink-soft);
  border-radius: 999px;
}

.dims-note {
  margin: 4px 0 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--ink-faint);
}

.score-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  font-size: 0.76rem;
  color: var(--ink-faint);
}

.score-meta .linkish {
  min-height: auto;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.topbar-nav {
  display: flex;
  align-items: center;
  gap: 2px;
}

/* Without the flex above, this form -- a block -- puts "Lock" on its own
   line, which is how the home screen's topbar has always rendered. */
.topbar-nav form {
  margin: 0;
}

/* A quiet way out of the booth. It is not a control -- it sits below them,
   in the footnote's register, because it is the exception and should read
   like one. */

.aside {
  margin: 18px 0 0;
  font-size: 0.82rem;
  color: var(--ink-faint);
}

/* The action in an aside is sometimes a link and sometimes a button in a
   form -- locking has to POST. They sit in the same sentence, so they have
   to read the same; `.linkish` alone leaves the button quieter and
   un-underlined next to the link. */
.aside a,
.aside button {
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.aside a:active,
.aside button:active {
  color: var(--ink);
}

/* The free take asks three optional questions before the booth. The text box
   wants the full width; the two radio rows are short enough to sit inline. */
.setup input[type="text"] {
  width: 100%;
}

/* --- the ladder, answering back -------------------------------------------
   Advice, not a badge. The level filter says nothing about how it is going;
   this does, once there are enough sessions at one level to say it honestly.
   No colour: amber is the timer and the active state, and a suggestion is
   neither.
--------------------------------------------------------------------------- */

.nudge {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 14px;
  margin: 20px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}

.nudge p {
  /* Basis rather than a fixed width: the sentence keeps the row to itself on
     a phone and sits beside the action once there is room for both. */
  flex: 1 1 15rem;
  margin: 0;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.nudge button {
  flex: 0 0 auto;
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --- footnote ------------------------------------------------------------- */

.footnote {
  padding: 18px 0 26px;
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--ink-faint);
}

.footnote b {
  font-weight: 600;
  color: var(--ink-soft);
}

/* --- login ---------------------------------------------------------------- */

.gate {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
  padding-bottom: 12vh;
}

.gate h1 {
  margin: 0;
  font-size: 1.6rem;
  font-weight: 300;
  letter-spacing: -0.02em;
}

.gate h1 b {
  font-weight: 800;
}

.gate form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.field label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink-soft);
}

input[type="password"],
input[type="text"],
textarea {
  min-height: var(--tap);
  padding: 12px 14px;
  background: var(--ground-sunk);
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  color: var(--ink);
}

input:focus,
textarea:focus {
  border-color: var(--signal-edge);
  outline: none;
  box-shadow: 0 0 0 3px var(--signal-sunk);
}

.error {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--warn);
}

/* --- motion --------------------------------------------------------------- */

.swap-in {
  animation: rise 220ms ease-out;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.htmx-request .btn-ghost {
  color: var(--ink-faint);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* Roomier once there is room, but the phone is the design target. */
@media (min-width: 640px) {
  body {
    font-size: 1.125rem;
  }

  .controls {
    flex-direction: row-reverse;
    align-items: center;
    justify-content: space-between;
  }

  .controls .btn-primary {
    min-width: 220px;
  }
}

/* The crawling bar is an animation, so reduced motion kills it. Fill the
   track instead of leaving a stub parked at 40%, which reads as stuck. */
@media (prefers-reduced-motion: reduce) {
  .scoring-bar span {
    width: 100%;
    opacity: 0.55;
  }
}

/* Reduced motion means a gentler answer, not no answer. The press still
   replies -- with light instead of movement. */
@media (prefers-reduced-motion: reduce) {
  .btn:active,
  .segmented button:active {
    transform: none;
  }

  .btn-primary:active {
    background: #ffbb4d;
  }

  .btn-ghost:active,
  .btn-stop:active {
    background: var(--signal-sunk);
  }
}

/* Frostier, not translucent. */
@media (prefers-reduced-transparency: reduce) {
  .topbar {
    background: var(--ground);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .topbar::after {
    background: linear-gradient(var(--ground), transparent);
  }
}

/* Near-solid surfaces and defined edges. The recessive tokens each move up a
   step: --ink-faint to 5.4:1, --ink-soft to 9.5:1 on the ground. */
@media (prefers-contrast: more) {
  :root {
    --ink-faint: #918a7e;
    --ink-soft: #c0b8aa;
    --line: #3a424c;
    --line-soft: #2b323a;
  }

  .topbar {
    background: var(--ground);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .btn-ghost,
  .tag {
    border-color: var(--ink-faint);
  }

  .transcript {
    border-left-color: var(--ink-faint);
  }
}

/* --- progress --------------------------------------------------------------
   One series per chart, so no categorical palette exists: the line wears the
   ink token and amber stays reserved for the active filter and the newest
   point. Five dimensions are five small multiples rather than five lines in
   one frame, which is what keeps it that way.
--------------------------------------------------------------------------- */

.streak {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin: 4px 0 20px;
  font-size: 0.86rem;
  color: var(--ink-soft);
}

.streak b {
  font-size: 2.3rem;
  font-weight: 200;
  letter-spacing: -0.03em;
  color: var(--ink);
}

.filters {
  margin: 0 0 10px;
}

.segmented-wrap {
  flex-wrap: wrap;
}

.segmented-wrap button {
  min-width: auto;
  padding: 0 12px;
}

.chart-block {
  position: relative;
  padding: 18px 0 4px;
  border-top: 1px solid var(--line-soft);
}

.chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.chart-head h2,
.recent h2 {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-faint);
}

.chart-latest {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
}

.chart-latest b {
  font-size: 1.5rem;
  font-weight: 300;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.up { color: #7fb283; }
.down { color: var(--warn); }

.chart {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  touch-action: pan-y;
}

/* Recessive grid and axes -- they orient, they do not compete. */
.grid {
  stroke: var(--line-soft);
  stroke-width: 1;
}

.axis {
  fill: var(--ink-faint);
  font-size: 10px;
  font-family: var(--font);
}

.chart-line {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chart-dot {
  fill: var(--ink-soft);
  /* A surface ring so overlapping marks stay countable. */
  stroke: var(--ground);
  stroke-width: 2;
}

/* The newest point is the "current" one, so it earns the signal colour. */
.chart-dot.is-last {
  fill: var(--signal);
}

.crosshair {
  stroke: var(--signal-edge);
  stroke-width: 1;
  opacity: 0;
}

.crosshair.is-on {
  opacity: 1;
}

.tooltip {
  position: absolute;
  top: 26px;
  transform: translateX(-50%);
  padding: 5px 9px;
  background: var(--ground-lift);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  pointer-events: none;
  white-space: nowrap;
}

.chart-note {
  margin: 10px 0 0;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--ink-faint);
}

.seam {
  display: block;
  margin-top: 6px;
  font-weight: 600;
  color: var(--warn);
}

/* --- small multiples ------------------------------------------------------ */

.facets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 4px 18px;
  padding: 20px 0;
  border-top: 1px solid var(--line-soft);
}

.facet {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 2px 10px;
  padding: 8px 0;
}

.facet-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 2px;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.facet-head b {
  font-weight: 700;
  color: var(--ink);
}

.spark {
  width: 100%;
  height: auto;
  overflow: visible;
}

.spark-line {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.spark-dot {
  fill: var(--signal);
  stroke: var(--ground);
  stroke-width: 1.5;
}

.facet-trend {
  font-size: 0.76rem;
  color: var(--ink-faint);
  text-align: right;
}

/* --- counted habits -------------------------------------------------------
   One row each rather than a grid of facets: these carry a note, and a note
   is unreadable in a third of a phone's width. The gridlines are drawn here
   and nowhere else on the page, because on these charts a line means
   something -- four fillers a minute, the edges of a comfortable pace.
--------------------------------------------------------------------------- */

.habits {
  padding: 22px 0 6px;
  border-top: 1px solid var(--line-soft);
}

.habits h2 {
  margin: 0 0 14px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-faint);
}

.habit {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: 14px;
  padding: 12px 0;
  border-top: 1px solid var(--line-soft);
}

.habit:first-of-type {
  border-top: 0;
}

.habit-head {
  grid-column: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0;
  font-size: 0.82rem;
  color: var(--ink-soft);
}

.habit-head b {
  font-weight: 700;
  color: var(--ink);
}

.spark--habit {
  grid-column: 1;
  width: 100%;
  /* auto, not a fixed height: the viewBox is wide and shallow so the line
     fills the row, and pinning the height would letterbox it again. */
  height: auto;
  margin-top: 6px;
}

.spark-grid {
  stroke: var(--line-soft);
  stroke-width: 1;
}

.habit-trend {
  grid-column: 2;
  grid-row: 1 / span 2;
  font-size: 0.82rem;
  color: var(--ink-faint);
  text-align: right;
  min-width: 3.2em;
}

/* .up and .down are defined far above, at the same specificity as the two
   rules below, so source order silently won and every trend rendered faint.
   Scoping the modifier is what makes it apply -- the dimension trends had
   been colourless since the day they were added. */
.facet-trend.up,
.habit-trend.up {
  color: #7fb283;
}

.facet-trend.down,
.habit-trend.down {
  color: var(--warn);
}

.habit-note {
  grid-column: 1 / -1;
  margin: 6px 0 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--ink-faint);
}

/* --- table view ----------------------------------------------------------- */

.table-view {
  padding: 4px 0 18px;
}

.table-view summary {
  padding: 10px 0;
  min-height: var(--tap);
  font-size: 0.8rem;
  color: var(--ink-faint);
  cursor: pointer;
}

.table-view summary:active,
.choice:active {
  color: var(--ink);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
}

.data-table th {
  padding: 6px 4px;
  text-align: left;
  font-weight: 600;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--line);
}

.data-table td {
  padding: 8px 4px;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--line-soft);
}

.data-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.data-table a {
  color: var(--ink);
  text-decoration: none;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 18px;
  padding: 30px 0;
}

.recent {
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}

/* --- setup ---------------------------------------------------------------- */

.setup {
  display: flex;
  flex-direction: column;
  gap: 26px;
  padding-top: 10px;
}

.setup-group {
  margin: 0;
  padding: 0;
  border: 0;
}

.setup-group legend {
  padding: 0;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-faint);
}

.setup-hint {
  margin: 6px 0 12px;
  font-size: 0.84rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.choice-list,
.radio-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.radio-row {
  flex-direction: row;
  gap: 18px;
}

.choice {
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: var(--tap);
  font-size: 0.95rem;
  cursor: pointer;
}

.choice input {
  width: 19px;
  height: 19px;
  accent-color: var(--signal);
}

/* --- interview ------------------------------------------------------------- */

/* A radio with a line of explanation under its label. The input aligns with
   the label's first line rather than the middle of the pair. */
.choice-stacked {
  align-items: flex-start;
  padding: 8px 0;
}

.choice-stacked input {
  margin-top: 2px;
  flex: none;
}

.choice-hint {
  display: block;
  margin-top: 2px;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--ink-faint);
}

.choice input:disabled + span {
  opacity: 0.45;
}

.interview-item + .interview-item {
  margin-top: 18px;
  border-top: 1px solid var(--line-soft);
}

.interview-item .review-prompt {
  margin: 6px 0 14px;
}

.key-points {
  margin-top: 14px;
  font-size: 0.88rem;
  color: var(--ink-soft);
}

/* Left as a list-item, like .table-view's: display:flex would drop the
   disclosure triangle, which is the only sign this opens. */
.key-points summary {
  padding: 12px 0;
  min-height: var(--tap);
  cursor: pointer;
  color: var(--ink-soft);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.key-points summary:active {
  color: var(--ink);
}

.key-points ul {
  margin: 0;
  padding-left: 18px;
  line-height: 1.55;
}

.key-points li + li {
  margin-top: 6px;
}

.interview-next {
  margin-top: 32px;
}

/* --- the playbook -----------------------------------------------------------
   Reference text, read between sittings. No amber anywhere: amber is the
   timer and the active state, and a page of advice is neither. The one step
   the time should go to is marked by weight, the way everything else here is. */

.playbook-jump {
  display: flex;
  gap: 6px 16px;
  flex-wrap: wrap;
  padding: 8px 0 4px;
  font-size: 0.84rem;
}

.playbook-jump a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--ink-soft);
  text-underline-offset: 3px;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.playbook-jump a:active {
  color: var(--ink);
}

.playbook-round {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 34px;
  scroll-margin-top: 64px;
}

.playbook-round + .playbook-round {
  margin-top: 26px;
  border-top: 1px solid var(--line-soft);
}

.playbook-graded {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--ink-soft);
}

.playbook-sub {
  margin: 14px 0 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink-faint);
}

.playbook-rules,
.playbook-list {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--ink-soft);
}

.playbook-rules b {
  color: var(--ink);
  font-weight: 600;
}

/* Lines you could say verbatim, set as speech like the follow-up question. */
.playbook-lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.playbook-lines li {
  padding: 10px 14px;
  background: var(--ground-lift);
  border-radius: 8px;
  font-size: 0.93rem;
  font-weight: 300;
  line-height: 1.45;
}

.playbook-lines small {
  display: block;
  margin-top: 2px;
  font-size: 0.76rem;
  font-weight: 400;
  color: var(--ink-faint);
}

/* The shape is a real sequence, so it is numbered. */
.shape {
  margin: 0;
  padding-left: 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.shape li::marker {
  font-size: 0.8rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.shape b {
  display: block;
  font-size: 0.95rem;
  font-weight: 600;
}

.shape span {
  display: block;
  margin-top: 2px;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* Drawn to scale: each segment's width is its seconds. */
.budget {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}

.budget-bar {
  display: flex;
  height: 10px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--ground-sunk);
}

.budget-bar span {
  background: var(--line);
  border-right: 2px solid var(--ground);
}

.budget-bar span:last-child {
  border-right: 0;
}

.budget-bar span.budget-key {
  background: var(--ink-soft);
}

.budget-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0;
  font-size: 0.8rem;
}

.budget-legend div {
  display: flex;
  gap: 6px;
}

.budget-legend dt {
  color: var(--ink-faint);
}

.budget-legend dd {
  margin: 0;
  color: var(--ink-soft);
}

.budget-total {
  margin: 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
}

/* Label and text pairs: skeleton answers, false friends, slips, themes. */
.skeleton,
.playbook-themes {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.skeleton div,
.playbook-themes div {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line-soft);
}

.skeleton dt,
.playbook-themes dt {
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--ink);
}

.skeleton dd,
.playbook-themes dd {
  margin: 0;
  min-width: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.skeleton s {
  color: var(--ink-faint);
  margin-right: 4px;
}

@media (max-width: 420px) {
  .skeleton div,
  .playbook-themes div {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}

/* --- the story bank --------------------------------------------------------- */

.coverage {
  margin: 12px 0 0;
  display: flex;
  flex-direction: column;
}

.coverage div {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line-soft);
}

.coverage dt {
  font-size: 0.9rem;
  font-weight: 500;
}

.coverage dd {
  margin: 0;
  min-width: 0;
  grid-column: 1;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--ink-faint);
}

.coverage .coverage-count {
  grid-column: 2;
  grid-row: 1;
  font-size: 0.82rem;
  color: var(--ink-soft);
}

/* A thin theme is a gap to fill, not an error, so it takes no warning
   colour: eight red counts on an empty bank would shout about nothing. A
   covered theme is marked by weight instead, like the weakest reading. */
.coverage div:not(.coverage-thin) .coverage-count {
  color: var(--ink);
  font-weight: 600;
}

.coverage div.coverage-thin .coverage-count {
  color: var(--ink-faint);
}

/* A link dressed as a button. Several screens use one; the underline that
   links get inside running text does not belong on them. */
a.btn {
  text-decoration: none;
}

.field-hint {
  margin: -4px 0 0;
}

.field input[type="text"] {
  width: 100%;
}

select {
  min-height: var(--tap);
  padding: 10px 12px;
  background: var(--ground-sunk);
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  color: var(--ink);
  width: 100%;
}

select:focus {
  border-color: var(--signal-edge);
  outline: none;
  box-shadow: 0 0 0 3px var(--signal-sunk);
}

.take-story {
  margin: 8px 0 0;
  font-size: 0.82rem;
  color: var(--ink-faint);
}

.take-story a {
  color: var(--ink-soft);
  text-underline-offset: 3px;
}

.danger {
  margin-top: 36px;
  padding-top: 8px;
  border-top: 1px solid var(--line-soft);
  font-size: 0.86rem;
}

.danger summary {
  display: list-item;
  padding: 12px 0;
  min-height: var(--tap);
  color: var(--ink-faint);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.danger summary:active {
  color: var(--ink);
}

.danger form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: flex-start;
}
