/* ---------------------------------------------------------------------------
   Tambahan khusus Whitelist Asset.

   app.css disalin apa adanya dari panel bio-amp dan sengaja tidak disentuh,
   supaya kalau berkas itu diperbarui di sana ia bisa ditimpa ke sini tanpa
   kehilangan apa pun. Semua yang hanya ada di aplikasi ini tinggal di berkas
   kedua ini — dimuat lewat <link> terpisah, persis seperti yang disarankan
   catatan di kepala app.css.

   Bahasa visualnya sama: timbul-tenggelam dari gradien dan bayangan, tanpa
   satu pun berkas gambar.
   --------------------------------------------------------------------------- */

/* ------------------------------------------------------- Daftar di dropzone */

/* Berkas yang sudah dipilih muncul di dalam kotaknya sendiri, bukan sebagai
   satu baris teks di bawahnya. Alasannya: setelah menjatuhkan lima berkas,
   yang ingin diperiksa orang adalah "apakah kelimanya benar" — dan itu tidak
   terjawab oleh tulisan "5 berkas".

   Kotaknya ikut berubah jadi permukaan terangkat saat berisi: cekungan
   menandakan wadah kosong yang menunggu diisi; begitu ada isinya, ia berhenti
   jadi undangan dan mulai jadi daftar. */
.dropzone.has-files {
  background-image: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  border-style: solid;
  border-color: var(--border-soft);
  box-shadow: inset 0 1px 0 var(--bevel-top), var(--lift);
  padding: .75rem;
  align-items: stretch;
  gap: .5rem;
}

.droplist {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  width: 100%;
  text-align: left;
}

.droplist-item {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .3125rem .5rem;
  background-image: linear-gradient(180deg, var(--sunk) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--groove);
  font-size: .8571rem;
}

.droplist-item .icon { color: var(--text-muted); flex: none; }

/* Nama berkas yang memendek sendiri saat sempit. Tanpa min-width:0 flexbox
   menolak menyusutkan anak yang isinya panjang, dan ukuran berkas di kanan
   terdorong keluar kotak. */
.droplist-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--mono);
}

.droplist-size {
  flex: none;
  color: var(--text-muted);
  font-size: .8125rem;
  font-variant-numeric: tabular-nums;
}

/* Tombol buang per baris. Dibuat sebagai <button> di dalam <label> dropzone,
   jadi kliknya harus dihentikan sebelum menembus ke label — kalau tidak,
   membuang satu berkas malah ikut membuka dialog berkas. Penghentian itu ada
   di upload.js; di sini hanya bentuknya. */
.droplist-drop {
  flex: none;
  display: inline-flex;
  align-items: center;
  padding: .125rem .3125rem;
  background: none;
  border: 1px solid transparent;
  border-radius: 3px;
  color: var(--text-muted);
  cursor: pointer;
  font: inherit;
}

.droplist-drop:hover { color: var(--danger); border-color: var(--danger-border); }

/* Baris keterangan yang kosong tidak boleh menyisakan jarak. #drop-hint hanya
   berisi teks setelah ada berkas dipilih; tanpa aturan ini ia tetap memakan
   satu baris kosong di antara kotak dan isian IP. */
#drop-hint:empty { display: none; }

/* -------------------------------------------------------- Keterangan format */

/* Kaki panel unggah: rujukan yang dibaca sekali lalu diabaikan. Dipisahkan
   dari badan panel dengan torehan yang sama seperti .form-foot, supaya ia
   terbaca sebagai catatan kaki dan tidak bersaing dengan tombol Unggah. */
.formats {
  border-top: 1px solid var(--border-soft);
  box-shadow: inset 0 1px 0 var(--bevel-top);
}

/* Kolom kiri dipatok supaya ketiga barisnya sejajar. .kv membiarkan kolom
   pertama selebar isinya, dan di sini isinya berbeda-beda panjang. */
.formats .kv th {
  width: 7.5rem;
  padding-top: .45rem;
  color: var(--text);
  font-weight: 700;
  font-size: .8571rem;
}

