/* ═══════════════════════════════════════════════════════════════════════
   Antarmuka Hatoba — rancangan v6, diterapkan.
   ═══════════════════════════════════════════════════════════════════════

   Berkas ini BUKAN tafsiran atas docs/mockup-v6/ui.css. Ia salinannya:
   bagian INTI di bawah diambil apa adanya dari sana, dan satu-satunya yang
   dibuang adalah perancah halaman peraga (.peraga/.layar/.cap/.bingkai) yang
   memang cuma ada untuk memajang 25 layar dalam satu dokumen.

   Alasannya sederhana. Setiap kali sebuah rancangan "diterjemahkan" ke kode,
   yang tersisa setelah enam bulan adalah kodenya, dan rancangannya jadi
   gambar yang tidak lagi cocok dengan apa pun. Dengan menyalin, satu-satunya
   cara keduanya berbeda adalah kalau seseorang sengaja mengubah salah satunya.

   Susunannya:
     1. INTI v6         disalin dari docs/mockup-v6/ui.css
     2. JEMBATAN TOKEN  nama token lama menunjuk ke nilai v6
     3. APLIKASI        komponen yang tidak ada di mockup karena mockup
                        tidak punya peta, dropdown pencarian, atau htmx

   SATU ATURAN YANG TIDAK BOLEH DILANGGAR: bagian 3 tidak boleh
   mendefinisikan token sama sekali. Saat berkas ini pertama dirakit, satu
   blok :root warisan ikut tersalin ke bagian 3 dan menimpa seluruh palet
   serta KEDUA huruf v6 — halaman tetap tampak masuk akal, tata letaknya v6,
   warnanya v5, dan tidak ada satu pun tanda di layar. Yang menemukannya
   pengukuran, bukan mata: document.fonts.check() mengembalikan false.
   Perakitnya sekarang menolak blok semacam itu, dan e2e-py/uji_tampilan_v6.py
   memeriksa hurufnya benar-benar termuat pada tiap jalan.

   Huruf tertanam ada di fonts.css, terpisah supaya bisa di-cache sendiri.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══ 1. INTI v6 ═══ */

/* ═══════════════════════════════════════════════════════════════════════
   HATOBA UI v6 — token & komponen
   ═══════════════════════════════════════════════════════════════════════

   Rancangan, BUKAN kode produksi. Aplikasi yang berjalan masih v5.

   ── Gagasan strukturalnya: SATU sistem, TIGA kerapatan ──────────────────

   Ketiga aplikasi Hatoba dipakai orang yang berbeda, dalam keadaan yang
   berbeda, dan itu bukan selera melainkan fakta yang bisa disebutkan:

     web     kasir/admin duduk, tetikus, layar 1366x768, berjam-jam
     lantai  orang gudang & kasir BERDIRI, tangan kotor, kadang gelap
     sentuh  pelanggan di HP murah, satu ibu jari, kuota terbatas

   Yang berbeda hanya SKALA — ukuran huruf, tinggi baris, luas sasaran
   sentuh, ketebalan garis. Warna, bentuk, dan bahasa komponennya sama
   persis. Karena itu ketiganya terlihat bersaudara tanpa harus dirawat
   sebagai tiga sistem terpisah.

   Dipasang lewat atribut di <html> atau pembungkus mana pun:

     <html data-rapat="lantai">

   ── Yang SENGAJA tidak dilakukan ────────────────────────────────────────

   Tidak ada kartu bersudut membulat dengan aksen warna di tepi kiri di
   mana-mana. Itu bentuk yang muncul di hampir semua rancangan sekarang,
   dan di layar yang isinya tabel angka ia menambah 16 px sisi kiri-kanan
   tanpa menambah satu pun informasi.

   Sebagai gantinya: GARIS. Nota, struk, dan label — benda yang sistem ini
   gantikan — memisahkan bagian dengan garis mendatar, bukan dengan kotak.
   Garis tidak makan tempat, tidak butuh bayangan, dan tetap terbaca saat
   dicetak hitam-putih.
   ═══════════════════════════════════════════════════════════════════════ */

/* @import DICABUT.
   fonts.css dimuat lewat <link> di layout.html, bukan @import. Keduanya
   sempat aktif sekaligus, jadi ketiga @font-face terdaftar dua kali. @import
   juga serial: peramban harus mengunduh app.css DULU sebelum tahu ada
   fonts.css, sementara <link> berjalan bersamaan. */

/* ── PALET ──────────────────────────────────────────────────────────────
   Merah adalah #e30016, nilai dari logo. (Komentar lama di sini pernah
   menyatakan merah digeser ke #c4142b — itu keputusan yang dibatalkan.)

   Yang lama merah pemadam kebakaran — saturasi penuh, rona 355°. Di layar
   toko yang menyala terang sepanjang hari ia menyilaukan, dan pada bidang
   lebar (bar atas, tombol besar) ia berdengung. Yang baru lebih dalam dan
   sedikit lebih hangat: masih tak salah lagi merah Hatoba, tapi bisa
   dipakai pada bidang lebar tanpa membuat mata lelah.

   Netralnya SENGAJA condong hangat (rona ~30°, saturasi ~8%), bukan abu
   murni. Abu murni terbaca sebagai "belum dipilih"; netral hangat
   menyambung ke kertas nota dan kardus — bahan dunia yang dilayani sistem
   ini.

   Warna semantik (ok/warn/crit) TERPISAH dari merah merek. Kalau keduanya
   sama, "ada yang salah" dan "ini tombol utama" jadi satu isyarat.        */

:root {
  /* Ground & permukaan */
  --kertas:      #f6f3ef;
  --kartu:       #ffffff;
  --kartu-2:     #faf7f3;
  --terangkat:   #ffffff;

  /* Tinta */
  --tinta:       #1a1512;
  --tinta-2:     #5c5551;
  /* #8b827c hanya 3,40:1 di atas --kertas — cukup untuk hiasan, TIDAK cukup
     untuk teks. Dan ia dipakai untuk SKU, tanggal, dan keterangan ubin:
     semuanya teks yang benar-benar dibaca orang. Dinaikkan sampai 4,50 pada
     latar tergelapnya (--kertas), rona dan saturasinya dipertahankan. */
  --tinta-3:     #776e69;

  /* Garis — tiga ketebalan visual, bukan tiga warna acak:
     halus  memisahkan baris dalam satu daftar
     tegas  memisahkan bagian yang berbeda
     kuat   batas komponen yang bisa disentuh (WCAG 1.4.11: minimal 3:1) */
  --garis:       #e6ded6;
  --garis-2:     #d3c8bf;
  --garis-3:     #857a72;

  /* Merek — DIAMBIL DARI LOGO, bukan dikarang.
     
     Nilai ini disampel dari piksel internal/web/static/logo-wordmark.png:
     lambangnya merah #e30016, wordmark "hatoba" hijau #009035. Versi
     sebelumnya memakai #c4142b — saya sendiri yang menggesernya menjauh
     dari logo, dan itu keliru. Hijau bahkan belum pernah dipakai sebagai
     warna merek sama sekali, padahal separuh logo memang hijau.
     
     DUA token per warna, karena satu nilai tidak bisa dua pekerjaan:
       --merah      TEKS/ikon di atas latar halaman
       --merah-isi  BIDANG, dengan --pada-merah di atasnya
     
     Diukur: #e30016 sebagai teks lolos 4,91 dan 4,60 tapi cuma 4,44 di
     latar halaman; #e10016 lolos di ketiganya dan tidak terbedakan mata.
     Bidangnya tetap #e30016 persis — di situlah merek benar-benar dilihat. */
  --merah:       #e10016;
  --merah-isi:   #e30016;
  --merah-tekan: #b30011;
  --merah-cuci:  #fdecec;
  --pada-merah:  #ffffff;

  /* Hijau merek. Sebagai teks #009035 cuma 3,77–4,17, jadi teksnya sedikit
     digelapkan; bidangnya #008932 supaya teks putih di atasnya lolos 4,54.
     Keduanya tidak terbedakan dari nilai logo. */
  --hijau:       #00812f;
  --hijau-isi:   #008932;
  --pada-hijau:  #ffffff;

  /* Semantik — terpisah dari merek */
  /* "Beres/aman" memakai HIJAU MEREK, bukan hijau lain.
     Separuh logo Hatoba hijau, dan di perdagangan ini hijau memang berarti
     cocok, lunas, cukup. Memakai hijau yang berbeda untuk arti yang sama
     membuat warna merek tidak pernah muncul di layar mana pun — dan itulah
     yang terjadi di v3, v5, dan v6 sebelum ini.
     ok-bg dinaikkan tipis (#e8f4ed -> #eaf5ef) supaya hijau merek lolos
     4,50 di atasnya. */
  --ok:          #00812f;  --ok-bg:   #ecf6f0;  --ok-garis:   #a8cdba;
  --warn:        #8a5200;  --warn-bg: #fdf1e0;  --warn-garis: #e4c290;
  --crit:        #a81d1d;  --crit-bg: #fbeceb;  --crit-garis: #e5b4b1;
  --info:        #1b5a8a;  --info-bg: #e9f2f9;  --info-garis: #a9c8de;

  /* Huruf */
  --sans: "Hatoba Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "Hatoba Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Skala jarak — kelipatan 4, dipotong pada nilai yang benar-benar dipakai */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  --lengkung:    6px;
  --lengkung-sm: 4px;

  /* ── KERAPATAN: web (bawaan) ──
     Kasir muda, 1366x768, berjam-jam. Kepadatan di sini BUKAN kelalaian —
     makin banyak baris yang muat, makin sedikit menggulung, dan menggulung
     adalah pekerjaan yang tidak menghasilkan apa-apa. */
  --t-xs:   12.5px;
  --t-sm:   12.5px;
  --t-dasar:14px;
  --t-lg:   16px;
  --t-xl:   20px;
  --t-2xl:  26px;
  --t-3xl:  32px;

  --tinggi-baris: 1.45;
  --baris-tabel:  34px;   /* tinggi satu baris tabel  */
  --sasaran:      32px;   /* sisi terkecil area sentuh */
  --tebal-garis:  1px;
  --pad-sel:      6px 10px;
}

/* ── KERAPATAN: lantai (Gudang & Kasir) ────────────────────────────────
   Dipakai BERDIRI, layar 60-80 cm dari mata, tangan kotor atau berkeringat,
   kadang cahaya gudang yang minim, kadang sambil memegang pemindai.

   Semua naik: huruf 1,15x, sasaran sentuh dari 32 ke 48 px (WCAG 2.2 SC
   2.5.5 menyarankan 44; 48 memberi ruang untuk tangan yang tidak presisi),
   garis dari 1 ke 1,5 px supaya batas tetap terlihat di layar redup.

   Ini alasan jawaban "desktop sengaja beda": bukan gaya yang berbeda,
   melainkan skala yang berbeda dari sistem yang sama.                    */
[data-rapat="lantai"] {
  --t-xs:   13px;
  --t-sm:   15px;
  --t-dasar:17px;
  --t-lg:   20px;
  --t-xl:   25px;
  --t-2xl:  32px;
  --t-3xl:  42px;

  --tinggi-baris: 1.5;
  --baris-tabel:  52px;
  --sasaran:      48px;
  --tebal-garis:  1.5px;
  --pad-sel:      12px 14px;
  --lengkung:     8px;
}

/* ── KERAPATAN: sentuh (pelanggan di HP) ───────────────────────────────
   Android kelas bawah, layar 5-6 inci, satu ibu jari, kuota terbatas.
   Sasaran 44 px, huruf tidak lebih kecil dari 13 px, dan chrome ditekan
   sesedikit mungkin — tiap piksel yang dipakai bingkai adalah piksel yang
   tidak dipakai barang dagangan.                                          */
[data-rapat="sentuh"] {
  --t-xs:   12px;
  --t-sm:   13.5px;
  --t-dasar:15px;
  --t-lg:   17px;
  --t-xl:   21px;
  --t-2xl:  26px;
  --t-3xl:  30px;

  --tinggi-baris: 1.5;
  --baris-tabel:  48px;
  --sasaran:      44px;
  --tebal-garis:  1px;
  --pad-sel:      10px 14px;
  --lengkung:     8px;
}

/* ── TEMA GELAP ─────────────────────────────────────────────────────────
   Hanya DUA tema di v6 (turun dari empat di v5). Dua tema yang matang
   lebih baik daripada empat yang setengah — dan tema "kontras" yang dulu
   dibuat untuk gudang bercahaya minim sekarang tidak perlu lagi: kerapatan
   `lantai` sudah menaikkan ukuran dan ketebalan garisnya.

   Gelapnya BUKAN abu kebiruan. Ia hitam kecokelatan, sekeluarga dengan
   netral hangat di tema terang, supaya merahnya tidak berubah watak.

   Merah dinaikkan kecerahannya di sini: #c4142b di atas hitam hanya 3,4:1,
   tidak cukup untuk teks. Yang dipakai #e8536a untuk teks, dan bidang
   merah pekat tetap memakai #c4142b dengan teks putih di atasnya.        */
@media (prefers-color-scheme: dark) {
  :root:not([data-tema="terang"]) {
    --kertas:     #161211;
    --kartu:      #1e1917;
    --kartu-2:    #241e1b;
    --terangkat:  #292220;

    --tinta:      #f2ece7;
    --tinta-2:    #b3a89f;
    --tinta-3:    #948a81;

    --garis:      #322a27;
    --garis-2:    #453b36;
    --garis-3:    #7d716a;

    /* HARUS sama persis dengan blok [data-tema="gelap"] di bawah.
       Sebelumnya blok ini ketinggalan: orang yang sistemnya gelap tapi tidak
       pernah memilih tema mendapat merah yang BERBEDA dari orang yang
       menekan tombol "Gelap", dan hijau merek tidak ada sama sekali. */
    --merah:      #ff2f44;
    --merah-isi:  #e30016;
    --merah-tekan:#b30011;
    --hijau:      #009b39;
    --hijau-isi:  #008932;
    --merah-cuci: #2a1518;
    --pada-merah: #ffffff;

    --ok:   #009b39;  --ok-bg:   #0f2319;  --ok-garis:   #2d5c43;
    --warn: #e0a44e;  --warn-bg: #2b1f0d;  --warn-garis: #6b4f1e;
    --crit: #f0736f;  --crit-bg: #2c1615;  --crit-garis: #6d3330;
    --info: #6fb2df;  --info-bg: #0f2130;  --info-garis: #2c5674;
  }
}
:root[data-tema="gelap"] {
  --kertas:     #161211;
  --kartu:      #1e1917;
  --kartu-2:    #241e1b;
  --terangkat:  #292220;
  --tinta:      #f2ece7;
  --tinta-2:    #b3a89f;
  --tinta-3:    #948a81;
  --garis:      #322a27;
  --garis-2:    #453b36;
  --garis-3:    #7d716a;
  --merah:      #ff2f44;
  --merah-isi:  #e30016;
  --merah-tekan:#b30011;
  --hijau:      #009b39;
  --hijau-isi:  #008932;
  --merah-cuci: #2a1518;
  --pada-merah: #ffffff;
  --ok:   #009b39;  --ok-bg:   #0f2319;  --ok-garis:   #2d5c43;
  --warn: #e0a44e;  --warn-bg: #2b1f0d;  --warn-garis: #6b4f1e;
  --crit: #f0736f;  --crit-bg: #2c1615;  --crit-garis: #6d3330;
  --info: #6fb2df;  --info-bg: #0f2130;  --info-garis: #2c5674;
}

/* ═══════════════════════════════════════════════════════════════════════
   DASAR
   ═══════════════════════════════════════════════════════════════════════ */

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

/* <form> tidak pernah butuh marjin bawaan: yang mengatur jarak adalah
   panel atau baris yang memuatnya. Tanpa aturan ini setiap templat
   menuliskannya sendiri — style="margin:0" muncul lima belas kali pada
   <form> saja, dan yang lupa menuliskannya tampil turun sendiri di dalam
   sel tabel. */
form { margin: 0; }

/* SVG sebaris punya ukuran bawaan yang masuk akal.
   
   <svg> tanpa width/height mengambil ukuran dari induknya, jadi satu ikon
   yang lupa diberi ukuran akan membesar memenuhi seluruh kotaknya. Sudah
   dua kali terjadi di rancangan ini (kotak cari, remah) sebelum aturan ini
   ada. Komponen yang butuh ukuran lain tetap boleh menimpanya. */
svg { width: 1em; height: 1em; flex: none; }

body {
  margin: 0;
  background: var(--kertas);
  color: var(--tinta);
  font-family: var(--sans);
  font-size: var(--t-dasar);
  line-height: var(--tinggi-baris);
  -webkit-font-smoothing: antialiased;
  /* Spasi Plus Jakarta Sans cuma 0,17em — terukur 17px berbanding 27,8px
     milik sans sistem pada ukuran 100px, jadi kata-kata saling menempel
     ("Uang hari ini" terbaca "Uanghariini"). Ditambah sampai wajar. */
  word-spacing: 0.09em;
  /* Ligatur standar dimatikan: huruf ini menyatukan "fi" dengan menyerap
     titik pada i, jadi "Konfirmasi" terbaca "Konfırmasi" pada 13px. */
  font-variant-ligatures: none;
}

/* Angka SELALU tabular. Di layar yang isinya membandingkan harga, jumlah,
   dan selisih, digit yang lebarnya berbeda membuat kolom bergoyang dan
   perbandingan jadi pekerjaan mata, bukan pekerjaan tata letak. */
/* Angka BERBARIS memakai tabular; angka BESAR tidak.
   
   Sebelumnya seluruh body memakai tabular-nums. Di kolom tabel itu benar —
   digit harus selebar sama supaya kolomnya lurus. Tapi pada angka besar yang
   berdiri sendiri, tabular memberi tiap digit lebar "0", jadi 121 terlihat
   renggang dan angkanya kehilangan bentuk. Yang besar pakai angka
   proporsional bawaan huruf. */
table, .num, .mono, .tab .n, .pasangan dd { font-variant-numeric: tabular-nums; }

h1, h2, h3 { margin: 0; font-weight: 700; letter-spacing: -0.011em; }
h1 { font-size: var(--t-2xl); }
h2 { font-size: var(--t-lg); }
h3 { font-size: var(--t-dasar); }
p  { margin: 0; }

/* Hanya DUA bobot tersedia (400/700), jadi <strong> tidak boleh dipakai
   sembarangan: di antarmuka, penebalan adalah isyarat "ini yang penting di
   baris ini", dan kalau separuh baris ditebalkan isyaratnya hilang. */
strong, b { font-weight: 700; }

a { color: inherit; text-decoration-color: var(--garis-2); text-underline-offset: 2px; }
a:hover { text-decoration-color: currentColor; }

/* ── Permukaan yang tidak digambar siapa pun ──
   Sorotan teks, kursor, dan batang gulung datang dengan bawaan browser yang
   bukan milik sistem rancangan mana pun. Ini bagian yang paling murah untuk
   membedakan halaman yang DIBUAT dari halaman yang DIRAKIT. */
::selection { background: var(--merah-isi); color: var(--pada-merah); }
:root { caret-color: var(--merah-isi); accent-color: var(--merah-isi); }
* { scrollbar-width: thin; scrollbar-color: var(--garis-2) transparent; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--garis-2); border-radius: 99px;
  border: 3px solid var(--kertas); background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--tinta-3); }

/* Fokus papan ketik SELALU terlihat, dan tidak bergantung warna merek —
   di atas tombol merah, garis merah tidak kelihatan. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--tinta);
  outline-offset: 2px;
  border-radius: var(--lengkung-sm);
}

.mono { font-family: var(--mono); letter-spacing: -0.005em; }
/* `.num` berarti ANGKA BERTABULASI — titik. Rata kanan diurus `th.num,
   td.num` di bagian TABEL, dan hanya di sana.
   
   Selama rata kanan ikut menempel di sini, setiap elemen blok yang memakai
   `.num` untuk sekadar merapikan digitnya ikut terlempar ke tepi kanan
   induknya: nomor pesanan sebagai <h1> di halaman rincian, SKU di baris
   kedua nama produk pada daftar Produk dan pratinjau Harga massal, serta
   jumlah pesanan dan total belanja di rincian Pelanggan. Satu kelas, dua
   tugas — dan yang kedua cuma benar di dalam sel tabel. */
.num  { font-variant-numeric: tabular-nums; }
.redup { color: var(--tinta-2); }
.redup-2 { color: var(--tinta-3); }
.kecil { font-size: var(--t-sm); }

