/*
 * Every colour is `var(--tg-theme-*, fallback)`. Telegram injects the real
 * values at runtime; the fallbacks are what make the app legible when opened
 * directly in a desktop browser during development.
 */

:root {
  --bg: var(--tg-theme-bg-color, #f2f2f7);
  --fg: var(--tg-theme-text-color, #000000);
  --hint: var(--tg-theme-hint-color, #8e8e93);
  --link: var(--tg-theme-link-color, #2f6fed);
  --btn: var(--tg-theme-button-color, #2f6fed);
  --btn-text: var(--tg-theme-button-text-color, #ffffff);
  --sec-bg: var(--tg-theme-secondary-bg-color, #ffffff);
  --radius: 12px;
  --gap: 12px;
}

/*
 * The fallback palette follows the reader's OS.
 *
 * Telegram injects the real values at runtime, so this block does nothing there —
 * the app follows the Telegram theme, light or dark, whichever the user picked.
 * Without it the web version was pinned to the light fallbacks above no matter
 * what the phone or computer was set to, so the same UI came out looking different
 * on the two entry points for a reason nobody could see.
 *
 * These are the values already used in this file for surfaces that read as
 * Telegram's own dark theme, so the web fallback does not introduce a third look.
 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: var(--tg-theme-bg-color, #1c1c1e);
    --fg: var(--tg-theme-text-color, #f2f2f7);
    --hint: var(--tg-theme-hint-color, #98989f);
    --link: var(--tg-theme-link-color, #6ea8ff);
    --btn: var(--tg-theme-button-color, #2f6fed);
    --btn-text: var(--tg-theme-button-text-color, #ffffff);
    --sec-bg: var(--tg-theme-secondary-bg-color, #2c2c2e);
  }
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.6 -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans TC',
    'PingFang TC', 'Microsoft JhengHei', sans-serif;
  overscroll-behavior-y: none;
}

.app {
  max-width: 560px;
  margin: 0 auto;
  /* Top padding clears the fixed companion block, bottom clears the fixed tab
     bar and the iPhone home indicator. Both numbers are load-bearing:
     `儲存` / `刪除` sit at the very bottom of 資料, and the hero total sits right
     under the companion.

     The top figure is **measured, not declared**: `--companion-h` is set by
     `app.js` from the block's real height. It was a fixed 116px, chosen to clear
     the block's 92px *minimum* with room for one extra wrapped line — and the
     moment the bubble ran to four or five lines, which it does on 試算結果, the
     block grew past it and covered the top of the hero. A fixed number cannot
     work here, because the block's height is a function of how the current
     sentence wraps, which is a function of the viewport, the font and the
     platform. Only measuring it can be right. */
  padding: calc(var(--companion-h, 116px) + 16px) 12px calc(84px + env(safe-area-inset-bottom));
}

/*
 * While the landing is up it occupies the whole screen, so it must not be pushed
 * down by the padding that exists to clear the companion block. A class rather
 * than `:has()`, so it does not depend on the engine supporting it.
 */
.app.booting {
  padding-top: 0;
}

.boot {
  padding: 40px 0;
  text-align: center;
  color: var(--hint);
}

/* ------------------------------------------------------------------ landing */

/*
 * The opening sequence. Everything here is decoration around one sentence that
 * is also in the DOM as text, so with motion switched off nothing is lost —
 * the words are still there, they just stop moving.
 *
 * Deliberately restrained: a breathing avatar and a pulsing ring. The brief was
 * "alive", not "showy", and `AGENTS.md` §八 leans the same way. No bounce, no
 * particles, no reward animation.
 */
.landing {
  padding: 48px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  min-height: 60vh;
  justify-content: center;
}

.landing-avatar {
  position: relative;
  width: 128px;
  height: 128px;
  /* The cutout is square artwork. A circle reads as a portrait rather than a
     sticker, and matches the ring this product already uses elsewhere. */
  border-radius: 50%;
  object-fit: cover;
  background: var(--sec-bg);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
  animation: breathe 2.4s ease-in-out infinite;
}

/* If the asset is missing we would otherwise show a broken-image glyph. */
.landing.no-avatar .landing-avatar {
  display: none;
}

.landing-line {
  margin: 0;
  /* 15px rather than the 12px used by .note — this is the one line being said
     out loud, not a footnote. `max-width` in `ch` keeps a long line from
     overflowing a 360px screen. */
  font-size: 15px;
  line-height: 1.7;
  color: var(--fg);
  min-height: 1.7em;
  max-width: 22ch;
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: baseline;
}

/*
 * The ellipsis, as three dots that arrive in turn.
 *
 * Kept out of the text proper and marked aria-hidden: a screen reader announcing
 * "正在撥打" / "好" / "朋友" / "……" as the dots appear is strictly worse than
 * announcing the sentence once. The dots are for the people who can see them.
 */
.landing-dots {
  display: inline-flex;
  gap: 3px;
  margin-left: 2px;
}

.landing-dots i {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: currentColor;
  animation: dot 1.2s ease-in-out infinite;
}

.landing-dots i:nth-child(2) {
  animation-delay: 0.2s;
}

.landing-dots i:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes dot {
  0%,
  60%,
  100% {
    opacity: 0.25;
  }
  30% {
    opacity: 1;
  }
}

.landing-line.is-intro {
  white-space: pre-line;
  color: var(--hint);
  font-size: 14px;
}

/* The ringing indicator: a ring that expands out of the avatar's edge. */
.landing-avatar.is-calling::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--link);
  animation: ring 1.2s ease-out infinite;
}

/* ------------------------------------------------------------ companion bar */

/*
 * The companion's line, pinned to the top of the screen.
 *
 * Fixed, mirroring the tab bar at the bottom — the reader can be halfway down a
 * long form and still be told what the field they are in wants. `.app` carries
 * matching top padding (below) so it can never cover the first field either.
 *
 * This reverses the earlier decision to keep it in the flow. `AGENTS.md` §2 was
 * amended accordingly: the companion is now the one exception to "nothing may
 * cover content", along with the tab bar, and the test that pinned it asserts
 * that both clear the content rather than that they stay in the flow.
 */
.companion {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 9;
  display: flex;
  gap: 12px;
  align-items: flex-start;
  /* Only as wide as the app column, and centred to match it — otherwise the
     bubble would stretch across a desktop window while everything else stays in
     a 560px column. */
  max-width: 560px;
  margin: 0 auto;
  padding: 10px 12px;
  text-align: left;
  background: var(--bg);
  border-bottom: 1px solid rgba(128, 128, 128, 0.14);
}

.companion-avatar {
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--sec-bg);
  animation: breathe 2.4s ease-in-out infinite;
}

/* The bubble. Ties to the avatar so it reads as one object. */
.companion-bubble {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  padding: 10px 14px;
  border-radius: 4px 14px 14px 14px;
  background: var(--sec-bg);
  font-size: 14px;
  line-height: 1.65;
}

.companion-name {
  display: block;
  margin-bottom: 2px;
  font-size: 12px;
  font-weight: 700;
  color: var(--hint);
}

/*
 * The typing effect.
 *
 * A clip reveal, not characters being appended. The text is in the DOM in full
 * from the first frame, and `steps()` walks the clip across it — so it looks
 * typed while a screen reader still reads the whole sentence in one go, and
 * someone who stops the animation still has the words rather than half of them.
 *
 * `steps()` rather than linear: linear slides the last glyph in, which reads as
 * a wipe. Steps reads as keystrokes.
 */
.companion-text {
  display: block;
}

/*
 * One clip per beat, revealed in sequence.
 *
 * The clip is still over text that is complete in the DOM from the first frame,
 * and the beats are still animated by `clip-path` rather than by appending
 * characters — so a screen reader reads the sentence in one go, and someone who
 * stops the animation sees the whole thing rather than half of it. What changed
 * is that there is now more than one clip: one clip over a two-line bubble
 * revealed both lines at once, which read as a caption being dropped in rather
 * than as someone talking. `companionBeats()` decides where the beats break and
 * how long each one takes; this only plays what it is handed.
 */
.companion-beat {
  display: block;
  overflow: hidden;
  animation: typing var(--beat-ms, 400ms) steps(var(--beat-steps, 12)) both;
  animation-delay: var(--beat-delay, 0ms);
}

@keyframes typing {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

/* ------------------------------------------------------------------ tabs */

/*
 * Moved to the bottom, icon over label.
 *
 * `env(safe-area-inset-bottom)` for the iPhone home bar, and `.app` carries
 * matching bottom padding (below) so the bar can never cover the last field —
 * which would be the worst possible place to lose a button, since that is where
 * 儲存 and 刪除 live.
 */
.tabs {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  display: flex;
  padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
  background: var(--bg);
  border-top: 1px solid rgba(128, 128, 128, 0.18);
}

/* Four labels in one row, so the type has to fit a narrow phone without
   wrapping — a wrapped tab label changes the height of the sticky bar and
   pushes the content down on every switch. */
.tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 6px 2px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--hint);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.tab svg {
  width: 22px;
  height: 22px;
  display: block;
}

/* Selected state uses colour *and* weight, so it does not depend on colour
   alone to be perceivable. */
.tab[aria-selected='true'] {
  color: var(--btn);
  font-weight: 700;
}

@keyframes breathe {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.03);
  }
}

@keyframes ring {
  0% {
    transform: scale(1);
    opacity: 0.5;
  }
  100% {
    transform: scale(1.35);
    opacity: 0;
  }
}

/*
 * `AGENTS.md` asks for accessibility basics and the brief asks for
 * prefers-reduced-motion specifically. The repo had no motion at all before
 * this, so there was no block to add to — this is the first one.
 *
 * Motion off must not remove information. Every word stays; only the movement
 * goes. Two things matter here:
 *
 * - The typing effect is a clip over text that is already complete in the DOM,
 *   so switching the animation off shows the full sentence rather than the empty
 *   `clip-path: inset(0 100%)` start state. That is the whole reason it is done
 *   with a clip instead of appending characters.
 * - `runLanding()` reads the same media query, so nothing waits on an animation
 *   that will not play.
 */
@media (prefers-reduced-motion: reduce) {
  .landing-avatar,
  .landing-avatar.is-calling::after,
  .landing-dots i,
  .companion-avatar,
  .companion-beat {
    animation: none;
  }

  /* Belt and braces: the clip must not be left at its start state if the
     animation is suppressed for any other reason (an old engine, an override).
     Every beat, including the ones still waiting on a delay. */
  .companion-beat {
    clip-path: none;
  }

  .landing-avatar.is-calling::after {
    display: none;
  }

  * {
    scroll-behavior: auto;
  }
}

/* ----------------------------------------------------------------- cards */

.card {
  background: var(--sec-bg);
  border-radius: var(--radius);
  padding: 14px;
  margin-top: var(--gap);
  min-width: 0;
}

.card > h2 {
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 700;
}

.card > h2 small {
  font-weight: 400;
  color: var(--hint);
}

.section-label {
  margin: 18px 2px 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--hint);
  text-transform: none;
  letter-spacing: 0.02em;
}

