/**
 * Presets — styles for the content blocks.
 *
 * Each preset owns a block of rules here, in catalog order. Nothing in this file
 * reaches outside a `.block`, so a preset's styling cannot bleed into the shell
 * or into another preset.
 *
 * ENTRANCE ANIMATION
 *   `[data-reveal]` is only honoured under `html[data-motion="on"]`, which JS
 *   sets on start-up and never sets under `prefers-reduced-motion`. Content is
 *   therefore visible by default and only *becomes* animatable once something is
 *   guaranteed to reveal it — a script error can never leave the page blank.
 */

.block__head { display: grid; gap: var(--sp-2); }
.block__head .kicker + .h2 { margin-top: var(--sp-1); }

html[data-motion="on"] [data-reveal="out"] {
  opacity: 0;
  /* transform only — never a property that participates in layout. */
  transform: translateY(14px);
}

html[data-motion="on"] [data-reveal] {
  transition:
    opacity 0.55s var(--ease-out) var(--reveal-delay, 0ms),
    transform 0.55s var(--ease-out) var(--reveal-delay, 0ms);
}

html[data-motion="on"] [data-reveal="in"] {
  opacity: 1;
  transform: none;
}

/* Printing never scrolls, so nothing would ever be revealed. Force the final
   state so a printed or PDF-exported article is complete. */
@media print {
  html[data-motion="on"] [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ------------------------------------------------------------- text preset */

/* The text preset is prose all the way down — no callout boxes. A point that
   needs emphasis needs a sentence that earns it, not a tinted card. */

/* ------------------------------------------------------ image and gallery */

.figure { display: grid; gap: var(--sp-3); }

/* In-block captions stay quiet: one step below body, tertiary ink. */
.figure figcaption {
  padding-inline: var(--sp-1);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--text-3);
}

/*
 * Ratio is reserved in CSS, so the box exists before the image loads.
 *
 * No fill and no border: an opaque `--surface-2` behind the image is lighter
 * than both the page and the artwork, so any pixel the image does not cover
 * reads as a pale grey plate around it.
 */
.frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
}

.frame--square { aspect-ratio: 1; }
.frame--four-three { aspect-ratio: 4 / 3; }
.frame--wide { aspect-ratio: 16 / 9; }

.frame__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.55s var(--ease-out);
  /* No bottom fade: the picture is shown whole, and the frame's rounded corner
     is the finish. The gradient mask read as an image missing its bottom edge. */
}

/* A breath of zoom on hover — the frame is a card, so it behaves like one.
   `auto` frames hold documents, not previews: they stay still. */
.frame:not(.frame--auto):hover .frame__img { transform: scale(1.02); }

.media-block { display: grid; gap: var(--sp-5); }
.media-block__text { display: grid; gap: var(--sp-4); }
.media-block__aside { display: grid; align-content: start; gap: var(--sp-3); min-width: 0; }

@media (min-width: 46rem) {
  .media-block--aside {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    align-items: start;
    gap: var(--sp-8);
  }

  /* The head spans both columns so the kicker stays on its own line. */
  .media-block--aside > .block__head { grid-column: 1 / -1; }
}

.gallery {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

/* ------------------------------------------------------------- disclosure */

.disclosure {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--bg);
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease);
}

.disclosure:hover { border-color: var(--line); }

.disclosure__summary {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: color var(--dur-fast) var(--ease);
}

.disclosure__summary::-webkit-details-marker { display: none; }
.disclosure__summary::marker { content: ""; }
.disclosure__summary:hover { color: var(--text); }
.disclosure__summary > svg:first-child { color: var(--blue-bright); flex: none; }
.disclosure__label { flex: 1; }
.disclosure__chevron { display: grid; color: var(--text-3); transition: rotate var(--dur) var(--ease); }
.disclosure[open] .disclosure__chevron { rotate: 180deg; color: var(--blue-bright); }

.disclosure__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-4);
  border-top: 1px solid var(--line-soft);
}

/* ------------------------------------------------------------ video preset */