/* ═══════════════════════════════════════════════════════════════════════
   KERANGKA — sidebar + isi
   ═══════════════════════════════════════════════════════════════════════ */

/* Cangkang jadi wadah pengukur.
   
   Aturan sempitnya dulu memakai @media, yang mengukur lebar JENDELA. Di
   halaman peraga ini cangkangnya hidup di dalam bingkai selebar ~340px
   sementara jendelanya lebar, jadi aturan itu tidak pernah menyala dan
   perilakunya tidak pernah bisa ditunjukkan. Di aplikasi sungguhan
   cangkangnya selebar jendela, jadi @container memberi hasil yang sama —
   sekaligus benar di dalam bingkai. */
/* Wadah pengukurnya BODY, bukan .cangkang sendiri.

   Ini menyimpang dari mockup, dan penyimpangannya adalah perbaikan cacat.
   Di mockup, `container` dipasang pada .cangkang — padahal kueri wadah tidak
   pernah cocok dengan wadahnya sendiri, hanya dengan keturunannya. Akibatnya
   satu-satunya aturan yang menyasar .cangkang di blok sempit —
   `grid-template-columns: 1fr` — TIDAK PERNAH menyala. Aturan lain di blok
   yang sama (sidebar jadi laci, tombol laci muncul) menyala, karena semuanya
   menyasar keturunan.

   Yang terlihat: pada lebar 420px sidebar benar berubah jadi laci melayang,
   tapi kolom 216px yang ditinggalkannya tetap dipesan grid. Isi halaman
   terjepit ke 216px, tabel selebar 426px meluap, dan halaman menggulung
   mendatar 41px. Terukur, bukan dikira-kira.

   Dengan wadahnya di body, .cangkang jadi keturunan dan seluruh blok
   sempit berlaku utuh. */
body { container: cangkang / inline-size; }
/* 100dvh, bukan 100%.
   
   `min-height: 100%` hanya berarti sesuatu kalau SELURUH rantai induknya
   punya tinggi — html dan body sekaligus. Keduanya tidak, jadi 100% jatuh
   ke `auto` dan cangkangnya cuma setinggi isinya: pada halaman Lokasi yang
   isinya dua baris, seluruh kerangka berhenti di 834px dan menyisakan
   166px kosong di jendela 1000px, dengan sidebar yang ikut terpotong.
   Satuan jendela tidak menuntut rantai apa pun. */
.cangkang { display: grid; grid-template-columns: 216px 1fr; min-height: 100dvh;
            position: relative; }

/* Sidebar setinggi JENDELA dan lengket, bukan setinggi isi halaman.
   
   Sebelumnya ia `static`, jadi tingginya ikut isi halaman: pada halaman
   pendek ia berhenti di tengah layar, dan pada halaman panjang ia ikut
   tergulung pergi bersama isinya — menu utama jadi sesuatu yang harus
   digulung balik ke atas dulu untuk dipakai. */
.sisi {
  position: sticky; top: 0; height: 100dvh;
  background: var(--kartu);
  border-right: var(--tebal-garis) solid var(--garis);
  padding: var(--s3) 0;
  display: flex; flex-direction: column; gap: 2px;
}

/* Daftar menunya sendiri yang menggulung kalau tidak muat, bukan sidebarnya.
   min-height:0 wajib: anak flex menolak menyusut di bawah tinggi isinya
   kecuali diberi tahu, dan tanpa itu overflow-y tidak pernah aktif. */
.menu-sisi { flex: 1; min-height: 0; overflow-y: auto; display: flex;
             flex-direction: column; gap: 2px; }

.merek {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s4) var(--s4);
  font-weight: 700; font-size: var(--t-lg); letter-spacing: -0.02em;
}

.grup {
  padding: var(--s4) var(--s4) var(--s1);
  font-size: var(--t-xs); font-weight: 700; color: var(--tinta-3);
  letter-spacing: 0.08em; text-transform: uppercase;
}

.sisi a {
  display: flex; align-items: center; gap: var(--s3);
  min-height: var(--sasaran);
  padding: 0 var(--s4);
  color: var(--tinta-2); text-decoration: none; font-size: var(--t-sm);
  border-left: 3px solid transparent;
}
.sisi a:hover { background: var(--kartu-2); color: var(--tinta); }
/* Menu aktif ditandai GARIS TEPI, bukan latar penuh. Latar penuh pada menu
   membuat sidebar terlihat seperti punya dua warna dasar; garis tepi
   menjawab "saya di mana" tanpa menambah bidang warna. */
.sisi a.on {
  color: var(--tinta); font-weight: 700;
  border-left-color: var(--merah);
  background: var(--kartu-2);
}
.sisi a svg { width: 17px; height: 17px; flex: none; opacity: .8; }
.sisi a.on svg { opacity: 1; }

.hitung {
  margin-left: auto; font-size: var(--t-xs); font-weight: 700;
  background: var(--merah-isi); color: var(--pada-merah);
  padding: 1px 6px; border-radius: 99px; min-width: 18px; text-align: center;
}

.kaki-sisi {
  margin-top: auto; padding: var(--s3) var(--s4) 0;
  border-top: var(--tebal-garis) solid var(--garis);
  display: flex; align-items: center; gap: var(--s2);
  font-size: var(--t-sm);
}
.avatar {
  width: 28px; height: 28px; border-radius: 99px; flex: none;
  background: var(--kartu-2); border: var(--tebal-garis) solid var(--garis-2);
  display: grid; place-items: center; font-size: var(--t-xs); font-weight: 700;
}
.versi { padding: var(--s3) var(--s4) 0; font-size: var(--t-xs); color: var(--tinta-3);
         font-family: var(--mono); }

/* Bar atas: di mana saya + cari + lingkungan mana */
.atas {
  display: flex; align-items: center; gap: var(--s4);
  padding: 0 var(--s5); height: 52px;
  border-bottom: var(--tebal-garis) solid var(--garis);
  background: var(--kartu);
  position: sticky; top: 0; z-index: 20;
}
.remah { display: flex; align-items: center; gap: var(--s2); font-size: var(--t-sm);
         color: var(--tinta-2); white-space: nowrap; }
.remah b { color: var(--tinta); font-size: var(--t-dasar); }

.cari-atas {
  margin-left: auto; display: flex; align-items: center; gap: var(--s2);
  background: var(--kartu-2); border: var(--tebal-garis) solid var(--garis);
  border-radius: 99px; padding: 0 var(--s3); height: 34px;
  width: 300px; min-width: 0; flex: 0 1 300px;
}
.cari-atas svg { width: 16px; height: 16px; flex: none; opacity: .6; }
/* Spesifisitas dinaikkan dengan sengaja.
   
   Begitu input ini diberi type="search" (yang memang harus, supaya aturan
   tinggi minimum kena), aturan `input[type=search]` generik jadi ikut
   berlaku dan menempelkan bingkai serta latar di DALAM pil carinya. Kedua
   selektor punya bobot sama, jadi urutan berkas yang menentukan — dan itu
   rapuh. `.atas .cari-atas input` menyelesaikannya secara eksplisit. */
.atas .cari-atas input {
  border: 0; background: none; box-shadow: none; border-radius: 0;
  font: inherit; color: inherit; width: 100%; min-width: 0;
  min-height: var(--sasaran); padding: 0;
}
/* Fokusnya dipasang pada PEMBUNGKUS, bukan pada input: cincin di sekeliling
   pil itulah yang orang lihat sebagai "kotak cari".
   
   Sebelumnya input ini menyetel `outline: none` (spesifisitas 0,1,1) dan
   aturan fokus global ditulis dengan :where() yang menyumbang spesifisitas
   NOL — jadi kotak cari utama tidak punya penanda fokus papan ketik sama
   sekali, di kedua puluh empat layar. */
.cari-atas:focus-within {
  outline: 2px solid var(--tinta); outline-offset: 2px;
}
.cari-atas input:focus-visible { outline: none; }

.lingkungan {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; padding: 3px 8px; border-radius: 99px;
  border: var(--tebal-garis) solid var(--warn-garis);
  color: var(--warn); background: var(--warn-bg);
}
.lingkungan.prod { border-color: var(--garis-2); color: var(--tinta-3); background: none; }

main { padding: var(--s5); }
.lebar { max-width: 1180px; }

/* ═══════════════════════════════════════════════════════════════════════
   KEPALA HALAMAN
   ═══════════════════════════════════════════════════════════════════════ */

.kepala { display: flex; align-items: flex-start; gap: var(--s4); margin-bottom: var(--s5); }
.kepala .sub { color: var(--tinta-2); font-size: var(--t-sm); margin-top: 2px; }
.kepala .aksi { margin-left: auto; display: flex; gap: var(--s2); flex-wrap: wrap; }

/* ═══════════════════════════════════════════════════════════════════════
   BAGIAN — garis, bukan kotak
   ═══════════════════════════════════════════════════════════════════════ */

.bagian { margin-bottom: var(--s6); }

/* Isi satu bagian: padding dan tumpukan menegak, sekali untuk semua.
   
   Sebelum ini setiap templat menuliskannya sendiri sebagai
   style="padding:var(--sp-5);display:grid;gap:var(--sp-3)" — sembilan
   templat, sembilan ejaan, dan sebagian lupa salah satu bagiannya sehingga
   isinya menempel ke garis kepala bagian. Itulah "banyak yang berantakan"
   yang dilaporkan pemilik 9 Okt 2026 (#237).
   
   Lebarnya TIDAK dibatasi di sini: ada bagian yang isinya tabel selebar
   layar dan ada yang isinya form sempit. Yang sempit memakai .sempit. */
.isi-bagian { padding: var(--s5); display: grid; gap: var(--s3); }
.isi-bagian.sempit > * { max-width: 640px; }

/* Jaraknya dari gap, bukan dari marjin anaknya. Inilah sebabnya templat
   lama menaburkan style="margin:0" — tiga puluh kali — pada hampir setiap
   <p> dan <form> di dalam panel: marjin bawaan menumpuk DI ATAS gap, dan
   yang lupa menuliskannya tampil lebih longgar daripada tetangganya. */
.isi-bagian > * { margin-block: 0; }

/* Panel yang isinya beberapa form berdiri sendiri — nonaktifkan akun,
   cabut sesi, setel sandi — perlu jarak sebesar jarak antar bagian, bukan
   sebesar jarak antar baris dalam satu form. */
.isi-bagian.lega { gap: var(--s5); }

/* Pemisah di DALAM satu panel: ruas yang bukan lanjutan ruas di atasnya.
   Garisnya sama dengan garis tabel, bukan garis kepala bagian yang tebal —
   kalau sama tebal, ruasnya terbaca sebagai bagian baru. */
.pisah-atas { border-top: 1px solid var(--garis); padding-top: var(--s4); }

/* Catatan di dalam bagian, di luar .isi-bagian: satu paragraf di atas atau
   di bawah tabel. Dulu ditulis dengan empat ejaan padding yang berbeda —
   semua sisi, atas saja, bawah saja, samping saja — jadi jarak catatan ke
   tabelnya berbeda di tiap layar. Sekarang satu jarak untuk semuanya. */
.catatan-bagian { padding: var(--s5); margin-block: 0; }

/* Teks yang barisnya sudah diatur sumbernya (pesan galat sync, keluaran
   perintah): jangan disusun ulang. */
.teks-apa-adanya { white-space: pre-wrap; }

.huruf-besar { text-transform: uppercase; }
.huruf-kecil { text-transform: lowercase; }
.renik { font-size: var(--fs-2xs); }

/* Satu paragraf yang berdiri SENDIRI di dalam bagian, di luar .isi-bagian:
   catatan di bawah tabel, keterangan di bawah form. Ia perlu sejajar
   dengan isi panel di atasnya, jadi padding sampingnya sama. */
.pinggir-bagian { padding: 0 var(--s5) var(--s5); margin-block: 0; }
.bagian > .kepala-bagian {
  display: flex; align-items: baseline; gap: var(--s3);
  padding-bottom: var(--s2);
  border-bottom: 2px solid var(--tinta);
  margin-bottom: var(--s3);
}
/* Pengganjal, perentang, dan baris tombol adalah alat tata letak: mereka
   melakukan satu hal yang sama di mana pun dipakai, jadi tidak boleh
   dikurung ke satu induk tertentu.
   
   Sebelumnya .spasi hanya hidup di dalam .kepala-bagian dan .bar-massal,
   .tumbuh hanya di dalam .bilah, dan .aksi-baris hanya di dalam .empty —
   nama yang bahkan tidak ada lagi di templat mana pun. Di luar itu mereka
   tidak melakukan apa-apa dan tidak mengeluh: tombol "Simpan" pada tiap
   jenjang ongkos kirim mestinya terdorong ke kanan oleh .spasi, dan selama
   ini cuma menempel di sebelah kotak centang. */
.spasi { margin-left: auto; }
.tumbuh { flex: 1 1 220px; min-width: 0; }
.aksi-baris { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }

/* Kartu dipakai HEMAT — hanya untuk hal yang benar-benar terangkat dari
   halaman (panel geser, dialog, ubin angka). Bukan untuk membungkus tabel. */
.kartu {
  background: var(--kartu);
  border: var(--tebal-garis) solid var(--garis);
  border-radius: var(--lengkung);
}

/* ═══════════════════════════════════════════════════════════════════════
   UBIN ANGKA
   ═══════════════════════════════════════════════════════════════════════ */

/* SATU angka utama per layar, bukan lima.
   
   Lima ubin selebar sama dengan angka sebesar sama adalah templat yang
   dipakai setiap dasbor, dan ia tidak memberi tahu apa pun tentang mana
   yang paling penting. Yang pertama jadi angka utama; sisanya ubin ringkas
   yang membawa tren atau proporsinya sendiri. */
.ubin-baris {
  display: grid; gap: var(--s3);
  grid-template-columns: minmax(280px, 1.6fr) repeat(auto-fit, minmax(158px, 1fr));
}
.ubin-baris.rata { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); }
.ubin {
  background: var(--kartu);
  border: var(--tebal-garis) solid var(--garis);
  border-radius: var(--lengkung);
  padding: var(--s3) var(--s4);
  /* Tren dan meter didorong ke dasar ubin, jadi seluruh baris punya satu
     garis dasar grafik walau tinggi teksnya berbeda-beda. */
  display: flex; flex-direction: column;
}
.ubin .spark { margin-top: auto; }
.ubin .meter { margin-top: auto; }
.ubin .meter + .meter-ket { margin-top: 5px; }
/* Tanpa `.ubin` di depannya: label kecil ini juga dipakai di luar ubin —
   "Alamat" dan "Catatan untuk kurir" di rincian Pesanan, dan pasangan
   serupa di rincian Transaksi dan Pelanggan. Di sana ia selama ini tampil
   sebagai teks badan biasa, jadi label dan isinya tidak terbedakan. */
.k { font-size: var(--t-sm); color: var(--tinta-2); font-weight: 400; }
.ubin .v { font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.015em;
           margin-top: 2px; line-height: 1.15; font-variant-numeric: proportional-nums; }
/* Angka utama: satu-satunya yang boleh sebesar ini. */
.ubin.utama { grid-row: span 1; }
.ubin.utama .v { font-size: 48px; letter-spacing: -0.03em; line-height: 1.02; margin-top: 4px; }
.ubin.utama .k { font-size: var(--t-dasar); }
.ubin .v .unit { font-size: var(--t-sm); font-weight: 400; color: var(--tinta-2); }
.ubin .d { font-size: var(--t-xs); color: var(--tinta-3); margin-top: 2px; }
/* Kegentingan ditandai di ANGKANYA, bukan dengan mewarnai seluruh ubin.
   Ubin berwarna penuh membuat halaman ringkasan jadi lampu lalu lintas dan
   mata berhenti bisa membedakan mana yang benar-benar mendesak. */
.ubin.warn .v { color: var(--warn); }
.ubin.crit .v { color: var(--crit); }
.ubin.ok   .v { color: var(--ok); }

.delta { font-size: var(--t-xs); font-weight: 700; }
.delta.naik { color: var(--ok); }
.delta.turun { color: var(--crit); }

/* ═══════════════════════════════════════════════════════════════════════
   TABEL — inti aplikasi ini
   ═══════════════════════════════════════════════════════════════════════ */

.gulung { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }

thead th {
  text-align: left; font-size: var(--t-xs); font-weight: 700;
  color: var(--tinta-2); text-transform: uppercase; letter-spacing: .06em;
  padding: var(--s2) 10px; white-space: nowrap;
  border-bottom: var(--tebal-garis) solid var(--garis-2);
  background: var(--kertas);
  /* Lengket di bawah bar atas yang juga lengket (52 px). Berlaku hanya saat
     HALAMAN yang menggulung — di halaman peraga ini beberapa cangkang
     ditumpuk dalam satu dokumen, jadi acuan `top` bukan lagi bar atasnya
     sendiri dan kepala tabel akan mengambang di atas baris. Karena itu
     peraga mematikannya (lihat .bingkai di bawah). */
  position: sticky; top: 52px; z-index: 10;
}
tbody td {
  padding: var(--pad-sel);
  border-bottom: var(--tebal-garis) solid var(--garis);
  height: var(--baris-tabel);
  vertical-align: middle;
}
tbody tr:hover { background: var(--kartu-2); }
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
tfoot td { padding: var(--pad-sel); font-weight: 700; border-top: 2px solid var(--tinta); }

/* Baris utama + baris keterangan dalam satu sel. Menaruh SKU di kolom
   sendiri memakan lebar yang dibutuhkan nama barang; menaruhnya di bawah
   nama membuat keduanya terbaca sebagai satu benda. */
.sel-utama { display: flex; flex-direction: column; gap: 1px; }
.sel-utama .k2 { font-size: var(--t-xs); color: var(--tinta-3); font-family: var(--mono); }

/* ═══════════════════════════════════════════════════════════════════════
   CHIP — status, dan label yang dicetak
   ═══════════════════════════════════════════════════════════════════════ */

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--t-xs); font-weight: 700; line-height: 1;
  padding: 4px 8px; border-radius: var(--lengkung-sm);
  border: var(--tebal-garis) solid var(--garis-2);
  background: var(--kartu-2); color: var(--tinta-2);
  white-space: nowrap;
}
.chip::before { content: ""; width: 6px; height: 6px; border-radius: 99px; background: currentColor; flex: none; }
.chip.polos::before { display: none; }
.chip.ok   { background: var(--ok-bg);   color: var(--ok);   border-color: var(--ok-garis); }
.chip.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-garis); }
.chip.crit { background: var(--crit-bg); color: var(--crit); border-color: var(--crit-garis); }
.chip.info { background: var(--info-bg); color: var(--info); border-color: var(--info-garis); }

/* Label kode: bentuknya meniru label barcode yang dicetak alat ini.
   Bukan kelucuan — kode yang tampil begini langsung dikenali sebagai
   "yang bisa dipindai", bukan sekadar teks. */
