/* =========================================================================
   assets/app.css -- UI DESKTOP (Claude Design "Diamondo Warehouse")
   =========================================================================
   Revolusi UI desktop, 13 Agu 2026. Ditulis dari NOL mengikuti handoff
   design_handoff_diamondo_warehouse -- bukan turunan assets/style.css.
   File ini dan style.css TIDAK PERNAH dimuat bersamaan: includes/header.php
   memilih salah satu lewat gerbang $ui_baru per halaman, jadi tidak ada
   kaskade yang saling berebut dan tidak perlu !important di mana pun.

   AWALAN d-  Semua class di sini berawalan d- (desktop). Alasannya
   assets/mobile.css menimpa nama generik (.card .btn .badge .field .pill
   .row) untuk UI mobile; tanpa awalan, halaman yang sudah dimigrasi akan
   kena aturan mobile di lebar tertentu. Awalan bikin tabrakan itu mustahil.

   LEBAR  Seluruh layout dibungkus @media (min-width:769px). Di bawah itu
   UI mobile (assets/mobile.css) yang berkuasa, persis seperti sekarang --
   revolusi ini TIDAK menyentuh mobile sama sekali. Blok penutup di akhir
   file menyembunyikan chrome desktop (.d-rail, .d-shell) di <=768px.

   TIGA JEBAKAN dari CATATAN-UI yang dipegang sejak baris pertama, bukan
   ditambal belakangan -- jangan dibalik tanpa membaca alasannya:
   1. Animasi masuk memakai animation-fill-mode:backwards, BUKAN both.
      both menahan transform setelah animasi selesai, jadi hover lift/zoom
      pada kartu KPI & baris tabel mati total.
   2. Kartu memakai backdrop-filter -> bikin stacking context baru ->
      z-index di DALAM kartu tidak bisa menang melawan kartu sesudahnya.
      Popover/dropdown karena itu dirender lewat portal ke <body>
      (lihat Diamondo.portal() di assets/app.js), bukan diakali z-index.
   3. .d-seg memakai grid-auto-flow:column + grid-auto-columns:1fr, BUKAN
      flex. Dengan flex tiap tombol selebar teksnya sendiri, jadi thumb
      yang bergerak translateX(i*100%) tidak pernah lurus dengan tombolnya.
   ========================================================================= */

/* -------------------------------------------------------------------------
   1. TOKEN
   Nilainya disalin persis dari :root prototipe. --primary default burgundy
   #8c1c33; preset Warna Aksen di Pengaturan menimpanya lewat inline style
   di <html> (lihat skrip anti-kedip di header.php).
   ------------------------------------------------------------------------- */
:root {
    --bg: #f2f2f5;
    --bg2: #e8e8ed;
    --card: rgba(255,255,255,.74);
    --card-solid: #fff;
    --rail: rgba(255,255,255,.42);
    --text: #1d1d1f;
    --muted: #6e6e73;
    --faint: #8e8e93;
    --border: rgba(0,0,0,.08);
    --hair: rgba(0,0,0,.05);
    --field: rgba(120,120,128,.09);
    /* Garis bantu horizontal di chart. Sengaja token sendiri, BUKAN --hair:
       --hair dipakai pemisah tipis di dalam kartu (nyaris tak terlihat, dan
       memang harus begitu), sedangkan garis chart perlu benar-benar terbaca
       sebagai kisi. Tetap abu-abu netral supaya tidak pernah tertukar dengan
       salah satu dari empat garis seri yang berwarna. */
    --d-grid-chart: rgba(0,0,0,.16);

    --primary: #8c1c33;
    --brand: #8c1c33;
    --green: #34c759;
    --red: #ff3b30;
    --orange: #ff9f0a;
    --merek-shopee: #ee4d2d;   /* oranye Shopee */
    --merek-tiktok: #42b549;   /* hijau Tokopedia -- kode channel 'tiktok' menampung keduanya */
    --purple: #af52de;
    --teal: #30b0c7;

    /* Resep permukaan. Dipakai ulang .d-card, .d-kpi, .d-sheet supaya
       kalau blur-nya perlu diturunkan (mis. keluhan performa) cukup satu
       tempat, bukan berburu ke belasan aturan. */
    --d-blur-card: blur(28px) saturate(180%);
    --d-blur-bar: blur(40px) saturate(190%);
    --d-blur-rail: blur(44px) saturate(200%);
    --d-blur-pop: blur(34px) saturate(190%);
    --d-shadow-card: 0 1px 2px rgba(0,0,0,.04), 0 20px 44px -30px rgba(0,0,0,.3);
    --d-shadow-pop: 0 26px 54px -22px rgba(0,0,0,.45);
    --d-ease-pop: cubic-bezier(.34,1.4,.42,1);
    --d-ease-out: cubic-bezier(.2,.9,.3,1);

    /* Padding kartu & baris tabel. Mode "Tampilan padat" cuma menimpa dua
       angka ini (lihat html.compact di bawah) -- itulah kenapa dipakai
       sebagai variabel, bukan ditulis langsung di tiap komponen. */
    --d-pad-card: 24px;
    --d-pad-row: 13px 14px;

    color-scheme: light;
}

/* ---- Alias token lama ----
   Halaman di LUAR handoff (login, lupa password, set password, syarat,
   kebijakan, menu staff) tidak dirancang ulang -- keputusannya "ikut token
   baru, layout tetap". Aturan tata letaknya dipindah apa adanya dari
   style.css, dan aturan itu menyebut nama token lama. Dipetakan di sini ke
   token baru supaya CSS-nya jalan tanpa disunting satu per satu, DAN supaya
   halaman-halaman itu otomatis ikut warna aksen serta mode gelap yang sama.
   Blok ini ikut terhapus kalau nanti halaman-halaman itu dirancang ulang. */
:root {
    --primary-dark: #6d1226;
    --danger:  var(--red);
    --success: var(--green);
    --warning: var(--orange);
    --hairline: var(--hair);
    --hairline-soft: var(--hair);
    --input-bg: var(--card-solid);
    --surface-tint: color-mix(in srgb, var(--text) 3%, transparent);
    --surface-tint-2: color-mix(in srgb, var(--text) 5%, transparent);
    --sidebar-color: #7a1226;
    --sidebar-color-light: #9c2438;
    --sidebar-color-dark: #4a0a16;
    --radius-lg: 22px;
    --radius-md: 14px;
    --radius-sm: 10px;
    --ease: cubic-bezier(.2, .9, .3, 1);
    --shadow-xs: 0 1px 2px rgba(0,0,0,.05);
    --shadow-sm: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
    --shadow-md: 0 12px 32px rgba(0,0,0,.10), 0 2px 8px rgba(0,0,0,.05);
    --shadow-lg: 0 24px 56px rgba(0,0,0,.16), 0 4px 14px rgba(0,0,0,.06);
    --scrollbar-thumb: rgba(140,140,150,.35);
    --scrollbar-thumb-hover: rgba(140,140,150,.55);
}

html[data-theme="dark"] {
    --bg: #0b0b0d;
    --bg2: #17171a;
    --card: rgba(28,28,30,.72);
    --card-solid: #1c1c1e;
    --rail: rgba(28,28,30,.44);
    --text: #f5f5f7;
    --muted: #a1a1a6;
    --faint: #8e8e93;
    --border: rgba(255,255,255,.11);
    --hair: rgba(255,255,255,.07);
    --field: rgba(120,120,128,.24);
    --d-grid-chart: rgba(255,255,255,.20);
    --merek-shopee: #ff7a5c;
    --merek-tiktok: #5fd46a;
    color-scheme: dark;
}

/* Wallpaper personalisasi aktif -> rail dinaikkan opasitasnya supaya label
   9,5px-nya tetap kebaca di atas foto apa pun. Ditandai dari PHP sebagai
   data-latar="1" di <html>. */
html[data-latar="1"]                    { --rail: rgba(255,255,255,.72); }
html[data-theme="dark"][data-latar="1"] { --rail: rgba(28,28,30,.74); }

html.compact {
    --d-pad-card: 17px;
    --d-pad-row: 8px 14px;
}

/* -------------------------------------------------------------------------
   2. DASAR
   ------------------------------------------------------------------------- */
* { box-sizing: border-box; }

/* [hidden] harus benar-benar menyembunyikan. Aturan bawaan browser untuk
   atribut ini cuma `display:none` biasa, jadi KALAH oleh display apa pun
   yang ditulis penulis stylesheet -- .d-actionbar{display:flex} membuat
   bar aksi massal tetap terlihat walau ditandai hidden. Ini satu-satunya
   !important di berkas ini, dan memang idiom standar untuk kasus ini. */
[hidden] { display: none !important; }

html, body { margin: 0; padding: 0; height: 100%; }

body {
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", "Helvetica Neue", system-ui, sans-serif;
    -webkit-font-smoothing: antialiased;
    letter-spacing: -.011em;
}

a { color: var(--primary); text-decoration: none; }
/* Garis bawah saat hover DIBATASI ke perangkat ber-tetikus. Di layar
   sentuh :hover "nyangkut" setelah ketukan -- jadi setiap tombol yang
   kebetulan berupa <a> (kartu Omzet dasbor, baris Perlu Direstok, panah
   kembali) tampil bergaris bawah dan tetap begitu sampai user mengetuk
   tempat lain. Di desktop tidak ada yang berubah. */
@media (hover: hover) {
    a:hover { text-decoration: underline; }
}

input, button, select, textarea { font-family: inherit; }
input:focus, textarea:focus { outline: none; }

/* Angka SELALU tabular supaya kolom Rupiah di tabel lurus ke bawah dan
   tidak "bergoyang" saat nilainya berubah lewat AJAX. */
.d-num, .d-kpi-nilai, .d-stepper-nilai { font-variant-numeric: tabular-nums; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: rgba(140,140,150,.35);
    border-radius: 8px;
    border: 3px solid transparent;
    background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(140,140,150,.55); background-clip: content-box; }

/* -------------------------------------------------------------------------
   3. KEYFRAME
   Sembilan animasi dari handoff. Yang memakainya WAJIB menutup dengan
   `backwards`, jangan `both` -- lihat jebakan #1 di kepala file.
   ------------------------------------------------------------------------- */
@keyframes d-fadeUp     { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes d-screenIn   { from { opacity: 0; transform: translateY(18px) scale(.992); } to { opacity: 1; transform: none; } }
@keyframes d-sheetIn    { from { opacity: 0; transform: translateY(40px) scale(.94); } to { opacity: 1; transform: none; } }
@keyframes d-backdropIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes d-rowIn      { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: none; } }
@keyframes d-toastIn    { from { opacity: 0; transform: translateY(22px) scale(.9); } to { opacity: 1; transform: none; } }
@keyframes d-dash       { from { stroke-dashoffset: 2400; } to { stroke-dashoffset: 0; } }
@keyframes d-grow       { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes d-growX      { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes d-popIn      { 0% { transform: scale(.7); opacity: 0; } 60% { transform: scale(1.08); } 100% { transform: scale(1); opacity: 1; } }
@keyframes d-spin       { to { transform: rotate(360deg); } }
@keyframes d-pulseRing  {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 32%, transparent); }
    70%  { box-shadow: 0 0 0 16px color-mix(in srgb, var(--primary) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent); }
}
@keyframes d-floatA     { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(40px,-30px) scale(1.12); } }
@keyframes d-floatB     { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(-50px,25px) scale(1.08); } }

/* Hormati preferensi sistem "kurangi gerak". Bukan basa-basi aksesibilitas:
   animasi dash chart 1,9 detik + 30-an baris tabel yang masuk bergantian
   memang bisa memicu mual buat sebagian orang. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
}

/* =========================================================================
   MULAI DARI SINI: HANYA DESKTOP (>=769px)
   ========================================================================= */
@media (min-width: 769px) {

/* -------------------------------------------------------------------------
   4. LATAR AMBIENT
   Dua blob warna aksen yang mengambang pelan + lapisan wallpaper opsional.
   pointer-events:none supaya tidak pernah menghalangi klik.
   ------------------------------------------------------------------------- */
.d-ambient {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    background: linear-gradient(160deg, var(--bg), var(--bg2));
}
.d-blob {
    position: absolute;
    border-radius: 50%;
    filter: blur(30px);
    transition: opacity .5s;
}
.d-blob-a {
    top: -180px; left: -120px; width: 620px; height: 620px;
    background: radial-gradient(circle at 40% 40%, color-mix(in srgb, var(--primary) 16%, transparent), transparent 68%);
    animation: d-floatA 22s ease-in-out infinite;
}
.d-blob-b {
    bottom: -220px; right: -140px; width: 680px; height: 680px;
    background: radial-gradient(circle at 60% 40%, color-mix(in srgb, var(--primary) 14%, transparent), transparent 68%);
    animation: d-floatB 26s ease-in-out infinite;
}
/* Wallpaper menyala -> blob aksen dipudarkan supaya fotonya tidak terlihat
   "kotor" tertimpa dua bercak warna. */
html[data-latar="1"] .d-blob { opacity: .25; }
.d-wallpaper {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
}

/* -------------------------------------------------------------------------
   5. SHELL + RAIL NAVIGASI
   Rail 92px tetap, tinggi penuh, scroll internal tanpa scrollbar terlihat.
   ------------------------------------------------------------------------- */
.d-shell {
    position: relative;
    z-index: 1;
    display: flex;
    height: 100vh;
    overflow: hidden;
}

.d-rail {
    position: relative;
    flex: 0 0 92px;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 14px 0;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none;
    background: var(--rail);
    backdrop-filter: var(--d-blur-rail);
    -webkit-backdrop-filter: var(--d-blur-rail);
    border-right: 1px solid var(--border);
    box-shadow: inset -1px 0 0 rgba(255,255,255,.06);
}
.d-rail::-webkit-scrollbar { display: none; }

/* Indikator aktif: kotak tint + garis 3px. Posisinya dihitung PHP
   (rail_offset() di includes/ui.php) dan ditulis sebagai inline style --
   di app multi-halaman tidak ada yang perlu "bergeser" antar halaman
   karena tiap pindah halaman itu muat ulang penuh, jadi tidak perlu JS
   sama sekali di sini. Transisi tetap dipasang untuk jaga-jaga kalau
   nanti ada halaman ber-tab yang menggeser indikator tanpa reload. */
.d-rail-tanda {
    position: absolute;
    left: 12px;
    top: 0;
    width: 68px;
    height: 52px;
    border-radius: 15px;
    background: color-mix(in srgb, var(--primary) 14%, transparent);
    transition: transform .45s var(--d-ease-pop);
    pointer-events: none;
}
.d-rail-garis {
    position: absolute;
    left: 12px;
    top: 0;
    width: 3px;
    height: 22px;
    border-radius: 2px;
    background: var(--primary);
    transition: transform .45s var(--d-ease-pop);
    pointer-events: none;
}

.d-rail-item {
    position: relative;
    z-index: 1;
    flex: 0 0 auto;
    width: 68px;
    height: 52px;
    margin-bottom: 4px;
    border: none;
    background: none;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    border-radius: 16px;
    color: color-mix(in srgb, var(--text) 82%, transparent);
    text-decoration: none;
    transition: color .25s, transform .3s var(--d-ease-pop);
}
.d-rail-item:hover { transform: translateY(-2px); text-decoration: none; color: var(--text); }
.d-rail-item.aktif { color: var(--primary); }
.d-rail-item svg { width: 22px; height: 22px; display: block; }
.d-rail-label { font-size: 9.5px; font-weight: 600; letter-spacing: -.01em; line-height: 1; }

/* Titik merah notifikasi (stok di bawah minimum / resi menunggu). Cincin
   --card-solid dipakai supaya titiknya tetap terpisah dari ikon di
   belakangnya, baik mode terang maupun gelap. */
.d-rail-dot {
    position: absolute;
    top: 9px;
    right: 15px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--red);
    box-shadow: 0 0 0 2px var(--card-solid);
}

.d-rail-pisah { width: 44px; height: 1px; background: var(--border); margin: 4px 0 8px; flex: 0 0 auto; }
.d-rail-isi   { flex: 1 0 14px; }

.d-rail-avatar {
    flex: 0 0 40px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: none;
    cursor: pointer;
    background: linear-gradient(150deg, #a82440, #6d1226);
    color: #fff;
    font-size: 15px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    text-decoration: none;
    transition: transform .3s var(--d-ease-pop);
}
.d-rail-avatar:hover { transform: scale(1.08); text-decoration: none; color: #fff; }
.d-rail-avatar img { width: 100%; height: 100%; object-fit: cover; }

/* -------------------------------------------------------------------------
   6. AREA KONTEN (kerangka -- desktop saja)
   ------------------------------------------------------------------------- */
.d-viewport { flex: 1; min-width: 0; height: 100%; overflow-y: auto; overflow-x: hidden; }
.d-page {
    max-width: 1180px;
    margin: 0 auto;
    padding: 30px 40px 80px;
    animation: d-screenIn .5s var(--d-ease-out) backwards;
}

} /* ==== akhir kerangka desktop-only ==== */

/* =========================================================================
   KOMPONEN -- BERLAKU DI SEMUA LEBAR
   =========================================================================
   Semua di bawah ini SENGAJA di luar @media (min-width:769px).

   Alasannya: dari 20-an halaman app, cuma 6 yang punya markup mobile
   sendiri (blok .m-only). Sisanya -- Pencarian, Barang Keluar, Transaksi,
   Biaya, Laporan, Pengaturan, Tenant, Scan -- memakai markup yang SAMA di
   HP maupun desktop, dan selama ini mobile.css yang meneman ulang markup
   itu (.card, .responsive-table, dst). Begitu halaman-halaman itu pindah
   ke class .d-*, mobile.css tidak lagi mengenalinya; kalau gaya .d-* juga
   terkunci di min-width:769px, hasilnya halaman tanpa gaya sama sekali di
   HP -- bukan "mobile tidak tersentuh", tapi mobile rusak.

   Yang tetap desktop-only hanyalah KERANGKA (rail, viewport, padding
   halaman, latar ambient) -- itu memang digantikan topbar + tab bar milik
   mobile.css. Komponen isinya dipakai bersama, dengan penyesuaian sempit
   di blok @media (max-width:768px) paling bawah.
   ========================================================================= */

.d-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 22px;
}
.d-head h1 { margin: 0; font-size: 32px; font-weight: 700; letter-spacing: -.035em; }
.d-head p  { margin: 5px 0 0; font-size: 14px; color: var(--muted); }
.d-head-aksi { display: flex; gap: 9px; align-items: center; }

.d-seksi {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 20px 0 12px;
}
.d-seksi-teks { font-size: 13.5px; font-weight: 700; letter-spacing: -.01em; }
.d-seksi::after { content: ""; flex: 1; height: 1px; background: var(--hair); }

/* ---- Teks telanjang di atas wallpaper foto ----
   Judul halaman, subjudul, dan label seksi tidak punya kartu di belakangnya.
   Di atas gradien turunan tema itu aman; di atas foto arbitrer tidak --
   teks #1d1d1f mode terang di atas foto malam praktis hilang.

   Yang ditimpa HANYA elemen telanjang itu. Sengaja bukan --text di level
   .d-page: token itu diwarisi juga oleh isi kartu, yang punya latar sendiri
   dan kontrasnya sudah benar -- menimpanya di sana justru merusak.

   Kombinator KETURUNAN (spasi), bukan anak-langsung (>). Terlihat seperti
   bisa dirapikan jadi '>' tapi JANGAN: badan halaman masih dibungkus
   .m-analitik > .m-analitik-body (panel geser Analitik milik UI mobile,
   lihat index.php), jadi .d-head bukan anak langsung .d-page dan seluruh
   aturan ini diam-diam tidak pernah cocok. */
.d-atas-foto-gelap .d-head h1,
.d-atas-foto-gelap .d-seksi-teks { color: #f5f5f7; text-shadow: 0 1px 4px rgba(0,0,0,.45); }
.d-atas-foto-gelap .d-head p { color: rgba(255,255,255,.78); text-shadow: 0 1px 4px rgba(0,0,0,.45); }
.d-atas-foto-gelap .d-seksi::after { background: rgba(255,255,255,.22); }

.d-atas-foto-terang .d-head h1,
.d-atas-foto-terang .d-seksi-teks { color: #1d1d1f; text-shadow: 0 1px 4px rgba(255,255,255,.5); }
.d-atas-foto-terang .d-head p { color: rgba(0,0,0,.68); text-shadow: 0 1px 4px rgba(255,255,255,.5); }
.d-atas-foto-terang .d-seksi::after { background: rgba(0,0,0,.18); }

/* Tombol ghost di kepala halaman juga duduk telanjang di atas foto, dan
   latarnya (--field) cuma 9% opak -- di atas foto ia hilang sama sekali.
   Diberi permukaan padat khusus saat wallpaper menyala. */
.d-atas-foto-gelap .d-head .d-btn-ghost,
.d-atas-foto-terang .d-head .d-btn-ghost {
    background: var(--card-solid);
    border: 1px solid var(--border);
}

/* -------------------------------------------------------------------------
   7. KARTU
   ------------------------------------------------------------------------- */
.d-card {
    background: var(--card);
    backdrop-filter: var(--d-blur-card);
    -webkit-backdrop-filter: var(--d-blur-card);
    border: 1px solid var(--border);
    border-radius: 22px;
    box-shadow: var(--d-shadow-card);
    padding: var(--d-pad-card);
    animation: d-fadeUp .55s var(--d-ease-out) backwards;
}
/* Dua kartu yang bersaudara langsung tetap berjarak 14px (handoff: "gap antar
   kartu 14px"). .d-card sendiri tidak punya margin, jadi kartu yang ditulis
   berurutan TANPA pembungkus .d-grid/.d-tumpuk akan benar-benar bersentuhan --
   terukur gap 0 di Performa Reseller & Integrasi. Di dalam pembungkus yang
   sudah punya gap, marginnya dinolkan lagi supaya tidak dobel. */
/* Pemilih SAUDARA UMUM (~), bukan saudara langsung (+): di antara dua kartu
   sering ada blok tersembunyi sumber-isi-sheet (mis. #blokGantiReseller),
   dan satu elemen di tengah sudah cukup membuat "+" tidak cocok -- kedua
   kartunya lalu menempel tanpa jarak sama sekali. */
.d-card ~ .d-card { margin-top: 14px; }
.d-grid > .d-card ~ .d-card,
.d-tumpuk > .d-card ~ .d-card,
.d-stack > .d-card ~ .d-card { margin-top: 0; }

.d-card-h2 {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    margin: 0 0 16px;
}
.d-card-h2 > :first-child { font-size: 17px; font-weight: 700; letter-spacing: -.02em; }

.d-grid { display: grid; gap: 14px; }
.d-grid-kpi   { grid-template-columns: repeat(auto-fit, minmax(238px, 1fr)); }
.d-grid-rasio { grid-template-columns: repeat(auto-fit, minmax(196px, 1fr)); }
/* Dasbor: kartu Rasio & Modal jadi ENAM sejak Total HPP dan Nilai Stok Gudang
   dipisah (15 Agu 2026). Dengan lantai 196px, lebar desktop biasa cuma muat 5
   jalur -- kartu ke-6 turun sendirian dan barisnya timpang. Lantainya
   diturunkan supaya keenamnya duduk satu baris.
   Di-scope ke body dasbor: .d-grid-rasio juga dipakai keuangan_view.php dan
   data_reseller.php dengan jumlah kartu yang lain, dan tidak ada alasan
   mengubah kepadatan di sana. */
body.dasbor-ringkas .d-grid-rasio,
body.dasbor-analitik .d-grid-rasio { grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
/* Enam kolom berarti tiap kartu ~146px ruang isi. Angka 20px membuat
   "Rp 198.611.295" (~145px) pas di ambang dan pecah jadi "Rp" / angka --
   satuan yang menggantung sendirian di satu baris. 18px menuntaskannya
   (~130px) tanpa mengubah kartu rasio di halaman lain. */
body.dasbor-ringkas .d-grid-rasio .d-kpi-nilai,
body.dasbor-analitik .d-grid-rasio .d-kpi-nilai { font-size: 18px; }
.d-grid-2     { grid-template-columns: 2fr 1fr; }
.d-grid-11    { grid-template-columns: 1fr 1fr; }
/* Dua kolom Pengaturan mulai menumpuk lebih awal daripada grid lain:
   kartunya berisi teks penjelas panjang, jadi di bawah ~1100px dua
   kolom sudah terlalu sempit untuk dibaca. */
@media (max-width: 1100px) { .d-atur-kolom { grid-template-columns: 1fr; } }
.d-grid-15    { grid-template-columns: 1.5fr 1fr; }

/* Kartu masuk bergantian, 45ms per kartu. Ditulis sebagai nth-child supaya
   halaman tidak perlu menghitung delay satu-satu di inline style. */
.d-grid > *:nth-child(1) { animation-delay: 0ms; }
.d-grid > *:nth-child(2) { animation-delay: 45ms; }
.d-grid > *:nth-child(3) { animation-delay: 90ms; }
.d-grid > *:nth-child(4) { animation-delay: 135ms; }
.d-grid > *:nth-child(5) { animation-delay: 180ms; }
.d-grid > *:nth-child(n+6) { animation-delay: 225ms; }

/* -------------------------------------------------------------------------
   8. KARTU KPI
   Tanpa border (border-color transparan), sparkline full-bleed menempel
   dasar kartu lewat margin negatif sebesar padding kartu.
   ------------------------------------------------------------------------- */
.d-kpi {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--card);
    backdrop-filter: var(--d-blur-card);
    -webkit-backdrop-filter: var(--d-blur-card);
    border: 1px solid transparent;
    border-radius: 22px;
    box-shadow: var(--d-shadow-card);
    padding: 18px 20px;
    text-decoration: none;
    color: inherit;
    animation: d-fadeUp .55s var(--d-ease-out) backwards;
    transition: transform .36s cubic-bezier(.34,1.2,.42,1), box-shadow .36s;
}
.d-kpi.d-kpi-spark { padding: 18px 20px 0; }

/* --d-kpi-warna di-set inline per kartu; bayangan hover ikut warnanya
   supaya tiap KPI terasa punya identitas sendiri, bukan drop shadow abu
   yang seragam. Tanpa outline, tanpa shade sudut (aturan handoff). */
.d-kpi:hover {
    transform: translateY(-4px) scale(1.03);
    box-shadow: 0 30px 56px -26px color-mix(in srgb, var(--d-kpi-warna, var(--primary)) 55%, transparent);
    z-index: 3;
    text-decoration: none;
}
a.d-kpi { cursor: pointer; }

/* Kartu KPI yang sedang dipilih (mis. reseller aktif di tab Performa). */
.d-kpi-terpilih {
    border-color: var(--primary);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 22%, transparent),
                var(--d-shadow-card);
}
.d-kpi:hover .d-kpi-spark-wrap { opacity: 1; }

.d-kpi-label { font-size: 12.5px; font-weight: 600; color: var(--muted); line-height: 1.35; }
.d-kpi-nilai {
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -.03em;
    margin-top: 6px;
    color: var(--d-kpi-warna, var(--text));
}
.d-kpi-delta {
    display: flex;
    align-items: baseline;
    gap: 5px;
    font-size: 11.5px;
    margin-top: 5px;
    flex-wrap: wrap;
}
.d-kpi-delta b { font-weight: 700; }
.d-kpi-delta .d-naik  { color: var(--green); }
.d-kpi-delta .d-turun { color: var(--red); }
.d-kpi-delta em { font-style: normal; color: var(--faint); }
.d-kpi-sub { font-size: 12px; color: var(--faint); line-height: 1.45; margin-top: 6px; flex: 1 1 auto; }
.d-kpi-isi { flex: 1 1 auto; }
.d-kpi-spark-wrap {
    margin: 14px -20px 0;
    padding-bottom: 2px;
    opacity: .85;
    transform-origin: bottom;
    transition: opacity .36s;
}
.d-kpi-spark-wrap svg { width: 100%; height: 42px; display: block; overflow: visible; }
.d-spark-garis { stroke-dasharray: 2400; animation: d-dash 1.6s cubic-bezier(.2,.8,.3,1) backwards; }

/* Kartu rasio (baris "Rasio & Modal"): angkanya 20px, bukan 26px seperti
   KPI utama -- handoff memisahkan keduanya, dan pada kartu selebar 196px
   angka 26px seperti "Rp 198.611.295" pecah jadi dua baris. */
.d-kpi-rasio .d-kpi-nilai { font-size: 20px; letter-spacing: -.02em; }

/* Kartu rasio harus tetap PERSEGI PANJANG (lebih lebar daripada tinggi).
   Semua sel grid meregang setinggi sel tertinggi, jadi tinggi barisnya
   ditentukan oleh kartu terisi paling banyak -- yaitu kartu dua-nilai
   (Total HPP + Nilai Stok Gudang). Karena itu yang dipendekkan kartu
   ITU, bukan grid-nya dibuat align-items:start: kalau tiap kartu setinggi
   isinya sendiri, dasar barisnya jadi bergerigi. */
.d-kpi-rasio .d-kpi-sub { flex: 0 0 auto; }

.d-kpi-ganda .d-kpi-nilai { font-size: 17px; letter-spacing: -.015em; margin-top: 3px; }
.d-kpi-ganda .d-kpi-pisah { margin: 8px 0; }

/* Dua nilai dipisah garis rambut (dipakai Total HPP / Nilai Stok Gudang). */
.d-kpi-pisah { height: 1px; background: var(--hair); margin: 10px 0; }

/* -------------------------------------------------------------------------
   9. TOMBOL
   ------------------------------------------------------------------------- */
.d-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    border-radius: 12px;
    cursor: pointer;
    font-weight: 600;
    white-space: nowrap;
    padding: 11px 20px;
    font-size: 14px;
    text-decoration: none;
    transition: all .28s cubic-bezier(.34,1.3,.42,1);
}
.d-btn:hover { transform: translateY(-2px); text-decoration: none; }
.d-btn:disabled { cursor: default; transform: none; }
.d-btn-kecil { padding: 8px 14px; font-size: 13px; border-radius: 10px; }
/* Ikon di dalam tombol. Tanpa ukuran eksplisit sebuah <svg> tanpa atribut
   width/height jatuh ke 0x0 di flex container -- jadi SELURUH ikon tombol
   (Export, Rentang tanggal, dst) tidak pernah terlihat sama sekali. */
