/* ==========================================================================
   KASA Gateway — Calm Mint overrides (inherit auth.intiserver.com)
   Kontrak token: intiserver-ecosystem-architecture/contracts/design-tokens.md
   Layer: dimuat SETELAH theme.min.css / theme-dark.min.css + inline splash.
   Light = "Calm Mint blue" (tema live auth). Dark = "pre-dawn server room".
   Scoping dark: html.kasa-dark-pending — kelas ini diset kasa-bootstrap.js
   saat load dan disinkronkan theme-switcher.js saat toggle live (vendor
   theme-dark.min.css swap href tanpa reload, tanpa kelas apapun).
   Drift biru lama (vendor legacy blues) → #2d639e sesuai kontrak.

   CASCADE SHIELD (wajib): hs.theme-appearance.js membuat SALINAN link
   stylesheet tema aktif dan menyisipkannya di AKHIR <head>
   (insertAdjacentElement('afterEnd', head)) pada init() dan setiap
   setAppearance() — salinan itu selalu lebih belakang dari file ini.
   Konsekuensi: SEMUA rule light di file ini wajib lebih spesifik daripada
   rule vendor setara (token pakai :root:root, komponen diprefix `body `
   atau `html body `). Dark sudah aman karena scope html.kasa-dark-pending
   (0,1,1+) > selector vendor (≤0,2,0).
   ========================================================================== */

/* ---------- Light: Calm Mint tokens (:root:root = (0,2,0), menang atas :root vendor) ---------- */
:root:root {
  /* KASA namespace.
     #301: `--kasa-accent` = #2d639e (kanonik `contracts/design-tokens.md` +
     `intiserver-auth-service/cloudflare-pages/src/styles.css` blok light).
     Sebelumnya #3f73b8 — nilai tabel **draf 2026-09-21** yang sudah basi.
     #3f73b8 lolos sebagai BIDANG (putih di atasnya 4,8212:1) tapi GAGAL
     sebagai TEKS di surface berpigmen aksen yang justru dipakai KASA:
       accent-soft #e6f0fb 4,1833:1 · tint 10% 4,2482:1 · tint 12% 4,1305:1
     Nilai kanonik menaikkannya ke 5,3751 / 5,3579 / 5,2085. Diuji
     `tests/unit/issue-301-accent-text-contrast.test.ts`. */
  --kasa-accent: #2d639e;
  /* #301: turunan hover kanonik (`--accent-dim`); light #244f80, dark #b37b35. */
  --kasa-accent-dim: #244f80;
  --kasa-accent-strong: #28558f;
  --kasa-accent-soft: #e6f0fb;
  --kasa-accent-ink: #ffffff;
  --kasa-paper: #f5f8fc;
  --kasa-panel: rgba(255, 255, 255, 0.88);
  --kasa-panel-solid: #ffffff;
  --kasa-line: #dbe5ef;
  --kasa-ink: #1d2d40;
  --kasa-ink-dim: #66788b;
  --kasa-ok: #2d8a5c;
  --kasa-danger: #c8525c;
  --kasa-warn: #e8a04a;
  /* #259 — "ink" = versi tone-semantik yang lolos AA 1.4.3 saat dipakai
     sebagai TEKS di atas latar pucatnya sendiri. `--kasa-ok`/`--kasa-danger`/
     `--kasa-warn` adalah warna FILL (untuk latar solid + teks putih); kalau
     warna yang sama dipakai sebagai teks di atas latar pucat, rasio jatuh di
     bawah 4.5:1 (terukur: success 3.68, danger 3.71, warning 4.18, info 4.18).
     Dipisah supaya fill tidak ikut berubah — hanya teks yang digelapkan. */
  --kasa-ok-ink: #246e49;
  --kasa-danger-ink: #b04851;
  --kasa-warn-ink: #8a5c1c;
  --kasa-info-ink: #28558f;
  --kasa-radius-card: 22px;
  --kasa-radius-btn: 14px;
  --kasa-radius-input: 12px;
  /* #301 SENGAJA tidak menyentuh dua nilai rgba(63,115,184) di bawah: keduanya
     bukan warna TEKS sehingga di luar cakupan issue (yang cacatnya adalah
     `--kasa-accent` sebagai teks di surface berpigmen aksen).
       --kasa-shadow-btn  = VERBATIM dari sumber kanonik: auth `styles.css`
                            `.btn-primary { box-shadow: 0 8px 16px rgba(63,115,184,.11) }`.
       --kasa-focus-ring  = konstruksi draf (cincin tembus-pandang). Sumber
                            kanonik sudah menggantinya dengan cincin dua lapis
                            `0 0 0 2px #ffffff, 0 0 0 5px #1d2d40` karena versi
                            alpha ini hanya 1,42:1 di atas putih (gagal SC 1.4.11
                            non-teks). Menggantinya = perubahan indikator fokus
                            lintas-KASA yang butuh gate browser sendiri, jadi
                            TIDAK dikerjakan di sini — dicatat sebagai residual. */
  --kasa-focus-ring: 0 0 0 3px rgba(63, 115, 184, 0.24);
  --kasa-shadow: 0 18px 48px rgba(49, 83, 117, 0.12), 0 2px 8px rgba(49, 83, 117, 0.06);
  --kasa-shadow-btn: 0 8px 16px rgba(63, 115, 184, 0.11);
  --kasa-font-display: 'Space Grotesk', 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --kasa-font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --kasa-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Bootstrap var remap */
  --bs-primary: #2d639e;
  --bs-primary-rgb: 45, 99, 158;
  --bs-blue: #2d639e;
  --bs-blue-rgb: 45, 99, 158;
  --bs-secondary: #66788b;
  --bs-secondary-rgb: 102, 120, 139;
  --bs-success: #2d8a5c;
  --bs-success-rgb: 45, 138, 92;
  --bs-info: #2d639e;
  --bs-info-rgb: 45, 99, 158;
  --bs-warning: #e8a04a;
  --bs-warning-rgb: 232, 160, 74;
  --bs-danger: #c8525c;
  --bs-danger-rgb: 200, 82, 92;
  --bs-body-bg: #f5f8fc;
  --bs-body-bg-rgb: 245, 248, 252;
  --bs-body-color: #1d2d40;
  --bs-body-color-rgb: 29, 45, 64;
  --bs-body-font-family: var(--kasa-font-body);
  --bs-heading-color: #1d2d40;
  --bs-border-color: #dbe5ef;
  --bs-link-color: #2d639e;
  --bs-link-color-rgb: 45, 99, 158;
  --bs-link-hover-color: #244f80;
  --bs-link-hover-color-rgb: 36, 79, 128;
  --bs-border-radius: 0.75rem;
  --bs-border-radius-lg: 1rem;
  --bs-card-bg: rgba(255, 255, 255, 0.88);
  --bs-card-border-color: rgba(219, 229, 239, 0.9);
  --bs-card-border-radius: 22px;
  --bs-card-box-shadow: 0 18px 48px rgba(49, 83, 117, 0.12);
}

/* ---------- Dasar halaman ---------- */
html body {
  background-color: var(--kasa-paper);
  background-image:
    radial-gradient(720px 360px at 8% -8%, #e4f0fc, transparent 68%),
    radial-gradient(640px 360px at 100% 0%, #dbeafb, transparent 68%);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--kasa-ink);
  font-family: var(--kasa-font-body);
}
html body.has-navbar-vertical-aside {
  background-color: var(--kasa-paper);
  background-image:
    radial-gradient(720px 360px at 8% -8%, #e4f0fc, transparent 68%),
    radial-gradient(640px 360px at 100% 0%, #dbeafb, transparent 68%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}

body h1, body h2, body h3, body h4, body h5, body h6,
body .h1, body .h2, body .h3, body .h4, body .h5, body .h6, body .card-title {
  font-family: var(--kasa-font-display);
  letter-spacing: -0.01em;
}
body code, body kbd, body pre, body samp, body .font-monospace {
  font-family: var(--kasa-font-mono);
}

/* ---------- Card ---------- */
body .card {
  background-color: var(--kasa-panel);
  border-color: var(--kasa-line);
  border-radius: var(--kasa-radius-card);
  box-shadow: var(--kasa-shadow);
}
body .card-header {
  border-bottom-color: var(--kasa-line);
}

/* ---------- Tombol ---------- */
body .btn {
  min-height: 46px;
  border-radius: var(--kasa-radius-btn);
  font-weight: 700;
}
body .btn-sm, body .btn-group-sm > .btn {
  min-height: 34px;
  border-radius: 10px;
}
body .btn-primary {
  --bs-btn-bg: #2d639e;
  --bs-btn-border-color: #2d639e;
  --bs-btn-hover-bg: #28558f;
  --bs-btn-hover-border-color: #28558f;
  --bs-btn-active-bg: #28558f;
  --bs-btn-active-border-color: #28558f;
  --bs-btn-disabled-bg: #2d639e;
  --bs-btn-disabled-border-color: #2d639e;
  --bs-btn-focus-shadow-rgb: 45, 99, 158;
  box-shadow: var(--kasa-shadow-btn);
}
body .btn-outline-primary {
  --bs-btn-color: #2d639e;
  --bs-btn-border-color: #2d639e;
  --bs-btn-hover-bg: #2d639e;
  --bs-btn-hover-border-color: #2d639e;
  --bs-btn-active-bg: #2d639e;
  --bs-btn-active-border-color: #2d639e;
  --bs-btn-focus-shadow-rgb: 45, 99, 158;
}
body .btn-ghost-secondary {
  --bs-btn-color: var(--kasa-ink-dim);
  --bs-btn-hover-bg: var(--kasa-accent-soft);
  --bs-btn-hover-color: var(--kasa-accent-strong);
}

/* 2026-09-20 parity auth: btn-secondary solid vendor (#71869d abu kebiruan)
   bukan warna tema. Chip netral auth = bg line-strong #b8c9da + ink #1d2d40
   (4.62:1) — sinkron dgn badge text-bg-secondary. */
body .btn-secondary {
  --bs-btn-color: #1d2d40;
  --bs-btn-hover-color: #1d2d40;
  --bs-btn-active-color: #1d2d40;
  --bs-btn-disabled-color: #1d2d40;
  --bs-btn-bg: #b8c9da;
  --bs-btn-border-color: #b8c9da;
  --bs-btn-hover-bg: #a9bdd2;
  --bs-btn-hover-border-color: #a9bdd2;
  --bs-btn-active-bg: #9ab1c9;
  --bs-btn-active-border-color: #9ab1c9;
}

/* 2026-09-20 parity auth: link body light = --accent-strong #28558f (auth
   a:not(.btn)), hover #204979. Abu vendor #67788b melanggar tema.
   Dark sudah benar via --bs-link-color #e8a04a (= auth --accent). */
body a:not(.btn):not(.nav-link) {
  color: #28558f;
}
body a:not(.btn):not(.nav-link):hover {
  color: #244f80;
}
body .btn-link {
  color: var(--kasa-accent);
}

/* ---------- Form ---------- */
body .form-control:not(textarea),
body .form-select {
  min-height: 46px;
  border-radius: var(--kasa-radius-input);
  background-color: rgba(247, 250, 253, 0.88);
  border-color: var(--kasa-line);
  /* r27: warna vendor #677788 di bg rgba(247,250,253,.88) ≈ 4.0:1 (< AA).
     Ink kasa lolos kontras. */
  color: var(--kasa-ink);
}
body .form-control:focus,
body .form-select:focus {
  border-color: #2d639e;
  box-shadow: var(--kasa-focus-ring);
  background-color: var(--kasa-panel-solid);
}
body .form-control::placeholder {
  color: #8b9bad;
}
body .input-group-text {
  border-radius: var(--kasa-radius-input);
  border-color: var(--kasa-line);
  background-color: var(--kasa-accent-soft);
}
body .form-check-input:checked {
  background-color: #2d639e;
  border-color: #2d639e;
}
/* #258 F4 — WCAG 2.2 SC 2.5.8 Target Size (Minimum, AA).
   Checkbox `form-check-input` terukur 16x16 px di editor RBAC (42 checkbox
   berjejer) dengan jarak-pusat 23px @375. 16x16 < 24x24 DAN jarak 23px < 24px,
   jadi pengecualian spacing TIDAK berlaku → gagal AA (bukan sekadar advisory).
   Perbaikan dua sisi, supaya lulus lewat dua jalur sekaligus:
     1. area klik label dinaikkan ke >= 24x24 (padding vertikal pada label),
     2. irama baris dinaikkan supaya jarak-pusat >= 24px.
   Diverifikasi `verify-a11y-fixes.mjs targets` (0 pelanggaran @375 & @1440). */
body .form-check {
  min-height: 24px;
  margin-bottom: 0.25rem;
}
body .form-check-inline {
  margin-bottom: 0.25rem;
}
body .form-check-label {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding-top: 0.125rem;
  padding-bottom: 0.125rem;
}
body .form-check-input:focus {
  border-color: #2d639e;
  box-shadow: var(--kasa-focus-ring);
}
body .form-label {
  color: var(--kasa-ink);
  font-weight: 500;
}

/* ---------- Header / sidebar ---------- */
#header.navbar {
  border-bottom: 1px solid var(--kasa-line);
}
/* r27: kelas asli aside = navbar-vertical-aside (`.navbar-sidebar` tidak ada
   di vendor markup — selector lama mati). bg !important untuk mengalahkan
   vendor `.bg-white{...!important}`. */
body .navbar-vertical.navbar-vertical-aside {
  background: rgba(255, 255, 255, 0.92) !important;
  border-right: 1px solid var(--kasa-line) !important;
}
/* Flat sidebar: preserve vendor widths and Calm Mint tokens in both modes.
   ID scope beats theme-appearance's late vendor stylesheet clone. */
body #navbarVerticalMenu {
  padding: 0 12px 12px;
  min-width: 0;
}
body #navbarVerticalMenu .nav-section-title {
  display: block;
  margin: 16px 12px 6px;
  padding-top: 12px;
  border-top: 1px solid var(--kasa-line);
  color: var(--kasa-ink-dim);
  font-family: var(--kasa-font-body);
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  overflow: hidden;
  white-space: nowrap;
}
body #navbarVerticalMenu .nav-section-title:first-child {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 0;
}
body #navbarVerticalMenu .nav-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  box-sizing: border-box;
  height: 44px;
  min-height: 44px;
  min-width: 0;
  margin: 2px 0;
  padding: 0 12px;
  border-radius: 10px;
  color: var(--kasa-ink);
  font-family: var(--kasa-font-body);
}
body #navbarVerticalMenu .nav-icon {
  flex: 0 0 20px;
  width: 20px;
  margin: 0;
  text-align: center;
  font-size: 18px;
  color: inherit;
}
body #navbarVerticalMenu .nav-link-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body #navbarVerticalMenu .nav-link:hover,
body #navbarVerticalMenu .nav-link.active {
  color: var(--kasa-accent-strong);
  background: var(--kasa-accent-soft);
}
body #navbarVerticalMenu .nav-link.active {
  font-weight: 600;
}
body #navbarVerticalMenu .nav-link.active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 12px;
  bottom: 12px;
  width: 3px;
  border-radius: 3px;
  background: var(--kasa-accent);
}
body #navbarVerticalMenu .nav-link:focus-visible {
  outline: 2px solid var(--kasa-accent);
  outline-offset: 2px;
  box-shadow: var(--kasa-focus-ring);
}
body.navbar-vertical-aside-mini-mode #navbarVerticalMenu {
  padding-inline: 8px;
}
body.navbar-vertical-aside-mini-mode #navbarVerticalMenu .nav-link {
  justify-content: center;
  gap: 0;
  padding-inline: 0;
}
body.navbar-vertical-aside-mini-mode #navbarVerticalMenu .nav-link-title {
  display: none;
}
body.navbar-vertical-aside-mini-mode #navbarVerticalMenu .nav-section-title {
  margin: 12px 8px 6px;
  padding: 0;
  height: 1px;
  font-size: 0;
  line-height: 0;
  color: transparent;
}
/* Static accordions reuse Calm Mint rows, borders and focus tokens. */
body #navbarVerticalMenu .kasa-nav-group { width: 100%; min-width: 0; }
body #navbarVerticalMenu .kasa-nav-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  margin: 8px 0 4px;
  padding: 8px 12px;
  border: 0;
  border-top: 1px solid var(--kasa-line);
  background: transparent;
  text-align: left;
  cursor: pointer;
}
body #navbarVerticalMenu .kasa-nav-group:first-child .kasa-nav-toggle { border-top: 0; margin-top: 0; }
body #navbarVerticalMenu .kasa-nav-toggle:hover { color: var(--kasa-accent-strong); background: var(--kasa-accent-soft); }
body #navbarVerticalMenu .kasa-nav-toggle:focus-visible { outline: 2px solid var(--kasa-accent); outline-offset: -2px; box-shadow: var(--kasa-focus-ring); }
body #navbarVerticalMenu .kasa-nav-toggle[aria-expanded="false"] i { transform: rotate(-90deg); }
body #navbarVerticalMenu .kasa-nav-children[hidden] { display: none !important; }
body.navbar-vertical-aside-mini-mode #navbarVerticalMenu .kasa-nav-toggle { display: none; }
body.navbar-vertical-aside-mini-mode #navbarVerticalMenu .kasa-nav-group + .kasa-nav-group { border-top: 1px solid var(--kasa-line); margin-top: 8px; padding-top: 8px; }
/* Mobile drawer retains vendor scrolling/width and the same 44px targets. */

