@import url("./tokens.css");

@font-face {
  font-family: "Plus Jakarta Sans";
  src: url("../fonts/plus-jakarta-sans-latin.woff2") format("woff2");
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
}

/* ============================================================================
   ALIAS LOKAL — satu-satunya blok yang boleh memuat nilai literal.
   Semua warna berakhir di token Design System (tokens.css di atas).
   --canvas DIHAPUS: Design System sudah menyediakannya dengan nilai identik.
   --yellow / --surface / --ink / --text DIPERTAHANKAN sebagai nama token lokal
   karena assets/js/app.js menulis ulang keempatnya saat runtime (Appearance).
   Font: ADR-G04 — --font-display & --font-body di-override ke Plus Jakarta Sans.
   --font-mono ikut di-override (owner, 12 Sep 2026): label kecil (kicker, meta,
   eyebrow, baris data) dan angka harga di semua blok TIDAK boleh dipaksa monospace —
   Geist Mono tidak pernah dimuat sebagai berkas, jadi yang tampil adalah monospace
   sistem yang sulit dibaca. Kerataan angka tetap dijaga oleh font-variant-numeric:
   tabular-nums yang sudah ada di selektor harga.
   ============================================================================ */
:root {
  --font-display: "Plus Jakarta Sans", Arial, sans-serif;
  --font-body: "Plus Jakarta Sans", Arial, sans-serif;
  --font-mono: "Plus Jakarta Sans", Arial, sans-serif;

  --ink: var(--ink-900);
  --text: var(--text-body);
  --muted: var(--text-muted);
  --line: var(--border-default);
  --soft: var(--bg-subtle);
  --surface: var(--bg-card);
  --yellow: var(--brand);
  /* Tinggi baris bilah navigasi bawah. Dipakai DUA tempat — tinggi tombolnya
     sendiri dan ruang bawah `.site-shell` — jadi ia harus punya satu sumber. */
  --bottom-nav-row: 58px;
  --green: var(--accent-mint);
  --warning: var(--accent-orange);
  --red: var(--tag-danger-text);
  --shell: 560px;
  --pad: var(--space-6);
  --radius: var(--radius-lg);
  --shadow: var(--shadow-pop);
}

* { box-sizing: border-box; }
/* Tombol yang menggantikan anchor (tujuannya kini dari Settings lewat Action Engine)
   harus mewarisi tipografi dan tidak membawa gaya bawaan browser. */
.link-list button, .schedule-contact button, .consultation > button { width: 100%; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  min-width: 320px;
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
body.drawer-is-open,
body:has(dialog[open]) { overflow: hidden; }
button, input { font: inherit; }
button, a { -webkit-tap-highlight-color: transparent; }
button { color: inherit; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* Ikon BIDANG (Material Symbols, ms-*) menyatakan dirinya lewat atribut
   fill="currentColor" stroke="none". Reset di atas mematikannya: CSS selalu
   mengalahkan atribut presentasi, jadi `fill: none` menang dan ikonnya
   digambar TANPA isi — tampak seperti tidak muncul sama sekali, bukan seperti
   galat. Aturan ini mengembalikannya, dicocokkan lewat `stroke="none"` yang
   HANYA dipasang toSvg() pada ikon berbendera filled. */
svg[stroke="none"] { fill: currentColor; stroke: none; }

.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* Lewati-ke-konten: menyalin pola yang sudah dipakai app admin (`admin/assets/admin.css`)
   supaya satu tautan berperilaku sama di dua aplikasi. Ia disembunyikan dengan
   `transform`, BUKAN `display:none`/`visibility`, karena keduanya membuat tautan
   hilang dari urutan fokus keyboard — persis fungsi yang seharusnya ia sediakan.
   `position: fixed` menahannya di luar `.site-shell` yang ber-`overflow: clip`. */
.skip-link { position: fixed; z-index: 999; top: 8px; left: 8px; padding: 10px 14px; background: var(--ink); color: var(--text-on-dark); transform: translateY(-150%); }
.skip-link:focus { transform: none; }

:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 3px; }
.site-shell { position: relative; width: 100%; max-width: var(--shell); min-height: 100vh; margin: 0 auto; background: var(--surface); border-inline: 1px solid var(--border-input); overflow: clip; }
.site-header { position: sticky; z-index: 30; top: 0; height: 68px; padding: 0 var(--pad); display: flex; align-items: center; justify-content: space-between; background: rgba(255, 255, 255, .94); border-bottom: 1px solid rgba(230, 232, 238, .9); backdrop-filter: blur(14px); }
.brand { width: 137px; overflow: hidden; }
.brand img { width: 137px; height: auto; }
.header-actions { display: flex; gap: 17px; align-items: center; }
.text-action { display: inline-flex; gap: 7px; align-items: center; padding: 8px 0; border: 0; background: none; font-size: var(--text-2xs); font-weight: 700; cursor: pointer; }
.text-action svg { width: 17px; height: 17px; }

.hero-pad { padding: 24px var(--pad) 22px; }
.hero-meta { display: flex; justify-content: space-between; align-items: center; margin-bottom: 17px; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 700; letter-spacing: .11em; text-transform: uppercase; }
.hero-meta strong { color: var(--text); font-size: var(--text-2xs); letter-spacing: .13em; }
.search-box { height: 50px; padding: 0 14px; display: flex; align-items: center; gap: 10px; background: var(--bg-subtle); border: 1px solid var(--border-default); border-radius: 13px; cursor: text; }
.search-box svg { width: 18px; height: 18px; color: var(--text-subtle); }
.search-box input { min-width: 0; flex: 1; border: 0; outline: 0; background: transparent; color: var(--text); font-size: var(--text-xs); }
.search-box input::placeholder { color: var(--text-subtle); }
.search-box kbd { min-width: 23px; padding: 2px 6px; border: 1px solid var(--border-input); border-radius: 6px; background: var(--vs-white); color: var(--text-subtle); font: 600 var(--text-2xs)/17px var(--font-body); text-align: center; }
.shortcut-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 17px 8px; margin-top: 20px; }
.shortcut { min-width: 0; padding: 0; display: flex; flex-direction: column; align-items: center; border: 0; background: none; color: var(--text-muted); font-size: var(--text-2xs); font-style: normal; font-weight: 650; line-height: 1.3; text-align: center; cursor: pointer; }
.shortcut > span { position: relative; width: 51px; height: 51px; margin-bottom: 7px; display: grid; place-items: center; background: var(--bg-subtle); border: 1px solid var(--border-default); border-radius: 13px; transition: transform .2s, border-color .2s, background .2s; }
.shortcut:hover > span { transform: translateY(-2px); border-color: var(--ink-200); background: var(--vs-white); }
.shortcut svg { width: 18px; height: 18px; }

.section { padding: 34px var(--pad); border-top: 1px solid var(--border-faint); }
.section-heading { margin-bottom: 17px; display: flex; align-items: end; justify-content: space-between; }
.section-heading > div > span,
.schedule-intro > span { display: none; margin-bottom: 4px; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .18em; text-transform: uppercase; }
.section-heading h2 { margin: 0; font-size: 23px; line-height: 1.1; letter-spacing: -.045em; }
.section-heading > button { display: flex; align-items: center; gap: 5px; padding: 6px 0; border: 0; background: none; color: var(--text-muted); font-size: var(--text-2xs); font-weight: 700; cursor: pointer; }
.section-heading > button svg { width: 14px; height: 14px; }

.promo-section { padding-inline: 0; padding-top: 6px; }
.promo-slider { display: flex; gap: 10px; overflow-x: auto; padding: 0 var(--pad) 4px; scroll-padding-inline: var(--pad); scroll-snap-type: x mandatory; scrollbar-width: none; }
.promo-slider::-webkit-scrollbar, .featured-track::-webkit-scrollbar, .gallery-track::-webkit-scrollbar { display: none; }
.promo-hero { position: relative; flex: 0 0 calc(100% - 30px); aspect-ratio: 1.72 / 1; overflow: hidden; border-radius: var(--radius); background: var(--bg-ink); color: var(--text-on-dark); scroll-snap-align: start; cursor: pointer; }
.promo-hero > img { width: 100%; height: 100%; object-fit: cover; }
.promo-hero::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(0,0,0,.84), rgba(0,0,0,.28) 67%, rgba(0,0,0,.04)); }
.promo-overlay, .banner-overlay { padding: 23px; }
.promo-overlay { position: absolute; z-index: 1; inset: 0; display: flex; flex-direction: column; align-items: flex-start; }
.promo-overlay > span { color: var(--yellow); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .18em; }
.promo-overlay h2 { margin: 8px 0 7px; font-size: clamp(23px, 7.2vw, 34px); line-height: .98; letter-spacing: -.055em; }
.promo-overlay p { margin: 0; color: var(--text-on-dark-muted); font-size: var(--text-2xs); }
.promo-overlay button { margin-top: auto; padding: 0; display: flex; align-items: center; gap: 7px; border: 0; background: none; color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: 750; cursor: pointer; }
.promo-overlay button svg { width: 14px; height: 14px; }
.slider-dots { margin-top: 10px; display: flex; justify-content: center; gap: 5px; }
.slider-dots button { width: 5px; height: 5px; padding: 0; border: 0; border-radius: 50%; background: var(--ink-200); cursor: pointer; transition: width .2s, border-radius .2s, background .2s; }
.slider-dots button.is-active { width: 20px; border-radius: 10px; background: var(--ink); }

/* `.link-cards` menumpang baris ini — dan menumpangnya HARUS di sini, sebelum
   @media. Ditulis di akhir berkas, ia mengalahkan `.section { padding: 45px }`
   milik ≥1024px sementara `.compact-promos` kalah, sehingga dua tumpukan kartu
   yang katanya berperlakuan sama justru berirama berbeda di desktop. */
.compact-promos, .link-cards { padding-top: 20px; padding-bottom: 20px; }
.compact-promos { display: grid; gap: 9px; }
.compact-promo { min-width: 0; padding: 7px; display: grid; grid-template-columns: 66px 1fr 20px; gap: 12px; align-items: center; border: 1px solid var(--border-default); border-radius: 13px; background: var(--vs-white); text-align: left; cursor: pointer; transition: border-color .2s, transform .2s; }
.compact-promo:hover { border-color: var(--ink-200); transform: translateY(-1px); }
.compact-promo img { width: 66px; height: 57px; background: var(--bg-inset); object-fit: cover; border-radius: 9px; }
.compact-promo > span { min-width: 0; display: grid; }
.compact-promo small { color: var(--accent-orange); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .14em; }
.compact-promo strong { margin: 2px 0 1px; overflow: hidden; font-size: var(--text-2xs); text-overflow: ellipsis; white-space: nowrap; }
.compact-promo em { overflow: hidden; color: var(--text-subtle); font-size: var(--text-2xs); font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
.compact-promo > svg { width: 15px; height: 15px; color: var(--text-subtle); }

.featured { padding-inline: 0; }
.featured .section-heading { padding-inline: var(--pad); }
.featured-track { display: flex; gap: 11px; overflow-x: auto; padding: 0 var(--pad) 5px; scroll-padding-inline: var(--pad); scroll-snap-type: x mandatory; scrollbar-width: none; }
.featured-card { min-width: 0; flex: 0 0 196px; padding: 0; overflow: hidden; border: 1px solid var(--border-default); border-radius: 15px; background: var(--vs-white); text-align: left; scroll-snap-align: start; cursor: pointer; }
.featured-card .image-wrap { position: relative; display: block; aspect-ratio: 1.44 / 1; overflow: hidden; background: var(--bg-inset); }
.featured-card img { width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; }
.featured-card:hover img { transform: scale(1.035); }
.featured-card .image-wrap b { position: absolute; left: 10px; top: 10px; padding: 5px 7px; border-radius: 6px; background: var(--ink); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: 750; }
.featured-card .card-copy { padding: 12px; display: grid; }
.featured-card strong { font-size: 12px; }
.featured-card em { margin: 4px 0 8px; font-size: var(--text-2xs); font-style: normal; font-weight: 750; }
.featured-card em small { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 500; }
.featured-card i { width: fit-content; padding: 3px 6px; border-radius: 5px; background: var(--tag-success-bg); color: var(--green); font-size: var(--text-2xs); font-style: normal; font-weight: 750; }
.featured-card i.limited { background: var(--tag-warning-bg); color: var(--tag-warning-text); }

/* Kartu grid (`.grid-card`) — anatomi kartu `card_grid` prototipe: gambar berasio 3:2
   di ATAS (badge opsional menumpang di atasnya), lalu judul, subjudul, harga, dan pil
   ketersediaan. Ia berbagi bahasa visual dengan `.featured-card` (border, radius,
   perbesaran gambar saat hover) karena di prototipe keduanya memang satu view-model.

   Kartu BARIS `.explore-card` di bawah tetap ada untuk daftar koleksi satu/dua kolom.
   Ia tidak dipakai grid lagi: pada 4 kolom kolom teksnya tersisa ~38px. */
.grid-card { min-width: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; border: 1px solid var(--border-default); border-radius: 15px; background: var(--vs-white); text-align: left; cursor: pointer; }
.grid-card .image-wrap { position: relative; display: block; aspect-ratio: 3 / 2; overflow: hidden; background: var(--bg-inset); }
.grid-card img { width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; }
.grid-card:hover img { transform: scale(1.035); }
.grid-card .image-wrap b { position: absolute; left: 10px; top: 10px; padding: 5px 7px; border-radius: 6px; background: var(--ink); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: 750; }
/* `align-content: start` menahan isi tetap di atas ketika grid meregangkan kartu
   pendek setinggi kartu tertinggi di barisnya. */
.grid-card .card-copy { padding: 12px; display: grid; align-content: start; }
.grid-card strong { font-size: var(--text-xs); line-height: 1.25; }
.grid-card .card-copy em { margin-top: 3px; color: var(--text-subtle); font-size: var(--text-2xs); font-style: normal; line-height: 1.35; }
.grid-card .card-copy b { margin-top: 7px; font-size: var(--text-2xs); }

.explore-grid { display: grid; gap: 10px; grid-template-columns: repeat(var(--cols-mobile, 1), minmax(0, 1fr)); }
/* Thumbnail bergaya "Warna" dan "Kosong" (`thumbStyle` pada listing).
   Kolom gambar `.explore-card` bertakar tetap 102px; mode "Kosong" melepas
   elemennya, jadi kolomnya ikut dilepas — tanpa itu teksnya jatuh ke kolom
   selebar 102px dan judul terpotong satu-dua kata per baris. */
.explore-card > .card-thumb-solid { width: 102px; height: 82px; border-radius: 10px; }
.explore-card.is-thumbless { grid-template-columns: 1fr 20px; }
/* Badge kartu tanpa area gambar. Di mode lain ia ditempel absolut di atas
   gambar; tanpa gambar ia tidak punya tempat menempel, jadi ia jadi pil biasa
   di kolom teks — memilih "Kosong" tidak boleh ikut menghapus badge. */
.grid-card .card-badge, .featured-card .card-badge {
  width: fit-content; margin-bottom: 7px; padding: 4px 7px; border-radius: 6px;
  background: var(--ink); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: 750;
}
.explore-card { min-width: 0; min-height: 100px; padding: 9px; display: grid; grid-template-columns: 102px 1fr 20px; gap: 13px; align-items: center; border: 1px solid var(--border-default); border-radius: 15px; background: var(--vs-white); text-align: left; cursor: pointer; }
.explore-card > img { width: 102px; height: 82px; border-radius: 10px; background: var(--bg-inset); object-fit: cover; }
.explore-card > span { min-width: 0; display: grid; }
.explore-card small { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .12em; }
.explore-card strong { margin: 3px 0 1px; font-size: var(--text-xs); }
.explore-card em { color: var(--text-subtle); font-size: var(--text-2xs); font-style: normal; }
.explore-card b { margin-top: 8px; font-size: var(--text-2xs); }
.explore-card b i { color: var(--text-subtle); font-size: var(--text-2xs); font-style: normal; font-weight: 500; }
.explore-card > span > i { width: fit-content; margin-top: 4px; padding: 2px 5px; border-radius: 4px; background: var(--tag-warning-bg); color: var(--tag-warning-text); font-size: var(--text-2xs); font-style: normal; font-weight: 750; }
.explore-card > svg { width: 15px; height: 15px; color: var(--text-subtle); transition: transform .2s; }
.explore-card:hover > svg { transform: translateX(3px); }

.feature-promo article { position: relative; aspect-ratio: 1.02 / 1; overflow: hidden; border-radius: 18px; background: var(--bg-ink); color: var(--text-on-dark); }
.feature-promo img { width: 100%; height: 100%; object-fit: cover; }
.feature-promo article::after { content: ""; position: absolute; inset: 0; background: linear-gradient(0deg, rgba(0,0,0,.88), rgba(0,0,0,.04) 70%); }
.feature-promo article > div { position: absolute; z-index: 1; inset: auto 22px 22px; }
.feature-promo small { color: var(--yellow); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .16em; }
.feature-promo h2 { margin: 7px 0; font-size: 29px; line-height: .98; letter-spacing: -.055em; }
.feature-promo p { margin: 0 0 18px; color: var(--text-on-dark-muted); font-size: var(--text-2xs); }
.feature-promo button { padding: 10px 13px; display: flex; align-items: center; gap: 7px; border: 0; border-radius: 8px; background: var(--vs-white); color: var(--text-body); font-size: var(--text-2xs); font-weight: 750; cursor: pointer; }
.feature-promo button svg { width: 13px; height: 13px; }

/* Satu kolom di mobile. Sebelumnya kartu ini SELALU baris: di lebar 375px teks
   dan tombol 102px saling menghimpit — judul pecah jadi tiga baris sementara
   tombolnya tetap sempit. Baris dikembalikan @640 ke atas, saat lebarnya memang
   cukup untuk dua kolom. */
.consultation { margin: 0 var(--pad); padding: 28px 23px; display: flex; flex-direction: column; align-items: stretch; gap: 20px; border: 0; border-radius: 18px; background: var(--ink); color: var(--text-on-dark); }
.consultation > div > span { color: var(--yellow); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .12em; }
.consultation h2 { margin: 8px 0; font-size: 25px; line-height: 1.02; letter-spacing: -.05em; }
.consultation p { margin: 0; color: var(--text-on-dark-muted); font-size: var(--text-2xs); }
.consultation > a, .consultation > button { flex: 0 0 auto; padding: 11px; display: grid; grid-template-columns: 1fr 13px; align-items: center; border: 1px solid rgba(255,255,255,.22); border-radius: 10px; font-size: var(--text-2xs); font-weight: 750; }
.consultation > a small, .consultation > button small { grid-column: 1 / -1; margin-bottom: 4px; color: var(--text-on-dark-muted); font-size: var(--text-2xs); letter-spacing: .12em; }
.consultation > a svg, .consultation > button svg { width: 13px; height: 13px; }

.highlights { padding-inline: 0; }
.highlights .section-heading { padding-inline: var(--pad); }
.gallery-track { display: flex; gap: 10px; overflow-x: auto; padding: 0 var(--pad) 5px; scroll-padding-inline: var(--pad); scroll-snap-type: x mandatory; scrollbar-width: none; }
.gallery-track button, .gallery-track > span { min-width: 0; flex: 0 0 82%; padding: 0; display: grid; border: 0; background: var(--vs-white); text-align: left; scroll-snap-align: start; cursor: zoom-in; }
.gallery-track img { width: 100%; aspect-ratio: 1.48 / 1; border-radius: 14px; object-fit: cover; }
.gallery-track span { margin-top: 9px; font-size: var(--text-2xs); font-weight: 750; }
.gallery-track small { color: var(--text-subtle); font-size: var(--text-2xs); }

.link-list { border-top: 1px solid var(--border-default); }
.link-list a, .link-list button { min-height: 67px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--border-default); }
.link-list span { display: grid; gap: 3px; }
.link-list small { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .14em; }
.link-list strong { font-size: var(--text-2xs); }
.link-list svg { width: 16px; height: 16px; color: var(--text-subtle); transition: transform .2s; }
.link-list a:hover svg, .link-list button:hover svg { transform: translate(2px, -2px); }

.accordion { border-top: 1px solid var(--border-default); }
.accordion details { border-bottom: 1px solid var(--border-default); }
/* Baris judul akordeon dipakai BERSAMA oleh dua pemakai: jawaban FAQ/Terms
   (`.accordion`) dan blok `accordion` yang isinya blok (`.block-accordion`, aturan
   di akhir berkas). Deklarasinya tinggal SEKALI di sini dan selektornya yang
   ditambah — mengubah tinggi atau tipografi judul karena itu cukup di satu tempat,
   dan dua akordeon di satu halaman mustahil terlihat sebagai dua desain.

   `.block-accordion` sengaja TIDAK ikut memakai kelas `.accordion`: `.accordion p`
   di bawah adalah selektor keturunan, dan isi blok itu adalah blok-blok penuh <p>
   milik orang lain yang akan diam-diam disusutkan olehnya. Rantai `>` dipakai
   supaya blok FAQ DI DALAM panel tetap dilayani `.accordion`-nya sendiri. */
.accordion summary,
.block-accordion > details > summary { min-height: 61px; display: flex; align-items: center; justify-content: space-between; gap: 15px; font-size: var(--text-2xs); font-weight: 700; cursor: pointer; list-style: none; }
.accordion summary::-webkit-details-marker,
.block-accordion > details > summary::-webkit-details-marker { display: none; }
.accordion summary span,
.block-accordion > details > summary span { font-size: 17px; font-weight: 400; transition: transform .2s; }
/* Judul panel dibungkus <h3> supaya halaman panjang yang dipecah jadi beberapa
   panel tetap punya daftar heading — cara utama pengguna pembaca layar berpindah
   di halaman pricelist. Yang ditambah HANYA semantiknya: tipografi dan margin
   diwarisi dari `summary`, jadi tidak ada satu piksel pun yang berubah dan judul
   panel tetap sekeluarga dengan akordeon FAQ. */
.block-accordion > details > summary > h3 { margin: 0; font: inherit; letter-spacing: inherit; }
.accordion details[open] summary span,
.block-accordion > details[open] > summary span { transform: rotate(45deg); }
.accordion p { margin: -5px 32px 18px 0; color: var(--text-subtle); font-size: var(--text-2xs); }

.site-footer { padding: 47px var(--pad) 40px; border-top: 1px solid var(--border-faint); background: var(--bg-subtle); text-align: center; }
.site-footer > img { width: 202px; margin: 0 auto 24px; }
.site-footer nav { display: flex; justify-content: center; gap: 20px; font-size: var(--text-2xs); font-weight: 750; }
.site-footer p { margin: 22px 0 0; color: var(--text-subtle); font-size: var(--text-2xs); line-height: 1.8; }

.search-results { padding: 10px var(--pad) 28px; border-bottom: 1px solid var(--border-faint); }
.search-result-head { margin-bottom: 9px; display: flex; justify-content: space-between; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
.search-result-list { display: grid; gap: 7px; }
.search-result { min-width: 0; padding: 8px; display: grid; grid-template-columns: 58px 1fr 18px; align-items: center; gap: 11px; border: 1px solid var(--border-default); border-radius: 11px; background: var(--vs-white); text-align: left; cursor: pointer; }
.search-result img { width: 58px; height: 48px; border-radius: 8px; object-fit: cover; }
.search-result > span { min-width: 0; display: grid; }
.search-result small { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .1em; }
.search-result strong { overflow: hidden; margin: 2px 0; font-size: var(--text-2xs); text-overflow: ellipsis; white-space: nowrap; }
.search-result em { overflow: hidden; color: var(--text-subtle); font-size: var(--text-2xs); font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
.search-result > svg { width: 14px; height: 14px; }
.search-empty { padding: 28px 15px; border-radius: 12px; background: var(--bg-subtle); color: var(--text-subtle); font-size: var(--text-2xs); text-align: center; }
body.is-searching .gateway-stream > .section,
body.is-searching .gateway-stream > .site-footer { display: none; }

.drawer-backdrop { position: fixed; z-index: 80; inset: 0; background: rgba(5,5,5,.38); backdrop-filter: blur(4px); }
.menu-drawer { position: fixed; z-index: 90; top: 0; right: 0; width: min(88vw, 370px); height: 100dvh; padding: 24px 26px 27px; display: flex; flex-direction: column; background: var(--vs-white); box-shadow: -18px 0 60px rgba(0,0,0,.16); transform: translateX(105%); visibility: hidden; transition: transform .35s cubic-bezier(.22,.84,.36,1), visibility .35s; }
.menu-drawer.is-open { transform: translateX(0); visibility: visible; }
.drawer-close, .dialog-close { width: 38px; height: 38px; display: grid; place-items: center; border: 1px solid var(--border-default); border-radius: 50%; background: var(--vs-white); cursor: pointer; }
.drawer-close { margin-left: auto; }
.drawer-close svg, .dialog-close svg { width: 17px; height: 17px; }
.menu-drawer nav { margin: auto 0; display: grid; }
.menu-drawer nav button { padding: 8px 0; display: flex; justify-content: flex-end; align-items: baseline; gap: 13px; border: 0; background: none; text-align: right; cursor: pointer; }
.menu-drawer nav small { color: var(--text-disabled); font-size: var(--text-2xs); font-weight: 650; }
.menu-drawer nav span { font-size: 31px; font-weight: 750; line-height: 1.2; letter-spacing: -.05em; transition: transform .2s; }
.menu-drawer nav button:hover span { transform: translateX(-4px); }
.drawer-social { display: flex; justify-content: flex-end; gap: 8px; }
.drawer-social a { width: 35px; height: 35px; display: grid; place-items: center; border: 1px solid var(--border-default); border-radius: 50%; font-size: var(--text-2xs); font-weight: 750; }

dialog { max-width: none; padding: 0; border: 0; background: var(--vs-white); color: var(--text); }
dialog::backdrop { background: rgba(5,5,5,.52); backdrop-filter: blur(5px); }
.listing-dialog { width: min(100% - 24px, 520px); max-height: calc(100dvh - 24px); overflow: auto; border-radius: 20px; box-shadow: var(--shadow); }
.listing-dialog > .dialog-close { position: sticky; z-index: 2; top: 13px; float: right; margin: 13px 13px -51px 0; box-shadow: 0 4px 14px rgba(0,0,0,.1); }
.listing-hero { position: relative; aspect-ratio: 1.45 / 1; overflow: hidden; background: var(--bg-inset); }
.listing-hero img { width: 100%; height: 100%; object-fit: cover; }
/* Hero berisi lebih dari satu foto: rel ber-snap, pola yang sama dengan
   `.gallery-track` milik photo_carousel — swipe ditangani browser, bukan skrip drag.
   `aspect-ratio` tetap tinggal di `.listing-hero`, jadi tinggi hero tidak berubah
   antara listing berfoto satu dan berfoto banyak. */
.listing-hero.is-gallery { display: block; }
.listing-hero-track { display: flex; height: 100%; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.listing-hero-track::-webkit-scrollbar { display: none; }
.listing-hero-slide { flex: 0 0 100%; padding: 0; border: 0; background: none; scroll-snap-align: start; cursor: zoom-in; }
.listing-hero-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Dot duduk DI ATAS foto — di bawahnya ia akan mendorong seluruh badan listing
   turun 15px hanya untuk tiga titik. */
.listing-hero.is-gallery .slider-dots { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0; }
.listing-hero.is-gallery .slider-dots button { background: rgba(255, 255, 255, .55); }
.listing-hero.is-gallery .slider-dots button.is-active { background: var(--vs-white); }
.listing-body { clear: both; padding: 22px; }
.listing-kicker { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .14em; }
/* h1 DAN h2 disasar aturan yang sama: builder listing dipakai dua wadah dan hanya
   TINGKAT judulnya yang berbeda (h1 di halaman /listing/:slug, h2 di dalam dialog).
   Tanpa `.listing-body h1` halaman penuh akan jatuh ke ukuran bawaan browser dan
   tampilannya ikut berubah — padahal yang diperbaiki cuma semantik heading. */
.listing-body h1,
.listing-body h2 { margin: 7px 0 4px; font-size: 27px; line-height: 1; letter-spacing: -.05em; }
.listing-price { font-size: var(--text-2xs); font-weight: 750; }
.listing-description { margin: 14px 0 18px; color: var(--text-subtle); font-size: var(--text-2xs); }
.option-list { display: grid; gap: 7px; }
.listing-option { padding: 12px; display: grid; grid-template-columns: 18px 1fr auto; gap: 9px; align-items: center; border: 1px solid var(--border-default); border-radius: 10px; background: var(--vs-white); text-align: left; cursor: pointer; }
.listing-option i { width: 14px; height: 14px; border: 1px solid var(--ink-200); border-radius: 50%; }
.listing-option span { display: grid; }
.listing-option strong { font-size: var(--text-2xs); }
.listing-option small { color: var(--text-subtle); font-size: var(--text-2xs); }
.listing-option b { font-size: var(--text-2xs); }
.listing-option.is-selected { border-color: var(--ink); background: var(--bg-subtle); }
.listing-option.is-selected i { border: 4px solid var(--ink); }
.listing-actions { margin-top: 18px; display: grid; grid-template-columns: 1fr auto; gap: 8px; }
.listing-actions a, .listing-actions button { min-height: 45px; padding: 0 16px; display: flex; align-items: center; justify-content: center; gap: 8px; border: 1px solid var(--ink); border-radius: 9px; font-size: var(--text-2xs); font-weight: 750; cursor: pointer; }
.listing-actions a, .listing-actions [data-listing-book] { background: var(--ink); color: var(--text-on-dark); }
.listing-actions button { background: var(--vs-white); }
.listing-actions [data-listing-share] { background: var(--vs-white); }
.listing-actions svg { width: 14px; height: 14px; }
.listing-actions .is-primary { background: var(--ink); color: var(--text-on-dark); }

/* Bar tombol aksi listing (actionbar.js, 17 Sep 2026). Anatomi dasar tombol —
   tinggi 45px, sudut, garis 1px, huruf — tetap `.listing-actions button` di atas;
   yang ditambah di sini hanya susunan, gaya, dan warna.
   Warna mengalir lewat tiga custom property per tombol. Warna bernama memakai token
   (`--ink` ditimpa Appearance saat runtime); warna kustom ditulis inline oleh JS
   yang sudah menghitung kontrasnya. */
.listing-actions.is-set { display: flex; flex-wrap: wrap; grid-template-columns: none; }
.listing-actions.is-set > .ab-btn { flex: 1 1 auto; min-width: 0; max-width: 100%; background: var(--ab-bg); border-color: var(--ab-border); color: var(--ab-text); }
.listing-actions.is-set > .ab-btn > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listing-actions.is-set > .ab-btn > svg { flex: none; }
.listing-actions.is-set > .ab-btn[data-display="icon"] { flex: 0 0 45px; width: 45px; padding: 0; }
.listing-actions.is-set > .ab-btn[data-display="icon"] > svg { width: 16px; height: 16px; }
/* Satu baris: tombol bertulisan berbagi sisa lebar sama rata dan memotong labelnya
   dengan elipsis; kotak ikon tetap 45px. */
.listing-actions.is-set[data-layout="row"] { flex-wrap: nowrap; }
.listing-actions.is-set[data-layout="row"] > .ab-btn:not([data-display="icon"]) { flex: 1 1 0; }
.listing-actions.is-set[data-layout="stack"] { flex-direction: column; }
.listing-actions.is-set[data-layout="stack"] > .ab-btn { flex: none; width: 100%; }

.ab-btn[data-color="ink"][data-variant="solid"] { --ab-bg: var(--ink); --ab-border: var(--ink); --ab-text: var(--text-on-dark); }
.ab-btn[data-color="ink"][data-variant="outline"] { --ab-bg: var(--vs-white); --ab-border: var(--ink); --ab-text: var(--ink); }
.ab-btn[data-color="ink"][data-variant="soft"] { --ab-bg: var(--bg-inset); --ab-border: var(--border-default); --ab-text: var(--ink); }
.ab-btn[data-color="brand"][data-variant="solid"] { --ab-bg: var(--brand); --ab-border: var(--brand-line); --ab-text: var(--brand-ink); }
.ab-btn[data-color="brand"][data-variant="outline"] { --ab-bg: var(--vs-white); --ab-border: var(--brand-line); --ab-text: var(--ink); }
.ab-btn[data-color="brand"][data-variant="soft"] { --ab-bg: var(--brand-soft); --ab-border: var(--brand-line); --ab-text: var(--brand-ink); }
/* Hanya di pratinjau dashboard: tombol yang belum tampil di halaman publik. */
.listing-actions.is-set > .ab-btn.is-pending { border-style: dashed; opacity: .5; }

.share-dialog { width: min(100% - 32px, 410px); padding: 28px; border-radius: 19px; }
.share-dialog .dialog-close { position: absolute; top: 16px; right: 16px; }
.share-dialog > span { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .16em; }
.share-dialog h2 { margin: 8px 45px 6px 0; font-size: 23px; line-height: 1.05; letter-spacing: -.045em; }
.share-dialog p { margin: 0 0 20px; color: var(--text-subtle); font-size: var(--text-2xs); }
.share-dialog > div { display: grid; gap: 8px; }
/* Popup aksi `confirm` (komposer alur node, 3 Sep 2026) — anatomi yang SAMA dengan
   dialog Share: kicker, judul, kalimat, dua tombol bertumpuk. Tombol Ya = ink
   (utama), Tidak = putih berbingkai. Nol nilai baru. */
.confirm-dialog { width: min(100% - 32px, 410px); padding: 28px; border-radius: 19px; }
.confirm-dialog .dialog-close { position: absolute; top: 16px; right: 16px; }
.confirm-dialog > span { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .16em; }
.confirm-dialog h2 { margin: 8px 45px 6px 0; font-size: 23px; line-height: 1.05; letter-spacing: -.045em; }
.confirm-dialog p { margin: 0 0 20px; color: var(--text-subtle); font-size: var(--text-2xs); }
.confirm-dialog p[hidden] { display: none; }
.confirm-dialog > div { display: grid; gap: 8px; }
.confirm-dialog > div button { min-height: 46px; padding: 0 16px; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--vs-white); color: var(--text-strong); font: inherit; font-size: var(--text-xs); font-weight: 700; cursor: pointer; }
.confirm-dialog > div button.is-primary { border-color: var(--ink); background: var(--ink); color: var(--text-on-dark); }
.confirm-dialog > div button:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.share-dialog a, .share-dialog > div button { min-height: 45px; padding: 0 13px; display: flex; justify-content: space-between; align-items: center; border: 1px solid var(--border-default); border-radius: 9px; background: var(--vs-white); font-size: var(--text-2xs); font-weight: 750; cursor: pointer; }
.share-dialog a:first-child { border-color: var(--ink); background: var(--ink); color: var(--text-on-dark); }
.share-dialog svg { width: 14px; height: 14px; }

.lightbox-dialog { width: min(100% - 24px, 880px); height: min(82dvh, 700px); overflow: hidden; border-radius: 18px; background: var(--ink); color: var(--text-on-dark); }
.lightbox-dialog .dialog-close { position: absolute; z-index: 2; right: 15px; top: 15px; border-color: rgba(255,255,255,.3); background: rgba(0,0,0,.4); color: var(--text-on-dark); }
.lightbox-dialog figure { width: 100%; height: 100%; margin: 0; display: grid; grid-template-rows: 1fr auto; }
.lightbox-dialog figure img { width: 100%; height: 100%; min-height: 0; object-fit: contain; }
.lightbox-dialog figcaption { padding: 15px 60px 18px; color: var(--text-on-dark-muted); font-size: var(--text-2xs); text-align: center; }
.lightbox-prev, .lightbox-next { position: absolute; z-index: 2; top: 50%; width: 38px; height: 38px; display: grid; place-items: center; border: 1px solid rgba(255,255,255,.28); border-radius: 50%; background: rgba(0,0,0,.38); color: var(--text-on-dark); cursor: pointer; }
.lightbox-prev { left: 13px; }
.lightbox-next { right: 13px; }
.lightbox-prev svg, .lightbox-next svg { width: 16px; height: 16px; }

/* ── Bilah navigasi bawah (mobile) ────────────────────────────────────────────
   Permintaan owner: layar publik terasa lebih "native" di ponsel.

   TIDAK ada padanannya di prototipe (ADR-G01) — satu-satunya elemen fixed-bottom di
   sana adalah toast. Jadi komponen ini TAMBAHAN atas prototipe, bukan reproduksi;
   ia sengaja meminjam bahasa yang sudah ada (ink untuk keadaan aktif, --text-subtle
   untuk istirahat, ikon dari registry kanonik) supaya tidak terbaca sebagai desain asing.

   Disembunyikan di >= 640px: di sana header + drawer sudah menampung navigasi, dan
   bilah bawah pada layar lebar justru memakan tinggi tanpa menambah jangkauan jempol.

   `env(safe-area-inset-bottom)` bukan hiasan — tanpa itu label tab tertimpa home
   indicator iPhone, dan itulah satu detail yang paling cepat membuat sebuah web
   terasa BUKAN aplikasi. */
.bottom-nav { position: fixed; z-index: 70; left: 50%; bottom: 0; width: 100%; max-width: var(--shell); transform: translateX(-50%); display: grid; grid-template-columns: repeat(4, 1fr); padding-bottom: env(safe-area-inset-bottom, 0px); border-top: 1px solid var(--border-default); background: rgba(255, 255, 255, .94); backdrop-filter: blur(14px); }
/* Target sentuh 56px: di atas minimum 44px, karena baris ini dipakai dengan jempol
   sambil berdiri, bukan dengan kursor. */
.bottom-nav button { min-height: var(--bottom-nav-row); padding: 8px 4px 9px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; border: 0; background: none; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 650; cursor: pointer; -webkit-tap-highlight-color: transparent; }
/* Ukuran ikon datang dari atribut `size` pada <vs-icon>, BUKAN dari CSS di sini:
   vs-icons.js merender ke Shadow DOM, jadi selector `.bottom-nav button svg` tidak
   pernah menjangkaunya. Aturan semacam itu bukan sekadar tidak berguna — ia
   menyiratkan kendali yang sebenarnya tidak ada. Yang MENEMBUS shadow boundary
   hanya properti terwarisi, dan `color` termasuk (HOST_CSS memakai color:inherit,
   ikonnya currentColor) — itulah sebabnya keadaan aktif di bawah memakai warna. */
/* Keadaan aktif dibawa WARNA + BOBOT, bukan warna saja: pembaca yang sulit
   membedakan abu dari hitam tetap melihat perbedaan tebal hurufnya. */
.bottom-nav button[aria-current="page"] { color: var(--text-strong); font-weight: 800; }

/* Shell diberi ruang bawah supaya isi terakhir halaman tidak tertutup bilah.
   Tingginya diturunkan dari SATU sumber (`--bottom-nav-row` + 1px border-top),
   bukan ditulis ulang sebagai angka lepas: versi pertama menyisakan 57px untuk
   bilah setinggi 59px, dan 2px terakhir halaman tertutup tanpa terlihat rusak. */
@media (max-width: 639px) {
  .site-shell { padding-bottom: calc(var(--bottom-nav-row) + 1px + env(safe-area-inset-bottom, 0px)); }
}
/* Catatan harga baru duduk DI SAMPING angkanya kalau kartunya memang cukup lebar.
   Di bawah itu ia menumpuk di bawah — satu kalimat harga yang utuh mengalahkan dua
   kolom yang saling menimpa. Ambangnya sengaja longgar: angka rupiah penuh pada
   `--text-h1` butuh ruang jauh lebih besar daripada frasa pendek seperti "Tarif flat". */
@media (min-width: 640px) {
  .bottom-nav { display: none; }
  .site-shell { padding-bottom: 0; }
}

.toast { position: fixed; z-index: 110; left: 50%; bottom: 22px; max-width: calc(100% - 40px); padding: 10px 15px; border-radius: 9px; background: var(--ink); color: var(--text-on-dark); box-shadow: var(--shadow); font-size: var(--text-2xs); font-weight: 650; opacity: 0; pointer-events: none; transform: translate(-50%, 14px); transition: opacity .2s, transform .2s; }
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* Gaya dialog form konsultasi DICABUT bersama JS-nya (R5 · Integrasi): rute
   `/f/:slug` kini punya runtime sendiri (`.vsform-*`). `.consultation` — kartu
   ajakan pada blok form_card — TETAP dipakai dan tidak ikut dihapus. */

.schedule-view { min-height: 100vh; background: var(--surface); }
.subpage-header { position: sticky; z-index: 30; top: 0; min-height: 68px; padding: 10px var(--pad); display: grid; grid-template-columns: 76px 1fr 65px; align-items: center; border-bottom: 1px solid var(--border-default); background: rgba(255,255,255,.95); backdrop-filter: blur(14px); }
.subpage-header button { padding: 7px 0; display: flex; align-items: center; gap: 4px; border: 0; background: none; font-size: var(--text-2xs); font-weight: 700; cursor: pointer; }
.subpage-header button:last-child { justify-content: flex-end; }
.subpage-header button svg { width: 15px; height: 15px; }
.subpage-header > div { min-width: 0; display: grid; text-align: center; }
/* Pusat topbar memuat LOGO. Tingginya dikunci supaya bilah tidak melar mengikuti
   rasio berkas logo yang diunggah admin. */
.subpage-brand { display: flex; align-items: center; justify-content: center; min-width: 0; }
/* Wordmark lebar: 18px tinggi terasa mendominasi topbar (owner, 12 Sep 2026) —
   diturunkan ke 13px dan diberi pagar lebar agar tidak menyentuh tombol di kedua sisi. */
.subpage-brand img { max-width: min(100%, 132px); height: 13px; width: auto; object-fit: contain; display: block; }
.subpage-header strong { font-size: var(--text-2xs); }
.subpage-header small { overflow: hidden; color: var(--text-subtle); font-size: var(--text-2xs); text-overflow: ellipsis; white-space: nowrap; }
.schedule-intro { padding: 18px var(--pad) 16px; }
.schedule-intro h1 { margin: 0 0 12px; font-size: 26px; line-height: 1; letter-spacing: -.055em; }

/* Kepala jadwal = BILAH STATUS OPERASIONAL, bukan sampul halaman.
   SPEC: "18.02 Dokumentasi Teknis/SPEC — Kepala Layar Jadwal (schedule-intro) — 2026-08-23.md"

   Versi sebelumnya menumpuk tiga fakta sebagai tiga kartu (187px) sehingga kartu ruang
   pertama baru mulai di y=589 dari viewport 812 — 72% layar habis sebelum jadwalnya
   terlihat. Sekarang jam + pil berbagi satu baris, dan tanggal turun jadi baris teks. */
.schedule-facts { display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--space-3); }
.schedule-fact { min-width: 0; margin: 0; padding: 8px 12px; display: grid; align-content: center; gap: 1px; border: 1px solid var(--border-default); border-radius: 12px; background: var(--vs-white); }
.schedule-fact > strong { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 500; letter-spacing: var(--ls-label-sm); text-transform: uppercase; }

/* DIVERGENSI: prototipe menaruh jam operasional sebagai kicker mono 10px — justru yang
   PALING kecil. Owner meminta ia paling menonjol, TAPI tidak besar. Jadi penonjolannya
   dibawa KONTRAS (satu-satunya permukaan gelap di kepala), bukan ukuran: nilainya 14px.
   `tabular-nums` menjaga lebar "10:00-19:00" tetap saat jam berganti musim (10-19 / 10-22). */
.schedule-fact.is-hours { flex: 1 1 auto; border-color: var(--ink); background: var(--bg-ink); }
.schedule-fact.is-hours > strong { color: var(--text-on-dark-muted); }
.schedule-fact.is-hours > span { color: var(--text-on-dark); font-size: var(--text-sm); font-weight: var(--fw-bold); letter-spacing: -.01em; font-variant-numeric: tabular-nums; }

/* Pil jumlah slot. Teksnya sudah membawa labelnya sendiri ("29 SLOT TERBUKA", ditulis
   app.js), jadi ia tidak diberi <strong> label lagi. */
.schedule-facts > b { flex: none; align-self: stretch; display: flex; align-items: center; padding: 0 9px; border-radius: 12px; background: var(--yellow); color: var(--ink); font-size: var(--text-2xs); letter-spacing: .08em; }

/* Tanggal hidup DI DALAM kartu jam: keduanya menjawab satu pertanyaan yang sama
   ("hari apa, buka jam berapa"), jadi memisahkannya jadi dua blok memecah satu fakta
   menjadi dua. Tetap <h2> supaya outline dokumen (h1 -> h2) tidak putus — dan karena
   itulah pembungkus kartunya <div>, bukan <p>: <p> tidak boleh memuat <h2>, parser
   akan menutupnya lebih awal dan strukturnya pecah tanpa satu pun pesan galat. */
.schedule-fact.is-hours > h2 { margin: 2px 0 0; color: var(--text-on-dark-muted); font-size: var(--text-2xs); font-weight: var(--fw-semibold); }

.schedule-legend { padding: 9px var(--pad); display: flex; flex-wrap: wrap; gap: 10px; border-block: 1px solid var(--border-faint); color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 650; }
.schedule-legend span { display: flex; align-items: center; gap: 5px; }
.schedule-legend i { width: 10px; height: 10px; border: 1px solid var(--border-input); border-radius: 3px; background: var(--vs-white); }
.schedule-legend i.hold { border-color: var(--brand-line); background: var(--brand-soft); }
.schedule-legend i.booked { border-color: var(--ink); background: var(--ink); }
.schedule-legend i.past { background: var(--bg-inset); }
.schedule-status { margin: 15px var(--pad) 0; padding: 10px 12px; border: 1px solid var(--border-default); border-radius: 9px; background: var(--bg-subtle); color: var(--text-subtle); font-size: var(--text-2xs); }
.schedule-status.is-live { border-color: var(--tag-success-border); background: var(--tag-success-bg); color: var(--tag-success-text); }
.schedule-status.is-fallback { border-color: var(--tag-warning-border); background: var(--tag-warning-bg); color: var(--tag-warning-text); }
.schedule-status button { margin-left: 7px; padding: 0; border: 0; background: none; color: inherit; font-size: inherit; font-weight: 800; text-decoration: underline; cursor: pointer; }
.schedule-rooms { padding: 17px var(--pad) 30px; display: grid; gap: 14px; }
.schedule-room { overflow: hidden; border: 1px solid var(--border-default); border-radius: 16px; background: var(--vs-white); }
.schedule-room > img { width: 100%; aspect-ratio: 1.88 / 1; object-fit: cover; }
.schedule-room-copy { padding: 14px 14px 10px; display: flex; justify-content: space-between; gap: 15px; }
.schedule-room-copy > span { min-width: 0; display: grid; }
.schedule-room-copy strong { font-size: 12px; }
.schedule-room-copy small { overflow: hidden; color: var(--text-subtle); font-size: var(--text-2xs); text-overflow: ellipsis; white-space: nowrap; }
.schedule-room-copy b { flex: none; color: var(--green); font-size: var(--text-2xs); }
/* Strip slot MEMBUNGKUS, tidak menggulir mendatar.
   Rail horizontal menyembunyikan jam-jam terakhir di balik tepi kartu: pengunjung
   yang mencari slot sore harus menebak bahwa strip itu bisa digeser, dan di kartu
   dua-kolom (≥640px) yang terlihat tinggal empat jam pertama. Dengan `auto-fill`
   seluruh jam tampil sekaligus, kolomnya SEJAJAR antar baris (itu sebabnya grid,
   bukan `flex-wrap` yang meninggalkan tepi kanan bergerigi), dan jumlah kolom
   mengikuti lebar kartu — sama untuk shell 560px maupun 980px tanpa media query. */
.slot-strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); gap: 6px; padding: 4px 14px 14px; }
.slot { height: 52px; padding: 0; display: grid; place-items: center; border: 1px solid var(--border-input); border-radius: 8px; background: var(--vs-white); font-size: var(--text-2xs); font-weight: 700; cursor: pointer; }
.slot:hover:not(:disabled) { border-color: var(--ink); }
.slot.hold { border-color: var(--brand-line); background: var(--brand-soft); color: var(--tag-identity-text); }
.slot.booked { border-color: var(--ink); background: var(--ink); color: var(--text-on-dark); }
.slot.past { background: var(--bg-inset); color: var(--text-disabled); }
.slot:disabled { cursor: not-allowed; }
/* R8 - baris tanggal (mencabut "hari ini saja" PRD 12.49), pemilih durasi, dan
   status `closed`. Tiga penambahan pada keluarga .schedule-* yang sudah ada;
   tidak ada keluarga kelas baru, dan tidak ada nilai warna literal. */
