/* ============================================================
 * style.css — Catatan Keuangan
 * Palet netral (putih + hitam, aksen biru) - tema asli aplikasi;
 * setiap nilainya lolos ambang kontras yang dijaga tests/test-assets.js.
 * ============================================================ */

:root {
  color-scheme: light;
  /* permukaan & tinta */
  --page: #f9f9f7;
  --surface: #fcfcfb;
  --ink: #0b0b0b;
  --ink-2: #52514e;
  --muted: #6e6c66;
  --grid: #e1e0d9;
  --baseline: #c3c2b7;
  /* Garis tepi kotak isian, tombol, dan batas track meter. Dipisah dari
   * --baseline karena kontrasnya wajib >= 3:1 (WCAG 1.4.11 komponen antarmuka),
   * sementara --baseline hanya garis struktur tabel/grafik yang boleh tipis. */
  --tepi-isian: #8a887f;
  --border: rgba(11, 11, 11, 0.10);
  /* seri kategorikal (urutan tetap) */
  --series-1: #2a78d6;
  --series-2: #c2521f;
  --series-3: #118a68;
  /* Status punya TIGA varian, karena satu warna tidak bisa memenuhi tiga
   * ambang kontras sekaligus:
   *   --status-*  : warna identitas, dipakai sebagai latar bertint (badge)
   *   --teks-*    : varian teks, >= 4.5:1 terhadap kartu & halaman
   *   --isi-*     : varian isian batang, >= 3:1 terhadap track meter/hbar
   * Ikon + label selalu menyertai warna, jadi warna tidak pernah jadi
   * satu-satunya pembawa makna. */
  --status-good: #0ca30c;
  --status-warning: #fab219;
  --status-serious: #ec835a;
  --status-critical: #d03b3b;
  --teks-good: #006300;
  --teks-warning: #8a5a00;
  --teks-serious: #b04a1c;
  --teks-critical: #c62d2d;
  --isi-good: #218a3f;
  --isi-warning: #9c7005;
  --isi-serious: #d9541f;
  --isi-critical: #d03b3b;
  /* aksen UI */
  --accent: #1f6fce;
  --accent-ink: #ffffff;
  --danger: #c8322c;
  --radius: 18px;
  --shadow: 0 2px 10px rgba(11, 11, 11, 0.06);
  --shadow-naik: 0 8px 26px rgba(11, 11, 11, 0.14);
  /* ---- gerak ----
   * Satu set token supaya seluruh animasi terasa berasal dari sistem yang
   * sama. Durasi sengaja pendek: elemen ini muncul ulang setiap kali data
   * dirender, jadi animasi panjang akan terasa lambat, bukan halus. */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --dur-mikro: 140ms;   /* hover, tekan tombol */
  --dur-masuk: 220ms;   /* elemen muncul */
  --dur-layar: 300ms;   /* panel & overlay */
  --tunda: 40ms;        /* jarak antar elemen pada efek berurutan */
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --page: #0d0d0d;
    --surface: #1a1a19;
    --ink: #ffffff;
    --ink-2: #c3c2b7;
    --muted: #9d9a92;
    --grid: #2c2c2a;
    --baseline: #383835;
    --tepi-isian: #807d75;
    --border: rgba(255, 255, 255, 0.10);
    --series-1: #5b9ef0;
    --series-2: #e8875a;
    --series-3: #39bd92;
    --teks-good: #4fc37f;
    --teks-warning: #e5b445;
    --teks-serious: #f0906a;
    --teks-critical: #f0736c;
    --isi-good: #3fae6a;
    --isi-warning: #d9a017;
    --isi-serious: #e8703a;
    --isi-critical: #e05050;
    --accent: #5b9ef0;
    /* Tinta di atas tombol beraksen: putih di atas biru terang ini hanya
     * 2,9:1. Tinta gelap justru yang terbaca (8,4:1). */
    --accent-ink: #0b0f14;
    --danger: #f07a72;
    --shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
    --shadow-naik: 0 8px 26px rgba(0, 0, 0, 0.5);
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--page);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

h1, h2, h3 { margin: 0 0 6px; }
h1 { font-size: 20px; }
h2 { font-size: 16px; margin-bottom: 12px; }
h3 { font-size: 14px; }

code { font-size: 13px; }

.num { font-variant-numeric: tabular-nums; }

/* ================= Atribut hidden =================
 *
 * `display: none` bawaan untuk [hidden] datang dari stylesheet PERAMBAN, dan
 * aturan stylesheet ini - berapa pun bobot selektornya - selalu menang atas
 * bawaan itu. Jadi setiap aturan `display` yang kebetulan mengenai sebuah
 * elemen diam-diam MEMBATALKAN `hidden`-nya.
 *
 * Itu sudah dua kali terjadi dan dua-duanya tidak terlihat dari sisi
 * JavaScript, karena kodenya memang sudah benar menyetel el.hidden = true:
 *
 *   .form-grid label { display: flex }            -> kolom "PIN dalam
 *       sekarang" tetap tampil di formulir BUAT PIN, padahal belum ada PIN
 *       yang bisa dimasukkan.
 *   @media (pointer: coarse) { .btn { display: inline-flex } }
 *       -> di PONSEL saja, tombol ber-atribut hidden tetap tampil; layar
 *       Pengaturan sempat menawarkan "+ Buat PIN dalam" dan "Ganti PIN
 *       dalam" bersamaan.
 *
 * Sebelum ini tiap kejadian ditambal satu per satu (.modal[hidden],
 * .auth-form[hidden], .pin-tawaran[hidden], ...) - tambalan yang justru
 * membuat seolah-olah `hidden` sudah pasti bekerja, padahal hanya bekerja di
 * tempat yang kebetulan sudah pernah ketahuan. Satu aturan berikut
 * menutupnya untuk SELURUH elemen, sekarang dan nanti.
 *
 * !important memang diperlukan di sini: tanpanya, satu aturan display biasa
 * mana pun kembali menang. */
[hidden] { display: none !important; }

/* ================= Header ================= */

.app-header {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 12px 16px 0;
  position: sticky;
  top: 0;
  z-index: 20;
  box-shadow: var(--shadow);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  max-width: 1100px;
  margin: 0 auto;
}

.brand { display: flex; align-items: center; gap: 10px; }
.brand-icon { font-size: 30px; }
.brand-sub { margin: 0; color: var(--ink-2); font-size: 12.5px; }

.month-nav, .year-nav {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Di tab Tahunan, navigasi bulan header disembunyikan (diganti navigasi tahun) */
.app-header.hide-month-nav .month-nav { visibility: hidden; }

.year-nav { justify-content: center; margin-bottom: 14px; }

.month-label {
  min-width: 130px;
  text-align: center;
  font-weight: 600;
  font-size: 15px;
}

.nav-btn {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 12px;
  cursor: pointer;
  font-size: 13px;
}
.nav-btn:hover { border-color: var(--accent); color: var(--accent); }
/* "Bulan Ini" adalah satu kendali — dipecah dua baris, tingginya melar dan
 * baris navigasi ikut naik sementara labelnya justru lebih sulit dibaca. */
.nav-btn { white-space: nowrap; }
.today-btn { font-weight: 600; }

/* Akun di navbar: avatar + nama + tombol keluar */
/* position: relative — menu profil di bawahnya diposisikan terhadap ini. */
.acct-nav { display: flex; align-items: center; gap: 6px; position: relative; }
.acct-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 12px 3px 3px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  max-width: 190px;
}
.acct-btn:hover { border-color: var(--accent); }
.acct-avatar {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
}
.acct-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
/* keadaan belum masuk (mode lokal): avatar netral */
.acct-btn.tanpa-akun .acct-avatar {
  background: var(--grid);
  color: var(--ink-2);
  font-weight: 400;
}
.acct-panah { font-size: 10px; color: var(--ink-2); margin-left: -2px; }
.acct-btn.tanpa-akun .acct-panah { display: none; }

/* ---- Menu profil ----
 * Dipasang di bawah tombol akun. z-index harus di atas isi header (20) tapi
 * di bawah gerbang masuk (100): saat gerbang terbuka, navbar memang tidak
 * boleh bisa disentuh sama sekali. */
.acct-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 190px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-naik);
  display: flex;
  flex-direction: column;
  gap: 2px;
  transform-origin: top right;
  /* Animasi sendiri, BUKAN turun-masuk: yang itu memakai translateY(-100%)
     karena dirancang untuk bilah selebar layar yang meluncur dari luar
     layar. Pada menu kecil, -100% berarti setinggi menunya sendiri - ia
     muncul di ATAS tombolnya, menutupi nama akun. */
  animation: menu-muncul var(--dur-mikro) var(--ease) both;
}
@keyframes menu-muncul {
  from { opacity: 0; transform: translateY(-6px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

.acct-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}
.acct-menu-item:hover { background: var(--grid); }
.acct-menu-item.bahaya { color: var(--danger); }
.acct-menu-item.bahaya:hover {
  background: rgba(208, 59, 59, .10);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}
/* Keluar dipisahkan garis: tindakan yang tidak bisa dibatalkan sebaiknya
 * tidak bersebelahan langsung dengan tindakan sehari-hari. */
.acct-menu-item.bahaya { margin-top: 4px; border-top: 1px solid var(--grid); padding-top: 11px; }

.tabs {
  display: flex;
  gap: 2px;
  overflow-x: auto;
  max-width: 1100px;
  margin: 10px auto 0;
  padding-bottom: 0;
}

.tab-btn {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  color: var(--ink-2);
  padding: 8px 14px 10px;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
}
.tab-btn:hover { color: var(--ink); }
.tab-btn.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* ================= Layout ================= */

.app-main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 16px;
}

.tab-panel { display: none; }
.tab-panel.active { display: block; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
}

.grid-2col {
  display: grid;
  /* minmax(0, ...) di semua grid berikut, bukan 1fr polos.
   *
   * "1fr" sebenarnya berarti minmax(AUTO, 1fr): kolomnya menolak menyusut di
   * bawah lebar min-content isinya. Begitu isi kolom memuat sesuatu yang tak
   * bisa dipatahkan — angka "Rp1.234.000", label kategori panjang, batang
   * grafik — kolomnya melebar melewati kartu, bukan menyusut. Di layar 320px
   * hal ini terukur: kolom Distribusi Setahun keluar 12px dari kartunya dan
   * kartu angka Tahunan keluar 1px dari kisinya. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}
.grid-2col .card { margin-bottom: 16px; }

.app-footer {
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  padding: 8px 16px 24px;
}

/* ================= Kartu statistik ================= */

.tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}

.tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--shadow);
}

.tile-label { color: var(--ink-2); font-size: 12.5px; margin-bottom: 4px; }
.tile-value { font-size: 21px; font-weight: 700; }
.tile-sub { color: var(--muted); font-size: 12px; margin-top: 4px; }

.tile-accent { border-left: 4px solid var(--series-3); }
.tile-danger { border-left: 4px solid var(--status-critical); }
.tile-danger .tile-value { color: var(--teks-critical); }

.note-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 16px;
  color: var(--ink-2);
  font-size: 14px;
}

.summary-strip { color: var(--ink-2); font-size: 14px; margin: 4px 0 12px; }
.card-strip {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--shadow);
}

