/* ============================================================================
 * kontrak.css — pelengkap AppStack untuk Sistem Kontrak PU
 * ============================================================================
 *
 * SEBELUMNYA berkas ini adalah JEMBATAN MIGRASI Tabler → AppStack: core
 * aplikasi di-clone dari ../aplikasi yang memakai Tabler, dan segelintir kelas
 * khas Tabler perlu didefinisikan ulang di sini supaya halaman warisan tidak
 * rusak.
 *
 * JEMBATAN ITU SUDAH DIBONGKAR (2026-09-01). Seluruh halaman kini memakai
 * komponen AppStack dan Bootstrap 5.3 asli, dan empat kelas yang dulu dicatat
 * sebagai utang sudah lunas:
 *
 *   .page-header / .page-title  DIHAPUS — tidak ada lagi yang memakainya;
 *                               judul halaman kini ada di topbar (index.volt)
 *   .card-actions               DIHAPUS — diganti `ms-auto d-flex gap-2`
 *   .empty*                     DIGANTI NAMA jadi .kosong*
 *
 * Dua di antaranya BERTABRAKAN NAMA dengan kelas milik AppStack yang artinya
 * berbeda, dan karena kontrak.css dimuat sesudah app.css, definisi di sinilah
 * yang menang — bug diam yang kini tidak mungkin terjadi lagi:
 *
 *   .card-actions  di AppStack = wadah ikon kecil di kepala kartu
 *   .empty         di AppStack = {margin:1px}, milik salah satu plugin
 *
 * Yang tersisa di berkas ini BUKAN lagi jembatan, melainkan dua hal:
 *
 *   1. UTILITAS KECIL yang tidak dipunyai AppStack maupun Bootstrap
 *      (.subheader, .card-table, .w-1, .kosong*)
 *   2. KOMPONEN MILIK APLIKASI INI SENDIRI — chip tahun anggaran, pita alur
 *      lima langkah, kartu rantai anggaran, daftar label/nilai
 *
 * ---------------------------------------------------------------------------
 * ATURAN PEMILIHAN KELAS (ditetapkan 2026-08-31, tetap berlaku)
 * ---------------------------------------------------------------------------
 * Komponen AppStack DIDAHULUKAN. Bootstrap dipakai hanya bila tema tidak punya
 * padanannya. Kelas baru ditambahkan di sini HANYA kalau keduanya tidak punya:
 *
 *   badge   -> .badge-subtle-* (AppStack)   bukan .bg-*-subtle (Bootstrap)
 *   kartu KPI -> .card + .stat (AppStack)
 *   kartu, grid, tombol, form -> Bootstrap, karena AppStack tidak
 *                                mendefinisikan padanannya sendiri
 *
 * Seluruh nilai memakai variabel CSS Bootstrap (--bs-*) agar ikut berubah
 * otomatis saat tema gelap aktif. Warna yang dipatok manual akan menjadi cacat
 * yang baru ketahuan setelah ada yang menyalakan dark mode.
 *
 * Konsekuensi yang diketahui dan diterima: .badge-subtle-* milik AppStack
 * memaku warnanya sebagai rgba literal dan tidak punya aturan khusus tema
 * gelap. Latarnya tembus pandang (alpha 0.175) sehingga tetap terbaca di atas
 * latar gelap, tapi ia tidak berbalik seperti variabel Bootstrap. Kalau dark
 * mode dinyalakan, inilah tempat pertama yang perlu diperiksa — bukan cacat
 * tersembunyi, melainkan tukar-tambah yang sudah dicatat.
 * ------------------------------------------------------------------------- */