.video { display: grid; gap: var(--sp-3); }

.video__stage {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  background: var(--bg-raised);
  box-shadow: var(--inset-top);
}

.video__poster,
.video__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/*
 * `color: transparent` is the fallback, and it is the whole fallback.
 *
 * The poster is a YouTube thumbnail on a remote CDN. There is deliberately no
 * JavaScript `onerror` swap — that mutated `src`, and the prerenderer saves the
 * DOM as it stands, so one failed fetch at build time was baked into the
 * published file forever (see presets/video.ts). If the image cannot load, this
 * hides the alt text that a broken `<img>` would otherwise spill across the
 * frame, leaving the stage's own background and the play button — which still
 * reads as a video, and still plays.
 */
.video__poster { object-fit: cover; color: transparent; }

/* The play button is the video's one action, so it carries the brand blue:
   a gradient disc with an inset highlight, growing slightly on hover. */
.video__play {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--blue-hover), var(--blue));
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 12px 32px rgba(0, 0, 0, 0.5);
  transition: scale var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.video__stage:hover .video__play { scale: 1.08; }

.video__duration {
  position: absolute;
  z-index: 2;
  top: var(--sp-3);
  right: var(--sp-3);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--r-sm);
  background: rgba(4, 8, 14, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #eaf0f7;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
}

.video__caption {
  position: absolute;
  z-index: 3;
  inset: auto 0 0 0;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: linear-gradient(180deg, rgba(4, 8, 14, 0), rgba(4, 8, 14, 0.85) 55%);
  color: #f2f6fb;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

/* The toggle hides the caption visually; the text stays in the DOM. */
.video__caption.is-hidden { opacity: 0; pointer-events: none; }

.video__cc {
  flex: none;
  padding: 1px 5px;
  border-radius: 4px;
  background: #eaf0f7;
  color: #061019;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-black);
  letter-spacing: 0.05em;
}

.video__loading {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--sp-2);
  background: var(--bg-raised);
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.video__tools { justify-content: flex-start; }
.video__summary,
.video__chapters { display: grid; gap: var(--sp-2); }

.spinner { display: grid; color: var(--blue-bright); animation: spin 0.9s linear infinite; }

@keyframes spin { to { rotate: 360deg; } }

/*
 * The chapter accordion.
 *
 * Each row is a `<details>` card: collapsed it shows start time, label, range,
 * and a chevron tile; opened, the chapter's own transcript segments unfold
 * underneath. The body animates with the grid-rows trick (0fr → 1fr), and the
 * preset's click handler delays `open` removal on close — so both directions
 * are smooth and nothing jumps.
 */
.chapters {
  display: grid;
  gap: var(--sp-2);
}

.chapters__row { display: grid; }

.chapters__item {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--inset-top);
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease);
}

.chapters__item:hover { border-color: var(--line); }
.chapters__item[open] { border-color: var(--blue-line); }

.chapters__summary,
.chapters__item--static {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
}

.chapters__summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.chapters__summary::-webkit-details-marker { display: none; }
.chapters__summary::marker { content: ""; }
.chapters__summary:hover .chapters__label { color: var(--text-bright); }

.chapters__time {
  padding: 3px var(--sp-2);
  border: 1px solid var(--blue-line);
  border-radius: 6px;
  background: var(--blue-dim);
  color: var(--blue-bright);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  font-variant-numeric: tabular-nums;
}

.chapters__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  color: var(--text);
  transition: color var(--dur-fast) var(--ease);
}

/* On a phone the range tucks under the label; from 34rem it joins the row. */
.chapters__range { grid-column: 2; white-space: nowrap; }

/* The chevron lives in a small round tile — same control language as the FAQ. */
.chapters__caret {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-3);
  transition: rotate var(--dur) var(--ease), color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.chapters__item[open] .chapters__caret {
  rotate: 180deg;
  color: var(--blue-bright);
  border-color: var(--blue-line);
  background: var(--blue-dim);
}