/* -------------------------------------------------------------- hero total */

.hero {
  margin-top: var(--gap);
  padding: 20px 16px;
  border-radius: var(--radius);
  background: var(--btn);
  color: var(--btn-text);
  text-align: center;
}

.hero .label {
  font-size: 13px;
  opacity: 0.85;
}

.hero .amount {
  font-size: 34px;
  font-weight: 800;
  letter-spacing: -0.5px;
  margin: 2px 0;
  font-variant-numeric: tabular-nums;
}

.hero .sub {
  font-size: 12px;
  opacity: 0.85;
}

/* ------------------------------------------------------------------ rows */

.row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid rgba(128, 128, 128, 0.12);
}

.row:last-child {
  border-bottom: 0;
}

/*
 * The stacked variant: label above, value below, both right-aligned.
 *
 * Used in 關於我, where the "value" is a phrase rather than an amount. Side by side
 * it wraps into three or four ragged lines because the two halves have nothing to
 * do with each other in length; stacked and right-aligned, both lines share one
 * edge and the eye reads straight down it.
 */
.row.stacked {
  display: block;
  padding: 10px 0;
  text-align: right;
}

.row.stacked .k,
.row.stacked .v {
  display: block;
  text-align: right;
}

.row.stacked .k {
  font-size: 12px;
  font-weight: 400;
  color: var(--hint);
}