/* ================= Meter (rasio) ================= */

.meter {
  position: relative;
  height: 8px;
  background: var(--grid);
  /* Track sendiri hanya 1,3:1 terhadap kartu, jadi ujung batangnya tak
   * terlihat. Menggelapkan track sampai 3:1 bukan jawabannya: kontras ISIAN
   * terhadap track malah anjlok ke ~1,4:1. Jadi track dibiarkan terang dan
   * batas luarnya yang digariskan. */
  box-shadow: inset 0 0 0 1px var(--tepi-isian);
  border-radius: 4px;
  margin-top: 8px;
  overflow: visible;
}
.meter.big { height: 12px; margin: 8px 0 4px; }
.meter-fill {
  height: 100%;
  background: var(--series-1);
  border-radius: 4px;
  min-width: 2px;
  transition: width .25s;
}
.meter-marker {
  position: absolute;
  top: -3px;
  width: 2px;
  height: 14px;
  background: var(--ink-2);
}
/* Warna status pada isian batang: memakai varian --isi-* yang dijamin >= 3:1
 * terhadap track.
 *
 * Kedua nama kelas ditulis lengkap dengan sengaja. Ditulis sebagai .fill-*
 * saja, spesifisitasnya sama dengan .hbar-fill yang muncul BELAKANGAN di
 * berkas ini, jadi warna status kalah dan batang grafik tetap biru walau
 * kelasnya sudah terpasang. */
.meter-fill.fill-good,    .hbar-fill.fill-good    { background: var(--isi-good); }
.meter-fill.fill-warn,    .hbar-fill.fill-warn    { background: var(--isi-warning); }
.meter-fill.fill-serious, .hbar-fill.fill-serious { background: var(--isi-serious); }
.meter-fill.fill-crit,    .hbar-fill.fill-crit    { background: var(--isi-critical); }

/* ================= Kartu anggaran (tab Harian) ================= */

.budget-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}
.budget-side { text-align: right; }
.budget-big { font-size: 26px; font-weight: 800; line-height: 1.2; }
.budget-ok   { color: var(--teks-good); }
.budget-warn { color: var(--ink); }
.budget-crit { color: var(--teks-critical); }

/* ================= Badge status (ikon + label) ================= */

.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
}
/* Kecuali di dalam kartu angka. Label status terpanjang ("▫️ Belum Ada Data" —
 * yang justru dilihat pengguna baru di layar PERTAMA) selebar 132px, sedangkan
 * kartu di layar 320px hanya 110px, jadi badge-nya keluar dari kartu. nowrap
 * tetap benar di baris tabel dan daftar transaksi (di sana ada ruang menyamping
 * atau wadah yang bisa digulir); di kartu sempit yang benar adalah membungkus. */
.tile-value .badge { white-space: normal; max-width: 100%; }
/* baris rgba = fallback untuk browser tanpa color-mix() */
.tone-good     { background: rgba(12, 163, 12, .14);  background: color-mix(in srgb, var(--status-good) 14%, transparent); color: var(--teks-good); }
.tone-warning  { background: rgba(250, 178, 25, .18); background: color-mix(in srgb, var(--status-warning) 18%, transparent); color: var(--ink); }
.tone-serious  { background: rgba(236, 131, 90, .18); background: color-mix(in srgb, var(--status-serious) 18%, transparent); color: var(--ink); }
.tone-critical { background: rgba(208, 59, 59, .15);  background: color-mix(in srgb, var(--status-critical) 15%, transparent); color: var(--teks-critical); }
.tone-neutral  { background: var(--grid); color: var(--ink-2); }

/* ================= Tabel ================= */

.table-wrap { overflow-x: auto; }

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.table th {
  text-align: left;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .03em;
  border-bottom: 2px solid var(--baseline);
  padding: 8px 10px;
}
.table th.num-h { text-align: right; }
.table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--grid);
}
.table td.num { text-align: right; font-variant-numeric: tabular-nums; }

.row-total td {
  font-weight: 700;
  border-top: 2px solid var(--baseline);
  border-bottom: none;
}
.row-saving td { background: rgba(42, 120, 214, .06); background: color-mix(in srgb, var(--series-1) 6%, transparent); }
.row-empty td { color: var(--muted); }
.row-link { cursor: pointer; }
.row-link:hover td { background: rgba(42, 120, 214, .07); background: color-mix(in srgb, var(--accent) 7%, transparent); }

/* input di tabel Pembagian Gaji */
.alloc-name { min-width: 150px; }
input.alloc-pct { width: 76px; display: inline-block; }

.pay-detail { font-size: 13px; color: var(--ink-2); }

/* ================= Formulir ================= */

.form-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px 14px;
}

.form-grid label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--ink-2);
  font-weight: 600;
}

/* Ratakan kotak input ke dasar tiap sel: label yang lebih tinggi (mis. karena
 * keterangan "(min. 8 karakter)" membungkus ke baris berikutnya) tidak lagi
 * membuat input di sebelahnya bergeser naik — semua input satu baris sejajar. */
.form-grid label > input,
.form-grid label > select {
  margin-top: auto;
}

.form-grid .span-2 { grid-column: span 2; }
.opt { font-weight: 400; color: var(--muted); }

/* Semua kolom isian bergaya sama.
 *
 * Sengaja memakai daftar PENGECUALIAN, bukan daftar jenis yang diizinkan:
 * versi sebelumnya menyebut text/date/number satu per satu, lalu saat fitur
 * akun ditambahkan seluruh input type="password" terlewat — tampil tanpa gaya
 * apa pun, lebih pendek 16px dari input di sebelahnya sehingga kolom formulir
 * terlihat tidak rata. Dengan pola ini, jenis input baru otomatis ikut rapi.
 *
 * :where() membuat bobot selektor tetap serendah "input" saja, supaya aturan
 * khusus seperti .alloc-pct (lebar 76px) tetap menang. */
input:where(:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="image"]):not([type="hidden"])),
select,
textarea {
  font: inherit;
  color: var(--ink);
  background: var(--page);
  border: 1px solid var(--tepi-isian);
  border-radius: 8px;
  padding: 8px 10px;
  width: 100%;
  /* Tinggi dipatok supaya semua jenis isian sejajar. Tanpa ini, input tanggal
   * ikut membesar karena ikon kalender bawaan peramban (40px) sementara input
   * teks 38px — cukup untuk membuat kolom bersebelahan terlihat tidak rata. */
  min-height: 40px;
}
/* textarea punya tinggi sendiri; aturan ini datang setelahnya agar menang */
textarea { resize: vertical; min-height: 90px; line-height: 1.5; }
/* Penanda kolom yang sedang diketik.
 *
 * Memakai box-shadow, bukan outline: outline digambar sebagai kotak persegi
 * DI LUAR kotak isian, sehingga kolom yang aktif terlihat seperti komponen
 * lain — sudut membulatnya seolah hilang dan ukurannya seakan berubah.
 * box-shadow mengikuti border-radius, jadi cincinnya menyatu dengan desain.
 *
 * Cincinnya TIDAK dihapus: tanpa penanda fokus, pengguna keyboard kehilangan
 * jejak posisi ketikannya. Yang diubah hanya bentuknya, bukan keberadaannya. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(42, 120, 214, .30);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
  /* outline transparan sengaja dipertahankan: pada mode kontras tinggi
   * Windows, box-shadow tidak dirender dan sistem akan mewarnai outline ini */
  outline: 2px solid transparent;
  outline-offset: 2px;
}

/* Peramban lama yang belum mengenal :focus-visible tetap dapat penanda */
@supports not selector(:focus-visible) {
  input:focus, select:focus, textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(42, 120, 214, .30);
  }
}

.form-actions {
  display: flex;
  /* Selalu membungkus. Baris tombol terpanjang (tab Pembagian Gaji: "+ Tambah
   * Kategori" + "Simpan Pembagian" + "Batalkan Perubahan") lebarnya ~430px —
   * di ponsel tombol ketiga terdorong keluar layar dan tak bisa ditekan sama
   * sekali, karena halaman tidak menggulir ke samping. */
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
  align-items: center;
}
/* .wrap kini sama saja dengan dasarnya; dipertahankan agar HTML lama tak perlu diubah */
.form-actions.wrap { flex-wrap: wrap; }

.btn {
  font: inherit;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--tepi-isian);
  border-radius: 8px;
  padding: 8px 16px;
  cursor: pointer;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn-primary:hover { filter: brightness(1.08); color: var(--accent-ink); }

.btn-danger { color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: rgba(208, 59, 59, .10); background: color-mix(in srgb, var(--danger) 10%, transparent); color: var(--danger); }

.file-btn { display: inline-block; }

/* ================= Daftar transaksi ================= */

.list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.list-head.sub { margin-top: 18px; border-top: 1px solid var(--grid); padding-top: 12px; }

.filters { display: flex; flex-wrap: wrap; gap: 8px; }
.filters select { width: auto; max-width: 100%; padding: 6px 8px; font-size: 13px; }

.day-group { margin-bottom: 14px; }

.day-head {
  display: flex;
  justify-content: space-between;
  font-weight: 700;
  font-size: 13.5px;
  color: var(--ink-2);
  border-bottom: 2px solid var(--baseline);
  padding: 6px 2px;
}

.entry {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 8px 2px;
  border-bottom: 1px solid var(--grid);
  flex-wrap: wrap;
}

.entry-main {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 260px;
  min-width: 0;
}

.entry-desc { overflow-wrap: anywhere; }
.entry-note { color: var(--muted); font-size: 13px; }
.entry-date { color: var(--muted); font-size: 12.5px; white-space: nowrap; }

.entry-side {
  display: flex;
  align-items: center;
  /* Wajib membungkus. Isinya (badge "Transfer · SeaBank" + nominal + dua
   * tombol) selebar 285px, sementara di layar 320px hanya tersedia 254px.
   * Karena baris ini dirapatkan ke kanan (justify-content di media ponsel),
   * kelebihannya meluber ke KIRI — badge-nya keluar dari kartu dan menempel
   * di tepi layar, dan halaman TIDAK ikut melebar sehingga cacat ini tak
   * terdeteksi dari lebar dokumen. */
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}

.entry-amount { font-weight: 700; white-space: nowrap; }

.entry-actions { display: flex; gap: 2px; }

.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
  padding: 4px 6px;
  border-radius: 6px;
  line-height: 1;
}
.icon-btn:hover { background: var(--grid); }

.cat-chip {
  background: rgba(42, 120, 214, .12);
  background: color-mix(in srgb, var(--series-1) 12%, transparent);
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
  padding: 2px 9px;
  border-radius: 999px;
  white-space: nowrap;
}

.pay-badge {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--grid);
  color: var(--ink-2);
  white-space: nowrap;
}
.pay-tunai    { background: var(--grid); }
.pay-qris     { background: rgba(27, 175, 122, .15); background: color-mix(in srgb, var(--series-3) 15%, transparent); color: var(--ink); }
.pay-transfer { background: rgba(42, 120, 214, .15); background: color-mix(in srgb, var(--series-1) 15%, transparent); color: var(--ink); }

