/* ==========================================================================
   ARG — BASE & KOMPONEN DASAR
   Bergantung pada tokens.css. Semua warna WAJIB lewat var(), tidak ada
   nilai heks langsung di file ini.
   ========================================================================== */
*{box-sizing:border-box}
/* Atribut `hidden` HARUS menang. Aturan bawaan browser hanyalah
   [hidden]{display:none}, sehingga setiap kelas yang menyetel display —
   .login-body{display:flex}, #app{display:grid}, .nota{display:flex} —
   diam-diam mengalahkannya dan elemen tetap terlihat. Tanpa baris ini,
   `elemen.hidden = true` di JavaScript tidak menyembunyikan apa pun. */
[hidden]{display:none!important}
html,body{height:100%}
/* ── Batang gulir SELALU dipesan tempatnya ─────────────────────────────
   Tanpa ini, halaman yang isinya cukup panjang memunculkan batang gulir
   dan seluruh isi terdorong 15 px ke kiri; halaman yang pendek tidak.
   Akibatnya tombol Bantuan — dan judul, dan seluruh tabel — BERPINDAH
   tempat begitu daftar disaring dari 15 project jadi 1. Dilaporkan MRI,
   8 Sep 2026, dan diukur di peramban: tepat 15 px.

   Pergeserannya juga terjadi di tengah pekerjaan: menambah baris sampai
   halamannya melewati tinggi layar menggeser seluruh formulir yang sedang
   diketik. `stable` memesan tempatnya sejak awal, jadi tidak ada yang
   bergerak — dan halaman pendek hanya menyisakan satu jalur kosong yang
   nyaris tidak terlihat pada latar sehalaman.

   `overflow-y:scroll` ikut dipasang karena `scrollbar-gutter` SENDIRIAN
   tidak berlaku: peramban baru memesan jalur itu kalau elemennya memang
   wadah gulir, sedangkan `html` bawaannya `overflow:visible`. Diuji di
   peramban — dengan gutter saja, clientWidth tetap 1440 dan isinya masih
   bergeser. */
html{overflow-y:scroll; scrollbar-gutter:stable}
body{
  margin:0; background:var(--page); color:var(--ink);
  font-family:var(--font-body); font-size:14px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4{font-family:var(--font-display); text-wrap:balance; margin:0}
button,input,select,textarea{font:inherit; color:inherit}
a{color:var(--blue)}
/* Cincin fokus dipakai SATU nilai untuk seluruh aplikasi. Dua lapis —
   garis tegas plus halo lembut — supaya tetap terlihat baik di atas tombol
   biru maupun di atas kertas putih; satu lapis saja akan hilang di salah
   satunya. `:focus-visible`, bukan `:focus`, agar tidak muncul saat diklik
   tetikus, tempat cincin itu hanya jadi kebisingan. */
:where(button,input,select,textarea,a,[tabindex]):focus-visible{
  outline:2px solid var(--fokus); outline-offset:2px; border-radius:var(--r-xs);
  box-shadow:var(--fokus-ring);
}
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{transition:none!important; animation:none!important}
}
.mono{font-family:var(--font-mono); font-variant-numeric:tabular-nums}
.num{font-variant-numeric:tabular-nums}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ── Tombol ───────────────────────────────────────────────────────────── */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  height:38px; padding:0 15px; border-radius:var(--r-sm);
  border:1px solid var(--line-2); background:var(--card); color:var(--ink);
  font-weight:600; font-size:13px; cursor:pointer; white-space:nowrap;
  transition:background var(--gerak-cepat), border-color var(--gerak-cepat),
             transform var(--gerak-cepat), box-shadow var(--gerak-cepat);
}
.btn:hover:not(:disabled){background:var(--card-2); border-color:var(--line-2)}
/* Umpan balik tekan: 1 piksel turun. Cukup untuk terasa, tidak cukup untuk
   mengganggu. */