/* No currency alignment to do here — this is prose, not a column of figures. */
.row.stacked .v {
  font-weight: 600;
  white-space: normal;
  line-height: 1.5;
}

.row .k {
  color: var(--hint);
  font-size: 14px;
}

.row .v {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.row.total {
  border-top: 1px solid rgba(128, 128, 128, 0.24);
  border-bottom: 0;
  margin-top: 4px;
  padding-top: 12px;
}

.row.total .k,
.row.total .v {
  color: var(--fg);
  font-weight: 800;
  font-size: 16px;
}

.note {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--hint);
}

/* The unsaved-changes notice. Deliberately not red: it is not an error, and
   red next to the delete button would read as a warning about deleting. */
.note.warn {
  color: var(--fg);
  font-weight: 600;
}

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

/* ------------------------------------------------------------------ forms */

.field {
  display: block;
  margin-top: 12px;
  /* Without this the field box can be sized by the input's intrinsic width
     instead of by the column, which is how a control ends up wider than the
     panel it sits in. */
  min-width: 0;
}

/* Both boxes in the chain between the input and the panel have to be allowed to
   shrink, or `width: 100%` on the input resolves against a wider container. */
.sec,
.sec-body {
  min-width: 0;
}

.field:first-of-type {
  margin-top: 0;
}