.d-btn svg, .d-btn-ikon {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
}
.d-btn-kecil svg { width: 15px; height: 15px; flex: 0 0 15px; }

.d-btn-primary {
    border: none;
    background: var(--primary);
    color: #fff;
    box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--primary) 90%, transparent);
}
.d-btn-primary:hover { color: #fff; }
.d-btn-outline { border: 1px solid var(--border); background: var(--card-solid); color: var(--text); }
.d-btn-outline:hover { color: var(--text); }
.d-btn-ghost   { border: none; background: var(--field); color: var(--text); }
.d-btn-ghost:hover { color: var(--text); }

/* Tombol destruktif: teks merah, latar tint 12%, border 30%. Merah terang
   HANYA di sini & status buruk -- lihat aturan #8 handoff. */
.d-btn-bahaya {
    border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
    background: color-mix(in srgb, var(--red) 12%, transparent);
    color: var(--red);
}
.d-btn-bahaya:hover { color: var(--red); }
/* Varian tenang: garis & teks tetap merah, tapi latarnya kartu -- bukan
   tint 12%. Dipakai tombol Logout penuh-lebar di Profil: blok merah
   selebar layar terbaca seperti peringatan, padahal logout itu tindakan
   biasa. Di tombol berukuran normal tint-nya tetap dipakai. */
.d-btn-bahaya.d-btn-tenang { background: var(--card-solid); }
.d-btn-bahaya.d-btn-tenang:hover { background: color-mix(in srgb, var(--red) 8%, var(--card-solid)); }

/* Pasangan HIJAU dari .d-btn-bahaya, pola yang sama persis (garis & teks
   berwarna di atas latar tint) -- untuk tindakan yang MENYELESAIKAN, bukan
   yang membatalkan. Dipakai berpasangan dengan .d-btn-bahaya supaya sepasang
   tombol terbaca sebagai "ya / tidak" cuma dari warnanya. */
.d-btn-oke {
    border: 1px solid color-mix(in srgb, var(--green) 32%, transparent);
    background: color-mix(in srgb, var(--green) 12%, transparent);
    color: var(--green);
}
.d-btn-oke:hover { color: var(--green); }

/* Pasangan KUNING dari .d-btn-bahaya/.d-btn-oke, pola yang sama persis.
   Untuk tindakan yang perlu MENONJOL tanpa berteriak bahaya: bukan merusak
   (jadi bukan merah), tapi juga bukan langkah biasa yang boleh ditekan
   tanpa pikir. Sejauh ini dipakai "Koreksi Angka" di sheet Detail
   Transaksi -- satu-satunya tombol di sana yang mengubah angka finansial.
   Warnanya dari token --warning (oranye), bukan kuning murni: kuning murni
   di atas latar terang nyaris tidak terbaca. */
.d-btn-kuning {
    border: 1px solid color-mix(in srgb, var(--warning) 32%, transparent);
    background: color-mix(in srgb, var(--warning) 14%, transparent);
    color: var(--warning);
}
.d-btn-kuning:hover { color: var(--warning); }

/* Tombol aksi yang isinya LAMBANG saja. Kolom Aksi di tabel sempit tidak muat
   dua tombol berlabel teks; lihat catatan di
   includes/belum_checkout_helper.php. Ukurannya dipatok (bukan lewat padding)
   supaya kedua tombol persis sama besar walau lambangnya beda bentuk. */
.d-btn.d-btn-aksi-ikon {
    width: 38px;
    height: 36px;
    padding: 0;
    gap: 0;
    flex: 0 0 auto;
    border-radius: 10px;
}
/* Garis lambang ditebalkan -- stroke bawaan d_svg() 1.7 terlalu tipis untuk
   dibaca sekilas sambil packing. Aturan CSS mengalahkan atribut stroke-width
   di <svg>, jadi d_svg() tidak perlu diubah & ikon lain tidak ikut menebal. */
.d-btn.d-btn-aksi-ikon svg {
    width: 20px;
    height: 20px;
    flex: 0 0 20px;
    stroke-width: 3;
}

/* Tombol simpan: label -> "Menyimpan..." + spinner -> "Tersimpan" hijau.
   Statusnya ditaruh di data-status oleh assets/app.js. */
.d-save[data-status="jalan"] { opacity: .75; cursor: default; }
.d-save[data-status="ok"]    { background: var(--green); }
.d-save-spinner {
    width: 13px;
    height: 13px;
    border-radius: 50%;
    border: 2px solid rgba(255,255,255,.45);
    border-top-color: #fff;
    animation: d-spin .7s linear infinite;
    flex: 0 0 13px;
}

/* -------------------------------------------------------------------------
   10. PILL, SEGMENTED, CHIP
   ------------------------------------------------------------------------- */
.d-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.d-pill {
    padding: 9px 16px;
    border-radius: 999px;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    flex: 0 0 auto;
    line-height: 1.2;
    border: 1px solid var(--border);
    background: var(--card-solid);
    color: var(--muted);
    text-decoration: none;
    transition: all .32s cubic-bezier(.34,1.3,.42,1);
}
.d-pill:hover { color: var(--text); text-decoration: none; }
.d-pill.aktif {
    border-color: transparent;
    background: var(--primary);
    color: #fff;
    box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--primary) 70%, transparent);
    transform: translateY(-1px);
}

/* JEBAKAN #3: grid, bukan flex. Kolom harus sama lebar atau thumb-nya
   tidak lurus dengan tombolnya. --d-seg-n = jumlah item, di-set inline. */
/* Kepala kartu Riwayat Transaksi: tab status di kiri, kotak cari mengisi
   sisa ruang di kanan. Sebelumnya keduanya bar filter tersendiri di atas
   kartu, yang memakan satu baris penuh sebelum tabelnya mulai. */
.d-trx-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
}
.d-trx-bar .d-seg { flex: 0 0 auto; }
.d-trx-bar .d-search { flex: 1; min-width: 0; }
.d-trx-status { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Kartu kepala Laporan: pintasan periode di kiri, pemilih tab menempel di
   kanan. Keduanya menyaring isi yang sama, jadi duduk di satu baris. */
.d-lap-bar {
    /* Menempel saat digulir, sama seperti bar filter di Dasbor: pilihan
       periode & pemilih tab dipakai bolak-balik sambil membaca tabel di
       bawahnya, jadi harus tetap terjangkau tanpa menggulir balik ke atas. */
    position: sticky;
    top: 0;
    z-index: 12;
    /* Blur versi BAR, bukan versi kartu: isi tabel lewat di baliknya saat
       digulir, dan blur kartu yang lebih tipis membuat angkanya masih
       terbaca samar menembus bar. Sama dengan .d-filter di Dasbor. */
    backdrop-filter: var(--d-blur-bar);
    -webkit-backdrop-filter: var(--d-blur-bar);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    /* Padding kartu filter memang lebih rapat dari kartu isi (24px) --
       prototipe handoff menimpanya jadi 18px 22px untuk kartu ini. */
    padding: 18px 22px;
    /* margin-bottom WAJIB: .d-card tidak punya margin sendiri, dan panel tab
       di bawahnya bermargin 0, jadi tanpa ini kedua kartu benar-benar
       bersentuhan (terukur gap 0). Handoff: jarak antar kartu 14px. */
    margin: 14px 0;
}
.d-lap-bar .d-seg { flex: 0 0 auto; }
/* Isyarat "sedang mengambil data" saat periode diganti tanpa muat ulang --
   tanpa kedip halaman, tidak ada tanda lain bahwa sesuatu sedang berjalan. */
.d-lap-sibuk { opacity: .55; pointer-events: none; transition: opacity .2s; }

/* SKU panjang di baris kedua No. Resi (mis. PORT96-P1-KANGSA-SILVER2-ABU)
   dulu menaikkan min-content kolom kiri sampai 790px, sehingga kartu
   Ringkasan di kanan cuma kebagian sisanya dan judulnya patah dua baris.
   Boleh dipenggal di mana saja; lebar kolomnya sendiri diatur lewat minmax
   di transaksi.php, jangan pakai min-width:0 di selnya -- kolom ber-fr
   (Jam, Penerima) akan runtuh sampai nol. */
.d-grid-trx .d-table-row .d-sub { overflow-wrap: anywhere; }
/* minmax(0,...) supaya perbandingan 2:1 benar-benar dipakai. Dengan `2fr 1fr`
   polos, lantai bawaan fr adalah min-content: kolom kiri mengembang ikut isi
   tabel (829px terukur) dan kartu Ringkasan kebagian sisanya -- judulnya
   patah dua baris. Lebar minimum tiap kolom tabel sudah dijaga lewat minmax
   di transaksi.php, jadi kolom kiri tetap tidak bisa gepeng. */
.d-grid-trx { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

/* Bar kepala halaman Stok: kotak cari memanjang di kiri, pemilih tab
   menempel di kanan. Satu baris untuk dua kontrol yang dulu memakan dua. */
.d-stok-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 14px;
}
/* Tempat aslinya: anak pertama kartu tabel. Marginnya menyesuaikan supaya
   menempel rapi ke kepala kartu, bukan mengambang. */
.d-card > .d-stok-bar { margin: 0 0 16px; }
/* Kartu tabel bisa menggulir mendatar; tanpa ini barnya ikut hanyut
   ke kiri saat tabel lebar digulir. */
.d-card-scroll > .d-stok-bar { position: sticky; left: 0; }
.d-stok-bar .d-search { flex: 1; min-width: 0; }
.d-stok-bar .d-seg { flex: 0 0 auto; }
.d-stok-bar input:disabled { cursor: not-allowed; }

.d-seg {
    position: relative;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    padding: 3px;
    border-radius: 13px;
    background: var(--field);
    width: max-content;
    max-width: 100%;
}
.d-seg-thumb {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc((100% - 6px) / var(--d-seg-n, 2));
    border-radius: 10px;
    background: var(--card-solid);
    box-shadow: 0 1px 3px rgba(0,0,0,.14);
    transition: transform .42s cubic-bezier(.34,1.35,.42,1);
    pointer-events: none;
}
.d-seg-item {
    position: relative;
    z-index: 1;
    padding: 9px 14px;
    border: none;
    background: none;
    cursor: pointer;
    font-size: 13.5px;
    font-weight: 600;
    white-space: nowrap;
    line-height: 1.3;
    text-align: center;
    color: var(--muted);
    text-decoration: none;
    transition: color .3s;
}
.d-seg-item:hover { text-decoration: none; color: var(--text); }
.d-seg-item.aktif { color: var(--text); }

/* Panel tab yang ditukar di sisi klien. Nama classnya SENGAJA tetap
   .tab-panel (tanpa awalan d-): halaman Stok sudah memakainya di markup dan
   di JS gantiTab(), dan menamainya ulang berarti menyentuh keduanya tanpa
   alasan. Gayanya sendiri sebelumnya ada di style.css lama -- tanpa aturan
   di bawah, SEMUA panel tampil bertumpuk sekaligus. */
.tab-panel { display: none; }

/* Tumpukan kartu berjarak, seperti pembungkus isi layar di prototipe.
   Dipakai panel tab halaman Scan, yang menaruh kartunya sebagai saudara
   langsung (bukan di dalam .d-grid).
   Kedua aturan .tab-panel di bawah ditulis dengan DUA class supaya menang
   atas .d-tumpuk polos -- kalau tidak, panel yang sedang tidak aktif ikut
   tampil begitu .d-tumpuk kebetulan berada di urutan CSS setelahnya. */
.d-tumpuk { display: flex; flex-direction: column; gap: 14px; }
.tab-panel.d-tumpuk:not(.active) { display: none; }
.tab-panel.d-tumpuk.active { display: flex; }
.tab-panel.active { display: block; animation: d-fadeUp .35s var(--d-ease-out) backwards; }

.d-chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.d-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 13px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    flex: 0 0 auto;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--muted);
    text-decoration: none;
    transition: all .3s cubic-bezier(.34,1.3,.42,1);
}
.d-chip:hover { text-decoration: none; }
.d-chip.aktif {
    border-color: transparent;
    background: color-mix(in srgb, var(--primary) 13%, transparent);
    color: var(--primary);
}
.d-chip-tanda { font-size: 13px; opacity: .75; }
.d-chip:not(.aktif) .d-chip-tanda { font-size: 14px; opacity: .6; }
.d-chips-ket { font-size: 12px; color: var(--faint); margin-left: 2px; }

/* -------------------------------------------------------------------------
   11. BAR FILTER STICKY
   z-index 12 + blur 40px. Menempel di atas viewport konten saat digulir.
   ------------------------------------------------------------------------- */
.d-filter {
    position: sticky;
    top: 0;
    z-index: 12;
    /* 14px, sama dengan jarak antar kartu di handoff. Dulu 4px, jadi bar
       filter terlihat menempel ke kartu pertama di bawahnya. */
    margin-bottom: 14px;
    padding: 14px 18px;
    border-radius: 20px;
    background: var(--card);
    backdrop-filter: var(--d-blur-bar);
    -webkit-backdrop-filter: var(--d-blur-bar);
    border: 1px solid var(--border);
    box-shadow: var(--d-shadow-card);
}
.d-filter-baris { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
/* Sepasang tombol di ujung baris filter (Filter + Reset di Arus Kas). */
.d-filter-aksi { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
.d-filter-pisah { height: 1px; background: var(--hair); margin: 12px 0; }

/* Bagian yang sedang diambil ulang lewat Diamondo.ajaxGanti(). Sengaja
   cuma diredupkan, BUKAN diganti spinner: isinya masih angka yang sah
   (periode sebelumnya), jadi lebih baik tetap terbaca sepersekian detik
   daripada berkedip jadi kosong. */
.d-memuat { opacity: .45; pointer-events: none; transition: opacity .15s; }

/* -------------------------------------------------------------------------
   12. TABEL
   Grid, bukan <table>: kolomnya perlu fr/minmax dan tiap baris perlu jadi
   kartu sendiri dengan radius + hover geser. --d-kolom di-set inline.
   ------------------------------------------------------------------------- */
.d-table-head,
.d-table-row {
    display: grid;
    grid-template-columns: var(--d-kolom);
    gap: 14px;
    align-items: center;
}
.d-table-head {
    padding: 0 14px 10px;
    font-size: 11.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--faint);
}
/* Judul kolom yang bisa diklik untuk mengurutkan. Panahnya muncul hanya
   pada kolom yang sedang aktif -- panah pucat di semua kolom sekaligus
   membuat header jadi ramai tanpa menambah informasi. */
.d-th-sort { cursor: pointer; user-select: none; transition: color .2s; }
.d-th-sort:hover { color: var(--text); }
.d-th-sort.sort-asc::after  { content: " \2191"; color: var(--primary); }
.d-th-sort.sort-desc::after { content: " \2193"; color: var(--primary); }

/* Bar paginasi. Markup-nya dibuat buatPaginasi() di assets/table-utils.js
   yang dipakai bersama halaman UI lama, jadi class-nya masih .btn/.small/
   .pagination-info. Ditema ulang di sini secara terkurung dalam
   .d-paginasi, supaya table-utils.js tidak perlu dicabang dua. */
.d-paginasi {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-top: 16px;
}
.d-paginasi:empty { display: none; }
.d-paginasi .pagination-info { font-size: 12.5px; color: var(--muted); }
.d-paginasi .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    padding: 7px 12px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--card-solid);
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color .2s, opacity .2s, transform .28s cubic-bezier(.34,1.3,.42,1);
}
/* Angkat 2px seperti semua tombol lain (handoff). Dulu cuma border yang
   berubah, jadi tombol paginasi terasa mati dibanding tombol di sekitarnya. */
.d-paginasi .btn:hover:not(:disabled) { border-color: var(--primary); transform: translateY(-2px); }
.d-paginasi .btn:disabled { opacity: .4; cursor: default; }

/* ---- Varian untuk <table> sungguhan ----
   d-table (grid) dipakai tabel yang barisnya dirender PHP dan tidak
   disentuh JS. Sebagian halaman lama punya tabel yang barisnya DIBANGUN
   ULANG oleh JS setelah aksi AJAX (mis. Daftar Produk: satu <tr> disusun
   ulang sel demi sel setelah Edit tersimpan). Mengubah tabel itu jadi
   <div> berarti ikut menulis ulang JS-nya -- pekerjaan berisiko yang tidak
   menghasilkan perbedaan apa pun di layar. Jadi tabel semacam itu tetap
   <table>, dan aturan di bawah membuatnya tampil sama dengan .d-table. */
.d-tabel {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0 4px;
    font-size: 13.5px;
}
.d-tabel .rt-head th,
.d-tabel thead th {
    padding: 0 14px 10px;
    font-size: 11.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--faint);
    text-align: left;
    white-space: nowrap;
}
/* Judul kolom ikut perataan selnya. Tanpa ini `text-align:left` di atas
   (spesifisitas 0,2,1) mengalahkan .d-kanan (0,1,0), sehingga SETIAP judul
   kolom angka berdiri di kiri sementara angkanya rata kanan -- matanya
   kehilangan garis bantu dan kolomnya jadi sulit dibaca. */
.d-tabel .rt-head th.d-kanan,
.d-tabel thead th.d-kanan { text-align: right; }
.d-tabel .rt-head th.d-tengah,
.d-tabel thead th.d-tengah { text-align: center; }
.d-tabel td {
    padding: var(--d-pad-row);
    background: var(--card-solid);
    border-top: 1px solid var(--hair);
    border-bottom: 1px solid var(--hair);
    vertical-align: middle;
    /* Jangan pernah memecah nilai di tengah. Tabel Paket punya 8 kolom, dan
       tanpa ini browser memenggal "Rp 611.500" jadi dua baris begitu ruang
       menyempit -- angka yang terbelah lebih sulit dibaca daripada tabel
       yang harus digulir sedikit. Kartunya sendiri yang bergulir mendatar,
       lihat .d-card-scroll. */
    white-space: nowrap;
}
/* Kolom nama boleh membungkus -- isinya kalimat, bukan angka. */
.d-tabel td.d-bungkus { white-space: normal; }

/* Baris pemisah di dalam tabel: bukan data, melainkan judul kelompok
   (dipakai sheet Rincian Batch Stok untuk memisahkan batch yang masih ada
   isinya dari riwayat yang sudah habis). Sengaja di-scope ke class sendiri
   supaya tidak menumpang aturan <td> biasa -- latar kartu, border, dan
   radius sudut dibatalkan semua, jadi ia terbaca sebagai label, bukan baris
   tabel yang kebetulan kosong. */
.d-tabel tr.batch-pemisah td {
    background: none;
    border: 0;
    border-radius: 0;
    padding: 18px 7px 6px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--faint);
}
/* JUDUL kolomnya juga. Aturan `white-space: nowrap` di atas berlaku pada
   .d-tabel .rt-head th / thead th (spesifisitas 0,2,1), jadi menempelkan
   .d-bungkus di <th> saja dulu tidak berpengaruh apa-apa -- judul dua kata
   seperti "Stok Sebelum" atau "Jumlah Masuk" tetap memaksa satu baris dan
   ikut mendorong tabel melewati lebar kartu. Selektornya disamakan panjang
   supaya menang, dan break-word (bukan anywhere) supaya judul pecah di
   SPASI lebih dulu, tidak di tengah kata. */
.d-tabel .rt-head th.d-bungkus,
.d-tabel thead th.d-bungkus { white-space: normal; overflow-wrap: break-word; }

/* Kartu yang isinya tabel lebar: yang bergulir KARTUNYA, bukan badan
   halaman (aturan handoff -- halaman tidak boleh pernah bergeser mendatar). */