.btn:active:not(:disabled){transform:translateY(1px)}
.btn-primary{box-shadow:var(--shadow-1)}
.btn-primary:active:not(:disabled){box-shadow:none}
/* Tombol yang sedang bekerja: tidak bisa diklik dua kali, dan terlihat
   sedang bekerja. Dipakai dengan menambahkan atribut data-sibuk. */
.btn[data-sibuk]{pointer-events:none; opacity:.75; position:relative}
.btn[data-sibuk]::after{
  content:''; width:13px; height:13px; border-radius:50%; flex:none;
  border:2px solid currentColor; border-top-color:transparent;
  animation:btnPutar .6s linear infinite;
}
@keyframes btnPutar{to{transform:rotate(360deg)}}
.btn:disabled{opacity:.55; cursor:not-allowed}
.btn-primary{background:var(--blue); border-color:var(--blue); color:var(--on-accent)}
.btn-primary:hover:not(:disabled){background:var(--blue-hi); border-color:var(--blue-hi)}
/* Lime dipakai HEMAT — hanya untuk satu aksi paling menentukan per layar. */
.btn-lime{background:var(--lime); border-color:var(--lime-dark); color:var(--on-lime)}
.btn-lime:hover:not(:disabled){background:var(--lime-dark)}
.btn-danger{background:var(--bad); border-color:var(--bad); color:var(--on-accent)}
/* Bahaya yang tidak berteriak. Tombol merah pekat di sebelah tombol maju
   ("Publikasikan", "Ajukan") menarik mata lebih kuat daripada aksi yang
   sebenarnya dituju, dan yang paling sering ditekan justru yang paling
   mencolok. Merahnya baru muncul penuh saat disentuh. */
.btn-danger-ghost{background:transparent; border-color:var(--bad-line); color:var(--bad)}
.btn-danger-ghost:hover{background:var(--bad-bg); border-color:var(--bad)}
.btn-danger-ghost:active{background:var(--bad); color:var(--on-accent)}
.btn-ghost{background:transparent; border-color:transparent}
.btn-ghost:hover:not(:disabled){background:var(--card-2); border-color:var(--line-2)}
/* 32 px, bukan 31. Aplikasi ini menetapkan sendiri batas bawah sasaran
   sentuh 32 px (uji 24), lalu tombol kecilnya berhenti satu piksel di
   bawahnya — dan tombol kecil adalah tombol yang PALING sering berdampingan
   dengan tombol lain. Satu piksel tidak terasa saat diklik tetikus; yang
   terasa adalah aturan yang dilanggar oleh yang menulisnya sendiri. */
.btn-sm{height:32px; padding:0 10px; font-size:12px}
.btn-icon{width:38px; padding:0}
.btn-icon.btn-sm{width:32px}
.btn svg{width:15px; height:15px; flex:none}

/* ── Panel ────────────────────────────────────────────────────────────── */
.panel{
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r); box-shadow:var(--shadow-1);
}
.panel-head{
  display:flex; align-items:center; gap:9px;
  padding:13px 15px; border-bottom:1px solid var(--line);
}
.panel-head h2{font-size:12px; font-weight:800; letter-spacing:.07em; text-transform:uppercase; color:var(--ink-2)}
.panel-head .count{margin-left:auto; font-family:var(--font-mono); font-size:11px; color:var(--ink-3); font-variant-numeric:tabular-nums}
.panel-body{padding:13px 15px}

/* ── Field ────────────────────────────────────────────────────────────── */
.grid-2{display:grid; grid-template-columns:1fr 1fr; gap:11px 18px}
.grid-3{display:grid; grid-template-columns:1fr 1fr 1fr; gap:11px 18px}
@media (max-width:700px){.grid-2,.grid-3{grid-template-columns:1fr}}
.f{display:flex; flex-direction:column; gap:5px; min-width:0}
.f label{font-size:11px; font-weight:700; color:var(--ink-2); letter-spacing:.02em}
.f label .req{color:var(--bad)}
.f input,.f select,.f textarea{
  height:38px; padding:0 11px; width:100%;
  border:1px solid var(--line-in); border-radius:var(--r-sm);
  background:var(--card-2); color:var(--ink);
}
.f textarea{height:auto; padding:9px 11px; resize:vertical; line-height:1.5}
.f input[readonly]{background:var(--sunk); color:var(--ink-2); cursor:default}