/* The unfold. `overflow: hidden` on the inner is what lets the row collapse
   to zero without the text peeking through. */
.chapters__body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur) var(--ease);
}

.chapters__item[open] .chapters__body { grid-template-rows: 1fr; }
.chapters__item.is-closing .chapters__body { grid-template-rows: 0fr; }

/* Opening from display:none needs an explicit starting point to animate. */
@starting-style {
  .chapters__item[open] .chapters__body { grid-template-rows: 0fr; }
}

.chapters__body-inner {
  overflow: hidden;
  min-height: 0;
  display: grid;
}

.chapters__seg {
  display: grid;
  gap: 2px;
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--line-soft);
}

.chapters__topic { font-size: var(--fs-sm); color: var(--text-bright); }
.chapters__text { color: var(--text-2); font-size: var(--fs-sm); line-height: var(--lh-normal); }

@media (min-width: 34rem) {
  .chapters__summary,
  .chapters__item--static { grid-template-columns: auto 1fr auto auto; }
  .chapters__range { grid-column: auto; }
}

/* --------------------------------------------------------- download preset */

.resource {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--inset-top);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.resource:hover { border-color: var(--blue-line); box-shadow: var(--inset-top), var(--shadow-md); }

.resource__icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--blue-line);
  border-radius: var(--r-md);
  background: var(--blue-dim);
  color: var(--blue-bright);
}

.resource__body { display: grid; gap: var(--sp-2); min-width: 0; }
.resource__name { font-size: var(--fs-md); font-weight: var(--fw-semi); }
.resource__desc { color: var(--text-2); font-size: var(--fs-sm); line-height: var(--lh-normal); }
.resource__action { display: grid; gap: var(--sp-2); align-content: start; }
.resource__status { min-height: 1.2em; font-size: var(--fs-xs); color: var(--text-3); }
.resource__status--ok { color: var(--pos); }
.resource__status--error { color: var(--danger); }
.resource__summary { display: grid; gap: var(--sp-2); }

@media (min-width: 40rem) {
  .resource { grid-template-columns: auto 1fr auto; align-items: start; }
}

/* -------------------------------------------------------------- faq preset */

/*
 * ONE CARD, ROWS INSIDE — not a heap of loose panels.
 *
 * Every question used to be its own bordered, filled card, and the block's own
 * outer card was hollowed out to let them float — so an eight-question FAQ read
 * as eight separate things sitting loose on the page, with nothing tying them
 * together. It is one list, so it is one card (the section keeps its normal
 * `.card` chrome, unlike before) with a hairline between rows.
 */
.faq { display: grid; }

.faq__item {
  border-top: 1px solid var(--line-soft);
  transition: background var(--dur-fast) var(--ease);
}

/* The first row sits against the heading, so it needs no rule above it. */
.faq__item:first-child { border-top: 0; }

.faq__item:hover { background: var(--surface); }
.faq__item[open] { background: var(--surface); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--tap);
  /* Inline padding matches the card's, so text lines up with the heading while
     the row's hairline still spans the full width. */
  padding: var(--sp-4) var(--pad-card);
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  letter-spacing: -0.01em;
  color: var(--text-bright);
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-fast) var(--ease);
}

.faq__q::-webkit-details-marker { display: none; }
.faq__q::marker { content: ""; }
.faq__q:hover { color: var(--blue-bright); }

/* The chevron lives in a small round tile — a control, not a loose glyph. */
.faq__caret {
  display: grid;
  place-items: center;
  flex: none;
  width: 28px;
  height: 28px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-3);
  transition: rotate var(--dur) var(--ease), color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

.faq__item[open] .faq__caret {
  rotate: 180deg;
  color: var(--blue-bright);
  border-color: var(--blue-line);
  background: var(--blue-dim);
}

.faq__a { padding: 0 var(--pad-card) var(--sp-4); }
.faq__a .prose { color: var(--text-2); font-size: var(--fs-md); }