.field > .label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 5px;
}

input[type='text'],
input[type='date'],
input[type='number'],
/* The 問題回報 box. It gets the same treatment as the inputs above rather than a
   near-copy: the selector is the one thing they have in common, and a second
   declaration list is a second thing to forget when the inputs change. Same
   16px floor for the same iOS zoom reason. */
textarea {
  display: block;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  padding: 11px 12px;
  border: 1px solid rgba(128, 128, 128, 0.3);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 16px;
  /* Without this a textarea is a single-line control by default and the 3 rows
     asked for are not honoured. */
  resize: vertical;
  min-height: 76px;
}

textarea:focus {
  outline: 2px solid var(--btn);
  outline-offset: 1px;
}
  display: block;
  width: 100%;
  /* A native date control reserves room for its year/month/day segments plus the
     picker button, and that reservation behaves like a minimum. On a narrow
     screen it wins over `width: 100%`, the control runs past the panel, and the
     panel's rounded corner clips it — which reads as "out of range".
     `max-width` on the host is not enough on its own; the shadow content inside
     has to be unconstrained too, hence the rules below. */
  min-width: 0;
  max-width: 100%;
  padding: 11px 12px;
  border: 1px solid rgba(128, 128, 128, 0.3);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  /* Below 16px, iOS Safari zooms the viewport when a field takes focus, and the
     page stays zoomed after blur. That reads as the form jumping out of range
     on exactly the phone this bug was reported from. 16px keeps it still. */
  font-size: 16px;
}

/*
 * The box a date control draws inside.
 *
 * Two earlier attempts fixed this with `min-width: 0` and `::-webkit-*` shadow
 * rules on the control itself. Both looked right and both failed: the control
 * carries an intrinsic minimum that wins over `width: 100%`, so the box itself
 * ran past the column and into the panel's padding, which is exactly what the
 * screenshot showed — the date fields wider than the number field above them,
 * on the same row of the same form.
 *
 * So the box belongs to this wrapper, not to the control. Its width comes from
 * the block layout like every other field, and `overflow: hidden` clips whatever
 * the control does inside. That works the same in every engine because it never
 * asks the engine to give up its intrinsic minimum.
 */
.field-box {
  position: relative;
  overflow: hidden;
  /* Matches the input's own box exactly, so a date field and a number field are
     the same shape in the same form. */
  border: 1px solid rgba(128, 128, 128, 0.3);
  border-radius: 10px;
  background: var(--bg);
}