.schedule-dates { padding: 0 var(--pad) 4px; display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; }
.schedule-dates::-webkit-scrollbar { display: none; }
.schedule-date { flex: 0 0 auto; min-height: var(--field-h-sm); padding: 0 12px; border: 1px solid var(--border-input); border-radius: var(--radius-sm); background: var(--vs-white); color: var(--text-body); font-size: var(--text-2xs); font-weight: 700; cursor: pointer; }
.schedule-date:hover { border-color: var(--ink); }
.schedule-date.is-today { border-color: var(--brand-line); }
.schedule-date.is-active { border-color: var(--ink); background: var(--ink); color: var(--text-on-dark); }
.schedule-durations-host { padding: 0 var(--pad) 4px; }
.schedule-durations-host[hidden] { display: none; }
.schedule-durations { display: flex; gap: 6px; }
.schedule-duration { min-height: var(--field-h-sm); padding: 0 12px; border: 1px solid var(--border-input); border-radius: var(--radius-pill); background: var(--vs-white); color: var(--text-body); font-size: var(--text-2xs); font-weight: 700; cursor: pointer; }
.schedule-duration.is-active { border-color: var(--ink); background: var(--yellow); color: var(--ink); }
.schedule-legend i.closed { border-color: var(--border-strong); background: repeating-linear-gradient(135deg, var(--bg-inset), var(--bg-inset) 2px, var(--vs-white) 2px, var(--vs-white) 4px); }
.slot.closed { border-color: var(--border-strong); background: repeating-linear-gradient(135deg, var(--bg-inset), var(--bg-inset) 3px, var(--vs-white) 3px, var(--vs-white) 6px); color: var(--text-disabled); }
.slot.is-selected { border-color: var(--ink); background: var(--yellow); color: var(--ink); }
.schedule-room-copy b.is-closed { color: var(--text-subtle); }
/* Blok `studio_schedule` di dalam Page: pembungkusnya .section, jadi padding
   horizontal keluarga .schedule-* dinolkan agar tidak dobel dengan padding section. */
.block-studio-schedule .schedule-dates, .block-studio-schedule .schedule-durations-host, .block-studio-schedule .schedule-rooms, .block-studio-schedule .schedule-legend { padding-left: 0; padding-right: 0; }
.block-studio-schedule .schedule-status { margin-left: 0; margin-right: 0; }
.block-studio-schedule .schedule-rooms { padding-top: 14px; padding-bottom: 0; }
/* Bilah fakta & baris kontak ikut aturan yang sama: padding horizontalnya milik
   .section. Bilah fakta dipasang hidden saat jadwalnya bukan dari VS OS
   (renderers.js, keputusan owner 13 Sep 2026) — dinyatakan di sini juga supaya
   `hidden` tidak bisa dikalahkan oleh display milik aturan lain. */
.block-studio-schedule .schedule-intro { padding: 0 0 var(--space-3); }
.block-studio-schedule .schedule-intro[hidden] { display: none; }
/* Jarak atas dinyatakan DI SINI, bukan diwarisi: di layar lama celah sebelum baris
   kontak datang dari padding-bottom 30px milik .schedule-rooms, dan di dalam blok
   padding itu dinolkan (baris di atas) supaya kaki blok tidak dobel. Tanpa aturan
   ini baris kontak menempel persis di kartu ruang terakhir. */
/* Cek Jadwal Studio (`schedule_finder`, 15 Sep 2026): tanggal → studio → jam.
   Kalender = unit GUI/03/DATE-PICKER (kepala mono, navigasi bulan, kisi 7 kolom,
   legenda). Titik status di bawah angka dipinjam dari `.vsform-day` supaya empat
   status hari terbaca dengan bahasa warna yang sama di form dan di sini. Strip jam
   memakai `.schedule-room` + `.slot-strip` apa adanya; jamnya <span>, jadi kursor
   tidak menjanjikan klik. */
.block-schedule-finder .sf-intro { margin: 0 0 var(--space-5); color: var(--text-muted); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
.sf-step { display: grid; gap: var(--space-3); }
.sf-step + .sf-step { margin-top: var(--space-6); }
.sf-kicker { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label-sm); text-transform: uppercase; }
.sf-cal-head, .sf-cal-nav { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.sf-cal-pick { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-strong); }
.sf-cal-nav b { font-family: var(--font-display); font-size: var(--text-sm); font-weight: var(--fw-bold); color: var(--text-strong); }
.sf-cal-nav button { width: var(--field-h-sm); height: var(--field-h-sm); display: grid; place-items: center; border: 1px solid var(--border-input); border-radius: var(--radius-sm); background: var(--vs-white); color: var(--text-muted); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.sf-cal-nav button:hover:not(:disabled) { background: var(--bg-subtle); }
.sf-cal-nav button:disabled { opacity: .35; cursor: default; }
.sf-cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.sf-dow { padding: 6px 0; text-align: center; font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-subtle); }
.sf-day { min-height: var(--field-h-sm); padding: 4px 0; display: grid; gap: 1px; place-items: center; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--text-strong); font: inherit; font-size: var(--text-xs); font-weight: var(--fw-medium); line-height: 1.2; cursor: pointer; }
.sf-day b { font-weight: inherit; }
.sf-day i { display: block; width: 5px; height: 5px; border-radius: 50%; background: var(--accent-mint); }
.sf-day.limited i { background: var(--accent-orange); }
.sf-day.full i { background: var(--ink-300); }
.sf-day.full { color: var(--text-muted); }
.sf-day.closed { background: repeating-linear-gradient(135deg, var(--bg-inset), var(--bg-inset) 3px, var(--vs-white) 3px, var(--vs-white) 6px); color: var(--text-disabled); }
.sf-day.closed i, .sf-day.past i, .sf-day.out i, .sf-day.unknown i, .sf-day.loading i { display: none; }
.sf-day.past, .sf-day.out, .sf-day.unknown, .sf-day.loading { color: var(--text-disabled); }
.sf-day:disabled { cursor: default; }
.sf-day:hover:not(:disabled):not(.is-selected) { background: var(--bg-subtle); }
.sf-day.is-today { box-shadow: inset 0 0 0 1.5px var(--ink); }
.sf-day.is-selected { background: var(--ink); color: var(--text-on-dark); }
.sf-day.is-selected i { background: var(--brand); }
.sf-day:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.sf-status:empty { display: none; }
.sf-status { color: var(--text-muted); font-size: var(--text-2xs); }
.sf-legend-today i { width: 9px; height: 9px; border-radius: 3px; background: none; box-shadow: inset 0 0 0 1.5px var(--ink); }
/* Kartu studio = `.grid-thumb` (ubin 1:1 + nama) dengan keping jumlah jam di
   pojok ubin — keping kaca gelap yang sama dengan `.catalog-badge`, terbaca di
   atas foto terang maupun gelap. Terpilih = cincin ink pada ubin, keping kuning. */
.sf-studio-rail { display: flex; gap: var(--space-3); overflow-x: auto; padding-bottom: var(--space-2); scroll-snap-type: x proximity; scrollbar-width: none; }
.sf-studio-rail::-webkit-scrollbar { display: none; }
.sf-studio-rail .grid-thumb { flex: 0 0 31%; scroll-snap-align: start; }
.sf-studio-tag { position: absolute; left: var(--space-2); bottom: var(--space-2); z-index: 1; max-width: calc(100% - var(--space-2) * 2); padding: 2px var(--space-2); border-radius: var(--radius-pill); background: color-mix(in srgb, var(--ink-900) 62%, transparent); color: var(--text-on-dark); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); line-height: var(--lh-snug); white-space: nowrap; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
.sf-studio.is-on .grid-thumb-tile { border-color: var(--ink); box-shadow: 0 0 0 2px var(--ink); }
.sf-studio.is-on .sf-studio-tag { background: var(--brand); color: var(--ink); }
.sf-studio.is-on .grid-thumb-name { font-weight: var(--fw-bold); }
.sf-studio { grid-template-rows: auto auto auto; gap: var(--space-1); }
.sf-studio .grid-thumb-tile { margin-bottom: var(--space-1); }
.sf-studio-pax { color: var(--text-subtle); font-size: var(--text-2xs); line-height: var(--lh-snug); }
.sf-studio.is-muted:not(.is-on) .grid-thumb-tile img { opacity: .5; }
.sf-studio.is-muted:not(.is-on) .grid-thumb-name { color: var(--text-muted); }
@media (min-width: 640px) { .sf-studio-rail .grid-thumb { flex-basis: 18%; } }
.block-schedule-finder .slot { cursor: default; }
.block-schedule-finder .schedule-room-copy b.is-closed { color: var(--text-muted); }
.block-schedule-finder .schedule-legend { padding-left: 0; padding-right: 0; }
.block-schedule-finder .schedule-status { margin-left: 0; margin-right: 0; }
.block-schedule-finder .schedule-contact { padding: var(--space-8) 0 0; }
.block-schedule-finder .schedule-contact button[disabled] { opacity: .45; cursor: not-allowed; }
.block-studio-schedule .schedule-contact { padding: var(--space-8) 0 0; }
.block-studio-schedule .schedule-contact button[disabled] { opacity: .45; cursor: not-allowed; }
.schedule-empty { padding: 30px 20px; border: 1px dashed var(--border-input); border-radius: 13px; color: var(--text-subtle); font-size: var(--text-2xs); text-align: center; }
.schedule-contact { padding: 0 var(--pad) 40px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.schedule-contact a, .schedule-contact button { min-width: 0; padding: 12px; display: grid; grid-template-columns: 22px 1fr 14px; gap: 8px; align-items: center; border: 1px solid var(--border-default); border-radius: 11px; }
.schedule-contact > a > svg, .schedule-contact > button > svg { width: 17px; height: 17px; }
.schedule-contact > a > svg:last-child, .schedule-contact > button > svg:last-child { width: 12px; height: 12px; }
.schedule-contact span { min-width: 0; display: grid; }
.schedule-contact small { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .1em; }
.schedule-contact strong { overflow: hidden; font-size: var(--text-2xs); text-overflow: ellipsis; white-space: nowrap; }

@media (min-width: 640px) {
  :root { --shell: 640px; --pad: 28px; }
  .consultation { flex-direction: row; align-items: end; justify-content: space-between; }
  .consultation p { max-width: 230px; }
  .consultation > a, .consultation > button { flex: 0 0 102px; }
  .site-header { height: 72px; }
  .brand, .brand img { width: 155px; }
  .hero-pad { padding-top: 29px; }
  .shortcut-grid { column-gap: 16px; }
  .shortcut { font-size: var(--text-2xs); }
  .shortcut > span { width: 57px; height: 57px; }
  .promo-hero { flex-basis: calc(100% - 42px); }
  .compact-promos { grid-template-columns: 1fr 1fr; }
  .featured-card { flex-basis: 224px; }
  .explore-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .explore-card { grid-template-columns: 86px 1fr 17px; }
  .explore-card > img { width: 86px; height: 82px; }
  .block-card[data-layout="compact"] .grid-card > .image-wrap { flex-basis: 86px; }
  .feature-promo article { aspect-ratio: 1.5 / 1; }
  .gallery-track button, .gallery-track > span { flex-basis: 47%; }
  .schedule-rooms { grid-template-columns: 1fr 1fr; }
  .schedule-intro { padding-top: 26px; }
  .schedule-intro > span { display: block; }
  .schedule-intro h1 { margin-bottom: 16px; font-size: 32px; }
}

@media (min-width: 1024px) {
  :root { --shell: 980px; --pad: 42px; }
  .site-header { height: 76px; }
  .hero-pad { padding-top: 34px; padding-bottom: 30px; }
  .shortcut-grid { column-gap: 25px; }
  .shortcut > span { width: 61px; height: 61px; }
  .promo-hero { flex-basis: calc(100% - 65px); aspect-ratio: 2.3 / 1; }
  .promo-overlay, .banner-overlay { padding: 34px; }
  .promo-overlay h2 { font-size: 42px; }
  .section { padding-top: 45px; padding-bottom: 45px; }
  .compact-promos { grid-template-columns: 1fr 1fr; }
  .featured-track { display: grid; grid-template-columns: repeat(3, 1fr); overflow: visible; }
  .featured-card { width: auto; }
  .explore-grid { grid-template-columns: repeat(var(--cols-desktop, 2), minmax(0, 1fr)); }
  .explore-card { grid-template-columns: 112px 1fr 20px; }
  .explore-card > img { width: 112px; height: 90px; }
  .block-card[data-layout="compact"] .grid-card > .image-wrap { flex-basis: 112px; }
  .feature-promo article { aspect-ratio: 2.15 / 1; }
  .feature-promo h2 { font-size: 40px; }
  .consultation { margin-inline: var(--pad); padding: 38px 36px; }
  .consultation h2 { font-size: 34px; }
  .gallery-track { display: grid; grid-template-columns: repeat(3, 1fr); overflow: visible; }
  .gallery-track button, .gallery-track > span { width: auto; }
  .quick-links, .faq { display: grid; grid-template-columns: 220px 1fr; gap: 60px; }
  .quick-links .section-heading, .faq .section-heading { align-self: start; }
  .search-result-list { grid-template-columns: 1fr 1fr; }
  .schedule-rooms { grid-template-columns: 1fr 1fr; gap: 18px; }
  .schedule-intro h1 { font-size: 42px; }
}

@media (max-width: 540px) {
  .listing-dialog { width: 100%; max-height: 92dvh; margin: auto 0 0; border-radius: 20px 20px 0 0; }
  .listing-dialog[open] { animation: sheet-in .28s ease-out; }
  @keyframes sheet-in { from { transform: translateY(30px); opacity: 0; } }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}


/* ── Blok berbasis registry (R2) ─────────────────────────────────────────────
   Seluruh isi beranda kini dirender dari Block Registry ke `.gateway-stream`;
   simpul warisan `data-legacy-block` sudah dicabut bersama markup statisnya.
   Aturan di bawah HANYA menambahkan yang belum punya anatomi — kelas lama
   (.site-header, .hero-pad, .promo-hero, .featured-card, …) dipakai ulang apa
   adanya supaya hasil render identik dengan desain yang sudah disetujui. */
.block-rich-text { margin: 0 var(--pad); }
.vs-rich { color: var(--text-body); font-size: var(--text-sm); line-height: var(--lh-relaxed); }
.vs-rich > :first-child { margin-top: 0; }
.vs-rich > :last-child { margin-bottom: 0; }
.vs-rich h2 { margin: var(--space-7) 0 var(--space-4); font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--fw-bold); line-height: var(--lh-snug); letter-spacing: var(--ls-tight); }
.vs-rich h3 { margin: var(--space-6) 0 var(--space-3); font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-semibold); line-height: var(--lh-snug); }
.vs-rich h1,.vs-rich h4,.vs-rich h5,.vs-rich h6 { margin: var(--space-5) 0 var(--space-3); font-family: var(--font-display); font-weight: var(--fw-semibold); line-height: var(--lh-snug); }
.vs-rich h1 { font-size: var(--text-h1); letter-spacing: var(--ls-display); }
.vs-rich h4 { font-size: var(--text-lg); }
.vs-rich h5,.vs-rich h6 { font-size: var(--text-sm); }
.vs-rich p { margin: 0 0 var(--space-4); }
.vs-rich strong { font-weight: var(--fw-semibold); color: var(--text-strong); }
.vs-rich ul, .vs-rich ol { margin: 0 0 var(--space-4); padding-left: var(--space-6); display: grid; gap: var(--space-2); }
.vs-rich li { margin: 0; }
.vs-rich blockquote { margin: 0 0 var(--space-4); padding-left: var(--space-5); border-left: 2px solid var(--border-default); color: var(--text-muted); }
.vs-rich a { color: var(--text-strong); text-decoration: underline; text-underline-offset: 3px; }
.vs-rich figure { margin: var(--space-5) 0; }
.vs-rich figure img { max-width: 100%; height: auto; display: block; }
.vs-rich figcaption { margin-top: var(--space-2); color: var(--text-muted); font-size: var(--text-xs); }
.vs-rich table { width: 100%; margin: 0 0 var(--space-4); border-collapse: collapse; }
.vs-rich th,.vs-rich td { padding: var(--space-2); border: 1px solid var(--border-default); text-align: left; vertical-align: top; }
.vs-rich th { background: var(--bg-subtle); font-weight: var(--fw-semibold); }
.vs-rich pre,.vs-rich code { font-family: ui-monospace,SFMono-Regular,Menlo,monospace; }
.vs-rich pre { margin: 0 0 var(--space-4); padding: var(--space-3); overflow: auto; background: var(--bg-subtle); border-radius: var(--radius-sm); }
.vs-rich mark { padding: 0 .15em; background: var(--tag-info-bg); color: inherit; }
.vs-rich[data-heading="editorial"] h2 { font-size: var(--text-h2); letter-spacing: var(--ls-display); }

.block-divider { margin: var(--space-7) var(--pad); }
.block-divider hr { margin: 0; border: 0; border-top: 1px solid var(--border-default); }

.block-spacer[data-size="s"] { height: var(--space-6); }
.block-spacer[data-size="m"] { height: var(--space-9); }
.block-spacer[data-size="l"] { height: var(--space-12); }

/* Header blok. `display: contents` menjaga <header class="site-header"> tetap
   menjadi anak KOTAK dari .gateway-stream, sehingga position:sticky-nya masih
   punya containing block setinggi seluruh stream. Pembungkus biasa akan membuat
   header berhenti menempel setelah beberapa piksel. */
.block-header { display: contents; }
.block-hero-meta { padding-bottom: 0; }
/* Padding hero dijahit lewat sibling: baris deskriptor → kotak cari → menu grid
   harus terbaca sebagai SATU blok hero seperti markup lamanya. */
.block-header + .block-search { padding-top: 0; }
/* `.search-results` (panel hasil, biasanya [hidden]) berdiri sebagai sibling DI ANTARA
   kotak cari dan menu grid, sehingga selector adjacent `+` yang dipakai sebelumnya
   tidak pernah cocok: jarak 22px yang tampil selama ini adalah padding-bottom kotak
   cari yang tidak pernah dinihilkan, bukan angka yang dirancang. Dipakai `~` (menu
   grid unique per gateway dan selalu sesudah kotak cari), lalu jarak antar-grup
   ditulis SEKALI di .block-menu-grid — bukan hasil penjumlahan dua padding tetangga. */
.block-search:has(~ .block-menu-grid) { padding-bottom: 0; }
/* Jarak grup hero harus jelas lebih besar dari ritme di dalam grid (row-gap 17px);
   kalau tidak, kotak cari dan menu grid terbaca sebagai satu tumpukan rapat. Fluid
   --space-8 → --space-9 mengikuti lebar layar, dan simetris atas-bawah supaya garis
   pemisah seksi berikutnya tidak menempel ke label baris terakhir. */
.block-menu-grid { padding-block: clamp(var(--space-8), 4.5vw, var(--space-9)); }
.block-menu-grid .shortcut-grid { margin-top: 0; }
/* Saat mencari, `.search-results` menyelinap DI ANTARA kotak cari dan menu grid.
   Padding bawah yang dinihilkan di atas harus dikembalikan khusus untuk keadaan
   ini — tanpa aturan ini hasil pertama menempel ke kotak carinya. */
body.is-searching .block-search:has(~ .block-menu-grid) { padding-bottom: var(--space-6); }


/* Meta kepala seksi — hitungan turunan data yang menggantikan kicker editorial
   literal (tidak ada schema blok yang menyimpan teks kicker). */
.section-meta { flex: none; color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: .12em; text-transform: uppercase; }

/* promo_slider = banner gambar saja: tanpa overlay teks, tanpa scrim. */
.promo-hero.is-plain::after { content: none; }
/* promo_card layout "banner" memakai anatomi .promo-hero di luar rail slider. */
.promo-banner { padding-inline: var(--pad); }
.promo-banner .promo-hero { flex: none; }
/* Satu blok promo_card = satu kartu compact. Kartu compact yang berurutan
   dirapatkan supaya terbaca sebagai satu kelompok, seperti markup lamanya. */
/* Dua blok sejenis yang berurutan dirapatkan jadi SATU kelompok: yang kedua tidak
   mengulang jarak atas maupun garis pemisahnya. Satu aturan untuk kedua keluarga —
   menuliskannya dua kali berarti dua tempat yang harus diingat saat berubah. */
.compact-promos + .compact-promos,
.link-cards + .link-cards { padding-top: 0; border-top: 0; }

/* Nada pil ketersediaan. Anatomi dasarnya sudah ada (.featured-card i /
   .explore-card > span > i); di sini hanya nada yang belum terpetakan. */
.featured-card i.limited, .explore-card > span > i.limited, .grid-card .card-copy > i.limited { background: var(--tag-warning-bg); color: var(--tag-warning-text); }
.featured-card i.soon, .explore-card > span > i.soon, .grid-card .card-copy > i.soon { background: var(--tag-info-bg); color: var(--tag-info-text); }
.featured-card i.full, .explore-card > span > i.full, .grid-card .card-copy > i.full { background: var(--tag-danger-bg); color: var(--tag-danger-text); }
.explore-card > span > i { background: var(--tag-success-bg); color: var(--green); }
.grid-card .card-copy > i { width: fit-content; margin-top: 7px; padding: 3px 6px; border-radius: 5px; background: var(--tag-success-bg); color: var(--green); font-size: var(--text-2xs); font-style: normal; font-weight: 750; }

/* Jawaban FAQ & Terms adalah rich text, bukan satu <p>. */
.accordion .accordion-answer { margin: -5px 32px 18px 0; color: var(--text-subtle); font-size: var(--text-2xs); }
.accordion .accordion-answer > :last-child { margin-bottom: 0; }
.accordion .accordion-answer p { margin: 0 0 var(--space-3); }

/* Social Links — gaya list berteks (keputusan owner 13 Agu 2026: registry ikon
   kanonik sengaja tidak memuat brand mark, jadi gaya ikon menunggu upstream). */
.social-list { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-5); font-size: var(--text-2xs); font-weight: 750; }
.social-list a { text-decoration: underline; text-underline-offset: 3px; }

/* Hasil pencarian tanpa gambar tetap mengisi kolom thumbnail grid-nya. */
.search-result .search-thumb { width: 58px; height: 48px; border-radius: 8px; background: var(--bg-inset); }

/* Kegagalan memuat konten tidak boleh terlihat seperti halaman yang memang kosong. */
.stream-error { display: grid; justify-items: start; gap: var(--space-3); }
.stream-error h2 { margin: 0; font-size: 23px; line-height: 1.1; letter-spacing: -.045em; }
.stream-error p { margin: 0; color: var(--text-subtle); font-size: var(--text-2xs); }
.stream-error button { min-height: 44px; margin-top: var(--space-2); padding: 0 16px; border: 1px solid var(--ink); border-radius: 9px; background: var(--ink); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: 750; cursor: pointer; }
.stream-error button:disabled { opacity: .5; cursor: progress; }

/* ════════════════════════════════════════════════════════════════════════════
   Enam tipe blok terakhir (card · listing_collection · gallery · image_banner ·
   link_card · button).

   LETAK BLOK INI PENTING. Ia berada SESUDAH seluruh @media di atas, sehingga
   aturan berspesifisitas sama — terutama `.link-cards` melawan
   `@media (min-width: 1024px) { .section { padding-top: 45px } }` — dimenangkan
   oleh urutan. Jangan dipindah ke atas "supaya berdekatan dengan kerabatnya".

   Seluruh blok di bawah berdiri di atas `.section`: irama vertikal dan garis
   rambut antar-blok datang dari SATU tempat. Prototipe memberi tiap blok padding
   sendiri (16/20/6, 14/…/4); itu lahir dari kanvas tanpa garis pemisah dan
   sengaja tidak ditiru. Pengecualiannya cuma `.link-cards`, yang memang perlu
   dirapatkan — dan itu pun menyalin perlakuan `.compact-promos` yang sudah ada.
   ════════════════════════════════════════════════════════════════════════════ */

/* ── Blok `card` — satu listing sebagai kartu mandiri ─────────────────────────
   Kartunya adalah `.grid-card` yang SAMA dengan card_grid (satu pembangun,
   `gridCard()`). Yang ditambahkan di sini hanya empat PRESET.

   Preset dipilih lewat `data-layout` pada wadah — pola `.block-spacer[data-size]`
   yang sudah ada — bukan kelas modifier baru pada kartunya, supaya `.grid-card`
   di dalam `.explore-grid` tidak pernah ikut berubah.

   Tinggi gambar prototipe (compact 96px · standard 150px · editorial 190px ·
   image 210px) diterjemahkan ke RASIO, bukan tinggi absolut: `.grid-card
   .image-wrap` memang berasio, dan tidak ada token DS untuk tinggi gambar.
   Rasionya dihitung pada lebar kartu kanonik — --shell 560px dikurangi
   2 × --pad (--space-6) = 520px:
       520/150 ≈ 3,47 → 7/2      520/210 ≈ 2,48 → 5/2      520/190 ≈ 2,74 → 11/4
   Karena rasio, gambar ikut membesar saat --shell melebar ke 980px. Itu perilaku
   yang sama dengan `.promo-hero` (1.72/1 → 2.3/1), bukan pengecualian baru. */

/* Kartu mandiri berdiri sendiri di atas kanvas, bukan satu sel di antara sel
   lain: bayangan dan angkat saat hover datang dari prototipe. `.grid-card` di
   dalam grid sengaja tetap rata — di sana bayangan per-sel jadi kebisingan. */
/* Kartu tunggal MENGISI lebar seksinya. `.grid-card` adalah <button>, dan tombol
   menyusut ke lebar isinya — di `card_grid` hal itu tak terlihat karena sel grid
   yang meregangkannya, tetapi blok `card` menaruhnya langsung di section
   display:block, sehingga kartunya berhenti ~60px sebelum tepi di ponsel. */
.block-card > .grid-card { width: 100%; }
.block-card .grid-card { box-shadow: var(--shadow-card); transition: transform .2s, box-shadow .2s; }
.block-card .grid-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-pop); }
/* Judul kartu selebar halaman lebih besar dari judul sel grid (--text-xs). */
.block-card .grid-card strong { font-family: var(--font-display); font-size: var(--text-sm); }

.block-card .grid-card > .image-wrap { aspect-ratio: 7 / 2; }
.block-card[data-layout="image"] .grid-card > .image-wrap { aspect-ratio: 5 / 2; }
.block-card[data-layout="editorial"] .grid-card > .image-wrap { aspect-ratio: 11 / 4; }
.block-card[data-layout="editorial"] .grid-card strong { font-size: var(--text-h4); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); }

/* Preset `compact` = kartu BARIS. Gambar menjadi kolom kiri bertakar tetap dan
   meregang setinggi kartu (`aspect-ratio: auto`), bukan kotak berasio yang
   memaksa tinggi kartu. Takarnya menyamai kolom thumbnail `.explore-card`
   (102px) — dua kartu baris pada halaman yang sama harus setakar, kalau tidak
   selisih beberapa piksel terbaca sebagai kesalahan. `min-width: 0` pada kolom
   teks wajib: tanpa itu judul panjang mendorong kolom gambar keluar kartu. */