/*
 * The outer card keeps its chrome — it IS the container that groups the rows.
 * Its side padding is dropped to `0` so each row's own padding runs the full
 * width and a hairline spans edge to edge; the card's border and fill still draw
 * the box around the whole set.
 */
.block--faq { padding-inline: 0; }
.block--faq .block__head { padding-inline: var(--pad-card); margin-bottom: var(--sp-4); }
.block--faq .faq { margin-inline: 0; }

/* ------------------------------------------------------------- bar chart */

.chart {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-raised);
}

.chart__head { display: grid; gap: 2px; }
.chart__title { font-size: var(--fs-sm); font-weight: var(--fw-semi); }
.chart__rows { display: grid; gap: var(--sp-3); }

/* Label above the bar on a phone; three columns once there is room. */
.bar {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "label value" "track track";
  align-items: center;
  gap: var(--sp-1) var(--sp-3);
}

.bar__label { grid-area: label; font-size: var(--fs-xs); color: var(--text-2); }
.bar__value { grid-area: value; font-size: var(--fs-xs); font-weight: var(--fw-bold); }

.bar__track {
  grid-area: track;
  height: 9px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  overflow: hidden;
}

.bar__fill {
  display: block;
  height: 100%;
  min-width: 4px;
  border-radius: var(--r-pill);
  background: var(--blue);
  transition: width 1s var(--ease);
}

.bar__fill--alt { background: #8b9099; }

@media (min-width: 34rem) {
  .bar {
    grid-template-columns: 9rem 1fr 3rem;
    grid-template-areas: "label track value";
  }
}

/* ------------------------------------------------------------- line chart */

.lc-stack { display: grid; gap: var(--sp-4); }

.lc {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-raised);
}

.lc__head { display: grid; gap: 2px; }
.lc__title { font-size: var(--fs-sm); font-weight: var(--fw-semi); }
.lc__legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }

.lc__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--text-2);
}

.lc__swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.lc__plot { position: relative; }
.lc__svg { width: 100%; height: auto; touch-action: pan-y; }
.lc__svg:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 2px; border-radius: var(--r-sm); }
.lc__grid { stroke: var(--line-soft); stroke-width: 1; }
.lc__guide { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 3; }
.lc__ytick { fill: var(--text-3); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.lc__xtick { fill: var(--text-2); font-size: 11px; }
.lc__line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lc__point { stroke: var(--bg-raised); stroke-width: 2; }

/* `pathLength=1` normalises the dash, so the draw works at any rendered width. */
.lc__line--draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: lc-draw 1s var(--ease) forwards;
}

@keyframes lc-draw { to { stroke-dashoffset: 0; } }

.lc__point--draw { opacity: 0; animation: lc-fade 0.3s ease forwards; }

@keyframes lc-fade { to { opacity: 1; } }

/* Held until the chart scrolls into view. */
.is-paused { animation-play-state: paused; }