/* ── Isian WAJIB yang ditinggalkan kosong ──────────────────────────────
   Bintang merah di label mengatakan isian ini wajib; ini mengatakan ia
   BELUM diisi. Sampai sekarang orang baru mengetahuinya saat menekan
   Terbitkan dan mendapat toast — jauh sesudah ia berpindah dari isiannya,
   dan tanpa petunjuk isian mana yang dimaksud kalau formnya panjang.

   Kekhususannya sengaja dinaikkan lewat `.f.f-salah`: `.f input[readonly]`
   di atas bernilai (0,2,1), dan aturan dengan satu kelas saja akan kalah
   lalu mati diam-diam — persis jebakan yang pernah menghapus ikon kalender.

   Warna dari token, tidak ada heks: --bad untuk garis dan teks, --bad-bg
   untuk latarnya. Latarnya sengaja sangat muda; merah pekat pada isian
   yang sedang diketik membuat tulisannya sendiri sulit dibaca. */
.f.f-salah input, .f.f-salah select, .f.f-salah textarea,
.prj-f.f-salah input, .prj-f.f-salah select, .prj-f.f-salah textarea{
  border-color:var(--bad); background:var(--bad-bg)}
.f.f-salah input:focus, .f.f-salah select:focus, .f.f-salah textarea:focus,
.prj-f.f-salah input:focus, .prj-f.f-salah select:focus, .prj-f.f-salah textarea:focus{
  border-color:var(--bad); box-shadow:0 0 0 3px var(--bad-line)}
.f-nota-salah{display:block; margin-top:4px; font-size:11.5px;
  line-height:1.35; color:var(--bad)}
.f input[data-mono],.f input.mono{font-family:var(--font-mono); font-variant-numeric:tabular-nums}
/* Checkbox diisolasi dari aturan input di atas — tanpa ini kotaknya
   ikut menerima padding & border-radius lalu terlihat "bergetar" saat fokus. */
input[type=checkbox]{width:auto;height:auto;padding:0;border:none;border-radius:0;background:none;accent-color:var(--blue)}
.f.err input,.f.err select,.f.err textarea{border-color:var(--bad); background:var(--bad-bg)}
.f .err-msg{font-size:11px; color:var(--bad); display:none}
.f.err .err-msg{display:block}

/* ── Pil status ───────────────────────────────────────────────────────── */
.pil{
  display:inline-flex; align-items:center; gap:5px;
  font-size:10.5px; font-weight:700; padding:3px 9px; border-radius:99px;
  white-space:nowrap; border:1px solid transparent; letter-spacing:.01em;
}
.pil-draf{background:var(--sunk); color:var(--ink-2); border-color:var(--line-2)}
.pil-proses{background:var(--info-bg); color:var(--info); border-color:var(--info-line)}
.pil-tunggu{background:var(--warn-bg); color:var(--warn); border-color:var(--warn-line)}
.pil-selesai{background:var(--ok-bg); color:var(--ok); border-color:var(--ok-line)}
.pil-batal{background:var(--bad-bg); color:var(--bad); border-color:var(--bad-line)}
/* Pil status membawa IKON, bukan hanya warna. Sekitar satu dari dua belas
   laki-laki sulit membedakan merah dan hijau; status yang hanya dibedakan
   warna berarti tidak terbaca oleh mereka. Ikonnya disisipkan lewat CSS
   supaya 14 form tidak perlu diubah satu per satu. */
.pil::before{font-size:11px; line-height:1}
.pil-draf::before{content:'○'}
.pil-proses::before{content:'◐'}
.pil-tunggu::before{content:'◔'}
.pil-selesai::before{content:'✓'}
.pil-batal::before{content:'✕'}

