/* ---------- Tokens ---------- */
:root {
  --bg: #F6F7F9;
  --surface: #FFFFFF;
  --ink: #15171C;
  --text-2: #4A5060;
  --text-3: #5A6070;
  --muted: #6E7484;
  --line: #DADDE3;
  --border: #E3E6EB;
  --border-strong: #D5D9E0;
  --dot-idle: #AEB4BF;

  --accent: #3346C8;
  --accent-ink: #2A3AB0;
  --accent-soft: #E8EBFB;

  --sesi-a-bg: #F3F4F8;
  --sesi-b-bg: #FBF6EE;
  --sesi-b-ink: #8A520F;

  /* Card padding: the same on every side and for every gap between the card's main parts
     (32 desktop, 24 tablet, 20 mobile). Radius: outer = inner + padding (12 + 32 = 44). */
  --card-pad: 32px;
  --radius-inner: 12px;
  --radius-nested: 8px; /* buttons inside a 12px box, e.g. the Sesi A/B buttons */
  --radius-card: calc(var(--radius-inner) + var(--card-pad));

  /* Letter-spacing for every uppercase label (eyebrow and the "PEKAN" word). */
  --eyebrow-tracking: 0.06em;

  --font: 'Figtree', system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* Code only (materi page). Loaded on materi.html, not on the timeline. */
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  /* Week card title: 24px closed, 32px open, on one 40px line (mobile 20 → 24 on 32). */
  --card-title-lh: 40px;
  /* Space above the title inside the card header: none here; mobile adds the "PEKAN 01" row. */
  --card-head-above: 0px;

  /* Timeline geometry. The dot's centre sits on the middle of the week title's line. */
  --spine-w: 28px;
  --dot-size: 12px;
  /* 1px: the card's border. */
  --dot-y: calc(1px + var(--card-pad) + var(--card-head-above) + var(--card-title-lh) / 2 - var(--dot-size) / 2);
}

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

/* The hidden attribute always wins over a component's display (e.g. .button, .load-error). */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 24px;
}