/* --- Kode di dalam teks: <code> ------------------------------------------
 *
 * PERSENTASE YANG BERTUMPUK. AppStack dan stylesheet Quill sama-sama menyetel
 * `code { font-size: 85% }`, dan 85% itu relatif terhadap induknya — bukan
 * terhadap ukuran dasar. Di dalam pembungkus yang sudah mengecil, ia mengecil
 * lagi:
 *
 *     body 0.825rem ........ 13,20 px
 *     .small 80% ........... 10,56 px
 *     code 85% ............... 8,98 px   <- di kotak petunjuk halaman impor
 *     code 85% (di tabel) ... 11,22 px   <- Tabel::kode()
 *
 * Sembilan piksel, untuk isi yang justru HARUS terbaca huruf demi huruf: kode
 * rekening, kode sub kegiatan, dan contoh format pagu. Kalau `5.2.04.01.001.00003`
 * tidak terbaca, kotak petunjuk yang memuatnya tidak menjelaskan apa pun.
 *
 * Diperbaiki dengan satuan ABSOLUT, bukan persen. Persen berapa pun akan
 * bertumpuk lagi begitu <code> dipakai di dalam pembungkus kecil yang lain;
 * `rem` memutus rantainya sekali untuk seluruh aplikasi. Nilainya sengaja
 * setara teks badan (13,20 px) — kode bukan keterangan tambahan, ia isinya.
 *
 * Latar, padding, dan radiusnya dibiarkan seperti bawaan tema.
 * ------------------------------------------------------------------------- */
code {
  font-size: 0.825rem;
}

/* --- Penanda isian wajib: label.required -------------------------------------
 *
 * KELAS INI SUDAH DIPAKAI 14 LABEL SEJAK LAMA — DAN TIDAK PERNAH MENGGAMBAR
 * APA PUN.
 *
 * `.required` datang dari Tabler, tempat core aplikasi ini di-clone. AppStack
 * tidak pernah mendefinisikannya, jadi <label class="form-label required"> di
 * formulir User, Role, Menu, Unit, dan Akses tampil persis sama dengan label
 * biasa. Tidak ada error, tidak ada yang menyadarinya — kelas kegagalan yang
 * sama dengan .page-header dan bg-*-lt yang sudah dicatat di kepala berkas ini.
 *
 * Sekarang ia benar-benar ada, dan dipakai SELURUH formulir.
 *
 * `content: " *"` DENGAN SPASI di depannya, bukan margin: bintangnya bagian
 * dari teks label, jadi ia ikut membungkus dengan benar saat labelnya panjang
 * dan terpotong dua baris.
 *
 * aria-hidden tidak bisa dipasang pada pseudo-element, dan memang tidak perlu:
 * pembaca layar mendapat kewajiban isian dari atribut `required` pada
 * kontrolnya sendiri, bukan dari bintang ini. Bintangnya untuk mata.
 */
.form-label.required::after {
  content: " *";
  color: var(--bs-danger);
  font-weight: 700;
}