.formats .kv td { padding: .3125rem 0 .625rem; }
.formats .kv tr:last-child td { padding-bottom: 0; }

.formats small { margin-top: .2rem; }

/* Deretan ekstensi. Chip <code> sudah bergaya cekung dari app.css; yang
   ditambahkan di sini hanya pembungkus yang bisa turun baris dengan rapi —
   satu baris koma sepanjang lima belas ekstensi tidak bisa dipindai mata. */
.extlist {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
}

.extlist code {
  font-size: .8125rem;
  padding: .1em .45em;
}

/* ----------------------------------------------------------------- URL akses */

/* Alamat unduh + tombol salin. Kotaknya readonly dan seluruh isinya terpilih
   saat diklik: alamat ini selalu disalin utuh, tidak pernah disunting
   sebagian. */
.urlrow { display: flex; gap: .5rem; align-items: stretch; }

.urlrow .input {
  font-family: var(--mono);
  font-size: .8571rem;
}

.urlrow .btn { flex: none; }

/* --------------------------------------------------------------- Fakta asset */

/* Deretan fakta pendek di halaman Kelola: pemilik, tipe, ukuran, hit, tanggal.

   Sebelumnya ini .kv — tabel dua kolom. Bentuk itu benar untuk pasangan
   label/keterangan yang keterangannya berupa kalimat, tapi salah di sini:
   kolom nilainya melar sampai tepi panel padahal isinya "6" atau "314.9 KB",
   jadi tiap baris menyisakan bidang kosong sepanjang setengah panel.

   Dijajar mendatar, lebar yang sama justru terpakai dan keenamnya bisa
   dipindai sekaligus, bukan dibaca menurun.

   Flex, BUKAN grid berkolom sama lebar. Itu percobaan pertama dan hasilnya
   salah: kolom yang sama lebar memaksa "hit: 6" memakan ruang selebar sebuah
   tanggal, sehingga yang keenam tidak kebagian dan turun ke baris bawah
   sendirian. Di sini tiap fakta berangkat dari lebar isinya sendiri
   (flex-basis auto), lalu sisa lebarnya dibagi rata (flex-grow) supaya
   barisnya tetap penuh sampai tepi. Kalau layarnya memang tidak cukup, ia
   turun baris — dan baris bawahnya pun ikut penuh, bukan menyisakan satu
   fakta yatim. */
.factgrid {
  display: flex;
  flex-wrap: wrap;
  gap: .875rem 1.5rem;
  margin: 0;
}

.factgrid > div { flex: 1 1 auto; }

.factgrid dt {
  display: flex;
  align-items: center;
  gap: .3rem;
  margin-bottom: .1rem;
  color: var(--text-muted);
  font-size: .8125rem;
}

.factgrid dt .icon { flex: none; }

/* break-word, bukan anywhere: yang terakhir ikut menurunkan lebar min-content
   sebuah fakta, jadi flex jadi lebih rela memampatkannya dan tanggal terbelah
   dua baris padahal barisnya masih muat. Ini hanya menolong kalau ada satu
   kata yang memang lebih panjang dari kotaknya — MIME panjang, misalnya. */
.factgrid dd {
  margin: 0;
  font-weight: 700;
  font-size: .9375rem;
  overflow-wrap: break-word;
}

/* ------------------------------------------------------------------- Jalan pulang */

/* Tautan kembali di atas halaman Kelola. Sengaja bukan .btn: ini navigasi,
   bukan tindakan, dan tombol di sebelah judul akan bersaing dengan saklar
   aktif/nonaktif yang memang tombol. */
.crumb {
  margin: 0 0 .75rem;
  font-size: .8571rem;
}

.crumb a {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  color: var(--text-muted);
  text-decoration: none;
}

.crumb a:hover { color: var(--text); text-decoration: underline; }

/* ------------------------------------------------------------------ Pratinjau */