a { color: var(--accent); }
a:hover { color: var(--accent-ink); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- Layout ---------- */
.page {
  max-width: 768px; /* 720px of content inside the 24px side padding */
  margin: 0 auto;
  padding: 96px 24px 144px;
}

.hero { display: flex; flex-direction: column; gap: 24px; }
/* The program goal, closing the hero: an outlined box (like the result box in a week card) with the
   way into the goal's detail in the top-right corner, as far from the right edge as from the top;
   the button drops below the text on mobile. */
.goal-box {
  margin-top: 8px; display: flex; align-items: flex-start; justify-content: space-between; gap: 16px 24px;
  padding: 24px; border: 1px solid var(--border-strong); border-radius: var(--radius-inner);
}
/* The text takes the remaining width, so the gap to the button is exactly the box padding (24). */
.goal-box__text { flex: 1; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.goal-box__title { margin-top: 4px; font-size: 20px; line-height: 32px; font-weight: 600; color: var(--ink); }
.goal-box__note { color: var(--text-2); }
.goal-box .button { flex: none; }

.eyebrow {
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  letter-spacing: var(--eyebrow-tracking);
  text-transform: uppercase;
  color: var(--text-3);
}
.eyebrow--accent { color: var(--accent); }
.eyebrow--a { color: var(--accent-ink); }
.eyebrow--b { color: var(--sesi-b-ink); }

.title {
  margin: 0;
  font-size: 56px;
  line-height: 64px;
  font-weight: 600;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.lead { margin: 0; font-size: 20px; line-height: 32px; color: var(--text-2); }
.meta { font-size: 12px; line-height: 16px; color: var(--text-3); }

/* ---------- Progress ---------- */
.progress { margin-top: 64px; display: flex; flex-direction: column; gap: 16px; }
.progress[hidden] { display: none; }
.progress__head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; flex-wrap: wrap;
}
.progress__text { font-size: 20px; line-height: 32px; font-weight: 600; }
.progress__bar { display: flex; gap: 4px; }
.progress__seg { flex: 1; height: 4px; border-radius: 999px; background: var(--line); }
.progress__seg.is-done { background: var(--ink); }
.progress__seg.is-now { background: var(--accent); }
.progress__note { margin: 0; color: var(--text-2); max-width: 600px; }

/* ---------- Timeline ---------- */
.timeline { list-style: none; margin: 64px 0 0; padding: 0; display: flex; flex-direction: column; }
/* Room above a week scrolled into view, so the card does not touch the top edge. */
.week { scroll-margin-top: 48px; }

.row {
  display: grid;
  grid-template-columns: 64px var(--spine-w) minmax(0, 1fr);
  column-gap: 16px;
}

/* Phase label: its text lines up with the card text. */
.phase { padding: 48px var(--card-pad) 16px; }
.timeline > :first-child .phase { padding-top: 0; }

/* Week number: the number sits on the week title's line, "PEKAN" right above it. */
.week-no {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 4px; padding-top: calc(1px + var(--card-pad) - 16px);
}
.week-no__word { font-size: 12px; line-height: 12px; font-weight: 600; letter-spacing: var(--eyebrow-tracking); text-transform: uppercase; color: var(--text-3); }
.week-no__num { font-size: 32px; line-height: var(--card-title-lh); font-weight: 600; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; color: var(--muted); }
.is-done .week-no__num { color: var(--ink); }
.is-now .week-no__num { color: var(--accent); }

/* Spine: each row draws its own segments so the line never breaks
   and never runs past the first or last dot. */
.spine { position: relative; }
.spine__seg { position: absolute; left: calc(var(--spine-w) / 2 - 1px); width: 2px; background: var(--line); }
.spine__seg.is-passed { background: var(--ink); }
.spine__seg--full { top: 0; bottom: 0; }
.spine__seg--top { top: 0; height: calc(var(--dot-y) + var(--dot-size) / 2); }
.spine__seg--bottom { top: calc(var(--dot-y) + var(--dot-size) / 2); bottom: 0; }

.spine__dot {
  position: absolute; z-index: 1;
  top: var(--dot-y); left: calc(var(--spine-w) / 2 - var(--dot-size) / 2);
  width: var(--dot-size); height: var(--dot-size);
  border-radius: 999px;
  background: var(--bg);
  border: 2px solid var(--dot-idle);
}
.spine__dot { transition: border-color 160ms ease; }
.is-done .spine__dot { background: var(--ink); border-color: var(--ink); }
.is-now .spine__dot {
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(51, 70, 200, 0.16);
}

.week__body { padding-bottom: 16px; min-width: 0; }

.card {
  border: 1px solid transparent;
  border-radius: var(--radius-card);
}
.card.is-open {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: 0 16px 40px -24px rgba(21, 23, 28, 0.3);
}
/* Hover on a closed week: the card shape shows faintly (white surface, border, no shadow) and the
   week's dot darkens, so it reads as clickable. Pointer devices only: on touch the state sticks. */
@media (hover: hover) {
  .card:not(.is-open):not(.is-locked):has(.card__toggle:hover) { background: var(--surface); border-color: var(--border); }
  .is-todo .row:has(.card:not(.is-open) button.card__toggle:hover) .spine__dot { border-color: var(--text-3); }
}

.card__toggle {
  width: 100%;
  display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;
  padding: var(--card-pad);
  border: 0; background: transparent; color: inherit;
  font: inherit; text-align: left; cursor: pointer;
  border-radius: var(--radius-card);
}
.card__heading { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.card__title { align-self: flex-start; font-size: 24px; line-height: var(--card-title-lh); font-weight: 600; letter-spacing: -0.02em; }
.card.is-open .card__title { font-size: 32px; }
/* A week that has not started: only its title, muted, with a small lock. Not a control. */
.card__toggle--locked { cursor: default; }
.card.is-locked .card__title { color: var(--text-3); }
.card.is-preview .card__title { color: var(--ink); }
/* Before week 1: the week's topics as an overview. Unlike .card__sub these wrap and never fold. */
.card__line { color: var(--text-3); }
.card__lock { display: inline-block; margin-left: 8px; vertical-align: -1px; color: var(--text-3); }
.card__sub { color: var(--text-3); max-height: 24px; overflow: hidden; }
/* Open, the Sesi B box below says the same thing in full, so the summary line folds away. */
.card.is-open .card__sub { max-height: 0; margin-top: -4px; opacity: 0; visibility: hidden; }
/* Mobile only: the week number moves into the card (see the mobile block). */
.card__week {
  display: none;
  font-size: 12px; line-height: 16px; font-weight: 600;
  letter-spacing: var(--eyebrow-tracking); text-transform: uppercase;
  color: var(--text-3);
}
.is-now .card__week { color: var(--accent-ink); }

.badge {
  flex: none;
  display: inline-flex; align-items: center;
  height: 28px; padding: 0 12px; margin-top: calc((var(--card-title-lh) - 28px) / 2);
  border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
  background: var(--accent-soft); color: var(--accent-ink);
}

/* Open/close: the details row grows from 0 to its own height (grid 0fr → 1fr), so the cards
   below slide instead of jumping. Closed details are hidden from screen readers and the tab
   order by visibility, which switches only after the closing transition (see Motion below). */
.card__detail { display: grid; grid-template-rows: 0fr; visibility: hidden; }
.card.is-open .card__detail { grid-template-rows: 1fr; visibility: visible; }
.card__clip { min-height: 0; overflow: hidden; }
.card__content { padding: 0 var(--card-pad) var(--card-pad); display: flex; flex-direction: column; gap: var(--card-pad); opacity: 0; }
.card.is-open .card__content { opacity: 1; }

.sessions { display: flex; flex-wrap: wrap; gap: var(--card-pad); }
.session {
  flex: 1 1 240px; min-width: 0;
  padding: 24px;
  border-radius: var(--radius-inner);
  display: flex; flex-direction: column; gap: 16px;
}
.session--a { background: var(--sesi-a-bg); }
.session--b { background: var(--sesi-b-bg); }
.session p { margin: 0; }

.list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }

.block { display: flex; flex-direction: column; gap: 12px; }
/* The result: where the steps lead. An outlined box (no fill, so it does not compete with the
   session boxes) with the target mark of the materi page's Target card; the note on how it is
   sent sits inside, under the result. */
.outcome {
  display: grid; grid-template-columns: 32px minmax(0, 1fr); column-gap: 12px; align-items: start;
  margin-top: 4px; padding: 16px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-inner);
}
.outcome__icon {
  width: 32px; height: 32px; margin-top: -4px; display: grid; place-items: center;
  border-radius: 8px; background: var(--accent-soft); color: var(--accent-ink);
}
.outcome__text { display: flex; flex-direction: column; gap: 4px; }
.outcome__text p { margin: 0; }
.outcome__text strong { font-weight: 600; }
.outcome__note { color: var(--text-3); }
.block__title { margin: 0; font-weight: 600; }
/* The task's context: why this week's steps, right under its title. */
.block__note { margin: -8px 0 0; color: var(--text-2); }

/* Task dialog: the week's task, the program goal, and one brief. The body scrolls inside the dialog. */
/* The 6px padding (cancelled by the negative margin) keeps room for the focus outline
   (2px + 4px offset), which the scroll container would otherwise clip. */
.brief-list { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 24px; margin: -6px; padding: 6px; scroll-padding: 6px; }
.dialog__heading { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* Back to the list, above the eyebrow: as tall as the close button and level with it, with the
   same space below it as the dialog has above it. */
.dialog__heading > .brief__back { height: 44px; margin: 0 0 calc(var(--dialog-pad) - 4px) -2px; }
.brief__lead { margin: 0; color: var(--text-2); }
.brief__part { display: flex; flex-direction: column; gap: 12px; }
.brief__part h3 { margin: 0; }
/* Must-haves sit in a tinted box, so they read as the core of the brief; extras stay plain. */
.brief__must { display: flex; flex-direction: column; gap: 12px; padding: 24px; border-radius: var(--radius-inner); background: var(--sesi-a-bg); }
/* Small label inside a part, under its eyebrow: "Wajib ada", "Tambahan, kalau tempo cukup". */
.brief__sub { margin: 0; font-weight: 600; color: var(--ink); }
/* A closing note inside a tinted box, under its list. */
.brief__sub-note { margin: 0; color: var(--text-2); }
/* User and business goal of a brief: label above, text below. */
.brief__facts { margin: 0; display: flex; flex-direction: column; gap: 16px; }
.brief__facts > div { display: flex; flex-direction: column; gap: 4px; }
.brief__facts dd { margin: 0; }
/* The assessment criteria, read as a set rather than steps. */
.brief__chips { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
.brief__chips li { padding: 4px 12px; border: 1px solid var(--border-strong); border-radius: 999px; color: var(--text-2); }
.brief__rows { display: flex; flex-direction: column; gap: 12px; }
/* Goal program and starter kit side by side at the top of the task view; stacked on mobile. */
.brief__rows--pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
a.brief__item { text-decoration: none; }
.brief__item-icon { flex: none; }
.brief__items { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
.brief__items li { display: flex; gap: 12px; }
.brief__icon { flex: none; margin-top: 4px; color: var(--accent-ink); }
.brief__items--extra { color: var(--text-2); }
.brief__items--extra .brief__icon { color: var(--text-3); }
.brief__refs { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
/* References leave the site, so they read as text links (accent, underline, external icon),
   not as buttons that act inside the modal. */
.brief__ref {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  color: var(--accent-ink);
  text-decoration: underline; text-decoration-color: var(--line); text-decoration-thickness: 1px; text-underline-offset: 4px;
  transition: color 160ms ease, text-decoration-color 160ms ease;
}
li.brief__ref { color: var(--text-2); text-decoration: none; }
.brief__ref:hover, .brief__ref:active { color: var(--accent); text-decoration-color: currentColor; }
.brief__ref-icon { color: currentColor; }
.brief__back {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; margin: -10px 0 4px -2px; padding: 0 2px; border: 0; background: none;
  color: var(--text-2); font: inherit; font-weight: 600; cursor: pointer;
}
.brief__back:hover { color: var(--ink); }
/* A row that opens another view of the dialog (goal, brief). --static: the same card, not clickable. */
.brief__item {
  width: 100%; display: flex; align-items: center; gap: 16px;
  padding: 16px 24px; text-align: left;
  border: 1px solid var(--border); border-radius: var(--radius-inner);
  background: var(--surface); color: var(--text-3); font: inherit; cursor: pointer;
}
.brief__item:hover { background: var(--bg); border-color: var(--border-strong); color: var(--ink); }
.brief__item--static { cursor: default; }
.brief__item--static:hover { background: var(--surface); border-color: var(--border); }
.brief__item-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.brief__item-title { color: var(--ink); font-weight: 600; }
.brief__item-summary { color: var(--text-2); }
/* Project list as one path: a node per project (P1, P2) on a rail, the card beside it, and the
   hand-off to the next project written beside the rail. The rail runs from the first node's
   centre to the last node's centre (36px = 16 card padding + half the 40px node). */
.flow { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
.flow__step { position: relative; display: grid; grid-template-columns: 40px minmax(0, 1fr); column-gap: 16px; padding-bottom: 12px; }
.flow__step:last-child { padding-bottom: 0; }
.flow--linked .flow__step::before {
  content: ""; position: absolute; left: 19px; top: 0; bottom: 0; width: 2px; background: var(--border-strong);
}
.flow--linked .flow__step:first-child::before { top: 36px; }
.flow--linked .flow__step:last-child::before { bottom: auto; height: 36px; }
.flow--linked .flow__step:only-child::before { display: none; }
.flow__node {
  position: relative; grid-column: 1; grid-row: 1; align-self: start; margin-top: 16px;
  width: 40px; height: 40px; display: grid; place-items: center;
  border: 2px solid var(--accent); border-radius: 50%; background: var(--surface);
  color: var(--accent); font-size: 12px; line-height: 16px; font-weight: 600;
}
.flow__step > .brief__item { grid-column: 2; grid-row: 1; }
.flow__handoff { grid-column: 2; margin: 0; padding: 12px 24px 0; color: var(--text-3); }
.flow__steps { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0; color: var(--text-2); }
.flow__item { display: inline-flex; align-items: center; gap: 8px; }
.flow__arrow { flex: none; color: var(--text-3); }

.actions { display: flex; flex-wrap: wrap; gap: 12px; }
.actions .button { gap: 8px; }

.button {
  display: inline-flex; align-items: center; justify-content: center;
  height: 48px; padding: 0 24px;
  border-radius: var(--radius-inner);
  border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--ink);
  font: inherit; font-weight: 600; text-decoration: none;
  cursor: pointer;
}
.button:hover { color: var(--ink); border-color: var(--dot-idle); }
.button--primary { background: var(--ink); border-color: var(--ink); color: #FFFFFF; }
.button--primary:hover { color: #FFFFFF; background: #2A2E37; }
.button[aria-disabled="true"] { opacity: 0.4; pointer-events: none; }

/* Meeting milestone (gate): its own timeline row after the week. A diamond sits on the line, like
   the week dots: filled once the week before it has passed, outlined before that. The text lines
   up with the card text. The line through the row continues to the next week; after the last
   week it stops at the diamond's centre (to-mark), which is then the end of the timeline. */
.gate-row .spine { min-height: 64px; }
.spine__seg--to-mark { top: 0; height: 16px; }
.gate__mark {
  position: absolute; z-index: 1;
  /* Centred on the line (x 14 = line left 13 + half its 2px) and on the text line
     (y 16 = 4 padding + half of the 24px line). 14px square → about 20px across when turned. */
  top: 9px; left: calc(var(--spine-w) / 2 - 7px);
  width: 14px; height: 14px;
  transform: rotate(45deg);
  background: var(--bg);
  border: 2px solid var(--dot-idle);
}
.gate-row.is-passed .gate__mark { background: var(--ink); border-color: var(--ink); }
/* 4px above, 36 below: the meeting sits close to the week it closes (proximity), with room before the next phase. */
.gate { display: flex; flex-wrap: wrap; align-items: center; column-gap: 12px; padding: 4px var(--card-pad) 36px; }
.gate__name { font-weight: 600; }
.gate__via { color: var(--text-3); }
/* With a Meet link (info.json meetUrl) the whole text is the link: "Google Meet" is underlined
   and a small arrow follows, so it reads as clickable before hover; on hover it turns accent. */
.gate--link { align-self: start; justify-self: start; text-decoration: none; color: var(--ink); border-radius: var(--radius-inner); }
.gate--link .gate__via { text-decoration: underline; text-decoration-color: var(--line); text-underline-offset: 4px; }
.gate__icon { align-self: center; margin-left: -8px; color: var(--text-3); }
.gate--link .gate__name, .gate--link .gate__via, .gate__icon { transition: color 160ms ease, text-decoration-color 160ms ease; }
.gate--link:hover .gate__name, .gate--link:hover .gate__via, .gate--link:hover .gate__icon { color: var(--accent-ink); }
.gate--link:hover .gate__via { text-decoration-color: currentColor; }

.load-error { margin-top: 32px; display: flex; flex-direction: column; gap: 8px; color: var(--text-2); }
.load-error p { margin: 0; }
.load-error[hidden] { display: none; }

/* ---------- Materi page ---------- */
/* Same width as the home page (.page), so the text keeps its left edge between pages. */
.page--reading { max-width: 768px; }

.back {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px;
  color: var(--text-2); font-weight: 600; text-decoration: none;
}
.back:hover { color: var(--ink); }

.reading__head { margin-top: 48px; display: flex; flex-direction: column; gap: 24px; }
/* Smaller than the main button (48px): secondary actions beside text, still a 44px target. */
.button--small { height: 44px; padding: 0 16px; border-radius: var(--radius-nested); }

/* Sesi A / Sesi B tabs: a segmented control, each session in its own color (tokens from the week card). */
.tabs[hidden] { display: none; }
.tabs {
  margin-top: 32px;
  display: flex; gap: 4px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
}
.tab {
  flex: 1 1 auto;
  min-height: 44px; padding: 0 16px;
  border: 0; border-radius: 999px;
  background: transparent; color: var(--text-2);
  font: inherit; font-weight: 600; white-space: nowrap;
  cursor: pointer;
}
.tab:hover { color: var(--ink); }
/* The selected tab's background is one highlight that slides between the tabs (materi.js sets
   --x and --w from the selected tab). It has a thin ring in the session color: Sesi B's cream
   alone is too close to white. */
.tabs { position: relative; }
.tab { position: relative; z-index: 1; }
.tabs__indicator {
  position: absolute; top: 4px; bottom: 4px; left: 0;
  width: var(--w, 0); transform: translateX(var(--x, 0));
  border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px currentColor;
}
.tabs[data-sesi="b"] .tabs__indicator { background: var(--sesi-b-bg); color: var(--sesi-b-ink); }
.tab[data-sesi="a"][aria-selected="true"] { color: var(--accent-ink); }
.tab[data-sesi="b"][aria-selected="true"] { color: var(--sesi-b-ink); }

.sesi .prose { margin-top: 32px; }

.notice {
  margin: 32px 0 0;
  padding: 12px 16px;
  border-radius: var(--radius-inner);
  background: var(--sesi-b-bg); color: var(--sesi-b-ink);
  font-weight: 600;
}

/* Target section of a materi, lifted into a card so it stands out from the content. */
.prose .target {
  padding: var(--card-pad);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: 0 16px 40px -24px rgba(21, 23, 28, 0.3);
}
.prose .target > * { margin: 0; }
.prose .target > * + * { margin-top: 16px; }
/* Heading row: target mark + title. The intro line ("Di akhir pekan, kamu bisa:") stays quiet. */
.prose .target__head { display: flex; align-items: center; gap: 16px; }
.target__icon {
  flex: none; width: 40px; height: 40px; display: grid; place-items: center;
  border-radius: var(--radius-inner); background: var(--accent-soft); color: var(--accent-ink);
}
.prose .target h2 { margin: 0; font-size: 24px; line-height: 32px; }
.prose .target__head + p { color: var(--text-2); }
/* Each goal is its own row with a numbered marker, so the list reads as a set of outcomes
   rather than running text. No dividers: the markers and a 12px gap keep the rows apart.
   Bullet and numbered lists look the same here. */
.prose .target ul, .prose .target ol {
  padding: 0; list-style: none; counter-reset: goal;
  display: flex; flex-direction: column; gap: 12px;
}
.prose .target li {
  counter-increment: goal;
  /* Same columns as the heading row: the number centred under the 40px icon, the text on the
     title's left edge. */
  display: grid; grid-template-columns: 40px minmax(0, 1fr); column-gap: 16px;
  margin: 0;
}
/* On the Sesi B tab the mark and the numbers take the Track Taste colors. */
.tabs[data-sesi="b"] ~ .sesi .target__icon,
.tabs[data-sesi="b"] ~ .sesi .target li::before { background: var(--sesi-b-bg); color: var(--sesi-b-ink); }
.prose .target li::before {
  content: counter(goal);
  justify-self: center;
  width: 24px; height: 24px; margin-top: 2px; display: grid; place-items: center;
  border-radius: 999px; background: var(--accent-soft); color: var(--accent-ink);
  font-size: 12px; line-height: 16px; font-weight: 600; font-variant-numeric: tabular-nums;
}

/* The week's task closes the Target card: what this week asks of the project, and a small
   secondary button to the brief. A grey inset box, so it reads as the card's outcome. */
.prose .target > .target__task {
  margin-top: var(--card-pad);
  display: flex; align-items: center; justify-content: space-between; gap: 16px 24px;
  padding: 24px; border-radius: var(--radius-inner); background: var(--bg);
}
.target__task-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.target__task-title { margin-top: 4px; font-size: 20px; line-height: 32px; font-weight: 600; color: var(--ink); }
.target__task-note { color: var(--text-2); line-height: 24px; }
.target__task .button { flex: none; }

/* "Di halaman ini": links to every other section, two columns when there is room.
   Same room above it as below it (the next section's divider, 48; mobile 16). */
.prose > .toc { margin-top: 48px; display: flex; flex-direction: column; gap: 12px; }
.prose .toc__list { margin: 0; padding-left: 0; list-style: none; columns: 2; column-gap: 32px; }
.prose .toc--inline a { display: flex; gap: 12px; }
.prose .toc__list li { margin: 0 0 8px; break-inside: avoid; }
.prose .toc__list li + li { margin-top: 0; }
.prose .toc__list li:last-child { margin-bottom: 0; }
.toc a { color: var(--text-2); text-decoration: none; }
/* Section numbers, the same ones shown above each section heading. */
.toc__num { flex: none; width: 24px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.toc--sidebar a.is-active .toc__num { color: var(--accent-ink); }
.toc a:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 4px; }
.prose h2 { scroll-margin-top: 24px; }
/* Each numbered section opens with a divider and room above, so sections read as separate parts. */
.prose .section-title {
  --section-pad: 48px;
  position: relative;
  margin-top: 48px; padding-top: var(--section-pad); border-top: 1px solid var(--border);
}
/* Section number, same size as the heading. Narrow screens: in front of the title.
   ≥880px: hangs in the left margin outside the reading column, like the week numbers on the
   timeline (720 column + 2 × (24 gap + 32 number + 24 edge − 24 padding) ≈ 832, rounded up). */
.section-title__num {
  margin-right: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--accent-ink);
}
@media (min-width: 880px) {
  /* nowrap: positioned outside the column it gets no width of its own and would break "02" apart. */
  .section-title__num { position: absolute; top: var(--section-pad); right: calc(100% + 24px); margin: 0; white-space: nowrap; }
}
.prose h2:focus { outline: none; }

/* Long-form reading: body 16/28, headings from the type scale, no extra fonts. */
.prose { margin-top: 48px; line-height: 28px; overflow-wrap: anywhere; }
.prose[hidden] { display: none; }
.prose > * { margin: 16px 0 0; }
.prose > :first-child { margin-top: 0; }
.prose h2 { margin-top: 48px; font-size: 24px; line-height: 32px; font-weight: 600; letter-spacing: -0.01em; }
.prose h3 { margin-top: 32px; font-size: 20px; line-height: 32px; font-weight: 600; }
.prose h4 { margin-top: 24px; font-size: 16px; line-height: 24px; font-weight: 600; }
.prose h2 + *, .prose h3 + *, .prose h4 + * { margin-top: 8px; }
.prose strong { font-weight: 600; }
.prose ul, .prose ol { padding-left: 24px; }
.prose li + li, .prose li > ul, .prose li > ol { margin-top: 8px; }
.prose code {
  font-family: var(--font-mono);
  font-size: inherit;
  padding: 0 4px;
  border-radius: 4px;
  background: var(--sesi-a-bg);
}
.prose pre {
  font-family: var(--font-mono);
  font-size: 16px;
  line-height: 24px;
  padding: 16px;
  border-radius: var(--radius-inner);
  background: var(--sesi-a-bg);
  overflow-x: auto;
  white-space: pre;
  overflow-wrap: normal;
}
.prose pre code { padding: 0; background: none; }
.prose blockquote {
  padding: 16px 24px;
  border-radius: var(--radius-inner);
  background: var(--sesi-a-bg);
  color: var(--text-2);
}
.prose blockquote > * { margin: 0; }
.prose blockquote > * + * { margin-top: 12px; }
.prose img {
  display: block; max-width: 100%; height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-inner);
}
.prose hr { height: 1px; margin: 48px 0; border: 0; background: var(--border); }
.prose table { display: block; width: 100%; overflow-x: auto; border-collapse: collapse; }
/* Cells keep whole words; a table wider than the screen scrolls on its own instead. */
.prose th, .prose td { padding: 8px 12px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; overflow-wrap: normal; }
.prose th { white-space: nowrap; }
.prose th { font-weight: 600; }

/* Sesi A's way on to Sesi B on the right, the way back on the left as the same quiet link as the
   top of the page. */
.reading__foot {
  margin-top: 64px; padding-top: 32px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: row-reverse; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px;
}
.reading__foot .button { gap: 8px; }
/* Always on the left, also in Sesi B where it is the only item (row-reverse would put it right). */
.reading__foot .back { margin-right: auto; }
/* Sesi B's own colors, as on its tab: the button reads as "go to that tab", not as a submit. */
.button--b { background: var(--sesi-b-bg); border-color: var(--sesi-b-ink); color: var(--sesi-b-ink); }
.button--b:hover { color: var(--sesi-b-ink); border-color: var(--sesi-b-ink); background: color-mix(in srgb, var(--sesi-b-bg), var(--sesi-b-ink) 6%); }

/* ---------- Floating button + dialog ---------- */
.fab {
  position: fixed; z-index: 20; right: 24px; bottom: 24px;
  height: 48px; padding: 0 20px 0 16px;
  display: inline-flex; align-items: center; gap: 8px;
  border: 0; border-radius: 999px;
  background: var(--ink); color: #FFFFFF;
  font: inherit; font-weight: 600; cursor: pointer;
  box-shadow: 0 8px 24px -8px rgba(21, 23, 28, 0.45);
}

.dialog {
  width: min(600px, calc(100% - 32px));
  max-height: calc(100vh - 64px);
  --dialog-pad: 32px;
  padding: var(--dialog-pad);
  border: 0;
  border-radius: 24px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 24px 64px -24px rgba(21, 23, 28, 0.5);
}
.dialog::backdrop { background: rgba(21, 23, 28, 0.4); }
/* Direction and gap live on the closed dialog too: while the closing fade plays, display stays
   flex but [open] no longer matches, and the content would jump into a row. */
.dialog { flex-direction: column; gap: var(--dialog-pad); }
.dialog[open] { display: flex; }

.dialog__head { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
/* With an eyebrow above the title, the close button lines up with the top of the heading. */
.dialog__head:has(.dialog__heading) { align-items: flex-start; }
.dialog__title { margin: 0; font-size: 24px; line-height: 32px; font-weight: 600; }

.icon-button {
  width: 44px; height: 44px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-inner);
  background: var(--sesi-a-bg); color: var(--ink); cursor: pointer;
}

/* Like the project brief, only the list scrolls: the title and close button stay in view on a
   short screen. The 10px padding is room for the focus outline and for the links' 44px touch
   area (which hangs 10px past the text), so a short list never shows a scrollbar. */
.info-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 32px 24px;
  min-height: 0; overflow-y: auto; margin: -10px; padding: 10px; scroll-padding: 10px;
}
.info-item { display: flex; flex-direction: column; gap: 4px; }
.info-item__title { font-weight: 600; }
/* Same text link as the brief's references: accent, thin underline, external icon. */
.info-item__link {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 8px; min-height: 44px; margin: -10px 0;
  color: var(--accent-ink);
  text-decoration: underline; text-decoration-color: var(--line); text-decoration-thickness: 1px; text-underline-offset: 4px;
  transition: color 160ms ease, text-decoration-color 160ms ease;
}
.info-item__link:hover, .info-item__link:active { color: var(--accent); text-decoration-color: currentColor; }
.info-item__empty { color: var(--text-2); }

/* ---------- Table of contents sidebar (wide screens) ---------- */
/* ≥1392px: reading column 768 + 2 × (gap 80 + sidebar 256 − 24 side padding).
   materi.js renders the list twice (.toc--inline and .toc--sidebar); this breakpoint picks one. */
/* Only the sticky sidebar marks the section being read; the inline list scrolls away with the
   text, so a highlight there would point at a section the reader has not reached. */
.toc--sidebar a.is-active { color: var(--accent-ink); font-weight: 600; }
.side { display: none; }
@media (min-width: 1392px) {
  /* The side column sits beside the tabs and the reading column (same top), and its content sticks
     while the page scrolls. */
  .reading__body { position: relative; }
  .side { display: block; position: absolute; top: 0; bottom: 0; left: calc(100% + 80px); width: 256px; }
  .side__sticky {
    position: sticky; top: 32px; max-height: calc(100vh - 64px);
    display: flex; flex-direction: column;
  }
  .toc-aside { min-height: 0; display: flex; flex-direction: column; }
  .prose > .toc--inline { display: none; }
  .toc--sidebar {
    min-height: 0; overflow-y: auto;
    display: flex; flex-direction: column; gap: 12px;
    /* Room for the focus outline inside the scroll container (see .brief-list). */
    margin: -6px; padding: 6px; scroll-padding: 6px;
  }
  .toc--sidebar .toc__list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px; }
  /* Always one line: long labels end in "…" and show in full on hover (title attribute).
     One line also means the bold active state can never rewrap the text. */
  .toc--sidebar a { display: grid; grid-template-columns: 24px minmax(0, 1fr); column-gap: 12px; padding: 4px 0; line-height: 24px; }
  .toc--sidebar .toc__num { width: auto; }
  .toc--sidebar .toc__text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
}

/* ---------- Loading: skeletons + fade-in ---------- */
/* Skeletons take the same size as the content they stand for, so nothing jumps when data arrives.
   Size them with --w / --h; the pulse and the fade-in stop under prefers-reduced-motion. */
.skeleton {
  display: block;
  width: var(--w, 100%); max-width: 100%; height: var(--h, 16px);
  border-radius: 4px;
  background: var(--border);
}
.skeleton--pill { border-radius: 999px; }
/* The title skeleton fills one line of .title (48px, mobile 40px) with a 32px block. */
.skeleton--title { height: 40px; margin: 12px 0; }

.week--skeleton .card__heading { padding: var(--card-pad); gap: 4px; }
/* One title line and one 24px Sesi B line, like a closed week card. */
.week--skeleton .skeleton--card-title { height: 24px; margin: calc((var(--card-title-lh) - 24px) / 2) 0; }
.week--skeleton .skeleton--card-sub { height: 16px; margin: 4px 0; }
.week--skeleton .week-no { gap: 4px; }
/* Mobile only, like .card__week. */
.week--skeleton .skeleton--week { display: none; }

/* Skeletons stay invisible for the first 400ms: on a normal connection the content arrives
   first, so the reader sees one fade-in instead of a skeleton flash. */
@media (prefers-reduced-motion: no-preference) {
  .skeleton, .progress__seg.is-loading {
    animation: skeleton-in 200ms 400ms both, skeleton-pulse 1.6s 600ms ease-in-out infinite;
  }
  .fade-in { animation: fade-in 240ms var(--ease-out) both; }
}
@keyframes skeleton-in { from { opacity: 0; } }
@keyframes skeleton-pulse { 50% { opacity: 0.5; } }
@keyframes fade-in { from { opacity: 0; } }

/* ---------- Page transitions ---------- */
/* Same-origin page changes cross-fade, and the open week's card title morphs into the materi
   page title (and back). Browsers without cross-document view transitions just navigate. */
/* Both pages render from stored data before their first paint (see lib.js), so the transition
   always runs between two finished pages. Both titles hug their text (fit-content), and the
   snapshots scale by height, so the text grows from 24px to the page title size without
   stretching. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-group(*) { animation-duration: var(--open-duration); animation-timing-function: var(--ease-out); }
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 240ms; }
  ::view-transition-old(week-title), ::view-transition-new(week-title) { height: 100%; width: auto; }
}
.card.is-open .card__title { view-transition-name: week-title; }
#materi-title { view-transition-name: week-title; width: fit-content; }

/* ---------- Kamus istilah ---------- */
.kamus__search { position: relative; margin-top: 48px; }
.kamus__search-icon { position: absolute; left: 16px; top: 16px; color: var(--text-3); pointer-events: none; }
.kamus__input {
  width: 100%; height: 48px; padding: 0 16px 0 44px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-inner);
  background: var(--surface); color: var(--ink); font: inherit;
  transition: border-color 160ms ease;
}
.kamus__input::placeholder { color: var(--text-3); }
.kamus__input:hover { border-color: var(--line); }
/* The letter index sticks to the top while reading. Its height (--alpha-h, set by kamus.js) keeps
   each letter heading clear of it when the reader jumps there. */
.alpha {
  position: sticky; top: 0; z-index: 2; margin-top: 16px; padding: 8px 0;
  display: flex; flex-wrap: wrap;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.alpha__letter {
  width: 36px; height: 44px; display: grid; place-items: center; border-radius: var(--radius-nested);
  color: var(--text-2); font-weight: 600; text-decoration: none;
  transition: color 160ms ease, background-color 160ms ease;
}
.alpha__letter:hover { color: var(--ink); background: var(--surface); }
.alpha__letter.is-active { color: var(--accent-ink); background: var(--accent-soft); }
/* No term starts with this letter under the current search. */
.alpha__letter.is-off { color: var(--border-strong); background: none; cursor: default; }
.kamus { margin-top: 32px; display: flex; flex-direction: column; gap: 48px; }
.kamus[hidden] { display: none; }
.kamus__group { scroll-margin-top: calc(var(--alpha-h, 60px) + 24px); display: flex; flex-direction: column; gap: 8px; }
.kamus__letter { margin: 0; font-size: 24px; line-height: 32px; font-weight: 600; color: var(--accent); }
.kamus__letter:focus { outline: none; }
.kamus__terms { margin: 0; }
.term { padding: 16px 0; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 4px; }
.term__name { font-weight: 600; color: var(--ink); }
.term__text, .term__week { margin: 0; }
.term__text { color: var(--text-2); }
/* Where the term is taught: a quiet text link to that week's materi. */
.term__week a {
  display: inline-flex; align-items: center; gap: 4px; min-height: 44px; margin: -10px 0;
  color: var(--accent-ink); font-size: 12px; line-height: 16px; font-weight: 600; text-decoration: none;
  transition: color 160ms ease;
}
.term__week a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
/* The week has not started yet: the same words, not a link. */
.term__week span { display: inline-block; padding: 4px 0; color: var(--text-3); font-size: 12px; line-height: 16px; font-weight: 600; }

/* ---------- Press state ---------- */
/* The browser's own tap highlight is a plain rectangle that ignores border-radius, so it is turned
   off and every control gets its own press state in its own shape. Surfaces (buttons, tabs, cards,
   rows) darken a step; text links get a soft rounded backdrop drawn with a box-shadow, so nothing
   around them moves. */
html { -webkit-tap-highlight-color: transparent; }
.button:active, .icon-button:active { background: var(--sesi-a-bg); }
.button--primary:active { background: #2A2E37; }
.button--b:active { background: color-mix(in srgb, var(--sesi-b-bg), var(--sesi-b-ink) 10%); }
.tab:active { background: var(--sesi-a-bg); }
.tab[aria-selected="true"]:active { background: transparent; }
.card__toggle:active { background: var(--sesi-a-bg); }
.card.is-open .card__toggle:active, .card__toggle--locked:active { background: transparent; }
.brief__item:active { background: var(--sesi-a-bg); border-color: var(--border-strong); }
.brief__item--static:active { background: var(--surface); border-color: var(--border); }
.fab:active { background: #2A2E37; }
.back:active, .brief__back:active, .toc a:active, .gate--link:active, .term__week a:active, .alpha__letter:not(.is-off):active {
  border-radius: 8px; background: var(--sesi-a-bg); box-shadow: 0 0 0 4px var(--sesi-a-bg);
}

/* ---------- Soft state changes ---------- */
/* Colors, backgrounds and shadows ease between states instead of switching at once. */
/* One easing for everything that moves. --open-duration is also OPEN_MS in app.js. */
:root { --ease-out: cubic-bezier(0.2, 0, 0, 1); --open-duration: 320ms; }
.button, .tab, .toc a, .toc__num, .back, .fab, .icon-button, .card, .card__toggle {
  transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease, box-shadow 200ms ease;
}
@media (prefers-reduced-motion: no-preference) {
  /* A week opens as one movement: the row grows, the card fills in, the title grows from 20 to
     24px, and the details fade in a little after the row starts, so they never look cut off.
     Closing: visibility waits until the row has shrunk; opening: it switches at once. */
  .card__detail { transition: grid-template-rows var(--open-duration) var(--ease-out), visibility 0s var(--open-duration); }
  .card.is-open .card__detail { transition: grid-template-rows var(--open-duration) var(--ease-out), visibility 0s 0s; }
  .card { transition: background-color 240ms ease, border-color 240ms ease, box-shadow 320ms ease; }
  .card__title { transition: font-size var(--open-duration) var(--ease-out); }
  /* Folding away: fade, then hide (visibility) once it is closed. Coming back: visible at once. */
  .card__sub {
    transition: max-height var(--open-duration) var(--ease-out), margin-top var(--open-duration) var(--ease-out),
      opacity 240ms ease 80ms, visibility 0s;
  }
  .card.is-open .card__sub {
    transition: max-height var(--open-duration) var(--ease-out), margin-top var(--open-duration) var(--ease-out),
      opacity 120ms ease, visibility 0s linear var(--open-duration);
  }
  .card__content { transition: opacity 160ms ease; }
  .card.is-open .card__content { transition: opacity 240ms ease 80ms; }
  /* Dialogs (Info kelas, project brief) fade in and rise 8px; closing fades out. display and
     overlay transition discretely so the closing fade can play. Browsers without @starting-style
     simply open without it. */
  .dialog {
    opacity: 0; transform: translateY(8px) scale(0.98);
    transition: opacity 200ms ease, transform var(--open-duration) var(--ease-out), overlay 200ms allow-discrete, display 200ms allow-discrete;
  }
  .dialog[open] { opacity: 1; transform: none; }
  @starting-style { .dialog[open] { opacity: 0; transform: translateY(8px) scale(0.98); } }
  .dialog::backdrop { opacity: 0; transition: opacity 200ms ease, overlay 200ms allow-discrete, display 200ms allow-discrete; }
  .dialog[open]::backdrop { opacity: 1; }
  @starting-style { .dialog[open]::backdrop { opacity: 0; } }
  .brief__item { transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease; }
  .brief__back { transition: color 160ms ease, border-color 160ms ease; }
  /* The tab highlight slides; is-ready (set by materi.js) keeps it still on first placement. */
  .tabs.is-ready .tabs__indicator {
    transition: transform 280ms var(--ease-out), width 280ms var(--ease-out), background-color 280ms ease, color 280ms ease;
  }
}
/* The timeline scrolls the clicked week itself while cards resize (app.js, toggleWeek), and
   measures the final layout with transitions off (is-measuring). */
.timeline { overflow-anchor: none; }
.timeline.is-measuring * { transition: none !important; }

/* ---------- Wide screens ---------- */
/* The week numbers and the line move into the left margin, so cards line up with the title and
   the progress bar and use the full content width. The line sits 40px from the cards (16 grid gap
   + 24 extra), so 148px = number column 64 + gap 16 + line 28 + 40. Only from 1064px:
   page 768 + 2 × (148 − 24 side padding + 24 to the screen edge), so the week numbers keep at
   least 24px from the edge. Update this breakpoint if .page max-width changes. */
@media (min-width: 1064px) {
  .timeline { margin-left: -148px; }
  .timeline .row > :last-child { margin-left: 24px; }
}

/* ---------- Tablet ---------- */
/* Between phone and the wide layout: one step down in padding and title size. */
@media (max-width: 1063px) {
  :root { --card-pad: 24px; }
  .page { padding-top: 80px; }
  .title { font-size: 40px; line-height: 48px; }
  .skeleton--title { height: 32px; margin: 8px 0; }
}

/* ---------- Mobile ---------- */
/* Reading comfort first, without wasted space: one 16px unit, the page's side padding, is also
   the card padding and every gap inside the card; gaps between page sections are two units (32).
   The timeline line takes as little width as it can, and the week label and "Pekan ini" share
   one row above the title, so the title and the Sesi B line get the card's full width. */
@media (max-width: 600px) {
  /* --radius-card follows automatically (12 + 16 = 28). */
  :root { --card-pad: 16px; --card-title-lh: 32px; --card-head-above: 24px; --spine-w: 16px; }
  .page { padding: 32px 16px 120px; }
  .hero { gap: 12px; }
  .title { font-size: 32px; line-height: 40px; }
  .lead { font-size: 16px; line-height: 24px; }
  .progress, .timeline { margin-top: 32px; }
  .progress { gap: 12px; }
  .progress__text { font-size: 16px; line-height: 24px; }
  /* The number column is dropped: the week number shows inside the card instead. */
  .row { grid-template-columns: var(--spine-w) minmax(0, 1fr); column-gap: 12px; }
  .row > :first-child { display: none; }
  .card__title { font-size: 20px; letter-spacing: -0.01em; }
  .card.is-open .card__title { font-size: 24px; }
  /* Header as a grid: "PEKAN 01" and the badge on the first 20px row (--card-head-above = 20 + 4
     gap), the title and the Sesi B line below at full width. */
  .card__toggle { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; row-gap: 4px; }
  .card__toggle .card__heading { display: contents; }
  .card__week { display: block; grid-column: 1; grid-row: 1; line-height: 20px; }
  .card__title, .card__sub, .card__line { grid-column: 1 / -1; }
  .badge { grid-column: 2; grid-row: 1; height: 20px; margin: 0; padding: 0 8px; }
  .week--skeleton .skeleton--week { display: block; }
  .week--skeleton .skeleton--week { margin: 4px 0 !important; }
  .phase { padding: 32px var(--card-pad) 8px; }
  .week__body { padding-bottom: 12px; }
  .session { padding: 16px; gap: 12px; }
  .fab { right: 16px; bottom: 16px; }
  .week { scroll-margin-top: 32px; }
  /* Materi page: every gap between its parts is the page's side padding (16): back link →
     heading → tabs → Target card → table of contents, and each section's divider has 16 above
     and below it. Inside the heading, 12 (like .hero). */
  .reading__head { margin-top: 16px; }
  .tabs, #sesi-tabs-skeleton { margin-top: 16px !important; }
  .sesi .prose, .notice { margin-top: 16px; }
  .prose > .toc { margin-top: 16px; }
  .reading__foot { margin-top: 32px; padding-top: 16px; flex-direction: column; align-items: stretch; gap: 8px; }
  .reading__foot .back { align-self: flex-start; }
  /* Target card: a 32px mark, so the goals keep more of the narrow width. */
  .target__icon { width: 32px; height: 32px; }
  .prose .target > .target__task { flex-direction: column; align-items: flex-start; padding: 16px; }
  .prose .target__head { gap: 12px; }
  .prose .target li { grid-template-columns: 32px minmax(0, 1fr); column-gap: 12px; }
  .skeleton--title { margin: 4px 0; }
  .prose .toc__list { columns: 1; }
  .tab { padding: 0 12px; }
  .prose { margin-top: 32px; }
  .prose h2 { margin-top: 40px; }
  .prose .section-title { --section-pad: 16px; margin-top: 16px; }
  /* A subheading still needs more room than the 16px between paragraphs, but less than a section. */
  .prose h3 { margin-top: 24px; }
  .dialog { --dialog-pad: 20px; }
  .reading__head { gap: 12px; }
  .goal-box { flex-direction: column; align-items: flex-start; padding: 16px; margin-top: 12px; }
  .kamus__search { margin-top: 32px; }
  /* One row that scrolls sideways, full 44px targets, edge to edge like the page padding. */
  .alpha { flex-wrap: nowrap; overflow-x: auto; margin: 16px -16px 0; padding: 8px 16px; scroll-padding: 0 16px; scrollbar-width: none; }
  .alpha::-webkit-scrollbar { display: none; }
  .alpha__letter { flex: none; width: 44px; }
  .kamus { gap: 32px; }
  .brief__must, .brief__item { padding: 16px; }
  .flow__handoff { padding: 12px 16px 0; }
  .flow__step { column-gap: 12px; }
}
