/* ==========================================================================
   TUTOORIA — 03. KOMPONEN
   Pustaka komponen global. Setiap komponen berdiri di atas token dan boleh
   dipakai di halaman mana pun (situs maupun app/). Tidak ada nilai mentah.

   Aturan penamaan
     .blok            komponen mandiri (.button, .card, .badge)
     .blok__bagian    bagian di dalamnya (.card__title)
     .blok--varian    varian tampilan/ukuran (.button--small)
   Satu tampilan = satu nama. Kalau sebuah komponen halaman butuh tampilan
   yang sudah ada (kartu, kotak ikon, label kecil), ia MEMAKAI kelas itu
   lalu menambah kelasnya sendiri hanya untuk tata letak, mis.
   <article class="card card--compact kpi">.

   Isi
     A. DASAR      ikon · tombol · tombol ikon · tautan panah · badge ·
                   chip · emblem · avatar · progres · spinner · pembatas
     B. PERMUKAAN  kartu · daftar keterangan · popover · menu · tabel ·
                   keadaan kosong
     C. FORMULIR   field · input/select/textarea · input berikon ·
                   input berawalan · kartu opsi · combobox · pemilih tanggal
     D. NAVIGASI   header & navbar · tab · footer
     E. BLOK ISI   fitur · statistik · kartu tutor · testimoni · slider ·
                   paket harga · akordeon · marquee
   ========================================================================== */


/* ##########################################################################
   A. DASAR
   ########################################################################## */

/* =======================================================
   IKON — Lucide v1.46.0 (lisensi ISC), di-inline agar tetap tampil
   tanpa JavaScript dan tanpa permintaan jaringan tambahan.
   Atribut default Lucide (fill/stroke/linecap/linejoin) dipusatkan di
   sini, sehingga markup tiap ikon cukup memuat data path-nya saja.
   Ukuran bawaan 1em (ikut huruf); pakai --s/--m/--l bila ikon harus
   berukuran tetap.
   ======================================================= */
.icon {
  inline-size: var(--icon-size, 1em);
  block-size: var(--icon-size, 1em);
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon--s { --icon-size: 1rem; }
.icon--m { --icon-size: 1.25rem; }
.icon--l { --icon-size: 1.5rem; }
.icon--bold { stroke-width: 2.5; }
/* Bintang penilaian dibaca sebagai lencana padat, bukan garis. */
.icon--fill { fill: currentColor; stroke: none; }

/* =======================================================
   TOMBOL
     primary    hitam, teks putih — hover garis ungu
     primary-2  ungu, teks hitam — aksi paling menonjol
     secondary  putih, garis 1px hitam — hover membalik jadi hitam
     invert     putih di atas bidang hitam — hover ungu
     outline    putih, garis 1px abu — aksi ringan di dalam isi
     ghost      tanpa bidang & garis — aksi paling ringan
   Semua tombol bergaris 1px (--border-hair); yang membedakan bobotnya
   adalah bidang dan warna garis, bukan ketebalan.
   Ukuran: --small (32px) · bawaan (40px) · --large (48px); --block
   untuk lebar penuh. Tinggi, padding, ukuran huruf, dan radius SELURUHNYA
   dari token --control-* (tokens.css) — sama persis dengan .input, .select
   dan .chip, jadi tombol dan kolom isian yang berdampingan sebangun, bukan
   hanya sama tinggi. Varian ukuran cukup menimpa tokennya.
   ======================================================= */
.button {
  --_bg: var(--clr-black);
  --_fg: var(--clr-white);
  --_border: var(--clr-black);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--control-gap);
  min-block-size: var(--control-h);
  padding-block: 0;
  padding-inline: var(--control-pad);
  border: var(--control-border) solid var(--_border);
  border-radius: var(--control-radius);
  background: var(--_bg);
  color: var(--_fg);
  font-family: var(--font-sans);
  font-size: var(--control-font);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-snug);
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.button:hover { color: var(--_fg); text-decoration: none; }
/* Ditekan. Di peranti sentuh tidak ada :hover, jadi tanpa aturan ini tombol
   tidak memberi tanda apa pun saat disentuh — sorotan biru bawaan peramban
   sudah dimatikan di base.css. Mengecil, bukan berubah warna: satu resep
   yang bekerja untuk keenam varian tombol sekaligus, tanpa perlu warna
   "ditekan" sendiri-sendiri. transform sengaja TIDAK dimasukkan ke daftar
   transition di atas: tanggapan sentuh harus seketika, dan animasi 120ms
   di sini justru terbaca sebagai jeda. */
.button:active { transform: scale(0.97); }

/* Primary — hover memudarkan isian DAN garisnya bersama-sama, ke hitam yang
   satu jenjang lebih terang. Garis tidak pernah berganti warna sendiri:
   garis ungu di atas isian hitam membuat tombol terlihat berbingkai, dan
   isian ungu membuatnya tertukar dengan primary-2. */
.button--primary { --_bg: var(--clr-black); --_fg: var(--clr-white); --_border: var(--clr-black); }
.button--primary:hover { --_bg: var(--bg-invert-hover); --_border: var(--bg-invert-hover); }

/* Primary-2 — latar ungu utama, teks hitam (8.2:1). */
.button--primary-2 { --_bg: var(--accent); --_fg: var(--accent-fg); --_border: var(--accent); }
.button--primary-2:hover { --_bg: var(--accent-hover); --_border: var(--accent-hover); }

.button--secondary { --_bg: var(--clr-white); --_fg: var(--clr-black); --_border: var(--clr-black); }
.button--secondary:hover { --_bg: var(--clr-black); --_fg: var(--clr-white); --_border: var(--clr-black); }

/* Untuk permukaan hitam, di mana primary (latar hitam) tidak terlihat. */
.button--invert { --_bg: var(--clr-white); --_fg: var(--clr-black); --_border: var(--clr-white); }
.button--invert:hover { --_bg: var(--accent); --_fg: var(--accent-fg); --_border: var(--accent); }

/* Outline & ghost memakai token semantik (--fg, --line), jadi ikut benar
   di atas permukaan hitam tanpa varian tambahan. Garis abu: aksi ringan
   tidak boleh berebut perhatian dengan secondary bergaris hitam. */
.button--outline {
  --_bg: transparent;
  --_fg: var(--fg);
  --_border: var(--line);
}
.button--outline:hover { --_border: var(--line-strong); }
.button--ghost { --_bg: transparent; --_fg: var(--fg); --_border: transparent; }
.button--ghost:hover { --_bg: color-mix(in oklab, var(--fg) 7%, transparent); }

/* Varian ukuran menimpa TOKEN, bukan properti: satu baris menggeser
   tinggi, padding, dan ukuran huruf sekaligus — dan kontrol lain yang
   diberi varian ukuran sama bergeser persis sejauh itu juga. */
.button--small {
  --control-h: var(--control-h-small);
  --control-pad: var(--control-pad-small);
  --control-font: var(--control-font-small);
}
.button--large {
  --control-h: var(--control-h-large);
  --control-pad: var(--control-pad-large);
  --control-font: var(--control-font-large);
}
/* Lebar penuh — tinggi dan padding tetap sama persis dengan tombol lain. */
.button--block { inline-size: 100%; }

/* Tampak nonaktif, tapi tetap bisa diklik: klik menunjukkan isian mana yang
   kurang, bukan diam tanpa penjelasan seperti <button disabled>. */
.button[aria-disabled="true"],
.button[aria-disabled="true"]:hover,
.button:disabled {
  --_bg: var(--clr-gray-100);
  --_fg: var(--fg-muted);
  --_border: var(--clr-gray-100);
}
.button:disabled { cursor: not-allowed; }

.button__arrow { transition: transform var(--dur-base) var(--ease-spring); flex: none; }
.button:hover .button__arrow { transform: translateX(0.28em); }

/* =======================================================
   TOMBOL IKON — bujur sangkar setinggi kontrol lain.
     bawaan     garis 1px hitam (padanan .button--secondary)
     --primary  bidang hitam, hover ungu (kirim pesan)
     --outline  garis 1px abu (alat bantu di dalam isi)
     --ghost    tanpa garis (di dalam isian, di sidebar)
   Ukuran: --small (32px) · bawaan (40px) · --large (48px) — bujur sangkar
   setinggi --control-h, radius --control-radius yang sama dengan .button
   dan .input di jenjang mana pun.
   ======================================================= */
.icon-button {
  --_size: var(--control-h);
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--_size);
  block-size: var(--_size);
  padding: 0;
  border: var(--control-border) solid var(--clr-black);
  border-radius: var(--control-radius);
  background: var(--clr-white);
  color: var(--clr-black);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.icon-button:hover { background: var(--accent); }
.icon-button:active { transform: scale(0.94); }
.icon-button .icon { --icon-size: 1.15rem; }

.icon-button--primary { background: var(--clr-black); border-color: var(--clr-black); color: var(--clr-white); }
.icon-button--primary:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.icon-button--outline {
  border-color: var(--line);
  background: transparent;
  color: var(--fg);
}
.icon-button--outline:hover { background: transparent; border-color: var(--line-strong); }
.icon-button--ghost { border-color: transparent; background: transparent; color: var(--fg); }
.icon-button--ghost:hover { background: color-mix(in oklab, var(--fg) 9%, transparent); }
.icon-button--small { --control-h: var(--control-h-small); }
.icon-button--large { --control-h: var(--control-h-large); }

.icon-button[aria-disabled="true"],
.icon-button:disabled {
  background: var(--clr-gray-100);
  border-color: var(--clr-gray-100);
  color: var(--fg-subtle);
  cursor: not-allowed;
  transform: none;
}
:is(.icon-button--ghost, .icon-button--outline):is([aria-disabled="true"], :disabled) {
  background: transparent;
  border-color: transparent;
  color: var(--clr-gray-300);
}

/* =======================================================
   TAUTAN PANAH — tautan teks berdiri sendiri, garis bawah 2px.
   ======================================================= */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-weight: var(--weight-semibold);
  font-size: var(--step--1);
  text-decoration: none;
  border-block-end: var(--border-bold) solid var(--line-strong);
  padding-block-end: 2px;
}
/* .link-arrow dipakai juga pada <button> (pembuka rak). Tombol membawa
   bingkai dan latar bawaan peramban, dan aturan di atas hanya menyetel
   garis BAWAH — tiga sisi lainnya akan tetap tergambar sebagai kotak. */
button.link-arrow {
  background: none;
  border-inline: 0;
  border-block-start: 0;
  padding-inline: 0;
  font: inherit;
  cursor: pointer;
}
.link-arrow svg { transition: transform var(--dur-base) var(--ease-spring); flex: none; }
.link-arrow:hover svg { transform: translateX(0.28em); }

/* Di layar sentuh tautan ini hanya ±26px tinggi. Area ketuknya diperluas
   lewat pseudo-elemen tak terlihat sampai --control-h, tanpa menggeser
   garis bawahnya menjauh dari teks. */
@media (pointer: coarse) {
  .link-arrow { position: relative; }
  .link-arrow::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-start: 50%;
    block-size: var(--control-h);
    translate: 0 -50%;
  }
}

/* =======================================================
   BADGE — label status kecil. SATU komponen untuk semua label:
   status kartu, jenis materi, sorotan, angka perubahan, tahun linimasa.
   Bentuknya pil (--radius-pill); yang membedakan varian hanya warna
   bidang dan garisnya, bukan bentuk atau ukuran.
     --white    putih + garis abu, huruf redup (bawaan, boleh ditulis
                eksplisit supaya sejajar dengan varian lain)
     --primary  bidang ungu, huruf hitam (sorotan utama)
     --soft     bidang ungu muda
     --gray     bidang abu
     --dark     bidang hitam, huruf putih
   Pengubah bentuk (boleh digabung dengan varian warna mana pun):
     --plain    angka rata lebar (tabular) — untuk badge berisi angka
                ("+36 poin"). Menggantikan .delta dan .timeline__year.
     --small    lebih kecil — label jenis di pojok kartu katalog
                (.card__badge memakai ukuran ini otomatis)
   ======================================================= */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--pill-pad-block) var(--pill-pad-inline);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--bg-raised);
  font-size: var(--step--2);
  font-weight: var(--weight-semibold);
  line-height: 1.2;
  color: var(--fg-muted);
  white-space: nowrap;
}
.badge .icon { --icon-size: 1.1em; stroke-width: 2.5; }
/* Ungu sebagai bidang, huruf hitam — satu-satunya cara ungu boleh membawa teks. */
.badge--white   { background: var(--bg-raised);      border-color: var(--line);           color: var(--fg-muted); }
.badge--primary { background: var(--accent);         border-color: var(--accent);         color: var(--accent-fg); }
.badge--soft    { background: var(--accent-mid);     border-color: var(--accent-mid);     color: var(--clr-black); }
.badge--gray    { background: var(--clr-gray-100);   border-color: var(--clr-gray-100);   color: var(--clr-black); }
.badge--dark    { background: var(--clr-black);      border-color: var(--clr-black);      color: var(--clr-white); }
.badge--plain   { font-variant-numeric: tabular-nums; }
/* Kecil — label jenis di pojok kartu katalog (.card__badge memakai ukuran
   ini otomatis). Badge bawaan terlalu nyaring berderet di 12 kartu. */