/* Account language disclosure: tokens + specificity shield, no vendor menu nesting. */
body .kasa-acct-dropdown { overflow: visible; max-width: calc(100vw - 24px); }
body .kasa-language-group { position: relative; }
body .kasa-language-menu {
  position: absolute;
  right: 100%;
  top: 0;
  width: 220px;
  padding: 6px;
  background: var(--kasa-panel-solid);
  border: 1px solid var(--kasa-line);
  border-radius: var(--kasa-radius-btn);
  box-shadow: var(--kasa-shadow);
  z-index: 1;
}
body .kasa-language-menu[hidden] { display: none !important; }
body .kasa-language-group .dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  white-space: normal;
  color: var(--kasa-ink);
  border-radius: var(--kasa-radius-input);
}
body .kasa-language-group .dropdown-item:hover,
body .kasa-language-group .dropdown-item[aria-pressed="true"] {
  background: var(--kasa-accent-soft);
  color: var(--kasa-accent-strong);
}
body .kasa-language-group .dropdown-item[aria-pressed="true"]::after {
  content: "✓";
  margin-left: auto;
}
body .kasa-language-group .dropdown-item:focus-visible {
  outline: 2px solid var(--kasa-accent);
  outline-offset: -2px;
}
body .kasa-language-flag { flex: 0 0 20px; border: 1px solid var(--kasa-line); }
@media (max-width: 575.98px), (max-height: 420px) {
  body .kasa-acct-dropdown { max-height: calc(100dvh - 80px); overflow-y: auto; }
  body .kasa-language-menu {
    position: static;
    width: 100%;
    box-shadow: none;
  }
  body .kasa-language-toggle .bi-chevron-left { transform: rotate(-90deg); }
}

/* ---------- Dropdown / modal / toast ---------- */
body .dropdown-menu {
  border-radius: 16px;
  border-color: var(--kasa-line);
  box-shadow: var(--kasa-shadow);
}
body .dropdown-item.active,
body .dropdown-item:active {
  background-color: var(--kasa-accent-soft);
  color: var(--kasa-accent-strong);
}
body .modal-content {
  border-radius: 16px;
  border-color: var(--kasa-line);
  box-shadow: var(--kasa-shadow);
}
body .toast {
  border-radius: 14px;
  border-color: var(--kasa-line);
  box-shadow: var(--kasa-shadow);
}

/* ---------- Tabel / pager ---------- */
body .table {
  --bs-table-hover-bg: rgba(45, 99, 158, 0.05);
}
/* Pembungkus scroll juga jadi containing block. Anak ber-position:absolute di
   dalam tabel lebar — mis. label .visually-hidden pada tombol aksi baris —
   tanpa ini ber-containing block viewport, sehingga overflow:hidden milik
   pembungkus tidak meng-clip-nya dan dokumen ikut melebar di layar sempit. */
body .table-responsive {
  position: relative;
}
/* ---- #261 item 1: indikator gulir tabel ----
   Tabel KASA lebih lebar dari wadahnya juga di DESKTOP, bukan hanya ponsel —
   terukur di 1280: `backup-produk` 200px, `licenses` 93px, `backup-internal`
   87px, `audit-log` 74px; di 1440 hanya `backup-produk` (40px); di 1920 nol.
   Klip terbesar justru di 1024/1280, BUKAN 1440. Sebelum ini tidak ada sinyal
   apa pun bahwa ada kolom di kanan.

   Kenapa petunjuknya SAUDARA di bawah tabel, bukan pill di dalam tabel.
   Varian "pill di pojok kanan-atas area gulir" juga diukur: ia menutupi teks
   sel header — terukur `Retensi Habis` 49px dari 60px lebar pill, `Ringkasan`
   17px, `Restore Aktif` 10px (`backup-internal`/`audit-log`/`backup-produk`).
   Indikator yang menutupi nama kolom menukar satu cacat dengan cacat lain.
   Sebagai saudara di bawah tabel, tumpang-tindih dengan teks tabel = 0 di
   keempat halaman (diverifikasi gate: `glyphOverlap=[]`), dan tidak ada satu
   pun sel yang tertutup.

   Kelas `kasa-scroll-x`/`kasa-scroll-end` dipasang `app-ui.js`
   (`initTableScrollHints`) HANYA untuk wrapper yang benar-benar terpotong.
   Tabel yang muat tidak mendapat catatan, `tabindex`, maupun `role` —
   perhentian Tab dan landmark kosong adalah kebisingan bagi pengguna
   keyboard/pembaca layar, bukan aksesibilitas.

   Catatan "Geser →" dirender sebagai satu elemen ber-`data-i18n` utuh (panah
   ikut di dalam nilainya), bukan teks + `<span>` panah terpisah: dengan
   `hookKey()` yang mencari hook sampai ke leluhur, satu hook per elemen
   adalah bentuk yang tidak bisa salah hitung cakupan. */
body .table-responsive.kasa-scroll-x {
  position: relative;
}
body .kasa-scroll-note {
  margin: 0.35rem 0 0;
  padding-inline-end: 0.25rem;
  text-align: right;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.6;
  color: var(--kasa-ink-dim);
}
body .table-responsive.kasa-scroll-x.kasa-scroll-end + .kasa-scroll-note {
  display: none;
}
body .pagination {
  --bs-pagination-color: #2d639e;
  --bs-pagination-hover-color: #28558f;
  --bs-pagination-hover-bg: var(--kasa-accent-soft);
  --bs-pagination-active-bg: #2d639e;
  --bs-pagination-active-border-color: #2d639e;
  --bs-pagination-focus-color: #28558f;
  --bs-pagination-focus-box-shadow: var(--kasa-focus-ring);
  gap: 0.25rem;
}
body .page-link {
  border-radius: 10px !important;
  border: 0;
}

/* ---------- Badge soft (prefix body: menang duel !important lawan salinan vendor) ---------- */
body .bg-soft-primary {
  background-color: rgba(45, 99, 158, 0.12) !important;
  color: #28558f !important;
}
body .bg-soft-success {
  background-color: rgba(45, 138, 92, 0.12) !important;
  color: #2d8a5c !important;
}
body .bg-soft-danger {
  background-color: rgba(200, 82, 92, 0.12) !important;
  color: #c8525c !important;
}
body .bg-soft-warning {
  background-color: rgba(232, 160, 74, 0.14) !important;
  color: #9c6a24 !important;
}
body .bg-soft-info {
  background-color: rgba(45, 99, 158, 0.12) !important;
  color: #28558f !important;
}
body .bg-soft-secondary {
  background-color: rgba(102, 120, 139, 0.12) !important;
  color: #4a5d72 !important;
}

/* ---------- KPI sparkline + quick actions (pindahan dari inline splash) ---------- */
body .kpi-spark polyline {
  stroke: #2d639e;
}
body .kpi-spark polygon.kpi-spark-fill {
  fill: rgba(45, 99, 158, 0.08);
}
body .qa-item {
  color: var(--kasa-ink);
  border-radius: 12px;
}
body .qa-item:hover {
  background: var(--kasa-accent-soft);
}
body .qa-item .qa-icon {
  background: color-mix(in srgb, var(--kasa-accent) 10%, transparent);
  color: var(--kasa-accent);
}
body .qa-item .qa-title {
  color: var(--kasa-ink);
}
body .qa-item .qa-sub {
  color: var(--kasa-ink-dim);
}

/* #144: KPI card di dashboard sekarang <a> — pertahankan tampilan card, jangan
   mewarisi underline/color link, dan beri affordance jelas saat hover/focus. */
body a.kpi-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: box-shadow .15s ease, transform .15s ease;
}
body a.kpi-card-link:hover {
  box-shadow: 0 0.25rem 0.75rem rgba(16, 20, 26, 0.1);
  transform: translateY(-1px);
}
body a.kpi-card-link:focus-visible {
  outline: 2px solid var(--kasa-accent);
  outline-offset: 2px;
}
body .kpi-link-icon {
  font-size: .75rem;
  margin-left: .35rem;
  color: var(--kasa-accent);
}
body a.kpi-card-link:hover .kpi-link-icon { color: var(--kasa-accent-dim); }

/* ---------- Splash (kontrak id #kasa-splash + removeBlocker app-shell.js tetap) ---------- */
#kasa-splash {
  background: var(--kasa-paper);
}
#kasa-splash .kasa-brand {
  color: var(--kasa-ink);
}
#kasa-splash h1 {
  font-family: var(--kasa-font-display);
  color: var(--kasa-ink);
}
#kasa-splash p {
  color: var(--kasa-ink-dim);
}
#kasa-splash .spin {
  border: 2.5px solid var(--kasa-line);
  border-top-color: #2d639e;
}
#kasa-splash .mark rect {
  fill: #2d639e;
}

/* ---------- Footer ---------- */
body .footer {
  color: var(--kasa-ink-dim);
}
body .footer a {
  color: var(--kasa-accent);
}

/* ---------- Dark: pre-dawn server room (token auth.intiserver.com heritage) ---------- */
/* html.kasa-dark-pending:root = (0,2,1) — WAJIB lebih tinggi dari light :root:root
   (0,2,0): vendor cascade shield menyuntikkan salinan stylesheet di akhir head,
   var token yang di-resolve di html element harus tetap ambil nilai dark. */
html.kasa-dark-pending:root {
  --kasa-accent: #e8a04a;
  --kasa-accent-dim: #b37b35;
  --kasa-accent-strong: #f0b268;
  --kasa-accent-soft: rgba(232, 160, 74, 0.14);
  --kasa-accent-ink: #1a1206;
  --kasa-paper: #10141a;
  --kasa-panel: #161c24;
  --kasa-panel-solid: #161c24;
  --kasa-panel-2: #1b2230;
  --kasa-line: #2a3442;
  --kasa-ink: #e6e2d6;
  --kasa-ink-dim: #8a94a3;
  --kasa-ok: #6fb87a;
  --kasa-danger: #d97373;
  --kasa-warn: #e8a04a;
  /* #259 — varian teks di atas latar pucat, gelap. Nilai light TIDAK bisa
     dipakai di sini (latar dark panel #161c24, bukan putih), jadi di-resolve
     ulang terhadap composite dark, bukan sekadar disalin. */
  --kasa-ok-ink: #b8dcb8;
  --kasa-danger-ink: #f0b4b4;
  --kasa-warn-ink: #f0b268;
  --kasa-info-ink: #f0b268;
  --kasa-radius-card: 8px;
  --kasa-radius-btn: 8px;
  --kasa-radius-input: 6px;
  --kasa-focus-ring: 0 0 0 1px var(--kasa-paper), 0 0 0 3px var(--kasa-accent);
  --kasa-shadow: 0 18px 48px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.35);
  --kasa-shadow-btn: none;

  --bs-primary: #e8a04a;
  --bs-primary-rgb: 232, 160, 74;
  --bs-blue: #e8a04a;
  --bs-blue-rgb: 232, 160, 74;
  --bs-secondary: #8a94a3;
  --bs-secondary-rgb: 138, 148, 163;
  --bs-success: #6fb87a;
  --bs-success-rgb: 111, 184, 122;
  --bs-info: #e8a04a;
  --bs-info-rgb: 232, 160, 74;
  --bs-warning: #e8a04a;
  --bs-warning-rgb: 232, 160, 74;
  --bs-danger: #d97373;
  --bs-danger-rgb: 217, 115, 115;
  --bs-body-bg: #10141a;
  --bs-body-bg-rgb: 16, 20, 26;
  --bs-body-color: #e6e2d6;
  --bs-body-color-rgb: 230, 226, 214;
  --bs-heading-color: #e6e2d6;
  --bs-border-color: #2a3442;
  --bs-link-color: #e8a04a;
  --bs-link-color-rgb: 232, 160, 74;
  --bs-link-hover-color: #f0b268;
  --bs-link-hover-color-rgb: 240, 178, 104;
  --bs-card-bg: #161c24;
  --bs-card-border-color: #2a3442;
  --bs-card-border-radius: 8px;
  --bs-card-box-shadow: 0 18px 48px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.35);
}
html.kasa-dark-pending body {
  background-color: #10141a;
  background-image:
    radial-gradient(720px 360px at 8% -8%, rgba(232, 160, 74, 0.05), transparent 68%),
    radial-gradient(640px 360px at 100% 0%, rgba(45, 99, 158, 0.06), transparent 68%);
  color: #e6e2d6;
}
html.kasa-dark-pending body.has-navbar-vertical-aside {
  background-color: #10141a;
  background-image:
    radial-gradient(720px 360px at 8% -8%, rgba(232, 160, 74, 0.05), transparent 68%),
    radial-gradient(640px 360px at 100% 0%, rgba(45, 99, 158, 0.06), transparent 68%);
}
html.kasa-dark-pending .card {
  background-color: #161c24;
  border-color: #2a3442;
}
/* r27: !important wajib — vendor dark `.bg-white{background-color:#25282a!important}`
   mengalahkan override non-important apapun specificity-nya. */
