/* ==========================================================================
   Dapur Kasir — Design System
   Palette preserved 1:1 from the original build. Typography, spacing and
   component surfaces reworked for a calmer, professional SaaS feel.
   ========================================================================== */

:root{
  /* Font stack — sistem/lokal saja, TIDAK ada Google Fonts (tanpa request
     jaringan tambahan = tanpa risiko flicker font & render lebih cepat). */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-heading: Georgia, "Times New Roman", "Noto Serif", serif;
  --font-brand: var(--font-ui);
  --font-mono: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* ---- Brand colors (unchanged) ---- */
  --bg:#F4FBF0;
  /* ===================== KENAPA PERMUKAAN TIDAK PUTIH MURNI LAGI =====================

     Neumorphism bekerja dengan SATU syarat: warna permukaan harus dekat
     dengan warna latarnya, supaya sorotan terang di tepi atas-kiri dan
     bayangan di bawah-kanan sama-sama kelihatan — itulah yang membuat
     kartu terbaca "timbul dari bidang yang sama", bukan "kertas putih
     ditempel di atas hijau muda".

     Di atas --bg #F4FBF0, permukaan #FFFFFF membuat sorotan putihnya
     MUSTAHIL terlihat (putih di atas putih). Jadi nilainya digeser tipis
     ke arah --bg: hue-nya SAMA PERSIS, cuma terangnya turun ~2%. Seluruh
     warna merek (jingga, hijau, kuning, merah) tidak disentuh sama sekali.
     ================================================================== */
  --surface:#FBFEF9;
  --surface-2:#F7FCF4;
  --ink:#1E2117;
  --ink-soft:#707A66;
  --accent:#F4801D;
  --accent-hover:#DD6D10;
  --accent-ink:#FFFFFF;
  --good:#2FAE4C;
  --good-dark:#1F8C3B;
  --good-bg:#E1F5E2;
  --yellow:#FFD54A;
  --yellow-dark:#E0AE1E;
  --yellow-bg:#FFF3C7;
  --warn:#E0483A;
  --warn-bg:#FCE6E2;
  --line:#E1EEDC;
  --line-strong:#CBE2C3;
  --dot: rgba(30,33,23,.045);

  /* ---- Radius scale ----
     Sudut sedikit lebih besar dari versi sebelumnya (14px) — bentuk yang
     lebih lembut ini yang paling terasa "modern" tanpa menyentuh warna. */
  --radius-xs: 8px;
  --radius-sm: 11px;
  --radius-md: 14px;
  --radius-lg: 18px;
  --radius-xl: 24px;
  --radius: var(--radius-lg);

  /* ---- Derived surfaces (new, still built from brand colors) ---- */
  --surface-sunken:#EFF7EA;
  --accent-soft: rgba(244,128,29,.10);
  --good-soft: rgba(47,174,76,.10);
  --ink-faint:#95A088;
  /* Lapisan tipis untuk gradasi halus di atas warna yang sudah ada —
     bukan warna baru, cuma terang/gelapnya digeser sedikit. */
  --sheen-up: rgba(255,255,255,.55);
  --sheen-down: rgba(30,33,23,.028);

  /* ---- Elevation scale ----
     Bayangan dibuat lebih menyebar & lebih tipis: ciri khas UI modern,
     kesannya melayang lembut alih-alih ada garis gelap di bawah kartu. */
  --shadow-xs: 0 1px 2px rgba(30,33,23,.04);
  --shadow-sm: 0 1px 2px rgba(30,33,23,.04), 0 3px 10px -5px rgba(30,33,23,.11);
  --shadow-md: 0 1px 3px rgba(30,33,23,.05), 0 10px 26px -12px rgba(30,33,23,.18);
  --shadow-lg: 0 8px 18px -8px rgba(30,33,23,.16), 0 30px 62px -28px rgba(30,33,23,.28);
  --shadow: var(--shadow-md);

  /* ---- Spacing scale ---- */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:28px; --sp-7:36px; --sp-8:48px;

  /* ---- Motion ---- */
  --ease: cubic-bezier(.2,.7,.3,1);

  /* ==========================================================================
     NEUMORPHISM (soft UI) + GLASSMORPHISM

     Dua resep yang dipakai di seluruh aplikasi. Keduanya TIDAK menambah
     satu warna merek pun — isinya cuma putih & hitam beralpha di atas
     warna yang sudah ada.

     ----- Neumorphism: SEPASANG bayangan, bukan satu -----
     Satu sorotan terang dari arah atas-kiri + satu bayangan gelap dari
     bawah-kanan. Dipakai BERPASANGAN; memakai salah satunya saja langsung
     kembali jadi bayangan drop biasa dan efeknya hilang.

       --nm-raised    kartu/tombol yang TIMBUL dari bidang
       --nm-raised-sm versi kecil untuk elemen padat (chip, tombol kecil)
       --nm-pressed   kolom isian & trek yang MASUK ke dalam bidang
       --nm-tekan     keadaan :active — timbul berubah jadi masuk

     Arah cahayanya KONSISTEN dari atas-kiri di seluruh aplikasi. Satu
     elemen yang arahnya terbalik langsung terlihat seperti salah render,
     bukan seperti variasi.

     ----- Glassmorphism: untuk yang MENGAMBANG di atas isi -----
     Hanya dipakai pada lapisan yang menutupi konten (modal, bilah bawah,
     bilah simpan lengket, toast, laci) — supaya apa yang tertutup masih
     terasa ada di baliknya. TIDAK dipakai pada kartu biasa: blur di
     puluhan kartu sekaligus membuat halaman berat dan teksnya kehilangan
     ketajaman.

     `backdrop-filter` punya padanan -webkit- yang MASIH diperlukan untuk
     Safari iOS — tablet/HP kasir banyak yang memakainya.
     ========================================================================== */
  --nm-light: rgba(255,255,255,.95);
  --nm-dark: rgba(125,145,115,.26);
  --nm-raised:
    -5px -5px 12px var(--nm-light),
     6px  6px 16px var(--nm-dark);
  --nm-raised-sm:
    -2px -2px 6px var(--nm-light),
     3px  3px 8px var(--nm-dark);
  --nm-raised-lg:
    -8px -8px 20px var(--nm-light),
     10px 10px 28px var(--nm-dark);
  --nm-pressed:
    inset 3px 3px 7px var(--nm-dark),
    inset -3px -3px 7px var(--nm-light);
  --nm-tekan:
    inset 2px 2px 5px var(--nm-dark),
    inset -2px -2px 5px var(--nm-light);

  --glass-bg: rgba(251,254,249,.72);
  --glass-brd: rgba(255,255,255,.75);
  --glass-blur: saturate(1.5) blur(18px);

  /* ===================== TINGGI BILAH BAWAH: SATU SUMBER =====================

     Bilah bawah MENGAMBANG (position:fixed), jadi ia tidak menyisakan
     ruang untuk dirinya sendiri — tiap halaman harus mencadangkannya.
     Angka itu dulu ditulis ulang di tiga tempat dengan nilai berbeda-beda,
     dan hasilnya isi halaman terpotong di bagian bawah.

     Sekarang tingginya DIPATOK di sini dan dipakai semua: tinggi bilahnya
     sendiri, cadangan ruang di `main`, posisi bilah keranjang mengambang,
     dan posisi bilah simpan lengket. Mengubah tinggi bilah = mengubah satu
     angka ini saja.

     Di layar lebar nilainya 0 — bilah bawahnya memang tidak ada. */
  --h-botnav: 0px;
  --ruang-botnav: 0px;
}
:root[data-theme="dark"]{
  --bg:#12140E;
  --surface:#1B1F15;
  --surface-2:#1F2417;
  --surface-sunken:#171B11;
  --ink:#F1F4EA;
  --ink-soft:#A2AD93;
  --ink-faint:#6E7A61;
  /* Jingga disamakan persis dengan light mode (bukan versi lebih terang
     lagi) — sekarang teks putih di atas tombol/elemen jingga sudah selalu
     dijaga kontrasnya lewat CSS-nya sendiri, jadi tidak perlu lagi jingga
     versi khusus untuk dark mode. */
  --accent:#F4801D;
  --accent-hover:#DD6D10;
  --accent-ink:#FFFFFF;
  --accent-soft: rgba(244,128,29,.16);
  --good:#4FC470;
  --good-dark:#12331C;
  --good-bg:#1E3521;
  --good-soft: rgba(79,196,112,.14);
  --yellow:#FFDA6B;
  --yellow-dark:#E0AE1E;
  --yellow-bg:#3B3018;
  --warn:#FF7A64;
  --warn-bg:#3B211C;
  --line:#3A4428;
  --line-strong:#4E5B36;
  --dot: rgba(255,255,255,.045);
  --sheen-up: rgba(255,255,255,.045);
  --sheen-down: rgba(0,0,0,.14);
  --shadow-xs: 0 1px 2px rgba(0,0,0,.30);
  --shadow-sm: 0 1px 2px rgba(0,0,0,.30), 0 3px 10px -5px rgba(0,0,0,.42);
  --shadow-md: 0 1px 3px rgba(0,0,0,.32), 0 10px 26px -12px rgba(0,0,0,.52);
  --shadow-lg: 0 8px 18px -8px rgba(0,0,0,.5), 0 30px 62px -28px rgba(0,0,0,.6);
  --shadow: var(--shadow-md);

  /* Neumorphism di tema gelap: sorotan terangnya HARUS jauh lebih redup.
     Nilai terang yang sama dengan tema terang akan terlihat seperti garis
     putih menyala di tepi tiap kartu, bukan seperti cahaya. Yang menanggung
     efek timbulnya di sini bayangan gelapnya. */
  --nm-light: rgba(255,255,255,.055);
  --nm-dark: rgba(0,0,0,.55);
  --glass-bg: rgba(27,31,21,.68);
  --glass-brd: rgba(255,255,255,.10);
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-ui);
  font-size:14px;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: 22px 22px;
  transition: background-color .2s var(--ease), color .2s var(--ease);
}

/* ---- Type roles ----
   Fraunces  → page/section headings, the one editorial accent
   Baloo 2   → brand mark only, kept small and contained
   Inter     → interface text, labels, buttons
   IBM Plex Mono → all numbers: prices, quantities, totals, stock counts
------------------------------------------------------------------ */
h1,h2,h3{
  font-family:var(--font-heading);
  font-optical-sizing:auto;
  font-weight:600;
  letter-spacing:-.01em;
  margin:0;
}
.display{font-family:var(--font-heading); font-weight:600;}
.brand-face{font-family:var(--font-brand); font-weight:700;}
.mono{font-family:var(--font-mono); font-variant-numeric: tabular-nums; font-feature-settings:"tnum";}
button{font-family:inherit;cursor:pointer;}
input,select{font-family:inherit; background:var(--surface-2); color:var(--ink);}
::selection{background:var(--accent-soft); color:var(--ink);}

/* Focus ring — consistent, visible, accessible */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:6px;
}

#app{display:flex; min-height:100vh;}

/* ==========================================================================
   Sidebar
   ========================================================================== */
.sidebar{
  width:244px; flex-shrink:0;
  /* Warna dasarnya tetap --good-dark; gradasinya cuma menggeser terang/gelap
     warna yang sama supaya sidebar terasa punya kedalaman, bukan bidang
     hijau datar. */
  background:
    linear-gradient(180deg, rgba(255,255,255,.07) 0%, rgba(255,255,255,0) 22%, rgba(0,0,0,.14) 100%),
    var(--good-dark);
  color:#fff;
  display:flex; flex-direction:column;
  padding:22px 14px;
  position:sticky; top:0; height:100vh;
  /* Sidebar sendiri TIDAK discroll lagi -- cuma <nav> di tengah (lihat di
     bawah) yang punya overflow-y:auto sendiri, supaya brand (logo/nama) di
     atas dan sidebar-foot (status database, akun, tema, mode demo) di
     bawah tetap kepampang terus walau daftar menunya panjang. */
  overflow:hidden;
  box-shadow: 1px 0 0 rgba(0,0,0,.08), 4px 0 24px rgba(0,0,0,.14);
  z-index:5;
}
.brand{
  display:flex; align-items:center; gap:11px;
  padding:0 6px 20px 6px;
  border-bottom:1px solid rgba(255,255,255,.14);
  margin-bottom:18px;
  flex-shrink:0;
}
.brand .mark{
  width:38px;height:38px;border-radius:12px;
  background:var(--accent);
  box-shadow:0 4px 12px -3px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.28), inset 0 0 0 1px rgba(255,255,255,.14);
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-brand); font-weight:800; font-size:15px;
  color:#fff;
  flex-shrink:0;
  overflow:hidden;
}
.brand .name{font-family:var(--font-brand); font-size:17px; font-weight:700; line-height:1.2; color:#fff;}
.brand .sub{font-size:11px; color:rgba(255,255,255,.62); letter-spacing:.03em; margin-top:1px;}

/* Dibatasi ke `.sidebar nav` — SELEKTOR ELEMEN `nav` polos dulu ikut kena
   ke <nav class="botnav"> di bilah bawah dan memaksanya menumpuk vertikal.
   Selektor elemen untuk tata letak khas satu komponen memang begitu: ia
   diam saja sampai ada komponen kedua memakai tag yang sama.

   flex:1 + min-height:0 = <nav> yang menyerap SISA tinggi sidebar dan
   men-scroll sendiri kalau menunya lebih panjang dari layar -- brand di
   atasnya dan sidebar-foot di bawahnya (lihat .sidebar{overflow:hidden}
   di atas) jadi tidak pernah ikut tergeser. */
.sidebar nav{
  display:flex; flex-direction:column; gap:3px;
  flex:1 1 auto; min-height:0; overflow-y:auto;
}
/* Scrollbar tipis, sama gayanya dengan sebelumnya tapi sekarang di <nav>
   (yang discroll), bukan lagi di .sidebar (yang sekarang diam). */
.sidebar nav::-webkit-scrollbar{width:6px;}
.sidebar nav::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16); border-radius:100px;}
.sidebar nav::-webkit-scrollbar-track{background:transparent;}
.navbtn{
  position:relative;
  display:flex; align-items:center; gap:11px;
  background:transparent; border:none; color:rgba(255,255,255,.74);
  padding:10px 12px; border-radius:var(--radius-sm); font-size:13.5px; font-weight:600;
  text-align:left;
  /* Item nav jadi <a href> sejak v1.19.0 (tiap menu = halaman PHP sendiri),
     jadi garis bawah & warna tautan bawaan browser harus dimatikan. Dulu
     elemennya <button> yang tidak punya keduanya. */
  text-decoration:none; cursor:pointer;
  transition:background .16s var(--ease), color .16s var(--ease), transform .16s var(--ease);
}
.navbtn .ic{width:18px; height:18px; flex-shrink:0; opacity:.92; display:flex; align-items:center; justify-content:center; font-size:16.5px; line-height:1; transition:transform .16s var(--ease);}
.navbtn:hover{background:rgba(255,255,255,.10); color:#fff;}
.navbtn:hover .ic{transform:scale(1.08);}
.navbtn.active{
  background:linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,0)), var(--accent);
  color:#fff; font-weight:700;
  box-shadow:0 4px 14px -4px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.22);
}

.navgroup{display:flex; flex-direction:column;}
.navbtn-parent{width:100%;}
.navbtn-parent .navcaret{margin-left:auto; flex-shrink:0; opacity:.6; font-size:11px; transition:transform .16s var(--ease);}
.navgroup.open .navbtn-parent .navcaret{transform:rotate(180deg);}
.navsub{
  display:flex; flex-direction:column; gap:1px;
  max-height:0; overflow:hidden;
  transition:max-height .18s var(--ease), margin .18s var(--ease);
  margin-top:0;
  padding-left:29px;
}
/* 220px pas untuk submenu terpanjang saat itu (6 item) -- begitu
   "Pengaturan" bertambah jadi 7 item (Log Aktivitas) isinya jadi 230px,
   overflow:hidden diam-diam memotong item terakhir ("Info Aplikasi") dan
   border sidebar-foot di bawahnya terlihat seperti bertabrakan dengan
   menu. Dilebarkan dengan longgar (bukan disesuaikan pas-pasan lagi)
   supaya submenu mana pun boleh tumbuh beberapa item lagi tanpa diam-diam
   terpotong seperti ini. max-height (bukan height/auto) memang perlu
   angka tetap supaya transisinya tetap mulus -- keterbatasan CSS untuk
   transisi ke tinggi otomatis. */