.badge--small,
.card__badge {
  padding: 0.3em 0.7em;
  font-size: var(--step--2);
}

/* =======================================================
   KALENDER — tabel bulan yang tertanam di halaman (bukan popover
   .datepicker, yang merupakan kontrol isian). Dipakai jadwal kelas live.
   Tanggal bertitik = ada acara; tanggal terpilih memakai aria-pressed,
   hari ini memakai aria-current="date".
   ======================================================= */
.calendar { display: grid; gap: var(--space-xs); }
.calendar__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2xs);
}
.calendar__title { font-size: var(--step-0); font-weight: var(--weight-semibold); }
.calendar__grid { inline-size: 100%; border-collapse: collapse; table-layout: fixed; }
.calendar__grid th {
  padding-block-end: var(--space-2xs);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  color: var(--fg-muted);
  text-align: center;
}
.calendar__grid td { padding: 2px; text-align: center; }
.calendar__cell {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 100%;
  min-block-size: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-m);
  background: none;
  color: var(--fg);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
/* Angka tanggal dibungkus supaya penanda "terpilih" dan "hari ini" bisa
   melingkari angkanya saja — di kalender bersel tinggi, bidang sebesar sel
   terbaca terlalu berat. */
.calendar__num {
  display: grid;
  place-items: center;
  min-inline-size: 2rem;
  block-size: 2rem;
  border-radius: var(--radius-pill);
}
.calendar__cell:hover { background: var(--bg-canvas-strong); }
.calendar__cell[aria-current="date"] { font-weight: var(--weight-semibold); box-shadow: inset 0 0 0 var(--border-hair) var(--line-strong); }
.calendar__cell[aria-pressed="true"] {
  background: var(--clr-black);
  color: var(--clr-white);
  font-weight: var(--weight-semibold);
}
/* Titik penanda acara — bidang ungu di atas putih, putih di atas sel
   terpilih yang hitam. */
.calendar__dot {
  position: absolute;
  inset-block-end: 0.3rem;
  inline-size: 0.3rem;
  block-size: 0.3rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
}
.calendar__cell[aria-pressed="true"] .calendar__dot { background: var(--clr-white); }
.calendar__legend {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--step--2);
  color: var(--fg-muted);
}
.calendar__legend .calendar__dot { position: static; }

/* =======================================================
   KBD — tombol papan ketik (⌘ K). Kotak kecil bergaris tipis, huruf
   redup; dipakai berderet untuk pintasan.
   ======================================================= */
.kbd {
  display: inline-grid;
  place-items: center;
  min-inline-size: 1.5rem;
  block-size: 1.5rem;
  padding-inline: 0.35em;
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--bg-raised);
  font-family: var(--font-sans);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  line-height: 1;
  color: var(--fg-muted);
}

/* =======================================================
   CHIP — penyaring & pilihan.
   Sengaja berbeda bentuk dari .tab — tab bergaris bawah untuk sumbu utama,
   chip berbidang untuk penyempitan. Garis 1px abu supaya tidak berebut
   perhatian dengan CTA bergaris 2px. Geometrinya dari token --control-*
   yang sama dengan .button dan .input, jadi chip penyaring yang berderet
   di atas sebuah kolom cari sebangun dengannya.
   ======================================================= */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--control-gap);
  min-block-size: var(--control-h);
  padding-inline: var(--control-pad);
  border: var(--control-border) solid var(--line);
  border-radius: var(--control-radius);
  background: var(--bg-raised);
  font-size: var(--control-font);
  font-weight: var(--weight-medium);
  color: var(--fg-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.chip:hover { border-color: var(--clr-black); color: var(--fg); }
/* Terpilih: chip penyaring (aria-pressed) dan chip formulir (radio/checkbox
   di dalam <label>) memakai satu bahasa visual yang sama. */
.chip[aria-pressed="true"],
.chip--check:has(:checked) {
  background: var(--clr-black);
  border-color: var(--clr-black);
  color: var(--clr-white);
}
.chip--check { position: relative; }   /* jangkar bagi input .sr-control */
.chip--check:has(:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.chip--small {
  --control-h: var(--control-h-small);
  --control-pad: var(--control-pad-small);
  --control-font: var(--control-font-small);
}

/* =======================================================
   EMBLEM — kotak persegi berisi ikon, nomor, atau huruf.
   Satu komponen untuk semua "kotak ungu": nomor fitur, langkah, tanda
   kutip, ikon Copilot, tanda selesai, huruf pilihan kuis. Ukuran kotak
   tetap, bukan mengikuti isinya, supaya ikon dan angka tampil sebagai
   benda yang sama; ikon & huruf di dalamnya ikut membesar sebanding.
     ukuran  --sm 28px · bawaan 32px · --md 40px · --lg 52px · --xl 72px
     warna   bawaan ungu · --gray · --invert
     huruf   --display memakai Farro (nomor langkah, tanda kutip)
   ======================================================= */
.emblem {
  --_size: 2rem;
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--_size);
  block-size: var(--_size);
  background: var(--accent);
  color: var(--accent-fg);
  border-radius: var(--radius-s);
  font-family: var(--font-sans);
  font-size: calc(var(--_size) * 0.4);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  /* Nol: jarak setelah digit terakhir menggeser angka dari titik tengah
     kotak. */
  letter-spacing: 0;
  line-height: 1;
}
.emblem .icon { --icon-size: calc(var(--_size) * 0.55); }
.emblem--sm { --_size: 1.75rem; }
.emblem--md { --_size: 2.5rem; }
.emblem--lg { --_size: 3.25rem; border-radius: var(--radius-m); }
.emblem--xl { --_size: 4.5rem;  border-radius: var(--radius-xl); }
.emblem--xl .icon { stroke-width: 2.5; }
.emblem--gray   { background: var(--clr-gray-100); color: var(--clr-black); }
.emblem--invert { background: var(--clr-black);    color: var(--clr-white); }
.emblem--display {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: calc(var(--_size) * 0.55);
}

/* =======================================================
   AVATAR — inisial atau foto dalam lingkaran.
     ukuran  --sm 36px · bawaan 48px · --lg 56px · --xl 72px
     warna   --black (bawaan) · --purple · --soft · --gray
   Foto: taruh <img alt=""> di dalamnya — dipotong bulat, wajah di atas.
   ======================================================= */
.avatar {
  --_size: var(--avatar-size, 3rem);
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--_size);
  block-size: var(--_size);
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--avatar-bg, var(--clr-black));
  color: var(--avatar-fg, var(--clr-white));
  font-family: var(--font-display);
  font-size: calc(var(--_size) * 0.34);
  font-weight: var(--weight-display);
  letter-spacing: 0;
  line-height: 1;
  user-select: none;
}
.avatar img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: center top;
}
.avatar:has(img) { background: var(--clr-gray-100); }
.avatar--sm { --avatar-size: 2.25rem; }
.avatar--lg { --avatar-size: 3.5rem; }
.avatar--xl { --avatar-size: 4.5rem; }
.avatar--black  { --avatar-bg: var(--clr-black);      --avatar-fg: var(--clr-white); }
.avatar--purple { --avatar-bg: var(--clr-purple);     --avatar-fg: var(--clr-black); }
.avatar--soft   { --avatar-bg: var(--clr-purple-100); --avatar-fg: var(--clr-black); }
.avatar--gray   { --avatar-bg: var(--clr-gray-200);   --avatar-fg: var(--clr-black); }

/* =======================================================
   PROGRES — satu komponen untuk bar progres, bar langkah pendaftaran,
   dan meter skor. Isian hitam di trek abu paling muda; di atas permukaan
   hitam isiannya ungu (8.2:1). Nilai lewat --v di .progress__fill.
     --accent  isian ungu — pengecualian sadar dari "ungu bukan garis"
               (2.21:1 di atas putih), hanya untuk bar langkah daftar.
               Kompensasinya: bar 6px dan teks "Langkah N dari 4" selalu
               ada sebagai pembawa informasi utama.
     --lg      10px, untuk meter skor
     --target  garis tegak di posisi --target (mis. nilai target)
   ======================================================= */
.progress {
  position: relative;
  display: block;
  block-size: var(--progress-h);
  border-radius: var(--radius-pill);
  background: var(--progress-track);
  overflow: hidden;
}
.progress__fill {
  position: absolute;
  inset: 0 auto 0 0;
  inline-size: var(--v, 0%);
  border-radius: inherit;
  background: var(--progress-fill, var(--fg));
  transition: inline-size var(--dur-slow) var(--ease-out);
}
.surface-invert .progress,
.progress--accent { --progress-fill: var(--accent); }
.progress--lg { block-size: var(--progress-h-lg); border-radius: var(--radius-xs); }
.progress--target { overflow: visible; }
.progress--target::after {
  content: "";
  position: absolute;
  inset-block: calc(var(--border-bold) * -1.5);
  inset-inline-start: var(--target, 70%);
  inline-size: var(--border-bold);
  background: var(--fg);
  box-shadow: 0 0 0 var(--border-hair) var(--bg);
}
.progress-label {
  display: flex;
  justify-content: space-between;
  gap: var(--space-s);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.progress-label b { color: var(--fg); font-weight: var(--weight-semibold); }

/* Bar langkah — deretan segmen .progress, satu per langkah. JS hanya
   mengubah data-state; lebar isiannya diturunkan dari situ. */
.steps-bar {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
.steps-bar__seg:is([data-state="done"], [data-state="current"]) { --v: 100%; }

/* =======================================================
   SPINNER
   ======================================================= */
.spinner {
  inline-size: 1.1rem;
  block-size: 1.1rem;
  border: var(--border-bold) solid var(--line);
  border-block-start-color: var(--fg);
  border-radius: var(--radius-pill);
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { rotate: 1turn; } }

/* =======================================================
   PEMBATAS BERTEKS — garis kiri-kanan dengan kata di tengah ("atau").
   Teksnya memakai .overline.
   ======================================================= */
.divider {
  display: flex;
  align-items: center;
  gap: var(--space-s);
}
.divider::before,
.divider::after {
  content: "";
  flex: 1;
  block-size: var(--border-hair);
  background: var(--line);
}


/* ##########################################################################
   B. PERMUKAAN
   ########################################################################## */

/* =======================================================
   KEPALA — satu komponen untuk setiap "judul + keterangan + alat":
   judul halaman, judul seksi, kepala panel dasbor, dan kepala katalog.
   Sebelum 21 Sep 2026 ada enam pola terpisah (.page-header, .section-head,
   .catalog-head__top, .app-head, .app-section__head, .card__head) dengan
   tiga sistem jarak berbeda.

   Ukuran judul TIDAK diatur di sini — ia datang dari elemen yang dipakai
   (h1/h2/h3) atau kelas .h1–.h4, jadi level semantik dan ukuran visual bisa
   berbeda tanpa kelas baru.

     bawaan     judul kiri, alat/catatan kanan, rata bawah
     --page     pembuka halaman: keterangannya seukuran .lead
     --ruled    garis tipis di bawahnya (pembuka halaman situs)
     --flush    tanpa jarak bawah — dipakai di dalam induk yang sudah
                punya gap sendiri (.app-section, .catalog-head)
   Bagian: __text (judul + keterangan) · __body (keterangan) ·
           __note (catatan kecil di kanan) · __tools (penyaring/aksi)
   ======================================================= */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-s) var(--space-l);
  min-inline-size: 0;
  margin-block-end: var(--space-xl);
}
.section-head__text { min-inline-size: 0; }
.section-head__body {
  margin-block-start: var(--space-2xs);
  /* Lebih sempit dari --measure: keterangan ini duduk di sebelah judul,
     bukan sebagai paragraf yang dibaca penuh. */
  max-inline-size: 46ch;
  color: var(--fg-muted);
  line-height: var(--leading-relaxed);
}
.section-head__note {
  font-size: var(--step--1);
  color: var(--fg-muted);
}
/* Alat penyempit daftar (penyaring + kotak cari) atau tombol aksi.
   Dibungkus satu elemen supaya semuanya turun bersama ke baris berikutnya
   saat ruangnya habis, bukan terpisah sendiri-sendiri. */
.section-head__tools {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2xs);
}
.section-head--page .section-head__body {
  margin-block-start: var(--space-s);
  font-size: var(--text-lead);
  color: var(--fg);
  max-inline-size: 52ch;
}
.section-head--ruled {
  padding-block-end: var(--space-l);
  border-block-end: var(--border-hair) solid var(--line);
}
.section-head--flush { margin-block-end: 0; }
/* Di bawah md alat ditumpuk selebar penuh, bukan dibiarkan membungkus
   sambil rata kanan. */
/* flex-basis: 100% memaksa alatnya turun ke baris sendiri; tanpa itu ia
   tetap duduk di sebelah judul dan tersisa selebar sisa baris — di ponsel
   dropdown dan kotak cari jadi separuh lebar layar di samping judul. */
@media (max-width: 47.99rem) {
  /* Judul dan catatannya hampir selalu patah dua baris di ponsel. Catatan
     itu milik judul, bukan blok berikutnya, jadi 16px terbaca sebagai
     jarak antar-blok yang keliru — turunkan ke 8px. */
  .section-head { row-gap: var(--space-2xs); }
  .section-head__tools {
    /* Alat penyaring memang blok baru, jadi ia mengambil kembali 8px yang
       hilang di atas lewat margin sendiri. */
    margin-block-start: var(--space-2xs);
    flex-basis: 100%;
    flex-direction: column;
    align-items: stretch;
  }
  .section-head__tools > * { inline-size: 100%; }
}