/* Gambar kecil di halaman kelola. Latarnya cekung supaya berkas dengan
   transparansi (PNG, WebP) tetap punya bidang yang terbaca di belakangnya. */
.preview {
  display: block;
  max-width: 100%;
  max-height: 16rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-image: linear-gradient(180deg, var(--sunk) 0%, var(--surface-2) 100%);
  box-shadow: var(--groove);
  padding: .25rem;
}

/* Pratinjau PDF: bingkai, bukan gambar. Tingginya dipatok — isinya bergulir
   sendiri, dan membiarkannya setinggi dokumen akan membuat satu berkas 40
   halaman mengubur seluruh panel di bawahnya. max-height dari .preview tidak
   berlaku di sini: iframe tidak menyusut mengikuti isinya seperti <img>. */
.preview-doc {
  width: 100%;
  height: 26rem;
  max-height: 70vh;
  padding: 0;
}

/* ------------------------------------------------------------------- Widget */

/* Daftar pilihan bercentang: widget yang dipasang, lalu mode URL akses.
   Tiap baris jadi kartunya sendiri, bukan sekadar deretan centang, karena yang
   dibaca orang di sini bukan namanya melainkan keterangannya — penanda, posisi
   penyisipan, akibat memilih mode. Itu tiga baris teks per pilihan, dan tiga
   baris teks tanpa bidang yang membatasinya menyatu dengan tetangganya. */
.picklist {
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.picklist-item {
  padding: .5rem .625rem;
  background-image: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: inset 0 1px 0 var(--bevel-top);
}

/* Kartu mode URL adalah <label> — seluruh bidangnya bisa diklik. Kartu widget
   adalah <div> karena memuat <select> di dalamnya, jadi yang bisa diklik hanya
   labelnya. Penunjuk tangan hanya dipasang pada yang memang begitu. */
label.picklist-item,
.picklist-item .check { cursor: pointer; }

.picklist-item:hover { border-color: var(--text-muted); }

/* Pemilih letak di dalam kartu widget. Rata dengan teks di sebelah kotak
   centang, bukan dengan tepi kartu: ia keterangan lanjutan dari widget itu,
   bukan pilihan yang berdiri sendiri. */
.picklist-opt {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .375rem .5rem;
  margin-top: .5rem;
  padding-left: 1.5rem;
}

.picklist-opt > label {
  font-size: .8125rem;
  font-weight: 400;
  color: var(--text-muted);
  margin-bottom: 0;
}

/* .input dari app.css melebar penuh; di sini ia berbagi baris dengan labelnya. */
.picklist-opt .input {
  flex: 1 1 12rem;
  width: auto;
  min-width: 0;
  max-width: 22rem;
}

/* min-width:0 supaya keterangan panjang memendek di dalam kartunya, bukan
   mendorong kotak centang keluar. */
.picklist-body {
  flex: 1 1 auto;
  min-width: 0;
}

.picklist-name {
  display: block;
  font-weight: 700;
  font-size: .8571rem;
}

.picklist-body small { display: block; margin-top: .15rem; }
.picklist-body small.mono { font-family: var(--mono); font-size: .8125rem; }

.widget-conditional[hidden] { display: none !important; }

/* Jarak sebelum judul kelompok kedua. Judulnya .form-section-title yang tidak
   punya margin atas sendiri — di tempat lain ia selalu jadi yang pertama. */
.picklist-gap { margin-top: 1rem; }

.form-section-title-help {
  position: relative;
  display: flex;
  align-items: center;
  gap: .4rem;
}

.help-tip {
  position: static;
  display: inline-grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  border: 1px solid var(--border-soft);
  border-radius: 50%;
  background: var(--sunk);
  color: var(--link);
  font: 700 .75rem/1 var(--sans);
  text-transform: none;
  letter-spacing: normal;
  cursor: help;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .35);
}

.help-tip:hover,
.help-tip:focus-visible {
  border-color: var(--link);
  outline: none;
}