.label-kode {
  font-family: var(--mono); font-size: var(--t-xs);
  padding: 2px 6px; border-radius: 2px;
  background: var(--kartu-2); border: var(--tebal-garis) dashed var(--garis-2);
  color: var(--tinta-2); white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════
   TOMBOL
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   HUKUM TOMBOL — merah berarti TIDAK BISA DIBATALKAN
   ═══════════════════════════════════════════════════════════════════════

   Sebelumnya semua tombol utama merah pekat, jadi "Tambah baris" (bisa
   dihapus lagi) dan "Terima & catat stok" (memposting stok, menulis ulang
   modal produk, mengunci dokumennya selamanya) berbentuk sama persis dan
   duduk di layar yang sama. Di aplikasi Gudang, yang menekannya sedang
   berdiri, tangan basah, cahaya kurang.

   Tiga pangkat, dan aturannya tertulis:

     .tbl.tetap  MERAH PEKAT + gembok. Hanya untuk yang tidak bisa
                 dibatalkan: posting stok, menyimpan harga massal, menerima
                 pembayaran, mengonfirmasi pesanan, membatalkan. Wajib
                 melewati satu langkah konfirmasi yang MENGULANG ANGKANYA.
     .tbl        Tindakan utama layar yang masih bisa diurungkan
                 (menyimpan produk, menerapkan saringan, membuat draf).
     .tbl.dua    Tindakan pendamping.
     .tbl.sunyi  Tindakan baris di dalam tabel.

   Merah yang dipakai untuk segalanya tidak memperingatkan apa pun.        */

.tbl {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: var(--sasaran); padding: 0 var(--s4);
  border-radius: var(--lengkung); border: var(--tebal-garis) solid transparent;
  font: inherit; font-size: var(--t-sm); font-weight: 700;
  background: var(--kartu); color: var(--tinta);
  border-color: var(--garis-3);
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.tbl:hover { background: var(--kartu-2); border-color: var(--tinta); }

/* Pangkat tertinggi: permanen. */
.tbl.tetap { background: var(--merah-isi); color: var(--pada-merah); border-color: var(--merah-isi); }
.tbl.tetap:hover { background: var(--merah-tekan); border-color: var(--merah-tekan); }
.tbl.tetap svg { width: 15px; height: 15px; }
/* Tombol kedua memakai GARIS TEPI yang benar-benar terlihat (kontras >= 3:1
   terhadap latar, WCAG 1.4.11). Tepi setipis --garis membuat tombol tampak
   seperti teks biasa, dan orang tidak menekannya. */
.tbl.dua {
  background: var(--kartu); color: var(--tinta);
  border-color: var(--garis-3);
}
.tbl.dua:hover { background: var(--kartu-2); }
.tbl.sunyi { background: none; border-color: transparent; color: var(--tinta-2); }
.tbl.sunyi:hover { background: var(--kartu-2); color: var(--tinta); }
.tbl.bahaya { background: var(--crit-bg); color: var(--crit); border-color: var(--crit-garis); }
.tbl.sm { min-height: 26px; padding: 0 10px; font-size: var(--t-xs); }
[data-rapat="lantai"] .tbl.sm { min-height: 38px; padding: 0 var(--s3); }
.tbl.besar { min-height: calc(var(--sasaran) * 1.35); font-size: var(--t-lg); padding: 0 var(--s5); }
.tbl:disabled { opacity: .45; cursor: not-allowed; }

/* ═══════════════════════════════════════════════════════════════════════
   ISIAN
   ═══════════════════════════════════════════════════════════════════════ */

label.bidang { display: flex; flex-direction: column; gap: 4px; font-size: var(--t-sm);
               font-weight: 700; color: var(--tinta-2); }
/* Daftar tipe ini SENGAJA panjang, dan `:not([type])` yang paling penting.
   
   Mockup menulis setiap isiannya lengkap dengan type="text", jadi daftar
   pendek di v6 cukup untuknya. Templat aplikasi tidak: 41 dari isiannya
   ditulis tanpa atribut type sama sekali (yang sah — bawaannya text), satu
   memakai type="email".
   
   Akibatnya tidak setengah-setengah, dan itu yang bikin sulit terlihat dari
   membaca kode: isian yang cocok tampil rapi, isian yang tidak cocok tampil
   POLOS bawaan peramban — tinggi berbeda, tepi berbeda, huruf berbeda, di
   dalam satu form yang sama. Paling parah di form daftar pelanggan, yang
   kebetulan hampir seluruhnya ditulis tanpa type. */
input:not([type]), input[type=text], input[type=search], input[type=number],
input[type=date], input[type=password], input[type=email], input[type=tel],
input[type=url], input[type=time], input[type=datetime-local],
select, textarea {
  min-height: var(--sasaran);
  padding: 0 10px;
  border: var(--tebal-garis) solid var(--garis-3);
  border-radius: var(--lengkung);
  background: var(--kartu); color: var(--tinta);
  font: inherit; font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
}
textarea { padding: var(--s2) 10px; min-height: 72px; resize: vertical; }
input::placeholder { color: var(--tinta-3); }
.bilah { display: flex; gap: var(--s3); align-items: flex-end; flex-wrap: wrap; }
/* Kotak centang cuma 18px sementara isian di sebelahnya 32px. Dirata-
   bawahkan, kotaknya duduk 14px lebih rendah dari tengah barisnya — di
   Ongkos kirim itu kotak "aktif" yang melorot di antara "Tarif (Rp)" dan
   "Simpan". Yang disamakan tinggi BARISNYA, bukan kotaknya: kotak 24px
   akan tampak kasar, dan 18px sudah memenuhi ambang sentuh lewat margin. */
.bilah > label:has(> input[type=checkbox]),
.bilah > label:has(> input[type=radio]) { min-height: var(--sasaran); }

/* Satu baris jenjang ongkos kirim: dua kotak kecil, satu centang, satu
   tombol. Tanpa batas lebar, .spasi mendorong "Simpan" sampai tepi kanan
   layar — 700px dari kotak yang disimpannya, dengan padang kosong di
   antaranya. Baris kendali boleh selebar layar kalau isinya memang selebar
   itu; yang ini tidak. */
.bilah.jenjang { max-width: 760px; }

/* Satu baris kotak centang: kotaknya dan tulisannya, sebaris, bisa diklik
   di mana saja.

   Kelas ini DIPAKAI templat Pengaturan sejak lama tapi tidak pernah punya
   satu pun aturan CSS — dan kelas mati tidak menghasilkan galat apa pun.
   Halamannya tetap tampil; yang hilang cuma tata letaknya, jadi kotak
   centang 18px berdiri di atas tulisannya sebagai dua baris terpisah.
   Ditemukan uji_tataletak.py, yang memang dibangun untuk menangkap persis
   hal ini. */
label.pilihan-kotak {
  display: flex; align-items: center; gap: var(--s2);
  min-height: var(--sasaran);
  font-size: var(--t-sm); font-weight: 600; color: var(--tinta);
  cursor: pointer;
}
label.pilihan-kotak > input[type=checkbox],
label.pilihan-kotak > input[type=radio] { flex: none; margin: 0; cursor: pointer; }

.petunjuk { font-size: var(--t-xs); color: var(--tinta-2); font-weight: 400;
            text-transform: none; letter-spacing: 0; }
.petunjuk strong { color: var(--tinta); }

/* Isian angka uang: mono + rata kanan, karena yang dilakukan orang dengan
   angka ini adalah membandingkannya dengan angka lain. */
input.uang { font-family: var(--mono); text-align: right; }

/* ═══════════════════════════════════════════════════════════════════════
   PESAN
   ═══════════════════════════════════════════════════════════════════════ */

.pesan {
  display: flex; gap: var(--s3); align-items: flex-start;
  padding: var(--s3) var(--s4); border-radius: var(--lengkung);
  border: var(--tebal-garis) solid var(--garis-2); background: var(--kartu-2);
  font-size: var(--t-sm); margin-bottom: var(--s4);
}
.pesan.ok   { background: var(--ok-bg);   color: var(--ok);   border-color: var(--ok-garis); }
.pesan.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-garis); }
.pesan.crit { background: var(--crit-bg); color: var(--crit); border-color: var(--crit-garis); }
.pesan b { display: block; }

/* Keadaan kosong: menjelaskan APA yang akan muncul di sini dan APA yang
   harus dilakukan — bukan sekadar "tidak ada data". */
.kosong {
  text-align: center; padding: var(--s7) var(--s5);
  border: var(--tebal-garis) dashed var(--garis-2); border-radius: var(--lengkung);
  color: var(--tinta-2);
}
.kosong h3 { color: var(--tinta); margin-bottom: var(--s1); }
.kosong p { font-size: var(--t-sm); max-width: 46ch; margin: 0 auto var(--s4); }

/* ═══════════════════════════════════════════════════════════════════════
   TAB
   ═══════════════════════════════════════════════════════════════════════ */

.tab { display: flex; gap: var(--s1); border-bottom: var(--tebal-garis) solid var(--garis);
       margin-bottom: var(--s4); overflow-x: auto; }
.tab a {
  padding: var(--s2) var(--s3); min-height: var(--sasaran);
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--t-sm); color: var(--tinta-2); text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: calc(var(--tebal-garis) * -1);
  white-space: nowrap;
}
.tab a:hover { color: var(--tinta); }
.tab a.on { color: var(--tinta); font-weight: 700; border-bottom-color: var(--merah); }
.tab .n { font-size: var(--t-xs); color: var(--tinta-3); font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════════════════════════════
   PANEL GESER (drawer)
   ═══════════════════════════════════════════════════════════════════════ */

.geser {
  background: var(--kartu); border: var(--tebal-garis) solid var(--garis-2);
  border-radius: var(--lengkung); overflow: clip;
  box-shadow: 0 12px 32px rgba(0,0,0,.10);
}
.geser-kepala { display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); border-bottom: var(--tebal-garis) solid var(--garis); }
.geser-isi { padding: var(--s4); }
.geser-kaki { padding: var(--s3) var(--s4); border-top: var(--tebal-garis) solid var(--garis);
  display: flex; gap: var(--s2); background: var(--kartu-2); }

/* Daftar pasangan istilah-nilai. Dipakai di semua panel detail. */
/* max-width, bukan selebar induknya.
   
   Daftar label–nilai hanya bisa dibaca kalau kedua sisinya masih tertangkap
   sekali pandang. Direntang selebar layar 1440px, label duduk di tepi kiri
   dan nilainya seribu piksel jauhnya — dan tiap baris menuntut mata
   menyeberang padang kosong untuk memasangkannya. Tabel berkolom banyak
   memang butuh seluruh lebar; daftar dua kolom tidak pernah. */
.pasangan { display: grid; grid-template-columns: minmax(0, 40%) 1fr; gap: 0;
            font-size: var(--t-sm); max-width: 560px; }
.pasangan dt { color: var(--tinta-2); padding: 7px 0; border-bottom: var(--tebal-garis) solid var(--garis); }
.pasangan dd { margin: 0; padding: 7px 0; border-bottom: var(--tebal-garis) solid var(--garis);
               text-align: right; font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════════════════════════════
   LANTAI — komponen khusus Gudang & Kasir
   ═══════════════════════════════════════════════════════════════════════ */

/* Papan tombol besar: menu utama aplikasi lapangan. Satu tugas per petak,
   cukup besar untuk ditekan tanpa melihat. */
.petak { display: grid; grid-template-columns: repeat(auto-fit, minmax(196px, 1fr)); gap: var(--s3); }
.petak a {
  display: flex; flex-direction: column; gap: var(--s2);
  padding: var(--s4); min-height: 108px;
  background: var(--kartu); border: var(--tebal-garis) solid var(--garis-2);
  border-radius: var(--lengkung); text-decoration: none; color: inherit;
}
.petak a:hover { border-color: var(--garis-3); background: var(--kartu-2); }
.petak .ikon { width: 30px; height: 30px; color: var(--merah); }
.petak .judul { font-weight: 700; font-size: var(--t-lg); }
.petak .ket { font-size: var(--t-sm); color: var(--tinta-2); }

/* Baris besar untuk daftar yang dibaca dari jauh */
.baris-lantai {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3) var(--s4);
  border-bottom: var(--tebal-garis) solid var(--garis);
}
.baris-lantai .isi { flex: 1; min-width: 0; }
.baris-lantai .judul { font-weight: 700; font-size: var(--t-lg); }
.baris-lantai .ket { font-size: var(--t-sm); color: var(--tinta-2); }

/* Total besar di kasir — angka yang dibaca pelanggan dari seberang meja */
.total-besar {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); padding: var(--s4) 0; border-top: 2px solid var(--tinta);
}
.total-besar .k { font-size: var(--t-lg); font-weight: 700; }
.total-besar .v { font-size: var(--t-3xl); font-weight: 700; letter-spacing: -.02em;
                  font-variant-numeric: tabular-nums; }

/* Pemilih metode bayar — jawaban langsung untuk F-621 */
.bayar { display: flex; gap: var(--s2); }
.bayar button {
  flex: 1; min-height: var(--sasaran); border-radius: var(--lengkung);
  border: var(--tebal-garis) solid var(--garis-3);
  background: var(--kartu); color: var(--tinta);
  font: inherit; font-weight: 700; font-size: var(--t-sm); cursor: pointer;
}
/* Yang terpilih DIBALIK warnanya, bukan sekadar ditebalkan: kasir melihatnya
   dari jarak satu meter sambil memegang uang. */
.bayar button.on { background: var(--tinta); color: var(--kertas); border-color: var(--tinta); }

/* Struk pratinjau — 31 karakter, persis lebar kertas termal 58 mm */
.struk {
  font-family: var(--mono); font-size: 12px; line-height: 1.35;
  white-space: pre; background: var(--kartu); color: var(--tinta);
  border: var(--tebal-garis) solid var(--garis-2); border-radius: var(--lengkung-sm);
  padding: var(--s3); overflow-x: auto;
}

/* ═══════════════════════════════════════════════════════════════════════
   SENTUH — komponen khusus aplikasi pelanggan
   ═══════════════════════════════════════════════════════════════════════ */

.hp {
  width: 360px; height: 720px; flex: none;
  background: var(--kertas); color: var(--tinta);
  border: 8px solid #221b19; border-radius: 28px; overflow: hidden;
  display: flex; flex-direction: column; position: relative;
}
.hp-atas {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); background: var(--kartu);
  border-bottom: var(--tebal-garis) solid var(--garis); flex: none;
}
.hp-isi { flex: 1; overflow-y: auto; padding: var(--s4); }
.hp-isi.rapat { padding: 0; }
.hp-bawah {
  display: flex; flex: none; border-top: var(--tebal-garis) solid var(--garis);
  background: var(--kartu);
}
.hp-bawah a {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--s2) 0 var(--s3); min-height: var(--sasaran);
  font-size: var(--t-xs); color: var(--tinta-3); text-decoration: none;
}
.hp-bawah a.on { color: var(--merah); font-weight: 700; }
.hp-bawah svg { width: 20px; height: 20px; }

/* Kartu produk. Gambar rasio 1:1 supaya baris tidak bergoyang saat gambar
   belum termuat — di HP murah dengan koneksi lambat, itu sering terjadi. */
.produk-petak { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); }
.produk {
  background: var(--kartu); border: var(--tebal-garis) solid var(--garis);
  border-radius: var(--lengkung); overflow: clip; display: flex; flex-direction: column;
}
.produk .gbr {
  aspect-ratio: 1; background: var(--kartu-2);
  display: grid; place-items: center; color: var(--tinta-3);
  border-bottom: var(--tebal-garis) solid var(--garis);
}
/* Kartu yang datanya BASI (mode luring) ditandai TEPINYA, bukan diredupkan.
   
   Versi sebelumnya memakai opacity .72 pada seluruh kartu, dan itu menyeret
   teksnya ke 2,78:1 — di layar yang justru paling butuh terbaca, karena
   pemiliknya sedang tanpa koneksi. Tepi putus-putus mengatakan hal yang sama
   tanpa menyentuh keterbacaan, dan spanduknya di atas sudah menjelaskan
   sisanya. */
.produk.basi { border-style: dashed; border-color: var(--warn-garis); }
.produk.basi .gbr { color: var(--tinta-3); }
.produk .isi { padding: var(--s2) var(--s3) var(--s3); display: flex; flex-direction: column; gap: 2px; }
.produk .nama { font-size: var(--t-sm); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.produk .harga { font-weight: 700; font-size: var(--t-lg); }

.bar-bawah {
  position: sticky; bottom: 0; background: var(--kartu);
  border-top: var(--tebal-garis) solid var(--garis);
  padding: var(--s3) var(--s4); display: flex; gap: var(--s3); align-items: center;
}

/* ═══════════════════════════════════════════════════════════════════════
   PEMILIH TEMA
   ═══════════════════════════════════════════════════════════════════════

   Di mockup ini mengambang di atas halaman peraga. Di aplikasi ia tinggal di
   kaki sidebar dan di halaman Pengaturan, jadi posisi lengketnya dilepas. */

/* Peraga saja. Ditaruh di ATAS halaman, bukan mengambang di kanan bawah:
   versi mengambangnya menutupi tombol utama di setidaknya empat layar —
   panel kunci nota, baris catatan harga massal, dan baris terakhir tabel
   kesehatan. Kontrol peraga yang menghalangi kontrol yang sedang dinilai
   adalah cacat, bukan bingkai. */
.pilih-tema {
  /* inline-flex, bukan flex: di mockup ia mengambang jadi lebarnya tidak
     pernah jadi soal. Di halaman Pengaturan ia anak blok biasa, dan `flex`
     membuatnya merentang selebar panel. */
  display: inline-flex; gap: var(--s1); padding: var(--s1);
  background: var(--kartu); border: var(--tebal-garis) solid var(--garis-2);
  border-radius: 99px; box-shadow: 0 6px 20px rgba(0,0,0,.12);
}
.pilih-tema button {
  min-height: 32px; padding: 0 var(--s3); border-radius: 99px; border: 0;
  background: none; color: var(--tinta-2); font: inherit; font-size: var(--t-xs);
  font-weight: 700; cursor: pointer;
}
.pilih-tema button.on { background: var(--tinta); color: var(--kertas); }

/* Tombol laci hanya ada di lebar sempit. */
.buka-laci {
  display: none; align-items: center; justify-content: center;
  width: var(--sasaran); height: var(--sasaran); flex: none;
  border: var(--tebal-garis) solid var(--garis-3); border-radius: var(--lengkung);
  background: var(--kartu); color: var(--tinta); cursor: pointer;
}
.buka-laci svg { width: 18px; height: 18px; }
.laci { position: absolute; opacity: 0; width: 1px; height: 1px; }
.cangkang:has(.laci:focus-visible) .buka-laci { outline: 2px solid var(--tinta); outline-offset: 2px; }

@container cangkang (max-width: 900px) {
  /* Sidebar TIDAK disembunyikan begitu saja.
     
     Versi sebelumnya memakai `display: none` tanpa pengganti apa pun — tidak
     ada tombol menu, tidak ada bar bawah, tidak ada tautan. Di bawah 900px
     seluruh navigasi lenyap dan halaman jadi jalan buntu. Padahal pemilik
     toko justru orang yang paling mungkin membukanya dari HP: ia memeriksa
     keadaan usahanya, bukan bekerja di dalamnya.
     
     Sekarang ia jadi laci: tersembunyi secara bawaan, dibuka lewat tombol
     di bar atas, dan tetap bisa dijangkau papan ketik. */
  .cangkang { grid-template-columns: 1fr; }
  .sisi {
    /* fixed terhadap JENDELA, bukan absolute terhadap cangkang.
       
       Alasan aslinya memilih absolute adalah halaman peraga v6, tempat
       cangkangnya hidup di dalam bingkai — di sana `fixed` membuat laci
       melompat keluar bingkainya. Berkas ini bukan halaman peraga: di
       aplikasi, cangkang ADALAH halamannya.
       
       Dan absolute punya harga yang tidak kelihatan di layar pendek:
       tingginya jadi setinggi seluruh cangkang. Di daftar Produk yang
       3.662px, laci yang dibuka menaruh nama pengguna dan tombol Keluar
       3.621px di bawah — tidak terjangkau tanpa menggulung seluruh
       katalog dulu. Terukur di jendela 820x700.
       
       Dengan fixed, lacinya setinggi jendela: menunya menggulung sendiri
       di dalam, dan blok penggunanya tetap di kiri bawah. */
    position: fixed; height: 100dvh; inset: 0 auto 0 0; width: 262px; z-index: 60;
    transform: translateX(-100%);
    box-shadow: 0 0 0 100vw rgba(0,0,0,0);
  }
  .cangkang:has(.laci:checked) .sisi { transform: none; box-shadow: 0 0 0 100vw rgba(0,0,0,.45); }
  .buka-laci { display: inline-flex; }

  /* Ubin: satu kolom. Ubin utama punya lebar minimum 280px, dan di lebar
     390px itu mendorong seluruh baris jadi 659px — dua ubin terakhir bukan
     tergulung keluar layar, tapi TERPOTONG hilang oleh `overflow: hidden`
     pada bingkainya. */
  .ubin-baris, .ubin-baris.rata { grid-template-columns: 1fr; }
}



/* ═══════════════════════════════════════════════════════════════════════
   LINIMASA PESANAN
   ═══════════════════════════════════════════════════════════════════════

   Diambil dari v5 setelah pemilik sistem menilai versi v5 lebih baik — dan
   penilaian itu benar. Yang v6 sempat buang, dan sekarang dikembalikan:

   1. KARTU JAWABAN di paling atas. Yang ditanyakan pembeli setelah memesan
      cuma satu: "kapan bisa saya ambil". Menjawabnya dengan chip status
      kecil di tengah halaman berarti menyuruh orang mencari jawaban dari
      pertanyaannya sendiri.

   2. KODE PENGAMBILAN sebagai benda, bukan keterangan. Ia yang ditunjukkan
      ke petugas di toko, jadi ia harus besar, monospasi, dan berjarak huruf
      lebar supaya bisa dibacakan lewat telepon tanpa salah dengar.

   3. LANGKAH BERJALAN membawa ISI, bukan cuma nama langkah. "Sedang
      disiapkan · 3 dari 4 barang siap" menjawab "sampai mana"; "Sedang
      disiapkan · menunggu" tidak menjawab apa pun.

   4. GARIS PENYAMBUNG antar titik. Tanpa itu ia daftar butir biasa, dan
      urutan waktunya harus disimpulkan sendiri oleh pembacanya.
   ═══════════════════════════════════════════════════════════════════════ */

/* Kartu jawaban. Warnanya mengikuti KEADAAN pesanan, bukan warna merek —
   "siap diambil" adalah kabar baik, dan kabar baik berwarna sama dengan
   kabar baik lain di seluruh sistem. */
.jawaban {
  border-radius: var(--lengkung); padding: var(--s4);
  border: var(--tebal-garis) solid var(--ok-garis); background: var(--ok-bg);
  margin-bottom: var(--s4);
}
.jawaban.warn { border-color: var(--warn-garis); background: var(--warn-bg); }
.jawaban.info { border-color: var(--info-garis); background: var(--info-bg); }
.jawaban .keadaan {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ok);
}
.jawaban.warn .keadaan { color: var(--warn); }
.jawaban.info .keadaan { color: var(--info); }
.jawaban .kapan {
  font-size: var(--t-xl); font-weight: 700; letter-spacing: -.02em;
  margin: 4px 0 2px; color: var(--tinta);
}
.jawaban .di-mana { font-size: var(--t-sm); color: var(--tinta-2); }
/* Kode pengambilan: jarak huruf lebar supaya tiap karakter berdiri sendiri
   dan bisa dibacakan lewat telepon tanpa tertukar. */
