:root{ --app-font: "Assistant", "Assistant", system-ui, Arial, sans-serif; }

/* Terapkan ke seluruh UI dan elemen Leaflet */
html, body, #map,
.leaflet-container,
.leaflet-container * ,
.panel, .modal, .form-panel,
.leaflet-tooltip,
.nw-status-items, .nw-item, .nw-children {
  font-family: var(--app-font) !important;
}

/* Pastikan angka di dalam marker (FTTH) ikut pakai Assistant */
.nw-laser-divicon, .nw-laser-divicon * {
  font-family: var(--app-font) !important;
}

/* Label nama di bawah marker */
.leaflet-tooltip.nw-label, .leaflet-tooltip.nw-hover-tip {
  font-family: var(--app-font) !important;
}

/* (opsional) perhalus rendering font kecil */
.nw-laser-divicon, .nw-laser-divicon * {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ===== Layout dasar ===== */
html, body, #map { height: 100%; margin: 0; }
body{ font-family: var(--app-font); }
#map{ width: 100%; height: 100%; }

/* ===== Scrollbar tipis (seluruh area yg bisa di-scroll) =====
   Scrollbar bawaan Windows 17px -- terlalu gemuk utk panel sempit spt rekap
   status & panel Area yg cuma ~120-240px: batangnya makan >10% lebar panel.
   8px cukup utk masih bisa diseret mouse.

   Dua mesin, sengaja TIDAK dicampur dalam satu aturan:
   - Chrome/Edge/Safari lewat ::-webkit-scrollbar (lebar bisa dipatok persis).
   - Firefox lewat scrollbar-width, dibungkus @supports supaya TIDAK ikut
     dibaca Chrome: sejak Chrome 121 scrollbar-width MENGALAHKAN
     ::-webkit-scrollbar, jadi kalau ditulis bareng yg 8px di atas mati dan
     kita balik dpt "thin" bawaan Chrome (~11px).

   Yang TIDAK tersentuh aturan ini: popup <select> yg terbuka (#recapFilter
   "Laser", BillDate, Paket). Daftar itu digambar browser di jendela terpisah,
   CSS halaman tidak sampai ke sana -- satu2nya cara menipiskannya adalah
   mengganti <select>-nya dgn panel sendiri spt .area-combo-panel. */
*::-webkit-scrollbar{ width:8px; height:8px; }
*::-webkit-scrollbar-track{ background:transparent; }
*::-webkit-scrollbar-thumb{
  background:#c1c5cb;
  border-radius:8px;
}
*::-webkit-scrollbar-thumb:hover{ background:#9aa0a8; }
*::-webkit-scrollbar-corner{ background:transparent; }

@supports not selector(::-webkit-scrollbar){
  *{ scrollbar-width:thin; scrollbar-color:#c1c5cb transparent; }
}

/* ===== Panel kontrol kanan (tanpa animasi) ===== */
/* Dok panel: kotak yg dipaku di kanan-atas layar, membungkus tombol
   sembunyikan + panel jadi satu grup. Posisi kiri tombol TIDAK lagi
   mengikuti lebar panel otomatis lewat CSS (right:100% pernah dicoba, lihat
   riwayat) -- itu diam2 tidak sinkron & tombolnya tertimpa panel. Sekarang
   applyCollapsed() (app.js) MENGUKUR lebar .panel sungguhan tiap dipanggil
   lalu menulis toggleBtn.style.right langsung, jadi SATU-SATUNYA tempat yg
   perlu benar. right:260px di .toggle-panel-btn di bawah cuma fallback
   (sekali render sebelum app.js jalan) -- kalau .panel berubah lebar lagi,
   cukup ubah width-nya; JS ikut otomatis, fallback CSS ini boleh menyusul
   belakangan tanpa mem-block apa pun. */
#panelDock{
  position: absolute; top: 0; right: 0; z-index: 400;
}

.panel{
  background: rgba(255,255,255,.96);
  padding: 5px;
  /* Lebar TETAP 240px -- nama panjang di rekap status TIDAK melebarkan
     panel; sebaliknya font namanya yg mengecil secukupnya spy tetap 1
     baris (lihat fitRecapNameFonts() di app.js). Kolom waktu (.nw-ts)
     tak ikut mengecil, fontnya fix. */
  width: 240px;
  transform: none !important;
  transition: none !important;
  position: absolute;
  z-index: 400;
  top: 0;
  right: 0;
}
.panel.collapsed{ display: none !important; }

.row{ display:flex; align-items:center; gap:5px; flex-wrap:wrap; margin-bottom:8px;}
input[type="range"]{ width:100%; }

/* right:250px = lebar .panel sungguhan (240 konten + 5+5 padding), sekadar
   fallback sebelum app.js sempat jalan -- lihat catatan #panelDock di atas.
   Nilai yg BENAR-BENAR dipakai ditulis JS (applyCollapsed) lewat inline
   style, yg selalu menang atas baris ini. */
.toggle-panel-btn{
  position: absolute; top: 0; right: 250px; z-index: 400;
  width: 32px; height: 34px;
  border: 0; background: rgba(255,255,255,.96); color:#000; cursor: pointer;
  font-size: 22px; padding-bottom: 4px;
  border-right: 1px solid #eee;
  transition: none !important;
}

/* ===== Leaflet UX ===== */
.leaflet-touch .leaflet-control-layers-toggle { width:30px; height:30px; }

/* Saat overlay Labels dimatikan via LayerToggle */
.labels-hidden .leaflet-tooltip.nw-label{ display:none !important; }
.labels-hidden .leaflet-pane.leaflet-overlay-pane { opacity: 1; }

/* ===== Blok kontrol kustom (disuntik ke Layers) ===== */
.leaflet-control-layers .layers-custom-block{
  margin-top:8px; padding-top:8px; border-top:1px solid #eee; font-size:12px;
}
.leaflet-control-layers .layers-custom-block .title{ margin-bottom:4px; font-weight:600; }
.leaflet-control-layers .layers-custom-block .row{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; }

/* Blok Notifikasi. Dulu satu kotak centang, lalu label + dropdown jeda;
   sekarang dua dropdown (jeda, batas baris) + tombol uji, rata kiri -- tidak
   ada lagi label yang perlu didorong menjauh, jadi tanpa flex-grow. */
.nw-notif-item{
  display:flex;
  align-items:center;
  gap:8px;
  position:relative;
  font:13px/19.5px var(--app-font);
  cursor:pointer;
  padding-left:4px;
}
.nw-notif-jeda{
  flex:0 0 auto;
  padding:2px 4px;
  border:1px solid #d1d5db;
  border-radius:4px;
  background:#fff;
  font:12px/1.2 var(--app-font);
  cursor:pointer;
}
.nw-notif-jeda:disabled{ background:#f3f4f6; color:#9ca3af; cursor:not-allowed; }
/* #nwNotifBaris bukan <select>, melainkan input teks + autocomplete jQuery UI
   (sama pola dgn #area/#source/#ip) -- lebar tak lagi menyesuaikan isi option
   sendiri jadi diberi eksplisit, dan kursornya teks spt input lain, bukan
   pointer bawaan .nw-notif-jeda yg dirancang utk <select>. */
#nwNotifBaris{ width:64px; cursor:text; }
/* Lihat catatan di app.js (blok Notifikasi) -- disembunyikan krn terbukti tak
   berpengaruh ke Android sungguhan, logikanya sengaja dibiarkan hidup. */
.nw-notif-baris-hidden{ display:none; }
#nwNotifBaris:disabled{ cursor:not-allowed; }
/* Tombol uji notifikasi, sebaris di kanan dropdown jeda. Sengaja kecil &
   sewarna dropdown -- ini alat pemeriksa sekali-sekali, bukan aksi utama.
   flex:0 0 auto supaya ia tidak ikut melar dan mendorong dropdown. */
.nw-notif-uji{
  flex:0 0 auto;
  margin:0;
  padding:3px 8px;
  border:1px solid #d1d5db;
  border-radius:4px;
  background:#fff;
  font:11px/1.35 var(--app-font);
  color:#374151;
  cursor:pointer;
}
.nw-notif-uji:hover:not(:disabled){ background:#f9fafb; }
.nw-notif-uji:disabled{ background:#f3f4f6; color:#9ca3af; cursor:not-allowed; }

/* Checkbox jenis Object di bawah radio Jenis Marker.
   Metrik disamakan PERSIS dgn label overlay bawaan Leaflet (Labels/Routes/
   Devices/Distance) supaya kotak centangnya sebaris lurus: label display
   block tanpa padding, input memakai margin .leaflet-control-layers-selector
   (2px 3px 3px 4px, relative, top 1px). Jangan pakai flex+gap di sini --
   itu yang dulu menggeser kotak 4px ke kiri & teks 13px ke kanan. */
.nw-objek-peta{ margin-top:4px; }
.nw-objek-item{
  display:block;
  position:relative;
  padding-right:32px;          /* ruang utk badge jumlah */
  font:13px/19.5px var(--app-font);
  cursor:pointer;
}
.nw-objek-item input{
  margin:2px 3px 3px 4px;
  position:relative;
  top:1px;
}
.nw-objek-jml{
  position:absolute; right:0; top:50%;
  transform:translateY(-50%);
  min-width:22px; padding:0 5px;
  border-radius:999px; background:#e5e7eb;
  font:600 11px/1.5 var(--app-font); text-align:center; color:#374151;
}

/* ===== Label permanen kecil di bawah marker ===== */
.leaflet-tooltip.nw-label{
  background: rgba(255,255,255,.80);
  color: #111;
  border: 1px solid rgba(0,0,0,.2);
  padding: 1px 4px;
  line-height: 1.1;
  border-radius: 3px;
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(0,0,0,.12);
  margin-top: 0px; /* rapat ke marker */
}
.nw-label-name{ font-weight:500; white-space:nowrap; }

/* ===== Label jarak antar titik (opsi layer "Distance") ===== */
.nw-distance-label{
  background: rgba(255,255,255,.9);
  color: #111;
  border: 1px solid rgba(0,0,0,.25);
  border-radius: 3px;
  padding: 1px 5px;
  font: 600 11px/1.3 var(--app-font);
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(0,0,0,.15);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* ===== Hover tooltip di marker ===== */
.leaflet-tooltip.nw-hover-tip{
  background:#fff8d6; color:#000; border:none; border-radius:6px;
  padding:6px 8px; font-size:12px;
  box-shadow:0 4px 14px rgba(0,0,0,.25);
  width:220px;
  overflow:hidden;
  opacity:0.8;
  border:1px solid black;
}
.nw-hover-tip .tt-title{ font-weight:700; margin-bottom:2px; }
.nw-hover-tip .tt-body{ opacity:.9; white-space:pre-line;line-height:0.7 }

/* ===== Modal Detail ===== */
.modal{ position:fixed; inset:0; z-index:2000; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,.45); padding:20px; }
.modal.open{ display:flex; }
.modal-card{ width:min(92vw,400px); background:#fff; border-radius:14px; box-shadow:0 18px 60px rgba(0,0,0,.3); overflow:hidden; color:#111827; font:14px/1.4 var(--app-font); }
.modal-header{ background:#2563eb; color:#fff; font-weight:700; padding:12px 16px; display:flex; align-items:center; justify-content:space-between; }
.modal-header.active{ background:#15803d; }
.modal-header.isolir{ background:#b91c1c; }
.modal-header-right{ display:flex; align-items:center; gap:8px; }
.modal-body{ padding:14px 16px; }
.modal-actions{ padding:12px 16px; display:grid; grid-template-columns:repeat(3,1fr); gap:10px; border-top:1px solid #e5e7eb; background:#fafafa; }
.info-box{ background:#fff8d6; border:1px solid #e8df9a; border-radius:8px; padding:8px 10px; margin-bottom:10px; font:12px/1.35 var(--app-font); }
/* Tabel label : nilai -- 3 kolom (label, titik dua, nilai) spy titik duanya
   sendiri jatuh rata sejajar antar baris, bukan cuma nilainya. */
.info-table{ width:100%; border-collapse:collapse; }
.info-table td{ padding:2px 0; vertical-align:middle; }
.info-table .k{ width:88px; white-space:nowrap; color:#78716c; font-weight:600; }
.info-table td:nth-child(2){ width:10px; color:#78716c; }
.info-table .v{ color:#1c1917; }
/* Baris "Riwayat Bayar": isinya ditarik dari router begitu kartu dibuka
   (lihat pasangRiwayatInfo() di app.js).
   overflow-X juga, bukan cuma Y: 5 kolom nowrap (Tgl/Jam/Nominal/Metode/Ket)
   lebih lebar dari kolom nilai di layar HP -- tanpa ini tabelnya memaksa
   SELURUH kartu melar & tombol aksi ikut kepotong. */
.nw-info-riwayat{ max-height:130px; overflow:auto; font-size:11px; }
/* label "Riwayat Bayar" sejajar baris pertama tabel, bukan melayang di
   tengahnya. */
.info-table tr.riwayat-row td{ vertical-align:top; }
/* 3 kolom, bukan 4: slotnya memang tiga (KTP, Rumah, Router -- lihat
   nwImageSlots() di netwatch_image_lib.php). Dgn 4 kolom, kolom keempat selalu
   kosong dan ketiga gambar jadi lebih sempit dari perlunya. */
.thumbs{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin-top:10px; }
/* wadah kosong tidak boleh menyisakan jarak: titik tanpa gambar sama sekali
   (mis. ODP) akan tampak punya ruang menganga di bawah detailnya. */
.thumbs:empty{ display:none; }
.thumb{ aspect-ratio:4/3; background:#f3f4f6; border:1px solid #e5e7eb; border-radius:8px; display:flex; align-items:center; justify-content:center; font-size:12px; color:#6b7280; overflow:hidden; }
.thumb img{ width:100%; height:100%; object-fit:cover; display:block; }

.btn{ display:inline-flex; align-items:center; justify-content:center; padding: 8px 15px; border-radius:5px; border:0; cursor:pointer; font-family:var(--app-font); }
.btn-blue{ background:#1d4ed8; color:#fff; } .btn-amber{ background:#f59e0b; color:#111827; }
.btn-red{ background:#ef4444; color:#fff; } .btn-green{ background:#10b981; color:#fff; }
.btn-slate{ background:#334155; color:#fff; } .btn-gray{ background:#9ca3af; color:#111827; }
.btn-teal{ background:#0d9488; color:#fff; } .btn-orange{ background:#c2410c; color:#fff; }

/* Badge status ISOLIR/ACTIVE di modal detail titik */
.badge-isolir{
  display:inline-block;
  padding:2px 8px;
  border-radius:10px;
  font-size:11px;
  font-weight:700;
  letter-spacing:.02em;
}
.badge-isolir.active{ background:#dcfce7; color:#15803d; }
.badge-isolir.isolir{ background:#fee2e2; color:#b91c1c; }

/* Pill status UP/DOWN di baris "Status" pada info-table */
.badge-status{
  display:inline-block;
  padding:1px 8px;
  border-radius:10px;
  font-size:11px;
  font-weight:700;
  letter-spacing:.02em;
}
.badge-status.up{ background:#dcfce7; color:#15803d; }
.badge-status.down{ background:#fee2e2; color:#b91c1c; }

.badge-wa{
  display:inline-flex;
  align-items:center;
  gap:5px;
  padding:2px 10px;
  border-radius:10px;
  font-size:11px;
  font-weight:700;
  letter-spacing:.02em;
  background:#25D366;
  color:#fff;
  text-decoration:none;
}
.badge-wa:hover{ background:#1ebe5b; }

/* ===== Overlay Add/Edit (draggable) ===== */
.form-overlay{ position:fixed; inset:0; z-index:2100; display:none; background:rgba(0,0,0,.45); }
.form-overlay.open{ display:block; }
.form-panel{
  position:absolute; width:100%; max-width:340px; max-height:90vh;
  background:#fff; border:1px solid #e5e7eb; border-radius:12px; box-shadow:0 18px 60px rgba(0,0,0,.3);
  overflow:auto;
}
.form-header{ display:flex; align-items:center; justify-content:space-between; padding:10px 12px; background:#2563eb; color:#fff; font-weight:700; cursor:move; }
.form-body{ padding:8px 10px; }
.form-actions{ display:flex; gap:8px; justify-content:flex-end; padding:10px 12px; background:#fafafa; border-top:1px solid #e5e7eb; }

/* Form grid */
.form-grid{ display:grid; grid-template-columns:70px 1fr; column-gap:5px; row-gap:5px; align-items:center; font-family:var(--app-font); }
.form-grid .l{ text-align:right; font-size:12px; color:#374151; padding-right:4px; }
.form-grid .i input, .form-grid .i select, .form-grid .i textarea{
  width:100%; padding:4px 5px; border:1px solid #d1d5db; border-radius:4px; font:12px/1.3 var(--app-font); box-sizing:border-box;
}
.inline-2{ display:grid; grid-template-columns:1fr 1fr; gap:6px; }

/* Grup radio Metode Pembayaran -- kotak sebaris spt select tapi 1 klik pilih,
   tanpa buka dropdown dulu. */
.nw-metode-group{
  display:flex; width:100%; border:1px solid #d1d5db; border-radius:4px; overflow:hidden;
}
.nw-metode-opt{
  flex:1 1 0; display:flex; align-items:center; justify-content:center;
  padding:4px 2px; font:12px/1.3 var(--app-font); color:#374151;
  border-right:1px solid #d1d5db; cursor:pointer; user-select:none; text-align:center;
}
.nw-metode-opt:last-child{ border-right:none; }
.nw-metode-opt input[type="radio"]{ position:absolute; opacity:0; width:0; height:0; margin:0; }
.nw-metode-opt:has(input:checked){ background:#1d4ed8; color:#fff; font-weight:600; }

/* Kolom yg punya centang penyaring di kanan input (Area, Source).
   Input mengambil sisa lebar; min-width:0 supaya ia benar2 mau menciut --
   tanpa itu lebar-minimum bawaan input mendorong centangnya keluar sel. */
.i-filter{ display:flex; align-items:center; gap:6px; }
.form-grid .i-filter > input{ flex:1 1 auto; min-width:0; }

/* Pendamping di kanan input yg BUKAN centang: dropdown asal saran di kolom IP.
   width:auto membatalkan `.form-grid .i select{width:100%}` -- tanpa itu ia
   mengambil seluruh sel dan mendorong input IP habis. */
.form-grid .i-filter > select{ flex:0 0 auto; width:auto; }

/* Centang penyaring. Sengaja kecil & redup: ia pembatas daftar saran, bukan
   isian yg ikut tersimpan. */
.f-filter{
  display:flex; align-items:center; gap:4px;
  flex:0 0 auto;
  font:11px/1.3 var(--app-font);
  color:#6b7280;
  cursor:pointer;
  user-select:none;
  white-space:nowrap;          /* labelnya jangan patah jadi dua baris */
}
/* Aturan umum `.form-grid .i input` memberi width:100% + padding + border ke
   SETIAP input di sel ini. Utk kotak centang itu keliru: ia jadi selebar
   kolom, teksnya terdesak & patah, dan bingkainya tampak seperti kotak isian.
   Semua itu dikembalikan di sini -- selector-nya lebih spesifik (0,2,1)
   sehingga menang atas aturan umum (0,2,1 juga, tapi ditulis belakangan). */
.form-grid .i .f-filter input[type="checkbox"]{
  width:auto;
  flex:0 0 auto;
  margin:0;
  padding:0;
  border:0;
  border-radius:0;
}

/* Kotak Images: 3 kotak berjajar, klik utk pilih gambar + preview.
   Pakai tinggi tetap (bukan aspect-ratio) supaya rapi di semua browser. */
.img-grid{ display:flex; gap:8px; align-items:flex-start; }
.img-slot{ flex:1 1 0; min-width:0; text-align:center; }
.img-slot .img-cap{ display:block; margin-top:3px; text-align:center; font-size:11px; color:#6b7280; }
.img-box{
  position:relative;
  width:100%;
  height:64px;
  box-sizing:border-box;
  border:1px dashed #9ca3af;
  border-radius:6px;
  background:#f9fafb;
  cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  overflow:hidden;
}
.img-box:hover{ border-color:#2563eb; background:#eff6ff; }
.img-box .img-plus{ font-size:22px; color:#9ca3af; line-height:1; }
.img-box .img-prev{
  display:none;
  width:100%; height:100%;
  object-fit:cover;
}
.img-box.has-img .img-plus{ display:none; }
.img-box.has-img .img-prev{ display:block; }
.img-box .img-clear{
  display:none;
  position:absolute; top:2px; right:2px;
  width:18px; height:18px; padding:0;
  border:none; border-radius:50%;
  background:rgba(0,0,0,.6); color:#fff;
  font-size:11px; line-height:18px; cursor:pointer;
}
.img-box.has-img .img-clear{ display:block; }

/* ===== Rekap Status ===== */
/* overflow-x:hidden krn nama tak lagi ditangani lewat ellipsis/scroll --
 * fitRecapNameFonts() (app.js) mengecilkan font nama spy pas di lebar
 * .panel yg tetap 240px. Ini cuma jaga2 sisa piksel pembulatan. */
.nw-status-items{ max-height:50vh; overflow-y:auto; overflow-x:hidden; padding-right:4px; }
/* Placeholder saat load pertama (sblm ada data): supaya panel tak terlihat
 * "terpotong" tanggung (cuma logo+gear) sebelum konten sungguhan masuk.
 * Otomatis lenyap begitu renderStatusRecap() pertama menimpa innerHTML ini. */
.nw-status-loading{ font:12px/1.35 var(--app-font); color:#6b7280; padding:6px 0; }
.nw-item{ display:flex; align-items:center; justify-content:space-between; font:12px/1.35 var(--app-font); cursor:pointer; border-top:1px solid #eee; }
.nw-item:hover{ background:#f3f4f6; }
.nw-section{ font:12px/1.35 var(--app-font); letter-spacing:.08em; color:#6b7280; padding:5px 0; font-weight:800; border-bottom:1px solid #eee; display:none; }

/* Dot status/laser yang bisa berisi angka */
.nw-name{ display:flex; align-items:center; gap:6px; min-width:0; }
.nw-dot{
  display:inline-flex; align-items:center; justify-content:center;
  width:12px; height:12px;
  border-radius:999px; background:#bbb; color:#fff;
  font-size:9px; font-weight:700;
  border:1px solid rgba(0,0,0,.8);
  padding:1px;
}
/* Nama 1 baris, tak pernah pecah baris -- fitRecapNameFonts() (app.js)
   mengecilkan font-size span ini spy muat di sisa lebar sesudah dot &
   kolom waktu (.nw-ts, font fix) dijatah duluan. flex:1 1 auto +
   min-width:0 justru yg dipakai fitRecapNameFonts utk MENGUKUR sisa
   lebar itu (box span ini otomatis dipepetkan flexbox ke lebar yg
   tersisa). ellipsis cuma jaring pengaman kalau nama tetap tak muat
   walau font sudah di titik minimum (RECAP_NAME_FONT_MIN). */
.nw-item .nw-name span:last-child{
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  min-width:0; flex:1 1 auto;
}
.nw-ts{ opacity:.65; font-size:12px; white-space:nowrap; margin-left:8px; flex-shrink:0; }

/* Nama pelanggan yg SEDANG terisolir (address-list ISOLIR router,
 * d.isolir) di panel kanan (OFFLINE/ONLINE) -- sama merah dgn badge-isolir
 * di modal detail & nw-row-isolir-on di NetWatch DataTable, spy konsisten. */
.nw-isolir-text{ color:#b91c1c; font-weight:700; }

/* Anak offline */
.nw-children{ display:none; padding:0 0 0 20px; }
.nw-item.is-open + .nw-children{ display:block; }
.nw-child{ display:flex; align-items:center; justify-content:space-between; font:12px/1.45 var(--app-font); cursor:pointer; }
.nw-child .nw-dot{ width:7px; height:7px; min-width:7px; min-height:7px; margin-right:6px; }
.nw-children .nw-ico{ font-size:14px; opacity:.7; margin-right:3px; line-height:1; }
/* Baris anak (dalam cluster OFFLINE): nama 1 baris juga, sama pola dgn
   .nw-name span:last-child di atas (dipakai fitRecapNameFonts juga). */
.nw-child .nm{ display:flex; align-items:center; min-width:0; flex:1 1 auto; }
.nw-child .nm span:last-child{
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  min-width:0; flex:1 1 auto;
}

/* Badges counter */
/* Tinggi 26px dipatok agar sebaris rata dgn seluruh kontrol lain (Area,
   BillDate, Paket, Laser, tsModeBtn). Dulu tingginya lahir sendiri dari
   padding 3px + teks 12px = 24px, jadi kedua badge duduk 2px lebih pendek
   dari tetangganya. Dgn tinggi tetap + align-items:center, padding vertikal
   tak diperlukan lagi. */
.badge{
  display:inline-flex; align-items:center; font-size:12px; font-weight:600;
  height:26px; box-sizing:border-box;
  padding:0 6px; border-radius:5px; cursor:pointer; user-select:none;
  font-family:var(--app-font);
  transition:filter .12s;
}
/* Satu keluarga dgn #recapFilter/#billDateFilter/#paketFilter/.area-combo:
   latar muda + tepi sedang + teks tua dari rona yg sama (var --f-off-bg/-br/-tx
   dan --f-on-bg/-br/-tx, lihat blok :root "Palet lembut baris filter" di
   atas), BUKAN lagi merah/hijau pekat -- biar baris ini terbaca sbg satu
   tema, bukan status loncengnya sendiri di antara kontrol yg lembut. */
.badge-red{
  background-color:var(--f-off-bg);
  border:1px solid var(--f-off-br);
  color:var(--f-off-tx);
}
.badge-green{
  background-color:var(--f-on-bg);
  border:1px solid var(--f-on-br);
  color:var(--f-on-tx);
}
.badge:hover{ filter:brightness(.98); }
.badge:active{ filter:brightness(.95); }
.badge.is-muted{ opacity:.55; }

/* Ikon marker kustom */
.nw-laser-divicon .nw-laser-ico,
.nw-empty-divicon .nw-hollow{
  box-sizing:border-box;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
}

/* Pointer */
.leaflet-marker-icon{ cursor:pointer; }

#logo{
    cursor: pointer;
    width: 180px;
	height:26px
}

/* Tombol pengaturan server (ikon gear) di kanan atas */
#login {
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: center;
    float: right;
    width: 26px;
    height: 26px;
    /* border ikut dihitung supaya ukurannya benar2 26x26 */
    box-sizing: border-box;
    padding: 0;
    border: 1px solid #333;
    border-radius: 0.25rem;
    color: #333;
}

#login svg{
    display:block;
}

#login:hover{
	background:cornflowerblue;
	cursor:pointer;
	color:white;
}

.leaflet-control-zoomx{

    width:30px;
    height:auto;

    display:flex;
    align-items:center;
    justify-content:center;

    background:#fff;

    border-bottom:1px solid #ccc;

    cursor:pointer;

    font-size:18px;

    user-select:none;
    background: white;
    border-radius: 6px;
    box-shadow: 0 2px 6px rgba(0,0,0,.25);
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    cursor:pointer;
    line-height:1.1;
}

.leaflet-control-zoomx:hover{
    background:#f0f0f0;
}

/* Reload control */
.leaflet-control-reload{
    width: 30px;
    height: 30px;
    background: white;
    border-radius: 6px;
    box-shadow: 0 2px 6px rgba(0,0,0,.25);
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    cursor:pointer;
    line-height:1.1;
}

.nw-table-control-icon{
    width:24px;
    height:24px;
    display:block;
}

/* Cluster control: dua warna saja, dipinjam dari tombol DataTable di atasnya --
 * terang = latar tombolnya (putih), gelap = warna ikonnya (#666). OFF memakai
 * pasangan itu apa adanya, ON menukarnya. Meminjam, bukan mengarang warna
 * ketiga, supaya dua tombol yang berdempetan ini tetap terlihat satu keluarga. */
.nw-cluster-control svg{

/* Search control (tombol input di bawah Cluster) */
}

.nw-search-control{ padding:0; position:relative; }

/* search button (uses same visual language as cluster button) */
.nw-search-control .nw-search-btn{ background:transparent; border:0; padding:6px; display:block; cursor:pointer; }
.nw-search-control .nw-search-btn svg{ width:20px; height:20px; }

/* floating panel that appears beneath the button */
.nw-search-panel{
  position:absolute;
  left:100%;
  top:0;
  margin-left:6px;
  background:#fff;
  box-shadow:0 6px 16px rgba(0,0,0,0.12);
  min-width:220px;
  z-index:1200;
  border-radius:6px;
}
.nw-search-input{
  width:220px;
  padding:6px 8px;
  border:1px solid #e6e6e6;
  border-radius:4px;
  font:12px/1.2 var(--app-font);
}
.nw-search-suggestions{
  display:none;
  margin-top:6px;
  max-height:260px;
  overflow:auto;
}
.nw-search-item{
  display:flex;
  align-items:center;
  gap:8px;
  padding:8px 10px;
  cursor:pointer;
  white-space:nowrap;
  border-radius:4px;
}
.nw-search-item.active, .nw-search-item:hover{ background:#f3f4f6; }

.nw-search-icon{
  width:18px;
  height:18px;
  flex:0 0 18px;
  background-size:contain;
  background-repeat:no-repeat;
  background-position:center;
  opacity:0.95;
  display:flex; align-items:center; justify-content:center; overflow:hidden;
}
.nw-search-icon img{ width:18px; height:18px; display:block; border-radius:999px; object-fit:cover; }

/* Icons per kind (inline SVG via data URI). Colors chosen to contrast */
.nw-kind-Obj{ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%230ea5a4' d='M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5A2.5 2.5 0 1112 6a2.5 2.5 0 010 5.5z'/></svg>"); }
.nw-kind-Area{ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%233b82f6' d='M12 2L3 6v6c0 5 3.8 9.8 9 10 5.2-.2 9-5 9-10V6l-9-4zM7 8h10v5H7V8z'/></svg>"); }
.nw-kind-Source{ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%23f59e0b' d='M4 6h16v2H4zM6 10h12v2H6zM8 14h8v4H8z'/></svg>"); }
.nw-kind-Loc{ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%236b7280' d='M12 2a7 7 0 100 14 7 7 0 000-14zm3.5 3.5L9 15l-1.5-4.5L15.5 5.5z'/></svg>"); }

.nw-search-label{ flex:1 1 auto; overflow:hidden; text-overflow:ellipsis; }



/* existing cluster block continues */
.nw-cluster-control svg{
    display:block;
}

.nw-cluster-control{
    background:#fff;
}

.nw-cluster-control path{
    fill:#666;
}

.nw-cluster-control:hover{
    background:#f0f0f0;
}

.nw-cluster-control.is-on{
    background:#666;
	border: 1px solid white;
}

.nw-cluster-control.is-on:hover{
    background:#555;
}

.nw-cluster-control.is-on path{
    fill:#fff;
}

.nw-reload-control.is-loading{
    font-size:0;
    cursor:default;
}

.nw-reload-control.is-loading::before{
    content:"";
    width:16px;
    height:16px;
    border:2px solid #cbd5e1;
    border-top-color:#2563eb;
    border-radius:50%;
    animation:nwReloadSpin .7s linear infinite;
}

@keyframes nwReloadSpin{
    to{ transform:rotate(360deg); }
}

/* Modal */
#reloadModal {
    position: fixed;
    left:0;top:0;right:0;bottom:0;
    background: rgba(0,0,0,.45);
    display:none;
    z-index:999999;
}
#reloadModal.open { display:flex;align-items:center;justify-content:center; }
#reloadModal .box {
    background:white;padding:16px;border-radius:10px;width:220px;
    font-family:var(--app-font);
    box-shadow:0 6px 20px rgba(0,0,0,.25);
}
#reloadModal .interval-option{
    padding:8px;border:1px solid #ddd;border-radius:6px;
    margin-top:6px;cursor:pointer;text-align:center;
}
#reloadModal .interval-option:hover{background:#eef2ff;}

/* Hide default zoom */
.leaflet-control-zoom { display:none !important; }

/* =========================================
   Reload Popup
========================================= */

.reload-popup-overlay{
  position:fixed;
  inset:0;
  background:rgba(0,0,0,.35);
  z-index:999999;
  display:none;
  align-items:center;
  justify-content:center;
}

.reload-popup-overlay.open{
  display:flex;
}

.reload-popup-box{
  width:300px;
  background:#f3f3f3;
  border-radius:4px;
  padding:14px;
  box-shadow:0 10px 25px rgba(0,0,0,.3);
  font-family:var(--app-font);
}

.reload-popup-title{
  font-size:16px;
  margin-bottom:10px;
  color:#333;
}

.reload-popup-select{
  width:100%;
  height:34px;
  border:1px solid #999;
  border-radius:2px;
  padding:0 8px;
  margin-bottom:16px;
  background:white;
  font-size:14px;
}

.reload-popup-actions{
  display:flex;
  justify-content:space-between;
  gap:8px;
}

.reload-popup-actions button{
  border:none;
  border-radius:3px;
  padding:8px 10px;
  font-size:12px;
  font-weight:700;
  cursor:pointer;
  color:white;
}

.reload-btn-red{
  background:#e74c3c;
}

.reload-btn-gray{
  background:#d6d6d6;
  color:#555 !important;
}

.reload-btn-blue{
  background:#3498db;
}

/* =========================
   Loading Overlay
========================= */

/* Popup konfirmasi (pengganti confirm bawaan browser) */
.nw-confirm-overlay{
  position:fixed;
  inset:0;
  z-index:1000000002;          /* di atas modal & autocomplete */
  display:none;
  align-items:center;
  justify-content:center;
  background:rgba(0,0,0,.45);
}
.nw-confirm-overlay.open{ display:flex; }

/* Overlay form Catat Pembayaran (#nwBayar) -- class TERPISAH dari
 * .nw-confirm-overlay di atas, SENGAJA, dgn z-index LEBIH RENDAH.
 *
 * Sebelumnya #nwBayar ikut memakai .nw-confirm-overlay begitu saja (utk
 * numpang gaya backdrop gelap + flex-center yg sama) -- tapi itu berarti
 * z-index-nya SAMA PERSIS dgn #nwConfirm (dialog Konfirmasi Pembayaran yg
 * dibuka DARI DALAM form ini saat tombol Simpan diklik). Dgn z-index sama,
 * yang menang cuma soal urutan HTML/spesifisitas selektor -- rapuh, dan
 * pernah kebukti: dialog Konfirmasi aktif & menunggu klik tapi tertutup
 * total oleh form Bayar di belakangnya, jadi Promise-nya tidak pernah
 * selesai dan tombol Simpan terlihat "macet" tanpa error apa pun.
 *
 * Class terpisah dgn angka z-index yg SECARA STRUKTURAL lebih kecil
 * menutup celah itu tanpa bergantung urutan HTML/DOM sama sekali -- dialog
 * apa pun yg memakai .nw-confirm-overlay (angkanya lebih besar) SELALU
 * menang atas form ini, titik. */
.nw-bayar-overlay{
  position:fixed;
  inset:0;
  z-index:1000000000;          /* di bawah .nw-confirm-overlay (1000000002) */
  display:none;
  align-items:center;
  justify-content:center;
  background:rgba(0,0,0,.45);
}
.nw-bayar-overlay.open{ display:flex; }
.nw-confirm-box{
  width:min(90vw,340px);
  background:#fff;
  border-radius:10px;
  box-shadow:0 18px 60px rgba(0,0,0,.3);
  overflow:hidden;
  font:13px/1.45 var(--app-font);
  color:#111827;
}
.nw-confirm-title{
  background:#2563eb;
  color:#fff;
  font-weight:700;
  padding:10px 14px;
}
.nw-confirm-msg{
  padding:14px;
  white-space:pre-line;        /* dukung \n pada pesan */
}
.nw-confirm-actions{
  display:flex;
  justify-content:flex-end;
  gap:8px;
  padding:10px 14px;
  background:#fafafa;
  border-top:1px solid #e5e7eb;
}

/* Checkbox "Kirim Tanda Terima via WhatsApp" + textarea preview/edit pesan,
   dipakai khusus oleh nwConfirmBayar() (lihat app.js). */
.nw-confirm-extra{ padding:0 14px 14px; }
.nw-confirm-extra label{
  display:flex;
  align-items:center;
  gap:6px;
  cursor:pointer;
  font-weight:600;
}
.nw-confirm-extra textarea{
  width:100%;
  box-sizing:border-box;
  margin-top:8px;
  font:12px/1.4 var(--app-font);
  padding:8px;
  border:1px solid #d1d5db;
  border-radius:6px;
  resize:vertical;
  white-space:pre-wrap;
}

/* Form pembayaran manual -- box lebih lebar drpd nw-confirm biasa krn isinya
   beberapa input, bukan cuma pesan+2 tombol. Kolom label dilebarkan drpd
   .form-grid bawaan (70px) krn "Tgl & Waktu"/"Keterangan" kepotong di situ. */
.nw-bayar-box{ width:min(92vw,360px); }
.nw-bayar-body{ padding:14px; grid-template-columns:90px 1fr; }

/* Popup "Aktifkan Titik" (pilihan Bayar/Dispensasi, lihat nwPilihAktifkan di
   app.js) -- box sedikit lebih lebar drpd nw-confirm biasa spy 3 tombol
   (Batal/Bayar/Dispensasi) tidak sesak, & boleh wrap di layar sempit. */
#nwAktifkan .nw-confirm-box{ width:min(92vw,380px); }
#nwAktifkan .nw-confirm-actions{ flex-wrap:wrap; }
#nwAktifkanTanggalBox{ padding:0 14px 14px; grid-template-columns:60px 1fr; }

.nw-bayar-riwayat{ padding:0 14px 10px; border-top:1px solid #e5e7eb; }
.nw-bayar-riwayat-title{
  font-size:11px; font-weight:700; color:#6b7280; margin:8px 0 4px;
  text-transform:uppercase; letter-spacing:.02em;
  display:flex; align-items:center; justify-content:space-between;
  cursor:pointer; user-select:none;
}
.nw-bayar-riwayat-arrow{ font-size:18px; line-height:1; transition:transform .15s ease; }
.nw-bayar-riwayat-title.open .nw-bayar-riwayat-arrow{ transform:rotate(180deg); }
/* overflow-x:auto -- tabel riwayat (5 kolom teks + kolom Aksi) lebih lebar
   drpd kotak dialog di layar sempit; TANPA ini kolom Aksi paling kanan
   kepotong diam-diam oleh overflow:hidden milik .nw-confirm-box (bukan
   hilang dari markup, cuma tak kelihatan & tak ke-scroll). */
.nw-bayar-riwayat-list{ max-height:130px; overflow-y:auto; overflow-x:auto; font-size:11px; }
.nw-bayar-riwayat-table{ width:100%; border-collapse:collapse; }
.nw-bayar-riwayat-table th{ text-align:left; color:#6b7280; font-weight:600; padding:2px 4px 2px 0; border-bottom:1px solid #e5e7eb; position:sticky; top:0; background:#fff; white-space:nowrap; }
.nw-bayar-riwayat-table td{ padding:3px 4px 3px 0; border-bottom:1px dashed #eee; color:#1c1917; white-space:nowrap; }
.nw-bayar-riwayat-table td.num{ text-align:right; }
.nw-bayar-riwayat-empty, .nw-bayar-riwayat-muted{ color:#9ca3af; font-style:italic; }
.nw-redaman-naik{ color:#ef4444; }   /* makin negatif = redaman memburuk */
.nw-redaman-turun{ color:#22c55e; }  /* makin ke 0 = redaman membaik */
.nw-redaman-tetap{ color:#9ca3af; }

/* Kolom aksi (Edit/Hapus) di kolom paling kanan tabel riwayat -- ikon saja,
   supaya tidak makan tempat. Lihat nwPasangRiwayatAksi di app.js. */
.nw-bayar-riwayat-table td.nw-riwayat-aksi{ padding-right:0; text-align:right; white-space:nowrap; }
.nw-riwayat-aksi-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:20px; height:20px; padding:0; margin-left:2px;
  border:none; border-radius:4px; background:transparent; cursor:pointer;
  color:#6b7280;
}
.nw-riwayat-aksi-btn:hover{ background:#f3f4f6; color:#1d4ed8; }
.nw-riwayat-aksi-btn.nw-riwayat-aksi-hapus:hover{ color:#dc2626; }

/* Log aktivitas fetch data (kotak kiri bawah).
   Idle: hanya baris terakhir. Saat fetch ATAU di-hover: 5 baris terakhir. */
.nw-activity{
  position:fixed;
  left:10px;
  bottom:10px;
  z-index:1200;
  max-width:min(85vw,380px);
  box-sizing:border-box;
  padding:5px 9px;
  border-radius:7px;
  background:rgba(17,24,39,.82);
  color:#e5e7eb;
  font:11px/1.55 var(--app-font, ui-monospace, monospace);
  box-shadow:0 3px 10px rgba(0,0,0,.3);
  pointer-events:auto;          /* perlu agar hover mengembang */
  cursor:default;
}
.nw-activity:empty{ display:none; }

/* Panel Layers (pojok kiri atas) harus di ATAS log aktivitas ini, bukan di
 * bawahnya. z-index di .leaflet-control-layers-expanded sendiri TIDAK CUKUP:
 * ia diposisikan relative DI DALAM .leaflet-top.leaflet-left (z-index:1000
 * bawaan Leaflet), yang sudah membentuk stacking context sendiri -- z-index
 * berapa pun di dalamnya tetap terjebak di lapisan 1000 itu, kalah dari
 * .nw-activity yang 1200. Yang perlu dinaikkan justru KONTAINERNYA.
 *
 * Dinaikkan tanpa syarat (bukan hanya saat -expanded) supaya cukup satu
 * aturan: saat menciut ia cuma ikon kecil tanpa dampak apa pun, jadi aman. */
.leaflet-top.leaflet-left{ z-index:1300; }
.nw-log-line{
  text-overflow:ellipsis;
}
.nw-log-line .t{ color:#9ca3af; }
.nw-log-line.ok  .m{ color:#86efac; }
.nw-log-line.err .m{ color:#fca5a5; }
/* kuota accesscode terlampaui: bukan error, tapi tetap harus terlihat */
.nw-log-line.warn .m{ color:#fcd34d; }

/* nomor urut hop pada jalur TRACE (1 = pangkal) */
.nw-trace-badge{
  display:flex; align-items:center; justify-content:center;
  width:18px; height:18px; border-radius:999px;
  background:#f59e0b; color:#111827;
  border:1.5px solid #fff; box-shadow:0 0 0 1px rgba(0,0,0,.35);
  font:700 10px/1 var(--app-font,'Assistant',system-ui,Arial,sans-serif);
  pointer-events:none;
}
/* idle (bukan .busy & tidak di-hover): tampilkan hanya baris paling baru */
/* Saat idle log menciut jadi satu baris. Baris TERPAKU (Limited Access &
   countdown) dikecualikan: keduanya menandai keadaan yang masih berlaku,
   jadi harus tetap terlihat justru ketika operator tidak memperhatikan. */
.nw-activity:not(.busy):not(:hover) .nw-log-line:not(:last-child):not(.nw-pinned){ display:none; }

/* Baris Limited Access: amber & diam. Merah sengaja TIDAK dipakai -- merah
   sudah berarti "server putus", dan peringatan yg menetap tidak boleh
   menenggelamkan gangguan sungguhan. */
.nw-log-line.nw-limited{ cursor:pointer; }
.nw-log-line.nw-limited:hover .m{ text-decoration:underline; }

/* Baris pengingat izin notifikasi: sama amber & menetap. Saat izinnya sudah
   DIBLOKIR ia tak bisa diapa-apakan dari sini (hanya lewat setelan situs),
   jadi tampilannya berhenti mengundang ketukan. */
.nw-log-line.nw-izin{ cursor:pointer; }
.nw-log-line.nw-izin:hover .m{ text-decoration:underline; }
.nw-log-line.nw-izin.nw-izin-mati{ cursor:default; }
.nw-log-line.nw-izin.nw-izin-mati:hover .m{ text-decoration:none; }

/* Berdenyut hanya sebentar saat angkanya berubah, lalu diam. */
@keyframes nwPulseLimited{ 0%,100%{ opacity:1; } 50%{ opacity:.35; } }
.nw-log-line.nw-pulse .m{ animation:nwPulseLimited 1s ease-in-out 3; }
@media (prefers-reduced-motion: reduce){
  .nw-log-line.nw-pulse .m{ animation:none; }
}

/* Daftar titik yang disembunyikan (isi modal) */
.nw-hidden-srv{ margin:8px 0 2px; font-weight:700; }
.nw-hidden-info{ margin-bottom:6px; font-size:12px; color:#4b5563; }
.nw-hidden-kosong{ margin:6px 0; font-size:12px; color:#6b7280; font-style:italic; }
.nw-hidden-tabel{ width:100%; border-collapse:collapse; font-size:12px; }
.nw-hidden-tabel th, .nw-hidden-tabel td{
  padding:4px 6px; border-bottom:1px solid #eee; text-align:left;
}
.nw-hidden-tabel th{ background:#f9fafb; font-weight:600; }
.nw-hidden-tabel td:first-child, .nw-hidden-tabel th:first-child{
  width:34px; color:#6b7280;
}

@keyframes nwSpin{
  to{
    transform:rotate(360deg);
  }
}

/* ===== Baris filter: [combo server/area] [dropdown laser] ===== */
#areaFilterRow{
    display: flex;
    align-items: center;
    gap: 5px;
    margin-bottom: 5px;
}

/* ===== Combobox filter Server/Area (1 baris + popup jsTree) =====
   Dipersempit: satu baris kini memuat Area + BillDate + Paket. Area tetap
   yang paling elastis, tapi tidak lagi menghabiskan sisa ruang. */
/* Tiga kontrol berbagi satu baris di panel 250px (~230px setelah padding+gap).
   Area cuma perlu memuat kata "Area", jadi basisnya dikecilkan dan TIDAK
   ikut tumbuh -- sisa ruang diberikan ke BillDate & Paket yang labelnya
   panjang ("BillDate", "Paket (312)") dan dulu terpotong jadi "Bill…"/"Pak…". */
/* Area sengaja jadi satu-satunya yg mengalah: flex-basis 0 + grow, jadi ia
   hanya mengambil SISA setelah BillDate & Paket dapat lebar alaminya. */
.area-combo{
    position: relative;
    flex: 1 1 0;
    min-width: 44px;
    font-family: var(--app-font);
}

.area-combo-input{
    width: 100%;
    height: 26px;
    box-sizing: border-box;
    /* chrome dirampingkan (20px) supaya kata "Area" tetap terbaca walau
       kotaknya menyusut sampai 46px saat nama paket panjang. */
    padding: 0 14px 0 6px;
    font-size: 12px;
    background-color: var(--f-area-bg);
    border: 1px solid var(--f-area-br);
    color: var(--f-area-tx);
    border-radius: 5px;
    font-family: var(--app-font);
}
.area-combo.has-sel .area-combo-input{
    border-color: #1d4ed8;
}
.area-combo-input::placeholder{ color:#374151; opacity:1; }
/* Filter aktif harus TERBACA, bukan cuma tersirat dari warna border: nama
   areanya ditebalkan & diberi warna aksen. Border sendirian terbukti tak
   cukup -- peta yg tersaring tampak sama persis dgn peta utuh. */
.area-combo.has-sel .area-combo-input::placeholder{
    color:#1d4ed8;
    font-weight:700;
}

/* ---- Penanda ringkasan: kotak centang, bukan teks "[v]"/"[-]" ----
   Metriknya sengaja disamakan dgn .jstree-checkbox di panel pohon tepat di
   bawahnya (kotak 14px, border 1px #444, radius 2px, centang & strip hitam),
   supaya lambangnya sama & tak perlu diartikan dua kali. */
.area-combo-mark{
    display:none;
    position:absolute;
    left:5px; top:50%;
    width:14px; height:14px;   /* sama persis dgn .jstree-checkbox::before */
    margin-top:-7px;
    box-sizing:border-box;
    border:1px solid #444;
    border-radius:2px;
    background:#fff;
    pointer-events:none;
}
.area-combo.has-mark .area-combo-mark{ display:block; }
/* beri ruang utk kotaknya supaya tidak menimpa nama area */
.area-combo.has-mark .area-combo-input{ padding-left:22px; }

/* centang hitam = seluruh anak terpilih */
.area-combo-mark.is-full::after{
    content:"";
    position:absolute; top:44%; left:50%;
    width:4px; height:8px;     /* sama persis dgn centang jsTree */
    transform:translate(-50%,-50%) rotate(45deg);
    border:solid #000;
    border-width:0 2px 2px 0;
    box-sizing:border-box;
}
/* strip hitam = sebagian anak dikecualikan */
.area-combo-mark.is-partial::after{
    content:"";
    position:absolute; top:50%; left:50%;
    width:8px; height:2px;     /* sama persis dgn strip undetermined jsTree */
    background:#000;
    transform:translate(-50%,-50%);
}

/* ---- Palet lembut baris filter ----
   Awalnya badge OFF/ON memakai merah & lime MURNI (status, bukan hiasan)
   sementara keempat filter di sekitarnya dibuat jauh lebih muda supaya
   terbaca sbg "kontrol" & tidak bersaing perhatian dgn status. Atas
   permintaan, OFF/ON kini ikut bergabung ke palet lembut yg sama -- baris ini
   jadi terasa SATU keluarga, dan OFF/ON tetap terbedakan lewat rona
   merah/hijau-nya sendiri (bukan lewat kepekatan yg beda sendirian).

   Tiap warna punya tiga tingkat (latar muda / tepi sedang / teks tua) dari
   rona yang sama, jadi kontras teksnya tetap terjaga.

   MERAH tetap dipakai KHUSUS utk OFF: di alat ini merah berarti gangguan,
   dan OFF memang gangguan sungguhan -- beda dgn 4 filter kontrol di sekitarnya
   yg sengaja menghindari merah krn bukan status apa pun. */
:root{
  --f-off-bg:#fef2f2;   --f-off-br:#fecaca;   --f-off-tx:#b91c1c;   /* merah  */
  --f-on-bg:#ecfdf5;    --f-on-br:#a7f3d0;    --f-on-tx:#047857;    /* hijau  */
  --f-area-bg:#eff6ff;  --f-area-br:#bfdbfe;  --f-area-tx:#1e40af;  /* biru   */
  --f-bill-bg:#fff7ed;  --f-bill-br:#fed7aa;  --f-bill-tx:#9a3412;  /* oranye */
  --f-paket-bg:#f0fdf4; --f-paket-br:#bbf7d0; --f-paket-tx:#166534; /* hijau  */
  --f-laser-bg:#faf5ff; --f-laser-br:#e9d5ff; --f-laser-tx:#6b21a8; /* ungu   */
}

/* panah dropdown seragam utk combo, #recapFilter, #billDateFilter */
:root{
    --nw-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23555' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.area-combo-caret{
    position: absolute;
    right: 3px;
    top: 50%;
    transform: translateY(-50%);
    width: 10px;
    height: 6px;
    background: var(--nw-arrow) no-repeat center / 10px 6px;
    cursor: pointer;
    pointer-events: auto;
}

.area-combo-panel{
    position: absolute;
    top: calc(100% + 3px);
    left: 0;
    /* 220 + padding 8 + border 2 = 230px total.
       Ruang dari tepi kiri .area-combo ke tepi kanan layar MINIMAL 255px di
       lebar layar mana pun, sebab .panel menempel kanan dan min-width-nya 250
       (+10 padding). Sejak panel ber-lebar-otomatis angka ini jadi batas bawah,
       bukan angka pasti -- panel boleh melebar, dan itu cuma menambah ruang.
       Dulu 250px -> total 260px -> meluber 15px dan memunculkan
       scrollbar horizontal tiap kali pohon Area dibuka.
       Jangan diganti right:0 utk "mengejar" tepi kanan: .area-combo cuma 50px,
       jadi di layar sempit popup-nya justru meluber ~80px ke KIRI. */
    width: 220px;
    max-width: 70vw;
    background: #fff;
    border: 1px solid #cbd5e1;
    border-radius: 6px;
    box-shadow: 0 8px 24px rgba(0,0,0,.18);
    z-index: 3000;
    padding: 4px;
}

.area-combo-panel #jstree{
    max-height: 40vh;
    overflow: auto;
    font-size: 12px;
}

/* rapatkan tema default jsTree agar muat di panel sempit */
.area-combo-panel .jstree-default .jstree-anchor{ height:22px; line-height:22px; }
.area-combo-panel .jstree-default .jstree-node,
.area-combo-panel .jstree-default .jstree-icon{ min-height:22px; }
.area-combo-panel .jst-count{
    font-size:10px;
    color:#6b7280;
    background:#eef2ff;
    border-radius:8px;
    padding:0 6px;
    margin-left:6px;
}

/* teks hasil pencarian jsTree dibuat standar (bukan italic/merah/tebal) */
.area-combo-panel .jstree-default .jstree-search{
    font-style: normal;
    font-weight: normal;
    color: inherit;
}

/* checkbox jsTree: kotak standar hitam biasa (buang sprite centang hijau tema) */
.area-combo-panel .jstree-default .jstree-checkbox{
    background: none !important;
    position: relative;
}
/* kotak dasar */
.area-combo-panel .jstree-default .jstree-checkbox::before{
    content: "";
    position: absolute; top: 50%; left: 50%;
    width: 14px; height: 14px;
    transform: translate(-50%, -50%);
    box-sizing: border-box;
    border: 1px solid #444;
    border-radius: 2px;
    background: #fff;
}
/* centang hitam saat terpilih penuh */
.area-combo-panel .jstree-default .jstree-clicked > .jstree-checkbox::after{
    content: "";
    position: absolute; top: 44%; left: 50%;
    width: 4px; height: 8px;
    transform: translate(-50%, -50%) rotate(45deg);
    border: solid #000;
    border-width: 0 2px 2px 0;
    box-sizing: border-box;
}
/* strip hitam saat sebagian (undetermined) */
.area-combo-panel .jstree-default .jstree-checkbox.jstree-undetermined::after{
    content: "";
    position: absolute; top: 50%; left: 50%;
    width: 8px; height: 2px;
    background: #000;
    transform: translate(-50%, -50%);
}

#recapFilter{
	flex: 0 0 auto;
	background-color: var(--f-laser-bg);
	border: 1px solid var(--f-laser-br);
	color: var(--f-laser-tx);
	font-family: var(--app-font);
	height: 26px;
    font-size: 12px;
    border-radius: 5px;
    /* panah sama persis dgn caret combo */
    appearance: none;
    -webkit-appearance: none;
    background-image: var(--nw-arrow);
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 10px 6px;
    padding: 0 13px 0 6px;
}

#recapFilter:hover{
  filter:brightness(.98);
}

#recapFilter:focus{
  border-color:#666;
}

/* BillDate & Paket memakai lebar ALAMI-nya (width:auto = selebar opsi
   terpanjang), bukan jatah tetap. Area yg mengambil sisanya dan menyusut
   duluan -- itu urutan prioritas yg diminta.

   BillDate dibiarkan tanpa batas: opsinya selalu pendek & terikat bentuk
   "DD = N ★" (terukur 78px), jadi tak mungkin meledak.

   Paket WAJIB dibatasi: namanya nama profile PPPoE, bisa sepanjang
   "paket-100rb-10M (98)" = 141px. Tanpa batas, 141 + 78 + gap = 229px dari
   230px yg ada -> Area tergencet habis dan baris melipat. 96px = nilai
   terbesar yg masih menyisakan Area 46px dalam satu baris (104px sudah
   melipat). Nama paket pendek spt "160rb (45)" cuma 79px, jadi batas ini
   tidak mengusik kasus umum -- Paket tetap tampil utuh. */
/* Lebar TETAP, bukan width:auto lagi.
   width:auto membuat <select> memesan ruang selebar OPSI TERPANJANG -- ruang
   yg hanya terpakai kalau opsi itu kebetulan dipilih. Akibatnya Paket duduk di
   79px padahal isinya cuma kata "Paket" (29px), sementara kotak Area tergencet
   sampai 27px ruang teks dan namanya terbaca "x86...".

   68px = 46px ruang teks: cukup utk label diam "BillDate" (41px) dan "Paket"
   (29px) dgn margin aman. Sisanya (84px) diberikan ke Area. Nilai terpilih yg
   lbh panjang (mis. "05 = 23 ★" 54px atau nama profile PPPoE) terpotong di
   kotak tertutup, tapi daftar dropdown-nya tetap menampilkan penuh. */
#billDateFilter, #paketFilter{
    flex: 0 0 auto;
    width: 68px;
    min-width: 0;
    text-overflow: ellipsis;
}

#paketFilter{
	height: 26px;
	font-family: var(--app-font);
    font-size: 12px;
    border-radius: 5px;
    background-color: var(--f-paket-bg);
    border: 1px solid var(--f-paket-br);
    color: var(--f-paket-tx);
    appearance: none;
    -webkit-appearance: none;
    background-image: var(--nw-arrow);
    background-repeat: no-repeat;
    background-position: right 5px center;
    background-size: 10px 6px;
    padding: 0 17px 0 5px;
}
#paketFilter:hover{ filter:brightness(.98); }
#paketFilter:focus{ border-color:#666; }

#billDateFilter{
	height: 26px;
	font-family: var(--app-font);
    font-size: 12px;
    border-radius: 5px;
    background-color: var(--f-bill-bg);
    border: 1px solid var(--f-bill-br);
    color: var(--f-bill-tx);
    /* panah sama persis dgn caret combo */
    appearance: none;
    -webkit-appearance: none;
    background-image: var(--nw-arrow);
    background-repeat: no-repeat;
    background-position: right 5px center;
    background-size: 10px 6px;
    padding: 0 17px 0 5px;
}

.leaflet-tooltip-right:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before{
    display:none !important;
}

.hide-labels .leaflet-tooltip.nw-label{
    display:none !important;
}

.nw-parent-octagon{
  position:relative;
  width:18px;
  height:18px;

  display:flex;
  align-items:center;
  justify-content:center;

  color:black;

  z-index:1;
}

.nw-parent-octagon::before{
  content:'';

  position:absolute;
  inset:0;

  background:rgba(0,0,0,0.5);

  clip-path:polygon(
    30% 0%,
    70% 0%,
    100% 30%,
    100% 70%,
    70% 100%,
    30% 100%,
    0% 70%,
    0% 30%
  );

  z-index:-2;
}

.nw-parent-octagon::after{
  content:'';

  position:absolute;
  inset:3px;

  background:rgba(255,255,255,.75);

  clip-path:polygon(
    30% 0%,
    70% 0%,
    100% 30%,
    100% 70%,
    70% 100%,
    30% 100%,
    0% 70%,
    0% 30%
  );

  z-index:-1;
}

.leaflet-control-zoomx{

    overflow:hidden;

    border-radius:6px;

    background:#fff;

    box-shadow:
      0 1px 5px rgba(0,0,0,.3);
}

.leaflet-control-zoomx .zoom-btn{

    width:50px;
    height:30px;

    display:flex;
    align-items:center;
    justify-content:center;

    cursor:pointer;

    font-size:20px;
    font-weight:bold;

    border-bottom:1px solid #ddd;

    background:#fff;
}

.leaflet-control-zoomx .zoom-btn:hover{

    background:#f3f4f6;
}

.leaflet-control-zoomx .zoom-level{
	height:30px;
    display:flex;
    flex-direction:column;

    align-items:center;
    justify-content:center;

    cursor:pointer;
    background:#fff;
}

.leaflet-right .leaflet-control{
	margin-bottom:0px;
	margin-right:5px;
}

.leaflet-control-scale{
    border:none !important;
}

.leaflet-control-scale-line{
	margin-bottom:5px;
    border:none !important;
    background:transparent !important;
    padding: 2px 5px 1px;
    white-space: nowrap;
    overflow: hidden;
	text-align:right;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

.table-modal{
    position:fixed;
    inset:0;
    background:rgba(0,0,0,.45);
    z-index:999999999;
    display:none;
}

.table-modal.open{
    display:flex;
    align-items:center;
    justify-content:center;
}

.table-modal-card{
    /* Lebar mengikuti isi (tabel), bukan 95vw kaku yg menyisakan ruang kosong
       lebar di kanan. max-width tetap mengunci agar tak melewati layar; kalau
       tabelnya lebih lebar, .table-modal-body yg menggulir. */
    width:max-content;
    max-width:95vw;
    height:90vh;
    background:#fff;
    border-radius:8px;
    overflow:hidden;
    display:flex;
    flex-direction:column;
}

/* Titlebar DataTable. min-height (bukan height) supaya header ikut tumbuh
   kalau isinya terpaksa turun baris di layar sempit — dengan height tetap,
   isi meluber keluar kotak dan saling tumpuk. */
.table-modal-header{
    /* proyek ini tanpa reset box-sizing global; tanpa border-box, min-height
       dihitung dari content-box saja lalu padding menumpuk (40 -> 52px). */
    box-sizing:border-box;
    min-height:40px;
    background:#2563eb;
    color:#fff;
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:8px;
    padding:6px 10px;
    /* satu ukuran font untuk seluruh titlebar; kontrol di dalamnya ikut ini */
    font:700 13px/1.35 var(--app-font);
}

/* Hanya tombol tutup. JANGAN pakai selector `.table-modal-header button`:
   spesifisitasnya (0,1,1) mengalahkan .nw-objfilter-btn (0,1,0), sehingga
   tombol filter Object ikut kena font 18px + border/background dicopot. */
#tableModalClose,
#belumTerdaftarClose{
    flex:0 0 auto;
    display:flex;
    align-items:center;
    justify-content:center;
    width:28px;
    height:28px;
    border:none;
    border-radius:4px;
    background:none;
    color:#fff;
    font-size:16px;
    line-height:1;
    cursor:pointer;
}
#tableModalClose:hover,
#belumTerdaftarClose:hover{ background:rgba(255,255,255,.18); }

/* Tombol "Belum Terdaftar": ikon saja, sebaris dgn badge OFF/ON & Laser di
   #nwCounters -- gaya & ukurannya SENGAJA disamakan persis dgn #tsModeBtn
   (26x26, keluarga netral/slate) krn keduanya kontrol utilitas di baris yg
   sama, bukan indikator STATUS spt badge OFF/ON yg pekat & mencolok. */
#btnBelumTerdaftar{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    width:26px;
    height:26px;
    box-sizing:border-box;
    padding:0;
    border:1px solid #cbd5e1;
    border-radius:5px;
    background-color:#f8fafc;
    color:#475569;
    box-shadow:0 1px 0 rgba(0,0,0,.10), inset 0 1px 0 rgba(255,255,255,.6);
    cursor:pointer;
    user-select:none;
    transition:background .12s, box-shadow .12s, transform .06s;
}
#btnBelumTerdaftar .ts-ico{
    width:15px; height:15px;
    flex:0 0 auto;
    fill:none;
    stroke:currentColor;
    stroke-width:1.6;
    stroke-linecap:round;
    stroke-linejoin:round;
    opacity:.9;
}
#btnBelumTerdaftar:hover{
    background-color:#f1f5f9;
    border-color:#94a3b8;
    box-shadow:0 2px 3px rgba(0,0,0,.15), inset 0 1px 0 rgba(255,255,255,.6);
}
#btnBelumTerdaftar:active{
    background-color:#e2e8f0;
    box-shadow:inset 0 1px 2px rgba(0,0,0,.25);
    transform:translateY(1px);
}

.belum-terdaftar-status{
  font:13px/1.4 var(--app-font);
  color:#374151;
  padding:4px 2px 10px;
}

.belum-terdaftar-table{
  width:100%;
  border-collapse:collapse;
  font-size:12px;
}
.belum-terdaftar-table th,
.belum-terdaftar-table td{
  padding:4px 8px;
  border-bottom:1px solid #e5e7eb;
  text-align:left;
  white-space:nowrap;
}
.belum-terdaftar-table thead th{
  position:sticky;
  top:0;
  background:#f5f5f5;
  font-weight:600;
}
.belum-terdaftar-table td.belum-terdaftar-aksi{ white-space:nowrap; }
.belum-terdaftar-table .belum-terdaftar-aksi .btn{
  padding:3px 8px;
  font-size:11px;
  margin-right:4px;
}
.belum-terdaftar-table .belum-terdaftar-aksi .btn:last-child{ margin-right:0; }

.table-modal-body{
    flex:1;
    padding:0px 5px 5px 5px;
	margin-top:5px;
}


#netwatchTable tbody td{
    padding:3px 5px;
}

.dt-center{
    text-align:center;
}

/* Badge nilai RX pada tabel: mengikuti warna marker di peta */
#netwatchTable td .rx-badge{
    display:inline-block;
    /* sama seperti .status-badge: teks "-24" hanya 17px, sisanya ruang mati */
    min-width:24px;
    padding:2px 5px;
    border-radius:5px;
    font-weight:700;
    font-size:11px;
    line-height:1.4;
}

/* Grid DataTable */
#netwatchTable{
    border-collapse:collapse !important;
    width:auto;
    table-layout:auto;
}

/* mobile: kolom yg dilipat DataTables Responsive muncul sbg baris-anak
   (klik tombol +). Biarkan tabel selebar kontainer supaya rapi. */
@media (max-width:767px){
  #netwatchTable{ width:100% !important; table-layout:auto !important; }
}

/* judul kolom rata tengah; judul boleh wrap (sel tetap nowrap) supaya
   kolom ber-sel kosong (mis. Billing Date) bisa menyempit & judulnya 2 baris */
#netwatchTable thead th{ text-align:center; white-space:normal; }

/* garis pembatas kolom yg bisa digeser (drag) */
#netwatchTable thead th .col-drag{
    position:absolute;
    top:0; right:0;
    width:6px; height:100%;
    cursor:col-resize;
    user-select:none;
}
#netwatchTable thead th .col-drag:hover{ background:rgba(37,99,235,.45); }

/* baris zebra biar mudah dibaca (DataTables memberi kelas .odd/.even) */
#netwatchTable tbody tr.odd  > *{ background:#f9fafb; }
#netwatchTable tbody tr.even > *{ background:#ffffff; }
#netwatchTable tbody tr:hover > *{ background:#eef2ff; }

/* Baris titik PASIF (ODP/ODC/JC/Tiang/Splitter) -- bukan pelanggan.
   Ditaruh SETELAH aturan zebra: spesifisitasnya sama (1,1,2), jadi yg
   belakangan menang dan warna ini tidak tertimpa .odd/.even. */
#netwatchTable tbody tr.nw-row-pasif > *{
    background:#eef2f7;
    color:#64748b;
    font-style:italic;
}
#netwatchTable tbody tr.nw-row-pasif:hover > *{ background:#dfe6ef; }

/* Baris ISOLIR: pelanggan yg entri netwatch-nya disabled. Sengaja bernuansa
   hangat, bukan abu spt titik pasif -- ini pelanggan diputus, bukan
   infrastruktur. Tidak dimiringkan supaya tetap beda dari baris pasif. */
#netwatchTable tbody tr.nw-row-isolir > *{
    background:#fdf1e3;
    color:#9a5b1e;
}
#netwatchTable tbody tr.nw-row-isolir:hover > *{ background:#f8e4cd; }

/* Baris yg TERISOLIR SEKARANG di address-list ISOLIR router (kolom Isolir
   kanan, toggle merah) -- lihat row.isolir/nw-row-isolir-on di app.js. BEDA
   dgn nw-row-isolir di atas (kategori dari status monitor disabled): titik
   yg sama bisa berstatus keduanya beda, jadi kelasnya terpisah. Ditaruh
   PALING BAWAH spy menang lawan nw-row-pasif/nw-row-isolir kalau kebetulan
   nempel bareng -- ISOLIR sungguhan lebih penting dilihat drpd kategorinya.
   Semi-transparan (rgba) sesuai desain: zebra/hover di baliknya masih ikut
   kelihatan, bukan ditimpa warna solid. */
#netwatchTable tbody tr.nw-row-isolir-on > *{
    background:rgba(239,68,68,.20);
}
#netwatchTable tbody tr.nw-row-isolir-on:hover > *{
    background:rgba(239,68,68,.30);
}

/* strip pengganti badge UNKNOWN pada titik pasif */
#netwatchTable td .status-na{
    display:inline-block;
    min-width:24px;
    color:#94a3b8;
    font-weight:700;
    font-size:11px;
    line-height:1.4;
    text-align:center;
}

/* badge Status (gaya spt kolom RX): tulisan putih di atas latar warna */
#netwatchTable td .status-badge{
    display:inline-block;
    /* min-width menyamakan lebar pil UP vs DOWN supaya kolom tak bergerigi.
       Ditekan dari 34px: teks "UP" cuma 15px, sisanya jadi ruang mati di
       DALAM badge (dulu ~9,5px kiri-kanan). */
    min-width:24px;
    padding:2px 5px;
    border-radius:5px;
    color:#fff;
    font-weight:700;
    font-size:11px;
    line-height:1.4;
    text-align:center;
}

/* Kolom Due: lama hari keterlambatan bayar (lihat dueBadgeRender di app.js).
   Gaya sama dgn .badge-tunggakan (kartu detail) spy "merah = telat" konsisten
   di seluruh app, tapi lebih ringkas biar muat di sel tabel. cursor:pointer
   krn diklik membuka form Bayar (lihat nwPasangDueKlikBayar di app.js). */
#netwatchTable td .nw-due-badge{
    display:inline-block;
    padding:1px 6px;
    border-radius:999px;
    background:#fee2e2;
    color:#b91c1c;
    border:1px solid #fca5a5;
    font-weight:700;
    font-size:11px;
    line-height:1.4;
    white-space:nowrap;
    cursor:pointer;
}

/* Varian LUNAS/belum jatuh tempo -- lingkaran hijau, centang putih, spy beda
   dari sel yg memang KOSONG (titik pasif/tanpa tanggal tagihan, lihat null
   check di dueBadgeRender). Dua kelas (.nw-due-badge.nw-due-ok) sengaja lebih
   spesifik drpd .nw-due-badge di atas, jadi menang menimpa warna merahnya. */
#netwatchTable td .nw-due-badge.nw-due-ok{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    width:16px;
    height:16px;
    padding:0;
    border-radius:50%;
    background:#22c55e;
    border:1px solid #16a34a;
    color:#fff;
    font-size:10px;
    font-weight:900;
    line-height:1;
}

/* Kolom LastPay: badge netral (abu-abu, bukan merah/hijau spt Due) krn ini
   cuma info riwayat, bukan status. Kosong (&hellip;) sampai di-hover --
   lihat lastPayBadgeRender & nwPasangLastPayHover di app.js. cursor:default
   krn hover cuma memuat teks, tidak ada aksi klik spt Due. */
#netwatchTable td .nw-lastpay-badge{
    display:inline-block;
    padding:1px 6px;
    border-radius:999px;
    background:#f1f5f9;
    color:#475569;
    border:1px solid #cbd5e1;
    font-weight:600;
    font-size:11px;
    line-height:1.4;
    white-space:nowrap;
    cursor:default;
}

/* Toggle switch kolom Isolir (lihat isolirToggleRender di app.js) -- ACTIVE
   hijau/knob kanan, ISOLIR merah/knob kiri, sama spt referensi desain switch
   yg diminta. justify-content dipakai supaya knob-nya tak perlu hitung px
   manual (aman kalau lebarnya nanti disesuaikan). */
#netwatchTable td .nw-isolir-switch{
    box-sizing:border-box;
    display:inline-flex;
    align-items:center;
    justify-content:flex-end;
    width:36px;
    height:18px;
    padding:2px;
    border:none;
    border-radius:999px;
    background:#22c55e;
    cursor:pointer;
    vertical-align:middle;
    transition:background-color .15s ease;
}
#netwatchTable td .nw-isolir-switch.is-isolir{
    justify-content:flex-start;
    background:#ef4444;
}
#netwatchTable td .nw-isolir-switch:disabled{
    opacity:.55;
    cursor:wait;
}
#netwatchTable td .nw-isolir-switch .nw-isolir-switch-knob{
    width:14px;
    height:14px;
    border-radius:50%;
    background:#fff;
    box-shadow:0 1px 2px rgba(0,0,0,.35);
}

#netwatchTable th,
#netwatchTable td{
    border:1px solid #d1d5db !important;
}

table.dataTable thead th{
}

.dt-right{
    text-align:right !important;
}

table.dataTable thead th{
    position:sticky;
    top:0;
    background:#f3f4f6 !important;
    z-index:10;
}

#netwatchTable td,
#netwatchTable th{
    padding:2px 2px !important;
    white-space:nowrap;
}

.dataTables_wrapper{
    height:100%;
}


#netwatchTable{
    font-size:11px !important;
}

#netwatchTable th{
    font-size:11px !important;
    font-weight:600;
}

#netwatchTable td{
    font-size:11px !important;
}

.dataTables_info,
.dataTables_filter,
.dataTables_length{
    font-size:11px !important;
}

table.dataTable{
    width:100% !important;
}

.dataTables_scrollHeadInner{
    width:100% !important;
}

.dataTables_scrollHeadInner table{
    width:100% !important;
}

.table-modal-body{
    flex:1;
    overflow:auto;
}

/* Kolom melar mengikuti lebar modal itu penyebab utama tampilan terasa
   longgar: `table.dataTable{width:100%!important}` memaksa tabel selebar
   wadah, lalu sisa ruang diserap kolom terlebar (Nama sempat kebagian 82px
   ruang kosong). Dengan width:auto, kolom menyusut mengikuti isinya.
   Butuh !important supaya menang atas aturan generik di atas. */
#netwatchTable{
    width:auto !important;
    table-layout:auto;
    /* DataTables memberi `margin:0 auto`; begitu lebarnya auto, tabel jadi
       melayang di tengah dan menyisakan celah di kiri. */
    margin-left:0 !important;
    margin-right:0 !important;
}

#netwatchTable thead th{

    position:sticky;

    top:0px;

    z-index:20;

    background:#f5f5f5;
	text-align: center !important;

	border-top: 1px solid #d1d5db  !important;
	border-bottom: 1px solid #d1d5db  !important;

	/* Garis atas/bawah header yg menempel digambar sbg INSET SHADOW, bukan
	   mengandalkan border di atas.
	   Sebabnya: #netwatchTable memakai border-collapse:collapse, dan dlm mode
	   itu garis sel dilukis oleh TABEL, bukan oleh selnya. Sel position:sticky
	   bergerak sendiri terhadap tabel, jadi border-nya tertinggal di posisi
	   asal lalu ikut tergulung -- sisi atas header tampak terbuka begitu
	   daftar digulung. Inset shadow dilukis oleh elemen itu sendiri sehingga
	   selalu ikut menempel. Border di atas tetap dibiarkan: ia masih berlaku
	   saat header belum tergulung & di tepi tabel. */
	box-shadow: inset 0 1px 0 #d1d5db, inset 0 -1px 0 #d1d5db;

	/* +3px dari tinggi sel biasa (19 -> 22px). Perlu !important & selector yg
	   lbh spesifik utk menang atas `#netwatchTable td, th { padding:2px 2px
	   !important }`. Judul memang butuh sedikit lbh lega: 2px dari padding
	   atas-bawahnya sudah termakan garis inset di kedua sisi. */
	padding-top: 5px !important;
	padding-bottom: 5px !important;
}

.table-tools{
    display:flex;
    align-items:center;
    justify-content:flex-end;
    gap:8px;
    /* boleh menyusut supaya tak mendorong judul keluar kotak */
    flex:1 1 170px;
    min-width:0;
}

/* judul + filter Object dikelompokkan di kiri, tools tetap di kanan */
.table-title-group{
    display:flex;
    align-items:center;
    gap:10px;
    flex:1 1 auto;
    min-width:0;
}

/* judul dipotong ellipsis, bukan mendorong search & tombol tutup */
.table-title-group > span{
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
    min-width:0;
}

/* ---- Filter Object (multi-pilih) di titlebar DataTable ---- */
/* min-width:0 wajib: tanpa ini tombol di dalamnya menolak menyusut dan
   meluber menimpa kotak search saat labelnya panjang. */
.nw-objfilter{ position:relative; flex:0 1 auto; min-width:0; }

.nw-objfilter-btn{
    display:flex; align-items:center; gap:6px;
    height:26px; padding:0 8px; max-width:100%;
    border:1px solid rgba(255,255,255,.55); border-radius:4px;
    background:rgba(255,255,255,.12); color:#fff;
    font:600 13px/1 var(--app-font); cursor:pointer;
    white-space:nowrap;
}
.nw-objfilter-btn:hover{ background:rgba(255,255,255,.22); }
/* label dipotong ellipsis kalau pilihannya panjang ("Object: 3 jenis" dst) */
#objFilterLabel{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nw-objfilter-caret{ flex:0 0 auto; font-size:9px; opacity:.85; }

.nw-objfilter-menu{
    position:absolute; top:calc(100% + 6px); left:0; z-index:60;
    /* min-width di sini lebar CONTENT (elemen ini tanpa border-box), jadi
       menu jadi 268+14 = 282px. Angkanya diambil dari baris ringkas
       ("Customer N · selain Customer N · total N") yg butuh 248px utk muat
       SATU baris -- sisanya kelonggaran utk angka 4 digit. Itu baris headline,
       yg dijaga jangan melipat.
       Baris Kuota sengaja dibiarkan melipat jadi 2 baris: butuh 290px kalau
       dipaksa 1 baris, terlalu lebar utk sebuah dropdown -- dan di ponsel
       tetap akan melipat krn kena max-width di media query. */
    min-width:268px; max-height:320px; overflow:auto;
    padding:6px;
    background:#fff; color:#111827;
    border:1px solid #d1d5db; border-radius:6px;
    box-shadow:0 8px 24px rgba(0,0,0,.18);
    font-weight:400;
}
.nw-objfilter-menu[hidden]{ display:none; }

.nw-objfilter-ringkas{
    padding:4px 6px 6px;
    margin-bottom:4px;
    border-bottom:1px solid #e5e7eb;
    font-size:11px; color:#374151;
}

.nw-objfilter-kuota{
    padding:4px 6px 6px;
    margin-bottom:4px;
    border-bottom:1px solid #e5e7eb;
    font-size:11px; color:#374151;
}
.nw-kuota-potong{ color:#b45309; font-weight:600; }

.nw-objfilter-item{
    display:flex; align-items:center; gap:8px;
    padding:4px 6px; border-radius:4px;
    font-size:12px; cursor:pointer;
}
.nw-objfilter-item:hover{ background:#f3f4f6; }
.nw-objfilter-nama{ flex:1 1 auto; }
.nw-objfilter-jml{
    flex:0 0 auto;
    min-width:28px; padding:0 6px;
    border-radius:999px; background:#e5e7eb;
    font-size:11px; font-weight:600; text-align:center;
}

.nw-objfilter-aksi{
    display:flex; gap:4px;
    margin-top:6px; padding-top:6px;
    border-top:1px solid #e5e7eb;
}
.nw-objfilter-aksi button{
    flex:1 1 auto;
    padding:4px 6px;
    border:1px solid #d1d5db; border-radius:4px;
    background:#f9fafb; color:#111827;
    font-size:11px; cursor:pointer;
}
.nw-objfilter-aksi button:hover{ background:#eef2ff; border-color:#a5b4fc; }

/* kolom nomor urut: sesempit mungkin, tidak ikut melar.
   Isinya = panah dtr-control (11px + 2px jeda) + nomor + padding 4px. */
#netwatchTable td.nw-rownum, #netwatchTable th:first-child{
    width:26px; text-align:center; color:#6b7280;
}

#tableSearch{
    /* melar/menyusut mengikuti sisa ruang, tak lagi 150px kaku.
       width kecil disengaja: itu basis flex-nya, jadi lebar ALAMI input (~173px)
       tak ikut menentukan lebar minimum kartu modal — biar kartu mengikuti
       lebar tabel. flex-grow yg memuaikannya kembali saat ruang tersedia. */
    flex:1 1 auto;
    width:90px;
    min-width:0;
    max-width:180px;
    height:26px;

    padding:0 8px;

    border:1px solid #ccc;
    border-radius:4px;

    font:400 13px/1 var(--app-font);
    color:#111827;
}

/* ---- Titlebar DataTable di layar sempit ----
   Judul aplikasi disembunyikan; ruangnya diberikan ke filter Object + search
   supaya ketiganya muat satu baris tanpa saling tumpuk. */
@media (max-width:767px){
    .table-modal-header{ padding:6px 8px; gap:6px; }
    .table-title-group > span{ display:none; }
    .table-tools{ flex:1 1 auto; }
    /* keduanya boleh menyusut, tapi masing-masing punya lantai minimum supaya
       tak ada yang tergencet habis; kelebihan teks jadi ellipsis. */
    .nw-objfilter{ min-width:76px; }
    #tableSearch{ max-width:none; min-width:76px; }
    .nw-objfilter-menu{ max-width:calc(100vw - 32px); }
}

/* Baris detail (baris-anak mobile): pakai flex, JANGAN float (float bikin
   tinggi <li> kacau/tidak rapi). Label lebar tetap, nilai mengisi sisanya. */
table.dataTable > tbody > tr.child ul.dtr-details > li{
	display:flex !important;
	align-items:baseline;
	gap:8px;
	padding:4px 0 !important;
	border-bottom:1px solid #f0f0f0 !important;
	line-height:1.4;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:last-child{ border-bottom:none !important; }
.dtr-title{
	float:none !important;
	flex:0 0 92px;
	text-align:left !important;
	font-weight:600;
}
.dtr-data{
	float:none !important;
	flex:1 1 auto;
	text-align:left !important;
	word-break:break-word;
}

/* icon collapsed */
table.dataTable.dtr-column.collapsed > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-column.collapsed > tbody > tr > th.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before{

    content:"▶" !important;

    color:#666 !important;

    background:none !important;
    border:none !important;
    box-shadow:none !important;
    border-radius:0 !important;

    display:inline-block !important;
    position:static !important;
    top:auto !important;
    left:auto !important;

    width:11px !important;
    height:auto !important;

    /* jeda antara panah dan nomor urut; 8px bikin kolom "#" terasa longgar */
    margin-right:2px !important;
    margin-top:0 !important;

    text-align:center;
    line-height:1 !important;
}

/* expanded */
table.dataTable.dtr-column.collapsed > tbody > tr.parent > td.dtr-control:before,
table.dataTable.dtr-column.collapsed > tbody > tr.parent > th.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before{

    content:"▼" !important;

    color:#666 !important;

    background:none !important;
    border:none !important;
    box-shadow:none !important;
    border-radius:0 !important;
    position:static !important;
    top:auto !important;
    left:auto !important;

    width:11px !important;
    height:auto !important;

    /* jeda antara panah dan nomor urut; 8px bikin kolom "#" terasa longgar */
    margin-right:2px !important;
    margin-top:0 !important;
    line-height:1 !important;
}

.dtr-details{
	margin-left:24px !important;
	width:calc(100% - 24px);
}

table.dataTable.dtr-inline.collapsed > tbody > tr.child > td.child{
	border-bottom:none !important;
}

.form-overlay{
    pointer-events:none;
}

.form-panel{
    pointer-events:auto;
}

/* Dropdown autocomplete */
.ui-autocomplete{
    /* Harus di atas modal Login (z-index 999999999), krn menu ini
       ditempelkan ke <body> - kalau lebih rendah, menunya tertutup modal. */
    z-index:1000000001 !important;

    font-size:12px !important;

    max-height:220px;
    overflow-y:auto;
    overflow-x:hidden;
}

/* ===== Login Modal ===== */
#loginRowsTable{
    width:100%;
    table-layout:auto;
    border-collapse:collapse;
    font-size:12px;
}

#loginRowsTable th,
#loginRowsTable td{
    border:1px solid #d1d5db;
    padding:2px;
    text-align:left;
    white-space:nowrap;
}

#loginRowsTable th{
    background:#f3f4f6;
    position:relative;
    text-align:center;
    font-size:14px;
    font-weight:700;
}

#loginRowsTable .col-expand,
#loginRowsTable .col-check,
#loginRowsTable .col-save,
#loginRowsTable .col-del{ width:1%; white-space:nowrap; }

#loginRowsTable .row-num{
    text-align:center;
    color:#6b7280;
    font-weight:600;
    white-space:nowrap;
}

/* Tanda "jalur cadangan": baris ini menunjuk router yang sama dgn baris di
   atasnya (mis. VPN kedua), jadi data hanya ditarik lewat sini bila jalur
   utamanya putus. Ditempel di kolom nomor supaya tidak memakan lebar kolom
   mana pun; warna barisnya sendiri sudah sama dgn baris utamanya. */
#loginRowsTable .row-backup{
    display:inline-block;
    margin-left:4px;
    font-size:11px;
    line-height:1;
    color:#2563eb;
    cursor:help;
}

#loginRowsTable .col-host{ width:40%; }
#loginRowsTable .col-user,
#loginRowsTable .col-pass,
#loginRowsTable .col-acc,
#loginRowsTable .col-sid{ width:15%; }

#loginRowsTable th.resizable{
    padding-right:10px;
}

#loginRowsTable .col-resizer{
    position:absolute;
    top:0;
    right:0;
    width:6px;
    height:100%;
    cursor:col-resize;
    user-select:none;
    touch-action:none;
}

#loginRowsTable .col-resizer:hover,
#loginRowsTable .col-resizer.resizing{
    background:rgba(29,78,216,.4);
}

#loginRowsTable input[type="text"],
#loginRowsTable input[type="password"]{
    width:100%;
    min-width:0;
    box-sizing:border-box;
    font-family:var(--app-font,'Assistant',system-ui,Arial,sans-serif);
    border:1px solid #ccc;
    border-radius:4px;
    padding:4px;
    font-size:12px;
}

#loginRowsTable .sid-cell{
    color:#374151;
}

.row-expand{
    background:none;
    border:none;
    cursor:pointer;
    font-size:24px;
    line-height:1;
    color:#374151;
    padding:0 2px;
    width:22px;
    text-align:center;
}

/* Baris OLT di dalam tabel utama (kolom sejajar dgn baris server) */
#loginRowsTable tr.olt-row td{
    background:#f9fafb;
    padding:1px !important;
}

/* ---- Warna per grup server ----
 * Tiap server dpt satu keluarga warna: baris server pakai warna tua,
 * baris OLT miliknya pakai warna muda dari keluarga yg sama. Tujuannya
 * supaya jelas OLT mana milik server mana saat servernya banyak. */
#loginRowsTable tr.srv-grp-0.srv-row td{ background:#bfdbfe; }  /* biru      */
#loginRowsTable tr.srv-grp-0.olt-row td{ background:#eff6ff; }
#loginRowsTable tr.srv-grp-1.srv-row td{ background:#bbf7d0; }  /* hijau     */
#loginRowsTable tr.srv-grp-1.olt-row td{ background:#f0fdf4; }
#loginRowsTable tr.srv-grp-2.srv-row td{ background:#fde68a; }  /* kuning    */
#loginRowsTable tr.srv-grp-2.olt-row td{ background:#fffbeb; }
#loginRowsTable tr.srv-grp-3.srv-row td{ background:#ddd6fe; }  /* ungu      */
#loginRowsTable tr.srv-grp-3.olt-row td{ background:#f5f3ff; }
#loginRowsTable tr.srv-grp-4.srv-row td{ background:#fbcfe8; }  /* merah muda*/
#loginRowsTable tr.srv-grp-4.olt-row td{ background:#fdf2f8; }
#loginRowsTable tr.srv-grp-5.srv-row td{ background:#a5f3fc; }  /* cyan      */
#loginRowsTable tr.srv-grp-5.olt-row td{ background:#ecfeff; }

/* tombol switch mode kolom kanan recap */
#tsModeBtn{
    margin-left:auto;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    /* ikon saja -> bujur sangkar, selebar tingginya.
       Ini salah satu dari dua langkah yg menghentikan #nwCounters melipat di
       panel 250px: membuang label tombol ini, lalu memendekkan #recapFilter
       dari "Total = 312" jadi "Laser" (terukur 26px lebih sempit). Kalau
       isinya nanti memanjang lagi -- angka jadi 4 digit, atau label dropdown
       ditambah -- barisnya akan melipat lagi. */
    width:26px;
    height:26px;                 /* patokan seragam sebaris: badge, Laser, filter */
    box-sizing:border-box;
    padding:0;
    font-size:12px;
    font-weight:600;
    font-family:var(--app-font);
    /* Nuansa netral (slate), bukan putih ber-tepi hitam seperti badge.
       Badge itu STATUS -- pekat & bertepi hitam supaya menonjol. Tombol ini
       cuma pengatur tampilan kolom, jadi ia masuk keluarga KONTROL yg lembut.
       Sengaja netral, bukan salah satu rona filter, agar tidak terbaca seolah
       terikat pada Area/BillDate/Paket/Laser. */
    border:1px solid #cbd5e1;
    border-radius:5px;
    background-color:#f8fafc;
    color:#475569;               /* ikon mengikuti currentColor */
    box-shadow:0 1px 0 rgba(0,0,0,.10), inset 0 1px 0 rgba(255,255,255,.6);
    cursor:pointer;
    user-select:none;
    transition:background .12s, box-shadow .12s, transform .06s;
}
/* Ikon mengikuti warna teks (currentColor) supaya otomatis sejalan dgn
   status hover/active tombolnya, tanpa aturan warna terpisah. */
#tsModeBtn .ts-ico{
    width:15px; height:15px;     /* sedikit lbh besar: kini satu2nya isi tombol */
    flex:0 0 auto;
    fill:none;
    stroke:currentColor;
    stroke-width:1.6;
    stroke-linecap:round;
    stroke-linejoin:round;
    opacity:.9;
}

#tsModeBtn:hover{
    background-color:#f1f5f9;    /* slate-100, tetap sekeluarga */
    border-color:#94a3b8;
    box-shadow:0 2px 3px rgba(0,0,0,.15), inset 0 1px 0 rgba(255,255,255,.6);
}
#tsModeBtn:active{
    background-color:#e2e8f0;
    box-shadow:inset 0 1px 2px rgba(0,0,0,.25);
    transform:translateY(1px);
}

#loginRowsTable tr.olt-head td.olt-h{
    font-size:10px;
    font-weight:600;
    color:#6b7280;
    text-transform:uppercase;
    letter-spacing:.03em;
    text-align:center;
    padding:5px !important;
}

