:root{
  --gold-900:#4a3300; --gold-700:#8a6200; --gold-500:#c9971f; --gold-300:#e8c26a; --gold-100:#fbf1dc;
  --ink-900:#1c1a16; --ink-700:#4a4640; --ink-400:#8a8479; --ink-100:#eeece7;
  --bg:#faf8f4; --white:#fff;
  --success:#1e7d3c; --danger:#b0292d; --warning:#b4780f; --info:#1a5a94; --accent-purple:#7c5cd6;
  --foil:linear-gradient(120deg,#f5d998,#c9971f 45%,#8a6200 75%,#f5d998);
  --font-display:'Fraunces',ui-serif,Georgia,serif;
  --font-body:'Manrope',system-ui,-apple-system,sans-serif;
  --radius:14px; --shadow:0 4px 16px rgba(28,26,22,.13), 0 1px 3px rgba(28,26,22,.12);
  --card-border:#e3ddcc;
  --topbar-h:56px; --bottomnav-h:68px;
  --ease:cubic-bezier(.22,.61,.36,1);
  font-size:16px;
}
*{box-sizing:border-box}
/* scrollbar-gutter:stable -- tanpa ini, halaman pendek (tanpa scrollbar) vs
   halaman panjang (ada scrollbar) py lebar viewport efektif beda ~15-17px,
   bikin .content yg di-center (margin:0 auto, lihat max-width di bawah)
   keliatan geser lebar/posisi tiap ganti halaman/tab -- mis. History tab
   Saldo (pendek) vs tab lain (panjang, ada tabel). Space scrollbar
   direservasi selalu supaya lebar efektif SELALU sama.
   TAPI ini HANYA dipasang lewat class .has-native-scrollbar (lihat script
   deteksi di src/partials/header.php, dijalankan sebelum stylesheet ini
   dimuat) -- memasangnya tanpa syarat ke SEMUA device (termasuk tablet
   layar besar dgn scrollbar overlay, mis. Samsung Galaxy Tab S) membuat
   browser menyisihkan ruang di tepi kanan viewport yg TIDAK PERNAH digambari
   scrollbar apa pun di device tsb (overlay scrollbar tidak makan ruang
   layout) -- ruang kosong permanen itu keliatan sbg "gap" tipis di tepi
   kanan layar, ketemu pas diuji di Tab S 16:10 (2026-08-13). Device dgn
   scrollbar klasik (kebanyakan browser desktop) tetap dapat perilaku stable
   ini krn deteksinya benar-benar mengukur lebar scrollbar asli, bukan
   nebak dari lebar layar/tipe device. */
/* touch-action:pan-x pan-y (bukan "manipulation") -- "manipulation" masih
   mengizinkan pinch-zoom, hanya mematikan double-tap-zoom. Membatasi ke
   pan-x/pan-y saja yang benar-benar memblokir gesture cubit di Safari 13+
   (iPhone/iPad) -- meta viewport user-scalable=no diabaikan iOS sejak iOS 10. */
html{overflow-x:hidden;overflow-y:auto;touch-action:pan-x pan-y}
html.has-native-scrollbar{scrollbar-gutter:stable}
body{margin:0;font-family:var(--font-body);background:var(--bg);color:var(--ink-900);font-variant-numeric:tabular-nums;touch-action:pan-x pan-y}
a{color:inherit;text-decoration:none}
h1,h2,h3{font-family:var(--font-display)}
.app-shell{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh}

/* Defensive responsive base: grid/flex items default to min-width:auto and refuse
   to shrink below their content's intrinsic size (classic CSS Grid/Flexbox trap) —
   this is what let wide <table>s blow up .card and its grid track on mobile. */
.grid > *{min-width:0}
.card{min-width:0}
img,table,video,iframe,pre,canvas{max-width:100%}
input,select,textarea,button{min-width:0}

.icon{width:1em;height:1em;display:inline-block;vertical-align:-3px;flex-shrink:0}

/* --- Top bar (brand + page title + user menu) --- */
.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:14px;height:var(--topbar-h);padding:0 18px;background:var(--white);box-shadow:var(--shadow);-webkit-transform:translateZ(0);transform:translateZ(0)}
.topbar-brand{display:flex;align-items:center;gap:8px;font-family:var(--font-display);font-weight:600;font-size:1.02rem;letter-spacing:.01em;color:var(--gold-700);flex-shrink:0}
.topbar-brand span{display:none}
.topbar-sep{display:none;color:var(--ink-400);flex-shrink:0}
.page-title{margin:0;font-family:var(--font-display);font-size:1.15rem;font-weight:600;color:var(--ink-900);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.topbar-user{position:relative;flex-shrink:0;display:flex;align-items:center;gap:6px}
.icon-btn{width:38px;height:38px;border-radius:50%;background:transparent;border:none;color:var(--ink-700);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}
/* Ikon gear Pengaturan & Kunci Layar sedikit lebih besar dari icon-btn lain
   (fullscreen) biar lebih kelihatan -- override cuma utk svg di dalam 2
   tombol ini. 2026-08-22: tombol Kunci Layar SEBELUMNYA kelewatan dari
   override ini (masih ikut ukuran default 20px), makanya kelihatan lebih
   tipis/kecil dibanding gear walau stroke-width-nya sama persis (1.8, lihat
   Helpers::icon()) -- bukan soal ketebalan, soal ukuran render. */
.icon-btn[title="Pengaturan"] svg, .icon-btn[title="Kunci layar"] svg{width:23px;height:23px}
.icon-btn:hover{background:var(--ink-100)}
.icon-btn .icon-minimize{display:none}
.icon-btn.is-fullscreen .icon-maximize{display:none}
.icon-btn.is-fullscreen .icon-minimize{display:flex}
/* Tombol Kunci Layar (Auto Lock) -- desktop ONLY, disembunyikan total di HP/
   tablet (bukan cuma disamarkan) sesuai permintaan fitur. Dipasangkan dgn
   cek window.innerWidth di auto-lock.js supaya konsisten dgn breakpoint
   desktop yg sama dipakai di seluruh app (lihat @media min-width:1000px
   utk .content di bawah). */
.icon-btn-desktop-only{display:none}
@media (min-width:1000px){
  .icon-btn-desktop-only{display:flex}
}
/* --- Auto Lock Layar (lihat assets/js/auto-lock.js + Pengaturan > "Auto
   Lock Layar (Desktop)") -- overlay dibangun murni via JS (bukan markup
   statis di header.php) supaya fitur benar2 TIDAK PERNAH ada di DOM sama
   sekali kalau device bukan desktop, bukan cuma disembunyikan CSS. */
.lock-overlay{position:fixed;inset:0;z-index:9999;background:var(--ink-900);display:flex;align-items:center;justify-content:center;padding:20px}
.lock-box{background:var(--white);border-radius:var(--radius);box-shadow:var(--shadow);padding:32px 28px;width:100%;max-width:340px;text-align:center}
.lock-icon{width:72px;height:72px;border-radius:50%;background:var(--gold-100);color:var(--gold-700);display:flex;align-items:center;justify-content:center;margin:0 auto 16px}
.lock-icon svg{width:36px;height:36px}
.lock-box h2{margin:0 0 8px;font-family:var(--font-display)}
.lock-box .hint{margin-bottom:18px}
.lock-box input[type="password"]{width:100%;box-sizing:border-box}
.lock-error{color:var(--danger);font-size:.82rem;margin-top:8px;text-align:left;display:none}
.lock-switch-user{display:block;margin-top:16px;font-size:.82rem;color:var(--ink-400)}
.user-chip{width:38px;height:38px;border-radius:50%;background:var(--gold-500);color:#fff;border:none;font-weight:700;font-size:1rem;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}
.user-dropdown{display:none;position:absolute;right:0;top:48px;background:#fff;border-radius:var(--radius);box-shadow:0 10px 30px rgba(0,0,0,.2);padding:14px 16px;min-width:190px;z-index:40}
.user-dropdown.open{display:block}
.user-dropdown-name{font-weight:700;font-size:.9rem}
.role-badge{display:inline-block;background:var(--gold-500);color:var(--ink-900);border-radius:20px;padding:2px 10px;font-size:.7rem;font-weight:700;margin:4px 0}
.logout-link{display:flex;align-items:center;gap:6px;margin-top:8px;color:var(--gold-700);font-weight:600}

.content{flex:1;width:100%;padding:20px 24px calc(var(--bottomnav-h) + env(safe-area-inset-bottom) + 24px);max-width:100%;min-width:0}

/* --- Bottom tab bar: "liquid" versi -- item aktif jadi bola emas yg
   menonjol lewat cekungan (notch) di tepi atas bar, dan meleleh/berpindah
   posisi dgn animasi native saat pindah menu (bukan pil/ring statis lagi).
   iOS Safari punya bug lama: elemen position:fixed bisa "lepas"/tumpang tindih
   sesaat dengan konten saat momentum-scroll berlangsung, dan translateZ(0)
   adalah perbaikan standar utk itu -- tapi properti itu jugalah yg memaksa
   elemen ini jadi compositing layer GPU sendiri. NOTE (2026-07-28):
   `will-change:transform` + `isolation:isolate` sudah dihapus krn kombinasi
   itu korupsi layer compositing di GPU Android kelas menengah (mis. Redmi
   Note 9): notch SVG (fill gelap, di-redraw tiap transition:d) + bola FAB
   yg posisinya dianimasikan terpisah jadi terlalu berat utk di-promote jadi
   layer sendiri. NOTE (2026-08-01): bug serupa (kali ini bola FAB tampak
   raksasa + notch SVG-nya tidak nge-blend/muncul spt bentuk sayap hitam)
   dilaporkan lagi di HP Redmi Note yg lebih lawas/GPU lebih lemah --
   translateZ(0) yg tersisa di sini kemungkinan masih memicu masalah GPU
   layer yg sama, jadi ikut dihapus juga (fixed-position tanpa translateZ
   masih valid krn browser modern umumnya sudah tidak butuh trik ini utk
   iOS Safari; kalau bug "lepas saat scroll" itu muncul lagi baru
   dipertimbangkan alternatif lain, jangan pasang translateZ balik). */
.bottom-nav{position:fixed;left:0;right:0;bottom:0;width:100%;z-index:35;display:flex;height:var(--bottomnav-h);padding:0 2px env(safe-area-inset-bottom);overflow:visible}

/* Bentuk bar sendiri digambar oleh path SVG ini (bukan background rect polos)
   supaya tepi atasnya bisa punya cekungan yg mengikuti posisi item aktif.
   Coordinat path dihitung ulang oleh JS (initLiquidNav di app.js) tiap kali
   posisi aktif berubah atau layar di-resize -- transition:d membiarkan
   browser sendiri yg menganimasikan perubahan bentuknya (native, tanpa rAF). */
.nav-notch-svg{position:absolute;left:0;right:0;bottom:calc(-1 * env(safe-area-inset-bottom));width:100%;overflow:visible;pointer-events:none}
.nav-notch-path{fill:var(--ink-900);stroke:var(--gold-500);stroke-width:1.5px;vector-effect:non-scaling-stroke;transition:d .45s var(--ease)}

.nav-item{position:relative;z-index:1;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:4px;min-height:var(--bottomnav-h);padding:6px 2px 8px;color:var(--ink-100);opacity:.6;font-size:.6rem;font-weight:700;letter-spacing:.01em;text-align:center;min-width:0;transition:opacity .25s var(--ease)}
.nav-item:active{transform:scale(.95)}
.nav-item.active{opacity:1}

.nav-icon{display:flex;align-items:center;justify-content:center;width:26px;height:26px;transition:opacity .2s var(--ease)}
.nav-icon .icon{width:20px;height:20px}
.nav-label{display:block;line-height:1.05;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:inherit;transition:opacity .2s var(--ease)}
/* Item aktif menyembunyikan ikonnya sendiri saja (direpresentasikan oleh bola
   .nav-fab yg melayang di atas cekungan) -- tapi teks labelnya tetap tampil
   supaya pengguna tetap tahu sedang di menu mana. */
.nav-item.active .nav-icon{opacity:0}

/* Bola emas yg "meleleh" berpindah antar-menu. Posisinya (left) diset oleh JS
   mengikuti titik tengah item aktif, dianimasikan lewat transition:left native. */
.nav-fab{position:absolute;top:-28px;left:0;width:52px;height:52px;border-radius:50%;background:var(--foil);display:flex;align-items:center;justify-content:center;box-shadow:0 10px 22px rgba(0,0,0,.28),0 3px 8px rgba(201,151,31,.4);transform:translateX(-50%);transition:left .45s var(--ease);z-index:2}
.nav-fab-icon{display:flex;color:var(--ink-900)}
.nav-fab-icon svg,.nav-fab-icon .icon{width:22px;height:22px}

/* --- Turbo Drive progress bar (navigasi antar menu tanpa full reload) ---
   Sengaja disembunyikan (2026-08-01): bar ini yg muncul saat koneksi lemah/
   upload foto besar terasa lama, terlihat spt "loading bar" yg mengganggu.
   TWA sendiri sudah fullscreen tanpa address bar, jadi tidak ada progress
   bar native Chrome yg perlu dikhawatirkan -- ini murni elemen Turbo. */
.turbo-progress-bar{display:none}

/* --- Modal ringan (dipakai menu Penjualan utk form jual per-item) --- */
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(28,26,22,.55);z-index:50;align-items:center;justify-content:center;padding:16px}
.modal-overlay.open{display:flex}
.modal-box{background:var(--white);border-radius:var(--radius);box-shadow:var(--shadow);padding:22px;width:100%;max-width:420px;max-height:90vh;overflow-y:auto}
.modal-box h3{margin:0 0 14px}

/* --- Kasir landing hero --- */
/* Dibagi grid 4:1 (kiri sambutan+omzet, kanan tombol Cetak Dokumen) --
   align-items:stretch (bukan center) supaya kolom kanan ikut setinggi penuh
   kartu hero, bukan cuma pill kecil yang mengambang di tengah. */
.kasir-hero{position:relative;display:grid;grid-template-columns:4fr 1fr;align-items:stretch;gap:16px;background:linear-gradient(135deg,var(--ink-900),var(--gold-900));color:#fff;border-radius:18px;padding:22px 24px;margin-bottom:20px;overflow:hidden}
.kasir-hero::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--foil)}
.kasir-hero-info{display:flex;flex-direction:column;justify-content:center}
.kasir-hero-label{font-family:var(--font-display);font-size:1.3rem;font-weight:600}
.kasir-hero-sub{font-size:.86rem;opacity:.85;margin-top:5px}
.kasir-hero-sub strong{font-family:var(--font-display);font-weight:600}
/* Tombol "Cetak Dokumen" -- kolom kanan (1 dari grid 4:1), desktop only
   (lihat @media max-width:720px di bawah) -- setinggi PENUH kartu hero
   (align-items:stretch di induk), gaya "glass" (border+bg putih transparan)
   supaya kontras di atas gradient gelap tanpa perlu warna baru. Ikon di
   atas + label di bawah (bukan sebaris) supaya proporsional mengisi ruang
   vertikal yang sekarang jauh lebih tinggi dari sebelumnya. */
.kasir-hero-cta{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;height:100%;padding:14px;border-radius:14px;border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.12);color:#fff;font-weight:700;font-size:.9rem;text-decoration:none;transition:background .15s var(--ease),border-color .15s var(--ease)}
.kasir-hero-cta:hover{background:rgba(255,255,255,.22);border-color:rgba(255,255,255,.55)}
.kasir-hero-cta .icon{width:24px;height:24px}
.kasir-hero-cta .icon{width:16px;height:16px;flex:none}
.btn-lg{min-height:48px;padding:13px 26px;font-size:1rem;flex-shrink:0}

.card{background:var(--white);border:1px solid var(--card-border);border-radius:var(--radius);box-shadow:var(--shadow);padding:20px;margin-bottom:20px}
/* Jarak judul card ke konten di bawahnya -- <summary> (dipakai di
   <details class="card">, 17+ tempat di app) TIDAK punya margin bawaan
   browser sama sekali (beda dari <h3> biasa yg masih dapat margin default
   UA stylesheet), jadi tanpa aturan ini kontennya nempel langsung ke judul.
   Berlaku global lewat 1 aturan supaya semua card collapsible otomatis
   dapat jarak yg sama, tanpa perlu diulang per halaman. */
.card > summary{margin-bottom:12px}
.card > h1:first-child,.card > h2:first-child,.card > h3:first-child,.card > h4:first-child{margin-top:0;margin-bottom:12px}
.grid{display:grid;gap:16px;margin-bottom:20px}
/* Card collapsible (<details class="card">) TIDAK boleh ikut stretch bareng
   card lain di baris grid yg sama -- default .grid pakai align-items:stretch
   supaya card statistik dkk tetap sama tinggi, tapi utk <details> itu bikin
   kotak kosong raksasa saat tertutup (tetap dipaksa setinggi sibling yg lagi
   terbuka). Di-scope ke details.card doang supaya .card biasa tidak berubah. */
.grid details.card{align-self:start}
.grid-4{grid-template-columns:repeat(4,1fr)}
.grid-3{grid-template-columns:repeat(3,1fr)}
.grid-2{grid-template-columns:repeat(2,1fr)}

/* Kartu statistik: dulu blok gradasi penuh warna (khas admin template generik),
   sekarang kartu tenang + garis aksen tipis di atas + ikon jadi watermark --
   variannya diatur lewat custom property --accent, bukan background inline. */
.stat-card{position:relative;background:var(--white);color:var(--ink-900);border:1px solid var(--card-border);border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow);overflow:hidden;--accent:var(--gold-500)}
.stat-card::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--accent)}
.stat-card .label{font-size:.74rem;font-weight:700;color:var(--ink-400);text-transform:uppercase;letter-spacing:.04em}
.stat-card .value{font-family:var(--font-display);font-size:1.6rem;font-weight:600;margin-top:6px;color:var(--ink-900)}
.stat-card--success{--accent:var(--success)}
.stat-card--danger{--accent:var(--danger)}
.stat-card--info{--accent:var(--info)}
.stat-card--purple{--accent:var(--accent-purple)}
.stat-card-sub{font-size:.72rem;color:var(--ink-400);margin-top:4px}
.stat-card-link{font-size:.68rem;display:block;margin-top:2px;color:var(--accent);font-weight:700}