.help-tip-content {
  position: absolute;
  z-index: 30;
  left: 0;
  bottom: calc(100% + .5rem);
  width: min(22rem, calc(100vw - 3rem));
  padding: .625rem .75rem;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--head-2);
  box-shadow: var(--lift-strong);
  color: var(--text);
  font: 400 .8125rem/1.45 var(--sans);
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: translateY(.2rem);
  transition: opacity .12s ease, transform .12s ease, visibility .12s;
}

.help-tip:hover .help-tip-content,
.help-tip:focus .help-tip-content,
.help-tip:focus-visible .help-tip-content {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}

/* Pemilih letak yang terlipat di dalam kartu widget.

   Diratakan dengan teks di sebelah kotak centang (padding kiri 1.5rem), sama
   seperti .picklist-opt: ia keterangan lanjutan dari widget itu, bukan pilihan
   yang berdiri sendiri.

   Segitiga bawaan peramban dibiarkan — sama alasannya dengan <details> di kaki
   panel: satu baris kecil tidak cukup penting untuk digambari caret sendiri. */
.widget-opt {
  margin-top: .5rem;
  padding-left: 1.5rem;
  font-size: .8125rem;
  color: var(--text-muted);
}

.widget-opt > summary {
  cursor: pointer;
  list-style-position: inside;
}

.widget-opt > summary:hover { color: var(--text); }
.widget-opt[open] > summary { margin-bottom: .375rem; }

/* Nilai yang sedang berlaku, tercetak di baris ringkasnya supaya lipatannya
   tidak perlu dibuka hanya untuk membacanya. */
.widget-opt-nilai { color: var(--text); }

/* Ditimpa khusus untuk asset ini: diberi warna supaya keadaan yang menyimpang
   dari bawaan tidak lenyap di balik lipatan. */
.widget-opt.is-custom .widget-opt-nilai {
  color: var(--warn);
  font-weight: 700;
}

/* .input dari app.css melebar penuh; di sini ia berdiri sendiri di bawah
   ringkasannya, jadi cukup selebar isinya dengan batas atas. */
.widget-opt select {
  width: auto;
  max-width: 22rem;
}

/* Daftar yang boleh panjang bergulir di dalam kotaknya sendiri.

   Instalasi dengan puluhan widget membuat halaman Kelola jadi gulungan tak
   berujung, dan yang paling dirugikan justru bagian "terpasang" di atasnya —
   ia terdorong keluar layar oleh katalog yang jarang dibaca. Batas tingginya
   dipilih supaya kira-kira empat kartu terlihat: cukup untuk tahu ini daftar
   yang bisa digulir, tanpa menelan seluruh panel.

   Padding kanan menyediakan ruang untuk batang gulir supaya ia tidak menindih
   tepi kartu, dan hanya muncul saat isinya memang melebihi batas. */
.picklist-scroll {
  max-height: 22rem;
  overflow-y: auto;
  padding-right: .25rem;
}

/* Kotak kode widget khusus. Cekung seperti .code-block, dan monospace supaya
   indentasi kode terbaca — .input dari app.css memakai fon teks biasa. */
