/* ============================ warna merek: hijau di atas putih ==================
   Sejak 29 Agustus 2026 situs dan aplikasi ini terang: kertas putih, hijau sebagai
   satu-satunya warna merek, ungu khusus untuk apa pun yang berbayar. Sebelumnya
   seluruhnya gelap dengan hijau zaitun — palet yang terlalu dekat dengan papan catur
   daring yang sudah ada lebih dulu, dan kemiripan itu bukan sesuatu yang perlu
   dipertahankan.

   Yang berubah hanya nilai di blok ini dan segelintir warna yang dulu ditulis mati
   untuk latar gelap. Nama peubahnya tetap sama persis, jadi setiap kartu, tombol, dan
   bilah di seluruh berkas ini tidak perlu tahu bahwa temanya berbalik.

   Tiga hal sengaja TIDAK ikut terang, dan masing-masing punya alasannya sendiri di
   tempatnya: panel .arena (papan skor yang ditempel, seperti papan hasil di dinding),
   poster juara (kartu yang dibagikan ke luar situs), dan bilah evaluasi (hitam-putih
   di sana berarti Hitam dan Putih, bukan tema).                                    */
:root {
  --bg: #f1f6f2;
  --bg-soft: #f7faf8;
  --bg-card: #ffffff;
  --line: #dde6e0;
  /* Warna scrollbar: terang seperti latar, batangnya cukup tua untuk terlihat tanpa
     pernah jadi hal paling mencolok di layar. */
  --scroll-track: #eaf0ec;
  --scroll-thumb: #c4d2c9;
  --scroll-thumb-hover: #a7b8ad;
  --text: #14211a;
  --muted: #5d6b63;
  /* Hijau merek. Dua nilai, dan keduanya dipakai untuk hal yang berbeda: --accent
     cukup tua untuk dibaca sebagai teks di atas putih (kontras 4.98:1) dan sebagai
     latar tombol bertulisan putih; --accent-bright hanya untuk bidang besar — kepala
     halaman, gradasi — tempat kontras teks tidak lagi jadi soal. */
  --accent: #14804a;
  --accent-dark: #0f6a3c;
  --accent-bright: #22a869;
  --accent-soft: #e6f4ec;
  /* Warna tulisan di atas tombol hijau. Dulu hitam kehijauan (#10240a) karena hijau
     zaitun lama terlalu terang untuk teks putih; hijau yang sekarang tua, dan putih
     adalah satu-satunya yang benar di atasnya. */
  --on-accent: #ffffff;
  /* Ungu: penanda berbayar, dan tidak dipakai untuk apa pun selain itu. Premium dan
     Pro memakai ungu yang sama supaya keduanya terbaca sebagai satu keluarga; yang
     membedakan lencananya adalah isi/garis, bukan warnanya. */
  --premium: #7a3fd0;
  --premium-dark: #6431ae;
  --premium-soft: #f3ecfd;
  --premium-line: #ddcbf8;
  /* Emas-jingga: warna paket **Premium** sejak 11 September 2026, atas permintaan
     ridwanyas — sampai hari itu Premium dan Pro memakai ungu yang sama dan hanya bisa
     dibedakan dengan membaca hurufnya. Sekarang ungu tinggal milik Pro.

     Emas yang dipakai di sini emas **tua**, bukan kuning. Alasannya sama dengan yang
     tertulis di blok `.vip` di bawah: emas muda di atas kertas putih adalah warna yang
     nyaris tidak terbaca. `--gold` lolos 4,5:1 di atas putih; yang muda (`--gold-lit`)
     hanya dipakai sebagai puncak kilau yang lewat, tidak pernah sebagai warna tulisan
     yang diam. */
  --gold: #b45309;
  --gold-dark: #8a3e06;
  --gold-lit: #e08a1e;
  --gold-soft: #fff6e5;
  --gold-line: #f5d9a8;
  --danger: #c8483a;
  --warn: #b8770c;
  --info: #1663c7;
  /* Petak papan hijau. Sejak 10 Sep 2026 memakai pasangan hijau chess.com apa adanya
     (#eeeed2 / #769656) atas permintaan ridwanyas — diukur dari gambar acuannya. */
  --light-sq: #eeeed2;
  --dark-sq: #769656;
  /* Sorotan langkah terakhir dan petak terpilih. Ikut tema, karena kontrasnya
     bergantung pada warna yang disorot: kuning di atas petak yang sudah kuning
     nyaris tidak terlihat. */
  --hl-last: rgba(255, 214, 41, .40);
  --hl-sel: rgba(255, 205, 20, .55);
  /* Rencana langkah (premove): biru tipis, sengaja bukan kuning. Sorotan kuning berarti
     "ini sudah terjadi"; rencana berarti "ini belum tentu terjadi", dan keduanya sering
     tampil di papan yang sama pada saat yang sama. */
  --hl-pre: rgba(46, 110, 190, .38);
  /* Bidak lawan yang terdampak langkah (Analysis): merah muda stroberi. */
  --hl-impact: rgba(255, 111, 156, .55);
  /* Huruf judul. Selalu berpasangan dengan huruf sistem sebagai cadangan: 33 KB
     yang gagal diunduh tidak boleh membuat judulnya hilang. */
  --font-display: 'Baloo 2', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --radius: 12px;
  --shadow: 0 6px 20px rgba(16, 44, 30, .09);
}

/* ---------- huruf ----------------------------------------------------------
   Diminta ridwanyas 4 September 2026: hurufnya "kurang semangat, harusnya dibuat
   variasi". Yang dipakai sebelumnya cuma satu — huruf bawaan sistem, dari judul
   sampai catatan kaki — dan halaman yang seluruhnya satu huruf tidak punya satu
   pun tempat yang lebih nyaring dari yang lain.

   Jadi ada dua sekarang, dan pembagiannya tetap: **judul dan angka besar** memakai
   Baloo 2 (bulat, tebal, bersemangat — sekeluarga rasa dengan maskot kudanya), isi
   tulisan tetap huruf sistem, yang paling enak dibaca panjang-panjang dan tidak
   perlu diunduh sama sekali.

   Berkasnya dititipkan di situs ini sendiri, bukan diambil dari Google Fonts:
   `font-src 'self'` di src/security.js memblokir semua sumber luar, dan halaman
   yang hurufnya diblokir CSP gagal tanpa suara. Satu berkas 33 KB, huruf variabel
   400–800 — bukan dua berkas untuk dua ketebalan. Lisensinya SIL Open Font License
   1.1 (lihat public/fonts/OFL.txt). */
@font-face {
  font-family: 'Baloo 2';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/baloo2-latin.woff2') format('woff2');
}

/* Bendera negara dari Twemoji (CC BY 4.0, lihat public/fonts/TWEMOJI-FLAGS-LICENSE.txt).
   Windows tidak punya bendera di font emojinya dan menggambar 🇺🇸 sebagai dua huruf "US"
   — ridwanyas 14 Sep 2026 minta bendera, bukan kode. unicode-range membatasi huruf ini
   ke Regional Indicator saja, jadi tidak ada karakter lain yang ikut berubah, dan
   berkasnya (78 KB) hanya diunduh halaman yang memang memuat bendera. */
@font-face {
  font-family: 'Twemoji Country Flags';
  font-display: swap;
  unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
  src: url('/fonts/TwemojiCountryFlags.woff2') format('woff2');
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; overflow-x: hidden; }
body {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
  min-height: 100vh;
}

/* ------------------- bidak bayangan di latar seluruh situs ----------------------
   ridwanyas, 10 September 2026: "kasih background icon bidak catur bayangan di samping,
   posisi random yang penting pas".

   Satu lapisan tetap (tidak ikut tergulir) berisi lima bidak besar berkadar 7%. Semuanya
   ditaruh di PINGGIR dan sebagian terpotong tepi layar: kolom tulisan ada di tengah, dan
   bayangan yang lewat di belakang kalimat berhenti menjadi hiasan dan mulai menjadi
   gangguan membaca.

   `z-index: -1` bisa dipakai justru karena latar `body` diteruskan ke kanvas peramban —
   lapisan bernilai negatif berada DI ATAS kanvas itu, dan di bawah seluruh isi halaman.
   Berkasnya berkas bidak yang sama dengan yang dipakai papan sungguhan; `grayscale`
   membuat yang berwarna ikut menjadi bayangan, jadi tidak ada berkas hiasan tersendiri
   yang harus ikut diperbarui tiap set bidak berubah.                              */
body::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    url('/img/pieces/wn.svg'), url('/img/pieces/bq.svg'), url('/img/pieces/wr.svg'),
    url('/img/pieces/bp.svg'), url('/img/pieces/wk.svg');
  background-repeat: no-repeat;
  background-position: -3% 16%, 102% 7%, 101% 66%, 1% 86%, 96% 97%;
  background-size: 240px, 180px, 210px, 150px, 130px;
  filter: grayscale(1);
  opacity: .07;
}
/* Layar sempit: dua saja, dan lebih kecil. Lima bidak di lebar 390px berhenti terbaca
   sebagai latar dan mulai terbaca sebagai gambar yang gagal dimuat. */
@media (max-width: 560px) {
  body::before {
    background-image: url('/img/pieces/wn.svg'), url('/img/pieces/bq.svg');
    background-position: -10% 12%, 106% 76%;
    background-size: 160px, 140px;
  }
}
/* Di dalam APK latar halamannya diurus cangkang native. */
html.native body::before { display: none; }
a { color: var(--accent); text-decoration: none; }
@media (hover: hover) { a:hover { text-decoration: underline; } }

/* ---------- layout ---------- */
.topbar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px; background: var(--bg-card);
  border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 40;
}
.topbar .brand { font-weight: 700; font-size: 18px; color: var(--accent); display: flex; align-items: center; gap: 8px; }
/* The mark is the two pieces the site is about, cut from the same set the board uses:
   knight in front, king half-tucked behind it, so they read as one shape at 26px. */
.topbar .brand span.logo { display: flex; align-items: center; }
.topbar .brand span.logo img { width: 26px; height: 26px; display: block; }
.topbar .brand span.logo img + img { margin-left: -9px; }
/* Logo 2026: satu ubin rumput + tulisan bergambar. Ubinnya sedikit lebih besar dari
   dua bidak lama (28px) karena isinya ikon utuh, bukan siluet; tulisannya diatur dari
   tingginya supaya panjangnya boleh ikut bahasa/huruf. */
.topbar .brand span.logo img.mark { width: 28px; height: 28px; border-radius: 7px; }
.topbar .brand img.wordmark { height: 21px; width: auto; display: block; }
@media (max-width: 720px) { .topbar .brand img.wordmark { height: 19px; } }

/* Lambang merek: kuda di depan raja, dan yang memisahkan keduanya adalah lingkaran
   gelap di sekeliling kuda — bukan jarak.

   Keduanya bidak putih dengan garis tepi setipis rambut, jadi pada 26px siluetnya
   menyatu jadi satu gumpalan yang tidak bisa dibaca sebagai dua bidak. Dua drop-shadow
   bertumpuk memberi tepi gelap yang cukup pekat tanpa menggeser tata letaknya sedikit
   pun, dan trik yang sama dipakai di dalam public/icon.svg untuk alasan yang sama.
   Dipasang pada gambar KEDUA saja: yang di belakang tidak perlu dipotong dari apa pun. */
.topbar .brand span.logo img + img,
.brand-mark .logo img + img,
.brand-inline img + img {
  filter: drop-shadow(0 0 2px rgba(20, 33, 26, .85)) drop-shadow(0 0 2px rgba(20, 33, 26, .55));
}
.topbar nav { display: flex; gap: 4px; flex: 1; overflow-x: auto; scrollbar-width: none; }
.topbar nav::-webkit-scrollbar { display: none; }
.topbar nav a {
  padding: 6px 12px; border-radius: 8px; color: var(--muted); white-space: nowrap; font-weight: 500;
}
.topbar nav a.active { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
/* Menu yang sengaja tidak ikut berbaris di bilah lebar (lihat BAR_HIDDEN di js/api.js).
   Ia dihidupkan kembali di dalam laci — aturan lacinya ada di media query 900px. */
.topbar nav a.bar-off { display: none; }
@media (hover: hover) {
  .topbar nav a:hover { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
}
.topbar nav a .ic { display: none; }
.topbar .who { display: flex; align-items: center; gap: 8px; white-space: nowrap; }

/* On a wide screen the groups are invisible scaffolding: `display: contents` drops the
   wrappers so the links lay out in one row exactly as they always did. */
.nav-group { display: contents; }
.nav-group-title, .nav-head, .nav-foot, .nav-toggle, .nav-backdrop, .tabbar { display: none; }

/* --- Pita kabar: satu baris kecil di bawah bilah atas -----------------------------
   ridwanyas, 10 Sep 2026: "menu top barnya buttonnya gede gede dan berantakan ada
   musik mati latar mati segala ... kalau bisa jadi tulisan berjalan kecil dibawah top
   bar karena hanya informasi saja."

   Isinya digandakan dua kali dan pitanya digeser -50%: itu yang membuat putarannya
   tidak pernah menampakkan celah kosong di ujung — satu salinan selalu menutupi jejak
   salinan yang lain. Menggeser -100% dengan satu salinan memberi kekosongan selebar
   layar di setiap putaran.

   Ia tidak `position: sticky`: yang menempel di atas hanya bilah judulnya, dan pita
   yang ikut menempel berarti dua baris tetap yang memakan tinggi layar ponsel. */
.ticker {
  overflow: hidden; background: var(--bg-soft); border-bottom: 1px solid var(--line);
  font-size: 12px; color: var(--muted); line-height: 1;
}
.ticker-run { display: flex; width: max-content; animation: ticker-run 30s linear infinite; }
.ticker-set { display: flex; align-items: center; gap: 26px; padding: 7px 26px 7px 0; white-space: nowrap; }
.ticker-item { display: inline-flex; align-items: center; gap: 6px; }
/* Saklar musik: tulisan biasa, bukan tombol berbingkai — begitu ia diberi bingkai, ia
   kembali jadi "tombol gede" yang justru dikeluhkan, cuma pindah tempat. */
.ticker button {
  background: none; border: 0; padding: 0; font: inherit; color: var(--accent);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.ticker .on { color: var(--accent); font-weight: 700; }
@media (hover: hover) { .ticker:hover .ticker-run { animation-play-state: paused; } }
@media (prefers-reduced-motion: reduce) { .ticker-run { animation: none; } }
@keyframes ticker-run { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---------- phones: a drawer instead of a sideways-scrolling row ----------------
   Ten links in a row on a 390px screen meant a menu nobody could see. Below this
   width the bar keeps only the brand and a button; the links move into a grouped
   drawer, and the five most-used ones sit in a thumb bar along the bottom.        */
@media (max-width: 900px) {
  /* The drawer is a child of the header, and a positioned ancestor with a z-index
     starts its own stacking context — so the drawer's z-index was being compared
     against its siblings inside the header, not against a backdrop appended to the
     body. The backdrop won, dimmed the menu, and swallowed every tap: touching a link
     hit the backdrop, which closes the drawer, so the menu vanished and nothing
     navigated. Both now live inside the header and are ordered against each other
     (backdrop 1, drawer 2), while the header as a whole sits above the page. */
  .topbar { padding: 10px 14px; gap: 10px; z-index: 60; }
  .topbar .brand { flex: 1; font-size: 17px; }
  .topbar .who { display: none; }

  .nav-toggle {
    display: block; background: var(--bg-soft); color: var(--text); border: 1px solid var(--line);
    font-size: 19px; line-height: 1; padding: 7px 12px; border-radius: 10px;
  }

  .topbar nav.drawer {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(82vw, 320px);
    flex-direction: column; gap: 0; overflow-y: auto; z-index: 2;
    background: var(--bg-card); border-left: 1px solid var(--line);
    padding: 0 0 calc(76px + env(safe-area-inset-bottom)); transform: translateX(102%);
    transition: transform .22s ease;
    box-shadow: -12px 0 32px rgba(16, 44, 30, .18);
    /* Parked off-canvas is not enough on its own: an invisible panel still swallows
       taps meant for the page behind it, and on iOS it can widen the viewport. */
    visibility: hidden; pointer-events: none;
  }
  body.nav-open .topbar nav.drawer { transform: translateX(0); visibility: visible; pointer-events: auto; }

  .nav-backdrop {
    display: block; position: fixed; inset: 0; z-index: 1; background: rgba(14, 30, 22, .42);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  body.nav-open .nav-backdrop { opacity: 1; pointer-events: auto; }
  body.nav-open { overflow: hidden; }

  /* Everything below belongs to the drawer the script builds. Scoped to `.drawer`
     so a page rendered by an older cached script keeps the plain menu instead of a
     half-styled one. */
  .topbar nav.drawer .nav-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 16px; border-bottom: 1px solid var(--line); font-weight: 700;
    position: sticky; top: 0; background: var(--bg-card); z-index: 1;
  }
  .nav-close { background: none; border: 0; color: var(--muted); font-size: 17px; padding: 4px 8px; }

  .topbar nav.drawer .nav-group { display: block; padding: 10px 10px 4px; }
  .topbar nav.drawer .nav-group + .nav-group { border-top: 1px solid var(--line); }
  .topbar nav.drawer .nav-group-title {
    display: block; padding: 4px 10px 8px; font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
  }
  .topbar nav.drawer a {
    display: flex; align-items: center; gap: 11px; padding: 11px 12px;
    border-radius: 10px; font-size: 15px; font-weight: 600; color: var(--text);
  }
  .topbar nav.drawer a .ic { display: inline-flex; justify-content: center; width: 24px; line-height: 0; }
  /* Di laci tidak ada yang perlu disembunyikan: yang dibuang tadi cuma tempatnya di
     bilah lebar, bukan menunya. */
  .topbar nav.drawer a.bar-off { display: flex; }
  .topbar nav.drawer a.active {
    background: linear-gradient(90deg, var(--accent-soft), transparent);
    color: var(--accent); box-shadow: inset 3px 0 0 var(--accent);
  }
  .topbar nav.drawer .nav-music { display: flex; }
  .topbar nav.drawer .nav-foot {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 14px 16px; margin-top: 8px; border-top: 1px solid var(--line); flex-wrap: wrap;
  }

  /* Bilah bawah: langit malam biru, bukan kertas putih.
     Permintaan ridwanyas 5 Sep 2026 ("jangan putih, coba biru elektrik langit, ada
     bayangan logo bidak catur"). Warnanya ditulis di sini apa adanya, bukan lewat
     `--bg-card`: seluruh sisa situs tetap terang, dan yang gelap memang hanya bilah
     ini — ia jadi terbaca sebagai lantai layar, bukan sebagai kartu yang kelupaan
     diwarnai. Bayangan bidaknya di `::before`, tiga saja: yang banyak berhenti
     terbaca sebagai bayangan dan mulai terbaca sebagai motif. */
  .tabbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    background: linear-gradient(180deg, #12326e 0%, #0a1d44 100%);
    border-top: 1px solid #1c4790;
    padding-bottom: env(safe-area-inset-bottom);
    isolation: isolate; overflow: hidden;
  }
  /* Bidaknya dijadikan siluet putih (`brightness(0) invert(1)`) lalu diredam sampai
     hampir hilang — yang dilihat mata tetap ikon menunya, bukan latarnya. */
  .tabbar::before {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    opacity: .13; filter: brightness(0) invert(1);
    background-repeat: no-repeat;
    background-image: url(/img/pieces/wn.svg), url(/img/pieces/wq.svg), url(/img/pieces/wp.svg);
    background-position: 8% 130%, 52% 150%, 92% 125%;
    background-size: 62px, 74px, 46px;
  }
  .tabbar a {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 8px 2px; font-size: 10.5px; font-weight: 600; color: #a9c4ef;
  }
  .tabbar a .ic { line-height: 0; display: inline-flex; }
  /* Menu yang sedang dibuka: biru elektrik, warna yang tidak dipakai apa pun lagi di
     situs — di atas langit malam, hijau merek kita justru redup. */
  .tabbar a.active { color: #5fd0ff; }
  .tabbar a { text-decoration: none; }
  /* The thumb bar floats over the page, so the page has to end above it. */
  body .container { padding-bottom: 86px; }
  /* Kaki halaman berdiri DI LUAR .container, jadi aturan di atas tidak menyentuhnya —
     tanpa baris ini baris hak cipta tertutup bilah jempol dan tidak bisa digulir lagi
     karena halamannya memang sudah habis. */
  .site-footer .sf-inner { padding-bottom: 104px; }
}

.container { max-width: 1180px; margin: 0 auto; padding: 18px 16px 60px; }

.grid { display: grid; gap: 16px; }
.grid.cards { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }

/* Tiga panel beranda: Misi hari ini, Partai terakhirmu, Statistik.
   Kolomnya ditulis mati (bukan `auto-fill`) karena yang diminta memang tiga sejajar di
   layar laptop, dan `auto-fill` menaruh sebanyak-banyaknya kolom selebar apa adanya —
   di layar 1180px itu bisa jadi empat kolom kurus dengan satu kolom kosong.
   1000px batasnya: di bawah itu satu panel jadi lebih sempit dari ±330px, dan tabel
   statistik mulai mematahkan barisnya sendiri.
   Tamu tidak punya misi, dan #missions yang kosong tetap memakan satu kolom kalau
   tidak disembunyikan — itulah gunanya aturan `:empty`. */
.home-panels { display: grid; gap: 16px; margin-top: 18px; align-items: start; }
.home-panels > * { min-width: 0; }
#missions:empty { display: none; }
@media (min-width: 620px) {
  .home-panels { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Kartu misi paling lebar isinya (empat baris misi + cincin), jadi ia yang
     mengambil dua kolom selama barisnya belum muat bertiga. */
  .home-panels > #missions { grid-column: 1 / -1; }
}
@media (min-width: 1000px) {
  .home-panels { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .home-panels > #missions { grid-column: auto; }
  /* Tamu tidak punya kartu misi, jadi tiga kolom akan menyisakan satu kolom kosong di
     kanan dan dua kartu yang tampak berhenti di tengah jalan. Peramban yang belum
     mengenal `:has()` mengabaikan seluruh aturan ini dan tetap mendapat tata letak tiga
     kolom yang benar — cuma kurang seimbang, bukan rusak. */
  .home-panels:has(> #missions:empty) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.play-layout { display: grid; gap: 18px; grid-template-columns: minmax(320px, 1fr) minmax(280px, 380px); align-items: start; }
@media (max-width: 900px) { .play-layout { grid-template-columns: 1fr; } }

/* Anak sebuah grid menolak menyusut di bawah lebar min-content-nya (`min-width: auto`).
   Di dalam kartu-kartu `.play-layout` ada pita tab yang tombolnya tidak boleh dipatah,
   jadi min-content kolomnya selebar seluruh deret tab — dan lebar itu merambat naik
   sampai kartunya sendiri lebih lebar dari layar: tepi kanan kartu beserta paddingnya
   keluar layar, tabnya berhenti bisa digulir, dan teks panjang terpotong di tengah.

   Aturan ini dulu hanya dipasang untuk `.play-page` sehingga halaman lain yang memakai
   `.play-layout` (event, masters, padepokan, explore, …) tetap meleber. Sekarang berlaku
   untuk semua, dan di layar lebar tidak ada ruginya: lebar minimum kolomnya sudah dijaga
   `minmax()`, bukan oleh min-content anaknya. */
.play-layout > *, .play-layout > .grid > * { min-width: 0; }

.card {
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px; box-shadow: var(--shadow);
}
.card h2, .card h3 { margin: 0 0 10px; font-size: 17px; }
.card p.hint { color: var(--muted); margin: 4px 0 12px; font-size: 13px; }

/* Judul: huruf tampilan, sedikit lebih rapat, dan sedikit lebih besar — huruf ini
   memang lebih pendek badannya daripada huruf sistem pada ukuran yang sama. */
h1, h2, h3, .page-title, .btn.cta { font-family: var(--font-display); letter-spacing: -.1px; }
h1, h2, h3 { line-height: 1.25; }
h1 { font-size: 26px; margin: 0 0 4px; }
.subtitle { color: var(--muted); margin: 0 0 18px; }

/* ---------- ikon ---------------------------------------------------------------
   Satu keluarga ikon garis menggantikan emoji (lihat js/icons.js). Yang diatur di
   sini cuma dua hal: ikonnya sendiri ikut ukuran hurufnya, dan keping berwarna yang
   membungkusnya.

   Nada warna keping (`--t`) didaftar sekali di bawah dan dipakai di mana pun menu itu
   muncul — beranda, laci aplikasi, kartu. Satu menu berwarna sama di semua tempat,
   jadi warnanya sendiri sudah menandai "ini yang tadi saya buka" sebelum labelnya
   sempat dibaca. Warnanya dipilih berpasangan, bukan diambil acak: enam nada yang
   sama-sama terang pada latar gelap dan sama-sama gelap pada latar terang. */
.ic-svg { display: inline-block; vertical-align: -.18em; flex: none; }

/* `--t` diwarisi dari kelas nada di kepingnya sendiri atau dari induknya — kartu menu
   memakainya juga untuk warna tepi saat disentuh, jadi nadanya dipasang di kartu dan
   keping ikut mewarisi. Tanpa keduanya, warnanya jatuh ke aksen situs. */
.ic-chip {
  width: var(--chip, 42px); height: var(--chip, 42px); flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: calc(var(--chip, 42px) * .32);
  color: var(--t, var(--accent));
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--t, var(--accent)) 26%, transparent),
    color-mix(in srgb, var(--t, var(--accent)) 8%, transparent));
  border: 1px solid color-mix(in srgb, var(--t, var(--accent)) 38%, transparent);
}

/* Nada keping berdiri di atas putih, jadi semuanya versi tua: nada muda yang dulu
   dipakai di atas hitam terbaca sebagai stabilo pucat di sini. */
.tone-green  { --t: #2f8f4e; }
.tone-blue   { --t: #1663c7; }
.tone-cyan   { --t: #0d7d96; }
.tone-violet { --t: #7442c8; }
.tone-amber  { --t: #b07908; }
.tone-rose   { --t: #c73b62; }
.tone-teal   { --t: #0f8f78; }
.tone-orange { --t: #d76a08; }

/* ---------- kepala halaman depan ------------------------------------------------
   Halaman depan dulu dibuka oleh sebuah <h1> di atas latar polos. Kepala berwarna ini
   memberinya hal yang sudah dipunyai halaman peringkat: satu bidang warna yang
   memberi tahu di mana kamu sedang berdiri, sebelum satu kata pun dibaca. Gradiennya
   hijau — warna merek — supaya ketiga kepala halaman (depan hijau, rating jingga,
   point biru) terbaca sebagai satu keluarga yang sama. */
.home-hero {
  position: relative; overflow: hidden; border-radius: 16px;
  padding: 24px 22px; margin-bottom: 18px; color: #fff;
  background: linear-gradient(120deg, #0d5f37 0%, #17864f 52%, #34b473 100%);
  box-shadow: 0 10px 30px rgba(23, 134, 79, .22);
}
/* --------------------- papan kecil 4x4 dengan bidak sungguhan --------------------
   Dipakai tiga tempat: sudut kanan kepala beranda, kepala /sejarah-catur, dan kartu
   Open Graph (public/__og-sejarah.html). Petak & bidaknya diatur di sini; yang mengatur
   ukuran, putaran, dan bayangannya adalah pemakainya masing-masing lewat `width`.

   Bidaknya berkas papan sungguhan, jadi tidak ada gambar hiasan tersendiri yang bisa
   diam-diam berbeda dari papan yang dimainkan orang.                              */
.mini-board { display: grid; grid-template-columns: repeat(4, 1fr); overflow: hidden; }
.mini-board i { position: relative; display: block; aspect-ratio: 1; }
.mini-board i.l { background: var(--light-sq); }
.mini-board i.d { background: var(--dark-sq); }
.mini-board img { position: absolute; left: 8%; top: 6%; width: 84%; height: 84%; }

.home-hero h1 { font-size: 26px; margin: 0 0 6px; color: #fff; }
.home-hero .subtitle { margin: 0; color: rgba(255, 255, 255, .9); }
.home-hero .brand-inline img { filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .35)); }
/* Bidak raksasa yang menembus sudut kanan: hiasan, bukan tombol — dan karena itu tidak
   pernah menangkap sentuhan. Ia menyingkir begitu papan kecil muncul di sudut yang sama:
   dua hiasan bertumpuk di satu sudut membuat keduanya terlihat seperti kecelakaan. */
.home-hero .hero-piece {
  position: absolute; right: -26px; bottom: -46px; width: 176px; opacity: .13;
  transform: rotate(-10deg); pointer-events: none;
}
/* Papan miring di sudut kanan kepala beranda (permintaan ridwanyas 10 Sep 2026, dari
   potongan kartu OG yang ia lihat). Ditaruh melayang, bukan sebagai kolom flex, supaya
   tinggi kepala halaman tetap ditentukan tulisannya — papan yang ikut menentukan tinggi
   akan membuat kepala halaman melompat panjangnya begitu sapaan pemain berganti.
   `padding-right` pada induknyalah yang menjaga tulisan tidak pernah lewat di bawahnya. */
.home-hero { padding-right: 196px; min-height: 172px; }
.home-hero .hero-board {
  position: absolute; right: 24px; top: 50%; width: 134px;
  transform: translateY(-50%) rotate(-4deg);
  border-radius: 14px; box-shadow: 0 14px 30px rgba(3, 26, 15, .34);
  pointer-events: none;
}
.home-hero:has(.hero-board) .hero-piece { display: none; }
@media (max-width: 620px) {
  .home-hero { padding-right: 118px; min-height: 128px; }
  .home-hero .hero-board { width: 92px; right: 14px; border-radius: 11px; }
}
@media (max-width: 560px) {
  .home-hero { padding: 20px 16px; padding-right: 118px; }
  .home-hero h1 { font-size: 22px; }
  .home-hero .hero-piece { width: 132px; right: -30px; bottom: -38px; }
}

/* --- Maskot beranda ---------------------------------------------------------
   Kuda catur kartun yang menyambut, memegang papan kecil berisi sapaan dan tips
   hari ini. Diminta ridwanyas 4 September 2026: berandanya "terlalu sepi dan
   datar". Gambarnya dibangkitkan public/js/mascot.js — berkas yang sama yang
   dirender jadi PNG untuk aplikasi Android, jadi kedua beranda menyambut dengan
   kuda yang sama persis.

   Dua lapis gerakan: yang terus-menerus (bernapas, berkedip) dan koreografinya —
   blok di bawah ini. Keduanya animasi CSS tanpa satu baris JavaScript pun; animasi
   beranda tidak boleh ikut mati ketika ada satu skrip lain yang gagal. */
.mascot-card {
  display: grid; grid-template-columns: minmax(88px, 128px) 1fr; gap: 10px;
  align-items: end; margin-top: 18px; padding: 12px 16px 0;
  border-radius: 18px; border: 1px solid var(--line); overflow: hidden;
  background:
    radial-gradient(120% 120% at 12% 100%, rgba(34, 168, 105, .16) 0%, rgba(34, 168, 105, 0) 60%),
    linear-gradient(160deg, #f4fbf6 0%, #e4f2e9 100%);
}
.mascot-art {
  display: block; width: 100%; height: auto;
  transform-origin: 50% 96%;
  animation: mk-breathe 3.6s ease-in-out infinite;
}

/* --- koreografi: satu putaran 18 detik -------------------------------------
   Diminta ridwanyas 4 September 2026: maskotnya melompat dua langkah ke kanan,
   lalu "sudah tidak dari samping lagi" — ia berbalik menghadap depan, melambai,
   dan dua detik kemudian tersenyum sambil tersipu, sebelum kembali ke posisi
   awalnya.

   Semuanya satu animasi yang **sama panjang** pada tiap lapis (18s), bukan
   beberapa pewaktu yang berjalan sendiri-sendiri: dua animasi 18 detik yang
   dimulai bersama tidak pernah bisa hanyut, sementara dua pewaktu selalu.

   Peta waktunya, sekali dan untuk semua keyframes di bawah:

     0–30%   diam menghadap kanan          60–72%  tersipu: pipi merona, senyum
     30–36%  dua lompatan ke kanan         72–75%  berbalik, kini menghadap kiri
     36–40%  berbalik menghadap depan      75–80%  dua lompatan pulang
     40–50%  melambai (dua ayunan)         80–83%  berbalik lagi ke kanan
     50–60%  jeda dua detik                83–100% diam seperti semula

   Berbalik badan digambar dengan `scaleX` yang menyempit sampai hampir nol lalu
   melebar lagi pada lapis satunya — cara animasi dua dimensi memutar sesuatu.
   Tanpa penyempitan itu yang terlihat cuma dua gambar yang saling menimpa. */
.mascot-figure { position: relative; z-index: 2; animation: mk-hop 18s ease-in-out infinite; }
.mk-side, .mk-front, .mk-front-arm { transform-box: view-box; transform-origin: 110px 120px; }
.mk-side { animation: mk-turn-side 18s ease-in-out infinite; }
.mk-front { opacity: 0; animation: mk-turn-front 18s ease-in-out infinite; }
.mk-front-arm { transform-origin: 150px 190px; animation: mk-front-wave 18s ease-in-out infinite; }
.mk-front-mouth { animation: mk-mouth 18s ease-in-out infinite; }
.mk-front-smile { opacity: 0; animation: mk-shy 18s ease-in-out infinite; }
/* `view-box` supaya titik putarnya dibaca dalam koordinat gambar (ARM_PIVOT di
   public/js/mascot.js), bukan dalam kotak lengan itu sendiri. */
.mk-arm { transform-box: view-box; animation: mk-arm-hop 18s ease-in-out infinite; }
.mk-blink { opacity: 0; animation: mk-blink 5.6s ease-in-out infinite; }
.mk-front-blink { opacity: 0; animation: mk-blink 4.7s ease-in-out infinite; }
.mascot-say {
  /* Dibatasi lebarnya: satu kalimat selebar layar 27 inci bukan balon bicara lagi,
     melainkan sebuah bilah. */
  position: relative; align-self: center; max-width: 560px; margin: 0 0 26px 8px;
  padding: 12px 14px; border-radius: 14px;
  background: var(--bg-card); border: 1px solid #cfe6d8;
  box-shadow: 0 6px 16px rgba(15, 106, 60, .1);
}
/* Ekor balon: dua segitiga bertumpuk — yang gelap sebesar garis tepinya, yang
   terang menutupinya satu piksel ke dalam. Satu segitiga saja akan kehilangan
   garis tepi tepat di tempat balonnya menunjuk. */
.mascot-say::before, .mascot-say::after {
  content: ''; position: absolute; left: -9px; top: 50%; width: 0; height: 0;
  border-top: 9px solid transparent; border-bottom: 9px solid transparent;
  border-right: 9px solid #cfe6d8; transform: translateY(-50%);
}
.mascot-say::after { left: -8px; border-right-color: var(--bg-card); }
.mascot-text { margin: 0; font-size: 14px; line-height: 1.5; color: var(--text); }
.mascot-text.swap { animation: mk-say .5s ease; }

@keyframes mk-breathe {
  0%, 100% { transform: translateY(0) scaleY(1); }
  50% { transform: translateY(-2px) scaleY(1.022); }
}
@keyframes mk-hop {
  0%, 30% { transform: translate(0, 0); }
  31.5% { transform: translate(5%, -7%); }
  33% { transform: translate(10%, 0); }
  34.5% { transform: translate(15%, -7%); }
  36%, 75% { transform: translate(20%, 0); }
  76.3% { transform: translate(15%, -7%); }
  77.5% { transform: translate(10%, 0); }
  78.8% { transform: translate(5%, -7%); }
  80%, 100% { transform: translate(0, 0); }
}
/* Lengan samping ikut terangkat tiap kali kakinya lepas dari tapak — lompatan
   dengan lengan yang diam terlihat seperti gambar yang digeser, bukan seperti
   maskot yang melompat. */
@keyframes mk-arm-hop {
  0%, 30% { transform: rotate(0deg); }
  31.5% { transform: rotate(-13deg); }
  33% { transform: rotate(0deg); }
  34.5% { transform: rotate(-13deg); }
  36%, 75% { transform: rotate(0deg); }
  76.3% { transform: rotate(-13deg); }
  77.5% { transform: rotate(0deg); }
  78.8% { transform: rotate(-13deg); }
  80%, 100% { transform: rotate(0deg); }
}
@keyframes mk-turn-side {
  0%, 36% { opacity: 1; transform: scaleX(1); }
  40% { opacity: 0; transform: scaleX(.08); }
  72% { opacity: 0; transform: scaleX(-.08); }
  75%, 80% { opacity: 1; transform: scaleX(-1); }
  81.5% { opacity: 1; transform: scaleX(-.08); }
  83%, 100% { opacity: 1; transform: scaleX(1); }
}
@keyframes mk-turn-front {
  0%, 36% { opacity: 0; transform: scaleX(.08); }
  40%, 72% { opacity: 1; transform: scaleX(1); }
  74%, 100% { opacity: 0; transform: scaleX(.08); }
}
@keyframes mk-front-wave {
  0%, 40% { transform: rotate(0deg); }
  42.5% { transform: rotate(-17deg); }
  44.5% { transform: rotate(5deg); }
  46.5% { transform: rotate(-15deg); }
  48% { transform: rotate(4deg); }
  49.5%, 100% { transform: rotate(0deg); }
}
@keyframes mk-mouth {
  0%, 60% { opacity: 1; }
  63%, 72% { opacity: 0; }
  73.5%, 100% { opacity: 1; }
}
@keyframes mk-shy {
  0%, 60% { opacity: 0; }
  63%, 72% { opacity: 1; }
  73.5%, 100% { opacity: 0; }
}
@keyframes mk-blink {
  0%, 90% { opacity: 0; }
  92%, 95% { opacity: 1; }
  97%, 100% { opacity: 0; }
}
@keyframes mk-say {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .mascot-art, .mascot-figure, .mk-side, .mk-front, .mk-arm, .mk-front-arm,
  .mk-front-mouth, .mk-front-smile, .mk-blink, .mk-front-blink,
  .mascot-text.swap { animation: none; }
}
@media (max-width: 560px) {
  .mascot-card { grid-template-columns: minmax(84px, 104px) 1fr; padding: 10px 12px 0; }
  .mascot-say { margin-bottom: 18px; padding: 10px 12px; }
  .mascot-text { font-size: 13px; }
}

/* Kartu menu beranda: keping ikon di kiri, judul di kanannya. */
.menu-card h2 {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  margin: 0 0 8px; font-size: 17px;
}
.menu-card h2 .mc-title { flex: 1; min-width: 0; }
/* Baris angka hidup di kartu menu (Teka-Teki: rentetan hari, soal hari ini, tingkat).
   Ditempel js/home.js sesudah kartunya berdiri, jadi kartu tanpa baris ini normal. */
.menu-card .mc-live {
  margin: 8px 0 0; padding-top: 8px; border-top: 1px solid var(--line);
  color: var(--t, var(--accent)); font-weight: 600;
}
.menu-card .mc-live .ic { vertical-align: -2px; }
.menu-card { transition: border-color .15s, transform .15s, box-shadow .15s; }
@media (hover: hover) {
  .menu-card:hover {
    border-color: color-mix(in srgb, var(--t, var(--accent)) 55%, var(--line));
    box-shadow: 0 10px 26px color-mix(in srgb, var(--t, var(--accent)) 18%, transparent);
    transform: translateY(-2px);
  }
}

/* ---------- controls ---------- */
button, .btn {
  font: inherit; font-weight: 600; cursor: pointer;
  background: var(--accent); color: var(--on-accent); border: none;
  padding: 10px 14px; border-radius: 9px; min-height: 40px;
  /* Tanpa ini Safari iOS menganggap dua ketukan cepat pada tombol (mis. ▶ di Analysis)
     sebagai double-tap zoom, dan halamannya membesar 2× (ridwanyas, 18 Sep 2026). */
  touch-action: manipulation;
}
button:hover, .btn:hover { background: var(--accent-dark); text-decoration: none; }
button:disabled { opacity: .5; cursor: not-allowed; }
button.ghost, .btn.ghost { background: var(--bg-soft); color: var(--text); border: 1px solid var(--line); }
button.ghost:hover { background: var(--accent-soft); }
button.danger { background: var(--danger); color: #fff; }
button.small { padding: 6px 10px; min-height: 32px; font-size: 13px; }
/* Tombol yang memang dicari orang, dibuat sebesar itu.
   Sesudah partai selesai yang ditanyakan hampir selalu sama — "tadi salahnya di mana"
   — tapi tautan menuju Analysis dulu sebesar dan sewarna tombol lain di sekitarnya,
   jadi langkah yang paling sering diambil justru harus dicari lebih dulu. */
.btn.cta, button.cta {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 52px; padding: 14px 16px;
  font-size: 16px; font-weight: 700; border-radius: 12px;
  box-shadow: 0 8px 22px rgba(20, 128, 74, .22);
}
.btn.cta:hover, button.cta:hover { box-shadow: 0 10px 26px rgba(20, 128, 74, .30); }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.row.tight { gap: 4px; }
.spacer { flex: 1; }

input, select, textarea {
  font: inherit; background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--line); border-radius: 9px; padding: 10px 12px; width: 100%;
}
label { display: block; font-size: 13px; color: var(--muted); margin: 10px 0 4px; }
textarea { min-height: 110px; resize: vertical; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; }

.badge {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 700; background: var(--bg-soft); color: var(--muted); border: 1px solid var(--line);
}
.badge.standard { color: var(--muted); }
/* Paket berbayar: lencananya diisi penuh — bukan sekadar tulisan berwarna. Yang gratis
   tetap abu-abu, jadi yang berbayar adalah satu-satunya yang menyala di deretan mana pun
   ia muncul. Ikon kecil di depannya dipasang di sini, bukan di tiap halaman: satu tempat,
   dan tidak ada halaman yang bisa lupa memasangnya.

   Dua warna, dua tingkat: **Premium emas-jingga, Pro ungu** (11 September 2026). Kilau
   yang berjalan di sepanjang lencana memakai `vip-shine` yang sama dengan nama pemain —
   satu animasi untuk dua hal yang memang berarti hal yang sama. Pro berjalan lebih lambat
   supaya dua lencana yang berdampingan di halaman Paket tidak berkedip bersamaan.

   `text-shadow` gelap itu bukan hiasan: pada puncak kilau, latarnya terang dan tulisan
   putih di atasnya kehilangan kontras. Bayangan tipis menahannya. */
.badge.premium {
  color: #fff; border-color: var(--gold-dark); letter-spacing: .01em;
  background: linear-gradient(100deg, var(--gold-dark) 0%, var(--gold) 26%, var(--gold-lit) 34%,
    var(--gold) 44%, var(--gold-dark) 70%, var(--gold) 88%, var(--gold-dark) 100%);
  background-size: 260% 100%;
  text-shadow: 0 1px 0 rgba(0, 0, 0, .3);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35);
  animation: vip-shine 5.5s linear infinite;
}
.badge.premium::before { content: "\25C6"; margin-right: 5px; font-size: 10px; vertical-align: .5px; }
.badge.admin { color: #155e75; border-color: #bfe3ef; background: #e4f5fa; }

.alert { padding: 10px 12px; border-radius: 9px; margin: 10px 0; font-size: 14px; }
.alert.error { background: #fdeeec; color: #a5372a; border: 1px solid #f2c4bd; }
.alert.info { background: #e9f3fb; color: #17537f; border: 1px solid #c3ddf1; }
.alert.ok { background: var(--accent-soft); color: #0f6a3c; border: 1px solid #b9ddc8; }

/* ---------- evaluation bar + opening name ---------- */
.eval-head { max-width: 620px; margin: 0 auto 8px; }
.opening {
  font-size: 13px; font-weight: 600; line-height: 1.35; min-height: 18px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.opening .eco {
  font-weight: 700; font-size: 11px; color: var(--accent);
  background: var(--accent-soft); border-radius: 4px; padding: 1px 5px; margin-right: 6px;
}
.eval-row { display: flex; align-items: center; gap: 10px; margin-top: 7px; }
/* Scoped under .eval-row (bukan .eval-head): analysis dan explore punya `.eval-bar`
   polos sendiri di bawah berkas ini, dan aturan tanpa induk akan kalah olehnya. Yang
   dipakai induknya adalah BARISNYA, karena di layar lebar baris ini pindah ke dalam
   .board-wrap dan berdiri tegak di samping papan (js/wide-board.js). */
.eval-row .eval-bar {
  position: relative; flex: 1; width: auto; height: 12px; border-radius: 6px; overflow: hidden;
  background: #2b2b31; box-shadow: inset 0 0 0 1px var(--line);
}
/* White's share grows from the left; the rest of the track is Black's. */
.eval-row .eval-bar .fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: var(--eval, 50%);
  background: linear-gradient(180deg, #ffffff, #dcdcd2);
  transition: width .45s cubic-bezier(.4, 0, .2, 1);
}
.eval-row .eval-bar .mid {
  position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; height: auto;
  background: rgba(154,160,170,.5);
}
.eval-row .eval-num { font-variant-numeric: tabular-nums; font-weight: 700; min-width: 56px; text-align: right; }
.eval-row .eval-num.mate { color: var(--warn); }
.eval-row .eval-note { font-size: 12px; color: var(--muted); min-width: 104px; }

@media (max-width: 420px) {
  .eval-row .eval-note { display: none; }
}

/* ---------- board ---------- */
.board-wrap { width: 100%; max-width: 620px; margin: 0 auto; position: relative; }

/* ---------- papan yang ditinggal: hitungan mundur --------------------------------
   Ditumpuk di atas papan supaya munculnya tidak menggeser apa pun, dan ditaruh di
   sepertiga atas — bukan di tengah — karena yang di tengah menutupi justru petak yang
   sedang dilihat orang. pointer-events dimatikan: papannya tetap harus bisa disentuh,
   termasuk untuk menyerah atau menawarkan remis selagi angkanya berjalan. */
.away-note {
  position: absolute; left: 50%; top: 26%; transform: translate(-50%, -50%);
  z-index: 5; pointer-events: none; text-align: center;
  min-width: 232px; max-width: 92%;
  padding: 12px 18px; border-radius: 12px;
  background: rgba(20, 33, 26, .93); border: 1px solid rgba(255, 255, 255, .16); color: #f2f6f3;
  box-shadow: 0 10px 34px rgba(16, 44, 30, .38);
}
.away-note b { display: block; font-size: 15px; margin-bottom: 3px; }
.away-note .away-count { font-size: 27px; font-weight: 800; line-height: 1.15; font-variant-numeric: tabular-nums; }
.away-note .muted { font-size: 12px; }
/* Menang: hijau. Kalah: merah, dan mulai berdenyut di lima detik terakhir — pada titik
   itu yang dibutuhkan bukan lagi keterangan, melainkan sesuatu yang sulit diabaikan. */
.away-note.win .away-count { color: #7fd18a; }
.away-note.lose .away-count { color: #e5484d; }
@keyframes awayPulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: no-preference) {
  .away-note.urgent .away-count { animation: awayPulse .9s ease-in-out infinite; }
}
.board {
  position: relative; width: 100%; aspect-ratio: 1 / 1;
  /* minmax(0, 1fr), bukan 1fr. `1fr` sendirian berarti `minmax(auto, 1fr)`: tiap petak
     boleh melebar melewati jatahnya kalau isinya menuntut lebih. Isi petaknya <img>
     bidak 90x90 dengan height:100% — tinggi persen di dalam baris yang tingginya belum
     pasti — dan begitu salah satu bidak dianimasikan (goyangan skak), peramban berhenti
     memakai tinggi petak dan jatuh ke ukuran asli gambarnya. Baris & lajur raja itu
     membengkak, sisanya terjepit sampai ada yang nol, dan baris paling bawah terpotong
     `overflow:hidden`. minmax(0, …) menghapus batas bawah itu: petak tidak pernah lagi
     bisa lebih besar daripada seperdelapan papan. Diukur 5 Sep 2026 di iPhone & di
     Chrome headless — lihat scripts/smoke-board-grid.mjs. */
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr)); grid-template-rows: repeat(8, minmax(0, 1fr));
  border-radius: 8px; overflow: hidden; touch-action: none; user-select: none;
  container-type: inline-size;
  box-shadow: var(--shadow);
}
.sq { position: relative; display: flex; align-items: center; justify-content: center; min-width: 0; min-height: 0; }
/* Tema papan kedua. Dipasang sebagai atribut pada <html> supaya seluruh papan di
   aplikasi ikut — bermain, Analysis, Explore, dan pratinjau di halaman Akun. */
:root[data-board="abu"] {
  --light-sq: #eef0f1;
  --dark-sq: #8e989f;
}
:root[data-board="coklat"] {
  --light-sq: #f0d9b5;
  --dark-sq: #b58863;
  /* Papan kayu sudah hangat dan kekuningan sejak awal, jadi kuning tipis di atasnya
     hampir tidak berubah apa-apa. Yang dipakai di sini kuning yang lebih pekat —
     bukan hijau atau biru, karena keduanya membuat papan klasik ini berhenti terlihat
     seperti kayu. */
  --hl-last: rgba(255, 225, 40, .45);
  --hl-sel: rgba(255, 215, 20, .62);
}
/* Tiga papan Nusantara. Sorotan langkahnya ikut disesuaikan: kuning tipis di atas
   papan kayu nyaris tak terlihat, dan di atas indigo ia berubah kehijauan. */
:root[data-board="jati"] {
  --light-sq: #e9d5b3;
  --dark-sq: #8f5f34;
  --hl-last: rgba(255, 226, 64, .42);
  --hl-sel: rgba(255, 216, 30, .6);
  --hl-pre: rgba(120, 170, 235, .38);
}
:root[data-board="batik"] {
  --light-sq: #e7e0cd;
  --dark-sq: #4c5a8e;
  --hl-last: rgba(255, 235, 120, .38);
  --hl-sel: rgba(255, 228, 90, .55);
  --hl-pre: rgba(140, 220, 200, .4);
}
:root[data-board="marmer"] {
  --light-sq: #f3f4f6;
  --dark-sq: #a4abb4;
}
.sq.light { background: var(--light-sq); }
.sq.dark  { background: var(--dark-sq); }
.sq.last  { box-shadow: inset 0 0 0 100px var(--hl-last); }
.sq.sel   { box-shadow: inset 0 0 0 100px var(--hl-sel); }
/* Skak tidak lagi mewarnai petaknya.
   Petak merah membuat satu kotak terlihat lebih besar daripada tetangganya — bidang
   jenuh selalu terbaca lebih luas daripada bidang teduh seukurannya — dan yang
   dikeluhkan ridwanyas (5 Sep 2026) persis itu: "kolomnya jadi membesar, malah aneh".
   Sejak 7 Sep 2026 tidak ada penggantinya: skak di situs tidak lagi menggambar
   apa pun di papan. Lihat blok skak di bawah. */
/* Ditulis sesudah .last dan .sel supaya rencana tetap terlihat di petak yang kebetulan
   juga petak langkah terakhir lawan. */
.sq.pre { box-shadow: inset 0 0 0 100px var(--hl-pre); }
.sq.impact { box-shadow: inset 0 0 0 100px var(--hl-impact); }
/* Koordinat memakai warna petak lawannya, jadi ia ikut berganti bersama temanya —
   ditulis mati sebagai hijau, angka-angka ini menjadi tidak terbaca di papan abu. */
.sq .coord { position: absolute; font-size: 10px; font-weight: 700; opacity: .6; color: var(--dark-sq); }
.sq.dark .coord { color: var(--light-sq); }
.sq .coord.file { right: 3px; bottom: 1px; }
.sq .coord.rank { left: 3px; top: 1px; }
/* SVG sprites from /img/pieces — sized to the square, never to a font. */
.piece {
  width: 100%; height: 100%; display: block; cursor: grab; z-index: 2;
  -webkit-user-drag: none;
}
.piece.dragging { opacity: .3; }
.piece.w { filter: drop-shadow(0 1px 2px rgba(0,0,0,.35)); }
.piece.b { filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
.dot {
  position: absolute; width: 28%; height: 28%; border-radius: 50%;
  background: rgba(18, 32, 24, .30); z-index: 1;
}
.dot.capture {
  width: 92%; height: 92%; background: transparent; border-radius: 50%;
  box-shadow: inset 0 0 0 6px rgba(18, 32, 24, .26);
}
/* Titik tujuan untuk rencana memakai warna rencana, bukan warna langkah biasa. */
.dot.pre { background: rgba(91,143,209,.5); }
.dot.pre.capture { background: transparent; box-shadow: inset 0 0 0 6px rgba(91,143,209,.45); }
.board-arrows { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.drag-ghost {
  position: fixed; pointer-events: none; z-index: 100; transform: translate(-50%, -50%) scale(1.08);
  filter: drop-shadow(0 4px 8px rgba(0,0,0,.45));
}

.promo {
  position: absolute; inset: 0; background: rgba(0,0,0,.6); z-index: 30;
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.promo button { background: var(--bg-card); color: var(--text); padding: 6px 10px; line-height: 0; }
.promo button .piece { width: 46px; height: 46px; }

/* ---------- game side panel ---------- */
.player-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 9px;
}
.player-row .name { font-weight: 600; }
/* Foto di plakat papan: sedikit lebih kecil dari tinggi barisnya supaya baris tidak
   tumbuh, dan tetap kotak lembut yang sama seperti di profil. */
.player-row .pfp { --pfp: 30px; }
.player-row.side-b .pfp { border-color: rgba(255, 255, 255, .28); background: rgba(255, 255, 255, .12); }
.player-row.side-b .pfp-letter { color: #fff; }
.player-row .clock {
  margin-left: auto; font-variant-numeric: tabular-nums; font-size: 20px; font-weight: 700;
  padding: 2px 10px; border-radius: 7px; background: var(--bg-card);
}
.player-row .clock.low { background: #fbe3df; color: #a5372a; }
.player-row .clock.active { background: var(--accent); color: var(--on-accent); }
.player-row .dot-status { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.player-row .dot-status.off { background: var(--danger); }

/* Papan nama ikut warna buah yang dipegang pemainnya: yang memegang hitam duduk di
   plakat gelap, yang memegang putih di plakat terang. Sebelum ini keduanya abu-abu
   sama, dan satu-satunya cara tahu siapa memegang apa adalah menghitung bidak — padahal
   papannya sendiri sudah menjawab, dan jawabannya berpindah setiap kali warna ditukar.

   Yang diberi warna adalah barisnya, bukan hanya namanya: sepotong kotak kecil di
   sebelah nama masih harus dicari mata, sedangkan bidang selebar papan tidak. */
.player-row.side-b {
  background: var(--text); border-color: var(--text); color: var(--bg-card);
}
.player-row.side-w {
  background: var(--bg-card); border-color: var(--line); color: var(--text);
}
/* Di plakat gelap, yang redup harus tetap terbaca dan hijau tua kehilangan kontrasnya. */
.player-row.side-b .muted,
.player-row.side-b .captured .adv { color: rgba(255, 255, 255, .72); }
.player-row.side-b .dot-status { background: var(--accent-bright); }
/* Jam memakai latar terang sendiri, jadi warna tulisannya tidak boleh ikut mewarisi
   putih dari barisnya — kecuali jam yang sedang berjalan & yang menipis, yang sudah
   membawa pasangan latar-tulisannya masing-masing. */
.player-row.side-b .clock { color: var(--text); }

/* Bilah momen kunci di halaman Partai Master. Duduk tepat di bawah papan karena yang
   dijelaskannya ada di papan — dan membungkus barisnya di ponsel, supaya kalimat dan
   dua tombolnya tidak pernah saling mendorong keluar layar. */
.moment {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 10px; padding: 8px 10px; border-radius: 10px;
  background: var(--bg-soft); border: 1px solid var(--line);
}
.moment-text { flex: 1 1 180px; font-size: 14px; font-weight: 600; }
.moment-tag { color: var(--accent); }

.moves {
  max-height: 320px; overflow-y: auto; font-variant-numeric: tabular-nums;
  display: grid; grid-template-columns: 34px 1fr 1fr; gap: 2px 6px; align-content: start; font-size: 14px;
}
.moves .num { color: var(--muted); }
.moves .mv { padding: 2px 6px; border-radius: 5px; cursor: pointer; }
@media (hover: hover) { .moves .mv:hover { background: var(--bg-soft); } }
.moves .mv.current { background: var(--accent); color: var(--on-accent); font-weight: 700; }

/* One palette for the whole review: the greens are moves you would play again, the
   yellows and oranges are the ones you would take back, and brilliance and theory get
   colours of their own so they never read as "merely good". */
/* Warna vonis sebagai TULISAN. Nilainya bukan yang dipakai keping bulat di bawah:
   keping itu bidang berwarna dengan huruf gelap di atasnya, sedangkan yang di sini
   adalah hurufnya sendiri di atas kertas putih — dan hijau muda yang cantik di atas
   hitam adalah hijau yang hilang di atas putih. */
.label-brilliant { color: #0e9c86; }
.label-great { color: #3d76bd; }
.label-best, .label-excellent { color: #2f8f4e; }
.label-good { color: #5c8a29; }
.label-book { color: #8a6a45; }
.label-forced { color: var(--muted); }
.label-inaccuracy { color: #a3800f; }
.label-mistake { color: #bb6f14; }
.label-missed { color: #cc563a; }
.label-blunder { color: #c33f2e; }

/* ---------- ulasan permainan: kedua pemain berdampingan ---------- */
.review td, .review th { padding: 5px 8px; border-bottom: 0; }
.review thead th { padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.review tbody th { font-size: 12.5px; text-transform: none; letter-spacing: 0; color: var(--text); font-weight: 600; }
.review td.right, .review td.left { font-weight: 800; font-variant-numeric: tabular-nums; width: 22%; }
.review td.mark { width: 34px; text-align: center; }
.review .acc-row td { padding: 8px; }
.review .acc-row th, .review .total-row th { color: var(--muted); font-weight: 600; }
.review .total-row td { color: var(--muted); font-weight: 600; }
.review .acc {
  display: inline-block; padding: 6px 14px; border-radius: 8px;
  font-size: 16px; font-weight: 800; font-variant-numeric: tabular-nums;
}
.review .acc.w { background: #f2f2ee; color: #14160f; }
.review .acc.b { background: #2b2f36; color: #eef1e9; border: 1px solid var(--line); }
.review .ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  font-size: 12px; font-weight: 800; line-height: 1; color: #12140f;
}
.review .ic.brilliant { background: #26c2a3; }
.review .ic.great { background: #6f9fe0; }
.review .ic.best { background: #7fa650; }
.review .ic.excellent { background: #95bb4a; }
.review .ic.good { background: #a3c96b; }
.review .ic.book { background: #c2a077; }
.review .ic.inaccuracy { background: #e8c65c; }
.review .ic.mistake { background: #e8913a; }
.review .ic.missed { background: #ef8a72; }
.review .ic.blunder { background: #e05b4a; }
.review .ic.forced { background: #4a4f57; color: #dfe3ea; }

.eval-bar { position: relative; width: 100%; height: 10px; background: #2b2b2b; border-radius: 6px; overflow: hidden; }
.eval-bar > div { height: 100%; background: #f0f0f0; transition: width .25s; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); }
/* Di ponsel, 10px kiri-kanan pada empat kolom sudah memakan 80px dari 390px yang ada —
   habis untuk ruang kosong, bukan untuk angka. Dikecilkan supaya tabel yang tadinya
   harus digulir mendatar kembali muat utuh; yang tetap tidak muat masih punya
   .table-scroll sebagai jaring pengaman. */
@media (max-width: 600px) {
  th, td { padding: 8px 6px; }
  th:first-child, td:first-child { padding-left: 2px; }
  th:last-child, td:last-child { padding-right: 2px; }
}
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
/* The colour split hangs under the result it belongs to, so the two read as one
   entry: no rule between them, and the border of the pair sits under the split. */
tr.split td { color: var(--muted); font-size: 12px; padding: 0 10px 8px; }
tr.split + tr th, tr.split + tr td { border-top: 0; }
tr:has(+ tr.split) th, tr:has(+ tr.split) td { border-bottom: 0; padding-bottom: 2px; }
tr.clickable { cursor: pointer; }
@media (hover: hover) { tr.clickable:hover td { background: var(--bg-soft); } }

.list { display: flex; flex-direction: column; gap: 8px; max-height: 60vh; overflow-y: auto; }
.list .item {
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px; cursor: pointer; background: var(--bg-soft);
}
@media (hover: hover) { .list .item:hover { border-color: var(--accent); } }
.list .item.active { border-color: var(--accent); background: var(--accent-soft); }
.list .item .meta { color: var(--muted); font-size: 12px; }

.pv { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; color: var(--text); }
.mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; }
.muted { color: var(--muted); }
.right { text-align: right; }
.left { text-align: left; }
.center { text-align: center; }
.hidden { display: none !important; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
/* Halaman Partai Master menyembunyikan baris penyaring momen dan tab urutannya untuk
   paket yang kolamnya memang tidak punya momen kunci (src/master-pool.js). `display:
   flex` di atas — dan di .filter-tabs — mengalahkan `display: none` bawaan atribut
   [hidden], jadi keduanya ditulis lagi di sini. Tanpa dua baris ini barisnya tetap
   berdiri, kosong, dan menyisakan pertanyaan tanpa jawaban. */
.chips[hidden], .filter-tabs[hidden] { display: none; }
/* Kalimat "paket ini membuka sekian partai", tepat di bawah judul halaman. */
.scope-note { font-size: 13px; margin-bottom: 14px; }
/* Lima belas keping pemain plus lima keping momen membungkus jadi sembilan baris di
   ponsel, dan papannya terdorong keluar layar sebelum satu bidak pun terlihat. Di layar
   sempit keduanya jadi satu baris yang digeser, dengan tepi memudar dari wireEdgeFade
   sebagai penanda masih ada lanjutannya. */
@media (max-width: 720px) {
  .chips.rail { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .chips.rail::-webkit-scrollbar { display: none; }
  .chips.rail .chip { white-space: nowrap; }
}
.chip {
  padding: 6px 10px; border-radius: 999px; background: var(--bg-soft); border: 1px solid var(--line);
  cursor: pointer; font-size: 13px;
}
.chip.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); font-weight: 700; }

.chat { display: flex; flex-direction: column; gap: 6px; }
.chat .log { max-height: 150px; overflow-y: auto; font-size: 13px; display: flex; flex-direction: column; gap: 4px; }
.chat .log .msg b { color: var(--accent); }

.progress { height: 8px; background: var(--bg-soft); border-radius: 5px; overflow: hidden; }
.progress > div { height: 100%; background: var(--accent); width: 0; transition: width .2s; }

footer.small { color: var(--muted); font-size: 12px; text-align: center; padding: 24px 0; }

/* ======================= kaki halaman beranda (tamu saja) =======================
   Dipasang js/site-footer.js dan hanya untuk pengunjung yang belum masuk. Ia menimpa
   `footer.small` dengan mengganti kelasnya, jadi tidak ada aturan di atas yang perlu
   dibatalkan satu per satu di sini.

   Pitanya melebar penuh sampai tepi layar (kaki halaman ada DI LUAR .container) dan
   isinya yang dibatasi 1180px — sama seperti `.container` — supaya barisnya tetap
   sejajar dengan kartu-kartu di atasnya betapapun lebar layarnya.                  */
.site-footer {
  margin-top: 40px; border-top: 1px solid var(--line);
  background: var(--bg-soft); color: var(--text);
  font-size: 14px; text-align: left; padding: 0;
}
.sf-inner { max-width: 1180px; margin: 0 auto; padding: 32px 16px 22px; }
.sf-top { display: grid; gap: 26px; }
@media (min-width: 860px) { .sf-top { grid-template-columns: minmax(250px, 1fr) 1.6fr; gap: 40px; } }
.sf-brand p { color: var(--muted); margin: 12px 0 14px; line-height: 1.6; max-width: 46ch; }
.sf-stores { display: flex; flex-wrap: wrap; gap: 8px; }
.sf-store {
  display: inline-flex; align-items: center; gap: 9px; text-decoration: none; color: var(--text);
  border: 1px solid var(--line); background: var(--bg-card); border-radius: 12px; padding: 7px 12px;
}
a.sf-store:hover { border-color: var(--accent); }
/* Toko yang tautannya belum ada. Kepingannya tetap ditampilkan — orang perlu tahu
   aplikasinya memang akan ada — tapi tidak pernah bisa ditekan. */
.sf-store.off { opacity: .68; }
.sf-store-txt { display: flex; flex-direction: column; line-height: 1.2; }
.sf-store-txt small { font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.sf-store-txt b { font-size: 13px; }
.sf-soon {
  font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
  background: var(--accent-soft); color: var(--accent-dark);
  border-radius: 999px; padding: 2px 7px; margin-left: 2px;
}
.sf-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 22px; }
.sf-col h4 {
  margin: 0 0 10px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
}
.sf-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.sf-col a { color: var(--text); text-decoration: none; }
.sf-col a:hover { color: var(--accent); text-decoration: underline; }
.sf-pay { margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--line); }
.sf-pay > b { font-size: 13px; }
.sf-pay-list { display: flex; flex-wrap: wrap; gap: 8px; margin: 11px 0 9px; }
.sf-pay-list span {
  border: 1px solid var(--line); background: var(--bg-card); color: var(--muted);
  border-radius: 999px; padding: 5px 11px; font-size: 12px; white-space: nowrap;
}
.sf-pay-note { color: var(--muted); font-size: 12px; }
.sf-bottom {
  margin-top: 18px; padding-top: 15px; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;
  gap: 10px 18px; color: var(--muted); font-size: 12.5px;
}
.sf-made { display: inline-flex; align-items: center; gap: 6px; }
.sf-made svg { display: block; }
.sf-legal { display: inline-flex; flex-wrap: wrap; gap: 14px; }
.sf-legal a { color: var(--muted); text-decoration: none; }
.sf-legal a:hover { color: var(--accent); text-decoration: underline; }
@media (max-width: 560px) {
  .sf-inner { padding: 26px 16px 20px; }
  .sf-bottom { flex-direction: column; align-items: flex-start; }
}

/* ---------- statistics ---------- */
.page-title { display: flex; align-items: center; gap: 8px; margin: 18px 0 12px; }
.page-title.small { font-size: 17px; margin-top: 24px; }

/* Kepala halaman Statistik.
   `flex-wrap` bukan hiasan: di layar 390px, avatar 64px + nama pemain + tombol "Profil
   publik →" tidak pernah muat satu baris, dan tanpa pembungkusnya tombol itu terdorong
   64px KE LUAR tepi kanan — terpotong dan tidak bisa diketuk sama sekali. Terukur
   `npm run shot:menus` 23 Sep 2026 (a#profileLink kanan 454 di layar 390). Namanya boleh
   menyusut (`min-width: 0`), tombolnya yang pindah baris. */
.profile-card { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.profile-card > div:not(.avatar) { min-width: 0; flex: 1; }
.profile-card .btn { margin-left: auto; }
/* Di ponsel tombolnya turun satu baris penuh. Membiarkannya "muat" di baris yang sama
   memang menghilangkan tumpahannya, tapi harganya nama & tanggal yang diperas jadi
   kolom tiga baris — yang rapi di sini adalah tombol selebar kartunya. */
@media (max-width: 600px) {
  .profile-card > div:not(.avatar) { flex: 1 1 140px; }
  .profile-card .btn { flex: 1 0 100%; margin-left: 0; text-align: center; }
}
.profile-card .avatar {
  width: 64px; height: 64px; border-radius: 10px; background: var(--bg-soft);
  display: flex; align-items: center; justify-content: center; flex: none;
}
.profile-card .avatar img { width: 48px; height: 48px; }

/* ---------- wajah & bendera: satu bentuk untuk seluruh situs -------------------------
   Kelasnya `pfp`, bukan `face`: `.face` sudah lebih dulu dipakai wajah yang bicara di
   Lawan Bot (`.bot-talk .face`) dan rook di Padepokan, dan dua aturan berbeda dengan nama
   sama adalah dua aturan yang saling menimpa di halaman yang memuat keduanya.

   Foto profil muncul di lima tempat berbeda — plakat papan di Duel Online dan Lawan Bot,
   kepala profil, daftar teman, papan peringkat — dan sebelum ini masing-masing menggambar
   kotaknya sendiri. Dirakit `avatarHtml()` di public/js/api.js; ukurannya satu variabel,
   supaya pemanggil cukup menyebut angka dan tidak perlu tahu ada border-radius di sini.

   `object-fit: cover` yang membuat fotonya "sesuai kotaknya": bagian terpanjang dipotong,
   bukan dipipihkan. Wajah yang dipipihkan adalah wajah orang lain — dan `aspect-ratio`
   saja tidak cukup, karena tanpa `cover` peramban tetap meregangkan gambarnya. */
.pfp {
  --pfp: 32px;
  width: var(--pfp); height: var(--pfp); flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden; vertical-align: middle;
  /* Kotak lembut, bukan lingkaran: sudut 30% mengikuti bentuk kartu di seluruh situs,
     dan foto persegi panjang kehilangan lebih sedikit isinya di sini daripada di dalam
     lingkaran. */
  border-radius: calc(var(--pfp) * 0.3);
  background: var(--bg-soft); border: 1px solid var(--line);
}
.pfp img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pfp-letter {
  font-weight: 800; color: var(--accent);
  font-size: calc(var(--pfp) * 0.44); line-height: 1; user-select: none;
}

/* Bendera di sebelah kanan nama. Tidak diberi latar dan tidak dikotaki: yang dibaca mata
   adalah bentuk benderanya sendiri, dan bingkai di sekelilingnya hanya membuat setiap
   baris papan peringkat terlihat seperti daftar tombol. */
/* Saklar musik di laci menu. Bentuknya mengikuti tautan menu di atasnya — ia memang
   satu baris di daftar yang sama, hanya saja yang dilakukannya bukan berpindah halaman. */
.nav-music {
  /* Di layar lebar `.nav-group { display: contents }` menumpahkan seluruh isi laci ke
     dalam bilah atas — termasuk tombol ini, lengkap dengan bingkai dan label dua
     barisnya. Di sana ia dimatikan; kabarnya pindah ke `.ticker`, dan saklarnya ada di
     pita itu. Di ponsel ia dihidupkan lagi di dalam laci (media ≤900px). */
  display: none; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: 9px; border: 1px solid transparent;
  background: none; color: inherit; font: inherit; font-weight: 600; cursor: pointer;
  text-align: left;
}
.nav-music:hover { background: var(--bg-soft); }
.nav-music .ic { width: 18px; text-align: center; color: var(--muted); }
.nav-music b { margin-left: auto; font-size: 12px; color: var(--muted); font-weight: 700; }
.nav-music.on { border-color: var(--accent); }
.nav-music.on .ic, .nav-music.on b { color: var(--accent); }

.flag {
  font-size: 1.05em; line-height: 1; margin-left: 2px;
  /* Twemoji dulu (lihat @font-face di atas) supaya Windows pun menggambar bendera, bukan
     dua huruf. Nama negaranya ada di title — muncul saat di-hover. */
  letter-spacing: 0;
  font-family: 'Twemoji Country Flags', 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
.who-name { font-size: 20px; font-weight: 700; display: flex; align-items: center; gap: 8px; }

.range-tabs, .filter-tabs, .adv-tabs { display: flex; gap: 4px; overflow-x: auto; margin: 12px 0; scrollbar-width: none; }
.range-tabs::-webkit-scrollbar, .filter-tabs::-webkit-scrollbar, .adv-tabs::-webkit-scrollbar { display: none; }

/* ---------- deretan yang bisa digeser: tanda bahwa masih ada isi di sebelah sana ------
   Dipakai bilah menu atas DAN pita tab di halaman Profil/Statistik. Kelas `over`,
   `at-start`, `at-end` dipasang wireEdgeFade() di public/js/api.js.

   Kenapa ini ada: pita yang meluber sudah bisa digeser sejak dulu, tapi tanpa penanda
   apa pun ia terbaca sebagai tulisan yang terpotong — dan tulisan terpotong terbaca
   sebagai halaman rusak, bukan sebagai undangan menggeser. Sisi yang sudah mentok tidak
   diburamkan, supaya keping pertama tidak terlihat pudar padahal tidak ada apa-apa yang
   tersembunyi di kirinya.

   scroll-snap tidak dipakai: kepingnya tidak sama lebar, dan snap pada keping yang
   lebarnya berbeda-beda justru membuat geseran terasa tersendat. */
.topbar nav.over, .range-tabs.over, .filter-tabs.over, .adv-tabs.over, .table-scroll.over, .chips.rail.over {
  mask-image: linear-gradient(90deg, transparent 0, #000 20px, #000 calc(100% - 20px), transparent 100%);
}
.topbar nav.over.at-start, .range-tabs.over.at-start, .filter-tabs.over.at-start,
.adv-tabs.over.at-start, .table-scroll.over.at-start, .chips.rail.over.at-start {
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 20px), transparent 100%);
}
.topbar nav.over.at-end, .range-tabs.over.at-end, .filter-tabs.over.at-end,
.adv-tabs.over.at-end, .table-scroll.over.at-end, .chips.rail.over.at-end {
  mask-image: linear-gradient(90deg, transparent 0, #000 20px, #000 100%);
}
.topbar nav.over.at-start.at-end, .range-tabs.over.at-start.at-end,
.filter-tabs.over.at-start.at-end, .adv-tabs.over.at-start.at-end,
.table-scroll.over.at-start.at-end, .chips.rail.over.at-start.at-end { mask-image: none; }

/* ---------- tabel lebar di dalam kartu ------------------------------------------------
   Tabel `width:100%` tetap bisa lebih lebar daripada kartunya: lebar min-content dari
   isi selnya yang menentukan, bukan angka 100% itu. Di halaman Profil selebar 390px,
   tabel Pembukaan keluar 4px dari kartunya dan menyeret SELURUH halaman ikut bergeser
   mendatar 3px — kolom "Skor" terpotong dan tidak ada cara membacanya.

   Jadi yang menggulir tabelnya sendiri, bukan halaman. `min-width: 0` wajib: tanpa itu
   pembungkus ini ikut menolak menyusut di bawah lebar min-content tabelnya, dan
   overflow-x tidak pernah menyala — jebakan yang sama persis dengan kartu .play-layout
   yang dulu meleber di ponsel. */
.table-scroll { overflow-x: auto; min-width: 0; -webkit-overflow-scrolling: touch; }
/* Sengaja TIDAK `width: max-content`. Itu melarang tulisannya dipatah, dan tabel
   Pembukaan yang tadinya 360px melar jadi 541px — kolom "Skor" jadi selalu harus
   digulir, padahal sebelumnya cuma kadang-kadang. `width:100%` bawaan tabel tetap
   berlaku: nama pembukaan yang panjang dipatah seperti biasa, dan penggulir ini baru
   menyala kalau lebar min-content-nya memang tidak muat. */
.table-scroll > table { min-width: 100%; }
.range, .adv {
  flex: none; background: transparent; color: var(--muted); border: 0;
  padding: 7px 13px; border-radius: 8px; font-weight: 600; font-size: 13px; cursor: pointer;
}
.range.active, .adv.active { background: var(--bg-card); color: var(--text); }

.total-card { display: flex; align-items: baseline; gap: 10px; }
.total-num { font-size: 34px; font-weight: 800; line-height: 1; }

.mode-list { display: flex; flex-direction: column; }
.mode-row {
  display: grid; grid-template-columns: 34px 1fr auto auto 14px;
  align-items: center; gap: 12px; padding: 13px 2px; cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.mode-row.static { cursor: default; }
.mode-row:last-child { border-bottom: 0; }
.mode-icon { font-size: 21px; text-align: center; }
.mode-name { font-weight: 600; }
.mode-name .small { font-size: 11px; font-weight: 400; }
.mode-rating { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.mode-row .chev { color: var(--muted); transition: transform .18s; }
.mode-row.open .chev { transform: rotate(90deg); }
.mode-detail { padding: 4px 2px 16px 46px; border-bottom: 1px solid var(--line); }

.delta { font-size: 13px; font-weight: 700; min-width: 68px; text-align: right; }
.delta.up { color: var(--accent); }
.delta.down { color: var(--danger); }
.delta.none { color: var(--muted); font-weight: 500; }

/* Win / draw / loss as one bar — the proportions read faster than three numbers. */
.wdl { display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin-bottom: 6px; }
.wdl .w { background: var(--accent); }
.wdl .d { background: #6b7280; }
.wdl .l { background: var(--danger); }
.wdl-legend { font-size: 12px; }

.kv { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; border-bottom: 1px solid var(--line); }
.kv:last-child { border-bottom: 0; }
table.mini { margin-top: 10px; font-size: 13px; }
.q-ok { color: var(--accent); } .q-warn { color: var(--warn); } .q-bad { color: var(--danger); }
.eco {
  font-weight: 700; font-size: 11px; color: var(--accent);
  background: var(--accent-soft); border-radius: 4px; padding: 1px 5px; margin-right: 6px;
}

/* ---------- tournaments ---------- */
.format-picker { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; margin: 10px 0; }
.format {
  text-align: left; background: var(--bg-soft); border: 1px solid var(--line); border-radius: 10px;
  padding: 11px 13px; cursor: pointer; display: flex; flex-direction: column; gap: 4px; color: var(--text);
}
.format.active { border-color: var(--accent); background: var(--accent-soft); }
.format .muted { font-size: 12px; font-weight: 400; line-height: 1.35; }
.format-note {
  font-size: 13px; color: var(--muted); background: var(--bg-soft);
  border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0; padding: 9px 12px; margin-bottom: 14px;
}
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0 14px; }

.t-card { display: block; color: var(--text); }
@media (hover: hover) { .t-card:hover { text-decoration: none; border-color: var(--accent); } }
.t-head { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
.pill {
  font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 99px;
  background: var(--bg-soft); color: var(--muted); white-space: nowrap;
}
.pill.running { background: var(--accent-soft); color: var(--accent); }
.pill.upcoming { background: rgba(91,143,209,.18); color: var(--info); }
.pill.finished { background: rgba(154,160,170,.16); color: var(--muted); }
.pill.cancelled { background: rgba(209,91,74,.18); color: var(--danger); }

.pairing {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 2px; border-bottom: 1px solid var(--line); flex-wrap: wrap;
}
.pairing:last-child { border-bottom: 0; }
.pairing.mine { background: var(--accent-soft); border-radius: 6px; padding-left: 8px; padding-right: 8px; }
.small-row { gap: 5px; }
ol.plain { margin: 0; padding-left: 20px; }
tr.me td { background: var(--accent-soft); }

/* ---------- events ---------- */
.level-card { display: flex; flex-direction: column; gap: 8px; }
.level-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.level-points { font-size: 22px; font-weight: 800; }
.eval-bar.level-bar { position: relative; width: 100%; height: 10px; border-radius: 5px; overflow: hidden; background: #e2eae5; }
.eval-bar.level-bar .fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  background: linear-gradient(90deg, var(--accent-dark), var(--accent)); transition: width .4s ease;
}
.pill.open { background: rgba(91,143,209,.18); color: var(--info); }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
ul.muted { padding-left: 18px; margin: 4px 0; }
ul.muted li { margin-bottom: 4px; }
input.gid { padding: 5px 8px; font-size: 12px; }

/* ---------- halaman event: palet gradasi ------------------------------------
   Keluhannya "terlalu polos dan membosankan", dan halaman ini memang paling rentan:
   isinya tabel, tab, dan angka — nyaris tak ada gambar. Yang ditambahkan bukan warna
   baru di mana-mana, melainkan **satu** gradasi yang dipakai berulang di tempat yang
   memang menuntut perhatian: judul halaman, cincin kartu level, batang kemajuan, tab
   yang sedang aktif, dan garis bawah tiap judul kartu.

   Semuanya dikurung di `body.events-page`. Gradasi ini milik halaman event saja —
   hijau `--accent` tetap warna merek di seluruh situs, dan menaruh gradasi di
   `:root` berarti setiap halaman lain ikut berubah tanpa diminta.

   Dua hal yang sengaja tidak ikut berubah:
   * `.q-ok` tetap hijau. Di riwayat point ia berarti "bertambah" dan lawannya merah;
     mengubahnya jadi merah muda menghapus artinya, bukan cuma warnanya.
   * Poster kepala halaman tetap emas. Ujung hangat gradasi ini (oranye ke kuning)
     sudah sekeluarga dengan emas, jadi keduanya duduk bersama tanpa bertengkar —
     dan poster itu satu-satunya bagian halaman yang memang sudah tidak polos. */
/* Ditaruh di `.container`, bukan di `body`: bilah atas, laci menu, dan bilah bawah
   adalah rangka yang sama di seluruh situs, dan menimpa `--accent` di `body` membuat
   ketiganya ikut merah muda — termasuk tautan yang sedang tidak aktif. Rangka tetap
   hijau merek; yang bergradasi hanya isi halamannya. */
.events-page .container {
  /* Bukan gradasi Instagram: titik awalnya biru langit, bukan nila, dan puncaknya
     ungu-magenta yang lebih terang — dibaca sebagai "menyala" sendiri, bukan
     kutipan lambang aplikasi lain. Ujung hangatnya sengaja berhenti di kuning
     emas supaya sekeluarga dengan judul emas dan poster di atasnya. */
  --ig: linear-gradient(135deg, #17d3ff 0%, #5b7bff 22%, #a04bff 46%, #ff3d9a 72%, #ffb648 100%);
  --accent: #e86bb4;
  --accent-dark: #ff3d9a;
  /* Emas judul: bukan satu warna, tapi tiga puncak terang di antara emas tua supaya
     hurufnya terlihat memantulkan cahaya, bukan dicat rata.

     Puncak terangnya emas muda, bukan krem: di atas kertas putih, krem adalah warna
     yang sama dengan halaman di belakangnya — judulnya berlubang di tiga tempat dan
     berhenti terbaca sebagai kata. Emas tuanya juga diturunkan sekalian, karena emas
     yang dihitung untuk latar hitam selalu satu tingkat terlalu terang di sini. */
  --gold: linear-gradient(100deg, #7a5410 0%, #b8860b 16%, #e0b93f 32%, #c9932a 48%, #7a5410 64%, #d9a441 82%, #8a5f10 100%);
}

/* Teks bergradasi. Emoji judul sengaja ditinggal di luar `span` ini: `background-clip:
   text` memotong emoji jadi siluet gradasi dan lambangnya hilang.

   Judulnya emas, bukan warna gradasi halaman: satu-satunya baris yang harus dibaca
   lebih dulu dari apa pun di halaman ini, dan emas itu warna yang sudah dipakai
   poster tepat di atasnya — dua benda paling menonjol jadi bicara satu bahasa.
   `background-size` lebih lebar dari hurufnya supaya puncak terangnya punya ruang
   untuk bergeser; tanpa itu animasinya tidak terlihat berpindah. */
.events-page .ig-text {
  background: var(--gold);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 900;
  letter-spacing: .3px;
  /* drop-shadow, bukan text-shadow: hurufnya transparan dan diisi latar, jadi
     text-shadow menggambar bayangan huruf kosong — cahayanya tidak muncul. */
  filter: drop-shadow(0 1px 1px rgba(122, 84, 16, .22));
}
@keyframes goldSheen { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
@media (prefers-reduced-motion: no-preference) {
  .events-page .ig-text { animation: goldSheen 6s ease-in-out infinite; }
}

/* Cincin gradasi setebal 1px: dua lapis latar, yang dalam dipotong di kotak padding
   dan yang luar di kotak batas — tepinya jadi gradasi tanpa satu pun elemen tambahan. */
.events-page .level-card {
  border-color: transparent;
  background-image:
    linear-gradient(var(--bg-card), var(--bg-card)),
    linear-gradient(160deg, rgba(129,52,175,.20), rgba(221,42,123,.14) 55%, rgba(245,133,41,.12)),
    var(--ig);
  background-origin: border-box;
  background-clip: padding-box, padding-box, border-box;
}
.events-page .level-points b { background: var(--ig); -webkit-background-clip: text; background-clip: text; color: transparent; }
.events-page .eval-bar.level-bar .fill { background: var(--ig); }

/* Garis pendek di bawah judul kartu — penanda yang cukup untuk memberi warna pada
   halaman tanpa membuat tiap kartu berteriak. */
.events-page .card > h3 { position: relative; padding-bottom: 9px; margin-bottom: 12px; }
.events-page .card > h3::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 46px; height: 3px; border-radius: 2px; background: var(--ig);
}

/* Tab aktif jadi pil bergradasi; yang tidak aktif tetap datar supaya yang aktif
   benar-benar terbaca sebagai satu-satunya yang menyala. */
.events-page .range.active {
  background: var(--ig); color: #fff; box-shadow: 0 2px 10px rgba(160,75,255,.35);
}

/* Kepala tabel: garis gradasi di bawahnya, bukan hurufnya yang diwarnai — huruf
   kapital kecil bergradasi kehilangan kontras justru di ukuran itu. */
.events-page table tr:first-child th { border-bottom: 2px solid transparent; background-image: var(--ig); background-repeat: no-repeat; background-position: bottom; background-size: 100% 2px; }
.events-page tr.me td { background: rgba(255,61,154,.12); }

.events-page .container a { color: var(--accent); }
/* Ditulis sesudah aturan tautan di atas dan sama-sama menyebut `.container`: tombol
   di halaman ini berupa `<a class="btn">`, jadi warna tautan merah muda tadi akan
   menang atas `color:#fff` yang selektornya lebih ringan — dan tulisan tombol hilang
   ke dalam gradasinya sendiri. */
.events-page .container .btn, .events-page .container button.btn {
  background: var(--ig); color: #fff; border: 0;
}
.events-page .container .btn:hover { background: var(--ig); filter: brightness(1.08); }
/* Kartu isi halaman event: abu-abu bergradasi, bukan abu-abu rata. Bedanya kecil
   (tiga langkah dalam satu keluarga warna) dan itu memang maksudnya — kartunya
   terlihat punya bidang atas dan bawah tanpa satu pun garis tambahan.

   `.level-card` dikecualikan: latarnya sudah tiga lapis untuk membuat cincin
   gradasi setebal 1px, dan menimpa `background` di sana menghapus cincinnya.
   Aturannya juga dibatasi ke anak langsung `.play-layout > .grid` supaya
   `.t-card` — kartu event yang bersarang di dalam salah satu kartu ini — tetap
   memakai warna datarnya dan masih terbaca sebagai benda terpisah di atasnya. */
.events-page .play-layout > .grid > .card:not(.level-card) {
  position: relative;
  overflow: hidden;
  background: linear-gradient(160deg, #ffffff 0%, #f7faf8 46%, #eef4f0 100%);
  border-color: #dde6e0;
}

/* Bidak sebagai pemanis, bukan sebagai gambar: sudah diburamkan dan tinggal 7%
   kelamnya, jadi yang tersisa hanya bayangan bentuk di pojok. Dipasang lewat
   ::before dan pointer-events:none supaya tidak satu pun tabel atau tombol di
   dalam kartu tertutup olehnya. */
.events-page .play-layout > .grid > .card:not(.level-card)::before {
  content: "";
  position: absolute; right: -22px; bottom: -30px;
  width: 168px; height: 168px;
  background: no-repeat center / contain;
  background-image: url("/img/pieces/wn.svg");
  filter: blur(3px) grayscale(1) brightness(.35);
  opacity: .07;
  pointer-events: none;
  z-index: 0;
}
/* Isinya diangkat ke atas lapisan bidak. Tanpa ini bidak yang digambar sesudah
   latar kartu akan menutupi baris tabel pertama, bukan duduk di belakangnya. */
.events-page .play-layout > .grid > .card:not(.level-card) > * { position: relative; z-index: 1; }

/* Satu bidak berbeda per kartu — empat kartu, empat siluet, jadi pojoknya tidak
   terlihat seperti cap yang sama diulang empat kali. */
.events-page .play-layout > .grid:nth-of-type(1) > .card:nth-of-type(2)::before { background-image: url("/img/pieces/wq.svg"); }
.events-page .play-layout > .grid:nth-of-type(2) > .card:nth-of-type(1)::before { background-image: url("/img/pieces/wk.svg"); }
.events-page .play-layout > .grid:nth-of-type(2) > .card:nth-of-type(2)::before { background-image: url("/img/pieces/wr.svg"); }

/* Hijau "bertambah" tidak ikut diretina — lihat catatan di atas. */
.events-page .q-ok { color: var(--accent); }
@media (hover: hover) { .events-page .t-card:hover { border-color: #ff3d9a; } }

/* ---------- leaderboard ---------- */
.podium { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; align-items: end; margin-bottom: 14px; }
.podium .slot {
  background: var(--bg-card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 10px; text-align: center; display: flex; flex-direction: column; gap: 3px;
}
.podium .slot.p1 { padding: 20px 10px; }
.podium .slot.mine { box-shadow: inset 0 0 0 1px var(--accent); }
.podium .medal { line-height: 0; }
.podium .who { font-weight: 700; overflow-wrap: anywhere; }
.podium .pts { font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums; }
.podium .slot.p1 .pts { font-size: 26px; }
@media (max-width: 560px) { .podium { grid-template-columns: 1fr; } .podium .slot.p1 { order: -1; } }

.table-wrap { overflow-x: auto; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.rank-cell { font-variant-numeric: tabular-nums; white-space: nowrap; }
td.medals { white-space: nowrap; }

/* Where the points came from, as one bar: rank prize, showing up, opening bonus. */
.pt-bar { display: flex; height: 5px; border-radius: 3px; overflow: hidden; background: var(--bg-soft); margin-top: 5px; max-width: 180px; }
.pt-bar .s1 { background: var(--accent); }
.pt-bar .s2 { background: var(--info); }
.pt-bar .s3 { background: var(--warn); }
.pt-legend { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
.pt-legend i.sw { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 5px; }
.pt-legend i.s1 { background: var(--accent); }
.pt-legend i.s2 { background: var(--info); }
.pt-legend i.s3 { background: var(--warn); }
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }

/* ---------- leaderboard, the bright version ---------------------------------
   The event ladder has to look like something worth entering, so this page runs
   warmer than the rest of the app: a gradient header, a colour per level, and a
   real podium. The colours carry meaning — each level keeps its hue everywhere it
   appears — so the brightness is doing work rather than just shouting.          */
.lb-hero {
  position: relative; overflow: hidden; border-radius: 16px; padding: 22px 20px; margin-bottom: 14px;
  background: linear-gradient(120deg, #7c3aed 0%, #db2777 45%, #f59e0b 100%);
  color: #fff; box-shadow: 0 10px 30px rgba(219,39,119,.25);
}
.lb-hero h1, .lb-hero h2 { margin: 0 0 4px; font-size: 24px; display: flex; align-items: center; gap: 9px; }
.lb-hero p { margin: 0; opacity: .92; font-size: 14px; }
.lb-hero .sparkle {
  position: absolute; right: -18px; top: -22px; line-height: 0; opacity: .2;
  transform: rotate(-12deg); pointer-events: none;
}
.lb-hero .cta {
  display: inline-block; margin-top: 12px; background: rgba(255,255,255,.18); color: #fff;
  border: 1px solid rgba(255,255,255,.45); padding: 7px 14px; border-radius: 99px; font-weight: 700; font-size: 14px;
}
@media (hover: hover) { .lb-hero .cta:hover { background: rgba(255,255,255,.3); text-decoration: none; } }

.lvl { --c: var(--muted); }
.lvl-beginner     { --c: #34d399; }
.lvl-rookie       { --c: #38bdf8; }
.lvl-challenger   { --c: #fb923c; }
.lvl-intermediate { --c: #c084fc; }
.lvl-master       { --c: #fbbf24; }
.lvl-badge {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 700;
  padding: 3px 9px; border-radius: 99px; white-space: nowrap;
  color: var(--c); background: color-mix(in srgb, var(--c) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--c) 40%, transparent);
}

.podium .slot { position: relative; }

.podium .crown {
  position: absolute; top: -14px; left: 50%; transform: translateX(-50%);
  line-height: 0; color: #fbbf24;
}

tr.lb-row td { transition: background .15s; }
@media (hover: hover) { tr.lb-row:hover td { background: var(--bg-soft); } }
tr.lb-row.top td { background: rgba(251,191,36,.07); }
.rank-pill {
  display: inline-flex; align-items: center; justify-content: center; min-width: 26px; height: 26px;
  border-radius: 8px; font-weight: 800; font-size: 13px; background: var(--bg-soft); color: var(--muted);
}
.rank-pill.g1 { background: linear-gradient(140deg, #fbbf24, #f59e0b); color: #3a2a05; }
.rank-pill.g2 { background: linear-gradient(140deg, #e2e8f0, #94a3b8); color: #1f2937; }
.rank-pill.g3 { background: linear-gradient(140deg, #f0a868, #c2703a); color: #2b1608; }
.pts-cell { font-size: 16px; font-weight: 800; color: var(--accent); font-variant-numeric: tabular-nums; }
.streak-tag { font-size: 11px; font-weight: 700; color: #fb923c; }
.mine-card {
  background: linear-gradient(120deg, rgba(124,58,237,.18), rgba(56,189,248,.12));
  border: 1px solid rgba(124,58,237,.45);
}
.scope-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.scope-tabs .range { border-radius: 99px; padding: 7px 14px; font-weight: 600; }
.scope-tabs .range.active { background: linear-gradient(120deg, #7c3aed, #db2777); color: #fff; }

/* ---------- area picker ---------- */
.modal-back {
  position: fixed; inset: 0; background: rgba(8,9,12,.72); z-index: 120;
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.modal { width: min(460px, 100%); max-height: 88vh; overflow-y: auto; box-shadow: var(--shadow); }
.loc-row { display: flex; flex-direction: column; gap: 5px; margin-top: 11px; font-size: 13px; font-weight: 600; }
.loc-row select { width: 100%; }
.loc-note { font-size: 12px; color: var(--muted); }

/* ---------- master opponents ---------- */
.master-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 10px; }

/* ---------- lawan bot: pilih lawan dulu, lalu papan ----------------------------
   The opponent picker sits above the board because choosing who to play is the
   first thing anyone does here. On a phone that would be a wall of faces to scroll
   past, so there the grid becomes one swipeable row — the board stays a thumb-flick
   away, and the whole roster is still reachable. */
.opponent-pick { margin-bottom: 14px; }
.opponent-pick .pick-levels { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
.opponent-pick .pick-levels .row { margin-top: 10px; }
label.inline { display: inline-block; margin: 0; }
select.narrow { width: auto; min-width: 110px; }

@media (max-width: 900px) {
  .opponent-pick .master-grid {
    display: flex; overflow-x: auto; gap: 8px; padding-bottom: 6px;
    scroll-snap-type: x proximity; scrollbar-width: none;
  }
  .opponent-pick .master-grid::-webkit-scrollbar { display: none; }
  .opponent-pick .master-card { flex: 0 0 128px; scroll-snap-align: start; }
}

/* ---------- scrollbar --------------------------------------------------------
   Scrollbar bawaan peramban di Windows berwarna putih-abu terang: di halaman
   segelap ini ia jadi garis paling mencolok di layar, padahal ia hanya penunjuk
   posisi. Diwarnai ulang mengikuti tema — batang membulat, tanpa garis tepi, dan
   sedikit lebih terang saat disentuh kursor supaya tetap terasa bisa ditarik.

   Dua sintaks yang berbeda dan keduanya perlu: `scrollbar-color` untuk Firefox,
   `::-webkit-scrollbar` untuk Chrome, Edge, dan Safari. */
* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) var(--scroll-track); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--scroll-track); border-radius: 8px; }
::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb); border-radius: 8px;
  border: 2px solid var(--scroll-track);   /* jarak tipis, supaya batangnya tidak menempel di tepi */
}
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); }
::-webkit-scrollbar-corner { background: var(--scroll-track); }

/* ---------- lawan bot di layar laptop (11-16 inci) -----------------------------
   Papan di kiri, pemilih lawan di kanan — susunan yang diminta ridwanyas (2026-08-23),
   dan yang sama dengan papan catur online mana pun.

   Sebelumnya pemilih lawan membentang selebar halaman di atas papan. Di layar 1366x768
   itu berarti papan mulai pada 870px: seorang pemain membuka halaman permainan dan
   tidak melihat satu petak pun sebelum menggulir. Kartunya juga tidak perlu selebar
   itu — delapan wajah dalam satu baris hanyalah delapan wajah yang jarang diganti. */
@media (min-width: 901px) {
  /* Papan mengikuti tinggi layar, bukan hanya lebar kolomnya. Angka 260px adalah
     bar keunggulan + dua baris pemain + tombol di bawah papan; di layar tinggi
     max-width 620px tetap yang menang. */
  /* Satu lebar untuk seluruh kolom kiri: papan, nama pemain, bar keunggulan, dan
     tombol-tombolnya. Sebelumnya hanya papan yang dibatasi, jadi baris nama
     membentang 751px di atas papan selebar 468px — mata langsung menangkap dua tepi
     yang tidak sejajar, dan itu yang membuat halaman terasa berantakan. */
  /* 700px, bukan 620: di layar 15-16 inci sisa ruangnya memang ada, dan papan yang
     lebih besar adalah alasan orang membuka halaman ini. Yang dikurangi dari tinggi
     layar dipangkas jadi 246px dengan merapatkan bar keunggulan, baris nama, dan
     tombol — bukan dengan membuang satu pun dari mereka. */
  .play-page { --board-w: min(700px, calc(100vh - 256px)); }

  /* Ruang mati di kolom kiri: setiap piksel di sini langsung jadi papan yang lebih besar. */
  .play-page .eval-head { margin-bottom: 6px; }
  .play-page .eval-head .eval-row { margin-top: 4px; }
  .play-page .player-row { padding: 5px 12px; }
  .play-page .board-wrap { margin: 6px auto !important; }
  .play-page .board-actions { margin-top: 8px; }
  .play-page .board-actions button { min-height: 46px; }
  .play-page .board-wrap,
  .play-page .eval-head,
  .play-page .player-row,
  .play-page .board-actions,
  .play-page #themeQuick,
  .play-page #status { max-width: var(--board-w); margin-left: auto; margin-right: auto; }

  /* Kolom kanan menggulir di dalam dirinya sendiri, bukan memanjangkan halaman.
     Tanpa ini tinggi halaman ditentukan oleh kolom yang lebih panjang — dan yang
     lebih panjang adalah daftar lawan, bukan papan; itulah sebabnya tombol di bawah
     papan tetap jatuh di luar layar meski papannya sendiri sudah muat. */
  .play-page .play-side {
    position: sticky; top: 12px;
    max-height: calc(100vh - 84px); overflow-y: auto; padding-right: 2px;
  }

  .play-page .opponent-pick { margin-bottom: 0; }
  /* Wajah dibuat lebih rapat: di kolom selebar 380px, kartu 132px hanya muat dua per
     baris dan sisanya jadi gulungan panjang. */
  .play-page .opponent-pick .master-grid {
    grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 8px;
    max-height: 30vh; overflow-y: auto; padding-right: 2px;
  }
  .play-page .opponent-pick .master-card { padding: 8px 5px 7px; border-radius: 11px; }
  .play-page .opponent-pick .master-card img { width: 42px; height: 42px; }
  .play-page .opponent-pick .master-card .m-name { font-size: 12px; margin-top: 2px; line-height: 1.15; }
  .play-page .opponent-pick .master-card .m-elo { font-size: 12px; }
  /* Tahun Elo dan puncaknya adalah keterangan untuk dibaca sekali, bukan tiap kali
     memilih lawan — di kolom sempit ia menghabiskan tiga baris per kartu. */
  .play-page .opponent-pick .master-card .m-elo-note { display: none; }
  .play-page .opponent-pick .pick-levels .row { flex-wrap: wrap; }
}

/* The three buttons a player reaches for mid-game, directly under the board rather
   than in a card below the move list — on a phone that card was a scroll away. */
.board-actions {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  max-width: 620px; margin: 10px auto 0;
}
.board-actions button {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 8px 6px; min-height: 52px; font-size: 12.5px; font-weight: 600; line-height: 1.2;
}
.board-actions button .ic { font-size: 17px; line-height: 1; }
#status { max-width: 620px; margin: 0 auto; }
.master-card {
  display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 8px 10px; color: var(--text); cursor: pointer; transition: border-color .15s, transform .15s;
}
.master-card img { border-radius: 50%; display: block; }
.master-card .m-name { font-weight: 700; font-size: 14px; margin-top: 4px; }
.master-card .m-elo { font-size: 13px; font-weight: 800; color: var(--warn); font-variant-numeric: tabular-nums; }
.master-card .m-elo-note { font-size: 10.5px; }
.master-card.active { border-color: var(--accent); background: var(--accent-soft); transform: translateY(-2px); }
@media (hover: hover) { .master-card:hover { border-color: var(--accent); } }

.master-detail {
  margin-top: 12px; padding: 11px 12px; border-radius: 12px;
  background: var(--bg-soft); border: 1px solid var(--line);
}
.master-detail ul { padding-left: 18px; }
.move-source {
  margin-top: 8px; padding: 8px 10px; border-radius: 9px; font-size: 12.5px;
  background: rgba(91,143,209,.12); border-left: 3px solid var(--info); color: var(--text);
}

/* ---------- profile: the page one player sends to another ----------------------
   Halaman yang dikirim seorang pemain ke pemain lain, jadi ia harus terbaca seperti
   kartu nama: siapa ini, seberapa sering main, seberapa sering menang, dan cara
   mainnya seperti apa. Sampai 11 September 2026 kepalanya oranye-kuning — peninggalan
   masa situs ini masih gelap seluruhnya; sekarang biru-putih. */
/* ---------- kepala profil ----------

   Bentuknya kartu profil jejaring profesional: spanduk biru selebar kartu, foto bulat
   yang menimpa batas bawahnya, lalu nama dan angka di atas kertas putih. Permintaan
   ridwanyas 11 September 2026 ("konsep biru LinkedIn"), dengan palet yang ia sebut
   sendiri: putih, hitam, biru, hijau, ungu, emas.

   Kenapa biru boleh ada di situs yang hijau: biru di sini tidak pernah jadi tombol dan
   tidak pernah jadi tautan — ia hanya bidang spanduk di satu kartu. Hijau merek tetap
   memegang semua ajakan bertindak, ungu tetap berarti Pro, emas tetap berarti Premium.
   Kalau suatu hari biru ini merambat ke tombol, dua warna akan berebut arti "tekan
   saya", dan yang kalah adalah hijaunya. */
.profile-hero {
  /* Tiga warna cincin, ditaruh di sini supaya penanda titik di keterangan dan busur
     donatnya mustahil berbeda. Di atas kertas putih hijau-emas-batu terbaca jelas;
     jingga lama dulu dipilih untuk latar jingga yang sudah tidak ada lagi. */
  --dial-win: #14804a;
  --dial-draw: #d98324;
  --dial-loss: #64748b;
  --hero-blue: #0a66c2;
  --hero-blue-deep: #07356b;
  --hero-ink: #0f1c2e;
  --hero-ink-soft: #5a6b80;
  --hero-line: #e4ecf5;

  position: relative; overflow: hidden; color: var(--hero-ink);
  /* Padding dibuang dari kartunya dan dipindah ke `.ph-body`: spanduknya harus menempel
     ke tiga sisi kartu, dan spanduk yang menyisakan jalur putih di kiri-kanannya
     terbaca sebagai gambar yang gagal memuat, bukan sebagai spanduk. */
  padding: 0;
  border: 1px solid var(--hero-line);
  background: var(--bg-card);
  box-shadow: 0 14px 34px rgba(10, 102, 194, .14);
  display: flex; flex-direction: column;
}

.ph-cover {
  /* Tiga cincin kecil duduk DI DALAM spanduk, menumpuk di pojok kanan (revisi ridwanyas
     11 Sep 2026: "maks 70px, tidak perlu satu baris dibagi tiga, taruh di pojok kanan").
     Spanduknya sendiri kembali setinggi semula. */
  position: relative; height: 118px;
  background:
    radial-gradient(120% 160% at 82% -30%, rgba(255, 255, 255, .34) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(135deg, var(--hero-blue) 0%, #0b5aab 52%, var(--hero-blue-deep) 100%);
}
/* Petak papan samar di dalam spanduk. Ia ada supaya kartu ini tidak bisa tertukar
   dengan kartu profil aplikasi mana pun, dan cukup samar agar nama di depannya tetap
   jadi hal pertama yang terbaca. */
.ph-cover::before {
  content: ''; position: absolute; inset: 0;
  background-image:
    linear-gradient(45deg, rgba(255, 255, 255, .10) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .10) 75%),
    linear-gradient(45deg, rgba(255, 255, 255, .10) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .10) 75%);
  background-size: 46px 46px;
  background-position: 0 0, 23px 23px;
  opacity: .5;
}
/* Kuda putih di ujung kanan spanduk, setengah keluar bingkai. */
.ph-cover::after {
  content: ''; position: absolute; right: -14px; bottom: -26px; width: 148px; height: 148px;
  background: url(/img/pieces/wn.svg) center/contain no-repeat;
  opacity: .16; pointer-events: none;
}
.ph-body {
  position: relative; z-index: 1;
  /* Jalur 12px di atas nama: tanpa itu angka rating berdiri menempel pada tepi bawah
     spanduk dan terbaca seolah ia bagian dari bidang birunya. Fotonya menutup jalur itu
     kembali lewat margin negatifnya sendiri. */
  padding: 12px 18px 18px; display: flex; flex-direction: column; gap: 16px;
}
.dial-defs { position: absolute; width: 0; height: 0; }

/* Foto menimpa spanduk, seperti kartu profesional pada umumnya: setengah di biru,
   setengah di putih.

   Yang naik ke atas spanduk **hanya fotonya**, bukan seluruh barisnya. Percobaan
   pertama menaikkan `.hero-top` seluruhnya, dan akibatnya angka rating ikut naik ke
   dalam bidang biru: tulisan gelap di atas biru tua, terbaca seperti kesalahan cetak.
   Nama dan rating tetap duduk di kertas putih. */
.hero-top { display: flex; align-items: flex-end; gap: 14px; }
.profile-hero .hero-piece {
  margin-top: -60px;
  flex: none; width: 96px; height: 96px; border-radius: 50%; display: grid; place-items: center;
  background: linear-gradient(180deg, #ffffff, #eef4fb);
  box-shadow: 0 8px 22px rgba(8, 53, 107, .28), 0 0 0 4px #ffffff;
}
.profile-hero .hero-piece img { width: 58px; height: 58px; display: block; }
/* Foto pemain memenuhi kotaknya, bidak tidak: bidak adalah ikon di atas latar terang
   dan kehilangan artinya kalau dipotong, sedangkan foto orang yang tidak memenuhi
   lingkarannya akan terlihat seperti gambar yang gagal dimuat. */
.profile-hero .hero-piece.hero-face { overflow: hidden; }
.profile-hero .hero-piece.hero-face img {
  width: 100%; height: 100%; object-fit: cover; border-radius: inherit;
}

/* Wajah lawan buatan: bidak kuda di atas latar, jadi ia harus utuh — bukan dipotong
   seperti foto orang. */
.pfp-bot img { object-fit: contain; padding: 3px; }
.hero-id { min-width: 0; padding-bottom: 2px; }
.hero-name {
  font-size: 22px; font-weight: 800; color: var(--hero-ink);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
/* Bendera di samping nama BERKIBAR (ridwanyas 11 Sep 2026): emoji tidak bisa beriak,
   jadi yang digerakkan bidangnya — berputar tipis pada sumbu tegak dengan sedikit miring,
   bertumpu pada tiangnya (sisi kiri). Hanya di kepala profil; di daftar & papan bendera
   tetap diam. */
.hero-name .flag {
  display: inline-block; transform-origin: 8% 55%; perspective: 120px;
  animation: flag-wave 2.2s ease-in-out infinite;
}
@keyframes flag-wave {
  0%, 100% { transform: rotateY(0deg) skewY(0deg); }
  25%      { transform: rotateY(-22deg) skewY(4deg); }
  50%      { transform: rotateY(0deg) skewY(0deg); }
  75%      { transform: rotateY(22deg) skewY(-4deg); }
}
@media (prefers-reduced-motion: reduce) { .hero-name .flag { animation: none; } }
.hero-sub { font-size: 13px; font-weight: 600; color: var(--hero-ink-soft); }
.hero-friend { margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; }
.hero-rating { margin-left: auto; text-align: right; padding-bottom: 2px; }
.hero-rating-num { font-size: 34px; font-weight: 800; line-height: 1; color: var(--hero-blue); }
.hero-rating-lbl { font-size: 12px; font-weight: 600; color: var(--hero-ink-soft); }

/* Panah naik. Bentuknya garis yang memanjat, bukan segitiga: yang ingin disampaikan
   adalah arah perjalanan rating, bukan sekadar tanda tambah. Hijau untuk naik, karena
   di kartu ini hijau memang berarti "bertambah" — sama dengan warna kemenangan di
   cincin di bawahnya. */
.up-chip {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  padding: 4px 10px 4px 8px; border-radius: 999px; font-size: 13px; font-weight: 800; white-space: nowrap;
  background: var(--accent-soft); color: var(--accent-dark); box-shadow: 0 3px 10px rgba(20, 128, 74, .16);
}
.up-chip.flat { background: #eef2f7; color: #566579; box-shadow: none; }
.up-ic { width: 15px; height: 15px; flex: none; }
.up-sub { font-size: 11px; font-weight: 600; color: inherit; opacity: .78; }

/* ---------- tiga cincin ---------- */
.dials { position: absolute; z-index: 1; right: 14px; top: 14px; display: flex; gap: 8px; }
/* Ubin 70×70 biru tua di atas spanduk biru, dengan percikan kembang api biru yang terus
   bergerak (::before & ::after: titik-titik radial yang mengembang dari pusat lalu
   memudar, dua lapis dengan tempo berbeda supaya tidak terasa berulang). Revisi
   ridwanyas 11 Sep 2026: "biru tua dan tidak polos, ada sesuatu yang hidup". */
.dial {
  position: relative; cursor: pointer; outline: none; overflow: visible;
  width: 70px; height: 70px; border-radius: 14px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  background: radial-gradient(120% 120% at 30% 20%, #0c4a91 0%, #072d5e 55%, #041d40 100%);
  border: 1px solid rgba(147, 197, 253, .35);
  box-shadow: 0 6px 16px rgba(2, 20, 48, .45), inset 0 1px 0 rgba(255, 255, 255, .12);
  animation: dial-in .45s cubic-bezier(.2, .8, .3, 1) both;
  transition: transform .18s ease, box-shadow .18s ease;
}
.dial::before, .dial::after {
  content: ''; position: absolute; inset: 0; border-radius: 14px; pointer-events: none;
  background-image:
    radial-gradient(circle, rgba(191, 219, 254, .95) 0 1px, transparent 1.6px),
    radial-gradient(circle, rgba(96, 165, 250, .9) 0 1.2px, transparent 1.8px),
    radial-gradient(circle, rgba(255, 255, 255, .85) 0 .8px, transparent 1.4px),
    radial-gradient(circle, rgba(147, 197, 253, .9) 0 1px, transparent 1.6px);
  background-size: 18px 18px, 26px 26px, 14px 14px, 22px 22px;
  background-position: 3px 5px, 9px 12px, 6px 2px, 14px 8px;
  transform-origin: 50% 50%;
  animation: dial-spark 2.6s ease-out infinite;
  opacity: 0;
}
.dial::after { animation-duration: 3.4s; animation-delay: -1.7s; background-size: 24px 24px, 16px 16px, 20px 20px, 30px 30px; }
.dial:nth-child(2)::before { animation-delay: -.9s; }
.dial:nth-child(3)::before { animation-delay: -1.8s; }
@keyframes dial-spark {
  0%   { transform: scale(.25); opacity: 0; }
  25%  { opacity: .9; }
  100% { transform: scale(1.35); opacity: 0; }
}
.dial:hover, .dial.open, .dial:focus-visible {
  transform: translateY(-2px); box-shadow: 0 10px 22px rgba(2, 20, 48, .55), inset 0 1px 0 rgba(255, 255, 255, .16);
}
/* Rincian di balik cincin: muncul saat disorot, terkunci dengan klik (.open). Semua
   popup ditambatkan ke tepi KANAN ubinnya — ubinnya sendiri sudah di pojok kanan, jadi
   ke kiri lah satu-satunya arah yang tidak keluar dari kartu (`overflow: hidden`). */
.dial-pop {
  position: absolute; top: calc(100% + 8px); right: 0;
  width: 270px; max-width: calc(100vw - 48px); text-align: left; cursor: default;
  background: #fff; color: var(--hero-ink); border: 1px solid var(--hero-line);
  border-radius: 12px; padding: 12px 14px; box-shadow: 0 16px 36px rgba(7, 53, 107, .28);
  opacity: 0; visibility: hidden; transition: opacity .15s ease, visibility .15s;
  z-index: 6; font-size: 12.5px; line-height: 1.45;
}
.dial:hover .dial-pop, .dial.open .dial-pop, .dial:focus-visible .dial-pop { opacity: 1; visibility: visible; }
/* Popup-nya menjulur ke bawah spanduk, ke wilayah `.ph-body` yang z-index-nya 1 —
   selama satu cincin disorot, spanduk naik ke atas badannya. */
.ph-cover:has(.dial:hover, .dial.open, .dial:focus-visible) { z-index: 3; }
.dial-pop-title { font-size: 13px; font-weight: 800; margin-bottom: 6px; }
.dial-pop-row { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; border-bottom: 1px dashed var(--hero-line); }
.dial-pop-row:last-child { border-bottom: 0; }
.dial-pop-row span { color: var(--hero-ink-soft); font-weight: 600; }
.dial-pop-row b { font-weight: 800; white-space: nowrap; }
.dial-pop-sep { height: 6px; }
.dial-pop p { margin: 6px 0 0; color: var(--hero-ink-soft); font-size: 11.5px; }
.dial:nth-child(2) { animation-delay: .07s; }
.dial:nth-child(3) { animation-delay: .14s; }
@keyframes dial-in { from { opacity: 0; transform: translateY(10px) scale(.96); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .dial { animation: none; }
  .dial::before, .dial::after { animation: none; opacity: .5; transform: none; }
}

.dial-ring { position: relative; z-index: 1; width: 42px; height: 42px; }
.dial-ring svg { display: block; width: 100%; height: 100%; }
.dial-track { fill: none; stroke: rgba(255, 255, 255, .14); stroke-width: 14; }
.dial-arc { fill: none; stroke-width: 14; stroke-linecap: round; }
.dial-mid { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 0; }
.dial-mid b { font-size: 13px; font-weight: 800; line-height: 1; color: #fff; }
.dial-mid i { font-style: normal; font-size: 8px; font-weight: 700; color: #bfdbfe; }
.dial-title {
  position: relative; z-index: 1; font-size: 9px; font-weight: 700; line-height: 1;
  color: #bfdbfe; letter-spacing: .02em; white-space: nowrap;
}
/* Keterangan panjang di bawah cincin tidak muat di 70px — isinya pindah ke popup. */
.dial-note { display: none; }
/* Namanya `dial-dot`, bukan `dot`: `.dot` sudah dipakai papan untuk titik tujuan
   langkah dan titik itu `position: absolute` — dipinjam ke sini, ia menumpuk di atas
   angkanya sendiri. */
.dial-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin: 0 3px 0 7px; vertical-align: 1px;
}
.dial-leg { display: inline-block; white-space: nowrap; }
.dial-leg + .dial-leg { margin-left: 5px; }
.dial-note .dial-leg:first-child .dial-dot { margin-left: 0; }
.dial-dot.win { background: var(--dial-win); }
.dial-dot.draw { background: var(--dial-draw); }
.dial-dot.loss { background: var(--dial-loss); }

/* Tablet & laptop (≥ 768px): ubin 90px di KIRI spanduk (revisi ridwanyas 11 Sep 2026:
   "iPad / di atas 10 inci di kiri, 90px"); di HP tetap di kanan (70/58px). Spanduknya
   ditinggikan supaya foto yang naik 48px dari bawah tidak menabrak ubin di atasnya:
   ubin berakhir di 14+90=104, foto mulai di 166−48=118. */
@media (min-width: 768px) {
  .ph-cover { height: 166px; }
  .dials { right: auto; left: 18px; gap: 10px; }
  .dial { width: 90px; height: 90px; border-radius: 16px; gap: 3px; }
  .dial::before, .dial::after { border-radius: 16px; }
  .dial-ring { width: 54px; height: 54px; }
  .dial-mid b { font-size: 16px; }
  .dial-mid i { font-size: 9px; }
  .dial-title { font-size: 10.5px; }
  .dial-pop { right: auto; left: 0; }
}

/* "Stats Diagram" — radar enam sumbu, di kepala profil tepat di bawah nama (urutan dari
   ridwanyas 11 Sep 2026). Lipatan di bawahnya memuat rincian sumbu yang punya komponen. */
.hero-stats { border-top: 1px solid var(--hero-line); padding-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.dna-fold { border: 1px solid var(--hero-line); border-radius: 12px; padding: 8px 12px; background: #f7fafd; }
.dna-fold summary { cursor: pointer; font-size: 12.5px; font-weight: 700; color: var(--hero-blue); list-style: none; }
.dna-fold summary::before { content: '▸ '; }
.dna-fold[open] summary::before { content: '▾ '; }
.dna-fold .dna-list { margin-top: 10px; }

/* Rating tertinggi · point · lencana · podium — tiap angka jadi kartu kecil berwarna
   dengan ikon di lingkaran tint dan pola geometris samar di latarnya (revisi ridwanyas
   11 Sep 2026: "tulisannya polos banget, kasih warna beda & sedikit background"). */
.hero-figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;
  border-top: 1px solid var(--hero-line); padding-top: 12px;
}
.fig {
  --fig: #0a66c2; --fig-soft: #e8f1fc; --fig-deep: #07356b;
  position: relative; overflow: hidden; display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 14px; border: 1px solid var(--hero-line);
  background:
    repeating-linear-gradient(135deg, transparent 0 9px, color-mix(in srgb, var(--fig) 6%, transparent) 9px 10px),
    linear-gradient(135deg, var(--fig-soft), #fff 70%);
}
.fig::after {
  /* Belah ketupat samar di sudut kanan — hiasan geometris, bukan tombol. */
  content: ''; position: absolute; right: -14px; top: -14px; width: 46px; height: 46px;
  background: var(--fig); opacity: .08; transform: rotate(45deg); border-radius: 8px;
}
.fig.gold { --fig: #b7791f; --fig-soft: #fff4d6; --fig-deep: #7c4a03; }
.fig.green { --fig: #14804a; --fig-soft: #e3f5ea; --fig-deep: #0b4f2c; }
.fig.purple { --fig: #7c3aed; --fig-soft: #efe6ff; --fig-deep: #4c1d95; }
.fig-ic {
  flex: none; width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
  font-size: 19px; line-height: 1; background: #fff; box-shadow: 0 2px 8px color-mix(in srgb, var(--fig) 28%, transparent);
}
.fig > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fig b { font-size: 19px; line-height: 1.1; color: var(--fig-deep); }
.fig b small { font-size: 12px; font-weight: 700; color: var(--fig); }
.fig span { font-size: 12px; font-weight: 700; color: var(--fig); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Menang · remis · kalah: satu bilah tiga warna + tiga angka, kembaran kartu "Catatan"
   di aplikasi. */
/* Judulnya pita hijau; latarnya hijau muda dengan daun & hati samar (SVG data URI) di
   sisi kanan — hiasan, dan sengaja pudar supaya angkanya tetap yang pertama terbaca. */
.hero-record {
  position: relative; overflow: hidden; border-top: 1px solid var(--hero-line); padding: 12px 14px 12px;
  display: flex; flex-direction: column; gap: 8px; border-radius: 14px;
  background: linear-gradient(120deg, #eef8f1 0%, #fff 60%);
}
.hero-record::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='72' height='72' viewBox='0 0 72 72'><path d='M14 44c0-10 8-18 20-18-2 12-9 18-20 18z' fill='%2314804a' opacity='.16'/><path d='M50 24c2-4 8-4 8 1 0 4-8 9-8 9s-8-5-8-9c0-5 6-5 8-1z' fill='%23e05a7a' opacity='.16'/></svg>");
  background-size: 72px 72px; background-position: right top;
  -webkit-mask-image: linear-gradient(90deg, transparent 35%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 35%, #000 100%);
}
.hero-record > * { position: relative; }
.hero-record-head { display: flex; justify-content: space-between; align-items: center; font-size: 12.5px; font-weight: 700; color: var(--hero-ink-soft); }
.hero-record-head b { color: var(--hero-ink); font-size: 13px; }
.pita {
  display: inline-block; padding: 4px 14px; color: #fff; font-weight: 800; font-size: 12px; letter-spacing: .02em;
  background: linear-gradient(90deg, #14804a, #1ea15f);
  clip-path: polygon(0 0, 100% 0, calc(100% - 7px) 50%, 100% 100%, 0 100%, 7px 50%);
}
.hero-record-tally i { display: block; font-style: normal; font-size: 16px; line-height: 1; margin-bottom: 2px; }
.hero-record-bar { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--hero-line); }
.hero-record-bar i { display: block; }
.hero-record-bar .win { background: var(--dial-win); }
.hero-record-bar .draw { background: var(--dial-draw); }
.hero-record-bar .loss { background: var(--dial-loss); }
.hero-record-tally { display: grid; grid-template-columns: repeat(3, 1fr); text-align: center; }
.hero-record-tally b { display: block; font-size: 18px; font-weight: 800; }
.hero-record-tally .win { color: var(--dial-win); }
.hero-record-tally .draw { color: var(--dial-draw); }
.hero-record-tally .loss { color: var(--dial-loss); }
.hero-record-tally span { font-size: 11.5px; font-weight: 600; color: var(--hero-ink-soft); }

.trophy-strip { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; }
.trophy-strip::-webkit-scrollbar { display: none; }
.trophy-chip {
  flex: none; display: flex; align-items: center; gap: 8px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px 6px 8px;
}
.trophy-chip span { display: flex; flex-direction: column; font-size: 13px; font-weight: 600; }
.trophy-chip-icon { font-size: 20px; }
.profile-hero .trophy-chip {
  background: #f7fafd; border-color: var(--hero-line); color: var(--hero-ink);
}
.profile-hero .trophy-chip .small { font-size: 11.5px; font-weight: 600; color: var(--hero-ink-soft); }

/* Ponsel: tiga cincin tetap tiga, karena membandingkannya berdampingan itulah gunanya.
   Yang mengecil hanya ukurannya. */
@media (max-width: 560px) {
  .ph-cover { height: 92px; }
  .ph-cover::after { width: 108px; right: -18px; bottom: -20px; }
  /* Ubin 58px di HP: tiga di pojok kanan atas, fotonya naik di kiri — tidak bertemu. */
  .dials { right: 10px; top: 10px; gap: 6px; }
  .dial { width: 58px; height: 58px; border-radius: 12px; }
  .dial::before, .dial::after { border-radius: 12px; }
  .dial-ring { width: 36px; height: 36px; }
  .dial-mid b { font-size: 11px; }
  .dial-mid i { font-size: 7px; }
  .dial-title { font-size: 8px; }
  .dial-pop { width: 240px; }
  .ph-body { padding: 10px 14px 14px; }
  /* Di 390px tidak ada susunan berdampingan yang bekerja: nama + bendera + lencana
     paket saja sudah menghabiskan lebarnya, dan foto 76px di sebelahnya memaksa nama
     pecah jadi tiga baris. Jadi di ponsel kartunya menumpuk — foto sendiri di atas
     (tetap menimpa spanduk), nama di bawahnya, rating di baris ketiga. */
  .hero-top { flex-direction: column; align-items: flex-start; gap: 10px; }
  .hero-id { width: 100%; min-width: 0; }
  .hero-rating {
    width: 100%; margin-left: 0; text-align: left; gap: 8px;
    display: flex; align-items: center; flex-wrap: wrap;
    border-top: 1px solid var(--hero-line); padding-top: 10px;
  }
  .hero-rating-lbl { font-size: 11.5px; white-space: nowrap; }
  .up-chip { margin-top: 0; margin-left: auto; max-width: 100%; }
  .dials { gap: 8px; }
  .dial { padding: 11px 5px 10px; gap: 6px; border-radius: 14px; }
  .dial-ring { width: 86px; }
  .dial-track, .dial-arc { stroke-width: 15; }
  .dial-mid b { font-size: 20px; }
  .dial-mid i { font-size: 11px; }
  .dial-title { font-size: 12.5px; }
  .dial-note { font-size: 10.5px; }
  .dial-dot { width: 7px; height: 7px; margin-left: 5px; }
  .profile-hero .hero-piece { width: 76px; height: 76px; margin-top: -48px; }
  .profile-hero .hero-piece img { width: 46px; height: 46px; }
  .hero-name { font-size: 19px; }
  .hero-rating-num { font-size: 27px; }
}

/* ---------- DNA pemain ----------

   Empat kartu: gaya main, pembukaan andalan, kelemahan berulang, dan master yang cara
   mainnya paling dekat. Angkanya dihitung server (src/player-dna.js) dari partai yang
   benar-benar dimainkan — tidak ada satu pun angka di sini yang ditulis di berkas ini.

   Warnanya mengikuti arti yang sudah berlaku di seluruh situs, bukan selera per kartu:
   biru untuk yang memaksa (agresif), ungu untuk yang rumit (taktis), hijau untuk yang
   sabar (posisional), emas untuk master, dan grafit untuk kelemahan — kelemahan yang
   diberi warna merah menyala terbaca sebagai hukuman, padahal isinya daftar latihan. */
.dna-grid {
  display: grid; gap: 12px; margin: 14px 0 4px;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  /* Tiap kartu setinggi isinya sendiri. Tanpa ini, kartu di sebelah radar ikut
     diregangkan setinggi radar dan separuh isinya jadi ruang kosong — yang terbaca
     sebagai kartu yang gagal memuat, bukan sebagai kartu yang memang pendek. */
  align-items: start;
}
.dna-card { position: relative; overflow: hidden; display: flex; flex-direction: column; gap: 12px; margin-bottom: 0; }
/* Garis warna di tepi atas tiap kartu, mengikuti warna ikonnya (:has), plus lingkaran
   tint samar di sudut kanan atas — kartu yang seluruhnya putih di atas halaman putih
   terbaca sebagai formulir (revisi ridwanyas 11 Sep 2026: "sepi dan monoton"). */
.dna-card::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 4px; background: linear-gradient(90deg, #0a66c2, #4f93d8); }
.dna-card::after {
  content: ''; position: absolute; right: -28px; top: -28px; width: 84px; height: 84px; border-radius: 50%;
  background: #0a66c2; opacity: .06; pointer-events: none;
}
.dna-card:has(.dna-ic.green)::before { background: linear-gradient(90deg, #14804a, #34c47c); }
.dna-card:has(.dna-ic.green)::after { background: #14804a; }
.dna-card:has(.dna-ic.ink)::before { background: linear-gradient(90deg, #1f2937, #64748b); }
.dna-card:has(.dna-ic.ink)::after { background: #1f2937; }
.dna-card:has(.dna-ic.gold)::before { background: linear-gradient(90deg, #b7791f, #f4c04f); }
.dna-card:has(.dna-ic.gold)::after { background: #b7791f; }
.dna-card > * { position: relative; }
.dna-head { display: flex; align-items: center; gap: 9px; }
.dna-ic {
  flex: none; width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center;
  font-size: 15px; line-height: 1;
}
.dna-ic.blue { background: #e8f1fc; color: #0a66c2; }
.dna-ic.green { background: var(--accent-soft); color: var(--accent-dark); }
.dna-ic.ink { background: #eaeef3; color: #1f2937; }
.dna-ic.gold { background: var(--gold-soft); color: var(--gold-dark); }
.dna-title { font-size: 14.5px; font-weight: 800; }
.dna-sub { font-size: 11.5px; font-weight: 600; color: var(--muted); margin-top: 1px; }

/* Satu batang untuk tiga gaya sekaligus. Tiga batang terpisah membuat orang
   membandingkan panjang yang tidak berbagi sumbu; satu batang menjumlah 100 dan
   perbandingannya terbaca tanpa satu angka pun dibaca. */
.dna-split { display: flex; height: 14px; border-radius: 999px; overflow: hidden; background: #eef2f7; }
.dna-split i { display: block; height: 100%; transition: width .5s cubic-bezier(.2, .8, .3, 1); }
.dna-split i.aggressive { background: linear-gradient(180deg, #2f80e0, #0a66c2); }
.dna-split i.tactical { background: linear-gradient(180deg, #9a62e8, var(--premium)); }
.dna-split i.positional { background: linear-gradient(180deg, #26b06f, var(--accent)); }
.dna-legend { display: flex; flex-direction: column; gap: 7px; }
.dna-leg { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; }
.dna-leg b { margin-left: auto; font-size: 14px; font-weight: 800; }
.dna-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.dna-swatch.aggressive { background: #0a66c2; }
.dna-swatch.tactical { background: var(--premium); }
.dna-swatch.positional { background: var(--accent); }

.dna-list { display: flex; flex-direction: column; gap: 10px; }
.dna-item { display: flex; align-items: flex-start; gap: 9px; font-size: 13px; }
.dna-item .eco {
  flex: none; font-size: 11px; font-weight: 800; letter-spacing: .02em;
  background: #eef4fb; color: #0b4f9e; border-radius: 6px; padding: 3px 6px;
}
.dna-item-main { min-width: 0; flex: 1; }
.dna-item-name { font-weight: 700; line-height: 1.3; }
.dna-item-note { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
/* Titik kecil di depan tiap kelemahan, bukan tanda silang merah: yang membaca daftar
   ini sedang melihat catatan latihannya sendiri. */
.dna-item .pip {
  flex: none; width: 7px; height: 7px; border-radius: 50%; background: #475569; margin-top: 6px;
}
.dna-count {
  flex: none; font-size: 11px; font-weight: 700; color: #475569;
  background: #eef2f7; border-radius: 999px; padding: 2px 8px; margin-top: 1px;
}

/* Master: nama, batang emas sepanjang persennya, angkanya di ujung. Peringkatnya
   dibaca dari urutan barisnya, jadi tidak perlu ada angka 1/2/3 yang ikut bersaing
   dengan persen di sebelahnya. */
.dna-gm { display: flex; align-items: center; gap: 10px; }
.dna-gm-name { font-size: 13px; font-weight: 700; min-width: 0; flex: 1; }
.dna-gm-elo { font-size: 11px; font-weight: 600; color: var(--muted); }
.dna-gm-bar { flex: 0 0 84px; height: 8px; border-radius: 999px; background: #f0eadf; overflow: hidden; }
.dna-gm-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--gold-lit), var(--gold)); }
.dna-gm-pct { flex: none; font-size: 13px; font-weight: 800; color: var(--gold-dark); width: 38px; text-align: right; }

.dna-empty { font-size: 12.5px; color: var(--muted); line-height: 1.5; }
/* Bukan .dna-empty: skrip potret menunggu kelas itu sebagai tanda DNA sudah datang. */
.dna-loading { font-size: 12.5px; color: var(--muted); }

/* Rincian Mental: nama + bobot, batang biru, angka — dan catatan satu baris di bawahnya
   yang menyebut angka mentahnya (akurasi sebelum/sesudah, jumlah partai). Baris yang
   belum terukur memudar, batangnya kosong, dan angkanya "—". */
.dna-mind-total { font-size: 15px; font-weight: 900; color: #0a66c2; margin-left: 4px; }
.dna-mind-row { display: grid; grid-template-columns: 1fr 64px 30px; column-gap: 8px; align-items: center; }
.dna-mind-row.unknown { opacity: .6; }
.dna-mind-name { font-size: 12.5px; font-weight: 700; min-width: 0; line-height: 1.25; }
.dna-mind-w { font-size: 10.5px; font-weight: 600; color: var(--muted); margin-left: 3px; white-space: nowrap; }
.dna-mind-bar { height: 8px; border-radius: 999px; background: #e8eef6; overflow: hidden; }
.dna-mind-bar i { display: block; height: 100%; background: linear-gradient(90deg, #4f93d8, #0a66c2); }
.dna-mind-val { font-size: 13px; font-weight: 800; color: #0a66c2; text-align: right; }
.dna-mind-row.unknown .dna-mind-val { color: #8a97a6; }
.dna-mind-note { grid-column: 1 / -1; font-size: 11.5px; color: var(--muted); line-height: 1.4; margin-top: -2px; }

/* ---------- radar enam kemampuan ----------

   Bentuk yang diminta ridwanyas (11 Sep 2026) beserta contoh gambarnya: segi enam
   dengan bidang berwarna di dalamnya. Warnanya biru — sama dengan spanduk profil di
   atasnya, karena keduanya menjawab pertanyaan yang sama ("orang ini siapa"), bukan
   hijau yang di situs ini berarti "tekan saya". */
.dna-radar { gap: 6px; }
/* Di layar lebar kartu radar mengambil dua kolom: segi enam berlabel enam kata tidak
   terbaca di dalam kartu selebar 248px. */
@media (min-width: 780px) { .dna-radar { grid-column: span 2; } }
.rd { width: 100%; max-width: 420px; margin: 0 auto; display: block; overflow: visible; }
.rd-ring { fill: none; stroke: #e6ecf3; stroke-width: 1; }
.rd-ring:last-of-type { stroke: #d7e1ec; }
.rd-spoke { stroke: #e0e8f1; stroke-width: 1; }
/* Sumbu yang belum ada angkanya: garis putus-putus, supaya "belum diukur" terlihat
   berbeda dari "diukur dan hasilnya rendah" bahkan sebelum keterangannya dibaca. */
.rd-spoke.unknown { stroke: #cfd8e3; stroke-dasharray: 3 4; }
.rd-area {
  fill: rgba(10, 102, 194, .26); stroke: #0a66c2; stroke-width: 2; stroke-linejoin: round;
}
.rd-dot { fill: #0a66c2; }
.rd-blank { fill: #ffffff; stroke: #c3cedb; stroke-width: 1.5; }
.rd-label { font-size: 12px; font-weight: 800; fill: #14211a; }
.rd-value { font-size: 11px; font-weight: 700; fill: #0a66c2; }
.rd-label.unknown { fill: #8a97a6; }
.rd-value.unknown { fill: #9aa6b4; font-weight: 600; }
@media (max-width: 560px) {
  .rd-label { font-size: 11px; }
  .rd-value { font-size: 10px; }
}

/* ---------- badges ---------- */
.badge-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin-bottom: 18px; }
.badge-card {
  background: var(--bg-card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px; display: flex; flex-direction: column; gap: 6px; position: relative; overflow: hidden;
}
.badge-card.earned { border-color: #b6dcc5; background: linear-gradient(180deg, var(--accent-soft), var(--bg-card)); }
/* Locked badges stay readable: the point of a locked badge is that the player can see
   exactly what it asks for, so only the icon is dimmed, never the text. */
.badge-card.locked .badge-icon { filter: grayscale(1); opacity: .45; }
.badge-icon { font-size: 30px; line-height: 1; }
.badge-name { font-weight: 700; font-size: 14px; }
.badge-desc { line-height: 1.35; }
.badge-foot { font-size: 12px; font-weight: 600; margin-top: auto; }
.badge-reward {
  position: absolute; top: 10px; right: 10px; font-size: 11px; font-weight: 700;
  color: var(--accent); background: var(--accent-soft); border: 1px solid #b6dcc5;
  border-radius: 999px; padding: 2px 7px;
}
.badge-card.pop { animation: badge-pop .45s cubic-bezier(.2,.9,.3,1.4) both; }
@keyframes badge-pop { from { transform: scale(.86); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .badge-card.pop { animation: none; } }

.next-row { display: flex; gap: 12px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--line); }
.next-row:last-of-type { border-bottom: 0; }
.next-icon { font-size: 22px; }
.next-body { flex: 1; display: flex; flex-direction: column; gap: 6px; }

/* ---------- game history ---------- */
.hist-list { display: flex; flex-direction: column; }
.hist-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 0;
  border-bottom: 1px solid var(--line); color: var(--text);
}
.hist-row:last-child { border-bottom: 0; }
a.hist-row:hover { text-decoration: none; background: var(--bg-soft); }
.hist-outcome { flex: none; width: 74px; font-weight: 700; font-size: 13px; }
.hist-vs { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: 14px; }
.hist-vs .muted { font-weight: 400; }
.hist-when { flex: none; }
@media (max-width: 520px) {
  .hist-when { display: none; }
}

/* ---------- review summary: a count is a way into the moves it counted ---------- */
.review td.pickable { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
.review td.pickable:hover { background: var(--bg-soft); }
.review td.picked {
  background: var(--bg-soft); border-radius: 6px;
  box-shadow: inset 0 0 0 1px var(--accent); text-decoration: none;
}
.pick-bar {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 10px; padding: 6px 8px;
}
.pick-bar button { padding: 4px 10px; font-size: 13px; line-height: 1.2; }
.pick-what { flex: 1; display: flex; flex-direction: column; font-size: 13px; line-height: 1.3; }

/* --- Login: tombol Google & pemisah --------------------------------------- */
/* Warna dan bentuknya mengikuti panduan merek Google: putih, teks abu gelap,
   logo asli. Tombol yang "disesuaikan" dengan tema gelap situs justru membuat
   orang ragu apakah ini benar-benar login Google. */
.btn.google {
  background: #fff;
  color: #1f1f1f;
  border: 1px solid #dadce0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  font-weight: 600;
}
.btn.google:hover { background: #f5f6f7; }
.btn.google svg { width: 18px; height: 18px; flex: none; }

.or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0;
  color: var(--muted);
  font-size: 13px;
}
.or::before, .or::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.form-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; font-size: 13px; }
.form-foot a { color: var(--muted); }

/* --- Papan peringkat: dua tab + arena bergaya game ------------------------- */
/* Perbaikan kontras: `.chip` mengganti latar tombol tapi tidak warnanya, sehingga
   teksnya mewarisi warna tulisan tombol accent — putih di atas keping berlatar terang,
   dan baru terbaca saat hover. Warnanya sekarang dinyatakan. */
.chip { color: var(--text); }
@media (hover: hover) { .chip:hover { background: var(--accent-soft); } }
.chip.active { color: var(--on-accent); }
@media (hover: hover) { .chip.active:hover { background: var(--accent-dark); } }

.lb-tabs { display: flex; gap: 8px; margin: 16px 0 14px; }
.lb-tab {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--bg-soft); color: var(--text); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px 10px; font-weight: 700; font-size: 14px;
}
@media (hover: hover) { .lb-tab:hover { background: var(--accent-soft); } }
.lb-tab.active { color: #fff; border-color: transparent; box-shadow: 0 6px 18px rgba(16, 44, 30, .20); }
.lb-tab.active[data-t="elo"] { background: linear-gradient(120deg, #2563eb, #06b6d4); }
.lb-tab.active[data-t="point"] { background: linear-gradient(120deg, #7c3aed, #db2777); }
@media (hover: hover) {
  .lb-tab.active[data-t="elo"]:hover { background: linear-gradient(120deg, #1d4ed8, #0891b2); }
  .lb-tab.active[data-t="point"]:hover { background: linear-gradient(120deg, #6d28d9, #be185d); }
}

/* Arena: panel gelap berlapis, tempat papan point berdiri. Semua hiasan hidup di
   dalam ::before/::after dan elemen .deco yang tidak menangkap sentuhan, jadi
   ornamen tidak pernah menghalangi baris yang bisa diklik. */
.arena {
  position: relative; overflow: hidden; border-radius: 16px; padding: 16px 14px;
  background:
    radial-gradient(120% 90% at 8% -10%, rgba(124,58,237,.38), transparent 60%),
    radial-gradient(100% 80% at 100% 0%, rgba(6,182,212,.22), transparent 55%),
    linear-gradient(165deg, #1b2044, #141733 55%, #10121f);
  border: 1px solid rgba(148,163,255,.28);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), var(--shadow);
}
/* Faset diagonal tipis — yang membuat latar terasa "panel game", bukan kotak polos. */
.arena::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(115deg, rgba(255,255,255,.035) 0 2px, transparent 2px 24px);
  mask-image: linear-gradient(180deg, #000, transparent 75%);
}
.arena > * { position: relative; z-index: 1; }
.arena .deco {
  position: absolute; pointer-events: none; user-select: none; z-index: 0;
  opacity: .07; filter: drop-shadow(0 6px 18px rgba(0,0,0,.6));
}
.arena .deco img { display: block; width: 100%; height: 100%; }
.arena-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.arena-head h3 { margin: 0; font-size: 16px; letter-spacing: .3px; }
.arena-head .count { color: #a5b4fc; font-size: 12px; font-weight: 700; }

.arena-row {
  display: grid; grid-template-columns: 44px 46px minmax(0, 1fr) auto; gap: 10px;
  align-items: center; padding: 9px 12px; margin-bottom: 8px; border-radius: 12px;
  background: rgba(255,255,255,.035); border: 1px solid rgba(255,255,255,.07);
  transition: transform .12s, background .12s;
}
@media (hover: hover) { .arena-row:hover { background: rgba(255,255,255,.08); transform: translateX(2px); } }
.arena-row.top1 {
  background: linear-gradient(90deg, rgba(251,191,36,.26), rgba(251,191,36,.05) 70%);
  border-color: rgba(251,191,36,.5);
}
.arena-row.top2 { background: linear-gradient(90deg, rgba(226,232,240,.2), rgba(226,232,240,.04) 70%); border-color: rgba(203,213,225,.45); }
.arena-row.top3 { background: linear-gradient(90deg, rgba(217,119,6,.22), rgba(217,119,6,.04) 70%); border-color: rgba(217,119,6,.45); }
.arena-row.me { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

/* Medali segi enam, mengikuti bentuk lencana peringkat di papan game. */
.rank-medal {
  width: 38px; height: 42px; display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 15px; color: #cbd5e1;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background: rgba(255,255,255,.07);
}
.rank-medal.g1 { background: linear-gradient(160deg, #fde68a, #f59e0b 60%, #b45309); color: #3a2a05; }
.rank-medal.g2 { background: linear-gradient(160deg, #f8fafc, #cbd5e1 55%, #94a3b8); color: #1f2937; }
.rank-medal.g3 { background: linear-gradient(160deg, #fcd9b0, #d97706 60%, #92400e); color: #2b1608; }

.arena-avatar {
  width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 32% 26%, #46528f, #171a33 70%);
  border: 2px solid rgba(255,255,255,.22); flex: none;
}
.arena-avatar img { width: 28px; height: 28px; }
/* Foto profil memenuhi bulatannya; bidak tetap kecil di tengah untuk yang belum berfoto. */
.arena-avatar img.photo { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.arena-row.top1 .arena-avatar { border-color: #fbbf24; box-shadow: 0 0 14px rgba(251,191,36,.45); }
.arena-row.top2 .arena-avatar { border-color: #e2e8f0; }
.arena-row.top3 .arena-avatar { border-color: #f0a868; }

.arena-who { min-width: 0; }
.arena-name { font-weight: 700; font-size: 15px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.arena-name a { color: #e8ecff; }
.arena-sub { color: #9aa6d8; font-size: 11.5px; margin-top: 2px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.arena-sub > span { display: inline-flex; align-items: center; gap: 4px; }
.arena-score { text-align: right; }
.arena-pts { font-size: 20px; font-weight: 800; color: #ffd469; line-height: 1.1; font-variant-numeric: tabular-nums; }
.arena-row.top1 .arena-pts { text-shadow: 0 0 16px rgba(251,191,36,.5); }
.arena-unit { font-size: 11px; color: #9aa6d8; font-weight: 600; }

/* Papan Elo memakai kerangka yang sama dengan warna yang lebih tenang: yang dilihat
   di sini adalah kekuatan bermain, bukan perayaan juara. */
.arena.calm {
  background:
    radial-gradient(110% 80% at 10% -10%, rgba(37,99,235,.30), transparent 60%),
    linear-gradient(165deg, #16213a, #121a2c 60%, #0f1220);
  border-color: rgba(96,165,250,.26);
}
.arena.calm .arena-pts { color: #7dd3fc; }
.arena.calm .arena-row.top1 { background: linear-gradient(90deg, rgba(56,189,248,.22), rgba(56,189,248,.04) 70%); border-color: rgba(56,189,248,.5); }
.arena.calm .arena-row.top1 .arena-avatar { border-color: #38bdf8; box-shadow: 0 0 14px rgba(56,189,248,.4); }
.arena.calm .arena-row.top1 .arena-pts { text-shadow: 0 0 16px rgba(56,189,248,.45); }

.wl { display: inline-flex; gap: 6px; font-variant-numeric: tabular-nums; }
.wl .w { color: #86efac; } .wl .d { color: #cbd5e1; } .wl .l { color: #fca5a5; }

@media (max-width: 560px) {
  .arena-row { grid-template-columns: 34px 38px minmax(0, 1fr) auto; gap: 8px; padding: 8px 9px; }
  .rank-medal { width: 30px; height: 34px; font-size: 13px; }
  .arena-avatar { width: 38px; height: 38px; } .arena-avatar img { width: 24px; height: 24px; }
  .arena-avatar img.photo { width: 100%; height: 100%; }
  .arena-pts { font-size: 17px; }
  .lb-tab { font-size: 13px; padding: 11px 8px; }
}

.arena-empty { text-align: center; padding: 26px 14px 30px; }
.arena-empty .big { line-height: 0; color: #9aa6d8; opacity: .55; margin-bottom: 10px; }
.arena-empty p { color: #a8b2dc; margin: 0 0 14px; }

/* Bar rincian point ikut menjadi anak flex di dalam .arena-sub — tanpa basis penuh
   ia berdesakan di baris yang sama dan menyusut sampai tidak terbaca. */
.arena-sub .pt-bar { flex: 0 0 100%; max-width: 180px; margin-top: 4px; background: rgba(255,255,255,.08); }

/* Kepala halaman peringkat versi rating: biru, sewarna dengan arena tab itu. */
.lb-hero.elo {
  background: linear-gradient(120deg, #1d4ed8 0%, #0891b2 55%, #22d3ee 100%);
  box-shadow: 0 10px 30px rgba(8,145,178,.25);
}

/* --- Lockup merek untuk halaman tanpa topbar ------------------------------- */
/* Halaman masuk/daftar/pemulihan tidak memasang topbar, jadi merek dipasang
   sendiri di sana.

   24 September 2026 (ridwanyas: *"perbaiki juga logo halaman login baik app atau web
   karena masih pakai logo lama"*): yang dipasang di sini bukan lagi dua berkas bidak
   papan yang ditumpuk CSS, melainkan **logo** — ubin rumput yang sama persis dengan
   ikon aplikasi di HP dan di Play, plus tulisan "Katuranga" bergradasi hijau. Keduanya
   satu berkas SVG dari scripts/make-brand-lockup.mjs; tidak ada teks HTML lagi di
   dalamnya, jadi hurufnya tidak bisa berubah mengikuti font sistem.

   Tingginya yang diatur, bukan lebarnya: lebar lockup ikut panjang tulisan, dan yang
   harus sama di setiap halaman adalah tinggi barisnya. */
.brand-mark {
  display: flex; align-items: center; justify-content: center;
  margin: 10px 0 16px;
}
.brand-mark .lockup { height: 46px; width: auto; display: block; }
@media (max-width: 420px) { .brand-mark .lockup { height: 40px; } }

/* Lockup lama (dua bidak + teks) masih dipakai halaman yang belum disentuh; aturannya
   dibiarkan supaya yang tertinggal tidak berubah jadi dua gambar raksasa. */
.brand-mark .logo { display: flex; align-items: center; }
.brand-mark .logo img { width: 38px; height: 38px; display: block; }
.brand-mark .logo img + img { margin-left: -13px; }
.brand-mark:has(.logo) {
  gap: 9px; font-size: 22px; font-weight: 800; letter-spacing: .2px; color: var(--text);
}

/* Versi sebaris, untuk dipakai di tengah kalimat. */
.brand-inline { display: inline-flex; align-items: center; vertical-align: -6px; }
.brand-inline img { width: 28px; height: 28px; display: block; }
.brand-inline img + img { margin-left: -10px; }

/* --- Poster event: spanduk besar di kepala halaman Event ------------------- */
/* Bentuknya meniru poster event game: sinar emas menyebar dari satu titik, bidak
   sebagai "bintang" di depannya, lalu blok judul yang berdiri sendiri. Semua lapisan
   hiasan memakai pointer-events:none supaya tidak ada tombol yang tertutup. */
.event-poster {
  position: relative; overflow: hidden; border-radius: 18px; margin: 14px 0 16px;
  min-height: 330px; display: flex; align-items: flex-end; justify-content: center;
  background: linear-gradient(180deg, #2b2417 0%, #1c1a15 45%, #141414 100%);
  border: 1px solid rgba(212,175,55,.35);
  box-shadow: 0 14px 40px rgba(0,0,0,.45);
  isolation: isolate;
}
/* Sinar: repeating-conic-gradient memberi kipas yang rata tanpa satu pun poligon
   ditulis tangan. Dua lapis dengan sudut berbeda supaya tidak terlihat mekanis. */
.event-poster .rays, .event-poster .rays2 {
  position: absolute; inset: -55% -25% -10% -25%; pointer-events: none; z-index: 0;
  mask-image: radial-gradient(60% 55% at 50% 30%, #000 35%, transparent 78%);
}
.event-poster .rays {
  background: repeating-conic-gradient(from 0deg at 50% 30%,
    rgba(212,175,55,.55) 0deg 6deg, transparent 6deg 21deg);
}
.event-poster .rays2 {
  background: repeating-conic-gradient(from 9deg at 50% 30%,
    rgba(255,214,102,.28) 0deg 3deg, transparent 3deg 27deg);
}
.event-poster .glow {
  position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background: radial-gradient(46% 38% at 50% 30%, rgba(255,206,84,.35), transparent 70%);
}

/* Bidak putih dijadikan emas lewat filter, bukan berkas gambar baru: satu set bidak
   tetap satu sumber kebenaran, dan warnanya bisa disetel tanpa render ulang. */
.event-poster .pieces { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.event-poster .pieces img {
  position: absolute; filter: sepia(.9) saturate(3.6) hue-rotate(-10deg) brightness(.98)
    drop-shadow(0 14px 26px rgba(0,0,0,.6));
}
.event-poster .pc-king   { width: 148px; left: 50%; top: 26px; transform: translateX(-50%); }
.event-poster .pc-knight { width: 122px; left: 15%; top: 66px; transform: rotate(-12deg); }
.event-poster .pc-queen  { width: 122px; right: 14%; top: 62px; transform: rotate(11deg); }
.event-poster .pc-rook   { width: 84px;  left: 3%;  bottom: 8px; transform: rotate(8deg); opacity: .55; }
.event-poster .pc-pawn   { width: 74px;  right: 4%; bottom: 4px; transform: rotate(-9deg); opacity: .55; }

.event-poster .confetti { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.event-poster .confetti i {
  position: absolute; width: 7px; height: 11px; border-radius: 2px; opacity: .75;
  background: #f5d26b;
}

.event-poster .copy {
  position: relative; z-index: 2; text-align: center; padding: 0 18px 22px;
  width: 100%;
  background: linear-gradient(180deg, transparent, rgba(10,10,10,.82) 42%);
}
.poster-badge {
  display: inline-flex; align-items: center; gap: 7px; margin-bottom: 10px;
  padding: 6px 16px; border-radius: 99px; font-weight: 800; font-size: 12.5px; letter-spacing: 1.4px;
  color: #2a1e00; background: linear-gradient(120deg, #ffe08a, #d4af37 60%, #b8860b);
  box-shadow: 0 6px 18px rgba(212,175,55,.35);
}
@keyframes posterPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.045); } }
@media (prefers-reduced-motion: no-preference) { .poster-badge { animation: posterPulse 2.4s ease-in-out infinite; } }

.poster-brand {
  font-size: 12.5px; font-weight: 800; letter-spacing: 4px; color: #e7d9a8; opacity: .9;
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.poster-brand img { width: 20px; height: 20px; }
.poster-title {
  margin: 2px 0 0; font-size: clamp(30px, 8.5vw, 52px); line-height: 1.02; font-weight: 900;
  letter-spacing: 1px; text-transform: uppercase;
  background: linear-gradient(180deg, #fff8e1 8%, #ffd86b 45%, #b8860b 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: 0 6px 26px rgba(212,175,55,.25);
}
.poster-year { font-size: 15px; font-weight: 700; letter-spacing: 8px; color: #d9c68c; margin-top: 2px; }
.poster-period {
  margin-top: 12px; display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;
  font-size: 13.5px; font-weight: 800; letter-spacing: 1.2px; color: #fff;
  border-top: 1px solid rgba(212,175,55,.45); border-bottom: 1px solid rgba(212,175,55,.45);
  padding: 7px 14px;
}
.poster-cta { margin-top: 14px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.poster-note { margin: 10px 0 0; font-size: 12px; color: #b9b09a; }

@media (max-width: 560px) {
  .event-poster { min-height: 280px; border-radius: 14px; }
  .event-poster .pc-king { width: 108px; top: 22px; }
  .event-poster .pc-knight { width: 88px; left: 6%; top: 62px; }
  .event-poster .pc-queen { width: 88px; right: 5%; top: 58px; }
  .event-poster .pc-rook, .event-poster .pc-pawn { display: none; }
  .poster-year { letter-spacing: 6px; }
}

/* --- Top bar: scrollbar tipis saat menu tidak muat ------------------------- */
/* Di laptop 14 inci deretan menu lebih lebar dari barnya, dan menu terakhir
   ("Profil") terpotong tanpa tanda apa pun — barisnya memang bisa digeser, tapi
   scrollbar-nya disembunyikan sehingga tidak ada yang memberi tahu. Di layar lebar
   scrollbar-nya sekarang ditampilkan setipis mungkin; mode drawer (HP) tetap tanpa
   scrollbar karena di sana menunya sudah jadi daftar vertikal penuh. */
@media (min-width: 901px) {
  .topbar nav {
    scrollbar-width: thin;
    scrollbar-color: rgba(20,33,26,.24) transparent;
    padding-bottom: 3px;
    scroll-behavior: smooth;
    overscroll-behavior-x: contain;
  }
  .topbar nav::-webkit-scrollbar { display: block; height: 5px; }
  .topbar nav::-webkit-scrollbar-track { background: transparent; }
  .topbar nav::-webkit-scrollbar-thumb { background: rgba(20,33,26,.20); border-radius: 99px; }
  @media (hover: hover) {
    .topbar nav::-webkit-scrollbar-thumb:hover { background: rgba(20,33,26,.36); }
  }

  /* Tepi yang memudar untuk bilah menu atas — aturannya sendiri ada di
     ".over" di luar media query ini, dipakai bersama pita tab yang lain. */
}

/* ---------- modal + pengumuman akhir partai ----------
   Papan yang diam tidak memberi tahu apa pun. Yang mengumumkan partai selesai adalah
   lapisan ini: menutupi layar, menahan sentuhan ke papan, dan menaruh langkah
   berikutnya sebagai tombol terbesar.

   Namanya .modal-card, bukan .modal: kelas .modal sudah dipakai pemilih wilayah
   (location.js) dengan ukuran dan animasinya sendiri, dan aturan di sini berada lebih
   bawah di berkas — menumpanginya akan mengecilkan serta menggeser kotak itu. */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 120;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(8, 9, 11, .72); backdrop-filter: blur(2px);
  /* Tak terlihat bukan berarti tak ada: tanpa `pointer-events: none`, lapisan selebar
     layar di z-index 120 ini tetap menangkap setiap ketukan selama ia belum `.show` —
     halaman terlihat normal dan tidak ada satu pun tombol yang bisa ditekan. */
  opacity: 0; pointer-events: none; transition: opacity .15s ease;
}
.modal-backdrop.show { opacity: 1; pointer-events: auto; }
.modal-card {
  position: relative; width: 100%; max-width: 380px;
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: 16px; padding: 22px 20px 18px; box-shadow: 0 18px 50px rgba(16, 44, 30, .24);
  transform: translateY(8px) scale(.97); transition: transform .17s ease;
  max-height: calc(100vh - 40px); overflow-y: auto;
}
.modal-backdrop.show .modal-card { transform: none; }
.modal-x {
  position: absolute; top: 8px; right: 8px; width: 32px; min-height: 0; padding: 0;
  background: none; border: 0; color: var(--muted); font-size: 15px; line-height: 32px;
}
.modal-x:hover { background: var(--bg-soft); color: var(--text); }
.modal-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 16px; }
.modal-actions .btn, .modal-actions button { width: 100%; text-align: center; grid-column: span 2; }
.modal-actions .half { grid-column: span 1; }

/* Garis warna di tepi atas: menang hijau, kalah merah, remis abu — terbaca sebelum
   satu kata pun sempat dibaca. */
.modal-card.game-over { text-align: center; border-top: 4px solid var(--muted); }
.modal-card.game-over.win { border-top-color: var(--accent); }
.modal-card.game-over.loss { border-top-color: var(--danger); }
.go-icon { font-size: 40px; line-height: 1; margin: 2px 0 6px; }
.go-title { margin: 0; font-size: 25px; letter-spacing: -.01em; }
.modal-card.game-over.win .go-title { color: var(--accent); }
.modal-card.game-over.loss .go-title { color: var(--danger); }
.go-reason { margin: 5px 0 0; color: var(--muted); font-size: 14px; }
.go-versus {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px;
  margin-top: 14px; padding: 10px 12px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 11px;
  font-size: 13px; font-weight: 600;
}
.go-side {
  text-align: left;
  /* Anak grid punya min-width auto: tanpa dinolkan, nama sepanjang
     "Bot level 4 — Klub" melebarkan kolomnya sendiri dan mendorong skor keluar
     kartu, bukan dipotong. Dan ellipsis hanya bekerja kalau teksnya dilarang
     berpindah baris. */
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.go-side.right { text-align: right; }
.go-side small {
  display: block; font-weight: 500; color: var(--muted); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis;
}
.go-score { font-size: 16px; font-weight: 700; letter-spacing: .02em; }
.modal-card.game-over .alert { text-align: left; margin: 10px 0 0; }

/* Baris tombol di bawah papan berubah begitu partai selesai: menyerah/petunjuk tidak
   lagi berarti apa-apa, jadi tempatnya diambil satu tombol "main lagi". */
.board-actions.finished { grid-template-columns: 1fr; }

/* Hasil partai DI TENGAH PAPAN (ridwanyas 14 Sep 2026), bukan baris di bawahnya —
   lihat showBoardResult() di game-over.js. Lapisannya selebar papan tapi tembus
   ketukan; hanya kartunya yang menangkap jari, jadi petak di sekelilingnya tetap
   bisa disentuh. Nama kelas berawalan `br-` karena `.card`, `.title`, `.x` sudah
   dipakai di berkas ini. */
.board-result {
  position: absolute; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center; padding: 12px;
  pointer-events: none; opacity: 0; transition: opacity .2s ease;
}
.board-result.show { opacity: 1; }
.br-card {
  pointer-events: auto; position: relative; width: min(300px, 88%);
  text-align: center; background: var(--bg-card); color: var(--text);
  border: 1px solid var(--line); border-top: 4px solid var(--muted); border-radius: 16px;
  padding: 16px 16px 14px; box-shadow: 0 18px 44px rgba(8, 20, 14, .38);
  transform: scale(.94); transition: transform .2s ease;
}
.board-result.show .br-card { transform: none; }
.br-card.win { border-top-color: var(--accent); }
.br-card.loss { border-top-color: var(--danger); }
.br-x {
  position: absolute; top: 6px; right: 6px; width: 30px; min-height: 0; padding: 0;
  background: none; border: 0; color: var(--muted); font-size: 14px; line-height: 30px;
}
.br-x:hover { background: var(--bg-soft); color: var(--text); }
.br-icon { font-size: 34px; line-height: 1; margin-bottom: 4px; }
.br-title { display: block; font-family: var(--font-display); font-size: 22px; line-height: 1.2; }
.br-card.win .br-title { color: var(--accent); }
.br-card.loss .br-title { color: var(--danger); }
.br-reason { margin: 4px 0 0; color: var(--muted); font-size: 13.5px; }
.br-rating { margin: 6px 0 0; font-size: 13.5px; }
.br-cta { margin-top: 12px; min-height: 48px; font-size: 15px; }
/* Kaca pembesar Analysis di atas tombol hijau: bayangan tipis supaya lensanya yang
   putih tidak melebur ke tulisan di sebelahnya. */
.ic-review { flex: none; }
/* Ikon berwarna (keping mengkilat) di dalam tombol biasa: sejajar dengan tulisannya,
   bukan duduk di garis dasar huruf. Bayangannya sudah di dalam SVG-nya sendiri. */
.ic-solid { flex: none; vertical-align: -5px; margin-right: 6px; }
.modal-actions .half:has(.ic-solid), .go-share:has(.ic-solid), .btn:has(.ic-solid),
.modal-actions button:has(.ic-solid) {
  display: inline-flex; align-items: center; justify-content: center; gap: 0;
}
/* Ikon SOLID sebagai lambang besar kartu (ajakan tanding ulang): tanpa margin tombol. */
.go-icon .ic-svg { margin: 0; vertical-align: middle; }

/* ---------- tawanan + keunggulan perwira ----------
   Barisnya ikut di dalam .player-row, di bawah nama: itu "area masing-masing pemain",
   dan menaruhnya di sana berarti tidak ada satu baris pun tambahan yang mendorong
   papan turun di layar ponsel. */
.player-row { flex-wrap: wrap; }
.player-row .name { flex: 0 1 auto; }
.captured {
  flex-basis: 100%; order: 10;
  display: flex; align-items: center; gap: 1px; min-height: 17px; margin-top: 1px;
}
.captured img {
  width: 17px; height: 17px; display: block;
  /* Tawanan bukan bagian dari posisi — dibuat redup supaya mata tidak salah membaca
     deretan ini sebagai bidak yang masih hidup. */
  opacity: .62;
  /* Ditumpuk sedikit seperti tawanan sungguhan yang disandarkan di tepi papan;
     yang pertama tidak digeser supaya barisnya tetap rata kiri. */
  margin-left: -4px;
}
.captured img:first-child { margin-left: 0; }
.captured .adv {
  margin-left: 6px; font-size: 12px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ---------- skor pertemuan ----------
   Tiga angka berurutan, menang–remis–kalah, dengan kuncinya ditulis di bawah: tanpa
   kunci itu "3–1–2" adalah tiga angka yang bisa dibaca dengan tiga cara berbeda. */
.h2h {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  margin-top: 12px; padding: 8px 12px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 11px;
}
.h2h .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); }
.h2h .val { font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
.h2h .val .sep { color: var(--muted); font-weight: 500; margin: 0 3px; }
.h2h .key { font-size: 10.5px; color: var(--muted); }

/* ---------- pemilih tampilan papan ---------- */
.theme-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-top: 10px; }
.theme-opt {
  display: flex; flex-direction: column; gap: 8px; align-items: stretch; text-align: center;
  background: var(--bg-soft); border: 2px solid var(--line); border-radius: 13px;
  padding: 10px; color: var(--text); cursor: pointer; font: inherit;
}
.theme-opt:hover { border-color: #bccbc3; background: var(--bg-soft); }
.theme-opt.active { border-color: var(--accent); }
.theme-opt .nm { font-size: 13px; font-weight: 700; }
.theme-opt.active .nm::after { content: ' ✓'; color: var(--accent); }
/* Pratinjau: papan 4x4 sungguhan dengan bidak sungguhan, bukan gambar tersendiri —
   apa yang dilihat di sini persis yang akan muncul di papan. */
.mini-board {
  display: grid; grid-template-columns: repeat(4, 1fr); aspect-ratio: 1/1;
  border-radius: 7px; overflow: hidden;
}
.mini-board div { position: relative; }
.mini-board img { width: 100%; height: 100%; display: block; }

/* ---------- ganti tampilan cepat, di halaman bermain ---------- */
.theme-quick { position: relative; display: flex; justify-content: flex-end; margin: 8px auto 0; max-width: 620px; }
.theme-quick .tq-now { font-weight: 600; }
.tq-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 50; min-width: 210px;
  background: var(--bg-card); border: 1px solid var(--line); border-radius: 12px;
  padding: 10px; box-shadow: var(--shadow);
}
.tq-group + .tq-group { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
.tq-label {
  display: block; font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); margin-bottom: 5px;
}
.tq-opt {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: 0; border-radius: 8px; padding: 7px 9px;
  color: var(--text); font-size: 13.5px; font-weight: 600; min-height: 0;
}
.tq-opt:hover { background: var(--bg-soft); }
.tq-opt.active { background: var(--bg-soft); color: var(--accent); }
.tq-opt.active::after { content: '✓'; margin-left: auto; }
/* Dua petak kecil: warna papannya sendiri yang menamai pilihannya. */
.tq-chip { display: inline-flex; border-radius: 4px; overflow: hidden; box-shadow: 0 0 0 1px var(--line); }
.tq-chip i { width: 11px; height: 15px; display: block; }
.tq-more { display: block; margin-top: 10px; font-size: 12px; text-align: center; }

/* ---------- kartu username di halaman Akun ---------- */
.rename-state { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.rename-state .pill { font-size: 12px; }

/* ---------- pilihan masuk yang setara ----------
   Tombol daftar manual dibuat selebar tombol Google, tepat di bawahnya. Sebelumnya
   mendaftar dengan email sendiri hanya berupa tautan teks di bawah judul — pilihan
   yang ada, tapi harus dicari lebih dulu. */
.btn.block {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; margin-top: 10px; font-weight: 600;
}
.or-split {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 0 4px; color: var(--muted); font-size: 12px;
}
.or-split::before, .or-split::after { content: ''; flex: 1; height: 1px; background: var(--line); }

/* Penanda persona. Warnanya sengaja bukan warna paket mana pun: ini bukan tingkatan
   akun, melainkan keterangan bahwa yang di seberang papan bukan orang. */
.badge.bot {
  background: #eef2f7; color: #4a6285; border-color: #d5dfea; margin-left: 5px;
  text-transform: lowercase; letter-spacing: .02em;
}

/* Kotak kode OTP: enam angka, dibaca dari email dan diketik ulang. Diberi jarak antar
   huruf dan font monospace supaya angkanya tidak menyatu saat dieja. */
.otp {
  font-size: 26px; font-weight: 700; letter-spacing: .3em; text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ---------- nama pemain berbayar ----------
   Warna yang bergerak pelan, dan berlian di depannya. Yang dijaga di sini adalah
   keterbacaan: nama tetap harus terbaca sebagai nama, jadi kilaunya berjalan di
   sepanjang huruf, bukan menyala di belakangnya. Puncak terangnya karena itu warna muda,
   bukan putih — nama yang puncaknya seputih halaman adalah nama yang hilang sekejap
   setiap kali kilaunya lewat.

   Ungu dasar di bawah ini kini milik **Pro** (dan admin). **Premium memakai emas-jingga**
   lewat `.vip.gold` — permintaan ridwanyas 11 September 2026, supaya dua tingkat
   berbayar bisa dibedakan di papan peringkat tanpa membuka profilnya.

   Emasnya sengaja emas tua. Halaman ini putih, dan emas muda di atas putih adalah warna
   yang nyaris tidak ada — itu sebabnya nama premium sempat dipindah dari emas ke ungu
   pada Agustus 2026. Yang membuatnya bisa kembali sekarang bukan seleranya, melainkan
   rentangnya: #8a3e06 → #e08a1e, bukan #d4af37 → #fff3c4. */
.vip {
  background: linear-gradient(100deg, #6431ae 0%, #9a63e0 22%, #c9a6f5 32%, #9a63e0 42%, #6431ae 64%, #9a63e0 86%, #6431ae 100%);
  background-size: 260% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  font-weight: 700;
  animation: vip-shine 5.5s linear infinite;
}
/* Emas Premium. Rentangnya lebih gelap daripada emas perhiasan justru supaya ia terbaca
   sebagai emas: yang muda menghilang ke dalam kertas putih dan terbaca sebagai kuning
   pudar, bukan sebagai logam. */
.vip.gold {
  background-image: linear-gradient(100deg, var(--gold-dark) 0%, var(--gold) 22%, var(--gold-lit) 32%,
    var(--gold) 42%, var(--gold-dark) 64%, var(--gold) 86%, var(--gold-dark) 100%);
}
@keyframes vip-shine {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}
/* Berlian tidak ikut dipotong oleh background-clip — kalau ikut, ia hilang bersama
   warnanya dan yang tersisa cuma ruang kosong di depan nama. */
.vip-gem {
  display: inline-block; margin-right: 4px;
  -webkit-text-fill-color: initial; color: initial;
  filter: drop-shadow(0 0 3px rgba(160, 220, 255, .75));
}
/* Sebagian orang mematikan animasi di tingkat sistem, dan permintaan itu berlaku juga
   di sini: emasnya tetap, kilaunya yang berhenti. */
@media (prefers-reduced-motion: reduce) {
  .vip { animation: none; background-position: 40% 0; }
}

/* --- panel admin: manajemen user ------------------------------------------- */
/* Daftar akun ditulis sebagai kartu, bukan tabel: yang membukanya hampir selalu
   sedang memegang ponsel, dan tabel tujuh kolom di layar 390px adalah tabel yang
   digeser ke samping untuk membaca satu barisnya. */
.switch-row { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; }
.switch-row input { width: 18px; height: 18px; margin-top: 2px; flex: none; }
.switch-row > span { display: flex; flex-direction: column; gap: 3px; }

.filters { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 4px; }
.filters input[type="search"] { flex: 1 1 220px; min-width: 160px; }

.user-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.urow {
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
  background: var(--bg-soft); display: flex; flex-direction: column; gap: 6px;
}
.urow .uhead { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.urow .uname { font-weight: 700; }
.urow .umeta {
  display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12.5px; color: var(--muted);
}
.urow .umeta span { white-space: nowrap; }
.urow .uact { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 2px; }
.urow .uact select { padding: 5px 8px; font-size: 12.5px; }
.ok-tick { color: var(--accent); font-weight: 700; }
.urow .uremark { display: flex; gap: 6px; align-items: center; }
.urow .uremark input { flex: 1; padding: 5px 8px; font-size: 12.5px; }

/* --- katalog paket --------------------------------------------------------- */
/* Nama paket berbayar diberi warna paketnya — di halaman ini dan di kepala tabel
   perbandingan di bawahnya. Warnanya yang bekerja, bukan hurufnya: tiga kartu berjajar
   dengan judul sewarna adalah tiga kartu yang harus dibaca satu per satu sebelum
   ketahuan mana yang gratis. Premium emas, Pro ungu (11 September 2026). */
.plan-name.paid.premium, th.gold-ink { color: var(--gold); }
.plan-name.paid.pro, th.paid-ink { color: var(--premium); }
/* Baris pertama kartu berbayar: "Semua fitur paket Standard". Ia bukan fitur melainkan
   pangkal daftarnya, jadi centangnya diganti tanda panah dan barisnya diberi garis
   pemisah di bawah — yang di bawahnya adalah tambahan, dan itu harus terlihat sebagai
   tambahan. */
.plan-list li.perk-all::before { content: "\2794"; }
.plan-list li.perk-all { padding-bottom: 8px; border-bottom: 1px dashed var(--line); margin-bottom: 2px; }
/* Penegas kecil di ujung baris — "(50× lebih banyak)". Digambar sebagai baris kedua
   yang redup, bukan disambung ke kalimatnya: penegas sebesar nama fiturnya membuat mata
   membaca angkanya lebih dulu dan fiturnya belakangan. */
.plan-list .perk-note { display: block; font-size: 12px; color: var(--muted); font-weight: 600; }
.plan .price { font-size: 26px; margin: 10px 0 2px; }
.plan .price b { font-size: 30px; }
/* Bintang dua penunjuk keterangan harga: harus terlihat kalau dicari, tidak boleh ikut
   terbaca sebagai bagian dari angkanya. Karena itu kecil, redup, dan tanpa jarak sebelum
   dirinya — menempel pada angka persis seperti catatan kaki di buku. */
.plan .price .star { font-size: 13px; color: var(--muted); margin-right: 2px; top: -.7em; }
/* Harga asli yang dicoret, dan di bawahnya harga yang benar-benar dibayar di web.
   Yang dicoret sengaja kecil dan redup: yang harus terbaca lebih dulu adalah angka yang
   ditagih, dan dua angka sama besar membuat mata berhenti dua kali untuk memutuskan
   mana yang berlaku. Lencananya ungu — warna berbayar di seluruh produk, bukan merah
   ala obral, karena yang ditawarkan tetap paket yang sama. */
.plan .was { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 14px; color: var(--muted); }
.plan .was + .price { margin-top: 2px; }
/* `potong`, bukan `promo`: `.promo` sudah dipakai tirai pemilih bidak — sebuah lapisan
   `position:absolute; inset:0` yang menutup seluruh layar, dan lencana kecil yang
   kebetulan memakai nama itu ikut membentang menutupi halaman. */
.pill.potong { background: rgba(122, 63, 208, .18); color: var(--premium); }
/* Lencana potongan ikut warna kartunya. Ungu di atas kartu Premium yang emas terbaca
   sebagai lencana milik kartu sebelah yang tersasar — dan halaman ini justru sedang
   berusaha membuat kedua kartu itu tidak tertukar. */
.plan-premium .pill.potong { background: rgba(180, 83, 9, .16); color: var(--gold); }
.plan-hi { border-color: var(--premium-line); box-shadow: 0 0 0 1px rgba(122, 63, 208, .22); }
.plan-mine { border-color: var(--accent); }
.plan-list { list-style: none; padding: 0; margin: 14px 0 16px; display: flex; flex-direction: column; gap: 6px; }
.plan-list li { padding-left: 22px; position: relative; font-size: 14px; }
.plan-list li::before { content: "✓"; position: absolute; left: 0; color: var(--accent); font-weight: 700; }

/* ---------- halaman bermain di ponsel ----------------------------------------
   Satu keluhan, satu penyebab: di layar 390px, kartu pemilih lawan setinggi satu
   layar penuh berdiri di atas papan, jadi yang membuka "Lawan Bot" melihat daftar
   master dan sama sekali tidak melihat papannya sampai ia menggulir. Di sini papan
   naik ke urutan pertama dan pemilih lawan turun ke bawahnya dalam keadaan terlipat.

   Yang dipakai cuma `order` pada wadah halaman, bukan markup kedua: satu halaman
   dengan dua susunan berarti dua tempat yang harus diubah setiap kali ada tombol
   baru, dan yang di ponsel selalu jadi yang ketinggalan. */
@media (max-width: 900px) {
  .play-page { display: flex; flex-direction: column; }
  .play-page .play-layout { order: 1; }
  /* Pemilih lawan sekarang tinggal di kolom kanan bersama daftar langkah. Di ponsel
     kolom itu jatuh ke bawah papan, dan di dalamnya pemilih lawan turun lagi ke
     paling bawah — yang dicari selagi bermain adalah langkah terakhir, bukan daftar
     lawan yang sudah dipilih. */
  .play-page .opponent-pick { order: 3; }

  /* Wajib, dan bukan hiasan: anak sebuah flex/grid menolak menyusut di bawah lebar
     min-content-nya. Pita langkah berisi kata-kata yang tidak boleh dipatah, jadi
     min-content-nya selebar seluruh partai — dan tanpa baris ini lebar itu merambat
     naik sampai ke halaman, mendorong papan keluar layar ke kanan. */
  .play-page, .play-page > *, .play-page .play-layout > *,
  .play-page .play-layout > .grid > *, .play-page .moves { min-width: 0; }

  /* Ruang mati dipangkas semuanya: di sinilah satu layar dimenangkan atau kalah. */
  .play-page .container, .play-page .card { padding: 12px; }
  .play-page .play-layout { gap: 10px; }
  .play-page .eval-head { margin-bottom: 6px; }
  .play-page .board-wrap { margin: 6px auto !important; }
  .play-page .player-row { padding: 6px 10px; }
  .play-page .board-actions { margin-top: 8px; }
  .play-page .board-actions button { min-height: 46px; font-size: 12px; }

  /* Daftar langkah jadi satu pita mendatar: di ponsel yang dicari cuma langkah
     terakhir, dan kolom tegak setinggi 320px memakan tempat papan demi riwayat yang
     tidak sedang dibaca siapa pun. */
  .play-page .moves {
    display: flex; gap: 8px; max-height: none; overflow-x: auto; overflow-y: hidden;
    padding-bottom: 2px; scrollbar-width: none;
  }
  .play-page .moves::-webkit-scrollbar { display: none; }
  .play-page .moves .num { position: sticky; left: 0; }
  .play-page .moves > * { white-space: nowrap; }
  /* Judul "Langkah" tidak menerangkan apa pun yang tidak sudah jelas dari isinya,
     dan sebelum ada langkah pertama kartunya cuma kotak kosong yang mendorong
     pemilih lawan keluar layar. Keduanya hanya untuk kartu langkah — judul "Cari
     lawan" di halaman duel adalah judul yang benar-benar menerangkan sesuatu. */
  .play-page .moves-card > h3 { display: none; }
  .play-page .moves-card:has(> .moves:empty) { display: none; }

  /* Pembukaan dan bar keunggulan jadi satu baris. Dua baris terpisah di atas papan
     berarti 28px yang dibayar setiap layar hanya untuk satu keterangan yang jarang
     dibaca selagi berpikir. */
  .play-page .eval-head { display: flex; align-items: center; gap: 10px; }
  .play-page .eval-head .opening {
    flex: 1 1 auto; min-width: 0; font-size: 12.5px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .play-page .eval-head .eval-row { flex: 0 0 auto; margin-top: 0; width: 46%; }
  .play-page .eval-row .eval-num { min-width: 44px; font-size: 13px; }

}

/* Kartu "Setelah selesai" hanya berisi satu kalimat janji sampai partainya usai.
   Selama masih berisi kalimat itu ia disembunyikan — begitu diisi hasil partai,
   :has berhenti cocok dan kartunya muncul sendiri tanpa satu baris JS pun. Berlaku
   di semua lebar layar: kartu kosong setinggi 90px di kolom kanan sama tidak
   berartinya di laptop seperti di ponsel. */
.play-page .end-card:has(#endBox > p.muted) { display: none; }

/* Kepala kartu yang bisa dilipat: baris yang bisa disentuh, bukan judul mati.
   `display: flex` sekaligus membuang segitiga bawaan <summary> — segitiga itu muncul
   dari display:list-item, dan ia tidak pernah sejajar dengan apa pun yang kita taruh
   di sebelahnya. */
summary.setup-summary {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-weight: 700; font-size: 15px; list-style: none; padding: 2px 0;
}
summary.setup-summary::-webkit-details-marker { display: none; }
summary.setup-summary .txt { flex: 1; }
summary.setup-summary .chev { color: var(--muted); transition: transform .15s; }
details[open] > summary.setup-summary .chev { transform: rotate(180deg); }

/* Di layar lebar tidak ada yang dilipat. Dua nasib berbeda: kepala pemilih lawan
   memang tidak perlu ada (kartunya punya judul sendiri di dalam), sedangkan kepala
   obrolan adalah satu-satunya judul kartu itu — jadi ia tinggal berhenti terlihat
   seperti tombol. */
@media (min-width: 901px) {
  summary.setup-summary.hide-wide { display: none; }
  summary.setup-summary.as-title { cursor: default; font-size: 17px; margin-bottom: 10px; }
  summary.setup-summary.as-title .chev { display: none; }
}

/* ---------- duel online: papan sebelum ada partai ---------------------------
   Keluhan ridwanyas 30 Agustus 2026: membuka Duel Online langsung disambut papan
   lengkap dengan tulisan "Menunggu lawan…", padahal belum satu pun mode dipilih —
   yang terbaca partai yang sedang berjalan, bukan halaman yang menunggu perintah.

   Papannya tidak dihapus, hanya dimundurkan: ia tetap memberi tahu halaman apa ini,
   sementara yang berdiri di depan cuma dua hal yang memang perlu dijawab dulu — mode
   yang dipilih dan tombol yang memulai pencarian.

   Kelas `idle` menempel sejak markup dan dilepas saat partai pertama dimulai; ia tidak
   pernah dipasang lagi sesudahnya, karena papan partai yang baru selesai justru yang
   ingin dilihat orang. `searching` menyembunyikan panggilan ini selagi antre: denyut
   radar di kartu di bawahnya sudah menjawab hal yang sama, dan dua tombol "Batal" di
   satu layar hanya membuat ragu yang mana yang benar. */
.play-page.idle #board {
  filter: blur(3px) saturate(.55);
  opacity: .38;
  pointer-events: none;
}
.play-page.idle .player-row { opacity: .5; }

.board-call {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 16px; text-align: center;
}
.play-page:not(.idle) .board-call, .play-page.idle.searching .board-call { display: none; }
.board-call .call-title { margin: 0; font-size: 15px; font-weight: 800; }
.board-call .call-note { margin: 0; font-size: 12px; color: var(--muted); }
/* Pil mode: menyebut yang sedang dipilih, dan membuka pemilih waktu yang sesungguhnya
   di kartu bawah — bukan pemilih kedua yang harus ikut diperbaiki tiap kali daftar
   kontrol waktunya berubah. */
.board-call .call-tc {
  display: inline-flex; align-items: baseline; gap: 7px;
  padding: 9px 16px; border-radius: 99px;
  background: var(--bg-card); border: 1px solid var(--line); color: var(--text);
  font-size: 14px; box-shadow: 0 6px 18px rgba(12, 20, 16, .10);
}
.board-call .call-tc b { font-size: 15px; }
.board-call .call-tc small { color: var(--muted); font-size: 12px; }
.board-call .call-tc .chev { color: var(--muted); font-size: 11px; }
.board-call .btn.cta { padding: 12px 26px; font-size: 16px; }

/* ---------- duel online di ponsel: dua keadaan, dua susunan -------------------
   Menganggur dan sedang bermain menginginkan hal yang berbeda tepat di bawah papan.
   Selagi mencari lawan, yang dicari tombol "Cari lawan"; begitu jam berjalan, yang
   dicari menyerah/tawar remis dan daftar langkah — dan pemilih waktu jadi kartu yang
   tidak berguna sampai partai ini selesai. Yang berpindah cuma urutan; tidak ada satu
   elemen pun yang dipindahkan oleh JS, jadi tidak ada susunan kedua yang harus ikut
   diubah setiap kali ada tombol baru. */
@media (max-width: 900px) {
  .play-page .play-layout > .grid { display: flex; flex-direction: column; }
  .play-page #lobbyCard { order: 1; }
  .play-page .game-card { order: 2; }
  .play-page .chat { order: 3; }

  .play-page.playing #lobbyCard { display: none; }
  .play-page.playing .game-card { order: 0; }
  /* Judul "Partai" berlebihan waktu kartunya sudah berada tepat di bawah papan. */
  .play-page.playing .game-card > h3 { display: none; }

  /* Tiga tombol partai jadi satu baris selebar layar: sejauh jempol, seukuran jempol. */
  .play-page .board-actions-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .play-page .board-actions-row button { width: 100%; min-height: 44px; }
}

/* ---------- suara: mencari lawan, kartu lawan, pemutar musik ------------------
   Tiga tampilan yang lahir bersama bunyinya. Yang bergerak di sini semuanya punya
   pasangan yang terdengar — denyut radar berpasangan dengan sonar, kartu lawan yang
   masuk berpasangan dengan nada "ketemu" — jadi orang yang mematikan suara tetap
   mendapat kabar yang sama lewat mata, dan sebaliknya. */

/* Menunggu di antrean. Denyut yang melebar keluar dari bidak di tengah. */
.searching {
  display: flex; align-items: center; gap: 14px;
  margin-top: 12px; padding: 12px 14px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius);
}
.searching .radar {
  position: relative; width: 54px; height: 54px; flex: none;
  display: grid; place-items: center;
}
.searching .radar img { width: 30px; height: 30px; position: relative; z-index: 2; }
.searching .radar .ring {
  position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid var(--accent); opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .searching .radar .ring { animation: radar-pulse 2.1s ease-out infinite; }
  .searching .radar .ring:nth-child(2) { animation-delay: .7s; }
  .searching .radar .ring:nth-child(3) { animation-delay: 1.4s; }
  .searching .radar img { animation: radar-bob 2.1s ease-in-out infinite; }
}
/* Tanpa animasi, denyutnya tetap harus terlihat sebagai lingkaran — bukan hilang
   sama sekali, karena itulah satu-satunya tanda halaman masih bekerja. */
@media (prefers-reduced-motion: reduce) { .searching .radar .ring:first-child { opacity: .35; } }
@keyframes radar-pulse {
  0% { transform: scale(.45); opacity: .65; }
  80% { opacity: 0; }
  100% { transform: scale(1.15); opacity: 0; }
}
@keyframes radar-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
.searching-txt { display: flex; flex-direction: column; gap: 2px; }

/* Kartu "lawan ketemu": hitam di atas, putih di bawah, warna kartunya sendiri-sendiri. */
.versus-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, .55);
  opacity: 0; transition: opacity .2s ease;
  padding: 20px;
}
.versus-overlay.show { opacity: 1; }
.versus {
  width: min(420px, 100%);
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px 16px; text-align: center;
}
.vs-head {
  margin: 0 0 12px; text-transform: uppercase; letter-spacing: .12em;
  font-size: 12px; color: var(--muted);
}
.vs-side {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 10px; text-align: left;
  transform: translateX(-24px); opacity: 0;
}
.vs-side.white { transform: translateX(24px); }
.versus-overlay.show .vs-side { transform: none; opacity: 1; transition: transform .32s ease-out, opacity .32s ease-out; }
.versus-overlay.show .vs-side.white { transition-delay: .1s; }
.vs-side.black { background: #2a2c31; border: 1px solid #2a2c31; color: #eceef0; }
.vs-side.white { background: #eceef0; border: 1px solid #d6d9dd; color: #17181b; }
.vs-side .vs-piece { width: 38px; height: 38px; flex: none; }
.vs-who { display: flex; flex-direction: column; min-width: 0; }
.vs-who b { font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vs-who span { font-size: 12px; opacity: .7; }
.vs-you {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 1px 6px; margin-left: 6px;
  vertical-align: middle;
}
.vs-mid { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 8px 0; }
.vs-word { font-weight: 800; font-size: 20px; letter-spacing: .1em; color: var(--accent); }
.vs-tc { font-size: 12px; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .vs-side, .versus-overlay.show .vs-side { transform: none; opacity: 1; transition: none; }
}

/* Pemutar musik di beranda. Satu baris: putar, nama trek, lompat, volume. */
.musicbar {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0 4px; padding: 10px 12px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius);
}
.musicbar .mb-play {
  width: 40px; height: 40px; flex: none; border-radius: 50%;
  padding: 0; font-size: 14px; line-height: 1;
}
.musicbar .mb-play.on { background: var(--accent); }
.musicbar .mb-txt { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.musicbar .mb-txt b { font-size: 14px; }
.musicbar .mb-txt span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.musicbar input[type="range"] { width: 110px; flex: none; accent-color: var(--accent); }
@media (max-width: 560px) {
  .musicbar input[type="range"] { width: 78px; }
}

/* Saklar di kartu setelan: barisnya sendiri yang jadi tombol, bukan kotak kecil di
   ujung — jempol memerlukan sasaran selebar baris. */
.toggle-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 12px; text-align: left;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 10px;
  color: var(--text); font: inherit; cursor: pointer;
}
.toggle-row span { display: flex; flex-direction: column; gap: 2px; }
.toggle-row small { color: var(--muted); font-size: 12px; }
.toggle-row .switch {
  width: 42px; height: 24px; flex: none; border-radius: 999px;
  background: var(--line); position: relative; transition: background .15s ease;
}
.toggle-row .switch::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%; background: var(--muted);
  transition: transform .15s ease, background .15s ease;
}
.toggle-row .switch.on { background: rgba(20, 128, 74, .28); }
.toggle-row .switch.on::after { transform: translateX(18px); background: var(--accent); }

/* Kartu "Cari lawan" selagi antre: yang tersisa hanya pembatalan dan denyutnya.
   Pemilih waktu dan hitungan lobi tidak bisa diapa-apakan sampai antreannya selesai,
   dan di ponsel keduanyalah yang mendorong denyut radar ke balik bilah menu bawah. */
.card.queueing .tc-pick,
.card.queueing #lobbyStats { display: none; }
.card.queueing .searching { margin-top: 0; }
/* Denyutnya naik ke atas tombol Batal: yang sedang terjadi dibaca lebih dulu daripada
   cara membatalkannya. */
.card.queueing { display: flex; flex-direction: column; }
.card.queueing #queueMsg { order: 1; }
.card.queueing .row { order: 2; margin-top: 12px; }


/* ---------- pemilih kontrol waktu (Duel Online) ---------------------------- */
/* Tertutup: satu baris — ikon kecepatan, waktunya, nama kelompoknya, panah.
   Terbuka: kelompoknya turun ke bawah tombol dan mendorong isi kartu, bukan melayang
   di atasnya; di ponsel lapisan melayang di dalam kartu sempit lebih sering menutupi
   hal yang sedang dibaca daripada menghemat ruang. */
.tc-pick { position: relative; }
.tc-toggle {
  width: 100%; display: flex; align-items: center; gap: 10px; text-align: left;
  background: var(--bg-soft); color: var(--text); border: 1px solid var(--line);
  border-radius: 12px; padding: 11px 13px; font-size: 15px; cursor: pointer;
}
.tc-toggle .tc-ic { font-size: 17px; line-height: 1; flex: none; }
.tc-toggle .tc-txt { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; }
.tc-toggle .tc-txt b { font-size: 16px; font-weight: 800; }
.tc-toggle .tc-txt small { color: var(--muted); font-size: 13px; }
.tc-toggle .chev { flex: none; color: var(--muted); font-size: 13px; transition: transform .18s ease; }
.tc-pick.open .tc-toggle { border-color: #bccbc3; }
.tc-pick.open .tc-toggle .chev { transform: rotate(180deg); }
@media (hover: hover) { .tc-toggle:hover { background: var(--accent-soft); } }

.tc-menu { margin-top: 12px; display: grid; gap: 14px; }
/* `display: grid` di atas mengalahkan `display: none` bawaan atribut [hidden] —
   tanpa baris ini daftarnya tidak pernah benar-benar tertutup. */
.tc-menu[hidden] { display: none; }
.tc-group-head {
  display: flex; align-items: center; gap: 7px;
  font-weight: 700; font-size: 14px; margin-bottom: 8px;
}
.tc-group-head .ic { font-size: 15px; }
.tc-group[data-mode="bullet"]    .tc-group-head { color: var(--warn); }
.tc-group[data-mode="blitz"]     .tc-group-head { color: #a3800f; }
.tc-group[data-mode="rapid"]     .tc-group-head { color: var(--accent); }
.tc-group[data-mode="classical"] .tc-group-head { color: var(--info); }

.tc-opts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.tc-opt {
  position: relative; background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 6px; font-size: 14px; font-weight: 700; cursor: pointer;
}
@media (hover: hover) { .tc-opt:hover { background: var(--accent-soft); } }
.tc-opt.active {
  border-color: var(--accent); color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* Tujuh menit tidak ada di tempat lain — penanda kecil ini yang membedakannya dari
   angka di sebelahnya, tanpa mengubah bentuk tombolnya. */
.tc-opt .tc-tag {
  position: absolute; top: -7px; right: -5px;
  background: var(--accent); color: var(--on-accent);
  font-size: 9px; font-weight: 800; font-style: normal; letter-spacing: .3px;
  padding: 2px 5px; border-radius: 999px; text-transform: uppercase;
}

/* Umpan bot: ada di DOM, tidak pernah ada di layar. Dikeluarkan dari alur dengan
   posisi absolut supaya tidak menggeser apa pun, bukan dengan `display:none` — sebagian
   bot memang melewati kolom yang display-nya none, dan yang dicari justru bot yang
   mengisi semua kolom. */
.hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

/* Kotak Cloudflare Turnstile di formulir Masuk/Daftar/Lupa password (js/turnstile.js).
   Tinggi disiapkan lebih dulu supaya tombol di bawahnya tidak melompat saat kotak muncul. */
.ts-box { margin-top: 14px; min-height: 65px; }

/* ---------- halaman Analysis: papan, tombol, dan vonis dalam satu layar --------
   Sebelumnya maju-mundur tinggal di dalam kartu di bawah baris pemain. Di layar
   1366x768 kartu itu mulai pada 827px — 59px di bawah tepi layar — sehingga menelusuri
   satu partai berarti menggulir turun, menekan ▶, menggulir naik untuk melihat papan,
   lalu mengulanginya untuk tiap langkah. Tombolnya sekarang menempel di bawah papan,
   dan papannya ikut tinggi layar (var(--board-w) dari .play-page) supaya keduanya
   muat bersama tanpa satu gulungan pun. */
.analysis-nav {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px;
  max-width: 620px; margin: 10px auto 0;
}
.analysis-nav button { min-height: 46px; padding: 6px 4px; font-size: 17px; line-height: 1; }
/* Maju satu langkah ditekan sebanyak jumlah langkah partai; ia tidak boleh terlihat
   sama dengan empat tombol di sebelahnya yang ditekan sekali-dua kali. */
.analysis-nav .step-next { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
@media (hover: hover) { .analysis-nav .step-next:hover { background: var(--accent-dark); } }

/* ---------- mesin sedang membedah: kabarnya di atas papan ------------------------
   Menunggu mesin memakan waktu semenit lebih, dan selama itu satu-satunya yang terjadi
   di layar adalah bilah kemajuan setipis rambut di kartu sebelah kanan — yang di ponsel
   bahkan tidak terlihat tanpa menggulir. Jadi kabarnya dipindah ke tempat mata sudah
   berada: papan itu sendiri.

   Papannya tidak ditutup, hanya disamarkan. Di baliknya bidak tetap berjalan, langkah
   demi langkah, mengikuti sejauh mana mesin sudah membedah — jadi yang ditonton orang
   selama menunggu adalah partainya sendiri diputar ulang, bukan sebuah lingkaran
   berputar yang tidak menceritakan apa-apa. */
.an-run {
  position: absolute; inset: 0; z-index: 6; border-radius: 8px;
  display: flex; align-items: flex-end;
  background: rgba(247, 250, 248, .34);
  /* Menahan ketukan: papan yang sedang diputar ulang bukan papan yang bisa dipakai. */
  cursor: progress;
}
.an-run.hidden { display: none; }
/* Selama mesin memakai papan, tombol yang menggerakkan papan tidak menerima ketukan. */
body.analysing .analysis-nav button,
body.analysing #runBtn { pointer-events: none; opacity: .5; }
/* Kabarnya menempel di tepi bawah papan seperti bilah pemutar video: di sana ia tidak
   pernah menutupi petak yang sedang bergerak. */
.an-run-foot {
  /* Setipis mungkin: setiap piksel tingginya adalah sepotong petak yang tertutup, dan
     yang tertutup di tepi bawah papan justru barisan bidak yang paling ramai. */
  width: 100%; padding: 7px 11px 7px;
  border-radius: 0 0 8px 8px; color: #f2f6f3;
  background: linear-gradient(rgba(20, 33, 26, 0), rgba(20, 33, 26, .8) 46%);
}
.an-run-line { display: flex; align-items: baseline; gap: 7px; font-size: 12px; }
.an-run-line b { font-size: 17px; font-variant-numeric: tabular-nums; }
.an-run-line span { color: rgba(255, 255, 255, .82); }
.an-run-track {
  margin-top: 5px; height: 5px; border-radius: 99px; overflow: hidden;
  background: rgba(255, 255, 255, .22);
}
.an-run-track > div {
  height: 100%; width: 0; border-radius: 99px; background: var(--accent-bright);
  transition: width .5s ease;
}

/* Vonis satu baris: apa yang dimainkan, seberapa mahal, dan pintu ke garis terbaiknya. */
.verdict {
  max-width: 620px; margin: 8px auto 0; min-height: 44px;
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px 12px; font-size: 14px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 9px;
}
/* Keterangan di baris atas, tombol di baris bawah dan selalu di tengah — di ponsel
   maupun di layar lebar, dan sama dengan susunan di aplikasi. Sampai 5 Sep 2026
   keduanya berbagi satu baris dengan tombol terdorong ke kanan; letak tombolnya ikut
   bergeser mengikuti panjang nama vonis, jadi tidak ada satu titik pun yang bisa
   dihafal ibu jari. */
.verdict .vline { display: flex; align-items: center; gap: 8px 10px; flex-wrap: wrap; }
/* Bintangnya benar-benar di tengah baris, bukan di tengah sekelompok tombol: tiga
   lajur, dan yang di tengah hanya berisi bintang. Ibu jari yang sudah hafal tempatnya
   tidak boleh meleset gara-gara tombol di sebelahnya berganti tulisan. */
.verdict .acts { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 10px; }
.verdict .acts > .star-btn { grid-column: 2; justify-self: center; }
.verdict .acts > .ghost { grid-column: 3; justify-self: end; }
/* Kalimat penjelas langkahnya — satu baris, warna redup, di antara keterangan dan
   tombol. Ukurannya sengaja tidak sekecil `.cost`: ini kalimat untuk dibaca, bukan
   angka untuk dilirik. */
.verdict .why { font-size: 13px; line-height: 1.45; color: var(--text); }
.verdict .played { font-weight: 700; }
.verdict .cost { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.verdict .grow { flex: 1 1 auto; }
.verdict button { min-height: 34px; padding: 6px 10px; font-size: 13px; }
/* Tombol berbayar yang tetap digambar: bentuknya utuh, warnanya diredam, dan sebuah
   gembok kecil menjelaskan kenapa — bukan tombol yang hilang tanpa keterangan. */
.ghost.locked { opacity: .72; }
.ghost.locked::after { content: " 🔒"; font-size: 11px; }

/* Bintang "langkah terbaik".
   Satu ikon, bukan kalimat: di ponsel "Langkah terbaik" memakan setengah baris, dan
   yang dicari mata adalah tombol yang selalu berada di tempat yang sama. Ia menyala
   hanya saat memang ada yang lebih baik daripada yang dimainkan; pada langkah yang
   sudah terbaik ia tetap digambar (tempatnya tidak boleh berpindah) tapi tenang. */
.star-btn {
  width: 38px; height: 38px; min-height: 38px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 19px; line-height: 1; border-radius: 50%; cursor: pointer;
  color: var(--muted); background: var(--bg); border: 1px solid var(--line);
  transition: transform .12s ease, box-shadow .2s ease;
}
.star-btn:active { transform: scale(.94); }
.star-btn.lit {
  color: #fff; background: #2f8f4e; border-color: #2f8f4e;
  box-shadow: 0 0 0 0 rgba(47, 143, 78, .55);
}
@media (prefers-reduced-motion: no-preference) {
  .star-btn.lit { animation: starGlow 2s ease-in-out infinite; }
}
@keyframes starGlow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(47, 143, 78, .5); }
  50%      { box-shadow: 0 0 0 7px rgba(47, 143, 78, 0); }
}

/* Panel garis terbaik dan panel coba-coba: dua kotak dengan tampilan yang sama, karena
   keduanya menjawab pertanyaan yang sama — "kalau begitu, apa yang lebih baik?". */
.panel {
  max-width: 620px; margin: 8px auto 0; padding: 10px 12px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 9px;
}
.panel .head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13.5px; }
.panel .head .grow { flex: 1 1 auto; }
.panel button.small { min-height: 32px; }

/* Garis lanjutan sebagai deretan langkah yang bisa ditekan: membacanya sebagai teks
   hanya memberi tahu urutannya, menekannya memperlihatkan posisinya. */
.best-line { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
.best-line .step {
  padding: 4px 8px; border-radius: 7px; cursor: pointer; font-size: 12.5px; font-weight: 600;
  background: var(--bg-card); border: 1px solid var(--line); color: var(--text);
  font-variant-numeric: tabular-nums; min-height: 0;
}
@media (hover: hover) { .best-line .step:hover { background: var(--accent-soft); } }
.best-line .step.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.best-line .step.start { background: transparent; color: var(--muted); }
.panel .why { margin: 7px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.4; }
.panel .head button.small { min-height: 30px; padding: 4px 9px; }

/* ---------- Explore: pohon pembukaan -------------------------------------------
   Satu baris = satu langkah yang pernah dimainkan dari posisi ini. Tiga hal dalam satu
   baris: langkahnya, berapa partai memainkannya, dan bagaimana hasilnya — dan hasil
   itu digambar sebagai bar tiga warna karena "42% / 26% / 32%" sebagai angka harus
   dibaca satu per satu, sementara bar-nya terbaca sekali lihat untuk dua belas baris
   sekaligus.

   Nama pembukaannya turun ke baris kedua, bukan dijejalkan ke kolom keempat: nama
   seperti "Sicilian Defense: Najdorf, English Attack" tidak muat di kolom mana pun
   yang masih menyisakan tempat untuk bar-nya. */
.book { display: grid; gap: 5px; }
.book-row {
  display: grid; grid-template-columns: minmax(96px, 1fr) minmax(44px, auto) minmax(96px, 42%);
  align-items: center; gap: 3px 10px;
  width: 100%; text-align: left; padding: 7px 10px;
  background: var(--bg-soft); border: 1px solid transparent; border-radius: 9px;
  color: var(--text); font-size: 14px; cursor: pointer; min-height: 0;
}
@media (hover: hover) { .book-row:hover { border-color: var(--accent); background: var(--accent-soft); } }
.book-row .bk-san { font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.book-row .bk-games { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; text-align: right; }
.book-row .bk-bar { display: flex; height: 19px; border-radius: 5px; overflow: hidden; background: #2f3237; }
/* Putih, remis, hitam — warna hasilnya sendiri, jadi ia tidak ikut berganti bersama
   tema papan atau mode gelap: bagian "putih menang" yang berwarna hijau muda harus
   dijelaskan lebih dulu sebelum bisa dibaca. */
.book-row .bk-bar i {
  display: flex; align-items: center; justify-content: center;
  font-style: normal; font-size: 10.5px; font-weight: 700; letter-spacing: -.2px;
  min-width: 0; overflow: hidden;
}
.book-row .bk-bar .w { background: #f1f1ea; color: #2c2c2c; }
.book-row .bk-bar .d { background: #9aa0aa; color: #fff; }
.book-row .bk-bar .b { background: #2f3237; color: #e9e9e9; }
.book-row .bk-name {
  grid-column: 1 / -1; color: var(--muted); font-size: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Sel pertama sebuah baris: langkahnya, lalu jalur yang paling sering dimainkan
   sesudahnya, dalam satu pita yang digeser ke kanan (permintaan ridwanyas 12 Sep 2026
   — menggantikan "1. e5" yang nomornya sama di semua baris). Pitanya menggulir di dalam
   selnya sendiri supaya kolom angka dan bar hasil tidak ikut bergeser. */
.book-row .bk-strip {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  touch-action: pan-x pan-y; -webkit-overflow-scrolling: touch;
}
.book-row .bk-strip::-webkit-scrollbar { display: none; }
/* Ujung kanan yang memudar: tanda bahwa pitanya masih berlanjut, tanpa satu pun
   bilah gulir. Tanpa ini langkah yang terpotong ("B", "c›") terbaca sebagai tulisan
   yang rusak. */
.book-row .bk-strip { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 22px), transparent); }
.book-row .bk-cont { display: inline-flex; gap: 7px; color: var(--muted); font-size: 12.5px; white-space: nowrap; }
.book-row .bk-cont i { font-style: normal; }
@media (hover: hover) { .book-row .bk-cont i:hover { color: var(--text); text-decoration: underline; } }
/* ★ langkah terbaik mesin (hijau) dan !! calon Brilian (warna lencana Brilian di
   Analysis) — dari /api/analysis/meter, ditempel sesudah bukunya tergambar. */
.book-row .bk-mark { display: inline-flex; gap: 3px; font-style: normal; }
.book-row .bk-mark:not(:empty) { margin-right: 5px; }
.book-row .bk-mark .star { color: var(--accent-bright); font-size: 15px; line-height: 1; }
.book-row .bk-mark .bril { color: #0e9c86; font-size: 13px; line-height: 1; letter-spacing: -1px; }
.book-row.is-best { border-color: var(--accent-bright); }
.book-row.is-brilliant { border-color: #26c2a3; }
.book-row.is-best.is-brilliant { border-color: #26c2a3; box-shadow: inset 0 0 0 1px var(--accent-bright); }

/* Tombol telusur di Explore (12 Sep 2026): ▶ hijau terang dan tidak pernah padam
   selama masih ada jalur buku (lihat stepNext di explore.js); ◀ biru electric —
   permintaan ridwanyas, supaya dua tombol yang paling sering ditekan tidak terlihat
   sama dengan tiga tombol di sebelahnya. */
.explore-page .analysis-nav .step-next { background: var(--accent-bright); border-color: var(--accent-bright); color: #fff; }
@media (hover: hover) { .explore-page .analysis-nav .step-next:hover { background: var(--accent); } }
.explore-page .analysis-nav #undo { background: #2962ff; border-color: #2962ff; color: #fff; }
@media (hover: hover) { .explore-page .analysis-nav #undo:hover { background: #1e4fd6; } }

/* Tiga gaya, tiga langkah (12 Sep 2026): kartu kecil per gaya di bawah catatan
   "posisi ini tidak ada lagi di koleksi". Warna tag-nya membedakan gayanya —
   posisional biru, taktikal ungu, agresif merah — dan sengaja bukan hijau supaya
   tidak terbaca sebagai "langkah terbaik" (itu urusan ★). */
.styles { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 10px; }
.styles h4 { margin: 0 0 8px; font-size: 13.5px; }
.style-list { display: grid; gap: 6px; }
.style-row {
  display: grid; grid-template-columns: auto auto 1fr; align-items: center; gap: 2px 8px;
  width: 100%; text-align: left; padding: 8px 10px; min-height: 0;
  background: var(--bg-soft); border: 1px solid transparent; border-radius: 9px;
  color: var(--text); font-size: 13px; cursor: pointer;
}
@media (hover: hover) { .style-row:hover { border-color: var(--accent); background: var(--accent-soft); } }
.style-row .st-tag {
  font-size: 10.5px; font-weight: 800; letter-spacing: .3px; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; color: #fff;
}
.style-row.posisional .st-tag { background: #1663c7; }
.style-row.taktikal .st-tag { background: #7442c8; }
.style-row.agresif .st-tag { background: #c73b62; }
.style-row .st-san { font-weight: 800; font-size: 14.5px; }
.style-row .st-eval { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; text-align: right; }
.style-row .st-why { grid-column: 1 / -1; font-size: 12.5px; }
.style-row .st-line { grid-column: 1 / -1; color: var(--muted); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.style-row .st-hint { grid-column: 1 / -1; color: var(--muted); font-size: 11px; font-style: italic; }

/* Nomor babak di pita Explore ikut bergulir (bukan `sticky` seperti .moves .num di
   halaman bermain): pitanya selalu digulir ke ujung, dan nomor yang menempel di kiri
   bertumpuk jadi "1516.". */
.explore-page .line-strip .num { position: static; }

/* Jalur yang sedang ditelusuri: satu pita mendatar di bawah papan, di web maupun di
   ponsel. Yang dicari di sini selalu ujungnya — langkah terakhir — bukan riwayat
   lengkapnya, dan kolom tegak setinggi 320px memakan tempat yang seharusnya jadi
   daftar pembukaan. */
.line-strip {
  display: flex; gap: 8px; max-height: none;
  overflow-x: auto; overflow-y: hidden; padding: 2px 0; margin-top: 10px;
  scrollbar-width: none;
}
.line-strip::-webkit-scrollbar { display: none; }
.line-strip > * { white-space: nowrap; }

@media (max-width: 900px) {
  /* Satu layar di ponsel: papan penuh di atas, daftar pembukaan menggulir di dalam
     dirinya sendiri di bawahnya. Tanpa batas tinggi ini, dua belas baris pembukaan
     mendorong kartu "Muat posisi" dan seluruh halaman jadi dua layar. */
  .explore-page .book { max-height: 34vh; overflow-y: auto; padding-right: 2px; }
  /* Judul "Pembukaan dari sini" tidak menerangkan apa pun yang tidak sudah jelas dari
     isinya, dan lima puluh piksel judul di ponsel adalah satu baris pembukaan yang
     tidak jadi terlihat. Jumlah partainya tetap — itu angka, bukan judul. */
  .explore-page .book-card > .row h3 { display: none; }
  /* Tanpa judulnya, pengganjal yang tadinya memisahkan judul dan angka tinggal
     mendorong angka itu sendirian ke tepi kanan. */
  .explore-page .book-card > .row .spacer { display: none; }
  .explore-page .book-card > .row { margin-bottom: 6px; }
  .explore-page .book-card { padding: 10px; }
  .explore-page .book-row { padding: 6px 9px; font-size: 13.5px; }
  .explore-page .book-row .bk-bar { height: 17px; }
  .explore-page .book-row .bk-name { font-size: 11.5px; }
  /* Tombol maju-mundur di sini ditekan sesekali, bukan sepanjang partai seperti di
     halaman Analysis — 40px sudah nyaman untuk ibu jari dan menyisakan satu baris
     pembukaan lagi. */
  .explore-page .analysis-nav button { min-height: 40px; font-size: 15px; }
  .explore-page .line-strip { margin-top: 6px; }
  /* Nomor babak yang `sticky` (aturan .play-page .moves .num di atas) bertumpuk di
     tepi kiri pita ini — "1516." — karena pitanya selalu digulir ke ujung. Di sini
     nomornya ikut bergulir saja. */
  .explore-page .line-strip .num { position: static; }
}

@media (min-width: 901px) {
  /* 24 Sep 2026: bar keunggulan tidak lagi berdiri di atas papan — ia pindah ke sisi
     kiri papan dan berdiri tegak (js/wide-board.js), jadi tinggi yang dipotong turun
     dari 384 ke 318px: bilah situs 57 + jarak 12 + dua plakat 84 + jarak papan 12 +
     tombol maju-mundur 54 + baris vonis 44 + sisa 55. Papan 384 → 450px di 1366x768,
     dan berhenti tumbuh di 760px supaya di layar tinggi ia tidak menelan kolom kanan. */
  .analysis-page { --board-w: min(760px, calc(100vh - 252px)); }
  .analysis-page .analysis-nav {
    max-width: var(--board-w); margin-left: auto; margin-right: auto;
  }
  /* Vonis, panel garis terbaik, dan grafik pindah ke kolom kanan (js/wide-board.js):
     di sana lebarnya lebar kartu, bukan lebar papan. */
  .analysis-page .play-side > .verdict,
  .analysis-page .play-side > .panel,
  .analysis-page .play-side > .graph-card { max-width: none; margin: 0; }
  .analysis-page .play-side > .graph-card { padding: 10px 12px; }

  /* Explore memakai perhitungan yang sama, dikurangi baris nama pemain dan vonis yang
     memang tidak ada di sana: yang di atas papan cuma bar keunggulan + nama pembukaan,
     yang di bawahnya baris tombol dan pita langkah. */
  .explore-page { --board-w: min(660px, calc(100vh - 300px)); }
  .explore-page .analysis-nav,
  .explore-page .line-strip {
    max-width: var(--board-w); margin-left: auto; margin-right: auto;
  }
}

/* Angka keunggulan perwira ("+3") mewarisi `padding: 7px 13px` dari `.range, .adv` —
   tab rentang waktu di halaman statistik yang kebetulan bernama sama. Padding itu
   meninggikan baris tawanan dari 18px jadi 32px di SETIAP halaman yang memakainya
   (duel online, lawan bot, dan sekarang analysis), jadi dua baris pemain memakan 28px
   layar untuk ruang kosong di sekeliling satu angka. */
.captured .adv { padding: 0; }

/* Baris pemain di halaman ini digambar ulang setiap langkah: tanpa tinggi tetap untuk
   deretan tawanan, papan tersentak turun 18px pada langkah pertama yang memakan bidak. */
.analysis-page .player-row { row-gap: 3px; }
.analysis-page .player-row .captured { min-height: 18px; flex-wrap: nowrap; overflow: hidden; }

@media (max-width: 900px) {
  .analysis-page .analysis-nav { margin-top: 8px; }
  .analysis-page .analysis-nav button { min-height: 44px; font-size: 15px; }
  .analysis-page .verdict { padding: 7px 10px; font-size: 13px; }
  .analysis-page .graph-card { margin-top: 10px; }
}

/* ---------- Padepokan ---------- */
/* Tampilan peta memakai kartu yang sama dengan halaman lain; yang ditambahkan di sini
   hanya bentuk yang memang belum ada — jenjang, gembok, dan kotak kandidat. */

.pad-head { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; margin: 18px 0 12px; }
.pad-head .subtitle { max-width: 62ch; }
.pad-rank { display: flex; align-items: center; gap: 9px; margin-left: auto; }
.pad-rank > div { display: flex; flex-direction: column; line-height: 1.25; }
.pad-rank-icon { font-size: 30px; line-height: 1; }
.pad-quota { margin-bottom: 14px; }

.pad-ladder { display: grid; gap: 12px; }
.pad-level.locked { opacity: .62; }
.pad-level-head { display: flex; align-items: center; gap: 12px; }
.pad-level-icon { font-size: 26px; line-height: 1; }

.pad-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
.pad-chip {
  font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 99px;
  background: var(--bg-soft); color: var(--muted); border: 1px solid var(--line);
}

.pad-classes { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; margin-top: 10px; }
.pad-class {
  background: var(--bg-card); border: 1px solid var(--line); border-radius: 9px; padding: 10px 12px;
  display: flex; flex-direction: column; gap: 7px;
}
/* Kelas yang bisa dibuka terasa bisa ditekan; yang terkunci tidak pernah berpura-pura bisa. */
.pad-class.open { cursor: pointer; }
.pad-class.open:hover, .pad-class.open:focus-visible { border-color: var(--accent); outline: none; }
.pad-class.done { border-color: #b6dcc5; background: linear-gradient(180deg, var(--accent-soft), var(--bg-soft)); }
.pad-class.locked { opacity: .55; }
.pad-class-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pad-class-note { line-height: 1.35; }
.pad-tick { color: var(--accent); font-size: 12px; font-weight: 700; }
.progress.slim { height: 5px; }

.pad-exam { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); }
.pad-locked-note { margin: 10px 0 0; }

.pad-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 8px; }
.pad-stats > div { display: flex; flex-direction: column; gap: 2px; }
.pad-stats b { font-size: 22px; line-height: 1.1; }

/* ---------- Padepokan: ruang latihan ---------- */
.pad-drill-head { margin: 14px 0 10px; }
.pad-turn { text-align: center; margin-top: 8px; font-size: 14px; }
.pad-timer {
  font-variant-numeric: tabular-nums; font-weight: 700; padding: 4px 10px;
  border-radius: 99px; background: var(--bg-soft); border: 1px solid var(--line);
}
.pad-timer.urgent { color: var(--danger); border-color: #f0b7ae; background: #fdeeec; }

.pad-cands { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.pad-cand {
  min-width: 62px; text-align: center; padding: 8px 10px; border-radius: 8px;
  border: 1px dashed var(--line); color: var(--muted); font-weight: 700;
}
.pad-cand.filled { border-style: solid; border-color: var(--accent); color: var(--text); background: var(--accent-soft); }

.pad-verdict, .pad-pick { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 6px; }
.pad-verdict button, .pad-pick button { flex: 1 1 auto; }

.pad-ok { color: var(--accent); font-weight: 600; }
.pad-no { color: var(--danger); }
/* "Terlihat tapi tidak dipilih" sengaja diberi warna sendiri: ia bukan benar, bukan
   salah biasa, dan justru itu satu-satunya vonis di halaman ini yang menunjuk langsung
   ke hal yang bisa diperbaiki besok pagi. */
.pad-near {
  color: #8a5300; background: #fff6e6;
  border: 1px solid #f0d29b; border-radius: 8px; padding: 9px 11px;
}

/* Lencana paket Pro: ungu, dengan kilau yang sama tapi lebih lambat. Sampai
   11 September 2026 ia bergaris dengan tulisan ungu sementara Premium diisi penuh —
   dua tingkat yang dibedakan oleh tebal-tipisnya, dan yang lebih tipis selalu terbaca
   sebagai yang lebih rendah. Sekarang keduanya penuh dan yang membedakan warnanya.
   Berliannya tetap kosong (◇) melawan ◆ milik Premium: satu petunjuk lagi yang tidak
   menuntut warna, untuk yang membedakan warna dengan susah payah. */
.badge.pro {
  color: #fff; border-color: var(--premium-dark); letter-spacing: .01em;
  background: linear-gradient(100deg, var(--premium-dark) 0%, var(--premium) 26%, #b98af0 34%,
    var(--premium) 44%, var(--premium-dark) 70%, var(--premium) 88%, var(--premium-dark) 100%);
  background-size: 260% 100%;
  text-shadow: 0 1px 0 rgba(0, 0, 0, .28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
  animation: vip-shine 7.5s linear infinite;
}
.badge.pro::before { content: "\25C7"; margin-right: 5px; font-size: 10px; vertical-align: .5px; }

/* ====================== halaman baca: Kursus & Papan Peringkat ==================
   Dua halaman ini lebih banyak dibaca daripada dimainkan, dan dulu merekalah satu-
   satunya yang terang di situs yang seluruhnya gelap — dipasang dengan
   `<body class="light">` dan sebuah palet biru kampusnya sendiri.

   Sejak seluruh situs terang, kelas itu tidak lagi diperlukan dan palet birunya
   dibuang: yang tersisa di bawah ini hanya bentuk yang memang milik kedua halaman
   ini — kepala halaman, tingkat, kartu kelas, podium — dan warnanya kini hijau merek
   yang sama dengan halaman lain. Jingga tetap dipakai untuk satu hal saja: ajakan
   bertindak di halaman Kursus, supaya tombol "mulai" tidak tenggelam di antara
   belasan hal hijau di sekitarnya.                                                */
:root {
  --tone-navy: #0d3a26;
  --tone-orange: #ef7d16;
  --tone-orange-dark: #d76a08;
  --tone-blue-soft: #e8f1fd;
}

/* ---------- kepala halaman ---------- */
.pad-hero {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin: 18px 0 16px; padding: 22px 24px; border-radius: var(--radius);
  background: linear-gradient(120deg, #0d5f37 0%, #17864f 60%, #34b473 100%); color: #fff;
}
.pad-hero h1 { margin: 4px 0 0; font-size: 27px; line-height: 1.2; }
.pad-hero p { margin: 8px 0 0; max-width: 64ch; color: rgba(255, 255, 255, .88); }
.pad-hero-tag {
  font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: #cdeedd;
}
.pad-hero .pad-rank { margin-left: auto; padding: 10px 14px; border-radius: 10px; background: rgba(255, 255, 255, .14); }
.pad-hero .pad-rank .muted, .pad-hero .pad-rank b { color: #fff; }

/* ---------- tingkat: satu baris yang bisa dibuka ---------- */
.pad-level { padding: 0; overflow: hidden; }
.pad-level-head {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 14px 16px; cursor: pointer; user-select: none;
}
.pad-level-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pad-level-name { flex: 1; min-width: 0; }
.pad-level-name h3 { margin: 1px 0 0; font-size: 18px; }
.pad-level-name p { margin: 2px 0 0; color: var(--muted); font-size: 13px; }
.pad-level-order {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
.pad-level-meta { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.pad-caret { color: var(--muted); transition: transform .18s ease; }
.pad-level.expanded .pad-caret { transform: rotate(180deg); }
/* Yang dilipat tidak sekadar disembunyikan mata: ia keluar dari alur, supaya halaman
   yang berisi lima tingkat tetap muat satu layar. */
.pad-level-body { display: none; padding: 0 16px 16px; border-top: 1px solid var(--line); }
.pad-level.expanded .pad-level-body { display: block; }
.pad-level.expanded .pad-level-head { background: var(--accent-soft); }

/* ---------- kelas: kartu yang berjajar, bukan daftar yang menurun ---------- */
.pad-classes { grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 12px; margin-top: 14px; }
.pad-class { padding: 14px; gap: 8px; }
.pad-class-title { margin: 0; font-size: 16px; line-height: 1.25; }
.pad-class-desc { margin: 0; font-size: 13px; line-height: 1.45; color: var(--muted); flex: 1; }
.pad-facts { display: flex; flex-wrap: wrap; gap: 5px; }
.pad-facts span {
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 99px;
  background: var(--bg); color: var(--muted); border: 1px solid var(--line);
}
.pad-step { font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.pad-count { font-size: 12px; font-weight: 700; color: var(--accent); }
.pad-lock { font-size: 12px; color: var(--muted); }
.pad-class-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
.pad-cta { font-size: 12px; font-weight: 700; color: var(--muted); }
.pad-class.open .pad-cta { color: var(--tone-orange, var(--warn)); }
.pad-exam { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.pad-class.open:hover, .pad-class.open:focus-visible {
  border-color: var(--accent); box-shadow: 0 6px 18px rgba(16, 44, 30, .10);
}
.pad-class.locked { opacity: 1; background: var(--bg-soft); border-style: dashed; }
.pad-class.locked .pad-class-title, .pad-class.locked .pad-class-desc { color: var(--muted); }
/* Satu-satunya jingga yang tersisa di situs, dan itu disengaja: tombol ujian berdiri
   di dalam kartu yang seluruhnya hijau, dan tombol hijau di sana berhenti terbaca
   sebagai langkah berikutnya. */
.pad-exam button { background: var(--tone-orange); color: #fff; }
.pad-exam button:hover { background: var(--tone-orange-dark); }
.pad-chip { background: var(--accent-soft); border-color: #c3e3d1; color: #0f6a3c; }

/* ---------- ruang latihan, dengan palet yang sama ---------- */

/* Benteng yang berputar selagi soal berikutnya dicari.
   Ditumpuk persis di atas papan — bukan menggantikannya — supaya tinggi halaman tidak
   berubah sedikit pun ketika ia muncul dan hilang; papan yang menyusut lalu tumbuh lagi
   di antara dua soal membuat kartu di sebelahnya melompat.
   Papannya sendiri sudah dirapikan ke posisi awal sebelum ini muncul: yang tidak boleh
   terlihat adalah posisi soal yang barusan dijawab masih terpampang sementara soal
   berikutnya diminta ke server. */
.pad-loader {
  /* Lengkungan sudutnya sama persis dengan papan (8px, lihat .board di atas): sudut
     yang lebih tumpul menyisakan sepotong petak gelap mengintip di luar tirai — cacat
     kecil yang justru langsung terlihat karena ia satu-satunya warna pekat di layar. */
  position: absolute; inset: 0; z-index: 6; border-radius: 8px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: rgba(247, 250, 248, .82);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.pad-loader p { margin: 0; font-size: 13px; font-weight: 600; color: var(--muted); }
/* Dua sisi satu keping: benteng putih di depan, benteng hitam di belakang. Berkas
   bidaknya diambil dari set yang sedang dipakai pemain (lihat pieceUrl di theme.js),
   jadi yang berputar selalu benteng yang sama dengan yang ada di papannya. */
.pad-rook { position: relative; width: 92px; height: 92px; transform-style: preserve-3d; }
.pad-rook .face {
  position: absolute; inset: 0; width: 100%; height: 100%;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  filter: drop-shadow(0 6px 14px rgba(16, 44, 30, .22));
}
.pad-rook .back { transform: rotateY(180deg); }
@keyframes padRookFlip {
  0% { transform: rotateY(0deg); }
  100% { transform: rotateY(360deg); }
}
/* Diam untuk yang meminta gerak seminimal mungkin: kepingnya tetap ada dan tetap
   memberi tahu bahwa ada yang sedang ditunggu, hanya tidak berputar. */
@media (prefers-reduced-motion: no-preference) {
  .pad-rook { animation: padRookFlip 1.3s ease-in-out infinite; }
}

@media (max-width: 560px) {
  .pad-rook { width: 74px; height: 74px; }
  .pad-hero { padding: 18px; }
  .pad-hero h1 { font-size: 22px; }
  .pad-classes { grid-template-columns: 1fr; }
  .pad-level-head { padding: 12px 13px; gap: 10px; }
  .pad-level-body { padding: 0 13px 14px; }
}

/* ---------- papan peringkat di atas latar terang ---------- */
/* Kartu di situs ini tidak membawa margin sendiri — yang mengatur jarak selalu induknya.
   Di halaman peringkat induknya tidak mengatur apa-apa, jadi penyaring dan papan skor
   menempel tanpa sela. Di atas latar gelap sambungannya nyaris tak terlihat; di atas
   latar terang ia terbaca sebagai satu kotak yang jebol. */
#paneElo > * + *,
#panePoint > * + * { margin-top: 14px; }
/*
   Dua papan di halaman ini adalah panel gelap berhias (.arena), dan keduanya sengaja
   TIDAK diubah: warnanya berdiri sendiri, tidak meminjam peubah tema, jadi ia tampil
   persis sama di atas latar terang maupun gelap — sebuah papan skor yang ditempel di
   halaman, seperti papan hasil di dinding gedung olahraga. Yang diberi warna baru hanya
   perabot di sekelilingnya: kepala halaman, tab, penyaring, dan kartu "posisi kamu".

   Satu hal dikunci dengan sengaja di bawah ini: peubah --accent dan --shadow dikembalikan
   ke nilai gelapnya di dalam .arena. Tanpa itu sorotan baris "ini aku" ikut berubah biru
   mengikuti tema halaman, dan panel yang seharusnya tidak berubah jadi berubah sedikit.
*/
.arena {
  --bg: #16171a;
  --bg-soft: #1e2024;
  --bg-card: #24262b;
  --line: #33363d;
  --text: #e8e9ec;
  --muted: #9aa0aa;
  --accent: #7fa650;
  --accent-dark: #6b8f42;
  --danger: #d15b4a;
  --warn: #e0a33e;
  --info: #5b8fd1;
  --shadow: 0 6px 24px rgba(0, 0, 0, .35);
  /* Warna teks dasar ikut dikembalikan. Judul panel tidak menulis warnanya sendiri —
     ia mewarisi dari halaman, dan di halaman terang ia jadi biru tua di atas panel biru
     tua. */
  color: var(--text);
}
.arena .btn { color: #10240a; }

/* Warna kepala halaman tetap menandai tab yang sedang dibuka — biru untuk rating,
   oranye untuk point — hanya birunya kini biru yang sama dengan sisa halaman. */
/* Warna kepala halaman tetap menandai tab yang sedang dibuka — hijau untuk point,
   jingga untuk rating. Dua kepala, dua warna, dan yang hijau adalah warna merek. */
.lb-hero {
  background: linear-gradient(120deg, #0d5f37 0%, #17864f 58%, #34b473 100%);
  box-shadow: 0 10px 30px rgba(23, 134, 79, .22);
}
.lb-hero.elo {
  background: linear-gradient(120deg, #b4530a 0%, #ef7d16 55%, #f8ae3c 100%);
  box-shadow: 0 10px 30px rgba(217, 106, 8, .22);
}
/* Judul papan rating hijau di atas jingga — permintaan ridwanyas. Hijaunya dipilih yang
   muda: hijau tua di atas jingga tua sama-sama gelap, dan judulnya justru hilang. */
.lb-hero.elo h2 { color: #7ff2ae; text-shadow: 0 1px 2px rgba(90, 40, 0, .35); }

.lb-tab.active[data-t="elo"] { background: linear-gradient(120deg, #b4530a, #ef7d16); }
.lb-tab.active[data-t="point"] { background: linear-gradient(120deg, #0d5f37, #17864f); }
@media (hover: hover) {
  .lb-tab.active[data-t="elo"]:hover { background: linear-gradient(120deg, #96450a, #d76a08); }
  .lb-tab.active[data-t="point"]:hover { background: linear-gradient(120deg, #0a4b2c, #0f6a3c); }
}

.scope-tabs .range.active { background: linear-gradient(120deg, #d76a08, #f6a623); }

.mine-card {
  background: linear-gradient(120deg, var(--accent-soft), #fff 70%);
  border-color: #c3e3d1;
}
.pts-cell { color: var(--tone-orange-dark); }
.streak-tag { color: var(--tone-orange-dark); }
tr.lb-row.top td { background: #fff7ea; }
.rank-pill { background: var(--accent-soft); color: #0f6a3c; }

/* Podium berdiri di atas putih sekarang, jadi juara kedua perlu abu-abu yang benar-benar
   terlihat — lapisan tembus pandangnya dulu dihitung untuk latar gelap dan di sini ia
   praktis hilang. */
.podium .slot.p1 {
  background: linear-gradient(160deg, #fff2d1, #fff); border-color: #f0b429;
  box-shadow: 0 8px 24px rgba(240, 180, 41, .22);
}
.podium .slot.p2 { background: linear-gradient(160deg, #eef2f7, #fff); border-color: #b6c2d1; }
.podium .slot.p3 { background: linear-gradient(160deg, #fdf0e2, #fff); border-color: #e0a066; }
.podium .slot.p1 .pts { color: #b8860b; }

/* =================================================================================
   Kerangka aplikasi — hanya di dalam APK (`html.native`, dipasang js/config.js).

   Web tidak pernah melihat satu pun aturan di blok ini: yang membuat sebuah situs
   terasa seperti situs (bilah merek, laci menu, halaman panjang yang digulir terus)
   justru yang membuat sebuah aplikasi terasa seperti situs yang dibungkus. Jadi di
   dalam cangkang, kerangkanya diganti — halamannya tetap halaman yang sama.
   ================================================================================= */

/* Bilah judul. Ikut menggulung bersama halaman? Tidak: nama layar dan pintu ke akun
   adalah dua hal yang harus selalu ada, persis seperti di aplikasi mana pun. */
html.native .appbar {
  display: flex; align-items: center; gap: 12px;
  padding: calc(9px + env(safe-area-inset-top)) 14px 9px;
  background: var(--bg-card); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 40;
}
html.native .appbar .ab-title {
  flex: 1; min-width: 0; font-size: 18px; font-weight: 700;
  display: flex; align-items: center; gap: 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
html.native .appbar .ab-brand { display: flex; align-items: center; flex: none; }
html.native .appbar .ab-brand img { width: 24px; height: 24px; display: block; }
html.native .appbar .ab-brand img + img { margin-left: -11px; }
html.native .appbar .ab-me { display: flex; align-items: center; gap: 8px; text-decoration: none; }
html.native .appbar .ab-rating {
  font-size: 12px; font-weight: 700; color: var(--muted);
  background: var(--bg-card); border: 1px solid var(--line);
  padding: 3px 9px; border-radius: 999px;
}
html.native .ava {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  display: grid; place-items: center; font-weight: 800; font-size: 14px;
}

/* Tab bawah. Lima petak setara, ikon di atas label — bentuk yang sudah dikenal jempol
   dari setiap aplikasi lain di HP yang sama. */
/* Langit malam biru, sama dengan `.tabbar` — dua bilah yang sama, satu untuk situs di
   peramban dan satu untuk cangkang aplikasi; kalau warnanya berbeda, pemakai yang
   berpindah di antara keduanya akan mengiranya dua produk. */
html.native .appnav {
  display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  background: linear-gradient(180deg, #12326e 0%, #0a1d44 100%);
  border-top: 1px solid #1c4790;
  padding-bottom: env(safe-area-inset-bottom);
  isolation: isolate; overflow: hidden;
}
html.native .appnav::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  opacity: .13; filter: brightness(0) invert(1);
  background-repeat: no-repeat;
  background-image: url(/img/pieces/wn.svg), url(/img/pieces/wq.svg), url(/img/pieces/wp.svg);
  background-position: 8% 130%, 52% 150%, 92% 125%;
  background-size: 62px, 74px, 46px;
}
html.native .appnav > a, html.native .appnav > button {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 9px 2px 8px; font-size: 10.5px; font-weight: 600;
  color: #a9c4ef; background: none; border: 0; text-decoration: none;
  font-family: inherit;
}
html.native .appnav .ic { line-height: 0; display: inline-flex; }
html.native .appnav > a.active { color: #5fd0ff; }
/* Halaman berakhir di atas tab, bukan di baliknya. */
html.native .container { padding-bottom: calc(84px + env(safe-area-inset-bottom)); }
html.native footer { display: none; }

/* Lembar "Lainnya": sisa menu, naik dari bawah — jarak tempuh jempol terpendek dari
   tab yang membukanya. */
html.native .sheet-wrap .sheet-backdrop {
  position: fixed; inset: 0; z-index: 70; background: rgba(14, 30, 22, .42);
  opacity: 0; pointer-events: none; transition: opacity .2s ease;
}
html.native body.sheet-open .sheet-backdrop { opacity: 1; pointer-events: auto; }
html.native .sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 71;
  max-height: 82vh; overflow-y: auto;
  background: var(--bg-card); border-top: 1px solid var(--line);
  border-radius: 18px 18px 0 0; box-shadow: 0 -12px 32px rgba(16, 44, 30, .18);
  padding: 8px 14px calc(18px + env(safe-area-inset-bottom));
  transform: translateY(102%); transition: transform .24s ease;
  visibility: hidden;
}
html.native body.sheet-open .sheet { transform: translateY(0); visibility: visible; }
html.native .sheet-grip {
  width: 38px; height: 4px; border-radius: 999px; background: var(--line);
  margin: 4px auto 12px;
}
html.native .sheet-me {
  display: flex; align-items: center; gap: 11px; padding: 10px 12px; margin-bottom: 6px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 12px;
  color: var(--text); text-decoration: none;
}
html.native .sheet-me .me-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; }
html.native .sheet-login { display: block; text-align: center; margin-bottom: 8px; }
html.native .sheet-group { padding: 10px 0 2px; }
html.native .sheet-group + .sheet-group { border-top: 1px solid var(--line); }
html.native .sheet-title {
  display: block; padding: 2px 2px 8px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
}
html.native .sheet-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
html.native .sheet-item {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 10px 4px; border-radius: 12px; text-align: center;
  font-size: 11px; font-weight: 600; color: var(--text); text-decoration: none;
  background: var(--bg-soft); border: 1px solid var(--line);
}
html.native .sheet-item .ic { line-height: 0; display: inline-flex; }
html.native .sheet-item.active { border-color: var(--accent); color: var(--accent); }
html.native .sheet-out { width: 100%; margin-top: 14px; }

/* Sub-menu halaman panjang. Satu baris yang bisa digeser, bukan dilipat: tab yang
   patah ke baris kedua berhenti terbaca sebagai satu deret pilihan. */
html.native .subnav {
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none;
  margin: 0 -16px 14px; padding: 0 16px 2px;
  position: sticky; top: calc(48px + env(safe-area-inset-top)); z-index: 30;
  background: var(--bg);
}
html.native .subnav::-webkit-scrollbar { display: none; }
html.native .subnav button {
  flex: none; padding: 8px 14px; border-radius: 999px; font-size: 13px; font-weight: 600;
  background: var(--bg-soft); color: var(--muted); border: 1px solid var(--line);
  font-family: inherit; white-space: nowrap;
}
html.native .subnav button.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

/* ---------- kotak pesan ------------------------------------------------------
   Amplop di pojok bilah judul, dengan angka merah kalau ada yang belum dibaca.
   Diminta ridwanyas 4 September 2026. Lencananya menempel di sudut amplop dan
   boleh keluar dari kotaknya (`position: absolute` di dalam pembungkus yang
   `relative`): lencana yang ikut mendorong tata letak akan menggeser seluruh
   bilah judul setiap kali satu pesan masuk. */
/* Di bilah merek amplop berdiri tepat sebelum tombol menu — juga di layar HP, di mana
   `.who` (nama + tombol keluar) memang disembunyikan. Kotak pesan yang hanya bisa
   dibuka dari dalam laci adalah kotak pesan yang lencananya tidak pernah terlihat. */
.topbar .inbox-link { order: 90; margin-left: auto; }
@media (min-width: 901px) {
  .topbar .inbox-link { margin-left: 0; order: 99; }
  .topbar .who { order: 100; }
}
.inbox-link {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 999px; flex: none;
  color: var(--text); text-decoration: none;
}
@media (hover: hover) { .inbox-link:hover { background: var(--bg-soft); text-decoration: none; } }
.badge-dot {
  position: absolute; top: 3px; right: 2px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px; border: 2px solid var(--bg-card);
  background: #d93b3b; color: #fff;
  font-size: 10px; font-weight: 800; line-height: 13px; text-align: center;
}

/* Satu pesan. Yang belum dibaca diberi garis tepi kiri berwarna DAN label "baru" —
   warna saja hilang bagi yang tidak bisa membedakannya. */
.msg-head { display: grid; grid-template-columns: 38px 1fr auto; gap: 10px; align-items: center; }
.msg-ic {
  width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
  color: var(--t, var(--accent)); background: color-mix(in srgb, var(--t, var(--accent)) 14%, transparent);
}
.msg-title { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.msg-title b { font-family: var(--font-display); font-size: 16px; line-height: 1.25; }
.msg-new {
  align-self: start; padding: 2px 8px; border-radius: 999px;
  background: #d93b3b; color: #fff; font-size: 10px; font-weight: 800;
}
.msg-body { margin: 10px 0 0; font-size: 14.5px; line-height: 1.55; white-space: pre-line; }
.msg.unread { border-left: 4px solid var(--accent); }

/* ---------- beranda aplikasi ----------
   Beranda web menjual: judul besar, satu paragraf per menu. Beranda aplikasi tidak
   perlu menjual apa pun kepada orang yang sudah memasangnya — ia harus membawanya
   bermain dalam satu ketukan, jadi paragrafnya dibuang dan yang tersisa tombol. */
html.native .app-hero {
  display: flex; align-items: center; gap: 12px; padding: 14px;
  background: linear-gradient(135deg, var(--accent-soft), var(--bg-card));
  border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 14px;
}
html.native .app-hero .ava { width: 44px; height: 44px; font-size: 19px; }
html.native .app-hero .h-txt { flex: 1; min-width: 0; }
html.native .app-hero .h-txt b { display: block; font-size: 16px; }
html.native .app-cta { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
html.native .app-cta a {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 16px 8px; border-radius: var(--radius); text-decoration: none;
  font-weight: 700; font-size: 15px; color: var(--on-accent); background: var(--accent);
}
/* Tombol hijau: ikonnya putih seperti tulisannya, bukan berwarna. Aksen di atas aksen
   adalah satu-satunya pasangan dari palet ini yang tidak bisa dibaca. */
html.native .app-cta a .ic { line-height: 1; color: currentColor; }
html.native .app-cta a.alt { background: var(--bg-card); color: var(--text); border: 1px solid var(--line); }
/* Tiga petak yang selalu penuh kiri-kanan, dan ikut membesar kalau layarnya besar.
   Diminta ridwanyas 4 September 2026. `repeat(3, 1fr)` sudah membuatnya penuh; yang
   kurang adalah isinya — petak selebar 300 piksel dengan ikon 40 piksel terlihat
   seperti kartu kosong yang kebetulan ada gambar kecil di tengahnya.

   Ukurannya diambil dari lebar barisnya sendiri (`cqw`), bukan dari lebar layar:
   petak ini juga dipakai di dalam kolom yang tidak selebar jendela, dan `vw` di sana
   akan menghitung ruang yang bukan miliknya. Satu petak ≈ 33cqw, jadi 11cqw kira-kira
   sepertiga lebar petak — perbandingan yang sama dengan 40 piksel pada petak 114 di
   HP. Batas atasnya bukan hiasan: tanpa itu petak di tablet mendatar setinggi 300
   piksel dan maskot di bawahnya terdorong keluar layar. */
html.native .tiles {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  container-type: inline-size;
}
html.native .tiles a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(6px, 1.8cqw, 11px);
  padding: clamp(14px, 4cqw, 26px) clamp(6px, 1.8cqw, 12px);
  border-radius: var(--radius); text-align: center;
  background: var(--bg-soft); border: 1px solid var(--line);
  color: var(--text); text-decoration: none; font-weight: 600;
  font-size: clamp(12px, 3.5cqw, 22px);
}
html.native .tiles a .ic-chip { --chip: clamp(40px, 11cqw, 76px); }
html.native .tiles a .ic-chip .ic-svg { width: clamp(21px, 5.8cqw, 40px); height: auto; }
/* Gembok petak menu: lencana emas, bukan tulisan abu-abu — sama seperti lencana
   "premium" di web, dan gembok tak berwarna terbaca sebagai menu yang rusak, bukan
   sebagai menu berbayar. Emas, bukan ungu, sejak 11 September 2026: yang ditawarkan
   petak terkunci adalah Premium — paket paling murah yang membukanya — dan ungu kini
   berarti Pro. */
html.native .tiles a .lock {
  font-size: 9px; font-weight: 700; color: #fff; background: var(--gold);
  border-radius: 999px; padding: 1px 7px;
}
html.native .tiles a .lock::before { content: "\25C6"; margin-right: 4px; font-size: 8px; }
html.native .sect-title {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); margin: 22px 0 8px;
}

/* ---------- layar pembuka aplikasi (js/splash.js) ----------
   Menutup seluruh layar dan mengunci gulir selama ia terpasang. Warnanya mentah, bukan
   token tema: layar ini harus tetap sama persis dengan layar pembuka Android yang baru
   saja ditinggalkan, dan yang di sana ditulis di res/values/colors.xml — bukan di sini. */
html.splash-on, html.splash-on body { overflow: hidden; }
.splash {
  position: fixed; inset: 0; z-index: 200;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: #ffffff; color: #14211a;
  padding: 24px calc(24px + env(safe-area-inset-left)) calc(28px + env(safe-area-inset-bottom));
  opacity: 1; transition: opacity .25s ease;
}
.splash.gone { opacity: 0; pointer-events: none; }

/* Adegan: dua kuda berhadapan di atas sepotong papan, masing-masing dengan gelembung
   bicara yang berdenyut bergantian — yang satu bicara, yang satu mendengar. */
.sp-scene { position: relative; width: 232px; height: 118px; }
.sp-board {
  position: absolute; left: 0; right: 0; bottom: 0; height: 15px;
  display: grid; grid-template-columns: repeat(8, 1fr); border-radius: 4px; overflow: hidden;
  box-shadow: 0 8px 24px rgba(16, 44, 30, .18);
}
.sp-board span { background: #eeeed2; }
.sp-board span.d { background: #769656; }
.sp-side { position: absolute; bottom: 13px; width: 74px; text-align: center; }
.sp-side img { width: 58px; height: 58px; display: block; margin: 0 auto; }
.sp-left { left: 6px; animation: sp-bob 2.6s ease-in-out infinite; }
.sp-right { right: 6px; animation: sp-bob 2.6s ease-in-out infinite 1.3s; }
/* Kuda hitam menghadap ke kiri supaya keduanya benar-benar berhadapan. */
.sp-right img { transform: scaleX(-1); }

.sp-bubble {
  display: inline-flex; gap: 4px; align-items: center;
  padding: 7px 9px; margin-bottom: 6px; border-radius: 11px;
  background: #f1f6f2; border: 1px solid #dde6e0;
  opacity: 0; animation: sp-say 2.6s ease-in-out infinite;
}
.sp-bubble.b2 { animation-delay: 1.3s; }
.sp-bubble i {
  width: 5px; height: 5px; border-radius: 50%; background: #7d8a83;
  animation: sp-dot 1.1s ease-in-out infinite;
}
.sp-bubble i:nth-child(2) { animation-delay: .16s; }
.sp-bubble i:nth-child(3) { animation-delay: .32s; }

@keyframes sp-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@keyframes sp-say { 0%, 8% { opacity: 0; transform: translateY(4px); } 16%, 46% { opacity: 1; transform: none; } 56%, 100% { opacity: 0; } }
@keyframes sp-dot { 0%, 100% { opacity: .35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-3px); } }

.sp-brand { font-size: 25px; font-weight: 800; letter-spacing: .01em; margin-top: 6px; }
.sp-line { font-size: 13px; color: #5d6b63; min-height: 19px; }
.sp-bar { width: 172px; height: 4px; border-radius: 999px; background: #dde6e0; overflow: hidden; }
.sp-bar i {
  display: block; width: 42%; height: 100%; border-radius: 999px; background: #14804a;
  animation: sp-slide 1.25s ease-in-out infinite;
}
@keyframes sp-slide { 0% { transform: translateX(-105%); } 100% { transform: translateX(245%); } }
.sp-ver { font-size: 11px; color: #8a958f; letter-spacing: .04em; }

/* Kalau menyalanya gagal, layar ini berhenti menjadi sambutan dan menjadi laporan. */
.sp-err {
  max-width: 320px; margin-top: 8px; padding: 14px; border-radius: 12px;
  background: #fdeeec; border: 1px solid #f2c4bd; text-align: center; font-size: 13px;
}
.sp-err b { display: block; margin-bottom: 6px; color: #a5372a; }
.sp-err .sp-msg { display: block; color: #6b4b45; word-break: break-word; }
.sp-err .sp-acts { display: flex; gap: 8px; justify-content: center; margin-top: 12px; }
.sp-err button {
  padding: 8px 14px; border-radius: 9px; font: inherit; font-weight: 600;
  background: #14804a; color: #fff; border: 0;
}
.sp-err button.ghost { background: transparent; color: #5d6b63; border: 1px solid #c4d2c9; }

/* Ada yang memilih sistemnya tidak bergerak; sambutan bukan alasan untuk mengabaikannya. */
@media (prefers-reduced-motion: reduce) {
  .sp-left, .sp-right, .sp-bubble, .sp-bubble i, .sp-bar i { animation: none; }
  .sp-bubble { opacity: 1; }
}

/* ================================================================ ulasan partai
   Lencana vonis di papan, cincin akurasi, layar ulasan, dan perayaan.
   Semuanya hanya lapisan tampilan: tidak satu pun angka di bawah ini dihitung
   di CSS, dan mematikan seluruh blok ini tidak menghilangkan satu pun informasi. */

.badge-layer { position: absolute; inset: 0; pointer-events: none; z-index: 5; }
.mv-badge {
  position: absolute; display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-weight: 800; line-height: 1; color: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,.45); border: 2px solid rgba(0,0,0,.25);
}
.mv-badge.pop { animation: badgePop 260ms cubic-bezier(.2,1.5,.4,1) both; }
@keyframes badgePop {
  0%   { transform: scale(.2) rotate(-18deg); opacity: 0; }
  60%  { transform: scale(1.18) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}
/* Warna lencana adalah warna vonisnya di daftar langkah — satu kosakata warna untuk
   seluruh ulasan, jadi merah selalu berarti hal yang sama di mana pun ia muncul. */
.mv-badge.label-brilliant { background: #26c2a3; }
.mv-badge.label-great { background: #6f9fe0; }
.mv-badge.label-best { background: #7fd18a; color: #14301c; }
.mv-badge.label-book { background: #c2a077; color: #2a1f12; }
.mv-badge.label-forced { background: #6d727b; }
.mv-badge.label-inaccuracy { background: #e0c15b; color: #3a2f07; }
.mv-badge.label-mistake { background: #e09a4a; color: #33200a; }
.mv-badge.label-missed { background: #ef8a72; color: #3a1710; }
.mv-badge.label-blunder { background: #e05b4a; }

/* --- cincin akurasi --- */
.acc-ring { position: relative; display: inline-block; }
.acc-ring svg { width: 100%; display: block; transform: rotate(-90deg); }
.acc-ring circle { fill: none; stroke-width: 9; stroke-linecap: round; }
.acc-ring circle.track { stroke: var(--bg-soft); }
.acc-ring .acc-val {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  gap: 1px; font-weight: 800; font-size: 22px; letter-spacing: -.02em;
}
.acc-ring.small .acc-val { font-size: 17px; }
.acc-ring .acc-val small { font-size: .6em; font-weight: 700; color: var(--muted); }
/* Kilau yang berjalan menyusuri busur. Digambar di atas busurnya dengan `screen`, jadi
   ia menerangkan warna yang sudah ada alih-alih menutupinya dengan putih — cincin merah
   tetap merah waktu dilewati cahaya. Jedanya lebih panjang daripada lintasannya supaya
   terbaca sebagai kilau sesekali, bukan sebagai benda yang berputar terus. */
.acc-ring circle.glint {
  stroke-width: 7; stroke-linecap: round; opacity: 0;
  mix-blend-mode: screen; filter: blur(1.1px);
}
.acc-ring circle.glint.run { animation: acc-glint 2600ms cubic-bezier(.5,0,.5,1) infinite; }
@keyframes acc-glint {
  0%   { stroke-dashoffset: 0; opacity: 0; }
  12%  { opacity: .95; }
  46%  { opacity: .95; }
  58%  { stroke-dashoffset: var(--glint-to); opacity: 0; }
  100% { stroke-dashoffset: var(--glint-to); opacity: 0; }
}
.acc-ring .acc-name {
  margin-top: 6px; font-size: 12px; color: var(--muted); text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --- Liga mingguan (/liga, 15 Sep 2026) — warna divisi datang dari server (--div). --- */
.liga-hero {
  display: flex; align-items: center; gap: 14px; padding: 18px; border-radius: 16px; color: #fff;
  background: linear-gradient(135deg, color-mix(in srgb, var(--div) 75%, #000) 0%, var(--div) 60%,
    color-mix(in srgb, var(--div) 75%, #fff) 100%);
  box-shadow: 0 8px 22px color-mix(in srgb, var(--div) 35%, transparent);
}
.liga-crest {
  flex: none; width: 58px; height: 58px; border-radius: 50%; background: rgba(255, 255, 255, .18);
  display: flex; align-items: center; justify-content: center; color: #fff;
}
.liga-hero-txt small { text-transform: uppercase; letter-spacing: .08em; font-weight: 800; font-size: 11px; opacity: .85; }
.liga-hero-txt h1 { margin: 2px 0; font-size: 24px; color: #fff; }
.liga-hero-txt p { margin: 0; font-size: 13.5px; opacity: .92; }
.liga-ladder { display: flex; gap: 6px; margin: 10px 0 4px; }
.liga-step {
  flex: 1; text-align: center; font-size: 12px; font-weight: 700; padding: 6px 4px; border-radius: 999px;
  background: var(--bg-soft); color: var(--muted); border: 1px solid var(--line);
}
.liga-step.on { background: var(--div); color: #fff; border-color: transparent; }
.liga-last { margin: 10px 0; padding: 10px 12px; border-radius: 12px; background: var(--bg-soft); font-size: 13.5px; }
.liga-last.up { background: color-mix(in srgb, #1f9d55 12%, transparent); }
.liga-last.down { background: color-mix(in srgb, #d64545 10%, transparent); }
.liga-group h2, .liga-join h2, .liga-rules h2 { font-size: 17px; margin: 0 0 10px; }
.liga-group h2 small { font-weight: 600; color: var(--muted); font-size: 12.5px; margin-left: 6px; }
.liga-row {
  display: grid; grid-template-columns: 26px 36px minmax(0, 1fr) auto; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 12px; margin-top: 2px;
}
.liga-row.up { background: color-mix(in srgb, #1f9d55 9%, transparent); }
.liga-row.down { background: color-mix(in srgb, #d64545 8%, transparent); }
.liga-row.me { outline: 2px solid var(--accent); outline-offset: -2px; }
.liga-place { font-weight: 900; font-size: 16px; text-align: center; color: var(--muted); font-variant-numeric: tabular-nums; }
.liga-row.up .liga-place { color: #1f9d55; }
.liga-row.down .liga-place { color: #d64545; }
.liga-name { min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; font-size: 14px; color: var(--text); }
.liga-name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.liga-name em {
  font-style: normal; font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent);
}
.liga-name small { flex-basis: 100%; font-size: 12px; color: var(--muted); }
.liga-pts { font-weight: 900; font-size: 18px; text-align: right; font-variant-numeric: tabular-nums; color: var(--text); }
.liga-pts small { display: block; font-size: 10.5px; font-weight: 700; color: var(--muted); }
.liga-sep { margin: 8px 4px; font-size: 11.5px; font-weight: 800; letter-spacing: .04em; display: flex; align-items: center; gap: 8px; }
.liga-sep::after { content: ''; flex: 1; border-top: 2px dashed currentColor; opacity: .5; }
.liga-rule-list { margin: 0; padding-left: 18px; font-size: 13.5px; line-height: 1.6; }
.liga-join-acts { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 10px; }

/* --- layar ulasan --- */
.modal-card.review-reveal { text-align: center; border-top: 4px solid var(--accent); }
.rv-title { margin: 2px 0 0; font-size: 23px; }
.rv-sub { margin: 2px 0 14px; font-size: 13px; }
/* Pita langkah brilian di kartu ulasan + kartu bagikan satu langkah (15 Sep 2026).
   Hijau toska = warna vonis Brilian (.label-brilliant), bukan ungu paket berbayar. */
.rv-brill {
  display: flex; align-items: center; gap: 12px; width: 100%; margin: 14px 0 0;
  padding: 10px 12px; border: 0; border-radius: 12px; cursor: pointer; text-align: left;
  color: #fff; background: linear-gradient(135deg, #0e9c86, #13b89c 55%, #0b7f6d);
  box-shadow: 0 6px 18px rgba(14, 156, 134, .35); animation: brill-pulse 2.4s ease-in-out infinite;
}
.rv-brill-mark {
  flex: none; width: 34px; height: 34px; border-radius: 50%; background: #fff; color: #0e9c86;
  display: inline-flex; align-items: center; justify-content: center; font-weight: 900; font-size: 15px;
}
.rv-brill-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.rv-brill-txt b { font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rv-brill-txt span { font-size: 13px; opacity: .9; }
.rv-brill-go {
  flex: none; display: inline-flex; align-items: center; gap: 4px; font-weight: 800; font-size: 13px;
  background: rgba(255, 255, 255, .18); padding: 5px 9px 5px 6px; border-radius: 999px;
}
@keyframes brill-pulse {
  0%, 100% { box-shadow: 0 6px 18px rgba(14, 156, 134, .35); }
  50% { box-shadow: 0 6px 28px rgba(14, 156, 134, .65); }
}
/* HP sempit: pil tinggal ikonnya, supaya "Langkah brilian!" tidak terpotong. */
@media (max-width: 420px) { .rv-brill-go-t { display: none; } .rv-brill-go { padding: 6px; } }
@media (prefers-reduced-motion: reduce) { .rv-brill { animation: none; } }
.modal-card.move-share { text-align: center; }
.modal-card.move-share.brilliant { border-top: 4px solid #0e9c86; }
.ms-title { margin: 2px 0 0; font-size: 23px; color: #0e9c86; }
.ms-sub { margin: 4px 0 12px; font-size: 13.5px; }
.ms-img {
  display: block; width: 100%; max-width: 420px; height: auto; margin: 0 auto;
  border-radius: 14px; box-shadow: 0 8px 24px rgba(0, 0, 0, .25);
}
#shareMoveBtn { display: inline-flex; align-items: center; gap: 6px; }
/* Dua cincin SAMA BESAR (5 Sep 2026). Dulu cincinku dibuat lebih besar supaya mata
   tidak perlu mencari mana yang miliknya — tapi ukuran lingkaran terbaca sebagai
   besarnya angka, jadi akurasi 85,8 milikku tampak mengalahkan 87,2 milik lawan.
   ridwanyas menyebutnya "bar meternya kebalik", dan dia benar: satu-satunya hal yang
   boleh berbeda ukurannya di sini adalah busurnya. Mana yang milikku sekarang
   ditandai namanya, bukan ukurannya. */
.rv-rings { display: flex; align-items: flex-start; justify-content: center; gap: 26px; }
.acc-ring.mine .acc-name { color: var(--text); font-weight: 700; }
.acc-ring.mine .acc-name::after {
  content: 'kamu'; display: block; font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent);
}
.rv-list { margin-top: 16px; display: grid; gap: 3px; }
.rv-row {
  display: grid; grid-template-columns: 46px 1fr 46px; align-items: center; gap: 8px;
  padding: 6px 10px 6px 12px; border-radius: 9px; background: var(--bg-soft);
  border-left: 3px solid currentColor;
  opacity: 0; transform: translateY(6px);
  transition: opacity 260ms ease, transform 260ms ease;
}
.rv-row.in { opacity: 1; transform: none; }
.rv-row .rv-name { text-align: left; font-size: 13.5px; color: var(--text); }
.rv-row .rv-name { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Kotak tanda vonis: lebar tetap, tanda di tengah persis (ridwanyas 14 Sep 2026) —
   "!!" dan "✓" tidak sama lebar, dan tanpa kotak nama vonisnya mulai di titik yang
   berbeda tiap baris. */
.rv-ic {
  flex: none; width: 26px; height: 26px; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, currentColor 14%, transparent);
  font-size: 13px; font-weight: 800; line-height: 1; letter-spacing: -.04em;
}

/* Kepala kartu ulasan = papan skor, sama dengan aplikasi (_SeatHeader + _ScoreBoard):
   foto bercincin (hijau untuk aku), nama + bendera, rating, skor besar di tengah. */
.rv-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: start; gap: 8px; margin: 6px 0 14px;
}
.rv-seat { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.rv-pic {
  padding: 2.5px; border-radius: 50%; border: 2px solid var(--line); line-height: 0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .1); margin-bottom: 4px;
}
.rv-seat.mine .rv-pic { border-color: var(--accent); }
/* Tautan ke profil di kepala kartu: tampilannya tidak berubah, hanya bisa diketuk. */
.rv-link { display: flex; flex-direction: column; align-items: center; gap: 2px; color: inherit; text-decoration: none; min-width: 0; max-width: 100%; }
.rv-pic .pfp { border-radius: 50%; }
.pfp-master img { object-fit: cover; }
/* Nama boleh turun ke baris kedua, bukan dipotong: kolom kursi di ponsel hanya ±110px,
   dan "Mikhail …" menyembunyikan justru hal yang dicari di kepala kartu. */
.rv-who {
  max-width: 100%; text-align: center; font-size: 13.5px; line-height: 1.3;
  color: var(--text); overflow-wrap: anywhere;
}
.rv-who b { font-weight: 700; }
.rv-seat.mine .rv-who b { font-weight: 800; }
.rv-flag { white-space: nowrap; }
.rv-elo { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.rv-you { font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); }
.rv-score {
  margin-top: 14px; display: flex; align-items: center; gap: 8px; padding: 8px 12px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: 12px;
}
.rv-score b { font-size: 30px; font-weight: 900; line-height: 1; font-variant-numeric: tabular-nums; color: var(--text); }
.rv-score i { font-style: normal; font-size: 22px; font-weight: 700; line-height: 1; color: var(--muted); }

/* Angka di kartu ulasan adalah TOMBOL menuju langkah-langkah yang dihitungnya.
   Warnanya diwarisi dari kelas vonis di barisnya, jadi tidak ada daftar warna kedua.
   Angka nol tetap digambar supaya kolomnya sejajar, tapi ia bukan tombol — dan
   bedanya harus terlihat sebelum disentuh, bukan sesudah. */
.rv-n {
  font-size: 17px; font-weight: 800; font-variant-numeric: tabular-nums;
  line-height: 1.5; text-align: center; border-radius: 8px; color: inherit;
}
button.rv-n {
  padding: 2px 0; border: 0; background: color-mix(in srgb, currentColor 12%, transparent);
  cursor: pointer; transition: background 140ms ease, transform 140ms ease;
}
button.rv-n:hover, button.rv-n:focus-visible {
  background: color-mix(in srgb, currentColor 24%, transparent);
}
button.rv-n:active { transform: scale(.92); }
.rv-n.zero { color: var(--muted); opacity: .5; font-weight: 700; }

/* --- perubahan rating di layar akhir partai --- */
.go-rating {
  margin: 12px auto 2px; display: flex; align-items: center; justify-content: center;
  gap: 10px; flex-wrap: wrap;
}
.go-rt-num { font-size: 30px; font-weight: 800; letter-spacing: -.02em; }
.go-rt-delta {
  font-size: 15px; font-weight: 800; padding: 3px 10px; border-radius: 999px;
  opacity: 0; transform: scale(.6);
  transition: opacity 220ms ease, transform 260ms cubic-bezier(.2,1.6,.4,1);
}
.go-rt-delta.in { opacity: 1; transform: none; }
.go-rt-delta.up { background: var(--accent-soft); color: var(--accent); }
.go-rt-delta.down { background: #fdeeec; color: var(--danger); }
.go-rt-tier {
  flex-basis: 100%; font-size: 14px; font-weight: 700; color: #a3800f;
  opacity: 0; transform: translateY(4px); transition: opacity 300ms ease, transform 300ms ease;
}
.go-rt-tier.in { opacity: 1; transform: none; }

/* --- kertas warna-warni --- */
.fx-confetti { position: fixed; inset: 0; pointer-events: none; z-index: 120; overflow: hidden; }
.fx-confetti i {
  position: absolute; top: -6vh; border-radius: 2px; opacity: .95;
  animation-name: fxFall; animation-timing-function: cubic-bezier(.25,.6,.5,1);
  animation-fill-mode: forwards;
}
@keyframes fxFall {
  0%   { transform: translate3d(0, -10vh, 0) rotate(0); opacity: 1; }
  100% { transform: translate3d(var(--drift, 0), 110vh, 0) rotate(var(--spin, 360deg)); opacity: .1; }
}

@media (prefers-reduced-motion: reduce) {
  .mv-badge.pop { animation: none; }
  .rv-row { opacity: 1; transform: none; transition: none; }
  .go-rt-delta { opacity: 1; transform: none; }
  .go-rt-tier { opacity: 1; transform: none; }
  .acc-ring circle.fill { transition: none; }
  .acc-ring circle.glint { display: none; }
}

/* Bagikan hasil: satu keping kecil di dalam kartu akhir partai, sengaja tidak ikut
   deretan tombol aksi supaya tidak ada tombol di sana yang tidak menutup modal. */
.go-share-row { margin: 12px 0 2px; }
.go-share { padding: 7px 14px; border-radius: 999px; font-size: 13px; }

/* ============================================================ rasa di tangan
   Bidak yang dimakan, jam yang kritis, dan riak sentuhan. */

.fx-layer { position: absolute; inset: 0; pointer-events: none; z-index: 4; }
.capture-fx { position: absolute; }
.capture-fx .ghost {
  position: absolute; inset: 0; width: 100%; height: 100%;
  animation: poofGhost 380ms cubic-bezier(.3,.7,.4,1) forwards;
}
.capture-fx i {
  position: absolute; left: 50%; top: 50%; border-radius: 50%;
  transform: translate(-50%, -50%);
  animation: poofBit 420ms cubic-bezier(.2,.7,.4,1) forwards;
}
@keyframes poofGhost {
  0%   { transform: scale(1); opacity: .9; }
  100% { transform: scale(.45) rotate(-14deg); opacity: 0; }
}
@keyframes poofBit {
  0%   { transform: translate(-50%, -50%) scale(1); opacity: .9; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.2); opacity: 0; }
}

/* Jam sendiri di bawah sepuluh detik: berdenyut, bukan sekadar merah. Denyutnya
   sedetik sekali — sama dengan detak jantung yang menemaninya di telinga. */
.player-row .clock.critical {
  background: #f7d7d1; color: #8f2b1f; font-variant-numeric: tabular-nums;
  animation: clockPulse 1s ease-in-out infinite;
}
@keyframes clockPulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(224,91,74,.5); }
  35%      { transform: scale(1.07); box-shadow: 0 0 0 7px rgba(224,91,74,0); }
}

/* Skak: TIDAK ADA tanda visual apa pun di papan situs — tidak petak diwarnai, tidak
   kilat tepi papan, dan sejak 7 September 2026 tidak lagi goyangan rajanya.
   Permintaan ridwanyas: "ketika skak jangan ada animasi apapun, biarkan polos saja
   karena sekarang rajanya jadi kecil ketika diskak."

   Rajanya yang mengecil itu BUKAN salah animasinya, dan ini pantas diingat: yang
   membuatnya kerdil adalah `.check` beberapa ribu baris di bawah — baris daftar periksa
   halaman /hapus-akun, `display: grid; grid-template-columns: 38px 1fr`. Petak yang
   diberi kelas `check` oleh board.js memungut aturan itu dan menggambar rajanya selebar
   kolom pertama daftar: 38px di petak 48px. Kelas petaknya sekarang `in-check`.
   Ini keempat kalinya nama kelas umum menabrak nama kelas lain di berkas ini (tiga
   sebelumnya `.promo`) — grep dulu sebelum memakai nama baru.

   Yang tersisa sebagai tanda skak di situs tinggal bunyi "te-ne-ne-neng" dan getarnya
   (`playCheck`/`buzzCheck`), dan keduanya sekarang berbunyi seketika — jedanya dulu
   ada supaya bunyi menutup goyangan, dan goyangannya sudah tidak ada.

   Aplikasi Flutter TIDAK ikut diubah: permintaannya khusus web. `_ShakePiece` dan
   `kCheckShake` di lib/widgets/chess_board.dart masih bergoyang seperti semula. */

@media (prefers-reduced-motion: reduce) {
  .player-row .clock.critical { animation: none; }
  .capture-fx { display: none; }
}

/* Riak sentuhan. Ukurannya dipasang dari JS (sebesar tombolnya), warnanya dari sini —
   putih di atas tombol hijau, dan sebuah lapisan gelap tipis di atas tombol putih.
   Satu riak untuk keduanya tidak ada: putih di atas putih tidak terlihat, dan gelap di
   atas hijau tua terlihat seperti noda. Yang dipakai currentColor — warna tulisan
   tombolnya sendiri, yang selalu berlawanan dengan latarnya. */
.tap-ripple {
  position: absolute; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, currentColor 0%, transparent 70%); opacity: .28;
  transform: scale(0);
  animation: tapRipple 500ms cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes tapRipple {
  from { transform: scale(0); opacity: .28; }
  to { transform: scale(1); opacity: 0; }
}

/* Halaman aplikasi yang masuk memudar sedikit ke atas. */
html.page-in body { animation: pageIn 200ms ease-out both; }
@keyframes pageIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Kerangka isi selagi menunggu jaringan: lebih baik daripada kotak kosong yang
   membuat orang mengira halamannya rusak, dan lebih jujur daripada pemutar tak
   berujung yang tidak menjanjikan bentuk apa pun. */
.skel { border-radius: 9px; background: #e6ece8; position: relative; overflow: hidden; }
.skel::after {
  content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.85), transparent);
  animation: skelShine 1.25s infinite;
}
.skel-line { height: 12px; margin: 8px 0; }
.skel-line.short { width: 45%; }
.skel-row { padding: 12px; border-radius: 10px; background: var(--bg-soft); margin-bottom: 8px; }
@keyframes skelShine { to { transform: translateX(100%); } }

@media (prefers-reduced-motion: reduce) {
  .tap-ripple { display: none; }
  html.page-in body { animation: none; }
  .skel::after { animation: none; }
}

/* ================================================================= lawan bicara
   Balon kata bot di halaman lawan bot. Melayang di atas papan tanpa mendorongnya:
   papan yang bergeser turun setiap kali bot berkomentar adalah gangguan, bukan
   kepribadian. */
.bot-talk {
  position: relative; z-index: 6; display: flex; align-items: flex-start; gap: 8px;
  margin: 4px 0 0; padding: 0 4px; height: 0;
  opacity: 0; transform: translateY(-6px);
  transition: opacity 200ms ease, transform 240ms cubic-bezier(.2,1.4,.4,1);
  pointer-events: none;
}
.bot-talk.show { opacity: 1; transform: none; pointer-events: auto; }
.bot-talk.hidden { display: none; }
.bot-talk .face {
  width: 34px; height: 34px; border-radius: 50%; flex: 0 0 34px;
  background: var(--bg-soft); border: 1px solid var(--line); object-fit: cover;
}
.bot-talk .face.hidden { display: none; }
.bot-talk .bubble {
  position: relative; max-width: 78%; padding: 8px 12px; border-radius: 12px;
  background: var(--bg-card); border: 1px solid var(--line); box-shadow: var(--shadow);
  font-size: 13.5px; line-height: 1.35; cursor: pointer;
}
.bot-talk .bubble b { display: block; font-size: 11.5px; color: var(--muted); margin-bottom: 1px; }
/* Ekor balon menunjuk ke wajahnya. */
.bot-talk .bubble::before {
  content: ''; position: absolute; left: -6px; top: 12px; width: 10px; height: 10px;
  background: var(--bg-card); border-left: 1px solid var(--line); border-bottom: 1px solid var(--line);
  transform: rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
  .bot-talk { transition: none; }
}

/* ================================================================ misi hari ini */
.mission-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.mission-ring { flex: 0 0 auto; }
.mission-list { margin-top: 12px; display: grid; gap: 6px; }
.mission {
  display: grid; grid-template-columns: 30px 1fr auto; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 10px; background: var(--bg-soft);
  border: 1px solid transparent; transition: border-color 200ms ease, background 200ms ease;
}
.mission.done { background: var(--accent-soft); border-color: #b6dcc5; }
.mission .mi { font-size: 17px; text-align: center; color: var(--warn); display: flex; justify-content: center; }
.mission.done .mi { color: var(--accent); font-weight: 800; }
.mission .mt b { display: block; font-size: 13.5px; }
.mission .mt small { color: var(--muted); font-size: 11.5px; }
.mission .mp { font-size: 12px; font-weight: 700; color: var(--muted); white-space: nowrap; }
.mission.done .mp { color: var(--accent); }
/* Peti: tertutup selama masih ada yang belum selesai, dan bergoyang sekali saat semua
   beres — satu-satunya gerak di kartu ini, jadi ia benar-benar menandai sesuatu. */
.chest {
  margin-top: 12px; display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 10px; background: var(--bg-soft);
  font-size: 13px; color: var(--muted);
}
.chest .ch { font-size: 20px; display: inline-flex; color: var(--warn); }
/* Api beruntun: satu-satunya ikon di kartu misi yang berwarna sendiri, karena angka di
   sebelahnya adalah hal yang paling ingin dilihat orang setiap pagi. */
.streak-fire { display: inline-flex; vertical-align: -.2em; color: #e2670c; }
.chest.open { background: #fdf6e3; color: #8a6a1a; }
.chest.open .ch { animation: chestPop 700ms cubic-bezier(.2,1.5,.4,1) 1; }
@keyframes chestPop {
  0% { transform: scale(.7) rotate(-8deg); }
  50% { transform: scale(1.25) rotate(8deg); }
  100% { transform: scale(1) rotate(0); }
}
@media (prefers-reduced-motion: reduce) {
  .chest.open .ch { animation: none; }
}

/* ====================================================================== emote
   Reaksi cepat di partai online: bilah tombol di kartu Partai, dan balon yang
   melayang naik di baris pengirimnya. */
.emote-bar {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;
  padding-top: 8px; border-top: 1px solid var(--line);
}
.emote-btn {
  width: 38px; height: 38px; padding: 0; font-size: 18px; line-height: 1;
  border-radius: 10px; background: var(--bg-soft); border: 1px solid var(--line);
}
.emote-btn.mute { margin-left: auto; font-size: 15px; }
.emote-btn.mute.on { background: #fbe3df; border-color: #f0b7ae; }

.emote-pop {
  position: relative; height: 0; text-align: center; font-size: 30px; line-height: 1;
  pointer-events: none; opacity: 0; z-index: 6;
}
.emote-pop.hidden { display: none; }
.emote-pop.mine { text-align: center; }
.emote-pop.show { animation: emoteFloat 2.2s ease-out forwards; }
@keyframes emoteFloat {
  0%   { opacity: 0; transform: translateY(6px) scale(.6); }
  15%  { opacity: 1; transform: translateY(0) scale(1.15); }
  30%  { transform: translateY(-2px) scale(1); }
  80%  { opacity: 1; transform: translateY(-14px) scale(1); }
  100% { opacity: 0; transform: translateY(-26px) scale(.9); }
}
@media (prefers-reduced-motion: reduce) {
  .emote-pop.show { animation: none; opacity: 1; }
}

/* ---------- hiasan yang dipasang administrator ------------------------------
   Satu aturan untuk semua petak (lihat src/assets.js dan js/site-assets.js):
   gambarnya dipasang sebagai latar, dengan tirai gelap di atasnya.

   Tirainya wajib dan tidak boleh dijadikan pilihan. Setiap kepala halaman di situs ini
   berteks putih di atas gradien gelap; satu foto terang yang diunggah administrator
   akan menelan teks itu seluruhnya, dan yang mengunggahnya tidak punya cara
   memperbaikinya selain menebak-nebak foto lain. Dengan tirai, foto apa pun tetap
   menyisakan halaman yang bisa dibaca. */
[data-asset].has-asset {
  background-image: linear-gradient(rgba(12, 13, 16, .5), rgba(12, 13, 16, .5)), var(--asset);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* Lambang musiman menggantikan sepasang bidak di bilah judul dengan satu gambar utuh.
   Lebarnya dipatok setara dua bidak supaya bilah judulnya tidak melompat ketika hiasan
   dipasang atau dilepas. */
.topbar .logo.logo-swapped img { width: 34px; height: 22px; object-fit: contain; }

/* ---------- menu yang menghasilkan uang -------------------------------------------
   Kursus, Paket, dan Event adalah tiga menu yang membiayai sisanya, dan di deretan
   menu yang seluruhnya abu-abu ketiganya persis sama tidak terlihatnya dengan
   "Turnamen". Yang membedakannya di sini warna penuh, bukan lencana kecil: warna
   terbaca dari sudut mata, tulisan kecil tidak.

   Tiga warna, tiga janji berbeda — ungu untuk yang berbayar (warna paket di seluruh
   produk), biru untuk pintu langganannya sendiri, emas untuk yang ada batas waktunya.
   Nyalanya ditahan pelan dan tidak pernah berhenti di keadaan redup: menu yang
   berkedip keras terbaca sebagai iklan, dan iklan adalah hal pertama yang mata orang
   belajar lewati.

   Nilai warnanya dipasang lewat `.paid-*` supaya satu-satunya yang perlu diubah
   untuk mengganti warna sebuah menu adalah `tone` di NAV_GROUPS (js/api.js).

   Namanya `paid`, bukan `promo`: `.promo` sudah dipakai sejak lama oleh tirai pemilih
   bidak promosi di atas papan (baris ~517), dan tirai itu `position: absolute; inset: 0`.
   Menu "Paket" yang kebetulan ikut memakai kelas itu selama beberapa menit membentang
   menutupi seluruh bilah atas — satu-satunya menu yang terlihat, di situs yang sedang
   melayani orang. Jangan pilih nama kelas tanpa mencarinya lebih dulu di berkas ini.  */
.paid-purple { --paid-a: #6d28d9; --paid-b: #a855f7; --paid-glow: rgba(168, 85, 247, .55); }
.paid-blue   { --paid-a: #1d4ed8; --paid-b: #3b82f6; --paid-glow: rgba(59, 130, 246, .55); }
.paid-gold   { --paid-a: #b45309; --paid-b: #f59e0b; --paid-glow: rgba(245, 158, 11, .62); }

@keyframes paid-pulse {
  0%, 100% { box-shadow: 0 1px 2px rgba(16, 44, 30, .18), 0 0 0 0 rgba(0, 0, 0, 0); }
  50%      { box-shadow: 0 1px 2px rgba(16, 44, 30, .18), 0 0 13px 2px var(--paid-glow); }
}

/* Kelipnya berlian: redup lalu sedikit membesar, bukan mati-hidup. Yang membuatnya
   terbaca sebagai kilau dan bukan sebagai lampu rusak adalah puncak di 70% yang
   melewati ukuran aslinya. */
@keyframes paid-twinkle {
  0%, 100% { opacity: 1; transform: scale(1); }
  45%      { opacity: .5; transform: scale(.85); }
  70%      { opacity: 1; transform: scale(1.12); }
}

.topbar nav a.paid,
.topbar nav a.paid:hover,
.topbar nav a.paid.active {
  background: linear-gradient(135deg, var(--paid-a), var(--paid-b));
  color: #fff; font-weight: 700; text-decoration: none;
  animation: paid-pulse 2.6s ease-in-out infinite;
}
/* Bentuk keping ini hanya berlaku di bilah lebar. Di bawah 900px `nav` yang sama
   menjadi laci, dan di sana tiap barisnya memang harus selebar lacinya — `inline-flex`
   di dalam laci akan menciutkan ketiganya jadi tiga kancing yang mengambang.

   Ikonnya dikembalikan: di lebar ini ikon menu memang disembunyikan
   (`.topbar nav a .ic`), tapi untuk ketiga menu ini justru ikon itulah setengah dari
   daya tariknya — permintaan ridwanyas, 1 September 2026. Tiga ikon melebarkan deretan
   menu ±50px; yang membuatnya tidak jadi masalah adalah urutan barunya (BAR_ORDER di
   js/api.js), yang menaruh ketiganya di tengah — kalau deretannya kepanjangan, yang
   pertama tergeser keluar layar sekarang Partai Master dan Explore, bukan Paket. */
@media (min-width: 901px) {
  .topbar nav a.paid { display: inline-flex; align-items: center; gap: 7px; padding: 6px 10px; }
  .topbar nav a.paid .ic { display: inline-flex; align-items: center; }
  /* Empat belas menu (jumlahnya sesudah masuk) plus tiga ikon tidak muat di layar 1280
     mana pun, dan deretan yang meluber diberi tepi memudar oleh `.topbar nav.over` —
     tepat di tempat Paket berdiri. Menu berbayar yang dipudarkan adalah kebalikan dari
     yang diminta, jadi jarak antar-menu dirapatkan 3px: cukup untuk memindahkan tepi
     memudar itu ke Partai Master, yang memang tidak apa-apa terpotong. */
  .topbar nav a { padding: 6px 9px; }
}

/* Laci (HP). Kotak yang sama, hanya melebar penuh seperti tetangganya. */
.topbar nav.drawer a.paid,
.topbar nav.drawer a.paid.active {
  background: linear-gradient(120deg, var(--paid-a), var(--paid-b));
  color: #fff; box-shadow: none;
  animation: paid-pulse 2.6s ease-in-out infinite;
}

/* Tab bawah web + tab bawah aplikasi: tidak diberi latar penuh — lima petak berlatar
   di dalam bilah setinggi 56px berhenti terlihat seperti tab dan mulai terlihat seperti
   tumpukan tombol. Warnanya dipakai pada ikon dan labelnya saja. */
.tabbar a.paid, html.native .appnav > a.paid { color: var(--paid-b); }
.tabbar a.paid .ic .ic-svg,
html.native .appnav > a.paid .ic .ic-svg { filter: drop-shadow(0 0 5px var(--paid-glow)); }

/* Lembar "Lainnya" di dalam aplikasi. */
html.native .sheet-item.paid,
html.native .sheet-item.paid.active {
  background: linear-gradient(135deg, var(--paid-a), var(--paid-b));
  color: #fff; border-color: transparent;
  animation: paid-pulse 2.6s ease-in-out infinite;
}

/* Berlian Paket berkelip di mana pun ia muncul. Hanya ikonnya — labelnya tetap diam,
   supaya tulisannya masih bisa dibaca sambil berjalan. */
.paid-blue .ic .ic-svg { animation: paid-twinkle 1.6s ease-in-out infinite; }

/* Hiasan yang bergerak adalah hal pertama yang dimatikan orang yang memintanya
   dimatikan — dan warnanya sendiri sudah cukup mengerjakan tugasnya tanpa gerak. */
@media (prefers-reduced-motion: reduce) {
  .paid, .paid .ic .ic-svg { animation: none !important; }
}

/* --- Zona berbahaya: hapus akun ---------------------------------------------
   Satu-satunya kartu di halaman Akun yang tidak bisa ditarik kembali sesudah
   tenggangnya lewat, jadi ia sengaja tidak terlihat seperti kartu tetangganya.
   Formulirnya sendiri masih tertutup di dalam <details>: yang paling sering
   menekan tombol ini adalah jempol yang salah sasaran, bukan orang yang sudah
   memutuskan — dan satu ketukan tambahan memisahkan keduanya. */
.card.danger-zone { border-color: #f2c4bd; }
.card.danger-zone > h3 { color: var(--danger); }
.card.danger-zone summary {
  cursor: pointer; color: var(--danger); font-weight: 600; font-size: 14px;
  padding: 8px 0; user-select: none;
}
.card.danger-zone summary:hover { text-decoration: underline; }

/* --- Putaran harian & Golden Spin -------------------------------------------
   Satu-satunya tempat di situs ini yang berwarna emas, dan itu disengaja: hijau
   adalah warna Katuranga, emas adalah warna hadiah. Kalau emas dipakai di tempat
   lain juga, jendela ini berhenti terasa seperti kejadian dan kembali menjadi
   satu kartu lagi. Nama kelasnya semua berawalan .spin- karena berkas ini dipakai
   bersama oleh enam halaman; lihat catatan di public/js/spin.js. */
.spin-back {
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
/* Halaman pertama (gratis) biru–putih susu, halaman kedua (dibayar) emas — lihat
   catatan TONES di public/js/spin.js. Kartunya ikut pindah dari ungu ke biru pada
   4 September 2026 bersama rodanya: ungu yang tersisa cuma satu petak, dan satu petak
   ungu di dalam kartu yang seluruhnya ungu tidak akan terlihat sebagai apa pun. Semua
   nilai di bawah adalah keadaan biru; `.golden` yang menimpanya, bukan sebaliknya. */
.spin-card {
  width: min(400px, 100%); max-height: 92vh; overflow-y: auto;
  border-radius: 18px; background: linear-gradient(180deg, #fffdf7 0%, #e6f0ff 100%);
  border: 1px solid #c9dcf7; box-shadow: 0 24px 60px rgba(12, 45, 110, .3);
  transform: translateY(12px) scale(.98); transition: transform .18s ease;
}
.spin-back.show .spin-card { transform: none; }
.spin-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  padding: 16px 18px; border-radius: 18px 18px 0 0;
  background: linear-gradient(135deg, #3D8BFF 0%, #0B49D0 100%);
  color: #ffffff;
}
.spin-head-title { font-size: 22px; font-weight: 800; letter-spacing: .04em; line-height: 1.1; }
.spin-head-sub { font-size: 12.5px; opacity: .8; margin-top: 2px; }
.spin-head-tools { display: flex; gap: 6px; }
.spin-odds-btn, .spin-x {
  width: 30px; height: 30px; border-radius: 999px; border: 0; cursor: pointer;
  background: rgba(255, 255, 255, .28); color: #ffffff; font-size: 15px; font-weight: 700;
}
.spin-odds-btn:hover, .spin-x:hover { background: rgba(255, 255, 255, .5); }
.spin-card.golden .spin-odds-btn, .spin-card.golden .spin-x { color: #3a2b00; background: rgba(255, 255, 255, .45); }
.spin-card.golden .spin-odds-btn:hover, .spin-card.golden .spin-x:hover { background: rgba(255, 255, 255, .8); }
/* Selagi roda berputar ✕ dan Lewati dimatikan (spinTo di spin.js) — dan terlihat mati. */
.spin-x:disabled, .spin-skip:disabled { opacity: .4; cursor: default; pointer-events: none; }

.spin-stage { position: relative; padding: 18px 18px 4px; display: grid; place-items: center; }
.spin-wheel { width: min(300px, 76vw); height: auto; filter: drop-shadow(0 8px 18px rgba(12, 45, 110, .28)); }
.spin-rotor { transform-origin: 160px 160px; }
/* Emoji hadiah, duduk di atas kancing mengkilap (lihat `badge()` di public/js/spin.js).
   Sebelumnya 17 piksel dan telanjang di atas petak; ridwanyas menyebutnya "terlalu kecil
   dan polos" pada 4 September 2026. Ukurannya sekarang mengisi kancing berjari-jari 17. */
.spin-wedge-icon { font-size: 22px; }
/* Warna tulisan petak datang dari atribut `fill` per petak (tiap nada membawa warna
   tulisannya sendiri), jadi di sini tinggal ukuran dan ketebalannya. */
.spin-wedge-label { font-size: 10px; font-weight: 700; }
.spin-wedge-amt { font-size: 11px; font-weight: 800; }
.spin-hub { font-size: 20px; font-weight: 800; }
.spin-pointer {
  position: absolute; top: 6px; left: 50%; transform: translateX(-50%);
  font-size: 26px; color: #0B49D0; text-shadow: 0 2px 4px rgba(0, 0, 0, .25);
}
.spin-card.golden .spin-pointer { color: #b8860b; }

.spin-foot { padding: 6px 18px 18px; display: grid; gap: 8px; text-align: center; }
.spin-go, .spin-buy, .spin-next {
  width: 100%; padding: 14px 16px; border: 0; border-radius: 12px; cursor: pointer;
  font-size: 16px; font-weight: 800; letter-spacing: .03em; color: #ffffff;
  background: linear-gradient(135deg, #3D8BFF 0%, #0B49D0 100%);
  box-shadow: 0 8px 20px rgba(11, 73, 208, .35);
}
.spin-card.golden .spin-go,
.spin-card.golden .spin-buy,
.spin-card.golden .spin-next {
  color: #3a2b00;
  background: linear-gradient(135deg, #ffd764 0%, #e9a90f 100%);
  box-shadow: 0 8px 20px rgba(184, 134, 11, .35);
}
.spin-go:disabled { opacity: .7; cursor: default; }
.spin-skip {
  width: 100%; padding: 10px; border: 1px solid #c9dcf7; border-radius: 12px;
  background: transparent; color: #2a4b7c; cursor: pointer; font-size: 14px;
}
.spin-card.golden .spin-skip { border-color: #e0cd9a; color: #6b5a2a; }
.spin-note { margin: 0; font-size: 12px; color: #6b7e9c; }
.spin-card.golden .spin-note { color: #8a7440; }
.spin-pitch { margin: 0 0 2px; font-size: 15px; font-weight: 700; color: #5a4400; }

.spin-prize { padding: 26px 10px 12px; text-align: center; }
/* Ikon hadiah memakai kancing yang sama dengan petak roda, dibesarkan: yang barusan
   berhenti di bawah penunjuk harus terlihat sebagai benda yang sama, bukan sebagai
   emoji lain yang kebetulan mirip. */
.spin-prize-icon {
  width: 108px; height: 108px; margin: 0 auto; border-radius: 999px;
  display: grid; place-items: center; font-size: 58px; line-height: 1;
  background:
    radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .95) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(150deg, #eaf3ff 0%, #bcd8fb 100%);
  border: 3px solid #ffffff;
  box-shadow: 0 10px 24px rgba(12, 45, 110, .28), inset 0 -8px 18px rgba(12, 45, 110, .12);
}
.spin-card.golden .spin-prize-icon {
  background:
    radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .95) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(150deg, #fff3d4 0%, #f2cf7c 100%);
  box-shadow: 0 10px 24px rgba(150, 105, 0, .3), inset 0 -8px 18px rgba(120, 85, 0, .12);
}
.spin-prize-text { margin-top: 10px; font-size: 17px; font-weight: 800; color: #14356E; }
.spin-prize-mult { margin-top: 6px; font-size: 13px; font-weight: 700; color: #b8860b; }
.spin-prize.jackpot .spin-prize-text { color: #6D34C4; }
.spin-card.golden .spin-prize-text { color: #4a3600; }
.spin-card.golden .spin-prize.jackpot .spin-prize-text { color: #b8860b; }

.spin-oddsbox { padding: 0 18px 16px; }
.spin-odds { width: 100%; font-size: 12.5px; border-collapse: collapse; }
.spin-odds th, .spin-odds td { padding: 6px 4px; border-bottom: 1px solid #dbe7f8; text-align: left; }
.spin-card.golden .spin-odds th, .spin-card.golden .spin-odds td { border-bottom-color: #eedfb4; }
.spin-odds .right { text-align: right; }

/* Halaman kedua: sama bentuknya, lebih tua warnanya — supaya "yang dibayar" terasa
   berbeda dari "yang gratis" tanpa perlu satu kalimat pun menjelaskannya. */
.spin-card.golden { background: linear-gradient(180deg, #fff8e6 0%, #f6e3b4 100%); }
.spin-card.golden .spin-head { background: linear-gradient(135deg, #f0c33c 0%, #cf8f00 100%); }
.spin-wheel.golden { filter: drop-shadow(0 8px 22px rgba(150, 105, 0, .4)); }

@media (prefers-reduced-motion: reduce) {
  .spin-rotor { transition: none !important; }
}

/* Tombol bantuan mesin (hadiah putaran harian) — menempel di pojok papan.
   Berkelip supaya terlihat tanpa harus dijelaskan, tapi kelipnya berhenti saat
   ditekan: yang berkedip terus sesudah dipakai terbaca seperti tombol rusak. */
.hint-row { display: flex; justify-content: flex-end; margin: -2px auto 8px; max-width: 620px; }
.hint-btn {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 12px; border: 0; border-radius: 999px; cursor: pointer;
  background: linear-gradient(135deg, #ffd764 0%, #e9a90f 100%); color: #3a2b00;
  font-size: 14px; font-weight: 800; box-shadow: 0 6px 16px rgba(184, 134, 11, .4);
  padding-left: 14px; padding-right: 14px;
  animation: hint-blink 1.4s ease-in-out infinite;
}
.hint-btn.busy, .hint-btn:disabled { animation: none; opacity: .75; }
.hint-ic { font-size: 15px; line-height: 1; }
/* Angka sisa jatah, dalam kapsul putih supaya terbaca sebagai hitungan — bukan sebagai
   bagian dari kata "Bantuan". */
.hint-n {
  background: rgba(255, 255, 255, .6); border-radius: 999px;
  padding: 1px 8px; font-size: 13px;
}
@keyframes hint-blink {
  0%, 100% { box-shadow: 0 6px 16px rgba(184, 134, 11, .4); transform: scale(1); }
  50% { box-shadow: 0 8px 22px rgba(233, 169, 15, .75); transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) { .hint-btn { animation: none; } }

/* --- Ajak Teman (/referral) -------------------------------------------------
   Empat bentuk kecil, dan semuanya soal satu hal: angka yang harus terbaca dari
   jarak satu lengan, di layar HP, tanpa dicari. */

/* Daftar syarat. Yang sudah beres dan yang belum dibedakan bentuk DAN warna —
   warna saja hilang bagi yang tidak bisa membedakannya.

   Diminta ridwanyas 4 September 2026: centangnya "jangan polos banget". Jadi yang
   sudah beres memakai centang di dalam keping hijau bergaris putih, dan yang belum
   memakai ikon barangnya sendiri — amplop, HP, kamera — di dalam lingkaran putus-
   putus. Sekilas lihat sudah ketahuan apa yang kurang, tanpa membaca satu kata pun. */
.checks { list-style: none; padding: 0; margin: 0; display: grid; gap: 4px; }
.check {
  display: grid; grid-template-columns: 38px 1fr; gap: 12px; align-items: center;
  padding: 9px 10px; border-radius: 12px; border: 1px solid transparent;
}
.check.todo { background: var(--bg-soft, #f7f8f9); border-color: var(--line, #eceff2); }
.check-mark {
  width: 38px; height: 38px; border-radius: 999px; display: grid; place-items: center;
}
.check.ok .check-mark {
  color: var(--on-accent, #fff); border: 2px solid var(--bg-card, #fff);
  background: linear-gradient(150deg, var(--accent) 0%, var(--accent-dark, #0F6A3C) 100%);
  box-shadow: 0 5px 12px rgba(31, 164, 99, .32);
}
.check.todo .check-mark {
  color: var(--muted, #667); border: 2px dashed var(--line, #cfd6dc);
  background: var(--bg-card, #fff);
}
.check-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.check-body b { font-size: 14.5px; }
.check.ok .check-body b { color: var(--accent-dark, #0F6A3C); }

/* Berapa yang sudah beres, sebagai satu bilah — "2 dari 3" jauh lebih cepat dibaca
   daripada menghitung sendiri tiga baris di bawahnya. */
.check-progress { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; }
.check-bar {
  flex: 1; height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--bg-soft, #eef1f4);
}
.check-bar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-dark, #0F6A3C) 100%);
  transition: width .5s ease;
}
.check-progress span { font-size: 13px; color: var(--muted, #667); white-space: nowrap; }
.check-progress b { font-family: var(--font-display); font-size: 15px; color: var(--text); }

/* Aturan main: satu ikon per aturan. Tiga paragraf sepanjang ini dalam satu daftar
   bertitik sama rupanya, dan yang dicari orang tenggelam di tengahnya. */
.reflist { list-style: none; padding: 0; margin: 0; display: grid; gap: 12px; }
.reflist li { display: grid; grid-template-columns: 32px 1fr; gap: 10px; align-items: start; }
.reflist-ic {
  width: 32px; height: 32px; border-radius: 10px; display: grid; place-items: center;
  color: var(--accent-dark, #0F6A3C); background: var(--accent-soft, #e7f4ec);
}

/* Status orang yang diajak: keping kecil berikon, bukan emoji yang digambar berbeda
   di tiap merek HP. */
.refpill {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  padding: 3px 9px 3px 7px; border-radius: 999px; font-size: 12.5px; font-weight: 600;
}
.refpill.buy { color: #7a4a00; background: #fdf0d8; }
.refpill.ok { color: var(--accent-dark, #0F6A3C); background: var(--accent-soft, #e7f4ec); }
.refpill.wait { color: var(--muted, #667); background: var(--bg-soft, #f1f3f5); }

/* Kotak tautan + tombol salin. Di HP keduanya menumpuk, karena tautan yang terpotong
   setengah tidak bisa dibaca ulang oleh orang yang ingin mengetiknya manual. */
.reflink { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.reflink input {
  flex: 1 1 240px; min-width: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--line, #dfe3e8); background: var(--bg-soft, #f7f8f9);
}

/* Tiga angka sejajar: mendaftar, bertahan, membeli. */
.refstats { display: flex; gap: 10px; flex-wrap: wrap; }
.refstats > div {
  flex: 1 1 90px; text-align: center; padding: 12px 8px; border-radius: 12px;
  background: var(--bg-soft, #f7f8f9); border: 1px solid var(--line, #eceff2);
}
.refstats b {
  display: block; font-family: var(--font-display); font-size: 26px; line-height: 1.1;
  color: var(--accent-dark, #0F6A3C);
}
.refstats span { display: block; margin-top: 3px; font-size: 12px; color: var(--muted, #667); }

/* Saldo: satu angka besar, karena itulah satu-satunya angka yang dicari orang saat
   membuka halaman ini untuk kesepuluh kalinya. */
.refbalance {
  font-family: var(--font-display); font-size: 36px; font-weight: 800; line-height: 1.15;
  letter-spacing: -0.5px; color: var(--accent-dark, #0F6A3C);
}

/* Kotak isian sempit di dalam baris tabel admin (nomor bukti transfer). Tanpa lebar
   yang dipatok, ia melebar mengikuti selnya dan mendorong tombol di sebelahnya keluar
   layar HP. */
.tiny { width: 110px; min-width: 0; padding: 6px 8px; font-size: 13px; }

/* --- Main Bareng Teman (/teman) ---------------------------------------------
   Satu baris per orang: namanya di kiri, tombolnya di kanan, dan di layar HP
   keduanya menumpuk daripada tombolnya terdorong keluar layar. */
.friend-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; padding: 9px 0; border-bottom: 1px solid var(--line, #eceff2);
}
.friend-row:last-child { border-bottom: 0; }
/* Titik online. Abu-abu berarti "sedang tidak ada" — dan tombol ajak main di baris itu
   memang dimatikan, jadi warnanya bukan satu-satunya yang mengatakannya. */
.friend-row .dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--line, #c9cfd6); margin-right: 6px; vertical-align: middle;
}
.friend-row .dot.on { background: #22a05a; box-shadow: 0 0 0 3px rgba(34, 160, 90, .18); }
/* Tombol pertemanan di kepala profil. Baris sendiri di bawah nama, bukan di sebelah
   angka rating: di layar HP, tiga hal dalam satu baris membuat yang ketiga terpotong. */
.hero-friend { margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; }
/* Ajakan main dari teman yang datang selagi halaman duel terbuka. Di paling atas
   halaman, bukan sebagai toast: toast hilang sendiri, dan ajakan yang hilang sendiri
   adalah partai yang tidak pernah terjadi. */
.friend-invite { border-color: #22a05a; margin-bottom: 14px; }

/* ---------- halaman turnamen: pasangan per babak, panel penyelenggara ----------
   Semua nama kelas di blok ini berawalan `trn-`. Itu bukan gaya penulisan, itu
   pengaman: `.check`, `.promo`, dan teman-temannya sudah empat kali menabrak nama yang
   dipakai bagian lain berkas ini, dan yang menabrak selalu nama pendek yang terdengar
   umum. Lihat catatan di blok skak. */

.trn-tabs {
  flex-wrap: wrap; gap: 4px; margin-bottom: 12px; padding: 4px;
  background: var(--bg-soft); border-radius: 10px;
}
/* `.range.active` mewarnai dirinya dengan `--bg-card` — putih. Di atas kartu yang juga
   putih, tab yang sedang dipilih jadi tidak terlihat sama sekali, dan tabnya terbaca
   sebagai tulisan biasa. Latar lunak di wadahnyalah yang membuat kepingnya muncul. */
.trn-tab.active { box-shadow: 0 1px 3px rgba(16, 40, 28, .12); }

/* Tabel pasangan, bentuk lembar turnamen sungguhan (ridwanyas 6 Okt 2026, contohnya
   dari chess-results): Meja · No. · Putih · Elo · Poin · Hasil · Poin · Hitam · Elo ·
   No. Sampai hari itu tiap meja digambar sebagai kartu bertumpuk; enak dibaca satu per
   satu, tapi panitia membaca satu KOLOM ke bawah ("meja 14 di mana"), dan kartu memaksa
   membaca seluruh kartu demi satu angka.

   Lebarnya tumbuh bersama panjang nama, jadi ia menggulir di dalam kotaknya sendiri —
   sama seperti tabel silang di bawah, dan karena alasan yang sama. */
.trn-table-wrap { overflow-x: auto; }
.trn-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.trn-table th, .trn-table td { border-bottom: 1px solid var(--line); padding: 6px 8px; white-space: nowrap; }
.trn-table th {
  background: var(--bg-soft); color: var(--muted); font-size: 11px; font-weight: 700;
  text-align: center; letter-spacing: .03em;
}
.trn-table th:nth-child(3), .trn-table th:nth-child(8) { text-align: left; }
/* Nomor meja: satu-satunya kolom yang dicari dari jauh, jadi ia biru dan tebal —
   permintaan ridwanyas, dan alasannya sama dengan nomor meja besar di lembar PDF. */
.trn-board { color: var(--info); font-weight: 800; font-size: 15px; text-align: center; }
/* Nomor pendaftaran. Sengaja kecil dan kelabu: ia dipakai saat mencocokkan dengan
   kertas pendaftaran, bukan saat membaca pasangannya. */
.trn-regno { color: var(--muted); text-align: center; font-variant-numeric: tabular-nums; }
.trn-table .trn-name { font-weight: 600; white-space: normal; overflow-wrap: anywhere; min-width: 110px; }
.trn-num { text-align: center; font-variant-numeric: tabular-nums; }
.trn-hasil { min-width: 150px; }
.trn-byerow .trn-name { font-weight: 500; }
/* Baris kedua: tombol "Main sekarang", tombol WO, keterangan hasil. Ia bagian dari meja
   di atasnya, jadi garis pemisah di antara keduanya dihapus — dan karena CSS tidak bisa
   melihat baris berikutnya, barisnya sendiri yang ditandai `.trn-open` di JS. */
.trn-open td { border-bottom: 0; }
.trn-xrow td { padding-top: 0; white-space: normal; }
.trn-xrow td > * { margin-right: 8px; }
.trn-mine { background: var(--accent-soft); }
/* Di ponsel kolom Elo disembunyikan — sepuluh kolom tidak muat di layar selebar telapak
   tangan, dan dari sepuluh itu Elo satu-satunya yang tidak dipakai saat memanggil meja
   atau mencatat hasil. Sisanya tetap, berikut gulir ke sampingnya. Lembar PDF dan
   tampilan desktop tidak ikut kehilangan apa pun. */
@media (max-width: 560px) {
  .trn-table th:nth-child(4), .trn-table td:nth-child(4),
  .trn-table th:nth-child(9), .trn-table td:nth-child(9) { display: none; }
}
/* Dipakai meja panitia (.trn-desk) — dua nama berhadapan dengan "vs" di tengahnya. */
.trn-right { text-align: right; align-items: flex-end; }
.trn-vs { font-size: 12px; color: var(--muted); }

/* Tiga keping hasil, selebar barisnya. Yang berlaku disorot; sisanya tetap tergambar
   supaya bentuknya sama entah hasilnya sudah ada atau belum — kotak yang berubah
   tinggi begitu hasil masuk membuat seluruh daftar melompat. */
.trn-res { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.trn-res .trn-seg {
  display: block; text-align: center; padding: 7px 4px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg-soft); color: var(--muted);
  font-size: 13px; font-weight: 600; line-height: 1.2;
}
/* Bye tidak punya tiga kemungkinan hasil; kepingnya satu, dan satu keping selebar
   sepertiga baris terbaca sebagai dua keping yang hilang. */
.trn-res .trn-seg:only-child { grid-column: 1 / -1; }
button.trn-seg { cursor: pointer; }
button.trn-seg:hover { border-color: var(--accent); color: var(--text); }
.trn-res .trn-seg.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.trn-player { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 4px 0; }
.trn-player .muted { margin-right: auto; }
.trn-pick { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.trn-pick.trn-off { opacity: .45; cursor: not-allowed; }

.trn-guest { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
.trn-h { margin: 16px 0 4px; font-size: 14px; }
.trn-h:first-of-type { margin-top: 8px; }

/* Sisa jatah membuat turnamen. Satu baris di atas tombolnya, bukan pemberitahuan yang
   muncul sesudah ditolak: aturan jatahnya tidak bisa ditebak dari tampilan mana pun,
   dan penolakan yang datang tanpa didahului keterangan terbaca sebagai kerusakan. */
.trn-quota {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 10px 12px; border-radius: 10px;
  background: var(--accent-soft); border: 1px solid var(--line); font-size: 14px;
}
.trn-quota[hidden] { display: none; }
.trn-quota.trn-quota-out { background: #fdf4e6; border-color: #eddcbb; }
.trn-quota .muted { flex: 1 1 220px; }

/* Formulir setelan penyelenggara. Kolomnya menyusut jadi satu di ponsel — panel ini
   duduk di kolom kanan yang sudah sempit, dan dua kolom di dalamnya membuat setiap
   angka jadi kotak selebar tiga huruf. */
.trn-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 6px; }
/* `display: grid` mengalahkan `display: none` bawaan atribut [hidden] — sama seperti
   `.tc-menu` di atas, dan sama-sama ditemukan dengan cara yang sama: formulirnya tetap
   tergambar padahal sudah disembunyikan. */
.trn-form[hidden] { display: none; }
.trn-form label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 2px; }
.trn-form .trn-wide { grid-column: 1 / -1; }
.trn-form input, .trn-form select { width: 100%; }
@media (max-width: 560px) { .trn-form { grid-template-columns: 1fr; } }

/* Setelan yang terkunci karena babaknya sudah berjalan. Dibiarkan terlihat, bukan
   dihapus: penyelenggara yang mencari kontrol waktunya harus menemukan jawabannya
   ("terkunci"), bukan menemukan kolomnya hilang dan mengira aplikasinya rusak. */
/* Meja panitia turnamen offline: satu nomor meja diketik, dua nama muncul, tiga tombol
   hasil. Bentuknya sengaja lebih besar dari kartu meja di daftar pasangan — yang
   memakainya sedang berdiri di belakang meja dengan orang menunggu di depannya. */
.trn-desk {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 10px;
  margin: 10px 0 8px; padding: 10px; border-radius: 10px;
  background: var(--bg-soft); border: 1px solid var(--line);
}
.trn-desk-names { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.trn-desk-names b { overflow-wrap: anywhere; }

/* Tabel silang round-robin. Lebarnya tumbuh bersama jumlah peserta — enam belas pemain
   berarti delapan belas kolom — jadi ia menggulir di dalam kotaknya sendiri, bukan
   mendorong seluruh halaman ke samping ([[play-layout-min-width]] pernah menggigit di
   tempat lain karena hal yang sama). */
.trn-cross-wrap { overflow-x: auto; }
.trn-cross { border-collapse: collapse; font-size: 13px; }
.trn-cross th, .trn-cross td {
  border: 1px solid var(--line); padding: 5px 7px; text-align: center; white-space: nowrap;
}
.trn-cross th { background: var(--bg-soft); color: var(--muted); font-size: 11px; }
.trn-cross-name { text-align: left; font-weight: 600; }
.trn-cross-self { background: var(--bg-soft); color: var(--muted); }
.trn-cross-win { color: var(--accent); font-weight: 700; }
.trn-cross-loss { color: var(--danger); }
.trn-cross-draw { color: var(--muted); font-weight: 600; }
.trn-cross tr.me { background: var(--accent-soft); }

/* Bagan sistem gugur: satu kolom per babak, dibaca dari kiri ke kanan sampai final.
   Menggulir mendatar di dalam kartunya sendiri — enam babak berarti tujuh kolom, dan
   di ponsel itu selalu lebih lebar dari layarnya. */
.trn-brk { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; align-items: stretch; }
.trn-brk-col { display: flex; flex-direction: column; gap: 6px; min-width: 150px; }
/* Judul di atas, petaknya diratakan di badan — bukan sebaliknya. Kalau judulnya ikut
   diratakan bersama petak, "Perempat final" dan "Final" berhenti di ketinggian yang
   berbeda dan seluruh baganya terbaca miring. */
.trn-brk-body { flex: 1; display: flex; flex-direction: column; justify-content: space-around; gap: 8px; }
.trn-brk-head {
  font-size: 11px; font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; text-align: center; padding-bottom: 2px;
}
.trn-brk-match {
  border: 1px solid var(--line); border-radius: 8px; background: var(--bg-soft);
  padding: 6px 8px; font-size: 13px; display: grid; gap: 2px;
}
.trn-brk-seat { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Yang naik ditebalkan dan diberi warna merek — di bagan, "menang" dan "lanjut" adalah
   hal yang sama, jadi satu penanda saja yang dipakai untuk keduanya. */
.trn-brk-up { font-weight: 800; color: var(--accent); }
.trn-brk-empty { background: transparent; border-style: dashed; text-align: center; }
.trn-brk-champ { background: var(--accent-soft); border-color: var(--accent-soft); text-align: center; }

.trn-locked { opacity: .55; }
.trn-lock-note {
  grid-column: 1 / -1; margin: 2px 0 0; font-size: 12px; color: var(--warn);
}

/* ————————————————————————————————————————————————— AI Coach di samping papan
   Pasangan `lib/widgets/coach_side.dart` di aplikasi. Gelap di halaman yang terang, dan
   itu disengaja: coach adalah satu-satunya suara lain di halaman ini, dan kartu seputih
   kartu "Partai" akan terbaca sebagai bagian dari papan skor. Tepinya berganti warna
   sesuai isi kalimatnya (emas peluang, merah peringatan, hijau langkah terbaik) supaya
   pemain tahu nadanya sebelum sempat membaca kalimatnya. */
.coach-live {
  --coach-edge: #12326E;
  display: grid;
  grid-template-columns: 46px 1fr auto;
  gap: 10px;
  align-items: start;
  padding: 10px 10px 12px;
  border-radius: var(--radius, 12px);
  border: 1.4px solid color-mix(in srgb, var(--coach-edge) 65%, transparent);
  background: linear-gradient(135deg, #0A1D44, #12326E);
  color: #EAF1FB;
}
.coach-live.is-empty { display: none; }
.coach-live.is-hidden { display: block; border: 0; background: none; padding: 0; }
.coach-live .coach-call {
  background: none;
  border: 1px solid var(--premium-line, #DDCBF8);
  color: var(--premium, #7A3FD0);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 13px;
  cursor: pointer;
}
.coach-live .coach-face {
  width: 46px; height: 46px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 20px;
  background: radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--aura) 70%, transparent), #0A1D44 70%);
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--aura) 55%, transparent), 0 0 14px color-mix(in srgb, var(--aura) 35%, transparent);
}
.coach-live .coach-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.coach-live .coach-head b { color: #E8C063; font-size: 12.5px; }
.coach-live .coach-badge {
  font-size: 11px; font-weight: 700; color: var(--c);
  border: 1px solid color-mix(in srgb, var(--c) 55%, transparent);
  background: color-mix(in srgb, var(--c) 18%, transparent);
  border-radius: 999px; padding: 1px 8px;
}
.coach-live .coach-say { margin: 3px 0 0; font-size: 13.5px; line-height: 1.3; }
.coach-live .coach-better { margin: 5px 0 0; font-size: 12.5px; font-weight: 600; color: #E8C063; }
.coach-live .coach-tools { display: grid; gap: 2px; }
.coach-live .coach-tools button {
  background: none; border: 0; color: #9FB3CE; cursor: pointer;
  font-size: 13px; line-height: 1; padding: 3px 4px; border-radius: 6px;
}
.coach-live .coach-tools button:hover { background: rgba(255, 255, 255, .08); color: #EAF1FB; }

/* ============================================================ AI Coach (/coach)
   Pasangan `lib/screens/coach_screen.dart` di aplikasi: biru malam dengan emas, satu
   bidang gelap di situs yang terang. Awalan `cp-` (coach page) sengaja berbeda dari
   `.coach-live` milik coach di samping papan — keduanya hidup di halaman yang berbeda
   dan tidak boleh saling mewarisi ukuran.

   Warnanya ditulis apa adanya, bukan lewat token palet: hijau-putih situs tidak punya
   satu pun nilai gelap, dan menambahkannya ke `:root` berarti mengundangnya dipakai di
   tempat lain. Yang gelap memang hanya halaman ini. */
.cp-shell { display: grid; gap: 14px; }
.cp-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.cp-col { display: grid; gap: 14px; min-width: 0; }

.cp-hero {
  border-radius: 16px; padding: 16px;
  background: linear-gradient(135deg, #0B1A33, #132A4E);
  border: 1.4px solid rgba(232, 192, 99, .34);
  box-shadow: 0 10px 26px rgba(11, 26, 51, .18);
  color: #EAF1FB;
}
.cp-hero .muted { color: #9FB3CE; }
.cp-hero-in { display: grid; grid-template-columns: auto 1fr auto; gap: 14px; align-items: center; }
.cp-face {
  width: 84px; height: 84px; border-radius: 50%; display: grid; place-items: center;
  background: radial-gradient(circle at 35% 30%, var(--aura, #E8C063), rgba(11, 26, 51, .9) 72%);
  border: 2px solid var(--aura, #E8C063);
  box-shadow: 0 0 22px color-mix(in srgb, var(--aura, #E8C063) 45%, transparent);
}
.cp-glyph { font-size: 34px; line-height: 1; }
.cp-hero-text { min-width: 0; }
.cp-hero-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.cp-name { color: #E8C063; font-size: 17px; }
.cp-style { color: #9FB3CE; font-size: 12.5px; }
.cp-tier {
  font-size: 11px; font-weight: 700; color: #0B1A33; background: #E8C063;
  border-radius: 999px; padding: 1px 8px;
}
.cp-say { margin: 6px 0 0; font-size: 15px; line-height: 1.4; font-weight: 600; }
.cp-lesson { margin: 5px 0 0; font-size: 13px; line-height: 1.4; color: #C9D8EE; }
.cp-goal { margin: 7px 0 0; font-size: 12.5px; color: #9FB3CE; }
.cp-goal span {
  display: inline-block; margin-right: 6px; font-weight: 700; color: #E8C063;
  text-transform: uppercase; letter-spacing: .6px; font-size: 10px;
}
.cp-bond { display: grid; gap: 4px; justify-items: end; text-align: right; min-width: 128px; }
.cp-bond-lv { color: #E8C063; font-weight: 700; font-size: 12.5px; }
.cp-bond-bar { display: block; width: 118px; height: 6px; border-radius: 999px; background: rgba(255, 255, 255, .14); }
.cp-bond-bar i { display: block; height: 100%; border-radius: 999px; background: #E8C063; }
.cp-bond-note { color: #9FB3CE; font-size: 11px; line-height: 1.3; }

.cp-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.cp-card-head h3 { margin: 0; font-size: 15px; }
.cp-chip {
  margin-left: auto; font-size: 11px; font-weight: 700; border-radius: 999px;
  padding: 1px 8px; background: var(--line); color: var(--muted);
}
.cp-chip.is-done { background: var(--accent); color: #fff; }

.cp-tasks { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.cp-tasks li { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
.cp-tasks li.is-done .cp-task-label { color: var(--muted); text-decoration: line-through; }
.cp-tick {
  width: 18px; height: 18px; flex: none; border-radius: 50%; display: grid; place-items: center;
  border: 1.5px solid var(--line); color: #fff; font-size: 11px; font-weight: 700;
}
.cp-tasks li.is-done .cp-tick { background: var(--accent); border-color: var(--accent); }
.cp-task-label { flex: 1; min-width: 0; }
.cp-task-num { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.cp-reward { margin: 9px 0 0; font-size: 12.5px; font-weight: 700; color: var(--accent); }

.cp-trend { margin-left: auto; font-size: 11px; font-weight: 700; border-radius: 999px; padding: 1px 8px; }
.cp-trend.up { background: #E6F4EC; color: #14804A; }
.cp-trend.down { background: #FBE9E7; color: #C8483A; }
.cp-trend.flat { background: var(--line); color: var(--muted); }
.cp-accuracy { margin: 0 0 10px; font-size: 20px; }
.cp-accuracy b { font-size: 26px; }
.cp-skills { display: grid; gap: 7px; }
.cp-skill { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.cp-skill-name { width: 108px; flex: none; color: var(--muted); }
.cp-skill-bar { flex: 1; height: 7px; border-radius: 999px; background: var(--line); min-width: 40px; }
.cp-skill-bar i { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.cp-skill-num { width: 26px; text-align: right; font-variant-numeric: tabular-nums; }
.cp-skill-none { flex: 1; color: var(--muted); font-style: italic; }
.cp-source { margin: 10px 0 0; }

.cp-notes + .cp-notes { margin-top: 10px; }
.cp-notes h4 { margin: 0 0 5px; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
.cp-notes ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.cp-note { font-size: 12.5px; border-radius: 999px; padding: 3px 10px; }
.cp-note.weak { background: #FBE9E7; color: #A5372B; }
.cp-note.strong { background: #E6F4EC; color: #14804A; }
.cp-note.pattern { background: var(--premium-soft, #F3ECFD); color: #6431AE; }

.cp-personas { display: grid; gap: 8px; }
.cp-persona {
  display: flex; align-items: center; gap: 10px; text-align: left; width: 100%;
  background: var(--bg-card); border: 1.4px solid var(--line); border-radius: 12px;
  padding: 9px 10px; cursor: pointer; color: inherit; font: inherit;
}
.cp-persona:hover { border-color: var(--aura, var(--accent)); }
.cp-persona.is-on { border-color: var(--aura, var(--accent)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--aura, var(--accent)) 24%, transparent); }
.cp-persona-face {
  width: 38px; height: 38px; flex: none; border-radius: 50%; display: grid; place-items: center;
  font-size: 19px; background: color-mix(in srgb, var(--aura, #E8C063) 20%, #fff);
  border: 1.4px solid color-mix(in srgb, var(--aura, #E8C063) 55%, transparent);
}
.cp-persona-text { display: grid; gap: 1px; min-width: 0; }
.cp-persona-text b { font-size: 13.5px; }
.cp-persona-text i { font-size: 11.5px; color: var(--muted); font-style: normal; }
.cp-persona-text em { font-size: 11.5px; color: var(--muted); }

.cp-locked { max-width: 620px; margin: 18px auto; }
/* Judulnya h1 (halaman ini terbuka untuk tamu & mesin pencari), tapi ukurannya tetap ukuran kartu. */
.cp-locked h1 { font-size: 22px; margin: 0 0 6px; }
.cp-locked-tag {
  display: inline-block; font-size: 10.5px; font-weight: 700; letter-spacing: 1px;
  text-transform: uppercase; color: #6431AE; margin-bottom: 4px;
}
.cp-locked-list { margin: 10px 0 14px; padding-left: 18px; display: grid; gap: 4px; font-size: 13.5px; }

/* Satu kolom di ponsel, dan tokohnya pindah ke atas tulisannya: tiga kolom pada 360px
   membuat sapaan coach selebar empat kata. */
@media (max-width: 720px) {
  .cp-cols { grid-template-columns: 1fr; }
  .cp-hero-in { grid-template-columns: auto 1fr; }
  .cp-bond { grid-column: 1 / -1; justify-items: start; text-align: left; }
  .cp-face { width: 64px; height: 64px; }
  .cp-glyph { font-size: 26px; }
}

/* ================================================ spanduk unggulan (beranda)
   AI Coach & Kursus, satu baris dua spanduk. Pasangan `feature_banner.dart` di
   aplikasi — angkanya disalin dari sana supaya keduanya terbaca sebagai satu produk.
   Merah muda sengaja tidak diberi nama di :root: ia milik spanduk AI Coach saja. */
.fbanners { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 0 0 14px; }
.fbanner {
  position: relative; overflow: hidden; isolation: isolate;
  display: grid; gap: 4px; align-content: space-between;
  min-height: 118px; padding: 11px 12px; border-radius: 12px;
  color: #fff; text-decoration: none;
  background: linear-gradient(135deg, #F43F8E, #A3134F);
  box-shadow: 0 6px 14px rgba(162, 19, 79, .28);
}
/* Kursus ungu, bukan merah muda kedua — permintaan ridwanyas 10 September 2026. Dua
   petak sewarna berdampingan terbaca sebagai satu spanduk lebar yang kebetulan retak
   di tengah; dua warna berbeda membuat keduanya terbaca sebagai dua pintu.
   Ungu adalah warna paket berbayar di seluruh situs (--premium) dan itu tidak keliru
   di sini: jatah kelas Kursus memang naik bersama paket. Angkanya ditulis langsung,
   bukan var(--premium): gradien butuh rentang terang-gelap yang lebih lebar daripada
   pasangan --premium/--premium-dark, dan #7A3FD0 tepat berada di tengah rentang ini. */
.fbanner.kursus {
  background: linear-gradient(135deg, #9A5BE8, #5B2BA0);
  box-shadow: 0 6px 14px rgba(91, 43, 160, .28);
}
.fbanner:hover { filter: brightness(1.06); }
.fb-ghost { position: absolute; right: -10px; bottom: -12px; opacity: .16; z-index: -1; line-height: 0; }
.fb-head { display: flex; align-items: center; gap: 6px; }
.fb-head b { font-size: 15px; font-weight: 800; }
.fb-line { font-size: 11.5px; line-height: 1.25; font-weight: 600; opacity: .92; }
.fb-go {
  justify-self: start; font-size: 10.5px; font-weight: 700; border-radius: 999px;
  padding: 3px 9px; background: rgba(255, 255, 255, .18); border: 1px solid rgba(255, 255, 255, .35);
}

/* ---------- gelar peringkat nasional (src/rank-titles.js) ------------------------------
   Hanya 100 besar per negara — ridwanyas 15 September 2026. Warnanya sengaja bukan ungu:
   ungu milik paket berbayar, dan gelar yang diraih di papan tidak boleh terbaca sebagai
   sesuatu yang dibeli. Versi `.ev` (garis tepi, latar putih) = gelar point Event, yang
   hanya tampil di partai Event. */
.rank-title {
  display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 8px 0 6px;
  border-radius: 999px; border: 1px solid transparent; vertical-align: middle;
  font-size: 11.5px; font-weight: 800; line-height: 1; white-space: nowrap; letter-spacing: .1px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .18), inset 0 1px 0 rgba(255, 255, 255, .4);
}
.rank-title svg { width: 13px; height: 13px; flex: none; stroke-width: 2.4; }
.rank-title .flag { margin-left: 0; font-size: 12px; }
.rank-title.rt-champion {
  background: linear-gradient(100deg, #8a3e06 0%, #e08a1e 30%, #ffd77a 48%, #e08a1e 66%, #8a3e06 100%);
  color: #3a1a00; border-color: #ffe3a3;
}
.rank-title.rt-silver {
  background: linear-gradient(100deg, #6b7580 0%, #c9d1d9 45%, #f4f7fa 55%, #9aa5b1 100%);
  color: #1d2733; border-color: #eef2f6;
}
.rank-title.rt-bronze {
  background: linear-gradient(100deg, #7a3f1d 0%, #c7773f 45%, #f0b584 55%, #9a5226 100%);
  color: #2b1203; border-color: #f6c9a3;
}
.rank-title.rt-top10 { background: linear-gradient(135deg, #b3261e, #e5484d); color: #fff; border-color: #ff8a8e; }
.rank-title.rt-top50 { background: linear-gradient(135deg, #0f6a3c, #22a869); color: #fff; border-color: #6fd6a2; }
.rank-title.rt-top100 { background: linear-gradient(135deg, #1f4f7a, #3b82c4); color: #fff; border-color: #8cc0ee; }
.rank-title.ev { background: #fff; border-width: 1.5px; box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
.rank-title.ev.rt-champion { color: #8a3e06; border-color: #e08a1e; }
.rank-title.ev.rt-silver { color: #4b5563; border-color: #9aa5b1; }
.rank-title.ev.rt-bronze { color: #7a3f1d; border-color: #c7773f; }
.rank-title.ev.rt-top10 { color: #b3261e; border-color: #e5484d; }
.rank-title.ev.rt-top50 { color: #0f6a3c; border-color: #22a869; }
.rank-title.ev.rt-top100 { color: #1f4f7a; border-color: #3b82c4; }
/* Di plakat papan gelarnya turun ke baris sendiri di bawah nama: di satu baris ia
   mendorong jam ke baris kedua begitu namanya panjang (`.player-row` membungkus). */
.player-row .rank-title { display: flex; width: max-content; margin-top: 4px; }

/* ====================================================================== duel: fokus mata
   Tinjauan halaman Duel Online, 17 Sep 2026 (ridwanyas: "ukuran papan, kefokusan mata
   pemain, nyaman, tidak membosankan"). Semua aturan di sini dikurung `.duel-page` atau
   `body.in-game` — halaman Lawan Bot, Analysis, dan Explore memakai `.play-page` yang
   sama dan sengaja tidak ikut berubah.

   Yang diukur sebelum diubah (Chrome headless, partai sungguhan):
   - HP 390: plakat atas 88px & bawah 117px karena jamnya jatuh ke baris kedua; tombol
     Menyerah/Remis dan daftar langkah di bawah lipatan, tertutup bilah menu bawah.
   - Laptop 1366×768: papan 512px, halaman tetap digulir (1023px), kartu "Cari lawan"
     berdiri paling atas di kolom kanan selama partai.
   - Desktop 1920×1080: papan 700px dengan ±180px tinggi tak terpakai.                 */

/* Plakat satu baris: [status][foto][nama / tawanan][jam]. */
.duel-page .player-row { flex-wrap: nowrap; min-height: 46px; transition: box-shadow .2s ease, border-color .2s ease; }
.duel-page .player-row .seat-who {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; justify-content: center; line-height: 1.25;
}
.duel-page .player-row .seat-who .name { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.duel-page .player-row .captured { flex-basis: auto; order: 0; min-height: 0; margin-top: 1px; }
.duel-page .player-row .captured img { width: 15px; height: 15px; opacity: .8; }
.duel-page .player-row .clock { flex: 0 0 auto; }

/* Giliran ditandai di tempat mata berada: plakat yang sedang jalan bertepi hijau, dan
   garis tipis menyala di tepi papan pada sisi itu. */
.duel-page.playing .player-row.turn { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.duel-page.playing .player-row.low-mine { border-color: #e05b4a; box-shadow: 0 0 0 2px rgba(224, 91, 74, .28); }
.duel-page .board-wrap::after {
  content: ''; position: absolute; left: 8%; right: 8%; height: 3px; border-radius: 3px;
  background: var(--accent); opacity: 0; pointer-events: none; transition: opacity .2s ease;
}
.duel-page.playing .board-wrap.turn-bottom::after { bottom: -5px; opacity: .85; }
.duel-page.playing .board-wrap.turn-top::after { top: -5px; opacity: .85; }

/* Pilihan papan & bidak kini di dasar kartu Partai, rata kiri. */
.duel-page .theme-in-card .theme-quick,
.game-screen .theme-in-card .theme-quick { justify-content: flex-start; margin: 10px 0 0; max-width: none; }

/* Kartu "lawan ditemukan": foto, bendera, skor pertemuan. */
.vs-side .pfp { flex: none; }
.vs-side .vs-piece.small { width: 24px; height: 24px; margin-left: -18px; align-self: flex-end; }
.vs-h2h { margin: 0 0 8px; font-size: 13px; color: var(--muted); }
.vs-h2h b { color: var(--text); font-variant-numeric: tabular-nums; }
.vs-h2h small { display: block; font-size: 10.5px; }

/* Latar bidak raksasa diredam selama partai — di semua lebar. */
body.in-game::before { opacity: .025; }

/* ---------- ponsel & tablet tegak ---------- */
@media (max-width: 900px) {
  /* Bilah menu bawah disingkirkan selama partai: ketukan meleset di sana membawa orang
     keluar dari papannya. Menu tetap bisa dibuka dari tombol ☰ di atas. */
  body.in-game .tabbar { display: none; }
  body.in-game .container { padding-bottom: calc(18px + env(safe-area-inset-bottom)); }

  /* Urutan layar HP: AI Coach (ringkas) → papan & plakat → pita langkah + tombol partai
     → kartu lain. Coach di ATAS papan, bukan di bawah: ia pemandu, jadi harus dekat
     mata — sama seperti panggung coach di atas papan di aplikasi (ridwanyas 17 Sep 2026:
     "AI Coach kan pemandu masa ditaruh dibawah"). `display: contents` meleburkan kolom
     kanan ke dalam susunan halaman supaya kartunya bisa diurutkan melewati papan. */
  .duel-page .play-layout { display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
  .duel-page .play-layout > .grid { display: contents; }
  .duel-page #coachMount { order: 0; }
  .duel-page .play-layout > div:first-child { order: 1; }
  .duel-page #lobbyCard { order: 2; }
  .duel-page .game-card { order: 3; }
  .duel-page .chat { order: 4; }
  .duel-page.playing .game-card { order: 2; }
  /* Coach ringkas di ponsel: wajah kecil, satu-dua baris kiat. */
  .duel-page .coach-live { grid-template-columns: 34px 1fr auto; gap: 8px; padding: 7px 8px 8px; }
  .duel-page .coach-live .coach-face { width: 34px; height: 34px; font-size: 16px; }
  .duel-page .coach-live .coach-say { font-size: 13px; margin-top: 1px; }
  .duel-page .coach-live .coach-better { font-size: 12px; margin-top: 3px; }
  .duel-page .coach-live .coach-tools { grid-auto-flow: column; }
  .duel-page .coach-live .coach-tools button { padding: 2px 6px; }
  .duel-page.playing .game-card { display: flex; flex-direction: column; padding: 10px 12px; }
  .duel-page.playing .game-card .moves { order: -1; margin: 0 0 8px !important; }
  .duel-page.playing .game-card #status:empty { display: none; }
  .duel-page .emote-bar { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: 5px; }
  .duel-page .emote-bar::-webkit-scrollbar { display: none; }
  .duel-page .emote-btn { flex: 0 0 auto; width: 36px; height: 36px; font-size: 17px; }
}

/* ---------- laptop & desktop ---------- */
@media (min-width: 901px) {
  /* Halaman lebih lebar dan papan mengikuti tinggi layar sampai 880px. 196px = bilah atas
     + dua plakat + jarak; pilihan tema tidak lagi di bawah papan, jadi angkanya turun
     dari 256px. */
  .container.duel-page { max-width: 1440px; }
  .duel-page { --board-w: min(880px, calc(100vh - 218px)); }
  .duel-page .play-layout { grid-template-columns: minmax(320px, 1fr) minmax(300px, 380px); }

  /* Kolom kanan: selama partai kartu Partai (tombol + langkah) paling atas, kartu
     "Cari lawan" disembunyikan — ia tidak bisa dipakai sampai partai selesai. */
  .duel-page .play-layout > .grid { display: flex; flex-direction: column; }
  .duel-page.playing #lobbyCard { display: none; }
  .duel-page.playing .game-card { order: -1; }
  .duel-page.playing .game-card > h3 { display: none; }

  /* Menu atas diredupkan selama partai; kembali penuh saat disentuh kursor. */
  body.in-game .topbar nav { opacity: .45; filter: saturate(.25); transition: opacity .25s ease, filter .25s ease; }
  body.in-game .topbar nav:hover, body.in-game .topbar nav:focus-within { opacity: 1; filter: none; }
}
.duel-page .game-card .hint-row { justify-content: flex-start; margin: 8px 0 0; max-width: none; }
@media (max-width: 560px) {
  .duel-page .player-row .seat-who .name { font-size: 15px; }
  .duel-page .player-row .clock { font-size: 19px; }
}
@media (min-width: 901px) {
  .container.duel-page { padding-top: 10px; padding-bottom: 14px; }
}
/* Nomor babak yang `sticky` menumpuk di atas langkah saat pita digeser ke ujung. */
.duel-page .moves .num { position: static; }
@media (max-width: 900px) {
  /* Tablet tegak: plakat selebar papan (maks 620px), bukan selebar layar. */
  .duel-page .player-row, .duel-page #coachMount { max-width: 620px; width: 100%; margin-left: auto; margin-right: auto; }
}
@media (min-width: 901px) {
  /* Kolom kanan menggulir sendiri — halaman tidak ikut memanjang karena obrolan. */
  .duel-page .play-layout > .grid { position: sticky; top: 10px; max-height: calc(100vh - 80px); overflow-y: auto; padding-right: 2px; }
}
@media (min-width: 901px) {
  /* AI Coach paling atas di kolom samping, di atas kartu Partai — pemandu dekat mata. */
  .duel-page #coachMount { order: -2; }
}

/* ====================================================================== ajakan tamu
   "#1 Aplikasi Catur buatan Indonesia yang menyenangkan" + Get Started, di bawah spanduk
   sambutan beranda tamu (ridwanyas 17 Sep 2026; acuan kartu gelap + tombol hijau besar).
   Latar arang supaya tombol hijaunya jadi benda paling terang di layar pertama. */
.guest-cta {
  margin: -4px 0 18px; padding: 26px 20px 28px; border-radius: 16px; text-align: center;
  background: #2b2a27; color: #fff; box-shadow: 0 10px 28px rgba(20, 20, 18, .18);
}
.guest-cta .gc-title {
  margin: 0 auto 22px; max-width: 16ch;
  font-family: var(--font-display);
  font-size: clamp(30px, 8.4vw, 52px); font-weight: 800; line-height: 1.08; letter-spacing: -.01em;
}
.guest-cta .gc-btn {
  position: relative; overflow: hidden;
  display: block; width: 100%; max-width: 440px; margin: 0 auto;
  padding: 18px 20px 17px; border-radius: 14px;
  font-family: var(--font-display);
  font-size: clamp(24px, 6.6vw, 30px); font-weight: 800; line-height: 1.1; color: #fff;
  text-decoration: none; text-shadow: 0 2px 0 rgba(0, 0, 0, .18);
  background: linear-gradient(180deg, #8fcb5f 0%, #74b247 100%);
  box-shadow: 0 6px 0 #4f8a2c, 0 12px 22px rgba(0, 0, 0, .28);
  transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
}
/* Kilau yang menyapu tombol tiap beberapa detik — memancing mata tanpa berkedip-kedip. */
.guest-cta .gc-btn::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: -40%; width: 30%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .45), transparent);
  transform: skewX(-18deg); animation: gcShine 3.2s ease-in-out infinite;
}
@keyframes gcShine { 0%, 55% { left: -40%; } 100% { left: 130%; } }
@media (hover: hover) {
  .guest-cta .gc-btn:hover { filter: brightness(1.06); text-decoration: none; }
}
.guest-cta .gc-btn:active { transform: translateY(4px); box-shadow: 0 2px 0 #4f8a2c, 0 6px 12px rgba(0, 0, 0, .24); }
.guest-cta .gc-btn:focus-visible { outline: 3px solid #cfeeb5; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .guest-cta .gc-btn::after { animation: none; display: none; } }

/* Layar lebar: judul di kiri, tombol di kanan — satu baris, bukan dua blok bertumpuk. */
@media (min-width: 901px) {
  .guest-cta {
    display: flex; align-items: center; justify-content: space-between; gap: 32px;
    padding: 30px 36px; text-align: left;
  }
  .guest-cta .gc-title { margin: 0; max-width: 24ch; font-size: 44px; }
  .guest-cta .gc-btn { flex: 0 0 340px; margin: 0; text-align: center; }
}

/* ====================================================================== bilah menu lebar
   17 Sep 2026 — lihat BAR_PRIMARY di js/api.js. Laci ponsel (nav.drawer) tidak lagi
   dituang mendatar di layar lebar; bilah ini yang tampil di sana. */
.topbar .barnav { display: none; }
@media (min-width: 901px) {
  .topbar nav.drawer { display: none; }
  .topbar .barnav {
    display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0;
    overflow: visible;
  }
  .topbar .barnav .bn-link,
  .topbar .barnav .bn-trigger {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 10px; border-radius: 9px; border: 0; background: none;
    color: var(--muted); font: inherit; font-size: 14.5px; font-weight: 600; white-space: nowrap;
    cursor: pointer; text-decoration: none;
  }
  .topbar .barnav .bn-ic { display: inline-flex; align-items: center; opacity: .75; }
  .topbar .barnav .bn-chev { font-size: 11px; margin-left: -2px; }
  .topbar .barnav .bn-link.active,
  .topbar .barnav .bn-trigger.active,
  .topbar .barnav .bn-drop.open > .bn-trigger { background: var(--accent-soft); color: var(--accent); }
  .topbar .barnav .bn-link.active .bn-ic { opacity: 1; }
  .topbar .barnav .bn-drop { position: relative; }
  .topbar .barnav .bn-menu {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
    min-width: 220px; padding: 6px; border-radius: 12px;
    background: var(--bg-card); border: 1px solid var(--line);
    box-shadow: 0 14px 34px rgba(16, 36, 26, .16);
    display: flex; flex-direction: column; gap: 1px;
  }
  .topbar .barnav .bn-menu[hidden] { display: none; }
  .topbar .barnav .bn-menu .bn-link { width: 100%; }
  .topbar .barnav .bn-articles { width: 340px; }
  .topbar .barnav .bn-article {
    white-space: normal;
    display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 9px;
    color: var(--text); font-size: 13.5px; font-weight: 600; line-height: 1.3; text-decoration: none;
  }
  .topbar .barnav .bn-tag { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--accent); }
  .topbar .barnav .bn-loading { margin: 8px 10px; }
  .topbar .barnav .bn-foot {
    display: flex; justify-content: space-between; gap: 10px; margin-top: 4px; padding: 8px 10px 4px;
    border-top: 1px solid var(--line); font-size: 13px; font-weight: 600;
  }
  @media (hover: hover) {
    .topbar .barnav .bn-link:hover, .topbar .barnav .bn-trigger:hover,
    .topbar .barnav .bn-article:hover { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
  }
}

/* ====================================================================== pemberitahuan cookie */
.cookie-note {
  position: fixed; left: 50%; bottom: 16px; z-index: 80;
  width: min(760px, calc(100% - 24px));
  display: flex; align-items: center; gap: 14px; padding: 14px 16px;
  border-radius: 14px; background: #20231f; color: #eef2ee;
  box-shadow: 0 16px 40px rgba(0, 0, 0, .32);
  transform: translate(-50%, 16px); opacity: 0; transition: transform .22s ease, opacity .22s ease;
}
.cookie-note.show { transform: translate(-50%, 0); opacity: 1; }
.cookie-note.closing { transform: translate(-50%, 16px); opacity: 0; }
.cookie-note .ck-ic { font-size: 26px; line-height: 1; }
.cookie-note .ck-txt { margin: 0; flex: 1; font-size: 13.5px; line-height: 1.45; }
.cookie-note .ck-txt a { color: #9ee07a; text-decoration: underline; }
.cookie-note .ck-ok {
  flex: none; padding: 10px 18px; border: 0; border-radius: 10px; cursor: pointer;
  background: linear-gradient(180deg, #8fcb5f 0%, #74b247 100%); color: #fff;
  font-weight: 800; font-size: 14px; box-shadow: 0 3px 0 #4f8a2c;
}
.cookie-note .ck-ok:active { transform: translateY(2px); box-shadow: 0 1px 0 #4f8a2c; }
/* Ponsel: di atas bilah menu bawah, dan tombolnya selebar kartu. */
@media (max-width: 900px) {
  /* Ringkas: satu baris teks kecil + tombol di kanan, supaya ajakan di beranda tidak tertutup. */
  .cookie-note { bottom: calc(72px + env(safe-area-inset-bottom)); gap: 10px; padding: 10px 12px; }
  .cookie-note .ck-ic { display: none; }
  .cookie-note .ck-txt { font-size: 12px; line-height: 1.4; }
  .cookie-note .ck-ok { padding: 9px 12px; font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) { .cookie-note { transition: none; } }
@media (min-width: 901px) {
  /* Menu tarik-turun menempel ke tepi kanan pemicunya: keduanya duduk di ujung kanan
     bilah, dan kotak selebar 340px yang menempel kiri keluar dari layar. */
  .topbar .barnav .bn-drop .bn-menu { left: auto; right: 0; }
}
/* Layar laptop: ikon menu disembunyikan supaya semua menu muat tanpa bertumpuk. */
@media (min-width: 901px) and (max-width: 1439px) {
  .topbar .barnav .bn-link > .bn-ic, .topbar .barnav .bn-trigger > .bn-ic { display: none; }
  .topbar .barnav { gap: 0; }
  .topbar .barnav .bn-link, .topbar .barnav .bn-trigger { padding: 7px 7px; font-size: 14px; }
  .topbar .barnav .bn-menu .bn-link > .bn-ic { display: inline-flex; }
}

/* ====================================================================== pemilih bahasa
   Pojok kanan atas, di sebelah Masuk / nama akun (17 Sep 2026) — lihat js/i18n.js. */
.topbar .lang-pick { position: relative; flex: none; }
.topbar .lang-pick .lp-toggle {
  display: inline-flex; align-items: center; gap: 5px; padding: 6px 9px; border-radius: 9px;
  background: var(--bg-soft); border: 1px solid var(--line); color: var(--text);
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; line-height: 1;
}
.topbar .lang-pick .lp-toggle:hover { background: var(--accent-soft); }
.topbar .lang-pick .lp-toggle svg { color: var(--accent); }
.topbar .lang-pick .lp-chev { font-size: 10px; color: var(--muted); }
.topbar .lang-pick.open .lp-toggle { border-color: var(--accent); background: var(--accent-soft); }
.topbar .lang-pick .lp-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 75; min-width: 200px; padding: 6px;
  border-radius: 12px; background: var(--bg-card); border: 1px solid var(--line);
  box-shadow: 0 14px 34px rgba(16, 36, 26, .16); display: flex; flex-direction: column; gap: 2px;
}
.topbar .lang-pick .lp-menu[hidden] { display: none; }
.topbar .lang-pick .lp-menu button {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border-radius: 9px;
  background: none; border: 0; color: var(--text); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
}
.topbar .lang-pick .lp-menu button b {
  display: inline-grid; place-items: center; width: 30px; height: 22px; border-radius: 6px;
  background: var(--bg-soft); border: 1px solid var(--line); font-size: 11.5px;
}
.topbar .lang-pick .lp-menu button i { margin-left: auto; font-style: normal; color: var(--accent); font-weight: 800; }
.topbar .lang-pick .lp-menu button.on { background: var(--accent-soft); color: var(--accent); font-weight: 700; }
.topbar .lang-pick .lp-menu button:hover { background: var(--accent-soft); }
@media (max-width: 900px) {
  /* Ponsel: tepat di kiri tombol ☰ (tombol Masuk ada di dalam laci). */
  .topbar .lang-pick { order: 1; }
  .topbar .nav-toggle { order: 2; }
  .topbar .lang-pick .lp-toggle { padding: 8px 9px; }
}

/* Daftar artikel terbaru di #guestIntro (dirakit server, src/routes/pages.js). */
.guest-articles { margin: 6px 0 0; padding-left: 18px; }
.guest-articles li { margin: 4px 0; }

/* Perkenalan tamu di beranda dengan dua foto (18 Sep 2026, permintaan ridwanyas).
   Teks di kiri, kolase foto di kanan: foto anak bermain (lanskap) di atas, foto tangan
   merapikan bidak (potret) menindih di bawahnya. Keduanya "menyatu" lewat bayangan
   lembut + tepi yang dipudarkan (mask), bukan garis kotak. Jarak ke petak menu di atasnya
   16px — sebelumnya kartu ini menempel karena .grid tidak punya margin bawah. */
.guest-intro { margin-top: 16px; display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 8px 28px; align-items: start; overflow: hidden; }
.guest-intro .gi-text { min-width: 0; grid-column: 1; grid-row: 1; }
.guest-intro .gi-text > :first-child { margin-top: 0; }
.gi-photos { position: relative; height: 380px; margin: 4px 0 0; grid-column: 2; grid-row: 1; }
.gi-photo {
  position: absolute; object-fit: cover; border-radius: 18px;
  box-shadow: 0 22px 44px -14px rgba(20, 60, 35, .45), 0 6px 14px rgba(0, 0, 0, .12);
  -webkit-mask-image: radial-gradient(120% 120% at 50% 45%, #000 62%, rgba(0, 0, 0, .55) 82%, transparent 100%);
          mask-image: radial-gradient(120% 120% at 50% 45%, #000 62%, rgba(0, 0, 0, .55) 82%, transparent 100%);
}
.gi-girl { top: 4px; right: 8px; width: 300px; height: 210px; transform: rotate(2deg); }
.gi-hands { left: 0; bottom: 0; width: 200px; height: 250px; transform: rotate(-3deg); }
.gi-credit { grid-column: 1 / -1; margin: 4px 0 0; font-size: 11px; color: var(--muted, #8a9a90); }
.gi-credit a { color: inherit; }
@media (max-width: 820px) {
  .guest-intro { grid-template-columns: 1fr; }
  .gi-photos { grid-column: 1; grid-row: 1; height: 190px; margin: 0 0 6px; }
  .guest-intro .gi-text { grid-row: 2; }
  .gi-girl { width: min(58%, 300px); height: 170px; right: 0; top: 6px; }
  .gi-hands { width: min(38%, 200px); height: 180px; left: 2px; bottom: 0; }
}

/* ---------- Toko: bingkai avatar & latar profil (barang Toko, 20 Sep 2026) ----------
   Kelas `cb-<nama>` dipasang di `.pfp` / `.hero-piece` oleh avatarHtml() dan profile.js;
   `cbg-<nama>` di `.ph-cover`. Nama = `meta.border` / `meta.background` di shop_items —
   satu nama satu kelas, jadi barang baru = satu blok baru di sini, bukan ubah JS. */
.cb { --cbw: 3px; box-shadow: 0 0 0 var(--cbw) var(--cbc, #9aa7b2); }
.hero-piece.cb { --cbw: 4px; }
.cb-silver { --cbc: #b8c2cc; }
.cb-wood { --cbc: #8b5a2b; box-shadow: 0 0 0 var(--cbw) #8b5a2b, 0 0 0 calc(var(--cbw) + 2px) #c9925a; }
.cb-emerald { --cbc: #10b981; box-shadow: 0 0 0 var(--cbw) #10b981, 0 0 10px rgba(16,185,129,.55); }
.cb-ice { --cbc: #7dd3fc; box-shadow: 0 0 0 var(--cbw) #bae6fd, 0 0 0 calc(var(--cbw) + 2px) #38bdf8, 0 0 10px rgba(56,189,248,.5); }
.cb-batik { box-shadow: 0 0 0 var(--cbw) #7c4a12, 0 0 0 calc(var(--cbw) + 2px) #d4a24c, 0 0 0 calc(var(--cbw) + 4px) #7c4a12; }
.cb-sakura { --cbc: #f9a8d4; box-shadow: 0 0 0 var(--cbw) #f9a8d4, 0 0 0 calc(var(--cbw) + 2px) #fdf2f8, 0 0 12px rgba(244,114,182,.55); }
.cb-flame { box-shadow: 0 0 0 var(--cbw) #f97316, 0 0 0 calc(var(--cbw) + 2px) #fbbf24, 0 0 14px rgba(249,115,22,.75); animation: cbFlame 1.2s ease-in-out infinite alternate; }
.cb-lightning { box-shadow: 0 0 0 var(--cbw) #818cf8, 0 0 0 calc(var(--cbw) + 2px) #c4b5fd, 0 0 14px rgba(99,102,241,.8); animation: cbFlash 2.4s steps(1) infinite; }
.cb-gold_crown { box-shadow: 0 0 0 var(--cbw) #f5b62b, 0 0 0 calc(var(--cbw) + 2px) #ffe58a, 0 0 0 calc(var(--cbw) + 4px) #b97a08, 0 0 16px rgba(245,182,43,.7); animation: cbGlow 1.6s ease-in-out infinite alternate; }
.cb-dragon { box-shadow: 0 0 0 var(--cbw) #b45309, 0 0 0 calc(var(--cbw) + 3px) #fbbf24, 0 0 0 calc(var(--cbw) + 5px) #7f1d1d, 0 0 18px rgba(251,191,36,.8); animation: cbGlow 1.2s ease-in-out infinite alternate; }
@keyframes cbGlow { from { filter: brightness(1); } to { filter: brightness(1.18) saturate(1.2); } }
@keyframes cbFlame { from { box-shadow: 0 0 0 var(--cbw) #f97316, 0 0 0 calc(var(--cbw) + 2px) #fbbf24, 0 0 10px rgba(249,115,22,.6); }
  to { box-shadow: 0 0 0 var(--cbw) #ef4444, 0 0 0 calc(var(--cbw) + 2px) #fde047, 0 0 20px rgba(249,115,22,.95); } }
@keyframes cbFlash { 0%, 88% { filter: none; } 90%, 94% { filter: brightness(1.6); } 92% { filter: brightness(1); } }

.cbg { position: relative; }
.cbg::after { content: ""; position: absolute; inset: 0; pointer-events: none; }
.cbg-snow { background: linear-gradient(180deg, #5b8def, #bfe1ff 70%, #fff) !important; }
.cbg-snow::after { background-image: radial-gradient(3px 3px at 20px 30px, #fff 60%, transparent 61%), radial-gradient(2px 2px at 90px 70px, #fff 60%, transparent 61%), radial-gradient(3px 3px at 160px 20px, #fff 60%, transparent 61%), radial-gradient(2px 2px at 230px 90px, #fff 60%, transparent 61%), radial-gradient(3px 3px at 300px 50px, #fff 60%, transparent 61%), radial-gradient(2px 2px at 370px 15px, #fff 60%, transparent 61%); background-size: 400px 120px; animation: cbgFall 6s linear infinite; }
@keyframes cbgFall { from { background-position: 0 -120px; } to { background-position: 30px 120px; } }
.cbg-rain { background: linear-gradient(180deg, #334155, #64748b 60%, #94a3b8) !important; }
.cbg-rain::after { background-image: repeating-linear-gradient(100deg, transparent 0 14px, rgba(255,255,255,.18) 14px 15px); animation: cbgRain 1.2s linear infinite; }
@keyframes cbgRain { to { background-position: 0 60px; } }
.cbg-bamboo { background: linear-gradient(180deg, #14532d, #16a34a 60%, #86efac) !important; }
.cbg-bamboo::after { background-image: repeating-linear-gradient(90deg, rgba(0,0,0,.18) 0 6px, transparent 6px 26px); }
.cbg-mountain { background: linear-gradient(180deg, #fdba74 0%, #f97316 30%, #4c1d95 100%) !important; }
.cbg-mountain::after { background: linear-gradient(to top, #1e1b4b 0 22%, transparent 22%), radial-gradient(circle at 70% 30%, #fff1b8 0 9%, transparent 10%); clip-path: polygon(0 100%, 0 70%, 18% 40%, 32% 62%, 48% 22%, 62% 55%, 78% 35%, 100% 68%, 100% 100%); background-color: #312e81; }
.cbg-beach { background: linear-gradient(180deg, #fb923c, #fde68a 45%, #0ea5e9 46%, #0369a1) !important; }
.cbg-ricefield { background: linear-gradient(180deg, #e0f2fe, #bbf7d0 40%, #4d7c0f) !important; }
.cbg-ricefield::after { background-image: repeating-linear-gradient(180deg, transparent 0 10px, rgba(255,255,255,.22) 10px 12px); top: 45%; }
.cbg-space { background: radial-gradient(circle at 70% 30%, #3b1d7a, #090b1f 60%) !important; }
.cbg-space::after { background-image: radial-gradient(1.5px 1.5px at 30px 20px, #fff 60%, transparent 61%), radial-gradient(1px 1px at 120px 60px, #fff 60%, transparent 61%), radial-gradient(2px 2px at 200px 30px, #fff 60%, transparent 61%), radial-gradient(1px 1px at 280px 80px, #fff 60%, transparent 61%), radial-gradient(1.5px 1.5px at 350px 40px, #fff 60%, transparent 61%), radial-gradient(28px 28px at 20% 65%, #fdba74 0 45%, #c2410c 46% 70%, transparent 71%); background-size: 400px 120px, 400px 120px, 400px 120px, 400px 120px, 400px 120px, 100% 100%; animation: cbgTwinkle 3s ease-in-out infinite alternate; }
@keyframes cbgTwinkle { from { opacity: .7; } to { opacity: 1; } }
.cbg-ocean { background: linear-gradient(180deg, #0ea5e9, #0c4a6e) !important; }
.cbg-ocean::after { background-image: radial-gradient(4px 4px at 40px 80px, rgba(255,255,255,.7) 60%, transparent 61%), radial-gradient(3px 3px at 140px 60px, rgba(255,255,255,.7) 60%, transparent 61%), radial-gradient(5px 5px at 260px 90px, rgba(255,255,255,.7) 60%, transparent 61%); background-size: 320px 120px; animation: cbgFall 5s linear infinite reverse; }
.cbg-neon_city { background: linear-gradient(180deg, #0f172a, #312e81) !important; }
.cbg-neon_city::after { background: repeating-linear-gradient(90deg, #1e293b 0 18px, #0f172a 18px 22px, #334155 22px 40px, #0f172a 40px 46px); top: 55%; box-shadow: inset 0 6px 0 #ec4899, inset 0 -0px 0 #22d3ee; }
.cbg-aurora { background: linear-gradient(180deg, #020617, #0f172a) !important; }
.cbg-aurora::after { background: linear-gradient(100deg, transparent 20%, rgba(52,211,153,.55) 40%, rgba(167,139,250,.55) 60%, transparent 80%); filter: blur(10px); animation: cbgAurora 6s ease-in-out infinite alternate; }
@keyframes cbgAurora { from { transform: translateX(-8%) skewX(-6deg); } to { transform: translateX(8%) skewX(6deg); } }
.cbg-volcano { background: linear-gradient(180deg, #1c1917, #7f1d1d 70%, #f97316) !important; }
.cbg-volcano::after { background-image: radial-gradient(3px 3px at 60px 40px, #fde047 60%, transparent 61%), radial-gradient(2px 2px at 180px 70px, #fb923c 60%, transparent 61%), radial-gradient(3px 3px at 300px 30px, #fde047 60%, transparent 61%); background-size: 360px 120px; animation: cbgFall 4s linear infinite reverse; }
.cbg-borobudur { background: linear-gradient(180deg, #fde68a, #f59e0b 50%, #78350f) !important; }
.cbg-borobudur::after { background: #451a03; clip-path: polygon(0 100%, 0 78%, 20% 78%, 20% 66%, 35% 66%, 35% 54%, 45% 54%, 45% 42%, 50% 34%, 55% 42%, 55% 54%, 65% 54%, 65% 66%, 80% 66%, 80% 78%, 100% 78%, 100% 100%); opacity: .85; }
/* Seri manis (20 Sep 2026) — versi web sederhana: gradasi + emoji besar di kanan, hati/jejak
   sebagai pola; gambar hidupnya ada di app (cosmetic_fx.dart). */
.cbg-kitty, .cbg-cats, .cbg-bunny, .cbg-teddy, .cbg-pony, .cbg-animals, .cbg-castle { --cbg-emoji: ''; }
.cbg-kitty::after, .cbg-cats::after, .cbg-bunny::after, .cbg-teddy::after, .cbg-pony::after, .cbg-animals::after, .cbg-castle::after { content: var(--cbg-emoji); display: flex; align-items: flex-end; justify-content: flex-end; padding: 0 14px 6px 0; font-size: 44px; line-height: 1; letter-spacing: 6px; text-shadow: 0 2px 6px rgba(0,0,0,.18); }
.cbg-kitty { --cbg-emoji: '🎀🐱'; background: linear-gradient(180deg, #ffc2da, #ffe4ef 60%, #fff5f9) !important; }
.cbg-kitty::before { content: ''; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(5px 5px at 30px 30px, #f472b6 60%, transparent 61%), radial-gradient(4px 4px at 110px 70px, #f9a8d4 60%, transparent 61%), radial-gradient(6px 6px at 200px 25px, #f472b6 60%, transparent 61%), radial-gradient(4px 4px at 290px 80px, #f9a8d4 60%, transparent 61%); background-size: 360px 120px; opacity: .6; animation: cbgFall 7s linear infinite reverse; }
.cbg-cats { --cbg-emoji: '🐱🐈🧶'; background: linear-gradient(180deg, #c4b5fd, #ede9fe 60%, #fdf4ff) !important; }
.cbg-cats::before { content: ''; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(4px 4px at 40px 20px, #7c3aed 60%, transparent 61%), radial-gradient(4px 4px at 150px 60px, #7c3aed 60%, transparent 61%), radial-gradient(4px 4px at 260px 35px, #7c3aed 60%, transparent 61%); background-size: 320px 120px; opacity: .2; animation: cbgFall 8s linear infinite; }
.cbg-bunny { --cbg-emoji: '🥕🐰'; background: linear-gradient(180deg, #bae6fd, #e0f2fe 62%, #4ade80 62%, #86efac) !important; }
.cbg-bunny::before { content: ''; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(18px 8px at 60px 22px, #fff 60%, transparent 61%), radial-gradient(22px 9px at 210px 34px, #fff 60%, transparent 61%); background-size: 340px 100%; animation: cbgDrift 12s linear infinite; }
.cbg-teddy { --cbg-emoji: '🎈🧸'; background: linear-gradient(180deg, #fde68a, #ffedd5 55%, #fecdd3) !important; }
.cbg-teddy::before { content: ''; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(3px 3px at 20px 10px, #f472b6 60%, transparent 61%), radial-gradient(3px 3px at 90px 50px, #60a5fa 60%, transparent 61%), radial-gradient(3px 3px at 170px 25px, #fbbf24 60%, transparent 61%), radial-gradient(3px 3px at 250px 70px, #34d399 60%, transparent 61%); background-size: 300px 120px; animation: cbgFall 6s linear infinite; }
.cbg-pony { --cbg-emoji: '🌈🦄'; background: linear-gradient(180deg, #93c5fd, #e9d5ff 55%, #fbcfe8) !important; }
.cbg-pony::before { content: ''; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(2px 2px at 30px 20px, #fff 60%, transparent 61%), radial-gradient(3px 3px at 120px 50px, #fff 60%, transparent 61%), radial-gradient(2px 2px at 220px 15px, #fff 60%, transparent 61%); background-size: 300px 120px; animation: cbgTwinkle 2.5s ease-in-out infinite alternate; }
.cbg-animals { --cbg-emoji: '🐼🐥🐸🐷'; background: linear-gradient(180deg, #fef3c7, #d9f99d 60%, #86efac) !important; }
.cbg-animals::after { font-size: 34px; }
.cbg-castle { --cbg-emoji: '🌙🏰'; background: linear-gradient(180deg, #4c1d95, #db2777 60%, #fbcfe8) !important; }
.cbg-castle::before { content: ''; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(2px 2px at 30px 20px, #fde68a 60%, transparent 61%), radial-gradient(3px 3px at 120px 50px, #fde68a 60%, transparent 61%), radial-gradient(2px 2px at 220px 15px, #fde68a 60%, transparent 61%), radial-gradient(2px 2px at 300px 40px, #fde68a 60%, transparent 61%); background-size: 340px 120px; animation: cbgTwinkle 3s ease-in-out infinite alternate; }
@keyframes cbgDrift { from { background-position: 0 0; } to { background-position: 340px 0; } }

/* ---------- Toko: halaman /toko ---------- */
.toko-page { max-width: 1040px; }
/* Latar paling belakang halaman Toko putih bersih (ridwanyas, 28 Sep 2026): panggung
   toko yang krem butuh bidang putih di belakangnya, bukan --bg kehijauan situs. */
body.shop-bg { background: #fff; }
/* ===== Toko gaya "petshop" (28 Sep 2026, ridwanyas): tenda hijau-putih, pita ungu +
   bintang, panggung krem, kartu bertopi warna kelangkaan. Brand tetap hijau-putih; ungu
   hanya pemanis di pita (izin ridwanyas). Pratinjau (.toko-pv*) & modal isi-Gold tak diubah. */
.shop-stage { position: relative; margin: 30px 0 16px; padding: 0 14px 18px; background: linear-gradient(180deg, #fffef8, #fff6e2); border: 1px solid #f0e2bd; border-top: 0; border-radius: 0 0 22px 22px; box-shadow: 0 12px 30px rgba(120,80,0,.10), inset 0 1px 0 #fffdf5; }
.shop-awning { position: relative; height: 42px; margin: 0 -1px; background: repeating-linear-gradient(90deg, #1aa657 0 26px, #ffffff 26px 52px); border-radius: 16px 16px 0 0; box-shadow: inset 0 -3px 6px rgba(0,0,0,.08); }
.shop-awning::after { content: ""; position: absolute; left: -1px; right: -1px; bottom: -15px; height: 17px; background: radial-gradient(13px at 13px 0, #1aa657 96%, #0000) 0 0/52px 17px repeat-x, radial-gradient(13px at 39px 0, #ffffff 96%, #0000) 0 0/52px 17px repeat-x; filter: drop-shadow(0 3px 2px rgba(0,0,0,.10)); }
.shop-star { position: absolute; top: -26px; left: 50%; transform: translateX(-50%); z-index: 3; width: 58px; height: 58px; filter: drop-shadow(0 4px 5px rgba(180,120,0,.4)); }
.shop-ribbon { position: relative; z-index: 2; width: max-content; max-width: 80%; margin: 2px auto 0; padding: 9px 30px; background: linear-gradient(180deg, #a855f7, #7c3aed); border-radius: 10px; box-shadow: 0 4px 0 #5b21b6, 0 8px 16px rgba(91,33,182,.35); }
.shop-ribbon span { font-family: var(--font-display, inherit); font-size: 25px; font-weight: 800; line-height: 1.05; letter-spacing: .3px; color: #fff; text-shadow: 0 2px 0 rgba(0,0,0,.18); }
.shop-ribbon::before, .shop-ribbon::after { content: ""; position: absolute; top: 52%; width: 18px; height: 26px; transform: translateY(-50%); background: #6d28d9; z-index: -1; }
.shop-ribbon::before { left: -13px; clip-path: polygon(0 50%, 100% 0, 100% 100%); }
.shop-ribbon::after { right: -13px; clip-path: polygon(0 0, 100% 50%, 0 100%); }
.shop-close { position: absolute; top: -4px; right: 8px; z-index: 4; width: 38px; height: 38px; border-radius: 50%; border: 3px solid #fff; background: radial-gradient(circle at 35% 30%, #ffb84d, #f5910f); color: #fff; font-size: 15px; font-weight: 900; display: grid; place-items: center; text-decoration: none; box-shadow: 0 4px 8px rgba(180,100,0,.35); }
.shop-wallet { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin: 16px 0 12px; }
.toko-chip { display: inline-flex; align-items: center; gap: 7px; padding: 7px 14px 7px 7px; border-radius: 999px; font-weight: 800; font-size: 16px; color: #5b4326; background: linear-gradient(180deg, #fff, #fdeec8); border: 1px solid #e6cf94; box-shadow: inset 0 1px 0 #fff, 0 2px 4px rgba(160,110,0,.12); }
.toko-coin { width: 24px; height: 24px; border-radius: 50%; display: inline-grid; place-items: center; font-weight: 900; font-size: 12px; font-family: var(--display, inherit); }
.toko-coin.mp { background: linear-gradient(180deg, #6ee7b7, #10b981); color: #052e16; box-shadow: inset 0 0 0 1.5px #a7f3d0; }
.toko-coin.gold { background: radial-gradient(circle at 35% 30%, #ffe58a, #f5b62b 55%, #b97a08); color: #3b2200; box-shadow: inset 0 0 0 1.5px #ffd86b; }
.toko-chip .plus { margin-left: 3px; width: 22px; height: 22px; border-radius: 50%; background: var(--premium, #7c3aed); color: #fff; display: inline-grid; place-items: center; font-size: 15px; text-decoration: none; box-shadow: 0 2px 3px rgba(124,58,237,.4); }
.shop-tabs-wrap { display: flex; justify-content: center; margin-bottom: 14px; }
.toko-tabs { display: inline-flex; gap: 2px; max-width: 100%; overflow-x: auto; scrollbar-width: none; padding: 5px; border-radius: 999px; background: #f3e6c6; box-shadow: inset 0 2px 4px rgba(150,100,0,.18); }
.toko-tabs::-webkit-scrollbar { display: none; }
.toko-tab { flex: none; padding: 8px 16px; border-radius: 999px; font-size: 14px; font-weight: 800; border: 0; background: transparent; color: #a07d3a; cursor: pointer; white-space: nowrap; }
.toko-tab.on { background: linear-gradient(180deg, #34d17a, #12a150); color: #fff; box-shadow: 0 2px 5px rgba(16,120,60,.4), inset 0 1px 0 rgba(255,255,255,.4); }
.toko-banner { border-radius: 18px; padding: 14px 16px; color: #fff; position: relative; overflow: hidden; margin-bottom: 14px; background: linear-gradient(115deg, #2b1163, #6d28d9 55%, #0e7a4a); border: 1px solid rgba(255,255,255,.18); }
.toko-banner::before { content: ""; position: absolute; inset: 0; background: linear-gradient(100deg, transparent 30%, rgba(52,211,153,.35) 45%, rgba(196,140,255,.4) 60%, transparent 75%); filter: blur(8px); }
.toko-banner > * { position: relative; }
.toko-banner .tag { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: .06em; padding: 3px 8px; border-radius: 999px; background: #ffd24d; color: #3b2200; }
.toko-banner h2 { margin: 8px 0 4px; font-size: 22px; color: #fff; }
.toko-banner p { margin: 0 0 12px; font-size: 13px; color: #ece4ff; max-width: 520px; }
.toko-banner img { position: absolute; right: 6px; bottom: -8px; width: 104px; filter: drop-shadow(0 6px 10px rgba(0,0,0,.5)); }
.toko-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
.toko-card { background: #fff; border: 1px solid #efe4c6; border-radius: 16px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 10px rgba(150,110,20,.12); position: relative; }
.toko-card.r-epic { border-color: #f0a6c2; }
.toko-card.r-legendary { border-color: #f3d071; box-shadow: 0 0 0 1px rgba(245,182,43,.35), 0 4px 14px rgba(245,182,43,.3); }
.toko-cap { display: flex; align-items: center; gap: 6px; padding: 6px 10px; font-size: 12px; font-weight: 800; color: #fff; }
.toko-cap .cap-ic { width: 16px; height: 16px; display: inline-grid; place-items: center; flex: none; }
.toko-cap .cap-ic svg { width: 16px; height: 16px; }
.toko-cap .cap-txt { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.toko-cap .cap-rar { font-size: 10px; letter-spacing: .05em; opacity: .92; text-transform: uppercase; flex: none; }
.toko-cap.common { background: #8b98a4; } .toko-cap.rare { background: #3b82f6; }
.toko-cap.special { background: #a855f7; } .toko-cap.epic { background: linear-gradient(90deg, #e9407a, #f97316); }
.toko-cap.legendary { background: linear-gradient(90deg, #f5b62b, #ffd86b); color: #3b2200; }
.toko-pv { height: 118px; position: relative; display: grid; place-items: center; overflow: hidden; background: linear-gradient(160deg, #eef4f0, #dfe9e3); }
.toko-pv .pfp { --pfp: 64px; border-radius: 50%; }
.toko-pv .toko-ic { width: 54px; height: 54px; color: #fff; filter: drop-shadow(0 4px 8px rgba(0,0,0,.35)); }
.toko-pv .toko-ic svg { width: 100%; height: 100%; }
.toko-pv.cbg { color: #fff; }
.toko-pv.k-board { --kb-l: #ede9fe; --kb-d: #7c3aed; }
.toko-pv.k-board::before { content: ""; width: 84px; height: 84px; border-radius: 8px; background: repeating-conic-gradient(var(--kb-d) 0 25%, var(--kb-l) 0 50%) 0 0 / 28px 28px; box-shadow: 0 0 14px rgba(0,0,0,.25); }
/* Warna petak = BoardSpec di app (shop.dart shopBoardOf) — satu papan, dua tempat. */
.toko-pv.kb-wood { --kb-l: #e9d5b3; --kb-d: #8f5f34; } .toko-pv.kb-green { --kb-l: #e8f1e4; --kb-d: #5f9463; }
.toko-pv.kb-marble { --kb-l: #f3f4f6; --kb-d: #a4abb4; } .toko-pv.kb-kawung { --kb-l: #e7e0cd; --kb-d: #4c5a8e; }
.toko-pv.kb-ice { --kb-l: #eaf6ff; --kb-d: #7fb8e6; } .toko-pv.kb-lavender { --kb-l: #f1e9ff; --kb-d: #9b7ae0; }
.toko-pv.kb-glass_space { --kb-l: #4a4f82; --kb-d: #23264a; background: radial-gradient(circle at 70% 30%, #3b1d7a, #090b1f 60%); }
/* Efek papan: papan mini + lambang efeknya di atasnya. */
.toko-pv.kb-fx { --kb-l: #e8f1e4; --kb-d: #5f9463; background: #13202b; }
.toko-pv.kb-fx .toko-fx { position: absolute; font-size: 44px; line-height: 1; filter: drop-shadow(0 4px 8px rgba(0,0,0,.5)); animation: tokoFxPulse 1.6s ease-in-out infinite alternate; }
.toko-pv.fx-mate_fireworks::before, .toko-pv.fx-mate_dragon::before { box-shadow: 0 0 24px #f59e0b, 0 0 6px #fde68a; }
.toko-pv.fx-check_lightning::before { box-shadow: 0 0 24px #818cf8; }
.toko-pv.fx-square_glow::before { box-shadow: 0 0 20px #4ade80; }
.toko-pv.fx-trail_sparkle::before { box-shadow: 0 0 18px #fde68a; }
.toko-pv.fx-capture_glass::before { box-shadow: 0 0 18px #67e8f9; }
.toko-pv.fx-capture_leaves::before { box-shadow: 0 0 18px #86efac; }
@keyframes tokoFxPulse { from { transform: scale(.92); } to { transform: scale(1.08); } }
.toko-pv.dark { background: #13202b; color: #fff; }
.toko-pv .toko-big { font-size: 46px; line-height: 1; filter: drop-shadow(0 4px 8px rgba(0,0,0,.45)); }
.toko-pv .toko-sub { position: absolute; bottom: 8px; left: 0; right: 0; text-align: center; font-size: 12px; font-weight: 800; opacity: .85; text-transform: capitalize; }
.toko-pv.toko-emotes { display: flex; flex-wrap: wrap; gap: 4px 8px; align-content: center; justify-content: center; padding: 8px 14px; font-size: 26px; line-height: 1.1; background: linear-gradient(160deg, #fff7ed, #ffedd5); }
.toko-pv.toko-bundle { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; padding: 6px; background: #13202b; place-items: stretch; }
.toko-pv.toko-bundle .toko-mini { overflow: hidden; border-radius: 8px; }
.toko-pv.toko-bundle .toko-mini .toko-pv { height: 106px; transform: scale(.8); transform-origin: center; }
.toko-pv .toko-plate { display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 12px; background: #f4efe6; color: #1b2430; font-size: 13px; max-width: 92%; }
.toko-pv .toko-plate .pfp { --pfp: 26px; }
.toko-pv .toko-plate b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.toko-pv .toko-plate small { opacity: .6; }
.toko-pv .toko-plate.pl-emerald { background: linear-gradient(180deg, #34d399, #059669); color: #fff; }
.toko-pv .toko-plate.pl-sapphire { background: linear-gradient(180deg, #60a5fa, #1d4ed8); color: #fff; }
.toko-pv .toko-plate.pl-gold_batik { background: radial-gradient(circle, #b45309 1.5px, transparent 2px) 0 0 / 10px 10px, linear-gradient(180deg, #fde68a, #d97706); color: #3b2200; }
.toko-pv .toko-plate.pl-flow { background: linear-gradient(90deg, #f97316, #ec4899, #8b5cf6); color: #fff; }
.toko-pv .toko-plate.pl-none b.hero-title { margin: 0; }
.toko-pv .toko-title-pv { display: inline-flex; align-items: center; gap: 10px; }
.toko-pv .toko-title-pv > span { display: flex; flex-direction: column; gap: 3px; }
.toko-pv .toko-title-pv em { font-style: normal; display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 800; background: linear-gradient(90deg, #f5b62b, #ffe58a); color: #3b2200; }
/* Halaman isi Gold (modal) */
.toko-topup { max-width: 560px; width: min(96vw, 560px); }
.toko-packs { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin-top: 12px; }
.toko-pack { position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 14px 10px 12px; border-radius: 14px; border: 1.5px solid var(--line); background: var(--bg-card); cursor: pointer; text-align: center; font: inherit; color: inherit; }
.toko-pack:hover:not(:disabled) { border-color: #f5b62b; box-shadow: 0 0 0 3px rgba(245,182,43,.25); }
.toko-pack:disabled { opacity: .5; cursor: not-allowed; }
.toko-pack.best { border-color: #f5b62b; background: linear-gradient(180deg, #fff9e6, var(--bg-card)); }
.toko-pack.best::after { content: "TERMURAH"; position: absolute; top: -9px; left: 50%; transform: translateX(-50%); font-size: 9px; font-weight: 900; letter-spacing: .08em; padding: 2px 8px; border-radius: 999px; background: linear-gradient(90deg, #f5b62b, #ffe58a); color: #3b2200; }
.toko-pack.short { opacity: .55; }
.toko-pack-gold { display: inline-flex; align-items: center; gap: 6px; font-weight: 900; font-size: 20px; }
.toko-pack-gold .toko-coin { width: 22px; height: 22px; font-size: 11px; }
.toko-pack-bonus { font-size: 11px; color: var(--muted); }
.toko-pack-price { font-weight: 800; font-size: 15px; margin-top: 4px; }
.toko-pack-per { font-size: 11px; color: var(--muted); }
.toko-rar { position: absolute; top: 8px; left: 8px; font-size: 10px; font-weight: 900; letter-spacing: .06em; padding: 3px 8px; border-radius: 999px; color: #fff; text-transform: uppercase; z-index: 2; box-shadow: 0 2px 6px rgba(0,0,0,.3); }
.toko-rar.common { background: #9aa7b2; color: #1b2430; } .toko-rar.rare { background: #3b82f6; } .toko-rar.special { background: #a855f7; }
.toko-rar.epic { background: linear-gradient(90deg, #e9407a, #f97316); } .toko-rar.legendary { background: linear-gradient(90deg, #f5b62b, #ffe58a); color: #3b2200; }
.toko-card, .toko-body { color: #26313f; }
.toko-badge { position: absolute; top: 30px; left: 0; z-index: 3; font-size: 10px; font-weight: 900; letter-spacing: .04em; padding: 4px 9px; color: #fff; }
.toko-badge.hemat { border-radius: 0 8px 8px 0; background: linear-gradient(180deg, #fb7185, #e11d48); box-shadow: 0 2px 5px rgba(190,20,60,.35); }
.toko-own { position: absolute; top: 30px; right: 8px; font-size: 10px; font-weight: 900; padding: 3px 7px; border-radius: 6px; background: rgba(20,30,40,.62); color: #fff; z-index: 3; }
.toko-own.on { background: linear-gradient(180deg, #34d17a, #12a150); }
.toko-body { padding: 9px 12px 4px; flex: 1; }
.toko-name { font-weight: 800; font-size: 15px; line-height: 1.2; }
.toko-desc { font-size: 12px; color: #7b8794; margin-top: 3px; min-height: 30px; }
.toko-btns { display: flex; gap: 6px; padding: 8px 10px 12px; }
.toko-btn { flex: 1; min-height: 36px; border-radius: 12px; border: 0; font-weight: 900; font-size: 13px; display: inline-flex; align-items: center; justify-content: center; gap: 5px; cursor: pointer; }
.toko-btn.mp { background: linear-gradient(180deg, #6ee7b7, #22c55e); color: #052e16; box-shadow: 0 3px 0 #15803d; }
.toko-btn.gold { background: linear-gradient(180deg, #ffe58a, #f5b62b); color: #3b2200; box-shadow: 0 3px 0 #b97a08; }
.toko-btn.dis { background: var(--bg-soft); color: var(--muted); cursor: default; font-size: 12px; }
.toko-btn.equip { background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent); }
.toko-btn:disabled { opacity: .55; cursor: not-allowed; box-shadow: none; }
.toko-btn .toko-coin { width: 16px; height: 16px; font-size: 9px; }
.toko-legend { margin-top: 16px; }
.toko-legend .row span { font-size: 11px; font-weight: 900; padding: 3px 9px; border-radius: 999px; color: #fff; letter-spacing: .05em; }
.toko-rules table { width: 100%; border-collapse: collapse; font-size: 13px; }
.toko-rules td, .toko-rules th { padding: 5px 6px; border-bottom: 1px solid var(--line); text-align: left; }
.toko-rules th { font-size: 12px; color: var(--muted); }
.toko-rules td.n { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
.toko-grants { display: flex; flex-wrap: wrap; gap: 8px; }
.toko-grant { padding: 8px 12px; border-radius: 12px; background: var(--accent-soft); font-size: 13px; font-weight: 700; }
@media (max-width: 520px) { .toko-grid { grid-template-columns: 1fr 1fr; gap: 9px; } .toko-name { font-size: 13.5px; } .toko-btn { font-size: 12px; } }
.hero-title { display: inline-block; margin: 2px 0 4px; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 800; background: linear-gradient(90deg, #f5b62b, #ffe58a); color: #3b2200; }

/* =================================================================================
   LAYAR MAIN (`.game-screen`) — /play-bot dan /play-online, 23 September 2026.

   Permintaan ridwanyas: di HP tampilannya harus terasa seperti aplikasi iPhone (untuk
   sementara pemakai iOS main lewat web), dan di desktop harus lebih padat sehingga
   tidak perlu banyak menggulir.

   Parameter yang diambil dari dua situs catur terbesar, bukan dari selera:
   * lichess — ukuran papan diikat ke TINGGI layar, bukan lebar kolomnya; halaman
     partai tidak pernah menggulir, yang menggulir hanya panel di sampingnya.
   * chess.com — kendali partai (balik papan / remis / menyerah) duduk tepat di bawah
     papan di HP, jam memakai angka tabular berukuran besar di ujung baris pemain, dan
     daftar langkah di HP jadi satu baris yang digulir mendatar, bukan kotak tinggi.
   * Keduanya memakai sasaran sentuh >= 44px (juga aturan Apple HIG).

   Kelasnya `.game-screen` dan bukan `.play-page`, karena /analysis, /explore dan
   /padepokan memakai `.play-page` juga dan tata letaknya sudah diukur sendiri
   (lihat catatan --board-w 100vh-384px di halaman Analysis).
   ================================================================================= */

/* ---------- HP: satu layar penuh yang terasa seperti aplikasi ---------- */
@media (max-width: 900px) {
  /* Bilah judul layar main: tombol kembali, nama layar, lalu palet & menu — bentuk
     yang sama dengan bilah judul di aplikasi. Merek dan pemilih bahasa tidak ikut:
     keduanya milik bilah situs, dan di layar partai yang dicari mata cuma jalan keluar
     dan nama layar tempatnya berada. Ditambahkan js/api.js (lihat `game-bar` di sana). */
  .topbar.game-bar {
    padding: calc(5px + env(safe-area-inset-top)) 8px 5px; gap: 4px;
  }
  .topbar.game-bar .brand,
  .topbar.game-bar .lang-pick { display: none; }
  .game-head { display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0; }
  .gb-back, .gb-look {
    background: none; border: 0; padding: 0; margin: 0; color: var(--text);
    width: 44px; height: 44px; flex: none; border-radius: 12px;
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; touch-action: manipulation;
  }
  @media (hover: hover) { .gb-back:hover, .gb-look:hover { background: var(--bg-soft); } }
  /* Tiga ikon kanan disamakan bentuknya — tombol menu yang berkotak sendirian di antara
     dua ikon polos terbaca sebagai tombol yang lebih penting daripada keduanya. Urutan
     kanannya: palet, pesan, menu, seperti di app. */
  .topbar.game-bar .nav-toggle {
    background: none; border: 0; padding: 0; width: 44px; height: 44px;
    display: inline-flex; align-items: center; justify-content: center; order: 3;
  }
  .topbar.game-bar .inbox-link { order: 2; width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  .topbar.game-bar .gb-look { order: 1; }
  .gb-title {
    font-family: var(--font-display); font-size: 17px; font-weight: 800;
    letter-spacing: -.1px; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* Halaman menu (Liga, Toko, Peringkat, Akun, …) memakai bilah yang sama — `page-bar`
     di js/api.js. Bedanya hanya dua: tidak ada ikon palet (di luar papan ia tidak punya
     apa pun untuk dibuka), dan tidak ada tombol kembali di halaman yang sudah berdiri di
     bilah tab bawah. Tanpa ini, berpindah dari layar partai ke menu berarti berpindah ke
     bilah yang lain sama sekali — merek, pemilih bahasa, lalu nama halamannya sekali
     lagi di dalam isi. */
  .topbar.page-bar {
    padding: calc(5px + env(safe-area-inset-top)) 8px 5px; gap: 4px;
  }
  .topbar.page-bar .brand,
  .topbar.page-bar .lang-pick { display: none; }
  .topbar.page-bar .nav-toggle {
    background: none; border: 0; padding: 0; width: 44px; height: 44px;
    display: inline-flex; align-items: center; justify-content: center; order: 3;
  }
  .topbar.page-bar .inbox-link { order: 2; width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  /* Tanpa panah kembali, judulnya mulai dari tepi yang sama dengan isi halaman di
     bawahnya; dengan panah, panah itulah yang memegang tepinya. */
  .topbar.page-bar .game-head:not(:has(.gb-back)) { padding-left: 8px; }
  /* Judul halaman yang sudah berdiri di bilah — dipasang js/api.js, hanya kalau kedua
     tulisannya memang sama persis. */
  .title-in-bar { display: none; }
  /* Merek yang digambar DI DALAM isi halaman keterangan (/tentang, /press, /app,
     /hapus-akun) ikut pergi di ponsel begitu bilahnya memakai bentuk aplikasi: tidak ada
     layar aplikasi yang menuliskan nama produknya sekali lagi tepat di atas judulnya.
     Di layar lebar ia tetap — di sana bilahnya memang bilah merek. */
  .topbar.page-bar ~ .container > .brand-mark { display: none; }
  /* Dompet Toko memegang tepi kanan sendiri begitu judulnya (yang tadinya `flex: 1`)
     pergi ke bilah — di aplikasi pun MP & Gold duduk di ujung kanan, bukan mengambang
     di kiri dengan separuh baris kosong di sebelahnya. */
  .toko-head:has(.title-in-bar) { justify-content: flex-end; }

  /* Papan menyentuh kedua tepi layar, persis seperti di aplikasi Flutter — di layar
     390px, 16px padding kiri-kanan berarti papan 358px, dan 20px itu satu petak penuh
     yang hilang. Sisa isinya (plakat, tombol, kartu) tetap masuk 10px seperti di app.
     300px = bilah judul 44 + dua plakat 96 + tombol 58 + tab bawah 64 + jaraknya. */
  .game-screen {
    --board-w: min(100vw, calc(100dvh - 300px));
    --game-inset: 10px;
    padding-left: 0; padding-right: 0; padding-top: 8px;
    -webkit-tap-highlight-color: transparent;
  }
  .game-screen .board-wrap { max-width: var(--board-w); margin: 6px auto !important; }
  .game-screen .eval-head,
  .game-screen .player-row,
  .game-screen .board-actions,
  .game-screen .board-actions-row,
  .game-screen #themeQuick,
  .game-screen #status,
  .game-screen .play-layout > .grid,
  .game-screen .play-layout > div > #offlineNote,
  .game-screen #coachMount {
    max-width: calc(var(--board-w) - var(--game-inset) * 2);
    margin-left: auto; margin-right: auto;
  }

  /* Baris pemain: foto, nama, rating, lalu jam di ujung kanan — bentuk yang sama di
     chess.com, lichess, dan di aplikasi Flutter kita sendiri. */
  .game-screen .player-row {
    padding: 6px 10px; border-radius: 14px; min-height: 46px; gap: 8px;
  }
  .game-screen .player-row .name { font-size: 15px; font-weight: 700; }
  .game-screen .player-row .pfp { --pfp: 32px; }
  .game-screen .player-row .clock {
    font-size: 19px; padding: 3px 10px; border-radius: 10px; letter-spacing: .5px;
  }

  /* Tombol partai: tiga pil setinggi jempol, sudut membulat ala iOS. */
  .game-screen .board-actions,
  .game-screen .board-actions-row {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px;
  }
  .game-screen .board-actions.finished { grid-template-columns: 1fr; }
  .game-screen .board-actions button,
  .game-screen .board-actions-row button {
    min-height: 50px; border-radius: 14px; font-size: 12.5px; font-weight: 700;
    touch-action: manipulation;
  }
  .game-screen .board-actions-row button { padding: 8px 6px; line-height: 1.2; }

  /* Warna tombol mengikuti aplikasi: netral untuk batal, hijau muda untuk petunjuk,
     merah muda untuk menyerah — bidang merah pekat selebar sepertiga layar terbaca
     sebagai peringatan bahaya, padahal menyerah adalah pilihan biasa. */
  .game-screen .board-actions button#hint,
  .game-screen .board-actions-row button#drawBtn {
    background: var(--accent-soft); color: var(--accent); border: 1px solid transparent;
  }
  .game-screen .board-actions button.danger,
  .game-screen .board-actions-row button.danger {
    background: #fdeaea; color: #c0342c; border: 1px solid #f5cdcb;
  }

  /* Kartu di bawah papan: sudut 18px dan sebuah gagang kecil di atasnya, supaya bagian
     bawah layar terbaca sebagai lembar yang bisa ditarik — bukan sebagai sisa halaman
     web yang kebetulan ada di bawah papan. */
  .game-screen:not(.duel-page) .play-layout > .grid { margin-top: 10px; gap: 10px; }
  /* Di /play-online kolom kanan `display: contents`, jadi yang harus dimasukkan ke
     dalam tepi layar adalah kartu-kartunya sendiri — bukan wadahnya, yang tidak
     menggambar apa pun. */
  .game-screen.duel-page .play-layout > .grid > * {
    width: 100%; max-width: calc(var(--board-w) - var(--game-inset) * 2);
    margin-left: auto; margin-right: auto;
  }
  /* Di /play-online kolom kanan memakai `display: contents` di HP supaya kartunya bisa
     diurutkan melewati papan — sebuah `::before` di sana akan menetas jadi pil abu-abu
     yang melayang sendirian di tengah halaman. */
  .game-screen:not(.duel-page) .play-layout > .grid::before {
    content: ''; display: block; width: 38px; height: 5px; border-radius: 999px;
    background: var(--line); margin: 0 auto 2px;
  }
  .game-screen .card { border-radius: 18px; padding: 14px; }

  /* Daftar langkah jadi satu baris mendatar: di HP sebuah kotak setinggi 320px berarti
     sepertiga layar dipakai riwayat, dan riwayat bukan yang sedang dilihat orang. */
  .game-screen .moves {
    display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 4px;
    max-height: none; align-items: center; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .game-screen .moves::-webkit-scrollbar { display: none; }
  .game-screen .moves .num,
  .game-screen .moves .mv { white-space: nowrap; flex: none; }
}

/* ---------- Desktop: padat, dan muat dalam satu layar ---------- */
@media (min-width: 901px) {
  /* Bilah judul layar main hanya untuk ponsel: di layar lebar bilah situs lengkap
     dengan menunya justru yang dicari, dan ruangnya memang ada. */
  .game-head, .gb-look { display: none; }
  /* 190px = bilah situs 57 + jarak atas 10 + nama pembukaan 22 + dua plakat pemain 84
     + jarak papan 8 + jarak bawah 9. Tidak ada lagi bilah keunggulan mendatar dan tidak
     ada lagi baris tombol di bawah papan: yang pertama berdiri tegak di samping papan,
     yang kedua pindah ke panel kanan (js/wide-board.js) — persis seperti chess.com.
     Angka lama 350px berarti papan 418px di laptop 1366x768, dan itulah yang dikeluhkan
     ridwanyas 24 Sep 2026 ("kecil banget papannya, padahal buka pakai laptop"). */
  .game-screen:not(.duel-page) { --board-w: min(820px, calc(100vh - 202px)); }
  /* Baris keterangan di bawah papan: satu baris, bukan sebuah paragraf dengan margin
     bawaan — 37px terakhir inilah yang menentukan halaman menggulir atau tidak. Selagi
     partai berjalan isinya sebuah `.alert` (giliran siapa, skak, hasil): dirampingkan
     juga, karena ia muncul justru pada saat halaman paling tidak boleh menggulir. */
  .game-screen #status p { margin: 4px 0 0; font-size: 13px; }
  .game-screen #status .alert { padding: 5px 10px; font-size: 13px; margin-top: 4px; }
  /* Duel punya ukurannya sendiri: tidak ada nama pembukaan di atas papan, jadi 22px
     lebih hemat daripada Lawan Bot. Baris tombolnya ikut pindah ke panel kanan. */
  .game-screen.duel-page { --board-w: min(880px, calc(100vh - 192px)); }
  .game-screen { padding-top: 10px; padding-bottom: 8px; }
  /* Plakat pemain diramping 10px: di layar lebar setiap piksel di sini diambil dari
     papan, dan foto 30px-lah yang menentukan tingginya, bukan jarak di sekelilingnya. */
  .game-screen .player-row { padding: 5px 10px; }
  .game-screen .player-row .clock { font-size: 18px; padding: 1px 9px; }
  /* Nama pembukaan: satu baris tipis, satu-satunya yang masih berdiri di atas papan. */
  .game-screen .eval-head { max-width: none; margin: 0 0 4px; }
  .game-screen .board-wrap { margin-top: 4px !important; margin-bottom: 4px !important; }
  /* Dua kolom yang saling menempel di tengah layar, bukan dua kolom yang direnggangkan
     selebar halaman: kolom kiri dibuat persis selebar papan, dan pasangan itu
     ditengahkan. Sebelum ini papan 438px berenang di dalam kolom 632px, dan yang
     terlihat adalah lapangan kosong di kiri papan. */
  .game-screen .play-layout {
    gap: 14px; justify-content: center;
    grid-template-columns: minmax(320px, var(--board-w)) minmax(280px, 360px);
  }
  .game-screen .play-layout > .grid { gap: 10px; }
  .game-screen .card { padding: 13px; }
  .game-screen .card h2, .game-screen .card h3 { margin-bottom: 8px; font-size: 16px; }
  .game-screen .card p.hint { margin: 2px 0 8px; }
  .game-screen .board-actions-row { max-width: var(--board-w); margin: 8px auto 0; display: flex; gap: 8px; }
  .game-screen .board-actions-row button { flex: 1; min-height: 40px; }
  .game-screen .moves { max-height: 210px; }

  /* ---------- bilah keunggulan tegak di samping papan ----------
     Dipindahkan ke dalam .board-wrap oleh js/wide-board.js, jadi tingginya SELALU
     persis tinggi papan tanpa menghitung apa pun. Ia keluar dari aliran halaman: 53px
     yang dulu dipakainya di atas papan sekarang jadi papan. Sama dengan chess.com dan
     lichess, dan alasannya sama — di layar lebar yang langka itu tinggi, bukan lebar. */
  .play-page .board-wrap > .eval-row {
    position: absolute; top: 0; bottom: 0; left: -34px; width: 26px;
    display: flex; flex-direction: column; gap: 0; margin: 0;
  }
  /* Cincin luar, bukan cuma cincin dalam: separuh bilah yang milik Putih berdiri di atas
     halaman yang juga putih, dan tanpa garis itu ujung bilahnya hilang. */
  .play-page .board-wrap > .eval-row .eval-bar {
    flex: 1; width: 26px; height: auto; border-radius: 7px;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .18);
  }
  /* Putih tumbuh dari bawah — ujung papan tempat Putih duduk. Kalau papannya dibalik
     (pemain memegang Hitam) ujungnya ikut bertukar lewat kelas `flip`. */
  .play-page .board-wrap > .eval-row .eval-bar .fill {
    top: auto; bottom: 0; left: 0; right: 0; width: auto; height: var(--eval, 50%);
    background: linear-gradient(90deg, #ffffff, #e4e4da);
    transition: height .45s cubic-bezier(.4, 0, .2, 1);
  }
  .play-page .board-wrap > .eval-row.flip .eval-bar .fill { top: 0; bottom: auto; }
  .play-page .board-wrap > .eval-row .eval-bar .mid { left: 0; right: 0; top: 50%; width: auto; height: 1px; }
  /* Angkanya duduk di ujung yang sedang unggul, di atas warnanya sendiri — jadi warna
     tulisannya ikut: gelap di atas putih, terang di atas hitam. */
  .play-page .board-wrap > .eval-row .eval-num {
    position: absolute; left: 0; right: 0; bottom: 3px; min-width: 0;
    font-size: 9.5px; text-align: center; letter-spacing: -.3px; color: #1d1d1f;
    white-space: nowrap; /* "-1.11" tidak boleh patah jadi dua baris di bilah 26px */
  }
  .play-page .board-wrap > .eval-row.lead-b .eval-num { bottom: auto; top: 3px; color: #f1f1f1; }
  .play-page .board-wrap > .eval-row.flip .eval-num { bottom: auto; top: 3px; }
  .play-page .board-wrap > .eval-row.flip.lead-b .eval-num { top: auto; bottom: 3px; }
  .play-page .board-wrap > .eval-row .eval-note { display: none; }

  /* ---------- kendali partai di panel kanan ----------
     Di ponsel tombol-tombol ini tetap tepat di bawah papan (23 Sep 2026); di laptop
     mereka justru barang yang paling mahal di sana, karena 62px tombol + 24px baris
     keterangan diambil langsung dari sisi papan. chess.com menaruhnya di panel, di
     bawah daftar langkah, dan itu pula tempatnya sekarang. */
  .game-screen .side-controls { display: grid; gap: 8px; }
  .game-screen .side-controls .board-actions { max-width: none; margin: 0; }
  .game-screen .side-controls .board-actions button {
    flex-direction: row; gap: 6px; min-height: 44px; padding: 8px 4px; font-size: 12px;
  }
  .game-screen .side-controls .board-actions-row { max-width: none; margin: 0; }
  .game-screen .side-controls #status { max-width: none; }
  .game-screen .side-controls #status:empty { display: none; }

  /* Panel kanan menggulir sendiri, halamannya tidak. Tanpa ini pemilih lawan yang
     tinggi (Lawan Bot) atau obrolan yang panjang (duel) mengembalikan gulir halaman
     yang justru dibuang 23 Sep — dan papan ikut naik-turun bersamanya. */
  .game-screen .play-layout > .grid {
    max-height: calc(100vh - 76px); overflow-y: auto; overscroll-behavior: contain;
    padding-right: 4px;
  }
}

/* ------------------------------------------------------------------ tamu ---- *
 * Ajakan mendaftar di halaman yang boleh dicoba tanpa akun (27 Sep 2026):
 * /play-bot dan /teka-teki. Dirakit `guestCta()` di js/api.js — satu bentuk untuk
 * semuanya, jadi kalimatnya bisa diperbaiki di satu tempat.
 */
.guest-note { border: 1px solid var(--line, #dfe5da); background: #f6faf3; }
.guest-note h3 { margin: 0 0 4px; font-size: 17px; }
.guest-note p { margin: 0 0 10px; }
.guest-note .row { gap: 8px; flex-wrap: wrap; }
@media (prefers-color-scheme: dark) { .guest-note { background: rgba(255, 255, 255, .04); } }

/* Bidak master terkunci bagi tamu: masih terlihat, jelas belum bisa dipakai. */
.master-card.locked { opacity: .55; filter: grayscale(.6); position: relative; }
.master-card.locked::after {
  content: '🔒'; position: absolute; top: 6px; right: 8px; font-size: 13px;
}

/* Judul & keterangan halaman yang hanya untuk mesin pencari dan pembaca layar:
 * terbaca alat bantu dan perayap, tidak memakan satu piksel pun di layar. Halaman
 * permainan sengaja muat satu layar tanpa gulir (lihat .game-screen), jadi teksnya
 * tidak boleh mendorong papan. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Keterangan panjang di dasar halaman permainan — untuk pembaca dan mesin pencari,
 * di bawah papan, jadi tidak mengganggu yang datang untuk bermain. */
.seo-body { margin: 22px auto 0; max-width: 760px; }
.seo-body h2 { margin: 18px 0 6px; font-size: 18px; }
.seo-body p, .seo-body li { color: var(--muted, #667); line-height: 1.6; }

/* ------------------------------------------------------- Catur Harian ------ *
 * Daftar papan harian (/harian, 27 Sep 2026). Satu baris = satu papan: foto lawan,
 * namanya, lalu keadaan giliran di kanan. Dibuat sebagai tombol, bukan div berpenangan
 * klik — papan dibuka dengan Enter juga, dan pembaca layar menyebutnya tombol.
 */
.dg-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 10px; margin: 0 0 6px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg-soft); color: var(--text); cursor: pointer;
}
@media (hover: hover) { .dg-row:hover { border-color: var(--accent); } }
.dg-main { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.dg-main b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dg-side { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: 0 0 auto; }

/* ------------------------------------------------------------------ Club ---- *
 * Halaman /club (27 Sep 2026). Spanduk club memakai gradien dua warna yang sama
 * dengan aplikasi (lib/clubs.dart `clubColors`) supaya club yang sama terlihat sama di
 * HP dan di peramban — warnanya datang dari server, kelas di sini hanya bentuknya.
 */
.club-banner {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: 12px; color: #fff; text-align: left;
}
.club-banner.big { padding: 14px 16px; border-radius: 14px; margin-bottom: 10px; }
.club-crest { font-size: 26px; line-height: 1; flex: 0 0 auto; }
.club-banner.big .club-crest { font-size: 38px; }
.club-id { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.club-id b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.club-id .small, .club-nums .small { opacity: .82; font-size: 12px; }
.club-tag { opacity: .8; font-weight: 700; }
.club-nums { display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 auto; }
.club-row { display: block; width: 100%; padding: 0; margin: 0 0 8px; border: 0; background: none; cursor: pointer; }
@media (hover: hover) { .club-row:hover .club-banner { filter: brightness(1.07); } }
.club-notice {
  margin: 8px 0; padding: 8px 10px; border-radius: 10px;
  background: var(--bg-soft); border: 1px solid var(--line);
}
.club-stats { flex-wrap: wrap; gap: 6px; margin: 8px 0 4px; }

/* Baris "kiri isi, kanan tombol" — dipakai daftar anggota, permintaan, dan Club Shop. */
.drow {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; margin: 0 0 6px; border: 1px solid var(--line);
  border-radius: 11px; background: var(--bg-soft); flex-wrap: wrap;
}
/* Dua kolom di layar lebar, satu kolom di ponsel — formulir setelan club & pendirian. */
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; margin: 6px 0; }
.grid2 label, .card > label { display: flex; flex-direction: column; gap: 3px; font-size: 13px; }
@media (max-width: 640px) { .grid2 { grid-template-columns: 1fr; } }

/* --------------------------------------------------- bingkai bergambar ----- *
 * Tiga bingkai naga (27 Sep 2026). Sembilan bingkai di atas cincin `box-shadow`;
 * yang ini GAMBAR — satu-satunya cara menggambar naga yang melingkari foto.
 *
 * Tiga hal yang harus berubah dibanding `.cb` biasa, dan ketiganya perlu:
 *   1. `overflow: visible` — naganya keluar dari kotak fotonya; tanpa ini yang terlihat
 *      cuma potongan badan di dalam kotak.
 *   2. `position: relative` — lapisan gambarnya dipasang sebagai `::after` absolut.
 *   3. foto di dalamnya diberi sudut membulat sendiri, karena pemotong milik `.pfp`
 *      (overflow) baru saja dimatikan.
 * `inset: -36%` = bingkai ±1,7× lebar foto; itu angka yang membuat badan naga duduk
 * tepat di luar sudut foto pada 32 px maupun 110 px (sudah dipotret keduanya).
 */
.cb-naga_samudra, .cb-naga_bara, .cb-naga_purnama {
  overflow: visible; position: relative;
  border-color: transparent; box-shadow: none;
}
.cb-naga_samudra > img, .cb-naga_bara > img, .cb-naga_purnama > img { border-radius: inherit; }
.cb-naga_samudra::after, .cb-naga_bara::after, .cb-naga_purnama::after {
  content: ''; position: absolute; inset: -36%;
  background: center / contain no-repeat;
  pointer-events: none;
}
.cb-naga_samudra::after { background-image: url(/img/borders/naga-samudra.svg); }
.cb-naga_bara::after { background-image: url(/img/borders/naga-bara.svg); }
.cb-naga_purnama::after { background-image: url(/img/borders/naga-purnama.svg); }
/* Denyut pelan — bukan putaran: bingkai yang berputar di sebelah nama pemain menarik
   mata terus-menerus dan membuat papan peringkat sulit dibaca. */
@media (prefers-reduced-motion: no-preference) {
  .cb-naga_samudra::after, .cb-naga_bara::after, .cb-naga_purnama::after {
    animation: cbNaga 2.8s ease-in-out infinite alternate;
  }
}
@keyframes cbNaga { from { filter: none; } to { filter: brightness(1.16) drop-shadow(0 0 5px currentColor); } }