/* ── Tabel ────────────────────────────────────────────────────────────── */
/* `display:flex` bukan kosmetik — ia yang membuat tabel berisi sedikit
   memenuhi wadahnya. Alasan lengkapnya di `table.tbl` di bawah. */
.tbl-wrap{overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-sm);
  display:flex}

/* ── Batang gulir mendatar KEDUA, di ATAS tabel ────────────────────────
   Peramban menaruh batang gulir di DASAR wadah yang menggulir. Pada tabel
   68 baris, dasar itu ada 68 baris di bawah: untuk melihat kolom Toko pada
   baris PERTAMA, orang harus turun ke dasar, menyeret ke kanan, lalu naik
   lagi — dan barisnya sudah lama hilang dari pandangan. Dilaporkan MRI,
   8 Sep 2026.

   LENGKET tepat di bawah bilah atas: kalau tidak, ia cuma memindahkan
   masalahnya dari dasar tabel ke puncak tabel. z-index-nya di bawah kepala
   tabel yang juga lengket, supaya keduanya tidak berebut tempat. */
.tbl-gulir-atas{overflow-x:auto; overflow-y:hidden; height:14px;
  position:sticky; top:calc(var(--topbar-h) + 2px); z-index:1;
  background:var(--card); border:1px solid var(--line); border-bottom:0;
  border-radius:var(--r-sm) var(--r-sm) 0 0}
.tbl-gulir-atas > div{height:1px}
/* Wadahnya kehilangan sudut atasnya supaya keduanya terbaca satu benda. */
.tbl-gulir-atas + .tbl-wrap{border-top-left-radius:0; border-top-right-radius:0}

/* ── JARAK KE KEPALA TABEL (16 Sep 2026) ──────────────────────────────
   Dilaporkan MRI: batang gulirnya menempel persis ke baris kepala, jadi
   pegangan gulir dan judul kolom berdempetan dan keduanya sama-sama sulit
   dikenali. Keduanya TETAP satu benda — yang ditambahkan cuma ruang
   bernapas di dalamnya.

   Jaraknya ditaruh sebagai padding pada WADAH TABEL, bukan sebagai
   padding-bottom pada batangnya. Peramban menaruh batang gulir di tepi
   KOTAK PADDING, bukan kotak isi, jadi `padding-bottom` pada elemen yang
   menggulir tidak menggeser batangnya satu piksel pun — ia cuma mendorong
   isi yang tidak terlihat di dalamnya. Dicoba lebih dulu, tidak bekerja.

   Hanya saat batangnya BENAR-BENAR tampil. `pasangGulirAtas()` menyembunyikan
   batang lewat atribut `hidden` begitu tabelnya muat, dan tanpa `:not([hidden])`
   jaraknya tetap ada pada tabel yang tidak punya batang gulir sama sekali —
   celah tanpa sebab di puncak setiap tabel sempit. */
.tbl-gulir-atas:not([hidden]) + .tbl-wrap{padding-top:7px}
/* Sel keadaan-kosong yang membentang seluruh kolom. Isinya dipusatkan pada
   LEBAR TABEL, bukan lebar layar — jadi begitu tabelnya lebih lebar daripada
   layar, kalimat "Belum ada pengajuan." mendarat di luar pandangan, dan
   justru keadaan yang paling perlu terbaca malah tidak terlihat.

   Hanya di HP: di layar lebar tabelnya muat, dan di sana teks terpusat lebih
   rapi. Dipaku ke tepi kiri wadah gulirnya supaya ia ikut ke mana pun tabel
   digulir. */