#loginRowsTable tr.olt-row td.olt-no{
    text-align:center;
    font-size:11px;
    color:#6b7280;
}

/* Syarat data redaman OLT. Sengaja SELALU terlihat, tidak dilipat di balik
   details/summary: yang membacanya adalah orang yang sedang mengisi IP OLT
   dan belum tahu ada syaratnya sama sekali -- kalau terlipat, tidak akan
   pernah dibuka. Kecil & abu2 supaya tidak menutupi barisnya sendiri. */

#loginRowsTable tr.olt-entry .olt-ip,
#loginRowsTable tr.olt-entry .olt-seri,
#loginRowsTable tr.olt-entry .olt-merk{
    width:100%;
    box-sizing:border-box;
    border:1px solid #ccc;
    border-radius:4px;
    padding:5px;
    font-size:11px;
}

#loginRowsTable tr.olt-entry td.olt-del-cell,
#loginRowsTable tr.olt-entry td.olt-save-cell{
    text-align:center;
}

#loginRowsTable tr.olt-entry .olt-del{
    background:#dc2626;
    color:#fff;
}

#loginRowsTable tr.olt-entry .olt-save{
    background:#1d4ed8;
    color:#fff;
}

#loginRowsTable tr.olt-entry .olt-save.success{
    background:#16a34a;
}