.block-card[data-layout="compact"] .grid-card { flex-direction: row; }
.block-card[data-layout="compact"] .grid-card > .image-wrap { flex: 0 0 102px; aspect-ratio: auto; }
.block-card[data-layout="compact"] .grid-card > .card-copy { flex: 1; min-width: 0; }

/* ── listing_collection layout "list" ────────────────────────────────────────
   Tumpukan SATU kolom di semua lebar (prototipe ADR-G01: flex-direction column,
   tanpa cabang isDesktop). `.explore-grid` sendiri dipaksa `repeat(2, …)` mulai
   640px dan `var(--cols-desktop)` mulai 1024px, jadi mengandalkan
   `--cols-mobile: 1` saja akan pecah jadi dua kolom begitu layar melebar. Yang
   dikunci HANYA jumlah kolomnya — gap, kartu baris, dan sisa anatominya tetap
   milik kelas yang ada. Spesifisitas (0,2,0) menang atas `.explore-grid` (0,1,0)
   di dalam media query tanpa perlu !important. */
.explore-grid.is-list { grid-template-columns: minmax(0, 1fr); }

/* ── Gallery (`gallery`, blok khusus Page) ───────────────────────────────────
   GRID foto persegi. `.gallery-track` BUKAN kerabatnya: itu rel photo_carousel —
   flex, scroll-snap, scroll-padding, dan @1024 berubah jadi grid 3 kolom tetap.
   Galeri punya kelasnya sendiri supaya perubahan pada salah satu pola tidak
   pernah merembes ke pola lainnya.

   `.explore-grid` juga tidak bisa ditumpangi: aturannya memaksa DUA kolom di
   @640 dan `--cols-desktop` di @1024, sehingga pilihan "3 kolom" admin akan
   lenyap di antara 640px–1023px tanpa jejak. Di sini kolom datang dari
   `--gallery-cols` dan berlaku di semua lebar, persis prototipe.

   Tiga nilai prototipe digeser ke token terdekat (nilai literal dilarang):
     border-radius 12px → --radius-md (14px), nilai yang sama dengan foto
                          `.gallery-track img` — satu radius untuk kedua pola.
     gap 10px           → --space-4 (12px), gutter yang sama dengan .promo-more-grid
     latar #F0F2F5      → --bg-inset (#F2F4F7). Nilai persisnya hanya ada sebagai
                          token GARIS (--line-100 / --border-faint), dan garis bukan
                          isian; seluruh penampung gambar lain memakai --bg-inset. */
.gallery-grid { display: grid; grid-template-columns: repeat(var(--gallery-cols, 2), minmax(0, 1fr)); gap: var(--space-4); }
.gallery-grid > button, .gallery-grid > span { min-width: 0; padding: 0; display: block; overflow: hidden; aspect-ratio: 1 / 1; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--bg-inset); }
/* Kursor zoom HANYA pada ubin yang benar-benar membuka lightbox. Saat lightbox
   dimatikan ubinnya <span> dan kursornya tetap default — isyarat yang jujur. */
.gallery-grid > button { cursor: zoom-in; }
.gallery-grid img { width: 100%; height: 100%; object-fit: cover; }

/* ── Video (blok ke-27) ───────────────────────────────────────────────────────
   Ukuran MENGIKUTI LEBAR: tidak ada tinggi tetap di mana pun. Bingkainya memakai
   `aspect-ratio` yang datang dari setting lewat custom property, jadi satu kelas
   melayani empat rasio dan tidak ada satu pun angka tinggi yang perlu ditebak.
   Latar gelap dipasang di bawah iframe supaya jeda muat tidak berkedip putih. */
.block-video .video-frame { position: relative; width: 100%; aspect-ratio: var(--video-ratio, 16 / 9); overflow: clip; border-radius: var(--radius-md); background: var(--vs-black); }
.block-video .video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Sematan Instagram membawa kartunya sendiri (header akun + tombol suka) dengan
   latar putih; sudut membulat di atas kartu putih terbaca sebagai bingkai ganda,
   jadi khusus sumber itu latarnya disamakan dengan permukaan halaman. */
.block-video[data-source="instagram"] .video-frame { background: var(--vs-white); }
.block-video .video-caption { margin-top: var(--space-3); display: block; color: var(--text-subtle); font-size: var(--text-2xs); }

/* ── image_banner ────────────────────────────────────────────────────────────
   Bingkainya memakai `.promo-hero` apa adanya: polanya identik (gambar penuh,
   sudut membulat, `::after` sebagai slot scrim, teks di atas gambar), jadi tidak
   ada keluarga kelas baru untuk bingkai. Yang ditulis di sini hanya tiga hal yang
   benar-benar berbeda dari promo: rasio, ARAH scrim, dan peran teksnya.

   Angka rasio memang literal di seluruh berkas ini (1.72/1, 3/2, 1.48/1, 1.44/1)
   — tokens.css tidak mendefinisikan token rasio, dan mengarang satu dilarang. */
.block-image-banner .promo-hero { aspect-ratio: 2 / 1; }
/* Banner tanpa Action bukan target klik; kursor tidak boleh menjanjikan klik yang
   tidak pernah terjadi. `.promo-hero` mewarisi cursor:pointer dari keluarga promo
   yang memang selalu bisa diklik. */
.block-image-banner .promo-hero:not([role="button"]) { cursor: default; }
/* Scrim banner jatuh dari ATAS ke bawah (teksnya duduk di kaki), sedangkan scrim
   promo menyapu dari kiri — hanya arahnya yang diganti, slotnya tetap `::after`.
   `opacity` dipakai supaya alpha 0.62 milik prototipe tercapai TANPA menulis satu
   pun nilai warna literal: gradien penuh dari `transparent` ke token, lalu seluruh
   lapisannya diredam — hasil alpha di tiap titik identik dengan rgba(5,5,5,.62).
   Catatan urutan: `.promo-hero.is-plain::after { content: none }` di atas punya
   spesifisitas yang SAMA dengan aturan ini, tetapi ia mendeklarasikan `content` —
   properti yang tidak disentuh di sini — sehingga pseudo-element-nya tetap tidak
   pernah lahir pada banner tanpa overlay. */
.block-image-banner .promo-hero::after { background: linear-gradient(180deg, transparent 30%, var(--vs-black) 100%); opacity: .62; }

/* Overlay banner: dua peran teks saja (caption + label CTA), di kaki gambar.
   `.promo-overlay` tidak dipakai ulang karena SELURUH aturan anaknya harus
   dibatalkan (kicker kuning ber-letter-spacing, h2 clamp 23–34px, tombol putih
   `margin-top:auto`) — melawan kelas sebanyak itu lebih rapuh daripada menulis
   tujuh deklarasi yang jujur. */
/* Padding SENGAJA ditumpangkan pada aturan `.promo-overlay` di atas, bukan ditulis
   ulang di sini: keduanya menumpang bingkai `.promo-hero` yang sama, dan aturan
   `@media` yang menaikkannya jadi 34px di ≥1024px hidup di blok media itu. Menulis
   padding sendiri di baris ini membuatnya beku di satu angka sementara saudaranya
   melangkah — dua overlay pada bingkai yang identik terbaca sebagai kesalahan. */
.banner-overlay { position: absolute; z-index: 1; inset: 0; display: flex; flex-direction: column; justify-content: flex-end; gap: var(--space-3); }
.banner-overlay > strong { color: var(--text-on-dark); font-size: var(--text-sm); font-weight: var(--fw-bold); line-height: var(--lh-snug); }
.banner-overlay > span { display: inline-flex; align-items: center; gap: var(--space-3); color: var(--yellow); font-size: var(--text-xs); font-weight: var(--fw-bold); }
/* Ukuran ikon di keluarga ini memang ditulis lepas (13–17px di .promo-overlay,
   .feature-promo, .text-action); tidak ada token ukuran ikon di tokens.css. */
.banner-overlay svg { width: 14px; height: 14px; }

/* ── Link Card (`link_card`) ─────────────────────────────────────────────────
   SATU tautan sebagai kartu berdiri sendiri. Dua acuan, keduanya sudah ada di
   berkas ini: ANATOMI dari `.explore-card` (grid auto | minmax(0,1fr) | auto,
   kartu berbingkai, ikon di ujung) dan IRAMA SEKSI dari `.compact-promos` —
   masalahnya sama persis, yaitu satu blok kecil yang sering ditumpuk beberapa
   kali berturut-turut dan harus terbaca sebagai satu kelompok, bukan sebagai
   beberapa seksi bergaris.

   Nilai prototipe yang tidak punya token bernilai persis dinaikkan ke anak
   tangga token terdekat (resep USAGE.md): radius 12px → --radius-md (14px),
   padding 14/15px → --space-4/--space-5 (12/16px), gap 13px → --space-4 (12px),
   subtitle 12px → --text-2xs (11px). Chevron #B9BEC7 tidak punya padanan sama
   sekali (--ink-200 = #C3C7CF); dipakai --text-subtle seperti SELURUH chevron
   lain di berkas ini, supaya satu keluarga ikon punya satu warna. Subtitle
   #9AA0AA memang persis --ink-300, tapi alias semantiknya adalah --text-disabled
   ("teks nonaktif") — dipakai --text-subtle seperti `.link-list small` yang
   membawa data yang sama. Peran tokennya yang menang, bukan angkanya. */
.link-cards { display: grid; gap: var(--space-3); }

/* `min-height` bukan hiasan: kartu tanpa ubin ikon DAN tanpa kicker tingginya
   jatuh ke ~44px, tepat di ambang target sentuh. Dikunci lewat token. */
.link-card {
  min-width: 0; min-height: var(--field-h);
  padding: var(--space-4) var(--space-5);
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-4); align-items: center;
  border: 1px solid var(--border-default); border-radius: var(--radius-md);
  background: var(--vs-white); box-shadow: var(--shadow-card);
  text-align: left; cursor: pointer; transition: border-color .2s;
}
/* Tanpa ubin ikon, kolomnya ikut dilepas — pola `.explore-card.is-thumbless`. */
.link-card.is-iconless { grid-template-columns: minmax(0, 1fr) auto; }
.link-card:hover { border-color: var(--ink-200); }
/* --space-9 bernilai tepat 40px, ukuran ubin di prototipe. */
.link-card-icon { width: var(--space-9); height: var(--space-9); display: grid; place-items: center; border: 1px solid var(--border-faint); border-radius: var(--radius-sm); background: var(--bg-subtle); }
.link-card-icon svg { width: 18px; height: 18px; }
.link-card-copy { min-width: 0; display: grid; gap: var(--space-1); }
.link-card-copy small { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: var(--fw-bold); letter-spacing: var(--ls-label); }
/* Label tautan sering berupa domain atau alamat panjang tanpa spasi; tanpa ini
   ia mendorong kolom ikon ekor keluar kartu. */
.link-card-copy strong { font-size: var(--text-sm); font-weight: var(--fw-bold); overflow-wrap: anywhere; }
/* Ikon ekor memakai gerak yang SAMA dengan `.link-list` — satu keluarga objek
   (Link), satu gerak — karena rupanya pun bisa dua (external-link/chevron). */
.link-card > svg { width: 16px; height: 16px; color: var(--text-subtle); transition: transform .2s; }
.link-card:hover > svg { transform: translate(2px, -2px); }

/* ── Button / CTA (blok `button`, pageOnly) ──────────────────────────────────
   Tombolnya MEMINJAM `.route-cta` supaya produk tidak punya dua skala tombol
   utama; yang benar-benar belum punya kelas cuma dua hal: tombol yang membentang
   penuh, dan varian outline.

   `display: grid` yang membentangkannya — item grid di-blockify, sehingga
   `inline-flex` milik `.route-cta` berhenti menyusut ke lebar teksnya. Itu pola
   yang sama dengan `.promo-detail-cta { width: 100% }`, hanya tanpa menambah
   deklarasi lebar di tombolnya.

   `.route-cta` sendiri ditulis JAUH DI BAWAH bersama kelas rute; aturan di sini
   tetap menang lewat SPESIFISITAS (`.route-cta.is-outline` 0,2,0 vs `.route-cta`
   0,1,0; `.block-button .route-cta:hover` 0,3,0) — posisinya tidak menentukan
   hasil, jadi jangan dipindah "supaya urut".

   #E1E4EA (border outline prototipe) tidak punya token bernilai sama; dipetakan
   ke --border-default (#E6E8EE), hairline resmi produksi. #101114 (teks outline)
   dipetakan ke --text-body (#111214); --text-strong terlalu gelap (#050505 =
   warna tombol primary). */
.block-button { display: grid; }
.route-cta.is-outline { border-color: var(--border-default); background: var(--surface-0); color: var(--text-body); }
/* Hover DIKUNCI ke blok ini: `.route-cta` di kartu detail promo sengaja tidak
   punya keadaan hover, dan menulis `.route-cta:hover` polos akan mengubah
   permukaan yang tidak diminta siapa pun.

   Dipakai `opacity` seperti prototipe, BUKAN `--primary-hover`. Alasannya bukan
   selera: warna diam tombol ini `var(--ink)`, dan `--ink` ditulis ULANG saat
   runtime oleh Appearance (app.js menyuntik warna Text pilihan admin). Menukar
   latarnya ke `--primary-hover` yang literal membuat tombol brand berwarna
   melompat ke abu-kehitaman begitu disentuh kursor — kustomisasi admin hilang
   tepat pada keadaan yang paling terlihat. Opasitas menjaga warnanya apa pun
   nilainya, dan `opacity` bukan besaran yang punya token di tokens.css. */
.block-button .route-cta:hover { opacity: .92; }
.block-button .route-cta.is-outline:hover { border-color: var(--border-default); background: var(--bg-subtle); }


/* ── Permukaan rute (R3) ─────────────────────────────────────────────────────
   Enam rute bersih memakai kerangka yang SUDAH ADA: topbar `.subpage-header`
   (styles.css:292, sama dengan layar Jadwal) dan `.section` sebagai wadah.
   Yang baru hanya kepala rute, status screen, dan halaman promo.            */

/* Kepala rute. Panel berisi yang sempat dipasang di sini DICABUT (owner, 4 Sep
   2026): halaman yang butuh kepala berbranding sudah dilayani FITUR HEADER
   HALAMAN (`page.header` → `.route-form-hero`, mesin yang sama dengan header
   form), jadi menambah panel kedua di kepala teks berarti dua mekanisme untuk
   maksud yang sama — dan yang kedua hanya berlaku di berkas gaya, tidak bisa
   diatur admin dari dashboard. Kepala teks kembali polos seperti sedia kala. */
.route-head { display: grid; gap: var(--space-2); }
.route-head > span { color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .18em; text-transform: uppercase; }
.route-head h1 { margin: 0; font-size: 29px; line-height: 1.02; letter-spacing: -.05em; }
.route-head .vs-rich { margin-top: var(--space-2); }
.route-empty { margin: 0; color: var(--text-subtle); font-size: var(--text-2xs); }

/* Detail listing sebagai HALAMAN. Anatominya sama persis dengan di dalam dialog —
   satu builder, dua wadah — jadi yang perlu diatur hanya pembatalan padding
   `.section` supaya hero-nya menempel tepi seperti di dialog. */
.route-listing { padding-inline: 0; padding-top: 0; border-top: 0; }
.route-listing .listing-hero { border-radius: 0; }

/* ── Detail promo `/promo/:slug` ─────────────────────────────────────────────
   Kartu mandiri, bukan elemen yang mengambang langsung di `.section`. Hero
   menempel ke tepi kartu — radius dan `overflow: clip` dipegang WADAH, jadi
   gambar tidak butuh radius sendiri dan tidak akan meleset setengah piksel di
   sudut. Anatomi kartunya meminjam kartu status 404 (border + radius-lg +
   surface-0 + shadow-card) supaya permukaan publik memakai satu bahasa.

   Skalanya fluid, bukan berbreakpoint: judul memakai clamp() dan grid "promo
   lain" memakai auto-fill, sehingga shell 560px → 980px tidak perlu media query
   tersendiri dan lebar di antaranya tidak pernah jatuh ke tata letak yang salah. */
.route-promo { display: grid; gap: var(--space-9); }

.promo-detail { display: grid; overflow: clip; border: 1px solid var(--border-default); border-radius: var(--radius-lg); background: var(--surface-0); box-shadow: var(--shadow-card); }
.promo-detail-hero { position: relative; aspect-ratio: 1.72 / 1; background: var(--bg-inset); }
.promo-detail-hero img { width: 100%; height: 100%; object-fit: cover; }
/* Badge = angka penawaran; ditempel di hero sebagai chip merek, bukan teks biasa. */
.promo-detail-badge { position: absolute; top: var(--space-4); left: var(--space-4); padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill); background: var(--brand); color: var(--brand-ink); font-size: var(--text-2xs); font-weight: var(--fw-bold); letter-spacing: var(--ls-label-sm); }
.promo-detail-badge.is-inline { position: static; justify-self: start; }
.promo-detail-body { padding: var(--space-6); display: grid; gap: var(--space-3); }
.promo-detail-label { color: var(--accent-orange); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .16em; }
.promo-detail-body h1 { margin: 0; font-family: var(--font-display); font-size: clamp(26px, 6.4vw, var(--text-h1)); line-height: var(--lh-tight); letter-spacing: var(--ls-display); }
.promo-detail-sub { margin: 0; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-relaxed); }
.promo-detail-period { margin: 0; display: flex; align-items: center; gap: var(--space-2); color: var(--text-subtle); font-size: var(--text-2xs); font-weight: var(--fw-semibold); letter-spacing: .06em; }
.promo-detail-period svg { flex: none; width: 14px; height: 14px; }
.promo-detail-cta { width: 100%; margin-top: var(--space-2); }

/* Promo lain — satu kartu per promo. Kolomnya ditentukan lebar yang tersedia
   (`auto-fill` + `min()`), jadi kartu tidak pernah lebih sempit dari 232px dan
   tidak pernah melebar melewati satu kolom penuh di layar sempit. */
.promo-more { display: grid; gap: var(--space-5); }
.promo-more-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 232px), 1fr)); gap: var(--space-4); }
.promo-mini { min-width: 0; display: grid; grid-template-rows: auto 1fr; overflow: clip; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); cursor: pointer; transition: transform .2s, border-color .2s, box-shadow .2s; }
.promo-mini:hover { transform: translateY(-2px); border-color: var(--ink-200); box-shadow: var(--shadow-card); }
.promo-mini:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.promo-mini-thumb { position: relative; aspect-ratio: 16 / 10; background: var(--bg-inset); }
.promo-mini-thumb img { width: 100%; height: 100%; object-fit: cover; }
.promo-mini-badge { position: absolute; top: var(--space-3); left: var(--space-3); padding: var(--space-1) var(--space-2); border-radius: var(--radius-pill); background: var(--brand); color: var(--brand-ink); font-size: var(--text-2xs); font-weight: var(--fw-bold); }
.promo-mini-body { padding: var(--space-4); display: grid; align-content: start; gap: var(--space-1); }
.promo-mini-body small { color: var(--accent-orange); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .14em; text-transform: uppercase; }
.promo-mini-body strong { color: var(--text-strong); font-size: var(--text-xs); line-height: var(--lh-snug); }
/* Subtitle dipotong dua baris supaya tinggi kartu tetap rata di satu baris grid. */
.promo-mini-body span { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; color: var(--text-subtle); font-size: var(--text-2xs); line-height: var(--lh-normal); }

/* R8 - hero berbranding form publik. Empat mode (foto / solid / gradient /
   pattern glyph) semuanya menulis gaya INLINE dari formHeaderStyle(); aturan di
   bawah hanya mengurus tata letak, scrim, dan warna teks. Tidak ada nilai warna
   literal: dua kelas tinta memakai token --text-on-dark dan --ink. */
.route-form-hero { position: relative; padding: var(--space-7) var(--pad); display: flex; align-items: flex-end; overflow: hidden; background-color: var(--bg-inset); }
.route-form-scrim { position: absolute; inset: 0; background: var(--ink); pointer-events: none; }
/* Scrim mengikuti arah tinta: latar gelap diredam dengan gelap, latar terang
   dengan terang. Scrim hitam di atas latar terang justru memperburuk kontras. */
.route-form-hero.is-ink-dark .route-form-scrim { background: var(--vs-white); }
.route-form-hero-copy { position: relative; display: grid; gap: var(--space-2); width: 100%; }
.route-form-hero-copy > span { font-size: var(--text-2xs); font-weight: 750; letter-spacing: .18em; text-transform: uppercase; opacity: .78; }
.route-form-hero-copy h1 { margin: 0; font-size: 29px; line-height: 1.02; letter-spacing: -.05em; }
.route-form-hero.is-ink-light { color: var(--text-on-dark); }
.route-form-hero.is-ink-dark { color: var(--ink); }
.route-form-mount { display: grid; gap: var(--space-3); }
.route-form-note { margin: 0; color: var(--text-subtle); font-size: var(--text-2xs); }

.route-cta { min-height: 45px; padding: 0 16px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; border: 1px solid var(--ink); border-radius: 9px; background: var(--ink); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: 750; cursor: pointer; }
.route-cta svg { width: 16px; height: 16px; }

/* Status screen — 404 dan seluruh keadaan gating memakai SATU kartu, persis seperti
   `notFoundVals()` prototipe yang dipakai ulang enam kali.

   Anatomi direproduksi dari kartu 404 rancangan owner:
   12.04.02/Storefront & Public.dc.html:714. Tiga nilai inline master diterjemahkan
   ke token karena skala token yang menang (USAGE.md):
     font-weight 800   → --fw-bold (skala token berhenti di 700)
     letter-spacing -0.03em → --ls-display (-0.02em)
     padding 34px      → --space-8 (32px)
   Baris kode mono mengikuti pola `GUI/21/DENIED` ("ERROR 403 · FORBIDDEN"). */
.route-status { padding-top: var(--space-8); }
.status-card { padding: var(--space-8); display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); }
.status-code { font-family: var(--font-display); font-size: var(--text-display-lg); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; color: var(--text-strong); }
.status-kicker { font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-subtle); }
.status-card h2 { margin: 0; font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-bold); color: var(--text-strong); }
.status-card p { margin: 0; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-relaxed); }
.status-cta { min-height: var(--field-h-sm); margin-top: var(--space-1); padding: 0 var(--space-4); display: inline-flex; align-items: center; border: 0; border-radius: var(--radius-xs); background: var(--primary); color: var(--primary-contrast); font-size: var(--text-xs); font-weight: var(--fw-semibold); cursor: pointer; }
.status-cta:hover { background: var(--primary-hover); }
.status-cta:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }

/* ── Runtime form publik `/f/:slug` (R5 · Integrasi) ─────────────────────────
   Satu permukaan untuk 21 tipe field, multi-step, dan layar kunci PIN. Anatominya
   mewarisi anatomi form konsultasi yang digantikannya — label di atas kontrol,
   kontrol selebar penuh, tinggi sentuh 44px — supaya perpindahannya tidak terasa
   seperti berganti produk. Stepper direproduksi dari GUI/09/STEPPER. */
.vsform { display: grid; gap: var(--space-4); }
/* Batas antar field.
   Sebelum ini SELURUH permukaan form memakai satu ukuran huruf — label, isi
   input, dan teks bantuan sama-sama 11px — sementara jarak DI DALAM satu field
   (4px) hampir sama dengan jarak ANTAR field (12px). Tidak ada satu pun sinyal
   yang memisahkan: pembaca harus mengeja isinya untuk tahu di mana satu field
   berakhir. Tiga hal diperbaiki bersamaan, karena memperbaiki salah satunya
   saja tidak cukup:
     1. label naik ke 13px + warna terkuat, bantuan tetap 11px redup — hierarki;
     2. jarak antar field (16px) jadi dua kali jarak di dalamnya (8px) — irama;
     3. garis rambut di antara dua field yang bersebelahan — batas eksplisit.
   Kelas `has-rule` dipasang runtime (markFieldBoundaries di formrun.js), BUKAN
   lewat selektor sibling `+`: `+` tetap menghitung tetangga yang sedang `hidden`,
   sehingga field pertama sesudah sebuah judul bagian ikut bergaris hanya karena
   ada field tersembunyi menyelip di antaranya. Field pertama sesudah judul memang
   tidak bergaris — judulnya sendiri sudah menjadi batas. */
.vsform-panel { display: grid; gap: var(--space-5); }
.vsform-panel > .vsform-field.has-rule { padding-top: var(--space-5); border-top: 1px solid var(--border-faint); }
.vsform-field { display: grid; gap: var(--space-3); }
.vsform-field > span { color: var(--text-strong); font-size: var(--text-xs); font-weight: var(--fw-semibold); }
.vsform-req { margin-left: 3px; color: var(--accent-red); font-style: normal; }
.vsform-field input, .vsform-field select, .vsform-field textarea {
  width: 100%; min-height: var(--field-h); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-input); border-radius: var(--radius-md);
  background: var(--vs-white); color: var(--text-strong); font: inherit; font-size: var(--text-xs); outline: 0;
}
.vsform-field textarea { min-height: calc(var(--field-h) * 2); resize: vertical; }
.vsform-field input:focus-visible, .vsform-field select:focus-visible, .vsform-field textarea:focus-visible {
  border-color: var(--border-strong); box-shadow: var(--shadow-focus);
}
.vsform-hint { color: var(--text-subtle); font-size: var(--text-2xs); line-height: var(--lh-normal); }
.vsform-warn { color: var(--tag-danger-text); font-size: var(--text-2xs); }

/* Pilihan berbasis kotak centang / radio. Seluruh baris dapat diklik. */
.vsform-choices { display: grid; gap: var(--space-2); }
.vsform-check {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--space-3);
  min-height: var(--field-h); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-faint); border-radius: var(--radius-md);
  font-size: var(--text-2xs); cursor: pointer;
}
.vsform-check:hover { border-color: var(--border-default); background: var(--surface-1); }
.vsform-check input { width: 17px; height: 17px; margin: 0; accent-color: var(--ink-900); }

/* Pilihan visual: gallery pick, visual grid, card select. Kartu mengisi lebar
   yang ada — bukan jumlah kolom tetap, karena rute ini dibuka di layar mana pun. */
.vsform-visual { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--space-3); }
.vsform-visual.is-card { grid-template-columns: minmax(0, 1fr); }
/* `settings.display` Card Select (card_list / compact_grid / image_grid / horizontal)
   selama ini HANYA menempelkan kelas `is-<display>`; tidak ada satu pun aturan yang
   menyasarnya, sementara `.is-card` di atas mengunci satu kolom. Akibatnya segmented
   "Tampilan" di dashboard bisa dipilih dan tidak pernah mengubah apa pun — kontrol
   mati yang terbaca sebagai kontrol hidup. Tiga baris di bawah menutup itu.
   `card_list` sengaja tidak punya aturan sendiri: ia MEMANG satu kolom di atas. */
.vsform-visual.is-card.is-compact_grid { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
/* `align-items: start` di atas menahan kartu agar setinggi ISINYA saja. Bawaan
   grid (`stretch`) menyamakan tinggi seluruh kartu dalam satu baris, sehingga
   kartu tanpa foto berdampingan dengan kartu berfoto berubah jadi kotak tinggi
   yang isinya melayang di tengah — persis rupa "gambar gagal dimuat" yang
   ingin dihindari. Baris jadi tidak rata tingginya, dan itu memang jujur. */
.vsform-visual.is-card.is-image_grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.vsform-visual.is-card.is-horizontal { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(210px, 1fr); overflow-x: auto; scroll-snap-type: x mandatory; }
.vsform-visual.is-card.is-horizontal .vsform-visual-item { scroll-snap-align: start; }
/* Di bawah 360px dua kolom menyisakan ~150px per kartu — label harga bermono
   ("Rp 175.000 / jam") meluber sebelum judulnya sempat terbaca. Turun ke satu
   kolom di sana lebih jujur daripada dua kolom yang isinya terpotong. */
@media (max-width: 359px) {
  .vsform-visual.is-card.is-compact_grid { grid-template-columns: minmax(0, 1fr); }
}
.vsform-visual-item {
  display: grid; gap: var(--space-2); padding: var(--space-3); text-align: left;
  border: 1px solid var(--border-faint); border-radius: var(--radius-md);
  background: var(--bg-card); font: inherit; cursor: pointer;
}
.vsform-visual.is-card .vsform-visual-item { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
/* Kartu Card Select yang membawa foto: gambar di ATAS, teks di bawah. Tanpa ini
   ia masuk kolom pertama grid dua-kolom milik aturan di atas dan menghimpit
   seluruh salinan teksnya ke sisa lebar yang tidak muat. */
.vsform-visual.is-card .vsform-visual-item.has-thumb { grid-template-columns: minmax(0, 1fr); align-items: start; }
.vsform-visual-item:hover { border-color: var(--border-default); }
/* Terpilih (keputusan owner 17 Okt 2026, kartu terpilih sering terlewat): kartu
   DIISI penuh warna pilihan field — hitam (bawaan), identity, atau custom yang
   dipasang runtime sebagai --vsform-sel-bg/-fg (teks putih/hitam, kontras tertinggi).
   Garis 1.5px dari GUI/15/ROOM-SELECTOR + radio bertitik dari "Panel pilihan ruang";
   setengah piksel ekstra digambar inset supaya kartu tidak bergeser saat dipilih. */
.vsform-visual { --vsform-sel-bg: var(--ink-900); --vsform-sel-fg: var(--text-on-dark); }
.vsform-visual.is-sel-identity { --vsform-sel-bg: var(--brand); --vsform-sel-fg: var(--brand-ink); }
.vsform-visual-item { position: relative; }
.vsform-visual-item.is-on {
  border-color: var(--vsform-sel-bg); background: var(--vsform-sel-bg); color: var(--vsform-sel-fg);
  box-shadow: inset 0 0 0 .5px var(--vsform-sel-bg), var(--shadow-card);
}
.vsform-visual-item.is-on .vsform-visual-copy b,
.vsform-visual-item.is-on .vsform-visual-copy small { color: var(--vsform-sel-fg); }
/* Lencana dibalik: lencana kuning di atas isi identity tidak akan terlihat. */
.vsform-visual-item.is-on .vsform-visual-copy i { background: var(--vsform-sel-fg); color: var(--vsform-sel-bg); }
.vsform-visual-mark {
  position: absolute; top: calc(var(--space-3) + var(--space-2)); right: calc(var(--space-3) + var(--space-2));
  width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--line-200); border-radius: var(--radius-pill); background: var(--surface-0); color: var(--surface-0);
}
.vsform-visual-item:not(.has-thumb) .vsform-visual-mark { top: var(--space-3); right: var(--space-3); }
.vsform-visual-item:not(.has-thumb) .vsform-visual-copy { padding-right: calc(20px + var(--space-2)); }
.vsform-visual-item.is-on .vsform-visual-mark { border-color: var(--vsform-sel-fg); background: var(--vsform-sel-bg); }
.vsform-visual-mark:not(.is-check)::after { content: ''; width: 9px; height: 9px; border-radius: var(--radius-pill); background: var(--vsform-sel-fg); opacity: 0; }
.vsform-visual-item.is-on .vsform-visual-mark:not(.is-check)::after { opacity: 1; }
/* Mode Multiple: kotak centang Multi-room selector — warna dibalik dari isi kartu. */
.vsform-visual-mark.is-check { border-radius: var(--radius-xs); }
.vsform-visual-mark.is-check svg { opacity: 0; }
.vsform-visual-item.is-on .vsform-visual-mark.is-check { background: var(--vsform-sel-fg); color: var(--vsform-sel-bg); }
.vsform-visual-item.is-on .vsform-visual-mark.is-check svg { opacity: 1; }
.vsform-visual-item:disabled { opacity: .45; cursor: not-allowed; }
.vsform-visual-item img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius-sm); }
.vsform-visual-copy { min-width: 0; display: grid; gap: 2px; }
.vsform-visual-copy strong { font-size: var(--text-2xs); font-weight: var(--fw-semibold); }
.vsform-visual-copy b { color: var(--text-body); font-family: var(--font-mono); font-size: var(--text-2xs); }
.vsform-visual-copy small { color: var(--text-subtle); font-size: var(--text-2xs); line-height: var(--lh-normal); }
.vsform-visual-copy i {
  justify-self: start; padding: 1px var(--space-2); border-radius: var(--radius-pill);
  background: var(--brand); color: var(--brand-ink); font-size: var(--text-2xs); font-style: normal; font-weight: var(--fw-semibold);
}

/* Konten struktural — judul, teks, pemisah. */
.vsform-heading { margin: var(--space-2) 0 0; font-size: var(--text-md); letter-spacing: var(--ls-tight); }
.vsform-desc { margin: 0; color: var(--text-muted); font-size: var(--text-2xs); line-height: var(--lh-relaxed); }
.vsform-divider { width: 100%; height: 1px; margin: var(--space-2) 0; border: 0; background: var(--border-faint); }
.vsform-rich { font-size: var(--text-2xs); line-height: var(--lh-relaxed); }

/* Persetujuan: dokumennya dapat dibaca DI TEMPAT, bukan tautan keluar. */
.vsform-agree { display: grid; gap: var(--space-2); }
.vsform-terms { border: 1px solid var(--border-faint); border-radius: var(--radius-md); background: var(--bg-subtle); }
/* Judul panel — dulu `summary` sebuah `<details>`. Ketentuan kini selalu terbuka
   (lihat renderer `agreement` di formrun.js), jadi tidak ada lagi yang bisa diklik
   dan `cursor: pointer` dicabut: kursor tangan pada teks yang tidak menanggapi
   klik adalah janji palsu. */
.vsform-terms-title {
  display: block; padding: var(--space-3) var(--space-3) 0; color: var(--text-strong);
  font-size: var(--text-2xs); font-weight: var(--fw-semibold);
}
/* Naik dari `--text-2xs` ke `--text-xs`: teks ini berpindah dari lampiran yang
   terlipat menjadi bacaan yang memang diminta dibaca sebelum menyetujui. */
.vsform-terms .vs-rich { padding: var(--space-2) var(--space-3) var(--space-3); font-size: var(--text-xs); line-height: var(--lh-relaxed); }

/* Stepper — GUI/09/STEPPER. Nomor, judul, dan penanda langkah yang sudah lewat. */
.vsform-steps { margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-3); list-style: none; }
.vsform-step { display: flex; align-items: center; gap: var(--space-2); color: var(--text-subtle); font-size: var(--text-2xs); }
.vsform-step b {
  width: 22px; height: 22px; display: grid; place-items: center; border-radius: var(--radius-pill);
  background: var(--ink-100); color: var(--text-muted); font-family: var(--font-mono); font-size: var(--text-2xs);
}
.vsform-step.is-active { color: var(--text-strong); font-weight: var(--fw-semibold); }
.vsform-step.is-active b { background: var(--ink-900); color: var(--text-on-dark); }
.vsform-step.is-done b { background: var(--tag-success-bg); color: var(--tag-success-text); }

/* Navigasi & keadaan akhir. */
.vsform-nav { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.vsform-submit, .vsform-back {
  min-height: 45px; padding: 0 var(--space-5); display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md); font: inherit; font-size: var(--text-2xs); font-weight: var(--fw-semibold); cursor: pointer;
}
.vsform-submit { flex: 1; border: 1px solid var(--ink-900); background: var(--ink-900); color: var(--text-on-dark); }
.vsform-submit:disabled { opacity: .6; cursor: progress; }
.vsform-back { border: 1px solid var(--border-default); background: var(--bg-card); color: var(--text-body); }
.vsform-status { color: var(--text-muted); font-size: var(--text-2xs); line-height: var(--lh-normal); }
.vsform-lock, .vsform-done {
  display: grid; gap: var(--space-3); padding: var(--space-5);
  border: 1px solid var(--border-faint); border-radius: var(--radius-lg); background: var(--bg-card);
}
.vsform-lock h3, .vsform-done h3 { margin: 0; font-size: var(--text-md); letter-spacing: var(--ls-tight); }
.vsform-lock p, .vsform-done p { margin: 0; color: var(--text-muted); font-size: var(--text-2xs); line-height: var(--lh-relaxed); }
.vsform-link {
  justify-self: start; padding: 0; border: 0; background: none; color: var(--text-body);
  font: inherit; font-size: var(--text-2xs); text-decoration: underline; cursor: pointer;
}
/* Pesan sukses yang dapat diatur admin (R6): rich text, jadi ia butuh ritme
   paragraf sendiri — `.vsform-done p` di atas hanya mengurus kalimat bawaan. */
.vsform-done-msg { color: var(--text-muted); font-size: var(--text-2xs); line-height: var(--lh-relaxed); }
.vsform-done-msg > :first-child { margin-top: 0; }
.vsform-done-msg > :last-child { margin-bottom: 0; }

/* ── Ergonomi form publik di layar sentuh ────────────────────────────────────
   Kontrol form mewarisi `--text-2xs` (11px). Token itu MICRO-LABEL — dimaksudkan
   untuk kapsul kategori dan keterangan, bukan untuk teks yang diketik pengunjung.
   Di ponsel akibatnya dua, dan keduanya tidak terlihat sama sekali di desktop:

   1. iOS Safari MEMBESARKAN seluruh halaman begitu field ber-font < 16px disentuh,
      dan tidak mengembalikannya setelah field ditinggalkan. Formnya terasa
      "melompat" sejak field pertama. 16px adalah ambang pasti milik iOS, bukan
      angka selera: 15.9px masih memicu zoom, jadi nilainya ditulis literal dan
      TIDAK boleh diganti token — semua token teks yang ada bernilai di bawahnya.
   2. 11px dengan padding vertikal 4px menyisakan pita teks ~13px di tengah kotak
      44px. Tingginya lolos ambang sentuh sementara isinya tidak terbaca, sehingga
      pemeriksaan aksesibilitas berbasis ukuran target pun lolos tanpa keluhan.

   Ditulis sebagai override di breakpoint ponsel supaya tipografi desktop yang
   sudah disetujui tidak ikut bergeser. `--field-h` di-override pada `.vsform`,
   bukan pada `:root`, karena token yang sama dipakai kontrol Schedule dan
   Listing — menaikkannya global akan menggeser komponen yang tidak diminta. */