/* Tabel dibungkus scroll horizontal sendiri (bukan dari lebar layar) supaya
   tabel lebar tidak pernah mendobrak keluar dari .card di ukuran manapun. */
/* Tabel lebar dibungkus scroll horizontal sendiri (lihat .table-scroll, dipasang
   otomatis oleh JS di app.js) -- SENGAJA bukan .card yang di-overflow:auto,
   supaya elemen lain di card yang sama (pagination-nav, hint, dsb) tidak ikut
   ke-geser bareng tabelnya di mobile. */
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
table{width:100%;border-collapse:collapse;font-size:.9rem;min-width:max-content}
/* Browser bug (Chromium): sebuah <td> ber-display:flex (dipakai utk grup
   tombol aksi Detail/Print/Hapus yg wrap rapi, lihat kasir/list.php dkk)
   di dalam table{min-width:max-content} di atas bikin engine salah hitung
   intrinsic width tabel jadi puluhan ribu px (kolom lain jadi ke-dorong
   keluar layar, kelihatan "acak"/tombol Hapus hilang krn ke-geser off-
   screen) -- dibuktikan lewat pengukuran manual, min-width:max-content
   HANYA jadi masalah kalau ada td flex di dalamnya, jadi di-nol-kan
   khusus utk tabel yg punya kolom aksi seperti itu. */