.jawaban .kode {
  font-family: var(--mono); font-size: var(--t-2xl); font-weight: 700;
  letter-spacing: .14em; margin-top: var(--s3); color: var(--tinta);
}

.linimasa { list-style: none; margin: 0; padding: 0; }
.linimasa li { display: flex; gap: var(--s3); padding-bottom: var(--s4); position: relative; }
/* Garis penyambung: dari bawah titik ini sampai titik berikutnya. */
.linimasa li:not(:last-child)::before {
  content: ""; position: absolute; left: 10px; top: 24px; bottom: 0;
  width: 2px; background: var(--garis-2);
}
.linimasa li.usai:not(:last-child)::before { background: var(--ok); }
.linimasa .titik {
  width: 22px; height: 22px; border-radius: 99px; flex: none; z-index: 1;
  display: grid; place-items: center; font-size: 11px;
  background: var(--kartu-2); color: var(--tinta-3);
  border: var(--tebal-garis) solid var(--garis-2);
}
/* Centangnya memakai --kertas, BUKAN putih tetap.
   
   Putih di atas --ok tema terang (#1c6b45) memang terbaca, tapi di tema
   gelap --ok menjadi hijau muda (#63c795) dan putih di atasnya cuma 2,07:1.
   Cacat yang sama persis dengan tombol merah: warna semantik dipakai
   sebagai LATAR tanpa varian gelapnya.
   
   --kertas menyelesaikannya tanpa token tambahan, karena kecerahannya
   selalu berlawanan dengan --ok: terang di tema terang, gelap di tema
   gelap. */
.linimasa li.usai .titik { background: var(--ok); color: var(--kertas); border-color: var(--ok); }
/* Langkah yang SEDANG berjalan memakai tinta, bukan warna semantik: ia
   bukan "baik" atau "buruk", ia "di sini sekarang". */
.linimasa li.kini .titik { background: var(--tinta); color: var(--kertas); border-color: var(--tinta); }
.linimasa .isi b { display: block; font-size: var(--t-sm); }
.linimasa .isi span { font-size: var(--t-xs); color: var(--tinta-2); }
.linimasa li:not(.usai):not(.kini) .isi b { color: var(--tinta-2); font-weight: 400; }


/* ═══════════════════════════════════════════════════════════════════════
   GERAK — satu momen, saat halaman terbuka
   ═══════════════════════════════════════════════════════════════════════

   Isi halaman naik sedikit dan memudar masuk, bagian demi bagian. Bukan
   efek berserakan di tiap komponen: satu gerakan yang dikarang, sekali,
   lalu diam. Layar operasional dipakai berjam-jam — apa pun yang bergerak
   setelah detik pertama akan jadi gangguan.

   Mulai dari keadaan YANG SUDAH TERLIHAT (opacity .001, bukan 0) supaya
   halaman tidak pernah tampak kosong kalau animasinya gagal.             */

@keyframes naik {
  from { opacity: .001; transform: translateY(7px); }
  to   { opacity: 1;    transform: none; }
}
main > .kepala,
main > .bagian,
main > .kartu,
main > .kosong,
.peraga > .layar {
  animation: naik 420ms cubic-bezier(.16, 1, .3, 1) both;
}
main > .bagian:nth-of-type(2) { animation-delay: 55ms; }
main > .bagian:nth-of-type(3) { animation-delay: 110ms; }
main > .bagian:nth-of-type(4) { animation-delay: 165ms; }

@media (prefers-reduced-motion: reduce) {
  main > .kepala, main > .bagian, main > .kartu, main > .kosong,
  .peraga > .layar { animation: none; }
}

/* ── Lambang + tagline ──
   Kotak merah berhuruf "h" diganti lambang aslinya, dan taglinenya dipasang:
   "Rumah Pangan Sehat" itu usahanya, bukan slogan. Katalognya memang ikan,
   daging beku, dan jahe merah. */
.merek .teks { display: flex; flex-direction: column; line-height: 1.15; }
.merek .tag {
  font-size: var(--t-xs); font-weight: 700; color: var(--merah);
  letter-spacing: 0.005em;
}


/* ═══════════════════════════════════════════════════════════════════════
   TREN & METER
   ═══════════════════════════════════════════════════════════════════════

   Dua bentuk, dua pekerjaan, dan tidak ada yang ketiga:

   SPARK  untuk angka yang bergerak sepanjang waktu (omzet, laba). Dua belas
          titik, garis 2 px, hari-hari sebelumnya diredupkan dan hari ini
          diberi warna — supaya yang dibaca lebih dulu adalah "sekarang di
          mana", bukan seluruh garisnya.

   METER  untuk angka yang sebenarnya PROPORSI. "622 pesanan" itu menyesatkan
          kalau 618 di antaranya sudah mati; "40 SKU kritis" berbeda artinya
          kalau 5 sudah habis sama sekali. Meter menunjukkan bagian itu tanpa
          menambah satu angka pun.

   Yang sengaja TIDAK dipakai: donat, pai, dan grafik batang untuk satu nilai.
   Untuk satu angka, angkanya sendiri sudah grafiknya.                      */

.spark { display: block; width: 100%; height: 34px; overflow: visible; margin-top: var(--s2); }
/* Varian tinggi untuk tren 30 hari.
   
   Tingginya TIDAK dipatok: kotak gambarnya sendiri yang lebih jangkung
   (200x44, dihitung di Go), dan SVG-nya diskalakan seragam. Versi pertama
   memakai kotak 200x34 yang sama dengan sparkline kecil lalu
   direntang dengan preserveAspectRatio="none" — dan peregangan tak seragam
   itu mengubah titik ujung yang bulat menjadi telur selebar 11px. */
.spark.besar { height: auto; }
/* Garis TIDAK ikut menebal saat kotaknya diperbesar.
   
   Penskalaan seragam 2,7x mengubah garis 2px rancangan menjadi 5,5px dan
   cincin putih di titik ujung menjadi 5,5px juga — tebal cincinnya melebihi
   titik yang seharusnya dilingkarinya. non-scaling-stroke menahan ketiganya
   pada tebal yang ditulis, jadi grafik besar dan sparkline kecil memakai
   bobot garis yang sama persis. */
.spark.besar .garis,
.spark.besar .dasar,
.spark.besar .ujung { vector-effect: non-scaling-stroke; }

/* Grafik besar tidak ikut animasi "menggambar diri sendiri", dan itu
   PILIHAN, bukan kelalaian.
   
   non-scaling-stroke membuat peramban menghitung panjang garis putus-putus
   dalam piksel LAYAR, bukan satuan viewBox. Panjang lintasan yang dikirim
   dari Go (298 satuan) benar di satuan viewBox dan salah di layar, tempat
   lintasan yang sama membentang 817px — jadi polanya berulang lagi dan
   garisnya hilang di bagian yang paling banyak bergerak.
   
   Keduanya tidak bisa dipegang bersama tanpa mengetahui skala tampilnya,
   dan skala itu ikut lebar kolom. Yang dipilih: garis 2px yang utuh.
   Sparkline kecil tetap menggambar dirinya — di sana strokenya ikut
   diskalakan, jadi satuannya cocok. */
.spark.besar .garis { stroke-dasharray: none; animation: none; }
.spark .garis { fill: none; stroke: var(--tinta-3); stroke-width: 2;
                stroke-linejoin: round; stroke-linecap: round; }
/* fill-opacity, BUKAN opacity — dan itu bukan gaya penulisan.
   
   Wash ini juga dianimasikan memudar masuk lewat @keyframes muncul, yang
   berakhir pada `opacity: 1`. Dengan animation-fill-mode `both`, nilai akhir
   itu MENETAP dan mengalahkan opacity apa pun yang ditulis di sini. Yang
   tergambar setelah 1,4 detik bukan wash 10% melainkan bidang pekat penuh
   yang menelan garis trennya sendiri.
   
   `fill-opacity` dan `opacity` properti yang berbeda dan berkalian, jadi
   keduanya bisa hidup berdampingan: satu mengatur seberapa tipis washnya,
   satu lagi memudarkannya masuk. */
.spark .isi   { fill: var(--tinta-3); fill-opacity: .10; }
/* Bagian terakhir memakai warna keadaan; sisanya redup. */
.spark .kini      { stroke: var(--ok); }
.spark .isi-kini  { fill: var(--ok); fill-opacity: .12; }
.spark.turun .kini     { stroke: var(--crit); }
.spark.turun .isi-kini { fill: var(--crit); fill-opacity: .12; }
/* Datar bukan naik. Tanpa keadaan ketiga, hari terakhir yang nilainya sama
   dengan hari sebelumnya — termasuk nol dibanding nol — ikut mewarisi hijau
   dari aturan .kini di atas. */
.spark.datar .kini  { stroke: var(--tinta-3); }
.spark.datar .ujung { fill: var(--tinta-3); }
/* Cincin permukaan 2 px supaya titik ujung tetap terbaca di atas garis. */
.spark .ujung { fill: var(--ok); stroke: var(--kartu); stroke-width: 2; }
.spark.turun .ujung { fill: var(--crit); }
/* Garis dasar: hairline satu langkah dari permukaan, tidak putus-putus. */
.spark .dasar { stroke: var(--garis); stroke-width: 1; }

/* Sorotan: titik + nilai muncul saat kursor di atasnya. */
.spark .sorot { opacity: 0; }
.spark:hover .sorot, .spark:focus-within .sorot { opacity: 1; }
.spark .pita { fill: transparent; }
.spark .pita:hover ~ .sorot { opacity: 1; }

.meter {
  display: block; height: 6px; margin-top: var(--s2);
  background: var(--garis); border-radius: 0 3px 3px 0; overflow: hidden;
}
.meter i {
  display: block; height: 100%; background: var(--tinta-3);
  border-radius: 0 3px 3px 0;
}
.meter.ok i   { background: var(--ok); }
.meter.warn i { background: var(--warn); }
.meter.crit i { background: var(--crit); }
.ubin .meter-ket { font-size: var(--t-xs); color: var(--tinta-2); margin-top: 5px; }


/* ═══════════════════════════════════════════════════════════════════════
   SENTUHAN — dua hal, keduanya punya alasan
   ═══════════════════════════════════════════════════════════════════════ */

/* 1. GARIS TREN MENGGAMBAR DIRINYA SENDIRI.
   
   Bukan animasi tempelan: garis itu memang catatan yang tumbuh hari demi
   hari, dan menggambarnya dari kiri ke kanan mengatakan persis itu. Sekali,
   saat halaman terbuka, lalu diam. Isi wash-nya menyusul memudar supaya
   garisnya yang dibaca lebih dulu.
   
   Panjangnya DINORMALKAN lewat pathLength="1" pada polyline-nya, bukan
   ditebak sebagai satu angka tetap.
   
   Versi v6 memakai 260 dengan alasan "lebih besar dari panjang lintasan mana
   pun di sini" — dan itu benar untuk sparkline dua belas titik di mockup.
   Grafik tren 30 hari melewatinya: lintasannya lebih dari 400 satuan, jadi
   polanya BERULANG — 260 tergambar, 260 berikutnya kosong. Yang terlihat
   bukan animasi yang salah, melainkan grafik yang garisnya hilang tepat di
   puncak-puncaknya, sementara isi wash di bawahnya tetap utuh.
   
   Dengan pathLength="1" satu satuan berarti "seluruh lintasan", berapa pun
   panjang sebenarnya. */
@keyframes gambar { from { stroke-dashoffset: var(--panjang-garis, 260); }
                     to { stroke-dashoffset: 0; } }
@keyframes muncul { from { opacity: 0; } to { opacity: 1; } }

.spark .garis {
  stroke-dasharray: var(--panjang-garis, 260);
  animation: gambar 900ms cubic-bezier(.22, 1, .36, 1) both;
  animation-delay: 240ms;
}
.spark .garis.kini { animation-delay: 1000ms; animation-duration: 320ms; }
.spark .isi   { animation: muncul 500ms ease-out both; animation-delay: 900ms; }
.spark .ujung { animation: muncul 300ms ease-out both; animation-delay: 1180ms; }
.meter i {
  transform-origin: left center;
  animation: tumbuh 700ms cubic-bezier(.22, 1, .36, 1) both;
  animation-delay: 300ms;
}
@keyframes tumbuh { from { transform: scaleX(0); } to { transform: scaleX(1); } }

@media (prefers-reduced-motion: reduce) {
  .spark .garis, .spark .isi, .spark .ujung, .meter i {
    animation: none; stroke-dashoffset: 0;
  }
}

/* 2. BUTIRAN KERTAS.
   
   Latar rata sempurna adalah latar yang tidak dipilih siapa pun. Butiran
   tipis memberi permukaannya sifat bahan — sekeluarga dengan kertas nota
   dan kardus, dunia yang dilayani sistem ini. Sangat lirih (3%): kalau
   terlihat sebagai tekstur, ia sudah terlalu kuat.
   
   Digambar SVG sebagai data URI, jadi tidak ada berkas gambar yang harus
   dimuat dan tidak ada permintaan jaringan. */
body::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  opacity: .030;
  background-image: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'>\
<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/>\
<feColorMatrix type='saturate' values='0'/></filter>\
<rect width='160' height='160' filter='url(%23n)'/></svg>");
}
.shell, .peraga { position: relative; z-index: 1; }


/* Wash tren di tema gelap harus lebih lirih: isian terang di atas latar
   gelap tampak lebih berat daripada isian gelap di atas latar terang pada
   opasitas yang sama. */
:root[data-tema="gelap"] .spark .isi { fill-opacity: .055; }
:root[data-tema="gelap"] .spark .isi-kini { fill-opacity: .075; }
@media (prefers-color-scheme: dark) {
  :root:not([data-tema="terang"]) .spark .isi { fill-opacity: .055; }
  :root:not([data-tema="terang"]) .spark .isi-kini { fill-opacity: .075; }
}


/* ═══════════════════════════════════════════════════════════════════════
   PILIH BARIS & TINDAKAN MASSAL
   ═══════════════════════════════════════════════════════════════════════

   Enam layar daftar sebelumnya tidak punya satu pun kotak pilih, sehingga
   622 konfirmasi pesanan dan 130 pengisian harga adalah 622 dan 130 kali
   muat halaman. Bar tindakannya menempel di bawah daftar supaya jumlah
   terpilih dan tombolnya selalu terlihat tanpa menggulung balik.           */

th.pilih, td.pilih { width: 1%; padding-right: 0; }
th.pilih input, td.pilih input {
  width: 16px; height: 16px; min-height: 0; margin: 0; accent-color: var(--merah-isi);
}
/* Baris terpilih memakai permukaan NETRAL, bukan cuci merah.
   
   Dua alasan: cuci merah menyeret teks sekunder ke 4,36:1, dan di sistem ini
   merah sudah berarti "kurang/terlambat" — memakainya untuk "terpilih"
   membuat satu warna mengatakan dua hal. Penanda pilihannya kotak centangnya
   sendiri, ditambah tanda tegas di tepi baris. */
tbody tr:has(td.pilih input:checked) td { background: var(--kartu-2); }
tbody tr:has(td.pilih input:checked) td:first-child {
  box-shadow: inset 3px 0 0 var(--tinta);
}

.bar-massal {
  position: sticky; bottom: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin: var(--s4) 0 0; padding: var(--s3) var(--s4);
  background: var(--kartu); border: var(--tebal-garis) solid var(--garis-3);
  border-radius: var(--lengkung);
  box-shadow: 0 -6px 18px -12px rgba(0,0,0,.28);
  font-size: var(--t-sm);
}

/* `display: flex` di atas MENGALAHKAN [hidden] bawaan peramban, yang cuma
   `display: none` dari lembar gaya agen-pengguna. Tanpa baris ini bar
   tindakan massal tampil terus — lengkap dengan "0 pesanan terpilih" dan
   tombol permanen yang tidak punya sasaran.
   
   Cacat yang sama pernah membuat kotak cari di dropdown produk tidak
   menyaring apa pun: [hidden] dipasang benar oleh skripnya, dan aturan
   display di lembar gaya diam-diam mengabaikannya. */
.bar-massal[hidden] { display: none; }
/* Tindakan yang menghapus/membatalkan tidak boleh berbagi bentuk dengan
   tindakan yang menyimpan. */
.tbl.bahaya {
  background: transparent; color: var(--crit);
  border-color: var(--crit-garis);
}
.tbl.bahaya:hover { background: var(--crit-bg); border-color: var(--crit); }


/* Pilihan bersegmen: dua-tiga pilihan yang saling meniadakan, terlihat
   semuanya sekaligus. Lebih cepat daripada dropdown untuk pilihan sesedikit
   ini, dan keadaannya terbaca tanpa dibuka. */
.pilih-segmen { display: inline-flex; border: var(--tebal-garis) solid var(--garis-3);
                border-radius: var(--lengkung); overflow: hidden; }
.pilih-segmen button {
  min-height: var(--sasaran); padding: 0 var(--s4); border: 0;
  border-right: var(--tebal-garis) solid var(--garis-3);
  background: var(--kartu); color: var(--tinta);
  font: inherit; font-size: var(--t-sm); font-weight: 700; cursor: pointer;
}
.pilih-segmen button:last-child { border-right: 0; }
.pilih-segmen button:hover { background: var(--kartu-2); }
.pilih-segmen button[aria-checked="true"] { background: var(--tinta); color: var(--kertas); }


/* ═══════════════════════════════════════════════════════════════════════
   KONFIRMASI, SEDANG DIPROSES, DAN SESUDAH
   ═══════════════════════════════════════════════════════════════════════

   Tiga keadaan yang sebelumnya tidak ada satu pun di 24 layar. Tiap alur
   berhenti tepat di detik penekanan tombol: tidak ada yang menahan sebelum
   yang permanen, tidak ada yang menandai sedang berjalan, dan tidak ada
   yang menunjukkan hasilnya. Puncak-akhir tidak punya akhir untuk dipakai. */

/* Langkah tahan sebelum yang permanen. Angkanya DIULANG di sini — bukan
   "Yakin?", tapi "ini yang akan tercatat". */
.tahan {
  border: var(--tebal-garis) solid var(--crit-garis);
  border-radius: var(--lengkung);
  background: var(--crit-bg);
  padding: var(--s4);
  display: grid; gap: var(--s3);
}
.tahan .ulang {
  font-family: var(--mono); font-size: var(--t-sm); color: var(--tinta);
  display: flex; gap: var(--s4); flex-wrap: wrap;
}
.tahan .ulang b { font-weight: 400; color: var(--tinta-2); }
.tahan .aksi { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }

/* Sedang diproses. Tombolnya tetap di tempatnya, lebarnya tidak berubah,
   dan teksnya tidak hilang — kalau tombol berubah ukuran saat ditekan,
   yang berikutnya bergeser dan orang menekan yang salah. */