textarea.input.mono {
  font-family: var(--mono);
  font-size: .8125rem;
  line-height: 1.5;
  resize: vertical;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

/* Keterangan penanda: terlipat, dibuka kalau dicari.

   app.css sudah punya dua bentuk <details>: baris pohon dan kepala panel, dua-duanya
   membuang segitiga bawaan dan menggambar caretnya sendiri. Yang ini tidak —
   segitiga bawaan peramban sudah cukup untuk satu baris kecil di kaki panel, dan
   menggambar caret sendiri di sini hanya menambah gaya untuk hal yang tidak
   dilihat orang. */
.formats > summary {
  cursor: pointer;
  list-style-position: inside;
}

.formats > summary:hover { color: var(--text); }
.formats[open] > summary { margin-bottom: .5rem; }

/* Tabel penanda memakai kaki panel .formats yang sama seperti keterangan
   format di halaman unggah, tapi isinya kolom kirinya <code>{{widget:slug}}</code>
   dan bukan satu kata. Lebar 7.5rem di .formats itu hanya usul; nowrap yang
   membuat tabelnya benar-benar memberi kolom itu ruang yang dibutuhkan. */
.formats .kv th code {
  font-size: .8125rem;
  padding: .1em .45em;
  white-space: nowrap;
}

/* --------------------------------------------------------------------- Aturan IP */

/* Daftar IP memakai lebar seadanya: kolom aturan cukup selebar teksnya,
   sisanya diberikan ke keterangan. Tanpa ini kolom aturan meregang sampai
   sepertiga tabel padahal isinya paling panjang 18 karakter. */
.table .ip-rule { width: 1%; white-space: nowrap; font-family: var(--mono); }

/* Kolom user agent. Isinya satu baris panjang tanpa spasi yang berguna, jadi
   ia harus dipatok dan dipendekkan — tanpa itu satu Mozilla/5.0 yang lengkap
   meregangkan tabelnya sampai kolom lain tidak terbaca.

   Dipakai bersama oleh tabel di halaman Kelola dan halaman Log. Sebelumnya
   keduanya memakai gaya sebaris dengan angka yang berbeda (16rem dan 15rem)
   padahal isinya kolom yang sama persis. */
.table .ua { max-width: 16rem; }

/* --------------------------------------------------- Alat terlipat di dalam sel */

/* Kontrol yang jarang dipakai, dilipat di dalam sel tabel — mis. "Ganti
   password" di halaman User.

   Sebelumnya kotak passwordnya berdiri sendiri sebagai baris tabel penuh untuk
   SETIAP user, dan selalu terbuka: pada sepuluh akun yang terlihat sepuluh
   kotak password menganga, mengaburkan daftarnya sekaligus mengundang salah
   ketik pada baris yang salah.

   Diratakan ke kanan mengikuti deretan tombol di atasnya, dan diberi jarak
   supaya tidak menempel. Segitiga bawaan peramban dibiarkan — satu baris kecil
   di dalam sel tidak cukup penting untuk digambari caret sendiri. */
.rowtool {
  margin-top: .375rem;
  font-size: .8125rem;
  color: var(--text-muted);
  text-align: right;
}

.rowtool > summary {
  cursor: pointer;
  list-style-position: inside;
}

.rowtool > summary:hover { color: var(--text); }
.rowtool[open] > summary { margin-bottom: .375rem; }

/* Isinya kembali rata kiri: kotak isian yang rata kanan sulit dibaca, dan
   deretan kontrolnya butuh titik awal yang tetap. */
.rowtool .btn-row {
  justify-content: flex-end;
  text-align: left;
}

/* .input dari app.css melebar penuh; di sini ia berbagi baris dengan tombol. */
.rowtool .input {
  flex: 0 1 11rem;
  width: auto;
  min-width: 0;
}

/* -------------------------------------------------------- URL Original di tabel */

/* Baris kedua di bawah nama berkas pada tabel Asset saya.

   Dipendekkan dengan ellipsis, bukan dibiarkan turun baris: alamat panjang yang
   membungkus dua-tiga baris membuat tinggi tiap baris tabel berbeda-beda dan
   deretan angka di kolom kanan berhenti sejajar. Alamat lengkapnya tetap
   terbaca lewat title, dan tautannya membuka alamat aslinya.

   max-width dipatok pada elemennya sendiri, bukan pada sel: sel tabel melar
   mengikuti isi, jadi ellipsis tidak akan pernah aktif kalau batasnya ditaruh
   di sana.

   Angkanya kecil dengan sengaja. Percobaan pertama memakai 20rem — cukup lebar
   untuk memuat alamat seperti "tool-kit-web.vercel.app/tools/text-editor" utuh,
   dan justru itu masalahnya: ellipsis tidak pernah aktif, tabelnya melebar
   melewati layar, dan .table-wrap menjawabnya dengan batang gulir mendatar.
   min() menjaga agar pada layar sempit batasnya ikut mengecil, bukan tetap
   12rem yang di ponsel pun masih terlalu lebar. */
.asset-origin {
  display: block;
  max-width: min(12rem, 42vw);
  margin-top: .1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .8125rem;
  font-weight: 400;
  color: var(--text-muted);
}

.asset-origin a { color: inherit; text-decoration: none; }
.asset-origin a:hover { color: var(--link); text-decoration: underline; }

/* Label tetap utuh di data, tetapi tidak boleh memaksa tabel melebar. */
.label-clip {
  display: inline-block;
  max-width: min(14rem, 40vw);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

.chip.label-clip { display: inline-block; }

.table .label-clip { max-width: min(10rem, 28vw); }

/* Pencarian dan filter label berbagi satu toolbar. */
.asset-search-form { min-width: 0; }
.asset-search-form input[type="search"] { width: 14rem; }

.label-filter-select {
  width: 13rem;
  min-width: 0;
  text-overflow: ellipsis;
}

/* Tabel asset harus mengikuti lebar panel, bukan lebar minimum isi sel. */
.asset-table-wrap { overflow-x: hidden; }
.asset-table { table-layout: fixed; }
.asset-table th,
.asset-table td { overflow: hidden; }
.asset-table th:nth-child(1) { width: 24%; }
.asset-table th:nth-child(2) { width: 17%; }
.asset-table th:nth-child(3) { width: 12%; }
.asset-table th:nth-child(4) { width: 6%; }
.asset-table th:nth-child(5) { width: 7%; }
.asset-table th:nth-child(6) { width: 9%; }
.asset-table th:nth-child(7) { width: 16%; }
.asset-table th:nth-child(8) { width: 9%; }

.asset-name-link,
.asset-mime {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.asset-table .asset-origin,
.asset-table .label-clip { max-width: 100%; }
.asset-table .asset-action { overflow: visible; }
.asset-table .asset-action .btn { max-width: 100%; }

@media (max-width: 48rem) {
  .asset-table th:nth-child(3),
  .asset-table td:nth-child(3),
  .asset-table th:nth-child(4),
  .asset-table td:nth-child(4),
  .asset-table th:nth-child(5),
  .asset-table td:nth-child(5),
  .asset-table th:nth-child(7),
  .asset-table td:nth-child(7) { display: none; }

  .asset-table th:nth-child(1) { width: 38%; }
  .asset-table th:nth-child(2) { width: 30%; }
  .asset-table th:nth-child(6) { width: 20%; }
  .asset-table th:nth-child(8) { width: 12%; }
  .asset-action-text { display: none; }
}

@media (max-width: 34rem) {
  .asset-search-form { width: 100%; }
  .asset-search-form input[type="search"],
  .asset-search-form .label-filter-select { flex: 1 1 10rem; width: auto; }
}

/* ------------------------------------------------------------------- Jendela */

/* Jendela "Cara Pakai" di halaman Asset saya.

   Warnanya diambil dari token yang sama dengan panel — permukaan gelap
   terangkat di atas alas yang digelapkan. Versi pertamanya putih dengan teks
   #333 dan terasa seperti potongan aplikasi lain yang tertempel di tengah
   halaman.

   .modal[hidden] ditulis eksplisit dan HARUS ada. Atribut `hidden` bawaan
   peramban hanyalah aturan display:none di stylesheet UA, dan aturan penulis
   mana pun mengalahkannya — termasuk display:flex di baris berikutnya. Tanpa
   baris ini jendelanya tampil terus sejak halaman dimuat, tidak peduli
   atribut hidden-nya masih terpasang. */
.modal[hidden] { display: none; }

.modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 100;
}

/* Alas gelap sekaligus sasaran klik untuk menutup. */
.modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .62);
}