@media (max-width:760px){
  .tbl td[colspan] > .kosong{
    position:sticky; left:0; width:max-content; max-width:86vw;
    text-align:left; margin-right:auto;
  }
}
/* ── LEBAR TABEL MENGIKUTI ISINYA, bukan diperas ke lebar wadah ────────
   `width:100%` memaksa tabel muat ke wadahnya, dan begitu jumlah kolomnya
   banyak setiap `width` pada <th> turun pangkat jadi saran yang diabaikan:
   yang menentukan tinggal lebar kata terpanjang di judulnya. Diukur pada
   Pengajuan — kolom Toko meminta 230 px dan hanya kebagian 78 px, sementara
   "Satuan Permintaan" yang isinya "btg" mendapat 97 px. Persis terbalik.

   Konsekuensinya disadari: tabel berkolom banyak digulir mendatar. Itu
   jujur — kolom selebar 38 px yang isinya nama material tidak.

   ── `min-width:100%` SENDIRIAN TIDAK PERNAH BEKERJA (16 Sep 2026) ────
   Aturan itu sudah ada di sini sejak 8 Sep dan komentarnya menjanjikan
   bahwa tabel berisi sedikit tetap memenuhi wadahnya. Ia keliru sejak
   ditulis: persennya dihitung terhadap kotak pembungkus tabel, sedangkan
   kotak itu sendiri menyusut mengikuti isinya karena `width:max-content`.
   Persennya melingkar lalu jatuh ke lebar isi. Diukur di Data Master tab
   Lokasi, 16 Sep 2026:

       .tbl-wrap  1263 px
       table.tbl  1080 px   <- min-width TERHITUNG 1080px, bukan 1261px

   Akibatnya 183 px bidang putih menganga di kanan tabel, DI DALAM kotak
   bergaris tepi — terbaca seperti tabel yang gagal dimuat. Dilaporkan MRI.
   Tidak ada galat yang bisa menyebutkannya; yang salah cuma layarnya.

   Yang membetulkannya `flex:1 0 auto` beserta `display:flex` pada
   `.tbl-wrap`. Sebagai flex item, ukuran dasar tabel tetap max-content-nya
   (`flex-basis:auto` -> `width:max-content`), lalu `flex-grow:1` membiarkan
   ia MENUMBUHI sisa ruang saat ruangnya berlebih. Saat max-content-nya
   justru melebihi wadah, `flex-shrink:0` menolak memerasnya dan wadah yang
   menggulir — persis perilaku 8 Sep.

   `min-width:100%` DIPERTAHANKAN sebagai jaring pengaman untuk peramban
   yang tidak menumbuhkan flex item tabel; ia tidak merugikan.

   Dibuktikan dengan mengukur KEDUA keadaan sesudah perubahan:

       Lokasi (6 kolom)   wrap 1261  tabel 1261  tidak menggulir
       Barang (8 kolom)   wrap 1261  tabel 1284  MENGGULIR

   dan lebar tiap kolom Barang sama persis dengan sebelum disentuh
   (Spesifikasi 196 px), jadi perapian 8 Sep tidak ikut tergeser. */
table.tbl{width:max-content; min-width:100%; flex:1 0 auto;
  border-collapse:collapse; font-size:13px}
/* Kepala tabel LENGKET. Pada tabel material yang panjang, menggulir ke baris
   ke-40 tanpa ini berarti kehilangan nama kolomnya — dan angka tanpa nama
   kolom adalah angka yang tidak bisa dipercaya. */