/* =======================================================
   KARTU — wadah dasar untuk SEMUA kotak berbingkai: kartu katalog,
   panel dasbor, KPI, kartu "sedang berjalan", kelas aktif, testimoni,
   paket harga, kartu produk, kuis. Komponen halaman hanya menambah
   tata letak isinya.
     --compact      padding 24px (dasbor, daftar padat)
     --sunken       latar abu cekung
     --flush        tanpa padding (gambar menempel ke tepi)
     --interactive  terangkat saat hover + tombol muncul dari bawah
   Tidak ada varian kartu hitam dalam deretan kartu (dihapus 21 Sep 2026);
   .surface-invert dipakai untuk bidang selebar halaman — seksi, footer,
   dan blok ajakan berlangganan di halaman belajar.
   Bagian: __head (judul + aksi sebaris), __kind (label jenis), __title,
   __sub, __badge, __body, __foot, __price, __meta, __cta.
   ======================================================= */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  min-inline-size: 0;
  margin: 0;
  /* Padding lewat variabel, bukan nilai langsung: .card__media harus
     membatalkannya dengan margin negatif yang persis sama besar. */
  --card-pad: var(--space-l);
  padding: var(--card-pad);
  background: var(--bg-raised);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-l);
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
/* 1.2rem, bukan --space-m (1.5rem): nilai di luar skala jarak, dipilih
   langsung supaya panel dasbor sedikit lebih rapat dari kartu biasa. */
.card--compact { --card-pad: var(--space-m); padding: var(--card-pad); }
.card--sunken  { background: var(--bg-sunken); }
.card--flush   { --card-pad: 0px; padding: 0; overflow: clip; }
/* Di ponsel padding 2rem memakan seperempat lebar layar sebelum huruf
   pertama muncul. Turun dua jenjang: 32→16px, dan --compact 24→12px.
   Padding yang dilepas langsung jadi lebar baca — di kisi KPI dua kolom
   pada layar 390px isi kartu menang 10px meski kartunya sendiri tidak
   melebar.

   Tidak turun lebih jauh dari 12px: di bawah itu huruf mulai terbaca
   menempel ke garis tepi kartu, dan kartunya berhenti terbaca sebagai
   wadah.

   --flush ditulis ulang di sini karena aturan di bawah urutannya menang
   atas deklarasi padding: 0 di atas. */
@media (max-width: 47.99rem) {
  .card          { --card-pad: var(--space-s);  padding: var(--card-pad); }
  .card--compact { --card-pad: var(--space-xs); padding: var(--card-pad); }
  .card--flush   { --card-pad: 0px;             padding: 0; }
}
.card--interactive:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-l);
  border-color: var(--line-strong);
}

/* GAMBAR KARTU — bidang di tepi atas kartu, membatalkan padding kartu lewat
   margin negatif sebesar --card-pad. Dengan begitu kartu tidak perlu jadi
   .card--flush + pembungkus isi kedua hanya demi satu gambar.
   Sementara ini isinya kosong: bidang abu polos sebagai tempat foto, tinggal
   diisi <img class="card__media-img"> tanpa mengubah aturan apa pun. */
.card__media {
  margin: calc(var(--card-pad) * -1) calc(var(--card-pad) * -1) 0;
  aspect-ratio: 16 / 9;
  background: var(--clr-gray-100);
  /* Radius dalam = radius kartu dikurangi tebal garisnya, kalau tidak
     sudutnya terlihat sebagai garis abu tipis di luar lengkungan. */
  border-start-start-radius: calc(var(--radius-l) - var(--border-hair));
  border-start-end-radius: calc(var(--radius-l) - var(--border-hair));
  overflow: clip;
}
.card__media img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  justify-content: space-between;
  gap: var(--space-2xs) var(--space-s);
}
.card__head--center { align-items: center; }
/* Label jenis di pojok kartu: emblem + .overline. */
.card__kind { display: inline-flex; align-items: center; gap: var(--space-2xs); }
/* Satu langkah lebih kecil dari judul blok: judul materi bisa dua sampai
   tiga baris. text-wrap: balance membagi baris sama panjang. */
/* Judul kartu: Inter tebal, bukan Farro — lihat --text-card-title.
   Berlaku untuk semua kartu (katalog, dasbor, kelas aktif) karena semuanya
   memakai kelas ini, apa pun tag heading-nya. */
.card__title {
  font-family: var(--font-sans);
  font-size: var(--text-card-title);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-normal);
  text-wrap: balance;
}
.card__sub { margin-block-start: var(--space-3xs); font-size: var(--step--1); color: var(--fg-muted); }
/* Badge dipaksa selebar isinya, bukan selebar kolom kartu. */
.card__badge { align-self: start; }
.card__body { color: var(--fg); font-size: var(--step-0); line-height: var(--leading-prose); }

/* Kaki kartu didorong ke dasar supaya tombol dan harga berbaris rapi
   meski panjang isi tiap kartu berbeda. */
.card__foot {
  margin-block-start: auto;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-s);
  padding-block-start: var(--space-s);
  border-block-start: var(--border-hair) solid var(--line);
}
/* Sederajat dengan .card__title, bukan lebih besar — kalau harga lebih
   nyaring dari judul, hierarkinya terbalik. */
.card__price { font-family: var(--font-display); font-weight: var(--weight-display); font-size: var(--text-card-title); white-space: nowrap; }
.card__meta { font-size: var(--step--2); color: var(--fg-muted); }

/* Bawaan (peranti sentuh): tombol berada di alur normal di bawah baris kaki,
   supaya tetap bisa ditekan tanpa hover. */
.card__cta { margin-block-start: var(--space-s); inline-size: 100%; }

/* Di peranti berkursor tombol disembunyikan di bawah tepi kartu lalu naik
   saat hover — tidak ada ruang kosong menganggur dan tinggi kartu tetap. */
@media (hover: hover) and (pointer: fine) {
  .card--interactive { position: relative; overflow: clip; }
  .card--interactive .card__cta {
    position: absolute;
    inset-inline: var(--space-l);
    inset-block-end: var(--space-l);
    /* WAJIB: inline-size dari aturan dasar (100%) menimpa lebar hasil
       inset-inline dan membuat tombol meluber keluar kartu. */
    inline-size: auto;
    margin: 0;
    transform: translateY(calc(var(--space-l) + var(--control-h)));
    transition: transform var(--dur-base) var(--ease-out);
  }
  .card--interactive:hover .card__cta,
  .card--interactive:focus-within .card__cta { transform: none; }

  /* Baris kaki meredup saat tombol naik — pergantiannya terbaca sengaja.
     Hanya opacity, bukan visibility, supaya tetap terbaca pembaca layar. */
  .card--interactive .card__foot { transition: opacity var(--dur-base) var(--ease-out); }
  .card--interactive:hover .card__foot,
  .card--interactive:focus-within .card__foot { opacity: 0; }
}

/* =======================================================
   RAK GESER — satu baris kartu mendatar per kategori, dengan tombol panah
   bundar di kedua tepinya. Dipakai halaman Try Out dan Bimbingan: sejak
   24 Sep 2026 keduanya tidak lagi memakai tab penyaring, melainkan satu
   seksi per kategori. Alasannya: dengan tab, sebelas kartu lain tersembunyi
   di balik klik dan orang harus tahu dulu apa yang dicarinya. Dengan rak,
   seluruh kategori terbaca sekaligus sambil digeser.

   Struktur: .shelf > .shelf__head (judul) + .shelf__body (tombol + .shelf__track).
   Tombolnya dipasang/ dilepas modul shelf() di main.js sesuai ada-tidaknya
   ruang geser — rak yang isinya muat penuh tidak menampilkan panah sama sekali.
   ======================================================= */
.shelf { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-m); }
/* Jarak antar-rak sebesar jarak antar-seksi: tiap rak adalah kategori
   tersendiri, dan kalau jaraknya sama dengan jarak di dalam rak, judul
   kategori berikutnya terbaca seperti keterangan kartu terakhir. */
.shelf + .shelf { margin-block-start: var(--space-2xl); }
/* Kepala rak menempel selama raknya masih terlihat: saat menggeser kartu ke
   kanan, nama kategorinya tetap ada di layar — tanpa itu orang mudah lupa
   sedang menggeser rak yang mana. Ia berhenti menempel sendiri begitu
   raknya habis, karena .shelf adalah blok penampungnya.

   inset-block-start sebesar --header-h: bilah atas situs juga sticky, jadi
   kalau kepala rak menempel di 0 ia akan bersembunyi di baliknya.

   WAJIB punya latar sendiri — kartu yang lewat di belakangnya harus
   tertutup, bukan terbaca menembus teks. */
.shelf__head {
  position: sticky;
  inset-block-start: var(--header-h);
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2xs) var(--space-s);
  padding-block: var(--space-2xs);
  background: var(--bg);
}
.shelf__title { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2xs) var(--space-s); min-inline-size: 0; }
.shelf__note { font-size: var(--step--1); color: var(--fg-muted); }
/* Tautan ke daftar penuh kategori itu (tryout-semua.html / bimbingan-semua.html). */
.shelf__all { flex: none; }
/* min-inline-size: 0 WAJIB. Tanpa itu lebar min-content deret kartu di
   dalamnya menular ke luar — overflow-x pada .shelf__track hanya menolkan
   lebar minimum saat ia jadi butir flex/grid, bukan saat ia blok biasa di
   dalam blok. Akibatnya seluruh halaman ikut melebar sampai ribuan piksel.
   Efek sampingnya lebih jauh: lebar track jadi tak-tentu, sehingga persen
   di grid-auto-columns jatuh balik ke max-content dan kartunya membesar
   mengikuti isi. Dua gejala, satu sebab. */
.shelf__body {
  position: relative;
  min-inline-size: 0;
  /* clip, bukan hidden: raknya tidak boleh jadi wadah gulir kedua, tapi
     sisa lebar track (yang dibentangkan margin negatif di ponsel) tidak
     boleh ikut melebarkan halaman. overflow-clip-margin menyisakan ruang
     supaya tombol panah yang sengaja menggantung di luar tepi tetap
     tergambar. */
  overflow: clip;
  /* Ruang sisa hanya sebesar gantungan tombol panah, dan panah hanya ada
     mulai lg — di bawah itu nol, supaya bentangan ke tepi layar tidak
     menyisakan lebar yang menular ke halaman. */
  overflow-clip-margin: 0;
}
@media (min-width: 64rem) {
  .shelf__body { overflow-clip-margin: var(--space-l); }
}

/* Jumlah kartu yang terlihat diatur satu variabel; pecahan di belakang koma
   membuat kartu berikutnya mengintip, yang memberi tahu bahwa barisnya masih
   bisa digeser — tanpa itu rak terbaca seperti kisi yang terpotong. */
.shelf__track {
  --shelf-cols: 1.15;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--shelf-cols) - 1) * var(--grid-col-gap)) / var(--shelf-cols));
  gap: var(--grid-col-gap);
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* Kartu yang terangkat saat hover tidak boleh terpotong tepi atas rak. */
  padding-block: var(--space-3xs);
}
.shelf__track::-webkit-scrollbar { display: none; }
/* Butir rak WAJIB boleh menyusut: lebar minimum bawaan butir grid adalah
   min-content isinya (judul panjang, badge yang tidak boleh patah), dan
   tanpa penolan ini kolomnya melar melewati lebar yang sudah dihitung —
   raknya tetap bisa digeser, tapi halamannya ikut melebar. */
/* display: grid pada butirnya supaya kartu di dalamnya ikut setinggi baris —
   tanpa itu tiap kartu setinggi isinya sendiri dan kaki kartu (harga +
   tombol) berbaris tidak rata di sepanjang rak. */
.shelf__track > * { display: grid; min-inline-size: 0; scroll-snap-align: start; }
@media (min-width: 30rem) { .shelf__track { --shelf-cols: 2.15; } }
@media (min-width: 48rem) { .shelf__track { --shelf-cols: 3; } }
@media (min-width: 64rem) { .shelf__track { --shelf-cols: 4; } }

/* Di bawah lg rak dibentangkan sampai tepi layar: kartu yang terpotong di
   tepi LAYAR terbaca "masih bisa digeser", yang terpotong rapi di garis
   gutter terbaca rusak. Aturan yang sama dipakai .row--rail. */
@media (max-width: 63.99rem) {
  /* Yang dibentangkan .shelf__body, bukan .shelf__track: kotak kliping
     harus ikut melebar sampai tepi layar, kalau tidak sisa lebar track
     menular jadi lebar halaman. */
  .shelf__body { margin-inline: calc(var(--gutter) * -1); }
  /* Kepalanya ikut dibentangkan — bukan demi tampilan, tapi supaya latar
     putihnya menutup SELURUH lebar layar saat menempel. Kalau hanya selebar
     container, kartu yang lewat di zona bentangan terbaca menembus di
     samping judulnya. */
  .shelf__head {
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }
  .shelf__track {
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
  }
}

/* Tombol panah: bundar, mengambang di tepi rak, setengah keluar dari
   kolomnya supaya tidak menutupi kartu. Hanya ada di peranti berkursor —
   di layar sentuh geser jari sudah cukup, dan tombol di tepi justru
   menghalangi kartu pertama/terakhir. */