.modal-content {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 44rem;
  max-height: 85vh;
  background-image: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--lift-strong);
  color: var(--text);
}

/* Kepala dan kaki dipatok (flex: none), badannya yang bergulir — kalau seluruh
   kotak yang bergulir, judul dan tombol Tutup ikut hilang ke atas persis saat
   dibutuhkan. */
.modal-head,
.modal-foot { flex: none; }

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .625rem .875rem;
  background-image: linear-gradient(180deg, var(--head) 0%, var(--head-2) 100%);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: inset 0 1px 0 var(--bevel-top);
}

.modal-title {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .9285rem;
  font-weight: 700;
  text-shadow: 0 1px 0 var(--bevel-top);
}

.modal-close {
  flex: none;
  padding: 0 .3rem;
  background: none;
  border: 0;
  color: var(--text-muted);
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.modal-close:hover { color: var(--text); }

.modal-body {
  overflow-y: auto;
  padding: .875rem;
}

.modal-body > :first-child { margin-top: 0; }
.modal-body > :last-child { margin-bottom: 0; }
.modal-body ol,
.modal-body ul { margin: .5rem 0; padding-left: 1.25rem; }
.modal-body li { margin: .25rem 0; }

/* Judul kelompok di dalam badan jendela ikut .form-section-title dari app.css;
   yang ditambahkan di sini hanya jaraknya, karena di sana ia selalu jadi yang
   pertama dan tidak punya margin atas sendiri. */
.modal-body .form-section-title { margin-top: 1.125rem; }

.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: .5rem;
  padding: .75rem .875rem;
  border-top: 1px solid var(--border-soft);
  box-shadow: inset 0 1px 0 var(--bevel-top);
}