table.tbl th{
  /* LEGAP, bukan --card-2 polos — alasan lengkapnya di core/app.css pada
     `.tbl-master thead th`. Ringkasnya: --card-2 ber-alfa 4% sejak palet
     v3, dan kepala tabel yang lengket tetapi tembus pandang tidak menutup
     satu baris pun yang bergulir di bawahnya. */
  background:var(--card);
  background-image:linear-gradient(var(--card-2),var(--card-2));
  text-align:left; font-size:10.5px; font-weight:800;
  letter-spacing:.06em; text-transform:uppercase; color:var(--ink-2);
  padding:9px 10px; border-bottom:1px solid var(--line-2);
  /* ── JUDUL BOLEH MELIPAT ────────────────────────────────────────────
     Dengan `nowrap`, lebar setiap kolom ditentukan panjang JUDULNYA, bukan
     panjang isinya — dan `width` pada <th> cuma jadi saran yang diabaikan.
     Diukur pada Pengajuan: "Satuan Permintaan" memakan 148 px untuk isi
     "btg", sementara "Toko / Vendor" yang berisi nama perusahaan cuma
     kebagian 112 px dari 230 px yang diminta. Persis terbalik.

     Melipat membuat judul panjang memakai dua baris pendek, dan lebar yang
     dibebaskan jatuh ke kolom yang isinya memang panjang. Dilaporkan MRI,
     8 Sep 2026. */
  /* Melipat di SPASI saja, tidak di tengah kata: `overflow-wrap:anywhere`
     memecah "SATUAN PERMINTAAN" jadi "SATU AN PERMI NTAA N" — judul yang
     harus dieja huruf per huruf lebih buruk daripada judul yang lebar. */
  white-space:normal;
  position:sticky; top:0; z-index:2;
}
/* Baris berselang: mata lebih mudah mengikuti satu baris ke kanan. */
table.tbl tbody tr:nth-child(even){background:var(--card-2)}
table.tbl td{padding:7px 10px; border-bottom:1px solid var(--line); vertical-align:middle}
table.tbl tr:last-child td{border-bottom:none}
table.tbl tbody tr:hover{background:var(--blue-soft)}
table.tbl td input,table.tbl td select{
  height:33px; padding:0 8px; width:100%;
  border:1px solid var(--line-2); border-radius:6px; background:var(--card);
}
table.tbl td input:focus,table.tbl td select:focus{border-color:var(--blue-hi)}

/* ── Sel teks tabel: <textarea> yang melipat sampai DUA baris ──────────
   <input> tidak bisa melipat sama sekali — itu sifat elemennya, bukan soal
   CSS — sedangkan nama barang dan nama toko sungguhan berjalan panjang.
   Tingginya dihitung isi (core/item-table.js) dan berhenti di dua baris.

   `resize:none` karena gagangnya di pojok kanan bawah menutupi sudut sel
   dan mengundang orang menyeretnya sampai satu baris setinggi setengah
   tabel. `overflow:hidden` supaya tidak ada batang gulir mini di dalam sel
   — pada tinggi 33 px batang gulir lebih besar daripada isinya. */
table.tbl td textarea{
  min-height:33px; padding:6px 8px; width:100%; display:block;
  border:1px solid var(--line-2); border-radius:6px; background:var(--card);
  font:inherit; font-size:12.5px; line-height:1.35;
  resize:none; overflow:hidden; white-space:pre-wrap; overflow-wrap:anywhere;
}
table.tbl td textarea:focus{border-color:var(--blue-hi)}

/* Kolom LENTUR — yang tidak diberi angka lebar oleh formnya. Ia yang
   seharusnya memakai sisa ruang, dan sejak judul kolom boleh melipat ia
   justru yang runtuh lebih dulu: kolom berangka menuntut lebarnya, dan
   yang tersisa untuk "Material / Deskripsi / Spesifikasi" cuma 38 px.
   Diberi lantai supaya tidak bisa diperas di bawah lebar yang masih bisa
   dibaca; kalau totalnya melebihi layar, tabelnya memang digulir mendatar
   — dan itu jujur, sedangkan kolom selebar 38 px tidak. */
table.tbl th.th-lentur{min-width:230px}
table.tbl td.td-lentur{min-width:230px}
/* Sel terkunci di dalam TABEL tidak diberi latar berbeda.
   Aturan sebelumnya menyalin kebiasaan `.f input[readonly]` dari formulir
   ke dalam tabel, padahal sel <input> yang terkunci di tabel TIDAK pernah
   diberi latar. Akibatnya dua sel yang keadaannya sama persis tampil
   berbeda dalam satu baris — Satuan Pembelian dan Toko berlatar #E9EFF6,
   sementara Qty Pembelian dan Harga Satuan tetap putih. Warna yang berarti
   dua hal berbeda berhenti berarti apa-apa. Dilaporkan MRI, 8 Sep 2026. */