@media (max-width: 639px) {
  .vsform { --field-h: 48px; }
  .vsform-field input, .vsform-field select, .vsform-field textarea {
    padding: var(--space-3) var(--space-4);
    font-size: 16px;
  }
  .vsform-field > span { font-size: var(--text-xs); }
  .vsform-hint, .vsform-warn { font-size: var(--text-xs); }
  .vsform-check { min-height: 48px; font-size: var(--text-xs); }
}

/* ── Promo slider berisi SATU promo ──────────────────────────────────────────
   `.promo-hero` sengaja lebih sempit dari relnya (`calc(100% - 30px)`, naik ke
   65px di desktop) supaya tepi slide berikutnya mengintip — isyarat "ini bisa
   digeser" tanpa perlu panah. Isyarat itu benar selama slide-nya lebih dari satu.

   Dengan satu promo tidak ada yang mengintip: sisa ruangnya jadi celah kosong di
   kanan dan kartunya terbaca sebagai terpotong, bukan sebagai rel yang bisa
   digeser. Titik indikator pun tidak digambar (renderers.js hanya memasangnya
   saat promos.length > 1), jadi tidak ada satu pun petunjuk yang menjelaskan
   celah itu.

   `:only-child` dipilih supaya aturannya MEMBATALKAN DIRI begitu promo kedua
   terbit — tidak ada kelas yang harus dilepas dan tidak ada keadaan yang bisa
   tertinggal salah. Ditulis dengan `.promo-slider >` agar spesifisitasnya (0,3,0)
   mengalahkan override flex-basis di kedua breakpoint tanpa bergantung urutan. */
.promo-slider > .promo-hero:only-child { flex-basis: 100%; }

/* ── Chip Select (`chip_select`) ─────────────────────────────────────────────
   Pil berwarna berikon untuk pilihan kategoris pendek. Nada warna diambil dari
   keluarga token `--tag-*` yang sudah ada — 11 pasang bg/border/text yang sudah
   disetel kontrasnya. Komponen ini TIDAK mendefinisikan warna sendiri; menambah
   nada berarti menambah token, bukan menuliskan hex di sini.

   Kelas nada dipasang oleh renderer dari data admin, jadi daftarnya di bawah
   berfungsi sekaligus sebagai penjaga: nada yang tidak punya kelas tidak akan
   pernah tampil berwarna, ia jatuh ke .is-neutral. */
.vsform-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.vsform-chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: var(--field-h); padding: 0 var(--space-4);
  border: 1px solid var(--tag-neutral-border); border-radius: var(--radius-pill);
  background: var(--tag-neutral-bg); color: var(--tag-neutral-text);
  font-size: var(--text-2xs); font-weight: var(--fw-semibold);
  cursor: pointer; transition: box-shadow .15s, transform .15s;
}
.vsform-chip-icon { display: inline-flex; line-height: 0; }
.vsform-chip-icon svg { width: 18px; height: 18px; }
.vsform-chip:hover { transform: translateY(-1px); }
/* Terpilih ditandai cincin sewarna teksnya, BUKAN dengan menukar latar jadi
   gelap: menukar latar menghapus nada yang justru jadi alasan chip ini berwarna,
   dan pengguna kehilangan jejak kategori yang baru saja ia pilih. */
.vsform-chip.is-on { box-shadow: 0 0 0 2px currentColor inset; }
.vsform-chip:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }

.vsform-chip.is-neutral  { border-color: var(--tag-neutral-border);  background: var(--tag-neutral-bg);  color: var(--tag-neutral-text); }
.vsform-chip.is-success  { border-color: var(--tag-success-border);  background: var(--tag-success-bg);  color: var(--tag-success-text); }
.vsform-chip.is-mint     { border-color: var(--tag-mint-border);     background: var(--tag-mint-bg);     color: var(--tag-mint-text); }
.vsform-chip.is-info     { border-color: var(--tag-info-border);     background: var(--tag-info-bg);     color: var(--tag-info-text); }
.vsform-chip.is-blue     { border-color: var(--tag-blue-border);     background: var(--tag-blue-bg);     color: var(--tag-blue-text); }
.vsform-chip.is-purple   { border-color: var(--tag-purple-border);   background: var(--tag-purple-bg);   color: var(--tag-purple-text); }
.vsform-chip.is-pink     { border-color: var(--tag-pink-border);     background: var(--tag-pink-bg);     color: var(--tag-pink-text); }
.vsform-chip.is-warning  { border-color: var(--tag-warning-border);  background: var(--tag-warning-bg);  color: var(--tag-warning-text); }
.vsform-chip.is-orange   { border-color: var(--tag-orange-border);   background: var(--tag-orange-bg);   color: var(--tag-orange-text); }
.vsform-chip.is-danger   { border-color: var(--tag-danger-border);   background: var(--tag-danger-bg);   color: var(--tag-danger-text); }
.vsform-chip.is-identity { border-color: var(--tag-identity-border); background: var(--tag-identity-bg); color: var(--tag-identity-text); }

/* ── Promo List (`promo_list`) ───────────────────────────────────────────────
   Indeks seluruh promo yang sedang tayang. Anatominya sengaja MENGIKUTI
   `.compact-promo` — thumbnail, salinan, chevron dalam grid 3 kolom — supaya
   daftar promo terbaca sebagai keluarga yang sama dengan kartu promo di beranda,
   bukan komponen asing. Bedanya hanya satu baris tambahan: periode berlaku.

   Judul dan subjudul TIDAK dipotong dengan ellipsis di sini (berbeda dari
   `.compact-promo`). Di beranda kartu promo adalah selingan sehingga satu baris
   sudah cukup; di halaman indeks, judul promo yang terpotong justru menyembunyikan
   satu-satunya informasi yang dipakai pengunjung untuk memilih. */
.promo-list-items { display: grid; gap: var(--space-3); }
.promo-list-item {
  min-width: 0; padding: var(--space-3);
  display: grid; grid-template-columns: 84px minmax(0, 1fr) 20px;
  gap: var(--space-4); align-items: center;
  border: 1px solid var(--border-default); border-radius: var(--radius);
  background: var(--vs-white); text-align: left; cursor: pointer;
  transition: border-color .15s, transform .15s;
}
.promo-list-item:hover { border-color: var(--ink-200); transform: translateY(-1px); }
.promo-list-item > img { width: 84px; height: 72px; border-radius: 10px; background: var(--bg-inset); object-fit: cover; }
.promo-list-copy { min-width: 0; display: grid; gap: 2px; }
.promo-list-copy small { color: var(--accent-orange); font-size: var(--text-2xs); font-weight: 800; letter-spacing: .14em; }
.promo-list-copy strong { font-size: var(--text-xs); line-height: 1.3; }
.promo-list-copy em { color: var(--text-subtle); font-size: var(--text-2xs); font-style: normal; line-height: 1.35; }
.promo-list-copy b { margin-top: 4px; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 600; }
.promo-list-item > svg { width: 15px; height: 15px; color: var(--text-subtle); }

/* ── Layar sukses form ───────────────────────────────────────────────────────
   Konfirmasi kiriman: penanda centang, ringkasan jawaban, lalu kode booking.
   Urutannya disengaja — mata mencari "berhasil?" lebih dulu, "apa yang saya
   kirim?" kedua, dan kodenya baru dibutuhkan saat menghubungi admin. */
.vsform-done { display: grid; justify-items: start; gap: var(--space-4); }
.vsform-done-badge {
  width: 56px; height: 56px; display: grid; place-items: center;
  border-radius: 50%; background: var(--tag-success-bg); color: var(--tag-success-text);
}
.vsform-done-badge svg { width: 34px; height: 34px; }

/* Ringkasan memakai <dl>: pasangan label–nilai memang daftar deskripsi, dan
   pembaca layar mengumumkannya berpasangan tanpa markup tambahan. */
.vsform-summary {
  width: 100%; margin: 0; padding: var(--space-4);
  display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  border: 1px solid var(--border-faint); border-radius: var(--radius-md);
  background: var(--surface-1);
}
.vsform-summary dt { color: var(--text-subtle); font-size: var(--text-2xs); }
.vsform-summary dd { margin: 0; color: var(--text-strong); font-size: var(--text-2xs); font-weight: var(--fw-semibold); overflow-wrap: anywhere; }

.vsform-done-code {
  width: 100%; padding: var(--space-3) var(--space-4);
  display: grid; gap: 2px;
  border: 1px dashed var(--border-strong); border-radius: var(--radius-md);
}
.vsform-done-code small { color: var(--text-subtle); font-size: var(--text-2xs); letter-spacing: .1em; text-transform: uppercase; }
/* Kode dibaca lalu DIKETIK ULANG orang; angka tabular menjaga lebarnya stabil
   dan huruf besar mencegah 0/O tertukar saat disalin. */
.vsform-done-code strong { font-size: var(--text-md); font-variant-numeric: tabular-nums; letter-spacing: .04em; }

@media (max-width: 639px) {
  .vsform-summary { grid-template-columns: minmax(0, 1fr); gap: 1px var(--space-3); padding: var(--space-3); }
  .vsform-summary dt { margin-top: var(--space-2); }
  .vsform-summary dt:first-of-type { margin-top: 0; }
}

/* ── Blok `accordion` (LAYOUT) ───────────────────────────────────────────────
   Panel buka-tutup yang isinya BLOK, bukan teks. Kerangkanya <details>/<summary>
   bawaan peramban, sama seperti akordeon FAQ; bentuk baris judulnya datang dari
   daftar selektor `.accordion summary` dkk. di atas, jadi tidak ada satu pun
   deklarasi tipografi yang ditulis dua kali.

   LETAK BLOK INI PENTING, dengan alasan yang sama seperti catatan `.link-cards`:
   `.block-accordion` berbobot SAMA (0,1,0) dengan `.section` milik
   `@media (min-width: 1024px)`, jadi yang menentukan adalah URUTAN, bukan bobot.
   Jangan dipindah ke atas media query itu.

   Yang SENGAJA tidak ditulis, supaya tidak ada yang "melengkapi"-nya nanti:
   tidak ada latar/kartu/bayangan pada summary (halaman ini memisahkan seksi dengan
   garis rambut — panel berkartu akan jadi satu-satunya blok berkartu); tidak ada
   @media baru (seluruh geometrinya bergantung pada --pad yang memang sudah
   responsif); tidak ada animasi tinggi buka-tutup; dan tidak ada blok
   prefers-reduced-motion kedua — sapuan global di atas sudah mematikan transisi
   penanda `+`. */

/* Irama vertikal blok ini dipikul summary (min-height-nya sudah 61px), BUKAN
   padding `.section`. Kalau keduanya hidup, baris judul panel yang tertutup jadi
   tiga kali tinggi baris judul FAQ di halaman yang sama.

   Padding INLINE sengaja dipertahankan: ia yang menaruh judul di dalam talang
   halaman, dan ia yang memberi cincin `:focus-visible` (outline-offset 3px) ruang
   supaya tidak terpotong `.site-shell { overflow: clip }`. */
.block-accordion { padding-block: 0; }

/* Blok anak membawa `.section` masing-masing — lengkap dengan talang `var(--pad)`
   dan garis rambutnya sendiri, sehingga garis di bawah summary dan jarak antar-isi
   datang gratis dari ritme yang sudah ada. Tanpa aturan ini isinya masuk DUA
   talang, dan blok penuh-lebar (.promo-section / .featured / .highlights) yang
   memang harus mepet tepi shell kehilangan sifatnya — rel bergulirnya ikut meleset
   karena `scroll-padding-inline` dihitung dari tepi yang bergeser.

   Nilainya DITURUNKAN dari --pad, bukan angka baru: ia ikut berubah sendiri saat
   --pad melebar di ≥1024px, jadi tidak ada @media tambahan yang harus dirawat. */
.block-accordion > details > .accordion-blocks { margin-inline: calc(var(--pad) * -1); }
/* Garis di bawah judul panel adalah MILIK PANEL, bukan pinjaman.
   Sebelumnya garis itu kebetulan `border-top` milik blok anak paling atas, sehingga
   lebarnya berubah mengikuti tipe blok yang ditaruh admin di urutan pertama: blok
   ber-`margin` sendiri (Rich Text) menariknya sejajar judul, blok `.section` polos
   (Promo Card) melebarkannya 42px ke tiap sisi. Satu panel yang sama menggambar dua
   garis berbeda hanya karena isinya ditukar. Garisnya kini dipasang pada `summary`
   — selebar judulnya, persis seperti `.accordion details` milik FAQ — dan garis
   pinjaman dari anak pertama dimatikan. */
.block-accordion > details[open] > summary { border-bottom: 1px solid var(--border-faint); }
.block-accordion > details > .accordion-blocks > .section:first-child { border-top: 0; }


/* ════════════════════════════════════════════════════════════════════════════
   Blok `compare_cards` — beberapa opsi bersanding sebagai kartu (blok ke-29).

   UNIVERSAL, bukan khusus harga (permintaan owner 29 Agu 2026). Satu kartu =
   satu opsi: ikon, kicker, judul, teks besar + catatan, baris data "Label |
   Nilai", daftar poin bercentang, lalu satu CTA. Bentuk itu melayani tabel harga
   keanggotaan, perbandingan paket, ringkasan layanan, atau daftar add-on tanpa
   perlu tipe blok kedua — karena yang berbeda di antaranya hanya ISI, bukan
   anatominya.

   ANATOMI diambil dari unit library `#pricing` pada master 12.04.02
   "Storefront & Public.dc.html": kepala seksi (kicker mono + h2 + lead ≤520px),
   grid `repeat(n, minmax(0,1fr))` gap 16px `align-items:start`, kartu = border
   hairline + radius + bayangan + kolom ber-gap, centang `--vs-yellow-shadow`,
   CTA selebar kartu. Baris data mengikuti `GUI/13/DESCRIPTION-LIST`.

   VARIAN menandai kartu unggulan, dan ketiganya adalah props SAH primitive Card
   DS (`elevation: flat|card|pop` · `accent` · `ink`) — bukan gaya karangan:

     surface  unggulan = permukaan gelap  (`vs-card--ink`)    ← brief owner
     accent   unggulan = rule kuning 3px  (`vs-card--accent`) ← unit library
     flat     tanpa bayangan, hairline saja (`elevation: flat`)

   Varian ditulis sebagai `data-variant` pada wadah — pola `.block-spacer[data-size]`
   dan `.grid-card[data-layout]` yang sudah ada — bukan kelas modifier baru.

   LETAK: akhir berkas, SESUDAH seluruh @media di atas. Aturan di dalam blok ini
   pun berurut base → 640 → 1024, jadi tidak ada yang menang karena kebetulan.
   Breakpoint memakai 640/1024 yang SUDAH dipakai berkas ini.
   ════════════════════════════════════════════════════════════════════════════ */

.compare-head { margin-bottom: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
/* Kicker punya kelas SENDIRI, bukan `.section-heading > div > span` yang
   `display:none` — slot itu dimatikan justru karena tidak ada schema yang
   mengisinya (lihat sectionHeading() di renderers.js). Di sini kickernya DATA. */
.compare-kicker { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label); text-transform: uppercase; }
.compare-head h2 { margin: 0; font-size: var(--text-h2); line-height: var(--lh-tight); letter-spacing: -.045em; }
.compare-lead { margin: 0; max-width: 520px; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-relaxed); }

.compare-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); align-items: start; }

/* `container-type: inline-size` — baris harga diatur lebar KARTU, bukan lebar
   layar. Dengan @media, kartu di grid 3 kolom tetap dianggap "desktop" lalu
   memakai dua kolom di ruang selebar ~300px: angka 36px seperti "Rp450.000"
   lebih lebar dari separuh kartu dan MELUBER menimpa catatannya. Lebar layar
   tidak pernah tahu berapa kolom yang dipilih admin; lebar kartu tahu. */
.compare-card { container-type: inline-size; padding: var(--space-7); display: flex; flex-direction: column; gap: var(--space-5); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); }

/* — Varian `surface` (bawaan): unggulan jadi kartu ink — */
.compare-grid[data-variant="surface"] .compare-card.is-featured { border-color: var(--bg-ink); background: var(--bg-ink); color: var(--text-on-dark); box-shadow: var(--shadow-pop); }

/* — Varian `accent`: unggulan tetap terang, ditandai rule kuning 3px seperti unit library — */
.compare-grid[data-variant="accent"] .compare-card.is-featured { border-top: 3px solid var(--brand); box-shadow: var(--shadow-pop); }

/* — Varian `flat`: hairline saja. Unggulan ditandai border penuh, bukan bayangan,
     karena varian ini justru dipilih saat halaman tidak ingin elevasi. — */
.compare-grid[data-variant="flat"] .compare-card { box-shadow: none; }
.compare-grid[data-variant="flat"] .compare-card.is-featured { border-color: var(--text-strong); }

/* ── Varian `media` — reproduksi unit "05 Cards · B — Studio card"
   (12.04.02 · Mobile UI.dc.html), diminta owner 31 Agu 2026.

   Anatomi yang ditiru, berurut: foto melebar penuh di atas · badge menempel di
   pojok kiri-atas foto · judul + satu baris keterangan · isi kartu berpadding
   sendiri · harga · aksi.

   SATU KOLOM DI SEMUA LEBAR. Percobaan pertama menaruh harga dan tombol
   berdampingan di baris penutup, dan itu gagal terukur: pada kartu 389px
   (container 387) baris itu membayar margin 20px×2 pada harga, 20px×2 pada
   tombol, dan column-gap 16px = 96px tetapan; tombol `auto` mengambil ~180px;
   sisanya 111px untuk harga — catatan harga pecah jadi tiga baris, dan pada
   414px tombolnya bahkan turun sendiri sehingga sisi kanan dibiarkan kosong.
   Kolom itu dicabut, bukan diberi ambang baru: selama masih ada satu keadaan
   dua kolom, selalu ada lebar di mana ia menjepit.

   Penggantinya tiga alat yang tidak bergantung lebar: tangga skala tanpa dua
   level kembar (28/22/16/14/13/11), tangga tinta (strong → body → muted →
   subtle), dan RITME SPASI BERJENJANG — 8px di dalam kelompok, 20px antar blok,
   24px sebelum harga, 32px sebelum aksi. Ritme ditulis sebagai margin per-anak,
   bukan `gap` seragam: satu nilai gap tidak dapat menyatakan jenjang. */
.compare-grid[data-variant="media"] .compare-card { padding: 0; overflow: hidden; display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; row-gap: 0; column-gap: 0; border-radius: var(--radius-md); }

/* Foto menempel ke tepi kartu — itulah yang membuat bentuk ini terbaca modern:
   tidak ada bingkai putih mengelilinginya. Kartu tanpa foto tidak menggambar
   apa pun di sini, jadi bentuknya tetap rapi (keputusan owner).
   `position: relative` WAJIB: badge di dalamnya berposisi absolut, dan tanpa
   leluhur berposisi ia hanya kebetulan mendarat benar karena containment
   `container-type` — ketergantungan tersembunyi yang pecah begitu ada yang
   mengubah containment. */
.compare-grid[data-variant="media"] .compare-media { position: relative; margin: 0; border-radius: 0; }
.compare-grid[data-variant="media"] .compare-card > :not(.compare-media) { margin-inline: var(--space-6); }
.compare-grid[data-variant="media"] .compare-card > :first-child:not(.compare-media) { margin-top: var(--space-6); }
.compare-grid[data-variant="media"] .compare-card > :last-child { margin-bottom: var(--space-6); }

/* LANTAI RITME: 20px antar blok mana pun. Renderer memancarkan delapan anak yang
   hampir semuanya opsional (media · card-top · desc · lede · rows · points · cta
   · note), jadi jarak tidak boleh ditulis per-pasangan — satu kombinasi terlewat
   berarti dua blok saling menempel. Pengecualian di bawah menang lewat
   spesifisitas (0,4,0 vs 0,3,0), bukan lewat urutan penulisan. */
.compare-grid[data-variant="media"] .compare-card > * + * { margin-top: var(--space-6); }
/* Deskripsi masih satu kelompok dengan judul di atasnya. */
.compare-grid[data-variant="media"] .compare-card > .compare-desc { margin-top: var(--space-3); }
/* JEDA BESAR #1 — batas babak identitas → harga. */
.compare-grid[data-variant="media"] .compare-card > .compare-lede { margin-top: var(--space-7); }
/* JEDA BESAR #2 — aksi adalah babak terakhir, jadi jedanya yang terbesar. */
.compare-grid[data-variant="media"] .compare-card > .compare-cta { margin-top: var(--space-8); }
/* Catatan kaki menempel pada blok di atasnya sebagai keterangan; margin ini juga
   membatalkan margin negatif bawaannya yang hanya benar di tata letak lama. */
.compare-grid[data-variant="media"] .compare-card > .compare-note { margin-top: var(--space-4); text-align: left; }
/* Kecuali saat catatan mengikuti harga langsung: di situ aturan umum menggambar
   garis rambut + padding 20px, jadi jarak di atas garis harus 20px agar simetris. */
.compare-grid[data-variant="media"] .compare-card > .compare-lede + .compare-note { margin-top: var(--space-6); }

/* Badge di atas foto — mono kecil, sudut membulat, seperti label "POPULER" di unit. */
/* Badge varian media memakai TIPOGRAFI BODY, bukan mono-uppercase (permintaan
   owner 1 Sep 2026). Mono + huruf besar + tracking lebar adalah bahasa label
   teknis — benar untuk penanda status di tabel, terlalu berat untuk satu janji
   pemasaran yang duduk di atas foto. Bentuk pil + huruf normal membuatnya
   terbaca sebagai keterangan, bukan kode. Penyimpangan sadar dari unit master
   (yang memakai mono 10px); dicatat di catatan paritas blok ini. */
.compare-grid[data-variant="media"] .compare-badge { font-family: var(--font-body); font-size: var(--text-xs); font-weight: var(--fw-semibold); letter-spacing: var(--ls-normal); text-transform: none; border-radius: var(--radius-pill); padding: var(--space-2) var(--space-4); }
.compare-badge.is-on-media { position: absolute; left: var(--space-4); top: var(--space-4); z-index: 1; }

/* Ikon tile DICABUT di varian ini: unit aslinya tidak punya ikon, dan di kartu
   selebar ponsel ia memakan 40px lebar judul tanpa menambah keterangan apa pun. */
.compare-grid[data-variant="media"] .compare-tile { display: none; }
/* `.compare-cta` bawaan berlebar 100%; di varian ini ia juga membawa margin
   samping, sehingga 100% + margin meluber melewati tepi kartu. */
.compare-grid[data-variant="media"] .compare-cta { width: auto; }
.compare-grid[data-variant="media"] .compare-card-top { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
/* Kepala satu kolom membuat badge mewarisi `justify-self: stretch`, sehingga
   latar warnanya membentang selebar kartu pada kartu TANPA foto. */
.compare-grid[data-variant="media"] .compare-card-top .compare-badge { justify-self: start; }

/* Judul dan angka adalah dua jangkar; keduanya naik ke tinta paling pekat karena
   di kartu satu kolom hanya skala, tinta, dan ritme yang tersisa sebagai alat. */
.compare-grid[data-variant="media"] .compare-card h3 { color: var(--text-strong); }

/* Blok harga: satu kolom di semua lebar. */
.compare-grid[data-variant="media"] .compare-lede { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-3); }
/* Coret, angka, dan satuan adalah SATU kalimat harga — jaraknya lebih rapat
   daripada jarak angka ke catatan. */
.compare-grid[data-variant="media"] .compare-lede-main { column-gap: var(--space-3); row-gap: var(--space-1); }
/* 36px → 28px. Bukan sekadar soal muat: pada 36px angka hanya berjarak setengah
   langkah dari judul 22px sehingga keduanya berebut jadi jangkar. */
.compare-grid[data-variant="media"] .compare-lede-value { font-size: var(--text-h2); color: var(--text-strong); }
/* Satuan dan catatan hari ini identik (13px, subtle) padahal bertetangga di blok
   yang sama; satuan naik satu tingkat supaya "Rp100.000 /jam" terbaca sebagai
   satu pernyataan, bukan angka lalu remah. Berhenti di 14px — sederajat prosa,
   tidak melewatinya. */
.compare-grid[data-variant="media"] .compare-lede-unit { font-size: var(--text-sm); color: var(--text-muted); }
/* Catatan harga adalah pita data satu baris, bukan paragraf: 1.62 hanya menambah
   tinggi tanpa menambah keterbacaan. */
.compare-grid[data-variant="media"] .compare-lede-note { line-height: var(--lh-normal); }

/* Kartu sangat sempit (grid 4 kolom di layar lega, dan zoom 200% di ponsel).
   Ambang 260px dipakai ulang dari blok yang sudah ada di berkas ini, bukan angka
   baru. LETAKNYA WAJIB SESUDAH dua aturan di atas: spesifisitasnya sama, jadi
   yang menentukan urutan. */
@container (max-width: 260px) {
  .compare-grid[data-variant="media"] .compare-lede-value { font-size: var(--text-h3); }
  .compare-grid[data-variant="media"] .compare-lede-unit { font-size: var(--text-xs); }
}

.compare-card-top { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }


/* Baris komposit ikon+teks+aksi SELALU grid auto|minmax(0,1fr)|auto — badge tidak
   boleh mendorong judul keluar kartu saat teksnya panjang. */
.compare-card-top { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-4); align-items: start; }
.compare-card-top.has-badge { grid-template-columns: auto minmax(0, 1fr) auto; }
.compare-card-top.no-icon { grid-template-columns: minmax(0, 1fr); }
.compare-card-top.no-icon.has-badge { grid-template-columns: minmax(0, 1fr) auto; }
.compare-tile { width: 40px; height: 40px; display: grid; place-items: center; border-radius: var(--radius-xs); background: var(--bg-subtle); color: var(--text-strong); }
.compare-tile svg { width: 20px; height: 20px; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-tile { background: var(--brand); color: var(--brand-ink); }
.compare-eyebrow { display: block; margin-bottom: var(--space-1); color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label-sm); text-transform: uppercase; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-eyebrow { color: var(--text-on-dark-muted); }
.compare-card h3 { margin: 0; font-size: var(--text-h3); line-height: var(--lh-snug); letter-spacing: var(--ls-display); }
/* Badge memakai 11 nada Fresh Tag System apa adanya — triad bg/border/text yang
   kontrasnya sudah disetel berpasangan. Nada dipilih per kartu, sehingga badge
   tidak lagi terbatas pada kartu unggulan: pricelist perlu "Hemat 20%" di kartu
   mana pun, dan kuning-di-atas-putih tidak terbaca. */
.compare-badge { align-self: start; padding: var(--space-2) var(--space-3); border: 1px solid transparent; border-radius: var(--radius-xs); background: var(--tag-identity-bg); color: var(--tag-identity-text); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-bold); letter-spacing: var(--ls-label-sm); text-transform: uppercase; white-space: nowrap; }
.compare-badge[data-tone="neutral"] { background: var(--tag-neutral-bg); border-color: var(--tag-neutral-border); color: var(--tag-neutral-text); }
.compare-badge[data-tone="success"] { background: var(--tag-success-bg); border-color: var(--tag-success-border); color: var(--tag-success-text); }
.compare-badge[data-tone="mint"] { background: var(--tag-mint-bg); border-color: var(--tag-mint-border); color: var(--tag-mint-text); }
.compare-badge[data-tone="info"] { background: var(--tag-info-bg); border-color: var(--tag-info-border); color: var(--tag-info-text); }
.compare-badge[data-tone="blue"] { background: var(--tag-blue-bg); border-color: var(--tag-blue-border); color: var(--tag-blue-text); }
.compare-badge[data-tone="purple"] { background: var(--tag-purple-bg); border-color: var(--tag-purple-border); color: var(--tag-purple-text); }
.compare-badge[data-tone="pink"] { background: var(--tag-pink-bg); border-color: var(--tag-pink-border); color: var(--tag-pink-text); }
.compare-badge[data-tone="warning"] { background: var(--tag-warning-bg); border-color: var(--tag-warning-border); color: var(--tag-warning-text); }
.compare-badge[data-tone="orange"] { background: var(--tag-orange-bg); border-color: var(--tag-orange-border); color: var(--tag-orange-text); }
.compare-badge[data-tone="danger"] { background: var(--tag-danger-bg); border-color: var(--tag-danger-border); color: var(--tag-danger-text); }
.compare-badge[data-tone="identity"] { background: var(--tag-identity-bg); border-color: var(--tag-identity-border); color: var(--tag-identity-text); }

.compare-desc { margin: 0; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-relaxed); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-desc { color: var(--text-on-dark-muted); }

/* Foto kartu. Rasio datang dari `--compare-media-ratio` yang dipasang renderer
   lewat ratioValue() — resolver rasio yang SAMA dengan blok video, bukan daftar
   kedua. */
.compare-media { border-radius: var(--radius-xs); overflow: hidden; background: var(--bg-subtle); aspect-ratio: var(--compare-media-ratio, 16 / 9); }
.compare-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Angka besar dan catatannya BERDAMPINGAN, bukan bertumpuk. Dua baris penuh
   untuk "Gratis" + "termasuk untuk semua booking rental" adalah tinggi yang
   dibayar tanpa menambah kejelasan; disandingkan, keduanya terbaca sebagai satu
   pernyataan harga. Kolom catatan `minmax(0,1fr)` boleh menyusut dan membungkus,
   dan di kartu sempit ia turun sendiri lewat aturan @container di bawah. */
.compare-lede { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3) var(--space-5); align-items: baseline; }
/* Coret, nilai, dan satuan duduk pada satu baris dasar — satu kalimat harga. */
.compare-lede-main { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3); }
/* Harga coret DIPAKSA ke barisnya sendiri (`flex-basis:100%`), bukan dibiarkan
   membungkus sesuai sisa ruang. Tanpa ini titik putusnya berpindah-pindah
   mengikuti lebar kartu: di satu lebar `/jam` yang terlempar sendirian, di
   lebar lain harga coretnya — dan "Rp400.000" terpisah dari satuannya adalah
   harga yang kehilangan separuh artinya. Sekarang urutannya selalu sama:
   harga lama di atas, harga berlaku + satuan menyatu di bawahnya. */