.d-card-scroll { overflow-x: auto; }
.d-tabel td:first-child {
    border-left: 1px solid var(--hair);
    border-radius: 14px 0 0 14px;
}
.d-tabel td:last-child {
    border-right: 1px solid var(--hair);
    border-radius: 0 14px 14px 0;
}
.d-tabel tbody tr,
.d-tabel tr { transition: transform .26s, box-shadow .26s; }
/* Baris masuk bergiliran (handoff: rowIn .42s, jeda 34ms/baris). Dulu cuma
   .d-table-row versi grid yang dapat; tabel <table> -- yang dipakai sebagian
   besar halaman -- muncul serentak tanpa animasi. */
.d-tabel tr:not(.rt-head) { animation: d-rowIn .42s var(--d-ease-out) backwards; }
.d-tabel tr:not(.rt-head):nth-child(1)  { animation-delay: 0ms; }
.d-tabel tr:not(.rt-head):nth-child(2)  { animation-delay: 34ms; }
.d-tabel tr:not(.rt-head):nth-child(3)  { animation-delay: 68ms; }
.d-tabel tr:not(.rt-head):nth-child(4)  { animation-delay: 102ms; }
.d-tabel tr:not(.rt-head):nth-child(5)  { animation-delay: 136ms; }
.d-tabel tr:not(.rt-head):nth-child(6)  { animation-delay: 170ms; }
.d-tabel tr:not(.rt-head):nth-child(7)  { animation-delay: 204ms; }
.d-tabel tr:not(.rt-head):nth-child(8)  { animation-delay: 238ms; }
.d-tabel tr:not(.rt-head):nth-child(9)  { animation-delay: 272ms; }
.d-tabel tr:not(.rt-head):nth-child(10) { animation-delay: 306ms; }
/* Lewat baris ke-11 delaynya dirata: lebih dari itu terasa seperti halaman
   lambat, bukan animasi -- alasan yang sama seperti .d-table-row. */
.d-tabel tr:not(.rt-head):nth-child(n+11) { animation-delay: 340ms; }

/* Hover: geser 4px + bayangan halus, sesuai handoff. Berlaku untuk SEMUA
   baris, bukan cuma .d-baris-klik -- di prototipe pun begitu. */
.d-tabel tr:not(.rt-head):hover td { background: color-mix(in srgb, var(--primary) 4%, var(--card-solid)); }
.d-tabel tr:not(.rt-head):hover {
    transform: translateX(4px);
    box-shadow: 0 10px 26px -18px rgba(0, 0, 0, .4);
}
.d-tabel .empty-state,
.d-tabel td.empty-state {
    padding: 34px;
    text-align: center;
    color: var(--faint);
    background: transparent;
    border: 0;
}
/* Beberapa badge berdampingan dalam satu sel, bukan bertumpuk lewat <br>.
   Dipakai kolom Status di tabel Produk supaya barisnya setinggi satu baris
   teks saja. flex-wrap dipertahankan supaya di kolom sempit badge-nya turun
   sendiri, bukan menjebol lebar tabel. */
.d-badge-baris {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
.d-badge-baris > .hide-mobile {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Label yang diawali kotak centang (mis. pilihan metrik grafik Laporan). */
.d-cek-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    font-size: 13.5px;
}

/* -------------------------------------------------------------------------
   TABEL LABA RUGI (tab Laporan > Laba Rugi)
   Baris pengurang ditulis menjorok & diredupkan; dua baris jumlah diberi
   tint (hijau untuk Laba Kotor, burgundy untuk Laba Bersih) seperti handoff.
   ------------------------------------------------------------------------- */
.d-tabel-labarugi .baris-lr-kurang td:first-child { padding-left: 30px; color: var(--muted); }
.d-tabel-labarugi .baris-lr-total-hijau td {
    background: color-mix(in srgb, var(--green) 11%, var(--card-solid));
    font-weight: 700;
}
.d-tabel-labarugi .baris-lr-total-brand td {
    background: color-mix(in srgb, var(--primary) 11%, var(--card-solid));
    font-weight: 700;
}
.d-tabel-labarugi .baris-lr-total-hijau:hover td,
.d-tabel-labarugi .baris-lr-total-brand:hover td { background: inherit; }

/* Daftar "label ... nilai" dua kolom di dalam kartu (kartu Titik Impas). */
.d-rincian { display: flex; flex-direction: column; gap: 2px; }
.d-rincian-baris {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    padding: 9px 0;
    font-size: 13.5px;
    border-bottom: 1px solid var(--hair);
}
.d-rincian-baris:last-child { border-bottom: 0; }
.d-rincian-baris > :first-child { color: var(--muted); }
.d-rincian-total {
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
    border-bottom: 0;
}

/* Baris produk nonaktif: diredupkan, bukan disembunyikan. */
.d-tabel tr.tr-nonaktif td { opacity: .55; }

/* Baris yang membuka sheet kalau diklik (Produk & Paket di halaman Stok).
   Handoff memindahkan Edit dari tombol per baris ke barisnya sendiri, jadi
   barisnya harus KELIHATAN bisa diklik: kursor berubah, dan seluruh baris
   bergeser sedikit ke kanan saat disorot -- gerakan yang sama dengan baris
   tabel di prototipe (translateX 4px + bayangan). */
.d-tabel tr.d-baris-klik { cursor: pointer; }
.d-tabel tr.d-baris-klik:hover {
    transform: translateX(4px);
    box-shadow: 0 10px 26px -18px rgba(0, 0, 0, .4);
}
.d-tabel tr.d-baris-klik:focus-visible {
    outline: none;
    transform: translateX(4px);
}
.d-tabel tr.d-baris-klik:focus-visible td {
    background: color-mix(in srgb, var(--primary) 7%, var(--card-solid));
}
.d-tabel tr.d-baris-klik:focus-visible td:first-child { box-shadow: inset 3px 0 0 var(--primary); }

/* Tabel pratinjau impor -- padat, dalam kotak bergulir. */
.d-tabel-pratinjau { width: 100%; border-collapse: collapse; font-size: 12px; }
.d-tabel-pratinjau th,
.d-tabel-pratinjau td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--hair);
    text-align: left;
    white-space: nowrap;
}
.d-tabel-pratinjau th {
    position: sticky;
    top: 0;
    background: var(--field);
    font-weight: 700;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--faint);
}

.d-table-body { display: flex; flex-direction: column; gap: 4px; }
.d-table-row {
    padding: var(--d-pad-row);
    border-radius: 14px;
    background: var(--card-solid);
    border: 1px solid var(--hair);
    font-size: 13.5px;
    color: inherit;
    text-decoration: none;
    animation: d-rowIn .42s var(--d-ease-out) backwards;
    transition: transform .26s, box-shadow .26s;
}
.d-table-row:hover {
    transform: translateX(4px);
    box-shadow: 0 10px 26px -18px rgba(0,0,0,.4);
    text-decoration: none;
}
a.d-table-row, .d-table-row[data-klik] { cursor: pointer; }
/* Sel dipotong ellipsis KECUALI kolom beraksi (.d-keep) -- tanpa ini
   tombol di kolom Aksi ikut terpotong begitu tabelnya sempit. */
.d-table-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.d-table-row > .d-keep { overflow: visible; }
.d-kanan  { text-align: right; }
.d-tengah { text-align: center; }
.d-table-kosong { padding: 34px; text-align: center; color: var(--faint); font-size: 13.5px; }

/* Delay masuk per baris 34ms. Dibatasi 12 baris lalu rata: lebih dari itu
   delay-nya jadi lebih dari 0,4 detik dan mulai terasa seperti halaman
   lambat, bukan animasi. Baris ke-25 ke atas langsung tampil tanpa animasi
   sama sekali -- tabel panjang tidak boleh menunda pembacaan. */
.d-table-body > .d-table-row:nth-child(1)  { animation-delay: 0ms; }
.d-table-body > .d-table-row:nth-child(2)  { animation-delay: 34ms; }
.d-table-body > .d-table-row:nth-child(3)  { animation-delay: 68ms; }
.d-table-body > .d-table-row:nth-child(4)  { animation-delay: 102ms; }
.d-table-body > .d-table-row:nth-child(5)  { animation-delay: 136ms; }
.d-table-body > .d-table-row:nth-child(6)  { animation-delay: 170ms; }
.d-table-body > .d-table-row:nth-child(7)  { animation-delay: 204ms; }
.d-table-body > .d-table-row:nth-child(8)  { animation-delay: 238ms; }
.d-table-body > .d-table-row:nth-child(9)  { animation-delay: 272ms; }
.d-table-body > .d-table-row:nth-child(10) { animation-delay: 306ms; }
.d-table-body > .d-table-row:nth-child(11) { animation-delay: 340ms; }
.d-table-body > .d-table-row:nth-child(n+12) { animation-delay: 374ms; }
.d-table-body > .d-table-row:nth-child(n+25) { animation: none; }

/* Tabel yang badannya dirender ulang berkala (Barang Keluar: polling 3 detik
   supaya terasa realtime saat resi discan). Di sana animasi masuk justru
   merugikan -- tiap muat ulang mengganti SELURUH isi tbody, jadi d-rowIn main
   lagi dari awal dan penyegaran yang seharusnya tak terasa malah jadi kedipan
   berjenjang tiap 3 detik.
   Berlaku di HP maupun desktop: aturannya tidak dibungkus media query.
   Ditaruh SESUDAH blok nth-child di atas -- kekhususannya seri (0,3,0), jadi
   yang menang adalah yang belakangan. */
.d-tanpa-animasi .d-table-body > .d-table-row,
.d-tanpa-animasi .d-table-row * { animation: none; }

/* SKU pada Barang Keluar dicetak tebal (permintaan pemilik 17 Agu 2026).
   Di HP ia menggantikan kolom Penerima, di desktop ia satu dari tujuh
   kolom -- di kedua-duanya dialah yang dibaca petugas saat mencocokkan
   barang, jadi ia yang perlu paling cepat ketemu mata. Tidak dibungkus
   media query: berlaku di HP maupun desktop.
   Di-scope ke .baris-belum-checkout, BUKAN ke [data-label="SKU"] umum:
   data-label dipasang otomatis oleh d_table() dari judul kolom, jadi
   selektor tanpa scope akan ikut menebalkan kolom SKU di halaman lain
   yang tidak meminta apa-apa.
   .detail-more ("+2 lainnya") sengaja dikembalikan ke berat biasa -- itu
   keterangan bahwa masih ada sisa, bukan bagian dari SKU-nya. */
.baris-belum-checkout > [data-label="SKU"] { font-weight: 700; }
.baris-belum-checkout > [data-label="SKU"] .detail-more { font-weight: 400; }

/* -------------------------------------------------------------------------
   13. BADGE & DELTA
   ------------------------------------------------------------------------- */
.d-badge {
    display: inline-block;
    animation: d-popIn .28s var(--d-ease-pop) backwards;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .01em;
    background: color-mix(in srgb, var(--d-badge-warna, var(--muted)) 14%, transparent);
    color: var(--d-badge-warna, var(--muted));
}
/* Badge yang sekaligus tautan (mis. "n SKU tertahan" di Data Reseller):
   tetap terlihat sebagai badge, bukan teks bergaris bawah. */
a.d-badge { text-decoration: none; cursor: pointer; }
.d-badge-ok    { --d-badge-warna: var(--green); }
.d-badge-warn  { --d-badge-warna: var(--orange); }
.d-badge-buruk { --d-badge-warna: var(--red); }
.d-badge-info  { --d-badge-warna: var(--teal); }
/* Kelabu: keadaan yang bukan kabar baik maupun buruk, cuma "tidak berjalan"
   (tenant Nonaktif & Internal di handoff). --d-badge-info sudah dipakai teal,
   dan teal terbaca seperti informasi penting. */
.d-badge-netral { --d-badge-warna: var(--muted); }
.d-badge-brand { --d-badge-warna: var(--primary); }
/* Warna merek marketplace. Ditaruh sebagai token supaya tema gelap bisa
   menaikkan terangnya tanpa mengulang aturan badge-nya. Nilai mentah merek
   itu dirancang untuk latar putih; di atas #0b0b0d hijau Tokopedia mulai
   redup, jadi versi gelapnya dinaikkan. */
.d-badge-shopee { --d-badge-warna: var(--merek-shopee); }
.d-badge-tiktok { --d-badge-warna: var(--merek-tiktok); }

.d-delta {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -.01em;
    background: color-mix(in srgb, var(--d-delta-warna) 14%, transparent);
    color: var(--d-delta-warna);
}
.d-delta-naik  { --d-delta-warna: var(--green); }
.d-delta-turun { --d-delta-warna: var(--red); }
.d-delta i { font-style: normal; font-size: 9px; }

/* -------------------------------------------------------------------------
   14. BAR HORIZONTAL & PROGRESS
   ------------------------------------------------------------------------- */
.d-bars { display: flex; flex-direction: column; gap: 13px; }
.d-bar-label { display: flex; justify-content: space-between; font-size: 13.5px; margin-bottom: 6px; }
.d-bar-label > :first-child { font-weight: 600; }
.d-bar-label > :last-child { color: var(--muted); font-variant-numeric: tabular-nums; }
.d-bar-track { height: 8px; border-radius: 6px; background: var(--field); overflow: hidden; }
.d-bar-isi {
    height: 100%;
    border-radius: 6px;
    background: var(--d-bar-warna, var(--primary));
    transform-origin: left;
    animation: d-growX .8s var(--d-ease-out) backwards;
    transition: width .6s;
}
/* ---------------------------------------------------------------------
   Grafik batang berkelompok (d_chart_batang) -- dua batang per kelompok.
   Tingginya persentase dari puncak, jadi tidak perlu diukur JS.
   --------------------------------------------------------------------- */
/* ---------------------------------------------------------------------
   Slot suara scan (sheet Unggah Suara Scan). Satu baris per kejadian, dan
   barisnya SEKALIGUS zona lepas berkas -- garis putus-putus bernada sesuai
   kejadiannya (hijau berhasil, merah gagal, kelabu selesai).
   --------------------------------------------------------------------- */
.d-slot-suara {
    --d-slot-warna: var(--muted);
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    border-radius: 16px;
    border: 1.5px dashed color-mix(in srgb, var(--d-slot-warna) 40%, transparent);
    background: color-mix(in srgb, var(--d-slot-warna) 5%, transparent);
    transition: border-color .22s, background .22s, transform .22s;
}
.d-slot-ok     { --d-slot-warna: var(--green); }
.d-slot-buruk  { --d-slot-warna: var(--red); }
.d-slot-netral { --d-slot-warna: var(--muted); }
.d-slot-suara.d-slot-aktif {
    border-color: var(--d-slot-warna);
    background: color-mix(in srgb, var(--d-slot-warna) 12%, transparent);
    transform: scale(1.01);
}
.d-slot-ikon {
    flex: 0 0 38px;
    width: 38px;
    height: 38px;
    border-radius: 11px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--d-slot-warna) 16%, transparent);
    color: var(--d-slot-warna);
}
.d-slot-ikon svg { width: 18px; height: 18px; }
.d-slot-teks { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.d-slot-judul { font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.d-slot-ket   { font-size: 12px; color: var(--muted); }
.d-slot-berkas {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11.5px;
    color: var(--faint);
    overflow-wrap: anywhere;
}
.d-slot-aksi { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }

/* Kepala sheet Edit Profil: avatar + tombol ganti foto berdampingan. */
.d-profil-atas { display: flex; align-items: center; gap: 16px; }
.d-profil-atas .user-avatar { flex: 0 0 62px; width: 62px; height: 62px; font-size: 22px; }

/* Indikator kekuatan password. */
.d-pw-kuat { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.d-pw-track { flex: 1; height: 5px; border-radius: 999px; background: var(--field); overflow: hidden; }
.d-pw-isi { display: block; height: 100%; width: 0; border-radius: 999px; transition: width .3s var(--d-ease-out), background .3s; }
.d-pw-label { font-size: 12px; font-weight: 700; flex: 0 0 auto; }

@media (max-width: 560px) {
    .d-slot-suara { flex-wrap: wrap; }
    .d-slot-aksi { width: 100%; justify-content: flex-end; }
}

/* Sheet dua-rentang: pasangan tanggal KIRI lawan KANAN, dipisah kolom
   "dibandingkan dengan" bergaris tegak. Bertumpuk atas-bawah membuat keempat
   kotaknya terbaca seperti satu daftar berurutan, bukan dua sisi. */
.d-banding-duo {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 12px;
    align-items: center;
}
/* Tombol tanggal mengisi penuh kolomnya -- kalau dibiarkan selebar isinya,
   sisa ruang di kanan tiap tombol jadi lubang kosong yang membuat sheet
   terasa jauh lebih lebar dari isinya. */
.d-banding-sisi .d-kal-tombol { width: 100%; justify-content: flex-start; }
.d-banding-sisi .d-field + .d-field { margin-top: 10px !important; }
.d-banding-vs {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    width: 82px;
    font-size: 12px;
    color: var(--muted);
    text-align: center;
    line-height: 1.3;
}
.d-banding-vs::before,
.d-banding-vs::after {
    content: "";
    flex: 1;
    width: 1px;
    background: var(--hair);
}
/* Di HP sheet-nya selebar layar -- tiga kolom jadi terlalu sempit, jadi
   dilipat: pasangan atas, pemisah mendatar, pasangan bawah. */
@media (max-width: 640px) {
    .d-banding-duo { grid-template-columns: 1fr; }
    .d-banding-vs {
        flex-direction: row;
        max-width: none;
        width: 100%;
        gap: 12px;
    }
    .d-banding-vs::before,
    .d-banding-vs::after { width: auto; height: 1px; }
}

.d-batang { --d-batang-tinggi: 220px; }
.d-batang-plot {
    display: flex;
    align-items: flex-end;
    gap: 6px;
    height: var(--d-batang-tinggi);
    margin-top: 8px;
}
.d-batang-grup {
    flex: 1 1 0;
    min-width: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: 9px;
}
.d-batang-pasangan {
    flex: 1;
    width: 100%;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 7px;
}
.d-batang-bar {
    position: relative;
    width: 26px;
    max-width: 22%;
    min-height: 3px;
    border-radius: 7px 7px 0 0;
    transform-origin: bottom;
    animation: d-grow .75s var(--d-ease-out) backwards;
    transition: filter .25s;
}
.d-batang-grup:nth-child(1) .d-batang-bar { animation-delay: 0ms; }
.d-batang-grup:nth-child(2) .d-batang-bar { animation-delay: 60ms; }
.d-batang-grup:nth-child(3) .d-batang-bar { animation-delay: 120ms; }
.d-batang-grup:nth-child(4) .d-batang-bar { animation-delay: 180ms; }
.d-batang-grup:nth-child(5) .d-batang-bar { animation-delay: 240ms; }
.d-batang-grup:nth-child(6) .d-batang-bar { animation-delay: 300ms; }
.d-batang-grup:nth-child(n+7) .d-batang-bar { animation-delay: 360ms; }
.d-batang-bar:hover { filter: brightness(1.12); }
.d-batang-nama {
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
/* Tooltip kustom -- handoff melarang title= bawaan browser. */
.d-batang-tip {
    position: absolute;
    bottom: calc(100% + 9px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 7px 11px;
    border-radius: 11px;
    background: var(--card-solid);
    border: 1px solid var(--border);
    box-shadow: 0 18px 34px -18px rgba(0,0,0,.45);
    font-size: 11.5px;
    color: var(--muted);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity .22s, transform .22s;
}
.d-batang-tip b { font-size: 13px; color: var(--text); font-variant-numeric: tabular-nums; }
.d-batang-bar:hover .d-batang-tip { opacity: 1; transform: translateX(-50%) translateY(0); }
.d-batang-legenda {
    display: flex;
    gap: 18px;
    margin-top: 16px;
    font-size: 12.5px;
    color: var(--muted);
}
.d-batang-legenda span { display: inline-flex; align-items: center; gap: 7px; }
.d-batang-legenda i { width: 16px; height: 4px; border-radius: 999px; }

.d-bars > *:nth-child(1) .d-bar-isi { animation-delay: 0ms; }
.d-bars > *:nth-child(2) .d-bar-isi { animation-delay: 70ms; }
.d-bars > *:nth-child(3) .d-bar-isi { animation-delay: 140ms; }
.d-bars > *:nth-child(4) .d-bar-isi { animation-delay: 210ms; }
.d-bars > *:nth-child(5) .d-bar-isi { animation-delay: 280ms; }
.d-bars > *:nth-child(n+6) .d-bar-isi { animation-delay: 350ms; }

/* Bar mini di kolom "Pangsa" tabel produk terbaik: 46x6px.
   display:block WAJIB di track & isi -- keduanya <span> (harus inline agar
   boleh duduk di dalam sel tabel bersama teks persennya), dan elemen inline
   mengabaikan width/height sepenuhnya, jadi tanpa ini barnya tidak pernah
   tergambar sama sekali. */
.d-pangsa { display: inline-flex; align-items: center; gap: 8px; justify-content: flex-end; }
.d-pangsa-track {
    display: block;
    width: 46px;
    height: 6px;
    border-radius: 4px;
    background: var(--field);
    overflow: hidden;
    flex: 0 0 46px;
}
.d-pangsa-isi {
    display: block;
    height: 100%;
    border-radius: 4px;
    background: var(--primary);
    transform-origin: left;
    animation: d-growX .8s var(--d-ease-out) backwards;
}

/* -------------------------------------------------------------------------
   15. ISIAN
   ------------------------------------------------------------------------- */
.d-field { display: block; }
.d-field-label { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 7px; }
.d-input {
    width: 100%;
    padding: 11px 14px;
    border-radius: 12px;
    border: 1px solid var(--border);
    background: var(--card-solid);
    color: var(--text);
    font-size: 14px;
    transition: border-color .25s, box-shadow .25s;
}
.d-input:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--primary) 16%, transparent);
}
.d-input-angka { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13.5px; }

/* Panah naik-turun bawaan browser di <input type="number"> dibuang.
   Dua alasan: bentuknya digambar OS (handoff melarang kontrol bawaan
   browser), dan di kolom harga tombol itu lebih sering jadi sumber salah
   input daripada bantuan -- sekali serempet, HPP berubah tanpa disadari.
   Angka tetap diketik seperti biasa; tempat yang memang butuh naik-turun
   sudah punya .d-stepper sendiri.
   Pasangannya di assets/app.js: roda mouse & tombol panah/PageUp/PageDown
   juga tidak lagi mengubah nilainya. */
.d-input[type="number"]::-webkit-outer-spin-button,
.d-input[type="number"]::-webkit-inner-spin-button,
.qty-field::-webkit-outer-spin-button,
.qty-field::-webkit-inner-spin-button,
.inp-qty-selamat::-webkit-outer-spin-button,
.inp-qty-selamat::-webkit-inner-spin-button {
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
}
.d-input[type="number"],
.qty-field,
.inp-qty-selamat {
    -moz-appearance: textfield;
    appearance: textfield;
}

/* Isian yang nilainya sudah diubah tapi BELUM disimpan (tabel HPP Reseller).
   Penanda ini yang membuat "n SKU diubah" di action bar bisa ditelusuri
   balik ke barisnya -- tanpa itu user harus mengingat sendiri mana yang
   tadi disentuh sebelum menekan Simpan. */
.d-input-berubah {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 6%, var(--card-solid));
}

/* <select> yang TIDAK bisa dijadikan pill.
   Aturan #1 handoff melarang <select>, dan hampir semuanya sudah diganti
   d_pilih(). Yang tersisa adalah dropdown yang daftar pilihannya DIBANGUN
   ULANG oleh JS saat berjalan (mis. daftar supplier solasi yang menyesuaikan
   jenis kardus terpilih) dan bisa memuat puluhan baris -- deretan pill di
   situ jadi tidak terpakai, bukan lebih baik. Ditema seperti .d-input,
   dengan panah digambar sendiri supaya tidak memakai panah bawaan OS. */
.d-select {
    width: 100%;
    padding: 11px 38px 11px 14px;
    border-radius: 12px;
    border: 1px solid var(--border);
    background-color: var(--card-solid);
    color: var(--text);
    font-size: 14px;
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: calc(100% - 19px) calc(50% + 1px), calc(100% - 14px) calc(50% + 1px);
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    transition: border-color .25s, box-shadow .25s;
}
.d-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--primary) 16%, transparent);
}

/* <select> aslinya TIDAK dibuang -- dia tetap sumber nilai & tetap ikut
   terkirim saat form disubmit; yang terlihat cuma tombol pengganti di
   sebelahnya (lihat pasangPilihKustom() di assets/app.js). Disembunyikan
   dengan cara yang tidak melepasnya dari form. */