/* --- Label kecil di atas angka besar (kartu ringkasan dashboard) ---------- */
.subheader {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

/* --- Tabel yang menempel ke tepi kartu ------------------------------------
 * Tabler membuat <table class="card-table"> menembus padding .card-body.
 * Di sini tabel memang berada langsung di dalam .card (tanpa .card-body),
 * jadi yang dibutuhkan hanya menghapus margin bawah bawaan <table> supaya
 * tidak menyisakan celah di atas tepi kartu.
 */
.card-table {
  margin-bottom: 0;
}

.card-table> :not(caption)>*>* {
  padding-inline: 1.25rem;
}

/* --- Kolom tabel selebar isinya ------------------------------------------
 * Dipakai pada kolom aksi supaya tidak ikut melar. width:1% + nowrap adalah
 * cara baku memaksa <td> menyusut ke lebar isinya.
 */
.w-1 {
  width: 1%;
  white-space: nowrap;
}

/* --- Keadaan kosong ------------------------------------------------------
 * Muncul saat daftar tidak berisi apa pun ATAU saat pencarian tidak menemukan
 * hasil. Keduanya memakai blok yang sama dengan teks berbeda — perbedaannya
 * ada di template, bukan di sini.
 */
.kosong {
  padding: 3rem 1rem;
  text-align: center;
}

.kosong-header {
  margin-bottom: 1rem;
  font-size: 2.5rem;
  font-weight: 600;
  line-height: 1;
  color: var(--bs-secondary-color);
}

.kosong-title {
  margin-bottom: 0.5rem;
  font-size: 1.125rem;
  font-weight: 600;
}

.kosong-subtitle {
  margin-bottom: 0;
  color: var(--bs-secondary-color);
}

.kosong-action {
  margin-top: 1.5rem;
}

/* ===========================================================================
 * KOMPONEN DOMAIN KONTRAK
 * ===========================================================================
 * Berbeda sifat dari blok di atasnya. Yang di atas adalah JEMBATAN MIGRASI
 * Tabler -> AppStack dan seharusnya menyusut sampai hilang; yang di bawah ini
 * adalah komponen milik aplikasi ini sendiri yang memang tidak ada di AppStack:
 * chip tahun anggaran, pita alur lima langkah, dan kartu rantai anggaran.
 *
 * Seluruhnya memakai variabel CSS Bootstrap (--bs-*) supaya ikut berubah saat
 * tema gelap dinyalakan. Warna yang dipatok manual akan menjadi cacat yang baru
 * ketahuan setelah ada yang menyalakan dark mode — pelajaran yang sudah dicatat
 * di kepala berkas ini untuk .badge-subtle-*.
 * ------------------------------------------------------------------------- */

/* --- Merek di sidebar ---------------------------------------------------- */
/* AppStack memasang .sidebar-brand sebagai blok rata tengah. Merek di sini
 * dua bagian — penanda "PU" dan dua baris nama instansi — jadi barisnya
 * dijadikan flex rata kiri, mengikuti mockup v6.
 */
.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 1.25rem 1.25rem 1rem;
  text-align: left;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.sidebar-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.375rem;
  height: 2.375rem;
  border-radius: 0.55rem;
  background: var(--bs-primary);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 800;
  letter-spacing: 0.02em;
}

/* Amber hanya berlaku di atas navy. Di tema terang penanda ini kembali ke
 * --bs-primary di atas — oranye di atas putih kehilangan pijakannya.
 */
html[data-sidebar-theme="dark"] .sidebar-brand-mark {
  background: linear-gradient(155deg, #e8871e, #c96b0e);
}

.sidebar-brand-title {
  display: block;
  font-size: 0.97rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.2px;
}

/* Warna ikut induknya lewat opacity, bukan putih yang dipatok, supaya baris
 * ini tetap terbaca kalau tema sidebar diganti terang.
 */
.sidebar-brand-sub {
  display: block;
  margin-top: 1px;
  font-size: 0.66rem;
  font-weight: 400;
  line-height: 1.3;
  color: inherit;
  opacity: 0.6;
}

/* --- Navigasi sidebar ----------------------------------------------------
 * Bentuk pil + kepala bagian, mengikuti mockup v6. Aturan warna dipagari
 * html[data-sidebar-theme=dark] SUPAYA TIDAK MENANG SENDIRI: aturan tema
 * terang milik AppStack punya spesifisitas yang sama persis, dan berkas ini
 * dimuat belakangan — tanpa pagar itu, sidebar tema terang ikut jadi navy
 * dengan teks putih di atas putih.
 * ------------------------------------------------------------------------- */
html[data-sidebar-theme="dark"] .sidebar,
html[data-sidebar-theme="dark"] .sidebar-content {
  background: #0b2545;
}

/* Latar .sidebar saja tidak cukup — .sidebar-content punya latarnya sendiri
 * dan menutupinya.
 */

.sidebar-nav {
  padding: 0.75rem 0 1.25rem;
}

.sidebar-header {
  padding: 0.85rem 1.375rem 0.3rem;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

html[data-sidebar-theme="dark"] .sidebar-header {
  color: rgba(255, 255, 255, 0.35);
}

.sidebar-link,
a.sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0 0.75rem 2px;
  padding: 0.5rem 0.625rem;
  border-radius: 0.5rem;
  font-size: 0.8125rem;
  font-weight: 500;
  /* line-height bawaan Bootstrap 1.5 membuat 22 baris menu melewati tinggi
   * layar 900px dan item terakhir terpotong. 1.3 mengembalikan kerapatan
   * mockup tanpa membuat sasaran klik terlalu kecil.
   */
  line-height: 1.3;
}

.sidebar-link i,
.sidebar-link svg,
a.sidebar-link i,
a.sidebar-link svg {
  width: 16px;
  height: 16px;
  margin-right: 0;
}

html[data-sidebar-theme="dark"] .sidebar-link:hover,
html[data-sidebar-theme="dark"] a.sidebar-link:hover {
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
}

html[data-sidebar-theme="dark"] .sidebar-item.active > .sidebar-link {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}

/* Batang penanda menempel ke tepi sidebar: pil punya margin kiri 0.75rem,
 * jadi -0.75rem mendudukkannya tepat di x=0.
 */
.sidebar-item.active > .sidebar-link::before {
  content: "";
  position: absolute;
  left: -0.75rem;
  top: 0.5rem;
  bottom: 0.5rem;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: #e8871e;
}

/* --- Judul halaman di topbar --------------------------------------------- */
.navbar-judul-utama {
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--bs-body-color);
}