.navgroup.open .navsub{max-height:320px; margin-top:2px; margin-bottom:4px;}
.navsubbtn{
  display:block; width:100%; text-align:left;
  background:transparent; border:none; color:rgba(255,255,255,.62);
  /* <a href> sejak v1.19.0 — lihat catatan di .navbtn. */
  text-decoration:none; cursor:pointer;
  padding:8px 12px; border-radius:8px; font-size:12.5px; font-weight:600;
  border-left:2px solid rgba(255,255,255,.14);
  border-radius:0 8px 8px 0;
  transition:background .14s var(--ease), color .14s var(--ease), border-color .14s var(--ease);
}
.navsubbtn:hover{background:rgba(255,255,255,.08); color:#fff;}
.navsubbtn.active{background:rgba(255,255,255,.12); color:#fff; font-weight:700; border-left-color:var(--accent);}

.user-badge{
  display:flex; align-items:center; gap:10px;
  padding:9px 10px; margin-bottom:10px; border-radius:10px;
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.1);
}
.user-badge-info{ flex:1; min-width:0; }
.user-badge .uname{ font-size:12.5px; font-weight:700; color:#fff; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.user-badge .urole{ font-size:10.5px; color:rgba(255,255,255,.55); margin-top:1px; }
.user-badge-out{
  background:rgba(255,255,255,.08); border:none; border-radius:8px; width:28px; height:28px; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; color:rgba(255,255,255,.7); font-size:14px;
  transition:background .12s var(--ease), color .12s var(--ease);
}
.user-badge-out:hover{ background:var(--warn); color:#fff; }
:root[data-theme="dark"] .user-badge-out:hover{ color:#2B1400; }

.sidebar-foot{flex-shrink:0; padding:14px 6px 0 6px; font-size:11px; color:rgba(255,255,255,.5); border-top:1px solid rgba(255,255,255,.14); padding-top:16px;}

/* Kotak demo permanen di sidebar-foot (khusus peran developer, lihat
   portal/index.php: #demoModeBox) -- SATU tombol yang gantian jadi "Load
   Demo" / "Keluar Mode Demo" lewat perbaruiKotakDemo() di assets/js/
   app.js, bukan dua elemen di dua tempat berbeda. .active ditambahkan
   selagi Mode Demo betulan aktif, murni penekanan visual (border/warna
   lebih tegas) supaya jelas ini bukan data asli. Warnanya SENGAJA ditulis
   langsung (bukan var(--yellow-bg) dkk, yang nilainya ditukar antara mode
   terang/gelap) -- sidebar ini sendiri selalu gelap terlepas dari tema
   aplikasi, cermin pola sync-badge/theme-toggle di atas yang juga pakai
   rgba(255,255,255,..) tetap, bukan token bertema. */
.demo-mode-box{
  background:rgba(255,213,74,.08); border:1px solid rgba(255,213,74,.22); border-radius:10px;
  padding:9px 10px; margin-bottom:10px;
  transition:background .16s var(--ease), border-color .16s var(--ease);
}
.demo-mode-box.active{ background:rgba(255,213,74,.14); border-color:rgba(255,213,74,.38); }
.demo-mode-box .demo-mode-text{ color:#FFD54A; font-size:10.5px; font-weight:700; line-height:1.4; margin-bottom:7px; }
.demo-mode-box button{
  width:100%; background:rgba(0,0,0,.28); color:#FFD54A; border:none; border-radius:7px;
  padding:6px 8px; font-size:11px; font-weight:700; cursor:pointer;
  transition:background .12s var(--ease);
}
.demo-mode-box button:hover{ background:rgba(0,0,0,.42); }

/* ===================== SALINAN DI LEMBAR PROFIL (ponsel) =====================

   Warna di atas ditulis untuk SIDEBAR, yang bidangnya selalu hijau tua
   terlepas dari tema aplikasi — karena itu kuning terang & hitam beralpha
   di situ terbaca dengan baik.

   Sejak kotak ini juga muncul di lembar Profil, warnanya harus ditulis
   ulang: lembar itu memakai `--surface`, yang di mode TERANG nyaris putih.
   Kuning #FFD54A di atas putih praktis tidak terbaca, dan tombol berlatar
   hitam-beralpha ikut terlihat seperti lubang gelap di tengah kartu
   terang — persis tabrakan warna yang dilaporkan.

   Yang dipakai di sini token bertema (`--yellow-bg`, `--ink`), jadi
   keterbacaannya ikut berubah benar di kedua mode tanpa nilai tetap apa
   pun. Kuning ditahan di LATAR & ikon saja; tulisannya `--ink` supaya
   kontras di mode mana pun. */
.sheet .demo-mode-box{
  background:var(--yellow-bg);
  border-color:var(--yellow-dark);
}
.sheet .demo-mode-box.active{
  background:var(--yellow-bg);
  border-color:var(--yellow-dark);
}
.sheet .demo-mode-box .demo-mode-text{ color:var(--ink); }
.sheet .demo-mode-box .demo-mode-text i{ color:var(--yellow-dark); }
.sheet .demo-mode-box button{
  background:var(--yellow-dark); color:#1E2117;
}
.sheet .demo-mode-box button:hover{ background:var(--yellow); }

.theme-toggle{display:flex; gap:2px; background:rgba(255,255,255,.09); border-radius:10px; padding:3px; margin-bottom:10px;}
.theme-toggle button{
  flex:1; background:transparent; border:none; border-radius:8px; padding:7px 4px;
  color:rgba(255,255,255,.6); font-size:14px; display:flex; align-items:center; justify-content:center;
  transition:background .12s var(--ease), color .12s var(--ease);
}
.theme-toggle button.active{background:var(--accent); color:#fff;}
.theme-toggle button:hover:not(.active){background:rgba(255,255,255,.09); color:#fff;}

.sync-badge{
  display:flex; align-items:center; gap:7px; justify-content:center;
  font-size:10.5px; font-weight:700; padding:7px 10px; border-radius:8px;
  background:rgba(255,255,255,.07); color:rgba(255,255,255,.58); letter-spacing:.01em;
}
.sync-badge::before{content:''; width:6px; height:6px; border-radius:50%; flex-shrink:0; background:currentColor;}
.sync-badge.disconnected::before{background:rgba(255,255,255,.4);}
.sync-badge.online{background:rgba(79,196,112,.16); color:#8FE4A6;}
.sync-badge.connecting{background:rgba(255,255,255,.09); color:rgba(255,255,255,.75);}
.sync-badge.connecting::before{background:#FFDA6B; animation:sync-pulse 1.3s ease-in-out infinite;}
.sync-badge.error{background:rgba(255,122,100,.16); color:#FFB2A0;}
@keyframes sync-pulse{ 0%,100%{opacity:1;} 50%{opacity:.35;} }

/* Versi di atas permukaan terang (mis. kartu "Status Saat Ini" di
   Pengaturan) — warna tetap di atas hanya cocok untuk sidebar yang SELALU
   gelap. Di sini teks dibuat netral (kontras tinggi di kedua tema),
   status cukup ditandai lewat warna titik kecilnya saja. */
.card .sync-badge{ background:var(--surface-sunken); color:var(--ink); }
.card .sync-badge.disconnected::before{ background:var(--ink-faint); }
.card .sync-badge.online::before{ background:var(--good); }
.card .sync-badge.connecting{ background:var(--surface-sunken); color:var(--ink); }
.card .sync-badge.connecting::before{ background:var(--yellow-dark); animation:sync-pulse 1.3s ease-in-out infinite; }
.card .sync-badge.error::before{ background:var(--warn); }

/* ==========================================================================
   Main
   ========================================================================== */
main{flex:1; min-width:0; padding:34px 38px 72px 38px; max-width:1440px;}
.pagehead{
  display:flex; justify-content:space-between; align-items:flex-end;
  margin-bottom:24px; padding-bottom:20px; gap:16px; flex-wrap:wrap;
  border-bottom:1px solid var(--line);
}
.pagehead h1{font-size:27px; margin:0 0 6px 0; letter-spacing:-.018em;}
.pagehead p{margin:0; color:var(--ink-soft); font-size:13.5px; max-width:64ch;}

/* ==========================================================================
   Grid kartu untuk layar lebar
   --------------------------------------------------------------------------
   Sebelumnya semua kartu menumpuk ke bawah selebar layar — di monitor lebar
   jadi banyak ruang kosong di kanan dan halaman terasa panjang sekali.
   Sekarang halaman memakai 12 kolom: tiap kartu menyatakan lebarnya lewat
   .col-4 / .col-5 / .col-6 / .col-7 / .col-8 / .col-12, jadi kartu yang
   sempit (form, ringkasan) bisa berdampingan dengan kartu yang lebar
   (tabel). Di bawah 1240px semuanya otomatis kembali menumpuk satu kolom
   supaya tabel tidak terhimpit.
   ========================================================================== */
.grid-cards{display:grid; grid-template-columns:repeat(12,1fr); gap:18px; align-items:start; margin-bottom:18px;}
/* min-width:0 WAJIB — tanpa ini tabel lebar memaksa kolom grid melar
   melebihi jatahnya dan layout ikut meleber ke samping. */
.grid-cards > *{min-width:0;}
.col-4{grid-column:span 4;}
.col-5{grid-column:span 5;}
.col-6{grid-column:span 6;}
.col-7{grid-column:span 7;}
.col-8{grid-column:span 8;}
.col-12{grid-column:span 12;}
@media (max-width:1240px){
  .col-4, .col-5, .col-6, .col-7, .col-8{grid-column:span 12;}
}

/* Beberapa kartu berdiri sendiri di dalam satu kolom grid — dirapatkan
   dengan jarak yang sama seperti gap grid-nya. */
.stack{display:flex; flex-direction:column; gap:18px;}

/* --------------------------------------------------------------------------
   Kolom mengalir untuk halaman yang isinya banyak kartu form dengan tinggi
   sangat berbeda (Pengaturan Landing Page). Grid 12 kolom meratakan tiap
   baris ke kartu tertinggi; multi-column menyeimbangkan tinggi kedua kolom
   sendiri, jadi tidak ada ruang kosong menganga di antara kartu.
   -------------------------------------------------------------------------- */
.form-columns{column-count:2; column-gap:18px;}
/* break-inside WAJIB — tanpa ini satu kartu bisa terpotong separuh di
   dasar kolom kiri dan disambung di puncak kolom kanan. */
.form-columns > *{break-inside:avoid; margin:0 0 18px 0;}

/* Baris tombol simpan yang menempel di bawah layar — di form sepanjang
   halaman Landing Page, tombolnya dulu hanya ada di dasar halaman. */
/* Bilah simpan lengket. PEKAT, bukan kaca: ia menempel di atas form yang
   tergulir, jadi backdrop-filter di sini ikut mem-blur ulang isi form pada
   setiap frame gulir — biaya yang sama dengan .botnav & keranjang
   mengambang, dan sama tidak perlunya. Kaca disimpan untuk lapisan yang
   muncul SESAAT (modal & lembar), bukan yang menetap di layar. */
.save-bar{
  position:sticky; bottom:0; z-index:4;
  display:flex; gap:9px; flex-wrap:wrap;
  padding:14px 16px; margin-top:4px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-md); box-shadow:var(--nm-raised-lg);
}
.eyebrow, p.eyebrow{
  display:inline-flex; align-items:center; gap:5px;
  font-family:var(--font-ui); font-size:10.5px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--good-dark); background:var(--good-soft);
  padding:4px 10px; border-radius:100px; font-weight:700; margin:0 0 8px 0;
}
:root[data-theme="dark"] .eyebrow{color:var(--yellow);}

/* Tombol netral: timbul saat diam, MASUK saat ditekan. Perubahan arah
   bayangan itulah umpan balik tekannya — jauh lebih terbaca di layar sentuh
   daripada sekadar menggelapkan warna, dan tidak butuh warna baru. */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  padding:9px 16px; border-radius:var(--radius-sm); border:1px solid var(--line-strong);
  background:var(--surface-2); color:var(--ink); font-size:13.5px; font-weight:600;
  line-height:1.35; white-space:nowrap;
  box-shadow:var(--nm-raised-sm);
  transition:transform .1s var(--ease), background .16s var(--ease), border-color .16s var(--ease), box-shadow .16s var(--ease), color .16s var(--ease);
}
.btn:hover{box-shadow:var(--nm-raised); border-color:var(--good); transform:translateY(-1px);}
.btn:active{transform:translateY(0); box-shadow:var(--nm-tekan);}
/* Tombol yang sebenarnya tautan (mis. "Lihat di GitHub" di Info Aplikasi) --
   tanpa ini, garis bawah bawaan <a> ikut tergambar di dalam tombolnya. */
a.btn{text-decoration:none;}
/* Tombol aksi (jingga) TIDAK memakai sorotan neumorphic: sorotan putih di
   tepi bidang jingga pekat terbaca sebagai cacat render, bukan cahaya. Yang
   dipakai bayangan berwarna jingga itu sendiri — bidangnya terasa melayang
   di atas halaman tanpa mengotori warnanya. */
.btn-primary{
  background:linear-gradient(180deg, var(--sheen-up), rgba(255,255,255,0)), var(--accent);
  border-color:var(--accent); color:#fff;
  box-shadow:0 4px 14px -4px rgba(244,128,29,.55), inset 0 1px 0 rgba(255,255,255,.3);
}
.btn-primary:hover{background:var(--accent-hover); border-color:var(--accent-hover); box-shadow:0 8px 22px -6px rgba(244,128,29,.62), inset 0 1px 0 rgba(255,255,255,.3);}
.btn-primary:active{box-shadow:inset 0 2px 6px rgba(120,55,0,.45);}
/* Teks tombol tetap putih di dark mode (permintaan user) — bayangannya
   saja yang disesuaikan supaya tombol tetap terasa menonjol di atas
   latar gelap. */
:root[data-theme="dark"] .btn-primary{box-shadow:0 2px 10px -3px rgba(0,0,0,.6);}
:root[data-theme="dark"] .btn-primary:hover{box-shadow:0 5px 16px -4px rgba(0,0,0,.7);}
.btn-ghost{background:transparent; border-color:transparent; color:var(--ink-soft); box-shadow:none;}
.btn-ghost:hover{color:var(--ink); background:var(--surface-sunken); box-shadow:none; border-color:transparent;}
.btn-sm{padding:6px 12px; font-size:12.5px; border-radius:var(--radius-xs);}
.btn-danger{color:var(--warn); border-color:transparent; background:var(--warn-bg);}
.btn-danger:hover{border-color:var(--warn);}
.btn[disabled]{opacity:.42; cursor:not-allowed; box-shadow:none; transform:none;}
.btn[disabled]:hover{transform:none; border-color:var(--line-strong);}

/* Kartu = bidang yang TIMBUL dari latar (neumorphism). Garis tepinya
   dipertahankan tapi dibuat sangat tipis: tanpa garis sama sekali, batas
   kartu hilang total di layar ponsel murah yang kontrasnya rendah — di
   situ sepasang bayangan lembut saja tidak cukup. */
.card{
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--radius); box-shadow:var(--nm-raised);
  /* Sudut ikut memotong isi — penting sejak header tabel diberi latar,
     supaya sudut atas tabel tidak menonjol keluar lengkung kartu. */
  overflow:hidden;
  transition:box-shadow .2s var(--ease), border-color .2s var(--ease), transform .2s var(--ease);
}
.card:hover{box-shadow:var(--nm-raised-lg); border-color:var(--line-strong);}
.pad{padding:20px 22px;}

/* ==========================================================================
   Kasir layout
   ========================================================================== */
.kasir-grid{display:grid; grid-template-columns:1fr 356px; gap:22px; align-items:flex-start;}

.prod-search{margin-bottom:16px; display:flex; gap:8px;}
.prod-search input{
  flex:1; padding:11px 14px 11px 40px; border-radius:100px; border:1px solid var(--line);
  background:var(--surface-2) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="%23707A66" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>') no-repeat 14px center;
  font-size:13.5px;
  box-shadow:var(--nm-pressed);
  transition:box-shadow .15s var(--ease), border-color .15s var(--ease);
}
.prod-search input:focus{outline:none; border-color:var(--accent); box-shadow:var(--nm-pressed), 0 0 0 3px var(--accent-soft);}

.chip-row{display:flex; gap:7px; flex-wrap:wrap; margin-bottom:16px;}
.chip{
  padding:7px 15px; border-radius:100px; border:1px solid var(--line-strong);
  background:var(--surface-2); color:var(--ink-soft); font-size:12.5px; font-weight:600;
  box-shadow:var(--nm-raised-sm);
  transition:background .14s var(--ease), color .14s var(--ease), border-color .14s var(--ease), box-shadow .14s var(--ease);
}
.chip:hover{border-color:var(--good); color:var(--ink); background:var(--good-soft);}
/* Chip TERPILIH sengaja MASUK ke dalam bidang, bukan timbul — "sedang
   aktif" terbaca sebagai tombol yang tertekan. Itu membedakannya dari chip
   lain tanpa mengandalkan warna saja, jadi tetap terbaca di layar yang
   warnanya pudar maupun oleh mata yang sulit membedakan hijau. */
.chip.active{
  background:var(--good); border-color:var(--good); color:#fff;
  box-shadow:inset 0 2px 6px rgba(14,70,28,.42), 0 1px 2px rgba(47,174,76,.3);
}
/* --good juga jadi lebih terang di dark mode (#4FC470) — sama seperti
   tombol primary, teks putih di atasnya kurang kontras. */
:root[data-theme="dark"] .chip.active{color:#0C2410;}

/* Desktop: kolomnya TETAP ikut lebar layar (auto-fill), tapi lebar minimum
   kartunya dinaikkan 162px -> 200px supaya fotonya ikut membesar seperti di
   ponsel. Tidak dipatok 3 kolom seperti versi ponsel dengan sengaja: di
   layar 1600px, tiga kartu berarti kartu selebar ~500px dengan foto setinggi
   itu juga — yang terlihat bukan lagi daftar produk melainkan galeri, dan
   jumlah produk yang terlihat sekaligus justru anjlok. */
.product-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:13px;}
.pcard{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-md);
  color:var(--ink); overflow:hidden;
  padding:15px; text-align:left; display:flex; flex-direction:column; gap:7px; position:relative;
  box-shadow:var(--nm-raised-sm);
  transition:border-color .16s var(--ease), box-shadow .16s var(--ease), transform .16s var(--ease);
}
/* Sapuan warna tipis yang muncul saat kursor lewat — isyarat "bisa diklik"
   yang lebih halus daripada mengubah warna latar seluruh kartu. */
.pcard::after{
  content:''; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
  background:linear-gradient(160deg, var(--good-soft), transparent 60%);
  opacity:0; transition:opacity .16s var(--ease);
}
.pcard:hover:not([disabled]){border-color:var(--good); box-shadow:var(--nm-raised-lg); transform:translateY(-3px);}
/* Kartu produk DITEKAN saat diklik: di layar sentuh inilah satu-satunya
   umpan balik bahwa ketukannya diterima, karena isi keranjang ada di panel
   terpisah yang mungkin tidak kelihatan di layar sempit. */
.pcard:active:not([disabled]){transform:translateY(0); box-shadow:var(--nm-tekan);}
.pcard:hover:not([disabled])::after{opacity:1;}
.pcard[disabled]{opacity:.42; cursor:not-allowed;}
.pcard .pname{font-weight:600; font-size:13.5px; line-height:1.3;}
.pcard .pprice{font-family:var(--font-mono); color:var(--accent); font-weight:600; font-size:13.5px;}
.pcard .pstock{font-size:11px; color:var(--ink-soft);}
/* Penanda kecil di kartu produk Kasir kalau produknya punya varian (mis.
   "Ekstra Telur") -- klik kartu ini membuka modal pilih varian alih-alih
   langsung +1, lihat bukaModalPilihVarian() di assets/js/app.js. Sengaja
   ikut alur normal (BUKAN position:absolute) sebagai elemen PALING ATAS
   di dalam kartu -- absolut mengambang di pojok dulu pernah bertabrakan
   dengan nama produk yang panjang/berbaris dua. */
.pvariant-hint{
  align-self:flex-start; display:inline-flex; align-items:center; gap:3px;
  font-size:9.5px; font-weight:700; color:var(--accent); background:var(--accent-soft, var(--good-soft));
  padding:2px 7px; border-radius:100px; letter-spacing:.01em;
}

/* ---------- Gambar produk (Produk Jadi & kartu Kasir) ----------
   Satu kelas .prod-thumb dipakai DUA tempat dengan ukuran berbeda: kecil
   di tabel Produk Jadi (ukuran dasar di bawah), besar melebar penuh di
   kartu Kasir (.pcard-thumb). Produk tanpa gambar memakai inisial
   namanya, bukan kotak kosong -- lihat htmlThumbProduk() di
   assets/js/app.js. */
.prod-baris{ display:flex; align-items:center; gap:11px; }
.prod-thumb{
  width:44px; height:44px; flex-shrink:0; overflow:hidden;
  border-radius:10px; border:1px solid var(--line);
  background:var(--surface-sunken);
}
.prod-thumb img{ width:100%; height:100%; object-fit:cover; display:block; }
.prod-thumb-kosong{
  display:flex; align-items:center; justify-content:center;
  font-weight:800; font-size:13px; color:var(--ink-faint); letter-spacing:.02em;
}
/* Di kartu Kasir gambarnya jadi "foto menu" selebar kartu -- rasionya
   dikunci 4:3 supaya tinggi tiap kartu tetap seragam berapa pun ukuran
   foto aslinya, jadi grid produknya tidak bergerigi. */
/* Melebar sampai tepi kartu — margin negatifnya sebesar padding .pcard
   (15px). Alasan `width:auto` sama persis dengan versi ponsel di media
   query bawah; rasionya 1/1 (bukan 4/3 seperti sebelumnya) supaya fotonya
   lebih tinggi dan tampil sama di kedua ukuran layar. */
.pcard-thumb{
  width:auto; height:auto; aspect-ratio:1/1;
  margin:-15px -15px 4px -15px;
  border-radius:var(--radius-md) var(--radius-md) 0 0;
  border-left:none; border-right:none; border-top:none;
}
.pcard-thumb.prod-thumb-kosong{ font-size:34px; }

/* Pratinjau di modal Tambah/Edit Produk. */
.prod-gambar-pratinjau{ margin-bottom:10px; }
.prod-gambar-pratinjau img{
  width:100%; max-width:200px; aspect-ratio:4/3; object-fit:cover; display:block;
  border-radius:var(--radius-sm); border:1px solid var(--line); background:var(--surface-sunken);
}

.kemasan-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:12px;}
.kemasan-item{
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-md);
  padding:14px; display:flex; flex-direction:column; gap:6px;
  box-shadow:var(--shadow-xs);
  transition:border-color .16s var(--ease), box-shadow .16s var(--ease), transform .16s var(--ease);
}
.kemasan-item:hover{border-color:var(--good); box-shadow:var(--shadow-sm); transform:translateY(-2px);}
.kemasan-item .ki-name{font-weight:600; font-size:12.5px; line-height:1.3;}
.kemasan-item .ki-stock{font-size:11px; color:var(--ink-soft);}
/* Penanda kecil kalau kemasan ini sudah punya qty dipilih di kartu "Kemasan
   Dipilih" sebelah kanan (lihat viewAmbilKemasan()) -- supaya kelihatan
   dari grid kiri tanpa harus melirik kartu detailnya tiap kali. */
.kemasan-item .ki-dipilih{font-size:11px; font-weight:700; color:var(--accent);}
/* Kartu detail Ambil Kemasan (panel kanan) -- lebar tetap sama seperti
   .receipt di Kasir (356px lewat .kasir-grid yang dipakai bersama)
   supaya kedua halaman terasa satu keluarga tampilan. */
.kemasan-detail .info-list .r{align-items:center;}

/* Kartu manajemen Stok Kemasan (admin/dev) — sengaja beda dari tabel di
   Stok Bahan Baku supaya kedua sub-menu terasa berbeda, bukan salinan. */
.kemasan-manage-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(248px,1fr)); gap:15px;}
.kemasan-card{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-md);
  padding:17px; display:flex; flex-direction:column; gap:12px;
  box-shadow:var(--shadow-xs);
  transition:border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.kemasan-card:hover{border-color:var(--line-strong); box-shadow:var(--shadow-sm);}
.kc-head{display:flex; align-items:flex-start; justify-content:space-between; gap:8px;}
.kc-name{font-weight:700; font-size:14px; line-height:1.3;}
.kc-actions{display:flex; gap:6px; flex-shrink:0;}
.kc-stats{display:flex; flex-direction:column; gap:6px;}
.kc-stats > div{display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:12.5px;}
.kc-label{color:var(--ink-soft);}
.kc-value{font-family:var(--font-mono); font-weight:600;}

.badge{display:inline-block; padding:3.5px 10px; border-radius:100px; font-size:10.5px; font-weight:800; letter-spacing:.02em; white-space:nowrap; line-height:1.45;}
.badge.good{background:var(--good-bg); color:var(--good-dark);}
.badge.warn{background:var(--warn-bg); color:var(--warn);}
.badge.low{background:var(--yellow-bg); color:var(--yellow-dark);}
/* Peran buatan sendiri — slug-nya bebas, jadi tidak bisa dipetakan ke
   warna tertentu seperti tiga peran bawaan. */
.badge.neutral{background:var(--surface-sunken); color:var(--ink-soft); border:1px solid var(--line-strong);}
/* Varian "info" — dipakai badge FIX di Changelog. Palet aplikasi ini tidak
   punya ungu/indigo, jadi dipakai JINGGA AKSEN (warna kedua aplikasi)
   dengan resep yang sama seperti varian lain: latar versi lembutnya,
   teks versi lebih tuanya. Tidak ada variabel warna baru. */
.badge.info{background:var(--accent-soft); color:var(--accent-hover);}
:root[data-theme="dark"] .badge.good{background:var(--good); color:#0C2410;}
:root[data-theme="dark"] .badge.warn{background:var(--warn); color:#1E2117;}
:root[data-theme="dark"] .badge.low{background:var(--yellow); color:#1E2117;}
/* Di tema gelap, --accent-hover terlalu redup di atas latar gelap —
   dipakai --accent yang memang sudah disetel untuk dark mode. */
:root[data-theme="dark"] .badge.info{background:var(--accent-soft); color:var(--accent);}

/* Receipt / cart panel — styled like a printed kasir receipt, torn at the
   top where it would come off the till. This is the app's one signature
   flourish: everything else stays quiet so this reads clearly. */
.receipt{
  background:var(--surface);
  border:1px solid var(--line);
  border-top:none;
  border-radius:0 0 16px 16px;
  position:sticky; top:44px;
  box-shadow:var(--nm-raised-lg);
  display:flex; flex-direction:column;
  max-height:calc(100vh - 76px);
}
.receipt::before{
  content:'';
  display:block;
  height:11px;
  margin-top:-11px;
  background:var(--surface);
  clip-path: polygon(
    0% 100%, 0% 20%, 4% 100%, 8% 20%, 12% 100%, 16% 20%, 20% 100%, 24% 20%,
    28% 100%, 32% 20%, 36% 100%, 40% 20%, 44% 100%, 48% 20%, 52% 100%, 56% 20%,
    60% 100%, 64% 20%, 68% 100%, 72% 20%, 76% 100%, 80% 20%, 84% 100%, 88% 20%,
    92% 100%, 96% 20%, 100% 100%, 100% 20%
  );
}
/* <button>, bukan <div> — di layar sempit ia yang membuka/menutup daftar
   isi keranjang. Di layar lebar tidak ada yang dilipat, jadi gaya tombol
   bawaannya dinetralkan sepenuhnya dan kursornya dikembalikan biasa: ia
   harus terbaca sebagai judul panel, bukan sebagai sesuatu yang bisa
   ditekan. */
.receipt-top{
  padding:16px 20px 12px 20px; border:none; border-bottom:1px dashed var(--line-strong);
  position:relative; background:transparent; color:inherit;
  width:100%; text-align:left; cursor:default; display:block;
}
.receipt-title{font-family:var(--font-heading); font-size:16px; font-weight:600; display:block;}
/* Caret cuma ada artinya saat keranjangnya dilipat — lihat media query
   layar sempit, yang menampilkannya kembali. */
.receipt-caret{display:none;}
.receipt-top::after{
  content:'';
  position:absolute; left:20px; right:20px; bottom:-1px; height:1px;
  background-image: radial-gradient(circle, var(--bg) 1.4px, transparent 1.6px);
  background-size:9px 100%;
  background-repeat:repeat-x;
}
.receipt-top h3{font-family:var(--font-heading); margin:0; font-size:16px; font-weight:600;}
.receipt-top .sub{font-size:11.5px; color:var(--ink-soft); margin-top:3px;}
.receipt-items{flex:1; overflow-y:auto; padding:4px 20px; min-height:90px;}
.rline{display:flex; align-items:center; gap:9px; padding:10px 0; border-bottom:1px solid var(--line);}
.rline:last-child{border-bottom:none;}
.rline .rname{flex:1; font-size:12.5px; font-weight:500; line-height:1.35;}
.rline .rvariant{font-size:10.5px; color:var(--accent); margin-top:1px;}
.rline .rprice{font-size:10.5px; color:var(--ink-soft);}
.qtybox{display:flex; align-items:center; gap:7px;}
.qtybox button{width:21px; height:21px; border-radius:6px; border:1px solid var(--line-strong); background:var(--surface-2); color:var(--ink); font-size:12px; line-height:1; transition:background .1s;}
.qtybox button:hover{background:var(--surface-sunken);}
.qtybox span{font-size:12.5px; min-width:16px; text-align:center; font-family:var(--font-mono);}
.rsub{font-family:var(--font-mono); font-size:12.5px; font-weight:600; min-width:66px; text-align:right;}
.empty-cart{color:var(--ink-soft); font-size:12.5px; text-align:center; padding:34px 10px;}

/* Kaki keranjang sekarang cuma berisi tombol "Proses" -- Total, Uang
   Dibayar, dan Jenis Transaksi sudah pindah ke modal Proses Pembayaran
   (lihat .pm-* di bawah), jadi aturan .totrow/.paybox/.change-line yang
   dulu ada di sini sudah tidak punya pemakai lagi dan ikut dihapus. */
.receipt-bottom{border-top:1px dashed var(--line-strong); padding:16px 20px 20px 20px; background:var(--surface-sunken); border-radius:0 0 16px 16px;}

/* ==========================================================================
   Tables
   ========================================================================== */
table{width:100%; border-collapse:collapse; font-size:13.5px;}
th{
  text-align:left; font-size:10.5px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--ink-soft); font-weight:700; padding:12px 16px;
  border-bottom:1px solid var(--line-strong); background:var(--surface-sunken);
  white-space:nowrap;
}
th:first-child{border-top-left-radius:var(--radius-sm);}
th:last-child{border-top-right-radius:var(--radius-sm);}
td{padding:13px 16px; border-bottom:1px solid var(--line); vertical-align:middle;}
/* Teks panjang memotong kata di kolom non-angka saja — kolom angka harus
   tetap satu baris supaya digitnya sejajar rapi. */
td:not(.num){overflow-wrap:break-word;}
tbody tr{transition:background .12s var(--ease);}
tbody tr:hover{background:var(--surface-sunken);}
tr:last-child td{border-bottom:none;}
td.num, th.num{text-align:right; font-family:var(--font-mono); font-variant-numeric:tabular-nums; white-space:nowrap;}
.rowname{font-weight:600;}
.rowsub{font-size:11.5px; color:var(--ink-soft); margin-top:1px;}
.actions-cell{display:flex; gap:6px; justify-content:flex-end; align-items:center;}

/* ===================== TOMBOL AKSI YANG SELALU IKON =====================

   Beda dari `.btn-aksi` di bagian bawah berkas ini: yang itu berteks di layar
   lebar dan baru berubah jadi ikon di bawah 880px. `.btn-ikon` ikon di SEMUA
   ukuran layar.

   Dipakai kolom Aksi di Pengaturan > Perangkat & Sesi, dan di situ memang itu
   yang benar: tabelnya sembilan kolom (nama perangkat, versi APK, OS,
   pengguna, sesi, terakhir login, cetak, IP, lokasi), jadi kolom Aksi
   berteks akan mendorong Lokasi & IP keluar layar bahkan di monitor —
   padahal keduanya justru alasan tabel ini dibuka.

   Caranya SAMA dengan .btn-aksi dan sengaja tidak dibuat ulang dengan cara
   lain: teksnya disembunyikan dengan font-size:0 (bukan display:none) supaya
   tetap terbaca pembaca layar, tingginya dipatok karena font-size:0 ikut
   meruntuhkan line-height, dan `gap:0` wajib karena teks selebar nol TETAP
   jadi item flex sehingga gap bawaan .btn menggeser ikonnya dari titik
   tengah. Penjelasan panjangnya ada di blok .btn-aksi.

   `title` & `aria-label` tetap ditulis di markup-nya — yang pertama untuk
   tooltip mouse, yang kedua supaya pembaca layar menyebut satu label pendek
   alih-alih membaca teks tombol yang tersembunyi. */
.btn-ikon{
  font-size:0;
  width:34px; min-width:34px;
  height:34px; padding:0;
  justify-content:center; align-items:center;
  gap:0;
}
.btn-ikon::before{
  font-family:"bootstrap-icons" !important;
  font-size:14.5px; line-height:1;
  font-weight:normal; font-style:normal;
  -webkit-font-smoothing:antialiased;
}
.btn-ikon-keluarkan::before{ content:"\f1c3"; }  /* bi-box-arrow-right */
.btn-ikon-hapus::before{ content:"\f5de"; }      /* bi-trash */

/* Sasaran sentuh di ponsel: 34px cukup untuk kursor mouse, tidak untuk ibu
   jari. Dilebarkan ke 38px dengan jarak antar-tombol yang sama seperti
   .btn-aksi, dan dijaga tetap sebaris — dua tombol ikon yang membungkus
   membuat tiap baris tabel dua kali lebih tinggi tanpa alasan. */
@media (max-width:880px){
  .btn-ikon{ width:38px; min-width:38px; height:38px; }
  .actions-cell:has(.btn-ikon){ flex-wrap:nowrap; gap:8px; }
  .tbl-scroll td:has(.btn-ikon), .tbl-scroll th.th-aksi{
    min-width:102px; padding-left:8px; padding-right:10px;
  }
}

/* --------------------------------------------------------------------------
   Kartu berisi tabel: judul + tombol aksi di header, tabelnya menempel
   penuh ke tepi kartu (tanpa .pad) supaya garis baris sejajar rapi dengan
   sisi kartu. Dipakai Manajemen Peran & Manajemen Pengguna.
   -------------------------------------------------------------------------- */
.card-head{
  display:flex; align-items:flex-start; justify-content:space-between; gap:14px;
  padding:17px 20px; border-bottom:1px solid var(--line);
  background:linear-gradient(180deg, var(--sheen-down), transparent);
}
.card-head h3{font-family:var(--font-brand); font-size:14.5px; font-weight:700; letter-spacing:-.005em; margin:0 0 3px 0;}
.card-head .rowsub{margin-top:0; line-height:1.5;}
.card-head .btn{flex-shrink:0;}
.card-body{padding:20px;}
.card > .tbl-scroll > table th:first-child{border-top-left-radius:0;}
.card > .tbl-scroll > table th:last-child{border-top-right-radius:0;}

/* Tabel yang lebih lebar dari kartunya DIGESER, bukan terpotong. .card
   sengaja overflow:hidden (memotong sudut, lihat di atas) -- tanpa pembungkus
   ini kolom yang tidak kebagian tempat hilang begitu saja dan tidak bisa
   dijangkau sama sekali, paling terasa di ponsel. Yang menggeser adalah
   tabelnya sendiri: header kartu, filter, dan tombol aksinya ada di LUAR
   .tbl-scroll jadi tetap diam di tempat saat tabelnya digeser. */
.tbl-scroll{overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch;}

/* Judul kartu versi ringkas untuk kartu ber-.pad (tanpa .card-head).
   Sebelumnya tiap pemakaian menulis ulang style inline yang sama —
   termasuk font-family 'Fraunces' yang sudah tidak dimuat lagi. */
.card-title{font-family:var(--font-brand); font-size:14.5px; font-weight:700; letter-spacing:-.005em; margin:0 0 3px 0;}
.card-title + .rowsub{margin:0 0 16px 0;}

/* Kepala kartu per kategori di halaman Produk Jadi */
.group-head{
  display:flex; align-items:center; gap:9px;
  padding:14px 20px; border-bottom:1px solid var(--line);
  background:linear-gradient(180deg, var(--sheen-down), transparent);
  font-size:12.5px; font-weight:700; color:var(--good-dark);
}
:root[data-theme="dark"] .group-head{color:var(--good);}
.group-dot{width:8px; height:8px; border-radius:50%; background:var(--good); flex-shrink:0;}
.group-count{margin-left:auto; font-weight:600; color:var(--ink-soft); font-size:11.5px;}

/* Lebar kolom dikunci lewat <colgroup> supaya proporsinya tetap dan tidak
   ikut melar mengikuti panjang isi (nama peran panjang dulu bikin kolom
   Aksi kepepet). */
.tbl-fixed{table-layout:fixed;}
.tbl-fixed td{vertical-align:middle; overflow-wrap:anywhere;}
.th-aksi{text-align:right;}

.tbl-users .c-user{width:34%;}
.tbl-users .c-peran{width:22%;}
.tbl-users .c-dibuat{width:20%;}
.tbl-users .c-aksi{width:24%;}
.tbl-roles .c-role{width:26%;}
.tbl-roles .c-akses{width:52%;}
.tbl-roles .c-aksi{width:22%;}
.tbl-kanal .c-jenis{width:70%;}
.tbl-kanal .c-aksi{width:30%;}

/* Tabel-tabel di Laporan Stok — sama alasannya dengan .tbl-fixed di atas:
   dulu tanpa colgroup, lebar tiap kolom ikut menyesuaikan isi baris demi
   baris jadi berubah-ubah antar kartu (kolom "Item" kartu satu bisa jauh
   lebih sempit dari kartu lain padahal sama-sama menampilkan nama bahan),
   dan tabel Posisi Stok yang 7 kolom jadi terasa sempit/berdesakan tanpa
   jatah lebar yang jelas per kolom. */
.tbl-posisi-stok .c-item{width:24%;}
.tbl-posisi-stok .c-jenis{width:12%;}
.tbl-posisi-stok .c-stok{width:12%;}
.tbl-posisi-stok .c-beli{width:14%;}
.tbl-posisi-stok .c-harga{width:14%;}
.tbl-posisi-stok .c-nilai{width:14%;}
.tbl-posisi-stok .c-status{width:10%;}

.tbl-rincian-stok .c-periode{width:12%;}
.tbl-rincian-stok .c-produk{width:26%;}
.tbl-rincian-stok .c-omzet{width:16%;}
.tbl-rincian-stok .c-bahan{width:18%;}
.tbl-rincian-stok .c-kemasan{width:28%;}

.tbl-produk-terjual .c-produk{width:50%;}
.tbl-produk-terjual .c-terjual{width:20%;}
.tbl-produk-terjual .c-omzet{width:30%;}

.tbl-bahan-terpakai .c-bahan{width:45%;}
.tbl-bahan-terpakai .c-terpakai{width:25%;}
.tbl-bahan-terpakai .c-nilai{width:30%;}

.tbl-kemasan-terpakai .c-kemasan{width:58%;}
.tbl-kemasan-terpakai .c-total{width:42%;}

.user-cell{display:flex; align-items:center; gap:10px; min-width:0;}
.user-cell-text{min-width:0;}
.user-cell-text .rowname{display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.user-avatar{
  width:32px; height:32px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center;
  background:var(--good-bg); color:var(--good-dark);
  font-size:11.5px; font-weight:800; letter-spacing:.02em;
}
:root[data-theme="dark"] .user-avatar{background:var(--good); color:#0C2410;}

/* Chip daftar menu yang boleh diakses sebuah peran */
.menu-chips{display:flex; flex-wrap:wrap; gap:5px;}
.menu-chip{
  display:inline-block; padding:3px 9px; border-radius:100px;
  background:var(--surface-sunken); border:1px solid var(--line);
  font-size:10.5px; font-weight:600; color:var(--ink-soft); white-space:nowrap;
}

/* Checkbox hak akses menu di modal peran */
.menu-check-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:8px;}
/* ===================== BARIS CENTANG =====================

   `.field label.menu-check`, bukan `.menu-check` saja: baris-baris ini
   hidup DI DALAM `.field` di modal Edit Peran, Edit Pengguna (termasuk
   pilihan dapur), dan Pengaturan Notifikasi — dan `.field label` di bawah
   menyetel `display:block`. Dua-duanya satu-kelas-satu-elemen, jadi yang
   menang cuma soal mana yang ditulis belakangan: barisnya pernah runtuh
   jadi label di bawah kotaknya karena itu. Spesifisitas yang lebih tinggi
   di sini membuatnya tidak lagi bergantung urutan tulis. */
.menu-check,
.field label.menu-check{
  display:flex; align-items:center; gap:10px; cursor:pointer;
  padding:10px 12px; border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--surface-2); font-size:13px; line-height:1.4;
  box-shadow:var(--nm-raised-sm);
  transition:border-color .15s var(--ease), background .15s var(--ease), box-shadow .15s var(--ease);
}
.menu-check:hover{ border-color:var(--good); }

/* Kotak centang digambar sendiri. Kotak bawaan browser tidak bisa diberi
   sudut, ukuran, maupun bayangan yang konsisten — bentuknya berbeda-beda
   di Chrome, Safari, dan WebView Android, dan `accent-color` hanya
   mengubah warnanya saja. */
.menu-check input[type="checkbox"]{
  appearance:none; -webkit-appearance:none; margin:0;
  width:19px; height:19px; flex:0 0 auto;
  border-radius:6px; border:1px solid var(--line-strong);
  background:var(--surface); cursor:pointer;
  box-shadow:var(--nm-pressed);
  display:grid; place-content:center;
  transition:background .14s var(--ease), border-color .14s var(--ease), box-shadow .14s var(--ease);
}
/* Tanda centangnya bentuk, bukan huruf "✓": glif centang berbeda lebar &
   posisinya di tiap huruf sistem, jadi ia tidak pernah benar-benar rata di
   tengah kotaknya. */
.menu-check input[type="checkbox"]::after{
  content:''; width:11px; height:11px;
  transform:scale(0); transition:transform .13s var(--ease);
  box-shadow:inset 1em 1em #fff;
  clip-path:polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
.menu-check input[type="checkbox"]:checked{
  background:var(--good); border-color:var(--good);
  box-shadow:0 2px 6px -2px rgba(47,174,76,.55);
}
.menu-check input[type="checkbox"]:checked::after{ transform:scale(1); }
.menu-check input[type="checkbox"]:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.menu-check input[type="checkbox"][disabled]{ opacity:.45; cursor:not-allowed; }
.menu-check:has(input[disabled]){ opacity:.6; cursor:not-allowed; }

/* Radio: BULAT, dan itu bukan hiasan. Di "Dapur yang Bisa Dipantau" (modal
   Edit Pengguna) radio & checkbox berdiri bersebelahan dalam satu kolom —
   "Semua dapur / Pilih sendiri" radio, lalu daftar dapurnya checkbox.
   Bentuk yang berbeda itu yang memberi tahu mana yang hanya boleh satu dan
   mana yang boleh banyak, sebelum satu kata pun dibaca. */
.menu-check input[type="radio"]{
  appearance:none; -webkit-appearance:none; margin:0;
  width:19px; height:19px; flex:0 0 auto;
  border-radius:50%; border:1px solid var(--line-strong);
  background:var(--surface); cursor:pointer;
  box-shadow:var(--nm-pressed);
  display:grid; place-content:center;
  transition:background .14s var(--ease), border-color .14s var(--ease), box-shadow .14s var(--ease);
}
.menu-check input[type="radio"]::after{
  content:''; width:9px; height:9px; border-radius:50%; background:#fff;
  transform:scale(0); transition:transform .13s var(--ease);
}
.menu-check input[type="radio"]:checked{
  background:var(--good); border-color:var(--good);
  box-shadow:0 2px 6px -2px rgba(47,174,76,.55);
}
.menu-check input[type="radio"]:checked::after{ transform:scale(1); }
.menu-check input[type="radio"]:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

/* Baris yang TERPILIH diberi penegasan sendiri — sekumpulan dua puluh baris
   yang cuma berbeda di kotak 19px sulit dipindai cepat. `:has()` tidak
   didukung browser lama; di situ barisnya tetap rapi, cuma tanpa sorotan
   ini. */
.menu-check:has(input:checked){
  border-color:var(--good); background:var(--good-soft);
}
:root[data-theme="dark"] .menu-check input[type="checkbox"]:checked::after{ box-shadow:inset 1em 1em #0C2410; }
:root[data-theme="dark"] .menu-check input[type="radio"]::after{ background:#0C2410; }

/* Submenu yang punya beberapa KARTU (Pengaturan Umum/Aplikasi, lihat
   dapurkasir_katalog_kartu()) -- checkbox kartunya dikelompokkan dalam
   kotak sendiri per submenu, supaya jelas beda dari submenu yang cuma
   satu checkbox polos (Pengaturan Operasional/Landing). */
.menu-check-kartu-group{
  grid-column:1 / -1;
  border:1px dashed var(--line-strong); border-radius:10px;
  padding:10px 11px;
}
.menu-check-kartu-group + .menu-check-kartu-group{ margin-top:8px; }
.menu-check-kartu-label{ font-size:12px; font-weight:700; color:var(--ink-soft); margin-bottom:7px; }
.menu-check-kartu-group .menu-check-grid{ margin:0; }

.toolbar{display:flex; justify-content:space-between; align-items:center; margin-bottom:16px; gap:10px; flex-wrap:wrap;}

/* ==========================================================================
   Modal
   ========================================================================== */
/* ===================== MODAL TIDAK PAKAI BLUR =====================

   Sama dengan lembar bawah: blur di sini dibayar SETIAP FRAME selama modal
   muncul & beranimasi, dan modal pembayaran adalah layar yang paling
   sering dibuka sepanjang hari — tiap transaksi. Di tablet kasir yang
   lebih lawas, itu terasa sebagai tersendat tepat saat kasir sedang
   terburu-buru.

   Yang hilang cuma kaburnya isi di balik modal — isi yang memang sudah
   tertutup modal itu sendiri. Bidang gelap beralpha di `.overlay` sudah
   cukup memisahkan modal dari halaman di belakangnya. */
/* ===================== MODAL SELALU LAPISAN PALING ATAS =====================

   z-index 70, dulu 50. Angka 50 itu DI BAWAH lembar bawah (59) dan
   latarnya (58), dan itu bug yang kelihatan: tombol "Keluar" ada DI DALAM
   lembar Profil, jadi dialog konfirmasi "Keluar dari akun ini?" dirender
   di BELAKANG lembar yang memanggilnya — pemakai menekan Keluar lalu
   seolah tidak terjadi apa-apa.

   Diperbaiki di sini, bukan cuma dengan menutup lembarnya lebih dulu di
   JS: modal adalah lapisan paling atas secara konsep, dan setiap tombol
   baru di dalam lembar yang membuka modal akan mengulang bug yang sama
   kalau yang dibetulkan hanya satu pemanggilnya. */
.overlay{
  position:fixed; inset:0; background:rgba(18,20,14,.55);
  display:flex; align-items:center; justify-content:center; z-index:70; padding:20px;
}
/* Latarnya PEKAT: modal ini dipakai mengetik angka uang, dan teks form di
   atas bidang tembus pandang sulit dibaca. */
.modal{
  background:var(--surface);
  border-radius:var(--radius-xl); width:100%; max-width:470px;
  box-shadow:var(--shadow-lg);
  max-height:88vh; overflow-y:auto; border:1px solid var(--line);
}
/* Kepala modal lengket DI DALAM elemen yang sudah nge-blur. Ia tidak boleh
   ikut tembus pandang — judulnya akan bertumpuk dengan isi yang tergulir di
   bawahnya — jadi diberi latar pekat sendiri. */
.modal-head{
  padding:19px 24px 15px 24px; border-bottom:1px solid var(--line);
  display:flex; justify-content:space-between; align-items:center; gap:12px;
  position:sticky; top:0; background:var(--surface); z-index:1;
}
.modal-head h3{font-family:var(--font-brand); margin:0; font-size:16px; font-weight:700; letter-spacing:-.01em;}
.modal-body{padding:20px 24px;}
.field{margin-bottom:15px;}
.field label{display:block; font-size:12px; font-weight:600; color:var(--ink-soft); margin-bottom:6px;}
/* Kolom isian = bidang yang MASUK ke dalam (neumorphic pressed). Garis
   tepinya dilembutkan jadi --line karena kedalamannya sudah dikerjakan
   bayangan; garis tebal + bayangan dalam sekaligus terlihat berat. */
/* `:not([type="checkbox"]):not([type="radio"])` BUKAN kerapian — tanpa itu,
   kotak centang di modal Edit Peran & Edit Pengguna ikut kena perlakuan
   kolom teks: `width:100%` membuatnya melar jadi batang selebar kolom, dan
   bayangan-dalamnya menggambar palung gelap di sepanjang batang itu.
   Keduanya `<input>` di dalam `.field`, jadi selektor ini memang mengenai
   mereka — dulu pun sudah, cuma belum kelihatan karena gayanya masih
   datar. */
.field input:not([type="checkbox"]):not([type="radio"]), .field select{
  width:100%; padding:10px 13px; border-radius:var(--radius-sm);
  border:1px solid var(--line); font-size:13.5px;
  background:var(--surface-2); color:var(--ink);
  box-shadow:var(--nm-pressed);
  transition:border-color .15s var(--ease), box-shadow .15s var(--ease), background .15s var(--ease);
}
.field input:not([type="checkbox"]):not([type="radio"]):hover:not(:focus), .field select:hover:not(:focus){border-color:var(--good);}
/* Saat fokus, cincin jingga DITAMBAHKAN di atas bayangan dalamnya — bukan
   menggantikannya. Kalau menggantikan, kolom yang sedang diisi mendadak
   terlihat rata sementara kolom lain di sebelahnya masih cekung. */
.field input:focus, .field select:focus{
  outline:none; border-color:var(--accent);
  box-shadow:var(--nm-pressed), 0 0 0 3px var(--accent-soft);
}
.field input::placeholder{color:var(--ink-faint);}
.field input[disabled], .field select[disabled]{opacity:.55; cursor:not-allowed;}
.img-preview-wrap{margin-top:10px;}
.img-field-preview{max-width:220px; max-height:140px; width:auto; height:auto; border-radius:10px; border:1px solid var(--line-strong); object-fit:cover; display:block;}
.field-row{display:flex; gap:11px; flex-wrap:wrap;}
.field-row .field{flex:1; min-width:150px;}

/* Slider (mis. Ukuran Logo di Layar Login) — reset total dari box input
   teks bawaan .field input di atas, digambar ulang jadi trek + gagang. */
.slider-row{display:flex; align-items:center; gap:12px;}
.slider-val{min-width:44px; text-align:right; font-weight:700; font-size:13px; color:var(--ink); flex-shrink:0;}
.field input[type="range"]{
  -webkit-appearance:none; appearance:none;
  flex:1; width:auto; height:20px; padding:0; border:none; background:transparent;
  cursor:pointer;
}
.field input[type="range"]:focus{outline:none; box-shadow:none;}
.field input[type="range"]::-webkit-slider-runnable-track{
  height:6px; border-radius:100px; background:var(--line-strong);
}
.field input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:18px; height:18px; margin-top:-6px; border-radius:50%;
  background:var(--accent); border:3px solid var(--surface);
  box-shadow:0 0 0 1px var(--line-strong);
  cursor:pointer; transition:transform .15s var(--ease);
}
.field input[type="range"]:hover::-webkit-slider-thumb{transform:scale(1.12);}
.field input[type="range"]::-moz-range-track{
  height:6px; border-radius:100px; background:var(--line-strong);
}
.field input[type="range"]::-moz-range-thumb{
  width:18px; height:18px; border-radius:50%; border:3px solid var(--surface);
  background:var(--accent); box-shadow:0 0 0 1px var(--line-strong);
  cursor:pointer; transition:transform .15s var(--ease);
}
.field input[type="range"]:hover::-moz-range-thumb{transform:scale(1.12);}

/* Ikon mata show/hide DI DALAM kolom password itu sendiri — bukan tombol
   terpisah di sebelahnya. Dipakai login, setup, koneksi database,
   Manajemen Pengguna, dan Pengaturan Aplikasi (semuanya field password). */
.password-field{position:relative;}
.password-field input{padding-right:40px;}
.password-toggle{
  position:absolute; right:4px; top:50%; transform:translateY(-50%);
  width:30px; height:30px; display:flex; align-items:center; justify-content:center;
  background:transparent; border:none; color:var(--ink-soft); font-size:15px;
  border-radius:7px; transition:color .12s var(--ease), background .12s var(--ease);
}
.password-toggle:hover{color:var(--ink); background:var(--surface-sunken);}
.modal-foot{padding:15px 24px 20px 24px; display:flex; gap:8px; justify-content:flex-end; border-top:1px solid var(--line); background:var(--surface-sunken);}
.close-x{background:none;border:none;font-size:18px;color:var(--ink-soft); line-height:1; padding:5px; border-radius:8px; transition:background .1s;}
.close-x:hover{background:var(--surface-sunken); color:var(--ink);}

.recipe-item{display:flex; gap:8px; align-items:center; margin-bottom:8px;}
.recipe-item select{flex:1.4;}
.recipe-item input{flex:1;}
.recipe-item .rm{background:var(--warn-bg); color:var(--warn); border:none; border-radius:8px; width:32px; height:36px; font-size:14px;}

/* ==========================================================================
   Stats & dashboards
   ========================================================================== */
/* --------------------------------------------------------------------------
   Kartu angka ringkasan. Empat warna aksen aslinya dipertahankan, tapi
   sekarang dipasang lewat variabel --stat-accent supaya bisa dipakai
   sekaligus oleh rel warna di kiri dan lingkaran titiknya — dan supaya
   kartu laba/rugi bisa menimpanya cukup dengan mengganti satu variabel,
   bukan menimpa beberapa properti sekaligus.
   -------------------------------------------------------------------------- */
.stat-row{display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:14px; margin-bottom:22px;}
.stat{
  --stat-accent: var(--good);
  position:relative; overflow:hidden;
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--radius-md); padding:17px 18px 17px 22px;
  box-shadow:var(--shadow-xs);
  transition:box-shadow .2s var(--ease), border-color .2s var(--ease), transform .2s var(--ease);
}
/* Rel warna di tepi kiri — pengganti garis tebal di atas kartu */
.stat::before{
  content:''; position:absolute; left:0; top:0; bottom:0; width:4px;
  background:var(--stat-accent);
}
/* Semburat warna yang sangat tipis di pojok kanan atas, memberi kesan
   kedalaman tanpa memasukkan warna baru ke palet. */
.stat::after{
  content:''; position:absolute; right:-30px; top:-30px; width:110px; height:110px;
  border-radius:50%; background:var(--stat-accent); opacity:.07; pointer-events:none;
}
.stat:hover{box-shadow:var(--shadow-sm); border-color:var(--line-strong); transform:translateY(-2px);}
.stat:nth-child(2){--stat-accent: var(--accent);}
.stat:nth-child(3){--stat-accent: var(--yellow-dark);}
.stat:nth-child(4){--stat-accent: var(--good-dark);}
/* --good-dark di dark mode adalah hijau sangat gelap (dipakai sebagai
   warna LATAR seperti sidebar/footer, bukan warna teks/ikon) — di atas
   --surface yang juga gelap, rel & ikon kartu ke-4 jadi nyaris tak
   kelihatan. Dark mode pakai --good (hijau terang) supaya tetap kontras. */
:root[data-theme="dark"] .stat:nth-child(4){--stat-accent: var(--good);}
.stat .k{
  display:flex; align-items:center; gap:7px;
  font-size:10.5px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--ink-soft); font-weight:700; margin-bottom:9px;
}
/* Ikon tidak boleh ikut mengecil saat labelnya panjang & terpaksa dua baris */
.stat .k i{flex-shrink:0;}
.stat .v{font-family:var(--font-mono); font-size:23px; font-weight:600; letter-spacing:-.02em; line-height:1.15;}
/* Kartu angka untung/rugi di Laporan Keuangan — warnanya ikut hasilnya,
   bukan ikut urutan kartu, jadi harus menang atas .stat:nth-child(n). */
.stat.pos, .stat.pos:nth-child(3){--stat-accent: var(--good);}
.stat.pos .v{color:var(--good-dark);}
:root[data-theme="dark"] .stat.pos .v{color:var(--good);}
.stat.neg, .stat.neg:nth-child(3){--stat-accent: var(--warn);}
.stat.neg .v{color:var(--warn);}
.angka-pos{color:var(--good-dark);}
:root[data-theme="dark"] .angka-pos{color:var(--good);}
.angka-neg{color:var(--warn);}

/* Pemilih periode di Riwayat & Laporan (bulanan / tahunan) */
.periode-head{display:flex; align-items:flex-start; justify-content:space-between; gap:14px; margin-bottom:16px;}
.periode-head h3{margin-bottom:4px;}
.periode-box .desc{margin-bottom:0;}
.periode-tag{
  flex-shrink:0; padding:6px 13px; border-radius:100px;
  background:var(--good-bg); color:var(--good-dark);
  font-size:11.5px; font-weight:800; letter-spacing:.02em; white-space:nowrap;
}
:root[data-theme="dark"] .periode-tag{background:var(--good); color:#0C2410;}

.conv-box{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  padding:20px 22px; margin-bottom:18px; box-shadow:var(--shadow-sm);
  transition:box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.conv-box:hover{box-shadow:var(--shadow-md); border-color:var(--line-strong);}
.conv-box h3{font-family:var(--font-brand); margin:0 0 4px 0; font-size:14.5px; font-weight:700; letter-spacing:-.005em;}
.conv-box .desc{font-size:12.5px; color:var(--ink-soft); margin-bottom:16px;}
.conv-controls{display:flex; gap:11px; align-items:flex-end; flex-wrap:wrap;}
.conv-controls .field{margin-bottom:0; min-width:172px;}
.conv-result{margin-top:16px; display:flex; flex-direction:column; gap:8px;}
.conv-result .r{display:flex; justify-content:space-between; align-items:center; gap:12px; background:var(--surface-sunken); border:1px solid var(--line); border-radius:var(--radius-sm); padding:10px 14px; font-size:13px;}
.conv-result .r b{font-family:var(--font-mono); color:var(--accent); white-space:nowrap;}

/* Daftar "label di kiri, nilai di kanan" di Pengaturan > Info Aplikasi.
   Mirip .conv-result di atas, tapi nilainya boleh membungkus ke baris
   berikutnya -- pesan commit dan path folder aplikasi bisa panjang sekali
   dan di .conv-result (white-space:nowrap) akan menjebol lebar kartunya. */
.info-list{display:flex; flex-direction:column; gap:8px;}
.info-list .r{
  display:flex; justify-content:space-between; align-items:baseline; gap:8px 14px; flex-wrap:wrap;
  background:var(--surface-sunken); border:1px solid var(--line); border-radius:var(--radius-sm);
  padding:10px 14px; font-size:13px;
}
.info-list .r > span{color:var(--ink-soft); flex-shrink:0;}
.info-list .r > b{font-weight:700; text-align:right; overflow-wrap:anywhere;}
.info-list .r > b.mono{font-family:var(--font-mono); font-size:12px; color:var(--accent);}
.info-list .r > .btn{flex-shrink:0;}

/* ---------- Modal Pembayaran (tombol "Proses" di Kasir) ----------
   Ringkasan pesanan sebelum transaksi benar-benar disimpan. Dibuat dari
   komponen yang sudah ada (.modal, .field, .btn) -- yang di bawah cuma
   tata letak khusus daftar item & baris totalnya. */
.pm-modal{ max-width:460px; }

/* Daftar item dibatasi tingginya lalu digulir: pesanan 20 baris tidak
   boleh mendorong Total & tombol Bayar keluar dari layar -- justru dua
   hal itu yang paling dibutuhkan kasir. */
.pm-items{
  max-height:38vh; overflow-y:auto;
  border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--surface-sunken); padding:4px 0; margin-bottom:14px;
}
.pm-item{
  display:flex; align-items:flex-start; gap:12px;
  padding:9px 13px; border-bottom:1px solid var(--line);
}
.pm-item:last-child{ border-bottom:none; }
.pm-item-kiri{ flex:1; min-width:0; }
.pm-item-sub{ font-weight:600; font-size:13px; white-space:nowrap; }
.pm-varian{ font-size:11.5px; color:var(--accent); margin-top:1px; }

.pm-total{
  display:flex; justify-content:space-between; align-items:baseline; gap:12px;
  padding-top:11px; border-top:1px solid var(--line-strong);
  font-family:var(--font-heading); font-size:19px; font-weight:600; color:var(--ink);
}
.pm-total .mono{ font-family:var(--font-mono); font-size:20px; }

.pm-kembali{ font-size:13px; font-weight:600; color:var(--good-dark); min-height:18px; }
:root[data-theme="dark"] .pm-kembali{ color:var(--good); }
.pm-kembali.neg{ color:var(--warn); }
/* Mode gelap WAJIB ditulis ulang untuk .neg juga: ":root[data-theme]
   .pm-kembali" di atas lebih spesifik daripada ".pm-kembali.neg", jadi
   tanpa baris ini baris "Kurang"/"Sisa belum dibayar" tetap tercat hijau di tema
   gelap -- persis kebalikan dari maksudnya. */
:root[data-theme="dark"] .pm-kembali.neg{ color:var(--warn); }

/* Tiga tombol (Batal / Bayar / Bayar & Cetak) sering tidak muat sebaris di
   layar sempit -- dibiarkan membungkus daripada terpotong. */
.pm-foot{ flex-wrap:wrap; }

/* ---------- Kolom "Data Pelanggan" (metode Bayar Nanti) ----------
   Dibungkus kotak sendiri di dalam modal pembayaran supaya jelas bahwa dua
   kolom ini MILIK metode Bayar Nanti -- bukan kolom tambahan yang selalu
   ada, dan bukan bagian dari ringkasan pesanan di atasnya. */
.pm-pelanggan{
  border:1px dashed var(--line-strong); border-radius:var(--radius-sm);
  background:var(--surface-sunken); padding:12px 13px; margin:12px 0 14px 0;
}
.pm-pelanggan-judul{
  display:flex; align-items:center; gap:6px;
  font-size:12px; font-weight:700; color:var(--ink); margin-bottom:10px;
}
.pm-pelanggan-judul i{ color:var(--accent); font-size:13px; }
.pm-wajib{
  font-size:9.5px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
  color:var(--warn); background:var(--warn-bg); border-radius:100px; padding:2px 7px; margin-left:5px;
}
/* Ikon di chip metode pembayaran -- sekadar penanda cepat, jangan sampai
   lebih menonjol dari labelnya. */
.chip i{ font-size:11.5px; margin-right:2px; opacity:.9; }

/* ---------- Tujuan pembayaran (rekening Transfer / gambar QRIS) ----------
   Bentuknya sengaja berbeda dari .pm-pelanggan (garis penuh, bukan putus-
   putus): ini BUKAN kolom yang harus diisi kasir, tapi informasi yang
   dibacakan/ditunjukkan ke pelanggan. */
.pm-tujuan{
  border:1px solid var(--line-strong); border-radius:var(--radius-sm);
  background:var(--surface-sunken); padding:12px 13px; margin:12px 0 14px 0;
}
/* Nomor rekening dibuat paling menonjol di kotaknya -- itu satu-satunya
   bagian yang benar-benar dibacakan digit per digit. */
.pm-rekening{ font-size:17px; font-weight:800; letter-spacing:.06em; color:var(--ink); margin:2px 0 3px; }
.pm-qris{ text-align:center; }
/* Cukup besar untuk discan dari layar kasir, tapi tidak sampai mendorong
   tombol Bayar keluar dari modal di layar pendek. */
.pm-qris img{
  width:100%; max-width:230px; height:auto; border-radius:var(--radius-sm);
  background:#fff; padding:8px; border:1px solid var(--line); margin:2px 0 6px;
}

/* ---------- Kartu "Metode Pembayaran" (Pengaturan Operasional) ----------
   Satu blok per metode. Dibuat grid supaya di layar lebar keempatnya
   berdampingan (sekali lihat langsung tahu mana yang aktif), dan menumpuk
   sendiri di layar sempit. */
.mb-daftar{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:12px; margin-top:14px;
}
.mb-blok{
  border:1px solid var(--line-strong); border-radius:var(--radius-sm);
  background:var(--surface-sunken); padding:12px 13px;
}
.mb-blok-head{
  display:flex; align-items:center; justify-content:space-between;
  gap:10px; flex-wrap:wrap; margin-bottom:10px;
}
.mb-blok-nama{ display:flex; align-items:center; gap:7px; font-size:13.5px; font-weight:700; color:var(--ink); }
.mb-blok-nama i{ color:var(--accent); font-size:15px; }
/* Checkbox "Aktif" dibuat sekecil mungkin supaya tidak tampak lebih penting
   dari nama metodenya. */
.mb-aktif{ padding:5px 9px; font-size:11.5px; }
.mb-blok-isi .field{ margin-bottom:8px; }
.mb-qris-pratinjau{ text-align:center; margin-bottom:10px; }
.mb-qris-pratinjau img{
  width:100%; max-width:150px; height:auto; border-radius:var(--radius-sm);
  background:#fff; padding:6px; border:1px solid var(--line);
}

/* ---------- Laporan Bayar Nanti ---------- */
.nanti-filter{
  display:grid; grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);
  gap:14px; align-items:end;
}

/* Rincian item DI DALAM satu sel tabel: nama+qty di kiri, subtotal rata
   kanan. Sengaja tidak jadi tabel bersarang -- cukup dua kolom flex supaya
   barisnya tetap ikut tinggi selnya dan tidak bikin tabel dalam tabel. */
.nanti-items{ display:flex; flex-direction:column; gap:3px; min-width:190px; }
.bn-line{ display:flex; align-items:baseline; gap:10px; font-size:12.5px; }
.bn-nama{ flex:1; min-width:0; }
.bn-varian{ color:var(--accent); font-size:11px; }
.bn-satuan{ color:var(--ink-soft); font-size:11px; white-space:nowrap; }
.bn-harga{ font-weight:600; white-space:nowrap; }

/* Riwayat setoran di modal terima pembayaran */
.nanti-riwayat{
  border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--surface-sunken); overflow:hidden;
}
.bn-setor{
  display:flex; justify-content:space-between; align-items:baseline; gap:12px;
  padding:8px 12px; font-size:12px; color:var(--ink-soft);
  border-bottom:1px solid var(--line);
}
.bn-setor:last-child{ border-bottom:none; }
.bn-setor .mono{ color:var(--ink); font-weight:600; }

/* Kolom nominal + tombol "Lunasi Semua" sebaris */
.nanti-nominal{ display:flex; gap:8px; align-items:stretch; }
.nanti-nominal input{ flex:1; min-width:0; }
.nanti-nominal .btn{ flex-shrink:0; }

/* Pratinjau struk di Pengaturan > Pengaturan Printer -- meniru kertas thermal, jadi
   sengaja monospace dan TIDAK membungkus baris (white-space:pre): kalau
   dibungkus, baris yang sebenarnya kepanjangan akan terlihat muat padahal
   di kertas terpotong. Lebar kolomnya sudah dihitung server sesuai
   character_width printer, lihat backend/printer/render_teks.php. */
.struk-preview{
  margin:0; padding:16px;
  background:var(--surface-sunken); border:1px solid var(--line); border-radius:var(--radius-sm);
  font-family:var(--font-mono); font-size:12px; line-height:1.45; color:var(--ink);
  white-space:pre; overflow-x:auto; min-height:120px;
}

/* Kartu "Informasi Pengembang" di Pengaturan > Info Aplikasi -- foto
   profil GitHub + nama/username/bio, lihat viewPengaturanInfo() di
   assets/js/app.js. flex-wrap supaya avatar & teks bisa turun bertumpuk
   di layar sempit alih-alih memepetkan bio-nya. */
/* Kartu Pengembang isinya cuma foto + nama + bio -- tanpa .rowsub di
   bawah judulnya seperti kartu lain, jadi jaraknya diatur di sini:
   judulnya dirapatkan ke tepi atas kartu (padding kartu dikurangi,
   margin judul dinolkan), lalu diberi jarak tegas ke area profilnya. */
.card-pengembang{ padding-top:16px; }
.card-pengembang .card-title{ margin-top:0; margin-bottom:14px; }

.dev-profile{display:flex; gap:20px; align-items:center; flex-wrap:wrap;}
.dev-avatar{
  width:84px; height:84px; border-radius:50%; object-fit:cover; flex-shrink:0;
  border:1px solid var(--line-strong); background:var(--surface-sunken);
}
.dev-profile-body{flex:1; min-width:180px; max-width:72ch;}
.dev-profile-nama{font-family:var(--font-brand); font-size:17px; font-weight:700; color:var(--ink);}
.dev-profile-bio{margin:7px 0 0 0; font-size:13.5px; line-height:1.55; color:var(--ink-soft); white-space:pre-wrap; overflow-wrap:anywhere;}

/* ---------- Changelog (Info Aplikasi) ---------- */
.cl-head{ margin:14px 0 10px; display:flex; flex-direction:column; gap:4px; align-items:flex-start; }
.cl-head > div:first-child{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.cl-tag{ font-size:14.5px; font-weight:800; color:var(--ink); }
.cl-head-judul{ font-family:var(--font-brand); font-size:15px; font-weight:700; color:var(--ink); }

/* Daftar ringkas: satu baris per perubahan (lihat htmlChangelogRingkas()
   di assets/js/app.js). Badge kolom kiri dibuat selebar tetap supaya
   deretan NEW/UPD/FIX lurus ke bawah dan barisnya mudah dipindai. */
.cl-ringkas{ margin-top:14px; }
.cl-item{ display:flex; gap:11px; align-items:flex-start; padding:9px 0; }
/* Garis tipis pemisah antar-versi — bukan antar-baris, jadi satu versi
   tetap terbaca sebagai satu kelompok. */
.cl-item.cl-awal-versi{ border-top:1px solid var(--line); padding-top:13px; margin-top:4px; }
.cl-ringkas > .cl-item:first-child{ border-top:none; margin-top:0; padding-top:0; }
.cl-badge{ min-width:42px; text-align:center; flex-shrink:0; margin-top:1px; }
.cl-item-teks{ flex:1; min-width:0; font-size:13.5px; line-height:1.6; color:var(--ink-soft); overflow-wrap:anywhere; }
.cl-item-versi{ color:var(--ink); font-weight:700; }
.cl-item-modul{ color:var(--ink); font-weight:600; }

/* Satu kelompok versi di modal Riwayat Rilis: kepala berisi tag & tanggal
   rilisnya, lalu daftar perubahan dalam bentuk yang sama persis dengan
   kartu Changelog. */
.rr-versi{ margin-bottom:18px; }
.rr-versi:last-child{ margin-bottom:0; }
.rr-versi-head{
  display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;
  padding-bottom:8px; border-bottom:1px solid var(--line-strong);
}
.rr-versi .cl-ringkas{ margin-top:6px; }

/* Modal yang isinya memang panjang (mis. Riwayat Rilis) — .modal biasa
   470px terlalu sempit untuk daftar perubahan sepanjang itu. */
.modal-lebar{ max-width:720px; }

/* Logo/inisial aplikasi di kepala kartu "Periksa Pembaruan" (Info
   Aplikasi) -- sudutnya sengaja memakai rasio radius:ukuran yang sama
   dengan .brand .mark di sidebar (12/38) supaya bentuknya konsisten
   dengan logo di tempat lain, walau ukurannya beda. */
.app-mark{
  width:52px; height:52px; border-radius:16px; flex-shrink:0; overflow:hidden;
  background:var(--accent); color:#fff;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--font-brand); font-weight:800; font-size:19px;
  box-shadow:0 4px 12px -3px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.22), inset 0 0 0 1px rgba(255,255,255,.1);
}
.app-mark img{width:100%; height:100%; object-fit:cover;}

/* "Zona Berbahaya" (tombol Reset Database) di card Detail Koneksi MySQL */
.danger-zone{
  margin-top:16px; padding:14px 16px; border:1px solid var(--warn); border-radius:var(--radius-md);
  background:var(--warn-bg);
  display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap;
}
.danger-zone-head strong{display:block; color:var(--warn); font-size:13.5px; margin-bottom:2px;}
.danger-zone-head .rowsub{margin:0;}

/* Kartu pilihan cakupan di langkah 1/3 modal Reset Database */
.reset-scope-opt{
  display:flex; align-items:flex-start; gap:10px;
  border:1px solid var(--line); border-radius:var(--radius-sm);
  padding:12px 13px; margin-bottom:10px; cursor:pointer;
  transition:border-color .14s var(--ease), background .14s var(--ease);
}
.reset-scope-opt:last-child{margin-bottom:0;}
.reset-scope-opt:hover{border-color:var(--line-strong); background:var(--surface-sunken);}
.reset-scope-opt input[type="radio"]{margin-top:3px; flex-shrink:0; accent-color:var(--warn);}
.reset-scope-opt strong{display:block; font-size:13.5px; margin-bottom:2px;}
.reset-scope-opt:has(input:checked){border-color:var(--warn); background:var(--warn-bg);}

.recipe-card{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  padding:18px 20px; box-shadow:var(--shadow-sm);
  transition:box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.recipe-card:hover{box-shadow:var(--shadow-md); border-color:var(--line-strong);}
/* Kartu resep berdampingan dua kolom di layar lebar — sebelumnya satu
   kartu per baris, jadi daftar produk yang panjang butuh scroll jauh.

   ===================== `min()` DI DALAM minmax() — JANGAN DILEPAS =====================

   Dulu di sini `minmax(420px, 1fr)` polos, dan itu BUG di layar HP.

   `auto-fill` memang menurunkan JUMLAH kolom jadi satu saat wadahnya
   sempit, tapi ia TIDAK menurunkan LEBAR MINIMUM track-nya. Jadi di HP
   360px, satu-satunya kolom itu tetap dipaksa 420px — kartunya 60px lebih
   lebar daripada layar, dan karena `.recipe-card` bukan elemen bergulir,
   yang melebar adalah SELURUH HALAMAN. Peramban ponsel menjawabnya dengan
   mengecilkan tampilan supaya muat; itulah yang terlihat & dilaporkan
   sebagai "kartu produk di Resep & Produksi suka zoom".

   Terukur sebelum perbaikan, dengan isi dipaksa selebar 360px:
     grid-template-columns terhitung : 420px
     lebar .recipe-card              : 420px  (lebih 60px)
     elemen yang ikut meluber        : 41

   `min(420px, 100%)` membuat lantainya ikut menyusut saat wadahnya lebih
   sempit dari 420px: di layar lebar tetap 420px (dua kolom seperti dulu,
   tidak ada yang berubah), di HP jadi 100% — pas selebar layar, sama
   seperti kartu di halaman lain.

   Ini satu-satunya grid di berkas ini yang lantainya pernah melebihi lebar
   HP; yang lain semuanya <=248px. */
.recipe-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(min(420px,100%),1fr)); gap:16px; align-items:start;}
.recipe-grid > *{min-width:0;}

/* Kepala kartu: nama produk + tombol "Atur Resep".

   `min-width:0` pada h4 WAJIB — anak flex bawaannya menolak menyusut lebih
   kecil daripada isinya, jadi nama produk yang panjang ("Nasi Goreng
   Spesial Telur Mata Sapi Plus Kerupuk Udang Jumbo") akan MELEBARKAN
   kartunya alih-alih dibungkus ke baris berikutnya. Dan tombolnya
   `flex-shrink:0` supaya yang mengalah namanya, bukan tombolnya — tombol
   yang tergencet jadi dua huruf tidak bisa dibaca maupun ditekan. */
.recipe-card .rh{display:flex; justify-content:space-between; align-items:flex-start; gap:10px; margin-bottom:11px;}
.recipe-card .rh h4{font-family:var(--font-heading); margin:0; font-size:15px; font-weight:600; min-width:0; overflow-wrap:anywhere;}
.recipe-card .rh .btn{flex-shrink:0;}

/* ===================== BARIS BAHAN: DIGULIR, BUKAN MELEBARKAN KARTU =====================

   Satu baris = nama bahan di kiri, takaran di kanan. Nama bahan datang dari
   pemakai dan bisa sepanjang apa pun ("Minyak Goreng Kelapa Sawit Kemasan
   Jerigen 5 Liter"), sementara takaran di kanan TIDAK boleh ikut terbungkus
   ke baris baru — angka yang terpisah dari satuannya salah terbaca.

   Jadi barisnya sendiri yang digulir mendatar saat isinya tidak muat,
   memakai pola `.tbl-scroll` yang sudah dipakai seluruh tabel di aplikasi
   ini. Yang bergulir SATU BARIS, bukan kartunya dan bukan halamannya —
   kartunya tetap selebar layar, persis seperti kartu di halaman lain.

   `overflow-y:hidden` ikut ditulis karena `overflow-x:auto` sendirian
   membuat sumbu Y jadi `auto` juga, dan itu memunculkan bilah gulir tegak
   setinggi beberapa piksel di dalam baris setinggi satu teks. */
.ing-line{
  display:flex; justify-content:space-between; gap:14px;
  font-size:12.5px; padding:7px 0;
  border-bottom:1px solid var(--line); color:var(--ink-soft);
}

/* Yang digulir HANYA nama bahannya, bukan seluruh barisnya.

   Percobaan pertama menggulir seluruh baris, dan itu salah dengan cara yang
   baru kelihatan saat difoto: takarannya ("150 gram / porsi") ikut terdorong
   ke luar layar, jadi yang tersisa di layar cuma nama bahan yang terpotong —
   sementara justru TAKARAN itu yang dicari orang saat membaca resep. Baris
   yang isinya tidak bisa dibaca tanpa digeser dulu lebih buruk daripada
   nama yang terpotong.

   Sekarang takarannya dipaku di kanan (`flex-shrink:0`) dan namanya yang
   mengalah. Nama lengkapnya tetap bisa dibaca dengan menggeser namanya
   sendiri — jadi tidak ada informasi yang hilang, cuma perlu satu gerakan
   untuk yang benar-benar panjang.

   `min-width:0` WAJIB: tanpa itu anak flex menolak menyusut lebih kecil
   daripada isinya, `overflow-x` tidak pernah punya alasan aktif, dan nama
   panjang kembali melebarkan kartunya — persis bug yang sedang diperbaiki. */
.ing-line > span:first-child{
  min-width:0;
  white-space:nowrap;          /* supaya MENGGULIR, bukan terbungkus */
  overflow-x:auto; overflow-y:hidden;
  -webkit-overflow-scrolling:touch;
  /* Bilah gulirnya disembunyikan: di baris setipis ini ia memakan ruang
     teks dan terlihat seperti garis yang salah. Jari tetap bisa menggeser,
     dan di desktop shift+roda tetap jalan. */
  scrollbar-width:none;
}
.ing-line > span:first-child::-webkit-scrollbar{ display:none; }
.ing-line > span:last-child{ flex-shrink:0; white-space:nowrap; }
.ing-line:last-of-type{border-bottom:none;}
.produksi-row{display:flex; gap:10px; align-items:center; margin-top:13px; padding-top:13px; border-top:1px dashed var(--line-strong); flex-wrap:wrap;}
.produksi-row input{width:92px; padding:9px 10px; border-radius:8px; border:1px solid var(--line-strong); font-family:var(--font-mono);}
.maxhint{font-size:11.5px; color:var(--good-dark); font-weight:600;}
:root[data-theme="dark"] .maxhint{color:var(--good);}
.maxhint.zero{color:var(--warn);}

.empty-state{text-align:center; padding:48px 20px; color:var(--ink-soft); display:flex; flex-direction:column; align-items:center;}
/* Ikon diberi lingkaran latar lembut — kalau berdiri sendiri di ruang
   kosong yang lebar, ikon garis tipis tampak seperti gambar gagal muat. */
.empty-state .glyph{
  width:52px; height:52px; margin-bottom:15px; border-radius:50%;
  background:var(--surface-sunken); border:1px solid var(--line);
  color:var(--ink-faint); display:flex; align-items:center; justify-content:center;
  font-size:23px; line-height:1;
}
.empty-state > div:last-child{max-width:42ch; line-height:1.55;}

.spinner{
  width:26px; height:26px; margin-bottom:14px;
  border:2.5px solid var(--line-strong);
  border-top-color:var(--accent);
  border-radius:50%;
  animation:spin .7s linear infinite;
}
@keyframes spin{ to{ transform:rotate(360deg); } }

.conn-result{
  display:flex; align-items:center; gap:9px;
  background:var(--surface-sunken); border:1px solid var(--line); border-radius:10px;
  padding:12px 14px; font-size:12.5px; color:var(--ink-soft);
}
.conn-result strong{color:var(--ink); font-weight:700;}
.conn-result .status-icon{font-size:15px; flex-shrink:0; margin-right:2px;}
.conn-result.conn-result-ok{background:var(--good-soft); border-color:var(--good); align-items:flex-start; flex-direction:column; gap:0;}
.conn-result.conn-result-ok strong{color:var(--ink);}
.conn-result.conn-result-ok .status-icon{color:var(--good);}
.conn-result.conn-result-error{background:var(--warn-bg); border-color:var(--warn); align-items:flex-start; flex-direction:column; gap:0;}
.conn-result.conn-result-error strong{color:var(--ink);}
.conn-result.conn-result-error .status-icon{color:var(--warn);}

/* ===================== TOAST TIDAK PERNAH BISA DIKLIK =====================

   `pointer-events:none` dan `visibility:hidden` di sini BUKAN kerapian — tanpa
   keduanya, kotak ini MEMATIKAN tiga tombol tengah bilah bawah di ponsel.

   Jalannya begini, dan tidak ada satu pun bagiannya yang terlihat di layar:
   toast() di assets/js/inti.js menulis `el.textContent = msg` lalu 2,2 detik
   kemudian HANYA melepas kelas `show` — teksnya tidak pernah dihapus. Jadi
   sesudah satu toast muncul sekali saja, tertinggal pil selebar kalimat
   terakhir (200-280px) yang `opacity:0`.

   Dan elemen ber-`opacity:0` TETAP menangkap ketukan. Dengan z-index 100 ia
   menang atas .botnav (60), dan dengan `bottom:24px` + `left:50%` ia duduk
   persis di pita vertikal bilah bawah (tinggi bilah 64-74px), di TENGAH.
   Hasilnya: Kemasan, Dapur, dan Menu mati total; Kasir di ujung kiri dan
   Profil di ujung kanan selamat karena meleset dari pil itu. Gejalanya
   terbaca sebagai "bilah bawahnya rusak", padahal bilahnya tidak bersalah.

   `visibility` dipakai supaya toast basi juga lepas dari pohon aksesibilitas
   — tanpa itu pembaca layar masih membacakan pesan yang sudah lama hilang
   dari pandangan.

   ===== KENAPA visibility PAKAI DELAY, BUKAN DURASI =====

   `visibility` itu properti DISKRIT: ia tidak bisa dianimasikan mulus, dan
   kalau diberi DURASI (`visibility .25s`) peramban membalikkannya di TENGAH
   transisi — 125ms. Akibatnya separuh pertama fade-in terjadi saat toast
   masih tak terlihat, lalu ia muncul mendadak di opacity 0,5. Terukur: pola
   itu sempat dipakai di sini dan membuat toast terlihat "nyangkut lalu
   menyentak".

   Yang benar DELAY, bukan durasi, dan arahnya berbeda di tiap keadaan:
     - saat MUNCUL (.show) -> `visibility 0s`, berbalik seketika supaya
       seluruh 250ms fade-in terlihat;
     - saat HILANG         -> `visibility 0s .25s`, baru berbalik SESUDAH
       fade-out selesai, supaya toast tidak lenyap mendadak di tengah fade.

   Mengosongkan textContent sesudah animasi sempat dipertimbangkan dan
   DITOLAK: ia menambah timer kedua yang harus selaras dengan durasi transisi
   di sini, dan tetap menyisakan kotak selebar padding (~40px) yang masih
   menutupi tombol tengah. */
.toast{
  position:fixed; bottom:24px; left:50%; transform:translateX(-50%) translateY(6px);
  background:var(--ink); color:var(--bg);
  padding:11px 20px; border-radius:100px; font-size:13px; font-weight:600;
  box-shadow:var(--shadow-lg); z-index:100; opacity:0;
  pointer-events:none; visibility:hidden;
  transition:opacity .25s var(--ease), transform .25s var(--ease), visibility 0s linear .25s;
}
:root[data-theme="dark"] .toast{background:#F1F4EA; color:#12140E;}
.toast.show{
  opacity:1; visibility:visible; transform:translateX(-50%) translateY(0);
  transition:opacity .25s var(--ease), transform .25s var(--ease), visibility 0s linear;
}

/* ==========================================================================
   Responsive
   ========================================================================== */
/* Antara 1240px dan 880px kolom grid sudah menumpuk (lihat aturan .col-*
   di atas), tapi kartu form yang tadinya dibatasi lebar tetap perlu
   dibatasi supaya barisnya tidak jadi terlalu panjang untuk dibaca. */
@media (max-width:1240px){
  .grid-cards{gap:16px;}
  .col-form{max-width:680px;}
  .form-columns{column-count:1;}
  .form-columns > *{max-width:680px;}
  .save-bar{max-width:680px;}
}
@media (max-width:880px){
  /* Bottom ditulis EKSPLISIT di dalam shorthand, bukan sebagai aturan
     terpisah: aturan terpisah pernah ditimpa diam-diam oleh
     `main{padding:16px}` di blok 520px yang letaknya lebih bawah, dan isi
     halaman terpotong bilah bawah tanpa ada yang terlihat salah di kode
     mana pun. */
  main{padding:20px 20px var(--ruang-botnav) 20px;}

  /* ===================== KERANJANG: BILAH MENGAMBANG YANG DILIPAT =====================

     Di layar lebar keranjang ada di kolom kanan, selalu kepampang selagi
     kasir memilih produk. Begitu kolomnya menumpuk jadi satu, keranjang
     jatuh ke BAWAH daftar produk — dan di situ ia tidak pernah terlihat
     saat sedang dipakai.

     Dua percobaan sebelumnya sama-sama salah ujungnya:
       - dipindah ke ATAS daftar  -> terlihat, tapi memakan ruang tetap
       - panel penuh mengambang   -> terlihat, tapi menutupi dua baris
                                     produk sepanjang waktu

     Yang dipakai sekarang: DISEMATKAN tapi DIRINGKAS. Yang mengambang cuma
     judul + jumlah item + tombol "Proses" (±92px) — cukup untuk memastikan
     item yang ditekan benar-benar masuk, tanpa menghalangi daftar produk
     yang berisi puluhan item. Daftar isinya dibuka saat dibutuhkan dengan
     menekan bilahnya, dan ditutup lagi dengan menekan sekali lagi.

     Daftar produk diberi cadangan ruang bawah sebesar bilah RINGKASnya
     saja — bukan sebesar keadaan terbukanya. Saat dibuka, keranjang memang
     MENUTUPI daftar, dan itu disengaja: pada saat itu yang sedang dibaca
     kasir isi keranjangnya, bukan produknya. */
  .kasir-grid{ grid-template-columns:1fr; }

  /* ===================== TIGA DI HP BIASA, 4-5 DI HP LEBAR =====================

     98px adalah angka HASIL UKUR, bukan hasil hitung di atas kertas.

     Hitungannya meleset karena lebar isi TIDAK sama dengan lebar layar
     dikurangi padding `main`: di 360px, grid-nya cuma 313px — ada ~15px
     lagi yang dimakan pembungkus di antaranya. Angka pertama yang dipakai
     (114px) dihitung dari model yang salah itu dan membuat HP 360px
     jatuh ke 2 KOLOM. Kalau angka ini perlu diubah lagi, UKUR lagi;
     jangan dihitung ulang dari lebar layar.

     Tangga yang benar-benar terukur di peramban:

       360px -> 3 kolom, foto  97px      480px -> 4 kolom, foto 100px
       390px -> 3 kolom, foto 107px      540px -> 4 kolom, foto 113px
       430px -> 3 kolom, foto 120px      620px -> 5 kolom, foto 105px

     Jadi HP biasa dijamin 3 (bukan 2), dan HP lebar memang boleh memuat
     4-5 seperti yang diminta — bukan dipaksa tetap 3, yang di layar lebar
     justru membuat kartunya melar tidak karuan.

     Angka lamanya 108px; dengan lebar isi 313px itu memberi 2 kolom di HP
     360px. Jadi 98px bukan sekadar "mengecilkan kembali" — ia memperbaiki
     kasus HP kecil yang selama ini memang sudah salah.

     Catatan untuk yang membaca riwayat: v1.20.2 justru MENGECILKAN kartu
     di sini supaya lebih banyak produk muat tanpa menggulir. Yang dibalik
     sekarang bukan jumlah kolomnya, melainkan UKURAN FOTONYA — lihat
     margin negatif .pcard-thumb di bawah. */
  .product-grid{ grid-template-columns:repeat(auto-fill,minmax(98px,1fr)); gap:8px; }
  .pcard{ padding:8px; gap:4px; border-radius:var(--radius-sm); }

  /* Foto MELEBAR SAMPAI TEPI kartu: margin negatifnya persis sebesar
     padding .pcard di atas (8px), jadi foto memakai seluruh lebar kartu
     alih-alih lebar-dikurangi-padding. Inilah yang membuat fotonya terasa
     jauh lebih besar tanpa kartunya ikut melebar.

     `width:auto` WAJIB menggantikan `width:100%` dari aturan dasar: dengan
     100%, lebarnya terkunci ke kotak isi kartu lalu margin negatif cuma
     MENGGESERNYA ke kiri — sisi kanannya menyisakan celah 16px. Dengan
     auto, .pcard yang flex-column meregangkannya sendiri, dan margin
     negatif menambah lebar di kedua sisi seperti yang dimaksud.

     .pcard sudah `overflow:hidden`, jadi sudut foto ikut terpotong rapi
     mengikuti sudut kartunya. */
  .pcard-thumb{
    width:auto;
    margin:-8px -8px 3px -8px;
    aspect-ratio:1/1;
    border-radius:var(--radius-sm) var(--radius-sm) 0 0;
    border-left:none; border-right:none; border-top:none;
  }
  .pcard-thumb.prod-thumb-kosong{ font-size:28px; }
  .pcard .pname{ font-size:11.5px; line-height:1.25; }
  .pcard .pprice{ font-size:11.5px; }
  .receipt{
    position:fixed; z-index:40;
    /* `top:auto` WAJIB — aturan dasar .receipt memberi `top:44px` untuk
       versi sticky-nya di layar lebar. Tanpa dinolkan, elemen fixed ini
       punya top DAN bottom sekaligus lalu direntang dari puncak layar. */
    top:auto;
    left:10px; right:10px;
    bottom:calc(var(--h-botnav) + 10px);
    max-height:none;
    border-top:1px solid var(--line);
    border-radius:var(--radius-lg);
    /* Pekat, BUKAN kaca — alasan yang sama persis dengan .botnav di atas,
       dan di sini bahkan lebih parah: panel ini mengambang tepat di atas
       daftar produk yang digulir, jadi backdrop-filter-nya harus mem-blur
       ulang kartu-kartu yang lewat di belakangnya pada SETIAP frame. */
    background:var(--surface);
    box-shadow:var(--shadow-lg);
  }
  /* Gerigi sobekan struk dibuang: ia bagian dari ilusi "kertas yang baru
     disobek dari mesin", dan ilusi itu rusak begitu kertasnya melayang
     dengan sudut membulat. */
  .receipt::before{ display:none; }

  .receipt-top{
    display:flex; align-items:center; justify-content:space-between; gap:10px;
    width:100%; text-align:left;
    background:transparent; border:none; border-bottom:1px dashed var(--line-strong);
    color:var(--ink);
  }
  .receipt-top{ cursor:pointer; }
  .receipt-caret{
    display:flex; color:var(--ink-soft); font-size:15px;
    transition:transform .2s var(--ease);
  }
  /* Daftar item DILIPAT secara bawaan. display:none (bukan tinggi 0)
     supaya daftar sepanjang apa pun tidak menyisakan sisa tinggi. */
  .receipt-items{ display:none; }
  body.keranjang-terbuka .receipt-items{ display:block; max-height:42dvh; }
  body.keranjang-terbuka .receipt-caret{ transform:rotate(180deg); }

  /* Tabel lebar sekarang DIGESER (lihat .tbl-scroll), bukan disembunyikan
     kolomnya. Sebelumnya kolom yang dianggap paling tidak kritis dilepas
     pakai display:none di blok 520px -- akibatnya data itu benar-benar tidak
     bisa dilihat dari ponsel, bukan cuma sempit. Tiap sel diberi lebar
     minimum supaya tabelnya melebar melewati kartu lalu pembungkusnya yang
     menggeser; jumlah kolom tidak perlu dihitung per tabel karena total
     lebarnya ikut sendiri. */
  .tbl-scroll th, .tbl-scroll td{min-width:88px;}
  /* Kolom ANGKA dirampingkan. 88px disediakan untuk kolom teks yang isinya
     bisa panjang; isi kolom angka pendek dan sudah rata kanan ("Rp9.500",
     "16"), jadi 88px di situ murni ruang kosong — dan ruang kosong itu yang
     mendorong kolom lain keluar layar sampai tertutup kolom aksi yang
     tersemat. Dua kolom angka saja sudah mengembalikan 32px. */
  .tbl-scroll th.num, .tbl-scroll td.num{min-width:72px;}
  /* Kolom centang "pilih baris" di Log Aktivitas justru harus tetap sesempit
     isinya -- min-width selalu mengalahkan width, jadi harus dikecualikan. */
  .tbl-scroll th.c-pick, .tbl-scroll td.c-pick{min-width:0;}
  /* Kolom bernama (produk/pengguna/item) butuh ruang lebih supaya namanya
     tidak terpecah jadi tiga baris. */
  .tbl-scroll .rowname{min-width:104px;}
  /* table-layout:fixed MENGABAIKAN min-width pada sel, jadi tabel .tbl-fixed
     yang kolomnya banyak harus diberi lebar minimum di tabelnya sendiri. */
  /* 470px dulu disetel untuk kolom Aksi berisi tombol BERTEKS ("Edit" +
     "Hapus"). Sejak keduanya jadi ikon 38px, lebar itu memaksa tabelnya
     meluber ~19px tanpa guna — dan yang terpotong justru tombol hapusnya. */
  .tbl-scroll > .tbl-users{min-width:440px;}
  .tbl-scroll > .tbl-rincian-stok{min-width:560px;}
  .tbl-scroll > .tbl-posisi-stok{min-width:700px;}
}

/* ==========================================================================
   BILAH BAWAH (layar sempit) + LEMBAR PROFIL

   Menggantikan tombol hamburger mengambang sebagai navigasi utama di
   ponsel. Alasannya bukan mode: di tablet dapur & HP kasir, menu yang
   paling sering ditekan harus berada dalam jangkauan IBU JARI — pojok kiri
   ATAS justru titik terjauh dari posisi tangan memegang perangkat.

   Bilah ini KACA, bukan bidang pekat: isi halaman yang tergulir di
   bawahnya masih terlihat samar, jadi pemakai tahu daftarnya masih
   berlanjut dan tidak terpotong di situ.
   ========================================================================== */
.botnav{
  display:none;
  /* z-index 60 = DI ATAS lembar (59) & latarnya (58), tapi di bawah modal
     (70). Bilah ini jadi satu-satunya hal yang tidak pernah tertimbun
     lembar: ia yang memegang penanda "lembar mana yang sedang terbuka",
     dan tiga tujuan lainnya tetap bisa ditekan tanpa menutup lembar dulu.
     Modal TETAP menang atasnya — dialog konfirmasi tidak boleh bisa
     dihindari dengan menekan navigasi di belakangnya. */
  position:fixed; left:0; right:0; bottom:0; z-index:60;
  /* `min-height`, BUKAN `height`. Tingginya perlu DIKETAHUI supaya tiap
     halaman bisa mencadangkan ruang untuknya — tapi kalau dipatok mati,
     isi bilahnya sendiri yang terpotong begitu angkanya kurang sedikit
     saja (label "Kasir" terpenggal separuh), dan itu jauh lebih kentara
     daripada cadangan ruang yang kurang 3px.

     Jadi angkanya jadi LANTAI: cukup untuk dicadangkan, dan bilahnya tetap
     boleh tumbuh kalau pemakai memperbesar ukuran huruf perangkatnya. */
  min-height:var(--h-botnav);
  /* Ruang aman iPhone: tanpa ini bilahnya tertimbun garis home indicator. */
  padding:7px 6px calc(7px + env(safe-area-inset-bottom)) 6px;
  /* ===================== KENAPA BILAH INI TIDAK PAKAI KACA =====================

     Sempat memakai backdrop-filter seperti modal & lembar, dan itu
     KESALAHAN yang terasa berat di perangkat sungguhan.

     backdrop-filter memaksa peramban mem-blur ulang apa pun yang ada di
     BELAKANG elemen — setiap frame. Bilah ini `position:fixed` dan SELALU
     di layar, tepat di atas daftar produk yang sedang digulir. Jadi
     biayanya bukan sekali saat muncul, tapi terus-menerus selama kasir
     menggulir: persis gejala "patah-patah berat" yang dilaporkan, dan
     paling parah di tablet murah yang justru dipakai di dapur.

     Kaca TETAP dipakai untuk modal, lembar menu/profil, dan bilah simpan —
     ketiganya muncul SESAAT saat dibuka, bukan menetap di layar.

     Yang dipakai di sini: latar nyaris pekat. Secara tampilan hampir tidak
     bisa dibedakan dari versi kacanya (alphanya memang sudah .72), tapi
     tidak ada satu pun blur per frame. */
  background:var(--surface);
  border-top:1px solid var(--line);
  box-shadow:0 -6px 24px -10px rgba(30,33,23,.22);
}
:root[data-theme="dark"] .botnav{ box-shadow:0 -6px 24px -10px rgba(0,0,0,.7); }

.botnav-item{
  flex:1 1 0; min-width:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  padding:6px 2px; border:none; background:transparent;
  color:var(--ink-soft); text-decoration:none;
  border-radius:var(--radius-sm);
  transition:color .15s var(--ease), background .15s var(--ease);
}
.botnav-ic{
  display:flex; align-items:center; justify-content:center;
  width:34px; height:28px; border-radius:100px; font-size:17px; line-height:1;
  transition:background .18s var(--ease), box-shadow .18s var(--ease), color .18s var(--ease);
}
.botnav-label{
  font-size:10.5px; font-weight:700; letter-spacing:.01em;
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Halaman yang SEDANG dibuka: pil jingga di balik ikonnya. Jingga dipakai
   di sini (bukan hijau seperti chip) karena ini penanda LOKASI, bukan
   pilihan yang bisa ditukar — sama dengan .navbtn.active di sidebar. */
.botnav-item.active{ color:var(--accent); }
.botnav-item.active .botnav-ic{
  background:var(--accent-soft);
  box-shadow:inset 0 1px 3px rgba(244,128,29,.18);
}
.botnav-item:active .botnav-ic{ box-shadow:var(--nm-tekan); }

/* ===================== LEMBAR TERBUKA = TOMBOLNYA IKUT AKTIF =====================

   Tombol Menu & Profil mendapat penanda jingga yang SAMA dengan Kasir /
   Kemasan / Dapur selagi lembarnya terbuka. Kelas .active-nya dipasang
   setSheet() di inti.js (tiga tujuan lain ditandai PHP saat merender
   halaman, karena penandanya tidak pernah berubah selama halaman hidup).

   Dan selama lembar terbuka, penanda di tujuan halaman DILEPAS: yang
   sedang dilihat pemakai adalah lembarnya, bukan halaman di belakangnya.
   Dua pil jingga sekaligus membuat "saya sedang di mana" jadi ambigu —
   persis alasan yang sama dengan `selected = tabAktif == t.tab &&
   !menuSheetTerbuka && !profilSheetTerbuka` di android/ui/BilahBawah.kt. */
body.menu-terbuka  .botnav-item.active:not(#botnavMenu),
body.sheet-terbuka .botnav-item.active:not(#botnavProfil){ color:var(--ink-soft); }
body.menu-terbuka  .botnav-item.active:not(#botnavMenu) .botnav-ic,
body.sheet-terbuka .botnav-item.active:not(#botnavProfil) .botnav-ic{
  background:transparent; box-shadow:none;
}

/* Avatar di tombol Profil — lingkaran berinisial, padanan .user-avatar di
   tabel pengguna supaya orang yang sama terbaca sama di dua tempat. */
.botnav-ava{
  width:26px; height:26px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--good-bg); color:var(--good-dark);
  font-size:10.5px; font-weight:800; letter-spacing:.02em;
  box-shadow:var(--nm-raised-sm);
}
:root[data-theme="dark"] .botnav-ava{ background:var(--good); color:#0C2410; }

/* ---------- Lembar profil (bottom sheet) ---------- */
/* backdrop-filter SENGAJA tidak ditulis di sini — lihat aturan
   `body.*-terbuka` di bawah. Lembar & latarnya tetap ADA di DOM selagi
   tertutup (cuma digeser keluar layar), jadi blur yang ditulis di aturan
   dasar akan terus dipelihara peramban sepanjang halaman dibuka —
   membebani setiap frame untuk sesuatu yang tidak kelihatan sama sekali. */
/* ===================== PATOKAN BAWAH = BILAH BAWAH, BUKAN TEPI LAYAR =====================

   Lembar DAN latarnya berhenti di tepi ATAS bilah bawah, tidak menutupinya.

   Dulu keduanya `bottom:0` sehingga bilah bawah ikut tertimbun. Akibatnya
   bukan cuma soal tampilan: tombol Menu/Profil yang BARUSAN ditekan jadi
   tidak terlihat sama sekali, jadi tidak ada penanda tombol mana yang
   sedang terbuka — dan tiga tujuan lain (Kasir/Kemasan/Dapur) jadi tidak
   bisa ditekan tanpa menutup lembarnya dulu.

   Bilah bawah sekarang duduk DI ATAS lembar & latarnya (z-index 60 > 59),
   jadi ia tetap terlihat, tetap bisa ditekan, dan lembarnya terbaca
   meluncur keluar dari BALIK bilah itu.

   ===== KENAPA --h-botnav-nyata, BUKAN --h-botnav =====

   `--h-botnav` adalah LANTAI, bukan tinggi sesungguhnya — lihat komentar
   `min-height` di `.botnav`. Terukur 64px sementara bilahnya 74px, jadi
   memakainya sebagai patokan posisi membuat lembar menindih bilah 10px:
   masalah yang sama, cuma lebih kecil sehingga lebih mudah lolos.

   `--h-botnav-nyata` diukur ResizeObserver di inti.js (ukurBilahBawah()).
   Nilai mundurnya tetap `--h-botnav` supaya lembar tetap kira-kira benar
   kalau JS gagal dimuat, bukan menempel ke tepi layar. */
.sheet-backdrop{
  display:none; position:fixed; z-index:58;
  top:0; left:0; right:0; bottom:var(--h-botnav-nyata, var(--h-botnav));
  background:rgba(18,20,14,.45);
  opacity:0; visibility:hidden;
  transition:opacity .22s var(--ease), visibility .22s var(--ease);
}
.sheet{
  display:none; position:fixed; left:0; right:0; bottom:var(--h-botnav-nyata, var(--h-botnav)); z-index:59;
  /* env(safe-area-inset-bottom) TIDAK ikut di sini lagi: lembar ini tidak
     lagi menyentuh tepi layar, dan ruang aman iPhone sudah dihitung
     --h-botnav (lihat media query) plus padding .botnav sendiri. Dibiarkan
     di sini = jarak bawah kebaca dobel. */
  padding:10px 16px 18px 16px;
  /* PEKAT, bukan kaca — lihat blok penjelasan di bawah aturan ini. */
  background:var(--surface);
  border:1px solid var(--line); border-bottom:none;
  border-radius:var(--radius-xl) var(--radius-xl) 0 0;
  box-shadow:var(--shadow-lg);
  /* 100% + tinggi bilah, BUKAN 100% saja. Sejak patokan bawahnya jadi tepi
     atas bilah bawah, `translateY(100%)` cuma menurunkan lembar sampai
     TEPAT menutupi bilah itu — jadi selagi "tertutup" ia justru menimbun
     navigasi. Ditambah --h-botnav supaya benar-benar keluar layar. */
  transform:translateY(calc(100% + var(--h-botnav-nyata, var(--h-botnav))));
  transition:transform .24s var(--ease);
  /* `touch-action:none` WAJIB supaya tarik-untuk-menutup bisa jalan: tanpa
     ini peramban mengklaim gerakan vertikal jari sebagai gulir halaman dan
     membatalkan pointer event kita di tengah jalan — gesturnya tidak pernah
     sampai ke pasangTarikLembar(). Daftar di dalamnya dikecualikan di
     aturan berikutnya, kalau tidak isinya ikut tidak bisa digulir. */
  touch-action:none;
}
/* Daftar panjang di lembar menu TETAP boleh digulir vertikal. pasangTarikLembar()
   sudah menolak memulai tarikan selama daftarnya belum di puncak, jadi
   keduanya tidak pernah berebut gerakan yang sama. */
.sheet-scroll{ touch-action:pan-y; }
/* Tiap lembar punya kelas <body> sendiri supaya keduanya tidak pernah naik
   bersamaan — lihat setSheet() di inti.js yang menjamin itu di sisi JS. */
body.sheet-terbuka #profilSheet{ transform:translateY(0); }
body.sheet-terbuka #profilBackdrop{ opacity:1; visibility:visible; }
body.menu-terbuka #menuSheet{ transform:translateY(0); }
body.menu-terbuka #menuBackdrop{ opacity:1; visibility:visible; }

/* ===================== KENAPA LEMBAR TIDAK PAKAI BLUR SAMA SEKALI =====================

   Sempat digerbangi supaya blur-nya cuma hidup saat lembar terbuka. Itu
   memperbaiki layar diam, tapi JUSTRU memperparah animasinya: blur menyala
   TEPAT saat lembar mulai bergeser naik, jadi selama 240ms peramban harus
   mem-blur ulang seluruh latar DI SETIAP FRAME sambil elemennya bergerak.
   Transform yang seharusnya murah (cuma menggeser lapisan yang sudah jadi)
   berubah jadi repaint penuh tiap frame — dan itulah patah-patah yang
   dilaporkan saat membuka lembar.

   Blur dibuang seluruhnya dari lembar & latarnya. Yang tersisa: latar
   pekat untuk lembarnya, dan bidang gelap beralpha untuk latarnya. Secara
   tampilan nyaris sama — yang hilang cuma kaburnya isi di balik lembar,
   yang toh tertutup lembar itu sendiri. */
body.sheet-terbuka #profilSheet,
body.menu-terbuka #menuSheet{
  /* `will-change` memberi tahu peramban untuk menaikkan lembar ini ke
     lapisannya sendiri SEBELUM animasi mulai. Tanpa itu, lapisannya baru
     dibuat di frame pertama animasi — dan frame pertama itu yang tersendat. */
  will-change:transform;
}
/* Halaman di belakang dikunci selagi lembar terbuka: tanpa ini, menggulir
   di atas lembar ikut menggulir halaman di baliknya (scroll chaining), dan
   isi lembar terasa "lepas" dari jarinya. */
body.sheet-terbuka, body.menu-terbuka{ overflow:hidden; }

/* Gagang kecil di puncak lembar — isyarat "ini bisa ditarik/ditutup" yang
   sudah jadi kebiasaan di aplikasi ponsel. */
.sheet-grip{
  width:38px; height:4px; border-radius:100px; margin:0 auto 14px auto;
  background:var(--line-strong);
}
.sheet-user{ display:flex; align-items:center; gap:12px; margin-bottom:18px; }
.sheet-ava{
  width:44px; height:44px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center;
  background:var(--good-bg); color:var(--good-dark);
  font-size:16px; font-weight:800;
  box-shadow:var(--nm-raised-sm);
}
:root[data-theme="dark"] .sheet-ava{ background:var(--good); color:#0C2410; }
.sheet-user-text{ min-width:0; }
.sheet-uname{ font-size:15px; font-weight:700; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.sheet-urole{ font-size:12px; color:var(--ink-soft); }
.sheet-sect{ margin-bottom:16px; }
.sheet-sect-label{
  font-size:10.5px; text-transform:uppercase; letter-spacing:.06em;
  font-weight:700; color:var(--ink-faint); margin-bottom:8px;
}
/* Sakelar tema versi lembar: BERLABEL, tidak cuma ikon. Di sidebar ikonnya
   sudah dikenal karena selalu kelihatan; di lembar yang jarang dibuka,
   ikon matahari/bulan tanpa teks bikin ragu. */
.theme-toggle-sheet{ display:flex; gap:8px; }
.theme-toggle-sheet button{
  flex:1; display:inline-flex; align-items:center; justify-content:center; gap:6px;
  padding:10px 6px; border-radius:var(--radius-sm);
  border:1px solid var(--line); background:var(--surface-2); color:var(--ink-soft);
  font-size:12px; font-weight:700;
  box-shadow:var(--nm-raised-sm);
  transition:color .15s var(--ease), background .15s var(--ease), box-shadow .15s var(--ease), border-color .15s var(--ease);
}
.theme-toggle-sheet button.active{
  background:var(--good); border-color:var(--good); color:#fff;
  box-shadow:inset 0 2px 6px rgba(14,70,28,.42);
}
:root[data-theme="dark"] .theme-toggle-sheet button.active{ color:#0C2410; }
.sheet-keluar{ width:100%; justify-content:center; }

/* ---------- Lembar Menu: kotak-kotak ---------- */
/* Lembar menu boleh jauh lebih tinggi daripada lembar profil (isinya bisa
   dua puluh kotak), jadi ia yang menggulir — bukan halaman di baliknya. */
/* Tingginya dibatasi supaya lembarnya sendiri yang menggulir, bukan tumbuh
   melewati layar. `display:flex` TIDAK ditulis di sini — ia disetel di
   aturan `.sheet` dalam media query. Pernah ditulis di sini dan kalah:
   `.sheet{display:block}` di media query berada LEBIH BAWAH dengan
   spesifisitas yang sama, jadi ia menang, sizing flex tidak pernah berlaku,
   dan daftar menunya memanjang melewati layar tanpa bisa digulir. */
.sheet-menu{ max-height:82dvh; }
/* `min-height:0` WAJIB, dan ini jebakan flexbox yang paling sering menggigit:
   anak flex bawaannya min-height:auto, artinya ia menolak menyusut lebih
   kecil daripada isinya — jadi `overflow-y:auto` tidak pernah punya alasan
   menggulir, dan isi yang melebihi max-height lembarnya cuma TERPOTONG.

   Akibat nyatanya saat baris ini belum ada: delapan menu Pengaturan sama
   sekali tidak bisa dijangkau dari ponsel. */
.sheet-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; }
.menu-grup-label{ margin-top:16px; }
.menu-grid{
  display:grid;
  /* auto-fill + minmax: jumlah kolomnya ikut lebar layar sendiri (3 di HP
     kecil, 4-5 di tablet) tanpa breakpoint tambahan per ukuran. */
  grid-template-columns:repeat(auto-fill, minmax(92px, 1fr));
  gap:10px;
}
.menu-tile{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
  padding:14px 8px; text-align:center; text-decoration:none;
  border-radius:var(--radius-md); border:1px solid var(--line);
  background:var(--surface); color:var(--ink);
  box-shadow:var(--nm-raised-sm);
  transition:box-shadow .16s var(--ease), border-color .16s var(--ease), transform .16s var(--ease);
}
.menu-tile:active{ transform:translateY(0); box-shadow:var(--nm-tekan); }
.menu-tile-ic{
  width:40px; height:40px; border-radius:var(--radius-sm);
  display:flex; align-items:center; justify-content:center;
  font-size:19px; line-height:1;
  background:var(--good-soft); color:var(--good-dark);
}
:root[data-theme="dark"] .menu-tile-ic{ color:var(--good); }
.menu-tile-label{
  font-size:11.5px; font-weight:700; line-height:1.3;
  /* Nama menu boleh dua baris ("Dapur Antrian Masakan"); lebih dari itu
     dipotong supaya tinggi kotak tetap seragam dalam satu baris grid. */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
/* Halaman yang sedang dibuka — jingga, sama dengan penanda di bilah bawah
   dan .navbtn.active di sidebar. */
.menu-tile.active{ border-color:var(--accent); box-shadow:var(--nm-tekan); }
.menu-tile.active .menu-tile-ic{ background:var(--accent-soft); color:var(--accent); }
.menu-tile.active .menu-tile-label{ color:var(--accent); }

@media (max-width:880px){
  /* 58px = tinggi isi bilah (ikon 28 + jarak 3 + label ~16 + padding 14),
     dibulatkan ke atas sedikit supaya tidak pernah kurang. --ruang-botnav
     menambahkan jarak nafas 16px di atasnya: isi halaman tidak boleh
     berhenti PERSIS di tepi bilah, karena baris terakhir jadi terasa
     terpotong walau sebenarnya utuh. */
  :root{
    --h-botnav: calc(64px + env(safe-area-inset-bottom));
    --ruang-botnav: calc(var(--h-botnav) + 16px);
  }
  .botnav{ display:flex; align-items:stretch; gap:2px; }
  .sheet-backdrop{ display:block; }
  /* `flex`, BUKAN `block`: lembar menu membatasi tingginya (max-height:82dvh)
     dan menyerahkan gulirannya ke .sheet-scroll. Dengan `block`, pembatas
     tinggi itu tidak pernah berlaku ke anaknya — daftar menunya memanjang
     melewati layar dan delapan menu Pengaturan tidak bisa dijangkau sama
     sekali. Baris ini yang menghidupkan sizing-nya. */
  .sheet{ display:flex; flex-direction:column; }

  /* ===================== SIDEBAR TIDAK DIPAKAI DI LAYAR SEMPIT =====================

     Dibuang sepenuhnya, bukan digeser keluar layar lalu bisa ditarik masuk
     lewat hamburger. Navigasinya kini bilah bawah + lembar kotak-kotak, dan
     menyisakan laci berisi menu yang SAMA cuma menambah jalan kedua ke
     tempat yang sama — dengan dua penanda "halaman aktif" yang harus dijaga
     tetap cocok selamanya.

     Seluruh mekanisme lacinya (tombol hamburger, latar gelap, kelas
     .sidebar-terbuka, dan JS-nya) IKUT DIBUANG, bukan disembunyikan — kode
     mati yang masih bisa dipanggil adalah kode yang suatu saat terpanggil.

     Yang ikut hilang dari layar sempit bersama sidebar: kotak Mode Demo.
     Itu alat developer yang memang dipakai dari komputer, dan lembar Profil
     tetap menyediakan ganti tema & keluar — dua hal yang benar-benar
     dibutuhkan staf dari HP. */
  .sidebar{ display:none; }

  /* Bilah simpan lengket harus berhenti DI ATAS bilah bawah, bukan di
     baliknya — tombol "Simpan" yang tertimbun navigasi membuat form
     panjang terasa tidak bisa disimpan sama sekali. */
  .save-bar{ bottom:calc(var(--h-botnav) + 8px); }

  /* ===================== 16px = AMBANG ZOOM OTOMATIS PERAMBAN =====================

     Safari iOS dan beberapa peramban Android MEMBESARKAN SENDIRI seluruh
     halaman begitu kolom isian yang ukuran hurufnya DI BAWAH 16px
     disentuh. Itu perilaku bawaan peramban, bukan bug aplikasi, dan tidak
     bisa dimatikan tanpa `maximum-scale=1` — yang justru melarang pemakai
     memperbesar halaman sama sekali (dan diabaikan iOS sejak lama).

     Paling terasa di Resep & Produksi: satu baris resep berisi dropdown
     bahan + kolom jumlah berdampingan, dan tiap kali kolom jumlah
     disentuh halaman melompat membesar lalu tata letaknya bergeser —
     persis saat pemakai sedang mengetik angka.

     Dua jalur yang kena, dan DUA-DUANYA di bawah ambang:
       .field input                 13.5px  (seluruh form aplikasi)
       .recipe-item/.produksi-row   ~13.3px (tanpa font-size sama sekali,
                                     jadi jatuh ke bawaan peramban)

     Jadi dinaikkan ke 16px untuk SEMUA kolom isian teks di layar sempit —
     bukan cuma halaman resep, karena ambangnya berlaku di mana saja.
     Checkbox, radio, dan range dikecualikan: ketiganya tidak pernah
     memunculkan papan ketik, jadi tidak pernah memicu zoom, dan 16px di
     sana malah merusak ukuran kotaknya.

     `.field select` DISEBUT TERPISAH, dan itu bukan kelebihan menulis:
     aturan `.field input…, .field select{font-size:13.5px}` punya
     kekhususan (0,1,1), sementara `select` polos cuma (0,0,1) — jadi
     selektor polos KALAH dan dropdown bahan tetap 13.5px. (Untuk `input`
     kebetulan seri di (0,3,1) karena tiga `:not([type=…])` ikut dihitung,
     lalu menang karena letaknya lebih bawah.) Diuji: tanpa baris ini
     `.field select` tetap 13.5px dan halaman masih ikut membesar. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,
  textarea,
  .field select{ font-size:16px; }
}

/* ===================== TABLET KECIL: JANGAN SAMPAI TUJUH KOLOM =====================

   Aturan `minmax(98px,…)` di atas dipilih supaya HP 360px tetap dapat 3
   kolom. Angka sekecil itu dibiarkan berlaku sampai 880px akan memberi
   tablet 7 kolom berisi foto 98px — persis kebalikan dari tujuan
   "fotonya diperbesar".

   Ambang 676px bukan angka bulat asal: di situlah aturan dasar mulai
   menghasilkan kolom KEENAM. Memulai band lebih awal akan membuat jumlah
   kolom justru BERKURANG saat layar melebar (mis. 5 kolom di 620px lalu
   4 kolom di 700px), yang terbaca seperti bug.

   Terukur dengan band ini: 700px -> 5 kolom (foto ~121px), 800px -> 5
   kolom (foto ~141px). */
@media (min-width:676px) and (max-width:880px){
  .product-grid{ grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); }
}

@media (max-width:520px){
  .kasir-grid{grid-template-columns:1fr;}
  main{padding:16px 16px var(--ruang-botnav) 16px;}
  .pagehead h1{font-size:22px;}
  .pagehead{padding-bottom:16px; margin-bottom:18px;}
  .grid-cards{gap:14px;}
  .stat-row{grid-template-columns:1fr 1fr; gap:10px;}
  .nanti-filter{grid-template-columns:1fr;}
  .nanti-items{min-width:140px;}
  .stat{padding:14px 14px 14px 18px;}
  .stat .v{font-size:19px;}
  .pad{padding:16px;}
  .card-head{padding:15px 16px;}
  .card-body{padding:16px;}
  th{padding:10px 12px;}
  td{padding:11px 12px;}
  .card-head{flex-direction:column; align-items:stretch;}
  .actions-cell{flex-wrap:wrap;}
}

/* ==========================================================================
   ANIMASI — transisi halus untuk pergantian tab & modal di Portal
   ========================================================================== */
@keyframes mainFadeIn{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:translateY(0); } }
#main.main-fade{ animation: mainFadeIn .32s var(--ease); }

@keyframes overlayFadeIn{ from{ opacity:0; } to{ opacity:1; } }
.overlay{ animation: overlayFadeIn .18s var(--ease); }

@keyframes modalPopIn{ from{ opacity:0; transform:translateY(10px) scale(.97); } to{ opacity:1; transform:translateY(0) scale(1); } }
.modal{ animation: modalPopIn .24s var(--ease); }

/* Reveal-on-scroll untuk landing page — kelas ditambah lewat JS
   (progressive enhancement, elemen tetap terlihat kalau JS gagal muat). */
.reveal{ opacity:0; transform:translateY(20px); transition:opacity .6s var(--ease), transform .6s var(--ease); }
.reveal.reveal-show{ opacity:1; transform:translateY(0); }
.reveal-group > *:nth-child(2){ transition-delay:.08s; }
.reveal-group > *:nth-child(3){ transition-delay:.16s; }
.reveal-group > *:nth-child(4){ transition-delay:.24s; }

/* ==========================================================================
   LANDING PAGE (index.php) — struktur & layout diadaptasi dari template
   food & beverage, warnanya dipetakan penuh ke token tema di atas supaya
   konsisten dengan Portal Staf dan otomatis ikut dark/light mode.
   ==========================================================================

   ===================== TIAP `transition` MENYEBUT PROPERTINYA =====================

   Aturan di bawah ini dulu memakai bentuk singkat `transition:.2s` yang
   diwarisi dari templatenya. Itu BUKAN sekadar gaya tulis — tanpa nama
   properti, nilainya jatuh ke `transition-property:all`, dan artinya
   peramban mengawasi SETIAP properti yang bisa dianimasikan pada elemen
   itu, lalu menganimasikan apa pun yang kebetulan berubah.

   Yang membuatnya mahal: bukan cuma `transform` & `opacity` yang murah
   (keduanya dikerjakan compositor, tanpa menyentuh tata letak), tapi juga
   `box-shadow`, `background`, bahkan `padding` & `width` kalau suatu saat
   ada aturan yang mengubahnya. Properti tata letak yang dianimasikan
   memaksa perhitungan tata letak ULANG di setiap frame — dan di halaman
   depan yang digulir dari HP, itu terasa langsung sebagai guliran yang
   tersangkut.

   Satu lagi yang tidak kelihatan: `all` ikut menganimasikan perubahan
   yang datang dari PERGANTIAN TEMA. Jadi menukar terang/gelap memicu
   ratusan animasi warna sekaligus. Portal sudah menambal itu dengan
   `:root.ganti-tema *` di paling bawah berkas ini; halaman depan tidak
   punya tombol tema, jadi ia tidak pernah kebagian tambalannya.

   Sekarang tiap aturan menyebut properti yang memang diubah oleh aturan
   `:hover`-nya sendiri. Tampilannya sama persis — yang hilang cuma
   pekerjaan untuk properti yang tidak pernah berubah.
   ========================================================================== */
.landing :where(a){ text-decoration:none; color:inherit; }
.landing img{ width:100%; display:block; }

.navbar{
  position:sticky; top:0; z-index:1000;
  background:rgba(255,255,255,.86);
  backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);
}
:root[data-theme="dark"] .navbar{ background:rgba(27,31,21,.86); }
.nav-container{
  max-width:1180px; margin:auto; padding:16px 25px;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
.logo{ display:flex; align-items:center; gap:10px; font-family:var(--font-brand); font-size:20px; font-weight:800; }
.logo .logo-mark{
  width:34px; height:34px; border-radius:9px; flex-shrink:0; overflow:hidden;
  background:var(--good-dark); color:#fff; display:flex; align-items:center; justify-content:center;
  font-size:14px; font-weight:800;
}
.logo .logo-mark img{ width:100%; height:100%; object-fit:cover; }
.logo span{ color:var(--accent); }
.nav-right{ display:flex; align-items:center; gap:18px; }
.nav-menu{ display:flex; gap:26px; list-style:none; font-size:13.5px; font-weight:600; }
.nav-menu a{ transition:color .2s var(--ease); color:var(--ink-soft); }
.nav-menu a:hover{ color:var(--accent); }
.nav-button{
  background:var(--accent); color:#fff; padding:10px 20px; border-radius:50px;
  font-size:13.5px; font-weight:700; transition:background-color .2s var(--ease), transform .2s var(--ease); white-space:nowrap;
}
.nav-button:hover{ background:var(--accent-hover); transform:translateY(-2px); }

.hero{
  max-width:1180px; margin:auto; min-height:560px; padding:64px 25px;
  display:grid; grid-template-columns:1fr 1fr; align-items:center; gap:50px;
}
.hero-badge{
  display:inline-block; background:var(--accent-soft); color:var(--accent-hover);
  padding:8px 15px; border-radius:50px; font-size:12.5px; font-weight:700; margin-bottom:18px;
}
:root[data-theme="dark"] .hero-badge{ color:var(--accent); }
/* min-width:0 supaya judul dengan kata panjang tidak memaksa kolom grid
   hero melar dan mendorong gambarnya keluar layar. */
.hero-content{ min-width:0; }
.hero h1{ font-family:var(--font-heading); font-size:clamp(38px, 5vw, 60px); line-height:1.06; letter-spacing:-.022em; margin-bottom:20px; font-weight:600; }
.hero h1 span{ color:var(--accent); }
.hero p{ color:var(--ink-soft); max-width:480px; margin-bottom:26px; font-size:15.5px; }
.hero-buttons{ display:flex; gap:14px; flex-wrap:wrap; }
.btn-hero-primary{
  background:var(--accent); color:#fff; padding:13px 24px; border-radius:50px; font-weight:700; transition:background-color .2s var(--ease), transform .2s var(--ease);
  display:inline-flex; align-items:center; gap:6px;
}
.btn-hero-primary:hover{ background:var(--accent-hover); transform:translateY(-3px); }
.btn-hero-secondary{ border:1px solid var(--line-strong); padding:13px 24px; border-radius:50px; font-weight:700; transition:border-color .2s var(--ease), color .2s var(--ease); }
.btn-hero-secondary:hover{ border-color:var(--accent); color:var(--accent); }

.hero-image{ position:relative; }
.hero-image img{ height:440px; object-fit:cover; border-radius:28px; box-shadow:var(--shadow-lg); }
.floating-card{
  position:absolute; bottom:22px; left:-20px; background:var(--surface);
  padding:14px 18px; border-radius:16px; box-shadow:var(--shadow-lg); border:1px solid var(--line);
}
.floating-card strong{ display:block; font-size:18px; font-family:var(--font-mono); }
.floating-card small{ color:var(--ink-soft); }

.section{ padding:80px 25px; }
.section-container{ max-width:1180px; margin:auto; }
.section-header{ text-align:center; margin-bottom:46px; }
.section-header .eyebrow{ margin:0 auto 8px auto; }
.section-header h2{ font-family:var(--font-heading); font-size:34px; letter-spacing:-.018em; margin:4px 0; font-weight:600; }
.section-header p{ color:var(--ink-soft); }

.categories{ display:grid; grid-template-columns:repeat(4, 1fr); gap:18px; }
.category{
  background:var(--surface); border:1px solid var(--line); border-radius:18px;
  padding:24px; text-align:center; transition:transform .25s var(--ease), box-shadow .25s var(--ease), border-color .25s var(--ease);
}
.category:hover{ transform:translateY(-6px); box-shadow:var(--shadow-md); border-color:var(--good); }
.category-icon{
  width:62px; height:62px; margin:auto auto 14px; display:flex; align-items:center; justify-content:center;
  background:var(--good-soft); border-radius:50%; font-size:28px;
}
.category h3{ margin-bottom:4px; font-size:15.5px; }
.category p{ color:var(--ink-soft); font-size:13px; }

.products{ display:grid; grid-template-columns:repeat(3, 1fr); gap:22px; }
.product{ background:var(--surface); border-radius:20px; overflow:hidden; border:1px solid var(--line); transition:transform .25s var(--ease), box-shadow .25s var(--ease); }
.product:hover{ transform:translateY(-6px); box-shadow:var(--shadow-md); }
.product-image{ height:210px; overflow:hidden; background:var(--surface-sunken); }
.product-image img{ height:100%; object-fit:cover; transition:transform .5s var(--ease); }
.product:hover img{ transform:scale(1.06); }
.product-content{ padding:20px; }
.product-tag{
  display:inline-block; font-size:10.5px; font-weight:700; background:var(--accent-soft); color:var(--accent-hover);
  padding:4px 10px; border-radius:20px; margin-bottom:9px; text-transform:uppercase; letter-spacing:.03em;
}
:root[data-theme="dark"] .product-tag{ color:var(--accent); }
.product h3{ font-family:var(--font-heading); font-size:18px; margin-bottom:6px; font-weight:600; }
.product-description{ color:var(--ink-soft); font-size:13.5px; margin-bottom:14px; }
.product-bottom{ display:flex; align-items:center; justify-content:space-between; }
.price{ color:var(--accent); font-size:18px; font-weight:800; font-family:var(--font-mono); }
.order-button{ background:var(--good-dark); color:white; padding:9px 16px; border-radius:30px; font-size:12.5px; font-weight:700; transition:transform .2s var(--ease), box-shadow .2s var(--ease); }
.order-button:hover{ transform:translateY(-2px); box-shadow:var(--shadow-sm); }

.promo{
  background:var(--good-dark); color:#fff; border-radius:26px; padding:50px;
  display:flex; align-items:center; justify-content:space-between; gap:30px; flex-wrap:wrap;
}
.promo h2{ font-family:var(--font-heading); font-size:32px; line-height:1.15; margin-bottom:8px; font-weight:600; }
.promo p{ color:rgba(255,255,255,.72); }
.promo-button{ background:var(--yellow); color:#241500; padding:13px 24px; border-radius:50px; font-weight:800; white-space:nowrap; transition:transform .2s var(--ease), box-shadow .2s var(--ease); }
.promo-button:hover{ transform:translateY(-2px); box-shadow:var(--shadow-md); }

.about{ display:grid; grid-template-columns:.9fr 1.1fr; gap:54px; align-items:center; }
.about-image img{ height:400px; object-fit:cover; border-radius:26px; box-shadow:var(--shadow-md); }
.about-content .eyebrow{ margin-bottom:8px; }
.about-content h2{ font-family:var(--font-heading); font-size:34px; line-height:1.15; margin:6px 0 18px; font-weight:600; }
.about-content p{ color:var(--ink-soft); margin-bottom:16px; }
.features{ display:grid; grid-template-columns:1fr 1fr; gap:13px; }
.feature{ padding:14px; background:var(--surface); border-radius:14px; border:1px solid var(--line); transition:transform .2s var(--ease), border-color .2s var(--ease); }
.feature:hover{ border-color:var(--good); transform:translateY(-2px); }
.feature strong{ display:block; margin-bottom:2px; font-size:13.5px; }
.feature span{ color:var(--ink-soft); font-size:12.5px; }

.testimonials{ display:grid; grid-template-columns:repeat(3, 1fr); gap:18px; }
.testimonial{ background:var(--surface); border:1px solid var(--line); border-radius:18px; padding:22px; transition:transform .25s var(--ease), box-shadow .25s var(--ease); }
.testimonial:hover{ transform:translateY(-4px); box-shadow:var(--shadow-md); }
.stars{ color:var(--yellow-dark); margin-bottom:13px; letter-spacing:2px; }
.testimonial p{ color:var(--ink-soft); margin-bottom:18px; font-size:13.5px; }
.customer{ display:flex; align-items:center; gap:11px; }
.avatar{
  width:38px; height:38px; background:var(--accent-soft); border-radius:50%;
  display:flex; align-items:center; justify-content:center; font-weight:800; color:var(--accent-hover); flex-shrink:0;
}
:root[data-theme="dark"] .avatar{ color:var(--accent); }
.customer strong{ font-size:13px; }
.customer small{ color:var(--ink-soft); }

.cta{ text-align:center; background:var(--accent-soft); border-radius:26px; padding:64px 30px; }
.cta h2{ font-family:var(--font-heading); font-size:36px; margin-bottom:8px; font-weight:600; }
.cta p{ color:var(--ink-soft); margin-bottom:22px; }

footer{ background:var(--good-dark); color:#fff; padding:46px 25px 22px; }
.footer-container{ max-width:1180px; margin:auto; display:flex; justify-content:space-between; gap:30px; flex-wrap:wrap; }
.footer-brand{ display:flex; align-items:center; gap:10px; margin-bottom:10px; }
.footer-logo{ font-family:var(--font-brand); font-size:20px; font-weight:800; }
.footer-description{ color:rgba(255,255,255,.62); max-width:340px; font-size:13.5px; }
/* Alamat & telepon usaha di footer landing page. list-style dimatikan di
   sini (bukan mengandalkan reset) karena <ul> di dalam footer tidak kena
   reset global mana pun di berkas ini. */
.footer-kontak{ list-style:none; margin:14px 0 0; padding:0; display:grid; gap:7px; max-width:340px; }
.footer-kontak li{ display:flex; align-items:flex-start; gap:9px; color:rgba(255,255,255,.62); font-size:13.5px; line-height:1.5; }
.footer-kontak i{ flex:0 0 auto; margin-top:2px; color:rgba(255,255,255,.5); }
.footer-kontak a{ color:rgba(255,255,255,.72); transition:color .15s; }
.footer-kontak a:hover{ color:#fff; }
.footer-links{ display:flex; gap:26px; flex-wrap:wrap; }
.footer-links a{ color:rgba(255,255,255,.75); font-size:13px; transition:color .15s; }
.footer-links a:hover{ color:#fff; }
.footer-bottom{
  max-width:1180px; margin:36px auto 0; padding-top:18px; border-top:1px solid rgba(255,255,255,.14);
  color:rgba(255,255,255,.5); font-size:12.5px; display:flex; justify-content:space-between; flex-wrap:wrap; gap:8px;
}

@media (max-width:900px){
  .nav-menu{ display:none; }
  .hero{ grid-template-columns:1fr; padding-top:40px; }
  .hero-image{ order:-1; }
  .hero-image img{ height:320px; }
  .categories{ grid-template-columns:1fr 1fr; }
  .products{ grid-template-columns:1fr 1fr; }
  .about{ grid-template-columns:1fr; }
  .testimonials{ grid-template-columns:1fr; }
  .promo{ flex-direction:column; align-items:flex-start; }
  .footer-container{ flex-direction:column; }
}
@media (max-width:600px){
  .hero{ min-height:auto; padding:40px 20px; }
  .hero h1{ font-size:38px; }
  .hero-image img{ height:260px; }
  .floating-card{ left:8px; padding:11px 14px; }
  .section{ padding:56px 20px; }
  .section-header h2{ font-size:27px; }
  .categories, .products{ grid-template-columns:1fr; }
  .promo{ padding:30px 22px; }
  .promo h2{ font-size:26px; }
  .about-content h2{ font-size:27px; }
  .features{ grid-template-columns:1fr; }
  .cta h2{ font-size:27px; }
}

/* ==========================================================================
   LOGIN / SETUP AWAL (portal/index.php, kondisi belum login)
   ========================================================================== */
.login-page{
  min-height:100vh; display:flex; align-items:center; justify-content:center; padding:24px;
  /* Cahaya lembut di belakang kartu — dua warna merek yang sudah ada,
     dipakai sangat tipis supaya halaman masuk tidak terasa kosong. */
  background-image:
    radial-gradient(ellipse 70% 52% at 50% -8%, var(--good-soft), transparent 70%),
    radial-gradient(ellipse 46% 38% at 88% 108%, var(--accent-soft), transparent 70%);
}
.login-wrap{ width:100%; max-width:392px; }
.login-brand{ text-align:center; margin-bottom:24px; }
.login-brand .mark{
  width:95px; height:95px; border-radius:24px; margin:0 auto 18px auto; overflow:hidden;
  background:var(--good-dark); color:#fff; display:flex; align-items:center; justify-content:center;
  font-family:var(--font-brand); font-weight:800; font-size:34px;
  box-shadow:var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,.16);
}
.login-brand h1{ font-family:var(--font-brand); font-size:21px; margin:0 0 5px 0; font-weight:700; letter-spacing:-.018em; }
.login-brand p{ margin:0; color:var(--ink-soft); font-size:13px; }

.login-card{ background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-xl); padding:28px; box-shadow:var(--shadow-lg); animation:modalPopIn .28s var(--ease); }
.login-card .field:last-of-type{ margin-bottom:0; }
.login-error{ background:var(--warn-bg); color:var(--warn); border-radius:10px; padding:10px 12px; font-size:12.5px; margin-bottom:14px; display:none; }
.login-error.show{ display:block; }
.login-setup-note{ background:var(--good-soft); color:var(--ink); border-radius:10px; padding:10px 12px; font-size:12.5px; margin-bottom:16px; line-height:1.5; }
.login-setup-note strong{color:var(--good-dark);}
:root[data-theme="dark"] .login-setup-note strong{color:var(--good);}
.login-submit{ width:100%; justify-content:center; margin-top:6px; }
/* Baris captcha di layar login: input kode di kiri (mengisi sisa ruang),
   gambar kode + tombol muat-ulang di kanan — ketiganya disamakan
   tingginya (--captcha-h) supaya sebaris rapi & proporsional. Lihat
   htmlVerifikasiLogin() di login.js. */
.captcha-row{ --captcha-h: 41px; display:flex; align-items:center; gap:8px; }
.captcha-row input{ flex:1; min-width:0; height:var(--captcha-h); }
.captcha-img{
  display:block; height:var(--captcha-h); width:auto; flex-shrink:0;
  border-radius:9px; border:1px solid var(--line-strong); background:var(--bg);
}
.captcha-refresh{
  width:var(--captcha-h); height:var(--captcha-h); padding:0 !important;
  flex-shrink:0; font-size:15px;
  display:inline-flex; align-items:center; justify-content:center;
}
/* Soal verifikasi matematika ("7 + 5 = ?") — pengganti .captcha-img saat
   mode verifikasi = soal matematika, sama tinggi & gaya kotaknya supaya
   tetap sebaris rapi dengan input jawaban & tombol muat-ulang. Lihat
   htmlVerifikasiLogin() di assets/js/login.js. */
.math-soal{
  display:inline-flex; align-items:center; justify-content:center;
  height:var(--captcha-h); padding:0 16px; flex-shrink:0; white-space:nowrap;
  border-radius:9px; border:1px solid var(--line-strong); background:var(--bg);
  font-family:var(--font-mono); font-weight:700; font-size:16px; letter-spacing:.02em;
}
/* Checkbox "ingat saya" di layar login — lihat htmlIngatSaya() di login.js. */
/* Checkbox polos tanpa kotak/border/latar — sengaja TIDAK pakai .menu-check
   (yang bentuknya kotak berlatar, dipakai di Manajemen Peran/checklist
   menu) supaya menyatu langsung dengan kartu login, bukan seperti elemen
   asing di antara input polos. */
.login-remember{
  display:flex; align-items:center; gap:8px; cursor:pointer;
  width:100%; margin:0 0 16px 0; font-size:13px; color:var(--ink);
}
.login-remember input{ width:15px; height:15px; flex-shrink:0; accent-color:var(--good); cursor:pointer; }
.login-back{ display:block; text-align:center; margin-top:16px; font-size:12.5px; color:var(--ink-soft); text-decoration:none; transition:color .15s; }
.login-back:hover{ color:var(--ink); }
.login-loading{ text-align:center; color:var(--ink-soft); font-size:13px; padding:20px 0; }

@media (prefers-reduced-motion: reduce){
  *{transition-duration:.001ms !important; animation-duration:.001ms !important;}
}

/* Tombol pensil di kartu profil (lembar Profil, layar sempit). Bulat & kecil
   supaya tidak bersaing perhatian dengan nama pemakai di sebelahnya — ia
   jalan masuk, bukan aksi utama. */
.sheet-edit{
  margin-left:auto; flex-shrink:0;
  width:34px; height:34px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line); background:var(--surface-2); color:var(--ink-soft);
  font-size:14px; box-shadow:var(--nm-raised-sm);
  transition:color .15s var(--ease), border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.sheet-edit:hover{ color:var(--accent); border-color:var(--accent); }
.sheet-edit:active{ box-shadow:var(--nm-tekan); }

/* ===================== TOMBOL AKSI JADI IKON DI LAYAR SEMPIT =====================

   Di ponsel, "Edit" & "Hapus" berubah jadi pensil & tong sampah. Alasannya
   ruang: kolom Aksi dengan dua tombol berteks memaksa tabel melebar jauh
   melewati layar, dan kolom yang benar-benar dibaca (nama produk, harga,
   stok) yang terdorong keluar.

   HANYA tombol Edit & Hapus yang ditandai `.btn-aksi` di markup — BUKAN
   semua tombol di dalam `.actions-cell`. Di sel yang sama ada "Cek",
   "Test Print", "Jadikan Default", "Bayar", dan "Rincian"; kalau aturan ini
   menyapu seluruh `.actions-cell .btn`, kelimanya ikut berubah jadi pensil
   dan kehilangan artinya sama sekali.

   Ikonnya digambar lewat `::before` memakai font Bootstrap Icons yang sudah
   dimuat — bukan elemen <i> tambahan di markup. Jadi teks "Edit"/"Hapus"
   tetap ADA di DOM untuk pembaca layar & `aria-label` tidak perlu
   ditambahkan 19 kali; yang disembunyikan cuma tampilannya.
   ============================================================================ */
@media (max-width:880px){
  .btn-aksi{
    /* Teksnya disembunyikan dengan font-size:0, bukan display:none — teks
       itu yang dibaca pembaca layar, jadi ia harus tetap ada di DOM.

       TINGGINYA WAJIB DIPATOK. `font-size:0` ikut meruntuhkan line-height,
       jadi tanpa baris ini tombolnya cuma setinggi ikonnya (±17px) —
       terlihat seperti pil gepeng, dan jauh di bawah sasaran sentuh yang
       bisa ditekan dengan ibu jari. 38px itu angka minimum yang nyaman. */
    font-size:0;
    width:38px; min-width:38px;
    height:38px; padding:0;
    justify-content:center; align-items:center;
    /* `gap:0` WAJIB, dan ini yang paling tidak kelihatan sebabnya: teks
       "Edit"/"Hapus" yang disembunyikan dengan font-size:0 TETAP jadi item
       flex (teks telanjang di dalam flex container jadi item anonim). Jadi
       di dalam tombol ada DUA item — ikon ::before dan teks selebar nol —
       dan `gap:7px` bawaan .btn tetap menyisipkan jarak di antara keduanya.
       Akibatnya ikonnya tergeser ~3,5px ke kiri dari titik tengah: cukup
       untuk terlihat miring, tidak cukup untuk ketahuan sebabnya. */
    gap:0;
  }
  .btn-aksi::before{
    font-family:"bootstrap-icons" !important;
    font-size:15px; line-height:1;
    font-weight:normal; font-style:normal;
    -webkit-font-smoothing:antialiased;
  }
  .btn-aksi-edit::before{ content:"\f4cb"; }   /* bi-pencil */
  .btn-aksi-hapus::before{ content:"\f5de"; }  /* bi-trash  */

  /* Dua tombol ikon berdampingan tidak boleh terlalu rapat: 38px sudah di
     ambang bawah sasaran sentuh yang nyaman, jadi jaraknya yang menjaga
     "Hapus" tidak tertekan saat yang dituju "Edit".

     `flex-wrap:nowrap` MEMBATALKAN `.actions-cell{flex-wrap:wrap}` di blok
     520px. Aturan itu benar untuk tombol BERTEKS — "Test Print" & "Jadikan
     Default" memang harus boleh turun baris. Tapi untuk dua tombol ikon
     selebar 38px, membungkus justru menumpuk pensil di atas tong sampah dan
     membuat tiap baris tabel dua kali lebih tinggi tanpa alasan.

     Sel yang memuatnya juga perlu dilebarkan: `min-width:88px` bawaan sel
     tabel menyisakan ~64px ruang isi sesudah padding — kurang untuk
     38+8+38. Diberi 108px supaya keduanya pasti sebaris. */
  .actions-cell{ gap:8px; }
  .actions-cell:has(.btn-aksi){ flex-wrap:nowrap; }

  /* Kolom aksi IKUT DIGESER bersama tabelnya — TIDAK disematkan ke tepi
     kanan. Versi tersemat sempat dicoba dan ditolak pemilik: kolom yang
     mengambang di atas tabel menutupi kolom di sebelahnya (Stok), dan
     menyembunyikan data demi tombol adalah pertukaran yang salah arah.

     Paddingnya dirapatkan dari 12px supaya kolom ini mengambil sesedikit
     mungkin ruang dari kolom data: 84px isi + 18px padding sudah cukup
     lega untuk dua tombol 38px. */
  .tbl-scroll td:has(.btn-aksi), .tbl-scroll th.th-aksi{
    min-width:102px; padding-left:8px; padding-right:10px;
  }
}

/* ===================== MEMATIKAN TRANSISI SAAT TEMA DITUKAR =====================

   Dipasang sesaat oleh tanpaTransisiSesaat() di assets/js/inti.js, lalu
   dilepas di frame berikutnya. Lihat blok penjelasan di fungsi itu:
   tanpa ini, menukar tema menyalakan ratusan animasi warna & bayangan di
   frame yang sama dan pergantiannya terasa tersendat.

   `*` beserta kedua pseudo-elemennya, dan `!important` — justru di sinilah
   pukul-rata itu benar: tujuannya memang membungkam SETIAP transisi, apa
   pun yang mendeklarasikannya dan seberapa pun spesifik selektornya. */
:root.ganti-tema *,
:root.ganti-tema *::before,
:root.ganti-tema *::after{
  transition:none !important;
}

/* ==========================================================================
   MODE HEMAT + CONTAINMENT — AGAR TETAP MULUS DI HP KELAS BAWAH
   ==========================================================================

   Bagian ini TIDAK mematikan animasi. Itu penting dibaca lebih dulu, karena
   jalan pintas yang biasa diambil ("perangkat lemah -> buang transisi")
   justru membuat aplikasinya terasa rusak, bukan ringan: tanpa transisi,
   lembar yang muncul mendadak dan tombol yang berubah seketika terbaca
   seperti layar yang nge-glitch.

   Yang dikerjakan di sini sebaliknya: membuat SETIAP FRAME jadi murah,
   supaya animasi yang sama bisa jalan penuh 60fps di perangkat yang
   sebelumnya cuma sanggup 15fps. Yang dibedakan antara perangkat kuat dan
   lemah bukan ADA/TIDAKNYA animasi, melainkan seberapa mahal satu frame.

   Satu-satunya pengecualian tetap `prefers-reduced-motion` di atas — itu
   permintaan PEMAKAI, bukan tebakan tentang perangkatnya, dan permintaan
   pemakai memang harus dipatuhi apa adanya.
   ========================================================================== */

/* ===================== 1. CONTAINMENT KARTU PRODUK =====================

   Grid Kasir adalah daftar terpanjang di aplikasi ini: katalog 150 produk
   berarti 150 kartu, masing-masing berisi foto, tiga baris teks, dan satu
   bayangan. Tanpa containment, peramban memperlakukan grid itu sebagai SATU
   kotak tata letak raksasa — jadi satu perubahan kecil di dalam satu kartu
   memaksa perhitungan ulang untuk seluruh 150-nya.

   `contain:content` = layout + paint + style. Artinya tiap kartu jadi
   batas: apa yang terjadi di dalamnya tidak pernah lagi membatalkan tata
   letak kartu lain.

   `content-visibility:auto` melangkah lebih jauh — kartu yang sedang DI
   LUAR layar tidak di-layout dan tidak dicat sama sekali. Di katalog 150
   produk, yang dikerjakan peramban tinggal ~12 kartu yang benar-benar
   terlihat. Inilah satu perubahan dengan selisih terbesar di halaman ini.

   DUA baris `contain-intrinsic-size` itu BUKAN duplikat yang bisa dirapikan:

     - Baris pertama (tanpa `auto`) adalah CADANGAN untuk peramban yang
       belum mengenal kata kunci `auto`. Tanpa baris itu, deklarasinya
       dianggap tidak sah seluruhnya — kartu di luar layar jadi setinggi
       NOL, dan seluruh grid-nya kolaps jadi satu baris gepeng.
     - Baris kedua yang dipakai peramban modern. `auto` berarti "pakai
       tinggi SUNGGUHAN yang terakhir diukur, angka di belakang hanya
       untuk perkiraan pertama" — itulah yang mencegah guliran melompat
       saat kartu masuk & keluar layar.

   Urutannya juga tidak boleh dibalik: yang didukung harus datang TERAKHIR
   supaya ia yang menang. */
.product-grid > .pcard{
  contain:content;
  content-visibility:auto;
  contain-intrinsic-size:300px;
  contain-intrinsic-size:auto 300px;
}

/* ===================== 2. SAPUAN HOVER DIBUANG DI LAYAR SENTUH =====================

   `.pcard::after` adalah lapisan gradien yang HANYA pernah terlihat saat
   kursor lewat. Di HP tidak ada kursor — jadi di layar sentuh ia 100%
   tidak akan pernah tampil, tapi tetap dibuat: satu pseudo-elemen dengan
   linear-gradient per kartu, yang ikut diperhitungkan peramban saat
   menyusun lapisan cat. Di katalog 150 produk itu 150 lapisan gradien yang
   mustahil dilihat siapa pun.

   `@media (hover:none)` adalah pertanyaan yang TEPAT di sini — bukan lebar
   layar. Tablet kasir yang dipasangi mouse tetap dapat sapuannya; HP layar
   lebar tidak, dan memang seharusnya tidak. */
@media (hover: none){
  .pcard::after{ display:none; }
}

/* ===================== 3. MODE HEMAT =====================

   Dinyalakan skrip pendeteksi di login/_layout.php (atribut data-perf di
   <html>), dan hanya untuk perangkat yang memang menunjukkan tandanya —
   RAM <=2GB, atau pemakainya sendiri menyalakan penghemat data.

   Yang diubah SEMUANYA lewat token bayangan, bukan lewat aturan per
   elemen. Itu sengaja: ada 94 deklarasi `box-shadow` di berkas ini, dan
   semuanya mengambil nilainya dari token di bawah. Jadi satu blok ini
   meringankan seluruh aplikasi sekaligus — tidak ada satu tombol pun yang
   bisa ketinggalan, dan menambah komponen baru nanti otomatis ikut.

   KENAPA BAYANGAN, bukan yang lain: bayangan ber-blur adalah hal termahal
   yang masih tersisa di aplikasi ini sesudah semua backdrop-filter dibuang
   dari portal. Biayanya naik kuadratis terhadap radius blur, DAN gaya
   neumorphic memasang DUA bayangan per elemen (cahaya dari kiri-atas,
   gelap dari kanan-bawah). Satu kartu produk = 2 bayangan blur 6-8px; satu
   grid 150 kartu = 300 bayangan yang harus dicat ulang tiap kali gridnya
   bergulir.

   Yang dipakai sebagai gantinya bayangan TUNGGAL dengan radius kecil.
   Isyarat kedalamannya tetap ada — kartunya masih terlihat timbul dari
   bidang — tapi harga catnya tinggal sebagian kecil. Arah cahayanya tetap
   dari atas-kiri supaya konsisten dengan mode penuh. */
:root[data-perf="hemat"]{
  --nm-raised:     0 1px 2px var(--nm-dark);
  --nm-raised-sm:  0 1px 2px var(--nm-dark);
  --nm-raised-lg:  0 2px 5px var(--nm-dark);
  --nm-pressed:    inset 0 1px 3px var(--nm-dark);
  --nm-tekan:      inset 0 1px 3px var(--nm-dark);

  --shadow-xs: 0 1px 2px rgba(30,33,23,.10);
  --shadow-sm: 0 1px 3px rgba(30,33,23,.12);
  --shadow-md: 0 2px 5px rgba(30,33,23,.16);
  --shadow-lg: 0 4px 10px rgba(30,33,23,.22);
  --shadow: var(--shadow-md);
}
:root[data-perf="hemat"][data-theme="dark"]{
  --shadow-xs: 0 1px 2px rgba(0,0,0,.42);
  --shadow-sm: 0 1px 3px rgba(0,0,0,.48);
  --shadow-md: 0 2px 5px rgba(0,0,0,.55);
  --shadow-lg: 0 4px 10px rgba(0,0,0,.62);
  --shadow: var(--shadow-md);
}

/* Durasi DIPENDEKKAN, bukan dinolkan.

   Di perangkat yang satu frame-nya lebih mahal, animasi 320ms tidak
   menghasilkan gerakan yang lebih halus — ia cuma memperpanjang waktu
   gerakan yang jumlah frame-nya tetap sedikit, dan jeda sebelum layar bisa
   dipakai jadi terasa. Dipendekkan ke sekitar 60% membuat ketukan terasa
   langsung merespons, sementara gerakannya masih terbaca sebagai transisi
   — bukan sebagai pergantian mendadak. */
:root[data-perf="hemat"] #main.main-fade{ animation-duration:.2s; }
:root[data-perf="hemat"] .modal{ animation-duration:.16s; }
:root[data-perf="hemat"] .overlay{ animation-duration:.12s; }

/* Sapuan gradien di kartu produk ikut dibuang di mode hemat, bahkan kalau
   perangkatnya punya kursor — di perangkat selemah ini, satu lapisan cat
   per kartu lebih berarti daripada isyarat hover-nya. */
:root[data-perf="hemat"] .pcard::after{ display:none; }

/* Blur yang masih tersisa di aplikasi tinggal di .navbar halaman depan
   (lihat aturannya di bagian LANDING PAGE). Di mode hemat ia dipadatkan:
   backdrop-filter pada elemen `position:sticky` berarti peramban mem-blur
   ulang isi halaman di belakangnya pada SETIAP frame guliran. */
:root[data-perf="hemat"] .navbar{
  backdrop-filter:none;
  -webkit-backdrop-filter:none;
  background:var(--surface);
}

/* Perkiraan tinggi kartu di layar sempit. Di HP 360px gridnya 3 kolom
   (lihat blok "TIGA DI HP BIASA" di atas), jadi kartunya ~100px lebar ->
   foto 1:1 setinggi ~100px + tiga baris teks ~70px. Angka yang terlalu
   jauh melesetnya cuma mempengaruhi SATU frame pertama — sesudah itu kata
   kunci `auto` memakai tinggi yang sungguhan — tapi perkiraan yang dekat
   membuat panjang bilah guliran sudah benar sejak awal, bukan menyusut
   sendiri sambil digulir. */
@media (max-width:880px){
  .product-grid > .pcard{
    contain-intrinsic-size:175px;
    contain-intrinsic-size:auto 175px;
  }
}

/* ==========================================================================
   BILAH PROGRES NAVIGASI KILAT
   ==========================================================================

   Garis tipis di puncak layar selagi modul sebuah menu diunduh. Lihat
   barNav() di assets/js/inti.js.

   Hanya terlihat pada kunjungan PERTAMA ke sebuah menu — sesudah itu
   modulnya sudah di memori dan perpindahannya seketika. Tapi justru di
   kunjungan pertama itulah ia dibutuhkan: tanpa isyarat apa pun, ketukan
   yang sedang menunggu unduhan terasa tidak direspons, dan pemakai
   menekannya berkali-kali.

   HANYA transform & opacity yang dianimasikan. Keduanya dikerjakan
   compositor tanpa menyentuh tata letak, jadi bilah ini tidak pernah ikut
   membebani frame yang sedang sibuk menggambar halaman barunya — kalau ia
   sampai menambah kerja layout, ia justru memperlambat hal yang sedang
   dilaporkannya. */
.nav-progres{
  position:fixed; top:0; left:0; right:0; height:2.5px;
  z-index:200;                      /* di atas sidebar & bilah bawah */
  background:var(--accent);
  transform:scaleX(0); transform-origin:0 50%;
  opacity:0; pointer-events:none;
}
/* Memanjang cepat sampai 90% lalu MELAMBAT — ia tidak tahu berapa lama
   unduhannya, dan bilah yang penuh lalu diam terbaca sebagai macet. */
.nav-progres.jalan{
  opacity:1;
  transform:scaleX(.9);
  transition:transform 2.4s cubic-bezier(.1,.8,.2,1), opacity .12s linear;
}
/* Selesai: dilarikan ke penuh lalu dipudarkan. */
.nav-progres.selesai{
  opacity:0;
  transform:scaleX(1);
  transition:transform .14s var(--ease), opacity .22s linear .08s;
}
@media (prefers-reduced-motion: reduce){
  /* Gerakannya dibuang, KEHADIRANNYA tidak: pemakai yang meminta gerakan
     dikurangi tetap butuh tahu ada yang sedang dimuat. */
  .nav-progres.jalan{ transform:scaleX(1); transition:none; }
}