select.d-select-asli {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.d-select-tombol {
    display: block;
    text-align: left;
    font-family: inherit;
    line-height: 1.35;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.d-select-tombol.d-select-kosong { color: var(--faint); }
.d-select-tombol:disabled { opacity: .55; cursor: not-allowed; }

.d-pilih-panel {
    position: absolute;
    z-index: 90;
    max-width: min(420px, calc(100vw - 24px));
    padding: 6px;
    border-radius: 16px;
    background: var(--card-solid);
    border: 1px solid var(--border);
    box-shadow: 0 24px 50px -18px rgba(0, 0, 0, .4);
    animation: d-fadeUp .24s var(--d-ease-out) backwards;
}
.d-pilih-cari {
    width: 100%;
    margin-bottom: 6px;
    padding: 9px 12px;
    border-radius: 11px;
    border: 1px solid var(--border);
    background: var(--field);
    color: var(--text);
    font-size: 13.5px;
}
.d-pilih-cari:focus {
    outline: none;
    border-color: var(--primary);
}
.d-pilih-daftar { max-height: 300px; overflow-y: auto; }
.d-pilih-grup {
    padding: 8px 12px 4px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--faint);
}
.d-pilih-opsi {
    display: block;
    width: 100%;
    padding: 10px 12px;
    border: none;
    background: none;
    border-radius: 11px;
    cursor: pointer;
    text-align: left;
    color: var(--text);
    font-size: 13.5px;
    font-family: inherit;
    transition: background .18s;
}
.d-pilih-opsi:hover,
.d-pilih-opsi:focus-visible { background: var(--field); outline: none; }
.d-pilih-opsi.aktif { background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--primary); font-weight: 600; }
.d-pilih-opsi:disabled { opacity: .45; cursor: default; }
.d-pilih-kosong { padding: 22px; text-align: center; font-size: 13px; color: var(--faint); }
.d-field-baris { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* Tiga isian sebaris (HPP / Harga Jual / Harga Reseller di form Paket). */
.d-field-baris-3 { grid-template-columns: 1fr 1fr 1fr; }

/* Kotak centang berlabel yang berdiri sendiri (bukan di dalam tabel):
   seluruh barisnya jadi sasaran klik, bukan cuma kotak 18px-nya. */
.d-toggle-baris {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 14px;
    border-radius: 12px;
    background: var(--field);
    font-size: 13.5px;
    cursor: pointer;
    transition: background .2s;
}
.d-toggle-baris:hover { background: color-mix(in srgb, var(--primary) 7%, var(--field)); }
.d-toggle-baris input { flex: 0 0 auto; }

/* -------------------------------------------------------------------------
   PEMILIH KOMPONEN PAKET (sheet Tambah / Edit Paket)
   Menggantikan deretan <select> bawaan browser: komponen terpilih tampil
   sebagai kartu (kode + modalnya + stepper qty + tombol buang), dan produk
   yang belum dipakai dipilih dari panel yang bisa dibuka-tutup.
   ------------------------------------------------------------------------- */
.komp-kepala {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}
.komp-baris {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    margin-bottom: 6px;
    border-radius: 14px;
    background: var(--card-solid);
    border: 1px solid var(--hair);
    animation: d-fadeUp .32s var(--d-ease-out) backwards;
}
.komp-info { flex: 1; min-width: 0; }
.komp-kode {
    display: block;
    font-size: 13.5px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.komp-ket {
    display: block;
    margin-top: 2px;
    font-size: 11.5px;
    color: var(--faint);
}
.komp-buang {
    flex: 0 0 26px;
    width: 26px;
    height: 26px;
    border: none;
    border-radius: 50%;
    background: var(--field);
    color: var(--muted);
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
    transition: background .22s, color .22s;
}
.komp-buang:hover {
    background: color-mix(in srgb, var(--red) 16%, transparent);
    color: var(--red);
}
.komp-kosong {
    padding: 20px;
    border-radius: 14px;
    background: var(--field);
    text-align: center;
    font-size: 13px;
    color: var(--faint);
}
.komp-tambah-wrap { margin-top: 10px; }
.komp-panel {
    margin-top: 8px;
    max-height: 220px;
    overflow: auto;
    padding: 6px;
    border-radius: 16px;
    background: var(--field);
    animation: d-fadeUp .3s var(--d-ease-out) backwards;
}
.komp-opsi {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    /* Border transparan, bukan `none`: hover mengubah WARNANYA, dan tanpa
       border yang sudah ada perubahan warna itu tidak terlihat sekaligus
       menggeser tata letak 1px saat muncul. */
    border: 1px solid transparent;
    background: none;
    border-radius: 11px;
    cursor: pointer;
    text-align: left;
    color: var(--text);
    transition: background .2s, transform .24s, border-color .24s;
}
/* Handoff: butir daftar di sheet -> translateX(4px) + border --primary. */
.komp-opsi:hover {
    background: var(--card-solid);
    transform: translateX(4px);
    border-color: var(--primary);
}
.komp-tambah-label {
    flex: 0 0 auto;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--primary);
}

.d-stepper {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px;
    border-radius: 12px;
    background: var(--field);
}
.d-stepper button {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    border: none;
    cursor: pointer;
    background: var(--card-solid);
    color: var(--text);
    font-size: 16px;
    font-weight: 600;
    line-height: 1;
    box-shadow: 0 1px 2px rgba(0,0,0,.12);
    transition: transform .22s;
}
.d-stepper button:active { transform: scale(.9); }
.d-stepper-nilai {
    min-width: 38px;
    width: 4.5em;
    text-align: center;
    font-size: 14.5px;
    font-weight: 700;
}
/* Angkanya bisa diketik langsung -- tombol saja tidak cukup untuk perubahan
   stok besar. Tampilannya tetap teks polos, bukan kotak input. */
input.d-stepper-nilai {
    border: none;
    background: none;
    color: inherit;
    font-family: inherit;
    padding: 0;
    border-radius: 8px;
}
input.d-stepper-nilai:focus { outline: 2px solid var(--primary); outline-offset: 2px; }

.d-toggle {
    position: relative;
    width: 50px;
    height: 30px;
    border-radius: 999px;
    border: none;
    cursor: pointer;
    background: var(--field);
    transition: background .32s;
    flex: 0 0 50px;
}
.d-toggle.aktif { background: var(--green); }
.d-toggle::after {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.3);
    transition: transform .34s var(--d-ease-pop);
}
.d-toggle.aktif::after { transform: translateX(20px); }

/* Drop zone berkas. Aturan #1 handoff melarang elemen bawaan browser, dan
   <input type="file"> adalah pelanggar terakhir yang tersisa: tombol
   "Choose Files"-nya digambar OS, tidak bisa ditema, dan berbahasa Inggris
   walau seluruh UI berbahasa Indonesia. Input aslinya tetap ada (dibuat
   tak terlihat, BUKAN display:none supaya tetap bisa difokus keyboard dan
   tetap ikut ter-submit); yang terlihat adalah zona ini. */
.d-drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 22px 18px;
    border-radius: 16px;
    border: 1.5px dashed var(--border);
    background: var(--field);
    text-align: center;
    cursor: pointer;
    transition: border-color .25s, background .25s;
}
.d-drop:hover { border-color: var(--primary); }
.d-drop.seret {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 8%, transparent);
}
.d-drop input[type="file"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
.d-drop svg { width: 22px; height: 22px; color: var(--faint); }
.d-drop-judul { font-size: 13.5px; font-weight: 600; }
.d-drop-ket   { font-size: 12px; color: var(--faint); }
.d-drop-nama  { font-size: 12.5px; font-weight: 600; color: var(--primary); }
/* Jarak antar langkah di blok Rekonsiliasi (index.php). Dulu style inline;
   dijadikan kelas supaya versi ringkas di kartu HP bisa merapatkannya tanpa
   perlu !important. Nilainya sama persis dengan inline yang digantikan. */
.d-rekon-langkah { margin-top: 16px; }

/* Checkbox bulat. appearance:none lalu digambar sendiri -- kotak centang
   bawaan OS satu-satunya elemen di halaman ini yang tidak ikut tema, dan
   di mode gelap ia tampil sebagai kotak putih terang yang mencolok. */
.d-check {
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    width: 21px;
    height: 21px;
    flex: 0 0 21px;
    border-radius: 7px;
    border: 1.5px solid var(--border);
    background: var(--card-solid);
    cursor: pointer;
    margin: 0;
    transition: background .24s var(--d-ease-pop), border-color .24s var(--d-ease-pop), transform .24s var(--d-ease-pop);
}
.d-check:hover { border-color: var(--primary); }
.d-check:checked {
    background: var(--primary);
    border-color: var(--primary);
    /* Sedikit membesar saat tercentang -- gerakan yang sama dengan
       prototipe, jadi perubahannya terasa, bukan cuma berganti warna. */
    transform: scale(1.06);
}
.d-check:checked::after {
    content: "";
    position: absolute;
    left: 6.5px;
    top: 3px;
    width: 5px;
    height: 10px;
    border: solid #fff;
    border-width: 0 2.2px 2.2px 0;
    transform: rotate(45deg);
}
/* Keadaan setengah (sebagian baris terpilih) di kotak "pilih semua". */
.d-check:indeterminate {
    background: var(--primary);
    border-color: var(--primary);
}
.d-check:indeterminate::after {
    content: "";
    position: absolute;
    left: 4px;
    top: 8.2px;
    width: 11px;
    height: 0;
    border-top: 2.2px solid #fff;
    border-radius: 2px;
}
/* Kolom yang gagal validasi. Ditandai app.js waktu peristiwa 'invalid'
   ditahan -- lihat pasangValidasiKustom(). Ini pengganti VISUAL dari
   gelembung bawaan browser, bukan tambahan di atasnya. */
.d-invalid,
.d-input.d-invalid,
.d-select.d-invalid {
    border-color: var(--red) !important;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 16%, transparent);
}
/* <select> yang sudah digantikan tombol kustom disembunyikan, jadi yang
   perlu disorot tombolnya -- bukan select-nya yang tak terlihat.

   PENTING soal arah selektor: pasangPilihKustom() di app.js memasang
   tombolnya dengan insertBefore(tombol, sel) -- jadi urutan DOM-nya
   TOMBOL dulu, baru <select>. Versi pertama aturan ini memakai
   `.d-invalid + .d-select-tombol` ("tombol SESUDAH select") dan karena itu
   tidak pernah cocok sama sekali. CSS tidak punya kombinator "saudara
   sebelumnya", jadi arahnya dibalik lewat :has(). */
.d-select-tombol:has(+ select.d-select-asli.d-invalid) {
    border-color: var(--red) !important;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 16%, transparent);
}

.d-check:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--primary) 16%, transparent);
}

/* Baris "Pilih semua N" di atas tabel bercentang. */
.d-pilih-semua {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    border-radius: 14px;
    background: var(--field);
    margin-bottom: 10px;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
}

/* -------------------------------------------------------------------------
   KOTAK SCAN (halaman Scan)
   Nama class-nya SENGAJA dipertahankan (.scan-box, .scan-input, .scan-sisa)
   dan bukan diberi awalan d-. Dua alasan: seluruh JS halaman Scan menyasar
   kelas & id ini (fokus otomatis, polling, mode kamera), dan assets/mobile.css
   sudah punya versi mobilenya sendiri untuk .scan-box -- dimuat setelah
   berkas ini, jadi di HP tetap versi mobile yang menang tanpa perlu diubah.
   ------------------------------------------------------------------------- */
.scan-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 32px 24px;
}
/* Lencana ikon di atas judul -- digambar CSS, bukan <img>, supaya ikut
   warna aksen yang dipilih user. */
.scan-box::before {
    content: "";
    width: 52px;
    height: 52px;
    border-radius: 15px;
    margin-bottom: 16px;
    animation: d-pulseRing 2.4s ease-out infinite;
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    background-image: linear-gradient(var(--primary), var(--primary)),
                      linear-gradient(var(--primary), var(--primary)),
                      linear-gradient(var(--primary), var(--primary)),
                      linear-gradient(var(--primary), var(--primary));
    background-size: 2px 18px, 2px 18px, 2px 18px, 2px 18px;
    background-position: 18px 17px, 23px 17px, 29px 17px, 34px 17px;
    background-repeat: no-repeat;
}
.scan-box > label {
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -.02em;
    margin-bottom: 6px;
}
.scan-box .scan-hint { max-width: 460px; margin: 0 0 18px; }

.scan-input {
    width: 100%;
    max-width: 500px;
    padding: 18px 20px;
    border-radius: 16px;
    border: 1px solid var(--border);
    background: var(--card-solid);
    color: var(--text);
    font-size: 19px;
    font-weight: 600;
    text-align: center;
    letter-spacing: .01em;
    transition: border-color .25s, box-shadow .25s;
}
.scan-input::placeholder { color: var(--faint); font-weight: 500; }
/* Tombol di bawah kotak input scan ("Proses", "Scan dengan Kamera HP"). */
.scan-aksi {
    display: flex;
    gap: 9px;
    flex-wrap: wrap;
    justify-content: center;
    margin-top: 14px;
}
.scan-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 15%, transparent);
}

/* Angka sisa antrean di dalam kartu scan. Class .stat-box dipertahankan --
   JS (muatUlangTerkonfirmasi) memakai closest('.stat-box') untuk menyalakan
   penanda oranye saat masih ada antrean. */
.scan-sisa {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 18px;
    padding: 8px 16px;
    border-radius: 999px;
    background: var(--field);
}
.scan-sisa .num { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.scan-sisa .label { font-size: 12.5px; color: var(--muted); }
/* Untuk admin pil ini menuju halaman Barang Keluar (daftar lengkapnya).
   Staff merendernya sebagai <div>, jadi aturan hover di bawah tidak kena. */
a.scan-sisa { text-decoration: none; color: inherit; transition: background .25s, transform .25s; }
a.scan-sisa:hover { background: var(--hair); transform: translateY(-1px); text-decoration: none; }
a.scan-sisa:active { transform: scale(.98); }
.scan-sisa.warn {
    background: color-mix(in srgb, var(--orange) 14%, transparent);
}
.scan-sisa.warn .num { color: var(--orange); }

/* Ringkasan total di kaki form COD. */
.cod-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    margin-top: 18px;
    padding: 14px 18px;
    border-radius: 14px;
    background: color-mix(in srgb, var(--green) 9%, transparent);
}
.cod-total > span { font-size: 13px; color: var(--muted); }
.cod-total > strong {
    font-size: 21px;
    font-weight: 700;
    letter-spacing: -.02em;
    color: var(--green);
    font-variant-numeric: tabular-nums;
}

.d-search { position: relative; }
.d-search-kotak {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    border-radius: 14px;
    background: var(--card-solid);
    border: 1px solid var(--border);
    transition: border-color .25s, box-shadow .25s;
}
.d-search-kotak:focus-within {
    border-color: var(--primary);
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--primary) 16%, transparent);
}
.d-search-kotak svg { width: 17px; height: 17px; flex: 0 0 17px; color: var(--faint); }
/* Tombol kosongkan, muncul hanya saat kotaknya berisi (komponen SearchBox
   handoff). Dipasang oleh assets/app.js supaya berlaku di semua kotak cari
   sekaligus, bukan ditulis ulang di tiap halaman. */
.d-search-kosongkan {
    flex: 0 0 22px;
    width: 22px;
    height: 22px;
    border: none;
    border-radius: 50%;
    background: var(--field);
    color: var(--muted);
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    /* TRANSITION, bukan animation. `animation` pada elemen yang lahir
       display:none tidak pernah berjalan -- animasinya dibatalkan selagi
       tersembunyi, dan menyalakan display kembali tidak memulainya lagi
       (terukur: 0 animasi aktif, transform langsung `none`). Transisi juga
       memberi gerakan DUA arah: mengecil lagi saat kotaknya dikosongkan,
       bukan lenyap seketika. */
    transform: scale(.5);
    opacity: 0;
    visibility: hidden;
    transition: transform .26s var(--d-ease-pop), opacity .2s ease,
                visibility .26s, background .2s, color .2s;
}
/* Muncul begitu kotaknya berisi. :placeholder-shown benar TEPAT saat input
   tidak berisi apa pun -- termasuk ketika nilainya disetel dari kode, yang
   tidak memicu event 'input'. */
.d-search-kotak input:not(:placeholder-shown) ~ .d-search-kosongkan {
    transform: scale(1);
    opacity: 1;
    visibility: visible;
}
/* Hover harus SELEKTIF SETARA aturan di atas, kalau tidak scale(1)-nya
   menang dan tombolnya tidak bereaksi disentuh kursor. */
.d-search-kotak input:not(:placeholder-shown) ~ .d-search-kosongkan:hover {
    transform: scale(1.14);
    background: var(--hair);
    color: var(--text);
}
.d-search-kotak input:not(:placeholder-shown) ~ .d-search-kosongkan:active {
    transform: scale(.92);
}
.d-search-kotak input { flex: 1; border: none; background: none; color: var(--text); font-size: 14.5px; min-width: 0; }
/* Panel saran HANYA saat ada teks -- kalau selalu tampil, panelnya
   menutupi daftar di bawahnya (catatan SearchBox di handoff). */
.d-search-saran {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    z-index: 20;
    padding: 6px;
    border-radius: 16px;
    background: var(--card-solid);
    border: 1px solid var(--border);
    box-shadow: 0 24px 50px -18px rgba(0,0,0,.4);
    animation: d-fadeUp .28s var(--d-ease-out) backwards;
}
.d-search-saran button {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    border: none;
    background: none;
    border-radius: 11px;
    cursor: pointer;
    text-align: left;
    color: var(--text);
    font-size: 13.5px;
    transition: background .2s;
}
.d-search-saran button:hover { background: var(--field); }
.d-search-saran b { font-weight: 600; }
.d-search-saran i { font-style: normal; font-size: 12px; color: var(--faint); }

/* -------------------------------------------------------------------------
   16. SHEET (modal tengah), TOAST, KABAR
   Handoff menyebutnya "bottom sheet" tapi prototipe & screenshot desktop
   sama-sama menengahkannya (max-width 620px, radius 26px penuh). Yang
   dipakai di sini adalah prototipe.
   ------------------------------------------------------------------------- */
.d-sheet-backdrop {
    position: fixed;
    inset: 0;
    z-index: 70;
    background: rgba(0,0,0,.34);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    animation: d-backdropIn .28s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px;
}
.d-sheet {
    width: 100%;
    max-width: 620px;
    max-height: 84vh;
    overflow-y: auto;
    background: var(--card-solid);
    border: 1px solid var(--border);
    border-radius: 26px;
    box-shadow: 0 40px 90px -24px rgba(0,0,0,.5);
    animation: d-sheetIn .5s cubic-bezier(.2,.95,.25,1.05);
}
.d-sheet-head {
    position: sticky;
    top: 0;
    background: var(--card-solid);
    padding: 22px 26px 14px;
    border-bottom: 1px solid var(--hair);
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    border-radius: 26px 26px 0 0;
    z-index: 1;
}
.d-sheet-judul { font-size: 19px; font-weight: 700; letter-spacing: -.02em; }
.d-sheet-sub   { font-size: 13px; color: var(--muted); margin-top: 3px; }
.d-sheet-tutup {
    flex: 0 0 30px;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: none;
    cursor: pointer;
    background: var(--field);
    color: var(--muted);
    font-size: 15px;
    line-height: 1;
    transition: transform .25s, background .25s;
}
.d-sheet-tutup:hover { transform: rotate(90deg); }
.d-sheet-isi { padding: 20px 26px 26px; }
/* flex-wrap WAJIB. Tanpa itu, begitu deretan tombol tidak muat, `nowrap` +
   `justify-content: flex-end` membuat kelebihannya meluber ke arah AWAL --
   yaitu KELUAR lewat tepi KIRI sheet, bukan ke kanan seperti yang orang duga.
   Terukur di sheet Edit Produk (19 Agu 2026): 5 tombol butuh 536px di ruang
   497px, dan tombol "Hapus" (yang ber-margin-right:auto) berdiri 12px di luar
   badan sheet. Gejalanya bikin bingung karena tombol yang keluar justru yang
   PALING KIRI, sementara yang kepanjangan ada di kanan.
   align-items:center menjaga tinggi tombol tetap sejajar begitu barisnya
   pecah jadi dua. */
.d-sheet-aksi {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 9px;
    justify-content: flex-end;
    margin-top: 22px;
}

.d-toast {
    position: fixed;
    /* Dipusatkan lewat margin auto, BUKAN left:50% + translateX(-50%).
       Dengan cara lama, animasi masuk d-toastIn ikut menulis transform dan
       menimpa translateX(-50%) selama animasi berjalan -- toast muncul
       serong ke kanan (tepi kirinya di tengah layar) lalu baru melompat ke
       tengah begitu animasinya habis. Pemusatan sekarang tidak lagi
       bergantung pada transform, jadi ia lahir tepat di tengah. */
    left: 0;
    right: 0;
    margin-inline: auto;
    width: fit-content;
    max-width: calc(100% - 32px);
    bottom: 34px;
    /* 80 = di atas backdrop sheet (70), di bawah dropdown/popover/kalender
       (90). Ini nilai DESKTOP dan sengaja dibiarkan apa adanya; yang perlu
       lebih tinggi cuma HP, lihat blok @media di bawah. */
    z-index: 80;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 13px 20px;
    border-radius: 16px;
    background: rgba(28,28,30,.92);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    color: #fff;
    font-size: 14px;
    font-weight: 600;
    box-shadow: 0 18px 50px -12px rgba(0,0,0,.5);
    animation: d-toastIn .5s var(--d-ease-pop);
}
.d-toast-ikon {
    width: 19px;
    height: 19px;
    border-radius: 50%;
    background: var(--d-toast-warna, var(--green));
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 19px;
}
.d-toast-ikon svg { width: 11px; height: 11px; }