#loginRowsTable tr.olt-empty td{
    font-size:11px;
}

#loginRowsTable tr.olt-addrow .olt-add-btn{
    padding:5px 5px;
    font-size:11px;
    border:none;
    border-radius:4px;
    background:#1d4ed8;
    color:#fff;
    cursor:pointer;
}

#loginRowsTable tr.olt-addrow .olt-add-btn:disabled{
    opacity:.5;
    cursor:default;
}

.icon-spinner{
    display:inline-block;
    width:12px;
    height:12px;
    border:2px solid rgba(255,255,255,.5);
    border-top-color:#fff;
    border-radius:50%;
    animation:nwLoginSpin .7s linear infinite;
}

@keyframes nwLoginSpin{
    to{ transform:rotate(360deg); }
}

.login-row-status{
    min-height:16px;
    margin-top:6px;
    font-size:12px;
    color:#dc2626;
}

#loginRowsTable td:first-child{
    text-align:center;
    width:28px;
}

#loginRowsTable .icon-btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    width:26px;
    height:26px;
    padding:0;
    border:none;
    border-radius:4px;
    cursor:pointer;
    font-size:13px;
    line-height:1;
    color:#fff;
}

#loginRowsTable .icon-btn:disabled{
    opacity:.6;
    cursor:default;
}