.tbl[aria-busy="true"] { pointer-events: none; }
.tbl[aria-busy="true"]::before {
  content: ""; width: 13px; height: 13px; flex: none;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 99px; animation: putar 700ms linear infinite;
}
@keyframes putar { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .tbl[aria-busy="true"]::before { animation: none; } }

/* Sesudah. Bukan toast yang lewat — ringkasan yang tinggal, dengan nomor
   dokumennya, supaya bisa dikutip saat ada pertanyaan. */
.sesudah {
  border: var(--tebal-garis) solid var(--ok-garis);
  border-radius: var(--lengkung);
  background: var(--ok-bg); color: var(--ok);
  padding: var(--s4); display: grid; gap: var(--s2);
}
.sesudah b { color: var(--ok); }
.sesudah .rinci { color: var(--tinta-2); font-size: var(--t-sm); }
.sesudah .lanjut { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s1); }


/* Galat di kolomnya sendiri. Pesan galat sebagus apa pun tidak berguna kalau
   ia hanya ada di galeri contoh dan tidak pernah muncul di sebelah kolom
   yang salah. */
.galat-kolom {
  display: block; font-size: var(--t-xs); color: var(--crit);
  font-weight: 400; text-transform: none; letter-spacing: 0;
  margin-top: 4px;
}
.galat-kolom b { color: var(--crit); }
input[aria-invalid="true"], select[aria-invalid="true"] {
  border-color: var(--crit); box-shadow: inset 0 -2px 0 var(--crit);
}


/* Tombol tuts. Dipakai di bar atas sebagai petunjuk pintasan dan di halaman
   bantuan sebagai daftarnya. */
kbd.pintas {
  display: inline-block; min-width: 18px; padding: 1px 5px;
  font-family: var(--mono); font-size: var(--t-xs); line-height: 1.5;
  text-align: center; color: var(--tinta-2);
  background: var(--kartu-2); border: var(--tebal-garis) solid var(--garis-2);
  border-radius: var(--lengkung-sm);
  /* Bevel tuts digambar dengan bayangan, bukan border bawah yang ditebalkan:
     border tebal di satu sisi kartu adalah bentuk yang sudah jadi bawaan di
     mana-mana, dan bayangan memang yang sebenarnya terjadi pada tuts. */
  box-shadow: 0 1px 0 var(--garis-2);
}
.cari-atas kbd.pintas { flex: none; margin-left: auto; }
.tbl.sunyi.bantuan { width: var(--sasaran); padding: 0; flex: none; }
.tbl.sunyi.bantuan svg { width: 18px; height: 18px; }


/* Chip "lainnya": jalan ke sisa kategori. Bentuknya chip supaya jelas ia
   anggota deret yang sama, bukan tombol asing. */
.chip.lagi {
  border-style: dashed; border-color: var(--garis-3); color: var(--tinta);
  cursor: pointer; font: inherit; font-size: var(--t-xs); font-weight: 700;
}
.chip.lagi::before { display: none; }
.chip.lagi:hover { background: var(--kartu-2); }


/* ═══════════════════════════════════════════════════════════════════════
   BELUM BERES — pokok halaman Ringkasan
   ═══════════════════════════════════════════════════════════════════════

   Dua bidang bersisian: berapa banyak yang menggantung, dan MANA yang
   dikerjakan lebih dulu. Yang kedua itu yang selama ini tidak pernah ada —
   halaman lama menyodorkan sembilan tugas tanpa mengatakan mana yang
   pertama, dan membiarkan orang yang memutuskan tiap pagi.                */

.belum-beres {
  display: grid; gap: var(--s4);
  grid-template-columns: minmax(240px, 1fr) minmax(280px, 1.1fr);
  border: var(--tebal-garis) solid var(--garis-2);
  border-radius: var(--lengkung);
  overflow: clip;
}
.belum-beres .pokok, .belum-beres .dulu { padding: var(--s4) var(--s5); }
.belum-beres .pokok { background: var(--kartu); }
.belum-beres .dulu  { background: var(--kartu-2); border-left: var(--tebal-garis) solid var(--garis); }
.belum-beres .k {
  font-size: var(--t-sm); color: var(--tinta-2);
}
.belum-beres .pokok .v {
  font-size: 52px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.02;
  margin: 2px 0 4px; font-variant-numeric: proportional-nums;
}
.belum-beres .pokok .v .unit { font-size: var(--t-lg); font-weight: 400;
  color: var(--tinta-2); margin-left: 6px; }
.belum-beres .d { font-size: var(--t-sm); color: var(--tinta-2); max-width: 46ch; }
.belum-beres .d b { color: var(--tinta); }
.belum-beres .dulu .apa {
  font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.02em;
  margin: 4px 0 2px;
}
.belum-beres .dulu .tbl { margin-top: var(--s3); }

@container cangkang (max-width: 760px) {
  .belum-beres { grid-template-columns: 1fr; }
  .belum-beres .dulu { border-left: 0; border-top: var(--tebal-garis) solid var(--garis); }
}


/* ═══════════════════════════════════════════════════════════════════════
   2. JEMBATAN TOKEN — nama lama, nilai v6
   ═══════════════════════════════════════════════════════════════════════

   Bagian 3 di bawah ditulis dengan kosakata token v5 (--surface, --line,
   --sp-4, --fs-sm). Menulis ulang ratusan baris itu satu per satu berarti
   ratusan kesempatan salah ketik untuk hasil yang tidak berubah sedikit pun.

   Yang dikerjakan di sini justru yang penting: nama-nama lama itu sekarang
   MENUNJUK ke nilai v6. Jadi setiap komponen aplikasi ikut berganti warna,
   jarak, dan lengkung mengikuti v6 — termasuk di tema gelap — tanpa satu
   pun nilai yang ditulis dua kali.

   Ini jembatan, bukan lapisan tetap. Yang menambah aturan BARU memakai token
   v6 langsung. */

:root {
  --bg: var(--kertas);
  --surface: var(--kartu);
  --surface-2: var(--kartu-2);

  --line: var(--garis);
  --line-2: var(--garis-2);
  --line-3: var(--garis-3);

  --text: var(--tinta);
  --text-2: var(--tinta-2);
  --text-3: var(--tinta-3);
  --text-inv: var(--kertas);

  --brand: var(--merah);
  --brand-press: var(--merah-tekan);
  --brand-wash: var(--merah-cuci);
  --brand-fill: var(--merah-isi);
  --brand-fill-press: var(--merah-tekan);
  --on-brand: var(--pada-merah);

  --leaf: var(--hijau);
  --leaf-press: var(--hijau-isi);
  --leaf-wash: var(--ok-bg);
  --leaf-fill: var(--hijau-isi);
  --leaf-fill-press: var(--hijau-isi);
  --on-leaf: var(--pada-hijau);

  --ok-line: var(--ok-garis);
  --warn-line: var(--warn-garis);
  --crit-line: var(--crit-garis);
  --info-line: var(--info-garis);

  /* Panel merek halaman masuk. Dipatok gelap di KEDUA tema — bukan
     diturunkan dari skala yang membalik, karena versi yang menurunkannya
     pernah tampil putih dengan tulisan putih. */
  --brand-panel: #14181a;
  --brand-panel-text: #f4f1ec;
  --brand-panel-dim: #a8b0b4;

  --sp-1: var(--s1); --sp-2: var(--s2); --sp-3: var(--s3); --sp-4: var(--s4);
  --sp-5: var(--s5); --sp-6: var(--s6); --sp-8: var(--s7);
  --lebar-etalase: 1240px;
  --sp-16: calc(var(--s7) * 1.6);

  --fs-2xs: 10px;            --fs-xs: var(--t-xs);   --fs-sm: var(--t-sm);
  --fs-base: var(--t-dasar); --fs-lg: var(--t-lg);   --fs-xl: var(--t-xl);
  --fs-2xl: var(--t-2xl);    --fs-3xl: var(--t-3xl); --fs-4xl: 42px;

  --fw-regular: 400; --fw-medium: 400; --fw-semibold: 700; --fw-bold: 700;

  --radius-sm: var(--lengkung-sm);
  --radius-md: var(--lengkung);
  --radius-lg: var(--lengkung);
  --radius-full: 99px;
  --r-lg: var(--lengkung);

  --hit-min: var(--sasaran);
  --row: var(--baris-tabel);
  --rule: var(--tebal-garis);
  --ikon: 17px;

  --lift-1: 0 1px 2px rgba(0,0,0,.05);
  --lift-2: var(--terangkat);
  --lift-3: 0 12px 32px rgba(0,0,0,.10);

  --side-bg: var(--kartu);
  --side-text: var(--tinta-2);
  --side-on: var(--tinta);
  --side-line: var(--garis);
  --side-sel: var(--kartu-2);
}

/* ═══════════════════════════════════════════════════════════════════════
   3. APLIKASI — yang tidak dipunyai mockup
   ═══════════════════════════════════════════════════════════════════════

   Mockup memajang tata letak; ia tidak punya peta Leaflet, dropdown
   pencarian yang menyaring 1.578 produk, penanda htmx sedang memuat, atau
   etalase toko. Semua itu tinggal di sini.

   Aturan yang bentrok dengan v6 SUDAH dibuang saat perakitan: kalau sebuah
   kelas juga didefinisikan v6, yang berlaku v6. Yang tersisa di bawah
   benar-benar hanya yang tidak dipunyai mockup. */



/* :where() dipakai supaya spesifisitasnya TIDAK berubah — aturan ini tetap
   (0,0,1) dan (0,1,1) persis seperti sebelumnya, hanya berhenti mengenai
   tautan yang berlagak tombol.

   Tanpa itu: a:visited berspesifisitas (0,1,1) dan MENGALAHKAN .btn (0,1,0),
   jadi begitu sebuah <a class="btn"> pernah diklik, warnanya berubah menjadi
   var(--brand) — merah di atas latar merah. Tombolnya tampak kosong, dan
   tulisannya baru muncul saat disorot karena .btn:hover (0,2,0) menang lagi.
   Dilaporkan pemilik sistem 18 Agt 2026 pada tombol "Tambah produk". */
/* Tautan berwarna TINTA, bukan merah.

   Diukur pada versi sebelumnya: 10,7% simpul teks di halaman Produk
   berwarna merah merek, 18,3% di Pesanan, dan 27,1% di Supplier — karena
   setiap nama produk, nomor struk, dan nama supplier kebetulan tautan.
   Layar berisi 1.575 baris merah terbaca sebagai kebisingan, bukan data,
   dan merahnya kehilangan daya justru pada tombol utama dan status
   genting.
   
   Garis bawah saat disorot yang menggantikan warna sebagai penanda
   "ini bisa diklik". */
a:where(:not(.btn)), a:where(:not(.btn)):visited { color: var(--text); text-decoration: none; text-underline-offset: 2px; }


/* Pemilih tema TIDAK didefinisikan ulang di sini.
   
   Sebelumnya ada salinan kedua di blok ini — sisa dari saat pemilihnya masih
   tinggal di kaki sidebar. Karena ia berdiri lebih belakang di berkas, ia
   menang atas v6 pada properti yang kebetulan sama namanya dan KALAH pada
   yang tidak: v6 tetap memberi latar, tepi, dan lengkung 99px, sementara
   salinan ini menimpa padding dan membiarkan `display:flex` merentangkan
   kotaknya selebar kolom.
   
   Yang tampil bukan salah satu dari keduanya, melainkan campurannya: pil
   selebar panel dengan tiga tombol berdesak di ujung kiri. Dilaporkan
   pemilik sistem 29 Agu 2026 sebagai "component select theme kurang rapi".
   
   Aturannya sekarang cuma satu, di bagian PEMILIH TEMA di atas. */




.side {
  background: var(--side-bg); border-right: 1px solid var(--side-line);
  display: flex; flex-direction: column; padding: var(--sp-4) 0 var(--sp-3);
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}

/* Merek: lambang + kata, seperti di mockup. Lambang saja membuat sudut
   kiri atas jadi teka-teki kecil pada layar yang tidak pernah dilihat
   orang baru sebelumnya. */
.side-brand {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-4) var(--sp-4); color: var(--side-on);
}

.side-brand img { display: block; height: 30px; width: auto; flex: none; }

.side-brand b { font-size: var(--fs-base); font-weight: var(--fw-semibold); letter-spacing: -.01em; }

.side-brand:hover { text-decoration: none; }

.side-grp {
  font: var(--fw-semibold) var(--fs-2xs)/1 var(--sans);
  /* --side-text, bukan --text-3: yang kedua adalah skala warna HALAMAN dan
     tidak tahu apa-apa soal latar sidebar. Di tema gelap dan tema kontras
     ia jatuh di bawah ambang keterbacaan. */
  letter-spacing: .13em; text-transform: uppercase; color: var(--side-text);
  padding: var(--sp-4) var(--sp-4) var(--sp-2);
}

.side a {
  display: flex; align-items: center; gap: var(--sp-2);
  margin: 1px var(--sp-2); padding: 7px var(--sp-3);
  border-radius: var(--radius-sm); color: var(--side-text);
  text-decoration: none; font-size: var(--fs-sm); position: relative;
}

.side a:hover { background: color-mix(in srgb, var(--side-on) 7%, transparent); color: var(--side-on); text-decoration: none; }

/* Baris versi di kaki sidebar (F-293). Sengaja paling redup di halaman:
   dibutuhkan saat melaporkan masalah, bukan saat bekerja. */
.side-versi {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  font-family: var(--mono); font-size: var(--fs-2xs);
  color: var(--side-text); opacity: .75; line-height: 1.5;
}

.side-foot {
  margin-top: auto; padding: var(--sp-3) var(--sp-4) 0;
  border-top: 1px solid var(--side-line); display: flex; align-items: center; gap: var(--sp-2);
}

.side-foot form { margin: 0; margin-left: auto; }

.side-foot .who { min-width: 0; }

.side-foot .who b {
  display: block; font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  color: var(--side-on); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.side-foot .who span { font-size: var(--fs-2xs); color: var(--side-text); }


/* ══ Bilah atas & kepala halaman ══════════════════════════════════════ */
.spacer { flex: 1; }


/* Kepala halaman TIDAK memberi padding mendatar sendiri: ia berada di dalam
   <main> yang sudah punya. Sebelumnya keduanya menumpuk, jadi judul halaman
   menjorok 48px sementara panel di bawahnya 24px — tepi kiri seluruh halaman
   jadi tidak lurus. */
.pagehead {
  display: flex; align-items: flex-end; gap: var(--sp-4); flex-wrap: wrap;
  padding: var(--sp-5) 0 var(--sp-4);
}

/* Judul halaman: 21px/640, bukan 26px/700.
   
   Diukur berdampingan dengan rancangan: judulnya di sana 21px dengan bobot
   640. Yang 26px/700 membuat tiap halaman dibuka dengan teriakan, dan
   karena SEMUA halaman memakainya, tidak ada satu pun judul yang jadi
   lebih penting dari yang lain — beratnya terpakai habis tanpa membedakan
   apa pun. */
.pagehead h1, .pagehead h2 { font-size: var(--fs-xl); font-weight: var(--fw-semibold); }

/* Sapaan di Ringkasan boleh lebih besar: ia satu-satunya judul yang bukan
   nama layar, melainkan kalimat pembuka hari. */
.pagehead h1.sapaan { font-size: 27px; }

.pagehead .actions { margin-left: auto; display: flex; gap: var(--sp-2); }

/* Halaman masuk memenuhi layar penuh. Kalau :has tidak didukung, yang
   terjadi cuma panel masuk tampil dalam lebar terbatas — tetap terpakai,
   tidak rusak.
   
   `.masuk`, bukan `.split`: kelasnya berganti nama dan aturan ini tidak
   ikut. Karena ia tidak pernah cocok, <main> tetap memakai padding 24px-nya
   — jadi layar masuk yang mestinya menempel ke empat tepi jendela justru
   berbingkai 24px, dan bingkai itu mendorong tingginya menjadi 1048px pada
   jendela 1000px. Halaman tanpa apa pun untuk digulung punya batang
   gulung. */
main:has(> .masuk) { max-width: none; padding: 0; }


/* ══ Kendali ══════════════════════════════════════════════════════════ */
button, .btn {
  font: var(--fw-medium) var(--fs-sm)/1.4 var(--sans);
  padding: 8px 14px; border-radius: var(--radius-sm); cursor: pointer;
  min-height: var(--hit-min);
  /* text-decoration:none HARUS ditulis di sini.
     Aturan tautan umum sengaja dikecualikan dari .btn (supaya a:visited
     tidak menimpa warnanya), dan efek sampingnya: <a class="btn"> tidak
     lagi kena text-decoration:none mana pun, sehingga jatuh ke bawaan
     browser — bergaris bawah. Regresi ini terlanjur ikut terkirim ke
     produksi bersama perbaikan :visited, dan baru ketahuan saat
     halamannya benar-benar dipotret. */
  text-decoration: none;
  background: var(--brand-fill); color: var(--on-brand);
  border: 1px solid var(--brand-fill); box-shadow: var(--lift-1);
}

button:hover, .btn:hover {
  background: var(--brand-fill-press); border-color: var(--brand-fill-press);
  color: var(--on-brand); text-decoration: none;
}

button[disabled], .btn[disabled] { opacity: .55; cursor: default; }

/* Tombol sekunder memakai --line-3, garis paling gelap.
   
   Diukur: dengan --line-2, tepinya cuma 1,58:1 terhadap latar halaman —
   jauh di bawah 3:1 yang dituntut WCAG 1.4.11 untuk batas komponen. Teks di
   dalamnya terbaca jelas (18,8), tapi kotaknya sendiri nyaris tidak
   terlihat, jadi ia tidak terbaca sebagai tombol.
   
   Ini cacat lama, bukan akibat perubahan v5 — tapi baru terasa setelah
   merah di sekelilingnya dibersihkan: selama layarnya ramai, tepi yang
   lemah tertutup keramaian.
   
   Rancangan v5 sendiri memakai tepi ~1,5:1 dan bersandar pada bayangan
   halus. Di sini rancangannya TIDAK diikuti: "sama seperti mockup" dan
   "tombolnya kelihatan" tidak bisa keduanya, dan yang kedua yang menentukan
   apakah orang menemukan tombolnya. */
/* Sisa kosakata LAMA yang masih dirujuk lembar gaya ini.
   
   `.btn` sudah tidak ada di satu templat pun sejak kosakata kelas diganti ke
   v6, jadi keempat aturan berikut mati diam-diam: yang paling terlihat
   `.btn.block`, yang membuat tombol "Daftar akun baru" di kartu masuk
   pelanggan lebih sempit daripada tombol di atasnya tanpa alasan.
   
   `.secondary`, `.big`, dan `.leaf` sudah punya padanan v6 (.dua, .besar,
   dan warna hijau merek), jadi yang disisakan cuma `.block` — satu-satunya
   yang v6 memang tidak punya, karena mockup tidak pernah memakai tombol
   selebar penuh. */

.tbl.block, button.block { width: 100%; }

/* Tombol hijau: dipakai etalase untuk tindakan yang MENYELESAIKAN belanja.
   Hijau merek, bukan merah — merah di sini berarti tidak bisa dibatalkan. */
.tbl.leaf, button.leaf {
  background: var(--hijau-isi); border-color: var(--hijau-isi); color: var(--pada-hijau);
}
.tbl.leaf:hover, button.leaf:hover { background: var(--hijau); border-color: var(--hijau); }

button.link {
  background: none; border: none; box-shadow: none; color: var(--text-2);
  padding: 0 var(--sp-2); font-weight: var(--fw-regular);
  /* min-height, bukan padding saja: WCAG 2.2 SC 2.5.8 mengukur kotak
     sasarannya, dan tombol ini sebelumnya 36x18. */
  min-height: var(--hit-min); display: inline-flex; align-items: center;
}

button.link:hover { color: var(--text); background: none; text-decoration: underline; }

/* Keterangan di DALAM label tidak ikut tebal.
   
   label diberi bobot 640 supaya namanya menonjol, dan seluruh isinya
   mewarisi itu — termasuk kalimat penjelas satu-dua baris yang seharusnya
   redup. Akibatnya "Dari siapa barangnya datang. Boleh dikosongkan…"
   tampil setebal namanya sendiri. */
/* .redup IKUT dikecualikan, bukan cuma .muted/.hint/small.
   
   Daftar di bawah ditulis saat tiga kelas itu yang ditemukan; .redup
   terlewat, dan ia justru yang dipakai templat Operasi stok dan Produk.
   Akibatnya "Dari siapa barangnya datang. Boleh dikosongkan…" tampil
   setebal nama kolomnya, sementara keterangan di sebelahnya yang kebetulan
   berada di luar <label> tampil redup — dua keterangan sejenis dengan dua
   bobot huruf, di dalam satu form yang sama.
   
   Daftar kelas selalu ada yang terlewat. Yang menutupnya di sini: aturan
   bobot hurufnya dipasang untuk SEMUA kelas keterangan yang dikenal. */
label .muted, label .hint, label small, label .redup, label .petunjuk {
  font-weight: var(--fw-regular);
}

input.big { padding: 13px var(--sp-4); font-size: var(--fs-base); border-radius: var(--radius-md); }


/* Keterangan kecil di bawah kolom isian: syarat, contoh, atau akibat.
   Ditulis di tempat orang mengisinya, bukan di pesan galat setelah gagal. */
.hint { display: block; margin-top: 5px; font-size: var(--fs-xs); color: var(--text-3); }

/* Bagian yang ditebalkan di dalam keterangan naik satu tingkat kecerahan.
   Keterangan boleh redup; syarat dan akibat yang ada di dalamnya tidak —
   "sasarannya SEMUA 1.579 produk" harus terbaca sekali lihat. */
.hint strong { color: var(--text-2); }


/* Label berdiri sendiri, di luar <label>. Dipakai ketika satu keterangan
   berlaku untuk BEBERAPA kolom sekaligus — mis. "isi salah satu" di atas
   kolom WhatsApp dan email. */
p.label, .label {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-3);
  margin: var(--sp-4) 0 6px;
}