.compare-lede-strike { flex-basis: 100%; color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-xs); text-decoration-thickness: 1px; }
.compare-lede-unit { color: var(--text-subtle); font-size: var(--text-xs); font-weight: var(--fw-medium); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede-strike,
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede-unit { color: var(--text-on-dark-muted); }
.compare-lede-value { font-family: var(--font-display); font-size: var(--text-h1); font-weight: var(--fw-bold); line-height: var(--lh-tight); letter-spacing: var(--ls-display); overflow-wrap: break-word; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede-value { color: var(--brand); }
.compare-lede-note { color: var(--text-subtle); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
/* Garis rambut SESUDAH blok harga — anatomi unit `#pricing` (12.04.02 ·
   Storefront & Public.dc.html) memisahkan identitas+harga dari daftar isinya
   dengan `height:1px;background:var(--line-100)`. Di app pemisah itu hilang,
   sehingga judul, harga, baris data, dan poin terbaca sebagai satu tumpukan
   panjang tanpa jenjang. Ditulis sebagai border pada elemen SESUDAHNYA supaya
   tidak ada simpul kosong tambahan di markup. */
/* Garis hanya untuk elemen ALIRAN, tidak pernah untuk tombol: `padding-top`
   pada <button> menambah ruang DI DALAM tombol, sehingga labelnya turun dari
   tengah dan tingginya melar — persis yang terjadi pada kartu yang langsung
   berakhir di CTA. Kartu tanpa daftar isi memang tidak butuh pemisah: di master
   pun garis itu memisahkan harga dari DAFTAR FITUR, dan kalau daftarnya tidak
   ada, tidak ada yang perlu dipisahkan. */
.compare-lede + .compare-rows,
.compare-lede + .compare-points,
.compare-lede + .compare-note { border-top: 1px solid var(--border-faint); padding-top: var(--space-6); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede + .compare-rows,
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede + .compare-points,
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede + .compare-note { border-top-color: rgba(255, 255, 255, .16); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-lede-note { color: var(--text-on-dark-muted); }

.compare-rows { margin: 0; display: flex; flex-direction: column; }
/* Kolom nilai `minmax(0, auto)`, BUKAN `auto`: dengan `auto` nilai yang panjang
   menolak menyusut dan mendorong dirinya keluar kartu di layar 375px — dan
   karena nilainya mono, tidak ada spasi yang bisa dijadikan titik putus. */
/* Kolom nilai memakai 1fr, BUKAN auto. Dengan `auto` di kolom kedua, nilai panjang
   menuntut lebar max-content dan menggencet kolom label sampai ~0px — label mono-nya
   lalu meluber menimpa nilainya, dan barisnya tak terbaca sama sekali. Urutan track
   dibalik supaya label mendapat lebar isinya lebih dulu dan nilai membungkus di sisa
   ruang. */
.compare-row { display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr); gap: var(--space-4); align-items: baseline; padding: var(--space-4) 0; border-top: 1px solid var(--border-faint); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-row { border-top-color: rgba(255, 255, 255, .16); }
.compare-row dt { overflow-wrap: break-word; color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label-sm); text-transform: uppercase; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-row dt { color: var(--text-on-dark-muted); }
.compare-row dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--fw-semibold); text-align: right; overflow-wrap: break-word; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-row dd { color: var(--brand); }

/* Satu kolom secara bawaan. `--compare-points-cols: 2` (dipasang renderer saat
   kartunya meminta) mengubahnya jadi kisi dua kolom — bukan `columns`, karena
   kolom-majemuk memecah satu <li> di tengah baris ketika teksnya membungkus. */
.compare-points { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(var(--compare-points-cols, 1), minmax(0, 1fr)); gap: var(--space-4); }
.compare-points li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); align-items: start; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-relaxed); }
.compare-points svg { width: 15px; height: 15px; margin-top: 3px; color: var(--vs-yellow-shadow); }
/* Poin yang TIDAK termasuk: silang, teks diredupkan. Ia tetap dibaca sebagai
   bagian daftar — dicoret akan membuatnya terbaca seperti harga yang batal. */
.compare-points li.is-off { color: var(--text-subtle); }
.compare-points li.is-off svg { color: var(--text-subtle); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-points li.is-off,
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-points li.is-off svg { color: var(--text-on-dark-muted); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-points li { color: var(--text-on-dark); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-points svg { color: var(--brand); }

/* CTA meminjam `.route-cta` yang sudah ada — satu tombol untuk seluruh Gateway,
   bukan tombol kedua yang harus dirawat sendiri. Di kartu ink ia dibalik jadi
   kuning-di-atas-hitam supaya tetap terbaca sebagai aksi utama. */
.compare-cta { width: 100%; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-cta { border-color: var(--brand); background: var(--brand); color: var(--brand-ink); }

.compare-note { margin-top: calc(var(--space-4) * -1); color: var(--text-subtle); font-size: var(--text-2xs); line-height: var(--lh-relaxed); text-align: center; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-note { color: var(--text-on-dark-muted); }

.compare-foot { margin: var(--space-5) 0 0; color: var(--text-subtle); font-size: var(--text-xs); line-height: var(--lh-relaxed); text-align: center; }

@media (min-width: 640px) {
  .compare-grid[data-cols="2"],
  .compare-grid[data-cols="3"],
  .compare-grid[data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .compare-grid[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Kartu sempit (mis. grid 4 kolom) turun SATU langkah skala tipe.

   LETAKNYA WAJIB DI SINI, sesudah `.compare-lede-value`. Ditulis lebih awal di
   berkas, aturan dasarnya yang datang belakangan menang lewat urutan dan blok ini
   mati diam-diam — tanpa galat, tanpa gejala selain angka yang tidak pernah
   mengecil.

   Ambangnya diukur, bukan ditebak: pada 4 kolom isi kartu 192px sementara nilai
   pada `--text-h1` sendirian sudah 197px, sehingga satuannya SELALU terdorong ke
   baris berikutnya dan "Rp100.000" terpisah dari "/jam". 260px berada di antara
   isi kartu 4 kolom (192px) dan 3 kolom (278px), jadi tata letak yang sudah muat
   tidak ikut mengecil. */
/* Badge turun ke barisnya SENDIRI di kartu sempit.

   `white-space: nowrap` membuat badge mengklaim lebar teks penuhnya lebih dulu —
   "PALING DIMINATI" saja sekitar 150px. Di kartu ~250px (grid 3 kolom pada panel
   preview, atau layar sedang) sisa untuk kolom judul tinggal remah, dan kicker
   panjang seperti "Pilihan tim produksi rutin" terjepit jadi empat baris di
   samping badge. Menurunkan badge ke baris kedua mengembalikan lebar penuh untuk
   nama tier, dan badge tetap terbaca sebagai penanda kartu.

   Ambang 340px diukur terhadap KOTAK ISI (`container-type: inline-size`), jadi
   kartu 333px di ponsel bernilai 285px sesudah padding — ia memang masuk ambang,
   dan itu BENAR: dicoba menurunkannya ke 270px pada 31 Agu 2026 supaya badge
   sebaris, hasilnya judul "Private Makeup Space" tersisa 116px dan pecah jadi
   tiga baris — kepala kartu justru naik dari 83px ke 113px. Badge di baris
   sendiri lebih murah daripada judul yang terjepit. */
@container (max-width: 340px) {
  /* Di kartu sempit catatan kembali ke bawah angka — disandingkan di situ ia
     tinggal dua-tiga kata per baris. */
  .compare-lede { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .compare-card-top.has-badge { grid-template-columns: auto minmax(0, 1fr); }
  .compare-card-top.no-icon.has-badge { grid-template-columns: minmax(0, 1fr); }
  .compare-card-top.has-badge .compare-badge { grid-column: 1 / -1; justify-self: start; }
}

@container (max-width: 260px) {
  .compare-lede-value { font-size: var(--text-h2); }
}

/* Catatan harga TIDAK pernah duduk di samping angkanya, dan itu keputusan, bukan
   kelalaian. Dicoba dua kali: `1fr auto` membuat kolom catatan merebut lebar penuh
   lalu angkanya meluber menimpanya; dua kolom sama lebar tetap gagal karena nilai
   rupiah penuh butuh ~231px sementara separuh kartu terlebar yang dipakai modul ini
   (2 kolom, isi 450px) hanya menyisakan ~217px. Ambang container yang benar-benar
   aman berada di atas lebar kartu mana pun yang bisa dihasilkan pengaturan kolom
   di panel properti — artinya aturan itu tidak akan pernah menyala. Menumpuknya
   ke bawah selalu terbaca dan mustahil bertabrakan. */


/* ── Section Nav — primitive DS Tabs, varian `underline` ─────────────────────
   Sumber: 12.04.00 `components/navigation/Tabs.jsx`. Katalog primitive menyebut
   peruntukan varian ini apa adanya: "tab bar underline (section halaman)".
   Nilai DS dipetakan ke token (padding 11/14px → --space-4/--space-5, 14px/600 →
   --text-sm/--fw-semibold, garis aktif --vs-black).

   DUA bentuk karangan sebelumnya dibuang, keduanya gagal karena label seksi
   panjangnya tidak sama: pil segmented berbagi rata memangkas label jadi elipsis,
   dan kisi membungkus membuat satu label pecah dua baris sementara tetangganya
   satu baris — barisnya ragged, dan owner menyebutnya kacau. Tab underline tidak
   punya masalah itu: lebar tab MENGIKUTI teksnya, tidak ada kotak yang harus rata.

   DI TELEPON tabnya menumpuk SATU PER BARIS (keputusan owner 4 Sep 2026), jadi
   tidak ada yang perlu digeser dan tidak ada yang terpotong. Garis bawah pindah
   ke tiap baris supaya tumpukannya terbaca sebagai daftar, bukan tombol melayang. */
.section-nav-tabs { display: flex; flex-direction: column; align-items: stretch; gap: 0; border-bottom: 0; }
.section-nav-tabs .vs-tab { justify-content: flex-start; margin-bottom: 0; padding: var(--space-4) 0; border: 0; border-bottom: 1px solid var(--border-default); background: transparent; color: var(--text-subtle); font-family: var(--font-body); font-size: var(--text-sm); font-weight: var(--fw-semibold); text-align: left; cursor: pointer; transition: color .14s ease, border-color .14s ease; }
.section-nav-tabs .vs-tab:last-child { border-bottom: 0; }
.section-nav-tabs .vs-tab:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--radius-xs); }
@media (hover: hover) { .section-nav-tabs .vs-tab:hover { color: var(--text-strong); } }

/* ≥640px kembali ke bentuk DS aslinya: satu baris, garis bawah menyatu, tab
   ditandai garis 2px saat disentuh penunjuk. */
@media (min-width: 640px) {
  .section-nav-tabs { flex-direction: row; gap: var(--space-2); border-bottom: 1px solid var(--border-default); }
  .section-nav-tabs .vs-tab { padding: var(--space-4) var(--space-5); margin-bottom: -1px; border-bottom: 2px solid transparent; }
  .section-nav-tabs .vs-tab:last-child { border-bottom: 2px solid transparent; }
  @media (hover: hover) { .section-nav-tabs .vs-tab:hover { border-bottom-color: var(--vs-black); } }
}

/* Seksi tidak boleh mendarat di bawah kepala halaman + bilah pintasan saat
   dilompati. 68px kepala + 44px bilah + napas. */
.block-catalog, .block-compare { scroll-margin-top: 124px; }

/* Bawaan DUA kolom — inilah selisih yang menjadi alasan blok ini ada. */
.catalog-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); align-items: stretch; }
/* `data-cols-m` DIKURUNG ke lebar telepon, dan itu bukan kerapian.

   Tanpa kurungan ini aturannya berlaku sampai desktop, dan karena tidak ada
   penimpa untuk `[data-cols="2"]` di ambang mana pun, kombinasi "mobile 1 kolom
   + desktop 2 kolom" — dua-duanya ditawarkan panel properti — terkunci SATU
   kolom di semua lebar. Spesifisitasnya sama (0,2,0), jadi yang menang hanya
   urutan; setelan desktop admin diabaikan tanpa satu pun gejala. */
@media (max-width: 639.98px) {
  .catalog-grid[data-cols-m="1"] { grid-template-columns: minmax(0, 1fr); }
}

/* Kartu = tombol. Reset bawaan tombol dilakukan di sini, bukan dengan `all:unset`
   yang juga membuang fokus terlihat dan sifat blok elemennya.

   `padding: 0` dan `margin: 0` WAJIB disebut. Peramban memberi <button> padding
   bawaan (Chrome 1px 6px), dan padding itu mendorong foto masuk dari tepi kartu —
   sisa ruang kosong di keempat sisi thumbnail yang terbaca sebagai gambar gagal
   dimuat. Ia tidak muncul sewaktu kartunya masih <article>; ia lahir bersama
   perubahan ke <button>. */
.catalog-card { appearance: none; -webkit-appearance: none; font: inherit; color: inherit; text-align: left; cursor: pointer; padding: 0; margin: 0; display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--line-100); border-radius: var(--radius-lg); background: var(--surface-0); box-shadow: none; transition: border-color .18s ease, box-shadow .18s ease; }
.catalog-card:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Hover DIKUNCI ke penunjuk yang benar-benar bisa hover: di layar sentuh keadaan
   hover menempel setelah tap dan kartu tertinggal terangkat sampai tap berikutnya. */
@media (hover: hover) {
  .catalog-card:hover { border-color: var(--line-200); box-shadow: var(--shadow-xs); }
  .catalog-card:hover .catalog-media img { transform: scale(1.03); }
}

/* Tanpa garis jahit di bawah foto. Tepi kartu sudah memisahkan foto dari badan;
   garis kedua di antaranya adalah ritme lama yang membuat kartu terbaca sebagai
   dua kotak yang ditempel, bukan satu benda. */
.catalog-media { position: relative; display: block; aspect-ratio: var(--catalog-media-ratio, 4 / 5); background: var(--ink-050); overflow: hidden; }
.catalog-media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .3s ease; }
@media (prefers-reduced-motion: reduce) { .catalog-media img { transition: none; } .catalog-card:hover .catalog-media img { transform: none; } }

/* Label menempel di foto (unit library), pil bukan kotak.

   MEMBUNGKUS, tidak memotong elipsis. Unit DS memakai `nowrap` karena labelnya
   satu kata ("Sale"); label di sini menerangkan syarat sewa — "Tergantung
   ketersediaan", "Khusus Limbo & Family" — dan pada kartu 160px (dua kolom di
   telepon) elipsis memangkasnya jadi "Tergantung ketersedi…", yaitu label yang
   kehilangan justru bagian yang membuatnya berarti. Radius pil menahan bentuknya
   tetap rapi di dua baris, dan pojok kiri atas foto adalah latar, bukan barangnya. */
/* Label = keping kaca gelap, bukan pil kuning.

   Pil bertona terang (anatomi `.compare-badge`) diadu dengan foto produk beralas
   abu terang: kontrasnya rendah, dan pada kisi 43 ubin sebelas pil kuning menjadi
   lapisan kedua yang bersaing dengan barangnya sendiri. Keping gelap tembus pandang
   terbaca di atas foto TERANG maupun GELAP dengan satu aturan, dan mengembalikan
   perhatian ke fotonya. `backdrop-filter: blur(4px)` mengikuti nilai yang sudah
   dipakai DS (Mobile UI: tombol suka di atas foto studio; Storefront: tombol Play
   di atas hero).

   Warnanya lewat `color-mix` dari `--ink-900`, bukan literal rgba seperti di master
   DS: doktrin berkas ini nol literal warna, dan token yang dicampur tetap ikut
   kalau palet berubah. */
.catalog-badge { position: absolute; left: var(--space-3); top: var(--space-3); z-index: 1; max-width: calc(100% - var(--space-3) * 2); padding: var(--space-1) var(--space-3); border: 0; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--ink-900) 62%, transparent); color: var(--text-on-dark); font-family: var(--font-body); font-size: var(--text-2xs); font-weight: var(--fw-semibold); line-height: var(--lh-snug); letter-spacing: var(--ls-normal); text-wrap: pretty; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
/* Tona hanya memberi SEMU aksen lewat tepi kiri, bukan mengganti alasnya: keping
   gelap adalah keputusan keterbacaan, dan mengembalikan alas terang untuk sebagian
   tona akan mengembalikan persis masalah yang baru saja ditutup. */
.catalog-badge[data-tone="warning"] { box-shadow: inset 2px 0 0 var(--brand); }
.catalog-badge[data-tone="danger"] { box-shadow: inset 2px 0 0 var(--tag-danger-bg); }

/* SKALA TIPE DITURUNKAN SATU TINGKAT PENUH (owner, 4 Sep 2026: "masih terlalu
   besar ukuran fontnya"). Nama 14 → 13, harga 18 → 14, satuan 13 → 11. Angka DS
   unit "Storefront catalog" (nama 14, harga 17) dirancang untuk kisi 4 kolom di
   layar lebar; di sini ubinnya 160px pada dua kolom telepon, dan pada lebar itu
   skala aslinya membuat kartu terbaca sebagai poster satu barang. Penyimpangan
   dari library yang DISENGAJA. */
.catalog-body { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; padding: var(--space-4) var(--space-4) var(--space-4); }
/* Item tanpa foto: labelnya tidak punya foto untuk ditempeli, jadi ia kembali
   menjadi elemen aliran di kepala badan kartu — dan di sana keping gelap tidak
   punya foto sebagai latar, jadi ia dikembalikan ke pil netral. */
.catalog-body > .catalog-badge { position: static; align-self: start; max-width: 100%; margin-bottom: var(--space-2); background: var(--tag-neutral-bg); color: var(--tag-neutral-text); backdrop-filter: none; -webkit-backdrop-filter: none; }
/* Ikon pengganti foto: anatomi `.compare-tile` (40px, radius-xs, bg-subtle). */
.catalog-icon { width: 40px; height: 40px; flex: none; display: grid; place-items: center; margin-bottom: var(--space-2); border-radius: var(--radius-xs); background: var(--bg-subtle); color: var(--text-strong); }
.catalog-icon svg { width: 20px; height: 20px; }
.listing-body > .catalog-icon { margin-bottom: var(--space-3); }
.catalog-name { margin: 0; font-family: var(--font-body); font-size: var(--text-xs); font-weight: var(--fw-semibold); line-height: var(--lh-snug); letter-spacing: var(--ls-normal); color: var(--text-strong); text-wrap: pretty; }
.catalog-meta { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label-sm); text-transform: uppercase; }
/* `margin-top: auto` mengunci harga ke DASAR kartu, sehingga baris harga seluruh
   kisi sejajar walau nama barangnya satu atau dua baris. */
.catalog-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); margin-top: auto; padding-top: var(--space-3); }
.catalog-price-strike { color: var(--text-subtle); font-size: var(--text-2xs); }
/* `tabular-nums` supaya kolom angka rata antar ubin — deretan harga yang digit-nya
   bergeser sedikit demi sedikit adalah yang membuat kisi terbaca gelisah. */
.catalog-price-value { font-family: var(--font-body); font-size: var(--text-sm); font-weight: var(--fw-bold); line-height: var(--lh-snug); letter-spacing: var(--ls-tight); color: var(--text-strong); font-variant-numeric: tabular-nums; }
.catalog-price-unit { color: var(--text-subtle); font-size: var(--text-2xs); }

.catalog-foot { margin: var(--space-5) 0 0; color: var(--text-subtle); font-size: var(--text-xs); line-height: var(--lh-relaxed); text-align: center; }

@media (min-width: 640px) {
  /* `[data-cols="2"]` disebut EKSPLISIT walau nilainya sama dengan bawaan: ia
     yang mengembalikan dua kolom sesudah kurungan mobile berakhir, dan tanpa
     baris ini satu-satunya yang menahannya adalah aturan dasar — cukup hari ini,
     tapi diam-diam bergantung pada tidak adanya aturan mobile baru di kemudian
     hari. Dinyatakan, bukan diandaikan. */
  .catalog-grid[data-cols="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .catalog-grid[data-cols="3"],
  .catalog-grid[data-cols="4"],
  .catalog-grid[data-cols="5"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .catalog-grid[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .catalog-grid[data-cols="5"] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   VARIAN BLOK MEDIA — Banner "stacked", Carousel "peek", Gallery "feed"
   Dipilih owner 1 Sep 2026 dari prototipe sembilan varian (_proto-media-blocks).

   POLA: sama seperti compare_cards, varian dipasang sebagai `data-variant` pada
   section-nya dan CSS di sini MENIMPA kelas dasar. Tidak ada keluarga kelas kedua
   untuk pola yang sama — satu perubahan pada dasar tidak boleh diam-diam melewati
   varian, dan sebaliknya.

   DUA PENYIMPANGAN DESIGN SYSTEM, disetujui owner 1 Sep 2026 setelah diberi tahu
   bentrokannya (dicatat juga di tools/parity-gate.php):

   1. Gallery "feed" melawan doktrin tertulis DS. Component Library.dc.html:824
      berbunyi "Foto duduk di slot berbingkai hairline, radius 14, tanpa tint",
      dan keempat layout galeri DS memakai gap 10-12px + radius + bingkai per
      foto. Varian ini justru jahitan 2px, nol radius, nol bingkai. Alasan owner:
      yang membuat belasan foto terbaca sebagai SATU BIDANG justru barisnya yang
      rata dan jahitannya yang nyaris hilang; begitu tiap foto dibingkai, ia
      kembali terbaca sebagai deretan kartu — persis yang sudah dilayani varian
      klasik. Kedua bentuk hidup berdampingan, admin memilih per blok.

   2. Carousel "peek" tidak punya unit DS sama sekali: `scroll-snap` muncul NOL
      kali di kedelapan master 12.04.02, dan unit bernama "Filmstrip" ternyata
      kisi 4 kolom statis. Yang menahannya tetap sah: rel ber-snap SUDAH jadi
      pola produksi yang tayang lewat gerbang — .promo-slider, .featured-track,
      .gallery-track, .listing-hero-track, dan .vsform-visual.is-horizontal.
      Yang benar-benar baru hanya TIGA: sliver foto berikutnya dikunci 40px,
      tombol panah, dan penghitung slide.

   Banner "stacked" TIDAK menyimpang: pola "kartu dua lapis foto + panel teks"
   punya empat pendukung di DS (Storefront & Public #hero B, #portfolio, dan
   Mobile UI "05 Cards · B — Studio card" beserta kembar desktopnya). Anatominya
   ditiru: gap NOL, jahitan berupa hairline, keduanya di dalam satu pembungkus
   ber-radius + overflow:hidden + bayangan kartu.

   NILAI: token dipakai di mana tokennya ADA (--space-*, --radius-*, --text-*,
   --fw-*, --ls-*). Yang tetap literal — 1px lebar garis, .2s durasi, opacity,
   aspect-ratio, z-index, 16px ikon — tidak punya token di tokens.css DAN sudah
   dipakai literal di berkas ini (mis. styles.css:83, :203, :825, :1004). Cincin
   fokus TIDAK ditulis ulang: `:focus-visible` global di baris 83 sudah memberi
   `outline: 3px solid var(--accent-blue)`. `:has()` tidak dipakai.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Banner · varian "stacked" ─────────────────────────────────────────────
   Panel teks TIDAK PERNAH duduk di atas foto, jadi kontras teks tidak lagi
   bergantung pada gelap-terangnya foto — dan toggle "Overlay gelap" kehilangan
   kuasanya menghapus keterbacaan (ia memang hanya berlaku di varian klasik). */
.block-image-banner[data-variant="stacked"] .banner-stack {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;                       /* jahitan = hairline, bukan jarak (anatomi DS) */
  overflow: hidden;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--surface-0);
  box-shadow: var(--shadow-card);
}
.block-image-banner[data-variant="stacked"] .banner-media {
  position: relative;
  margin: 0;
  aspect-ratio: var(--banner-ratio, 2 / 1);
  background: var(--bg-inset);
}
.block-image-banner[data-variant="stacked"] .banner-media img {
  width: 100%; height: 100%;
  display: block;
  object-fit: cover;
  border-radius: 0;
}
.block-image-banner[data-variant="stacked"] .banner-badge {
  position: absolute; z-index: 1; top: var(--space-4); left: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--yellow);
  color: var(--brand-ink);
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
}
/* Panel = grid satu kolom berbaris bernama. Setiap anak dipatok ke barisnya
   sendiri, jadi urutan tampil tidak bergantung urutan DOM dan baris yang
   kosong (kicker/deck/cta tidak diisi) tidak meninggalkan celah ganda:
   baris `auto` tanpa isi tingginya nol, dan jaraknya hanya `row-gap`. */
.block-image-banner[data-variant="stacked"] .banner-panel {
  padding: var(--space-6);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "kicker" "title" "deck" "cta";
  row-gap: var(--space-3);
  border-top: 1px solid var(--border-faint);   /* JAHITAN antar-lapis */
  align-content: start;
}
.block-image-banner[data-variant="stacked"] .banner-kicker { grid-area: kicker; min-width: 0; }
.block-image-banner[data-variant="stacked"] .banner-title { grid-area: title; min-width: 0; overflow-wrap: anywhere; }
.block-image-banner[data-variant="stacked"] .banner-deck { grid-area: deck; min-width: 0; }
.block-image-banner[data-variant="stacked"] .banner-cta { grid-area: cta; }
/* Banner teks saja — anatomi unit "CTA band" (Storefront & Public.dc.html
   #marketing): satu kartu tanpa lapis foto, padding lebih lega, judul
   display satu tingkat lebih besar karena kartunya kini hanya teks. Tidak ada
   jahitan: tidak ada lapis lain yang perlu dipisahkan. */
.block-image-banner[data-variant="stacked"] .banner-stack.no-media .banner-panel {
  padding: var(--space-8);
  row-gap: var(--space-4);
  border-top: 0;
  border-bottom: 0;
}
.block-image-banner[data-variant="stacked"] .banner-stack.no-media .banner-title { font-size: var(--text-h3); }
/* Flip: panel naik ke atas foto. Urutan DOM tidak dibalik — foto tetap ditulis
   lebih dulu supaya pembaca layar dan urutan fokus tidak ikut terbalik. */
.block-image-banner[data-variant="stacked"] .banner-stack.is-flip .banner-media { order: 2; }
.block-image-banner[data-variant="stacked"] .banner-stack.is-flip .banner-panel {
  order: 1;
  border-top: 0;
  border-bottom: 1px solid var(--border-faint);
}
.block-image-banner[data-variant="stacked"] .banner-stack.is-ink {
  border-color: var(--ink);
}
.block-image-banner[data-variant="stacked"] .banner-stack.is-ink .banner-panel {
  border-color: rgba(255, 255, 255, .14);
  background: var(--ink);
  color: var(--text-on-dark);
}
.block-image-banner[data-variant="stacked"] .banner-kicker {
  color: var(--text-subtle);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}
.block-image-banner[data-variant="stacked"] .banner-stack.is-ink .banner-kicker { color: var(--yellow); }
.block-image-banner[data-variant="stacked"] .banner-title {
  margin: 0;
  font-size: var(--text-h4);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.block-image-banner[data-variant="stacked"] .banner-deck {
  margin: 0;
  max-width: 46ch;                              /* measure baca, bukan lebar kotak */
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--lh-normal);
}
.block-image-banner[data-variant="stacked"] .banner-stack.is-ink .banner-deck { color: rgba(255, 255, 255, .78); }
.block-image-banner[data-variant="stacked"] .banner-cta {
  margin-top: var(--space-2);
  justify-self: start;
  display: inline-flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--text-on-dark);
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  cursor: pointer;
}
.block-image-banner[data-variant="stacked"] .banner-stack.is-ink .banner-cta {
  background: var(--yellow);
  color: var(--brand-ink);
}
.block-image-banner[data-variant="stacked"] .banner-cta svg { width: 16px; height: 16px; }
/* Kartu yang seluruhnya bisa diklik. `cursor` dan pengangkatan dipasang pada
   pembungkusnya; tombolnya sendiri tetap satu-satunya nama aksesibel. */
.block-image-banner[data-variant="stacked"] .banner-stack.is-linked { cursor: pointer; }
/* Pengangkatan hover = idiom kartu yang sudah ada di berkas ini
   (styles.css:736 `.block-card .grid-card:hover`). --shadow-card-hover TIDAK
   ADA di tokens.css; token elevasi-terangkat yang benar adalah --shadow-pop. */
.block-image-banner[data-variant="stacked"] .banner-stack.is-linked:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-pop);
}

/* ── Photo Carousel · varian "peek" ────────────────────────────────────────
   Sliver foto berikutnya dikunci 40px di SETIAP lebar: itu satu-satunya isyarat
   yang memberi tahu pengunjung bahwa rel masih berlanjut ke kanan. Angka tetap,
   bukan persentase, supaya isyaratnya sama besar di 320px maupun 894px. */
.highlights[data-variant="peek"] .carousel-head {
  padding-inline: var(--pad);
  margin-bottom: var(--space-5);
  display: flex; align-items: end; justify-content: space-between; gap: var(--space-4);
}
.highlights[data-variant="peek"] .carousel-titles { min-width: 0; display: grid; gap: var(--space-2); }
.highlights[data-variant="peek"] .carousel-kicker {
  color: var(--text-subtle);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}
.highlights[data-variant="peek"] .carousel-titles h2 {
  margin: 0;
  font-size: var(--text-h4);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.highlights[data-variant="peek"] .carousel-tools { flex: none; display: flex; align-items: center; gap: var(--space-4); }
.highlights[data-variant="peek"] .carousel-count {
  color: var(--text-subtle);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
}
.highlights[data-variant="peek"] .carousel-nav { display: flex; gap: var(--space-2); }
.highlights[data-variant="peek"] .carousel-nav button {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--surface-0);
  color: var(--text-body);
  cursor: pointer;
}
.highlights[data-variant="peek"] .carousel-nav button:disabled { opacity: .38; cursor: default; }
.highlights[data-variant="peek"] .carousel-nav svg { width: 16px; height: 16px; }
/* Lebar ubin = seluruh lebar isi DIKURANGI sliver 40px dan jaraknya. `--pad` ada
   dua kali karena rel diberi padding kiri-kanan sebesar itu. */
/* LEBAR UBIN — turunan, bukan angka yang dikira-kira. Yang dikunci adalah
   SLIVER-nya: 40px foto berikutnya harus terlihat di tepi kanan layar, di lebar
   berapa pun, karena itulah satu-satunya isyarat bahwa rel masih berlanjut.

   `100%` pada flex-basis mengacu ke KOTAK-ISI rel — kedua padding rel SUDAH
   dikurangi darinya. Sementara ruang yang benar-benar terlihat di sebelah kanan
   tepi snap adalah `lebar layar - padding KIRI` saja, alias `100% + --pad`.
   Percobaan pertama mengurangi `--pad * 2` lagi dari 100% dan itu memotong
   padding dua kali: slivernya membengkak jadi 112px, bukan 40px.

       ruang terlihat = 100% + --pad
       ruang terlihat = ubin + jarak + sliver
       ubin           = 100% + --pad - jarak - 40px                       */
.highlights[data-variant="peek"] .gallery-track > button,
.highlights[data-variant="peek"] .gallery-track > span {
  flex: 0 0 calc(100% + var(--pad) - var(--space-4) - 40px);
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.highlights[data-variant="peek"] .gallery-track { gap: var(--space-4); }
.highlights[data-variant="peek"] .gallery-track img { border-radius: 0; }
/* Caption jadi pil yang duduk DI ATAS foto (anatomi "06 Gallery · A"), bukan
   baris teks di bawahnya seperti varian klasik. */
.highlights[data-variant="peek"] .gallery-track > button > span,
.highlights[data-variant="peek"] .gallery-track > span > span {
  position: absolute; z-index: 1; left: var(--space-3); bottom: var(--space-3);
  margin: 0;
  max-width: calc(100% - var(--space-6));
  padding: var(--space-2) var(--space-4);
  overflow: hidden;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--surface-0);
  color: var(--text-body);
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.highlights[data-variant="peek"] .carousel-dots { margin-top: var(--space-4); }

/* ── Gallery · varian "feed" ───────────────────────────────────────────────
   Kisi berhenti jadi kumpulan kartu dan jadi SATU bidang: jahitan 2px, nol
   radius, nol bingkai, dan kisinya edge-to-edge sementara kepala seksi tetap
   berpagar `--pad` — idiom yang sudah dipakai .featured dan .highlights. */
.block-gallery[data-variant="feed"] { padding-inline: 0; }
.block-gallery[data-variant="feed"] .section-heading,
.block-gallery[data-variant="feed"] .gallery-head { padding-inline: var(--pad); }
.block-gallery[data-variant="feed"] .gallery-grid {
  gap: var(--space-1);
  grid-template-columns: repeat(var(--gallery-cols, 3), minmax(0, 1fr));
}
.block-gallery[data-variant="feed"] .gallery-grid > button,
.block-gallery[data-variant="feed"] .gallery-grid > span {
  position: relative;
  border: 0;
  border-radius: 0;
}
.block-gallery[data-variant="feed"] .gallery-grid img { transition: transform .2s; }
.block-gallery[data-variant="feed"] .gallery-grid > button:hover img { transform: scale(1.035); }
/* Selubung hover: peredam + ikon + penanda urutan. `aria-hidden` di DOM, jadi
   penanda ini TIDAK menggantikan nama aksesibel tombolnya. */
.block-gallery[data-variant="feed"] .gallery-veil {
  position: absolute; inset: 0; z-index: 1;
  display: grid; place-content: center; justify-items: center; gap: var(--space-2);
  padding: var(--space-3);
  background: var(--vs-black);
  color: var(--text-on-dark);
  opacity: 0;
  transition: opacity .2s;
}
.block-gallery[data-variant="feed"] .gallery-grid > button:hover .gallery-veil,
.block-gallery[data-variant="feed"] .gallery-grid > button:focus-visible .gallery-veil { opacity: .46; }
.block-gallery[data-variant="feed"] .gallery-veil svg { width: 22px; height: 22px; }
.block-gallery[data-variant="feed"] .gallery-idx {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
}
/* Pintu "+N lagi". Selubungnya PERMANEN (bukan hanya saat hover) supaya angkanya
   terbaca di layar sentuh, tempat hover tidak pernah terjadi. */
.block-gallery[data-variant="feed"] .gallery-more {
  position: absolute; inset: 0; z-index: 1;
  display: grid; place-content: center; justify-items: center; gap: var(--space-2);
  padding: var(--space-3);
  background: var(--vs-black);
  color: var(--text-on-dark);
  opacity: .58;
  transition: opacity .2s;
}
.block-gallery[data-variant="feed"] .gallery-grid > button:hover .gallery-more { opacity: .68; }
.block-gallery[data-variant="feed"] .gallery-more svg { width: 20px; height: 20px; }
.block-gallery[data-variant="feed"] .gallery-more-n {
  font-size: var(--text-md);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-display);
  line-height: 1;
}
.block-gallery[data-variant="feed"] .gallery-more-t {
  font-size: var(--text-2xs);
  opacity: .78;
}
/* Kepala kisi feed: kicker + judul + hitungan. Dipakai hanya bila admin mengisi
   judul; galeri tanpa judul tetap jadi bidang foto polos. */
.block-gallery[data-variant="feed"] .gallery-head {
  margin-bottom: var(--space-5);
  display: flex; align-items: end; justify-content: space-between; gap: var(--space-4);
}
.block-gallery[data-variant="feed"] .gallery-head-titles { min-width: 0; display: grid; gap: var(--space-2); }
.block-gallery[data-variant="feed"] .gallery-kicker {
  color: var(--text-subtle);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}
.block-gallery[data-variant="feed"] .gallery-head h2 {
  margin: 0;
  font-size: var(--text-h4);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.block-gallery[data-variant="feed"] .gallery-count {
  flex: none;
  color: var(--text-subtle);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
}

/* Kolom bertambah mengikuti BREAKPOINT YANG SUDAH ADA di berkas ini (640 dan
   1024), bukan ambang baru. Ambang 1024px prototipe sengaja TIDAK dipakai
   sebagai @container: kotak-isi terlebar yang bisa dihasilkan shell produksi
   adalah 980 - 2x42 - 2x1 = 894px, jadi aturan @container di 1024px tidak akan
   pernah menyala sekali pun. */
@media (min-width: 640px) {
  .block-gallery[data-variant="feed"] .gallery-grid { grid-template-columns: repeat(var(--gallery-cols-md, 4), minmax(0, 1fr)); }
  /* Dua ubin terlihat: ruang terlihat = 2 ubin + 2 jarak + sliver. */
  .highlights[data-variant="peek"] .gallery-track > button,
  .highlights[data-variant="peek"] .gallery-track > span { flex: 0 0 calc((100% + var(--pad) - (var(--space-4) * 2) - 40px) / 2); }
}
@media (min-width: 1024px) {
  .block-gallery[data-variant="feed"] .gallery-grid { grid-template-columns: repeat(var(--gallery-cols-lg, 5), minmax(0, 1fr)); }
  /* Di lebar ini `.gallery-track` bawaan BERHENTI jadi rel: styles.css:573
     mengubahnya jadi `display: grid` 3 kolom dengan `overflow: visible`, dan
     baris berikutnya melepas lebar ubinnya (`width: auto`). Varian klasik memang
     dirancang begitu — di layar lebar ia berubah jadi kisi, bukan carousel.

     Varian peek TIDAK boleh ikut: sliver dan tombol panah adalah SELURUH
     identitasnya, dan sebuah kisi statis membuat panah yang sudah digambar
     menunjuk ke tempat yang tidak ada. Jadi rel-nya dikembalikan di sini secara
     eksplisit — pertama kali ketahuan saat uji 1200px: slivernya 30px, bukan 40,
     karena `flex-basis` yang saya tulis tidak berlaku sama sekali di dalam grid.

     Tiga ubin terlihat: ruang terlihat = 3 ubin + 3 jarak + sliver. */
  .highlights[data-variant="peek"] .gallery-track {
    display: flex;
    overflow-x: auto;
  }
  .highlights[data-variant="peek"] .gallery-track > button,
  .highlights[data-variant="peek"] .gallery-track > span {
    width: auto;
    flex: 0 0 calc((100% + var(--pad) - (var(--space-4) * 3) - 40px) / 3);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SCHED-FIELDS — field form jadwal (R9 · Form Booking Sewa Studio, 3 Sep 2026)
   Desain klien Opsi 4: chip studio, pemilih bulan + rel tanggal, (F2) kartu ruang.
   Anatomi diambil dari yang SUDAH tayang: .vsform-chip untuk studio,
   .schedule-date untuk chip tanggal. Nol nilai hardcoded — semua token.
   ═══════════════════════════════════════════════════════════════════════════ */
.vsform-studios { gap: var(--space-2); }
.vsform-studio { border-color: var(--border-input); background: var(--vs-white); color: var(--text-body); font-weight: var(--fw-bold); }
.vsform-studio.is-on { box-shadow: none; border-color: var(--ink); background: var(--ink); color: var(--text-on-dark); }
.vsform-sched { display: grid; gap: var(--space-3); }
.vsform-sched-empty { padding: var(--space-4); border: 1px dashed var(--border-input); border-radius: var(--radius-md); color: var(--text-subtle); font-size: var(--text-2xs); text-align: center; }
.vsform-sched-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.vsform-sched-head b { font-size: var(--text-xs); font-weight: var(--fw-bold); color: var(--text-strong); }
.vsform-sched-nav { display: flex; gap: var(--space-2); }
.vsform-sched-nav button { width: var(--field-h-sm); height: var(--field-h-sm); border: 1px solid var(--border-input); border-radius: var(--radius-sm); background: var(--vs-white); color: var(--text-body); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.vsform-sched-nav button:disabled { opacity: .35; cursor: default; }
.vsform-rail { display: flex; gap: 6px; overflow-x: auto; padding-bottom: var(--space-2); scrollbar-width: none; scroll-snap-type: x proximity; }
.vsform-rail::-webkit-scrollbar { display: none; }
.vsform-day { flex: 0 0 auto; min-width: 52px; min-height: 52px; padding: 6px 8px; display: grid; gap: 1px; place-items: center; border: 1px solid var(--border-input); border-radius: var(--radius-sm); background: var(--vs-white); color: var(--text-body); font: inherit; font-size: var(--text-2xs); font-weight: var(--fw-bold); line-height: 1.2; cursor: pointer; scroll-snap-align: start; }
.vsform-day b { font-size: var(--text-sm); }
.vsform-day i { display: block; width: 5px; height: 5px; border-radius: 50%; background: var(--accent-mint); }
.vsform-day.limited i { background: var(--accent-orange); }
.vsform-day.full i { background: var(--ink-300); }
.vsform-day.full { color: var(--text-disabled); }
.vsform-day.closed { border-color: var(--ink-100); background: repeating-linear-gradient(135deg, var(--bg-inset), var(--bg-inset) 3px, var(--vs-white) 3px, var(--vs-white) 6px); color: var(--text-disabled); }
.vsform-day.closed i, .vsform-day.past i, .vsform-day.out i, .vsform-day.unknown i, .vsform-day.loading i { display: none; }
.vsform-day.past, .vsform-day.out, .vsform-day.unknown { border-color: var(--bg-inset); background: var(--bg-inset); color: var(--text-disabled); }
.vsform-day.loading { border-style: dashed; color: var(--text-disabled); }
.vsform-day.is-today { box-shadow: inset 0 0 0 1px var(--brand-line); border-color: var(--brand-line); }
.vsform-day.is-selected { border-color: var(--ink); background: var(--ink); color: var(--text-on-dark); }
.vsform-day.is-selected i { background: var(--brand); }
.vsform-day:disabled { cursor: default; }
.vsform-day:hover:not(:disabled):not(.is-selected) { border-color: var(--ink); }
.vsform-sched-status:empty { display: none; }
.vsform-sched-legend { display: flex; flex-wrap: wrap; gap: var(--space-3); color: var(--text-muted); font-size: var(--text-2xs); }
.vsform-sched-legend i { display: inline-block; width: 7px; height: 7px; margin-right: 4px; border-radius: 50%; vertical-align: 1px; background: var(--accent-mint); }
.vsform-sched-legend i.limited { background: var(--accent-orange); }
.vsform-sched-legend i.full { background: var(--ink-300); }
.vsform-sched-legend i.closed { background: var(--ink-200); }

/* F2 — field `slot_picker`: SATU kartu ruang (.schedule-room + .slot-strip yang
   sudah tayang di layar jadwal) + kaki ringkasan hitam. Yang baru hanya tiga hal:
   jam yang akan MEMUTUS deret (putus-putus), jam yang DITOLAK server saat submit
   (tersorot merah), dan kaki ringkasan. Semua token; nol angka baru. */
.vsform-slot { display: grid; gap: var(--space-3); }
.vsform-slot .schedule-room { border-radius: var(--radius-md); }
.vsform-slot .schedule-room > img { aspect-ratio: 2.6 / 1; }
.vsform-slot .slot-strip { padding-top: 0; }
.vsform-slot .slot.is-gap { border-style: dashed; color: var(--text-disabled); background: var(--vs-white); }
.vsform-slot .slot.is-failed { border-color: var(--tag-danger-border); background: var(--tag-danger-bg); color: var(--tag-danger-text); }
.vsform-slot .slot:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.vsform-slot-hint { min-height: 1.2em; }
.vsform-slot-hint:empty { display: none; }
.vsform-slotsum {
  position: sticky; bottom: var(--space-3); z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md); background: var(--ink); color: var(--text-on-dark);
  font-size: var(--text-2xs); box-shadow: var(--shadow-md);
}
.vsform-slotsum > span { min-width: 0; display: grid; gap: 2px; }
.vsform-slotsum small { color: var(--ink-300); font-size: var(--text-2xs); letter-spacing: .08em; text-transform: uppercase; }
.vsform-slotsum strong { font-size: var(--text-xs); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.vsform-slotsum b { flex: none; padding: 4px 10px; border-radius: var(--radius-pill); background: var(--yellow); color: var(--ink); font-size: var(--text-2xs); font-variant-numeric: tabular-nums; }
.vsform-slotsum.is-empty { background: var(--bg-inset); color: var(--text-subtle); box-shadow: none; }
.vsform-slotsum.is-empty small { color: var(--text-subtle); }
.vsform-slotsum.is-empty b { background: var(--ink-100); color: var(--text-subtle); }
/* Di HP kaki ringkasan menempel DI ATAS bilah navigasi bawah, bukan di baliknya.
   Tingginya diturunkan dari sumber yang sama dengan padding `.site-shell`
   (`--bottom-nav-row` + 1px border + safe-area). Ditulis SESUDAH aturan dasar di
   atas: sebuah @media dengan spesifisitas sama yang ditaruh lebih awal di berkas
   kalah cascade dan tidak pernah berlaku — terbukti di browser 3 Sep. */
@media (max-width: 639px) {
  .vsform-slotsum { bottom: calc(var(--space-3) + var(--bottom-nav-row) + 1px + env(safe-area-inset-bottom, 0px)); }
}


/* ════════════════════════════════════════════════════════════════════════════
   KELUARGA PRICELIST — port master "Pricelist Blocks v2.dc.html"
   (project claude.ai/design 1fa4489a, 11 Sep 2026; 22 unit).

   Pemetaan unit → blok ada di blocks.js. Dialek master (inline style, px, cqi)
   diterjemahkan ke token: radius 14→--radius-md · 10→--radius-sm · 6→--radius-xs;
   11px mono→--text-2xs · 13→--text-xs · 14→--text-sm · 15–16→--text-md ·
   18–22→--text-h4/--text-h3 · 24–28→--text-h2 · 30–36→--text-h1; padding
   clamp(20px,5.4cqi,32px)→clamp(space-6, 5.4cqi, space-8) dengan container-type
   pada KARTU — bentuk diatur lebar kartu, bukan lebar layar (aturan yang sama
   dengan compare_cards).

   Nol literal warna: putih-transparan di permukaan gelap memakai --text-on-dark /
   --text-on-dark-muted dan color-mix atas --vs-white; garis pada gelap memakai
   color-mix(--vs-white 14%). Variabel `--pl-*` di bawah adalah ALIAS LOKAL keluarga
   ini, bukan token baru — semuanya berakhir di token.

   LETAK: akhir berkas, SESUDAH seluruh @media/@container blok lain.
   ════════════════════════════════════════════════════════════════════════════ */

/* ── Primitif bersama ──────────────────────────────────────────────────────── */
.block-featured-offer, .block-price-rows, .block-price-menu, .block-price-accordion, .block-duration-picker,
.block-addon-calculator, .block-price-strip, .block-subscription-card, .block-option-card, .block-ticket-card,
.block-bundle-card { scroll-margin-top: 124px; }

/* Kartu tunggal (2d 2f 2g 2h): "melebar mengikuti kolom" — di stream satu kolom
   ia dibatasi selebar kartu grid 2 kolom supaya angka tidak mengambang di kanvas
   lebar; di ponsel ia selebar stream. */
.pl-single { container-type: inline-size; max-width: 560px; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); }

/* Tag bertona — triad Fresh Tag System, sama dengan .compare-badge / .catalog-badge. */
.pl-tag { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-1) var(--space-3); border: 1px solid transparent; border-radius: var(--radius-pill); font-family: var(--font-body); font-size: var(--text-2xs); font-weight: var(--fw-semibold); line-height: var(--lh-normal); white-space: nowrap; background: var(--tag-identity-bg); border-color: var(--tag-identity-border); color: var(--tag-identity-text); }
.pl-tag.has-dot::before { content: ""; width: 6px; height: 6px; border-radius: var(--radius-pill); background: currentColor; }
.pl-tag[data-tone="neutral"] { background: var(--tag-neutral-bg); border-color: var(--tag-neutral-border); color: var(--tag-neutral-text); }
.pl-tag[data-tone="success"] { background: var(--tag-success-bg); border-color: var(--tag-success-border); color: var(--tag-success-text); }
.pl-tag[data-tone="mint"] { background: var(--tag-mint-bg); border-color: var(--tag-mint-border); color: var(--tag-mint-text); }
.pl-tag[data-tone="info"] { background: var(--tag-info-bg); border-color: var(--tag-info-border); color: var(--tag-info-text); }
.pl-tag[data-tone="blue"] { background: var(--tag-blue-bg); border-color: var(--tag-blue-border); color: var(--tag-blue-text); }
.pl-tag[data-tone="purple"] { background: var(--tag-purple-bg); border-color: var(--tag-purple-border); color: var(--tag-purple-text); }
.pl-tag[data-tone="pink"] { background: var(--tag-pink-bg); border-color: var(--tag-pink-border); color: var(--tag-pink-text); }
.pl-tag[data-tone="warning"] { background: var(--tag-warning-bg); border-color: var(--tag-warning-border); color: var(--tag-warning-text); }
.pl-tag[data-tone="orange"] { background: var(--tag-orange-bg); border-color: var(--tag-orange-border); color: var(--tag-orange-text); }
.pl-tag[data-tone="danger"] { background: var(--tag-danger-bg); border-color: var(--tag-danger-border); color: var(--tag-danger-text); }
.pl-tag[data-tone="identity"] { background: var(--tag-identity-bg); border-color: var(--tag-identity-border); color: var(--tag-identity-text); }

/* Tombol: .route-cta bawaan = ink. Varian `brand` primitive Button = kuning di
   atas gelap. Tangga tinggi 36 / 45 / 52 mengikuti primitive Button sm/md/lg. */
.pl-cta { white-space: nowrap; }
.pl-cta.is-brand { border-color: var(--brand-accent); background: var(--brand-accent); color: var(--brand-accent-ink); }
.pl-cta.is-block { width: 100%; }
.pl-cta.is-sm { min-height: 36px; padding: 0 var(--space-4); }
.pl-cta.is-lg { min-height: 52px; padding: 0 var(--space-6); font-size: var(--text-xs); }

/* Daftar centang — anatomi .compare-points, dengan varian di atas gelap. */
.pl-points { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
.pl-points li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); align-items: start; color: var(--text-body); font-size: var(--text-xs); line-height: var(--lh-normal); }
.pl-points svg { width: 15px; height: 15px; margin-top: 2px; color: var(--brand-line); }
.pl-points li.is-off { color: var(--text-subtle); }
.pl-points li.is-off svg { color: var(--text-subtle); }
.pl-points.is-on-dark li { color: color-mix(in srgb, var(--vs-white) 90%, transparent); }
.pl-points.is-on-dark svg { color: var(--brand-accent); }
.pl-points.is-on-dark li.is-off, .pl-points.is-on-dark li.is-off svg { color: var(--text-on-dark-muted); }

/* Pil pilihan (1c durasi · 2f varian): ink saat aktif, garis saat tidak. */
.pl-pill { min-height: 44px; padding: var(--space-3) var(--space-5); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1); flex: 1 1 90px; border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--surface-0); color: var(--text-strong); font: inherit; font-size: var(--text-sm); font-weight: var(--fw-semibold); cursor: pointer; transition: background .16s ease, color .16s ease, border-color .16s ease; }
.pl-pill small { font-size: var(--text-2xs); font-weight: var(--fw-medium); opacity: .7; }
.pl-pill.is-on { border-color: var(--bg-ink); background: var(--bg-ink); color: var(--text-on-dark); }