/* Blok kode di dalam jendela: bidang cekung, sama seperti kotak isian dan
   chip <code> di seluruh panel. Bergulir mendatar sendiri — baris PHP yang
   panjang tidak boleh melebarkan jendelanya. */
.code-block {
  margin: .75rem 0;
  padding: .625rem .75rem;
  background-image: linear-gradient(180deg, var(--sunk) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--groove);
  overflow-x: auto;
}

.code-block pre { margin: 0; }

/* app.css memberi SETIAP <code> bentuk chip: latar cekung, garis tepi, dan
   box-shadow inset. Di dalam .code-block bentuk itu harus dicabut seluruhnya —
   termasuk box-shadow-nya, yang mudah terlewat karena background dan border
   sudah dinolkan. Bayangan yang tertinggal itu terbaca sebagai bidang cekung
   KEDUA di dalam bidang cekung .code-block, persis seperti dua latar bertumpuk.

   Chip <code> di tempat lain dalam jendela ini — misalnya TOKEN_ASSET_ANDA di
   daftar langkah — sengaja dibiarkan apa adanya; di sana bentuk chip itu justru
   yang dimaksud. */
.code-block code {
  display: block;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  color: var(--text);
  font-family: var(--mono);
  font-size: .8125rem;
  line-height: 1.5;
  white-space: pre;
}

/* Bagian yang harus diganti sendiri oleh pemakai. Bukan merah — merah di panel
   ini selalu berarti bahaya atau kegagalan; ini justru satu-satunya bagian yang
   memang harus disentuh. */
.code-block strong {
  color: var(--warn);
  font-weight: 700;
}

/* ------------------------------------------------------------ Langkah unggah */

/* Jarak khusus wizard unggah. Aturan ini dibatasi oleh .upload-page agar
   halaman Kelola yang memakai komponen form dan picklist yang sama tidak ikut
   berubah. */
.upload-page .panel {
  margin-bottom: 1.25rem;
}

.upload-page .panel-head {
  padding: .75rem 1rem;
}

.upload-page .panel-body {
  padding: 1.125rem;
}

.upload-page .field {
  margin-bottom: 1.125rem;
}

.upload-page .field > label {
  margin-bottom: .4rem;
}

.upload-page .field > small {
  margin-top: .4rem;
}

.upload-page .form-section-title {
  margin-bottom: .75rem;
}

.upload-page .picklist-gap {
  margin-top: 1.5rem;
}

.upload-page .picklist {
  gap: .75rem;
}

.upload-page .picklist-item {
  padding: .75rem;
}