/* ══ Pesan ════════════════════════════════════════════════════════════ */
.err, .flash {
  border-radius: var(--radius-sm); padding: 10px var(--sp-3); margin: 0 0 var(--sp-4);
  font-size: var(--fs-sm); border: 1px solid transparent;
}

.err, .flash.err  { background: var(--crit-bg); color: var(--crit); border-color: var(--crit-line); }


/* ══ Ubin ringkasan ═══════════════════════════════════════════════════
   Dipisah garis rambut, bukan dibungkus kartu masing-masing. */
.tiles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(196px, 1fr));
  gap: 1px; background: var(--line); border-radius: var(--radius-md);
  overflow: hidden; box-shadow: var(--lift-1); margin-bottom: var(--sp-4);
}

.tile { background: var(--surface); padding: var(--sp-4); display: grid; gap: var(--sp-2); }

.tile a { color: inherit; }


/* ══ Daftar "butuh tindakan" ══════════════════════════════════════════ */

/* ══ Panel & kotak ════════════════════════════════════════════════════ */
/* Kartu punya GARIS TEPI, bukan cuma bayangan.
   
   `border: 0` membuat sudut membulatnya tidak terlihat sama sekali: tanpa
   tepi, tidak ada apa pun yang dibulatkan — yang tersisa hanya bidang putih
   di atas latar yang nyaris seputih itu, dibatasi bayangan setipis 1px.
   Rancangan v5 memakai 1px --rule dengan radius 12px.
   
   overflow:hidden dipertahankan supaya baris tabel di dalamnya ikut
   terpotong mengikuti sudut, bukan menyembul persegi di pojok bawah. */
.panel {
  background: var(--surface); border-radius: var(--radius-lg);
  box-shadow: var(--lift-1);
  border: 1px solid var(--line);

  /* clip, BUKAN hidden.
     
     Keduanya sama-sama memotong isi yang menyembul dari sudut membulat,
     tapi `hidden` menjadikan kartunya wadah gulung — dan kepala tabel yang
     lengket lalu berpegang pada kartu yang tidak pernah bergulir, sehingga
     ikut hanyut. `clip` memotong tanpa menjadi wadah gulung, jadi kepala
     tabel tetap berpegang pada halaman. */
  overflow: clip;
}

.panel > table { border: 0; }

.panel-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line);
}

/* 17px, bukan 14,5px: judul kartu di rancangan cukup besar untuk memisahkan
   satu kartu dari kartu di sebelahnya tanpa garis tambahan. */
.panel-head h2, .panel-head h3 { font-size: var(--fs-lg); font-weight: var(--fw-semibold); line-height: 1.5; }


/* Dua kolom sama lebar untuk halaman detail. Di bawah 900px keduanya
   menumpuk (lihat blok layar sempit di bawah). */
.dua-kolom { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); align-items: start; }

.dua-kolom > * { margin-top: 0; }


/* Tautan kembali di kepala halaman. Ditempatkan DI ATAS judul, bukan di
   atas seluruh halaman seperti sebelumnya, supaya ia terbaca sebagai
   bagian dari kepala dan tidak menggeser tepi atas halaman. */
.back {
  display: inline-block; margin-bottom: 6px;
  font-size: var(--fs-xs); color: var(--text-2); text-decoration: none;
}

.back:hover { color: var(--text); text-decoration: underline; }

/* Pembungkus tabel menggulung SENDIRI, mendatar dan menegak.
   
   Sebelum ini ia cuma overflow-x, dan itulah sebabnya kepala tabel yang
   sudah ditandai `position: sticky` tidak pernah benar-benar lengket:
   overflow-x saja tetap membuat kotak ini jadi wadah gulung, jadi kepala
   tabel berpegang pada kotak yang tidak pernah bergulir menegak — lalu ikut
   hanyut bersama halaman. Diukur di katalog 1.575 baris: kepalanya berada
   di -1231px, jauh di luar layar.
   
   Tingginya dibatasi terhadap tinggi jendela dikurangi bar atas dan kepala
   halaman, supaya yang bergulir adalah tabelnya — bukan seluruh halaman. */
/* SATU bilah gulung, bukan dua.
   
   Pembungkus ini sempat menggulung sendiri (overflow:auto + max-height)
   supaya kepala tabel bisa lengket. Akibatnya di layar lebar ada dua bilah
   gulung sekaligus — miliknya sendiri dan milik halaman — dan orang harus
   menebak mana yang menggerakkan tabelnya.
   
   Di layar lebar tabelnya muat mendatar, jadi pembungkusnya tidak perlu
   menggulung sama sekali; kepala tabel lalu berpegang pada HALAMAN dan
   tetap lengket, cukup diberi jarak setinggi bar atas. Gulungan mendatar
   baru dinyalakan di layar sempit, tempat ia memang dibutuhkan. */
.scroll-x { overflow: visible; }

tfoot th.numeric {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-weight: var(--fw-bold); font-size: var(--fs-base);
}

td.numeric, th.numeric {
  text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

th.numeric { font-family: var(--sans); }

td.negative { color: var(--crit); }


/* Garis tepi kiri baris = tingkat kegentingan. Struktural, bukan hiasan. */
/* Batang kegentingan di tepi kiri baris DICABUT.
   
   Setiap tabel yang memakainya sudah menyebutkan keadaannya dengan kata di
   barisnya sendiri — kolom Status pada Pesanan, Supplier, Lokasi, Kategori,
   Pengguna, Tier; chip "dibatalkan" pada Transaksi. Batang itu mengulang
   hal yang sama dengan warna, dan di halaman Pesanan hampir SELURUH
   barisnya menunggu konfirmasi, jadi yang tersisa cuma satu pita jingga
   sepanjang tabel yang tidak membedakan apa pun.
   
   Keputusan pemilik, 21 Agustus 2026. */
/* align-items: flex-end, BUKAN center.
   Dilaporkan pemilik sistem: "button di samping dropdown itu tidak di
   tengah". Diukur dan memang benar — tombolnya 8,9px lebih tinggi.
   Sebabnya <label>Cari <input></label> menumpuk teks label DI ATAS kotak
   isian, sementara tombol tidak punya label. Dengan align-items:center,
   yang disejajarkan adalah kotak LABEL-nya; kotak isian di dalamnya jadi
   turun sendirian dan tombol di sebelahnya terlihat melayang.
   
   Dirapatkan dari dasarnya, lalu tinggi seluruh kontrolnya disamakan
   supaya pusatnya benar-benar segaris — bukan sekadar mendekati. */
.toolbar {
  display: flex; align-items: flex-end; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line);
}

.toolbar input, .toolbar select, .toolbar button, .toolbar .btn,
.toolbar .pilih-pemicu, .toolbar a.pagination {
  height: 38px; box-sizing: border-box;
}

.toolbar input[type=checkbox], .toolbar input[type=radio] { height: auto; }

/* Penanda "sedang mencari". htmx menyisipkan aturan serupa sendiri, tapi
   ditulis eksplisit di sini supaya perilakunya tidak bergantung pada
   detail internal pustaka. */
.htmx-indicator { opacity: 0; transition: opacity .2s; }

.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

.toolbar label { margin: 0; }

.toolbar input, .toolbar select { margin-top: 0; }

.tabs { display: flex; gap: 2px; padding: 0 var(--sp-4); border-bottom: 1px solid var(--line); }

.tabs a {
  padding: 10px var(--sp-3); font-size: var(--fs-sm); color: var(--text-2);
  border-bottom: 2px solid transparent; margin-bottom: -1px; text-decoration: none;
}

.tabs a:hover { color: var(--text); text-decoration: none; }


/* ══ Judul & baris saringan ═══════════════════════════════════════════ */
h1.heading, h2.heading {
  font-size: var(--fs-2xl); margin: 0 0 var(--sp-4); font-weight: var(--fw-bold);
}

h2.heading { font-size: var(--fs-lg); margin-top: var(--sp-6); }

.row { display: flex; gap: var(--sp-3); align-items: end; flex-wrap: wrap; margin-bottom: var(--sp-4); }

.row label { margin: 0; }

.grow { flex: 1; min-width: 200px; }

.right { margin-left: auto; display: flex; align-items: center; gap: var(--sp-3); }

.muted { color: var(--text-2); font-size: var(--fs-sm); }


.pagination {
  display: flex; gap: var(--sp-2); align-items: center;
  padding: var(--sp-3) var(--sp-4); color: var(--text-3); font-size: var(--fs-xs);
  border-top: 1px solid var(--line);
}

.pagination a { color: var(--brand); font-weight: var(--fw-medium); }

.pagination .end { margin-left: auto; }

/* .pagination dipakai juga langsung di <a> sebagai gaya tab/tautan (mis.
   tab jenis laporan, tautan "Bersihkan"), bukan hanya sebagai wadah baris. */
/* Tautan di dalam bilah halaman. Ditulis di WADAHNYA, bukan di tiap
   templat: markupnya <a> polos dan tersebar di belasan halaman, jadi
   memperbaikinya satu per satu berarti yang kesekian pasti terlewat. */
.pagination a {
  display: inline-flex; align-items: center; min-height: var(--hit-min);
  padding: 0 var(--sp-1);
}


/* Tautan "Sebelumnya/Berikutnya". Dulu display:inline dengan padding 0,
   jadi kotak sasarannya cuma 68x14 — gagal WCAG 2.2 SC 2.5.8 sama seperti
   tombol Keluar, dan sama-sama tidak ada yang menyadarinya. */
a.pagination {
  display: inline-flex; align-items: center; min-height: var(--hit-min);
  padding: 0 var(--sp-2); border: 0; color: var(--brand);
  font-weight: var(--fw-medium); font-size: var(--fs-sm);
}


/* ══ Keadaan kosong ═══════════════════════════════════════════════════ */
.empty {
  padding: var(--sp-16) var(--sp-5); text-align: center; color: var(--text-2);
  display: grid; gap: var(--sp-2); justify-items: center;
}

.empty h3 { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text); }

.empty p { margin: 0; max-width: 40ch; font-size: var(--fs-sm); }

.empty .aksi-baris { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }


/* ══ Masuk: terbagi dua ═══════════════════════════════════════════════
   Panel kiri bukan dekorasi — ia memuat keadaan sistem, supaya kasir yang
   membuka aplikasi saat jaringan mati tahu sebelum mencoba masuk. */
.center { min-height: 80vh; display: grid; place-items: center; padding: var(--sp-5); }

.split { display: grid; grid-template-columns: 1.05fr 1fr; min-height: 100vh; }

.split-brand {
  position: relative; overflow: hidden;
  background: var(--brand-panel); color: var(--brand-panel-text);
  display: flex; flex-direction: column; justify-content: space-between; padding: var(--sp-8);
}

/* Tekstur garis barcode: polanya diambil dari benda yang memang dihasilkan
   sistem ini (label EPL2), bukan pola hias acak. */
.split-brand::before {
  content: ""; position: absolute; inset: 0; opacity: .14;
  background:
    repeating-linear-gradient(90deg, #fff 0 2px, transparent 2px 7px),
    repeating-linear-gradient(90deg, #fff 0 5px, transparent 5px 19px),
    repeating-linear-gradient(90deg, #fff 0 1px, transparent 1px 12px);
  -webkit-mask: linear-gradient(160deg, #000 0%, transparent 62%);
          mask: linear-gradient(160deg, #000 0%, transparent 62%);
}

.split-brand::after {
  content: ""; position: absolute; width: 520px; height: 520px; border-radius: 50%;
  right: -190px; bottom: -230px; opacity: .5; filter: blur(6px);
  background: radial-gradient(circle, #e30016 0%, transparent 68%);
}

.split-brand > * { position: relative; z-index: 1; }

.split-brand img { display: block; height: 46px; width: auto; }

.split-brand h1 {
  margin-top: var(--sp-6); font-size: var(--fs-3xl); font-weight: var(--fw-bold);
  letter-spacing: -.035em; max-width: 15ch;
}

.split-brand p { margin: var(--sp-4) 0 0; max-width: 34ch; color: var(--brand-panel-dim); }

.split-stats { display: flex; gap: var(--sp-6); flex-wrap: wrap; }

.split-form {
  background: var(--surface); border-left: 1px solid var(--line); display: flex; flex-direction: column;
  justify-content: center; padding: var(--sp-8) var(--sp-6);
}

.split-form form { width: 100%; max-width: 352px; margin: 0 auto; }

.split-form h1 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); margin-bottom: 6px; }

.split-form label { margin-bottom: var(--sp-4); }

.divider { display: flex; align-items: center; gap: var(--sp-3); color: var(--text-3); font-size: var(--fs-xs); margin: var(--sp-4) 0; }

.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }


/* Kartu dipertahankan untuk halaman kecil (pesan, ubah sandi) yang tidak
   memerlukan susunan terbagi dua. */
.card {
  background: var(--surface); border-radius: var(--radius-md); padding: var(--sp-6);
  width: 100%; max-width: 380px; box-shadow: var(--lift-2);
}

.card h1 { margin: 0 0 var(--sp-5); font-size: var(--fs-xl); }

.card .brand-top { display: block; height: 34px; width: auto; margin-bottom: var(--sp-5); }


/* ══ Bilah atas toko pelanggan ════════════════════════════════════════
   Etalase tidak memakai sidebar: menunya sedikit dan pengunjung datang
   untuk barangnya, bukan untuk menu. */
/* Lebar isi etalase DIBATASI dan diketengahkan (dilaporkan pemilik 7 Sep
   2026: di layar 1920px kartu memenuhi layar dari tepi ke tepi, sepuluh
   kolom, "marginnya hilang"). Grid auto-fill memang mengisi berapa pun
   lebar yang diberikan — yang harus dibatasi adalah wadahnya. 1240px:
   lima kartu 200px plus selanya, cukup lebar untuk nama produk yang
   panjang, cukup sempit untuk dipindai mata tanpa menoleh. */
main.etalase {
  max-width: var(--lebar-etalase); margin: 0 auto; box-sizing: border-box;
}

.bar {
  display: flex; align-items: center; gap: var(--sp-5);
  /* Isi bar sejajar dengan tepi wadah etalase di bawahnya, sementara
     latar barnya sendiri tetap selebar layar. */
  padding: 0 max(var(--sp-5), calc((100% - var(--lebar-etalase)) / 2 + var(--sp-5)));
  height: 60px;
  background: var(--surface); border-bottom: 1px solid var(--line);
}

.brand { display: flex; align-items: center; gap: var(--sp-2); text-decoration: none; color: var(--text); }

.brand img { height: 30px; width: auto; display: block; }

.brand:hover { text-decoration: none; }

.bar nav { display: flex; gap: var(--sp-5); }

.bar nav a {
  color: var(--text-2); font-size: var(--fs-sm); font-weight: var(--fw-medium);
  padding: 2px 0; text-decoration: none;
}

.bar nav a:hover { color: var(--text); text-decoration: none; }


/* ══ Katalog toko ═════════════════════════════════════════════════════ */
/* 200px, bukan 180px. Di layar 1913px yang 180 menghasilkan SEMBILAN kolom
   selebar 195px — dan nama produk sungguhan ("Chocolate Almond Stick 100gr
   Istana", "Detergent Ocean Dream Bantu Mama 600ml") tidak muat satu baris di
   lebar itu, jadi hampir setiap kartu terpaksa dua baris. */
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--sp-4); }

.catalog-card {
  background: var(--surface); border-radius: var(--radius-md); overflow: hidden;
  display: block; color: inherit; box-shadow: var(--lift-1);
}

.catalog-card:hover { text-decoration: none; box-shadow: var(--lift-2); }

.thumb, .thumb-placeholder { aspect-ratio: 1 / 1; width: 100%; display: block; object-fit: cover; }

.thumb-placeholder {
  background: linear-gradient(150deg, var(--leaf-wash), var(--surface-2));
  display: flex; align-items: center; justify-content: center; color: var(--leaf);
}

.thumb-placeholder svg { width: 34%; height: 34%; opacity: .55; }

/* display: block WAJIB. .body adalah <span> (kartunya <a>, dan <a> tidak
   boleh berisi <div>), dan padding pada elemen inline yang berisi anak
   berdisplay block cuma berlaku pada pecahan pertamanya — nama, harga, dan
   label ketersediaan menempel di tepi kiri kartu. Dilaporkan pemilik
   7 Sep 2026 ("margin di itemnya hilang, terutama sebelah kiri"); diukur:
   x nama == x kartu walau padding-nya 12px. */
.catalog-card .body { display: block; padding: var(--sp-3); }

/* Nama SELALU memakan dua baris, dipotong kalau lebih.
   
   Tanpa tinggi yang dipatok, nama satu baris dan nama dua baris membuat harga
   dan label ketersediaan di bawahnya berdiri pada ketinggian berbeda — di satu
   baris kartu yang sama. Terukur di katalog produksi: selisihnya 17px, persis
   satu baris teks, di SETIAP baris kartu.
   
   Yang terlihat bukan "kartunya beda tinggi" (kotaknya sendiri sudah sama
   rata karena grid), melainkan harga dan chip yang naik-turun seperti gigi
   gergaji — dan itu yang membuat halaman terbaca berantakan.
   
   Aturan potong dua baris ini sudah ada untuk lebar HP sejak awal; yang
   terlewat justru layar lebar, tempat kolomnya paling banyak dan
   efeknya paling kelihatan. */
.catalog-card .name {
  font-weight: var(--fw-semibold); margin-bottom: 4px;
  font-size: var(--fs-sm); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  /* min-height, bukan height: dua baris DISEDIAKAN, dan nama satu baris
     tetap menempati ruang yang sama supaya yang di bawahnya sejajar. */
  min-height: calc(2 * 1.35em);
}

/* .name TIDAK ikut: ia -webkit-box supaya bisa dipotong dua baris, dan
   mengembalikannya ke block akan membatalkan pemotongannya. */
.catalog-card .price, .catalog-card .avail { display: block; }

.catalog-card .price {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: var(--fs-base); font-weight: var(--fw-semibold);
}

.catalog-card .avail { margin-top: 6px; }


/* Halaman produk etalase: gambar di kiri, panel rincian di kanan. Di layar
   sempit keduanya menumpuk. */
.pd-wrap .panel { flex: 1; min-width: 280px; }


.pd-wrap { display: flex; gap: var(--sp-6); flex-wrap: wrap; margin-bottom: var(--sp-4); }

.pd-wrap .thumb, .pd-wrap .thumb-placeholder { width: 240px; border-radius: var(--radius-md); }


/* Pemilih titik lokasi di halaman Ongkos kirim.
   Penanda DIAM di tengah dan petanya yang bergerak — kursor tidak menutupi
   titik yang sedang dibidik, dan yang terbaca di layar selalu titik yang
   akan tersimpan. */
.peta-pilih { position: relative; margin: 0 var(--sp-3) var(--sp-3); }

.peta-pilih .peta-kanvas {
  height: 320px; border-radius: var(--radius-md); border: 1px solid var(--line);
}


/* Penandanya harus terbaca di atas peta apa pun: jalan terang, sawah hijau,
   maupun blok bangunan abu-abu. Karena itu ia bergaris putih di luar dan
   berwarna merek di dalam — bukan sekadar garis tipis yang hilang begitu
   latarnya ramai. */
.peta-pilih .peta-silang {
  position: absolute; left: 50%; top: 50%; width: 34px; height: 34px;
  margin: -17px 0 0 -17px; pointer-events: none; z-index: 500;
}

.peta-pilih .peta-silang::before,
.peta-pilih .peta-silang::after {
  content: ""; position: absolute; background: var(--brand);
  box-shadow: 0 0 0 2px rgba(255,255,255,.95);
}

.peta-pilih .peta-silang::before { left: 50%; top: 0; width: 3px; height: 100%; margin-left: -1.5px; }

.peta-pilih .peta-silang::after  { top: 50%; left: 0; height: 3px; width: 100%; margin-top: -1.5px; }


/* Keterangan cara pakainya menempel di petanya sendiri, bukan di bawahnya:
   yang perlu tahu adalah orang yang sedang menatap peta.
   Ditaruh di KANAN BAWAH, di atas baris atribusi. Bagian atas peta adalah
   tempat daftar hasil pencarian turun, dan apa pun yang menetap di sana akan
   tertutup persis saat orang sedang mencari. */
.peta-pilih .peta-ket {
  position: absolute; right: 10px; bottom: 34px; z-index: 500; max-width: 60%;
  background: rgba(255,255,255,.92); color: #16100f;
  padding: 5px 9px; border-radius: var(--radius-sm, 6px);
  font-size: var(--fs-xs); pointer-events: none;
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
}


/* Pencarian nama tempat di halaman Ongkos kirim. */
.cari-tempat { position: relative; display: flex; gap: var(--sp-2); margin: 0 var(--sp-3) var(--sp-2); }

.cari-tempat input { flex: 1; margin: 0; }

.cari-hasil {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 600;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-md); margin-top: 4px; overflow: hidden;
  box-shadow: 0 4px 14px rgba(0,0,0,.12); max-height: 260px; overflow-y: auto;
  padding: var(--sp-2); font-size: var(--fs-xs); color: var(--text-2);
}