/* Segmented control (2d) — anatomi GUI/02/SEGMENTED-CONTROL: wadah beralas
   surface-1, tepi line-200, radius sm, padding 3px; tombol aktif = ink. */
.pl-seg { display: inline-flex; align-self: flex-start; gap: var(--space-1); padding: 3px; border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--surface-1); }
.pl-seg-btn { min-height: 34px; padding: 0 var(--space-5); border: 0; border-radius: calc(var(--radius-sm) - 2px); background: transparent; color: var(--text-muted); font: inherit; font-size: var(--text-xs); font-weight: var(--fw-semibold); cursor: pointer; transition: background .16s ease, color .16s ease; }
.pl-seg-btn.is-on { background: var(--bg-ink); color: var(--text-on-dark); }

/* Stepper jumlah (2g). */
.pl-stepper { display: inline-flex; align-items: center; border: 1px solid var(--border-default); border-radius: var(--radius-sm); overflow: hidden; }
.pl-stepper-btn { width: 40px; height: 40px; display: grid; place-items: center; border: 0; background: var(--surface-0); color: var(--text-strong); cursor: pointer; }
.pl-stepper-btn:disabled { color: var(--text-disabled); cursor: default; }
.pl-stepper-btn svg { width: 14px; height: 14px; }
.pl-stepper-value { min-width: 36px; height: 40px; display: grid; place-items: center; border-inline: 1px solid var(--border-default); font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-strong); font-variant-numeric: tabular-nums; }
@media (hover: hover) { .pl-stepper-btn:not(:disabled):hover { background: var(--surface-1); } }

/* ── featured_offer (1f 3a 3b 3c 3d 3e) ────────────────────────────────────── */
.fo-card { container-type: inline-size; --pl-pad: clamp(var(--space-6), 5.4cqi, var(--space-8)); border-radius: var(--radius-md); overflow: hidden; color: var(--text-on-dark); }
.fo-kicker { display: block; font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--text-on-dark-muted); }
.fo-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-4); flex-wrap: wrap; }
.fo-titles { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.fo-title { margin: 0; font-family: var(--font-display); font-size: clamp(var(--text-h3), 5.4cqi, var(--text-h1)); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1.08; }
.fo-intro { margin: 0; max-width: 420px; font-size: var(--text-sm); line-height: var(--lh-normal); color: color-mix(in srgb, var(--vs-white) 78%, transparent); }
.fo-badge { align-self: flex-start; padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill); background: var(--brand-accent); color: var(--brand-accent-ink); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-semibold); letter-spacing: var(--ls-label-sm); text-transform: uppercase; white-space: nowrap; }
.fo-price { display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap; }
.fo-price-value { font-family: var(--font-display); font-size: clamp(var(--text-h2), 7.6cqi, var(--text-display-lg)); font-weight: var(--fw-bold); letter-spacing: -.03em; line-height: 1; white-space: nowrap; }
.fo-price-strike { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-on-dark-muted); }
.fo-price-unit { font-size: var(--text-xs); color: color-mix(in srgb, var(--vs-white) 70%, transparent); }
.fo-actions { display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap; padding-top: var(--space-5); border-top: 1px solid color-mix(in srgb, var(--vs-white) 14%, transparent); }
.fo-actions.is-stacked { flex-direction: column; align-items: stretch; gap: var(--space-3); padding-top: 0; border-top: 0; }
.fo-actions.is-stacked .pl-cta { width: 100%; }
.fo-actions.is-stacked .fo-note { text-align: center; }
.fo-note { font-size: var(--text-xs); color: color-mix(in srgb, var(--vs-white) 70%, transparent); }
.fo-card .pl-points { grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: var(--space-3) var(--space-7); }
.fo-card .pl-points li { font-size: var(--text-sm); }

/* gradient (1f) — kartu gradient brand, bayangan identitas. */
.fo-card[data-variant="gradient"] { display: flex; flex-direction: column; gap: clamp(var(--space-5), 5cqi, var(--space-7)); padding: var(--pl-pad); background: var(--brand-gradient); box-shadow: var(--shadow-identity); }

/* ink (3c) — hitam pekat, rule brand 3px di atas, angka berwarna brand. */
.fo-card[data-variant="ink"] { display: flex; flex-direction: column; gap: var(--space-6); padding: var(--pl-pad); background: var(--bg-ink); border-top: 3px solid var(--brand-accent); }
.fo-card[data-variant="ink"] .fo-price-value { color: var(--brand-accent); }
.fo-card[data-variant="ink"] .fo-badge { background: transparent; border: 1px solid var(--brand-accent); color: var(--brand-accent); }
.fo-countdown { display: flex; flex-direction: column; gap: var(--space-1); align-items: flex-start; }
.fo-countdown-digits { font-family: var(--font-mono); font-size: var(--text-h4); color: var(--brand-accent); font-variant-numeric: tabular-nums; }
.fo-quota { display: flex; flex-direction: column; gap: var(--space-3); }
.fo-quota-line { display: flex; justify-content: space-between; gap: var(--space-3); font-size: var(--text-xs); color: color-mix(in srgb, var(--vs-white) 70%, transparent); }
.fo-quota-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.fo-quota-bar { height: 4px; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--vs-white) 14%, transparent); overflow: hidden; }
.fo-quota-bar span { display: block; height: 100%; background: var(--brand-accent); }

/* soft (3d) — gradient terang, tinta hitam, angka pendamping berjajar. */
.fo-card[data-variant="soft"] { display: flex; flex-direction: column; gap: var(--space-6); padding: var(--pl-pad); background: var(--brand-gradient-soft); border: 1px solid var(--brand-line); box-shadow: var(--shadow-card); color: var(--text-body); }
.fo-card[data-variant="soft"] .fo-kicker { color: var(--text-subtle); }
.fo-card[data-variant="soft"] .fo-title, .fo-card[data-variant="soft"] .fo-price-value { color: var(--text-strong); }
.fo-card[data-variant="soft"] .fo-intro, .fo-card[data-variant="soft"] .fo-note { color: var(--text-muted); }
.fo-card[data-variant="soft"] .fo-price-strike, .fo-card[data-variant="soft"] .fo-price-unit { color: var(--text-subtle); }
.fo-card[data-variant="soft"] .fo-badge { background: var(--tag-identity-bg); border: 1px solid var(--tag-identity-border); color: var(--tag-identity-text); font-family: var(--font-body); text-transform: none; letter-spacing: var(--ls-normal); }
.fo-card[data-variant="soft"] .fo-actions { border-top-color: var(--brand-line); }
.fo-card[data-variant="soft"] .pl-points svg { color: var(--ink-900); }
.fo-facts { display: flex; align-items: flex-end; gap: clamp(var(--space-6), 5.4cqi, var(--space-8)); flex-wrap: wrap; }
.fo-fact { display: flex; flex-direction: column; gap: var(--space-2); padding-bottom: var(--space-2); }
.fo-fact.is-main { padding-bottom: 0; }
.fo-fact-value { font-family: var(--font-mono); font-size: var(--text-md); color: var(--text-strong); }
.fo-fact:nth-child(n+3) .fo-fact-value { color: var(--text-subtle); text-decoration: line-through; }

/* split (3a) — dua panel melipat: gradient (harga) | putih (inklusi + CTA). */
.fo-card[data-variant="split"] { display: flex; flex-wrap: wrap; border: 1px solid var(--border-default); box-shadow: var(--shadow-identity); }
.fo-pane { flex: 1 1 260px; min-width: 0; padding: var(--pl-pad); display: flex; flex-direction: column; justify-content: space-between; gap: var(--space-7); }
.fo-pane-ink { background: var(--brand-gradient); }
.fo-pane-light { flex-basis: 280px; background: var(--surface-0); color: var(--text-body); gap: var(--space-5); }
.fo-list { display: flex; flex-direction: column; gap: var(--space-4); }
.fo-list-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.fo-list-head .fo-kicker { color: var(--text-subtle); }
.fo-pane-light .fo-note { color: var(--text-subtle); }

/* strip (3b) — satu baris melipat: judul · harga+pil · tombol. */
.fo-card[data-variant="strip"] { display: flex; align-items: center; gap: var(--space-5) var(--space-8); flex-wrap: wrap; padding: clamp(var(--space-5), 5cqi, var(--space-7)) clamp(var(--space-5), 5cqi, var(--space-8)); background: var(--brand-gradient); box-shadow: var(--shadow-identity); }
.fo-card[data-variant="strip"] .fo-titles { flex: 1 1 220px; gap: var(--space-2); }
.fo-card[data-variant="strip"] .fo-title { font-size: clamp(var(--text-h4), 5cqi, var(--text-h3)); line-height: 1.15; }
.fo-card[data-variant="strip"] .fo-price { gap: var(--space-3); }
.fo-card[data-variant="strip"] .fo-price-value { font-size: clamp(var(--text-h3), 5.4cqi, var(--text-h1)); }
.fo-card[data-variant="strip"] .fo-price .fo-badge { align-self: baseline; }

/* hero (3e) — kolom isi | foto, lalu baris metrik beralas lebih gelap. */
.fo-card[data-variant="hero"] { display: flex; flex-direction: column; background: var(--brand-gradient); box-shadow: var(--shadow-identity); }
.fo-hero-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.fo-hero-copy { padding: clamp(var(--space-6), 5.4cqi, var(--space-9)); display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }
.fo-hero-copy .fo-actions { border-top: 0; padding-top: 0; }
.fo-hero-media { min-height: 220px; background: color-mix(in srgb, var(--vs-white) 8%, transparent); border-left: 1px solid color-mix(in srgb, var(--vs-white) 12%, transparent); }
.fo-hero-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fo-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 110px), 1fr)); border-top: 1px solid color-mix(in srgb, var(--vs-white) 14%, transparent); background: color-mix(in srgb, var(--vs-black) 35%, transparent); }
.fo-metric { padding: var(--space-5) clamp(var(--space-5), 5cqi, var(--space-7)); display: flex; flex-direction: column; gap: var(--space-1); border-right: 1px solid color-mix(in srgb, var(--vs-white) 10%, transparent); }
.fo-metric:last-child { border-right: 0; }
.fo-metric strong { font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; }
.fo-metric span { font-size: var(--text-xs); color: var(--text-on-dark-muted); }

/* ── price_rows (1b tabel · 2c kartu baris) ───────────────────────────────── */
.pr-wrap[data-variant="table"] { container-type: inline-size; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); overflow: hidden; }
.pr-head { padding: var(--space-5) var(--space-6) var(--space-4); display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; border-bottom: 1px solid var(--border-default); }
.pr-titles { display: flex; flex-direction: column; gap: var(--space-2); }
.pr-title { margin: 0; font-family: var(--font-display); font-size: var(--text-h3); letter-spacing: var(--ls-display); color: var(--text-strong); }
.pr-aside { font-size: var(--text-xs); color: var(--text-subtle); }
.pr-row { width: 100%; display: flex; align-items: center; gap: var(--space-4) var(--space-6); flex-wrap: wrap; padding: var(--space-4) var(--space-6); border: 0; border-bottom: 1px solid var(--border-faint); background: transparent; color: inherit; font: inherit; text-align: left; }
button.pr-row { cursor: pointer; }
@media (hover: hover) { button.pr-row:hover { background: var(--surface-1); } button.pr-row:hover .pr-go { border-color: var(--border-strong); } }
.pr-name { display: flex; flex-direction: column; gap: var(--space-1); flex: 1 1 180px; min-width: 0; }
.pr-name strong { font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--text-strong); }
.pr-name span { font-size: var(--text-xs); color: var(--text-muted); }
.pr-prices { display: flex; gap: var(--space-6); align-items: center; justify-content: space-between; flex: 1 1 240px; margin-left: auto; }
.pr-cell { display: flex; flex-direction: column; gap: var(--space-1); }
.pr-cell-label { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-subtle); }
.pr-cell-value { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-strong); font-variant-numeric: tabular-nums; }
.pr-cell.is-muted .pr-cell-value { color: var(--text-muted); }
.pr-go { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid var(--border-default); border-radius: var(--radius-pill); color: var(--text-strong); flex: 0 0 auto; }
.pr-go svg { width: 14px; height: 14px; }
.pr-go.is-empty { visibility: hidden; }
.pr-foot { padding: var(--space-4) var(--space-6); display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; background: var(--surface-1); }
.pr-footnote { font-size: var(--text-xs); color: var(--text-muted); }

.pr-wrap[data-variant="cards"] { display: flex; flex-direction: column; gap: var(--space-3); }
.pr-card { container-type: inline-size; width: 100%; display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); color: inherit; font: inherit; text-align: left; }
button.pr-card { cursor: pointer; }
@media (hover: hover) { button.pr-card:hover { border-color: var(--border-strong); } }
.pr-thumb { width: 68px; height: 68px; flex: 0 0 auto; display: grid; place-items: center; border: 1px solid var(--border-faint); border-radius: var(--radius-sm); background: var(--surface-1); color: var(--text-subtle); overflow: hidden; }
.pr-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pr-thumb svg { width: 18px; height: 18px; }
.pr-card-body { flex: 1 1 140px; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); }
.pr-card-body strong { font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--text-strong); line-height: var(--lh-snug); }
.pr-card-sub { font-size: var(--text-xs); color: var(--text-muted); }
.pr-card-price { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-1); margin-left: auto; }
.pr-card-price b { font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); color: var(--text-strong); }
.pr-card-price span { font-size: var(--text-2xs); color: var(--text-subtle); }

/* ── price_menu (1d) ──────────────────────────────────────────────────────── */
.pm-wrap { container-type: inline-size; padding: clamp(var(--space-6), 5.4cqi, var(--space-8)); display: flex; flex-direction: column; gap: var(--space-7); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); }
.pm-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; padding-bottom: var(--space-4); border-bottom: 1px solid var(--border-strong); }
.pm-title { margin: 0; font-family: var(--font-display); font-size: clamp(var(--text-h4), 5cqi, var(--text-h2)); letter-spacing: var(--ls-display); color: var(--text-strong); }
.pm-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); gap: var(--space-7) var(--space-9); }
.pm-cat { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.pm-item { display: flex; align-items: baseline; gap: var(--space-3); min-width: 0; }
.pm-item-name { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.pm-item-name strong { font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--text-strong); }
.pm-item-name span { font-size: var(--text-xs); color: var(--text-muted); line-height: 1.45; }
.pm-dots { flex: 1; min-width: 16px; border-bottom: 1px dotted var(--ink-300); transform: translateY(-4px); }
.pm-item-price { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-strong); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pm-foot { margin: 0; font-size: var(--text-xs); color: var(--text-subtle); line-height: var(--lh-normal); }

/* ── price_accordion (1g) ─────────────────────────────────────────────────── */
.pa-wrap { border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); overflow: hidden; }
.pa-cat { border-bottom: 1px solid var(--border-default); }
.pa-cat:last-of-type { border-bottom: 0; }
.pa-summary { min-height: 56px; padding: var(--space-4) var(--space-6); display: flex; align-items: center; gap: var(--space-4); cursor: pointer; list-style: none; }
.pa-summary::-webkit-details-marker { display: none; }
@media (hover: hover) { .pa-summary:hover { background: var(--surface-1); } }
.pa-num { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-subtle); flex: 0 0 auto; }
.pa-titles { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; min-width: 0; }
.pa-titles strong { font-size: var(--text-md); font-weight: var(--fw-semibold); color: var(--text-strong); }
.pa-titles span { font-size: var(--text-xs); color: var(--text-muted); }
.pa-from { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }
.pa-chev { display: grid; place-items: center; color: var(--text-strong); flex: 0 0 auto; transition: transform .16s ease; }
.pa-chev svg { width: 16px; height: 16px; }
.pa-cat[open] > .pa-summary .pa-chev { transform: rotate(180deg); }
.pa-body { padding: 0 var(--space-6) var(--space-5); display: flex; flex-direction: column; }
.pa-item { display: flex; align-items: center; gap: var(--space-4); padding: 11px 0; border-top: 1px solid var(--border-faint); }
.pa-item-name { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; min-width: 0; font-size: var(--text-sm); color: var(--text-strong); }
.pa-item-desc { font-size: var(--text-xs); color: var(--text-muted); }
.pa-item-price { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-strong); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pa-foot { padding: var(--space-4) var(--space-6); display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; background: var(--surface-1); font-size: var(--text-xs); color: var(--text-muted); }
@media (prefers-reduced-motion: reduce) { .pa-chev { transition: none; } }

/* ── duration_picker (1c) ─────────────────────────────────────────────────── */
.dp-card { container-type: inline-size; display: flex; flex-wrap: wrap; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); overflow: hidden; }
.dp-pane { flex: 1 1 280px; min-width: 0; padding: clamp(var(--space-5), 5cqi, var(--space-7)); display: flex; flex-direction: column; gap: var(--space-5); }
.dp-pane-sum { flex-basis: 240px; background: var(--surface-1); justify-content: space-between; gap: var(--space-4); }
.dp-head { display: flex; flex-direction: column; gap: var(--space-2); }
.dp-kicker-row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.dp-title { margin: 0; font-family: var(--font-display); font-size: clamp(var(--text-h4), 5cqi, var(--text-h3)); letter-spacing: var(--ls-display); line-height: 1.15; color: var(--text-strong); }
.dp-desc { margin: 0; font-size: var(--text-xs); line-height: var(--lh-normal); color: var(--text-muted); }
.dp-options-wrap { display: flex; flex-direction: column; gap: var(--space-3); }
.dp-options { display: flex; gap: var(--space-3); flex-wrap: wrap; }
.dp-sum { display: flex; flex-direction: column; gap: var(--space-3); }
.dp-line { display: flex; justify-content: space-between; gap: var(--space-3); font-size: var(--text-xs); color: var(--text-muted); }
.dp-line-value { font-family: var(--font-mono); color: var(--text-strong); font-variant-numeric: tabular-nums; }
.dp-line-value.is-good { color: var(--accent-mint); }
.dp-rule { height: 1px; background: var(--border-default); }
.dp-total { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
.dp-total-value { font-family: var(--font-display); font-size: clamp(var(--text-h3), 5.4cqi, var(--text-h1)); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); color: var(--text-strong); }
.dp-pane-sum .compare-note { margin-top: 0; }

/* ── addon_calculator (1i) ────────────────────────────────────────────────── */
.ac-card { container-type: inline-size; display: flex; flex-direction: column; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); overflow: hidden; }
.ac-head { padding: var(--space-5) var(--space-6); display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; border-bottom: 1px solid var(--border-default); }
.ac-titles { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; min-width: 160px; }
.ac-titles strong { font-size: var(--text-md); color: var(--text-strong); }
.ac-base-price { font-family: var(--font-mono); font-size: var(--text-md); color: var(--text-strong); }
.ac-body { padding: var(--space-5) var(--space-6); display: flex; flex-direction: column; gap: var(--space-4); }
.ac-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr)); gap: var(--space-3); }
.ac-tile { min-height: 44px; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); text-align: left; border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--surface-0); font: inherit; cursor: pointer; transition: background .16s ease, border-color .16s ease; }
@media (hover: hover) { .ac-tile:hover { border-color: var(--border-strong); } }
.ac-tile.is-on { background: var(--brand-soft); border-color: var(--brand-line); }
.ac-tile-top { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; }
.ac-tile-name { font-size: var(--text-xs); font-weight: var(--fw-semibold); color: var(--text-strong); }
.ac-box { width: 18px; height: 18px; display: grid; place-items: center; border: 1px solid var(--ink-200); border-radius: var(--radius-xs); color: transparent; flex: 0 0 auto; }
.ac-box svg { width: 12px; height: 12px; }
.ac-tile.is-on .ac-box { background: var(--bg-ink); border-color: var(--bg-ink); color: var(--text-on-dark); }
.ac-tile-desc { font-size: var(--text-2xs); line-height: 1.4; color: var(--text-muted); }
.ac-tile-price { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-strong); }
.ac-bar { padding: var(--space-4) var(--space-6); display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; background: var(--bg-ink); color: var(--text-on-dark); }
.ac-bar-copy { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; min-width: 150px; }
.ac-bar-label { font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-on-dark-muted); }
.ac-bar-total { font-family: var(--font-display); font-size: clamp(var(--text-h4), 5cqi, var(--text-h2)); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); }

/* ── price_strip (1j) ─────────────────────────────────────────────────────── */
.pst-card { container-type: inline-size; display: flex; flex-direction: column; border: 1px solid var(--border-default); border-radius: var(--radius-lg); background: var(--surface-0); box-shadow: var(--shadow-card); overflow: hidden; }
.pst-head { padding: var(--space-5) var(--space-6) var(--space-2); display: flex; flex-direction: column; gap: var(--space-2); }
.pst-grab { width: 36px; height: 4px; margin: 0 auto var(--space-3); border-radius: var(--radius-pill); background: var(--ink-200); }
.pst-head-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.pst-title { font-family: var(--font-display); font-size: var(--text-h4); letter-spacing: var(--ls-display); color: var(--text-strong); }
.pst-rail { display: flex; gap: var(--space-3); overflow-x: auto; scroll-snap-type: x mandatory; padding: var(--space-3) var(--space-6) var(--space-5); scrollbar-width: none; }
.pst-rail::-webkit-scrollbar { display: none; }
.pst-item { scroll-snap-align: start; flex: 0 0 clamp(150px, 44%, 190px); padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); text-align: left; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); font: inherit; cursor: pointer; transition: border-color .16s ease, box-shadow .16s ease; }
.pst-item.is-on { border-color: var(--border-strong); box-shadow: var(--shadow-card); }
.pst-media { aspect-ratio: 4 / 3; display: grid; place-items: center; border: 1px solid var(--border-faint); border-radius: var(--radius-xs); background: var(--surface-1); color: var(--text-subtle); overflow: hidden; }
.pst-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pst-media svg { width: 18px; height: 18px; }
.pst-copy { display: flex; flex-direction: column; gap: var(--space-1); }
.pst-copy strong { font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--text-strong); }
.pst-copy span { font-size: var(--text-2xs); color: var(--text-muted); }
.pst-price { display: flex; align-items: baseline; gap: var(--space-2); }
.pst-price b { font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--fw-medium); color: var(--text-strong); }
.pst-price span { font-size: var(--text-2xs); color: var(--text-subtle); }
.pst-bar { padding: var(--space-4) var(--space-6) var(--space-5); display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; border-top: 1px solid var(--border-default); }
.pst-bar-copy { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; min-width: 140px; }
.pst-bar-name { font-size: var(--text-2xs); color: var(--text-muted); }
.pst-bar-price { display: flex; align-items: baseline; gap: var(--space-2); }
/* Angka dan satuan tidak boleh pecah baris: pada 390px "Rp 350.000" yang patah
   jadi dua baris terbaca sebagai dua angka. Tombolnya yang turun (flex-wrap),
   bukan angkanya yang patah. */
.pst-bar-price b { font-family: var(--font-display); font-size: var(--text-h2); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); color: var(--text-strong); white-space: nowrap; }
.pst-bar-price span { font-size: var(--text-2xs); color: var(--text-subtle); white-space: nowrap; }
/* Di kartu sempit (ponsel 375px) angka 28px + tombol lg tidak muat sebaris: tombol
   turun ke bawah dan melebar penuh — angkanya TIDAK mengecil dan tidak tertimpa. */
@container (max-width: 420px) {
  .pst-bar { flex-direction: column; align-items: stretch; }
  .pst-bar .pl-cta { width: 100%; }
}

/* ── subscription_card (2d) ───────────────────────────────────────────────── */
.sub-card { padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-5); }
.sub-head { display: flex; flex-direction: column; gap: var(--space-3); }
.sub-kicker-row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.sub-price { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.sub-price-value { font-family: var(--font-display); font-size: clamp(var(--text-h3), 5.4cqi, var(--text-h1)); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; color: var(--text-strong); }
.sub-price-unit { font-size: var(--text-xs); color: var(--text-subtle); }
.sub-note { font-size: var(--text-xs); color: var(--text-muted); }

/* ── option_card (2f) ─────────────────────────────────────────────────────── */
.oc-card { padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-5); }
.oc-head { display: flex; gap: var(--space-4); align-items: flex-start; flex-wrap: wrap; }
.oc-thumb { width: 84px; height: 84px; flex: 0 0 auto; border: 1px solid var(--border-faint); border-radius: var(--radius-sm); background: var(--surface-1); overflow: hidden; }
.oc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.oc-titles { flex: 1 1 140px; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.oc-title { font-size: var(--text-md); line-height: var(--lh-snug); color: var(--text-strong); }
.oc-sub { font-size: var(--text-xs); color: var(--text-muted); }
.oc-options { display: flex; flex-direction: column; gap: var(--space-3); }
.oc-list { display: flex; flex-direction: column; gap: var(--space-3); }
.oc-option { width: 100%; min-height: 44px; padding: 0 var(--space-4); display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); text-align: left; border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--surface-0); color: var(--text-strong); font: inherit; font-size: var(--text-xs); font-weight: var(--fw-semibold); cursor: pointer; transition: background .16s ease, color .16s ease, border-color .16s ease; }
.oc-option.is-on { border-color: var(--bg-ink); background: var(--bg-ink); color: var(--text-on-dark); }
.oc-option-price { font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--fw-medium); opacity: .8; }
.oc-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; padding-top: var(--space-4); border-top: 1px solid var(--border-faint); }
.oc-foot-copy { display: flex; flex-direction: column; gap: var(--space-1); }
.oc-foot-desc { font-size: var(--text-2xs); color: var(--text-muted); }
.oc-foot-price { font-family: var(--font-display); font-size: var(--text-h2); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; color: var(--text-strong); }

/* ── ticket_card (2g) ─────────────────────────────────────────────────────── */
.tk-card { display: flex; flex-direction: column; }
.tk-top { padding: var(--space-5); display: flex; gap: var(--space-5); align-items: flex-start; flex-wrap: wrap; }
.tk-date { width: 64px; flex: 0 0 auto; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; text-align: center; }
.tk-date-month { display: block; padding: var(--space-2) 0; background: var(--bg-ink); color: var(--text-on-dark); font-family: var(--font-mono); font-size: 10px; letter-spacing: var(--ls-label-sm); }
.tk-date-day { display: block; padding: var(--space-3) 0 var(--space-2); font-family: var(--font-display); font-size: var(--text-h2); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; color: var(--text-strong); }
.tk-copy { flex: 1 1 150px; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.tk-copy > span { font-size: var(--text-xs); color: var(--text-muted); }
.tk-tag { border-radius: var(--radius-xs); }
.tk-title { font-size: var(--text-md); line-height: var(--lh-snug); color: var(--text-strong); }
/* "Sobekan" tiket: garis putus + dua lekukan setengah lingkaran beralas kanvas. */
.tk-tear { position: relative; height: 1px; margin: 0 var(--space-4); border-top: 1px dashed var(--ink-300); }
.tk-tear::before, .tk-tear::after { content: ""; position: absolute; top: -8px; width: 16px; height: 16px; border-radius: var(--radius-pill); background: var(--bg-app); }
.tk-tear::before { left: -22px; border-right: 1px solid var(--border-default); }
.tk-tear::after { right: -22px; border-left: 1px solid var(--border-default); }
.tk-foot { padding: var(--space-5) var(--space-5) var(--space-5); display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.tk-price { display: flex; flex-direction: column; gap: var(--space-1); flex: 1 1 120px; }
.tk-price-unit { font-size: var(--text-2xs); color: var(--text-muted); }
.tk-total { font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; color: var(--text-strong); }

/* ── bundle_card (2h) ─────────────────────────────────────────────────────── */
.bd-card { padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-5); }
.bd-head { display: flex; flex-direction: column; gap: var(--space-2); }
.bd-title { margin: 0; font-family: var(--font-display); font-size: var(--text-h3); letter-spacing: var(--ls-display); line-height: 1.15; color: var(--text-strong); }
.bd-list { display: flex; flex-direction: column; }
.bd-item { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; border-top: 1px solid var(--border-faint); }
.bd-thumb { width: 34px; height: 34px; flex: 0 0 auto; border: 1px solid var(--border-faint); border-radius: var(--radius-xs); background: var(--surface-1); overflow: hidden; }
.bd-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bd-copy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.bd-name { font-size: var(--text-xs); color: var(--text-strong); }
.bd-desc { font-size: var(--text-2xs); color: var(--text-muted); }
.bd-price { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-subtle); white-space: nowrap; }
.bd-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; padding-top: var(--space-4); border-top: 1px solid var(--border-strong); }
.bd-total { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.bd-total-value { font-family: var(--font-display); font-size: var(--text-h2); font-weight: var(--fw-bold); letter-spacing: var(--ls-display); line-height: 1; color: var(--text-strong); }

/* ── Perluasan compare_cards (2b 2e 2j) ───────────────────────────────────── */
/* Kepala penjual (2e): avatar inisial ink + nama/keterangan; duduk di atas judul. */
.compare-author { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); align-items: center; margin-bottom: var(--space-4); }
.compare-avatar { width: 32px; height: 32px; display: grid; place-items: center; border-radius: var(--radius-pill); background: var(--bg-ink); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: var(--fw-bold); }
.compare-author-copy { display: flex; flex-direction: column; min-width: 0; }
.compare-author-copy strong { font-size: var(--text-xs); font-weight: var(--fw-semibold); color: var(--text-strong); }
.compare-author-copy span { font-size: var(--text-2xs); color: var(--text-muted); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-avatar { background: var(--brand); color: var(--brand-ink); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-author-copy strong { color: var(--text-on-dark); }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-author-copy span { color: var(--text-on-dark-muted); }
/* Baris meta berikon (2b): potongan berjajar, melipat. */
.compare-meta { display: flex; gap: var(--space-4); flex-wrap: wrap; font-size: var(--text-xs); color: var(--text-muted); }
.compare-meta-item { display: inline-flex; align-items: center; gap: var(--space-2); }
.compare-meta-item svg { width: 13px; height: 13px; }
.compare-grid[data-variant="surface"] .compare-card.is-featured .compare-meta { color: var(--text-on-dark-muted); }
/* Baris data sebagai ubin (2j): kotak kecil auto-fit, label mono di atas nilai. */
.compare-grid[data-rows="tiles"] .compare-rows { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 120px), 1fr)); gap: var(--space-3); }
.compare-grid[data-rows="tiles"] .compare-row { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-sm); }
.compare-grid[data-rows="tiles"] .compare-row dt { font-size: 10px; }
.compare-grid[data-rows="tiles"] .compare-row dd { text-align: left; font-family: var(--font-body); font-size: var(--text-sm); font-weight: var(--fw-semibold); }
.compare-grid[data-rows="tiles"] .compare-lede + .compare-rows { border-top: 0; padding-top: 0; }
.compare-grid[data-variant="surface"][data-rows="tiles"] .compare-card.is-featured .compare-row { border-color: color-mix(in srgb, var(--vs-white) 18%, transparent); }

/* ── Standar mobile keluarga pricelist (owner, 11 Sep 2026: "ukuran font terlalu
   besar, sesuaikan standar mobile") ────────────────────────────────────────────
   Diatur lebar KARTU (container), bukan layar: kartu ≤ 420px = ponsel atau kolom
   sempit. Tangga: harga 24px (h2 → turun satu langkah) · judul 20px · angka
   pendamping 16px · label 11px. Angka display 7.6cqi tetap berlaku di kartu lebar. */
@container (max-width: 420px) {
  .fo-title { font-size: var(--text-h3); line-height: 1.12; }
  .fo-price-value { font-size: 24px; }
  .fo-card[data-variant="strip"] .fo-price-value { font-size: var(--text-h4); }
  .fo-countdown-digits { font-size: var(--text-md); }
  .fo-fact-value { font-size: var(--text-sm); }
  /* Baris metrik hero: tiga kolom 110px tidak muat di 327px — "Rp 48 M" pecah dua
     baris dan menimpa labelnya. Di kartu sempit tiap metrik jadi SATU BARIS
     (angka kiri, label kanan) dipisah garis, bukan tiga kolom yang berdesakan. */
  .fo-metrics { grid-template-columns: minmax(0, 1fr); }
  .fo-metric { flex-direction: row; align-items: baseline; justify-content: space-between; gap: var(--space-3); padding: var(--space-4) clamp(var(--space-5), 5cqi, var(--space-7)); border-right: 0; border-top: 1px solid color-mix(in srgb, var(--vs-white) 10%, transparent); }
  .fo-metric:first-child { border-top: 0; }
  .fo-metric strong { font-size: var(--text-h4); }
  .fo-hero-media { min-height: 180px; }
  .pr-card-price b { font-size: var(--text-md); }
  .pm-title { font-size: var(--text-h4); }
  .dp-title { font-size: var(--text-h4); }
  .dp-total-value { font-size: 24px; }
  .ac-bar-total { font-size: var(--text-h3); }
  .pst-bar-price b { font-size: 24px; }
  .sub-price-value { font-size: 24px; }
  .oc-foot-price { font-size: 24px; }
  .tk-total { font-size: var(--text-h3); }
  .tk-date-day { font-size: var(--text-h3); }
  .bd-total-value { font-size: 24px; }
  .bd-title { font-size: var(--text-h4); }
}