/* ================= Chip pengaturan ================= */

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--page);
  border: 1px solid var(--tepi-isian);
  border-radius: 999px;
  padding: 4px 6px 4px 12px;
  font-size: 13px;
}

.chip-x {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 15px;
  cursor: pointer;
  padding: 0 6px;
  border-radius: 50%;
  line-height: 1.2;
}
.chip-x:hover { color: var(--danger); background: rgba(208, 59, 59, .12); background: color-mix(in srgb, var(--danger) 12%, transparent); }

.add-row { display: flex; gap: 8px; max-width: 420px; }
/* min-width: 0 wajib — tanpa itu input memakai lebar minimum bawaannya
 * (~180px + placeholder) dan tombol "+ Tambah" terdorong keluar layar 320px. */
.add-row input { flex: 1; min-width: 0; }
.add-row .btn { white-space: nowrap; }

.hint { color: var(--muted); font-size: 13px; margin: 8px 0 0; }
.hint.warn { color: var(--ink); background: rgba(250, 178, 25, .18); background: color-mix(in srgb, var(--status-warning) 18%, transparent); border-radius: 8px; padding: 8px 12px; }
.hint-inline { color: var(--muted); font-size: 12px; font-weight: 400; }

/* ================= Grafik: batang horizontal ================= */

.hbar { display: flex; flex-direction: column; gap: 8px; }

.hbar-row {
  display: grid;
  /* minmax(0, 1fr), bukan 1fr: dasar 1fr adalah "min-content", jadi track
   * grafik menolak menyusut di bawah lebar isinya dan barisnya melebihi layar
   * ponsel — seluruh halaman ikut bisa digeser ke samping. */
  grid-template-columns: minmax(110px, 170px) minmax(0, 1fr) minmax(90px, auto);
  align-items: center;
  gap: 10px;
}

.hbar-label {
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hbar-track {
  background: var(--grid);
  box-shadow: inset 0 0 0 1px var(--tepi-isian);
  border-radius: 4px;
  height: 16px;
}

.hbar-fill {
  height: 100%;
  background: var(--series-1);
  border-radius: 0 4px 4px 0;
  min-width: 2px;
}
.hbar-fill.fill-s2 { background: var(--series-2); }

.hbar-value { font-size: 13px; text-align: right; white-space: nowrap; }

/* Keterangan warna di bawah grafik: kotak contoh + kalimat, supaya arti
 * warnanya tidak perlu ditebak. Kosong (dan tak memakan ruang) bila tidak
 * ada kategori yang melewati anggaran. */
.ket-grafik:empty { display: none; }
.ket-lewat {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: 3px;
  background: var(--isi-critical);
  vertical-align: -1px;
}

/* Angka bernada status (kolom Selisih). Nada diambil dari status baris itu
 * sendiri, BUKAN dari tanda plus/minus: pada baris tabungan, selisih negatif
 * berarti menabung MELEBIHI target - itu kabar baik, bukan buruk. */
.num-good     { color: var(--teks-good); }
.num-warning  { color: var(--teks-warning); }
.num-serious  { color: var(--teks-serious); }
.num-critical { color: var(--teks-critical); }

/* batang berpasangan (target vs realisasi) */
.hbar-track.slim { height: 9px; }
.hpair-tracks { display: flex; flex-direction: column; gap: 2px; }
.hpair-values { display: flex; flex-direction: column; text-align: right; line-height: 1.35; }
.hpair-values .v-a { font-size: 12px; color: var(--muted); }
.hpair-values .v-b { font-size: 13px; font-weight: 600; }

.chart-empty { color: var(--muted); font-size: 14px; text-align: center; padding: 18px 8px; }

/* ================= Grafik: kolom SVG ================= */

.chart-scroll { overflow-x: auto; }

/* min-width 800: pada layar sempit grafik digulir, bukan diperkecil sampai
 * label sumbu tak terbaca (skala terkecil ~0,83x dari viewBox 960) */
.colchart { width: 100%; min-width: 800px; height: auto; display: block; }

.colchart .grid { stroke: var(--grid); stroke-width: 1; }
.colchart .baseline { stroke: var(--baseline); }
.colchart .tick { fill: var(--muted); font-size: 13px; font-family: inherit; }
.colchart .s1 { fill: var(--series-1); }
.colchart .s2 { fill: var(--series-2); }
.colchart .s3 { fill: var(--series-3); }
.colchart .hover-zone:hover { fill: rgba(11, 11, 11, .04); fill: color-mix(in srgb, var(--ink) 4%, transparent); }

.chart-legend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  font-size: 13px;
  color: var(--ink-2);
}
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.legend-swatch.s1 { background: var(--series-1); }
.legend-swatch.s2 { background: var(--series-2); }
.legend-swatch.s3 { background: var(--series-3); }

/* ================= Gerbang akun (layar sebelum masuk) ================= */
.auth-gate {
  position: fixed;
  inset: 0;
  z-index: 100;
  overflow-y: auto;
  background: var(--page);
  padding: 24px 16px;
  display: flex;
}
/* Aplikasi di belakang gerbang tidak boleh ikut tergulir */
body.gate-open { overflow: hidden; }

/* Ditengahkan lewat "margin: auto", BUKAN align-items/justify-content.
 * Pada wadah yang bisa digulir, penengahan dengan align-items memotong bagian
 * ATAS isi begitu isinya lebih tinggi dari layar — bagian yang terpotong tidak
 * bisa dijangkau karena berada di luar area gulir. Dengan margin auto, sisa
 * ruang dibagi saat isi muat, dan otomatis jadi nol saat isi kepanjangan
 * sehingga formulir panjang (Daftar, Kode Pemulihan) tetap tergulir utuh. */
.auth-gate-inner {
  width: 100%;
  max-width: 620px;
  margin: auto;
}
/* Jalan kembali ke dashboard dari layar masuk. Sengaja tampil sebagai
   tautan sekunder, bukan tombol penuh: tindakan utama di layar ini adalah
   Masuk, dan tombol bergaya sama akan bersaing dengannya. */
.tautan-kembali {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 14px;
  padding: 6px 12px 6px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 13px;
  text-decoration: none;
  transition:
    color var(--dur-mikro) var(--ease),
    border-color var(--dur-mikro) var(--ease),
    transform var(--dur-mikro) var(--ease);
}
.tautan-kembali:hover {
  color: var(--accent);
  border-color: var(--accent);
  transform: translateX(-2px);
}
.tautan-kembali:active { transform: translateX(0); transition-duration: 40ms; }
.tautan-kembali:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 0 3px rgba(42, 120, 214, .30);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.auth-gate-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 12px 0 20px;
}
.auth-gate-brand h1 { margin: 0; font-size: 22px; }
.auth-gate-brand .brand-sub { margin: 2px 0 0; }
.auth-gate-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px 18px 20px;
}

@media (max-width: 520px) {
  .auth-gate { padding: 16px 12px 32px; }
  .auth-gate-card { padding: 14px; }
  .auth-gate-brand h1 { font-size: 19px; }
}

/* ---- Penutup pemuatan ----
 * Warnanya sama persis dengan latar gerbang masuk, jadi pergantiannya
 * tidak terlihat sebagai kedipan. */
.layar-memuat {
  position: fixed;
  inset: 0;
  z-index: 150;
  background: var(--page);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 14px;
}
body.muat-awal { overflow: hidden; }
/* Gerbang & kunci muncul TANPA animasi pada pemuatan pertama: tidak ada
 * apa pun untuk "dimasuki", dan selama memudar isinya tembus pandang. */
/* :not([hidden]) ikut ditulis BUKAN untuk menyaring apa pun, melainkan untuk
 * menyamai bobot selektor aturan animasinya yang muncul lebih belakang di
 * berkas ini - tanpa itu, aturan yang belakangan tetap menang dan gerbang
 * tetap memudar masuk. */
.auth-gate.tanpa-animasi:not([hidden]),
.auth-gate.tanpa-animasi:not([hidden]) .auth-gate-brand,
.auth-gate.tanpa-animasi:not([hidden]) .auth-gate-card,
.auth-gate.tanpa-animasi:not([hidden]) .tautan-kembali { animation: none !important; }

/* Panel "aplikasi tidak dapat dimuat". Sengaja tidak memakai token warna:
 * kalau CSS ikut gagal dimuat, panel ini harus tetap terbaca. */
.modul-gagal {
  max-width: 560px;
  margin: 48px auto;
  padding: 20px;
  border: 1px solid #d03b3b;
  border-radius: 10px;
  background: #fff;
  color: #0b0b0b;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}
.modul-gagal h1 { font-size: 19px; margin: 0 0 8px; }

/* ================= Kunci layar ("PIN dalam") =================
 * Memakai kerangka .auth-gate; yang khas di sini hanya kartunya yang lebih
 * sempit dan barisan titiknya. */

.kunci-inner { max-width: 380px; }
.kunci-kartu { padding: 26px 20px 22px; text-align: center; }

.kunci-kepala { margin-bottom: 20px; }
.kunci-gembok { font-size: 34px; line-height: 1; display: block; margin-bottom: 8px; }
.kunci-kepala h1 { font-size: 19px; margin: 0 0 4px; }
.kunci-kepala .hint { margin: 0; }
.kunci-app { font-weight: 600; color: var(--ink-2); }

/* Pembungkus yang bisa diketuk: SELURUH area titik memfokuskan kotak isian,
 * sehingga papan angka ponsel terbuka tanpa perlu membidik kotak setinggi
 * beberapa piksel. */
.kunci-tap {
  display: block;
  position: relative;
  padding: 10px 0 6px;
  cursor: text;
}

/* Kotak isian sungguhan: tak terlihat, tapi TETAP bisa difokuskan dan tetap
 * menerima ketikan. Jangan diganti display:none atau visibility:hidden -
 * keduanya membuatnya tidak bisa difokuskan sama sekali, dan papan angka
 * tidak akan pernah muncul. */
.kunci-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: 0;
  padding: 0;
  /* 16px: di bawah itu Safari iOS ikut memperbesar halaman begitu kotak isian
   * difokuskan, dan seluruh layar kunci tampak melompat. */
  font-size: 16px;
  color: transparent;
  background: transparent;
  caret-color: transparent;
}

.kunci-titik {
  display: flex;
  justify-content: center;
  gap: 14px;
}
.kunci-titik i {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--tepi-isian);
  background: transparent;
  transition: background var(--dur-mikro) var(--ease),
              border-color var(--dur-mikro) var(--ease);
}
.kunci-titik i.isi {
  background: var(--accent);
  border-color: var(--accent);
  animation: titik-isi var(--dur-mikro) var(--ease);
}
@keyframes titik-isi {
  from { transform: scale(.4); }
  60%  { transform: scale(1.25); }
  to   { transform: scale(1); }
}

/* Sedang memeriksa: titik berdenyut bergantian supaya jeda PBKDF2 terbaca
 * sebagai "sedang bekerja", bukan sebagai aplikasi yang menggantung. */