.shelf__nav {
  position: absolute;
  inset-block-start: 50%;
  translate: 0 -50%;
  z-index: 1;
  border-radius: var(--radius-pill);
  background: var(--bg-raised);
  border: var(--border-hair) solid var(--line);
  box-shadow: var(--shadow-m);
}
.shelf__nav:hover { background: var(--bg-canvas-strong); }
.shelf__nav--prev { inset-inline-start: calc(var(--space-l) * -1); }
.shelf__nav--next { inset-inline-end: calc(var(--space-l) * -1); }
/* [hidden] ditegaskan modul JS; di sini hanya keadaan tanpa kursor. */
@media (max-width: 63.99rem), (pointer: coarse) {
  .shelf__nav { display: none; }
}

/* Deskripsi kartu di dalam rak: satu jenjang lebih kecil dan dipotong tiga
   baris. Kartu rak berdampingan dalam satu baris, jadi paragraf yang
   panjangnya berbeda-beda membuat ubin angka dan kaki kartu tidak sebaris —
   dan di lebar kartu ±17rem, empat baris teks sudah mengalahkan judulnya. */
/* Berlaku untuk kartu yang berdiri BERDAMPINGAN — baik di rak geser maupun
   di kisi katalog penuh. Nama penyelenggara panjangnya berbeda-beda
   ("Tutooria" satu baris, "Institut Teknologi Bandung" tiga baris di lebar
   kartu rak), dan tiap selisih baris menggeser seluruh isi kartu di
   bawahnya. Tingginya dikunci sebesar tiga baris .overline — kasus
   terpanjang — supaya judul dan ubin angka mulai di garis yang sama. */
:is(.shelf, .catalog-grid) .card__head { min-block-size: 2.75rem; align-items: start; }

:is(.shelf, .catalog-grid) :is(.card__title, .card__body) {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Tinggi dikunci, bukan sekadar dibatasi: judul satu baris dan judul dua
   baris akan menggeser ubin angka di bawahnya, dan dalam satu rak
   pergeseran itu langsung terlihat sebagai baris yang tidak rata. */
:is(.shelf, .catalog-grid) .card__title {
  -webkit-line-clamp: 2;
  line-clamp: 2;
  min-block-size: calc(2em * var(--leading-normal));
}
:is(.shelf, .catalog-grid) .card__body {
  font-size: var(--step--1);
  line-height: var(--leading-normal);
  -webkit-line-clamp: 3;
  line-clamp: 3;
  min-block-size: calc(3em * var(--leading-normal));
}

/* =======================================================
   UBIN ANGKA — tiga fakta kartu yang berdiri berdampingan, masing-masing
   ikon di atas nilainya. Dipakai kartu try out (tanggal · jumlah soal ·
   durasi) menggantikan .meta-list empat baris: empat baris label-nilai
   memakan separuh tinggi kartu padahal yang dibaca sekilas cuma angkanya.
   Nilai saja yang ditulis — satuannya ("soal", "menit") sudah ikut di
   dalam nilainya, dan labelnya dibaca pembaca layar lewat .visually-hidden
   di ubin yang nilainya tidak menyebut dirinya sendiri (tanggal).
   ======================================================= */
.stat-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
.stat-tile {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--space-3xs);
  /* Jarak tepi mendatar sengaja kecil: isinya nilai pendek, dan tiap
     piksel yang tersisa menahan tanggal tetap satu baris. */
  padding: var(--space-xs) var(--space-3xs);
  border-radius: var(--radius-m);
  background: var(--bg-sunken);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  text-align: center;
  text-wrap: balance;
}
.stat-tile .icon { --icon-size: 1rem; color: var(--fg-muted); }

/* =======================================================
   DAFTAR KETERANGAN — label kiri, nilai kanan, bergaris per baris.
   ======================================================= */
.meta-list { display: grid; gap: var(--space-2xs); margin: 0; }
.meta-list > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-s);
  padding-block-end: var(--space-2xs);
  border-block-end: var(--border-hair) solid var(--line);
  font-size: var(--step--1);
}
.meta-list dt { color: var(--fg-muted); }
.meta-list dd { margin: 0; font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
/* Satu garis pemisah saja di antara dua blok: baris terakhir daftar sudah
   bergaris bawah, jadi kaki kartu yang menyusul yang mengalah. */
.meta-list + .card__foot {
  border-block-start: 0;
  padding-block-start: 0;
}

/* =======================================================
   POPOVER — permukaan untuk SEMUA panel yang mengambang: dropdown menu
   navigasi, panel combobox, kalender. Posisi & cara muncul diatur
   komponen pemiliknya; popover hanya memberi bidang, garis, dan bayangan.
     bawaan   garis --line + bayangan (lepas dari pemicunya)
     --field  garis hitam — panel yang menempel pada isian yang sedang
              fokus, jadi garisnya menyambung dengan garis isian itu
   ======================================================= */
.popover {
  z-index: var(--z-overlay);
  background: var(--bg-raised);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-l);
}
.popover--field { border-color: var(--clr-black); }
@keyframes popover-in    { from { opacity: 0; translate: 0 -0.35rem; } }
@keyframes popover-in-up { from { opacity: 0; translate: 0 0.35rem; } }

/* =======================================================
   MENU — daftar butir yang bisa dipilih di dalam popover atau sidebar:
   rumpun & topik di dropdown Kelas, opsi combobox, riwayat Copilot.
     hover            bidang abu tipis
     [aria-current] / [aria-expanded="true"]  bidang abu + tebal
     [data-active]    sorotan papan ketik — bidang ungu muda
     [aria-selected]  tebal (terpilih di combobox)
   ======================================================= */
.menu-list {
  display: grid;
  /* minmax(0, 1fr): butir bertanda .menu-item--truncate teksnya nowrap,
     jadi lebar min-content-nya sepanjang judul terpanjang — tanpa kunci
     ini daftarnya melebarkan kolom induknya alih-alih memotong teksnya. */
  grid-template-columns: minmax(0, 1fr);
  gap: calc(var(--space-3xs) / 2);
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Judul kelompok di atas sebuah .menu-list — menu sidebar aplikasi
   ("Belajar") dan riwayat Copilot ("Minggu ini" / "Bulan lalu"). Teksnya
   .overline; yang diatur di sini hanya penempatannya: jarak tepi sama
   dengan .menu-item, jadi judulnya persis sejajar dengan isi butirnya. */
.menu-group {
  margin-block-end: var(--space-2xs);
  padding-inline: var(--space-xs);
}

.menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  inline-size: 100%;
  /* Kait varian: tinggi, radius, dan warna hover bisa diganti tanpa
     menulis ulang seluruh komponen (dipakai menu sidebar aplikasi). */
  min-block-size: var(--menu-item-h, 2.5rem);
  padding: var(--space-2xs) var(--space-xs);
  border: 0;
  border-radius: var(--menu-item-radius, var(--radius-s));
  background: none;
  color: var(--fg);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.menu-item:hover,
.menu-item[aria-expanded="true"] { background: var(--menu-item-hover, color-mix(in oklab, var(--fg) 7%, transparent)); text-decoration: none; }
.menu-item:is([aria-current="page"], [aria-current="true"]) {
  background: color-mix(in oklab, var(--fg) 7%, transparent);
  font-weight: var(--weight-semibold);
}
.menu-item[data-active="true"] { background: var(--accent-mid); }
/* Terpilih di combobox = tebal saja. Sebagai TAB (menu bagian tegak) ia
   juga mendapat bidang, sama seperti butir [aria-current]. */
.menu-item[aria-selected="true"] { font-weight: var(--weight-semibold); }
.menu-item[role="tab"][aria-selected="true"] { background: var(--menu-item-hover, color-mix(in oklab, var(--fg) 7%, transparent)); }
/* Teks panjang dipotong dengan elipsis, bukan membungkus baris. */
.menu-item--truncate { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Butir berbagian — ikon · teks + keterangan · status. Dipakai daftar
   materi di halaman belajar. Status = lingkaran kosong; selesai
   (data-state="done") = lingkaran hitam bercentang. Terkunci
   (data-locked) = teks redup, ikon gembok dari markup. */
.menu-item__icon { display: grid; place-items: center; flex: none; color: var(--fg-muted); }
.menu-item__icon .icon { --icon-size: 1rem; }
.menu-item__text { flex: 1; min-inline-size: 0; display: grid; gap: 0.1rem; line-height: var(--leading-snug); }
.menu-item__meta { font-size: var(--step--2); font-weight: var(--weight-body); color: var(--fg-muted); }
.menu-item__status {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border: var(--border-bold) solid var(--clr-gray-300);
  border-radius: var(--radius-pill);
  color: transparent;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.menu-item__status .icon { --icon-size: 0.75rem; stroke-width: 3; }
.menu-item[data-state="done"] .menu-item__status { background: var(--fg); border-color: var(--fg); color: var(--bg); }
.menu-item[data-locked] { color: var(--fg-muted); }

/* =======================================================
   TABEL — satu gaya untuk tabel data (riwayat try out) dan tabel
   perbandingan (harga). Pembungkus .table-wrap menggulir mendatar.
     kepala   gaya .overline
     --compare  kepala Farro bergaris hitam 2px, sel di tengah, kolom
                pertama menempel saat digeser
   ======================================================= */
/* position: relative WAJIB — teks .visually-hidden di dalam sel berposisi
   absolut; tanpa blok penampung di sini ia lolos keluar dari area gulir
   dan melebarkan seluruh halaman di ponsel (dulu 647px di layar 360px). */
.table-wrap { position: relative; overflow-x: auto; }
.table { inline-size: 100%; border-collapse: collapse; font-size: var(--step--1); }
.table :is(th, td) {
  padding: var(--space-xs) var(--space-s);
  border-block-end: var(--border-hair) solid var(--line);
  text-align: start;
  white-space: nowrap;
}
.table thead th {
  font-size: var(--step--2);
  font-weight: var(--weight-semibold);
  color: var(--fg-muted);
}
.table tbody th { font-weight: var(--weight-semibold); white-space: normal; min-inline-size: 12rem; }
.table [data-num] { text-align: end; font-variant-numeric: tabular-nums; }

/* Tabel data di dalam kartu: tepi kiri-kanan rata dengan isi kartu. */
.table--flush :is(th, td):first-child { padding-inline-start: 0; }
.table--flush :is(th, td):last-child { padding-inline-end: 0; text-align: end; }
.table--flush tbody tr:last-child :is(th, td) { border-block-end: 0; }

.table--compare :is(th, td) { padding: var(--space-s) var(--space-m); }
.table--compare thead th {
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-snug);
  color: var(--fg);
  border-block-end: var(--border-bold) solid var(--line-strong);
}
.table--compare tbody th { font-weight: var(--weight-medium); min-inline-size: 14rem; }
.table--compare td,
.table--compare thead th:not(:first-child) { text-align: center; }
.table--compare td[data-yes] { font-weight: var(--weight-semibold); }
.table--compare td[data-no] { color: var(--fg-subtle); }
/* Kolom nama fitur menempel saat tabel digeser mendatar — tanpa itu, di
   ponsel "Ya / —" kehilangan label barisnya. Latarnya menyamai seksi. */
.table--compare tbody th,
.table--compare thead th:first-child {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  background: var(--table-bg, var(--bg));
}
.section--sunken .table--compare { --table-bg: var(--bg-sunken); }
@media (max-width: 47.99rem) {
  .table--compare :is(th, td) { padding-inline: var(--space-s); }
  .table--compare tbody th {
    min-inline-size: 9rem;
    /* Garis pemisah hanya di ponsel, saat kolom ini memang menempel. */
    box-shadow: inset calc(var(--border-hair) * -1) 0 0 var(--line);
  }
}

/* =======================================================
   KEADAAN KOSONG — hasil saring nihil, daftar belum berisi.
     bawaan    seluruh halaman/katalog kosong — ruang lega, rata tengah
     --compact di dalam panel atau daftar kecil (daftar materi, sesi
               live, tanya jawab): teks redup tanpa ruang berlebih
   ======================================================= */
/* Keadaan kosong. Dua cara memakainya:
   1. Polos — satu <p> berisi kalimat ("Belum ada kelas yang cocok…").
      Dipakai untuk hasil penyaring kosong, di mana penyebabnya jelas dan
      jalan keluarnya ada di kotak cari tepat di atasnya.
   2. Lengkap — .empty-state__icon + __title + __body. Dipakai untuk
      keadaan kosong yang berdiri sendiri, yang bukan akibat penyaring:
      utas tanya jawab yang belum berisi, misalnya. Ikonnya bidang bulat
      redup (bukan ilustrasi) supaya tidak berebut perhatian dengan aksi
      di sekitarnya, judulnya menyebut apa yang belum ada, dan kalimat di
      bawahnya menerangkan apa yang akan muncul di situ nanti.
   Tanpa tombol ajakan kalau aksinya sudah terlihat di layar — dua pintu
   ke aksi yang sama membuat murid ragu mana yang benar. */
.empty-state {
  display: grid;
  justify-items: center;
  gap: var(--space-3xs);
  padding-block: var(--space-3xl);
  text-align: center;
  color: var(--fg);
}
.empty-state__icon {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-block-end: var(--space-2xs);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--fg-muted);
}
.empty-state__icon .icon { --icon-size: 1.25rem; }
.empty-state__title { font-size: var(--step-0); font-weight: var(--weight-semibold); }
.empty-state__body {
  max-inline-size: 42ch;
  font-size: var(--step--1);
  color: var(--fg-muted);
  line-height: var(--leading-relaxed);
  /* Rata tengah tiga baris yang panjangnya berbeda jauh terbaca berantakan;
     balance membagi barisnya sama panjang. */
  text-wrap: balance;
}
/* Jalan keluar (kalau ada) berdiri agak jauh dari kalimatnya — ia aksi,
   bukan baris ketiga teks. */
.empty-state .button { margin-block-start: var(--space-s); }
/* Di DALAM kartu jaraknya lebih kecil daripada keadaan kosong selebar
   halaman: kartunya sendiri sudah memberi jarak tepi. */
.card > .empty-state { padding-block: var(--space-l); }
/* Versi sebaris: satu kalimat redup tanpa jarak sendiri, untuk pesan
   "tidak ada yang cocok" di dalam daftar yang sudah berbingkai. */
.empty-state--compact {
  /* stretch, bukan start: butirnya melebar penuh jadi text-align di
     penimpaan halaman (mis. .learn-side) tetap menentukan perataannya. */
  justify-items: stretch;
  padding-block: 0;
  text-align: start;
  font-size: var(--step--1);
  color: var(--fg-muted);
}


/* ##########################################################################
   C. FORMULIR
   ########################################################################## */

.form { display: grid; gap: var(--space-m); }

/* Pesan terkirim — bidang ungu, huruf hitam, garis hitam. */
.form__status {
  padding: var(--space-s) var(--space-m);
  border-radius: var(--radius-m);
  background: var(--accent);
  color: var(--accent-fg);
  border: var(--border-bold) solid var(--clr-black);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
}

/* minmax(0, 1fr): isi yang tidak boleh patah — teks pemicu combobox,
   misalnya — punya lebar minimum sepanjang teksnya. Tanpa kunci ini kolom
   .field ikut melebar sepanjang teks itu dan isian meluber keluar kartu
   di layar sempit. */
.field { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-2xs); }
.field__label {
  font-size: var(--step--1);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-snug);
}
/* Label + tautan sebaris ("Kata sandi" · "Lupa?"). */
.field__label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-s);
}
.field__hint { font-size: var(--step--2); color: var(--fg); }
/* Galat tidak boleh bergantung pada warna saja, dan ungu gagal kontras
   sebagai warna teks. Jadi: huruf hitam di atas bidang ungu muda. */