/* Banner peringatan lebar penuh (koneksi putus, sinkron gagal, restok). */
.d-kabar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 20px;
    border-radius: 18px;
    margin-bottom: 14px;
    background: color-mix(in srgb, var(--d-kabar-warna, var(--orange)) 9%, var(--card-solid));
    border: 1px solid color-mix(in srgb, var(--d-kabar-warna, var(--orange)) 35%, transparent);
    text-decoration: none;
    color: inherit;
    animation: d-fadeUp .5s var(--d-ease-out) backwards;
}
a.d-kabar:hover { text-decoration: none; }
.d-kabar-ikon {
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--d-kabar-warna, var(--orange)) 18%, transparent);
    color: var(--d-kabar-warna, var(--orange));
    font-size: 17px;
    font-weight: 700;
}
.d-kabar-judul { font-size: 14.5px; font-weight: 700; letter-spacing: -.01em; }
.d-kabar-teks  { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.d-kabar-aksi  { margin-left: auto; display: flex; gap: 8px; flex: 0 0 auto; }
.d-kabar-warn  { --d-kabar-warna: var(--orange); }
.d-kabar-buruk { --d-kabar-warna: var(--red); }
/* Hijau untuk kabar BERHASIL. Sebelumnya pesan sukses memakai nada warn,
   jadi "Produk berhasil diperbarui" tampil oranye seperti peringatan. */
.d-kabar-ok    { --d-kabar-warna: var(--green); }

/* Varian ringkas: satu baris keterangan tanpa judul & tanpa tombol. Dipakai
   untuk catatan yang sesekali perlu dibereskan, supaya tidak menyaingi
   banner di atasnya yang isinya pekerjaan hari ini. */
.d-kabar-ringkas { padding: 11px 16px; gap: 11px; border-radius: 14px; }
.d-kabar-ringkas .d-kabar-ikon { flex: 0 0 24px; width: 24px; height: 24px; font-size: 13px; }
.d-kabar-ringkas .d-kabar-teks { margin-top: 0; }

/* Varian untuk banner BERPARAGRAF (mis. peringatan "337 transaksi belum
   punya HPP" di Laporan yang isinya beberapa kalimat + tautan + tombol).
   .d-kabar biasa itu flex row -- setiap potongan teks & tautan di dalamnya
   jadi kolom flex tersendiri, sehingga kalimatnya terpotong-potong jadi
   kolom-kolom sempit. Varian ini kembali ke aliran teks normal. */
.d-kabar-blok {
    display: block;
    font-size: 13.5px;
    line-height: 1.55;
}
.d-kabar-blok .d-btn { margin-top: 10px; }

/* Sparkline mungil di dalam kartu KPI Laporan (Chart.js). Tingginya
   sebelumnya dipatok blok <style> di laporan.php yang ikut dibuang saat
   migrasi -- tanpa aturan ini kanvasnya memuai setinggi kartunya. */
.mini-chart {
    display: block;
    width: 100% !important;
    height: 36px !important;
    margin-top: 10px;
    pointer-events: none;
}

/* -------------------------------------------------------------------------
   17. POPOVER (portal ke <body>)
   JEBAKAN #2: ini WAJIB anak langsung <body>, bukan di dalam kartu.
   Kartu ber-backdrop-filter bikin stacking context; popover di dalamnya
   akan tertimpa kartu berikutnya berapa pun z-index-nya.
   ------------------------------------------------------------------------- */
.d-pop {
    position: fixed;
    z-index: 90;
    padding: 12px 14px;
    border-radius: 16px;
    background: color-mix(in srgb, var(--card-solid) 97%, transparent);
    backdrop-filter: var(--d-blur-pop);
    -webkit-backdrop-filter: var(--d-blur-pop);
    border: 1px solid var(--border);
    box-shadow: var(--d-shadow-pop);
    animation: d-popIn .2s var(--d-ease-pop) backwards;
    pointer-events: none;
}
.d-pop-tgl { font-size: 12.5px; font-weight: 700; margin-bottom: 8px; letter-spacing: -.01em; }
.d-pop-baris {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    line-height: 1.7;
    white-space: nowrap;
}
.d-pop-titik { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; }
.d-pop-nama  { color: var(--muted); }
.d-pop-nilai { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; padding-left: 14px; }

/* -------------------------------------------------------------------------
   18. CHART TREN
   SVG dirender PHP; JS cuma mengurus hover snapping & popover.
   ------------------------------------------------------------------------- */
/* Dua rasio grafik tren, satu tampil per lebar layar -- lihat catatan di
   index.php. Versi sempit mati di sini, dinyalakan di blok @media HP. */
.d-tren-sempit { display: none; }
.d-chart { position: relative; }
.d-chart svg { width: 100%; height: auto; display: block; overflow: visible; }
.d-chart-seri {
    stroke-dasharray: 2600;
    animation: d-dash 1.9s cubic-bezier(.2,.8,.3,1) backwards;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* Titik chart ikut popIn saat grafik pertama tampil (handoff: popIn dipakai
   untuk titik chart, popover, & badge). Delay 1s menunggu garisnya selesai
   digambar oleh d-dash -- kalau bersamaan, titiknya muncul di garis yang
   belum ada. */
.d-chart-titik {
    transition: r .18s cubic-bezier(.34,1.5,.42,1), fill .18s;
    animation: d-popIn .3s var(--d-ease-pop) 1s backwards;
    transform-box: fill-box;
    transform-origin: center;
}
.d-chart-panduan { opacity: 0; transition: opacity .15s; }
.d-chart.d-hover .d-chart-panduan { opacity: .5; }
.d-chart-sumbu {
    display: flex;
    justify-content: space-between;
    font-size: 11.5px;
    color: var(--faint);
    margin-top: 8px;
    padding: 0 14px;
}
.d-chart-legenda { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 14px; }
.d-chart-legenda span { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--muted); }

/* -------------------------------------------------------------------------
   19. KALENDER KUSTOM
   Pengganti <input type="date"> yang dilarang handoff. Latar 97% opak
   supaya angka di belakangnya tidak menembus lewat blur.
   ------------------------------------------------------------------------- */
.d-kal { position: relative; display: inline-block; }
.d-kal-tombol {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: 12px;
    border: 1px solid var(--border);
    background: var(--card-solid);
    color: var(--text);
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color .25s;
}
.d-kal-tombol:hover { border-color: var(--primary); }
.d-kal-tombol svg { width: 15px; height: 15px; color: var(--faint); }
.d-kal-panel {
    position: fixed;
    z-index: 90;
    width: 268px;
    padding: 14px;
    border-radius: 18px;
    background: color-mix(in srgb, var(--card-solid) 97%, transparent);
    backdrop-filter: var(--d-blur-pop);
    -webkit-backdrop-filter: var(--d-blur-pop);
    border: 1px solid var(--border);
    box-shadow: 0 30px 60px -20px rgba(0,0,0,.55);
    animation: d-popIn .2s var(--d-ease-pop) backwards;
}
.d-kal-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.d-kal-nav button {
    width: 28px; height: 28px; border-radius: 8px; border: none;
    background: var(--field); color: var(--text); cursor: pointer; font-size: 14px; line-height: 1;
}
.d-kal-bulan { font-size: 13.5px; font-weight: 700; }
.d-kal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.d-kal-hari { font-size: 10.5px; font-weight: 700; color: var(--faint); text-align: center; padding: 4px 0; text-transform: uppercase; }
.d-kal-tgl {
    aspect-ratio: 1;
    border: none;
    background: none;
    border-radius: 9px;
    font-size: 12.5px;
    color: var(--text);
    cursor: pointer;
    transition: background .2s;
}
.d-kal-tgl:hover { background: var(--field); }
.d-kal-tgl.kosong { visibility: hidden; }
.d-kal-tgl.pilih { background: var(--primary); color: #fff; font-weight: 700; }
.d-kal-backdrop { position: fixed; inset: 0; z-index: 89; background: transparent; }

/* -------------------------------------------------------------------------
   20. ACTION BAR STICKY BAWAH (dipakai HPP Reseller)
   ------------------------------------------------------------------------- */
.d-actionbar {
    position: sticky;
    bottom: 0;
    z-index: 11;
    margin-top: 14px;
    padding: 14px 20px;
    border-radius: 18px;
    display: flex;
    align-items: center;
    gap: 12px;
    background: var(--card);
    backdrop-filter: var(--d-blur-bar);
    -webkit-backdrop-filter: var(--d-blur-bar);
    border: 1px solid var(--border);
    box-shadow: 0 -8px 30px -18px rgba(0,0,0,.4);
}
.d-actionbar-ket { font-size: 13px; font-weight: 600; color: var(--muted); }
.d-actionbar-aksi { margin-left: auto; display: flex; gap: 9px; }

/* -------------------------------------------------------------------------
   21. UTILITAS
   ------------------------------------------------------------------------- */
.d-stack   { display: flex; flex-direction: column; gap: 14px; }
.d-row     { display: flex; align-items: center; gap: 10px; }
.d-row-wrap{ display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.d-spacer  { margin-left: auto; }
.d-muted   { color: var(--muted); }
.d-faint   { color: var(--faint); font-size: 12px; }
.d-sub     { font-size: 12px; color: var(--faint); margin-top: 2px; }
/* Sub-baris di dalam sel: .d-sub sendiri tidak menyetel display (dipakai
   inline juga di beberapa tempat), jadi yang mau turun baris memakai ini. */
.d-sub-blok { display: block; }
/* Pasangan util untuk ISI DI DALAM sel tabel. .d-hp-off tidak bisa dipakai
   di sini: aturannya di-scope ke anak LANGSUNG .d-table-row/.d-table-head
   (kalau tidak, ia akan menyembunyikan sel yang kebetulan bersarang).
   Keduanya mengasumsikan elemen BLOK -- lihat aturan HP di bagian mobile. */
.d-hp-saja { display: none; }
.d-tebal   { font-weight: 700; }
.d-sr {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---- Util lama yang aturan DESKTOP-nya dulu ada di style.css ----
   Nama-nama ini masih dipakai markup di beberapa halaman. Yang versi
   mobilenya sudah ada di mobile.css (.hide-mobile, .m-only, .m-sembunyi)
   tidak perlu ditulis ulang -- di desktop mereka memang harus tampil apa
   adanya. Yang WAJIB ada di sini cuma yang aturan desktopnya menyembunyikan
   sesuatu, karena tanpa itu elemennya bocor ke tampilan desktop. */
.show-mobile-only { display: none; }

/* ---- .grid-stats / .stat-box ----
   Pola kartu angka warisan style.css yang masih dipakai beberapa halaman
   (Scan, Laporan, Reseller). Ditema di sini alih-alih diganti markup satu
   per satu: bentuknya memang sama dengan kartu KPI, dan mengubah puluhan
   blok markup untuk hasil visual yang identik cuma menambah risiko.
   :not(.scan-sisa) mengecualikan pil angka di dalam kotak scan -- itu
   memakai .stat-box juga (JS-nya mencari closest('.stat-box')), tapi
   bentuknya pil, bukan kartu. */
.grid-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 14px;
    margin-bottom: 14px;
}
.stat-box:not(.scan-sisa) {
    background: var(--card);
    backdrop-filter: var(--d-blur-card);
    -webkit-backdrop-filter: var(--d-blur-card);
    border: 1px solid var(--border);
    border-radius: 18px;
    padding: 16px 18px;
    text-decoration: none;
    color: inherit;
    box-shadow: var(--d-shadow-card);
    animation: d-fadeUp .5s var(--d-ease-out) backwards;
    transition: transform .3s var(--d-ease-pop), box-shadow .3s;
}
a.stat-box:not(.scan-sisa):hover { transform: translateY(-3px); }
.stat-box:not(.scan-sisa) .num {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}
.stat-box:not(.scan-sisa) .label {
    font-size: 12.5px;
    color: var(--muted);
    margin-top: 4px;
    line-height: 1.35;
}
.stat-box.warn:not(.scan-sisa) .num  { color: var(--orange); }
.stat-box.danger:not(.scan-sisa) .num { color: var(--red); }
.stat-box.success:not(.scan-sisa) .num { color: var(--green); }
.stat-box .num.positif { color: var(--green); }
.stat-box .num.negatif { color: var(--red); }

/* ---- <details> sebagai kartu yang bisa dilipat ---- */
details.d-card { padding: 0; overflow: hidden; }
details.d-card > summary {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: var(--d-pad-card);
    cursor: pointer;
    list-style: none;
    user-select: none;
}
/* Segitiga bawaan browser diganti tanda sendiri supaya sama di semua OS. */
details.d-card > summary::-webkit-details-marker { display: none; }
details.d-card > summary::marker { content: ""; }
details.d-card > summary::after {
    content: "";
    margin-left: auto;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--faint);
    border-bottom: 2px solid var(--faint);
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform .28s var(--d-ease-out);
}
details.d-card[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
details.d-card > summary h2 { font-size: 17px; font-weight: 700; letter-spacing: -.02em; }
details.d-card > summary:hover { background: color-mix(in srgb, var(--primary) 4%, transparent); }
.details-card-body { padding: 0 var(--d-pad-card) var(--d-pad-card); }

/* Judul seksi dengan keterangan kecil di kanannya. */
.judul-dengan-meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

/* ---- Modal kamera scan (dipakai di HP, dibuka dari tab Sore & Retur) ----
   Aturannya dulu di style.css. Yang paling penting display:none-nya: tanpa
   itu tombol tutup & teks petunjuknya tercecer di dasar halaman desktop,
   padahal modalnya belum dibuka sama sekali. */
.camera-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(0,0,0,.88);
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.camera-modal.show { display: flex; }
.camera-modal-inner { position: relative; width: 100%; max-width: 460px; }
.camera-modal-close {
    position: absolute;
    top: -46px;
    right: 0;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    cursor: pointer;
    background: rgba(255,255,255,.16);
    color: #fff;
    font-size: 18px;
    line-height: 1;
}
.camera-modal-hint {
    margin-top: 14px;
    text-align: center;
    color: rgba(255,255,255,.82);
    font-size: 13px;
}
#cameraReader { border-radius: 18px; overflow: hidden; }

/* ---- Sisa komponen warisan style.css yang masih dipakai markup ----
   Ditemukan lewat penyisiran: seluruh class yang dipakai halaman termigrasi,
   ada di style.css, tapi tidak ada di app.css maupun mobile.css. Tanpa blok
   ini elemennya tampil telanjang (paling terasa di keranjang Retur Manual
   yang dipakai harian). */

/* Baris tabel yang bisa diklik. */
.row-clickable { cursor: pointer; }
.row-clickable:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* Deretan chip/tag di atas daftar. */
.tag-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }

/* Badge status retur (ungu -- bukan merah: retur bukan kegagalan). */
.badge-retur {
    display: inline-block;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    background: color-mix(in srgb, var(--purple) 14%, transparent);
    color: var(--purple);
}

/* Badge tren naik/turun di kartu Laporan. */
.trend-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    line-height: 1;
}
.trend-badge.up   { background: color-mix(in srgb, var(--green) 14%, transparent); color: var(--green); }
.trend-badge.down { background: color-mix(in srgb, var(--red) 14%, transparent);   color: var(--red); }
/* Varian netral: dipakai saat datanya belum cukup untuk menghitung tren
   ("Data belum cukup" / "0% hari ini") -- selama ini tidak punya aturan
   sama sekali sehingga badge-nya tampil tanpa latar. */
.trend-badge.flat { background: var(--field); color: var(--muted); }

/* Kartu pemilih reseller (tab Performa). Dipindah dari <style> sebaris di
   data_reseller.php; warnanya dulu dipatok teal sehingga tidak ikut warna
   aksen yang dipilih pengguna. */
.reseller-card {
    cursor: pointer;
    position: relative;
    transition: box-shadow .22s var(--d-ease-out), border-color .22s var(--d-ease-out), transform .22s var(--d-ease-out);
}
.reseller-card:hover {
    border-color: var(--primary);
    transform: translateY(-2px);
    box-shadow: 0 12px 28px -20px color-mix(in srgb, var(--primary) 70%, transparent);
}
.reseller-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.reseller-card.active {
    border-color: var(--primary);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 30%, transparent);
}
.reseller-rank { position: absolute; top: 12px; right: 14px; font-size: 22px; line-height: 1; }

/* Judul kolom yang bisa diurutkan (nama lama, dipakai JS pasangSortTabel). */
.th-sort { cursor: pointer; user-select: none; }
.th-sort:hover { color: var(--text); }
.th-sort.sort-asc::after  { content: " \2191"; color: var(--primary); }
.th-sort.sort-desc::after { content: " \2193"; color: var(--primary); }

/* Keranjang Retur Manual di halaman Scan. */
.retur-item-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 13px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--card-solid);
    margin-bottom: 8px;
    transition: border-color .2s, background .2s;
}
.retur-item-row:hover { border-color: var(--primary); }
.retur-item-row input[type="checkbox"] { width: 18px; height: 18px; flex: 0 0 18px; }
.retur-item-row:has(input:checked) {
    border-color: var(--green);
    background: color-mix(in srgb, var(--green) 8%, var(--card-solid));
}
.retur-item-nama { flex: 1; font-size: 14px; min-width: 0; }
.retur-item-qty  { font-size: 13px; color: var(--muted); font-weight: 600; }
.retur-item-row-wrap { margin-bottom: 8px; }
.retur-item-row-wrap .retur-item-row { margin-bottom: 0; }
.retur-item-popup {
    margin: -1px 0 0;
    padding: 12px 14px;
    border: 1px solid var(--green);
    border-top: none;
    border-radius: 0 0 12px 12px;
    background: color-mix(in srgb, var(--green) 6%, var(--card-solid));
}
.retur-item-popup-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 13.5px; }
.qty-field {
    width: 76px;
    padding: 8px 10px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--card-solid);
    color: var(--text);
    font-size: 13.5px;
    font-weight: 600;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.qty-field:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 15%, transparent);
}

/* Alert lama yang belum sempat jadi .d-kabar. */
.alert {
    padding: 14px 18px;
    border-radius: 16px;
    margin-bottom: 14px;
    font-size: 13.5px;
    line-height: 1.55;
    border: 1px solid color-mix(in srgb, var(--d-alert-warna, var(--orange)) 35%, transparent);
    background: color-mix(in srgb, var(--d-alert-warna, var(--orange)) 9%, var(--card-solid));
}
.alert-success { --d-alert-warna: var(--green); }
.alert-danger  { --d-alert-warna: var(--red); }
.alert-warn    { --d-alert-warna: var(--orange); }

/* ---- Kontrol halaman Pengaturan & Profil ----
   Nama class-nya dipertahankan (JS pengaturan.php menyasar .segmented
   button, .swatch, dan input di dalam .switch). Ditulis ulang di sini
   dengan bahasa visual UI baru, bukan disalin mentah dari style.css. */

/* Segmented control kecil (Terang / Gelap / Ikuti Sistem). */
.segmented {
    display: inline-flex;
    gap: 3px;
    padding: 3px;
    background: var(--field);
    border-radius: 13px;
}
.segmented button {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 10px 16px;
    border: none;
    border-radius: 10px;
    background: none;
    color: var(--muted);
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background .25s, color .25s;
}
.segmented button svg { width: 19px; height: 19px; }
.segmented button:hover { color: var(--text); }
.segmented button.active {
    background: var(--card-solid);
    color: var(--primary);
    box-shadow: 0 1px 3px rgba(0,0,0,.14);
}

/* Baris preferensi dengan switch di kanan. */
.switch-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 0;
}
.switch-row + .switch-row { border-top: 1px solid var(--hair); }
.switch-row-text  { display: flex; flex-direction: column; gap: 3px; }
.switch-row-title { font-weight: 600; font-size: 14px; }
.switch-row-desc  { font-size: 12.5px; color: var(--muted); line-height: 1.45; }

.switch { position: relative; display: inline-block; width: 50px; height: 30px; flex: 0 0 50px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch-track {
    position: absolute;
    inset: 0;
    background: var(--field);
    border-radius: 999px;
    cursor: pointer;
    transition: background .3s;
}
.switch-track::before {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.3);
    transition: transform .32s var(--d-ease-pop);
}
.switch input:checked + .switch-track { background: var(--green); }
.switch input:checked + .switch-track::before { transform: translateX(20px); }
.switch input:focus-visible + .switch-track {
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--primary) 16%, transparent);
}

/* Swatch warna aksen. */
.swatch-row { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 14px; }
.swatch {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    padding: 2px;
    border: none;
    background: none;
    cursor: pointer;
    font-family: inherit;
}
.swatch-dot {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex: 0 0 32px;
    background: var(--sw);
    transition: box-shadow .3s, transform .3s var(--d-ease-pop);
}
.swatch-label { font-size: 11.5px; font-weight: 600; color: var(--muted); transition: color .3s; }
.swatch:hover .swatch-dot { transform: scale(1.06); }
/* Cincin memakai warna swatch-nya sendiri, bukan --primary: saat pengguna
   membandingkan enam pilihan, penandanya harus warna yang dipilih itu. */
.swatch.active .swatch-dot {
    transform: scale(1.1);
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--sw) 30%, transparent);
}
.swatch.active .swatch-label { color: var(--text); }

/* Blok setelan yang kontrolnya turun ke baris berikutnya (Warna aksen). */
.d-atur-blok { padding: 16px 0 4px; border-top: 1px solid var(--hair); }

/* Blok "Foto latar belakang": pratinjau kecil + keterangan, di kotak --field
   seperti handoff. */
.d-atur-latar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px;
    border-radius: 16px;
    background: var(--field);
}
.d-atur-latar-pratinjau {
    flex: 0 0 72px;
    width: 72px;
    height: 48px;
    border-radius: 11px;
    border: 1px solid var(--hair);
}
.d-atur-latar-teks { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }

/* Deretan tombol di kaki kartu setelan (tes suara, aksi akun). */
.d-atur-tombol { display: flex; gap: 9px; flex-wrap: wrap; padding-top: 16px; }
.d-atur-tombol + .d-atur-tombol { padding-top: 9px; }

/* Dua kontrol berdampingan di kanan satu baris setelan (toggle + tombol). */
.d-atur-kontrol { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }

/* Isian angka dengan satuan tertulis di sebelahnya ("200 juta"). Satuannya
   teks biasa, bukan bagian input -- jadi nilai yang dikirim tetap angka
   bersih dan tidak perlu dibersihkan lagi di server. */
.d-satuan { display: inline-flex; align-items: center; gap: 8px; }
.d-satuan-label { font-size: 12.5px; color: var(--muted); }

/* Sepasang tombol di kolom Aksi tabel, rata kanan & tidak boleh terpotong. */
.d-aksi-baris { display: inline-flex; align-items: center; gap: 8px; justify-content: flex-end; }
/* Komisi Diamondo: hijau tebal -- itu angka yang dicari saat menyetel HPP. */
.d-komisi { color: var(--green); }

/* Kotak isian HPP Reseller dibatasi lebarnya. .d-input bawaannya 100%, jadi
   kotaknya memenuhi seluruh kolom -- padahal isinya cuma bilangan enam digit,
   dan kotak selebar itu membuat angkanya melayang jauh dari judul kolomnya.
   Ditempel ke kanan supaya sejajar dengan kolom angka lain di tabel. */
.input-hpp-reseller {
    width: 100%;
    max-width: 176px;
    margin-left: auto;
    text-align: right;
}

/* Progres target (kartu reseller terpilih). */
.d-progres-blok { margin-top: 18px; }
.d-progres-label { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--muted); margin-bottom: 7px; }
.d-progres-track { height: 8px; border-radius: 6px; background: var(--field); overflow: hidden; }
.d-progres-isi {
    height: 100%;
    border-radius: 6px;
    background: linear-gradient(90deg, var(--primary), var(--purple));
    transform-origin: left;
    animation: d-growX .8s var(--d-ease-out) backwards;
    transition: width .6s;
}
/* Slider volume -- satu-satunya <input type="range"> di aplikasi. Handoff
   tidak punya komponen ini (volume scan tambahan kita sendiri), jadi gayanya
   diselaraskan dengan .d-progres-track di atas: tinggi 8px, radius 6px, alas
   --field, isian warna aksen. Sebelumnya dibiarkan native, jadi satu-satunya
   kontrol biru di seluruh UI burgundy.

   Isian di kiri thumb digambar sebagai gradien dua-henti pada track, BUKAN
   ::-moz-range-progress: WebKit tidak punya padanan pseudo-element itu, dan
   satu gradien memberi hasil identik di kedua mesin. Persennya dititipkan JS
   lewat --d-range-isi (lihat pengaturan.php). */
.d-range {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 18px; /* ruang untuk thumb -- tinggi TRACK diatur di pseudo-elementnya */
    background: none;
    cursor: pointer;
}
.d-range:focus { outline: none; }
.d-range::-webkit-slider-runnable-track {
    height: 8px;
    border-radius: 6px;
    background: linear-gradient(90deg,
        var(--primary) var(--d-range-isi, 70%), var(--field) var(--d-range-isi, 70%));
}
.d-range::-moz-range-track {
    height: 8px;
    border-radius: 6px;
    background: linear-gradient(90deg,
        var(--primary) var(--d-range-isi, 70%), var(--field) var(--d-range-isi, 70%));
}
.d-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    margin-top: -5px; /* (8 - 18) / 2, memusatkan thumb di track */
    border-radius: 50%;
    background: var(--card-solid);
    border: 2px solid var(--primary);
    box-shadow: 0 1px 4px rgba(0, 0, 0, .18);
    transition: transform .2s var(--d-ease-pop), box-shadow .2s;
}
.d-range::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--card-solid);
    border: 2px solid var(--primary);
    box-shadow: 0 1px 4px rgba(0, 0, 0, .18);
    transition: transform .2s var(--d-ease-pop), box-shadow .2s;
}
.d-range:hover::-webkit-slider-thumb  { transform: scale(1.12); }
.d-range:hover::-moz-range-thumb      { transform: scale(1.12); }
.d-range:active::-webkit-slider-thumb { transform: scale(1.2); }
.d-range:active::-moz-range-thumb     { transform: scale(1.2); }
/* Cincin fokus digantung di thumb, bukan outline elemen -- outline pada
   <input type=range> menggambar kotak mengelilingi SELURUH lebar slider. */
.d-range:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 1px 4px rgba(0, 0, 0, .18), 0 0 0 4px color-mix(in srgb, var(--primary) 22%, transparent);
}
.d-range:focus-visible::-moz-range-thumb {
    box-shadow: 0 1px 4px rgba(0, 0, 0, .18), 0 0 0 4px color-mix(in srgb, var(--primary) 22%, transparent);
}
/* Nonaktif saat toggle "Suara saat scan" dimatikan: isian & thumb kehilangan
   warna aksen supaya tidak terlihat masih bisa disetel. */
.d-range:disabled { cursor: not-allowed; }
.d-range:disabled::-webkit-slider-runnable-track {
    background: linear-gradient(90deg,
        var(--faint) var(--d-range-isi, 70%), var(--field) var(--d-range-isi, 70%));
}
.d-range:disabled::-moz-range-track {
    background: linear-gradient(90deg,
        var(--faint) var(--d-range-isi, 70%), var(--field) var(--d-range-isi, 70%));
}
.d-range:disabled::-webkit-slider-thumb { border-color: var(--faint); transform: none; }
.d-range:disabled::-moz-range-thumb     { border-color: var(--faint); transform: none; }

/* Kolom yang isinya jangan pernah dipotong ellipsis (tombol aksi). */
.d-tabel td.d-keep { white-space: nowrap; }

/* Segmented versi teks -- tanpa ikon, dipakai baris setelan yang kontrolnya
   duduk di kanan label. Tanpa ini tiap tombol jadi kolom (ikon di atas teks)
   sehingga barisnya jauh lebih tinggi daripada baris lain. */
.segmented-teks button { flex-direction: row; padding: 9px 15px; font-size: 13px; }

/* Sorotan sesaat untuk menunjukkan tujuan sebuah tombol lompat. */
.d-sorot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 35%, transparent); transition: box-shadow .3s; }

/* Baris tabel yang BARU saja ditambahkan lewat AJAX -- disorot sebentar
   supaya mata tahu ke mana harus melihat setelah menekan Simpan.
   Sorotannya di <td>, bukan box-shadow di <tr> seperti .d-sorot: di ponsel
   tiap baris jadi kartu dengan box-shadow sendiri (.d-tabel tr:not(.rt-head)
   di blok mobile) yang menang spesifisitas dan menelan sorotannya. */
.d-tabel tr.d-baris-baru td {
    background: color-mix(in srgb, var(--primary) 12%, var(--card-solid));
    transition: background .45s;
}
.d-atur-blok > .switch-row-title { display: block; font-weight: 600; font-size: 14px; }
.d-atur-blok > .switch-row-desc { display: block; font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.45; }