.kunci-titik.sibuk i {
  animation: titik-denyut 900ms var(--ease) infinite;
  background: var(--muted);
  border-color: var(--muted);
}
.kunci-titik.sibuk i:nth-child(2) { animation-delay: 90ms; }
.kunci-titik.sibuk i:nth-child(3) { animation-delay: 180ms; }
.kunci-titik.sibuk i:nth-child(4) { animation-delay: 270ms; }
.kunci-titik.sibuk i:nth-child(5) { animation-delay: 360ms; }
.kunci-titik.sibuk i:nth-child(6) { animation-delay: 450ms; }
@keyframes titik-denyut {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

/* PIN salah. Titiknya ikut memerah supaya penolakan tidak hanya lewat teks. */
.kunci-titik.salah i { border-color: var(--isi-critical); background: var(--isi-critical); }
.kunci-tap.salah { animation: goyang 360ms var(--ease); }
@keyframes goyang {
  0%, 100% { transform: translateX(0); }
  15%      { transform: translateX(-9px); }
  30%      { transform: translateX(8px); }
  45%      { transform: translateX(-6px); }
  60%      { transform: translateX(4px); }
  80%      { transform: translateX(-2px); }
}

/* Fokus terlihat di TITIKNYA, karena kotak isiannya sendiri tak terlihat.
 * Tanpa ini pengguna papan ketik tidak punya petunjuk apa pun. */
.kunci-tap:focus-within .kunci-titik i { border-color: var(--accent); }
.kunci-tap:focus-within {
  outline: 2px solid transparent;
  border-radius: 10px;
  box-shadow: 0 0 0 3px rgba(39, 113, 203, .28);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
}

.kunci-status {
  min-height: 20px;
  margin: 14px 0 4px;
  font-size: 13px;
  color: var(--ink-2);
}
.kunci-status.salah { color: var(--teks-critical); }
.kunci-status.ok { color: var(--teks-good); }

.kunci-aksi { justify-content: center; margin-top: 6px; }
.kunci-panel { text-align: left; }
.kunci-panel h2 { font-size: 17px; }

/* Hanya untuk pembaca layar: label kotak isian yang tak terlihat. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Tawaran PIN dalam setelah daftar. Dibedakan dari kartu gerbang di
 * sekitarnya lewat garis aksen, bukan lewat warna latar yang mencolok:
 * ini saran, bukan peringatan. */
.pin-tawaran {
  margin-top: 16px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius);
  background: var(--page);
  animation: muncul-halus var(--dur-masuk) var(--ease) both;
}
.pin-tawaran h3 { margin-bottom: 6px; }
.pin-tawaran p { margin: 0 0 8px; }

/* ---- Kartu Akun: tiap bagian punya judul + satu tombol tindakan ----
 * Formulirnya baru muncul setelah tombolnya ditekan. Bukan <details>: judul
 * <summary> yang tertutup terlihat seperti teks biasa, dan tindakan di
 * dalamnya (ganti password!) tidak pernah ditemukan. */
.akun-bagian {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--grid);
}
.akun-baris {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.akun-baris h3 { margin: 0; }
.akun-form { margin-top: 12px; }
.akun-keluar { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--grid); }

/* ---- PIN dalam di tab Pengaturan ---- */
.pin-dalam {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--grid);
}
.pin-dalam h3 { margin-bottom: 4px; }
.pin-dalam .kunci-status { text-align: left; margin: 8px 0; font-weight: 600; }
/* Baris pesan di dalam layar timbul PIN. .kunci-status dasarnya ditengahkan
 * (dirancang untuk layar kunci); di dalam formulir ia harus sejajar dengan
 * label kolom di atasnya. */
#pin-form-status { text-align: left; margin: 10px 0 0; font-weight: 600; }
#pin-form-status:empty { margin: 0; }

/* ================= Layar timbul (modal) =================
 * Dipakai tab Pengaturan yang kini bukan tab lagi.
 *
 * z-index 90: DI BAWAH gerbang masuk & kunci layar (100). Kalau lebih tinggi,
 * pengaturan bisa menutupi layar masuk - dan orang yang belum berhak bisa
 * menekan tombolnya. */
.modal {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  padding: 24px 16px;
}

/* Layar timbul yang dibuka DARI dalam layar timbul lain (formulir PIN dalam
 * di atas Pengaturan). Tetap di bawah gerbang akun & layar kunci (z 100):
 * saat aplikasi terkunci, tidak satu pun modal boleh bisa disentuh. */
.modal-atas { z-index: 95; }

/* Panel bertugas tunggal - satu formulir pendek - tidak perlu selebar panel
 * Pengaturan yang memuat banyak kartu. */
.modal-panel-sempit { max-width: 460px; }

.modal-tirai {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .45);
  animation: muncul-halus var(--dur-mikro) var(--ease) both;
}

/* margin: auto, BUKAN align-items - alasannya sama dengan gerbang masuk:
 * pada wadah yang bisa digulir, penengahan dengan align-items memotong bagian
 * ATAS isi begitu isinya lebih tinggi dari layar, dan bagian yang terpotong
 * tidak bisa dijangkau. */
.modal-panel {
  position: relative;
  margin: auto;
  width: 100%;
  max-width: 780px;
  max-height: calc(100vh - 48px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-naik);
  animation: muncul-skala var(--dur-masuk) var(--ease) both;
}

.modal-kepala {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--grid);
}
.modal-kepala h2 { margin: 0; font-size: 17px; }

.modal-tutup {
  flex: none;
  width: 34px;
  height: 34px;
  border: 1px solid var(--tepi-isian);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.modal-tutup:hover { border-color: var(--danger); color: var(--danger); }

/* Yang tergulir adalah ISInya, bukan halaman di belakangnya. */
.modal-isi {
  overflow-y: auto;
  padding: 16px 16px 4px;
  -webkit-overflow-scrolling: touch;
}
.modal-isi .card:last-child { margin-bottom: 12px; }

/* Halaman di belakang modal tidak boleh ikut tergulir. */
body.layar-terbuka { overflow: hidden; }

@media (max-width: 640px) {
  /* Di ponsel modal memenuhi layar: panel mengambang dengan tepi 16px
   * menyisakan kolom isi yang terlalu sempit untuk formulir di dalamnya. */
  .modal { padding: 0; }
  .modal-panel {
    max-width: none;
    max-height: none;
    height: 100%;
    border: 0;
    border-radius: 0;
  }
  .modal-isi { padding: 12px 12px 4px; }
}

/* ================= Akun (daftar / masuk / pemulihan) ================= */
.subtabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 4px 0 14px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 8px;
}
.subtab-btn {
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.subtab-btn:hover { border-color: var(--accent); color: var(--accent); }
.subtab-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}


/* ---- Tombol yang tampil sebagai tautan ----
 * Untuk jalan keluar sekunder DI DALAM formulir ("Lupa password?"): bukan
 * tindakan utama, jadi tidak boleh berbentuk tombol yang bersaing dengan
 * "Masuk", tapi tetap harus terbaca sebagai sesuatu yang bisa ditekan -
 * karena itu bergaris bawah, bukan sekadar teks berwarna. */
.tautan-aksi {
  border: 0;
  background: none;
  padding: 2px 0;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.tautan-aksi:hover { color: var(--ink); }
/* Di layar sentuh, teks setinggi 18px terlalu kecil untuk dituju jempol.
 * Yang diperbesar sasarannya, bukan hurufnya. */
@media (pointer: coarse) {
  .tautan-aksi { padding: 12px 0; }
}
/* Sejajar kanan, mengikuti kolom password di atasnya - di kiri ia menempel
 * pada kotak centang "tetap masuk" di bawahnya dan terbaca seperti bagian
 * dari kalimat itu. */
.baris-lupa { margin: 8px 0 0; text-align: right; }

/* Baris kotak centang (mis. "tetap masuk" di formulir Masuk). Kotaknya
 * disejajarkan dengan baris pertama teks, bukan dengan tengah blok, supaya
 * keterangan dua baris di bawahnya tidak menggeser kotak ke bawah. */
.cek-baris {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin-top: 12px;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}
.cek-baris input[type="checkbox"] {
  flex: none;
  width: 17px;
  height: 17px;
  margin: 1px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
/* Pada layar sentuh kotaknya diperbesar agar nyaman ditekan. Seluruh label
 * memang sudah bisa ditekan, tetapi kotak sekecil 17px terlihat seperti
 * hiasan, bukan kendali. */
@media (pointer: coarse) {
  .cek-baris { margin-top: 14px; }
  .cek-baris input[type="checkbox"] { width: 24px; height: 24px; margin-top: 0; }
}
.cek-baris small {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

.account-line {
  margin: 0 0 12px;
  font-size: 14px;
  color: var(--ink);
}

#auth-change-box {
  margin-top: 14px;
  border-top: 1px solid var(--border);
  padding-top: 10px;
}
#auth-change-box summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--ink-2);
}
#auth-change-box summary:hover { color: var(--accent); }
#auth-change-box h3 { margin: 14px 0 8px; font-size: 14px; }

/* Kode pemulihan hanya tampil sekali — dibuat sangat menonjol supaya
   tidak terlewat, sebab tidak bisa ditampilkan ulang. */
.recovery-box {
  margin-top: 16px;
  padding: 14px 16px;
  border: 2px solid var(--status-warning);
  border-radius: var(--radius);
  background: rgba(250, 178, 25, .10);
  background: color-mix(in srgb, var(--status-warning) 10%, transparent);
}
.recovery-box h3 { margin: 0 0 6px; font-size: 15px; }
.recovery-box p { margin: 0 0 10px; font-size: 13px; color: var(--ink-2); }
.recovery-code {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 2px;
  text-align: center;
  padding: 12px;
  margin: 0 0 12px !important;
  border-radius: 8px;
  background: var(--surface);
  border: 1px dashed var(--tepi-isian);
  color: var(--ink) !important;
  user-select: all;
  overflow-wrap: anywhere;
}

/* Baris filter di atas tabel (mis. pemilih kolom rekap tahunan). */
.baris-filter {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: -4px 0 12px;
  font-size: 13px;
  color: var(--ink-2);
}
.baris-filter select { max-width: 100%; }

/* ================= Tabel alokasi: dasar % atau Rp tetap ================= */
.alloc-mode {
  font: inherit;
  font-size: 13px;
  padding: 4px 6px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink);
}
.alloc-rp-input { width: 110px; }
/* baris beranggaran tetap diberi penanda halus agar mudah dibedakan sekilas */
#alloc-table tr.mode-rp .alloc-rp { font-weight: 600; }

/* ---- Ponsel: tiap kategori jadi blok bertumpuk ----
 * Lima kolom tidak muat di layar sempit; menggeser tabel ke samping untuk
 * mengisi anggaran terasa seperti bekerja lewat lubang kunci. Di bawah 640px
 * tiap <tr> jadi kartu: nama kategori selebar penuh, lalu Dasar / Nilai /
 * Target Rp tepat di bawahnya. Labelnya dibaca dari data-label supaya sel
 * tetap bisa dikenali tanpa baris judul. */