.field__error {
  justify-self: start;
  padding: var(--pill-pad-block) var(--pill-pad-inline);
  background: var(--accent-mid);
  color: var(--clr-black);
  border-radius: var(--radius-s);
  font-size: var(--step--2);
  font-weight: var(--weight-semibold);
}
.field__error:empty { display: none; }
/* Saran pengganti, mis. username yang sudah dipakai. */
.field__suggest { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2xs); }
.field__suggest-label { font-size: var(--step--2); color: var(--fg-muted); }

/* Fieldset sebagai .field — untuk kelompok radio/checkbox. Legend tidak
   ikut gap grid, jadi jaraknya diberi langsung. */
fieldset.field { min-inline-size: 0; padding: 0; border: 0; }
fieldset.field > legend { padding: 0; margin-block-end: var(--space-2xs); }

/* =======================================================
   INPUT, SELECT, TEXTAREA — satu kotak isian.
   1px, bukan 2px — garis tebal membuat formulir terasa berat. Tinggi,
   padding, ukuran huruf, dan radius dari token --control-* yang sama
   dengan .button, .icon-button, dan .chip: kolom isian dan tombol di
   sebelahnya punya inset teks dan sudut yang identik. Textarea dikecualikan
   dari tinggi tetap — ia tumbuh mengikuti isi.
   Fokus: garis hitam + cincin ungu 3px (ungu sebagai bidang).
   ======================================================= */
.input, .textarea, .select {
  /* block, bukan inline-block bawaan: sebagai elemen sebaris ia duduk di
     garis alas dan menyisakan ruang turunan huruf di bawahnya — di dalam
     pembungkus seperti .field-shell, jarak bawahnya jadi terlihat lebih
     besar dari ketiga sisi lain. */
  display: block;
  inline-size: 100%;
  /* Kontrol isian harus boleh menyusut di dalam flex/grid; lebar minimum
     bawaan <input> (±20 karakter) kalau tidak dinolkan akan mendorong
     induknya melebar dan halaman meluber di layar sempit. */
  min-inline-size: 0;
  padding-inline: var(--control-pad);
  background: var(--bg-raised);
  border: var(--control-border) solid var(--line);
  border-radius: var(--control-radius);
  font-size: var(--control-font);
  color: var(--fg);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.input, .select { block-size: var(--control-h); padding-block: 0; }
.input::placeholder, .textarea::placeholder { color: var(--fg-subtle); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--clr-black);
  box-shadow: var(--ring-field);
}
.input[aria-invalid="true"], .textarea[aria-invalid="true"], .select[aria-invalid="true"] {
  border-color: var(--clr-black);
}
.textarea { resize: vertical; min-block-size: 8rem; padding-block: var(--control-pad-block); line-height: var(--leading-prose); }
/* Tanpa JS <select> bawaan yang tampil, dengan panah gambar CSS. Dengan JS
   ia diganti combobox (lihat bawah) yang memakai kelas .select juga. */
.select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  /* Panah berjarak --control-pad dari tepi kanan, sama dengan inset teks
     di tepi kiri; ruang teks disisakan selebar padding + panah. */
  background-position: calc(100% - var(--control-pad) - 0.32rem) 55%,
                       calc(100% - var(--control-pad)) 55%;
  background-size: 0.36rem 0.36rem, 0.36rem 0.36rem;
  background-repeat: no-repeat;
  padding-inline-end: calc(var(--control-pad) * 2 + 0.75rem);
}

/* Varian ukuran — sejajar dengan .button--small / .button--large dan
   menimpa token yang sama persis, jadi kolom isian --small dan tombol
   --small yang berdampingan tetap sebangun. */
.input--small, .select--small, .textarea--small {
  --control-h: var(--control-h-small);
  --control-pad: var(--control-pad-small);
  --control-font: var(--control-font-small);
}
.input--large, .select--large, .textarea--large {
  --control-h: var(--control-h-large);
  --control-pad: var(--control-pad-large);
  --control-font: var(--control-font-large);
}

/* Kolom isian di bawah 16px membuat iOS memperbesar halaman begitu kolom
   difokuskan, lalu meninggalkannya dalam keadaan ter-zoom. Di layar sentuh
   ukuran hurufnya karena itu dinaikkan ke 16px — tapi lewat max(), supaya
   jenjang yang sudah lebih besar (kontrol --large di layar lebar) tidak
   justru DIKECILKAN ke 16px. 16px di sini sengaja px, bukan rem: ambang
   iOS-nya px mutlak dan tidak ikut --root-size. */
@media (pointer: coarse) {
  .input, .textarea, .select, .input-affix {
    font-size: max(16px, var(--control-font));
  }
}

/* =======================================================
   CANGKANG ISIAN — kotak isian putih di dalam cincin tipis. Dua lapis
   garis membuatnya terbaca sebagai titik mulai halaman, bukan isian biasa:
   kolom cari beranda aplikasi dan kotak tanya jawab memakainya.
   ======================================================= */
.field-shell {
  padding: var(--space-3xs);
  border: var(--border-hair) solid var(--line);
  border-radius: calc(var(--radius-l) + var(--space-3xs));
  background: var(--bg-canvas);
}
.field-shell :is(.input, .textarea) {
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-s);
}
.field-shell :is(.input, .textarea):focus { box-shadow: var(--ring-field); }

/* Input berikon — ikon di pembungkus, bukan di .field, karena .field juga
   memuat label dan pesan galat. */