/* Ringkasan akun & daftar pasangan kunci-nilai (Tentang Aplikasi). */
.settings-account { display: flex; align-items: center; gap: 14px; }
.settings-account-name { font-size: 15px; font-weight: 700; }
.settings-account-meta { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.settings-kv { display: flex; flex-direction: column; gap: 2px; }
.settings-kv-item {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    padding: 10px 0;
    font-size: 13.5px;
}
.settings-kv-item + .settings-kv-item { border-top: 1px solid var(--hair); }
.settings-kv-item .k { color: var(--muted); }

/* -------------------------------------------------------------------------
   HALAMAN PROFIL -- identitas terpusat
   -------------------------------------------------------------------------
   Profil sengaja dibuat RINGKAS (15 Agu 2026, permintaan pemilik): kartu
   foto/informasi akun/detail akun pindah ke Pengaturan, yang memang sudah
   punya sheet "Edit Profil & Foto". Yang tersisa di sini cuma identitas +
   tiga baris pintasan, jadi halamannya terbaca sekali lihat di HP.

   Semua di luar @media, dipakai di lebar berapa pun -- di desktop blok ini
   dibatasi lebarnya lewat .d-profil-sempit supaya tidak melar jadi pita
   selebar layar. */
.d-profil-sempit { max-width: 460px; margin-left: auto; margin-right: auto; }

.d-profil-identitas { text-align: center; padding: 4px 0 20px; }
.d-profil-avatar {
    width: 96px;
    height: 96px;
    margin: 0 auto 14px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    font-size: 38px;
    font-weight: 700;
    color: #fff;
    /* Gradasi diturunkan dari --primary, bukan dipatok #a82440 seperti
       .user-avatar, supaya ikut waktu user mengganti warna aksen. */
    background: linear-gradient(150deg,
        color-mix(in srgb, var(--primary) 82%, #fff),
        color-mix(in srgb, var(--primary) 78%, #000));
    box-shadow: 0 10px 26px -10px color-mix(in srgb, var(--primary) 60%, transparent);
}
.d-profil-avatar img { width: 100%; height: 100%; object-fit: cover; }
.d-profil-nama { font-size: 20px; font-weight: 700; letter-spacing: -.02em; }
.d-profil-identitas .d-badge { margin-top: 9px; }
.d-profil-meta { font-size: 13px; color: var(--muted); margin-top: 9px; }

/* Nada badge merek -- .d-badge-info sudah dipakai teal, dan role "Admin"
   di rujukan berwarna burgundy (identitas Diamondo). */
.d-badge-merek { --d-badge-warna: var(--primary); }

/* Baris kartu yang bisa diketuk, dengan chevron di kanan. Memakai .switch-row
   sekaligus supaya garis rambut antar baris (aturan .switch-row + .switch-row)
   tetap berlaku walau barisnya <button>, bukan <div>. */
.d-baris-tuju {
    width: 100%;
    background: none;
    border: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    border-radius: 10px;
}
.d-baris-tuju:hover { background: var(--surface-tint-2); }
.d-baris-tuju:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}
.d-baris-tuju .d-chevron { width: 18px; height: 18px; color: var(--faint); flex: 0 0 18px; }

/* Avatar (dipakai Pengaturan & Profil). */
.user-avatar {
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(150deg, #a82440, #6d1226);
    color: #fff;
    font-size: 17px;
    font-weight: 700;
    overflow: hidden;
}
.user-avatar-img { object-fit: cover; }

/* Foto profil besar di halaman Profil. */
.profile-photo,
.profile-photo-placeholder {
    width: 132px;
    height: 132px;
    border-radius: 50%;
    margin: 0 auto 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    object-fit: cover;
    overflow: hidden;
}
.profile-photo-placeholder {
    background: linear-gradient(150deg, #a82440, #6d1226);
    color: #fff;
    font-size: 46px;
    font-weight: 700;
}

/* ---- Tur onboarding (assets/tour.js) ----
   Gayanya dulu di style.css. Sorotannya memakai trik box-shadow raksasa:
   satu kotak transparan seukuran elemen sasaran, dengan bayangan sebesar
   layar -- jadi seluruh halaman gelap KECUALI elemen itu, tanpa perlu
   memotong lubang di elemen lain. */
.tour-spotlight {
    position: fixed;
    z-index: 300;
    border-radius: 16px;
    pointer-events: none;
    box-shadow: 0 0 0 9999px rgba(0,0,0,.62);
    transition: all .35s var(--d-ease-out);
}
.tour-popover {
    position: fixed;
    z-index: 301;
    width: 320px;
    max-width: calc(100vw - 32px);
    padding: 20px 22px;
    border-radius: 20px;
    background: var(--card-solid);
    border: 1px solid var(--border);
    box-shadow: 0 30px 70px -20px rgba(0,0,0,.55);
    animation: d-popIn .28s var(--d-ease-pop) backwards;
}
.tour-popover-judul { font-size: 16.5px; font-weight: 700; letter-spacing: -.02em; }
.tour-popover-isi   { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin-top: 8px; }
.tour-popover-aksi  { display: flex; align-items: center; gap: 10px; margin-top: 18px; }
.tour-popover-progress { font-size: 12px; color: var(--faint); margin-right: auto; }
.tour-btn-lanjut {
    padding: 9px 18px;
    border-radius: 12px;
    border: none;
    background: var(--primary);
    color: #fff;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
}
.tour-btn-lewati {
    padding: 9px 14px;
    border-radius: 12px;
    border: none;
    background: none;
    color: var(--muted);
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
}
.tour-btn-lewati:hover { color: var(--text); }

/* Legenda ringkasan per kategori / per ekspedisi di halaman Scan. */
.scan-legend {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px 18px;
    align-items: center;
}
.scan-legend > * { display: flex; align-items: baseline; gap: 8px; font-size: 13.5px; }


/* -------------------------------------------------------------------------
   MENU "LAINNYA" -- kartu fitur berkelompok
   -------------------------------------------------------------------------
   Dipakai lainnya.php: satu kartu per halaman, dikelompokkan (Operasional,
   Keuangan, Akun), mengikuti template yang diberikan pemilik. Ikon & warna
   tint-nya datang dari PHP lewat inline style, jadi menambah menu baru cukup
   menambah satu baris di $grupMenu tanpa menyentuh CSS.

   Sengaja DI LUAR @media: halaman ini sah dibuka di lebar berapa pun. Di HP
   ia jadi satu-satunya jalan ke menu yang tidak muat di bar bawah; di
   desktop ia jadi peta fitur yang menyebutkan keterangan tiap halaman --
   sesuatu yang tidak diberikan rail kiri.
   ------------------------------------------------------------------------- */
.d-menu-grup { margin-bottom: 24px; }

.d-menu-judul {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--faint);
    margin: 0 0 10px 2px;
}

.d-menu-grid {
    display: grid;
    /* Di HP tepat 2 kolom seperti template; di layar lebar ikut mengisi
       ruang yang ada tanpa kartu jadi selebar layar. */
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 12px;
}

.d-menu-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 14px;
    border-radius: 16px;
    background: var(--card-solid);
    border: 1px solid var(--hair);
    box-shadow: 0 1px 3px rgba(0,0,0,.05);
    text-decoration: none;
    color: inherit;
    transition: transform .18s var(--d-ease-out, ease), box-shadow .18s, border-color .18s;
}
.d-menu-item:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px -10px rgba(0,0,0,.28);
    border-color: color-mix(in srgb, var(--primary) 35%, transparent);
}
.d-menu-item:active { transform: translateY(0); }

.d-menu-ikon {
    width: 38px;
    height: 38px;
    border-radius: 11px;
    display: grid;
    place-items: center;
    font-size: 18px;
    line-height: 1;
    margin-bottom: 11px;
}

.d-menu-label {
    font-size: 14px;
    font-weight: 700;
    color: var(--text);
    line-height: 1.25;
}
.d-menu-sub {
    font-size: 11.5px;
    color: var(--muted);
    line-height: 1.35;
    margin-top: 3px;
}


/* -------------------------------------------------------------------------
   22. PENYESUAIAN MOBILE (<=768px)
   Ditulis DI SINI, bukan di mobile.css, supaya revolusi desktop ini tidak
   menyentuh berkas mobile sama sekali.

   Wrapper-nya memakai class GANDA di header.php: .app-shell.d-shell dan
   .container.d-page. Nama lama dipertahankan karena mobile.css masih
   memakai .app-shell untuk menyisakan ruang setinggi bar navigasi bawah.

   PERUBAHAN 14 Agu 2026 (keputusan pemilik "satu UI saja"): mobile.css
   TIDAK lagi berisi UI mobile tersendiri -- ia menyusut dari 2.426 baris
   jadi ~140, isinya cuma menetralkan markup .m-* dan menggambar navigasi
   bawah. Konsekuensinya untuk blok ini: topbar mobile SUDAH TIDAK ADA,
   jadi judul halaman & padding badan halaman yang dulu ditanggung
   mobile.css sekarang jadi tanggung jawab di sini.
   ------------------------------------------------------------------------- */
@media (max-width: 768px) {

/* Rail kiri tidak dipakai di HP -- sulit dijangkau jempol. Navigasinya
   pindah ke bar bawah (.m-tabbar, digambar mobile.css). */
.d-rail, .d-ambient { display: none; }

/* Padding badan halaman. Versi desktop (.d-page, 30px 40px 80px) hidup di
   dalam @media (min-width:769px), jadi di HP tidak ada padding sama sekali
   -- dulu itu disediakan mobile.css lewat .container. Tanpa ini kartu
   menempel ke tepi layar dan judul seksi terpotong.
   Jarak ke bar bawah datang dari .app-shell (lihat mobile.css). */
.d-page { padding: 16px 16px 24px; }

/* Judul halaman TAMPIL lagi. Dulu disembunyikan karena topbar mobile
   sudah menampilkannya; topbar itu ikut pensiun bersama UI mobile
   terpisah, jadi tanpa aturan ini halaman tidak punya judul sama sekali.
   Ukurannya diturunkan dari 32px -- di layar 390px judul sebesar itu
   memakan seperempat layar sebelum isi pertama terlihat. */
.d-head { display: block; margin-bottom: 14px; }
.d-head h1 { font-size: 23px; letter-spacing: -.02em; }
.d-head p  { font-size: 12.5px; margin-top: 3px; }
.d-head-aksi { flex-wrap: wrap; width: 100%; margin-top: 12px; }
/* Tombol kepala melar mengisi barisnya. Dengan lebar sesuai isi, barisnya
   berakhir ragged: di Stok (bar 348px) baris pertama cuma terpakai 312px
   dan baris kedua 153px, jadi "+ Tambah Produk" menggantung di kiri dengan
   195px ruang kosong di sebelahnya. Karena wrap-nya tetap ditentukan lebar
   asli isinya (flex-basis auto), pengelompokan baris tidak berubah -- yang
   berubah cuma sisa ruang dibagi rata, dan tombol yang sendirian di
   barisnya jadi selebar layar, yang kebetulan persis perlakuan yang pantas
   untuk aksi utama. */
.d-head-aksi > * { flex: 1 1 auto; }

/* Banner .d-kabar: tombolnya turun ke baris sendiri. Di 390px,
   .d-kabar-aksi (margin-left:auto) menyisakan cuma 147px untuk teks --
   judul "12 produk perlu restok" pecah jadi dua baris dan daftar SKU-nya
   patah-patah, kartunya menjulang 159px. flex-basis 100% memaksa aksinya
   wrap, sehingga teks dapat lebar penuh. .d-kabar-blok tidak kena: ia
   display:block, jadi flex-wrap di sini tidak berlaku baginya. */
.d-kabar { flex-wrap: wrap; }
.d-kabar > :not(.d-kabar-ikon):not(.d-kabar-aksi) { flex: 1 1 0; min-width: 0; }
.d-kabar-aksi { flex: 1 1 100%; margin-left: 0; }
.d-kabar-aksi > * { flex: 1 1 auto; }

/* Deret tombol .d-row-wrap ikut mengisi barisnya, dengan alasan sama
   seperti .d-head-aksi: di Stok, "Nonaktifkan Terpilih" (181px) dan
   "Aktifkan Terpilih" (159px) tidak muat berdampingan di 315px, jadi
   keduanya turun sendiri-sendiri dan menggantung ragged di kiri.
   Sengaja > .d-btn, bukan > * : wadah yang sama dipakai pencarian.php
   untuk deretan pill contoh pencarian, dan pill memang harus selebar
   isinya, bukan melar. */
.d-row-wrap > .d-btn { flex: 1 1 auto; }

/* Satu kolom. Kartu KPI 26px berdampingan di layar 390px membuat label
   panjang seperti "Total Omzet Setelah Potongan Marketplace" pecah jadi
   empat baris. */
.d-grid-kpi, .d-grid-rasio, .d-grid-2, .d-grid-15, .d-grid-11 { grid-template-columns: 1fr; }

/* ---- Segmented control jadi strip yang bisa digulir ----
   Di desktop tiap tab selebar 1fr dan thumb-nya bergeser translateX(i*100%).
   Di layar 400px, empat label seperti "Daftar & Target" + "HPP Reseller" +
   "Performa" + "Rekonsiliasi" butuh ~444px -- kolom 1fr yang sama lebar
   memaksa teksnya (nowrap) menonjol keluar dan halaman ikut bergeser.
   Di sini grid ditukar jadi flex: tiap tab selebar isinya sendiri, dan
   yang berlebih digulir di dalam strip-nya. Thumb disembunyikan -- posisi
   persentasenya tidak lagi berarti begitu lebar tiap kolom berbeda-beda;
   tab aktif ditandai latar padat langsung pada tombolnya. */
.d-seg {
    display: flex;
    width: 100%;
    max-width: none;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.d-seg::-webkit-scrollbar { display: none; }
.d-seg-thumb { display: none; }
.d-seg-item { flex: 0 0 auto; padding: 9px 13px; }
/* Strip berisi 5 tab atau lebih dirapatkan. Di 390px, kelima tab Stok
   ("Produk", "Restok 12", "Paket", "Batch", "SKU 5") butuh 340px di kotak
   selebar 315px -- ia memang bisa digulir, tapi tab terakhir tertinggal
   terpotong di tepi dan itu terbaca sebagai rusak, bukan sebagai "geser
   saya". Padding 13px -> 8px membebaskan 50px, cukup untuk memuat semuanya
   sekaligus, dan strip yang tabnya sedikit (Biaya 3 tab, Transaksi 4)
   tetap lapang karena aturan ini tidak mengenainya.
   Pola :nth-last-child(n+5) = "hitung dari BELAKANG": cocok untuk item yang
   masih punya >=4 saudara sesudahnya. Sengaja dihitung dari belakang, bukan
   :first-child:nth-last-child(...) seperti resep quantity-query yang biasa
   dikutip -- anak pertama .d-seg adalah <span class="d-seg-thumb">, bukan
   tab, jadi versi :first-child tidak akan pernah cocok. Dari belakang,
   tab-tab itu memang elemen terakhir, jadi ada-tidaknya thumb tidak
   berpengaruh. */
.d-seg-item:nth-last-child(n+5),
.d-seg-item:nth-last-child(n+5) ~ .d-seg-item {
    padding-left: 8px;
    padding-right: 8px;
}
.d-seg-item.aktif {
    background: var(--card-solid);
    border-radius: 10px;
    box-shadow: 0 1px 3px rgba(0,0,0,.14);
}

/* ---- Label pendek khusus HP ----
   Elemen ber-atribut data-m-teks menampilkan isi atribut itu sebagai
   gantinya: teks aslinya di-font-size:0, ::before yang menggambar. Dipakai
   pada tab Stok & Reseller supaya kelima label ("Restok (12)", "SKU Baru
   (3)", "Daftar & Target") muat utuh dan strip tabnya tidak perlu digulir
   menyamping sama sekali. Di desktop atributnya tidak berefek apa pun.
   !important karena .d-seg-item sudah memasang font-size sendiri dengan
   spesifisitas yang sama. */
[data-m-teks] { font-size: 0 !important; }
[data-m-teks]::before {
    content: attr(data-m-teks);
    display: block;
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: normal;
    text-align: center;
}

/* Tabel <table> tetap lebar di HP (sel-selnya nowrap supaya angka tidak
   terbelah), jadi SETIAP kartu harus bisa menggulirkan isinya sendiri.
   Tanpa ini badan halaman yang ikut bergeser mendatar -- persis yang
   dilarang handoff. Tabel .d-table (grid) tidak terpengaruh: ia sudah
   menumpuk jadi kartu di lebar ini. */
.d-card { padding: 16px; border-radius: 18px; overflow-x: auto; }


/* ---- <table class="d-tabel">: TETAP TABEL, TIDAK DITUMPUK ----
   Keputusan pemilik 15 Agu 2026, membalik aturan sebelumnya. Sebelum ini
   tabel dibongkar jadi blok (kepala disembunyikan, tiap sel berdiri sendiri
   berlabel) demi syarat lama "tabel tidak boleh lebih lebar dari layar".
   Syarat itu TETAP berlaku -- yang berubah cara memenuhinya: kolom yang
   tidak krusial disembunyikan dengan .hide-mobile, bukan seluruh tabel
   dibongkar jadi kartu vertikal.

   .hide-mobile bukan kelas baru: konvensinya sudah dipakai checkout.php,
   produk.php, dashboard_staff.php, daftar_tenant.php, integrasi.php, dan
   paket_view.php. Yang baru cuma <th>-nya ikut disembunyikan -- dulu tidak
   perlu karena seluruh <thead> memang mati. */
.d-tabel {
    /* fixed: lebar kolom ditentukan kepala tabel, bukan isi selnya. Tanpa
       ini satu SKU panjang melebarkan kolomnya dan mendorong tabel keluar
       layar -- persis yang harus dihindari. */
    table-layout: fixed;
    width: 100%;
}
.d-tabel thead { display: table-header-group; }
.d-tabel tr.rt-head { display: table-row; }
.d-tabel th,
.d-tabel td {
    padding: 9px 7px;
    font-size: 12px;
    /* DIBUNGKUS, bukan dipotong ellipsis (permintaan pemilik 15 Agu 2026:
       judul kolom "tulis seutuhnya, boleh dipotong jadi 2 [baris]", dan isi
       sel "jangan sampai terpotong juga").
       Yang menjaga tabel tetap muat kini table-layout:fixed + pembungkusan:
       lebar kolom sudah dipatok, jadi isi yang panjang turun ke baris
       berikutnya alih-alih mendorong tabel keluar layar.
       overflow-wrap:anywhere WAJIB: tanpa itu satu kata tanpa spasi (SKU,
       nomor resi, email) tidak bisa dipatahkan dan tetap meluber. */
    white-space: normal;
    overflow-wrap: anywhere;
    vertical-align: middle;
}
/* SELURUH penyetelan kepala tabel di HP ditulis dengan selektor PANJANG
   (.d-tabel .rt-head th / .d-tabel thead th, spesifisitas 0,2,1) supaya
   menyamai versi desktopnya. `.d-tabel th` saja cuma 0,1,1 -- kalah, dan
   itu membuat TIGA setelan sekaligus tidak pernah berlaku di HP:
   white-space, padding, dan font-size.
   Akibatnya terukur di tabel HPP Reseller: judul tetap `nowrap` sehingga
   "Komisi Diamondo" minta 133px di kolom 96px -> tabel meluber 36px; dan
   setelah itu dibungkus pun padding desktop 14px menyisakan ruang isi cuma
   68px sementara kata "DIAMONDO" butuh 71px -> pecah jadi "DIAMOND / O".
   Dengan padding 7px + font 10.5px, ruang isinya 82px dan katanya utuh.
   Sel <td> tidak kena masalah ini: aturan desktopnya juga 0,1,1, jadi blok
   HP yang ada di bawahnya menang lewat urutan. */
.d-tabel .rt-head th,
.d-tabel thead th {
    padding: 0 7px 8px;
    font-size: 10.5px;
    line-height: 1.25;
    white-space: normal;
    /* break-word, BUKAN anywhere: `anywhere` memotong di tengah kata walau
       katanya sebenarnya muat sendirian. `break-word` hanya memotong kata
       yang memang tidak muat, jadi judul pecah di SPASI lebih dulu. */
    overflow-wrap: break-word;
    /* Judul dua baris tetap rata bawah dengan judul satu baris. */
    vertical-align: bottom;
}
/* Perataan desktop DIPERTAHANKAN -- dulu dipaksa rata kiri karena label &
   nilai berdiri berlawanan waktu ditumpuk. Sekarang tidak ditumpuk lagi. */

/* Dengan table-layout:fixed, kolom TANPA lebar membagi sisa ruang RATA.
   Akibatnya kolom angka pendek ("742", badge "Aman") mendapat jatah sama
   besar dengan kolom SKU, dan SKU-lah yang pecah jadi banyak baris -- padahal
   ia satu-satunya kolom yang membuat barisnya bisa dikenali. Kolom rata-kanan
   (angka & status) dipatok secukupnya supaya sisanya jatuh ke kolom teks.
   96px, bukan 84: "Rp 345.541.950" pada 12px butuh ~92px, dan sejak isi sel
   dibungkus (bukan dipotong) angka sesempit itu akan pecah jadi dua baris.
   Halaman yang butuh lain tinggal menimpanya dengan style="width:..." di
   <th>-nya, pola yang memang sudah dipakai (mis. kolom kotak centang 32px
   di produk.php). */
.d-tabel th.d-kanan { width: 96px; }
/* td juga: sebagian tabel tidak punya <th> sama sekali (mis. Laba Rugi di
   laporan.php). Di table-layout:fixed yang menentukan lebar adalah BARIS
   PERTAMA, jadi aturan ini hanya berlaku untuk tabel tanpa kepala -- yang
   punya <th> tetap memakai angka di atas. */
.d-tabel td.d-kanan { width: 96px; }
.d-tabel th.hide-mobile,
.d-tabel td.hide-mobile { display: none; }
/* ---- SEL BERISI KENDALI, BUKAN TEKS ----
   Pembungkusan di atas ditujukan untuk TEKS. Kalau kena kendali, hasilnya
   justru rusak: label satuan "juta" pecah jadi j-u-t-a menurun, dan tombol
   "Nonaktifkan" jadi kolom huruf. Kendali dikecualikan di sini. */
.d-tabel .d-satuan-label,
.d-tabel .d-btn,
.d-table-row .d-satuan-label,
.d-table-row .d-btn { white-space: nowrap; overflow-wrap: normal; }

/* Kotak isian di dalam tabel menyusut ikut kolomnya. Lebar inline (mis.
   style="width:110px" di kolom Target) ditimpa max-width, yang memang
   berkuasa atas width. */
.d-tabel .d-input,
.d-table-row .d-input { max-width: 100%; min-width: 0; }
.d-tabel .d-satuan,
.d-table-row .d-satuan { display: flex; gap: 5px; min-width: 0; }
.d-tabel .d-satuan > .d-input,
.d-table-row .d-satuan > .d-input { flex: 1 1 auto; }

/* Deretan tombol aksi TURUN ke baris berikutnya alih-alih memaksa kolomnya
   melebar sampai tabel meluber. */
.d-tabel .d-aksi-baris,
.d-table-row .d-aksi-baris { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }

/* Tombol aksi dikecilkan supaya MUAT di kolomnya. Sel .d-keep sengaja
   overflow:visible (tombol tidak boleh terpotong), tapi itu berarti tombol
   yang kelewat lebar menindih kolom sebelahnya -- terukur "Nonaktifkan"
   menimpa label satuan 7px. Dengan 11px + padding 8px, tombol terpanjang
   (~88px) muat di kolom 96px. */
.d-tabel .d-aksi-baris .d-btn,
.d-table-row .d-aksi-baris .d-btn {
    font-size: 11px;
    padding: 5px 8px;
}
/* ...KECUALI tombol berlambang, yang justru di HP-lah ia dibutuhkan seukuran
   itu. Aturan di atas (padding 5px 8px) akan memipihkannya jadi 34x28 -- di
   bawah ambang sasaran sentuh yang nyaman. Ditulis terpisah di sini, bukan
   digabung ke aturan dasarnya, karena selektor di atas lebih spesifik dan
   akan menang. */
.d-tabel .d-aksi-baris .d-btn-aksi-ikon,
.d-table-row .d-aksi-baris .d-btn-aksi-ikon {
    padding: 0;
    width: 38px;
    height: 36px;
}

.d-tabel tr.rt-extra { display: none; }
.d-tabel tr.rt-extra.show { display: table-row; }

/* Tabel biasa (tanpa .d-tabel) dipadatkan saja -- jumlahnya sedikit dan
   kolomnya pendek. */
table:not(.d-tabel) th,
table:not(.d-tabel) td { padding: 8px 6px; font-size: 12px; }
table:not(.d-tabel) .hide-mobile { display: none; }

/* ---- .show-mobile-only: pasangan mobile-nya sempat hilang ----
   app.css memasang display:none untuk kelas ini di semua lebar, dan aturan
   yang MENYALAKANNYA kembali di HP dulu tinggal di mobile.css -- ikut
   terbuang waktu berkas itu diringkas dari 2.331 jadi 357 baris. Akibatnya
   tombol "Scan dengan Kamera HP" di checkout.php (tab Sore & Return)
   tidak pernah muncul di ponsel, padahal seluruh alurnya masih utuh:
   modal #cameraModal, html5-qrcode, dan listener-nya semua ada.
   Baris kedua perlu karena .d-btn memakai inline-flex untuk menengahkan
   ikon & teksnya -- kalau dipaksa block, isinya kembali rata kiri. */
.show-mobile-only { display: block; }
.d-btn.show-mobile-only { display: inline-flex; }

/* ---- Toast harus menang atas modal kamera scan ----
   .camera-modal ber-z-index 200, jadi dengan nilai desktop (80) hasil scan
   lewat kamera HP diumumkan DI BALIK layar hitam kamera dan tidak pernah
   terlihat. Kenaikan ini sengaja dikurung di lebar HP: satu-satunya pemicu
   modal itu tombol .show-mobile-only di checkout.php, yang di desktop
   display:none -- jadi di sana modalnya mustahil terbuka dan tumpukan
   desktop tidak perlu ikut diutak-atik sama sekali.

   Dipilih @media, bukan body:has(.camera-modal.show), supaya tidak
   bergantung pada dukungan :has(): kalau :has() tidak didukung, toast diam-
   diam turun lagi ke 80 dan bug-nya balik tanpa ketahuan. */
.d-toast { z-index: 400; }
/* Isi <details> adalah wadah TERSENDIRI di dalam kartu, jadi overflow milik
   kartu tidak menolongnya -- tabel di dalam <details> (mis. "Resi Belum
   Discan" di Scan) tetap mendorong halaman melebar tanpa aturan ini. */
.details-card-body { overflow-x: auto; padding: 0 16px 16px; }

/* Bar filter tidak menempel di HP: topbar-nya sudah position:fixed, jadi
   sticky top:0 akan menyelinap di baliknya. */
.d-filter { position: static; padding: 12px 14px; }
.d-lap-bar { position: static; }

/* ---- BAR CARI + TAB (Stok, Transaksi) ----
   Di desktop kotak cari dan tab strip duduk sebaris. Di HP tidak muat, dan
   cara gagalnya diam-diam: .d-seg dipatok `flex: 0 0 auto` (di Stok 5 tab =
   315px, sudah selebar layar) sementara .d-search `flex: 1; min-width: 0`,
   jadi kotak carinya diperas sampai LEBAR 0 -- bukan sekadar sempit, tapi
   lenyap sepenuhnya di balik tab strip. Terukur di 390px: .d-search width 0,
   input #cariStok width 4px, keduanya bertumpuk dengan .d-seg.
   Ditumpuk dua baris. Urutan DOM dipertahankan (cari di atas, tab di bawah)
   supaya urutan fokus keyboard tetap sama dengan urutan yang terlihat. */
.d-stok-bar,
.d-trx-bar { flex-wrap: wrap; }
.d-stok-bar > .d-search,
.d-trx-bar  > .d-search,
.d-stok-bar > .d-seg,
.d-trx-bar  > .d-seg { flex: 1 1 100%; min-width: 0; }

/* ---- TABEL GRID (.d-table): TETAP TABEL, TIDAK DITUMPUK ----
   Keputusan pemilik 15 Agu 2026, membalik pola sebelumnya: "jangan ada card
   vertikal untuk tabel, jadikan ke samping saja". Yang dulu ditumpuk jadi
   label-atas-nilai kini kembali jadi baris menyamping seperti di desktop.

   Supaya tetap muat tanpa gulir mendatar, kolom yang tidak krusial DIBUANG
   di HP, bukan dikecilkan: d_table() menandainya dengan .d-hp-off (lihat
   opsi 'hp' => false di includes/ui.php) dan sekaligus menyiapkan
   --d-kolom-hp -- daftar lebar kolom yang TERSISA saja. Tanpa var kedua itu
   grid tetap memesan tempat untuk kolom yang sudah disembunyikan, dan
   kepala tabel jadi meleset dari isinya. */
.d-table-head,
.d-table-row {
    grid-template-columns: var(--d-kolom-hp, var(--d-kolom));
    gap: 8px;
}
/* min-width:0 WAJIB di kedua baris: jalur fr tidak mau menyusut di bawah
   ukuran isinya tanpa ini, jadi tabel tetap meluber walau lantai minmax
   sudah dilepas. */
.d-table-head > *,
.d-table-row > * { min-width: 0; }
/* Padding & border kepala DISAMAKAN dengan baris. Keduanya grid terpisah,
   jadi jalur fr-nya dihitung dari lebar-isi kotaknya masing-masing: selisih
   border 1px saja sudah membuat kolom meleset ~1px dan makin menumpuk ke
   kanan. Border transparan, cuma untuk menyamakan perhitungan. */
.d-table-head {
    padding: 0 10px 8px;
    border-left: 1px solid transparent;
    border-right: 1px solid transparent;
    font-size: 10px;
}
/* Judul kolom DIBUNGKUS, tidak dipotong ellipsis (permintaan pemilik 15 Agu
   2026: "jangan ditulis menjadi ... ketika kepanjangan, tulis seutuhnya").
   "Target Bulanan" jadi dua baris, bukan "TARGET BUL...". align-items:end
   pada kepala supaya judul satu baris & dua baris tetap rata di bawah. */
.d-table-head {
    align-items: end;
    line-height: 1.25;
}
.d-table-head > * { white-space: normal; overflow-wrap: anywhere; }
.d-table-body { gap: 6px; }
.d-table-row { padding: 10px; }

/* Kolom yang dinyatakan tidak krusial untuk layar HP. */
.d-table-head > .d-hp-off,
.d-table-row > .d-hp-off { display: none; }

/* Isi DI DALAM sel yang berbeda antara HP & desktop -- beda dari .d-hp-off
   di atas yang membuang SATU KOLOM PENUH. Dipakai sub-baris di bawah No.
   Resi pada Barang Keluar: nomor pesanan di desktop, jam impor di HP.
   Server merender dua-duanya; di sinilah salah satunya dipilih. */
.d-hp-sembunyi { display: none; }
.d-hp-saja     { display: block; }


/* Isi sel juga DIBUNGKUS, tidak dipotong. Barisnya jadi lebih tinggi -- itu
   konsekuensi yang diterima pemilik, asal tidak ada yang hilang.
   overflow-wrap:anywhere: kata tanpa spasi (SKU panjang, nomor resi) tetap
   dipatahkan di tengah, jadi tidak ada yang meluber keluar kolomnya. */
.d-table-row > * {
    font-size: 12.5px;
    min-width: 0;
    overflow: visible;
    white-space: normal;
    overflow-wrap: anywhere;
}
/* (.d-keep tidak perlu aturan overflow lagi di sini -- sejak isinya
   dibungkus, seluruh sel sudah overflow:visible.) */

.d-pangsa { justify-content: flex-start; }

/* ---- SHEET: jadi bottom sheet ----
   Di HP inilah bentuk yang benar, dan kebetulan cocok dengan istilah
   "bottom sheet" di README handoff -- prototipe desktopnya sendiri
   menengahkan (lihat catatan di bagian 16). */
.d-sheet-backdrop { align-items: flex-end; padding: 0; }
.d-sheet {
    max-width: none;
    max-height: 92vh;
    border-radius: 26px 26px 0 0;
    animation: d-sheetUpMobile .42s cubic-bezier(.2,.95,.25,1.02);
}
.d-sheet-head { border-radius: 26px 26px 0 0; padding: 18px 20px 12px; }
.d-sheet-isi  { padding: 16px 20px 24px; }
.d-sheet-aksi { flex-direction: column-reverse; }
.d-sheet-aksi > * { width: 100%; }

/* Action bar sticky duduk di atas tab bar, bukan di baliknya.
   var() dengan fallback: kalau mobile.css berubah, ini turun jadi 0
   dan barnya tetap terlihat, cuma menempel dasar layar. */
.d-actionbar { bottom: var(--m-tabbar-h, 0px); flex-wrap: wrap; }
.d-actionbar-aksi { margin-left: 0; width: 100%; }

.d-chart-legenda { gap: 10px; }
/* Rasio ponsel (380x230) menggantikan rasio desktop (880x210): di kartu
   selebar ~315px yang desktop cuma setinggi 75px. */
.d-tren-lebar  { display: none; }
.d-tren-sempit { display: block; }
.d-field-baris { grid-template-columns: 1fr; }

}

@keyframes d-sheetUpMobile {
    from { opacity: 0; transform: translateY(100%); }
    to   { opacity: 1; transform: none; }
}


/* =========================================================================
   HALAMAN DI LUAR HANDOFF -- login, lupa password, set password, menu staff
   =========================================================================
   Dipindahkan dari assets/style.css saat berkas itu dihapus (Tahap 7b).
   Keputusan user: halaman-halaman ini "ikut token baru, layout tetap" --
   jadi aturannya sengaja TIDAK dirancang ulang, cuma diangkut. Warnanya
   ikut berubah sendiri karena semua menyebut token (--primary, --card,
   --text) yang kini bernilai palet baru; nama token lama yang tidak ada di
   app.css dipetakan lewat blok "Alias token lama" di bagian atas berkas.
   ========================================================================= */

html[data-theme="dark"] .badge-warn { background: rgba(255,149,0,0.2); color: #ffb84d; }

html[data-theme="dark"] .badge-kuning { background: rgba(255,204,0,0.22); color: #e0c04a; }

html[data-theme="dark"] .badge-danger { background: rgba(255,59,48,0.2); color: #ff6961; }

.brand-mark {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: 9px;
    background: rgba(255,255,255,0.14);
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25), 0 1px 2px rgba(0,0,0,0.15);
    font-weight: 700;
    font-size: 15px;
    letter-spacing: -0.02em;
    color: #fff;
}

/* Tabel yang lebih ramping (dipakai di Daftar Produk) supaya tidak terlalu lebar */
.table-compact th, .table-compact td {
    font-size: 13px;
    padding: 9px 11px;
}

.badge-warn { background: rgba(255,149,0,0.15); color: #c93400; }

.badge-kuning { background: rgba(255,204,0,0.22); color: #8a6d00; }

.badge-danger { background: rgba(255,59,48,0.14); color: #d70015; }

.badge-muted { background: rgba(110,110,115,0.12); color: var(--muted); }

.field-password-wrap { position: relative; }

.field-password-wrap input { padding-right: 42px; }

.btn-toggle-password {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    cursor: pointer;
    padding: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    border-radius: 6px;
    transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.btn-toggle-password:hover { color: var(--text); background: var(--hairline-soft); }

.btn-toggle-password svg { width: 19px; height: 19px; }

/* ================= HALAMAN LOGIN & SETUP ADMIN ================= */
body.login-page {
    background: linear-gradient(160deg, var(--sidebar-color-dark) 0%, var(--sidebar-color) 55%, var(--sidebar-color-light) 100%);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.login-wrap {
    width: 100%;
    padding: 20px;
    display: flex;
    justify-content: center;
    transition: opacity 0.3s ease, transform 0.3s ease;
}

.login-card {
    background: var(--card);
    border-radius: 24px;
    padding: 36px 32px;
    width: 100%;
    max-width: 400px;
    box-shadow: var(--shadow-lg);
}

.login-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    font-size: 19px;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text);
    margin-bottom: 6px;
}

.login-brand .brand-mark {
    color: #fff;
    font-weight: 700;
    align-items: center;
    justify-content: center;
}

.login-subtitle {
    color: var(--muted);
    font-size: 13.5px;
    text-align: center;
    margin: 0 0 22px;
}

.login-btn {
    width: 100%;
    padding: 12px 16px;
    font-size: 15px;
    background: var(--sidebar-color);
    margin-top: 6px;
}

.login-btn:hover { background: var(--sidebar-color-dark); }

/* ================= BOOT / GREETING SCREEN (setelah login berhasil) ================= */
.boot-screen {
    position: fixed;
    inset: 0;
    z-index: 999;
    overflow: hidden;
    background: linear-gradient(180deg, var(--sidebar-color-dark) 0%, var(--sidebar-color-light) 100%);
    opacity: 1;
    animation: bootScreenIn 0.7s var(--ease);
    transition: opacity 0.8s var(--ease);
    padding: 24px;
    text-align: center;
}

.boot-screen.fade-out { opacity: 0; }

/* Langit sesuai jam login (ditentukan lewat JS, lihat login.php) --
   gradasi dipilih supaya tetap lembut/tidak silau, bukan warna jenuh
   penuh, karena ini cuma latar belakang sebentar di belakang teks. */
.boot-screen.boot-pagi {
    background: linear-gradient(180deg, #3d5b82 0%, #7fa6c9 45%, #f4c99a 100%);
    --boot-tanggal: #4c7090;
    --boot-sapaan:  #12293f;
    --boot-nama:    #516c83;
    --boot-garis:   #8c1c33;
}

.boot-screen.boot-siang {
    background: linear-gradient(180deg, #4f8fc7 0%, #86bfe6 55%, #cfe7f5 100%);
    /* Siang punya langitnya sendiri (biru penuh, tanpa semburat jingga di
       bawah), tapi PALETNYA sama dengan pagi -- kedua langit sama-sama biru
       terang, jadi keduanya butuh teks gelap yang sama. */
    --boot-tanggal: #4c7090;
    --boot-sapaan:  #12293f;
    --boot-nama:    #516c83;
    --boot-garis:   #8c1c33;
}

.boot-screen.boot-sore {
    background: linear-gradient(180deg, #3c3a63 0%, #a4536f 42%, #e6875e 72%, #ffce97 100%);
    --boot-tanggal: #e4c3ca;
    --boot-sapaan:  #ffffff;
    --boot-nama:    #f1d6d4;
    --boot-garis:   #f7e7e6;
}

.boot-screen.boot-malam {
    background: linear-gradient(180deg, #05070d 0%, #0f1626 55%, #1c2942 100%);
    --boot-tanggal: #9395a1;
    --boot-sapaan:  #f5f5f7;
    --boot-nama:    #a4a5af;
    --boot-garis:   #c8354f;
}

/* Gambar dipasang di layer .boot-scene (bukan langsung di .boot-screen)
   supaya bisa di-zoom pelan tanpa ikut menggeser teks/konten. */
.boot-screen.boot-malam .boot-scene {
    background: url('img/boot-malam.jpg') center center / cover no-repeat;
}

.boot-screen.boot-pagi .boot-scene {
    background: url('img/boot-pagi.jpg') center center / cover no-repeat;
}

.boot-screen.boot-siang .boot-scene {
    background: url('img/boot-siang.jpg') center center / cover no-repeat;
}

.boot-screen.boot-sore .boot-scene {
    background: url('img/boot-sore.jpg') center center / cover no-repeat;
}

/* Elemen dekoratif (matahari/awan/bintang) -- murni visual, tidak
   mengganggu pembaca layar (aria-hidden di HTML) & tidak menangkap klik.
   Dizoom pelan ("Ken Burns", kalem ala Apple) di keempat tema -- foto malam
   maupun langit awan pagi/siang/sore -- supaya latarnya terasa hidup. */
.boot-scene {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    animation: bootPhotoZoom 26s ease-in-out infinite alternate;
}

.boot-stars {
    position: absolute;
    inset: 0;
    opacity: 0;
    background-image:
        radial-gradient(1.6px 1.6px at 10% 20%, rgba(255,255,255,1), transparent),
        radial-gradient(1.6px 1.6px at 25% 45%, rgba(255,255,255,0.85), transparent),
        radial-gradient(1.2px 1.2px at 40% 15%, rgba(255,255,255,1), transparent),
        radial-gradient(1.6px 1.6px at 55% 62%, rgba(255,255,255,0.8), transparent),
        radial-gradient(1.2px 1.2px at 70% 25%, rgba(255,255,255,1), transparent),
        radial-gradient(1.8px 1.8px at 85% 50%, rgba(255,255,255,0.9), transparent),
        radial-gradient(1.2px 1.2px at 15% 72%, rgba(255,255,255,0.85), transparent),
        radial-gradient(1.6px 1.6px at 33% 82%, rgba(255,255,255,0.95), transparent),
        radial-gradient(1.2px 1.2px at 60% 88%, rgba(255,255,255,0.85), transparent),
        radial-gradient(1.8px 1.8px at 92% 78%, rgba(255,255,255,1), transparent),
        radial-gradient(1.2px 1.2px at 5% 40%, rgba(255,255,255,0.8), transparent),
        radial-gradient(1.6px 1.6px at 47% 30%, rgba(255,255,255,1), transparent),
        radial-gradient(1.2px 1.2px at 78% 10%, rgba(255,255,255,0.9), transparent),
        radial-gradient(1.6px 1.6px at 95% 35%, rgba(255,255,255,0.8), transparent),
        radial-gradient(1.2px 1.2px at 20% 58%, rgba(255,255,255,1), transparent),
        radial-gradient(1.4px 1.4px at 8% 88%, rgba(255,255,255,0.85), transparent),
        radial-gradient(1.2px 1.2px at 30% 8%, rgba(255,255,255,0.9), transparent),
        radial-gradient(1.6px 1.6px at 50% 92%, rgba(255,255,255,0.85), transparent),
        radial-gradient(1.2px 1.2px at 65% 8%, rgba(255,255,255,1), transparent),
        radial-gradient(1.4px 1.4px at 75% 68%, rgba(255,255,255,0.8), transparent),
        radial-gradient(1.2px 1.2px at 88% 20%, rgba(255,255,255,0.9), transparent),
        radial-gradient(1.6px 1.6px at 3% 60%, rgba(255,255,255,0.95), transparent),
        radial-gradient(1.2px 1.2px at 42% 68%, rgba(255,255,255,0.85), transparent),
        radial-gradient(1.4px 1.4px at 98% 58%, rgba(255,255,255,0.9), transparent);
    background-repeat: no-repeat;
}

/* Kerlip tipis di atas foto -- aksen halus, bukan lapisan bintang penuh
   (foto malam sudah punya bintangnya sendiri), jadi rentang opacity-nya
   sengaja dibuat rendah lewat keyframe terpisah dari tema lain.
   opacity statis WAJIB ada: basis .boot-stars itu opacity 0, dan pada
   prefers-reduced-motion animasinya dimatikan -- tanpa nilai statis ini
   bintangnya hilang sama sekali untuk pengguna tersebut. */
.boot-screen.boot-malam .boot-stars {
    opacity: 0.3;
    animation: bootTwinkleSubtle 4.5s ease-in-out infinite;
}

/* Bintang jatuh -- streak yang muncul sekali lalu memudar, cukup untuk
   durasi tampil boot screen (~5 detik), tidak perlu berulang. */
.boot-shooting-star {
    position: absolute;
    width: 2px;
    height: 2px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 8px 1.5px rgba(255,255,255,0.9);
    opacity: 0;
}

.boot-shooting-star::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 0;
    width: 90px;
    height: 1.5px;
    background: linear-gradient(90deg, rgba(255,255,255,0.85), rgba(255,255,255,0));
    transform: translateY(-50%) rotate(-35deg);
    transform-origin: right center;
}

.boot-screen.boot-malam .boot-shooting-star.s1 {
    top: 18%;
    left: 66%;
    animation: bootShootingStar1 1.5s linear 2.5s 1;
}

/* Scrim sekarang sangat tipis, bukan lagi peredup kuat.
   Dulu ia perlu gelap karena SEMUA tema memakai teks putih di atas foto
   terang. Ilustrasi baru memilih warna teksnya sendiri per tema (pagi pakai
   teks gelap), jadi meredupkan langit justru merusak keduanya: langitnya
   kotor DAN kontras teks gelap ikut turun. Yang tersisa cuma peneduh sangat
   halus untuk berjaga di layar yang sangat terang. */
.boot-scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: none;
}

/* Sore & malam tetap dapat peneduh tipis: teksnya putih, dan di layar HP
   pemotongan 'cover' bisa mendaratkan teks di bagian langit yang paling
   terang (pita jingga di sore, pantulan bulan di malam). */
.boot-screen.boot-sore .boot-scrim {
    background: radial-gradient(ellipse 60% 42% at 50% 50%, rgba(60,20,40,0.16) 0%, rgba(60,20,40,0.06) 62%, rgba(60,20,40,0) 88%);
}

.boot-screen.boot-malam .boot-scrim {
    background: radial-gradient(ellipse 60% 42% at 50% 50%, rgba(0,0,0,0.22) 0%, rgba(0,0,0,0.08) 62%, rgba(0,0,0,0) 88%);
}

.boot-content {
    position: relative;
    z-index: 2;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* gap 0: jarak antar unsur diatur sendiri-sendiri lewat margin, karena
       mockup memakai jarak yang BERBEDA di tiap celah (tanggal->sapaan rapat,
       nama->garis renggang). Satu nilai gap tidak bisa meniru itu. */
    gap: 0;
}

/* Font "Hello" ala Apple: bentuk rounded/friendly dari sistem font.
   ui-rounded & -apple-system-rounded adalah nilai khusus yang dikenali
   Safari/WebKit untuk memakai varian SF Pro Rounded milik sistem (tanpa
   perlu mengunggah file font Apple, yang memang tidak boleh didistribusikan
   di luar platform Apple). Di browser lain otomatis jatuh ke system-ui. */
.boot-tanggal,
.boot-greeting,
.boot-name {
    font-family: -apple-system-rounded, ui-rounded, "SF Pro Rounded", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

/* Baris tanggal & jam. Huruf kapital berjarak lebar -- ini yang membuatnya
   terbaca sebagai keterangan, bukan judul kedua. */
.boot-tanggal {
    font-size: clamp(11px, 1.05vw, 14px);
    font-weight: 500;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--boot-tanggal, rgba(255,255,255,0.75));
    margin: 0 0 18px;
    opacity: 0;
    transform: translateY(8px);
    animation: bootTextIn 0.9s var(--ease) 0.35s forwards;
}

.boot-greeting {
    font-size: clamp(32px, 4.4vw, 56px);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--boot-sapaan, #fff);
    margin: 0;
    opacity: 0;
    transform: translateY(10px);
    animation: bootTextIn 0.9s var(--ease) 0.75s forwards;
}

.boot-name {
    font-size: clamp(16px, 1.55vw, 23px);
    font-weight: 400;
    letter-spacing: 0;
    color: var(--boot-nama, rgba(255,255,255,0.92));
    margin: 10px 0 0;
    opacity: 0;
    transform: translateY(8px);
    animation: bootTextIn 0.9s var(--ease) 1.35s forwards;
}

/* Garis aksen pendek penutup. Warnanya beda per tema (merah tua di pagi,
   putih di sore, merah terang di malam) -- diambil dari mockup, bukan satu
   warna merek yang dipaksakan ke tiga langit yang kontrasnya jauh berbeda. */
.boot-rule {
    width: 54px;
    height: 3px;
    border-radius: 999px;
    background: var(--boot-garis, #fff);
    margin: 30px 0 0;
    opacity: 0;
    transform: translateY(6px);
    animation: bootTextIn 0.7s var(--ease) 1.9s forwards;
}

@media (prefers-reduced-motion: reduce) {
    .boot-tanggal, .boot-greeting, .boot-name, .boot-rule {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
    }
    .boot-stars {
        animation: none !important;
    }
    .boot-shooting-star {
        display: none !important;
    }
    .boot-screen { transition: none; animation: none !important; opacity: 1 !important; }
    .boot-scene { animation: none !important; transform: none !important; }
    .login-wrap { transition: none !important; }
}

@keyframes bootPhotoZoom {
    from { transform: scale(1); }
    to { transform: scale(1.08); }
}

@keyframes bootTwinkleSubtle {
    0%, 100% { opacity: 0.18; }
    50% { opacity: 0.4; }
}

@keyframes bootTextIn {
    to { opacity: 1; transform: translateY(0); }
}

@keyframes bootShootingStar1 {
    0% { opacity: 0; transform: translate(0, 0); }
    45% { opacity: 1; }
    68% { opacity: 1; }
    100% { opacity: 0; transform: translate(-170px, 115px); }
}

/* =====================================================================
   LAYAR LOGIN (handoff design_handoff_login -- opsi 2a gelap / 1a terang)
   =====================================================================
   Panel merek kiri + form kanan. Warnanya TIDAK dipatok burgundy mati:
   semuanya diturunkan dari --primary, jadi tema tersimpan DAN warna aksen
   pilihan user ikut berlaku di layar login (aturan handoff no.4: "Login
   mengikuti tema tersimpan", bukan selalu terang).

   Handoff memberi dua nilai burgundy: #8c1c33 untuk ground terang dan
   #a82440 untuk ground gelap ("satu langkah lebih terang supaya kontrasnya
   cukup"). Itu warna yang sama dinaikkan terangnya, jadi di sini dihitung
   dari --primary alih-alih ditulis dua kali -- supaya aksen non-burgundy
   pun dapat perlakuan sama. */
/* Layar login mengisi jendela penuh -- tanpa ini body membawa margin
   bawaan browser dan panel split-nya tidak menempel tepi. */
body.d-log-body { margin: 0; background: var(--bg); }

.d-log {
    --d-log-aksen: var(--primary);
    /* Cadangan untuk mesin tanpa warna relatif: campur sedikit putih. */
    --d-log-aksen-terang: color-mix(in srgb, var(--primary) 88%, #fff);
    --d-log-panel-1: var(--primary);
    --d-log-panel-2: var(--primary-dark);
    --d-log-teks-panel: #fff;
    --d-log-teks-panel-sub: rgba(255, 255, 255, .72);
    --d-log-teks-panel-poin: rgba(255, 255, 255, .9);
    --d-log-teks-panel-kaki: rgba(255, 255, 255, .5);
    --d-log-logo-bg: rgba(255, 255, 255, .18);
    --d-log-logo-br: rgba(255, 255, 255, .28);
    --d-log-centang-bg: rgba(255, 255, 255, .2);
    --d-log-fokus-tebal: 14%;

    display: grid;
    grid-template-columns: .86fr 1fr;
    min-height: 100vh;
    background: var(--bg);
}
/* Warna relatif: menaikkan lightness, bukan mencampur putih -- hasilnya
   mempertahankan saturasi. #8c1c33 jadi rgb(170,34,62), praktis identik
   dengan #a82440 yang diminta handoff; color-mix dengan putih hanya
   memberi rgb(154,55,75) yang jelas pudar.

   `calc(l + 7)` TANPA satuan persen: di sintaks warna relatif hsl(), `l`
   resolve jadi NUMBER, bukan percentage -- `calc(l + 7%)` mencampur dua
   tipe dan seluruh deklarasinya dibuang browser (tombol Masuk sempat
   kehilangan latarnya sama sekali di mode gelap karena ini).

   Kondisi @supports HARUS menguji bentuk yang benar-benar dipakai:
   `hsl(from red h s l)` saja lolos di Chrome sementara versi calc-nya
   tidak, jadi penjaga yang terlalu longgar akan meloloskan nilai rusak.
   Ditulis SETELAH nilai color-mix di atas supaya mesin tanpa dukungan
   tetap dapat cadangannya. */
@supports (color: hsl(from red h s calc(l + 7))) {
    .d-log { --d-log-aksen-terang: hsl(from var(--primary) h s calc(l + 7)); }
}

html[data-theme="dark"] .d-log {
    --d-log-aksen: var(--d-log-aksen-terang);
    --d-log-panel-1: color-mix(in srgb, var(--primary) 42%, #000);
    --d-log-panel-2: color-mix(in srgb, var(--primary) 9%, #000);
    --d-log-teks-panel: #f5f5f7;
    --d-log-teks-panel-sub: rgba(245, 245, 247, .6);
    --d-log-teks-panel-poin: rgba(245, 245, 247, .82);
    --d-log-teks-panel-kaki: rgba(245, 245, 247, .4);
    --d-log-logo-bg: rgba(255, 255, 255, .1);
    --d-log-logo-br: rgba(255, 255, 255, .16);
    --d-log-centang-bg: color-mix(in srgb, var(--d-log-aksen-terang) 60%, transparent);
    --d-log-fokus-tebal: 26%;
}

/* ---------------------------------------------- panel merek (kiri) ---- */
.d-log-merek {
    position: relative;
    overflow: hidden;
    padding: 52px 46px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    background: linear-gradient(150deg, var(--d-log-panel-1), var(--d-log-panel-2));
    border-right: 1px solid var(--border);
}
.d-log-glow {
    position: absolute;
    width: 440px;
    height: 440px;
    top: -150px;
    right: -170px;
    border-radius: 50%;
    background: radial-gradient(circle, color-mix(in srgb, var(--d-log-aksen-terang) 55%, transparent), transparent 68%);
    filter: blur(10px);
    pointer-events: none;
}
/* Semua isi panel duduk di ATAS glow. */
.d-log-merek > *:not(.d-log-glow) { position: relative; }

.d-log-merek-atas { display: flex; align-items: center; gap: 12px; }
.d-log-logo {
    width: 38px;
    height: 38px;
    border-radius: 11px;
    background: var(--d-log-logo-bg);
    border: 1px solid var(--d-log-logo-br);
    display: grid;
    place-items: center;
    font-size: 17px;
    font-weight: 700;
    color: var(--d-log-teks-panel);
    cursor: default;
    transition: transform .4s var(--d-ease-pop), background .3s, box-shadow .4s;
}
.d-log-merek-atas:hover .d-log-logo {
    transform: rotate(-6deg) scale(1.1);
    background: color-mix(in srgb, var(--d-log-logo-bg) 70%, #fff);
    box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--d-log-aksen-terang) 90%, transparent);
}
.d-log-nama {
    font-size: 14.5px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--d-log-teks-panel);
}

.d-log-judul {
    margin: 0;
    font-size: 38px;
    line-height: 1.14;
    font-weight: 700;
    letter-spacing: -.035em;
    color: var(--d-log-teks-panel);
    max-width: 340px;
    text-wrap: pretty;
}
.d-log-sub {
    margin: 18px 0 0;
    font-size: 14px;
    line-height: 1.6;
    color: var(--d-log-teks-panel-sub);
    max-width: 320px;
}
.d-log-poin {
    margin: 34px 0 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 13px;
}
.d-log-poin li {
    display: flex;
    align-items: center;
    gap: 11px;
    font-size: 13px;
    color: var(--d-log-teks-panel-poin);
    cursor: default;
    transition: transform .35s var(--d-ease-out), color .3s;
}
.d-log-poin li:hover { transform: translateX(6px); color: #fff; }
.d-log-centang {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    flex: none;
    display: grid;
    place-items: center;
    font-size: 10px;
    color: #fff;
    background: var(--d-log-centang-bg);
}
.d-log-kaki { font-size: 11.5px; color: var(--d-log-teks-panel-kaki); }

/* ----------------------------------------------- panel form (kanan) --- */
.d-log-form {
    padding: 52px 56px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    background: var(--bg);
}
.d-log-form-isi { width: 100%; max-width: 380px; }

.d-log-h2 {
    margin: 0;
    font-size: 27px;
    font-weight: 700;
    letter-spacing: -.03em;
    color: var(--text);
}
.d-log-h2-sub { margin: 7px 0 0; font-size: 13.5px; color: var(--muted); }

.d-log-form form {
    margin-top: 30px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}
.d-log-field { display: flex; flex-direction: column; gap: 7px; }
.d-log-label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.d-log-label-baris { display: flex; justify-content: space-between; align-items: baseline; }

.d-log-lupa {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--d-log-aksen);
    text-decoration: none;
    display: inline-block;
    transition: color .25s, transform .3s var(--d-ease-out);
}
.d-log-lupa:hover { color: var(--text); transform: translateX(-2px); }

.d-log-input {
    width: 100%;
    box-sizing: border-box;
    padding: 13px 14px;
    font: inherit;
    font-size: 14px;
    color: var(--text);
    background: var(--card-solid);
    border: 1px solid var(--border);
    border-radius: 12px;
    transition: border-color .25s, box-shadow .3s, transform .3s var(--d-ease-out);
}
.d-log-input:hover {
    border-color: color-mix(in srgb, var(--border) 60%, var(--text));
    box-shadow: 0 8px 20px -14px rgba(0, 0, 0, .9);
    transform: translateY(-1px);
}
.d-log-input:focus {
    outline: none;
    border: 1.5px solid var(--d-log-aksen);
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--d-log-aksen) var(--d-log-fokus-tebal), transparent);
}
/* Ring fokus melebar saat kursor juga di atasnya -- disebut khusus di
   handoff untuk isian password. */
.d-log-input:focus:hover {
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--d-log-aksen) calc(var(--d-log-fokus-tebal) + 8%), transparent);
    transform: translateY(-1px);
}
/* Chrome mengecat isian ter-autofill biru muda dengan !important internal --
   satu-satunya cara menahannya adalah menimpa latar lewat inset box-shadow
   raksasa. Ring fokus ikut ditulis ulang di sini karena keduanya memakai
   properti yang sama; kalau tidak, isian yang diisi password manager
   kehilangan ring fokusnya. */
.d-log-input:-webkit-autofill,
.d-log-input:-webkit-autofill:hover {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px var(--card-solid) inset;
    caret-color: var(--text);
}
.d-log-input:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 1000px var(--card-solid) inset,
                        0 0 0 3.5px color-mix(in srgb, var(--d-log-aksen) var(--d-log-fokus-tebal), transparent);
}

.d-log-input.salah { border-color: var(--red); }
.d-log-input.salah:focus {
    border-color: var(--red);
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--red) 20%, transparent);
}

.d-log-input-bungkus { position: relative; display: flex; align-items: center; }
.d-log-input-bungkus .d-log-input { padding-right: 108px; }
.d-log-lihat {
    position: absolute;
    right: 12px;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--d-log-aksen);
    background: none;
    border: 0;
    padding: 4px 7px;
    border-radius: 8px;
    cursor: pointer;
    transition: background .25s, color .25s, transform .3s var(--d-ease-pop);
}
.d-log-lihat:hover {
    background: color-mix(in srgb, var(--d-log-aksen) 26%, transparent);
    color: #fff;
    transform: scale(1.06);
}
.d-log-lihat:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--d-log-aksen) 32%, transparent);
}

/* --------------------------------------------------- ingat saya ------ */
.d-log-ingat {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-top: 2px;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
    transition: transform .3s var(--d-ease-out);
}
.d-log-ingat:hover { transform: translateX(4px); }
/* Kotak asli disembunyikan TAPI tetap fokusabel & terbaca pembaca layar --
   bukan display:none, yang akan mengeluarkannya dari urutan tab. */
.d-log-ingat input {
    position: absolute;
    opacity: 0;
    width: 20px;
    height: 20px;
    margin: 0;
    cursor: pointer;
}
.d-log-kotak {
    width: 20px;
    height: 20px;
    border-radius: 6px;
    flex: none;
    display: grid;
    place-items: center;
    font-size: 11px;
    font-weight: 700;
    color: transparent;
    background: transparent;
    border: 1.5px solid var(--border);
    transition: transform .35s var(--d-ease-pop), box-shadow .3s, background .2s, border-color .2s, color .2s;
}
.d-log-ingat input:checked ~ .d-log-kotak {
    background: var(--d-log-aksen);
    border-color: var(--d-log-aksen);
    color: #fff;
}
.d-log-ingat:hover .d-log-kotak {
    transform: scale(1.14);
    box-shadow: 0 6px 16px -8px color-mix(in srgb, var(--d-log-aksen) 100%, transparent);
}
.d-log-ingat input:focus-visible ~ .d-log-kotak {
    box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--d-log-aksen) 30%, transparent);
}