.cari-hasil button {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 8px 10px; font-size: var(--fs-xs); color: var(--text);
  border-radius: var(--radius-sm, 6px); cursor: pointer;
}

/* color ikut ditulis, alasan yang sama dengan .pilih-opsi di bawah:
   `button:hover` memutihkan teks setiap <button> yang disorot. */
.cari-hasil button:hover { background: var(--surface-2); color: var(--text); }


.cari-hasil-kepala {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); padding: 2px 10px 6px; color: var(--text-3);
  border-bottom: 1px solid var(--line); margin-bottom: 4px;
}

.cari-hasil .cari-tutup {
  width: auto; padding: 3px 10px; background: none; border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px); color: var(--text-2);
  font-size: var(--fs-xs); cursor: pointer;
}

.cari-hasil .cari-tutup:hover { background: var(--surface-2); color: var(--text); }


/* Disembunyikan dari mata TAPI tetap di dalam form dan tetap bisa menerima
   fokus — bukan display:none, karena klik pada teks <label> harus tetap
   mendarat di sini supaya bisa diteruskan ke tombolnya. */
/* Pembungkus yang dibuat select.js. Tanpa aturan ini ia sebuah <span>
   biasa — jadi inline — sementara pemicu di dalamnya width:100%. Yang
   melebar mengikuti induknya bukan pembungkusnya, dan .tumbuh yang
   dipasang di label tidak pernah sampai ke pemicunya. */
.pilih { display: block; position: relative; min-width: 0; }

select.pilih-asli {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  opacity: 0; pointer-events: none;
}


/* Nilainya SAMA PERSIS dengan aturan isian di bagian ISIAN — bukan mirip.
   
   Sebelumnya pemicu ini punya geometrinya sendiri: padding 9px dan
   margin-top 5px. Hasilnya 38px, sementara setiap <input>, <select>, dan
   tombol di sebelahnya 32px. Dalam satu .bilah yang dirata-bawahkan,
   selisih 6px itu terlihat sebagai dropdown yang menyembul lebih tinggi
   dari kotak di kirinya — di Produk, Pesanan, Supplier, Penerimaan,
   Transaksi, Pelanggan, Harga massal, dan Sinkronisasi sekaligus.
   
   margin-top 5px juga peninggalan: dulu label tidak memberi jarak sendiri.
   Sekarang label sudah flex-column dengan gap 4px, jadi margin itu menjadi
   jarak KEDUA — dan itulah yang membuat pemicunya turun sendirian. */
.pilih-pemicu {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  width: 100%; min-height: var(--sasaran); padding: 0 10px;
  border: var(--tebal-garis) solid var(--garis-3); border-radius: var(--lengkung);
  font: inherit; font-size: var(--t-sm); font-weight: 400;
  color: var(--tinta); background: var(--kartu); text-align: left; cursor: pointer;
}

.pilih-pemicu:hover:not(:disabled) { border-color: var(--line-3, var(--line-2)); }

.pilih-pemicu:focus-visible { outline: 2px solid var(--text); outline-offset: -1px; }

.pilih-pemicu[aria-expanded="true"] { border-color: var(--brand); }

.pilih-pemicu:disabled { color: var(--text-3); cursor: not-allowed; background: var(--surface-2); }

.pilih-teks { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pilih-panah {
  flex: none; width: 0; height: 0; border-left: 4px solid transparent;
  border-right: 4px solid transparent; border-top: 5px solid var(--text-3);
}


/* position:fixed dan ditempel ke <body>: toolbar ada di dalam .panel dan
   .scroll-x yang punya overflow, dan daftar absolut akan terpotong justru
   ketika ia paling panjang. */
.pilih-daftar {
  position: fixed; z-index: 900; overflow-y: auto; max-height: 260px;
  /* Sampai di ujung daftar, gulungannya BERHENTI — tidak diteruskan ke
     halaman di belakangnya, yang akan menutup daftarnya. */
  overscroll-behavior: contain;
  padding: var(--sp-1); background: var(--surface);
  border: 1px solid var(--line-2); border-radius: var(--radius-md);
  box-shadow: 0 6px 20px rgba(0,0,0,.16);
}

.pilih-opsi {
  display: block; width: 100%; padding: 7px var(--sp-3); border: 0;
  border-radius: var(--radius-sm); background: none; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); color: var(--text);
  text-align: left; white-space: nowrap;
}

.pilih-opsi:disabled { color: var(--text-3); cursor: not-allowed; }


/* Baris yang disaring keluar harus BENAR-BENAR hilang.
   
   Penyaringnya memasang atribut `hidden`, dan browser menyembunyikan
   [hidden] lewat `display: none` di stylesheet bawaannya. Tapi aturan
   `.pilih-opsi { display: block }` di atas berasal dari stylesheet penulis,
   dan stylesheet penulis selalu menang atas bawaan browser — jadi `hidden`
   terpasang tanpa efek apa pun.
   
   Akibatnya kotak cari tidak menyaring apa-apa: mengetik "Gurame" di daftar
   1.579 produk tetap menampilkan 1.579 baris. Sisi JS-nya sudah benar sejak
   awal (opsiTampak menyaring !el.hidden, jadi panah atas-bawah memang
   melompati yang tersaring) — yang bohong cuma yang terlihat di layar. */
.pilih-opsi[hidden] { display: none; }


/* ══ Panel geser ══════════════════════════════════════════════════════
   Satu panel untuk melihat detail sekaligus mengubah. Digambar di server
   dan keadaannya ada di URL (?buka=), jadi ia bertahan saat halaman dimuat
   ulang dan tetap bekerja tanpa JavaScript — termasuk menutupnya, yang
   cuma sebuah tautan kembali ke daftar. */

.geser-latar { position: fixed; inset: 0; z-index: 800; display: flex; }

/* Tirai adalah <a>, bukan <div> dengan onclick: menutup panel harus bisa
   dilakukan tanpa JavaScript sama sekali. */
.geser-tirai {
  position: absolute; inset: 0; background: rgba(0,0,0,.34);
  animation: geser-pudar .16s ease-out;
}

/* Bentuk panel melayang. Ditulis sebagai `.geser-latar .geser` supaya
   spesifisitasnya di atas `.geser` polos milik v6, dan supaya `.geser` yang
   dipakai sebagai kartu biasa di tempat lain tidak ikut berubah.

   z-index: 1 yang menyelesaikan masalahnya. Tanpa itu panel `static` dan
   tirai `absolute`, dan elemen berposisi SELALU digambar di atas yang tidak
   — berapa pun urutan DOM-nya. */
.geser-latar .geser {
  position: relative; z-index: 1;
  margin-left: auto; display: flex; flex-direction: column;
  width: min(460px, 92vw); height: 100%; overflow-y: auto;
  border: 0; border-left: var(--tebal-garis) solid var(--garis-2);
  border-radius: 0;
  box-shadow: -6px 0 28px rgba(0,0,0,.18);
  animation: geser-masuk .18s ease-out;
}

/* Kedua animasi ini ikut terbuang bersama aturan lain yang selektornya
   tanpa kelas. `geser-pudar` bahkan masih DIRUJUK tirai di atas tanpa
   pernah didefinisikan — animasi yang tidak ada tidak menimbulkan galat,
   ia cuma diam. */
@keyframes geser-masuk { from { transform: translateX(16px); opacity: .6; } }
@keyframes geser-pudar { from { opacity: 0; } }

.geser-silang {
  flex: none; font-size: var(--fs-xl); line-height: 1; color: var(--text-3);
  text-decoration: none; padding: 0 var(--sp-1);
}

.geser-silang:hover { color: var(--text); text-decoration: none; }


/* Daftar fakta yang tidak bisa diubah dari panel ini. */
.fakta {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-1) var(--sp-4);
  margin: 0 0 var(--sp-5); font-size: var(--fs-sm);
}

.fakta dt { color: var(--text-2); font-size: var(--fs-xs); font-weight: var(--fw-semibold); }

.fakta dd { margin: 0; color: var(--text); }


/* ══ Halaman Pengaturan ═══════════════════════════════════════════════ */
/* Di sini dulu ada tiga aturan `.panel .pilih-tema ...`, lengkap dengan
   komentar yang menjelaskan bahwa merekalah yang benar-benar berlaku.
   Itu sudah tidak benar sejak panel diberi nama .bagian: `.panel` tidak
   ada di satu templat pun, jadi ketiganya tidak pernah cocok dengan apa
   pun — termasuk saat komentarnya ditulis. */

.facts-tema { display: grid; grid-template-columns: auto 1fr; gap: 7px var(--s4);
              margin: var(--s4) 0 0; font-size: var(--t-sm); }

.facts-tema dt { font-weight: 700; color: var(--tinta); }

.facts-tema dd { margin: 0; color: var(--tinta-2); }


.tautan-setelan { list-style: none; margin: 0; padding: 0; }

.tautan-setelan li { padding: var(--sp-3) 0; border-bottom: 1px solid var(--line); }

.tautan-setelan li:last-child { border-bottom: 0; }

.tautan-setelan a { font-weight: var(--fw-semibold); }

.tautan-setelan span { display: block; font-size: var(--fs-xs); color: var(--text-2); margin-top: 2px; }


/* Baris kedua di dalam sel tabel (SKU di bawah nama produk).
   Pada kepadatan "padat" ia disembunyikan: tinggi baris tidak bisa turun
   di bawah dua baris teks, jadi tanpa ini pilihan "padat" cuma jadi tombol
   yang tidak mengubah apa pun — dan itu terbukti waktu diukur di browser
   (padat 45px, sama persis dengan standar). Identitasnya tidak hilang:
   SKU tetap bisa dicari dan tetap tampil di kepadatan lain. */
.baris-dua { font-size: var(--fs-2xs); line-height: 1.35; }

[data-padat="padat"] .baris-dua { display: none; }


/* ── Bar atas ────────────────────────────────────────────────────────
   Tempelnya di atas (sticky) supaya "di mana saya" tetap terjawab setelah
   menggulung tabel seribu baris. */
.top {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-5); min-height: 52px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: saturate(180%) blur(8px);
  border-bottom: 1px solid var(--line);
}

.crumb {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-sm); color: var(--text-3); min-width: 0;
}

.crumb b { color: var(--text); font-weight: var(--fw-semibold); }


.omni {
  margin-left: auto; display: flex; align-items: center; gap: var(--sp-2);
  min-width: 240px; max-width: 340px; flex: 0 1 auto;
  height: 34px; padding: 0 var(--sp-3);
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--radius-md); color: var(--text-3);
}

.omni:focus-within { border-color: var(--text-3); }

.omni input {
  flex: 1; min-width: 0; border: 0; background: none; padding: 0;
  font: inherit; font-size: var(--fs-sm); color: var(--text); height: 100%;
}

.omni input:focus { outline: none; }


.env {
  flex: none; font: var(--fw-semibold) var(--fs-2xs)/1 var(--sans);
  letter-spacing: .07em; text-transform: uppercase;
  padding: 5px 9px; border-radius: var(--radius-full); border: 1px solid currentColor;
}

.env-prod { color: var(--text-3); }

.env-dev  { color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); }


/* Bar atas sudah memberi jarak sendiri, jadi isinya tidak perlu jarak atas
   sebesar dulu. */
.top + main > .wrap:first-child,
.top + .flash + main > .wrap:first-child { padding-top: var(--sp-4); }


/* ── Ubin Ringkasan ──────────────────────────────────────────────────
   Kartu terpisah, bukan satu bar bersambung: tiap angka punya kotaknya
   sendiri supaya bisa dibaca satu per satu tanpa mata menghitung kolom. */
.kpi-row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--sp-3); margin-bottom: var(--sp-3);
}

.kpi {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--lift-1);
  padding: var(--sp-3) var(--sp-4); min-width: 0;
}

/* .ubin, bukan .kpi: ubin angka berganti nama saat lembar ini dirakit ulang
   dan ketiga aturan ini tertinggal pada nama lamanya. Yang terlihat di
   Ringkasan dan Penerimaan: satuan "baris" menempel tanpa jarak pada
   angkanya, dan baris kaki ubin menumpuk menegak alih-alih berjajar. */
.ubin small { margin-left: 6px; font-size: var(--t-sm); color: var(--tinta-3); }

.ubin .v-tautan { text-decoration: none; display: inline-block; }

.ubin .kaki { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }


/* Antrean kerja TIDAK memakai batang kegentingan di tepi kiri.
   
   Aturan tabel umum memberi tiap baris crit/warn batang 3px berwarna, dan
   di sini seluruh tujuh barisnya kena — jadi warnanya tidak lagi
   membedakan apa pun, cuma menambah satu bidang jingga di sepanjang kartu.
   Rancangan v5 tidak memakainya sama sekali di antrean; kegentingannya
   dibawa ikon dan kalimatnya sendiri ("habis — tidak bisa dijual").
   
   Dibatasi pada baris antrean saja: di tabel lain batang itu masih berguna
   karena hanya sebagian kecil barisnya yang genting. */
tr:has(.antre) td:first-child::before { content: none; }

/* Tinggi sel dilepas dari kepadatan tabel lewat sel-nya, bukan lewat flex. */
tr:has(.antre) td { height: auto; padding: 11px var(--sp-3); }

.tanda {
  flex: none; width: 26px; height: 26px; border-radius: var(--radius-sm);
  display: grid; place-items: center; margin-top: 1px;
  background: var(--surface-2); color: var(--text-3); border: 1px solid var(--line);
}

/* Kotak ikonnya netral. Barisnya sudah punya garis tepi kiri berwarna
   untuk kegentingan; mewarnai ikonnya juga berarti mengatakan hal yang
   sama dua kali, dengan ongkos satu bidang warna lagi di layar. */

/* ── Kesehatan sistem ────────────────────────────────────────────────── */
.kolom-kanan { display: grid; gap: var(--sp-3); align-content: start; min-width: 0; }

.sehat { padding: var(--sp-3) var(--sp-4); display: grid; gap: 10px; }

.sehat .baris { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }

.pil {
  flex: none; font: var(--fw-semibold) var(--fs-2xs)/1 var(--sans);
  padding: 5px 9px; border-radius: var(--radius-full); border: 1px solid currentColor;
}


/* Lencana angka di menu. Ditaruh paling kanan supaya labelnya tetap rata
   kiri dengan menu lain — angka yang menempel pada teks membuat panjang
   tiap baris menu berbeda-beda dan sulit dipindai. */
.side .tally {
  margin-left: auto; font-family: var(--mono); font-size: var(--fs-2xs);
  padding: 2px 7px; border-radius: var(--radius-full); line-height: 1.5;

  /* Warnanya DIBALIK dari pasangan warna sidebar, bukan merah.
     
     Percobaan pertama memakai latar merah 70% dengan teks --side-on, dan
     kontrasnya jatuh di dua tema sekaligus: 3,84 di tema terang (yang
     sidebarnya justru TERANG, jadi --side-on di sana gelap) dan 2,93 di
     tema malam (merahnya di sana muda). Merah tidak bisa aman di dua arah
     sekaligus tanpa satu token khusus per tema.
     
     Pasangan --side-on / --side-bg adalah kontras tertinggi yang dimiliki
     sidebar di tema mana pun — dan lencana ini sudah cukup menonjol karena
     bentuknya pil terisi, tanpa perlu warna. */
  background: var(--side-on); color: var(--side-bg);
}


/* Kotak cari di dalam dropdown. Menempel di atas supaya tetap terlihat
   waktu daftarnya digulung — kalau ikut hanyut, orang kehilangan tempat
   mengetik persis saat ia sedang mencari. */
.pilih-cari {
  position: sticky; top: 0; z-index: 1;
  width: 100%; margin-bottom: var(--sp-2); padding: 7px var(--sp-3);
  border: 1px solid var(--line-3); border-radius: var(--radius-sm);
  font: inherit; font-size: var(--fs-sm); color: var(--text);
  background: var(--surface);
}

.pilih-kosong {
  padding: 10px var(--sp-3); font-size: var(--fs-sm); color: var(--text-2);
}


/* Dua kolom isian bersanding. Modal dan harga jual selalu diisi bersama;
   menaruhnya berjajar membuat hubungan itu terlihat tanpa perlu dijelaskan. */
.dua-kolom { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }


/* Umur pesanan. Angka, jadi mono — dan hanya yang sudah lama diberi warna:
   kalau semuanya berwarna, warnanya berhenti berarti apa-apa. */
.umur { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-2); }

/* ═══════════════════════════════════════════════════════════════════════
   ELEMEN POLOS — <label> dan <button> tanpa kelas
   ═══════════════════════════════════════════════════════════════════════

   Mockup tidak pernah menulis satu pun <label> atau <button> polos: setiap
   isian di sana adalah `label.bidang` dan setiap tombol adalah `.tbl`.
   Aplikasi menulis 117 label polos dan 13 tombol polos.

   Yang salah bukan templatnya. Menambahkan class="bidang" 117 kali adalah
   117 kesempatan salah ketik untuk hasil yang, kalau benar semua, sama
   persis dengan aturan di bawah ini. Jadi elemen polosnya yang diberi
   perlakuan v6 — bukan gaya kedua yang mirip, melainkan nilai yang SAMA,
   ditulis sekali. */

main label:not(.bidang):not(.buka-laci):not(.laci),
.geser label:not(.bidang) {
  display: flex; flex-direction: column; gap: 4px;
  font-size: var(--t-sm); font-weight: 700; color: var(--tinta-2);
}
/* Label yang membungkus kotak centang atau radio TIDAK boleh jadi kolom:
   kotaknya harus berdiri di samping tulisannya, bukan di atasnya.

   :not() yang sama diulang di sini BUKAN karena perlu menyaring apa pun —
   ketiganya sudah pasti benar — melainkan supaya spesifisitasnya menang.
   Versi pertama aturan ini tidak pernah berlaku sekali pun: :has() memakai
   spesifisitas argumen terkuatnya, jadi selektornya (0,2,3) kalah dari
   aturan dasar di atas yang (0,3,2). Yang terlihat: kotak centang berdiri
   sendirian di tengah dengan tulisannya di bawah, di form Produk. */