/* Kepala seksi (.compare-head — dipakai compare_cards DAN keluarga pricelist) di
   layar ponsel: 28px dengan tracking −.045em terlalu rapat dan terlalu besar untuk
   judul panjang seperti "Kartu harga universal (compare_cards diperluas)" — kata
   saling menempel dan pecah tiga baris. Standar mobile: 22px, tracking display
   −.02em, line-height snug; kicker dan lead diberi jarak sendiri. Desktop tetap. */
@media (max-width: 639px) {
  .compare-head { gap: var(--space-2); margin-bottom: var(--space-5); }
  .compare-head h2 { font-size: var(--text-h3); line-height: var(--lh-snug); letter-spacing: var(--ls-display); overflow-wrap: anywhere; }
  .compare-kicker { font-size: var(--text-2xs); }
  .compare-lead { font-size: var(--text-xs); line-height: var(--lh-normal); margin-top: var(--space-1); }
}

/* Compare Cards di ponsel disamakan dengan standar mobile keluarga pricelist
   (owner, 11 Sep 2026: "sesuaikan semua"): angka harga 36 → 24px, judul kartu
   22 → 20px. Varian media sudah 28px lewat aturannya sendiri; aturan ini menang
   karena ditulis paling akhir dengan spesifisitas sama. Desktop tidak berubah. */
@media (max-width: 639px) {
  .compare-card h3 { font-size: 20px; }
  .compare-lede-value, .compare-grid[data-variant="media"] .compare-lede-value { font-size: 24px; }
  .compare-lede-unit { font-size: var(--text-xs); }
}
.block-html-component{width:100%;overflow:hidden}
.block-html-component img{max-width:100%;height:auto}
.block-html-component table{width:100%;border-collapse:collapse}
.block-html-component th,.block-html-component td{padding:.65rem;border:1px solid var(--line-200,#d8d8d8);text-align:left;vertical-align:top}
.block-html-component pre{max-width:100%;overflow:auto}

/* ── Listing Promo (15 Sep 2026) ────────────────────────────────────────────
   Anatomi `.promo-hero` + `.promo-overlay` milik promo_card layout "banner".
   Satu penyimpangan disengaja: rasio dipindah dari KARTU ke OVERLAY yang
   relatif. `aspect-ratio` pada kotak bertinggi auto melebar bila isinya lebih
   tinggi, sedangkan pada kartu absolut isinya meluber keluar bingkai — dan
   kartu listing membawa baris harga yang tidak dimiliki kartu promo. Foto jadi
   latar absolut di belakang overlay. Breakpoint = 640/1024 yang sudah ada. */
.listing-promo .promo-hero { display: grid; aspect-ratio: auto; }
.listing-promo .promo-hero > img { position: absolute; inset: 0; }
.listing-promo .promo-overlay { position: relative; inset: auto; aspect-ratio: 1.72 / 1; }
.listing-promo .promo-overlay > p { margin-bottom: var(--space-3); }
.listing-promo-price { color: var(--text-on-dark); font-size: var(--text-sm); font-weight: var(--fw-bold); }
.listing-promo .promo-overlay button { padding-top: var(--space-4); }
.listing-promo-grid { padding-inline: var(--pad); display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
.listing-promo-grid .promo-hero { flex: none; }
@media (min-width: 640px) {
  .listing-promo-grid:not([data-cols="1"]) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .listing-promo-grid:not([data-cols="1"]) .promo-overlay { aspect-ratio: 4 / 3; }
  .listing-promo-grid:not([data-cols="1"]) .promo-overlay h2 { font-size: var(--text-h2); }
}
@media (min-width: 1024px) {
  .listing-promo[data-layout="carousel"] .promo-overlay,
  .listing-promo-grid[data-cols="1"] .promo-overlay { aspect-ratio: 2.3 / 1; }
  .listing-promo-grid[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .listing-promo-grid:not([data-cols="1"]) .promo-overlay { padding: var(--space-6); }
}
/* `.promo-overlay` adalah flex kolom ber-align flex-start: anaknya selebar
   isinya, jadi judul listing panjang ("Paket Foto Keluarga Studio Limbo")
   meluber keluar sisi kanan kartu. Anak dibatasi lebar overlay dan boleh patah. */
.listing-promo .promo-overlay > * { max-width: 100%; min-width: 0; }
.listing-promo .promo-overlay h2 { overflow-wrap: anywhere; }
/* Akar luberan judul: `.promo-hero` grid tanpa kolom eksplisit membuat trek
   `auto`, dan overlay (min-width:auto) melebarkan trek selebar isi tak-patah
   — 351px di kartu 293px, lalu `overflow:hidden` memotong judulnya. Trek
   minmax(0,1fr) mengunci overlay selebar kartu. */
.listing-promo .promo-hero { grid-template-columns: minmax(0, 1fr); }
.listing-promo .promo-overlay { min-width: 0; }
/* Kartu kustom tanpa tujuan klik: bukan tombol, jadi kursor tidak boleh menjanjikan klik. */
.listing-promo .promo-hero.is-static { cursor: default; }

/* ── Card Grid · tampilan Thumbnail (owner 15 Sep 2026) ─────────────────────
   Ubin 1:1 bertepi + nama opsional, kisi rapat. Kolom di tiga lebar diatur
   renderer lewat --cols-mobile/--cols-tablet/--cols-desktop (sudah dijepit per
   tampilan). Selektor (0,2,0) sengaja menang atas `.explore-grid` di 640/1024
   yang memaksa 2 kolom. Setiap ubin = grid baris auto|auto dengan min-width:0,
   jadi nama panjang patah di kolomnya sendiri dan tinggi baris kisi tetap rata. */
.explore-grid.is-thumb { gap: var(--space-4) var(--space-3); grid-template-columns: repeat(var(--cols-mobile, 3), minmax(0, 1fr)); }
@media (min-width: 640px) {
  .explore-grid.is-thumb { grid-template-columns: repeat(var(--cols-tablet, 3), minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .explore-grid.is-thumb { grid-template-columns: repeat(var(--cols-desktop, 5), minmax(0, 1fr)); }
}
.grid-thumb { min-width: 0; padding: 0; display: grid; grid-template-rows: auto auto; align-content: start; gap: var(--space-2); border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.grid-thumb-tile { position: relative; display: block; aspect-ratio: 1 / 1; overflow: hidden; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--bg-inset); }
.grid-thumb-tile img { width: 100%; height: 100%; display: block; object-fit: cover; transition: transform .35s ease; }
.grid-thumb:hover .grid-thumb-tile img { transform: scale(1.035); }
.grid-thumb:focus-visible { outline: 0; }
.grid-thumb:focus-visible .grid-thumb-tile { box-shadow: var(--shadow-focus); }
.grid-thumb-initial { position: absolute; inset: 0; display: grid; place-items: center; color: var(--text-subtle); font-size: var(--text-h3); font-weight: var(--fw-bold); }
.grid-thumb-name { min-width: 0; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-size: var(--text-2xs); font-weight: var(--fw-semibold); line-height: var(--lh-snug); overflow-wrap: anywhere; }
@media (prefers-reduced-motion: reduce) { .grid-thumb-tile img { transition: none; } }

/* ── Card Grid · batas baris (owner 15 Sep 2026) ────────────────────────────
   Rel halaman: satu halaman = satu kisi `.explore-grid` selebar seksi, ber-snap.
   Kolom halaman diatur kisi itu sendiri (--cols-*), rel hanya menjejerkannya.
   `align-items: start` — halaman terakhir yang lebih pendek tidak meregang.
   Tanpa overscroll-behavior: rel dengan satu halaman tidak bisa digulir, dan
   `contain` pada wadah tak-tergulir mematikan roda vertikal halaman. */
.cg-pager { display: grid; grid-auto-flow: column; grid-auto-columns: 100%; gap: var(--space-4); align-items: start; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.cg-pager::-webkit-scrollbar { display: none; }
.cg-pager > .cg-page { min-width: 0; scroll-snap-align: start; }
.cg-pager + .slider-dots { margin-top: var(--space-4); }

/* ── Detail listing · deskripsi dilipat & opsi 2 kolom (owner 15 Sep 2026) ───
   Lipatan ±4 baris teks deskripsi (11px × lh 1.62 ≈ 18px/baris), memudar di
   bawah lewat mask supaya terbaca "masih ada lanjutannya". Tombol toggle =
   tautan teks berikon, sejajar tipografi deskripsi. */
.listing-description.is-clamped { max-height: calc(1em * var(--lh-relaxed, 1.62) * 4); overflow: hidden; -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent); mask-image: linear-gradient(to bottom, #000 55%, transparent); }
/* Saat terlipat, margin bawah deskripsi (18px) dibuang dan tombol ditarik
   rapat ke teks: kalau tidak, pudaran mask + margin + margin tombol terbaca
   sebagai celah kosong besar yang memisahkan tombol dari teks yang dibukanya. */
.listing-description.is-clamped { margin-bottom: 0; }
.listing-description.is-clamped + .listing-desc-toggle { margin-top: var(--space-1); }
.listing-desc-toggle { margin: 0 0 var(--space-5); padding: var(--space-2) 0; display: inline-flex; align-items: center; gap: var(--space-2); border: 0; background: none; color: var(--text-strong); font: inherit; font-size: var(--text-2xs); font-weight: var(--fw-semibold); cursor: pointer; }
.listing-desc-toggle svg { width: 14px; height: 14px; }
.listing-desc-toggle:focus-visible { outline: 0; border-radius: var(--radius-xs); box-shadow: var(--shadow-focus); }
/* 2 kolom: isi kartu BERTUMPUK. Kolom 1 = penanda radio, kolom 2 = nama,
   keterangan, harga — tiap bagian di barisnya sendiri, jadi nama panjang tidak
   pernah menindih harga. `align-items: stretch` + tinggi 100% meratakan tinggi
   kartu sebaris; harga didorong ke dasar kartu. */
.option-list.is-grid2 { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
.option-list.is-grid2 .listing-option { height: 100%; grid-template-columns: 18px minmax(0, 1fr); grid-template-rows: auto 1fr auto; align-items: start; row-gap: var(--space-2); }
.option-list.is-grid2 .listing-option i { grid-row: 1; margin-top: 1px; }
.option-list.is-grid2 .listing-option span { grid-column: 2; grid-row: 1 / span 2; min-width: 0; overflow-wrap: anywhere; }
.option-list.is-grid2 .listing-option b { grid-column: 2; grid-row: 3; }
.option-pager + .slider-dots { margin-top: var(--space-3); }

/* ── Open Slot Session (PRD 12.78, 15 Sep 2026) ──────────────────────────────
   Satu anatomi kartu untuk field `open_slot_picker` DAN blok `open_slot_list`
   (openslots.js renderOpenSlotBoard), plus editor partner `/os/<token>`
   (partner.js). Hanya token; kartu meminjam skala `.schedule-room`. */
.os-host { display: grid; gap: var(--space-3); }
.os-empty { padding: var(--space-4); border: 1px dashed var(--border-default); border-radius: var(--radius-md); color: var(--text-subtle); font-size: var(--text-xs); text-align: center; }
.os-note { color: var(--text-subtle); font-size: var(--text-2xs); }
.os-board { display: grid; gap: var(--space-4); }
.os-group { display: grid; gap: var(--space-2); min-width: 0; }
.os-group-head { display: grid; gap: 2px; }
.os-group-head strong { font-size: var(--text-xs); color: var(--text-strong); }
.os-group-head small { font-size: var(--text-2xs); color: var(--text-subtle); }
.os-rail { display: grid; gap: var(--space-2); }
.os-board.is-rail .os-rail { grid-auto-flow: column; grid-auto-columns: minmax(220px, 78%); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: var(--space-1); scrollbar-width: none; }
.os-board.is-rail .os-rail::-webkit-scrollbar { display: none; }
.os-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; padding: var(--space-3) var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); scroll-snap-align: start; min-width: 0; }
.os-card.is-on { border-color: var(--ink); box-shadow: 0 0 0 1px var(--ink) inset; }
.os-card.is-full { background: var(--bg-inset); color: var(--text-disabled); }
.os-card.is-failed { border-color: var(--tag-danger-border); background: var(--tag-danger-bg); }
.os-card-copy { display: grid; gap: 2px; min-width: 0; }
.os-card-copy strong { font-size: var(--text-sm); color: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.os-card-time { font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-subtle); }
.os-card-room, .os-card-note { font-size: var(--text-2xs); color: var(--text-subtle); }
.os-card.is-full .os-card-time, .os-card.is-full .os-card-room { color: var(--text-disabled); }
.os-card-foot { display: grid; gap: var(--space-1); justify-items: end; flex: none; }
.os-card-foot b { font-size: var(--text-2xs); color: var(--text-subtle); font-weight: var(--fw-semibold); }
.os-card-foot b.is-closed { color: var(--text-disabled); }
.os-card-cta { min-height: var(--field-h-sm); padding: 0 var(--space-4); border: 1px solid var(--ink); border-radius: var(--radius-pill); background: var(--ink); color: var(--text-on-dark); font: inherit; font-size: var(--text-xs); font-weight: var(--fw-semibold); cursor: pointer; }
.os-card-cta.is-on { background: var(--yellow); border-color: var(--yellow); color: var(--ink); }
.os-card-cta:disabled { border-color: var(--border-default); background: var(--bg-inset); color: var(--text-disabled); cursor: not-allowed; }
.os-card-cta:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.vsform-openslot { display: grid; gap: var(--space-3); }

/* Editor partner */
.os-editor { display: grid; gap: var(--space-6); }
.os-editor.is-busy { opacity: .7; pointer-events: none; }
.os-head { display: grid; gap: var(--space-1); }
.os-head > span { font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-subtle); }
.os-code { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-subtle); }
.os-notice { padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); border: 1px solid var(--tag-neutral-border); background: var(--tag-neutral-bg); color: var(--tag-neutral-text); font-size: var(--text-xs); }
.os-notice.is-success { border-color: var(--tag-success-border); background: var(--tag-success-bg); color: var(--tag-success-text); }
.os-notice.is-warning { border-color: var(--tag-warning-border); background: var(--tag-warning-bg); color: var(--tag-warning-text); }
.os-notice.is-danger { border-color: var(--tag-danger-border); background: var(--tag-danger-bg); color: var(--tag-danger-text); }
.os-section { display: grid; gap: var(--space-4); padding: var(--space-5); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-0); }
.os-section-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; }
.os-section-head h2 { margin: 0; font-size: var(--text-h4); }
.os-section-head small { color: var(--text-subtle); font-size: var(--text-2xs); }
.os-lead { margin: 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
.os-chip { cursor: default; }
.os-form { display: grid; gap: var(--space-4); }
.os-row { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.os-readonly { margin: 0; padding: var(--space-3); border-radius: var(--radius-sm); background: var(--bg-inset); color: var(--text-muted); font-size: var(--text-sm); }
.os-banner { display: grid; gap: var(--space-3); padding: var(--space-4); border-radius: var(--radius-md); border: 1px solid var(--tag-warning-border); background: var(--tag-warning-bg); color: var(--tag-warning-text); }
.os-banner p { margin: 0; font-size: var(--text-xs); }
.os-requests { display: grid; gap: var(--space-2); }
.os-request { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2) var(--space-3); align-items: center; padding: var(--space-3) 0; border-top: 1px solid var(--border-faint); }
.os-request-copy { display: grid; gap: 2px; min-width: 0; }
.os-request-copy strong { font-size: var(--text-sm); }
.os-request-copy small { color: var(--text-subtle); font-size: var(--text-2xs); }
.os-request-copy a { color: inherit; }
.os-request-actions { grid-column: 1 / -1; display: flex; gap: var(--space-2); flex-wrap: wrap; }
.os-mini { min-height: var(--field-h-sm); padding: 0 var(--space-4); border: 1px solid var(--ink); border-radius: var(--radius-pill); background: var(--ink); color: var(--text-on-dark); font: inherit; font-size: var(--text-xs); font-weight: var(--fw-semibold); cursor: pointer; }
.os-mini.is-ghost { background: var(--surface-0); color: var(--text-body); border-color: var(--border-default); }
.os-mini:disabled { opacity: .5; cursor: not-allowed; }
.os-mini:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.os-link { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2); align-items: center; }
.os-link a { font-size: var(--text-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-body); }
.os-blocks { display: grid; gap: var(--space-2); }
.os-block { border: 1px solid var(--border-default); border-radius: var(--radius-md); overflow: hidden; }
.os-block-bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2); align-items: center; padding: var(--space-2) var(--space-3); background: var(--bg-subtle); }
.os-block-bar strong { font-size: var(--text-xs); }
.os-block-tools { display: flex; gap: var(--space-1); flex-wrap: wrap; justify-content: flex-end; }
.os-block-tools .os-mini { padding: 0 var(--space-3); }
.os-block-editor { display: grid; gap: var(--space-3); padding: var(--space-4); }
.os-schema-head { font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-subtle); }
.os-toggle { grid-template-columns: auto minmax(0, 1fr); align-items: center; }
.os-toggle input { width: 18px; height: 18px; accent-color: var(--ink); }
.os-add select { width: 100%; min-height: var(--field-h); border: 1px dashed var(--border-default); border-radius: var(--radius-sm); background: var(--surface-0); font: inherit; padding: 0 var(--space-3); }
.os-preview { display: grid; gap: var(--space-3); padding-top: var(--space-4); border-top: 1px solid var(--border-faint); }
.os-preview-label { font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; color: var(--text-subtle); }

/* ── Profile Cards (profile_cards, 17 Sep 2026) ───────────────────────────────
   Anatomi unit DS GUI/20 "Staff card": avatar lingkaran · nama display tebal ·
   keterangan muted · lencana di nama. Ukuran avatar sm/md/lg = 56/72/96 (skala
   GUI/20/AVATAR-SCALE). Warna lencana lewat --pf-verified; bawaan biru sistem. */
.profile-cards { --pf-avatar: 72px; --pf-verified: var(--accent-blue); }
.profile-cards[data-size="sm"] { --pf-avatar: 56px; }
.profile-cards[data-size="lg"] { --pf-avatar: 96px; }
.profile-cards .section-heading > div > span { display: block; margin-bottom: 4px; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .16em; }
.profile-card { min-width: 0; margin: 0; padding: 0; display: grid; justify-items: center; gap: var(--space-3); border: 0; background: none; color: inherit; font: inherit; text-align: center; }
button.profile-card { cursor: pointer; }
.profile-card:focus-visible { outline: 0; }
.profile-card:focus-visible .profile-avatar { box-shadow: var(--shadow-focus); }
.profile-avatar { position: relative; width: var(--pf-avatar); height: var(--pf-avatar); flex: none; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--border-default); border-radius: var(--radius-pill); background: var(--bg-inset); }
.profile-avatar img { width: 100%; height: 100%; display: block; object-fit: cover; transition: transform .35s ease; }
button.profile-card:hover .profile-avatar img { transform: scale(1.05); }
.profile-initial { color: var(--text-subtle); font-size: calc(var(--pf-avatar) * .38); font-weight: var(--fw-bold); }
.profile-copy { min-width: 0; display: grid; gap: 2px; }
.profile-name { min-width: 0; display: inline-flex; align-items: center; justify-content: center; gap: 4px; font-size: var(--text-xs); font-weight: var(--fw-bold); line-height: 1.2; }
.profile-name > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.profile-verified { flex: none; display: inline-grid; place-items: center; color: var(--pf-verified); }
.profile-verified svg { width: 15px; height: 15px; }
.profile-caption { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--text-muted); font-size: var(--text-2xs); line-height: 1.35; }
/* Kisi: kolom dari setelan; ≥640px memakai kolom desktop. */
.profile-grid { padding-inline: var(--pad); display: grid; grid-template-columns: repeat(var(--cols-mobile, 2), minmax(0, 1fr)); gap: var(--space-5) var(--space-3); }
@media (min-width: 640px) { .profile-grid { grid-template-columns: repeat(var(--cols-desktop, 4), minmax(0, 1fr)); } }
/* Carousel: rel snap yang sama dengan promo_slider; kartu selebar avatar + napas. */
.profile-carousel { display: flex; gap: var(--space-4); overflow-x: auto; padding: 0 var(--pad) 4px; scroll-padding-inline: var(--pad); scroll-snap-type: x mandatory; scrollbar-width: none; }
.profile-carousel::-webkit-scrollbar { display: none; }
.profile-carousel .profile-card { flex: 0 0 calc(var(--pf-avatar) + 56px); scroll-snap-align: start; }
/* Daftar: satu baris per profil, avatar kiri, teks kiri, panah kanan bila bisa diklik. */
.profile-list { padding-inline: var(--pad); display: grid; }
.profile-list .profile-card { grid-template-columns: auto minmax(0, 1fr) auto; justify-items: start; align-items: center; gap: var(--space-4); padding: var(--space-3) 0; border-bottom: 1px solid var(--border-default); text-align: left; }
.profile-list .profile-card:last-child { border-bottom: 0; }
.profile-list .profile-name { justify-content: flex-start; }
.profile-list .profile-card > svg { width: 16px; height: 16px; color: var(--text-subtle); }
/* Banner: kartu lebar digeser, avatar besar di kiri, di atas kartu bergaris. */
.profile-banner { display: flex; gap: var(--space-3); overflow-x: auto; padding: 0 var(--pad) 4px; scroll-padding-inline: var(--pad); scroll-snap-type: x mandatory; scrollbar-width: none; }
.profile-banner::-webkit-scrollbar { display: none; }
.profile-banner .profile-card { flex: 0 0 82%; scroll-snap-align: start; grid-template-columns: auto minmax(0, 1fr); justify-items: start; align-items: center; gap: var(--space-4); padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--vs-white); text-align: left; }
.profile-banner .profile-card:only-child { flex-basis: 100%; }
.profile-banner .profile-name { justify-content: flex-start; font-size: var(--text-sm); }
.profile-banner .profile-name > span:first-child { white-space: normal; }
@media (min-width: 640px) { .profile-banner .profile-card { flex-basis: 48%; } }
/* Kartu profil berfoto besar (cardStyle, owner 17 Okt 2026) — dirakit dari Product
   Card inset (.pc-card) dan Event Card overlay (.ev-card), tanpa unit library atas
   keputusan owner. --pf-base = warna kartu; --pf-ink = putih atau ink-900 dari JS. */
.profile-photo {
  --pf-base: var(--ink-900); --pf-ink: var(--text-on-dark);
  --pf-ink-soft: color-mix(in srgb, var(--pf-ink) 74%, var(--pf-base));
  position: relative; min-width: 0; display: flex; flex-direction: column; margin: 0; padding: 0; overflow: hidden;
  border: 0; border-radius: var(--radius-xl); background: var(--pf-base); color: var(--pf-ink);
  font: inherit; text-align: left; box-shadow: var(--shadow-card); container-type: inline-size;
}
.profile-photo[data-ink="dark"] { --pf-ink: var(--ink-900); }
button.profile-photo { cursor: pointer; }
.profile-photo:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.profile-photo-media { position: relative; display: grid; place-items: center; overflow: hidden; background: var(--ink-050); }
.profile-photo-media img { width: 100%; height: 100%; display: block; object-fit: cover; transition: transform .35s ease; }
button.profile-photo:hover .profile-photo-media img { transform: scale(1.04); }
.profile-photo .profile-initial { font-size: var(--text-h2); }
.profile-photo-head { min-width: 0; display: grid; gap: var(--space-2); }
.profile-photo .profile-name { justify-content: flex-start; font-family: var(--font-display); font-size: var(--text-h4); line-height: var(--lh-snug); letter-spacing: var(--ls-display); }
.profile-photo .profile-name > span:first-child { white-space: normal; }
.profile-photo .profile-verified svg { width: 18px; height: 18px; }
.profile-photo .profile-caption { -webkit-line-clamp: 3; color: var(--pf-ink-soft); font-size: var(--text-xs); line-height: var(--lh-normal); }
.profile-photo-panel { position: relative; flex: 1; display: grid; align-content: space-between; gap: var(--space-5); padding: var(--space-6) var(--space-5) var(--space-5); }
.profile-photo-cta {
  justify-self: start; min-height: 44px; display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 0 var(--space-5); border-radius: var(--radius-pill); background: var(--pf-ink); color: var(--pf-base);
  font-size: var(--text-xs); font-weight: var(--fw-semibold); box-shadow: var(--shadow-card);
}
.profile-photo-cta svg { width: 16px; height: 16px; flex: none; }
.profile-photo-cta { white-space: nowrap; }
/* Kartu sempit (2 kolom di HP): tombol selebar panel, panah dilepas supaya label tidak patah. */
@container (max-width: 220px) {
  .profile-photo-cta { justify-self: stretch; justify-content: center; padding: 0 var(--space-3); }
  .profile-photo-cta svg { display: none; }
}
/* Foto + panel: foto membulat di dalam kartu, nama & tombol di panel warna kartu. */
.profile-photo[data-style="inset"] { padding: var(--space-3); }
.profile-photo[data-style="inset"] .profile-photo-media { aspect-ratio: 1 / 1; border-radius: var(--radius-lg); }
.profile-photo[data-style="inset"] .profile-photo-panel { padding: var(--space-5) var(--space-3) var(--space-2); }
/* Foto pudar & nama di atas: foto penuh, pudar + blur ke warna kartu di bawah. */
.profile-photo[data-style="fade"], .profile-photo[data-style="hero"] { aspect-ratio: 3 / 4; justify-content: flex-end; }
.profile-photo[data-style="fade"] .profile-photo-media, .profile-photo[data-style="hero"] .profile-photo-media { position: absolute; inset: 0; }
/* Panel tidak meregang di kartu foto penuh: ia duduk di bawah, di atas pudar warna. */
.profile-photo[data-style="fade"] .profile-photo-panel { flex: none; }
.profile-photo-shade { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(180deg, transparent 40%, color-mix(in srgb, var(--pf-base) 72%, transparent) 62%, var(--pf-base) 86%); }
.profile-photo-shade::before { content: ''; position: absolute; inset: 48% 0 0; -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); -webkit-mask-image: linear-gradient(180deg, transparent, var(--ink-900) 60%); mask-image: linear-gradient(180deg, transparent, var(--ink-900) 60%); }
.profile-photo[data-style="hero"] .profile-photo-shade { background: linear-gradient(180deg, color-mix(in srgb, var(--pf-base) 82%, transparent) 0%, color-mix(in srgb, var(--pf-base) 48%, transparent) 20%, transparent 40%, transparent 58%, color-mix(in srgb, var(--pf-base) 72%, transparent) 76%, var(--pf-base) 94%); }
/* Nama di atas duduk di foto, bukan di warna padat: bayangan tipis warna kartu menjaga
   keterbacaan di foto yang terang dan ramai. */
.profile-photo[data-style="hero"] .profile-photo-head { text-shadow: 0 1px 10px color-mix(in srgb, var(--pf-base) 70%, transparent); }
.profile-photo[data-style="hero"] .profile-photo-shade::before { inset: 66% 0 0; }
.profile-photo[data-style="hero"] .profile-photo-head { position: absolute; top: 0; left: 0; right: 0; justify-items: center; padding: var(--space-7) var(--space-5) 0; text-align: center; }
.profile-photo[data-style="hero"] .profile-name { justify-content: center; font-size: var(--text-h3); }
.profile-photo-foot { position: relative; display: flex; justify-content: center; padding: var(--space-5); }
.profile-cards[data-style] .profile-grid { gap: var(--space-4); }
.profile-cards[data-style] .profile-carousel .profile-photo { flex: 0 0 min(78%, 300px); scroll-snap-align: start; }
@media (prefers-reduced-motion: reduce) { .profile-avatar img { transition: none; } }

/* ══ Tipe Page — kepala profile / link-in-bio / landing (16 Okt 2026) ═════════
   Anatomi master 12.04.02 "Storefront & Public.dc.html": 08 Profile (A Creator ·
   B Team member), 09 Link-in-bio (A Minimal · C Editorial cover), 02 Hero (C
   Full-bleed · B Editorial split). Latar cover/hero ditulis INLINE oleh
   formHeaderStyle() (mesin header berbranding yang sama); aturan di bawah hanya
   tata letak. Ukuran px yang tidak punya token (avatar 74/78/64, cover) disalin
   dari unitnya, sama seperti skala avatar Profile Cards.                       */
.page-typed { display: block; }
body.is-searching .gateway-stream > .page-typed { display: none; }

.page-identity { position: relative; color: var(--text-strong); }
.page-identity .pid-name { min-width: 0; display: flex; align-items: center; gap: var(--space-2); }
.page-identity .pid-name h1 { margin: 0; min-width: 0; font-family: var(--font-display); font-size: clamp(22px, 5.6vw, var(--text-h2)); font-weight: var(--fw-bold); line-height: 1.1; letter-spacing: -.02em; overflow-wrap: anywhere; }
.pid-verified { flex: none; display: inline-grid; place-items: center; color: var(--accent-blue); }
.pid-verified svg { width: 20px; height: 20px; }
.pid-kicker { display: block; color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .06em; text-transform: uppercase; }
.pid-bio { color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-normal); }
.pid-actions { display: flex; gap: var(--space-3); }
.pid-actions .pl-cta.is-block { flex: 1 1 0; min-width: 0; }

/* Statistik — 08 A: baris rata dengan garis atas-bawah, angka display + label mono. */
.pid-stats { margin: 0; padding: var(--space-4) var(--space-2); display: flex; justify-content: space-between; gap: var(--space-4); border-top: 1px solid var(--line-100); border-bottom: 1px solid var(--line-100); }
.pid-stats > div { min-width: 0; display: flex; flex-direction: column-reverse; align-items: center; text-align: center; }
.pid-stats dd { margin: 0; font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-bold); color: var(--text-strong); }
.pid-stats dt { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .06em; text-transform: uppercase; }

/* Avatar — lingkaran (08) atau bersudut (09 C). Cincin putih memisahkannya dari cover. */
.pid-avatar { --pid-avatar: 74px; position: relative; width: var(--pid-avatar); height: var(--pid-avatar); flex: none; display: grid; place-items: center; overflow: hidden; border: 3px solid var(--surface-0); border-radius: var(--radius-pill); background: var(--bg-inset); }
.pid-avatar[data-shape="rounded"] { border-radius: var(--radius-md); }
.pid-avatar img { width: 100%; height: 100%; display: block; object-fit: cover; }
.pid-initial { color: var(--text-subtle); font-family: var(--font-display); font-size: calc(var(--pid-avatar) * .38); font-weight: var(--fw-bold); }

/* Tinggi cover mengikuti lebar (≈3:1 seperti unit 288×96) dengan batas atas; bukan
   aspect-ratio + max-height, yang menyusutkan LEBAR cover begitu batasnya kena. */
.pid-cover { position: relative; overflow: hidden; height: clamp(96px, 33vw, 260px); background-color: var(--bg-inset); }
.pid-body { position: relative; padding: 0 var(--pad) var(--space-6); display: flex; flex-direction: column; gap: var(--space-4); }
.pid-id { min-width: 0; display: grid; gap: var(--space-1); }

/* 08 A · Creator — avatar menumpang setengah di cover, teks rata kiri. */
.page-identity[data-type="profile"][data-layout="creator"] .pid-body { margin-top: calc(var(--pid-avatar-pull, 37px) * -1); }
/* 08 B · Team member — tanpa cover, semuanya di tengah. */
.page-identity[data-layout="member"] .pid-body { padding-top: var(--space-8); align-items: center; text-align: center; }
.page-identity[data-layout="member"] .pid-avatar { --pid-avatar: 78px; border: 0; }
.page-identity[data-layout="member"] .pid-name { justify-content: center; }
.page-identity[data-layout="member"] .pid-actions { width: 100%; }
.pid-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); }
.pid-chip.pl-tag { border-radius: var(--radius-xs); }

/* 09 A · Minimal — kolom tengah, nama lebih kecil, keterangan berhuruf badan. */
.page-identity[data-type="linkinbio"][data-layout="minimal"] .pid-body { padding-top: var(--space-9); align-items: center; text-align: center; }
.page-identity[data-type="linkinbio"][data-layout="minimal"] .pid-avatar { --pid-avatar: 72px; border: 0; }
.page-identity[data-type="linkinbio"] .pid-name h1 { font-size: clamp(18px, 4.8vw, var(--text-h3)); }
.page-identity[data-type="linkinbio"][data-layout="minimal"] .pid-name { justify-content: center; }
.page-identity[data-type="linkinbio"] .pid-kicker { font-family: var(--font-body); font-size: var(--text-xs); letter-spacing: 0; text-transform: none; color: var(--text-muted); }
.page-identity[data-type="linkinbio"] .pid-actions { width: 100%; }
/* 09 C · Editorial cover — cover lebih tinggi, avatar bersudut di kiri + nama di sampingnya. */
.page-identity[data-layout="cover"] .pid-cover { height: clamp(120px, 45vw, 280px); }
.page-identity[data-layout="cover"] .pid-body { margin-top: -32px; }
.page-identity[data-layout="cover"] .pid-avatar { --pid-avatar: 64px; }
.pid-row { display: flex; align-items: flex-end; gap: var(--space-4); }
.pid-row .pid-id { padding-bottom: var(--space-2); }
/* Nama tidak boleh naik ke atas cover: puncaknya dijaga sejauh tumpangan avatar.
   Bila nama + subjudul lebih tinggi dari sisa avatar, avatar yang turun sedikit. */
.page-identity[data-layout="cover"] .pid-row .pid-id { margin-top: 32px; }

/* Link-in-bio: kolom sempit selebar kartu tunggal (.pl-single 560px), seksi rapat tanpa garis. */
.page-typed[data-page-type="linkinbio"] { max-width: 560px; margin-inline: auto; }
.page-typed[data-page-type="linkinbio"] > .section { padding-top: var(--space-4); padding-bottom: var(--space-4); border-top: 0; }

/* 02 C · Full-bleed — latar penuh, gradasi gelap ke bawah, teks terang di kaki. */
.page-identity[data-type="landing"][data-layout="overlay"] { min-height: 440px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; background: var(--bg-ink); color: var(--text-on-dark); }
.page-identity[data-layout="overlay"] .pid-backdrop { position: absolute; inset: 0; }
.page-identity[data-layout="overlay"]::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, color-mix(in srgb, var(--vs-black) 25%, transparent) 0%, color-mix(in srgb, var(--vs-black) 72%, transparent) 100%); pointer-events: none; }
.page-identity[data-layout="overlay"] .pid-copy { position: relative; z-index: 1; padding: var(--space-10) var(--pad); display: flex; flex-direction: column; gap: var(--space-4); }
.page-identity[data-layout="overlay"] .pid-kicker { color: color-mix(in srgb, var(--vs-white) 75%, transparent); letter-spacing: .16em; }
.page-identity[data-type="landing"] .pid-name h1 { max-width: 560px; font-size: clamp(34px, 8vw, var(--text-display-lg)); line-height: 1; }
.page-identity[data-layout="overlay"] .pid-name h1 { color: var(--vs-white); }
.page-identity[data-layout="overlay"] .pid-bio, .page-identity[data-layout="overlay"] .pid-bio * { color: var(--text-on-dark-muted); }
.page-identity[data-layout="overlay"] .pl-cta.is-outline { border-color: color-mix(in srgb, var(--vs-white) 35%, transparent); background: color-mix(in srgb, var(--vs-white) 12%, transparent); color: var(--vs-white); }
.page-identity[data-type="landing"] .pid-actions { margin-top: var(--space-3); flex-wrap: wrap; }
.page-identity[data-type="landing"] .pid-stats { margin-top: var(--space-5); padding: 0; justify-content: flex-start; gap: var(--space-8); border: 0; }
.page-identity[data-type="landing"] .pid-stats > div { align-items: flex-start; text-align: left; }
.page-identity[data-type="landing"] .pid-stats dd { font-size: 26px; letter-spacing: -.01em; }
.page-identity[data-layout="overlay"] .pid-stats dd { color: var(--vs-white); }
.page-identity[data-layout="overlay"] .pid-stats dt { color: color-mix(in srgb, var(--vs-white) 60%, transparent); letter-spacing: .1em; }

/* 02 B · Editorial split — kolom teks + kolom foto; di ponsel foto turun ke bawah. */
.page-identity[data-type="landing"][data-layout="split"] { display: grid; grid-template-columns: minmax(0, 1fr); background: var(--surface-0); border-bottom: 1px solid var(--line-200); }
.page-identity[data-layout="split"] .pid-copy { padding: var(--space-10) var(--pad); display: flex; flex-direction: column; justify-content: center; gap: var(--space-4); }
.page-identity[data-layout="split"] .pid-kicker { color: var(--vs-yellow-shadow); letter-spacing: .16em; }
.page-identity[data-layout="split"] .pid-name h1 { font-size: clamp(30px, 7vw, var(--text-display)); line-height: 1.04; }
.page-identity[data-layout="split"] .pid-bio { max-width: 360px; font-size: var(--text-md); line-height: 1.6; }
.page-identity[data-layout="split"] .pid-media { position: relative; overflow: hidden; min-height: 280px; background-color: var(--bg-inset); }