#loginRowsTable .row-save{
    background:#1d4ed8;
}

#loginRowsTable .row-save.success{
    background:#16a34a;
}

#loginRowsTable .row-save.error{
    background:#dc2626;
}

#loginRowsTable .row-del{
    background:#dc2626;
}

#login{
    cursor:pointer;
}

/* Item */
.ui-menu-item-wrapper{
    font-size:12px !important;

    padding:4px 8px !important;

    line-height:1.3;
}

/* Item terpilih */
.ui-state-active,
.ui-widget-content .ui-state-active{

    font-size:12px !important;

    border:none !important;

    background:#3b82f6 !important;

    color:#fff !important;
}

.ui-autocomplete{
    font-family: var(--app-font) !important;
    font-size:11px !important;
}

.ui-autocomplete .ui-menu-item{
    font-family: var(--app-font) !important;
    font-size:11px !important;
}

.ui-autocomplete .ui-menu-item-wrapper{
    font-family: var(--app-font) !important;
    font-size:11px !important;
    padding:3px 6px !important;
}

.ui-widget{
    font-family:var(--app-font) !important;
    font-size:11px !important;
}

.ui-menu-item-wrapper{
    padding:2px 6px !important;
}


.k{
	font-weight:bold;
}

.leaflet-control-attribution.leaflet-control{
	display:none;
}