.input-wrap { position: relative; display: block; }
.input-wrap__icon {
  position: absolute;
  inset-inline-start: var(--control-pad);
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: 1.15rem;
  block-size: 1.15rem;
  color: var(--fg-subtle);
  pointer-events: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.input-wrap:focus-within .input-wrap__icon { color: var(--fg); }
.input--with-icon { padding-inline-start: calc(var(--control-pad) * 2 + 1.15rem); }

/* Input berawalan (@, +62) — pembungkusnya yang berperan sebagai kotak,
   supaya lebar awalan bebas tanpa hitungan padding manual. */
.input-affix {
  display: flex;
  align-items: center;
  gap: var(--control-gap);
  block-size: var(--control-h);
  padding-inline: var(--control-pad);
  background: var(--bg-raised);
  border: var(--control-border) solid var(--line);
  border-radius: var(--control-radius);
  font-size: var(--control-font);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.input-affix:focus-within {
  border-color: var(--clr-black);
  box-shadow: var(--ring-field);
}
.input-affix:has([aria-invalid="true"]) { border-color: var(--clr-black); }
.input-affix > .input {
  flex: 1;
  min-inline-size: 0;
  block-size: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
}
.input-affix__prefix { flex: none; font-weight: var(--weight-medium); }
.input-affix__prefix--divided {
  align-self: stretch;
  display: grid;
  place-items: center;
  padding-inline-end: var(--space-xs);
  border-inline-end: var(--border-hair) solid var(--line);
}
/* Status di ujung kanan input: memutar saat mengecek, centang saat lolos. */
.input-affix__status { flex: none; display: grid; place-items: center; inline-size: 1.5rem; block-size: 1.5rem; }
.input-affix__status > * { grid-area: 1 / 1; display: none; }
.input-affix__status[data-state="checking"] .spinner { display: block; }
.input-affix__status[data-state="ok"] .icon {
  display: block;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  padding: 0.3rem;
  border-radius: var(--radius-pill);
  background: var(--clr-black);
  color: var(--clr-white);
  stroke-width: 3;
}

/* =======================================================
   KARTU OPSI — pilihan besar yang bisa diklik: kartu tujuan belajar
   (<label> + checkbox), pemantik obrolan Copilot (<button>), dan pilihan
   jawaban kuis (--row). Satu bentuk: garis 1px abu, hover garis hitam.
     terpilih   bidang ungu + garis hitam (ungu sebagai bidang)
   Bagian: __title, __desc, __check (kotak centang di kanan).
   ======================================================= */
.option-list { display: grid; gap: var(--space-2xs); margin: 0; padding: 0; list-style: none; }
.option-card {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--space-3xs);
  inline-size: 100%;
  padding: var(--space-s) var(--space-m);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-l);
  background: var(--bg-raised);
  color: var(--fg);
  text-align: start;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.option-card:hover { border-color: var(--clr-black); }
.option-card:active { transform: scale(0.99); }
.option-card:has(.option-card__check) { padding-inline-end: calc(var(--space-s) * 2 + 1.5rem); }
.option-card:has(:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.option-card__title { font-weight: var(--weight-semibold); font-size: var(--step-0); line-height: var(--leading-snug); }
.option-card__desc { font-size: var(--step--1); line-height: var(--leading-normal); }
.option-card__check {
  position: absolute;
  inset-inline-end: var(--space-s);
  inset-block-start: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  border: var(--border-bold) solid var(--line);
  border-radius: var(--radius-s);
  background: var(--clr-white);
  color: var(--clr-white);
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.option-card__check .icon { --icon-size: 1rem; stroke-width: 3; scale: 0; transition: scale var(--dur-base) var(--ease-spring); }
.option-card:has(:checked) { background: var(--accent); border-color: var(--clr-black); }
.option-card:has(:checked) .option-card__check { background: var(--clr-black); border-color: var(--clr-black); }
.option-card:has(:checked) .option-card__check .icon { scale: 1; }

/* Baris: emblem huruf · teks · ikon hasil. Untuk pilihan jawaban. */
.option-card--row {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  min-block-size: var(--control-h);
  padding: var(--space-2xs) var(--space-s);
  border-radius: var(--radius-m);
  font-size: var(--step--1);
}
.option-card--row > .icon { margin-inline-start: auto; --icon-size: 1.1rem; stroke-width: 2.5; }
.option-card:disabled { cursor: default; }
.option-card:disabled:hover { border-color: var(--line); }
/* Hasil jawaban — tidak bergantung warna saja: ikonnya berbeda dan
   hasilnya ditulis. Benar = bidang ungu; salah = garis hitam putus. */
/* Pilihan yang sedang dipilih tapi BELUM dinilai: garis hitam + bidang abu.
   Ungu disimpan untuk keadaan "benar" sesudah jawabannya diperiksa, jadi
   dua keadaan itu tidak tertukar. */
.option-card[aria-pressed="true"] {
  border-color: var(--clr-black);
  background: var(--bg-canvas-strong);
}
.option-card[aria-pressed="true"] .emblem { background: var(--clr-black); color: var(--clr-white); }
.option-card[data-result] .emblem { background: var(--clr-black); color: var(--clr-white); }
.option-card[data-result="right"],
.option-card[data-result="right"]:hover { background: var(--accent); border-color: var(--clr-black); }
.option-card[data-result="wrong"],
.option-card[data-result="wrong"]:hover { border-color: var(--clr-black); border-style: dashed; }

/* =======================================================
   SAKELAR — pilihan hidup/mati yang berlaku seketika (pengaturan
   notifikasi, privasi). Bedanya dengan .chip--check dan .option-card:
   di sana pilihan baru berlaku setelah tombol simpan ditekan, di sini
   kontrolnya sendiri yang menyatakan keadaan.
     mati   trek abu, tombol putih, tombol di kiri
     hidup  bidang ungu bergaris hitam, tombol hitam, tombol di kanan
   Keadaan tidak pernah bergantung warna saja: posisi tombol ikut
   berpindah. Checkbox aslinya tetap ada (.sr-control) — yang digambar
   di sini hanya kulitnya, jadi papan ketik dan pembaca layar tetap
   memakai kontrol bawaan.
   ======================================================= */
.switch {
  /* Tinggi trek satu jenjang di bawah --control-h-small: sakelar duduk
     di dalam baris teks, bukan berdiri sendiri seperti tombol. */
  --switch-h: 1.5rem;
  --switch-w: 2.625rem;
  --switch-pad: 0.1875rem;
  position: relative;   /* jangkar bagi input .sr-control */
  display: inline-grid;
  flex: none;
}
.switch__track {
  display: flex;
  align-items: center;
  inline-size: var(--switch-w);
  block-size: var(--switch-h);
  padding: var(--switch-pad);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--clr-gray-200);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.switch__thumb {
  --_thumb: calc(var(--switch-h) - (var(--switch-pad) + var(--border-hair)) * 2);
  inline-size: var(--_thumb);
  block-size: var(--_thumb);
  border-radius: var(--radius-pill);
  background: var(--clr-white);
  box-shadow: var(--shadow-s);
  transition: translate var(--dur-base) var(--ease-spring),
              background var(--dur-fast) var(--ease-out);
}
/* Jarak tempuh = lebar trek − tingginya; sisanya sudah dipotong padding. */
.switch:has(:checked) .switch__thumb {
  translate: calc(var(--switch-w) - var(--switch-h)) 0;
  background: var(--clr-black);
}
.switch:has(:checked) .switch__track {
  background: var(--accent);
  border-color: var(--clr-black);
}
.switch:has(:focus-visible) .switch__track {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.switch:has(:disabled) .switch__track { cursor: default; opacity: 0.45; }

/* =======================================================
   COMBOBOX — pengganti <select> bergaya Select2 (main.js modul 11).
   Pemicunya memakai .select dan panelnya .popover.popover--field, opsinya
   .menu-item — jadi tinggi, garis, radius, dan cincin fokus otomatis sama
   dengan isian lain, dan sorotan opsinya sama dengan menu lain.
   ======================================================= */
.combo { position: relative; }

.combo__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  padding-inline-end: 1em;
  background-image: none;   /* panah bawaan .select diganti ikon */
  text-align: start;
  cursor: pointer;
}
.combo__value { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo__trigger[data-placeholder="true"] .combo__value { color: var(--fg-subtle); }
.combo__trigger:disabled { cursor: not-allowed; opacity: 0.5; }

.combo__chevron {
  --icon-size: 1.15rem;
  transition: rotate var(--dur-base) var(--ease-out);
}
.combo[data-open="true"] .combo__chevron { rotate: 180deg; }
.combo[data-open="true"] > :is(.combo__trigger, .input) {
  border-color: var(--clr-black);
  box-shadow: var(--ring-field);
}

/* Isian bebas: ikon panah di ujung kanan, hanya bila ada daftar saran. */
.combo--input > .input { padding-inline-end: calc(var(--control-pad) * 2 + 0.75rem); }
.combo--input > .combo__chevron {
  position: absolute;
  /* Diukur dari tepi luar pembungkus, jadi tambahkan garis isian agar
     sejajar dengan panah .combo__trigger (di dalam garis + padding 1em). */
  inset-inline-end: calc(1em + var(--border-hair));
  inset-block-start: calc(var(--control-h) / 2);
  translate: 0 -50%;
  pointer-events: none;
}
.combo--input[data-has-options="false"] > .combo__chevron { display: none; }
.combo--input[data-has-options="false"] > .input { padding-inline-end: var(--control-pad); }

.combo__panel {
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(var(--control-h) + var(--space-3xs));
  overflow: hidden;
  animation: popover-in var(--dur-fast) var(--ease-out);
}
.combo[data-placement="top"] .combo__panel {
  inset-block: auto calc(100% + var(--space-3xs));
  animation-name: popover-in-up;
}

.combo__search {
  position: relative;
  padding: var(--space-2xs);
  border-block-end: var(--border-hair) solid var(--line);
}
.combo__search > .icon {
  position: absolute;
  inset-inline-start: calc(var(--space-2xs) + var(--space-xs));
  inset-block-start: 50%;
  translate: 0 -50%;
  --icon-size: 1rem;
  color: var(--fg-muted);
  pointer-events: none;
}
/* Kotak cari di dalam panel: setinggi kontrol kecil, sejajar dengan butir
   daftar di bawahnya. */
.combo__search .input {
  --control-h: var(--control-h-small);
  --control-pad: var(--control-pad-small);
  --control-font: var(--control-font-small);
  padding-inline-start: calc(var(--control-pad) + 1.5rem);
}

.combo__list {
  max-block-size: min(17rem, 45vh);
  padding: var(--space-3xs);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Baris pilihan mengikuti tinggi kontrol, supaya daftar yang terbuka
   terasa sekeluarga dengan kotak pemicunya. */
.combo__option { min-block-size: var(--control-h); padding-inline: var(--control-pad); font-weight: var(--weight-body); }
.combo__text strong { font-weight: var(--weight-display); text-decoration: underline; text-underline-offset: 0.2em; }
.combo__tick { --icon-size: 1rem; stroke-width: 3; visibility: hidden; }
.combo__option[aria-selected="true"] .combo__tick { visibility: visible; }
.combo__empty { padding: var(--space-s); font-size: var(--step--1); color: var(--fg-muted); }

/* =======================================================
   PEMILIH TANGGAL — pengganti popup <input type="date"> (main.js 11b).
   Satu keluarga dengan combobox: kotak .input-affix, panel
   .popover.popover--field, tombolnya .icon-button.
     terpilih   bidang ungu + huruf hitam tebal (ungu sebagai bidang)
     disorot    bidang ungu muda, sama dengan opsi aktif menu
     hari ini   titik HITAM di bawah angka — bukan ungu (2.21:1 hilang)
     di luar rentang  abu, tanpa hover
   ======================================================= */
.datepicker { position: relative; }

/* Padding kanan dipangkas karena bidang hover tombol sudah memberi jarak —
   ikonnya tetap ±1em dari tepi. */
.datepicker__box { padding-inline-end: 0.35em; }
.datepicker__toggle .icon { --icon-size: 1.2rem; }
.datepicker[data-open="true"] .datepicker__toggle { background: var(--accent-mid); }
.datepicker[data-open="true"] .datepicker__box {
  border-color: var(--clr-black);
  box-shadow: var(--ring-field);
}

/* Panel tidak ikut selebar kolom: di formulir 30rem sel tanggal akan
   jadi 64px — terlalu renggang untuk dibaca sebagai satu bulan. */
.datepicker__panel {
  --_cell: 2.75rem;   /* 44px — ambang sasaran sentuh */
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: calc(var(--control-h) + var(--space-3xs));
  inline-size: min(100%, 22rem);
  padding: var(--space-xs);
  animation: popover-in var(--dur-fast) var(--ease-out);
}
.datepicker__panel:focus { outline: none; }
.datepicker[data-placement="top"] .datepicker__panel {
  inset-block: auto calc(100% + var(--space-3xs));
  animation-name: popover-in-up;
}

.datepicker__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2xs);
  margin-block-end: var(--space-2xs);
}
/* Judul bulan memakai Farro — satu-satunya huruf display di panel, jadi
   ia yang memberi tahu "ini kalender Tutooria", bukan kalender sistem. */
.datepicker__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  min-block-size: var(--control-h-small);
  padding-inline: var(--space-2xs);
  margin-inline-start: calc(var(--space-2xs) * -0.5);
  border: 0;
  border-radius: var(--radius-s);
  background: transparent;
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-snug);
  color: var(--fg);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.datepicker__title:hover { background: color-mix(in oklab, var(--fg) 7%, transparent); }
.datepicker__chevron {
  --icon-size: 1rem;
  stroke-width: 2.5;
  transition: rotate var(--dur-base) var(--ease-out);
}
.datepicker[data-view="year"] .datepicker__chevron { rotate: 180deg; }

.datepicker__nav { display: flex; gap: var(--space-3xs); }
.datepicker[data-view="year"] .datepicker__nav { visibility: hidden; }
.datepicker__nav .icon { --icon-size: 1.1rem; }

/* Tinggi badan dipatok = kepala hari + 6 baris, supaya berpindah antara
   tanggal, bulan, dan tahun tidak membuat panel melompat. */
.datepicker__body {
  position: relative;   /* acuan offsetTop untuk menggulir daftar tahun */
  block-size: calc(2rem + var(--_cell) * 6 + var(--space-3xs) * 5);
  overflow: hidden;
}
.datepicker__body > * { animation: datepicker-swap var(--dur-fast) var(--ease-out); }
@keyframes datepicker-swap { from { opacity: 0; } }
.datepicker[data-view="year"] .datepicker__body {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.datepicker__grid {
  inline-size: 100%;
  border-collapse: separate;
  border-spacing: 0 var(--space-3xs);
  margin-block-start: calc(var(--space-3xs) * -1);
  table-layout: fixed;
}
.datepicker__grid th { block-size: 2rem; padding: 0; text-align: center; }
.datepicker__grid td { padding: 0; block-size: var(--_cell); }

.datepicker__cell {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 100%;
  block-size: 100%;
  padding: 0;
  border: 0;
  border-radius: var(--radius-s);
  background: transparent;
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
/* Di pilihan tahun & bulan, titik mulai langsung disorot. Di kalender
   tanggal tidak — sorotan pada tanggal acak mudah dikira terpilih. */
.datepicker__cell:hover,
.datepicker__cell:focus-visible,
.datepicker:not([data-view="day"]) .datepicker__cell[tabindex="0"] { background: var(--accent-mid); }
.datepicker__cell[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: var(--weight-display);
}
.datepicker__cell[aria-disabled="true"] {
  background: transparent;
  color: var(--clr-gray-300);
  cursor: not-allowed;
}
/* Cincin fokus di dalam sel — di luar ia akan menabrak sel tetangga. */
.datepicker__cell:focus-visible {
  outline: var(--border-bold) solid var(--clr-black);
  outline-offset: calc(var(--border-bold) * -1);
  border-radius: var(--radius-s);
}
.datepicker__day[aria-current="date"]::after {
  content: "";
  position: absolute;
  inset-block-end: 0.4rem;
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: 0.25rem;
  block-size: 0.25rem;
  border-radius: var(--radius-pill);
  background: currentColor;
}

.datepicker__months,
.datepicker__years {
  display: grid;
  gap: var(--space-3xs);
}
/* Bulan: 3 × 4 mengisi penuh tinggi badan — sasaran ketuk besar. */
.datepicker__months {
  block-size: 100%;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(4, 1fr);
}
.datepicker__years {
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: var(--_cell);
  padding-block: var(--space-3xs);
}


/* ##########################################################################
   D. NAVIGASI
   ########################################################################## */

/* =======================================================
   HEADER SITUS
   ======================================================= */
.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-header);
  background: color-mix(in oklab, var(--bg) 88%, transparent);
  backdrop-filter: blur(14px);
  border-block-end: var(--border-hair) solid transparent;
  transition: border-color var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out);
}
.site-header.is-stuck {
  border-block-end-color: var(--line);
  background: color-mix(in oklab, var(--bg) 96%, transparent);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  block-size: var(--header-h);
}

/* --- Logo --- */
.logo {
  display: block;
  flex: none;
  line-height: 0;
  text-decoration: none;
}
.logo__img {
  block-size: var(--logo-h);
  inline-size: auto;
  aspect-ratio: var(--logo-ratio);
  transition: opacity var(--dur-fast) var(--ease-out);
}
.logo:hover .logo__img { opacity: 0.7; }
.logo:focus-visible { border-radius: var(--radius-s); }

/* =======================================================
   NAVBAR — [Logo] … [Menu] [CTA]. Jarak lega antara kelompok menu dan
   tombol CTA supaya keduanya terbaca sebagai dua kelompok terpisah.
   ======================================================= */
.nav { display: flex; align-items: center; gap: var(--space-xl); }
.nav { flex: 1 1 auto; }   /* mengisi ruang antara logo dan tombol — lihat CARI GLOBAL */

/* --- CARI GLOBAL ---
   Bilah atas kini selebar viewport (.container--fluid), dan ruang menganggur
   di tengahnya dipakai kotak cari. Formnya duduk di dalam .nav: di bawah
   64rem .nav berubah jadi panel mobile, jadi kotak cari ikut pindah ke sana
   tanpa markup kedua. margin-inline-end: auto yang mendorong daftar menu dan
   tombol tetap menempel ke kanan berapa pun lebar layarnya. */
.site-search {
  /* Mengisi SELURUH ruang antara logo dan menu — tanpa batas lebar. Batas
     34rem sempat ada dan dicabut user 24 Sep 2026: di layar lebar kotak
     cari yang berhenti di tengah menyisakan lubang kosong yang lebih
     mencolok daripada kotak cari yang panjang. Keterbacaan tidak jadi
     masalah karena isinya satu baris pendek, bukan paragraf. */
  flex: 1 1 auto;
  min-inline-size: 0;          /* boleh menyusut; tanpa ini <input> memaksa bilah meluber */
}
.site-search .input { block-size: var(--control-h-sm, 2.5rem); }
@media (max-width: 63.99rem) {
  /* Di panel mobile ia jadi baris penuh di atas daftar menu. */
  .site-search { flex: none; max-inline-size: none; margin-inline-end: 0; }
  .site-search .input { block-size: var(--control-h); }
}
.nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  list-style: none;
  margin: 0;
  padding: 0;
}
/* NAV LINK — tautan menu utama: garis bawah 2px yang memuai saat hover,
   menetap saat aktif. */