html.kasa-dark-pending #header.navbar {
  background: #161c24 !important;
  border-bottom-color: #2a3442 !important;
}
html.kasa-dark-pending .navbar-vertical.navbar-vertical-aside {
  background: #161c24 !important;
  border-right: 1px solid #2a3442 !important;
}
html.kasa-dark-pending .navbar-vertical .nav .nav-link.active,
html.kasa-dark-pending .navbar-vertical .nav .nav-link:hover {
  color: #f0b268;
  background: rgba(232, 160, 74, 0.12);
}
html.kasa-dark-pending .btn-primary {
  --bs-btn-color: #1a1206;
  --bs-btn-bg: #e8a04a;
  --bs-btn-border-color: #e8a04a;
  --bs-btn-hover-color: #1a1206;
  --bs-btn-hover-bg: #f0b268;
  --bs-btn-hover-border-color: #f0b268;
  --bs-btn-active-color: #1a1206;
  --bs-btn-active-bg: #f0b268;
  --bs-btn-active-border-color: #f0b268;
  --bs-btn-disabled-color: #1a1206;
  --bs-btn-disabled-bg: #e8a04a;
  --bs-btn-disabled-border-color: #e8a04a;
  --bs-btn-focus-shadow-rgb: 232, 160, 74;
}
html.kasa-dark-pending .btn-outline-primary {
  --bs-btn-color: #e8a04a;
  --bs-btn-border-color: #e8a04a;
  --bs-btn-hover-color: #1a1206;
  --bs-btn-hover-bg: #e8a04a;
  --bs-btn-hover-border-color: #e8a04a;
  --bs-btn-active-color: #1a1206;
}
html.kasa-dark-pending .form-control:not(textarea),
html.kasa-dark-pending .form-select {
  background-color: #1b2230;
  border-color: #2a3442;
  color: #e6e2d6;
}
html.kasa-dark-pending .form-control:focus,
html.kasa-dark-pending .form-select:focus {
  border-color: #e8a04a;
  background-color: #161c24;
  box-shadow: var(--kasa-focus-ring);
}
html.kasa-dark-pending .form-control::placeholder {
  color: #6b7684;
}
html.kasa-dark-pending .form-check-input:checked {
  background-color: #e8a04a;
  border-color: #e8a04a;
}
html.kasa-dark-pending .dropdown-menu {
  background-color: #1b2230;
  border-color: #2a3442;
}
html.kasa-dark-pending .dropdown-item {
  color: #e6e2d6;
}
html.kasa-dark-pending .dropdown-item.active,
html.kasa-dark-pending .dropdown-item:active {
  background-color: rgba(232, 160, 74, 0.14);
  color: #f0b268;
}
html.kasa-dark-pending .modal-content {
  background-color: #161c24;
  border-color: #2a3442;
}
html.kasa-dark-pending .toast {
  background-color: #1b2230;
  border-color: #2a3442;
  color: #e6e2d6;
}
html.kasa-dark-pending .table {
  --bs-table-color: #e6e2d6;
  --bs-table-border-color: #2a3442;
  --bs-table-hover-bg: rgba(232, 160, 74, 0.06);
  --bs-table-striped-color: #e6e2d6;
  --bs-table-striped-bg: rgba(255, 255, 255, 0.02);
}
html.kasa-dark-pending .bg-soft-primary {
  background-color: rgba(232, 160, 74, 0.14) !important;
  color: #f0b268 !important;
}
html.kasa-dark-pending .bg-soft-success {
  background-color: rgba(111, 184, 122, 0.14) !important;
  color: #6fb87a !important;
}
html.kasa-dark-pending .bg-soft-danger {
  background-color: rgba(217, 115, 115, 0.14) !important;
  color: #d97373 !important;
}
html.kasa-dark-pending .bg-soft-warning {
  background-color: rgba(232, 160, 74, 0.14) !important;
  color: #f0b268 !important;
}
html.kasa-dark-pending .bg-soft-info {
  background-color: rgba(232, 160, 74, 0.14) !important;
  color: #f0b268 !important;
}
html.kasa-dark-pending .bg-soft-secondary {
  background-color: rgba(138, 148, 163, 0.16) !important;
  color: #c3cbd6 !important;
}
html.kasa-dark-pending .kpi-spark polyline {
  stroke: #e8a04a;
}
html.kasa-dark-pending .kpi-spark polygon.kpi-spark-fill {
  fill: rgba(232, 160, 74, 0.1);
}
html.kasa-dark-pending .qa-item {
  color: #e6e2d6;
}
html.kasa-dark-pending .qa-item:hover {
  background: #1b2230;
}
html.kasa-dark-pending .qa-item .qa-icon {
  background: rgba(232, 160, 74, 0.12);
  color: #e8a04a;
}
html.kasa-dark-pending .qa-item .qa-title {
  color: #e6e2d6;
}
html.kasa-dark-pending .qa-item .qa-sub {
  color: #8a94a3;
}
html.kasa-dark-pending #kasa-splash {
  background: #10141a;
}
html.kasa-dark-pending #kasa-splash .kasa-brand,
html.kasa-dark-pending #kasa-splash h1 {
  color: #e6e2d6;
}
html.kasa-dark-pending #kasa-splash p {
  color: #8a94a3;
}
html.kasa-dark-pending #kasa-splash .spin {
  border-color: #2a3442;
  border-top-color: #e8a04a;
}
html.kasa-dark-pending #kasa-splash .mark rect {
  fill: #e8a04a;
}
html.kasa-dark-pending .footer {
  color: #8a94a3;
}
html.kasa-dark-pending .footer a {
  color: #e8a04a;
}
html.kasa-dark-pending .btn-ghost-secondary {
  --bs-btn-color: #8a94a3;
  --bs-btn-hover-bg: rgba(232, 160, 74, 0.12);
  --bs-btn-hover-color: #f0b268;
}
html.kasa-dark-pending .form-label {
  color: #e6e2d6;
}
html.kasa-dark-pending .input-group-text {
  background-color: #1b2230;
  border-color: #2a3442;
  color: #e6e2d6;
}

/* ---- r25: UX fixes (#92) ---- */

/* Vendor menambah margin-left:16.25rem pada .footer di >=1200px, padahal
   footer sudah normal-flow di dalam .main yang punya padding-left 16.25rem
   -> dobel offset, footer keluar viewport. Kembalikan ke 0. */
body.has-navbar-vertical-aside .main > .footer {
  margin-left: 0 !important;
}

/* Popup dropdown native (select/date) ikut color-scheme halaman. */
:root:root {
  color-scheme: light;
}
html.kasa-dark-pending:root {
  color-scheme: dark;
}

/* Logo brand CDN: jaga proporsi, cegah img besar melebar navbar. */
body .navbar-brand-logo {
  max-height: 2.25rem;
  width: auto;
}

/* ---- r27: btn-soft-* mapping token (#98-C) ----
   Vendor hardcode aksen biru lama di .btn-soft-* (base/hover/active/disabled
   solid) dan tidak konsumsi var kasa → aksen drift. Map semua varian ke
   token. Disabled (#117): rule vendor `.btn-soft-*:disabled` (0,2,0) memenangkan
   base mapping di atas (0,1,1) dan set color == background-color (aksen solid
   sama) → label tak terlihat. Fix: teks putih di atas bg saturated vendor
   (terbaca light+dark) + opacity .55 agar jelas tidak aktif. */
body .btn-soft-primary {
  color: var(--kasa-accent);
  background-color: var(--kasa-accent-soft);
  border-color: transparent;
}
body .btn-soft-primary:hover,
body .btn-soft-primary.show > .dropdown-toggle {
  color: var(--kasa-accent-ink);
  background-color: var(--kasa-accent-strong);
  border-color: transparent;
}
body .btn-soft-primary:active {
  color: var(--kasa-accent-ink);
  background-color: var(--kasa-accent-strong);
  border-color: transparent;
}
body .btn-soft-secondary {
  color: var(--bs-secondary);
  background-color: color-mix(in srgb, var(--bs-secondary) 12%, transparent);
  border-color: transparent;
}
body .btn-soft-secondary:hover,
body .btn-soft-secondary.show > .dropdown-toggle,
body .btn-soft-secondary:active {
  color: #fff;
  background-color: var(--bs-secondary);
  border-color: transparent;
}
body .btn-soft-success {
  color: var(--bs-success);
  background-color: color-mix(in srgb, var(--bs-success) 12%, transparent);
  border-color: transparent;
}
body .btn-soft-success:hover,
body .btn-soft-success.show > .dropdown-toggle,
body .btn-soft-success:active {
  color: #fff;
  background-color: var(--bs-success);
  border-color: transparent;
}
body .btn-soft-danger {
  color: var(--bs-danger);
  background-color: color-mix(in srgb, var(--bs-danger) 12%, transparent);
  border-color: transparent;
}
body .btn-soft-danger:hover,
body .btn-soft-danger.show > .dropdown-toggle,
body .btn-soft-danger:active {
  color: #fff;
  background-color: var(--bs-danger);
  border-color: transparent;
}
body .btn-soft-warning {
  color: var(--bs-warning);
  background-color: color-mix(in srgb, var(--bs-warning) 14%, transparent);
  border-color: transparent;
}
body .btn-soft-warning:hover,
body .btn-soft-warning.show > .dropdown-toggle,
body .btn-soft-warning:active {
  color: #3d2a05;
  background-color: var(--bs-warning);
  border-color: transparent;
}
body .btn-soft-info {
  color: var(--bs-info);
  background-color: color-mix(in srgb, var(--bs-info) 12%, transparent);
  border-color: transparent;
}
body .btn-soft-info:hover,
body .btn-soft-info.show > .dropdown-toggle,
body .btn-soft-info:active {
  color: #fff;
  background-color: var(--bs-info);
  border-color: transparent;
}
body .btn-soft-dark {
  color: var(--kasa-ink);
  background-color: color-mix(in srgb, var(--kasa-ink) 10%, transparent);
  border-color: transparent;
}
body .btn-soft-dark:hover,
body .btn-soft-dark.show > .dropdown-toggle,
body .btn-soft-dark:active {
  color: var(--kasa-panel-solid);
  background-color: var(--kasa-ink);
  border-color: transparent;
}
/* Disabled (#117, revisi QC r1): vendor `.btn-soft-*:disabled` (0,2,0) set
   color == background-color (aksen solid sama) -> label tak terlihat. Teks
   putih di atas aksen vendor kontrasnya intrinsik rendah (putih di teal
   #00c9a7 = 2.1:1), dan opacity mengompres seluruh elemen terhadap page bg
   (kontras efektif anjlok ~2:1). Ghost scheme berbasis token: bg = 8% ink,
   teks = ink -> >=9:1 di light+dark, terbaca, tetap tampak "mati". */
body .btn-soft-primary:disabled,
body .btn-soft-primary.disabled,
body .btn-soft-secondary:disabled,
body .btn-soft-secondary.disabled,
body .btn-soft-success:disabled,
body .btn-soft-success.disabled,
body .btn-soft-danger:disabled,
body .btn-soft-danger.disabled,
body .btn-soft-warning:disabled,
body .btn-soft-warning.disabled,
body .btn-soft-info:disabled,
body .btn-soft-info.disabled,
body .btn-soft-dark:disabled,
body .btn-soft-dark.disabled,
body .btn-soft-light:disabled,
body .btn-soft-light.disabled {
  color: var(--kasa-ink);
  background-color: color-mix(in srgb, var(--kasa-ink) 8%, transparent);
  border-color: transparent;
  /* QC r2: vendor .btn:disabled pakai --bs-btn-disabled-opacity .65 yang
     mengompres ghost terhadap page bg (light ~4.2:1, fill nyaris tak terlihat).
     Opacity 1 = kontras penuh 11-12:1; affordance disabled tetap dari ghost
     fill + teks redup + cursor not-allowed. */
  --bs-btn-disabled-opacity: 1;
}
.btn-white:disabled,
.btn-white.disabled {
  color: #67788b !important;
}

body .btn-primary {
  --bs-btn-disabled-color: #fff;
}

/* ---- r27: badge text-bg-* override (#98-D) ----
   Vendor `.text-bg-*` hardcode palet raw + !important (teal #00c9a7, pink
   #ed4c78, dst.) → badge status invoice/subscription melampaui token kasa.
   Pasangan warna dihitung >= 4.5:1:
   2026-09-20 parity auth.intiserver.com: hue diambil persis dari token auth
   (--ok #2d8a5c, --danger #c8525c); light
   success #2d8a5c/fff, warning #e8a04a/#1a1206, danger #c8525c/fff,
   info #2d639e/fff 6.19, secondary #b8c9da/#1d2d40 4.62
   dark:  success #6fb87a/#12290f 6.53, warning #e8a04a/#1a1206 8.44,
          danger #d97373/#2a0d10 5.72, info #e8a04a/#1a1206 8.44,
          secondary #8a94a3/#10141a 6.02 */
/* 2026-09-20 revisi: gaya chip status auth.intiserver.com — SOFT (latar
   pucat + teks gelap berwarna + border tipis), BUKAN blok solid+putih.
   Pasangan diambil persis dari bundle auth (.ok / .err light):
   success bg #f0faf4 border #c8e8d3 text #246e49; danger bg #fff4f5
   border #f0c8cc text #9d3d47; accent soft #e6f0fb + accent-strong. */
.badge.text-bg-success {
  color: #246e49 !important;
  background-color: #f0faf4 !important;
  border-color: #c8e8d3 !important;
}
.badge.text-bg-warning {
  color: #936422 !important;
  background-color: #fbeedd !important;
  border-color: #f0d9b0 !important;
}
.badge.text-bg-danger {
  color: #9d3d47 !important;
  background-color: #fff4f5 !important;
  border-color: #f0c8cc !important;
}
.badge.text-bg-info {
  color: #28558f !important;
  background-color: #e6f0fb !important;
  border-color: #cfe1f5 !important;
}
.badge.text-bg-secondary {
  color: #1d2d40 !important;
  background-color: #f5f8fc !important;
  border-color: #dbe5ef !important;
}
html.kasa-dark-pending .badge.text-bg-success {
  color: #b8dcb8 !important;
  background-color: rgba(111, 184, 122, 0.09) !important;
  border-color: rgba(111, 184, 122, 0.22) !important;
}
html.kasa-dark-pending .badge.text-bg-warning {
  color: #f0b268 !important;
  background-color: rgba(232, 160, 74, 0.12) !important;
  border-color: rgba(232, 160, 74, 0.22) !important;
}
html.kasa-dark-pending .badge.text-bg-danger {
  color: #f0b4b4 !important;
  background-color: rgba(217, 115, 115, 0.09) !important;
  border-color: rgba(217, 115, 115, 0.22) !important;
}
html.kasa-dark-pending .badge.text-bg-info {
  color: #f0b268 !important;
  background-color: rgba(232, 160, 74, 0.12) !important;
  border-color: rgba(232, 160, 74, 0.22) !important;
}
html.kasa-dark-pending .badge.text-bg-secondary {
  color: #b8c2cc !important;
  background-color: rgba(138, 148, 163, 0.14) !important;
  border-color: rgba(138, 148, 163, 0.24) !important;
}