.navbar-judul-sub {
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--bs-secondary-color);
}

/* --- Inisial pengguna di navbar ------------------------------------------ */
.avatar-inisial {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--bs-primary);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 600;
}

/* --- Chip tahun anggaran -------------------------------------------------
 * Penyaring periode global (§5.5). Ia SELALU tampil karena penyaring yang
 * tidak terlihat adalah penyaring yang membuat orang bertanya "kenapa kontrak
 * saya hilang". Varian -tombol hanya dipasang bila memang ada tahun lain yang
 * bisa dipilih.
 */
.chip-tahun {
  display: inline-flex;
  flex-direction: column;
  padding: 0.25rem 0.75rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  background: var(--bs-tertiary-bg);
  line-height: 1.15;
  text-decoration: none;
}

.chip-tahun-label {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

.chip-tahun-nilai {
  font-size: 0.95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--bs-body-color);
}

a.chip-tahun-tombol:hover {
  border-color: var(--bs-primary);
  background: var(--bs-primary-bg-subtle);
}

/* --- Chip cakupan unit ---------------------------------------------------
 * Bentuknya memakai kelas chip-tahun di atas — sengaja, keduanya penyaring
 * global yang mengunci sesi dan harus terlihat sama. Yang ditimpa hanya
 * nilainya: nama unit adalah teks panjang, bukan angka empat digit, jadi
 * tabular-nums tidak berlaku dan panjangnya harus dipotong supaya nama sepanjang
 * "Dinas PU dan Penataan Ruang" tidak mendorong menu akun keluar dari layar.
 */