main label:not(.bidang):not(.buka-laci):not(.laci):has(> input[type=checkbox]),
main label:not(.bidang):not(.buka-laci):not(.laci):has(> input[type=radio]) {
  flex-direction: row; align-items: center; gap: var(--s2);
  font-weight: 400; color: var(--tinta);
}

/* Kotak centang dan radio bawaan cuma 13x13 px — jauh di bawah ambang
   24x24 WCAG 2.2 SC 2.5.8, dan terlalu kecil untuk ditekan jari di layar
   sentuh gudang.

   Aturan ini ada di lembar gaya sebelumnya dan ikut terbuang saat lembar
   ini dirakit ulang, karena perakitnya membuang setiap aturan yang
   selektornya tanpa kelas. Pengukur pun tidak menangkapnya: pemeriksa
   sasaran sentuh justru MENGECUALIKAN checkbox dan radio. */
input[type=checkbox], input[type=radio] {
  width: 18px; height: 18px; flex: none; accent-color: var(--merah-isi);
  /* 18px yang terlihat, 24px yang bisa ditekan: memperbesar kotaknya
     sendiri sampai 24px membuatnya tampak kasar, sementara sasaran
     sentuhnya tetap harus 24px. */
  outline-offset: 3px; margin: 3px;
}

main button:not([class]),
main button[type=submit]:not([class]),
.geser button:not([class]) {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: var(--sasaran); padding: 0 var(--s4);
  border-radius: var(--lengkung);
  border: var(--tebal-garis) solid var(--garis-3);
  font: inherit; font-size: var(--t-sm); font-weight: 700;
  background: var(--kartu); color: var(--tinta);
  cursor: pointer; white-space: nowrap;
}
main button:not([class]):hover,
.geser button:not([class]):hover { background: var(--kartu-2); border-color: var(--tinta); }
/* .link dipakai untuk tindakan yang bentuknya tautan, bukan tombol. */
main button.link {
  display: inline-flex; align-items: center; min-height: var(--sasaran);
  background: none; border: 0; color: var(--tinta-2);
  font: inherit; font-size: var(--t-sm); padding: 0 var(--s2); cursor: pointer;
  text-decoration: underline; text-decoration-color: var(--garis-2);
  text-underline-offset: 2px;
}
main button.link:hover { color: var(--tinta); }
main button:disabled { opacity: .45; cursor: not-allowed; }

/* ── Kepala tabel yang lengket ─────────────────────────────────────────
   Di mockup, tabel menggulung bersama HALAMAN, jadi kepalanya berhenti 52px
   dari atas — tepat di bawah bar atas yang juga lengket.

   Di aplikasi tabel dibungkus .gulung yang punya overflow-x, dan menurut
   spesifikasi CSS itu menjadikannya wadah gulung untuk KEDUA sumbu. Acuan
   `top` berubah dari jendela menjadi kotak itu sendiri, sehingga kepala
   tabel turun 52px ke dalam dan menimpa baris pertamanya. Terlihat jelas di
   daftar Produk: nama produk pertama tertutup separuh oleh kepala kolom. */
.gulung thead th { top: 0; }

/* Satuan di samping angka besar butuh jarak. Tanpa ini "1.280" dan "SKU"
   menempel jadi "1.280SKU" — terbaca sebagai satu kata, bukan angka
   beserta satuannya. */
.ubin .v .unit { margin-left: 3px; }

/* ── Lebar isi ─────────────────────────────────────────────────────────
   Tabel boleh selebar layar; prosa tidak. Baris teks sepanjang 1.400px
   membuat mata kehilangan awal baris berikutnya. */
main > .bagian > p, main > p.sub, .petunjuk, .kosong p { max-width: 78ch; }

/* ── Kolom isi tidak boleh melebar mengikuti isinya ────────────────────
   Anak grid punya `min-width: auto` secara bawaan, artinya ia menolak
   menyempit di bawah lebar isi terlebarnya. Tabel selebar 426px di jendela
   420px karena itu MENDORONG kolomnya, bukan menggulung di dalam .gulung —
   dan seluruh halaman ikut bergeser mendatar.

   Terukur di /: luapan 41px pada lebar 420px. Tidak terlihat di mockup
   karena di sana cangkangnya hidup di dalam bingkai berlebar tetap. */
.cangkang > div { min-width: 0; }

/* ═══════════════════════════════════════════════════════════════════════
   LAYAR SEMPIT — komponen aplikasi
   ═══════════════════════════════════════════════════════════════════════

   Inti v6 sudah mengurus kerangkanya sendiri (sidebar jadi laci, ubin jadi
   satu kolom) di blok @container-nya. Yang di bawah ini komponen yang tidak
   dipunyai mockup, jadi tidak ada yang mengurusnya di sana.

   Ditulis dengan @container, bukan @media, mengikuti inti — yang menentukan
   adalah lebar cangkang, dan itulah yang diukur. */

@container cangkang (max-width: 900px) {
  /* Dua kolom jadi satu. Tanpa ini keduanya bertahan 291px berdampingan di
     jendela 420px dan mendorong halaman 41px keluar layar — terukur di
     Ringkasan, bukan dikira-kira. */
  .dua-kolom { grid-template-columns: 1fr; }

  /* Anak grid menolak menyempit di bawah lebar isi terlebarnya kecuali
     diberi tahu. Tabel di dalamnya sudah punya .gulung sendiri; yang perlu
     dipastikan cuma kolomnya boleh menyempit. */
  .dua-kolom > * { min-width: 0; }

  /* Kotak cari di bar atas melepas lebar tetapnya. 300px di jendela 420px
     tidak menyisakan ruang untuk remah-jejak maupun tombol laci. */
  .cari-atas { flex: 1 1 120px; width: auto; min-width: 0; }
  .cari-atas kbd.pintas { display: none; }

  /* Kepala halaman menumpuk: judul di atas, tombolnya di bawah — bukan
     saling berebut satu baris. */
  .kepala { flex-wrap: wrap; }
  .kepala .aksi { margin-left: 0; width: 100%; }

  /* Lencana lingkungan dilepas di layar sempit. Ia yang PALING TIDAK
     mendesak di bar atas — kabar tentang server, bukan tentang pekerjaan —
     dan lebarnya 134px mendorong bar keluar layar sejauh 37px pada jendela
     420px. Nilainya tetap terbaca di kaki sidebar bersama versi. */
  .atas .lingkungan { display: none; }
}

/* Halaman masuk hidup DI LUAR .cangkang (tanpa sesi, tanpa sidebar), jadi
   tidak ada wadah yang bisa dikuerikan. Di sini @media memang alat yang
   benar, bukan pilihan yang kalah. */
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; min-height: auto; }
  .split-brand { padding: var(--s6); min-height: 200px; }
  .split-form { padding: var(--s6); }
}

/* Panel geser ikut aturan gerak yang sama dengan inti v6: orang yang
   memilih mengurangi gerakan tidak boleh tetap kena animasi masuk. */
@media (prefers-reduced-motion: reduce) {
  .geser, .geser-tirai { animation: none; }
}

/* Baris versi di kaki sidebar. "pengembangan · skema 21" tidak muat di 216px
   pada ukuran mono normal, dan pecah jadi dua baris dengan "21" sendirian. */
.sisi .versi { font-size: 10px; letter-spacing: -0.02em; }

/* ═══════════════════════════════════════════════════════════════════════
   HALAMAN MASUK — layar 01 rancangan v6
   ═══════════════════════════════════════════════════════════════════════

   Isian di KIRI, alasan memakainya di kanan. Versi sebelumnya menaruh panel
   merek di kiri dan formnya di kanan tanpa dipusatkan menegak, jadi isian
   yang harus dikerjakan orang justru yang paling jauh dari titik baca
   pertama, dan menggantung di sepertiga atas layar. */

.masuk {
  display: grid; grid-template-columns: 1fr 1fr;
  /* dvh, bukan vh: di peramban HP, vh memakai tinggi layar SEBELUM bar
     alamat menyusut, jadi halaman selalu sedikit lebih tinggi dari
     jendelanya dan memunculkan batang gulung menegak yang tidak perlu. */
  min-height: 100dvh;
}
.masuk-isian {
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--s7) var(--s6);
  background: var(--kertas);
}
/* margin-inline:auto — tanpa ini kolomnya memang membentang setengah layar
   tapi isinya menempel di tepi kiri, jadi di layar 1440px formulirnya duduk
   304px dari tengah kolomnya sendiri. Yang terlihat: halaman masuk yang
   berat sebelah ke kiri. */
.masuk-isian > * { max-width: 360px; width: 100%; margin-inline: auto; }
.masuk-merek { margin-bottom: var(--s5); }
.masuk-merek img { width: 148px; height: auto; display: block; }
.masuk-isian h1 { margin-bottom: 2px; }
.masuk-isian .sub { margin-bottom: var(--s5); font-size: var(--t-sm); }
.masuk-form { display: grid; gap: var(--s3); }
.masuk-form .block { width: 100%; }
.masuk-isian .versi { padding-left: 0; margin-top: var(--s6); }

/* Panel kanan dipatok gelap di KEDUA tema — nilainya tidak diturunkan dari
   skala netral yang membalik, karena versi yang menurunkannya pernah tampil
   putih dengan tulisan putih (rasio 1,00). */
.masuk-panel {
  background: var(--brand-panel); color: var(--brand-panel-text);
  padding: var(--s7) var(--s6);
  display: flex; flex-direction: column; justify-content: center; gap: var(--s6);
  position: relative; overflow: clip;
}
.masuk-panel > * { position: relative; z-index: 1; max-width: 30ch; }
.masuk-panel h2 { font-size: var(--t-2xl); line-height: 1.25; margin-bottom: var(--s3); }
.masuk-panel p { font-size: var(--t-sm); color: var(--brand-panel-dim); line-height: 1.6; }
/* Garis-garis barcode. Diambil dari benda yang memang dihasilkan sistem ini. */
.masuk-panel::before {
  content: ""; position: absolute; inset: 0; z-index: 0; opacity: .5;
  background: repeating-linear-gradient(90deg,
    rgba(255,255,255,.10) 0 2px, transparent 2px 5px,
    rgba(255,255,255,.06) 5px 6px, transparent 6px 11px);
}
.masuk-angka { display: flex; gap: var(--s6); max-width: none; }
.masuk-angka .v { font-size: var(--t-xl); font-weight: 700; }
.masuk-angka .k { font-size: var(--t-xs); color: var(--brand-panel-dim); }

/* Di layar sempit panelnya turun ke bawah isian: yang harus dikerjakan tetap
   yang pertama terlihat, dan panel tidak memakan separuh layar HP. */
@media (max-width: 860px) {
  .masuk { grid-template-columns: 1fr; min-height: 100dvh; }
  .masuk-isian { padding: var(--s6) var(--s5); }
  .masuk-isian > * { max-width: 420px; }
  .masuk-panel { padding: var(--s6) var(--s5); gap: var(--s4); }
  .masuk-panel h2 { font-size: var(--t-xl); }
}

/* ── Irama menegak ─────────────────────────────────────────────────────
   Diatur di SATU tempat, bukan lewat margin di tiap komponen.

   Aturan ini sempat hilang saat lembar gaya dirakit ulang: perakitnya
   membuang setiap aturan yang selektornya tanpa kelas, dan `main > * + *`
   termasuk. Yang terjadi bukan "agak rapat" — seluruh blok di setiap
   halaman menempel tanpa jarak sama sekali, paling terlihat di form
   pendaftaran pelanggan yang isinya memang tumpukan isian. */
main > * + * { margin-top: var(--s5); }

/* Di dalam form MENEGAK, jarak antar isian diatur di wadahnya — sekali,
   untuk semua anaknya — bukan ditambal per pasangan elemen.

   Versi pertama aturan ini menyebutkan pasangannya satu per satu
   (label + label, label + .petunjuk, ...). Yang terjadi: begitu ada anak
   yang bukan label — di form Produk ada <div> berisi dua kolom Modal dan
   Harga jual — pasangannya tidak disebut, jarak hilang, dan keterangan dua
   kolom itu menempel persis ke label berikutnya. Daftar pasangan selalu ada
   yang terlewat; wadahnya tidak.

   Form MENDATAR dikecualikan: .bilah dan kawan-kawan memakai flex, dan
   margin-top di sana justru merusak perataannya. */
main form:not([class]) > * + *,
main form.bagian > * + *,
form.masuk-form > * + * { margin-top: var(--s3); }

/* Keterangan menempel pada isian di atasnya, bukan berdiri sendiri. */
main form:not([class]) > .petunjuk,
main form:not([class]) > label + .petunjuk { margin-top: 4px; }

/* Kartu isian yang berdiri sendiri (masuk/daftar pelanggan, ubah sandi).
   Isinya SELALU tumpukan menegak, jadi jaraknya diatur wadahnya sekali —
   bukan ditambal margin per elemen yang selalu ada yang terlewat. */
.kartu form, form.kartu, form.card { display: grid; gap: var(--s3); }
.kartu form .petunjuk, form.kartu .petunjuk, form.card .petunjuk { margin-top: calc(var(--s3) * -1 + 2px); }

/* `.err` masih dipakai 17 kali di templat sebagai pesan galat sebaris —
   peninggalan kosakata lama yang tidak ikut berganti saat kelas diseragamkan
   ke v6. Diberi gaya di sini, BUKAN diganti namanya di tujuh belas tempat:
   yang penting galatnya terbaca sebagai galat, dan penggantian nama massal
   sudah sekali ini terbukti menyeret cacat sendiri.

   Nilainya sama persis dengan .pesan.crit — bukan warna kedua yang mirip. */
.err {
  color: var(--crit); font-size: var(--t-sm);
}
p.err, div.err {
  display: block; padding: var(--s3) var(--s4);
  border: var(--tebal-garis) solid var(--crit-garis);
  border-radius: var(--lengkung);
  background: var(--crit-bg);
  margin-bottom: var(--s4);
}

/* ═══════════════════════════════════════════════════════════════════════
   ETALASE DI LAYAR HP — disamakan dengan aplikasi pelanggan
   ═══════════════════════════════════════════════════════════════════════

   Pembeli yang sama membuka toko ini lewat dua pintu: aplikasi di HP-nya,
   dan tautan WhatsApp yang terbuka di peramban. Kalau keduanya terasa
   seperti dua toko berbeda, yang satu selalu terasa "versi seadanya".

   Yang disamakan bukan tema (itu sudah lewat token v6), melainkan
   BENTUKNYA: dua kolom, kartu ringkas, chip kategori yang digeser, dan bar
   bawah yang tetap di tempatnya.

   Kerapatan `sentuh` dipasang di sini juga — sasaran 44px, huruf tidak
   lebih kecil dari 12. Yang memegangnya satu ibu jari, bukan tetikus. */

@media (max-width: 640px) {
  :root {
    --t-xs: 12px; --t-sm: 13.5px; --t-dasar: 15px; --t-lg: 17px;
    --sasaran: 44px; --lengkung: 8px;
  }

  /* Dua kolom, bukan satu.
     
     Ambang lama 180px: pada layar 390px, setelah padding halaman, ruangnya
     342px — dan dua kolom 180px butuh 376px. Jadi selalu jatuh ke SATU
     kolom dengan kartu setinggi setengah layar, sementara aplikasi
     menampilkan empat produk sekaligus. */
  .catalog-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s3);
  }
  .catalog-card .body { padding: var(--s3); }
  .catalog-card .name {
    /* Nama panjang dipotong dua baris, supaya tinggi kartu tidak berayun
       dan barisnya tetap rata — persis seperti di aplikasi. */
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; min-height: 2.7em;
  }
  .catalog-card .price { font-size: var(--t-sm); }

  /* Judul halaman dilepas. Di layar sekecil ini ia memakan seperempat
     layar pertama untuk mengatakan hal yang sudah jelas dari isinya —
     aplikasi pun tidak punya judul, ia langsung membuka dengan kotak cari. */
  main > .kepala { display: none; }
  main { padding: var(--s3); }
  .bagian > form.bilah { padding: 0 0 var(--s3); }

  /* Bar atas: merek mengecil, keranjang jadi ikon di kanan. */
  .bar { gap: var(--s3); padding: 0 var(--s4); height: 56px; }
  .bar .brand img { height: 26px; width: auto; }
  .bar nav { display: none; }        /* pindah ke bar bawah */
  .bar .right { margin-left: auto; font-size: var(--t-sm); }

  /* Ruang untuk bar bawah, supaya isi terakhir tidak tertutup. */
  body { padding-bottom: 68px; }
}

/* ── Chip kategori yang digeser ────────────────────────────────────────
   Menggantikan dropdown + tombol Cari. Dropdown menyembunyikan pilihannya
   di balik satu ketukan dan satu layar penuh daftar; chip memperlihatkan
   kategori yang ada sekaligus, dan itu yang dilakukan aplikasi. */
.chip-kategori {
  display: flex; gap: var(--s2); overflow-x: auto; padding-bottom: var(--s2);
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.chip-kategori::-webkit-scrollbar { display: none; }
.chip-kategori a {
  flex: none; display: inline-flex; align-items: center;
  min-height: var(--sasaran); padding: 0 var(--s4);
  border: var(--tebal-garis) solid var(--garis-3); border-radius: 99px;
  background: var(--kartu); color: var(--tinta);
  font-size: var(--t-sm); font-weight: 700; text-decoration: none;
  white-space: nowrap;
}
.chip-kategori a.on {
  background: var(--hijau-isi); border-color: var(--hijau-isi); color: var(--pada-hijau);
}

/* ── Bar bawah ─────────────────────────────────────────────────────────
   Tiga tujuan yang sama dengan aplikasi: Belanja, Pesanan, Akun.
   Keranjang TIDAK jadi tab — ia ikon di bar atas dengan jumlahnya, supaya
   tetap terjangkau dari layar mana pun termasuk halaman detail. Keputusan
   yang sama dengan aplikasi (7 Agu 2026). */
.bar-bawah { display: none; }

@media (max-width: 640px) {
  .bar-bawah {
    display: flex; position: fixed; inset: auto 0 0 0; z-index: 60;
    background: var(--kartu); border-top: var(--tebal-garis) solid var(--garis);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .bar-bawah a {
    flex: 1; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 2px; min-height: 60px;
    color: var(--tinta-2); text-decoration: none;
    font-size: var(--t-xs); font-weight: 700;
  }
  .bar-bawah a svg { width: 21px; height: 21px; }
  .bar-bawah a.on { color: var(--merah); }
}

/* Ikon keranjang dengan jumlahnya, di bar atas. */
.keranjang-ikon { position: relative; display: inline-flex; align-items: center;
  min-height: var(--sasaran); padding: 0 var(--s2); color: var(--tinta); }
.keranjang-ikon svg { width: 22px; height: 22px; }
.keranjang-ikon .n {
  position: absolute; top: 4px; right: 0;
  background: var(--merah-isi); color: var(--pada-merah);
  font-size: 10px; font-weight: 700; line-height: 1;
  padding: 2px 5px; border-radius: 99px;
}

/* Dropdown kategori dan chip TIDAK pernah tampil bersamaan: keduanya
   melakukan hal yang sama, dan dua kontrol untuk satu pekerjaan membuat
   orang ragu mana yang berlaku. */
.chip-kategori { display: none; }
@media (max-width: 640px) {
  .chip-kategori { display: flex; }
  .pilih-kategori { display: none; }
  /* Tombol Cari juga dilepas: chip sudah menyaring saat diketuk, dan
     kotak cari mengirim sendiri saat Enter ditekan. */
  .bagian > form.bilah > button[type=submit] { display: none; }
}

/* Dropdown kategori disembunyikan di HP dengan rantai :not() yang SAMA
   seperti aturan label polos di atas.

   Bukan gaya berlebihan: aturan label polos itu (0,3,2), dan `.pilih-kategori`
   sendirian cuma (0,1,0) — ia kalah, dan dropdown tetap tampil berdampingan
   dengan chip yang menggantikannya. Dua kontrol untuk satu pekerjaan membuat
   orang ragu mana yang berlaku. */
@media (max-width: 640px) {
  main label.pilih-kategori:not(.bidang):not(.buka-laci):not(.laci) { display: none; }
}

@media (max-width: 640px) {
  /* Nama pelanggan dilepas di layar sempit — namanya sudah ada di tab Akun,
     dan di sini ia mendorong keranjang keluar dari ujung kanan. */
  .bar .right .redup { display: none; }
  .keranjang-ikon { margin-left: auto; }
  .bar .right { margin-left: auto; }
  .bar .right + .keranjang-ikon { margin-left: var(--s2); }
}