/* ------------------------------------------------------- tombol ------ */
.d-log-tombol {
    margin-top: 8px;
    padding: 13px 15px;
    font: inherit;
    font-size: 14.5px;
    font-weight: 600;
    color: #fff;
    background: var(--d-log-aksen);
    border: 0;
    border-radius: 12px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    box-shadow: 0 12px 24px -14px color-mix(in srgb, var(--d-log-aksen) 70%, transparent);
    transition: transform .32s var(--d-ease-pop), box-shadow .35s, background .25s;
}
.d-log-tombol:hover {
    transform: translateY(-3px) scale(1.015);
    background: var(--d-log-aksen-terang);
    box-shadow: 0 22px 38px -18px color-mix(in srgb, var(--d-log-aksen) 95%, transparent);
}
.d-log-tombol:active {
    transform: translateY(0) scale(.985);
    box-shadow: 0 8px 18px -12px color-mix(in srgb, var(--d-log-aksen) 90%, transparent);
}
.d-log-tombol:focus-visible {
    outline: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--d-log-aksen) 32%, transparent);
}
.d-log-tombol.memuat { opacity: .6; pointer-events: none; }
.d-log-spin {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, .35);
    border-top-color: #fff;
    display: none;
    animation: d-log-putar .7s linear infinite;
}
.d-log-tombol.memuat .d-log-spin { display: block; }
@keyframes d-log-putar { to { transform: rotate(360deg); } }