/* ===================== Ajakan pasang PWA =====================
   Kartu mengambang di atas peta. Ditaruh di bawah (bukan atas) supaya tidak
   menutupi baris filter & tombol yang dipakai bekerja; di HP jempol juga lebih
   dekat ke sana. Muncul hanya lewat pwaInstall() di app.js. */
.pwa-install{
  position:fixed;
  left:50%;
  bottom:16px;
  transform:translateX(-50%);
  z-index:1200;                      /* di atas panel & kontrol Leaflet */
  display:flex;
  align-items:center;
  gap:12px;
  width:calc(100% - 24px);
  max-width:420px;
  padding:12px 14px;
  box-sizing:border-box;
  background:#fff;
  border:1px solid #e5e7eb;
  border-radius:14px;
  box-shadow:0 12px 32px rgba(0,0,0,.22);
  font-family:var(--app-font);
}
.pwa-install[hidden]{ display:none; }   /* [hidden] kalah oleh display:flex */

/* Kartu ini menimpa kontrol kanan-bawah peta di layar sempit. Itu disengaja
   dibiarkan -- lihat catatan di pwaInstall() (app.js) soal upaya menggeser
   kontrol yang dibatalkan karena tidak bisa diandalkan. */

.pwa-ikon{
  width:44px; height:44px;
  flex:0 0 auto;
  border-radius:10px;
  object-fit:cover;
}
.pwa-teks{ flex:1 1 auto; min-width:0; }
.pwa-judul{ font-size:14px; font-weight:700; color:#111827; line-height:1.25; }
.pwa-sub{ margin-top:2px; font-size:12px; color:#6b7280; line-height:1.35; }

.pwa-btn{
  flex:0 0 auto;
  padding:8px 16px;
  border:0;
  border-radius:8px;
  background:#2563eb;                /* sama dgn theme_color manifest */
  color:#fff;
  font:600 13px/1 var(--app-font);
  cursor:pointer;
}
.pwa-btn:hover{ background:#1d4ed8; }

.pwa-tutup{
  flex:0 0 auto;
  width:26px; height:26px;
  padding:0;
  border:0;
  border-radius:6px;
  background:transparent;
  color:#9ca3af;
  font-size:20px;
  line-height:1;
  cursor:pointer;
}
.pwa-tutup:hover{ background:#f3f4f6; color:#4b5563; }

/* Layar sempit: tetap SATU baris, hanya dirampingkan. Versi sebelumnya
   membungkus tombol ke baris sendiri -- kartunya jadi 158px tinggi di layar
   360px, lebih tinggi daripada sekadar membiarkan keterangannya turun baris. */
@media (max-width:400px){
  .pwa-install{ gap:10px; padding:10px 12px; }
  .pwa-ikon{ width:38px; height:38px; }
  .pwa-btn{ padding:8px 12px; }
}

/* Gelembung .nw-gelembung DIHAPUS bersama pengelompokan semantik per Area/ODP.
   Tampilan kelompok sekarang bawaan Leaflet.markercluster, yang membawa CSS-nya
   sendiri (MarkerCluster.css + MarkerCluster.Default.css, lihat index.php). */

/* Gelembung markercluster harus berada DI ATAS marker titik biasa.
   devicesPane dinaikkan z-index-nya di aplikasi ini, dan tanpa aturan ini
   gelembungnya tertimbun di bawah titik-titik yang justru sedang diwakilinya. */
.marker-cluster{ z-index:650; }

/* Baris "Tunggakan" di kartu detail. Hanya tampil kalau memang menunggak
   (lihat tunggakanRowHTML() di app.js), jadi warnanya boleh tegas -- ia tidak
   pernah muncul untuk pelanggan yang lunas. */
.badge-tunggakan{
  display:inline-block; padding:1px 8px; border-radius:999px;
  background:#fee2e2; color:#b91c1c; border:1px solid #fca5a5;
  font-weight:700; white-space:nowrap;
}