.upload-page .picklist-opt {
  margin-top: .75rem;
}

.upload-page .form-foot {
  margin-top: 1.25rem;
  padding-top: 1rem;
}

.upload-page .btn-row {
  gap: .75rem;
}

.upload-page form > .btn-block,
.upload-page form > .btn-row:not(.form-foot) {
  margin-top: .5rem;
}

.upload-page #agent-mode-options {
  margin-top: 1.5rem;
}

.upload-page #agent-mode-options .form-section-title {
  margin-top: 0;
}

.upload-page .paste-code-title {
  margin-top: 1.5rem;
}

/* Kode siap tempel di langkah Selesai sangat panjang (puluhan baris, dan bisa
   berulang untuk tiap berkas dalam satu unggahan). Menampilkannya terbentang
   memenuhi layar dan mengubur tombol Salin — padahal pemakai TIDAK perlu
   membaca kodenya, cuma menyalinnya ke berkas PHP. Jadi tampilannya dipendekkan
   ke kotak bergulir.

   Kodenya tetap DIRENDER (bukan display:none / <details> terlipat): tombol
   Salin punya jalur cadangan untuk http biasa yang menyorot lalu execCommand
   'copy' pada elemen <code> yang tampil — menyembunyikannya membuat penyalinan
   gagal diam-diam di konteks non-HTTPS. Kotak yang dipendekkan tetap terpilih
   penuh, jadi Ctrl+C manual pun tetap menyalin seluruh kode. */
.code-block--ringkas {
  max-height: 11.5em;
  overflow: auto;
}

.upload-page .draft-cancel {
  display: flex;
  justify-content: flex-end;
  margin: -.5rem 0 1rem;
}

@media (max-width: 34rem) {
  .upload-page .panel-head,
  .upload-page .panel-body {
    padding-left: .875rem;
    padding-right: .875rem;
  }
}

/* Penunjuk langkah wizard unggah: Berkas → IP → Label → Selesai. Bentuknya
   deretan pil seperti .chip, bukan garis progres — bahasa visual pil sudah
   ada di seluruh aplikasi, dan empat langkah tidak butuh geometri baru.
   Ini <ol>, jadi urutannya tetap terbaca oleh pembaca layar walau penomoran
   bawaannya dibuang. */
.steps {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .375rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.step {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .25rem .7rem .25rem .35rem;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  color: var(--text-muted);
  font-size: .8571rem;
}

/* Nomor langkah dalam bidang cekung — yang aktif dan yang lewat menimpanya. */
.step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  background-image: linear-gradient(180deg, var(--sunk) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border);
  box-shadow: var(--groove);
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
}

.step-num .icon { width: .8rem; height: .8rem; }

.step.is-current {
  background-image: linear-gradient(180deg, var(--primary) 0%, var(--primary-2) 100%);
  border-color: var(--primary-2);
  color: var(--primary-text);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);
  font-weight: 700;
}

.step.is-current .step-num {
  background-image: none;
  background-color: rgba(0, 0, 0, .25);
  border-color: transparent;
  box-shadow: none;
}

.step.is-done { color: var(--ok); border-color: var(--ok-border); }

.step.is-done .step-num {
  background-image: linear-gradient(180deg, var(--ok-bg) 0%, var(--ok-bg-2) 100%);
  border-color: var(--ok-border);
  color: var(--ok);
}

/* Baris yang menandai keadaan "belum ada IP sama sekali". Bukan flash —
   ini bukan hasil dari tindakan barusan, melainkan sifat asset yang berlaku
   terus sampai diperbaiki. */
.notice {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  padding: .625rem .75rem;
  background-image: linear-gradient(180deg, var(--warn-bg) 0%, var(--warn-bg-2) 100%);
  border: 1px solid var(--warn-border);
  border-radius: var(--radius);
  box-shadow: inset 0 1px 0 var(--bevel-top);
  color: var(--warn);
}

.notice .icon { flex: none; margin-top: .15em; }
.notice b { color: var(--text); }