table.tbl td textarea[readonly]{color:var(--ink-2); cursor:default}
.td-num{text-align:right; font-family:var(--font-mono); font-variant-numeric:tabular-nums}
.td-tight{width:1%; white-space:nowrap}

/* ── Nota / callout ───────────────────────────────────────────────────── */
.nota{
  display:flex; gap:9px; padding:11px 12px; border-radius:10px;
  font-size:12px; line-height:1.5; color:var(--ink);
  background:var(--info-bg); border:1px solid var(--info-line);
}
.nota svg{width:15px; height:15px; flex:none; margin-top:2px; color:var(--info)}
.nota-warn{background:var(--warn-bg); border-color:var(--warn-line)}
.nota-warn svg{color:var(--warn)}
.nota-bad{background:var(--bad-bg); border-color:var(--bad-line)}
.nota-bad svg{color:var(--bad)}
.nota-ok{background:var(--ok-bg); border-color:var(--ok-line)}
.nota-ok svg{color:var(--ok)}

/* ── Toast ────────────────────────────────────────────────────────────── */
#toastWrap{
  position:fixed; bottom:20px; left:50%; transform:translateX(-50%);
  z-index:var(--lapis-toast); display:flex; flex-direction:column; gap:8px; align-items:center;
  pointer-events:none; padding:0 12px; max-width:100%;
}
.toast{
  display:flex; align-items:center; gap:9px;
  background:var(--navy); color:#fff; padding:10px 15px; border-radius:var(--r-sm);
  font-size:13px; font-weight:500; box-shadow:var(--shadow-2);
  opacity:0; transform:translateY(10px) scale(.97);
  transition:opacity var(--gerak-lambat) var(--lengkung), transform var(--gerak-lambat) var(--lengkung);
  max-width:min(92vw,480px);
}
.toast.show{opacity:1; transform:none}
/* Ikon, bukan hanya warna — pada layar silau hijau dan merah terlihat sama. */
.toast-ikon{flex:none; width:18px; height:18px; border-radius:var(--r-pil);
  background:rgba(255,255,255,.22); display:grid; place-items:center;
  font-size:11px; font-weight:800; line-height:1}
.toast-teks{min-width:0}
.toast.ok{background:var(--ok)}
.toast.err{background:var(--bad)}
.toast.warn{background:var(--warn)}
/* Toast ber-Urungkan HARUS bisa diklik — pembungkusnya sengaja
   pointer-events:none supaya toast biasa tidak menghalangi klik di
   belakangnya, jadi yang ini mengembalikannya untuk dirinya sendiri. */
.toast-urung{pointer-events:auto; background:var(--navy)}
.toast-aksi{
  flex:none; margin-left:4px; background:none; border:1px solid rgba(255,255,255,.35);
  color:#fff; font-size:12px; font-weight:700; padding:4px 10px;
  border-radius:var(--r-xs); cursor:pointer;
  transition:background var(--gerak-cepat), border-color var(--gerak-cepat);
}
.toast-aksi:hover{background:rgba(255,255,255,.16); border-color:rgba(255,255,255,.6)}

/* ── Modal ────────────────────────────────────────────────────────────── */
.modal-bg{
  position:fixed; inset:0; z-index:var(--lapis-modal); background:rgba(15,23,51,.5);
  display:none; align-items:center; justify-content:center; padding:18px;
}
.modal-bg.show{display:flex}
.modal{
  background:var(--card); border-radius:var(--r); box-shadow:var(--shadow-2);
  width:100%; max-width:460px; max-height:88vh; overflow:auto;
}
.modal-head{padding:15px 18px; border-bottom:1px solid var(--line)}
.modal-head h3{font-size:15px; font-weight:800}
.modal-body{padding:15px 18px}
.modal-foot{padding:13px 18px; border-top:1px solid var(--line); display:flex; gap:9px; justify-content:flex-end}