table:has(td[style*="display:flex"]){min-width:0}
/* Plain label:value info tables (2 columns, no header row) should behave
   like a normal table -- wrap long values instead of inheriting the
   max-content sizing above (that rule exists for genuinely wide multi-column
   data tables so THEY scroll instead of breaking .card; applied to a simple
   key/value table it just forces unnecessary horizontal scroll and cuts long
   values off-screen, e.g. a long alamat/bank name in kasir/detail.php). */
.table-info{min-width:0}
.table-info td{word-break:break-word}
th,td{padding:10px 12px;text-align:left;border-bottom:1px solid var(--ink-100)}
th{background:var(--gold-100);color:var(--gold-900);font-weight:600}
tr:hover td{background:#faf6ec}

/* display:inline-flex + explicit height (not just vertical padding) is what
   guarantees every .btn/.btn-sm/.btn-lg renders the exact same height
   regardless of whether it holds plain text, an icon+text combo, or sits
   next to a differently-sized sibling in a flex toolbar row -- padding-only
   sizing drifts a few px depending on line-height/icon/font rendering,
   which is what caused buttons to visually "hug" their text unevenly. */
.btn{display:inline-flex;align-items:center;justify-content:center;text-align:center;gap:6px;min-height:38px;padding:9px 18px;border-radius:10px;border:none;font-weight:700;font-family:var(--font-body);cursor:pointer;font-size:.9rem;line-height:1.15;transition:transform .12s var(--ease),background .15s var(--ease),box-shadow .15s var(--ease),opacity .15s var(--ease)}
.btn:active{transform:scale(.96)}
.btn-primary{background:var(--gold-500);color:#fff;box-shadow:0 2px 8px rgba(201,151,31,.35)}
.btn-primary:hover{background:var(--gold-700);box-shadow:0 4px 12px rgba(201,151,31,.4)}
.btn-danger{background:var(--danger);color:#fff}
.btn-secondary{background:var(--ink-100);color:var(--ink-900)}
.btn-secondary:hover{background:#e4e1d9}
.btn-sm{min-height:30px;padding:5px 12px;font-size:.8rem}
/* Tombol yang di-disable JS (guard anti-double-submit di app.js, jalan
   otomatis di SETIAP form yang submit-nya lolos -- termasuk semua tombol
   Hapus/Simpan) TANPA aturan ini terlihat 100% identik dgn tombol normal
   (opacity/warna/cursor semua sama persis, sudah diverifikasi lewat
   getComputedStyle) -- pengguna klik Hapus, form sudah terkirim & sukses,
   tapi krn tombolnya kelihatan tidak berubah sama sekali, terkesan "diam/
   macet" sampai di-refresh manual baru kelihatan hasilnya. Ini akar
   masalahnya, bukan proses hapus yang gagal.*/
.btn:disabled{opacity:.55;cursor:not-allowed;box-shadow:none;transform:none;pointer-events:none}

/* Compact form controls that must line up with .btn-sm in the same toolbar
   row (date/month/year filters) or inline table-row edit forms -- sized to
   match .btn-sm's 30px height exactly instead of each page hand-rolling its
   own one-off padding (which is what made these rows look misaligned). */
.input-sm{height:30px;padding:0 10px;border:1px solid var(--card-border);border-radius:8px;font-size:.82rem;font-family:inherit;background:#fff;box-sizing:border-box}
.input-sm:focus{outline:2px solid var(--gold-300);border-color:var(--gold-500)}
.inline-edit-row input,.inline-edit-row select{height:30px;padding:0 10px;font-size:.85rem}

.form-group{margin-bottom:16px;min-width:0}
/* Native date/month picker input bisa punya preferred-width jauh lebih besar
   dari yg keliatan di sejumlah browser/OS (mis. iPad Safari) -- dibatasi
   max-width di layar lebar supaya rapi. TAPI beberapa versi WebKit/Mobile
   Safari diketahui TIDAK sepenuhnya menghormati width/max-width CSS pada
   input[type=date] (replaced element, ukurannya sebagian dikontrol native
   shadow-root-nya sendiri) -- makanya solusi utamanya BUKAN cuma andalkan
   max-width ini, lihat .filter-bar di bawah utk solusi yg beneran robust
   (paksa 1 kolom penuh di layar sedang ke bawah, bukan andalkan native
   control mau nurut dibatasi lebarnya atau tidak). */
input[type="date"], input[type="month"]{max-width:170px}

/* Filter bar (rentang tanggal/bulan + tombol Tampilkan/Reset/navigasi bulan)
   -- dipakai di Peleburan, Arus Kas (2x), History (arsip harga), Estimasi
   History. Solusi paling robust utk cegah "menumpuk" (mis. di iPad Safari,
   lihat komentar di atas input[type=date]) BUKAN membatasi lebar si native
   control, tapi memaksa SATU kolom penuh (flex-direction:column) di layar
   sedang ke bawah (1024px mencakup iPad portrait 768 & landscape 1024)
   supaya tidak akan pernah ada 2 elemen berebut baris yang sama sama sekali,
   apa pun lebar asli native control-nya. */
.filter-bar{display:flex;gap:12px;align-items:end;flex-wrap:wrap;margin-bottom:14px}
@media (max-width:1024px){
  .filter-bar{flex-direction:column;align-items:stretch;gap:10px}
  .filter-bar .form-group{width:100%}
  .filter-bar input[type="date"],.filter-bar input[type="month"],.filter-bar input[type="search"]{max-width:none;width:100%}
  .filter-bar > .btn,.filter-bar > a.btn{width:100%;text-align:center}
}
/* Kotak cari (kasir/list.php) -- lebar tetap cuma di layar >1024px, di bawah
   itu .filter-bar sudah paksa full-width lewat rule di atas (mis. specificity
   ID vs class bisa selisih, jadi sengaja dipisah per breakpoint biar tidak
   pernah rebutan mana yg menang). */
@media (min-width:1025px){
  #formSearchInvoice input[type="search"]{width:260px}
}
label{display:block;font-weight:600;margin-bottom:6px;font-size:.88rem;color:var(--ink-700)}
input,select,textarea{width:100%;padding:10px 12px;border:1px solid #ddd;border-radius:8px;font-size:.9rem;font-family:inherit;background:#fff}
input:focus,select:focus,textarea:focus{outline:2px solid var(--gold-300);border-color:var(--gold-500)}
.hint{font-size:.78rem;color:var(--ink-400);margin-top:4px}

/* Sembunyikan tombol spinner (naik/turun 1 angka) bawaan browser di
   input[type=number] -- murni kolom isian angka, tanpa tombol kecil itu. */
.no-spinner::-webkit-inner-spin-button,
.no-spinner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.no-spinner{-moz-appearance:textfield}

/* Badge status "hallmark stamp": cincin tipis + titik, meniru cap kadar emas
   (hallmark/assay mark) yang menjadi identitas visual bisnis ini. */
.badge{display:inline-flex;align-items:center;gap:5px;padding:3px 10px 3px 8px;border-radius:20px;font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:.03em;border:1.5px solid currentColor}
.badge::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;flex-shrink:0}
.badge-success{background:#dcf3e2;color:var(--success)}
.badge-warning{background:#fbeed4;color:var(--warning)}
.badge-danger{background:#fbdcdc;color:var(--danger)}
.badge-info{background:#dbe9f7;color:var(--info)}

.flash{padding:12px 16px;border-radius:8px;margin-bottom:16px;font-size:.9rem}
.flash.success{background:#dcf3e2;color:var(--success)}
.flash.error{background:#fbdcdc;color:var(--danger)}

.login-wrap{min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;background:linear-gradient(135deg,var(--ink-900),var(--gold-900))}
.login-card{background:#fff;border-radius:16px;padding:40px 36px;width:360px;max-width:100%;box-shadow:0 20px 50px rgba(0,0,0,.3)}
.login-card h1{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;font-size:1.3rem;text-align:center;color:var(--gold-700);margin-bottom:4px}
.login-card .sub{text-align:center;color:var(--ink-400);font-size:.85rem;margin-bottom:24px}
.login-footer{margin-top:28px;text-align:center;color:#fff;font-size:.53rem;opacity:.75;text-transform:lowercase}

/* Tab jadi pill/chip yg wrap rapi ke baris berikutnya (bukan digeser
   kanan-kiri, bukan juga underline-tab lama yg makan tinggi banyak baris
   begitu jumlah tab banyak, mis. 7 tab di History) -- ikon opsional per tab
   bikin tetap gampang dikenali walau labelnya dipendekkan. */
.tabs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:18px}
.tab{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;font-weight:600;font-size:.82rem;color:var(--ink-700);background:var(--white);border:1px solid var(--card-border);border-radius:20px;white-space:nowrap;transition:background .2s var(--ease),color .2s var(--ease),border-color .2s var(--ease)}
.tab .icon{width:14px;height:14px;flex-shrink:0}
.tab:hover{background:var(--gold-100);border-color:var(--gold-300)}
.tab.active{color:#fff;background:var(--gold-500);border-color:var(--gold-500)}
.tab.active:hover{background:var(--gold-500)}

.chart-wrap{width:100%;height:360px;margin:0 auto}

/* --- Kasir redesign: picker kartu jenis transaksi (Perhiasan / Logam Mulia) --- */
.picker-card{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px;border:2px solid var(--card-border);border-radius:var(--radius);background:#fff;cursor:pointer;text-align:center;font-family:inherit;width:100%;transition:border-color .15s var(--ease),transform .12s var(--ease),box-shadow .15s var(--ease)}
.picker-card:hover{border-color:var(--gold-300);transform:translateY(-2px);box-shadow:var(--shadow)}
.picker-card.active{border-color:var(--gold-500);background:var(--gold-100)}
.picker-card .picker-icon{width:56px;height:56px;border-radius:50%;background:var(--gold-100);color:var(--gold-700);display:flex;align-items:center;justify-content:center;transition:background .15s var(--ease),color .15s var(--ease)}
.picker-card .picker-icon .icon{width:28px;height:28px}
.picker-card.active .picker-icon{background:var(--gold-500);color:#fff}
.picker-card .picker-label{font-family:var(--font-display);font-weight:600;font-size:1.08rem;color:var(--ink-900)}
.picker-card .picker-sub{font-size:.76rem;color:var(--ink-400)}

/* --- Switch 2-kolom (tombol, tanpa tanda bulat radio) untuk Arah Transaksi & Cash/Transfer ---
   position:relative WAJIB di sini -- .switch2 input di bawah pakai
   position:absolute, dan tanpa leluhur yg punya position selain static,
   containing block-nya jatuh ke initial containing block (dokumen/viewport),
   bukan kotak .switch2 sendiri. Karena aturan global `input{width:100%}` di
   bawah, radio yg absolute itu jadi ikut resolve 100% dari LEBAR VIEWPORT
   (bukan dari .switch2 yg cuma ~300-400px) -- radionya sendiri tak terlihat
   (opacity:0) tapi footprint layout-nya yg raksasa itu bikin
   document.scrollWidth membengkak jauh melebihi viewport di halaman mana pun
   yg pakai switch ini (Arah Transaksi/Cash-Transfer di Kasir, Rotasi Layar
   di Pengaturan) -- ketemu saat audit responsivitas 2026-08-13. */
.switch2{position:relative;display:grid;grid-template-columns:1fr 1fr;gap:6px;background:var(--ink-100);padding:4px;border-radius:12px}
.switch2 input{position:absolute;opacity:0;pointer-events:none}
.switch2 label{display:flex;align-items:center;justify-content:center;padding:10px 8px;border-radius:9px;font-weight:700;font-size:.86rem;cursor:pointer;color:var(--ink-700);text-align:center;transition:background .15s var(--ease),color .15s var(--ease)}
.switch2 input:checked + label{background:var(--gold-500);color:#fff;box-shadow:0 2px 8px rgba(201,151,31,.35)}

.switch2 input:focus-visible + label{outline:2px solid var(--gold-500);outline-offset:2px}

/* --- Baris barang dinamis (Tambah Item) --- */
.item-row{position:relative;border:1px solid var(--card-border);border-radius:12px;padding:16px;padding-right:46px;margin-bottom:14px;background:var(--bg)}
.item-row-remove{position:absolute;top:10px;right:10px;width:30px;height:30px;border-radius:50%;border:none;background:var(--ink-100);color:var(--danger);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}
.item-row-remove:hover{background:#fbdcdc}
.item-row-subtotal{font-size:.82rem;color:var(--ink-700);margin-top:2px;text-align:right;font-weight:600}
.pagination-nav{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:14px;flex-wrap:wrap}
.pagination-info{font-size:.82rem;color:var(--ink-700)}

/* --- Ringkasan total (gram & pembayaran) --- */
.totals-box{display:flex;justify-content:flex-end;gap:28px;flex-wrap:wrap;background:var(--gold-100);border-radius:12px;padding:14px 18px;margin:6px 0 18px}
.totals-box .totals-item{text-align:right}
.totals-box .totals-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-700);font-weight:700}
.totals-box .totals-value{font-family:var(--font-display);font-size:1.3rem;font-weight:700;color:var(--gold-900)}

/* --- Kotak upload foto (dropzone) --- */
.upload-box{border:2px dashed var(--card-border);border-radius:14px;padding:26px 16px;text-align:center;cursor:pointer;background:#fff;transition:border-color .15s var(--ease),background .15s var(--ease);position:relative}
.upload-box:hover{border-color:var(--gold-500);background:var(--gold-100)}
.upload-box.has-file{border-style:solid;border-color:var(--success);padding:0;overflow:hidden;height:220px}
.upload-box .upload-icon{color:var(--ink-400);margin-bottom:10px}
.upload-box .upload-icon .icon{width:34px;height:34px}
.upload-box .upload-label{color:var(--ink-700);font-weight:600;font-size:.9rem;margin-bottom:6px}
.upload-box .upload-action{color:var(--gold-700);font-weight:700;font-size:.85rem}
.upload-box input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer}
.upload-box .upload-preview{display:none;width:100%;height:100%;object-fit:cover}
.upload-box.has-file .upload-preview{display:block}
/* Foto potret (KTP/barang dijepret tegak) diputar 90 derajat spy tetap
   tampil landscape, konsisten dgn perlakuan di kasir/detail.php + PDF
   (lihat fixPhotoFrameOrientation di app.js) -- sebelumnya cuma diterapkan
   di halaman detail/PDF, TIDAK di pratinjau step 4 ini, jadi terlihat
   seolah cuma foto KTP yang "auto-rotate" (karena KTP kartu memang wajar
   difoto melebar) padahal foto barang yang dijepret tegak juga perlu ini. */
.upload-box .upload-preview.is-portrait{position:absolute;top:50%;left:50%;width:auto;height:auto;max-width:none;max-height:none;object-fit:none;transform:translate(-50%,-50%) rotate(90deg)}
.upload-box.has-file .upload-placeholder{display:none}
.upload-box-filename{font-size:.72rem;color:var(--ink-400);padding:6px 10px;background:#fff;text-align:center}

/* --- Validasi realtime nama pemilik rekening vs nama pelanggan --- */
.name-check-hint{font-size:.78rem;margin-top:4px;font-weight:700}
.name-check-hint.match{color:var(--success)}
.name-check-hint.mismatch{color:var(--danger)}

/* --- Wizard form invoice (alur "next" ala registrasi, 4 langkah) --- */
.wizard-progress{margin-bottom:22px}
.wizard-progress-text{font-size:.8rem;font-weight:700;color:var(--gold-700);margin-bottom:6px;text-transform:uppercase;letter-spacing:.03em}
.wizard-progress-bar{height:6px;border-radius:3px;background:var(--ink-100);overflow:hidden}
.wizard-progress-fill{height:100%;background:var(--gold-500);transition:width .25s var(--ease)}
.wizard-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:26px;padding-top:18px;border-top:1px solid var(--ink-100)}

/* --- Halaman konfirmasi sukses simpan invoice --- */
.success-icon{width:88px;height:88px;border-radius:50%;background:var(--success);color:#fff;display:flex;align-items:center;justify-content:center;margin:0 auto 20px}
.success-icon .icon{width:44px;height:44px}
.success-actions{display:flex;align-items:center;justify-content:center;gap:64px;flex-wrap:wrap;margin-top:16px}
.success-actions-group{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}

/* --- Bingkai foto auto-landscape (KTP/barang) -- sama seperti di invoice PDF:
   foto portrait diputar 90 derajat via JS (app.js: fixPhotoFrameOrientation)
   supaya lebarnya tetap 100% mengikuti bingkai, foto landscape apa adanya. --- */
.photo-frame-wrap{position:relative;border:1px solid var(--card-border);border-radius:8px;width:100%;overflow:hidden;background:var(--ink-100)}
.photo-frame{display:block;width:100%;height:auto;cursor:zoom-in}
.photo-frame.is-portrait{position:absolute;top:50%;left:50%;width:auto;height:auto;transform:translate(-50%,-50%) rotate(90deg)}

/* --- Lightbox: preview foto full-screen (klik gambar mana pun ber-atribut [data-lightbox]) --- */
.lightbox-overlay{position:fixed;inset:0;background:rgba(20,18,14,.9);display:none;align-items:center;justify-content:center;z-index:1000;padding:24px}
.lightbox-overlay.open{display:flex}
.lightbox-overlay img{max-width:95vw;max-height:92vh;object-fit:contain;border-radius:10px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
/* Foto portrait dipaksa landscape juga di lightbox, konsisten dengan preview
   bingkai & invoice PDF -- tukar batas lebar/tinggi (vw/vh) lalu putar 90
   derajat, supaya hasil putarannya tetap muat di viewport. */
.lightbox-overlay img.is-portrait{max-width:92vh;max-height:95vw;transform:rotate(90deg)}
.lightbox-close{position:absolute;top:18px;right:22px;width:44px;height:44px;border-radius:50%;background:rgba(255,255,255,.15);color:#fff;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:1.6rem;line-height:1}
.lightbox-close:hover{background:rgba(255,255,255,.28)}

/* --- Choices.js (dropdown searchable) menyatu dgn style input bawaan --- */
.choices{margin-bottom:0;font-size:.9rem}
.choices__inner{background:#fff!important;border:1px solid #ddd!important;border-radius:8px!important;padding:6px 8px!important;min-height:auto!important}
.choices__list--dropdown{border-radius:8px!important;border-color:#ddd!important}
.choices__input{background:#fff!important}
.is-focused .choices__inner,.is-open .choices__inner{border-color:var(--gold-500)!important}

@media print{
  .topbar,.bottom-nav,.no-print{display:none!important}
  .content{padding:0;max-width:100%}
  body{background:#fff}
}

/* Di dalam app terpasang (TWA/PWA standalone): tombol layar-penuh manual jadi
   percuma (APK sudah force-immersive sendiri), dan Danger Zone (hapus data
   demo) SENGAJA disembunyikan di sini supaya aksi destruktif itu cuma bisa
   dilakukan lewat browser biasa di web -- tetap ada & bisa dipakai, cuma
   terpusat di satu tempat. display-mode ini juga match utk PWA yg di-"Add to
   Home Screen" (bukan cuma APK TWA), jadi browser tab biasa tidak terpengaruh. */
@media (display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui){
  #btnFullscreen{display:none}
  .danger-zone-card{display:none}
}

/* Tablet (primary target, e.g. Samsung Galaxy Tab ~800-1280px): roomier nav labels + brand text */
@media (min-width: 600px){
  .topbar-brand span{display:inline}
  .topbar-sep{display:inline}
  .topbar{padding:0 24px}
  .nav-item{font-size:.72rem}
}
@media (max-width: 900px){
  .grid-4{grid-template-columns:repeat(2,1fr)}
  .grid-3{grid-template-columns:repeat(2,1fr)}
}
/* Tablet landscape lebar (mis. Samsung Tab dipasang landscape di APK, ~1024-1600px):
   tanpa batas ini konten melar edge-to-edge dan terasa seperti web di-stretch,
   bukan app. Cuma .content yang dibatasi & dipusatkan -- .bottom-nav & .topbar
   SENGAJA tetap full-width (lihat catatan GPU-compositing di atas .bottom-nav,
   jangan sentuh transform-nya). */
@media (min-width: 1000px){
  .content{max-width:1300px;margin:0 auto}
  .login-footer{font-size:.7rem}
}
@media (max-width: 720px){
  .content{padding:16px 16px calc(var(--bottomnav-h) + env(safe-area-inset-bottom) + 20px)}
  .grid-4,.grid-3,.grid-2{grid-template-columns:1fr}
  /* Picker jenis transaksi Kasir (Perhiasan/Logam Mulia) SENGAJA tetap 2 kolom
     berdampingan (kiri Perhiasan, kanan Logam Mulia) meski di layar sempit --
     cuma 2 tombol pendek, tidak perlu ikut collapse jadi 1 kolom spt card
     berisi tabel/data. */
  #jenisPickerGrid{grid-template-columns:repeat(2,1fr)}
  .chart-wrap{height:280px}
  .kasir-hero{grid-template-columns:1fr;text-align:center}
  /* Tombol "Cetak Dokumen" DULU disembunyikan di sini (komentar lama:
     "khusus desktop") -- 2026-08-13 dibuka utk semua device atas permintaan
     user, semua role sudah bisa akses (Auth::requireRole di kasir/
     cetak-kertas-kop.php sudah mengizinkan OWNER/KEPALA_TOKO/STAFF, tidak
     pernah dibatasi role sama sekali -- yang membatasi cuma CSS ini). Grid
     kasir-hero jadi 1 kolom di layar sempit, jadi tombolnya otomatis
     tersusun di bawah info sambutan, bukan lagi berdampingan. */
  .btn-lg{width:100%}
}
@media (max-width: 480px){
  .card{padding:14px}
  .login-card{width:100%;padding:30px 22px}
  .stat-card .value{font-size:1.25rem}
  .nav-label{font-size:.6rem}
}
/* Owner sees all 8 nav items -- on narrow phones (~360-400px) that leaves
   under 45px per item, not enough for "Database"/"Peleburan"/"Pengaturan"
   at any legible single-line font-size (was silently ellipsis-truncating to
   "Databa…" / "Peleburu…" / "Pengat…"). Fix: only the active item keeps its
   label (icon-only for the rest), so just one label ever needs to fit --
   still allowed to wrap to 2 lines as a fallback for the longest ones.
   Scoped to .nav-owner (8 items) only -- Kepala Toko (3) / Staff (2) have
   plenty of room already and keep every label visible, per instruction. */
@media (max-width: 420px){
  .nav-owner .nav-item{padding-left:0;padding-right:0}
  .nav-owner .nav-item:not(.active) .nav-label{display:none}
  .nav-owner .nav-item.active .nav-label{white-space:normal;font-size:.62rem;line-height:1.15;overflow:visible;text-overflow:clip;max-width:100%;overflow-wrap:break-word}
}