/* A halo keeps the value legible where it crosses a line or a gridline. */
.lc__plabel {
  font-size: 10.5px;
  font-weight: var(--fw-semi);
  paint-order: stroke;
  stroke: var(--bg-raised);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.lc__tip {
  position: absolute;
  z-index: 3;
  translate: -50% 0;
  min-width: 8rem;
  max-width: 13rem;
  padding: var(--sp-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  box-shadow: var(--shadow-pop);
  pointer-events: none;
}

.lc__tip-x { margin-bottom: var(--sp-1); font-size: var(--fs-xs); font-weight: var(--fw-bold); }

.lc__tip-row {
  display: grid;
  grid-template-columns: 10px 1fr auto;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
}

.lc__tip-row + .lc__tip-row { margin-top: 3px; }
.lc__tip-label { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lc__tip-val { font-weight: var(--fw-bold); }

/* ------------------------------------------------------------ promo preset */

/* The promo is the one block allowed to wear the brand blue on its sleeve:
   a tinted border and a wash that fades out before the copy. */
.block--promo {
  border-color: var(--blue-line);
  background: linear-gradient(180deg, var(--blue-dim), var(--surface) 55%);
}
.promo { display: grid; gap: var(--sp-6); align-items: center; }
.promo__body { display: grid; gap: var(--sp-3); justify-items: start; }
.promo__cta { margin-top: var(--sp-2); }

/*
 * The vertical visual.
 *
 * `aspect-ratio: 9 / 16` and `object-fit: cover` together mean the FRAME
 * decides the shape and the asset is centred inside it — an image that is not
 * exactly 9:16 is cropped rather than letterboxed, so the card's proportions
 * are the same on every article.
 *
 * On a phone the visual is capped and centred: a full-width 9:16 frame would
 * be taller than the screen. On desktop it takes its own column beside the
 * copy.
 */
.promo__visual {
  position: relative;
  width: 100%;
  max-width: 15rem;
  margin: 0 auto;
  border-radius: var(--r-lg);
  overflow: hidden;
}

.promo__img {
  display: block;
  width: 100%;
  /* `height: auto` first: the `width`/`height` attributes on the element are a
     presentational hint that would otherwise pin the box to its intrinsic size
     and leave `aspect-ratio` with nothing to compute. The attributes stay
     because they are what reserves the right space before the image loads. */
  height: auto;
  aspect-ratio: 9 / 16;
  object-fit: cover;

  /* No bottom fade: the promo visual is shown whole, finished by the card's own
     rounded corner rather than dissolved into it. */
}

@media (min-width: 44rem) {
  /* Copy on the left, the vertical visual on the right — narrow enough that
     9:16 stays a card, not a poster. */
  .promo--visual { grid-template-columns: 1fr 11.5rem; }
  .promo__visual { max-width: none; margin: 0; }
}

/* ------------------------------------------------------- loading / states */

.state {
  display: grid;
  place-items: center;
  gap: var(--sp-3);
  min-height: 7rem;
  padding: var(--sp-5);
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  background: var(--bg);
  color: var(--text-3);
  text-align: center;
}

.state--tall { min-height: 16rem; }
.state--compact { min-height: 4rem; padding: var(--sp-4); }
.state--error { border-color: rgba(224, 115, 107, 0.35); color: var(--danger); }
.state__text { font-size: var(--fs-sm); color: var(--text-2); max-width: 40ch; }

.state--loading { align-content: center; gap: var(--sp-2); }

.state__bar {
  width: min(14rem, 70%);
  height: 10px;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--surface), var(--surface-2), var(--surface));
  background-size: 200% 100%;
  animation: shimmer 1.2s linear infinite;
}

.state__bar--short { width: min(8rem, 45%); }

@keyframes shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* --------------------------------------------------------- list preset */

.list { display: grid; gap: var(--sp-3); margin: 0; padding-left: var(--sp-5); }
.list__item { color: var(--text-2); font-size: var(--fs-base); line-height: var(--lh-relaxed); }
.list__item::marker { color: var(--blue-bright); font-weight: var(--fw-bold); }
.block--list .prose + .list { margin-top: var(--sp-4); }

/* -------------------------------------------------------- table preset */

/*
 * The table fits if it possibly can; the scroll is the last resort.
 *
 * A fee table reflowed into one column per line has lost the comparison it
 * exists to make, so the grid stays a grid — but staying a grid does not require
 * staying WIDE. `.table-scroll` is the fallback for the tables that genuinely
 * cannot fit at 390px, not the plan for all of them. It is focusable and
 * labelled, so when it is reached it can still be scrolled from a keyboard.
 */
/*
 * `min-width: 0` is what actually makes the scroll work.
 *
 * A grid item defaults to `min-width: auto`, i.e. "never shrink below your
 * content" — so a wide table pushed its own block wider than the column, and the
 * whole PAGE scrolled sideways instead of the table. The overflow has to be
 * allowed to happen inside this box before `overflow-x` has anything to do.
 */
.table-scroll {
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /*
   * No box around the table. With block pertama flowing on the page background,
   * the site owner wants the table to read like the original article — rows on
   * the page, not a bordered panel — so only its own row hairlines draw. The
   * horizontal scroll still works; it just has no visible frame.
   */
  -webkit-overflow-scrolling: touch;
}

.table-scroll:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
}