.nav__link {
  position: relative;
  /* inline-flex untuk SEMUA butir, termasuk <a>. Kalau tautan dibiarkan
     `display: inline`, padding-block-nya tidak menambah tinggi baris,
     sementara <button> yang inline-flex bersifat atomik sehingga padding-nya
     dihitung penuh — itu yang membuat baseline "Kelas" turun 4,5px. */
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  /* Seukuran label tombol di sebelahnya (--control-font, 15px), bukan
     --step--1. Menu dan tombol CTA duduk di satu baris header; dua ukuran
     huruf yang berbeda di sana terbaca sebagai dua sistem, bukan dua
     peran. Yang membedakan bobotnya tinggal bidang dan garis bawah. */
  font-size: var(--control-font);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-snug);
  text-decoration: none;
  padding-block: var(--space-2xs);
  color: var(--fg);
  transition: color var(--dur-fast) var(--ease-out);
}
.nav__link::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: var(--border-bold);
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: inline-start;
  transition: transform var(--dur-base) var(--ease-out);
}
.nav__link:hover::after { transform: scaleX(1); }
.nav__link[aria-current="page"] { font-weight: var(--weight-semibold); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

/* --- Dropdown "Kelas" --- */
.nav__item { position: relative; }

/* Pemicu dropdown adalah <button> sungguhan, bukan tautan palsu:
   ia mengubah state di halaman, bukan berpindah halaman. */
.nav__trigger {
  border: 0;
  /* padding bawaan <button> membuat garis bawahnya lebih lebar dari tautan lain */
  padding-inline: 0;
  background: none;
  /* Sengaja TIDAK menyetel font apa pun di sini. Baik shorthand `font: inherit`
     maupun `font-size: inherit` akan mengalahkan --control-font milik
     .nav__link (spesifisitas sama, aturan ini ditulis belakangan). Keluarga
     huruf sudah ditangani reset `button { font: inherit }` di base.css. */
  cursor: pointer;
}
.nav__chevron { transition: transform var(--dur-base) var(--ease-out); }
.nav__trigger[aria-expanded="true"] .nav__chevron { transform: rotate(180deg); }
.nav__trigger[aria-expanded="true"]::after { transform: scaleX(1); }
/* Butir dropdown yang sedang aktif ikut menandai pemicunya. */
.nav__item:has([aria-current="page"]) .nav__trigger { font-weight: var(--weight-semibold); }
.nav__item:has([aria-current="page"]) .nav__trigger::after { transform: scaleX(1); }

/* Panelnya .popover; di sini hanya posisi & cara muncul. */
.nav__menu {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2xs));
  inset-inline-start: calc(var(--space-2xs) * -1);
  padding: var(--space-m);
  box-shadow: var(--shadow-m);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.35rem);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              visibility 0s linear var(--dur-fast);
}
.nav__menu[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

/* Tiga kolom: satu per jenjang (SD · SMP · SMA & SMK). Dulu dua kolom
   rumpun keilmuan bergaya kampus; sejak 24 Sep 2026 sumbu utamanya jenjang,
   karena itu yang pertama dipilih murid atau orang tuanya. */
.nav__menu-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(10rem, 1fr));
  gap: var(--space-l);
}
/* Baris terakhir tiap kolom: "Semua kelas SD" dst. Dipisah garis tipis
   supaya terbaca sebagai jalan keluar, bukan mata pelajaran kesekian. */
.nav__leaf--all {
  margin-block-start: var(--space-2xs);
  padding-block-start: var(--space-2xs);
  border-block-start: var(--border-hair) solid var(--line);
  border-radius: 0;
  color: var(--fg-muted);
}
.nav__leaf--all:hover { color: var(--fg); }
.nav__col-title { margin-block-end: var(--space-2xs); }

/* Baris mata pelajaran memakai .menu-item; namanya tidak boleh patah
   sehingga kolomnya menyempit sendiri. */
.nav__leaf { white-space: nowrap; }

/* --- Dua tombol aksi di ujung kanan --- */
.nav__cta { display: flex; align-items: center; gap: var(--space-2xs); }

.nav-toggle { display: none; flex: none; }

/* CTA di bilah header — hanya untuk tablet & ponsel besar. Di sana menu
   tersembunyi di balik tombol ikon, dan bilah selebar 480–1023px terasa
   kosong kalau hanya berisi logo + ikon. Di bawah 30rem ruangnya tidak
   cukup, jadi CTA hanya ada di dalam panel. Selektor dua kelas sengaja:
   .button (display: inline-flex) punya spesifisitas sama. */
.site-header__inner > .site-header__cta { display: none; }
@media (min-width: 30rem) and (max-width: 63.99rem) {
  .site-header__inner > .site-header__cta { display: inline-flex; margin-inline-start: auto; }
}

/* --- Navigasi mobile & tablet (< lg) --- */
@media (max-width: 63.99rem) {
  .nav-toggle { display: grid; }
  .nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    /* Panel bisa lebih tinggi dari layar. Body dikunci saat panel terbuka,
       jadi panel sendiri yang harus bisa digulir. */
    max-block-size: calc(100dvh - var(--header-h));
    overflow-y: auto;
    overscroll-behavior: contain;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-l);
    padding: var(--space-xl) var(--gutter) var(--space-2xl);
    background: var(--bg);
    border-block-end: var(--border-bold) solid var(--clr-black);
    box-shadow: var(--shadow-l);
    transform: translateY(-110%);
    visibility: hidden;
    transition: transform var(--dur-base) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .nav[data-open="true"] {
    transform: translateY(0);
    visibility: visible;
    transition-delay: 0s;
  }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__link {
    /* block, bukan inline: garis pemisah harus selebar panel. */
    display: block;
    font-family: var(--font-display);
    font-weight: var(--weight-display);
    font-size: var(--text-h3);
    color: var(--fg);
    padding-block: var(--space-s);
    border-block-end: var(--border-hair) solid var(--line);
  }
  .nav__link::after { display: none; }
  /* Bidang ungu selebar panel penuh — kalau hanya ditarik sebagian,
     tepinya tidak sejajar dengan tepi panel maupun teks tautan lain. */
  .nav__link[aria-current="page"] {
    background: var(--accent);
    color: var(--accent-fg);
    padding-inline: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    border-block-end-color: transparent;
  }
  /* display ditulis ulang: .nav__link { display: block } di atas punya
     spesifisitas sama dan menang karena urutan. */
  .nav__trigger {
    display: flex;
    inline-size: 100%;
    justify-content: space-between;
    text-align: start;
  }
  /* Di panel mobile dropdown tidak mengambang — ia memuai di tempat,
     memakai trik grid 0fr→1fr yang sama seperti akordeon. */
  .nav__menu {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: transparent;
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--dur-base) var(--ease-inout);
  }
  .nav__menu[data-open="true"] { grid-template-rows: 1fr; }
  .nav__menu-cols {
    overflow: hidden;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-m);
  }
  .nav__col-title { padding-inline-start: var(--space-m); }
  .nav__menu .menu-list { gap: 0; }
  .nav__leaf {
    font-size: var(--step-0);
    padding: var(--space-xs) 0 var(--space-xs) var(--space-m);
    border-radius: 0;
    border-block-end: var(--border-hair) solid var(--line);
  }

  .nav__cta { flex-direction: column; align-items: stretch; gap: var(--space-2xs); }
  .nav .button { justify-content: center; }
}

/* Tablet: dua tombol aksi di panel berdampingan sama lebar. */
@media (min-width: 30rem) and (max-width: 63.99rem) {
  .nav__cta { flex-direction: row; }
  .nav__cta .button { flex: 1; }
}

/* =======================================================
   TAB — penyaring sumbu utama. Teks bergaris bawah, bukan pil, supaya
   kepala halaman terasa ringan. Bentuk garis sama dengan .nav__link.
   ======================================================= */
.tabs {
  display: flex;
  gap: var(--space-l);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  position: relative;
  flex: none;
  padding: var(--space-s) 0;
  border: 0;
  background: none;
  /* Seukuran label tombol: tab dan tombol berdiri berdampingan di kepala
     katalog, dan dua ukuran huruf di baris yang sama langsung terbaca
     sebagai salah. Ikut --control-font supaya keduanya tidak bisa
     berpisah lagi. */
  font-size: var(--control-font);
  font-weight: var(--weight-medium);
  /* Tiap kata berhuruf besar di awal — jadi label tab tetap seragam
     walau teksnya di markup ditulis dengan huruf kecil. */
  text-transform: capitalize;
  color: var(--fg-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
}
.tab::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: calc(var(--border-hair) * -1);
  block-size: var(--border-bold);
  z-index: 1;
  background: var(--line-strong);
  transform: scaleX(0);
  transition: transform var(--dur-base) var(--ease-out);
}
.tab:hover { color: var(--fg); }
.tab:is([aria-pressed="true"], [aria-selected="true"], [aria-current="page"]) { color: var(--fg); font-weight: var(--weight-semibold); }
.tab:is([aria-pressed="true"], [aria-selected="true"], [aria-current="page"])::after { transform: scaleX(1); }

/* --pill — segmen di dalam trek abu, tanpa garis bawah.
   Dipakai untuk saklar TINGKAT HALAMAN ("bagian mana dari halaman ini yang
   sedang saya lihat"). .tabs biasa, yang bergaris bawah, dipakai penyaring
   DI DALAM salah satu bagian itu. Dua peran yang berbeda harus punya bentuk
   yang berbeda: dua baris tab bergaris bawah yang bertumpuk membuat orang
   mengira keduanya menyaring hal yang sama.

   Segmen aktif memakai bidang HITAM, bukan ungu: ungu di atas putih hanya
   boleh jadi bidang besar, dan huruf putih di atasnya tidak akan terbaca
   (2,21:1). Hitam/putih di sini 17:1. */
.tabs--pill {
  gap: var(--space-3xs);
  padding: var(--space-3xs);
  border-radius: var(--radius-pill);
  background: var(--bg-canvas-strong);
}
.tabs--pill .tab {
  /* Segmen berbagi lebar rata: saklar dua pilihan yang lebarnya mengikuti
     panjang teks terbaca sebagai dua tombol lepas, bukan satu saklar. */
  flex: 1;
  /* 12px atas-bawah, bukan 8px: segmennya jadi setinggi 46px dan memenuhi
     sasaran ketuk 44px yang dipakai seluruh kontrol di peranti sentuh. */
  padding: var(--space-xs) var(--space-s);
  border-radius: var(--radius-pill);
  text-align: center;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.tabs--pill .tab::after { content: none; }
.tabs--pill .tab:is([aria-pressed="true"], [aria-selected="true"]) {
  background: var(--clr-black);
  color: var(--clr-white);
}

/* =======================================================
   FOOTER — permukaan hitam (token dibalik di base.css).
   Tata letaknya memakai .row: merek selebar penuh di ponsel & tablet,
   lalu merek + empat kolom tautan sebaris mulai lg.
   ======================================================= */
.site-footer {
  padding-block: var(--space-3xl) var(--space-xl);
}
.site-footer a { text-decoration: none; }
.site-footer a:hover { text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 2px; }

.footer__top {
  --row-gap: var(--space-xl);
  padding-block-end: var(--space-2xl);
  border-block-end: var(--border-hair) solid var(--line);
}
/* Saat blok merek selebar baris, tautan logo (display: block) ikut
   memanjang dan area ketuknya meluber ke ruang kosong di kanan logo. */
.footer__top .logo { display: inline-block; }
.footer__pitch { max-inline-size: 30ch; font-size: var(--step--1); margin-block-start: var(--space-m); }
.footer__col-title { margin-block-end: var(--space-s); }
.footer__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2xs); font-size: var(--step--1); }
/* Layar sentuh: tautan footer hanya 17px tinggi dengan jarak 8px. Di sini
   jaraknya dipindah ke dalam tautan, jadi tiap baris ±46px dan seluruhnya
   bisa diketuk — tanpa area ketuk yang saling tumpang tindih. */
@media (pointer: coarse) {
  .footer__list { gap: 0; }
  .footer__list a { display: inline-block; padding-block: var(--space-xs); }
}
.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding-block-start: var(--space-l);
  font-size: var(--step--2);
  color: var(--fg-muted);
}


/* ##########################################################################
   E. BLOK ISI
   ########################################################################## */

/* =======================================================
   FITUR BERNOMOR — nomor memakai .emblem, bukan teks ungu.
   Deretannya memakai .row (row-cols) di halaman.
   ======================================================= */