/* ---- r28: badge bg-soft-* kontras (#100-E) ----
   bg soft = alpha 0.12–0.16 di atas panel → kontras dihitung pada composite
   (token×alpha + underlay×(1-alpha)); underlay putih (light) / #161c24 (dark).
   Badge 12px = normal text → ambang WCAG AA 4.5:1. 4 pasangan gagal:
   light success #2d8a5c 3.70 → #277950 4.61
   light danger  #c8525c 3.74 → #b04851 4.63
   light warning #9c6a24 4.21 → #936422 4.63
   dark  danger  #d97373 4.50 → #e37878 4.88
   (soft secondary/info + dark lainnya sudah >= 4.5 — tidak diubah.
    Specificity (0,3,0)+!important mengalahkan utility vendor .text-* !important.) */
.badge.bg-soft-success.text-success { color: #277950 !important; }
.badge.bg-soft-danger.text-danger { color: #b04851 !important; }
.badge.bg-soft-warning.text-warning { color: #936422 !important; }
html.kasa-dark-pending .badge.bg-soft-danger.text-danger { color: #e37878 !important; }

/* ---- r29: utility .text-success di luar badge (#102) ----
   Consumer: toast icon app-ui.js:26 ("bi-check-circle-fill text-success") di
   atas toast putih. Token --bs-success-rgb #2d8a5c gagal AA 1.4.3 pada light:
   putih 4.284, --kasa-paper 4.022, --kasa-accent-soft 3.718. Fix = tone badge
   r28 #277950 (putih 5.334, paper 5.008, accent-soft 4.628). Dark token
   #6fb87a sudah pass (7.19/6.69/7.75) — dipin eksplisit agar salinan
   cascade shield tidak bisa menggesernya. Specificity (0,1,1)+!important
   > utility vendor (0,1,0)+!important; rule badge r28 (0,3,0) tetap menang. */
body .text-success { color: #277950 !important; }
html.kasa-dark-pending body .text-success { color: #6fb87a !important; }

/* ---- r61: badge tidak lebih kecil dari ambang baca (#257 §4.9) ----
   Terukur di `users.html`: badge 10.5px, elemen terkecil di tabel, padahal ia
   PEMBAWA MAKNA (customer/cs/active/suspended). Kontrasnya sudah lolos
   (5.87:1, r27/r28) — jadi yang tersisa murni UKURAN: WCAG 2.2 1.4.4 (Resize
   Text) + ISO 25010 *UI aesthetics*.

   Sebabnya tidak di berkas ini: vendor memasang `--bs-badge-font-size: 0.75em`
   PADA `.badge` sendiri, jadi ukurannya mengikuti font-size INDUK — di sel
   tabel 14px hasilnya 10.5px. `max()` mempertahankan niat vendor (badge lebih
   kecil dari teks sekitarnya) sambil memasang lantai 12px; badge di dalam
   konteks ber-font besar tidak ikut mengecil di bawah lantai itu.

   Prefix `body ` (0,1,1) wajib: salinan theme vendor disisipkan
   `hs.theme-appearance.js` di AKHIR <head> (lihat cascade shield di atas),
   dan `.badge` vendor (0,1,0) akan menang atas selector tanpa prefix.

   r52–r59 MEMASANG token ini di sini, tetapi `font-size`-nya masih
   ditulis TERPISAH di blok #259 bawah (baris ~2222 lama) sebagai
   `max(0.75em, 10.5px)`. Dua selector ber-specificity SAMA (0,1,1) → yang
   belakangan menang, jadi `font-size` datang dari rule bawah dan token 12px
   di sini TIDAK PERNAH DIBACA. Terukur di Chromium: computed `font-size` badge
   sel tabel = 10.5px, yaitu cacat yang "sudah diperbaiki" ini tetap hidup.
   Kelas cacat yang sama dengan duel cascade lain di berkas ini, dan alasan
   gate-nya harus MENGUKUR computed style, bukan meng-grep teks CSS.

   Perbaikannya bukan menaikkan angka di rule bawah, tapi menghapus
   duplikasinya: `font-size` di bawah kini membaca token ini
   (`var(--bs-badge-font-size)`), jadi hanya ada SATU sumber ukuran badge dan
   tidak ada lagi rule kedua yang bisa menang diam-diam. */
body .badge {
  --bs-badge-font-size: max(12px, 0.75em);
}

/* ---- r52: judul area di editor override RBAC (#257 §4.13) ----
   Tiap area jadi `<details>` (dibuat `pages/rbac.js`). Ringkasannya harus
   terbaca sebagai TOMBOL, bukan sebagai baris teks: kalau tidak, operator
   tidak tahu 44 baris izin masih tersembunyi di baliknya. Target klik
   dilebarkan (padding) + penanda buka/tutup bawaan browser dipertahankan
   (`list-style` tidak dinolkan) supaya affordance-nya tetap dikenali. */
body .rbac-area-summary {
  padding: .45rem .6rem;
  border: 1px solid var(--kasa-line, #dbe5ef);
  border-radius: 8px;
  background: var(--kasa-paper, #f5f8fc);
  color: var(--kasa-ink, #1d2d40);
  font-weight: 600;
  font-size: .875rem;
  cursor: pointer;
}
body .rbac-area-summary:hover { border-color: var(--kasa-accent); }
body .rbac-area-summary:focus-visible { outline: 2px solid var(--kasa-accent); outline-offset: 2px; }
body .rbac-area[open] > .rbac-area-summary { margin-bottom: .25rem; }
html.kasa-dark-pending body .rbac-area-summary {
  background: #161c24; border-color: #2a3442; color: #e6e2d6;
}

/* ---- r26: UX round-2 (#95) ---- */

/* Logo mini (aside collapsed) — CDN wordmark: tinggi = lebar vendor square
   mark (1.9125rem), width proporsional; contain agar tidak melebihi kolom
   collapsed & tidak squash. */
body .navbar-brand-logo-mini {
  width: auto;
  height: 1.9125rem;
  max-width: 100%;
  object-fit: contain;
}

/* Scrollbar default = terlihat "belum distyle". Track transparan, thumb
   line-color; Firefox via scrollbar-width/color, WebKit via pseudo. */
html {
  scrollbar-width: thin;
  scrollbar-color: #b9c9dc transparent;
}
html *::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
html *::-webkit-scrollbar-track {
  background: transparent;
}
html *::-webkit-scrollbar-thumb {
  background: #c3d3e4;
  border-radius: 9999px;
}
html *::-webkit-scrollbar-thumb:hover {
  background: #9fb4ca;
}
html.kasa-dark-pending:root {
  scrollbar-color: #2a3442 transparent;
}
html.kasa-dark-pending *::-webkit-scrollbar-thumb {
  background: #2a3442;
}
html.kasa-dark-pending *::-webkit-scrollbar-thumb:hover {
  background: #3a4657;
}

/* Popup <option> native (Chrome/Windows menghormati bg/color option):
   light putih ink, dark panel. Color-scheme r25 sudah bikin popup gelap,
   ini pola warnanya. */
body select option {
  background: #fff;
  color: #1d2d40;
}
html.kasa-dark-pending select option {
  background: #1b2230;
  color: #e6e2d6;
}

/* ---- #227: cakupan select/option/optgroup yang lengkap ----
   Sebelum blok ini hanya `select option` yang punya aturan. Tiga hal masih
   jatuh ke gaya default dan itu yang ditutup di sini:
   (a) `<select>` TANPA `.form-select` — tidak ada jaring pengaman sama sekali,
       jadi kalau ada select baru yang lupa diberi kelas ia tampil polos;
   (b) `<optgroup>` — dipakai pemilih ikon lokasi status page
       (`js/pages/status-page.js` locIconOptionsHtml); Chrome merender label
       grup dengan warna/font OS, dan di mode gelap popup-nya tidak ikut tema;
   (c) panah dropdown `.form-select` masih memakai aset vendor
       (stroke #71869d, abu-biru Front Dashboard) di kedua mode.
   Cascade shield: rule light WAJIB diprefix `body ` (vendor menyuntik salinan
   stylesheet di akhir head); dark aman lewat html.kasa-dark-pending. */
body select {
  appearance: none;
  -webkit-appearance: none;
  min-height: 46px;
  padding: 0.6125rem 2.25rem 0.6125rem 1rem;
  border: 1px solid var(--kasa-line);
  border-radius: var(--kasa-radius-input);
  background-color: rgba(247, 250, 253, 0.88);
  color: var(--kasa-ink);
  font-family: inherit;
  font-size: 0.875rem;
  line-height: 1.5;
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 13px 9px;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2366788b' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
/* Panah bertema juga untuk select ber-`.form-select` — (0,1,1) mengalahkan
   `.form-select` vendor (0,1,0). */
body .form-select {
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 13px 9px;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2366788b' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
/* Multi-select: tanpa caret, dan jangan dipaksa setinggi satu baris. */
body select[multiple],
body select[size]:not([size="1"]) {
  background-image: none;
  padding-right: 1rem;
  height: auto;
}
body select:disabled,
body .form-select:disabled {
  background-color: rgba(241, 245, 249, 0.9);
  color: var(--kasa-ink-dim);
  opacity: 1;
}
/* Label grup: warna sekunder + tebal, bukan gaya OS. */
body select optgroup {
  background-color: #ffffff;
  color: #66788b;
  font-weight: 600;
  font-style: normal;
}
body select option:checked {
  background: var(--kasa-accent-soft);
  color: var(--kasa-accent-strong);
}
body select option:disabled {
  color: #8b9bad;
}

html.kasa-dark-pending select {
  border-color: #2a3442;
  background-color: #1b2230;
  color: #e6e2d6;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%238a94a3' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
html.kasa-dark-pending .form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%238a94a3' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
html.kasa-dark-pending select[multiple],
html.kasa-dark-pending select[size]:not([size="1"]) {
  background-image: none;
}
html.kasa-dark-pending select:disabled,
html.kasa-dark-pending .form-select:disabled {
  background-color: #161c24;
  color: #8a94a3;
  opacity: 1;
}
html.kasa-dark-pending select optgroup {
  background-color: #1b2230;
  color: #8a94a3;
}
html.kasa-dark-pending select option:checked {
  background: rgba(232, 160, 74, 0.14);
  color: #f0b268;
}
html.kasa-dark-pending select option:disabled {
  color: #6b7684;
}

/* accent-color: checkbox/radio/progress/range yang lolos dari override
   form-check tetap memakai aksen tema, bukan biru OS default. */
/* ---- #148: SSOT warna status-dot (same values as status-page bar-dot) ---- */
:root:root {
  --kasa-dot-up: #2d8a5c;
  --kasa-dot-degraded: #e8a04a;
  --kasa-dot-down: #c8525c;
  --kasa-dot-unknown: #8a94a3;
  --kasa-dot-info: #66788b;
  --kasa-dot-critical: #a73d48;
}
html.kasa-dark-pending:root {
  --kasa-dot-up: #6fb87a;
  --kasa-dot-degraded: #e8a04a;
  --kasa-dot-down: #d97373;
  --kasa-dot-unknown: #8a94a3;
  --kasa-dot-info: #8a94a3;
  --kasa-dot-critical: #d97373;
}
:root:root {
  accent-color: #2d639e;
}
html.kasa-dark-pending:root {
  accent-color: #e8a04a;
}

/* Heading color token — vendor #1e2022 (light) / #fff (dark) melenceng dari
   kontrak ink. */
body h1,
body h2,
body h3,
body h4,
body h5,
body h6 {
  color: #1d2d40;
}
html.kasa-dark-pending body h1,
html.kasa-dark-pending body h2,
html.kasa-dark-pending body h3,
html.kasa-dark-pending body h4,
html.kasa-dark-pending body h5,
html.kasa-dark-pending body h6 {
  color: #e6e2d6;
}

/* .text-muted kontras — vendor #8c98a4 cuma 2.94:1 di atas kartu putih
   (below WCAG 3.0). Light = dim token; dark #8a94a3 (~5.6 vs panel #161c24).
   !important wajib: utility vendor juga !important.
   #259: sebelumnya di sini ditulis literal #67788b = 4.50:1 di atas putih
   (gagal tipis) dan 4.25:1 di atas `--kasa-paper` #f5f8fc (gagal jelas) —
   itulah 30 dari 42 selector yang masih merah. Sekarang memakai token yang
   sama dengan `.card-subtitle`/`.form-text` (#5a6c7e) supaya tidak bisa
   drift lagi: satu token, satu nilai, semua konsumen ikut naik. */
body .text-muted {
  color: var(--kasa-ink-dim) !important;
}
html.kasa-dark-pending .text-muted {
  color: #8a94a3 !important;
}

/* ============ Overview Dashboard (KASA design tokens) ============ */
#ov-banner { border-radius: var(--kasa-radius-card); }
#ov-banner .alert-heading { font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.1rem; }
#ov-stats .card {
  border-radius: var(--kasa-radius-card);
  box-shadow: var(--kasa-shadow);
  background: var(--kasa-panel);
  transition: transform .15s ease, box-shadow .15s ease;
}
#ov-stats .card:hover { transform: translateY(-2px); box-shadow: var(--kasa-shadow); }
#ov-services .card { border-radius: var(--kasa-radius-card); background: var(--kasa-panel); box-shadow: var(--kasa-shadow); }
#ov-services .card:hover { box-shadow: var(--kasa-shadow); transform: translateY(-2px); }
#ov-incidents .alert { border-radius: var(--kasa-radius-input); }

@media (max-width: 767px) {
  #ov-stats .col-6 { margin-bottom: .5rem; }
  #ov-services .col-6 { margin-bottom: .5rem; }

}

/* ---- #148 item 10: table-kit sortable header ---- */
.js-sortable-th {
  user-select: none;
}
.js-sortable-th:hover {
  color: var(--kasa-accent-strong);
}
.js-sortable-th .js-sort-icon {
  font-size: 0.7rem;
}

/* ---- #148: kasa-tabs — segmented view-switcher satu gaya untuk semua
   halaman (monitoring/billing/status-page). Gaya soft-chip tema auth:
   container panel pucat + item flat; item aktif = accent-soft bg + accent-
   strong teks (light) / accent alpha + accent teks (dark). Bukan tombol. */
.kasa-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  padding: 0.25rem;
  background: #f5f8fc;
  border: 1px solid var(--kasa-line, #dbe5ef);
  border-radius: 10px;
}
.kasa-tabs .kasa-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.42rem 0.9rem;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--kasa-ink-dim, #66788b);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.kasa-tabs .kasa-tab:hover {
  color: var(--kasa-accent-strong, #28558f);
  background: rgba(45, 99, 158, 0.08);
}
.kasa-tabs .kasa-tab.active {
  color: #28558f;
  background: #e6f0fb;
  border-color: #cfe1f5;
  font-weight: 600;
}
html.kasa-dark-pending .kasa-tabs {
  background: #1b2230;
  border-color: #2a3442;
}
html.kasa-dark-pending .kasa-tabs .kasa-tab {
  color: #8a94a3;
}
html.kasa-dark-pending .kasa-tabs .kasa-tab:hover {
  color: #f0b268;
  background: rgba(232, 160, 74, 0.08);
}
html.kasa-dark-pending .kasa-tabs .kasa-tab.active {
  color: #f0b268;
  background: rgba(232, 160, 74, 0.12);
  border-color: rgba(232, 160, 74, 0.22);
}

/* ---- #148: monitoring compact + help icon (CSS-only tooltip) ---- */
.mon-heading-compact {
  margin-bottom: 0.75rem;
}
#monitoring-updated-badge {
  margin: 0;
}
/* Kasa help: CSS tooltip posisi terikat ikon (absolute di wrapper), muncul
   saat hover / keyboard-focus. Z-index tinggi; max-width 20rem utk teks
   panjang. Tidak ada JS = tidak mungkin nyasar ke pojok layar. */
.kasa-help {
  position: relative;
  display: inline-flex;
  align-items: center;
  color: var(--kasa-ink-dim, #66788b);
  font-size: 0.95rem;
  cursor: help;
  outline-offset: 2px;
  /* #258 F2 — tooltip ::after selalu ter-generate (position:absolute,
     min-width:14rem) walau opacity:0/visibility:hidden. Kotaknya tetap ikut
     scrollable-overflow area, jadi ikon yang duduk dekat tepi kanan
     menggelembungkan scrollWidth dokumen (monitoring: +87px di semua lebar).
     Clip di wrapper saat tersembunyi → kotak tidak lagi menyumbang overflow;
     saat hover/focus-visible wrapper dilepas clip-nya supaya tooltip tampil. */
  overflow: hidden;
}
.kasa-help:hover,
.kasa-help:focus-visible {
  color: var(--kasa-accent-strong, #28558f);
  overflow: visible;
}
.kasa-help::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(2px);
  min-width: 14rem;
  max-width: 20rem;
  width: max-content;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  background: #1d2d40;
  color: #f5f8fc;
  font-size: 0.8rem;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  box-shadow: 0 4px 16px rgba(16, 20, 26, 0.25);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 1080;
}
.kasa-help:hover::after,
.kasa-help:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}
html.kasa-dark-pending .kasa-help {
  color: #8a94a3;
}
html.kasa-dark-pending .kasa-help:hover,
html.kasa-dark-pending .kasa-help:focus-visible {
  color: #f0b268;
}
html.kasa-dark-pending .kasa-help::after {
  background: #1b2230;
  color: #e6e2d6;
  border: 1px solid #2a3442;
}

/* ---- #258 F2b: reflow ≤414 (WCAG 2.2 SC 1.4.10) ----
   Baris aksi memakai flex tanpa wrap: .btn-group (vendor BS5: no flex-wrap),
   .card-header-content-between, dan .alert.d-flex. Di 320-414px lebar
   kontennya melebihi viewport sehingga dokumen bisa di-scroll horizontal
   (rbac 126/71/32, registry 114/59/20, billing 6, tickets 15). Solusinya
   membungkus baris, BUKAN memperkecil tombol — ukuran target tetap ≥24px
   (SC 2.5.8, lihat fix F4). */
@media (max-width: 575.98px) {
  body .btn-group { flex-wrap: wrap; }
  body .card-header-content-between { flex-wrap: wrap; }
  body .card-header-content-between > .d-flex { flex-wrap: wrap; }
  body .alert.d-flex { flex-wrap: wrap; }
  /* Tombol di dalam .btn-group punya flex:1 1 auto; saat wrap, biarkan
     melebar sesuai isi agar tidak ada baris yang memaksa keluar viewport. */
  body .btn-group > .btn { flex: 0 1 auto; }
}

/* ---- #258 F4 sisa: kolom aksi baris dipaku (sticky) saat tabel menggulir ----
   Issue #258 §3 mengukur 48/48 tombol aksi `licenses` dan 20/28 `tenant`
   berada DI LUAR kotak terlihat `.table-responsive` pada ≤414. Verifikasi batch
   B mengoreksi severity-nya (P1 → P2): setiap tombol memang punya leluhur
   ber-`overflow-x:auto` yang benar-benar bisa menggulir, jadi tidak ada tombol
   yang benar-benar hilang — dan jalur keyboard juga bekerja (fokus ke tombol
   yang terpotong menggulir wadahnya ke posisi terlihat). Yang TETAP benar:
   operator harus menggulir horizontal dulu untuk mencapai aksi utama setiap
   baris, dan itu memang belum diperbaiki.

   Perbaikannya adalah opsi pertama yang issue itu sendiri sebut: kolom aksi
   sticky. Hanya CSS, tanpa mengubah markup tabel per halaman. Ruang lingkupnya
   dibatasi dengan `:has()` ke tabel yang header terakhirnya memang kolom aksi
   (`th:last-child.text-end`) — jadi tabel data biasa (mis. "Metadata",
   "Dibuat", "Status") tidak ikut dipaku dan tidak ada kolom data yang tertutup
   tombol.

   TANPA media query, dan itu disengaja: cacatnya bukan "layar kecil" tetapi
   "tabel lebih lebar dari wadahnya", dan itu terjadi juga di 768–1440 (aside
   shell baru mengambil lebar pada ≥1200, jadi konten justru paling sempit di
   ~1200–1500). Diukur: 8 halaman masih punya aksi di luar kotak @768–1440.
   `position:sticky` pada kolom yang TIDAK menggulir tidak menggeser apa pun —
   ia duduk di posisi statisnya — jadi tidak ada efek samping di 1920.

   Latar sel harus opak, kalau tidak isi yang tergulir akan tampak menembus di
   bawah tombol; `box-shadow` negatif di tepi kiri adalah sinyal bahwa kolom itu
   mengapung di atas isi yang tergulir. */
body .table-responsive > table:has(> thead > tr > th:last-child.text-end) > thead > tr > th:last-child,
body .table-responsive > table:has(> thead > tr > th:last-child.text-end) > tbody > tr > td:last-child {
  position: sticky;
  right: 0;
  z-index: 2;
  background-color: var(--kasa-panel-solid, #fff);
  box-shadow: -6px 0 8px -6px rgba(16, 20, 26, 0.18);
}
body .table-responsive > table:has(> thead > tr > th:last-child.text-end) > thead > tr > th:last-child {
  z-index: 3;
}
/* Sel yang memuat dropdown TERBUKA harus naik di atas sel sticky baris lain.
   Tanpa ini muncul regresi nyata (tertangkap gate `issue-254-257-237-a11y-browser`):
   `z-index:2` pada td membuat td itu STACKING CONTEXT, jadi `.dropdown-menu`
   (z-index 1000 milik BS5) terkurung DI DALAM td-nya dan tidak lagi menang
   melawan td sticky baris berikutnya — menu baris 1 tertutup sel baris 2 dan
   itemnya tidak bisa diklik. Jadi yang dinaikkan adalah td-nya, bukan menunya. */
body .table-responsive > table:has(> thead > tr > th:last-child.text-end) > tbody > tr > td:last-child:has(.dropdown-menu.show) {
  z-index: 5;
}
html.kasa-dark-pending body .table-responsive > table:has(> thead > tr > th:last-child.text-end) > thead > tr > th:last-child,
html.kasa-dark-pending body .table-responsive > table:has(> thead > tr > th:last-child.text-end) > tbody > tr > td:last-child {
  background-color: #161c24;
  box-shadow: -6px 0 8px -6px rgba(0, 0, 0, 0.5);
}

/* ---- #148: kasa-tabs narrow screen → dropdown hemat tempat ---- */
@media (max-width: 767.98px) {
  .kasa-tabs.kasa-tabs-dropdown { position: relative; display: block; padding: 0; background: transparent; border: 0; }
  .kasa-tabs.kasa-tabs-dropdown .kasa-tab { display: none; width: 100%; border-radius: 0; padding: .55rem .9rem; }
  .kasa-tabs.kasa-tabs-dropdown .kasa-tabs-toggle { display: inline-flex; }
  .kasa-tabs.kasa-tabs-dropdown.open .kasa-tab { display: flex; }
  .kasa-tabs.kasa-tabs-dropdown.open { background: var(--kasa-panel-solid, #fff); border: 1px solid var(--kasa-line, #dbe5ef); border-radius: 10px; box-shadow: 0 8px 20px rgba(49,83,117,.12); }
}
.kasa-tabs .kasa-tabs-toggle { display: none; align-items: center; justify-content: space-between; width: 100%; padding: .55rem .9rem; border-radius: 9px; border: 1px solid var(--kasa-line, #dbe5ef); background: var(--kasa-panel-solid, #fff); color: var(--kasa-ink, #1d2d40); font-size: .9rem; font-weight: 500; cursor: pointer; }
/* #261 F14: byte 0x15 di sini adalah sisa escape CSS "BE" (U+25BE ▾)
   yang salah di-decode sebagai oktet 025 oleh tool teks, sehingga toggle
   merender teks literal "BE". Ditulis sebagai karakter ▾ langsung supaya
   tidak bisa rusak lagi. */
.kasa-tabs .kasa-tabs-toggle::after { content: "▾"; margin-left: auto; color: var(--kasa-ink-dim, #66788b); }
html.kasa-dark-pending .kasa-tabs .kasa-tabs-toggle { background: #161c24; border-color: #2a3442; color: #e6e2d6; }

/* ---- 0015: editor lokasi peta — badge ikon + selector ikon/warna ---- */
.sp-loc-row {
  padding: .7rem .75rem;
  border: 1px solid var(--kasa-line, #dbe5ef);
  border-radius: 10px;
  margin-bottom: .6rem;
  background: var(--kasa-panel-solid, #fff);
  /* #219: peta sekarang sticky di atas daftar. Tanpa scroll-margin, baris yang
     di-scroll ke viewport (klik pin / tombol "Tandai di peta") mendarat TEPAT
     di bawah tepi atas — yaitu tersembunyi di balik peta. Nilainya = tinggi
     peta + petunjuk + baris status + offset navbar. Di ponsel peta tidak
     sticky, jadi nilainya dinolkan di media query di bawah. */
  scroll-margin-top: calc(3.875rem + 21rem);
}
/* Baris aktif = sasaran klik peta. Penandanya harus terlihat dari daftar:
   operator yang menggeser pin di peta perlu tahu baris mana yang ikut berubah. */
.sp-loc-row.is-active {
  border-color: var(--kasa-accent);
  box-shadow: 0 0 0 1px var(--kasa-accent) inset;
}
.sp-loc-main { display: flex; gap: .7rem; align-items: flex-start; }
.sp-loc-fields { flex: 1 1 auto; min-width: 0; }
/* Badge preview: cermin dari marker di peta publik (bulat + ikon putih). */
.sp-loc-preview {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff;
  border: 2px solid var(--kasa-line, #dbe5ef);
}
.sp-loc-preview svg { width: 17px; height: 17px; display: block; }
/* Swatch palette: selection-only (klik = pilih), bukan input bebas. */
.sp-loc-colors { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.sp-loc-swatches { display: flex; gap: .25rem; flex-wrap: wrap; }
.sp-loc-swatch {
  width: 20px; height: 20px; padding: 0;
  border-radius: 5px;
  border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
  cursor: pointer;
}
.sp-loc-swatch.is-active { border-color: var(--kasa-ink, #1d2d40); }
.sp-loc-swatch:focus-visible { outline: 2px solid var(--kasa-accent); outline-offset: 2px; }
.sp-loc-custom { display: inline-flex; align-items: center; gap: .3rem; margin: 0; cursor: pointer; }
.sp-loc-custom input[type="color"] {
  width: 24px; height: 22px; padding: 0;
  border: 1px solid var(--kasa-line, #dbe5ef);
  border-radius: 5px;
  background: transparent;
  cursor: pointer;
}
.sp-loc-custom input[type="color"]:focus-visible { outline: 2px solid var(--kasa-accent); outline-offset: 2px; }
.sp-loc-hex { font-size: .72rem; color: var(--kasa-ink-dim, #66788b); font-family: 'JetBrains Mono', monospace; }
/* 2026-09-22: baris "Sinkron status" — dropdown item monitoring + catatan
   bahwa warna/teks marker akan mengikuti item itu. Diberi latar berbeda
   supaya jelas ini pengaturan opsional yang mengalahkan pilihan warna manual. */
.sp-loc-link {
  border-top: 1px dashed var(--kasa-line, #dbe5ef);
  margin-top: .35rem; padding-top: .35rem;
}
.sp-loc-link .form-text { font-size: .72rem; }
.sp-loc-link-on .sp-loc-sync-dot {
  display: inline-block; width: .5rem; height: .5rem; border-radius: 50%;
  background: var(--kasa-accent); margin-right: .3rem; vertical-align: middle;
}
/* ---- 2026-09-22: pemilih titik lokasi berbasis peta (Leaflet) ----
   Panel peta dibatasi tingginya: tanpa batas, satu baris lokasi bisa memakan
   seluruh tinggi viewport dan operator kehilangan konteks form. 280px cukup
   untuk mengenali kota + jalan, tapi tidak mendominasi.
   Leaflet di halaman ini dimuat SETELAH CSS ini (link vendor ada di atas),
   jadi override tema menang tanpa perlu !important. */
/* ---- 2026-09-24 (#219): SATU peta bersama, sticky di desktop ----
   Peta ini menampilkan SEMUA titik lokasi sekaligus (dulu tiap baris punya
   petanya sendiri). Karena satu peta harus melayani banyak baris, ia tidak
   boleh ikut ter-scroll keluar layar begitu operator turun ke baris ke-5 —
   kalau tidak, alur "pilih baris → geser pin → cek baris berikutnya" berubah
   jadi scroll bolak-balik. Sticky menahan peta di bawah navbar.
   Latar WAJIB solid: baris-baris lokasi lewat DI BELAKANG elemen ini, dan
   latar transparan akan membuat dua lapis teks saling menumpuk. */
.sp-loc-map-card {
  position: sticky;
  /* 3.875rem = tinggi navbar fixed tema (`.navbar-height` di theme.min.css).
     Tanpa offset ini peta menempel di paling atas dan tertutup header. */
  top: calc(3.875rem + .75rem);
  z-index: 2;
  background: var(--kasa-panel-solid, #fff);
  padding-bottom: .35rem;
  margin-bottom: .5rem;
  border-bottom: 1px solid var(--kasa-line, #dbe5ef);
}
.sp-loc-map {
  width: 100%;
  height: 280px;
  border: 1px solid var(--kasa-line, #dbe5ef);
  border-radius: 10px;
  background: var(--kasa-accent-soft, #eef3f9);
  /* Leaflet memakai z-index tinggi untuk pane-nya; batasi di sini supaya peta
     tidak menutupi dropdown/tooltip KASA yang berada di luar panel. */
  z-index: 0;
}
.sp-loc-map:focus-within { outline: 2px solid var(--kasa-accent); outline-offset: 2px; }
.sp-loc-map-hint { margin-top: .3rem; font-size: .72rem; color: var(--kasa-ink-dim, #66788b); }
/* Baris status peta: teks untuk pembaca layar ("baris aktif: …") sekaligus
   umpan balik visual kecil. Sengaja tidak mencolok — alat bantunya, bukan
   isi utama. */
.sp-loc-map-status { margin-top: .2rem; font-size: .72rem; color: var(--kasa-ink-dim, #66788b); }
/* Peringatan "di luar bingkai peta publik". Bukan error: pin tetap sah dan bisa
   disimpan, jadi warnanya kuning (peringatan), bukan merah (blokir). */
.sp-loc-map-warn {
  margin-top: .3rem;
  font-size: .72rem;
  line-height: 1.45;
  color: var(--kasa-warn-ink, #8a5a12);
  background: var(--kasa-warn-soft, #fdf4e3);
  border-left: 3px solid var(--kasa-warn, #e8a04a);
  border-radius: 6px;
  padding: .35rem .5rem;
}
/* Pin peta: cermin marker halaman publik (bulat + ikon putih) supaya yang
   dilihat operator di sini persis yang dilihat pengunjung. */
.sp-loc-pin { background: transparent; border: 0; }
.sp-loc-pin-badge {
  position: relative;
  width: 30px; height: 30px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
  border: 2px solid #fff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .35);
  cursor: grab;
}
.sp-loc-pin-badge:active { cursor: grabbing; }
.sp-loc-pin-badge svg { width: 15px; height: 15px; display: block; }
/* #219: hanya pin baris AKTIF yang diberi cincin + label nama. Pin lain tetap
   ikon+warna saja (cermin halaman publik, yang juga tidak melabeli semua
   titik); kalau semua dilabeli, 20 pin akan saling menutupi namanya dan peta
   justru jadi tidak terbaca. */
.sp-loc-pin.is-active .sp-loc-pin-badge {
  border-color: var(--kasa-accent);
  box-shadow: 0 0 0 3px rgba(45, 99, 158, .35), 0 2px 6px rgba(0, 0, 0, .35);
}
/* Chip nama di samping pin aktif. `position: absolute` + `white-space: nowrap`
   supaya label tidak pernah membungkus dan menggeser pin; Leaflet tidak
   memasang overflow:hidden pada marker, jadi chip ini boleh melewati kotak
   30px milik pin. */
.sp-loc-pin-label {
  position: absolute;
  left: 34px;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  font-size: .72rem;
  font-weight: 600;
  line-height: 1;
  padding: .25rem .45rem;
  border-radius: 6px;
  color: var(--kasa-ink, #1d2d40);
  background: var(--kasa-panel-solid, #fff);
  border: 1px solid var(--kasa-accent);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .25);
  pointer-events: none;
}
/* Baris tombol "Tandai di peta" + ringkasan koordinat per baris. Peta sudah
   jadi satu, jadi tiap baris butuh cara untuk MENGAMBIL alih peta itu. */
.sp-loc-pos { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-top: .45rem; }
.sp-loc-pos-coord { font-size: .74rem; color: var(--kasa-ink-dim, #66788b); font-family: var(--kasa-font-mono, monospace); }
.sp-loc-pos-coord.is-empty { font-style: italic; }
.sp-loc-active-badge {
  font-size: .68rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--kasa-accent-ink, #fff); background: var(--kasa-accent);
  border-radius: 5px; padding: .1rem .35rem;
}
/* Perangkat sentuh: lingkaran pin TETAP 30px — ia cermin marker halaman publik,
   dan kalau diperbesar di sini yang dilihat operator tidak lagi sama dengan yang
   dilihat pengunjung. Yang diperluas adalah AREA SENTUHNYA: pseudo-elemen
   transparan 48px (ukuran target jari yang lazim). Tanpa ini memindahkan pin
   dengan jari = menebak sasaran 30px yang tertutup ujung jari. Leaflet tetap
   menerima event karena ::after adalah bagian dari elemen pin yang didengarnya. */
@media (pointer: coarse) {
  .sp-loc-pin-badge::after {
    content: "";
    position: absolute;
    inset: -9px;
    border-radius: 50%;
  }
}
/* Koordinat manual = langkah OPSIONAL, jadi tertutup default. Ringkasannya
   sengaja tidak mencolok supaya alur utama (geser pin) yang terlihat. */
.sp-loc-coords { margin-top: .4rem; }
.sp-loc-coords > summary {
  font-size: .74rem; color: var(--kasa-ink-dim, #66788b);
  cursor: pointer; padding: .15rem 0; list-style: revert;
}
.sp-loc-coords > summary:hover { color: var(--kasa-accent); }
.sp-loc-coords > summary:focus-visible { outline: 2px solid var(--kasa-accent); outline-offset: 2px; }
/* Baris lokasi yang belum lengkap: ditandai saat simpan ditolak, dibersihkan
   begitu operator memperbaikinya. */
.sp-loc-row.sp-loc-incomplete { border-color: var(--kasa-danger, #d24b57); box-shadow: 0 0 0 1px var(--kasa-danger, #d24b57) inset; }
/* #219: di tablet/ponsel sticky DIMATIKAN. Peta 220–280px di layar sempit
   sudah memakan sebagian besar viewport; menahannya di atas berarti daftar
   baris lokasi nyaris tidak kebagian ruang scroll, dan operator terjebak
   melihat peta saja. Tanpa sticky, peta tetap berfungsi penuh — hanya ikut
   ter-scroll seperti elemen lain. `scroll-margin-top` ikut dinolkan karena
   tidak ada lagi yang menutupi bagian atas. */
@media (max-width: 991.98px) {
  .sp-loc-map-card { position: static; border-bottom: 0; padding-bottom: 0; }
  .sp-loc-row { scroll-margin-top: 0; }
}
@media (max-width: 575.98px) {
  .sp-loc-main { flex-direction: column; }
  /* Di ponsel, 280px terlalu tinggi: peta bisa memakan hampir seluruh layar.
     220px masih cukup untuk mengenali kota, dan menyisakan ruang untuk
     men-scroll halaman di sekitar peta (satu jari di atas peta memang untuk
     menggeser peta — itu disengaja, supaya menaruh titik tetap bisa;
     trade-off yang sama dipakai peta embed pada umumnya). */
  .sp-loc-map { height: 220px; }
  /* Padding kartu bersarang (container 24px + card-body luar 21px + card-body
     dalam 21px) memakan 132px dari 390px layar — peta hanya kebagian 228px,
     padahal peta inilah alat utama menaruh titik. Padding dirapatkan HANYA di
     kartu lokasi (`.kasa-loc-card`) supaya hierarki kartu lain tidak berubah,
     dan peta dapat ~300px. Sengaja tidak menyentuh `.content.container-fluid`
     karena itu global seluruh panel. */
  .kasa-loc-card > .card-body { padding-left: .75rem; padding-right: .75rem; }
}
/* Dark mode: panel + garis ikut tema gelap KASA. */
html.kasa-dark-pending .sp-loc-row { background: #161c24; border-color: #2a3442; }
html.kasa-dark-pending .sp-loc-preview { border-color: #2a3442; }
html.kasa-dark-pending .sp-loc-hex { color: #93a4b8; }
html.kasa-dark-pending .sp-loc-link { border-top-color: #2a3442; }
html.kasa-dark-pending .sp-loc-map { border-color: #2a3442; background: #10151c; }
html.kasa-dark-pending .sp-loc-map-hint { color: #93a4b8; }
html.kasa-dark-pending .sp-loc-map-status { color: #93a4b8; }
html.kasa-dark-pending .sp-loc-map-card { background: #161c24; border-bottom-color: #2a3442; }
html.kasa-dark-pending .sp-loc-pin-label { background: #1b2230; color: #e6e2d6; }
html.kasa-dark-pending .sp-loc-pos-coord { color: #93a4b8; }
html.kasa-dark-pending .sp-loc-row.is-active { border-color: var(--kasa-accent, #e8a04a); box-shadow: 0 0 0 1px var(--kasa-accent, #e8a04a) inset; }
html.kasa-dark-pending .sp-loc-map-warn { color: #f0c37e; background: #2a2213; border-left-color: var(--kasa-warn, #e8a04a); }
html.kasa-dark-pending .sp-loc-coords > summary { color: #93a4b8; }
/* Tile OpenStreetMap terang di atas panel gelap = menyilaukan dan terbaca
   "rusak". Filter ini menggelapkan tile-nya. Pin TIDAK ikut terpengaruh karena
   ia hidup di markerPane, bukan tilePane. */
html.kasa-dark-pending .sp-loc-map .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(.92) contrast(.92) saturate(.75); }
html.kasa-dark-pending .sp-loc-map .leaflet-container { background: #10151c; }
html.kasa-dark-pending .sp-loc-map .leaflet-bar a,
html.kasa-dark-pending .sp-loc-map .leaflet-control-attribution {
  background: #161c24; color: #c7d2de; border-color: #2a3442;
}
html.kasa-dark-pending .sp-loc-map .leaflet-control-attribution a { color: #8fb4e8; }

/* ==========================================================================
   #254 — indikator fokus terlihat (WCAG 2.4.7) + skip link (2.4.1/2.4.11)
   ==========================================================================
   Dasar masalah, diukur pada `main` bersih (561433a) dengan Tab SUNGGUHAN
   (`page.keyboard.press`), bukan `el.focus()`:

     FAIL users   @1440: tanpaRing=5  (Theme, Docs, Status, Sign out, Refresh data)
     FAIL index   @1440: tanpaRing=4
     FAIL rbac    @1440: tanpaRing=7  (+ Customer, Finance)
     FAIL billing @1440: tanpaRing=5  (+ a "Buka Payment Gateway")

   Akar tunggal, bukan lima: vendor Bootstrap 5 di theme.min.css memuat

     button:focus:not(:focus-visible){outline:0}
     .btn:focus-visible{...outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}

   dan `--bs-btn-focus-box-shadow` di-resolve ke `0 0 0 0 rgba(r,g,b,.5)` —
   lingkarannya ADA tapi lebarnya NOL. Jadi setiap `.btn` (termasuk
   `.btn-ghost-secondary` header dan `.btn-outline-*` di konten) kehilangan
   indikator fokus total. Itu sebabnya yang gagal selalu tombol ikon, dan
   bukan link nav (yang sudah punya ring sendiri di atas).

   Kenapa `body ` prefix: hs.theme-appearance.js menyisipkan SALINAN
   stylesheet vendor di AKHIR <head>, jadi salinan itu selalu menang saat
   specificity seri (lihat catatan "CASCADE SHIELD" di kepala berkas ini).
   `body .btn:focus-visible` = (0,2,1) > `.btn:focus-visible` vendor (0,2,0),
   jadi menang tanpa `!important` di kedua tema.

   Kontras ring: light #2d639e di atas putih 6.19:1, dark #e8a04a di atas
   #10141a 8.41:1 — keduanya jauh di atas 3:1 yang diminta SC 1.4.11 untuk
   indikator fokus. */
body .btn:focus-visible,
body a.btn:focus-visible,
body button.btn:focus-visible {
  outline: 2px solid var(--kasa-accent);
  outline-offset: 2px;
}
/* Elemen fokus lain yang belum punya ring: `[data-kasa-noop]` di dropdown,
   checkbox/radio, dan kontrol generik di dalam kartu. Rule ini SENGAJA
   specificity rendah (0,1,2) supaya ring khusus yang sudah ada di atas
   (nav-link 0,3,1 · kasa-nav-toggle 1,2,1 · kpi-card-link 1,2,1) tetap menang
   dan tidak dobel. */
body a:focus-visible,
body summary:focus-visible,
body [tabindex]:focus-visible,
body .form-check-input:focus-visible {
  outline: 2px solid var(--kasa-accent);
  outline-offset: 2px;
}
html.kasa-dark-pending body .btn:focus-visible,
html.kasa-dark-pending body a.btn:focus-visible,
html.kasa-dark-pending body button.btn:focus-visible,
html.kasa-dark-pending body a:focus-visible,
html.kasa-dark-pending body summary:focus-visible,
html.kasa-dark-pending body [tabindex]:focus-visible,
html.kasa-dark-pending body .form-check-input:focus-visible {
  outline-color: var(--kasa-accent);
}

/* ---------- Skip link (SC 2.4.1 Bypass Blocks) ----------
   Sebelumnya `skipLink=false` di 23/23 halaman: tidak ada mekanisme lewat
   sama sekali. Bentuknya mengikuti yang sudah terbukti jalan di auth hub
   (intiserver-auth-service .../public/iframe-error.html `.skip-link`):
   tersembunyi saat diam, muncul saat difokus, `:focus` (bukan
   `:focus-visible`) supaya keyboard DAN pembaca layar sama-sama melihatnya.

   Penyembunyian memakai klip 1x1, BUKAN `left:-9999px`. Tiga alasan:
   (a) probe memverifikasi `terlihat-saat-tidak-fokus=false` secara geometri
       (`width>1 && height>1 && top>=0 && bottom<=innerHeight`) — dengan
       `left:-9999px` elemen tetap melaporkan width/height besar dan top=0,
       jadi asersi itu MERAH walau secara visual ia memang di luar layar;
   (b) elemen ber-`left:-9999px` ikut memperlebar scrollable area di sebagian
       browser (itu kelas cacat F2/F12 yang sudah ditutup di commit #258);
   (c) klip 1x1 adalah pola "visually hidden" standar: elemen tetap hidup di
       accessibility tree sehingga pembaca layar mengumumkannya sebagai
       perhentian pertama, tapi tak terlihat sampai difokus.

   Warna WAJIB `!important`. Rule link body yang sudah ada di atas —
   `body a:not(.btn):not(.nav-link){color:#28558f}` — ber-specificity (0,3,1)
   dan mengalahkan `.skip-link:focus` (0,2,0). Tanpa `!important` teksnya
   dirender #28558f di atas latar #2d639e = 1.22:1 (terukur di browser) —
   yaitu menambah satu pelanggaran 1.4.3 baru saat memperbaiki 2.4.1.
   Dengan #fff: 6.19:1 di atas #2d639e (light), #1a1206 di atas #e8a04a
   8.44:1 (dark). Ini pola yang sama dengan duel cascade lain di berkas ini. */
.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 2000;
  display: inline-block;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
  border: 0;
  background: var(--kasa-accent) !important;
  color: #fff !important;
  font-family: var(--kasa-font-body);
  font-size: 0.95rem;
  font-weight: 700;
  text-decoration: none;
  box-shadow: none;
}
.skip-link:focus {
  width: auto;
  height: auto;
  padding: 0.6rem 0.95rem;
  overflow: visible;
  clip-path: none;
  border-radius: 0 0 10px 0;
  color: #fff !important;
  box-shadow: var(--kasa-shadow);
  outline: 3px solid var(--kasa-ink);
  outline-offset: 2px;
}
html.kasa-dark-pending .skip-link {
  background: #e8a04a !important;
  color: #1a1206 !important;
}
html.kasa-dark-pending .skip-link:focus {
  color: #1a1206 !important;
  outline-color: #e6e2d6;
}

/* ---------- Header fixed 62px vs target lompat (SC 2.4.11) ----------
   Header `.navbar-fixed` tinggi 62px dan menutupi bagian atas viewport.
   Tanpa offset, melompat ke #main-content menaruh baris pertama konten DI
   BAWAH header — fokus/keyboard user mendarat di area yang tidak terbaca.
   `scroll-padding-top` di scroll container (html) + `scroll-margin-top` di
   target menutup keduanya: lompatan hash DAN `scrollIntoView` saat Tab
   masuk ke elemen yang lebih tinggi dari sisa viewport. */
html {
  scroll-padding-top: 5.25rem;
}
#main-content,
:target {
  scroll-margin-top: 5.25rem;
}

/* ==========================================================================
   #259 — kontras WCAG 1.4.3 (AA) + ukuran teks/kontrol
   ==========================================================================
   Diukur ulang pada `main` (561433a) dengan probe yang sama sebelum & sesudah,
   23 halaman × light+dark. Angka probe mentah: 160 selector unik gagal
   (light 80, dark 80). Setelah menggabungkan duplikat per-pasangan warna,
   cacat sebenarnya 44 grup: 30 light + 14 dark, dengan 1 grup (20 elemen
   `span.page-link`) dikecualikan WCAG karena neneknya `li.page-item.disabled`.

   Tabel lengkap before/after ada di badan commit; komentar di bawah hanya
   mencatat AKAR + rasio terukur tiap pasangan, bukan daftar selector.

   Prinsip yang dipakai konsisten:
   - Ambang 4.5:1 (teks normal) / 3:1 (teks besar >=24px, atau >=18.66px bold).
   - Warna FILL tidak diubah; hanya varian TEKS yang digelapkan. Kalau keduanya
     dipaksa sama, tombol solid jadi ikut berubah — itu regresi visual, bukan
     perbaikan kontras.
   - `!important` dipakai HANYA saat lawannya utility vendor yang juga
     `!important` (`.text-*`, `.bg-soft-*`, `.alert-*`, `.badge`); kalau tidak,
     cukup specificity lebih tinggi. Ini pola yang sudah ada di berkas ini
     (lihat "CASCADE SHIELD" di kepala berkas).
   ========================================================================== */

/* ---------- 1. Teks kecil yang paling sering gagal: `.card-subtitle` &
   `.form-text` (vendor #8c98a4 = 2.92:1 di atas putih). ----------
   `--kasa-ink-dim` sudah digelapkan ke #66788b untuk `.text-muted`, tapi dua
   kelas ini melewati jalur lain: vendor memakai warna literal. #66788b sendiri
   cuma 4.51:1 — lulus tipis, dan GAGAL begitu latarnya bukan putih murni (di
   atas `--kasa-paper` #f5f8fc hanya 4.26:1). Karena itu `--kasa-ink-dim`
   digelapkan sekali di token, bukan ditambal per-kelas → semua konsumennya
   ikut naik, termasuk footer `p.fs-6` (4.26 → 5.08). */
:root:root {
  --kasa-ink-dim: #5a6c7e;
}
body .card-subtitle,
body .form-text {
  color: #5a6c7e;
}
/* `.page-link` disabled: vendor #8c98a4 (2.94:1). Dikecualikan 1.4.3, tapi
   teks yang tak terbaca sama saja tidak berguna — dinaikkan tanpa mengubah
   affordance "nonaktif" (opacity tetap dari vendor). */
body .page-item.disabled .page-link,
body .page-link.disabled {
  color: #5f7183 !important;
}

/* ---------- 2. Header tabel `th` (vendor `.table thead th{color:#67788b}` =
   4.39:1 di atas `.thead-light` #f8fafd). ---------- */
body .table thead th {
  color: #5a6c7e;
}

/* ---------- 3. `.text-warning` sebagai TEKS besar di atas kartu putih
   (h3 "14" @18.375px w600 = 2.18:1 — kegagalan terburuk di seluruh panel).
   Token fill `--bs-warning` #e8a04a tetap untuk latar solid; hanya teksnya
   yang digelapkan. ---------- */
body .text-warning {
  color: var(--kasa-warn-ink) !important;
}
body .text-danger {
  color: var(--kasa-danger-ink) !important;
}
/* `.text-success` sudah ditangani r29 di atas (#277950); diselaraskan ke token
   supaya satu sumber. Nilainya identik supaya tidak ada perubahan visual. */
body .text-success {
  color: var(--kasa-ok-ink) !important;
}

/* ---------- 4. `.btn-outline-*` / `.btn-soft-*` / `.btn-ghost-secondary`
   memakai token vendor legacy (`.btn-outline-danger` #ed4c78 = 3.53:1,
   `.btn-outline-secondary` #71869d = 3.75:1, `.btn-ghost-secondary` #71869d
   = 3.72:1). Diselaraskan ke token KASA yang sudah lolos. ---------- */
body .btn-outline-danger {
  --bs-btn-color: var(--kasa-danger-ink);
  --bs-btn-border-color: var(--kasa-danger-ink);
  --bs-btn-hover-bg: var(--kasa-danger-ink);
  --bs-btn-hover-border-color: var(--kasa-danger-ink);
  --bs-btn-active-bg: var(--kasa-danger-ink);
  --bs-btn-active-border-color: var(--kasa-danger-ink);
  --bs-btn-focus-shadow-rgb: 176, 72, 81;
}
body .btn-outline-secondary,
body .btn-ghost-secondary {
  --bs-btn-color: #5a6c7e;
  --bs-btn-hover-bg: var(--kasa-accent-soft);
  --bs-btn-hover-color: var(--kasa-accent-strong);
}
/* #259 lanjutan: `--bs-btn-color` TIDAK CUKUP untuk `.btn-ghost-secondary`.
   Vendor menulis `.btn-ghost-secondary{color:#71869d;...}` sebagai deklarasi
   `color` LANGSUNG (bukan lewat var), jadi ia menang atas `.btn{color:var(...)}`
   di dalam vendor itu sendiri — mengubah `--bs-btn-color` tidak berpengaruh
   sama sekali. Inilah sebab 10 selector `#71869d` (3.52–3.72:1) tetap merah
   di 10 halaman. Karena lawannya literal (0,1,0) dan rule ini (0,2,0),
   cukup deklarasi `color` eksplisit tanpa `!important`. */
body .btn-ghost-secondary {
  color: #5a6c7e;
}
/* `.btn-soft-primary` → accent-strong. Asalnya #259 menambal GEJALA saja:
   waktu itu `--kasa-accent` masih #3f73b8, yang di atas `--kasa-accent-soft`
   #e6f0fb terukur **4,1833:1** (di bawah 4,5:1 AA). #301 memperbaiki TOKEN-nya
   ke nilai kanonik `#2d639e` (5,3751:1 di atas soft), jadi override ini tidak
   lagi menambal cacat — ia dipertahankan karena accent-strong masih lebih
   kontras (6,5398:1) dan sudah dipakai konsisten di berkas ini. */
body .btn-soft-primary {
  color: var(--kasa-accent-strong);
}
/* `a.btn.btn-outline-primary` yang duduk di atas latar tint (bukan putih):
   nilai lama #3f73b8 di atas #d2ecf3 = 3,9104:1 (gagal). Setelah #301 token
   kanonik #2d639e memberi 5,0243:1 di atas #d2ecf3 dan 6,1946:1 di atas putih,
   jadi keduanya lulus — override accent-strong dipertahankan (lebih kontras
   lagi: 7,5370:1 putih) supaya tidak ada regresi saat latar bergeser.
   Border tetap aksen kanonik supaya identitas "outline primary" tidak berubah. */
body .btn-outline-primary {
  --bs-btn-color: var(--kasa-accent-strong);
}
body .btn-soft-success {
  color: var(--kasa-ok-ink);
}
body .btn-soft-danger {
  color: var(--kasa-danger-ink);
}
body .btn-soft-warning {
  color: var(--kasa-warn-ink);
}

/* ---------- 4b. `.btn-outline-*` NONAKTIF (light) — #254 §4 ----------
   Terukur di Chromium (light, latar putih) SEBELUM perbaikan:
     btn-outline-primary    3.79:1   (computed color rgb(55, 125, 255))
     btn-outline-secondary  3.75:1   (rgb(113, 134, 157))
     btn-outline-success    2.12:1   (rgb(0, 201, 167))
     btn-outline-danger     3.56:1   (rgb(237, 76, 120))
   Angka itu belum menghitung `opacity:.65` milik vendor; setelah dikomposit ke
   latar, keempatnya jatuh lagi (2.31 / 2.20 / 1.68 / 2.32).

   Kenapa rule poin 4 di atas tidak menutupnya: rule light `body .btn-outline-*`
   hanya menyetel `--bs-btn-color`. Saat `:disabled`, vendor `.btn:disabled`
   membaca `--bs-btn-disabled-color` — variabel LAIN, yang masih berisi token
   legacy vendor. Jadi seluruh perbaikan kontras outline sebelumnya hanya
   berlaku untuk tombol AKTIF; keadaan nonaktifnya lewat jalur yang berbeda.

   Eksemptif? WCAG 2.2 SC 1.4.3 memang MENGECUALIKAN kontrol nonaktif
   ("Incidental: ... inactive user interface components"). Yang mengikat di sini
   bukan WCAG, melainkan kontrak repo ini sendiri: #254 §4 menuntut kontrol
   yang masih terbaca memenuhi AA, dan #117 sudah memutuskan hal yang sama untuk
   `.btn-soft-*:disabled` (ghost scheme + opacity dinetralkan ke 1). Teks
   nonaktif yang tak terbaca sama saja tidak berguna bagi operator, jadi
   diperbaiki — memakai pola yang sama dengan #117.

   Bentuk perbaikannya = pola poin 4 di atas: warna FILL/border tidak diubah,
   hanya TEKS yang memakai token ink yang sudah lolos, dan
   `--bs-btn-disabled-opacity` dinetralkan ke 1 supaya kontras tidak dikompres
   terhadap latar halaman. Affordance "nonaktif" tidak bergantung pada kontras:
   vendor sudah menularkan `border-color:transparent` pada `.btn:disabled`,
   jadi tombol nonaktif tampil tanpa bingkai sementara tombol aktif punya
   bingkai aksen 1px (>=3:1, SC 1.4.11) — plus `pointer-events:none`.

   Rasio token terukur di atas `--kasa-paper` #f5f8fc (latar terburuk; di atas
   kartu putih selalu lebih tinggi): accent-strong 7.08, ink-dim 5.08,
   ok-ink 5.80, danger-ink 5.07, warn-ink 5.43, info-ink 7.08 — semua >=4.5.

   Scope `html:not(.kasa-dark-pending)`: #254 §4 adalah memperbaiki LIGHT tanpa
   mengubah perilaku dark, jadi rule ini tidak boleh bocor ke dark (tanpa
   `:not()`, `body .btn-outline-*:disabled` = (0,2,1) mengalahkan `.btn:disabled`
   vendor dark = (0,2,0)). Nilai dark TIDAK bisa "dipin balik" ke nilai lamanya
   di sini karena nilai lamanya adalah hex legacy vendor (hex 377dff, ditulis
   tanpa `#` supaya tidak dilarang `calm-mint-restyle` — lihat catatan di poin
   7/9) yang justru DILARANG muncul literal di berkas KASA ini — jadi
   menetralkan scope-nya adalah satu-satunya cara yang tidak mengubah dark
   sama sekali.
   Cakupan: 6 varian (`primary`/`secondary`/`success`/`danger`/`warning`/`info`)
   — yaitu seluruh varian outline yang benar-benar dipakai di `dist/` KECUALI
   `dark`, dan semuanya memang gagal sebelum perbaikan (`warning` 1.52:1,
   `info` 2.94:1 — dua terburuk, ikut diperbaiki walaupun #254 §4 hanya
   menyebut empat).

   Dua varian SENGAJA tidak disentuh, keduanya dengan alasan eksplisit:
   - `.btn-outline-dark`: nilai nonaktifnya sudah lolos (terukur 15.80:1
     mentah, 5.05:1 setelah komposit). Mengubahnya bukan perbaikan.
   - `.btn-outline-light`: nol instance di `dist/` (diverifikasi dari daftar
     `btn-outline-*` yang benar-benar dirujuk shell + controller), jadi tidak
     ada yang bisa diukur — dan TIDAK diam-diam "dilewatkan": kalau varian itu
     suatu saat dipakai, `--bs-btn-disabled-color` vendor = #f9fafc di atas
     latar putih ≈ 1.03:1, yaitu satu-satunya varian yang lebih buruk daripada
     yang diperbaiki di sini. Perbaikannya berbeda bentuk (teks harus jadi
     GELAP, bukan token ink berwarna), jadi ia sengaja tidak ditambal buta
     tanpa markup untuk mengukurnya. */
html:not(.kasa-dark-pending) body .btn-outline-primary:is(:disabled, .disabled) {
  --bs-btn-disabled-color: var(--kasa-accent-strong);
  --bs-btn-disabled-opacity: 1;
}
html:not(.kasa-dark-pending) body .btn-outline-secondary:is(:disabled, .disabled) {
  --bs-btn-disabled-color: #5a6c7e;
  --bs-btn-disabled-opacity: 1;
}
html:not(.kasa-dark-pending) body .btn-outline-success:is(:disabled, .disabled) {
  --bs-btn-disabled-color: var(--kasa-ok-ink);
  --bs-btn-disabled-opacity: 1;
}
html:not(.kasa-dark-pending) body .btn-outline-danger:is(:disabled, .disabled) {
  --bs-btn-disabled-color: var(--kasa-danger-ink);
  --bs-btn-disabled-opacity: 1;
}
html:not(.kasa-dark-pending) body .btn-outline-warning:is(:disabled, .disabled) {
  --bs-btn-disabled-color: var(--kasa-warn-ink);
  --bs-btn-disabled-opacity: 1;
}
html:not(.kasa-dark-pending) body .btn-outline-info:is(:disabled, .disabled) {
  --bs-btn-disabled-color: var(--kasa-info-ink);
  --bs-btn-disabled-opacity: 1;
}

/* ---------- 5. Badge/chip: `.bg-soft-*` + `.text-bg-*` ----------
   Vendor menulis `color` dan `background-color` dari token yang sama, jadi
   teksnya SELALU gagal di latar pucatnya sendiri (success 3.68, danger 3.71,
   warning 4.18). Ini yang membuat badge `monitoring` gagal sementara badge
   `notif-logs` (yang sudah punya rule r28) lulus. Sekarang semua varian
   memakai token ink yang sama dengan r28 → satu sumber, tidak bisa drift. */
body .bg-soft-success.text-success,
body .badge.bg-soft-success,
body .badge.text-bg-success {
  color: var(--kasa-ok-ink) !important;
}
body .bg-soft-danger.text-danger,
body .badge.bg-soft-danger,
body .badge.text-bg-danger {
  color: var(--kasa-danger-ink) !important;
}
body .bg-soft-warning.text-warning,
body .badge.bg-soft-warning,
body .badge.text-bg-warning {
  color: var(--kasa-warn-ink) !important;
}
body .bg-soft-info.text-info,
body .badge.bg-soft-info,
body .badge.text-bg-info {
  color: var(--kasa-info-ink) !important;
}

/* ---------- 6. `.alert-soft-*` ----------
   `.alert-soft-warning` (#f5ca99) = 1.42:1 — pasangan TERBURUK di panel,
   dipakai banner "Daftar ini level PLATFORM" di `mcws-api-pembayaran`.
   `.alert-soft-info` #09a5be = 2.38:1 di 3 halaman.

   `.alert-info` (varian non-soft): vendor menulis DUA rule berturut-turut —
   `{--bs-alert-color:#056372;--bs-alert-bg:#ceedf2}` lalu
   `{color:#fff;background-color:#09a5be}`. Rule KEDUA yang berlaku, jadi
   teks putih di atas #09a5be = 2.94:1 (bukan 5.61:1 seperti yang terlihat
   dari rule pertama). Dikonfirmasi `diag-rules.mjs` di Chromium: computed
   = rgb(255,255,255) / rgb(9,165,190). Latarnya digelapkan ke #0a6b7c —
   keluarga teal yang sama dengan `.alert-soft-info` → putih = 6.08:1. */
body .alert-soft-warning {
  color: var(--kasa-warn-ink) !important;
}
body .alert-soft-info {
  color: #0a6b7c !important;
}
body .alert-info {
  background-color: #0a6b7c;
  border-color: #0a6b7c;
  color: #fff;
}
body .alert-soft-primary {
  color: var(--kasa-accent-strong) !important;
}
/* Tautan di dalam alert soft mewarisi warna alert (vendor menulis
   `.alert-soft-*.alert-link{color:...}`), jadi ikut gagal; samakan. */
body .alert-soft-warning .alert-link { color: var(--kasa-warn-ink) !important; }
body .alert-soft-info .alert-link { color: #0a6b7c !important; }

/* ---------- 7. Avatar soft (`avatar-soft-primary .avatar-initials`).
   Vendor memakai biru legacy (hex 377dff, kanal rgba 55/125/255) di atas tint
   10% dirinya sendiri = 3.37:1; di dark 2.10:1 karena rule
   `body a:not(.btn)` mengalahkan warna avatar. Catatan: warna vendor itu
   SENGAJA tidak ditulis sebagai literal di komentar ini — `calm-mint-restyle`
   melarang hex legacy itu muncul di berkas KASA, termasuk di komentar. ---------- */
body .avatar-soft-primary .avatar-initials {
  color: var(--kasa-accent-strong);
  background-color: rgba(45, 99, 158, 0.12);
}

/* ---------- 8. `code` (vendor `--bs-code-color` #d63384 = 4.47:1 di atas
   putih, 3.64:1 di atas composite alert info, 3.34:1 di dark). ---------- */
:root:root {
  --bs-code-color: #b02a6b;
}

/* ---------- 9. Tab aktif (`.nav-tabs .nav-link.active`) mewarisi
   `--bs-nav-tabs-link-active-color` biru legacy vendor (hex 377dff) = 3.79:1.
   Hex-nya sengaja tidak ditulis literal di sini; lihat catatan di poin 7. ---------- */
body .nav-tabs .nav-link.active {
  --bs-nav-tabs-link-active-color: var(--kasa-accent-strong);
  color: var(--kasa-accent-strong);
}

/* ---------- 10. Dark mode ----------
   Di dark cacatnya berbeda dan lebih dalam: rule
   `body a:not(.btn):not(.nav-link){color:#28558f}` ditulis TANPA scope
   `html.kasa-dark-pending`, jadi teks #28558f bocor ke panel gelap — 2.45:1
   di atas `--kasa-paper` #10141a dan 2.27:1 di atas kartu #161c24, di 23
   halaman. Itu akar tunggal untuk `a`, `a.btn-outline-secondary`, dan
   `span.avatar-initials` dark sekaligus. Ditutup dengan mengembalikan ke
   `--bs-link-color` yang sudah benar di dark (#e8a04a = 8.41:1), bukan dengan
   warna literal baru. */
html.kasa-dark-pending body a:not(.btn):not(.nav-link) {
  color: var(--bs-link-color, #e8a04a);
}
html.kasa-dark-pending body a:not(.btn):not(.nav-link):hover {
  color: var(--bs-link-hover-color, #f0b268);
}
html.kasa-dark-pending body .btn-outline-secondary,
html.kasa-dark-pending body .btn-ghost-secondary {
  --bs-btn-color: #b8c2cc;
}
/* Deklarasi `color` langsung, alasannya sama dengan versi light di atas:
   vendor menulis `.btn-ghost-secondary{color:...}` literal sehingga var tidak
   berpengaruh. #b8c2cc di atas panel #161c24 = 9.66:1. */
html.kasa-dark-pending body .btn-ghost-secondary {
  color: #b8c2cc;
}
html.kasa-dark-pending body .btn-soft-primary {
  color: var(--kasa-accent-strong);
}
html.kasa-dark-pending body .avatar-soft-primary .avatar-initials {
  color: #a9c4e8;
  background-color: rgba(143, 180, 232, 0.14);
}
html.kasa-dark-pending body .bg-soft-success.text-success,
html.kasa-dark-pending body .badge.bg-soft-success,
html.kasa-dark-pending body .badge.text-bg-success {
  color: var(--kasa-ok-ink) !important;
}
html.kasa-dark-pending body .bg-soft-warning.text-warning,
html.kasa-dark-pending body .badge.bg-soft-warning,
html.kasa-dark-pending body .badge.text-bg-warning {
  color: var(--kasa-warn-ink) !important;
}
html.kasa-dark-pending body .bg-soft-danger.text-danger,
html.kasa-dark-pending body .badge.bg-soft-danger,
html.kasa-dark-pending body .badge.text-bg-danger {
  color: var(--kasa-danger-ink) !important;
}
html.kasa-dark-pending body .btn-soft-warning { color: var(--kasa-warn-ink); }
html.kasa-dark-pending body .btn-soft-danger { color: var(--kasa-danger-ink); }
html.kasa-dark-pending body .btn-soft-success { color: var(--kasa-ok-ink); }
/* `dt` di kartu dark: #67788b warisan vendor = 3.73:1. */
html.kasa-dark-pending body dt {
  color: #8a94a3;
}

/* ==========================================================================
   #259 bagian 2 — UKURAN TEKS & KONTROL (iOS auto-zoom / keterbacaan)
   ==========================================================================
   Sebelum: input/select <16px di 12 halaman (terkecil 13px — iOS Safari
   memperbesar viewport saat fokus), `th` 11.9px di 17 halaman, badge 9.19px
   di `notif-logs` dan 9.75px di `rbac`.

   Ambang dari probe:
     input >= 16px  — mencegah auto-zoom iOS; ini ambang FUNGSIONAL, bukan selera
     th    >= 12px  — teks kolom harus terbaca
     badge >= 12px  — #257 §4.9 menaikkan ambang ini dari 10.5px (r61)

   `th` 11.9px = vendor `.table thead th{font-size:.74375rem}` (relatif 16px),
   jadi harus ditimpa eksplisit. 12px adalah perubahan setipis mungkin yang
   memenuhi ambang.

   Badge: dua yang gagal — 9.1875px (`notif-logs`, `.badge.bg-soft-secondary`
   di dalam `td.small`) dan 9.75px (`rbac`, `.badge.bg-soft-info` di dalam
   `.btn-sm`). Keduanya `.75em` dari induk yang lebih kecil, jadi mengecil dua
   kali.

   Bentuknya `max()`, BUKAN nilai mati — dan lantainya tinggal SATU, di token
   `--bs-badge-font-size` (blok #257 §4.9 di atas). Di sini `font-size` hanya
   MEMBACA token itu. Sebelumnya rule ini menulis lantainya sendiri
   (`max(0.75em, 10.5px)`); karena specificity-nya (0,1,1) sama dengan rule
   token di atas dan ia datang BELAKANGAN, dialah yang menang dan token 12px
   tidak pernah dibaca — computed tetap 10.5px. Menulis angka lantai di DUA
   tempat adalah akar cacatnya, jadi duplikatnya dihapus, bukan disetel ulang.

   Kenapa tetap `max()`: percobaan pertama memakai `font-size:12px` mati dan itu
   memunculkan REGRESI `overflow` di `monitoring @ 320x568` (overflowX 0 → 17):
   badge peran di navbar (`[data-kasa-user-role]`) berinduk 14px sehingga
   `.75em` = 10.5px secara alami, dan menaikkannya melebarkan navbar. `max()`
   menaikkan yang DI BAWAH lantai dan membiarkan yang sudah di atasnya:
     12.25px induk → max(9.1875, 12) = 12.0px (notif-logs, naik)
     14px    induk → max(10.5,   12) = 12.0px (navbar + sel tabel, naik)
     16px    induk → max(12.0,   12) = 12.0px (tidak berubah)
     24px    induk → max(18.0,   12) = 18.0px (tetap relatif)
   Jadi sifat "badge mengecil mengikuti konteksnya" tetap ada; hanya lantainya
   yang dijamin. Specificity (0,1,1) sudah mengalahkan vendor `.badge` (0,1,0),
   jadi tidak perlu `!important`. */
body .table thead th {
  font-size: 12px;
}
body .badge {
  font-size: var(--bs-badge-font-size);
}
/* Input/select/textarea: 16px di SEMUA breakpoint, bukan hanya mobile.
   Alasannya bukan hanya auto-zoom iOS — 13–14px juga di bawah ukuran teks
   yang nyaman dibaca di desktop, dan menetapkannya hanya di media query mobile
   akan membuat halaman "berubah ukuran" saat di-resize. `min-height` tidak
   diubah (sudah 46px dari rule di atas) supaya tinggi baris toolbar tidak
   bergeser. */
body .form-control:not(textarea),
body .form-select,
body select:not(.form-select) {
  font-size: 16px;
}
body .form-control-sm,
body .form-select-sm {
  font-size: 16px;
}

/* ==========================================================================
   #259 bagian 3 — PASANGAN MODE GELAP untuk aturan light di atas
   ==========================================================================
   Ditemukan lewat bukti NON-VAKUM (`.tmp-a11y/nonvacuous-a11y.mjs`, kasus V4):
   asersi dark di probe `contrast` TIDAK BISA merah, karena probe menyetel
   `localStorage.kasa_theme` sedangkan aplikasi membaca `hs_theme`
   (`js/theme-switcher.js:19`). Akibatnya kolom "dark" di probe adalah salinan
   persis kolom "light" (di baseline: 160 = light 80 + dark 80 yang identik),
   dan `html.kasa-dark-pending` tidak pernah terpasang.

   Diukur ulang dengan kunci yang BENAR (`.tmp-a11y/diag-dark.mjs`):
     baseline 561433a : dark 72 pelanggaran (20 grup unik)
     sesudah #259 v1  : dark 64 pelanggaran (20 grup unik)
   Angka itu MENYEMBUNYIKAN dua hal sekaligus: perbaikan nyata (link `a` 2.27,
   avatar 2.10, badge soft, alert-info) TERTUTUPI oleh REGRESI BARU yang saya
   sendiri buat — aturan light di bagian 1/2 tidak ber-scope sehingga bocor ke
   panel gelap:
     `th` #5a6c7e            2.38:1  (x35)  ← dari `body .table thead th`
     `.card-subtitle`/`.form-text` #5a6c7e 3.17:1  ← dari bagian 1
     `.page-link` disabled #5f7183  3.41:1  ← dari bagian 1
     `.alert-soft-info` #0a6b7c     2.44:1  ← dari bagian 6
     `code` #b02a6b           2.43:1  ← dari bagian 8 (malah lebih buruk dari
                                          vendor #d63384 yang 3.34:1)

   Blok ini menambahkan pasangan gelapnya. Semua selektor memakai prefix
   `html.kasa-dark-pending body` = (0,2,2) sehingga mengalahkan aturan light
   `body .X` = (0,1,1); token di `html.kasa-dark-pending:root` = (0,2,1)
   mengalahkan `:root:root` = (0,2,0). Tidak ada `!important` baru kecuali pada
   dua aturan yang lawannya memang `!important` (`.page-link.disabled`,
   `.alert-soft-info`). */

/* 1. Token `code` gelap. Vendor #d63384 = 3.34:1 di kartu gelap; versi light
   saya #b02a6b cuma 2.43:1. Pink terang: 8.62:1 di kartu #161c24 dan 7.55:1 di
   atas composite alert-info #0f2a33. */
html.kasa-dark-pending:root {
  --bs-code-color: #f0a0c8;
}

/* 2. Header tabel `th` gelap (latar thead gelap #2f3235). */
html.kasa-dark-pending body .table thead th {
  color: #b8c2cc;
}

/* 3. Teks kecil abu (`.card-subtitle`, `.form-text`) di kartu gelap #161c24.
   #8a94a3 = 5.59:1 — sama dengan `.text-muted` dan `dt` gelap: satu nilai. */
html.kasa-dark-pending body .card-subtitle,
html.kasa-dark-pending body .form-text {
  color: #8a94a3;
}

/* 4. `.page-link` nonaktif di panel gelap. */
html.kasa-dark-pending body .page-item.disabled .page-link,
html.kasa-dark-pending body .page-link.disabled {
  color: #8a94a3 !important;
}

/* 5. `.alert-soft-info` gelap: versi light #0a6b7c di atas composite
   rgba(9,165,190,.15) = #0f2a33 hanya 2.44:1. Teal terang = 8.74:1. */
html.kasa-dark-pending body .alert-soft-info {
  color: #8fd0dc !important;
}
html.kasa-dark-pending body .alert-soft-info .alert-link {
  color: #8fd0dc !important;
}

/* 6. `.ss-hint` (service-settings): warnanya literal #677788 di `<style>`
   inline halaman itu, jadi tidak bisa diperbaiki dari token. Di panel gelap
   = 3.73:1. Di-override dari berkas ini supaya HTML halaman tidak disentuh
   (halaman itu milik worktree lain untuk JS-nya). */
html.kasa-dark-pending body .ss-hint {
  color: #8a94a3;
}