.table th,
.table td {
  padding: var(--sp-3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
  /*
   * A word longer than its column breaks rather than pushing the table wide.
   *
   * This only ever fires when there is no space to break at — a long slug, a
   * URL, a run-on identifier. Without it, one such token puts a floor under its
   * column exactly the way `white-space: nowrap` used to (see the header rules
   * below), and it does it invisibly, in one cell of one table.
   */
  overflow-wrap: anywhere;
}

.table thead th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  color: var(--text-bright);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  border-bottom: 1px solid var(--line);
}

/*
 * HEADERS WRAP. THIS IS THE SINGLE BIGGEST REASON TABLES USED TO OVERFLOW.
 *
 * Both header rules carried `white-space: nowrap`, justified as "keeps rows
 * scannable while scrolling". That argument optimises the wrong thing: the goal
 * is for the reader NOT to have to scroll. A `<th>` that refuses to wrap sets a
 * floor under its column equal to its longest line, and auto table layout has no
 * choice but to honour it — which is how a table with ONE column and no data
 * cells at all (`cara-daftar-syarat-star-seller-shopee`, 13 rows of prose in
 * `<th scope="row">`) managed to overflow a 390px phone by 889 pixels.
 *
 * Wrapping costs vertical space, and vertical space is the dimension the design
 * already calls free (DESIGN.md §4). It changes no fact: a label on two lines is
 * the same label, `<th scope>` is untouched, and no row or column is dropped.
 *
 * Numbers do not wrap anyway — "Rp60.000" and "1–3 hari kerja" have no break
 * opportunity inside the value, so the comparison a fee table exists to make
 * survives intact. Only prose-length headers reflow, and those were never
 * scannable at one glance to begin with.
 */
.table tbody th {
  color: var(--text-bright);
  font-weight: var(--fw-semi);
}

.table td { color: var(--text-2); }
.table tbody tr:last-child th,
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: rgba(47, 111, 208, 0.05); }

.table__note { margin-top: var(--sp-3); }

@media (min-width: 40rem) {
  .table { font-size: var(--fs-base); }
  .table th, .table td { padding: var(--sp-3) var(--sp-4); }
}

/*
 * `auto` — the image shown whole, at its own shape.
 *
 * No locked ratio and no crop. These are documents rather than previews: a
 * fee-calculation screenshot is 1:2.5 and its last row is the total, so cropping
 * it would delete the answer the reader came for.
 */
.frame--auto { aspect-ratio: auto; }

.frame--auto .frame__img {
  position: static;
  height: auto;
  object-fit: contain;
}

/* ------------------------------------------------------- the merged card */

/*
 * One card, several blocks.
 *
 * Prose, the pictures illustrating it, a table, a video, a download: all of it
 * is the article running on, so it shares one container instead of being cut
 * into a stack of bordered panels (see `renderBlocks` in presets/index.ts). The
 * parts keep their own markup and their own inner furniture — a download is
 * still its own raised panel, a video still its own frame — and only the OUTER
 * card chrome is taken off them, because the wrapper now draws it once.
 */
/*
 * Block pertama flows on the page background, not inside a card.
 *
 * The site owner asked for the main content run to read like the original
 * article layout: prose and tables sitting directly on the page, edge to edge of
 * the reading column, with the reading gutter still supplied by `.container`.
 * Only parts that carry their own frame keep one — the video stage and its
 * caption stay boxed (see `.video__stage`). So the wrapper drops its own card
 * chrome here, and the one box left inside the run is the video's.
 */
.block-group {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.block-group > .block {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

/*
 * Breathing room between parts, larger than the card's default `* + *`.
 *
 * Inside one card the gap is the only thing separating a picture from the
 * paragraph after it, so it has to read as a section break rather than as a
 * paragraph break.
 */
.block-group > * + * { margin-top: var(--stack); }