@media (min-width: 640px) {
  .pid-avatar { --pid-avatar: 96px; }
  .page-identity[data-type="profile"][data-layout="creator"] .pid-body { --pid-avatar-pull: 48px; }
  .page-identity[data-layout="cover"] .pid-avatar { --pid-avatar: 80px; }
  .page-identity[data-layout="cover"] .pid-body { margin-top: -40px; }
  .page-identity[data-layout="cover"] .pid-row .pid-id { margin-top: 40px; }
  .page-identity[data-type="profile"] .pid-actions { max-width: 420px; }
  .page-identity[data-layout="member"] .pid-actions { max-width: 420px; }
  .page-identity[data-type="landing"][data-layout="split"] { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); min-height: 420px; }
  .page-identity[data-layout="split"].is-single { grid-template-columns: minmax(0, 1fr); }
  .page-identity[data-layout="split"] .pid-media { min-height: 420px; border-left: 1px solid var(--line-100); }
}

/* ══ Blok halaman personal & dekoratif (16 Okt 2026) ═════════════════════════
   Master 12.04.02 "Storefront & Public.dc.html": 09 Link-in-bio (CTA Cards,
   ikon sosial), 08 Profile A (Service List, Stats Band Garis), 03 Marketing
   (Stats Band Ink, Feature List, Testimonials Kutipan, Logo Strip Kartu),
   11 Social proof (Testimonials Kartu, Logo Strip Sebaris).                   */

/* Social Links · Ikon — 09 A: lingkaran 36px bergaris, simbol Material text-strong. */
.social-links[data-align="start"] .social-list, .social-links[data-align="start"] .social-icons { justify-content: flex-start; }
.social-icons { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4); }
.social-icons a { width: 36px; height: 36px; display: inline-grid; place-items: center; border: 1px solid var(--line-200); border-radius: var(--radius-pill); color: var(--text-strong); transition: border-color .2s, background-color .2s; }
.social-icons a:hover { border-color: var(--border-strong); background: var(--bg-subtle); }
.social-icons a:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.social-icons svg { width: 16px; height: 16px; }

/* CTA Cards — 09 A/B/C. */
.cta-wrap { display: grid; gap: var(--space-3); }
.cta-card { min-width: 0; margin: 0; display: flex; align-items: center; gap: var(--space-4); padding: 13px 15px; border: 1px solid var(--line-200); border-radius: 12px; background: var(--surface-0); color: var(--text-strong); font: inherit; text-align: left; cursor: pointer; transition: transform .2s, border-color .2s, box-shadow .2s; }
.cta-card:hover { border-color: var(--border-strong); }
.cta-card:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.cta-card svg { width: 16px; height: 16px; flex: none; }
.cta-icon { flex: none; display: inline-grid; place-items: center; }
.cta-label { min-width: 0; flex: 1; display: grid; gap: var(--space-1); }
.cta-label strong { font-size: var(--text-sm); font-weight: var(--fw-semibold); line-height: 1.25; }
.cta-label small { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); }
.cta-go { flex: none; display: inline-grid; place-items: center; margin-left: auto; color: var(--text-subtle); }
.cta-card[data-tone="ink"] { border-color: var(--ink-900); background: var(--ink-900); color: var(--vs-white); }
.cta-card[data-tone="ink"] .cta-icon { color: var(--vs-yellow); }
.cta-card[data-tone="ink"] .cta-go, .cta-card[data-tone="ink"] .cta-label small { color: var(--text-on-dark-muted); }
.cta-card[data-tone="brand"] { border-color: transparent; background: var(--brand-gradient); color: var(--vs-white); }
.cta-card[data-tone="brand"] .cta-go, .cta-card[data-tone="brand"] .cta-label small { color: var(--text-on-dark-muted); }
.cta-code { flex: none; padding: 3px 7px; border-radius: 5px; background: var(--vs-yellow); color: var(--vs-black); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-bold); letter-spacing: .06em; }
/* 09 B · Kisi — kotak ikon di atas label; strip brand selebar penuh. */
.cta-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.cta-grid .cta-card { flex-direction: column; align-items: flex-start; gap: var(--space-3); padding: 15px 12px; }
.cta-grid .cta-card svg { width: 19px; height: 19px; }
.cta-grid .cta-label strong { font-size: var(--text-xs); }
.cta-grid .cta-card.is-strip { grid-column: 1 / -1; flex-direction: row; align-items: center; padding: 14px 15px; }
.cta-grid .cta-card.is-strip svg { width: 15px; height: 15px; }
/* 09 C · Baris — thumbnail 38px kiri, padding rapat; kartu Unggulan = foto 96px + kaki. */
.cta-rows .cta-card { padding: var(--space-3); border-radius: var(--radius-sm); }
.cta-thumb { width: 38px; height: 38px; flex: none; display: grid; place-items: center; overflow: hidden; border-radius: 8px; background: var(--ink-050); color: var(--ink-300); }
.cta-thumb img { width: 100%; height: 100%; object-fit: cover; }
.cta-thumb svg { width: 15px; height: 15px; }
.cta-rows .cta-card.is-featured { padding: 0; flex-direction: column; align-items: stretch; gap: 0; overflow: hidden; border-radius: 12px; }
.cta-photo { display: block; height: clamp(96px, 28vw, 200px); overflow: hidden; background: var(--bg-inset); }
.cta-photo img { width: 100%; height: 100%; display: block; object-fit: cover; }
.cta-foot { padding: 12px 13px; display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.cta-foot .cta-label strong { font-weight: var(--fw-bold); }
.cta-chip { flex: none; padding: 7px 11px; border-radius: 8px; background: var(--ink-900); color: var(--vs-white); font-size: var(--text-2xs); font-weight: var(--fw-bold); }

/* Service List — 08 A "Layanan". */
.service-kicker { display: block; margin-bottom: var(--space-3); color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .1em; text-transform: uppercase; }
.service-rows { display: grid; }
.service-row { min-width: 0; margin: 0; padding: var(--space-4) 0; display: flex; align-items: center; gap: var(--space-4); border: 0; border-bottom: 1px solid var(--line-100); background: none; color: inherit; font: inherit; text-align: left; }
.service-row:last-child { border-bottom: 0; }
button.service-row { cursor: pointer; }
button.service-row:focus-visible { outline: 0; box-shadow: var(--shadow-focus); border-radius: var(--radius-xs); }
.service-row > svg { width: 16px; height: 16px; flex: none; color: var(--text-subtle); }
.service-tile { width: 34px; height: 34px; flex: none; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--line-200); border-radius: 8px; background: var(--surface-1); color: var(--text-strong); }
.service-tile img { width: 100%; height: 100%; object-fit: cover; }
.service-tile svg { width: 16px; height: 16px; }
.service-copy { min-width: 0; flex: 1; display: grid; gap: var(--space-1); }
.service-copy strong { color: var(--text-strong); font-size: var(--text-xs); font-weight: var(--fw-semibold); }
.service-copy small { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); }
.service-note { margin: var(--space-3) 0 0; color: var(--text-subtle); font-size: var(--text-2xs); }

/* Stats Band — Ink (03) dan Garis (08 A). */
.stats-grid { margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-7); }
.stats-grid > div { min-width: 0; display: flex; flex-direction: column-reverse; gap: var(--space-2); }
.stats-grid dd { margin: 0; font-family: var(--font-display); font-weight: var(--fw-bold); letter-spacing: -.02em; }
.stats-grid dt { font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .1em; text-transform: uppercase; }
.stats-band[data-variant="ink"] .stats-grid { padding: 30px; border-radius: var(--radius-md); background: var(--ink-900); align-content: center; }
.stats-band[data-variant="ink"] dd { color: var(--vs-white); font-size: 38px; line-height: 1; }
.stats-band[data-variant="ink"] .is-accent dd { color: var(--vs-yellow); }
.stats-band[data-variant="ink"] dt { color: color-mix(in srgb, var(--vs-white) 55%, transparent); }
.stats-band[data-variant="line"] .stats-grid { padding: var(--space-4) var(--space-2); display: flex; justify-content: space-between; gap: var(--space-4); border-top: 1px solid var(--line-100); border-bottom: 1px solid var(--line-100); }
.stats-band[data-variant="line"] .stats-grid > div { align-items: center; text-align: center; gap: 0; }
.stats-band[data-variant="line"] dd { color: var(--text-strong); font-size: var(--text-h4); }
.stats-band[data-variant="line"] dt { color: var(--text-subtle); letter-spacing: .06em; }

/* Feature List — Kisi fitur & Langkah (03). */
.feature-list .section-heading > div > span { display: block; margin-bottom: 4px; color: var(--text-subtle); font-size: var(--text-2xs); font-weight: 750; letter-spacing: .16em; }
.feature-grid { padding: 30px; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 26px; border: 1px solid var(--line-200); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); }
.feature-grid .feature-item { display: flex; flex-direction: column; gap: var(--space-4); }
.feature-tile { width: 44px; height: 44px; display: grid; place-items: center; border: 1px solid var(--line-200); border-radius: var(--radius-sm); background: var(--surface-1); color: var(--text-strong); }
.feature-tile svg { width: 20px; height: 20px; }
.feature-copy h4 { margin: 0 0 var(--space-2); color: var(--text-strong); font-family: var(--font-display); font-size: 17px; font-weight: var(--fw-bold); }
.feature-copy p { margin: 0; color: var(--text-muted); font-size: var(--text-xs); line-height: 1.55; }
.feature-grid .feature-copy h4 { margin-top: 6px; }
.feature-steps { padding: 28px; display: flex; flex-direction: column; gap: 22px; border: 1px solid var(--line-200); border-radius: var(--radius-md); background: var(--surface-1); }
.feature-steps .feature-item { display: flex; gap: var(--space-5); }
.feature-num { flex: none; color: var(--vs-yellow-shadow); font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--fw-semibold); }
.feature-steps .feature-copy h4 { margin: 0 0 var(--space-2); font-size: var(--text-md); }
.feature-steps .feature-copy p { font-size: var(--text-xs); line-height: 1.5; }

/* Testimonials — Kutipan (03) & Kartu ulasan (11). */
.testi-card { margin: 0; display: flex; flex-direction: column; border: 1px solid var(--line-200); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); }
.testi-card blockquote { margin: 0; }
.testi-card blockquote p { margin: 0; }
.testi-who { margin-top: auto; display: flex; align-items: center; gap: var(--space-4); }
.testi-who > span:last-child { min-width: 0; display: grid; }
.testi-who strong { color: var(--text-strong); font-size: var(--text-sm); font-weight: var(--fw-semibold); }
.testi-who small { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .06em; text-transform: uppercase; }
.testi-avatar { width: 40px; height: 40px; flex: none; display: grid; place-items: center; overflow: hidden; border-radius: var(--radius-pill); background: var(--ink-900); color: var(--vs-white); font-size: var(--text-xs); font-weight: var(--fw-bold); }
.testi-avatar img { width: 100%; height: 100%; object-fit: cover; }
.testi-quotes { display: grid; gap: var(--space-5); }
.testi-quotes .testi-card { padding: 32px; gap: var(--space-6); }
.testi-mark { display: block; color: var(--vs-yellow); }
.testi-mark svg { width: 30px; height: 30px; }
.testi-quotes blockquote p { color: var(--text-strong); font-family: var(--font-display); font-size: clamp(18px, 4.6vw, var(--text-h3)); font-weight: var(--fw-medium); line-height: 1.4; letter-spacing: -.01em; }
.testi-cards { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(240px, 82%); gap: var(--space-5); overflow-x: auto; padding-bottom: 4px; scroll-snap-type: x mandatory; scrollbar-width: none; }
.testi-cards::-webkit-scrollbar { display: none; }
.testi-cards .testi-card { padding: var(--space-6); gap: var(--space-4); scroll-snap-align: start; }
.testi-stars { display: flex; gap: 2px; color: var(--vs-yellow); }
.testi-stars svg { width: 18px; height: 18px; }
.testi-cards blockquote p { color: var(--text-body); font-size: var(--text-xs); line-height: 1.6; }
.testi-cards .testi-avatar { width: 32px; height: 32px; font-size: var(--text-2xs); }
.testi-cards .testi-who strong { font-size: var(--text-xs); }
.testi-cards .testi-who small { text-transform: none; letter-spacing: 0; }

/* Logo Strip — Kartu (03) & Sebaris (11). */
.logo-box { display: flex; flex-direction: column; align-items: center; gap: var(--space-5); padding: 26px 30px; border: 1px solid var(--line-200); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); }
.logo-kicker { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .14em; text-transform: uppercase; }
.logo-names { width: 100%; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-5) var(--space-9); }
.logo-names span { color: var(--ink-300); font-family: var(--font-display); font-size: 19px; font-weight: var(--fw-bold); }
.logo-strip[data-variant="inline"] .logo-box { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: var(--space-5) 36px; padding: 22px; background: var(--surface-1); box-shadow: none; }
.logo-strip[data-variant="inline"] .logo-names { width: auto; gap: var(--space-5) 36px; }
.logo-strip[data-variant="inline"] .logo-names span { font-size: 17px; }

@media (min-width: 640px) {
  .stats-band[data-variant="ink"] .stats-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
  .testi-cards { grid-auto-flow: row; grid-auto-columns: auto; grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); overflow: visible; }
}
@media (prefers-reduced-motion: reduce) { .cta-card, .social-icons a { transition: none; } }

/* ══ Induk & sub page (16 Okt 2026) ══════════════════════════════════════════
   Breadcrumb = GUI/08 (13px, induk muted, pemisah chevron, page ini kuat).
   Blok Sub Pages memakai anatomi CTA Cards; yang khas hanya keterangan SEO
   yang dipotong dua baris dan foto di ubin kisi.                              */
.page-crumb { padding: var(--space-4) var(--pad) 0; display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; font-size: var(--text-xs); }
.page-crumb a { color: var(--text-muted); }
.page-crumb a:hover { color: var(--text-strong); }
.page-crumb svg { width: 13px; height: 13px; color: var(--text-disabled); }
.page-crumb span { min-width: 0; overflow: hidden; color: var(--text-strong); font-weight: var(--fw-semibold); text-overflow: ellipsis; white-space: nowrap; }
.sub-pages .sub-pages-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-family: var(--font-body); line-height: 1.4; }
.sub-pages .cta-icon.has-photo { width: 100%; height: 72px; overflow: hidden; border-radius: 8px; background: var(--bg-inset); }
.sub-pages .cta-icon.has-photo img { width: 100%; height: 100%; display: block; object-fit: cover; }

/* ── market_carousel (owner 17 Okt 2026) ────────────────────────────────────
   Kartu = unit "Marketplace" master 12.04.02 "Commerce & Marketplace.dc.html":
   tepi --line-200, radius 14 (--radius-md), --shadow-card → --shadow-pop saat
   hover, foto 1:1 beralas --ink-050 bergaris --line-100, nama 14/600, harga
   display 16/700, kaki bergaris: avatar xs + penjual + bintang --vs-yellow.
   Rel edge-to-edge (idiom .highlights): kepala berpagar --pad, rel ber-padding
   --pad. Lebar kartu = turunan "peek" photo_carousel — sliver 40px kartu
   berikutnya selalu terlihat:  kartu = (100% + pad − n·jarak − 40px) / n.
   Jumlah per layar: 2 di ponsel, maks 3 di ≥640, `data-per-view` di ≥1024. */
/* Rel bersama: dipakai juga product_card, event_card, stacked_cards (row), plan_cards
   (carousel). `.has-rail` = seksi edge-to-edge; tanpa itu kepala & kisi berpagar
   padding seksi biasa. */
.has-rail { padding-inline: 0; --mc-gap: var(--space-4); }
.has-rail > .mc-head { padding-inline: var(--pad); }
.block-market-carousel { --mc-n: 2; }
.mc-head { margin-bottom: var(--space-5); display: flex; align-items: end; justify-content: space-between; gap: var(--space-4); }
.mc-titles { min-width: 0; display: grid; gap: var(--space-2); }
.mc-kicker { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium); letter-spacing: var(--ls-label); text-transform: uppercase; }
.mc-titles h2 { margin: 0; font-size: var(--text-h4); letter-spacing: var(--ls-display); line-height: var(--lh-snug); }
.mc-intro { margin: 0; max-width: 60ch; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
.mc-tools { flex: none; display: flex; align-items: center; gap: var(--space-4); }
.mc-all { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-2) 0; border: 0; background: none; color: var(--text-strong); font: inherit; font-size: var(--text-2xs); font-weight: var(--fw-semibold); cursor: pointer; }
.mc-all svg { width: 14px; height: 14px; }
.mc-all:focus-visible { outline: 0; border-radius: var(--radius-xs); box-shadow: var(--shadow-focus); }
.mc-count { color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); font-variant-numeric: tabular-nums; }
.mc-nav { display: flex; gap: var(--space-2); }
.mc-nav button { width: 32px; height: 32px; display: grid; place-items: center; padding: 0; border: 1px solid var(--border-default); border-radius: var(--radius-pill); background: var(--surface-0); color: var(--text-body); cursor: pointer; }
.mc-nav button:disabled { opacity: .38; cursor: default; }
.mc-nav button:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.mc-nav svg { width: 16px; height: 16px; }
/* Padding bawah memberi ruang bayangan kartu — rel ber-overflow memotongnya. */
.mc-track { display: flex; gap: var(--mc-gap); overflow-x: auto; padding: var(--space-1) var(--pad) var(--space-5); scroll-padding-inline: var(--pad); scroll-snap-type: x mandatory; scrollbar-width: none; }
.mc-track::-webkit-scrollbar { display: none; }
.mc-track > .mc-card { flex: 0 0 calc((100% + var(--pad) - var(--mc-gap) * var(--mc-n) - 40px) / var(--mc-n)); min-width: 0; scroll-snap-align: start; }
.mc-card { appearance: none; -webkit-appearance: none; font: inherit; color: inherit; text-align: left; padding: 0; margin: 0; display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--line-200); border-radius: var(--radius-md); background: var(--surface-0); box-shadow: var(--shadow-card); cursor: pointer; transition: box-shadow .18s ease; }
.mc-card.is-static { cursor: default; }
.mc-card:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
@media (hover: hover) { .mc-card:not(.is-static):hover { box-shadow: var(--shadow-pop); } }
@media (prefers-reduced-motion: reduce) { .mc-card { transition: none; } }
.mc-media { position: relative; display: block; aspect-ratio: var(--mc-media-ratio, 1 / 1); background: var(--ink-050); border-bottom: 1px solid var(--line-100); overflow: hidden; }
.mc-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mc-placeholder { position: absolute; inset: 0; display: grid; place-items: center; color: var(--ink-300); }
.mc-placeholder svg { width: 22px; height: 22px; }
.mc-body { flex: 1; display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4) var(--space-5); }
/* Nama mendorong harga + kaki ke dasar kartu, supaya kartu sebaris rata bawah. */
.mc-name { margin-bottom: auto; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-size: var(--text-sm); font-weight: var(--fw-semibold); line-height: var(--lh-snug); color: var(--text-strong); text-wrap: pretty; overflow-wrap: anywhere; }
.mc-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); }
.mc-price-value { font-family: var(--font-display); font-size: var(--text-md); font-weight: var(--fw-bold); color: var(--text-strong); font-variant-numeric: tabular-nums; white-space: nowrap; }
.mc-price-unit, .mc-price-strike { color: var(--text-subtle); font-size: var(--text-2xs); }
.mc-foot { display: flex; align-items: center; gap: var(--space-3); padding-top: var(--space-4); border-top: 1px solid var(--line-100); }
.mc-avatar { width: 24px; height: 24px; flex: none; display: grid; place-items: center; border-radius: var(--radius-pill); background: var(--ink-900); color: var(--text-on-dark); font-size: var(--text-2xs); font-weight: var(--fw-semibold); }
.mc-seller { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); font-size: var(--text-xs); }
.mc-rating { margin-left: auto; flex: none; display: flex; align-items: center; gap: var(--space-1); color: var(--text-strong); font-size: var(--text-2xs); font-weight: var(--fw-semibold); }
.mc-rating svg { width: 12px; height: 12px; color: var(--vs-yellow); fill: currentColor; }
.block-market-carousel .slider-dots { margin-top: var(--space-2); }
.mc-foot-note { margin: var(--space-4) 0 0; color: var(--text-subtle); font-size: var(--text-xs); line-height: var(--lh-relaxed); text-align: center; }
/* Ponsel: penghitung disembunyikan supaya judul tidak terdesak panah. */
@media (max-width: 639px) { .mc-count { display: none; } }
.has-rail > .mc-foot-note { margin-inline: var(--pad); }
@media (min-width: 640px) {
  .block-market-carousel:not([data-per-view="2"]) { --mc-n: 3; }
}
@media (min-width: 1024px) {
  .has-rail[data-per-view="3"] { --mc-n: 3; }
  .has-rail[data-per-view="4"] { --mc-n: 4; }
  .has-rail[data-per-view="5"] { --mc-n: 5; }
}

/* ── Kisi kartu bersama (layout "Kisi") ─────────────────────────────────────
   1 kolom di ponsel; 2 di ≥640; `data-cols` (3–5) di ≥1024. Breakpoint = 640/1024
   yang sudah ada. Kartu meregang setinggi baris supaya kaki kartu rata. */
.cards-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); align-items: stretch; }
@media (min-width: 640px) { .cards-grid:not([data-cols="1"]) { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) {
  .cards-grid[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .cards-grid[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .cards-grid[data-cols="5"] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
/* Kartu besar: 1 per layar di ponsel (+ intipan 40px), 2 di ≥640, per-view di ≥1024. */
.block-product-card, .block-event-card, .block-stacked-cards, .block-plan-cards { --mc-n: 1; }
@media (min-width: 640px) {
  .block-product-card.has-rail, .block-event-card.has-rail, .block-stacked-cards.has-rail, .block-plan-cards.has-rail { --mc-n: 2; }
}
.mc-track > .pc-card, .mc-track > .ev-card, .mc-track > .sk-card, .mc-track > .plc-item { flex: 0 0 calc((100% + var(--pad) - var(--mc-gap) * var(--mc-n) - 40px) / var(--mc-n)); min-width: 0; scroll-snap-align: start; }
/* Rel kartu besar butuh ruang bayangan --shadow-pop di bawahnya. */
.block-product-card .mc-track, .block-stacked-cards .mc-track, .block-plan-cards .mc-track { padding-bottom: var(--space-7); }

/* ── product_card (referensi "Nike AirMax1", 17 Okt 2026) ─────────────────── */
.pc-card { display: flex; flex-direction: column; padding: var(--space-3); border: 1px solid var(--line-100); border-radius: var(--radius-xl); background: var(--surface-0); box-shadow: var(--shadow-card); }
.pc-media { position: relative; aspect-ratio: var(--pc-ratio, 1 / 1); overflow: hidden; border-radius: var(--radius-lg); background: var(--pc-grad, var(--surface-0)); box-shadow: var(--shadow-card); }
.pc-photos { position: absolute; inset: 0; display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.pc-photos::-webkit-scrollbar { display: none; }
.pc-photos img { box-sizing: border-box; flex: 0 0 100%; width: 100%; height: 100%; display: block; object-fit: cover; scroll-snap-align: start; }
.block-product-card[data-fit="contain"] .pc-photos img { object-fit: contain; padding: var(--space-6); }
.pc-media .slider-dots { position: absolute; left: 0; right: 0; bottom: var(--space-4); margin: 0; }
.pc-body { container-type: inline-size; flex: 1; display: flex; flex-direction: column; padding: var(--space-6) var(--space-3) var(--space-2); }
.pc-title { margin: 0; font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--fw-bold); line-height: var(--lh-snug); letter-spacing: var(--ls-display); color: var(--text-strong); text-wrap: balance; }
.pc-sub { margin: var(--space-2) 0 0; color: var(--text-muted); font-size: var(--text-2xs); }
/* Tanpa batas baris (owner 17 Okt 2026): di pricelist keterangan = isi paket, dan isi
   yang terpotong "…" menyembunyikan apa yang dibeli. Kartu sebaris tetap rata bawah
   karena kaki didorong `margin-top: auto`. */
.pc-desc { margin: var(--space-5) 0 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--lh-relaxed); overflow-wrap: anywhere; }
.pc-foot { margin-top: auto; padding-top: var(--space-6); display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.pc-price { min-width: 0; display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); }
.pc-price b { font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-bold); color: var(--text-strong); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pc-price span, .pc-price s { color: var(--text-subtle); font-size: var(--text-2xs); }
.pc-cta { flex: none; min-height: 44px; display: inline-flex; align-items: center; gap: var(--space-4); padding: var(--space-2) var(--space-2) var(--space-2) var(--space-5); border: 0; border-radius: var(--radius-pill); background: var(--ink-900); color: var(--text-on-dark); font: inherit; font-size: var(--text-xs); font-weight: var(--fw-semibold); cursor: pointer; }
.pc-cta[aria-label] { padding: var(--space-2); }
.pc-cta[data-tone="brand"] { background: var(--brand-accent); color: var(--brand-accent-ink); }
.pc-cta[data-tone="gradient"] { background: var(--pc-grad); color: var(--text-on-dark); }
.pc-cta[data-tone="gradient"][data-ink="dark"] { color: var(--text-strong); box-shadow: inset 0 0 0 1px var(--line-200); }
.pc-cta-icon { width: 30px; height: 30px; display: grid; place-items: center; border-radius: var(--radius-pill); background: var(--surface-0); color: var(--ink-900); }
.pc-cta-icon svg { width: 16px; height: 16px; transform: rotate(-45deg); }
.pc-cta:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
/* Kartu sempit (ponsel, 1 kartu + intipan): harga dan tombol berlabel tidak muat
   sebaris — tombol `flex: none` dan harga `nowrap` saling tindih. Kaki menumpuk:
   harga di atas, tombol selebar kartu (label kiri, lingkaran panah kanan). Ambang
   340px = ambang @container kartu yang sudah ada di berkas ini. Tombol bulat
   varian Minimal tetap sebaris — ia selalu muat. */
@container (max-width: 340px) {
  .pc-foot:has(.pc-cta:not([aria-label])) { flex-direction: column; align-items: stretch; gap: var(--space-4); }
  .pc-foot .pc-cta:not([aria-label]) { justify-content: space-between; }
}
.pc-card[data-variant="bleed"] { padding: 0; overflow: hidden; }
.pc-card[data-variant="bleed"] .pc-media { border-radius: 0; box-shadow: none; border-bottom: 1px solid var(--line-100); }
.pc-card[data-variant="bleed"] .pc-body { padding: var(--space-5) var(--space-5) var(--space-5); }
.pc-card[data-variant="minimal"] .pc-title { font-size: var(--text-h4); }

/* ── event_card (referensi "Spill the tea and knit", 17 Okt 2026) ─────────── */
.ev-card { --ev-base: var(--ink-900); position: relative; width: 100%; display: flex; flex-direction: column; justify-content: flex-end; aspect-ratio: var(--ev-ratio, 4 / 5); padding: 0; overflow: hidden; border: 0; border-radius: var(--radius-xl); background: var(--ev-base); color: var(--text-on-dark); font: inherit; text-align: left; box-shadow: var(--shadow-card); cursor: pointer; }
.ev-card.is-static { cursor: default; }
.ev-card:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.ev-media { position: absolute; inset: 0; display: block; background: var(--ink-050); }
.ev-media img { width: 100%; height: 100%; display: block; object-fit: cover; }
.ev-shade { position: absolute; inset: 0; background: var(--ev-shade, linear-gradient(180deg, transparent 30%, var(--ev-base) 72%)); }
.ev-panel { position: relative; display: grid; gap: var(--space-5); padding: var(--space-6); }
.ev-title { font-family: var(--font-display); font-size: var(--text-h2); font-weight: var(--fw-bold); line-height: 1.05; letter-spacing: var(--ls-display); text-wrap: balance; }
.ev-info { display: flex; align-items: center; gap: var(--space-4); }
.ev-date { width: 52px; flex: none; display: grid; overflow: hidden; border-radius: var(--radius-sm); background: var(--surface-0); color: var(--text-strong); text-align: center; }
.ev-date-month { padding: var(--space-1) 0; background: color-mix(in srgb, var(--ev-base) 82%, var(--ink-900)); color: var(--text-on-dark-muted); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); }
.ev-date-day { padding-top: var(--space-2); font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-bold); line-height: 1; }
.ev-date-wd { padding: var(--space-1) 0 var(--space-2); color: var(--text-subtle); font-family: var(--font-mono); font-size: var(--text-2xs); }
.ev-where { flex: 1; min-width: 0; display: grid; gap: var(--space-1); }
.ev-when { flex: none; display: grid; gap: var(--space-1); text-align: right; }
.ev-venue, .ev-time { font-size: var(--text-sm); font-weight: var(--fw-semibold); }
.ev-sub { overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; color: var(--text-on-dark-muted); font-size: var(--text-xs); line-height: var(--lh-snug); }
/* Split / light: foto di atas (setengah kartu), panel di bawah. */
.ev-card[data-variant="split"], .ev-card[data-variant="light"] { aspect-ratio: auto; justify-content: flex-start; }
.ev-card[data-variant="split"] .ev-media, .ev-card[data-variant="light"] .ev-media { position: relative; aspect-ratio: 4 / 3; }
.ev-card[data-variant="split"] .ev-panel { flex: 1; background: var(--ev-grad, var(--ev-base)); }
.ev-card[data-variant="light"] { background: var(--surface-0); color: var(--text-strong); border: 1px solid var(--line-200); }
.ev-card[data-variant="light"] .ev-panel { flex: 1; }
.ev-card[data-variant="light"] .ev-sub { color: var(--text-muted); }
.ev-card[data-variant="light"] .ev-date { border: 1px solid var(--line-200); }
.ev-card .catalog-badge { z-index: 2; }

/* ── stacked_cards (referensi Slack/Notion/Trello, 17 Okt 2026) ──────────────
   Tanpa unit library — owner memilih blok ini secara eksplisit. Hanya token. */
.sk-card { width: 100%; display: flex; flex-direction: column; padding: 0; overflow: hidden; border: 1px solid var(--line-200); border-radius: var(--radius-lg); background: color-mix(in srgb, var(--surface-0) 90%, transparent); color: var(--text-strong); font: inherit; text-align: left; box-shadow: var(--shadow-pop); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); cursor: pointer; transition: transform .22s ease; }
.sk-card.is-static { cursor: default; }
.sk-card:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.sk-card.has-grad { background: var(--sk-grad); border-color: color-mix(in srgb, var(--surface-0) 40%, transparent); }
.sk-head { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--line-200); background: var(--surface-1); font-size: var(--text-sm); font-weight: var(--fw-semibold); }
.sk-card.has-grad .sk-head { background: color-mix(in srgb, var(--surface-0) 28%, transparent); border-bottom-color: color-mix(in srgb, currentColor 12%, transparent); }
.sk-head svg { width: 20px; height: 20px; flex: none; }
.sk-body { display: grid; gap: var(--space-2); padding: var(--space-5) var(--space-5) var(--space-6); }
.sk-kicker { color: var(--text-muted); font-size: var(--text-xs); }
.sk-title { font-family: var(--font-display); font-size: var(--text-h4); font-weight: var(--fw-semibold); line-height: var(--lh-snug); letter-spacing: var(--ls-display); text-wrap: pretty; }
.sk-rule { height: 1px; margin: var(--space-4) 0 var(--space-3); background: color-mix(in srgb, currentColor 14%, transparent); }
.sk-date { font-size: var(--text-sm); font-weight: var(--fw-bold); }
.sk-meta { color: var(--text-muted); font-size: var(--text-xs); }
.sk-card[data-ink="light"] { color: var(--text-on-dark); }
.sk-card[data-ink="light"] .sk-kicker, .sk-card[data-ink="light"] .sk-meta { color: var(--text-on-dark-muted); }
/* Tumpuk: ponsel = vertikal saling tindih, selang-seling kiri/kanan. */
.sk-stack { display: flex; flex-direction: column; align-items: center; padding: var(--space-5) 0; }
.sk-stack > .sk-card { width: min(300px, 86%); position: relative; }
/* Tindih tipis 4px: kemiringan kartu (±13px di sudut) sudah membuat kesan bertumpuk;
   tindih lebih besar menutup baris terakhir kartu di atasnya. */
.sk-stack > .sk-card + .sk-card { margin-top: calc(var(--space-2) * -1); }
.sk-stack > .sk-card:nth-child(odd) { align-self: flex-start; }
.sk-stack > .sk-card:nth-child(even) { align-self: flex-end; }
.sk-stack[data-tilt="on"] > .sk-card:nth-child(odd) { transform: rotate(-3deg); }
.sk-stack[data-tilt="on"] > .sk-card:nth-child(even) { transform: rotate(2.5deg); }
.sk-stack > .sk-card:hover, .sk-stack > .sk-card:focus-visible { z-index: 5; transform: translateY(-4px); }
@media (min-width: 640px) {
  .sk-stack { flex-direction: row; justify-content: center; padding: var(--space-9) var(--space-7); }
  .sk-stack > .sk-card { width: 260px; flex: 0 0 auto; }
  .sk-stack > .sk-card:nth-child(odd), .sk-stack > .sk-card:nth-child(even) { align-self: center; }
  .sk-stack > .sk-card + .sk-card { margin: 0 0 0 calc(var(--space-9) * -1); }
  /* Kartu kedua di depan (seperti referensi): judulnya tidak tertutup kartu kanan. */
  .sk-stack > .sk-card:nth-child(2) { z-index: 2; }
  .sk-stack > .sk-card:nth-child(odd) { transform: translateY(var(--space-7)); }
  .sk-stack > .sk-card:nth-child(even) { transform: translateY(calc(var(--space-6) * -1)); }
  .sk-stack[data-tilt="on"] > .sk-card:nth-child(odd) { transform: translateY(var(--space-7)) rotate(-6deg); }
  .sk-stack[data-tilt="on"] > .sk-card:nth-child(even) { transform: translateY(calc(var(--space-6) * -1)) rotate(4deg); }
  .sk-stack > .sk-card:hover, .sk-stack > .sk-card:focus-visible { transform: translateY(-6px) rotate(0deg); }
}
@media (prefers-reduced-motion: reduce) { .sk-card { transition: none; } }

/* ── plan_cards (referensi "Personal / Business Plan", 17 Okt 2026) ───────── */
.plc-item { --plc-grad: linear-gradient(90deg, var(--line-200), var(--line-200)); display: flex; flex-direction: column; min-width: 0; }
.plc-card { position: relative; z-index: 1; flex: 1; display: flex; flex-direction: column; padding: var(--space-7); border: 1px solid transparent; border-radius: var(--radius-xl); background: linear-gradient(var(--surface-0), var(--surface-0)) padding-box, var(--plc-grad) border-box; box-shadow: var(--shadow-card); }
.plc-item[data-variant="outline"] .plc-card { border-width: 2px; }
.plc-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.plc-name { margin: 0; font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--fw-semibold); letter-spacing: var(--ls-display); color: var(--text-strong); }
.plc-badge { flex: none; padding: var(--space-2) var(--space-4); border-radius: var(--radius-pill); background: var(--ink-900); color: var(--text-on-dark); font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: var(--ls-label-sm); text-transform: uppercase; box-shadow: var(--shadow-xs); }
.plc-price { margin-top: var(--space-5); display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); }
.plc-price b { font-family: var(--font-display); font-size: var(--text-h1); font-weight: var(--fw-bold); line-height: 1; letter-spacing: var(--ls-display); color: var(--text-strong); font-variant-numeric: tabular-nums; }
.plc-price span { color: var(--text-muted); font-size: var(--text-xs); }
.plc-price s { color: var(--text-subtle); font-size: var(--text-xs); }
.plc-sub { margin: var(--space-3) 0 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
.plc-rule { height: 1px; margin: var(--space-6) 0; background: var(--plc-grad); }
.plc-feats { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-5); }
.plc-feats li { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: var(--space-3); align-items: start; }
.plc-feats li > svg { width: 18px; height: 18px; margin-top: var(--space-1); color: var(--plc-accent, var(--text-strong)); }
.plc-feats li.is-off { opacity: .5; }
.plc-feats li.is-off > svg { color: var(--text-subtle); }
.plc-feat { display: grid; gap: var(--space-1); }
.plc-feat strong { color: var(--text-strong); font-size: var(--text-sm); font-weight: var(--fw-medium); }
.plc-feat span { color: var(--text-muted); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
.plc-cta { width: 100%; display: flex; align-items: center; justify-content: center; gap: var(--space-3); border: 0; background: var(--ink-900); color: var(--text-on-dark); font: inherit; font-family: var(--font-display); font-size: var(--text-md); font-weight: var(--fw-semibold); cursor: pointer; }
.plc-item.has-grad .plc-cta { background: var(--plc-grad); }
.plc-item[data-ink="dark"] .plc-cta { color: var(--text-strong); }
.plc-cta svg { width: 18px; height: 18px; }
.plc-cta:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
/* Bar: menyelip di BAWAH kartu — margin negatif sebesar radius kartu. */
.plc-item[data-variant="bar"] .plc-cta { margin-top: calc(var(--radius-xl) * -1); padding: calc(var(--radius-xl) + var(--space-5)) var(--space-6) var(--space-5); border-radius: 0 0 var(--radius-xl) var(--radius-xl); }
/* Inline/outline: tombol didorong ke dasar kartu; elemen tepat di atasnya memberi jarak minimum. */
.plc-card > .plc-cta { margin-top: auto; padding: var(--space-4) var(--space-6); border-radius: var(--radius-md); }
.plc-card > :nth-last-child(2):not(.plc-top) { margin-bottom: var(--space-6); }
.plc-note { margin: var(--space-4) 0 0; padding-inline: var(--space-2); display: flex; gap: var(--space-2); color: var(--text-subtle); font-size: var(--text-2xs); line-height: var(--lh-relaxed); }
.plc-note b { flex: none; color: var(--text-strong); font-weight: var(--fw-semibold); }