.field-box.invalid {
  border-color: #e5484d;
}

/* The control inside it is just a surface: no border of its own, and nothing that
   would make it taller than the wrapper it is clipped to. */
.field-box input[type='date'] {
  border: 0;
  border-radius: 0;
  background: transparent;
  max-width: none;
  min-height: 0;
}

/* The focus ring has to be drawn by the wrapper — an outline on a clipped child
   would be cut off by the very `overflow: hidden` that fixes the width. */
.field-box:focus-within {
  outline: 2px solid var(--btn);
  outline-offset: -1px;
}

.field-box input[type='date']:focus {
  outline: none;
}

/* The segment row inside the date control. `min-inline-size` rather than
   `min-width` so Firefox takes it too — it is the engine that honours the
   logical property and will otherwise keep the control wide. */
input[type='date']::-webkit-date-and-time-value {
  min-width: 0;
  min-inline-size: 0;
  /* `min-width: 0` lets the *control* shrink, but nothing stopped the date text
     inside it from still claiming its full width and spilling past the edge.
     `overflow` is what clips the shadow content. */
  overflow: hidden;
}

input[type='date']::-webkit-datetime-edit {
  min-width: 0;
  padding: 0;
  overflow: hidden;
}

/* Firefox builds the calendar button as its own box and lets it claim width. */
input[type='date']::-webkit-calendar-picker-indicator {
  padding: 0;
  margin-left: 4px;
}

input:focus {
  outline: 2px solid var(--btn);
  outline-offset: -1px;
}

input.invalid {
  border-color: #e5484d;
}

.field .hint {
  font-size: 12px;
  color: var(--hint);
  margin-top: 4px;
}

.field .error {
  font-size: 12px;
  color: #e5484d;
  margin-top: 4px;
}

/* ---------------------------------------------------------------- toggles */