.feature {
  position: relative;
  padding-block-start: var(--space-l);
  border-block-start: var(--border-bold) solid var(--line-strong);
}
.feature__title { font-size: var(--text-h3); margin-block: var(--space-xs) var(--space-2xs); }
.feature__body { color: var(--fg); font-size: var(--step--1); }

/* =======================================================
   STATISTIK — angka besar + label.
   ======================================================= */
.stat__value {
  font-family: var(--font-display);
  font-size: var(--text-h1);
  font-weight: var(--weight-display);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  /* Angka tidak boleh dipatah ("68.00 / 0") oleh overflow-wrap dari
     base.css. Kalau kolomnya terlalu sempit, jumlah kolomnya yang diubah. */
  white-space: nowrap;
  overflow-wrap: normal;
}
.stat__label {
  margin-block-start: var(--space-2xs);
  font-size: var(--step--1);
  color: var(--fg);
  text-wrap: balance;
}

/* =======================================================
   KARTU TUTOR — .card.card--flush.card--interactive + foto berukuran
   tetap, teks di atasnya.
   Kartu memakai aspect-ratio, bukan tinggi mengikuti isi. Itu kuncinya:
   tombol bisa memuai tanpa mengubah tinggi kartu, sehingga baris grid
   tidak pernah melompat saat kursor lewat.
   ======================================================= */
.tutor-card {
  position: relative;
  display: block;
  /* 4/5 = rasio berkas fotonya, jadi tidak ada bagian yang terpangkas. */
  aspect-ratio: 4 / 5;
  /* Container query: isinya menyesuaikan LEBAR KARTU, bukan lebar layar. */
  container: tutor-card / inline-size;
  background: var(--clr-gray-100);
}

/* Tempat foto pengajar; bidang abu jadi cadangan bila foto gagal muat. */
.tutor-card__photo {
  position: absolute;
  inset: 0;
  background: var(--clr-gray-100);
}
.tutor-card__photo img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Wajah ada di bagian atas bingkai; teks menutup bagian bawah. */
  object-position: center top;
}

/* Teks duduk langsung di atas foto. Gradien gelap di sini BUKAN hiasan
   melainkan scrim: satu-satunya cara menjamin teks putih terbaca di atas
   foto apa pun. Itu sebabnya ia pengecualian dari aturan "tanpa gradien". */
.tutor-card__overlay {
  /* Satu nilai untuk padding, jarak tombol, dan jarak geser hover —
     ketiganya wajib sama. Mode ringkas cukup mengganti variabel ini. */
  --_pad: var(--space-m);
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  padding: var(--_pad);
  color: var(--clr-white);
  /* padding-atas ekstra + gradien panjang: saat lapisan digeser turun,
     teks harus tetap berada di atas bagian yang gelap. */
  padding-block-start: var(--space-3xl);
  background: linear-gradient(to top,
              rgb(var(--shadow-color) / 0.90) 0%,
              rgb(var(--shadow-color) / 0.80) 35%,
              rgb(var(--shadow-color) / 0.45) 65%,
              rgb(var(--shadow-color) / 0) 100%);
}
/* Badge jenjang duduk di atas foto, bukan di judul: "Matematika SMA" sebagai
   satu kalimat membuat mata membaca jenjang sebagai bagian dari nama mata
   pelajaran, dan kartu jadi sulit dipindai saat 5 kartu sebaris. Putih —
   bukan ungu — karena ia melayang di atas foto yang warnanya tak bisa
   ditebak, dan pil putih selalu terbaca. z-index 1 menaruhnya di atas scrim. */
.tutor-card__level {
  position: absolute;
  z-index: 1;
  inset-block-start: var(--space-s);
  inset-inline-start: var(--space-s);
}
@container tutor-card (max-width: 14rem) {
  .tutor-card__level { inset-block-start: var(--space-2xs); inset-inline-start: var(--space-2xs); }
}

.tutor-card__subject {
  font-size: var(--step-0);
  line-height: var(--leading-snug);
  color: var(--clr-white);
}
.tutor-card__tutor {
  margin-block-start: var(--space-3xs);
  font-size: var(--step--1);
  color: rgb(255 255 255 / 0.78);
}
/* Tombol di bawah teks; jaraknya SAMA dengan padding bawah lapisan, jadi
   geseran (padding + tinggi tombol) menyembunyikannya tepat di tepi kartu. */
.tutor-card__cta {
  margin-block-start: var(--_pad);
  inline-size: 100%;
}

/* Mode ringkas — kartu di bawah 14rem. Padding, huruf, dan tombol turun
   satu jenjang. --control-h ditimpa di lapisan, bukan di tombol, karena
   jarak geser hover juga membacanya. */
@container tutor-card (max-width: 14rem) {
  .tutor-card__overlay {
    --_pad: var(--space-xs);
    --control-h: var(--control-h-small);
    --control-pad: var(--control-pad-small);
    --control-font: var(--control-font-small);
    padding-block-start: var(--space-2xl);
  }
  .tutor-card__subject { font-size: var(--step--1); }
  .tutor-card__tutor { font-size: var(--step--2); }
}

/* Saat tidak di-hover, lapisan teks digeser turun sejauh tinggi tombol +
   jaraknya, sehingga tombol terpotong `overflow: clip`. Hover menariknya
   kembali — tanpa ruang kosong DAN tanpa mengubah tinggi kartu. */
@media (hover: hover) and (pointer: fine) {
  .tutor-card__overlay {
    transform: translateY(calc(var(--_pad) + var(--control-h)));
    transition: transform var(--dur-base) var(--ease-out);
  }
  .tutor-card:hover .tutor-card__overlay,
  .tutor-card:focus-within .tutor-card__overlay { transform: none; }
}

/* =======================================================
   TESTIMONI — .card + tanda kutip (.emblem) + penulis (.avatar).
   ======================================================= */
.quote { gap: var(--space-m); block-size: 100%; }
/* Tanda kutip Farro duduk tinggi di kotaknya; diturunkan supaya tampak
   di tengah. */
.quote__mark { padding-block-start: 0.3em; font-size: calc(var(--_size) * 0.9); }
.quote__text {
  font-size: var(--step-0);
  line-height: var(--leading-normal);
  flex: 1;
}
.quote__author { display: flex; align-items: center; gap: var(--space-s); }
.quote__name { font-weight: var(--weight-semibold); font-size: var(--step--1); }
.quote__role { font-size: var(--step--2); color: var(--fg-muted); }

/* =======================================================
   SLIDER TESTIMONI (Swiper)
   Sebelum Swiper hidup (atau kalau pustakanya gagal dimuat) seksinya
   tetap terbaca sebagai grid biasa — bukan tumpukan yang rusak.
   ======================================================= */
.swiper:not(.swiper-initialized) .swiper-wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--col-min-m), 1fr));
  gap: var(--grid-gap) var(--grid-col-gap);
}
.swiper-slide { block-size: auto; }
.swiper-slide .quote { block-size: 100%; }

.slider-nav {
  display: none;
  gap: var(--space-2xs);
  justify-content: flex-end;
  margin-block-start: var(--space-l);
}
[data-testimonial-slider][data-ready="true"] .slider-nav { display: flex; }
.slider-nav .icon-button.swiper-button-disabled {
  opacity: 0.35;
  pointer-events: none;
}

/* =======================================================
   PAKET HARGA — .card + subgrid lintas kartu.
   Paket unggulan dibedakan lewat badge .plan__flag "Paling dipilih" dan
   CTA .button--primary-2, bukan permukaan hitam.
   ======================================================= */
.plan {
  gap: var(--space-m);
  padding: var(--space-xl) var(--space-l);
  block-size: 100%;
  position: relative;
}

/* Subgrid menyamakan tinggi tiap baris LINTAS kartu — sehingga harga,
   daftar fitur, dan tombol berbaris rapi meski panjang deskripsinya
   berbeda. Browser tanpa subgrid tetap aman memakai flex dari .card. */
@supports (grid-template-rows: subgrid) {
  .plans { grid-template-rows: auto auto 1fr auto; }
  .plan {
    display: grid;
    grid-row: span 4;
    grid-template-rows: subgrid;
    align-content: start;
  }
}

.plan__flag {
  position: absolute;
  inset-block-start: var(--space-m);
  inset-inline-end: var(--space-m);
}
.plan__name { font-size: var(--text-h3); }
.plan__price {
  font-family: var(--font-display);
  font-size: var(--text-h1);
  font-weight: var(--weight-display);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
}
/* Satuan dipisah ke barisnya sendiri: menempel di baseline angka display
   membuatnya mewarisi letter-spacing rapat dan huruf saling menabrak. */
.plan__period {
  display: block;
  margin-block-start: var(--space-3xs);
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: var(--weight-body);
  letter-spacing: normal;
  line-height: var(--leading-normal);
  color: var(--fg-muted);
}
.plan__desc { font-size: var(--step--1); color: var(--fg); }

/* Daftar centang — fitur paket. Ikon hitam; di kartu hitam ungu. */
.check-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-xs); align-content: start; }
.check-list li {
  display: flex;
  align-items: start;
  gap: var(--space-2xs);
  font-size: var(--step-0);
  line-height: var(--leading-normal);
}
.check-list svg { flex: none; --icon-size: 1.15em; color: var(--fg); margin-block-start: 0.15em; }
.surface-invert .check-list svg { color: var(--accent); }
.check-list li[data-off] { color: var(--fg-muted); text-decoration: line-through; text-decoration-color: var(--fg-subtle); }
.check-list li[data-off] svg { color: var(--fg-subtle); }

/* =======================================================
   AKORDEON (FAQ)
   ======================================================= */
.accordion { border-block-start: var(--border-hair) solid var(--line); }
.accordion__item { border-block-end: var(--border-hair) solid var(--line); }
.accordion__trigger {
  inline-size: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  padding-block: var(--space-m);
  /* <button> punya padding-inline bawaan ~6px; kalau tidak dinolkan,
     judul akordeon menjorok dan tidak sejajar dengan judul seksi. */
  padding-inline: 0;
  border: 0;
  background: none;
  text-align: start;
  font-family: var(--font-display);
  font-size: var(--text-h4);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-normal);
  cursor: pointer;
}
/* Kotak "+" — bentuk .icon-button kecil, tapi bukan tombol sendiri:
   yang bisa diklik seluruh baris. */
.accordion__icon {
  flex: none;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  display: grid;
  place-items: center;
  border: var(--border-bold) solid var(--line-strong);
  border-radius: var(--radius-s);
  transition: transform var(--dur-base) var(--ease-spring),
              background var(--dur-base) var(--ease-out);
}
.accordion__icon .icon { --icon-size: 1rem; }
.accordion__trigger:hover .accordion__icon { background: var(--accent); }
.accordion__trigger[aria-expanded="true"] .accordion__icon {
  transform: rotate(45deg);
  background: var(--accent);
}
.accordion__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-base) var(--ease-inout);
}
.accordion__panel[data-open="true"] { grid-template-rows: 1fr; }
.accordion__panel > div { overflow: hidden; }
.accordion__panel p {
  padding-block-end: var(--space-m);
  max-inline-size: var(--measure);
  color: var(--fg);
  font-size: var(--step-0);
}

/* Berkotak — tiap butir bidang abu cekung dengan chevron, judul Inter
   Grotesk. Untuk daftar yang dipindai cepat (bab materi), bukan FAQ.
   Bagian tambahan: __label (judul, memuai) dan __meta (angka ringkas). */
.accordion--boxed { display: grid; gap: var(--space-2xs); border-block-start: 0; }
.accordion--boxed .accordion__item { border-block-end: 0; border-radius: var(--radius-m); background: var(--bg-sunken); }
.accordion--boxed .accordion__trigger {
  gap: var(--space-s);
  padding: var(--space-s);
  border-radius: var(--radius-m);
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
}
.accordion__label { flex: 1; min-inline-size: 0; }
.accordion__meta {
  flex: none;
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.accordion--boxed .accordion__icon { inline-size: auto; block-size: auto; border: 0; }
.accordion--boxed .accordion__icon .icon { --icon-size: 1.1rem; }
.accordion--boxed .accordion__trigger:hover .accordion__icon { background: none; }
.accordion--boxed .accordion__trigger[aria-expanded="true"] .accordion__icon { transform: rotate(180deg); background: none; }
.accordion--boxed .accordion__panel .menu-list { padding: 0 var(--space-2xs) var(--space-2xs); }

/* =======================================================
   MARQUEE (pita berjalan)
   Jarak antar-item hanya diatur di dalam track (gap + padding penutup),
   bukan di pembungkus — kalau keduanya dipakai, sambungan antar salinan
   melebar dan putarannya terlihat tersendat.
   ======================================================= */
.marquee {
  display: flex;
  overflow: hidden;
  user-select: none;
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--space-l);
  min-inline-size: 100%;
  padding-inline-end: var(--space-l);
  animation: marquee-scroll 38s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-h2);
  white-space: nowrap;
  color: var(--fg);
}
.marquee__item::after {
  content: "";
  inline-size: 0.32em;
  block-size: 0.32em;
  border-radius: var(--radius-pill);
  background: var(--fg);
  flex: none;
}
@keyframes marquee-scroll {
  to { transform: translateX(-100%); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee { mask-image: none; flex-wrap: wrap; gap: var(--space-l); }
}