@media (max-width: 640px) {
  #alloc-table thead { display: none; }
  #alloc-table tr {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "kategori kategori"
      "dasar    nilai"
      "target   hapus";
    gap: 8px 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--grid);
  }
  #alloc-table td {
    display: block;
    padding: 0;
    border: 0;
    text-align: left;
  }
  #alloc-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 11.5px;
    color: var(--ink-2);
    margin-bottom: 3px;
  }
  #alloc-table td[data-label="Kategori"] { grid-area: kategori; }
  #alloc-table td[data-label="Dasar"]    { grid-area: dasar; }
  #alloc-table td[data-label="Nilai"]    { grid-area: nilai; }
  #alloc-table td[data-label="Target Rp"] { grid-area: target; }
  #alloc-table td.alloc-hapus { grid-area: hapus; justify-self: end; align-self: end; }
  #alloc-table .alloc-name, #alloc-table .alloc-mode { width: 100%; min-width: 0; }
  /* Sel grid tidak boleh menolak menyusut (min-width:auto bawaan grid item):
   * itulah 5px sisa luberannya. Keterangan persen otomatis pindah ke baris
   * sendiri supaya tidak berjejer dengan kotak isian. */
  #alloc-table td { min-width: 0; overflow-wrap: anywhere; }
  #alloc-table .alloc-rp-input { width: calc(100% - 28px); }
  #alloc-table .alloc-eff-pct { display: block; margin-top: 2px; }
  /* Badge TOTAL ("Belum ada pemasukan") adalah sisa luberan terakhir:
   * .badge menolak patah baris, dan di sel selebar ~300px ia mendorong tabel
   * keluar layar. Di sini boleh patah. */
  #alloc-table .badge { white-space: normal; line-height: 1.25; }
  /* Baris TOTAL: dua sel angka berdampingan di bawah judulnya. */
  #alloc-table tr.row-total {
    grid-template-areas:
      "judul judul"
      "pct   rp";
  }
  #alloc-table tr.row-total td:nth-child(1) { grid-area: judul; }
  #alloc-table tr.row-total td:nth-child(2) { display: none; }
  #alloc-table tr.row-total td:nth-child(3) { grid-area: pct; }
  #alloc-table tr.row-total td:nth-child(4) { grid-area: rp; }
  #alloc-table tr.row-total td:nth-child(5) { display: none; }
  /* Tabelnya dijadikan blok selebar wadah. Dibiarkan display:table, lebar
   * intrinsiknya mengikuti isi terlebar (masih meluber ~5px) walau barisnya
   * sudah grid. */
  #alloc-table, #alloc-table tbody { display: block; width: 100%; }
  /* Pembungkusnya tidak perlu digeser lagi. */
  #alloc-edit-table .table-wrap { overflow-x: visible; }

  /* Rekap per Bulan: dengan tiga kolom pun badge Status yang tak boleh
   * patah ("Perlu Perbaikan") masih mendorong tabel 38px melewati layar.
   * Di sini badge boleh patah dan sel dipadatkan. */
  #tahunan-table .table th, #tahunan-table .table td { padding-left: 6px; padding-right: 6px; }
  #tahunan-table .badge { white-space: normal; line-height: 1.25; }
  #tahunan-table .table-wrap { overflow-x: visible; }
}

/* ================= Peringatan gagal simpan ================= */
/* Menggantikan alert() yang, saat kuota penuh, muncul berulang tiap autosave
   dan membuat aplikasi tidak bisa dipakai. Sengaja menempel di atas layar
   supaya tidak terlewat, tapi tidak memblokir interaksi. */
.save-error {
  position: sticky;
  top: 0;
  z-index: 50;
  padding: 10px 16px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--accent-ink);
  background: var(--danger);
  box-shadow: var(--shadow);
}

/* ================= Salinan internal (tab Pengaturan) ================= */
.backup-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  color: var(--ink-2);
}
.backup-row:last-of-type { border-bottom: 0; }
.btn-mini {
  font: inherit;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--danger);
  cursor: pointer;
}
.btn-mini:hover { border-color: var(--danger); }

/* ================= Distribusi setahun & penilaian akhir ================= */

.dist-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

.verdict-banner { text-align: center; padding: 14px 8px 6px; }
.verdict-banner .badge { font-size: 19px; padding: 8px 22px; }
.verdict-banner .hint { margin-top: 12px; }

/* ================= Tooltip grafik ================= */

.chart-tooltip {
  position: fixed;
  z-index: 100;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  padding: 8px 12px;
  font-size: 13px;
  pointer-events: none;
  /* Di layar 320px, 260px tetap muat, tetapi tooltip dipanggil juga lewat
   * sentuhan — min() menjaga agar isinya tidak pernah terpotong tepi layar. */
  max-width: min(260px, calc(100vw - 16px));
  line-height: 1.5;
}

/* ================= Beranda ponsel (sapaan + pintasan) =================
 *
 * Hanya tampil di layar kecil; yang memutuskan JavaScript lewat lebar layar
 * (fitur/dashboard.js), bukan aturan di sini - supaya tidak ada dua sumber
 * kebenaran untuk pertanyaan "beranda mana yang sedang berlaku".
 * ============================================================ */
.sapaan-ponsel {
  margin: 6px 0 2px;
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--ink);
}
.sapaan-sub {
  margin: 0 0 18px;
  font-size: 14px;
  color: var(--muted);
}

.pintasan { display: grid; gap: 10px; }

/* Satu baris penuh per pintasan: ikon besar di kiri, judul + keterangan di
 * kanan. Dibuat grid tiga kolom, bukan flex, supaya keterangan yang memanjang
 * membungkus ke bawah tanpa pernah mendorong ikon atau panahnya. */
.pintasan-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 14px;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition:
    transform var(--dur-mikro) var(--ease),
    border-color var(--dur-mikro) var(--ease),
    box-shadow var(--dur-mikro) var(--ease);
}
.pintasan-item:hover { border-color: var(--accent); }
/* Ditekan: sedikit mengecil. Di layar sentuh tidak ada hover, jadi inilah
 * satu-satunya tanda bahwa ketukannya diterima. */
.pintasan-item:active { transform: scale(.985); box-shadow: none; }
.pintasan-item:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(42, 120, 214, .30);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.pintasan-ikon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 15px;
  background: rgba(39, 113, 203, .12);
  background: color-mix(in srgb, var(--aksen-pintas, var(--accent)) 14%, transparent);
  color: var(--aksen-pintas, var(--accent));
}
.pintasan-ikon svg {
  width: 27px;
  height: 27px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Judul dan keterangan sengaja dibedakan tegas - bukan hanya ukurannya.
 * Judulnya tebal dan rapat; keterangannya lebih kecil, lebih renggang, dan
 * redup, supaya satu kali lirik sudah cukup membedakan keempat pintasan. */
.pintasan-judul {
  display: block;
  font-size: 16.5px;
  font-weight: 700;
  letter-spacing: -.012em;
  line-height: 1.2;
}
.pintasan-ket {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .005em;
  line-height: 1.35;
  color: var(--muted);
}
.pintasan-panah { color: var(--muted); font-size: 24px; line-height: 1; }

/* Warna ikon dibedakan per pintasan: mencatat dan melihat adalah dua jenis
 * tindakan, dan uang masuk bukan uang keluar. */
#pintas-catat-pengeluaran { --aksen-pintas: var(--accent); }
#pintas-hari-ini         { --aksen-pintas: var(--series-2); }
#pintas-bulan-ini        { --aksen-pintas: var(--ink-2); }
#pintas-catat-gaji       { --aksen-pintas: var(--series-3); }

/* ================= Navigasi ponsel =================
 *
 * Di layar kecil bilah enam tab dilepas: beranda ponsel sudah menjadi pintu
 * masuk, dan bilah melayang setinggi dua baris memakan seperlima layar untuk
 * tujuan yang sebagian besarnya jarang dibuka.
 *
 * Gantinya bilah di ATAS berisi satu tombol kembali - tanpa itu, layar yang
 * dibuka dari pintasan menjadi jalan buntu. Yang menyalakan semua ini kelas
 * `mode-ponsel` pada <body>, dipasang inti/tampilan.js; stylesheet ini tidak
 * mengulang pertanyaannya sendiri lewat media query terpisah.
 * ============================================================ */
.mode-ponsel .tabs { display: none; }
/* Bantalan bawah dulu dihitung dari tinggi bilah melayang. Tanpa bilah itu,
 * ruang kosong sebesar 150px hanya membuat halaman terasa menggantung. */
.mode-ponsel .app-main { padding-bottom: 28px; }

/* Judul besar per seksi tidak ikut ditampilkan saat bilah navigasi menyebut
 * judul yang sama persis - dua kali menulis "Harian" di satu layar hanya
 * membuang tinggi layar yang justru mahal di ponsel. */
.mode-ponsel .judul-halaman { display: none; }

.bilah-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
}
.nav-kembali {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: 44px;           /* sasaran ketuk; lihat blok pointer: coarse */
  padding: 6px 12px 6px 4px;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
}
.nav-kembali:active { opacity: .6; }
.nav-kembali:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 3px rgba(42, 120, 214, .30);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}
.nav-panah { font-size: 28px; line-height: 1; margin-top: -3px; }
.nav-judul {
  grid-column: 2;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--ink);
}

/* ---- "Lainnya": tiga tab sisanya, di dalam layar Harian ----
 * Sengaja lebih ringkas daripada pintasan di beranda (tanpa keterangan,
 * ikon lebih kecil): ini tujuan sekunder, dan bentuknya harus mengatakan itu. */
.lainnya { margin-top: 18px; }
.lainnya-judul {
  margin: 0 0 8px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
.lainnya-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 10px 14px;
  margin-bottom: 8px;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  cursor: pointer;
}
.lainnya-item:active { transform: scale(.99); }
.lainnya-item:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  border-color: var(--accent);
}
.lainnya-ikon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  color: var(--ink-2);
}
.lainnya-ikon svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.lainnya-teks { font-size: 15px; font-weight: 600; }
.lainnya-panah { color: var(--muted); font-size: 22px; line-height: 1; }

/* ================= Responsif ================= */