/* ------------------------------------------------- galat & catatan --- */
.d-log-galat { margin: 0; font-size: 12.5px; color: var(--red); }
.d-log-galat-blok { margin-top: 2px; }

.d-log-catatan {
    margin: 26px 0 0;
    padding-top: 20px;
    border-top: 1px solid var(--hair);
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--faint);
}
.d-log-catatan a {
    color: var(--d-log-aksen);
    font-weight: 600;
    text-decoration: none;
    transition: color .25s;
}
@media (hover: hover) { .d-log-catatan a:hover { text-decoration: underline; } }
.d-log-legal { margin: 10px 0 0; font-size: 12px; color: var(--faint); }
.d-log-legal a { color: var(--faint); text-decoration: none; }
.d-log-legal a:hover { color: var(--text); }

/* ----------------------------------------------- animasi masuk ------- */
/* backwards, BUKAN both -- 'both' menahan transform setelah animasi selesai
   sehingga semua hover lift di atas mati (peringatan eksplisit di handoff). */
@keyframes d-log-naik {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; }
}
.d-log-form-isi > *    { animation: d-log-naik .55s var(--d-ease-out) backwards; }
.d-log-form-isi > form { animation-delay: 60ms; }
.d-log-catatan,
.d-log-legal           { animation-delay: 120ms; }

/* ------------------------------------------------------ responsif ---- */
@media (max-width: 900px) {
    .d-log { grid-template-columns: 1fr; }
    .d-log-merek {
        padding: 30px 28px;
        gap: 20px;
        border-right: 0;
        border-bottom: 1px solid var(--border);
    }
    .d-log-judul { font-size: 25px; max-width: none; }
    .d-log-sub { margin-top: 12px; max-width: none; }
    .d-log-poin { margin-top: 22px; }
    .d-log-form { padding: 34px 28px 44px; }
    .d-log-form-isi { max-width: none; }
}
@media (max-width: 600px) {
    .d-log-poin { display: none; }
}

/* Hormati prefers-reduced-motion: animasi masuk dimatikan & lift hover
   diganti perubahan warna saja (aturan handoff). */
@media (prefers-reduced-motion: reduce) {
    .d-log-form-isi > * { animation: none; }
    .d-log-poin li:hover,
    .d-log-ingat:hover,
    .d-log-input:hover,
    .d-log-input:focus:hover,
    .d-log-lupa:hover,
    .d-log-tombol:hover,
    .d-log-lihat:hover { transform: none; }
    .d-log-merek-atas:hover .d-log-logo { transform: none; }
    .d-log-ingat:hover .d-log-kotak { transform: none; }
}


/* =========================================================================
   KABAR SCAN GAGAL -- kartu besar di tengah layar
   -------------------------------------------------------------------------
   Toast biasa (.d-toast) setinggi 40px di pojok bawah tidak terbaca oleh
   petugas packing: mereka memegang barang, berdiri satu-dua meter dari
   layar, dan yang paling perlu sampai justru ALASAN gagalnya. Jadi khusus
   kegagalan scan dipakai kartu ini. Scan berhasil TETAP toast -- kalau
   semua kejadian ditengahkan, yang darurat kehilangan artinya.

   Kelasnya di-scope sendiri (.d-scan-*), TIDAK menumpang .d-sheet: sheet
   punya kepala lengket, tombol tutup yang berputar, dan padding yang
   diasumsikan ada isian form di dalamnya.

   z-index 500: di atas .camera-modal (200) dan .d-toast (400) -- kabar ini
   harus tetap terbaca walau scan dilakukan lewat kamera HP.
   ========================================================================= */
.d-scan-kabar {
    position: fixed;
    inset: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0,0,0,.5);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    animation: d-backdropIn .18s ease;
    cursor: pointer; /* seluruh lapisan = area tutup */
}
.d-scan-kabar-kartu {
    width: 100%;
    max-width: 560px;
    padding: 34px 32px 30px;
    border-radius: 24px;
    text-align: center;
    background: var(--card-solid);
    border: 2px solid var(--red);
    box-shadow: 0 40px 90px -24px rgba(0,0,0,.55);
    animation: d-scanKabarIn .34s cubic-bezier(.2,.95,.25,1.05);
}
/* Sengaja TIDAK memakai d-sheetIn: keyframe itu mulai dari translateY(40px),
   dan selama animasinya belum tuntas kartunya duduk 40px di bawah titik
   tengah. Terukur meleset persis segitu saat animasi tidak sempat berjalan
   (tab di latar dicekik browser). Skala tidak menggeser titik pusat, jadi
   kartu ini selalu tepat di tengah berapa pun keadaan animasinya. */
@keyframes d-scanKabarIn {
    from { opacity: 0; transform: scale(.94); }
    to   { opacity: 1; transform: none; }
}
.d-scan-kabar-ikon {
    width: 62px;
    height: 62px;
    margin: 0 auto 18px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--red);
    color: #fff;
    font-size: 34px;
    font-weight: 800;
    line-height: 1;
}
.d-scan-kabar-judul {
    margin: 0 0 10px;
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -.02em;
    line-height: 1.15;
    color: var(--red);
}
/* Alasannya yang paling penting dibaca -- jadi ia yang paling besar setelah
   judul, bukan keterangan kecil abu-abu seperti di toast. */
.d-scan-kabar-teks {
    margin: 0;
    font-size: 17px;
    line-height: 1.5;
    color: var(--text);
    word-break: break-word;
}
.d-scan-kabar-tutup {
    margin-top: 22px;
    font-size: 13px;
    color: var(--muted);
}

/* Nada "dibatalkan": kejadian yang paling mahal kalau lolos (barang keluar
   gudang tanpa pemasukan), jadi kartunya berdenyut sampai ditutup. */
.d-scan-kabar-batal .d-scan-kabar-kartu {
    animation: d-scanKabarIn .34s cubic-bezier(.2,.95,.25,1.05), d-scanDenyut 1.1s ease-in-out .34s infinite;
}
@keyframes d-scanDenyut {
    0%, 100% { box-shadow: 0 40px 90px -24px rgba(0,0,0,.55), 0 0 0 0 rgba(255,59,48,.55); }
    50%      { box-shadow: 0 40px 90px -24px rgba(0,0,0,.55), 0 0 0 16px rgba(255,59,48,0); }
}

@media (max-width: 768px) {
    .d-scan-kabar { padding: 18px; }
    .d-scan-kabar-kartu { padding: 28px 22px 24px; border-radius: 20px; }
    .d-scan-kabar-judul { font-size: 22px; }
    .d-scan-kabar-teks  { font-size: 16px; }
}

@media (prefers-reduced-motion: reduce) {
    .d-scan-kabar,
    .d-scan-kabar-kartu,
    .d-scan-kabar-batal .d-scan-kabar-kartu { animation: none; }
}


/* =====================================================================
   KALKULATOR HARGA SHOPEE (kalkulator.php + assets/kalkulator.js)
   =====================================================================
   Dua kolom di desktop: kolom kiri yang digulir berisi input, kolom kanan
   berisi kartu hasil yang MENEMPEL -- angka laba harus selalu kelihatan
   sementara pemilik mengutak-atik program di bawah. Di HP keduanya
   menumpuk dan kartu hasil naik ke ATAS input (lihat blok mobile.css),
   karena di layar sempit yang dicari duluan justru angkanya.
   ===================================================================== */

.kalk-kolom {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 14px;
    align-items: start;
}
.kalk-kartu-hasil { position: sticky; top: 18px; }

/* ----- pencarian produk & kategori ----- */
/* Panel saran menggantung di atas isi berikutnya, jadi pembungkusnya wajib
   position:relative -- tanpa itu ia mendorong kartu memanjang tiap kali
   muncul dan seluruh kolom bergoyang saat mengetik. */
.kalk-cari-bungkus { position: relative; }

/* ...tapi position:relative saja belum cukup. .d-card memasang
   backdrop-filter, dan itu MEMBUAT STACKING CONTEXT: z-index:40 di panel
   saran hanya berlaku di dalam kartunya sendiri, jadi kartu berikutnya
   ("Arah hitung") yang digambar belakangan tetap menimpanya. Yang harus
   naik itu KARTUNYA, selagi ada panel yang terbuka. Sekalian batalkan
   overflow-x:auto bawaan HP (bagian tabel di atas) yang ikut memotong
   panel di tepi bawah kartu. */
.d-card:has(.kalk-saran:not([hidden])) {
    position: relative;
    z-index: 60;
    overflow: visible;
}
.kalk-saran {
    position: absolute;
    left: 0; right: 0; top: 100%;
    z-index: 40;
    margin-top: 6px;
    max-height: 320px;
    overflow-y: auto;
    background: var(--card-solid);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--d-shadow-pop);
    padding: 5px;
}
.kalk-saran-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    text-align: left;
    padding: 9px 11px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    cursor: pointer;
    font: inherit;
    color: var(--text);
}
.kalk-saran-item:hover,
.kalk-saran-item:focus-visible { background: var(--field); }
.kalk-saran-utama { font-weight: 600; font-size: 13.5px; }
.kalk-saran-sub   { font-size: 12px; color: var(--muted); }
.kalk-saran-kosong { padding: 12px; font-size: 13px; color: var(--muted); }

.kalk-terpilih {
    margin: 8px 0 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}
.kalk-terpilih b { font-variant-numeric: tabular-nums; color: var(--text); }
.kalk-kunci {
    font-weight: 500;
    color: var(--faint);
    text-transform: none;
    letter-spacing: 0;
}

/* ----- baris program ----- */
.kalk-program .kalk-baris { align-items: center; }
.kalk-baris-kanan { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.kalk-kotak { position: relative; display: flex; align-items: center; }
/* Padding kanan berlaku di SEMUA pemakaian .kalk-kotak: itu tempat duduk
   .kalk-satuan, dan tanpanya angka yang rata-kanan menabrak satuannya.
   Lebar sempitnya cuma milik baris program -- kolom "Target untung" memakai
   kotak yang sama tapi selebar kartunya, jadi biarkan .d-input width:100%. */
.kalk-kotak .d-input { padding-right: 32px; }
.kalk-program .kalk-kotak .d-input { width: 92px; }
.kalk-satuan {
    position: absolute;
    right: 10px;
    font-size: 11.5px;
    color: var(--faint);
    pointer-events: none;
}
/* Baris yang dimatikan tetap terbaca (angkanya masih informatif) tapi jelas
   tidak ikut hitungan -- diredupkan, bukan disembunyikan. */
.kalk-mati { opacity: .45; }

/* ----- kartu hasil ----- */
.kalk-kosong, .kalk-mustahil {
    padding: 22px 4px;
    font-size: 13.5px;
    color: var(--muted);
    text-align: center;
}
.kalk-mustahil {
    text-align: left;
    background: color-mix(in srgb, var(--red) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--red) 26%, transparent);
    border-radius: var(--radius-md);
    padding: 14px 16px;
    color: var(--text);
}
.kalk-mustahil strong { color: var(--red); display: block; margin-bottom: 4px; }
.kalk-mustahil p { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.5; }

.kalk-angka-utama {
    display: grid;
    gap: 10px;
    margin-bottom: 18px;
}
.kalk-angka {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 14px;
    background: var(--surface-tint);
    border-radius: var(--radius-md);
}
.kalk-angka-label { font-size: 11.5px; font-weight: 600; color: var(--muted); letter-spacing: .01em; }
.kalk-angka-nilai {
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}
.kalk-angka-ket { font-size: 12px; color: var(--faint); }
.kalk-angka.kalk-ok   .kalk-angka-nilai { color: var(--green); }
.kalk-angka.kalk-rugi .kalk-angka-nilai { color: var(--red); }

.kalk-rinci-judul {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--faint);
    margin-bottom: 8px;
}
.kalk-rinci {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 64px 96px;
    align-items: center;
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--hair);
}
.kalk-rinci:last-child { border-bottom: 0; }
.kalk-rinci-kiri { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.kalk-rinci-label { font-size: 13px; font-weight: 600; }
.kalk-rinci-ket   { font-size: 11.5px; color: var(--faint); }
.kalk-rinci-bar {
    height: 5px;
    border-radius: 3px;
    background: var(--field);
    overflow: hidden;
}
.kalk-rinci-bar span {
    display: block;
    height: 100%;
    border-radius: 3px;
    background: var(--primary);
}
.kalk-rinci-nilai { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13.5px; }
/* Modal bukan potongan Shopee -- dipisahkan supaya tidak terbaca sebagai
   salah satu biaya marketplace, tapi tetap perlu terlihat karena ia yang
   memakan sisa uang cair. */
.kalk-rinci-modal { border-top: 1px solid var(--border); border-bottom: 0; margin-top: 4px; padding-top: 10px; }
.kalk-rinci-modal .kalk-rinci-label { color: var(--muted); }

.kalk-skenario {
    margin-top: 14px;
    padding: 11px 13px;
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--orange) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--orange) 26%, transparent);
    font-size: 12.5px;
    line-height: 1.55;
}
.kalk-skenario strong { display: block; margin-bottom: 2px; }

/* ----- tab Tarif Kategori ----- */
/* Daftar jenis produk resmi Shopee bisa 40 item dalam satu sel; dipotong
   satu baris supaya tabelnya tetap bisa dipindai, teks penuhnya tetap ada
   di DOM untuk pencarian. */
.kalk-jenis {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
    color: var(--muted);
}
.kalk-baris-mati { opacity: .45; }

@media (max-width: 900px) {
    .kalk-kolom { grid-template-columns: 1fr; }
    .kalk-kartu-hasil { position: static; }
}