.chip-cakupan-nilai {
  max-width: 13rem;
  overflow: hidden;
  font-variant-numeric: normal;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Pita alur lima langkah ----------------------------------------------
 * Sistem ini adalah urutan lima langkah, dan pengguna baru perlu melihat
 * urutan itu sebelum melihat angka. Diambil dari mockup, tapi ditulis dengan
 * komponen Bootstrap alih-alih warna yang dipatok.
 */
.pita-alur {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.pita-alur-langkah {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1 1 10rem;
  padding: 0.625rem 0.875rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

a.pita-alur-langkah:hover {
  border-color: var(--bs-primary);
  background: var(--bs-primary-bg-subtle);
}

.pita-alur-langkah.nonaktif {
  opacity: 0.55;
}

.pita-alur-nomor {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--bs-primary);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
}

.pita-alur-langkah.nonaktif .pita-alur-nomor {
  background: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
}

.pita-alur-teks {
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.2;
}

.pita-alur-angka {
  font-size: 0.6875rem;
  font-weight: 400;
  color: var(--bs-secondary-color);
}

/* --- Kartu rantai anggaran -----------------------------------------------
 * Muncul di formulir paket, kontrak, dan tagihan begitu induknya dipilih.
 * Isinya jenjang di atas baris yang sedang dibuat, supaya operator melihat
 * pagu mana yang akan ia potong SEBELUM mengetik angkanya.
 */
.rantai {
  padding: 0.875rem 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
  background: var(--bs-tertiary-bg);
}

.rantai.kosong {
  color: var(--bs-secondary-color);
  font-style: italic;
}

.rantai-baris {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.25rem 0;
  font-size: 0.8125rem;
}

.rantai-baris+.rantai-baris {
  border-top: 1px dashed var(--bs-border-color);
}

.rantai-label {
  flex: 0 0 auto;
  font-weight: 600;
  color: var(--bs-secondary-color);
}

.rantai-nilai {
  text-align: right;
}

.rantai-angka {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* --- Daftar pasangan label/nilai pada halaman detail --------------------- */
.kv {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--bs-border-color);
}

.kv:last-child {
  border-bottom: 0;
}

.kv-label {
  flex: 0 0 auto;
  color: var(--bs-secondary-color);
  font-size: 0.8125rem;
}

.kv-nilai {
  text-align: right;
  font-weight: 500;
  /* min-width:0 supaya nilai panjang boleh membungkus, bukan mendorong
   * labelnya keluar. text-wrap:pretty menyeimbangkan baris terakhir sehingga
   * nama panjang seperti "Program Pengembangan Sistem Penyediaan Air Minum"
   * tidak menyisakan satu kata sendirian di baris kedua. */
  min-width: 0;
  text-wrap: pretty;
}

.kv-nilai.angka {
  font-variant-numeric: tabular-nums;
}

/* --- Penanda kolom terkunci pada formulir --------------------------------
 * Dipakai untuk nilai kontrak yang sudah punya tagihan disetujui (§5.7) dan
 * untuk nilai SP2D yang mengikuti nilai tagihan (§4.4). Input readonly saja
 * tidak cukup: tanpa penanda, kolom yang tidak bisa diketik terbaca sebagai
 * formulir yang rusak.
 */
.form-terkunci {
  background: var(--bs-secondary-bg) !important;
  cursor: not-allowed;
}

.label-terkunci {
  display: inline-block;
  margin-left: 0.375rem;
  padding: 0.05rem 0.4rem;
  border-radius: 0.25rem;
  background: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
}

/* --- Judul kartu yang diikuti subjudul -----------------------------------
 * Bootstrap memberi .card-subtitle margin-top NEGATIF (-0.25 * spacer) supaya
 * ia merapat ke .card-title yang punya margin-bottom positif. Seluruh halaman
 * di aplikasi ini menulis judulnya `<h5 class="card-title mb-0">` — margin
 * bawahnya dimatikan, sementara tarikan negatif subjudulnya tetap berlaku.
 * Hasilnya subjudul naik 4px MENIMPA baris judul: itulah tumpang tindih yang
 * terlihat di hampir semua kartu.
 *
 * Diperbaiki di sini, bukan dengan membuang `mb-0` di 20-an berkas view,
 * supaya pola "judul + subjudul" tetap satu bentuk dan tidak bisa lupa.
 */
.card-title.mb-0+.card-subtitle {
  margin-top: 0.25rem;
}

/* --- Batang kemajuan realisasi fisik -------------------------------------
 * Menggantikan .progress/.progress-bar Bootstrap KHUSUS untuk sel tabel.
 * Alasannya bukan selera: keduanya butuh tinggi dan lebar yang diatur per
 * pemakaian, dan satu-satunya cara ringkasnya — atribut style — diabaikan
 * browser karena CSP aplikasi ini melarang gaya sebaris. Tinggi dan lebar
 * minimum karena itu ditetapkan di sini sekali untuk semua; hanya lebar isian
 * yang tersisa untuk dipasang ui.js dari data-persen.
 */
.kemajuan {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.kemajuan-alur {
  flex: 1 1 auto;
  min-width: 72px;
  height: 6px;
  border-radius: 3px;
  background: var(--bs-secondary-bg);
  overflow: hidden;
}

.kemajuan-isi {
  width: 0;
  /* ui.js yang mengisinya; nol = data belum terbaca */
  height: 100%;
  border-radius: 3px;
}

.kemajuan-angka {
  flex: 0 0 auto;
  font-size: 0.8125rem;
  font-family: var(--bs-font-monospace);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Kepala kartu: judul + tombol aksi -----------------------------------
 * Pola yang dipakai sepuluh halaman daftar:
 *
 *   <div class="card-header d-flex align-items-center">
 *     <div>judul + subjudul</div>
 *     <div class="ms-auto d-flex ...">tombol</div>
 *
 * Keduanya flex item yang boleh menyusut, dan subjudul di sini panjang-panjang
 * (satu-dua kalimat penjelas). Yang terjadi: teks mendorong, tombol mengalah,
 * dan "Tambah Paket" pecah jadi dua baris di dalam tombol yang tergencet.
 * Tombol adalah ukuran tetap — yang boleh mengalah adalah teksnya.
 */
.card-header.d-flex>.ms-auto {
  flex: 0 0 auto;
}

.card-header.d-flex>.ms-auto .btn {
  white-space: nowrap;
}

/* min-width:0 melawan bawaan flex item yang menolak menyempit di bawah lebar
 * kata terpanjangnya — tanpa ini blok judul tetap mendorong keluar kartu. */
.card-header.d-flex> :first-child {
  min-width: 0;
}

/* --- Lebar minimum tabel daftar ------------------------------------------
 * Sebuah tabel di dalam .table-responsive TIDAK langsung menggulir saat
 * kolomnya kebanyakan. Browser lebih dulu memeras setiap kolom yang boleh
 * dibungkus sampai selebar kata terpanjangnya, dan baru meluber kalau sudah
 * tidak bisa lagi. Akibatnya pada layar 1280 px: "Pembangunan Jembatan Kampung
 * Naha Aruq" turun jadi lima baris satu kata, dan tingginya baris ikut kacau —
 * persis keluhan "tergencet".
 *
 * Lebar minimum di bawah membalik urutan itu: kolom mendapat lebar wajarnya
 * lebih dulu, dan yang menggulir adalah pembungkusnya. Nilainya sengaja 1000px
 * — tepat di bawah lebar area isi pada layar 1440 px, sehingga layar lebar
 * TIDAK mendapat batang gulir yang tidak perlu, sementara layar sempit
 * mendapat kolom yang masih terbaca.
 *
 * Dipasang per tabel, bukan ke semua .table: tabel master hanya empat kolom
 * dan sudah muat di mana pun.
 *
 * Selektornya keturunan, bukan anak langsung: DataTables menyisipkan
 * pembungkusnya sendiri (.dt-container > .row > .col) di antara .table-
 * responsive dan <table>, jadi `>` tidak pernah cocok.
 */
.table-responsive table.tabel-lega {
  min-width: 1000px;
}
/* --- Formulir satu kartu dengan label bagian -----------------------------
 * Dipakai formulir kontrak (kontrak/form.volt). Sebelumnya formulir itu lima
 * .card bertumpuk, masing-masing dengan .card-header sendiri; sekarang satu
 * kartu dengan lima label bergaris di dalamnya. Susunannya diambil dari
 * mockup v6 (.sec-label), tapi HANYA susunannya — seluruh isian tetap
 * memakai .row/.col-md-* dan kontrol Bootstrap yang sama seperti dulu.
 *
 * Kenapa bukan .card-header: kepala kartu membawa latar, batas, dan padding
 * horizontalnya sendiri, dan lima di antaranya membuat satu formulir terbaca
 * seperti lima formulir terpisah. Label bergaris memisahkan bagian tanpa
 * memisahkan dokumennya.
 */
.judul-bagian {
  margin: 1.75rem 0 1rem;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid var(--bs-border-color);
}

/* Bagian pertama menempel ke tepi atas .card-body, tanpa jarak tambahan. */
.judul-bagian:first-child {
  margin-top: 0;
}

.jb-baris {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
  font-weight: 700;
  /* Sengaja LEBIH PEKAT dari --bs-emphasis-color (#495057) yang dipakai judul
   * kartu di halaman lain. Nilai ini diambil dari mockup v6 (--ink-900) dan
   * itulah yang disetujui saat pratinjau. Kalau kelak seluruh teks aplikasi
   * dipekatkan mengikuti mockup, baris ini yang pertama boleh kembali ke
   * variabel. */
  color: #16212b;
}

/* Kotak bernomor. Navy dipatok, sama seperti latar sidebar di atas, dengan
 * konsekuensi yang sama pula — lihat catatan tema gelap di kepala berkas.
 * Ukurannya tetap 20x20 supaya deret 1..5 sama lebar; itu sebabnya ia bukan
 * .badge, yang lebarnya mengikuti isi.
 */
.jb-baris .n {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 6px;
  background: #0b2545;
  color: #fff;
  font-family: var(--bs-font-monospace);
  font-size: 11px;
}

/* Penjelasan bagian. Dulu .card-subtitle; model v6 tidak punya tempat untuk
 * ini, jadi tempatnya dibuat — kalimatnya isi, bukan hiasan.
 */
.jb-sub {
  margin-top: 0.3rem;
  font-size: 0.75rem;
  color: var(--bs-secondary-color);
}
/* --- Kartu datar untuk SELURUH aplikasi ----------------------------------
 * Bawaan AppStack memberi setiap .card bayang halus tanpa batas:
 *     box-shadow: 0 0 .875rem 0 rgba(41,48,66,.05)
 *     border-width: 0; border-color: transparent; border-radius: .25rem
 * Mockup v6 kebalikannya — datar, bergaris 1px, sudut 10px.
 *
 * Berlaku untuk 115 kartu di 54 view, SENGAJA. Sempat dicoba sebagai kelas
 * penanda `.kartu-datar` di masing-masing kartu, dan itu keliru: begitu
 * sebagian kartu datar sementara sisanya berbayang, satu halaman terbaca
 * seperti dua aplikasi — persis kegagalan yang sudah diperingatkan komentar
 * di puncak kontrak/index.volt soal kartu KPI. Rupa kartu adalah keputusan
 * satu aplikasi, bukan keputusan per halaman, jadi tempatnya di satu aturan.
 *
 * Spesifisitasnya sama dengan milik AppStack (0,1,0); yang memenangkan aturan
 * ini semata-mata urutan muat — kontrak.css sesudah app.css di index.volt.
 */
.card {
  /* KEDUA radius harus diset bersamaan.
   *
   * Bootstrap membulatkan anak-anak kartu yang menempel ke tepi —
   * .card-header:first-child, .card-footer:last-child, .card>.list-group —
   * memakai --bs-card-inner-border-radius, BUKAN border-radius kartunya.
   * Menaikkan sudut luar sendirian meninggalkan variabel itu di 0.25rem
   * bawaan AppStack, dan karena latar kepala kartu putih sementara latar
   * halaman abu (#f7f9fc), kotak putih itu menyembul keluar dari lengkung
   * kartu: sudut kiri-atas dan kanan-atas terlihat tergigit. Sudah terjadi.
   *
   * Rumus dalamnya mengikuti Bootstrap sendiri: radius luar dikurangi tebal
   * garis, supaya lengkung dalam sejajar dengan sisi dalam garisnya.
   */
  --bs-card-border-radius: 0.625rem;
  --bs-card-inner-border-radius: calc(0.625rem - 1px);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-card-border-radius);
  box-shadow: none;
}

/* ---------------------------------------------------------------------------
 * Pohon unit organisasi (rencana 28) — /admin/unit
 *
 * Dipakai HANYA oleh satu halaman, dan satu-satunya daftar di aplikasi ini yang
 * bukan DataTables. Alasan lengkapnya di UnitController::indexAction().
 *
 * Garis penghubungnya digambar dengan ::before/::after pada kotak selebar rel,
 * bukan dengan gambar atau karakter: ia harus ikut memanjang saat baris
 * meninggi karena nama unit yang membungkus.
 * ------------------------------------------------------------------------- */

.pohon-garis {
  display: flex;
  flex-shrink: 0;
  align-self: stretch;
}

.pohon-rel,
.pohon-siku {
  width: 1.125rem;
  flex-shrink: 0;
  position: relative;
}

/* Garis leluhur yang masih punya adik: diteruskan melewati baris ini. */
.pohon-rel.lanjut::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0.5rem;
  width: 1px;
  background: var(--bs-border-color);
}

/* Siku ke baris ini sendiri: turun separuh, lalu membelok ke kanan. */
.pohon-siku::before {
  content: "";
  position: absolute;
  top: 0;
  height: 50%;
  left: 0.5rem;
  width: 1px;
  background: var(--bs-border-color);
}

.pohon-siku::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0.5rem;
  width: 0.625rem;
  height: 1px;
  background: var(--bs-border-color);
}

/* Masih punya adik di bawahnya: garisnya diteruskan melewati siku. */
.pohon-siku.lanjut::before {
  bottom: 0;
  height: auto;
}

.pohon-tombol {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  margin-right: 0.4rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.25rem;
  background: var(--bs-body-bg);
  color: var(--bs-secondary-color);
  cursor: pointer;
}

.pohon-tombol:hover {
  color: var(--bs-body-color);
  background: var(--bs-tertiary-bg);
}

.pohon-tombol i {
  font-size: 0.625rem;
  transition: transform 0.14s ease;
}

.pohon-tombol[aria-expanded="true"] i {
  transform: rotate(90deg);
}

/* Baris daun: tempatnya tetap dipesan supaya nama unit sebidang dengan
   saudaranya yang punya tombol. */
.pohon-daun {
  visibility: hidden;
  border-color: transparent;
  background: none;
}

@media (prefers-reduced-motion: reduce) {
  .pohon-tombol i { transition: none; }
}

.pohon-nama mark {
  padding: 0 0.125rem;
  border-radius: 0.1875rem;
}

/* --- Dropdown Cakupan & Tahun anggaran (rencana 30) ----------------------
 * Chip yang sama, tapi menekannya membuka daftar di tempat, bukan pindah ke
 * /pilih-unit atau /pilih-tahun.
 *
 * Tanda panahnya dipasang lewat ::after pada chip, BUKAN lewat .dropdown-toggle:
 * chip ini flex-direction column, jadi caret bawaan Bootstrap akan jatuh sebagai
 * baris ketiga di bawah nilainya.
 */
.chip-pemicu {
  position: relative;
  padding-right: 1.75rem;
}

.chip-pemicu::after {
  content: "";
  position: absolute;
  right: 0.7rem;
  top: 50%;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 1.5px solid var(--bs-secondary-color);
  border-bottom: 1.5px solid var(--bs-secondary-color);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 0.15s ease;
}

.chip-pemicu[aria-expanded="true"] {
  border-color: var(--bs-primary);
  background: var(--bs-primary-bg-subtle);
}

.chip-pemicu[aria-expanded="true"]::after {
  border-color: var(--bs-primary);
  transform: translateY(-30%) rotate(225deg);
}

@media (prefers-reduced-motion: reduce) {
  .chip-pemicu::after { transition: none; }
}

.chip-menu {
  min-width: 18rem;
  max-width: 22rem;
}

.chip-menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  white-space: normal;
}

.chip-menu-cek {
  width: 1rem;
  flex-shrink: 0;
  color: var(--bs-primary);
}

/* Nama unit TIDAK dipotong di sini. Di chip ia berelipsis supaya menu akun tidak
   terdorong keluar layar; di dropdown pengguna membandingkan nama utuh. */
.chip-menu-nama {
  flex: 1;
  min-width: 0;
}

.chip-menu-item[aria-current="true"] .chip-menu-nama {
  font-weight: 600;
}

.chip-menu-tahun {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.chip-menu-kaki {
  padding: 0.25rem 1.5rem 0.5rem;
  font-size: 0.75rem;
  color: var(--bs-secondary-color);
  white-space: normal;
}

/* --- Kotak cari pohon unit (rencana 28, dibetulkan rencana 31) ------------
 * Lebarnya ditetapkan di sini, BUKAN lewat style="width: 15rem" di view seperti
 * versi pertama. CSP aplikasi ini `style-src 'self'` tanpa 'unsafe-inline',
 * jadi atribut style di HTML diblokir browser dan kotaknya jatuh ke lebar
 * bawaannya -- tanpa satu pun pesan di layar.
 */
.pohon-cari {
  width: 15rem;
  max-width: 100%;
}