@media (max-width: 900px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-2col { grid-template-columns: minmax(0, 1fr); }
  .dist-wrap { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
  body { font-size: 14px; }

  /* Header menempel di atas layar (sticky). Dengan tiga baris penuh — merek +
   * keterangan, navigasi bulan, lalu tab — tingginya ~185px, lebih dari
   * seperempat layar ponsel dan ikut menutupi isi setiap kali menggulir.
   * Yang dipangkas hanya hiasannya; semua kendali tetap ada. */
  .app-header { padding: 8px 12px 0; }
  .header-inner { gap: 8px; }
  .brand { flex: 1 1 auto; min-width: 0; gap: 8px; }
  .brand-icon { font-size: 24px; }
  .brand-sub { display: none; }
  .brand h1 { font-size: 17px; }
  .acct-nav { flex: none; }
  .acct-btn { max-width: 132px; }
  /* Navigasi bulan turun ke barisnya sendiri, ditengahkan, agar kedua panah
   * tidak berdesakan dengan tombol akun di baris merek.
   *
   * order: 1 penting. Di HTML urutannya merek → navigasi bulan → akun, jadi
   * tanpa ini navigasi bulan (lebar 100%) memotong barisnya dan tombol akun
   * terlempar ke baris KETIGA — header jadi setinggi tiga baris lagi, persis
   * yang hendak dihindari. Dengan order, akun naik ke baris merek. */
  /* flex-wrap sebagai jaring pengaman: bila label bulan kelak lebih panjang
   * (atau ukuran huruf sistem diperbesar), barisnya pecah rapi alih-alih
   * mendorong tombol keluar layar. */
  .month-nav { order: 1; flex: 1 0 100%; flex-wrap: wrap; justify-content: center; }
  /* Lebar tetap 105px untuk label bulan membuat baris ini 322px di layar
   * 320px — "Bulan Ini" pecah jadi dua baris. Label dibiarkan selebar isinya. */
  .month-label { min-width: 0; }
  .nav-btn { padding: 8px 10px; }
  /* Di tab Tahunan navigasi ini disembunyikan. Di layar lebar dipakai
   * visibility agar tata letak tidak melompat — di ponsel baris kosong
   * setinggi 44px itu justru memakan ruang yang sudah sempit. */
  .app-header.hide-month-nav .month-nav { display: none; }

  .filters { width: 100%; }
  .filters select { flex: 1 1 140px; }

  /* Kolom isian TIDAK ikut mengecil ke 14px.
   *
   * Safari iOS memperbesar sendiri seluruh halaman begitu kolom berukuran
   * di bawah 16px disentuh, dan tidak pernah mengecilkannya kembali — layar
   * masuk langsung terlihat "melompat" dan sebagian tombol keluar layar.
   * Selektor elemen polos ini sengaja dipakai supaya kelas seperti .btn
   * (yang bobotnya lebih tinggi) tetap memegang ukurannya sendiri. */
  input, select, textarea { font-size: 16px; }
  .tiles { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }
  .tile-value { font-size: 17px; }
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .form-grid .span-2 { grid-column: span 1; }
  .entry-side { width: 100%; justify-content: flex-end; }
  .hbar-row { grid-template-columns: 90px minmax(0, 1fr) auto; gap: 6px; }
  .month-label { min-width: 105px; font-size: 14px; }
}

/* Layar 320px (iPhone SE generasi pertama, dan ponsel mana pun dengan ukuran
 * huruf sistem diperbesar). Ruang tersisa di baris navigasi bulan tinggal
 * 296px — persis selebar dua panah 44px + label 105px + "Bulan Ini" 85px +
 * tiga jarak 6px. Karena pas di ambang, barisnya pecah dua dan header
 * bertambah 50px. Jaraknya dirapatkan HANYA di sini; pada 360px ke atas
 * ruangnya berlebih dan 6px tetap dipakai supaya kedua panah tidak terasa
 * berdempetan dengan tombol "Bulan Ini". */
@media (max-width: 360px) {
  .month-nav { gap: 4px; }
}

/* ============================================================
 * Gerak & animasi
 *
 * Prinsipnya: gerakan hanya untuk menjelaskan perubahan, bukan hiasan.
 * Elemen di aplikasi ini dirender ulang cukup sering (ganti bulan, simpan
 * transaksi, sinkron masuk), jadi animasi dibuat pendek dan halus supaya
 * pengulangannya tidak terasa mengganggu.
 *
 * Seluruh blok ini dimatikan bagi pengguna yang meminta "kurangi gerakan"
 * di pengaturan sistemnya — lihat bagian paling bawah.
 * ============================================================ */

@keyframes muncul {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@keyframes muncul-halus {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes muncul-skala {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes turun-masuk {
  from { opacity: 0; transform: translateY(-100%); }
  to   { opacity: 1; transform: none; }
}

/* batang grafik tumbuh dari kiri — dipakai scaleX supaya lebar akhirnya
   (yang diatur inline saat render) tidak perlu diketahui CSS */
@keyframes tumbuh-kanan {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes denyut-perhatian {
  0%, 100% { box-shadow: 0 0 0 0 rgba(250, 178, 25, 0); }
  50%      { box-shadow: 0 0 0 6px rgba(250, 178, 25, .28); }
}

/* ---------------- Perpindahan tab ---------------- */
/* Panel aktif muncul lembut. Karena tab non-aktif memakai display:none,
   animasi otomatis terpicu ulang setiap kali tab dibuka. */
.tab-panel.active { animation: muncul-halus var(--dur-layar) var(--ease) both; }

/* Isi panel menyusul berurutan supaya perpindahan terasa mengalir, bukan
   berganti mendadak. Hanya beberapa elemen pertama yang diberi jeda —
   selebihnya muncul bersamaan agar halaman panjang tidak terasa lambat. */
.tab-panel.active > .card,
.tab-panel.active > .note-card,
.tab-panel.active > .summary-strip,
.tab-panel.active > .grid-2col > .card {
  animation: muncul var(--dur-masuk) var(--ease) both;
}
.tab-panel.active > *:nth-child(2) { animation-delay: calc(var(--tunda) * 1); }
.tab-panel.active > *:nth-child(3) { animation-delay: calc(var(--tunda) * 2); }
.tab-panel.active > *:nth-child(4) { animation-delay: calc(var(--tunda) * 3); }
.tab-panel.active > *:nth-child(5) { animation-delay: calc(var(--tunda) * 4); }
/* kolom kanan pada tata letak dua kolom menyusul kolom kiri */
.tab-panel.active > .grid-2col > .card:nth-child(2) { animation-delay: calc(var(--tunda) * 1); }

/* Kartu angka dibuat ulang tiap render, jadi ikut beranimasi saat pindah
   bulan atau setelah mencatat transaksi — sekaligus menandakan data berubah. */
.tile { animation: muncul var(--dur-masuk) var(--ease) both; }
.tiles .tile:nth-child(2) { animation-delay: calc(var(--tunda) * 1); }
.tiles .tile:nth-child(3) { animation-delay: calc(var(--tunda) * 2); }
.tiles .tile:nth-child(4) { animation-delay: calc(var(--tunda) * 3); }

/* Daftar harian dikelompokkan per tanggal; yang dianimasikan cukup
   kelompoknya, bukan tiap baris — daftar panjang tetap terasa ringan. */
.day-group { animation: muncul var(--dur-masuk) var(--ease) both; }
.day-group:nth-child(2) { animation-delay: calc(var(--tunda) * 1); }
.day-group:nth-child(3) { animation-delay: calc(var(--tunda) * 2); }

/* ---------------- Grafik ---------------- */
.hbar-fill {
  transform-origin: left center;
  animation: tumbuh-kanan var(--dur-layar) var(--ease) both;
}
.hbar-row:nth-child(2) .hbar-fill { animation-delay: calc(var(--tunda) * 1); }
.hbar-row:nth-child(3) .hbar-fill { animation-delay: calc(var(--tunda) * 2); }
.hbar-row:nth-child(4) .hbar-fill { animation-delay: calc(var(--tunda) * 3); }
.hbar-row:nth-child(5) .hbar-fill { animation-delay: calc(var(--tunda) * 4); }

.meter-fill { transition: width var(--dur-layar) var(--ease); }

.colchart { animation: muncul-halus var(--dur-layar) var(--ease) both; }

/* ---------------- Interaksi ---------------- */
.btn, .nav-btn, .icon-btn, .btn-mini, .subtab-btn, .chip-x, .acct-btn {
  transition:
    background-color var(--dur-mikro) var(--ease),
    border-color var(--dur-mikro) var(--ease),
    color var(--dur-mikro) var(--ease),
    box-shadow var(--dur-mikro) var(--ease),
    transform var(--dur-mikro) var(--ease);
}
.btn:hover, .nav-btn:hover, .acct-btn:hover { transform: translateY(-1px); }
/* umpan balik tekan: tombol turun sedikit, jadi terasa "ditekan" */
.btn:active, .nav-btn:active, .btn-mini:active, .acct-btn:active,
.subtab-btn:active, .icon-btn:active {
  transform: translateY(1px);
  transition-duration: 40ms;
}
.btn-primary:hover { box-shadow: var(--shadow-naik); }

.tab-btn {
  transition:
    color var(--dur-mikro) var(--ease),
    /* di ponsel tab aktif berupa gelembung berisi, jadi latarnya ikut
       beralih - tanpa ini gelembungnya melompat muncul */
    background-color var(--dur-mikro) var(--ease),
    border-bottom-color var(--dur-masuk) var(--ease);
}

/* Baris transaksi punya tombol ubah/hapus — sorotan membantu menunjukkan
   baris mana yang sedang dituju. */
.entry, .row-link { transition: background-color var(--dur-mikro) var(--ease); }
.entry:hover, .row-link:hover {
  background: rgba(11, 11, 11, .035);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
}

/* input:focus mengubah border-color — transisinya membuat fokus terasa
   mengalir alih-alih berkedip */
input, select, textarea { transition: border-color var(--dur-mikro) var(--ease); }

/* ---------------- Gerbang akun & panel akun ---------------- */
.auth-gate:not([hidden]) { animation: muncul-halus var(--dur-layar) var(--ease) both; }
.auth-gate:not([hidden]) .auth-gate-brand {
  animation: muncul var(--dur-masuk) var(--ease) both;
}
.auth-gate:not([hidden]) .auth-gate-card {
  animation: muncul-skala var(--dur-layar) var(--ease) both;
  animation-delay: calc(var(--tunda) * 1.5);
}

/* pindah antar tab Masuk/Daftar/Lupa */
.auth-pane:not([hidden]) { animation: muncul var(--dur-masuk) var(--ease) both; }

/* Kode pemulihan hanya tampil sekali — denyut singkat menariknya ke mata,
   berhenti sendiri setelah dua kali supaya tidak berkedip terus. */
.recovery-box:not([hidden]) {
  animation:
    muncul-skala var(--dur-layar) var(--ease) both,
    denyut-perhatian 1.4s var(--ease) 2;
}

#auth-change-box[open] > form { animation: muncul var(--dur-masuk) var(--ease) both; }

/* ---------------- Pemberitahuan ---------------- */
.save-error:not([hidden]) { animation: turun-masuk var(--dur-layar) var(--ease) both; }

/* ---------------- Hormati preferensi sistem ----------------
 * Sebagian orang mual atau terganggu oleh gerakan (vestibular disorder),
 * dan mereka sudah menyatakannya lewat pengaturan sistem. Semua animasi di
 * atas dimatikan — bukan dipercepat setengah — karena maksud pengaturan itu
 * memang menghilangkan gerakan, bukan menguranginya sedikit. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  /* transform sisa dari efek hover/tekan ikut dinetralkan */
  .tautan-kembali:hover, .tautan-kembali:active { transform: none; }
  .btn:hover, .nav-btn:hover, .acct-btn:hover,
  .btn:active, .nav-btn:active, .btn-mini:active, .acct-btn:active,
  .subtab-btn:active, .icon-btn:active { transform: none; }
  /* Goyang "PIN salah" seluruhnya berupa transform, jadi memendekkan
   * durasinya saja tidak cukup - harus benar-benar dimatikan. Penolakan
   * tetap terbaca lewat warna titik dan teks statusnya. */
  .kunci-tap.salah { animation: none; transform: none; }
  .kunci-titik.sibuk i { animation: none; opacity: 1; }
}

/* ============================================================
 * Layar sentuh & ponsel
 *
 * Blok ini sengaja diletakkan PALING BAWAH. Isinya sebagian besar menimpa
 * aturan di atas dengan selektor yang sama persis, jadi urutannya — bukan
 * bobot selektornya — yang menentukan siapa yang menang. Memindahkannya ke
 * atas akan membuat seluruh bagian ini tidak berpengaruh.
 * ============================================================ */

/* ---------------- Tanpa penundaan & tanpa kotak sorot ----------------
 * touch-action: manipulation mematikan tunggu-ketukan-ganda 300 ms yang masih
 * dipakai Safari iOS: tanpa ini setiap tombol terasa lambat merespons.
 * Kotak sorot biru bawaan iOS dihapus karena bentuknya persegi — pada tombol
 * bersudut membulat dan tombol ikon bundar, kotak itu terlihat seperti cacat
 * render. Gantinya ada umpan balik :active di bawah, jadi ketukan tetap
 * terlihat direspons. */
button,
.btn,
.tautan-kembali,
summary {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* ---------------- Perangkat tanpa tetikus ----------------
 * Di layar sentuh, :hover TIDAK hilang setelah jari diangkat — peramban
 * menahannya sampai ada ketukan di tempat lain. Akibatnya tombol yang baru
 * ditekan tetap tampak terangkat dan berwarna aksen, seolah-olah masih aktif
 * atau sedang memuat, dan baris transaksi yang disentuh tetap tersorot.
 * Semua efek hover di atas dikembalikan ke nilai dasarnya di sini. */
@media (hover: none) {
  .btn:hover { border-color: var(--tepi-isian); color: var(--ink); }
  .btn-primary:hover {
    border-color: var(--accent);
    color: var(--accent-ink);
    filter: none;
    box-shadow: none;
  }
  .btn-danger:hover { background: var(--surface); border-color: var(--danger); color: var(--danger); }
  .nav-btn:hover { border-color: var(--border); color: var(--ink); }
  .acct-btn:hover { border-color: var(--border); }
  /* Menu profil: hover dinetralkan seperti tombol lain, tapi ketukan tetap
     diberi umpan balik lewat :active - tanpa itu, memilih dari menu terasa
     seperti tidak terjadi apa-apa sampai layarnya berpindah. */
  .acct-menu-item:hover { background: none; }
  .acct-menu-item:active { background: var(--grid); }
  .tab-btn:hover { color: var(--ink-2); }
  .tab-btn.active:hover { color: var(--accent); }
  .subtab-btn:hover { border-color: var(--border); color: var(--ink-2); }
  .subtab-btn.active:hover { border-color: var(--accent); color: var(--accent-ink); }
  .icon-btn:hover { background: none; }
  .btn-mini:hover { border-color: var(--border); }
  .chip-x:hover { color: var(--muted); background: none; }
  #auth-change-box summary:hover { color: var(--ink-2); }
  .tautan-kembali:hover { color: var(--ink-2); border-color: var(--border); transform: none; }
  .entry:hover { background: transparent; }
  .row-link:hover td { background: transparent; }
  .colchart .hover-zone:hover { fill: transparent; }
  /* efek "terangkat" ikut dinetralkan — lihat blok Interaksi di atas */
  .btn:hover, .nav-btn:hover, .acct-btn:hover { transform: none; }
}

/* ---------------- Ukuran sasaran sentuh ----------------
 * Ujung jari menutupi sekitar 9 mm layar; panduan Apple dan WCAG 2.5.5
 * sama-sama menyebut 44 px sebagai sasaran minimum. Ukuran dasar di berkas
 * ini dirancang untuk penunjuk tetikus (tombol ~36 px, tombol ikon ~22 px),
 * jadi di layar sentuh ukurannya dinaikkan — bukan diubah tampilannya. */
@media (pointer: coarse) {
  .btn {
    /* inline-flex, bukan sekadar min-height: pada <button> tinggi minimum
     * tidak menengahkan teksnya sendiri, dan label ikut menempel ke atas. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 10px 16px;
  }
  .nav-btn {
    min-height: 44px;
    min-width: 44px;
    padding: 8px 12px;
    font-size: 14px;
  }
  .tab-btn { min-height: 44px; padding: 11px 14px; }
  /* 44px, sama seperti tab utama: di layar masuk inilah satu-satunya
   * navigasi (Masuk / Daftar / Lupa Password / Kode Pemulihan). */
  .subtab-btn { min-height: 44px; padding: 11px 14px; }
  .acct-btn { min-height: 40px; }
  .tautan-kembali { min-height: 40px; }
  .btn-mini { min-height: 38px; padding: 8px 14px; }

  /* Tombol ✏️ dan 🗑️ tiap transaksi: dasarnya hanya ~22x22 px dan berjarak
   * 2 px. Di ponsel itu berarti satu ketukan meleset bisa MENGHAPUS transaksi
   * padahal maksudnya mengubah — jaraknya diperlebar, bukan hanya kotaknya. */
  .icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding: 0;
    font-size: 16px;
  }
  .entry-actions { gap: 8px; }

  .chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    min-height: 32px;
    padding: 0;
  }
  .chip { padding: 4px 4px 4px 12px; }

  /* Kolom isian disamakan dengan tombol supaya tinggi barisnya rata.
   * textarea punya tinggi sendiri (90 px) dan sengaja tidak disebut di sini. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]),
  select { min-height: 44px; }

  /* Umpan balik ketukan. Menggantikan kotak sorot bawaan yang dimatikan di
   * atas: tanpa salah satunya, tombol terasa tidak merespons di layar sentuh. */
  .btn:active { background: rgba(11, 11, 11, .07); background: color-mix(in srgb, var(--ink) 7%, var(--surface)); }
  .btn-primary:active { background: var(--accent); filter: brightness(.92); }
  .icon-btn:active, .nav-btn:active { background: var(--grid); }
}

/* ---------------- Bilah tab yang bisa digulir ----------------
 * Tujuh tab tidak muat di layar ponsel, jadi bilahnya digulir ke samping.
 * scroll-snap membuat gulirannya berhenti rapi di awal sebuah tab, bukan
 * memotong labelnya di tengah. Pasangannya ada di app.js: tab yang aktif
 * digulirkan ke dalam pandangan, sebab tab terakhir (Pengaturan) berada jauh
 * di luar layar dan tanpa itu pengguna tidak tahu tab itu ada. */
.tabs {
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scroll-padding-left: 12px;
}
.tab-btn { scroll-snap-align: start; }
@media (pointer: coarse) {
  /* Batang gulir di sini hanya menambah tinggi header; posisi tab yang
   * terpotong sudah cukup menandakan bilahnya bisa digeser. */
  .tabs { scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
}

/* ---------------- Peringatan gagal simpan ----------------
 * Dipindah ke BAWAH layar. Sebelumnya menempel di atas dengan z-index 50 —
 * lebih tinggi daripada header (20) — sehingga selama peringatan tampil
 * (dan ia bertahan sampai penyimpanan berhasil) bilah tab tertutup dan
 * aplikasi tidak bisa dinavigasi sama sekali di ponsel. */
@keyframes naik-masuk {
  from { opacity: 0; transform: translateY(100%); }
  to   { opacity: 1; transform: none; }
}
.save-error {
  position: fixed;
  top: auto;
  bottom: 0;
  left: 0;
  right: 0;
  /* env(): menghindari batang beranda iPhone yang menutupi baris terakhir */
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
}
.save-error:not([hidden]) { animation: naik-masuk var(--dur-layar) var(--ease) both; }

/* ============================================================
 * Tema putih-hitam & tata letak ponsel
 *
 * Diletakkan PALING AKHIR dengan sengaja: sebagian besar aturan di sini
 * menimpa aturan di atas dengan selektor yang sama, jadi urutan berkaslah
 * yang menentukan pemenangnya.
 * ============================================================ */

/* ---- Kartu: sudut besar, tanpa garis tepi, bayangan lembut ---- */
.card, .tile, .note-card, .card-strip {
  border: 0;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
h2 { font-size: 17px; }

/* ---- Judul halaman besar (hanya ponsel; di desktop bilah tab sudah cukup) ---- */
.judul-halaman { display: none; }

/* ---- Tombol: pil ---- */
.btn, .nav-btn, .acct-btn, .subtab-btn { border-radius: 999px; }
.btn { padding: 9px 16px; }

/* ---- Pemilih segmen (tab akun): wadah abu, segmen aktif = pil putih ---- */
.subtabs {
  display: flex;
  gap: 0;
  padding: 4px;
  background: var(--grid);
  border-radius: 999px;
}
.subtab-btn {
  flex: 1 1 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  padding: 9px 10px;
  font-weight: 600;
  text-align: center;
}
.subtab-btn:hover { border-color: transparent; color: var(--ink); }
.subtab-btn.active {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
}

/* ---- Tombol aksi bundar di daftar: ubah = abu-abu, hapus = merah muda ---- */
.icon-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--grid);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
}
.icon-btn[data-act^="del"], .icon-btn[data-act="del-cat"], .icon-btn[data-act="drop-backup"] {
  background: rgba(200, 57, 47, .12);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}
.entry-actions { gap: 6px; }

/* ---- Baris daftar: nama tebal, keterangan di bawahnya, nominal di kanan ---- */
.entry { padding: 12px 0; border-bottom: 1px solid var(--grid); }
.entry-amount { font-size: 15px; }
.entry-side .pay-badge { border-radius: 999px; }
/* nominal pemasukan/tabungan hijau seperti "+Rp 1.8jt" di referensi */
#income-list .entry-amount, #saving-list .entry-amount { color: var(--teks-good); }

/* ---- Ringkasan Dashboard: kartu donat + hero + dua kartu angka ---- */
.ringkas { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }
/* grid-column penuh: di desktop .ringkas dua kolom, dan tanpa ini dua kartu
 * angka cuma mengisi separuh kiri, menyisakan lubang di kanan. */
.ringkas-kecil { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ringkas-kecil .tile-value { font-size: 20px; }
.ringkas-kecil .tile-value .badge { font-size: 13px; }

.kartu-donat {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px;
}
.donat { flex: none; width: 96px; height: 96px; }
.donat-teks { min-width: 0; }
.donat-angka { font-size: 26px; font-weight: 800; line-height: 1.15; }
.donat-sub { color: var(--ink-2); font-size: 13.5px; margin-top: 2px; }
.donat-legenda { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font-size: 12.5px; color: var(--ink-2); }
.donat-legenda i {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 6px; vertical-align: 1px;
}
.donat-legenda i.l-a { background: var(--series-1); }
.donat-legenda i.l-b { background: var(--isi-good); }

/* Hero: gradasi grafit-hitam, teks putih. Satu-satunya kartu bernada penuh
 * di halaman, supaya "tabungan terkumpul" langsung tertangkap mata. Nilainya
 * ditulis langsung (bukan token) karena kartu ini sengaja TIDAK ikut berganti
 * antara mode terang dan gelap - ia jangkar visual halaman. */
.kartu-hero {
  position: relative;
  padding: 18px;
  border-radius: var(--radius);
  color: #ffffff;
  background: linear-gradient(135deg, #3d3d3a 0%, #101010 100%);
  box-shadow: var(--shadow-naik);
}
@media (prefers-color-scheme: dark) {
  /* Di mode gelap gradasi yang sama nyaris menyatu dengan halaman; garis tepi
   * tipis mengembalikan batas kartunya. */
  .kartu-hero { border: 1px solid var(--border); }
}
.kartu-hero .hero-label { font-size: 13.5px; opacity: .9; }
.kartu-hero .hero-angka { font-size: 30px; font-weight: 800; letter-spacing: -.01em; margin: 2px 0 12px; }
.kartu-hero .hero-aksi {
  position: absolute; top: 16px; right: 16px;
  border: 0; border-radius: 999px; padding: 8px 14px;
  background: rgba(255, 255, 255, .22); color: #fff; font: inherit; font-size: 13px; cursor: pointer;
}
.kartu-hero .hero-aksi:hover { background: rgba(255, 255, 255, .32); }
.kartu-hero .hero-meter { height: 9px; border-radius: 999px; background: rgba(255, 255, 255, .28); overflow: hidden; }
.kartu-hero .hero-isi { height: 100%; border-radius: 999px; background: #fff; transition: width var(--dur-layar) var(--ease); }
.kartu-hero .hero-kaki { display: flex; justify-content: space-between; gap: 8px; margin-top: 8px; font-size: 13px; opacity: .95; }

/* ---- "+ Catat": formulir dilipat di ponsel, dibuka lewat pil ---- */
.pil-catat {
  border: 0; border-radius: 999px; padding: 8px 14px;
  background: var(--accent); color: var(--accent-ink);
  font: inherit; font-size: 13.5px; font-weight: 700; cursor: pointer;
  display: none;   /* hanya ponsel */
}

/* ---- Tabel alokasi Dashboard: di ponsel jadi kartu kategori ---- */
@media (max-width: 640px) {
  #dash-alloc thead { display: none; }
  #dash-alloc .table-wrap { overflow-x: visible; }
  #dash-alloc table, #dash-alloc tbody { display: block; width: 100%; }
  /* Tiap baris jadi satu kartu kategori: nama + status di baris atas, lalu
   * TIGA kolom angka (Target / Realisasi / Selisih) di baris bawah.
   *
   * Sebelumnya ketiga sel angka itu dilempar ke SATU grid-area yang sama
   * ("rincian") dengan harapan mengalir sebagai satu kalimat. Itu tidak
   * pernah bisa terjadi: anak grid selalu di-blokkan, jadi display:inline
   * diabaikan dan ketiganya menumpuk persis di petak yang sama - angkanya
   * saling menimpa dan tidak terbaca. Sekarang masing-masing punya petaknya
   * sendiri, dengan labelnya sebagai baris kecil di atas angka. */
  #dash-alloc tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "nama   nama      status"
      "target realisasi selisih";
    align-items: start;
    gap: 8px 10px;
    padding: 12px 0;
    border-bottom: 1px solid var(--grid);
  }
  #dash-alloc td {
    display: block;
    padding: 0;
    border: 0;
    text-align: left;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  #dash-alloc td[data-label="Kategori"] { grid-area: nama; font-weight: 700; font-size: 15px; }
  #dash-alloc td[data-label="Status"] { grid-area: status; justify-self: end; align-self: center; }
  #dash-alloc td[data-label="Target %"] { display: none; }
  #dash-alloc td[data-label="Target Rp"]  { grid-area: target; }
  #dash-alloc td[data-label="Realisasi"]  { grid-area: realisasi; }
  #dash-alloc td[data-label="Selisih"]    { grid-area: selisih; }
  #dash-alloc td[data-label="Target Rp"],
  #dash-alloc td[data-label="Realisasi"],
  #dash-alloc td[data-label="Selisih"] {
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.3;
  }
  /* Label ditulis lewat ::before supaya angka tetap bisa dibaca sendirian
   * tanpa kepala tabel (thead disembunyikan di lebar ini). */
  #dash-alloc td[data-label="Target Rp"]::before,
  #dash-alloc td[data-label="Realisasi"]::before,
  #dash-alloc td[data-label="Selisih"]::before {
    display: block;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
  }
  #dash-alloc td[data-label="Target Rp"]::before  { content: "Target"; }
  #dash-alloc td[data-label="Realisasi"]::before  { content: "Realisasi"; }
  #dash-alloc td[data-label="Selisih"]::before    { content: "Selisih"; }
  #dash-alloc .badge { white-space: normal; line-height: 1.25; }
  #dash-alloc tr:last-child { border-bottom: 0; }
  #dash-alloc tr.row-total td[data-label="Kategori"] { letter-spacing: .04em; }
}

/* ============================================================
 * Ponsel (<= 640px)
 * ============================================================ */
@media (max-width: 640px) {
  body { background: var(--page); }

  /* Header ramping: hanya navigasi bulan + profil. Merek disembunyikan -
   * judul halaman besar di bawahnya sudah menyebut konteksnya. */
  .app-header { padding: 8px 12px 10px; box-shadow: none; border-bottom: 0; background: var(--page); }
  .brand { display: none; }
  .header-inner { gap: 8px; }
  /* Tombol profil berdiri sendiri di baris PERTAMA dan ditengahkan; navigasi
   * bulan turun ke baris di bawahnya. Keduanya flex-basis 100% supaya masing-
   * masing memaksa barisnya sendiri, dan order dipakai karena di HTML
   * urutannya justru terbalik (navigasi bulan lebih dulu). */
  .acct-nav { order: 0; flex: 1 0 100%; justify-content: center; }
  .month-nav { order: 1; flex: 1 0 100%; justify-content: center; }
  /* Menu profil ikut ditengahkan terhadap tombolnya. left+margin, BUKAN
   * translateX: keyframe menu-muncul memakai transform, jadi transform di
   * sini akan tertimpa begitu animasinya berjalan. */
  .acct-menu { left: 50%; right: auto; margin-left: -95px; transform-origin: top center; }
  .month-label { min-width: 0; font-size: 14px; }
  .today-btn { padding: 6px 10px; }

  .judul-halaman {
    display: block;
    margin: 6px 0 14px;
    font-size: 30px;
    font-weight: 800;
    letter-spacing: -.01em;
  }

  /* ---- Bilah tab: "bubble" mengambang ala iOS ----
   * Bukan bilah selebar layar yang menempel di tepi bawah, melainkan panel
   * membulat yang melayang dengan jarak dari ketiga tepi. Keenam tab dibagi
   * DUA BARIS berisi tiga: grid tiga kolom + enam tombol mengalir sendiri
   * menjadi 3 atas / 3 bawah, tanpa markup tambahan di index.html.
   *
   * Padding bawah .app-main dihitung dari tinggi bubble (dua baris ~54px +
   * jarak + bantalan) supaya baris terakhir daftar tidak tertutup panel. */
  .app-main { padding: 12px 12px 150px; }
  .tabs {
    position: fixed;
    left: 12px; right: 12px;
    bottom: calc(10px + env(safe-area-inset-bottom));
    z-index: 30;
    margin: 0;
    max-width: none;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 4px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: 28px;
    /* Latar semi-tembus + blur: kaca ala iOS. Warna cadangan ditulis lebih
     * dulu untuk peramban tanpa color-mix; tanpa blur pun tetap terbaca
     * karena kepekatannya sudah 88%. */
    background: var(--surface);
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    backdrop-filter: blur(20px) saturate(180%);
    box-shadow: 0 12px 30px rgba(11, 11, 11, .16), 0 2px 8px rgba(11, 11, 11, .08);
    overflow: visible;
  }
  .tab-btn {
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 9px 2px;
    border: 0;
    border-bottom: 0;
    border-radius: 20px;
    font-size: 10.5px;
    color: var(--ink-2);
    white-space: nowrap;
  }
  .tab-btn .ikon-tab { width: 22px; height: 22px; }
  /* Tab aktif = gelembung berisi, bukan sekadar warna teks: pada panel kaca
   * perbedaan warna teks saja terlalu tipis untuk terbaca sekilas.
   *
   * :hover DAN :focus ikut ditulis, dan itu bukan pengulangan yang sia-sia.
   * Di blok @media (hover: none) ada .tab-btn.active:hover { color: accent }
   * - aturan untuk bilah bergaris-bawah versi lama. Bobot selektornya lebih
   * tinggi daripada .tab-btn.active polos, jadi ia tetap menang di sini. Dan
   * peramban ponsel MENAHAN keadaan :hover pada tombol yang baru diketuk:
   * begitu satu tab dipilih, warnanya jadi biru di atas gelembung biru dan
   * ikon SVG-nya - yang memakai stroke: currentColor - lenyap sama sekali. */
  .tab-btn.active,
  .tab-btn.active:hover,
  .tab-btn.active:focus,
  .tab-btn.active:focus-visible {
    color: var(--accent-ink);
    background: var(--accent);
    border-bottom: 0;
    font-weight: 700;
    box-shadow: 0 4px 12px rgba(11, 11, 11, .18);
  }
  @media (prefers-color-scheme: dark) {
    .tabs {
      box-shadow: 0 12px 30px rgba(0, 0, 0, .55), 0 2px 8px rgba(0, 0, 0, .4);
      border-color: rgba(255, 255, 255, .14);
    }
    .tab-btn.active { box-shadow: 0 4px 14px rgba(0, 0, 0, .5); }
  }
  /* Gerbang & layar kunci (z 100) tetap di atas bilah ini (z 30). */

  .ringkas { grid-template-columns: 1fr; }
  /* Angka-angka lama sudah ada di donat, hero, dan dua kartu angka. */
  #dash-tiles, #dash-subtiles { display: none; }
  .kartu-donat { padding: 16px; }
  .donat { width: 88px; height: 88px; }

  /* Formulir catat dilipat: daftar dulu, formulir saat diminta. */
  .pil-catat { display: inline-block; }
  .kartu-form:not(.terbuka) { display: none; }
  .list-head { align-items: center; }

  /* Pemilih segmen memenuhi lebar */
  .subtabs { width: 100%; }
}

/* Desktop: ikon tab kecil di kiri label.
 *
 * Dibungkus min-width, BUKAN dibiarkan polos. Aturan ini berada di bawah blok
 * ponsel, dan bobot selektornya sama - tanpa pembungkus, ia menang di ponsel
 * juga: ikon 22px bilah bubble menyusut jadi 17px dan display kembali
 * inline-flex. */
@media (min-width: 641px) {
  .tab-btn { display: inline-flex; align-items: center; gap: 7px; }
  .tab-btn .ikon-tab { width: 17px; height: 17px; flex: none; }
}
.ikon-tab { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ---- Donat: track tipis, busur berwarna nada status, angka di tengah ---- */
.donat-track { fill: none; stroke: var(--grid); stroke-width: 12; }
.donat-busur { fill: none; stroke: var(--series-1); stroke-width: 12; stroke-linecap: round; }
.donat-busur.fill-good    { stroke: var(--isi-good); }
.donat-busur.fill-warn    { stroke: var(--isi-warning); }
.donat-busur.fill-serious { stroke: var(--isi-serious); }
.donat-busur.fill-crit    { stroke: var(--isi-critical); }
.donat-label { fill: var(--accent); font-size: 20px; font-weight: 800; }