.switch {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 27px;
  flex: none;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.switch .track {
  position: absolute;
  inset: 0;
  background: rgba(128, 128, 128, 0.34);
  border-radius: 999px;
  transition: background 0.18s;
}

.switch .track::before {
  content: '';
  position: absolute;
  width: 21px;
  height: 21px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.18s;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.switch input:checked + .track {
  background: #2fa84f;
}

.switch input:checked + .track::before {
  transform: translateX(19px);
}

.switch input:focus-visible + .track {
  outline: 2px solid var(--btn);
  outline-offset: 2px;
}

/* -------------------------------------------------------- checkbox rows */

.check {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 10px 0;
  border-bottom: 1px solid rgba(128, 128, 128, 0.12);
  cursor: pointer;
}

.check:last-child {
  border-bottom: 0;
}

.check .box {
  width: 22px;
  height: 22px;
  flex: none;
  margin-top: 1px;
  border: 2px solid rgba(128, 128, 128, 0.45);
  border-radius: 6px;
  display: grid;
  place-items: center;
  color: transparent;
  font-size: 14px;
  font-weight: 700;
}

.check input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.check input:checked ~ .box {
  background: #2fa84f;
  border-color: #2fa84f;
  color: #fff;
}

.check .text {
  min-width: 0;
}

.check .text b {
  display: block;
  font-weight: 600;
  font-size: 15px;
}

.check .text span {
  font-size: 12px;
  color: var(--hint);
}

.check input:focus-visible ~ .box {
  outline: 2px solid var(--btn);
  outline-offset: 2px;
}

/* --------------------------------------------------------------- buttons */

.btn {
  display: block;
  width: 100%;
  padding: 13px;
  border: 0;
  border-radius: 11px;
  background: var(--btn);
  color: var(--btn-text);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.btn.ghost {
  background: transparent;
  color: var(--link);
  border: 1px solid var(--link);
}

.btn + .btn {
  margin-top: 8px;
}

.btn-row {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.btn-row .btn {
  margin-top: 0;
  /* `.btn` is width:100% for the stacked case, which would make both buttons
     claim half the row. Saving is the common action and delete is rare and
     destructive, so they are not equal: delete is sized to its label. */
  width: auto;
}

.btn-row .btn:not(.danger) {
  flex: 1 1 auto;
}

.btn-row .btn.danger {
  flex: 0 0 auto;
}

.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
}

.pill.ok {
  background: #2fa84f;
  color: #fff;
}

.pill.warn {
  background: #f5a524;
  color: #fff;
}

.pill.off {
  background: rgba(128, 128, 128, 0.22);
  color: var(--hint);
}

.progress {
  height: 6px;
  border-radius: 999px;
  background: rgba(128, 128, 128, 0.2);
  overflow: hidden;
  margin: 8px 0 6px;
}

.progress > i {
  display: block;
  height: 100%;
  background: #2fa84f;
  transition: width 0.2s;
}

.empty {
  padding: 26px 14px;
  text-align: center;
  color: var(--hint);
  font-size: 14px;
}

/* "Which fields are you missing?" — the one thing the empty state has to say.
   Left-aligned because it is a list to read, not a headline to admire. */
ul.missing {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  text-align: left;
  display: inline-block;
}

ul.missing li {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 9px 12px;
  margin-bottom: 8px;
  border-radius: 10px;
  background: var(--bg);
  border: 1px solid rgba(128, 128, 128, 0.22);
}

ul.missing li b {
  color: var(--fg);
  font-size: 14px;
}

ul.missing li span {
  color: var(--hint);
  font-size: 12.5px;
}

/* Destructive, and it is the one button in the app that cannot be undone. */
.btn.danger {
  background: transparent;
  color: #e5484d;
  border: 1px solid rgba(229, 72, 77, 0.45);
}

.btn.danger:hover,
.btn.danger:active {
  background: rgba(229, 72, 77, 0.1);
}

/* --------------------------------------------------------------------- QA */

/* Native <details>, so the collapse behaviour and the platform accessibility
   come for free. Only the marker and the spacing are restyled. */
.qa {
  background: var(--sec-bg);
  border-radius: var(--radius);
  margin-top: var(--gap);
  overflow: hidden;
}

/* ---------------------------------------------------- collapsible sections */

/* Every panel below the hero can be folded away, so the first screen stays
   short and the summary line still carries the number that matters. */
.sec {
  background: var(--sec-bg);
  border-radius: var(--radius);
  margin-top: var(--gap);
  overflow: hidden;
}

.sec > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 14px;
  font-weight: 600;
  font-size: 15px;
  cursor: pointer;
  list-style: none;
  min-height: 44px; /* comfortable tap target on a phone */
}

/* The platform's own triangle marker, replaced with one we can align. */
.sec > summary::-webkit-details-marker {
  display: none;
}

.sec > summary::after {
  content: '＋';
  color: var(--hint);
  font-weight: 400;
  flex: none;
  font-size: 17px;
}

.sec[open] > summary::after {
  content: '－';
}

.sec[open] > summary {
  border-bottom: 1px solid rgba(128, 128, 128, 0.14);
}

.sec-body {
  padding: 12px 14px 14px;
  min-width: 0;
}

.sec-title {
  min-width: 0;
}

.sec-badge {
  margin-left: auto;
  flex: none;
  font-weight: 600;
  font-size: 13px;
  color: var(--hint);
  font-variant-numeric: tabular-nums;
}

.sec[open] .sec-badge {
  color: var(--text);
}

/* A short, calm aside — used for "here's the one thing to notice". */
.card.callout {
  border-left: 3px solid var(--link);
}

/* The author story. Longer measure and more line height than the rest of the
   app, because this is the one place that is meant to be read. */
.lede {
  font-size: 16px;
  line-height: 1.7;
  margin: 0 0 12px;
}

.story p {
  margin: 0 0 14px;
  line-height: 1.85;
}

/* The one sentence in the story that is the point of it. */
.story .pull {
  font-size: 15px;
  border-left: 3px solid var(--link);
  padding-left: 12px;
  color: var(--text);
}

.src {
  margin: 12px 0 0;
  border-top: 1px solid rgba(128, 128, 128, 0.16);
  padding-top: 10px;
  font-size: 12.5px;
}

.qa > summary {
  padding: 14px;
  font-weight: 600;
  font-size: 15px;
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

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