/* ==========================================================================
   FIGTREE — DISAJIKAN DARI SERVER KAMI SENDIRI, BUKAN DARI GOOGLE
   --------------------------------------------------------------------------
   Sampai 24 Agustus 2026 index.html, __demo.html, dan __demo_login.html memuat
   huruf ini dari fonts.googleapis.com. Tiga akibatnya, dan yang ketiga yang
   membuatnya bukan sekadar soal kecepatan:

     1  HURUFNYA BERKEDIP. `display=swap` berarti tulisan digambar dulu dengan
        huruf sistem, lalu MELOMPAT ke Figtree begitu berkasnya tiba. Orang yang
        pertama kali membuka halaman kita melihat halaman itu berubah bentuk di
        depan matanya.
     2  DI JARINGAN YANG MEMBLOKIR ATAU LAMBAT, Figtree tidak pernah tiba, dan
        etalase kita tampil dengan huruf bawaan sistem — bukan huruf yang kita
        pilih. Diukur di kontainer ini: permintaannya GAGAL, dan halamannya tetap
        tampil, dengan huruf yang bukan milik kita.
     3  TIAP PENGUNJUNG — termasuk yang belum pernah mendaftar dan belum
        menyetujui apa pun — mengirimkan alamat IP dan identitas perambannya ke
        server pihak ketiga sebelum satu huruf pun tergambar. Kebijakan Privasi
        kami menyebutkan pihak ketiga yang kami pakai satu per satu, lalu
        berbunyi "Kami tidak memberikan datamu kepada pihak lain selain di atas."
        Google Fonts tidak ada di daftar itu.

          >> Sebuah kalimat kebijakan yang dibantah oleh satu baris <link> di
             halaman depan bukan kebijakan yang perlu ditulis ulang; ia baris
             <link> yang perlu dihapus.

   Berkasnya SIL Open Font License 1.1 (assets/fonts/LICENSE-Figtree-OFL.txt),
   yang memang dibuat untuk ini. Delapan berkas, 70 KB seluruhnya — lebih kecil
   daripada satu foto profil, dan sekali diunduh ia tinggal di cache.

   `font-display: swap` DIPERTAHANKAN dengan sengaja: berkasnya sekarang datang
   dari host yang sama dengan halamannya, jadi jendela kedipnya tinggal beberapa
   milidetik — tapi kalau suatu hari ia lambat, tulisan tetap terbaca. Teks yang
   tak terlihat selama menunggu huruf lebih buruk daripada teks berhuruf lain.
   ========================================================================== */
@font-face{font-family:"Figtree";font-style:normal;font-weight:400;font-display:swap;src:url("../fonts/figtree-latin-ext-400.woff2") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Figtree";font-style:normal;font-weight:400;font-display:swap;src:url("../fonts/figtree-latin-400.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Figtree";font-style:normal;font-weight:500;font-display:swap;src:url("../fonts/figtree-latin-ext-500.woff2") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Figtree";font-style:normal;font-weight:500;font-display:swap;src:url("../fonts/figtree-latin-500.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Figtree";font-style:normal;font-weight:600;font-display:swap;src:url("../fonts/figtree-latin-ext-600.woff2") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Figtree";font-style:normal;font-weight:600;font-display:swap;src:url("../fonts/figtree-latin-600.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Figtree";font-style:normal;font-weight:700;font-display:swap;src:url("../fonts/figtree-latin-ext-700.woff2") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Figtree";font-style:normal;font-weight:700;font-display:swap;src:url("../fonts/figtree-latin-700.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}

/* ============================================================
   Spotious V2 — application styles
   Layout: constant top title bar + contextual bottom taskbar,
   with a middle stage that is a sliding stack on mobile and a
   3-pane layout on desktop.
   ============================================================ */

:root{
  /* Merah Laravel, disetel supaya bisa dipakai.
     #F05340 apa adanya cuma memberi kontras 3.5 untuk tulisan putih di atasnya —
     di bawah 4.5 yang dibutuhkan teks ukuran biasa, dan tombol utama adalah tombol
     yang paling sering ditekan di seluruh aplikasi.

     Sempat dipakai #D63E27 (4.6), tapi itu mepet ke ambangnya. #C93A22 memberi 5.1:
     ruang amannya lebih lega, warnanya lebih pekat, dan masih jelas merah Laravel. */
  --accent:#C93A22; --accent-weak:#fff1ee; --accent-text:#b8300f;
  --bg:#f5f5f7; --surface:#ffffff; --surface-2:#fafafa;
  /* Dua kelabu sekunder, DIUKUR bukan dikira-kira (20 Jul 2026).
     Sebelumnya --faint #a1a1aa hanya 2.56:1 di atas putih — jauh di bawah 4.5 yang
     dituntut untuk teks biasa, dan ia justru dipakai untuk yang paling kecil: jam di
     daftar chat, label bagian, keterangan medan. Di layar HP di bawah matahari itu
     praktis hilang.
     Keduanya digeser bersama supaya JENJANGNYA tetap terbaca — kalau hanya faint yang
     dinaikkan, ia jadi 4.49 sementara muted 4.83 dan bedanya lenyap.
     22 Jul 2026: --faint digelapkan lagi #767680 -> #6b6b74. Yang lama duduk PERSIS di
     4.49 — di garis 4.5 tanpa margin sama sekali; audit demo menandainya berkali-kali.
     Sekarang 5.28, jenjang ke --muted (6.32) tetap terjaga.
       --muted 4.83 -> 6.32   --faint 2.56 -> 5.28  (di atas putih) */
  --text:#18181b; --muted:#5f5f68; --faint:#6b6b74; --line:#e8e8ec;
  /* Crimson, bukan merah. Begitu aksennya jadi merah, warna bahaya HARUS pindah rona:
     var(--danger) yang lama cuma berjarak 29 dari aksen baru, dan tombol "Sign in" akan
     tampak sama dengan tombol "Hapus workspace". Itu bukan soal selera. */
  --good:#16a34a; --good-text:#177245; --danger:#9f1239; --radius:12px;
  /* BUTIR 130 — sapuan hover NETRAL. Ia dipakai di lima tempat sejak lama, dan selama itu
     ia TIDAK PERNAH punya definisi: empat di antaranya memanggilnya tanpa cadangan, jadi
     `background` jatuh ke nilai awal — transparan. Tiga hover tanpa umpan balik sama sekali,
     dan satu kotak yang seharusnya bernada lembut tampil polos.
     Nilainya bukan tebakan: `.media-file:hover` sudah menulis cadangan rgba(127,127,127,.08)
     bertahun rilis, jadi itulah nada yang memang dimaksud. Abu netral, bukan turunan aksen —
     hover adalah "kursornya di sini", bukan "ini penting".
     Mode gelap butuh sapuan yang lebih terang, bukan lebih pekat: di atas #17171c, abu 8%
     praktis tak terlihat, dan sapuan yang tak terlihat sama saja dengan tidak ada. */
  --hover:rgba(127,127,127,.09);
  /* #398 — Merah untuk BIDANG TERISI yang memuat tulisan putih (lencana, toast galat, tombol
     bahaya saat hover). Dipisah dari --danger karena keduanya punya tugas berbeda: --danger dipakai
     sebagai WARNA TULISAN di atas latar, jadi di mode gelap ia sengaja dibuat muda (#ef5f8b) agar
     terbaca. Warna muda itu lalu ikut terpakai sebagai LATAR di bawah tulisan putih — dan di situ
     rasionya jatuh (3,1:1). Token ini tetap pekat di kedua mode supaya putih di atasnya aman. */
  --danger-solid:#9f1239;
  /* Tangga tenggat, dipisah dari merah.
     Setelah aksen jadi merah, penanda "Overdue" yang juga merah membuat layar penuh
     warna panas yang semuanya berteriak sama kerasnya. Sekarang tenggat memakai amber
     dan naik bertingkat: makin telat makin pekat. Keduanya lolos 4.5 di latar terang —
     yang lama (#e8811a) sebenarnya cuma 2.77 dan sudah gagal sejak dulu. */
  --due-late:#7c2d12; --due-today:#b45309;
  --prio-med:#1d5fb0; --prio-low:#5f6470;
  --topbar-h:56px;
  /* Safe areas. index.html ships viewport-fit=cover and the manifest is
     display:standalone, so once installed the page draws edge to edge —
     under the status bar at the top and the home indicator at the bottom.
     Every bar that touches an edge must pad itself by these, or its content
     sits underneath the system UI. In a normal browser tab they resolve to 0,
     so these are free on desktop. */
  --sat:env(safe-area-inset-top,0px);
  --sab:env(safe-area-inset-bottom,0px);
  /* Real on-screen height of the title bar, insets included. Anything
     positioned relative to the title bar must use this, not --topbar-h. */
  --topbar-total:calc(var(--topbar-h) + var(--sat));
  /* TUMPUKAN MENGAMBANG BAWAH — satu patokan untuk pita lingkungan, toast, dan bilah pembaruan.
     Ketiganya dulu position:fixed dengan left:50%, yaitu dipatok ke TENGAH VIEWPORT. Padahal
     tata letak aplikasi ini membagi layar jadi kolom-kolom seukuran, jadi tengah viewport bukan
     ruang kosong — itu kolom yang kebetulan melintasi tengah, lengkap dengan .col-foot-nya.
     Karena itu CEO melihat bilah versi menimpa "+ Acara baru" di 1440px.
     --float-b menaruh dasar tumpukan tepat DI ATAS .col-foot (min-height-nya topbar-h + sab),
     jadi tak ada yang bisa menutupi tombol aksi di lebar mana pun. */
  --float-b:calc(var(--topbar-h) + var(--sab) + 12px);
  --envrib-h:0px;   /* jadi 29px hanya di Preview/Demo (lihat :root.has-envrib) */
  /* RUANG YANG DISISAKAN TUMPUKAN MENGAMBANG untuk isi kolom di bawahnya. Ditulis JS
     (terbitkanRuangMelayang) dari kotak yang DIUKUR, tidak pernah oleh tangan di sini:
     berapa besarnya bergantung bilah mana yang sedang hidup, dan itu keadaan jalan.
     0px = tidak ada yang menetap di atas kolom mana pun. Lihat butir 174 + B-5. */
  --float-stack-h:0px;
  /* TEPI TEKS KOLOM BERANDA — satu tempat, dan semua yang harus mendarat di garis ini
     MENURUNKANNYA dari sini alih-alih menyalin angkanya.
     Ditemukan tests/rig_v360_tepi.mjs saat menyapu butir 195: judul bagian dan tiap baris
     menu duduk di 74px, sementara isi .today-card duduk di 77px — kartunya menghitung
     14 + 1 + 6 sendiri, dan 21 bukan 18. Tiga piksel: terlalu kecil untuk terbaca sebagai
     maksud, terlalu besar untuk tak terlihat. Bentuknya sama persis dengan butir 195. */
  --col-teks:var(--sp-x18);
  /* Lebar rel ikon paling kiri — SATU-SATUNYA tempat angka ini boleh ditulis.
   *
   * BUTIR 143. Komentar lama di sini sudah berbunyi "ditulis sekali", dan itu TIDAK BENAR:
   * variabelnya dibuat, tak satu pun pemakainya diubah. Diukur 9 Agu 2026 — angka 64 hidup di
   * LIMA tempat sekaligus (ditulis TANPA bentuk aturannya dengan sengaja; gerbang rilis
   * membaca berkas ini dengan grep, dan keterangan yang berbentuk kode akan ia baca sebagai
   * kode — komentar tidak boleh bisa memenangkan ATAU mengalahkan gerbang):
   *
   *     app.css  baris  81   deklarasi var         <- sumber yang diklaim tunggal
   *     app.css  baris 298   aturan co-rail        <- angka telanjang di flex-basis
   *     app.css  baris 980   aturan col-strip      <- angka telanjang, DAN aturannya mati
   *     app.css  baris 993   aturan m-rail         <- angka telanjang di width
   *     app.js   baris 3977  innerWidth dikurangi  <- angka telanjang, dan INI yang menentukan
   *                                                   berapa kolom muat di desktopStage()
   *
   * Akibatnya mengubah baris ini saja menghasilkan NOL perubahan di layar, dan melupakan
   * app.js membuat ambang kaskade (2 x MINW + rel) salah tanpa suara. Sekarang keempat
   * pemakainya membaca var ini, aturan matinya dihapus, dan app.js membacanya sekali lewat
   * railW(). Gerbang `bin/cek_rel_butir143.php` merah kalau angka telanjang lahir lagi.
   *
   * KENAPA 56, BUKAN 64. Di layar 320px rel 64 menyisakan kolom 256px, sementara baris kepala
   * Tugas butuh 287px (Indonesia) — panenya jadi bisa digeser ke samping. 56 adalah "sedikit
   * saja" terjauh yang boleh diambil: rel harus tetap memuat sasaran sentuh 44px (.co-home,
   * .co-add, .m-mini) dengan sisa 6px tiap sisi. 48 memuatnya mepet tanpa sisa.
   * 56 saja TIDAK cukup menyelesaikan 320px — baris kepala Tugas ikut diperbaiki; lihat
   * .panel-head-split. */
  --rail-w:56px;
  /* Liquid glass — translucent frosted surfaces for floating panels. */
  --glass:rgba(255,255,255,.68); --glass-strong:rgba(255,255,255,.82);
  --glass-brd:rgba(255,255,255,.55); --glass-blur:saturate(175%) blur(20px); --glass-hi:rgba(255,255,255,.6);
  /* ======================= SKALA JARAK (v360) =======================
     Empat belas langkah, dipilih dari yang paling sering dipakai di berkas ini —
     bukan dari deret matematis. Deret yang cantik meleset dari yang dipakai orang.

     Dipakai untuk jarak saja: margin, padding, gap dan longhand-nya. TIDAK untuk
     top/right/bottom/left/inset — angka di situ menjawab "di mana", bukan "berapa
     lega", dan menyeretnya ke skala akan memindahkan barang, bukan merapikannya.
     Nilai negatif dan di atas 48px juga tetap angka: itu penempatan. */
  --sp-1:1px; --sp-2:2px; --sp-4:4px; --sp-6:6px; --sp-8:8px; --sp-10:10px; --sp-12:12px; --sp-14:14px; --sp-16:16px; --sp-20:20px; --sp-24:24px; --sp-32:32px; --sp-40:40px; --sp-48:48px;

  /* TINGKAT KEDUA — dan kenapa ia harus ada.

     Percobaan pertama menjepret semua ke langkah terdekat: 9 jadi 8, 11 jadi 12.
     Semua gerbang hijau, semua uji lulus, nol kegagalan struktural. Lalu
     tests/rig_v360_jarak.mjs menimbangnya: 2.447 dari 2.616 elemen bergeser,
     yang terbesar 19px, di baris ketujuh menu Akun — tiap baris menyusut 2-3px
     dan tujuh baris menumpuk. Semua angka yang biasa dipercaya berkata "aman",
     dan produknya tetap rusak. Dengan tingkat kedua ini: NOL bergeser.

     Jadi ini bukan kompromi. Ini pengakuan bahwa 639 tempat itu dipilih orang
     satu per satu, dan rilis kerapian tidak berhak menimpanya diam-diam. */
  --sp-x3:3px; --sp-x5:5px; --sp-x7:7px; --sp-x9:9px; --sp-x11:11px; --sp-x13:13px; --sp-x15:15px; --sp-x17:17px; --sp-x18:18px; --sp-x22:22px; --sp-x26:26px; --sp-x28:28px; --sp-x30:30px; --sp-x34:34px; --sp-x36:36px; --sp-x37:37px; --sp-x38:38px; --sp-x41:41px; --sp-x42:42px; --sp-x44:44px;

  /* --- WARISAN: 637 tempat, 20 nilai. Angka ini hanya boleh mengecil. --- */
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overscroll-behavior:none}
body{font-family:"Figtree",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased;letter-spacing:-.006em}
h1,h2,h3,.brand,.lr-title{letter-spacing:-.02em}
#app{height:100vh;height:100dvh;height:var(--app-h,100dvh);overflow:hidden}
svg{display:block}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
input,textarea,select{font:inherit;color:inherit}
/* Sorotan teks punya tokennya SENDIRI, bukan --accent-weak. --accent-weak adalah warna "isian
   lembut" (avatar, chip, baris terpilih) dan memang dibuat nyaris tak terlihat — DIUKUR: 1,10:1
   di atas putih dan 1,15:1 di atas permukaan gelap. Untuk isian yang ditemani ikon atau teks itu
   pas; untuk SOROTAN, satu-satunya tugasnya justru terlihat, dan sorotan yang 1,1:1 sama saja
   dengan tidak ada. Di mode gelap ia juga terbaca sebagai merah marun kotor — itu keluhan CEO.
   DIUKUR (1 Agu 2026): terang #ffc4b3 = 1,52:1 di atas putih, teksnya 8,59:1 · gelap #7a3223 =
   1,96:1 di atas permukaan, teksnya 6,97:1. Pembanding: sorotan bawaan peramban ±1,35:1. */
:root{--sel-bg:#ffc4b3;--sel-fg:#5c1a08}
:root[data-theme="dark"]{--sel-bg:#7a3223;--sel-fg:#ffd9cf}
::selection{background:var(--sel-bg);color:var(--sel-fg)}
::-moz-selection{background:var(--sel-bg);color:var(--sel-fg)}

.avatar{border-radius:50%;background:var(--accent-weak);color:var(--accent-text);display:flex;align-items:center;justify-content:center;font-weight:600;flex-shrink:0}
.badge{background:var(--accent);color:#fff;border-radius:999px;font-size:11px;font-weight:600;min-width:18px;height:18px;display:flex;align-items:center;justify-content:center;padding:0 var(--sp-x5)}
.btn{display:inline-flex;align-items:center;gap:var(--sp-x7);border-radius:10px;padding:var(--sp-x9) var(--sp-14);font-size:14px;font-weight:500;border:1px solid var(--line);background:var(--surface);text-decoration:none;transition:background .12s,border-color .12s,transform .08s}
.btn:hover{background:var(--surface-2)}
.btn-primary{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 1px 2px rgba(201,58,34,.28)}
.btn-primary:hover{background:var(--accent-text)}
.btn-ghost{border-color:transparent}
.btn-soft{background:var(--surface-2);border-color:var(--line);color:var(--muted);font-weight:500}
.btn-soft:hover{background:var(--line);color:var(--text)}
.btn-danger{color:var(--danger);border-color:var(--line)}
.btn-danger:hover{background:#fef2f2}
.icon-btn{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--muted);position:relative}
.icon-btn:hover{background:var(--surface-2);color:var(--text)}
.btn:active,.icon-btn:active,.send:active,.chip:active,.list-row:active,.menu-row:active{transform:scale(.98)}

/* ---------- app shell ---------- */
.app{height:100%;display:flex;flex-direction:column}
/* TUGAS-168 (koreksi 16 Agu 2026) — PADDING MENDATAR DIBUANG.
   Bilah atas kini membawa nama tiap kolom, SEJAJAR dengan kolomnya. Padding 16px di sini
   akan menggeser tiap sel dari kolom yang ia namai — dan geseran itu tidak akan terlihat
   sebagai cacat, ia akan terlihat seperti "kira-kira di atasnya". Jaraknya dipindah ke dalam
   sel, tempat ia tidak mengubah garis kolomnya. */
.topbar{height:var(--topbar-total);flex-shrink:0;display:flex;align-items:center;padding:var(--sat) 0 0;border-bottom:1px solid var(--line);background:var(--surface);box-shadow:0 1px 2px rgba(0,0,0,.04);position:relative;z-index:5}
/* SATU SUMBER LEBAR, DIBACA DUA KALI — lihat tbKolom() di app.js.
   .tb-rail memakai --rail-w yang SAMA dengan .co-rail dan .m-rail; .tb-col memakai
   flex:1 1 0 yang SAMA dengan .col. Tidak ada angka kedua yang harus dijaga tetap sama. */
.tb-rail{flex:0 0 var(--rail-w);min-width:0;display:flex;align-items:center;justify-content:center;overflow:hidden}
/* v365 — BARISNYA DIGESER MENDATAR saat rantainya lebih panjang daripada layar. Itu sifat
   yang memang sudah ada di jejak lama, bukan aturan baru: tidak muat -> digeser. Nol susunan
   kedua, nol "…", nol aturan prioritas — semuanya karangan yang dibatalkan.
   `scrollbar-width:none` karena barisnya setinggi 32px: batang gulir di sana memakan
   sepertiga tingginya dan menutupi nama yang justru sedang dibaca. */
/* --gulir-sengaja:1 — DAN INI PENYATAAN YANG TERTINGGAL SEJAK KONVENSINYA LAHIR.
   Bilah jejak adalah penggulung yang PALING sengaja di seluruh aplikasi: seluruh keputusan
   pemilik tentang judul panjang ("kalau judul nya panjang gapapa, kan bisa di geser2")
   bersandar padanya. Ia tetap tidak pernah menyatakan dirinya, dan alat ukur melaporkannya
   MERAH pada hari pertama rantainya benar-benar melewati lebar bilah.
     >> Konvensi yang dipasang pada semua yang teringat saat itu akan melewatkan justru yang
        paling jelas — sebab yang paling jelas tidak terasa perlu didaftarkan.
   Dinyatakan DI ELEMENNYA, seperti .tb-chips dan .tv-bar, bukan di daftar nama kelas di
   dalam berkas ujinya. */
.tb-cols{--gulir-sengaja:1;flex:1;min-width:0;display:flex;align-items:center;height:100%;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;scroll-behavior:smooth}
.tb-cols::-webkit-scrollbar{display:none}
/* Ruas yang KOLOMNYA sudah turun dari kiri: lebar seisinya, bukan selebar kolom — kolomnya
   tidak ada lagi untuk disejajari. Ia berdiri di kiri, dan barisnya digeser untuk melihatnya. */
/* ==========================================================================
   RUAS YANG BISA DIKLIK HARUS TERLIHAT BISA DIKLIK — DAN BENTUKNYA DICARI DI JEJAK LAMA
   ==========================================================================
   Percobaan pertama v365 menukar <span> jadi <button> dan berhenti di situ. Di 2016px dan
   1426px tiap ruas sudah punya kolomnya sendiri, jadi "seluruh rantai" dan "nama tiap kolom"
   KEBETULAN himpunan yang sama — dan gambarnya keluar IDENTIK BIT PER BIT dengan v364.

     >> Kendali yang serupa bit-per-bit dengan keterangan adalah kendali yang tidak akan
        ditemukan siapa pun. Ia ada di kode, ia lulus tiap gerbang, ia tidak ada bagi orang
        yang memakainya.

   Dan itu membatalkan seluruh alasan pemilik memintanya kembali: "biar bisa jump kemana2".
   Melompat yang tidak terlihat bisa dilompati tidak membuat apa pun lebih gampang.

   BENTUKNYA TIDAK DIKARANG. Jejak lama punya penandanya, dan penanda itu MASIH ADA di berkas
   ini beberapa puluh baris di atas — `.crumb` + `button.crumb:hover`: warna muted saat diam,
   kursor penunjuk, dan pil membulat yang terisi --hover saat disorot. Pemilik mengenali bentuk
   itu sebagai jejak selama berbulan-bulan; kami yang menghapusnya, tanpa ada yang memutuskan.
   Yang dikembalikan bentuk ITU, bukan garis bawah karangan saya.

   TANPA TANDA '>': posisi kolomnya yang mengerjakan pekerjaan pemisah, dan tanda yang
   mengambang di tengah jarak kolom akan terbaca sebagai kesalahan.
   ========================================================================== */
/* inline-flex + nowrap: tanda pemisah adalah SAUDARA dari labelnya di dalam tombol yang sama,
   jadi tanpa ini ia membungkus ke baris kedua dan mendorong seluruh bilah atas jadi dua baris.
   Terlihat di gambar 400px, bukan di angka mana pun — label sudah nowrap, tombolnya belum. */
.tb-col-naik,.tb-col-lalu{background:none;border:0;font:inherit;text-align:left;cursor:pointer}
/* KEKHUSUSAN, BUKAN URUTAN — dan ini KEDUA KALINYA perangkap yang sama di berkas yang sama.
   `.tb-col{display:block}` hidup dua puluh baris di bawah dan berkekhususan SAMA, jadi ia
   menang: labelnya jadi blok, dan tanda pemisah membungkus ke baris kedua. Bilah atas jadi
   dua baris di 400px — terlihat di GAMBAR, bukan di satu angka pun, karena tiap angka yang
   kami ukur tetap benar. */
.tb-cols > .tb-col-naik,.tb-cols > .tb-col-lalu{display:inline-flex;align-items:center;white-space:nowrap}
.tb-col-naik > .tb-lbl,.tb-col-lalu > .tb-lbl{color:var(--muted);font-weight:600;transition:background .12s,color .12s}
.tb-col-naik:hover > .tb-lbl,.tb-col-lalu:hover > .tb-lbl{background:var(--hover);color:var(--text);border-radius:7px}
/* LEBAR SEISINYA, DINYATAKAN — dan `flex:0 0 auto` saja TIDAK cukup: ukuran dasar item flex
   yang blok tetap diselesaikan terhadap wadahnya, jadi ruas leluhur ikut selebar kolom. Terukur
   344px untuk kata "Tasks", dengan teksnya di ujung KIRI yang jauh di luar layar — sehingga
   secuil yang tersisa di kanan cuma padding kosong.

     >> Secuil yang memperlihatkan ruang kosong tidak memberi tahu apa pun. Yang memberi tahu
        ada jalur adalah HURUF.

   DAN SELEKTORNYA DIPERKUAT, BUKAN DIPINDAH. `.tb-col-lalu` dan `.tb-col` punya kekhususan
   yang SAMA, jadi yang menang cuma yang ditulis belakangan — dan `.tb-col` kebetulan ada 12
   baris di bawah sini. Aturan yang benar karena URUTANNYA akan salah pada suntingan pertama
   yang memindahkan salah satunya, dan tidak ada yang akan tahu kenapa. */
.tb-cols > .tb-col-lalu{flex:0 0 auto;width:max-content}
/* Tanda pemisah jejak, dikembalikan atas perintah pemilik. MENEMPEL di kanan namanya, di
   dalam tombol yang sama — jadi ia ikut disorot bersama namanya dan tidak pernah jadi
   sasaran ketuk tersendiri. Warnanya lebih pudar daripada nama termuda supaya ia membaca
   sebagai tanda, bukan sebagai teks. */
/* Jaraknya memakai skala v360 (--sp-N), BUKAN warisan --sp-xN. Percobaan pertama memakai
   --sp-x3/x5/x7 dan ratchet warisan langsung memerah: 637 -> 641. Angka itu hanya boleh
   MENGECIL, dan kode BARU yang menambahnya menukar sebuah aturan dengan kenyamanan sesaat. */
.tb-pisah{color:var(--faint);margin:0 var(--sp-2) 0 var(--sp-6);font-weight:400;user-select:none}
.tb-col:last-child .tb-pisah{display:none}
/* .crumb-wsmenu DIHAPUS 17 Agu 2026 bersama chevronnya — aturan pemilik. Satu sasaran:
   nama/inisial workspace. Bukan di home -> HOME; di home -> informasi workspace. */
/* Chevron MENEMPEL pada avatarnya (nol jarak di antaranya), supaya keduanya terbaca sebagai
   satu chip — dan pemisah '›' yang berdiri sesudahnya, di tempat pemisah.

   SLOT REL ITU --rail-w, DAN SEKARANG IA MEMIKUL TIGA BAGIAN, BUKAN DUA.

   Percobaan pertama menaruh '›' sesudah chevron tanpa mengubah apa pun yang lain: isinya jadi
   60px di slot 56px, dan `overflow:hidden` memotong tanda itu di TENGAH GLIFNYA — persis tanda
   yang ditagih pemilik. Gambar yang memperlihatkannya. Angka "muat" yang kami ukur memang
   memerah, tapi ia satu angka untuk seluruh slot: ia berkata "60 > 56", tidak berkata bagian
   mana yang dikorbankan.

     >> Slot berlebar tetap yang isinya bertambah satu bagian tidak pernah menawar. Ia memotong
        bagian TERAKHIR — dan bagian terakhir selalu yang baru saja ditambahkan.

   Jadi ukurannya DITURUNKAN dari --rail-w, tidak diketik. Chevron dan pemisah memakai angkanya
   sendiri; avatarnya mengambil SISANYA. Menambah --rail-w melebarkan avatarnya, menguranginya
   menyempitkannya, dan tidak ada angka kedua yang harus diingat siapa pun — yang persis
   dijanjikan komentar di bawah dan belum pernah benar-benar dikerjakan. */
:root{--tbws-pisah:12px; --tbws-lega:16px;
      --tbws-ava:calc(var(--rail-w) - var(--tbws-pisah) - var(--tbws-lega))}
.tb-ws-grup{display:flex;align-items:center;gap:0;height:100%}
.tb-rail{padding-right:0}
/* CHEVRONNYA DIHAPUS 17 Agu 2026 — aturan pemilik, dan aturannya menghapus kendalinya, bukan
   memindahkannya. Semalam ia dibesarkan jadi 18x34 lalu ditempelkan pada chip supaya berhenti
   terbaca sebagai tanda baca; hari ini pertanyaannya berbeda dan lebih baik: kenapa ia ada?

     >> Kendali yang letaknya harus ditambal supaya tidak salah dibaca adalah kendali yang
        sedang meminta ditanyakan apakah ia perlu ada.

   Aturan .tb-ws-menu ikut dihapus, bukan ditinggal "kalau-kalau dipakai lagi": aturan CSS yang
   tidak punya elemen adalah aturan yang tidak ada yang bisa membuktikannya salah. */
.tb-ws-grup .tb-ws-ava{width:auto}
/* Pemisah di slot rel punya jaraknya SENDIRI, dan bukan karena selera: di antara ruas, jarak
   kanannya dikerjakan padding 14px milik label berikutnya. Di sini label berikutnya ada di
   dalam daerah yang DIGESER — jadi apa pun yang kebetulan berada di tepi geseran menempel
   pada tanda ini. Jarak kanannya harus dibawa sendiri.

     >> Jarak yang dipinjam dari tetangga hilang begitu tetangganya bisa bergeser. */
.tb-rail .tb-pisah{margin:0 var(--sp-4) 0 var(--sp-2)}
/* SELNYA TIDAK PUNYA PADDING SENDIRI — DAN INI BUKAN SELERA, INI SYARAT SEJAJAR.
   `flex:1 1 0` membagi RUANG SISA, lalu menambahkannya di atas ukuran dasar tiap item; dan
   ukuran dasar sebuah item tidak pernah bisa lebih kecil daripada padding-nya sendiri. Jadi
   tiga sel dengan padding 20 / 28 / 110 mendapat bagian sisa yang sama tapi lebar AKHIR yang
   berbeda — terukur 57,34px meleset di 1426px, dengan `flex:1 1 0` terpasang di keduanya.

     >> Dua aturan yang sama tidak menjamin dua GARIS yang sama. Padding di atas item flex
        adalah angka kedua yang cuma kebetulan cocok saat semua selnya kebetulan sama.

   Sel kosong. Yang berpadding elemen di DALAMNYA. Dengan begitu ukuran dasar tiap sel nol
   tanpa kecuali, dan sisanya dibagi rata — apa pun isinya. */
/* BUTIR 168 (v385) — LEBARNYA IKUT ISINYA, BUKAN IKUT SLOTNYA.
   v365 sudah membuat sel ini TIDAK BISA MENYUSUT supaya jejaknya bisa digeser, dan
   alasannya benar: item flex yang boleh menyusut tidak pernah membuat barisnya lebih
   panjang daripada layar. Tapi ia berhenti separuh jalan — ukuran dasarnya tetap SLOT
   (100%/n), jadi jumlah seluruh sel selalu PERSIS selebar bilahnya, dan `overflow-x:auto`
   di atas baris yang tidak pernah lebih lebar dari wadahnya tidak pernah menggulir apa pun.
   Terukur 18 Agu 2026: scrollWidth == clientWidth di 958, 1426, dan 2016px.

   Dan pembagian rata itu penyakit keduanya: "Tasks" butuh 71px dan mendapat 451px,
   sementara judul tugas butuh 881px dan mendapat 451px yang sama — 51% terbaca.
   Di layar pemilik (958px, judul produksi) yang terbaca 55px dari 1170px: 4,7%.

     >> Ruang yang dibagi RATA di antara hal-hal yang kebutuhannya tidak rata bukan keadilan;
        ia cuma memindahkan seluruh kekurangannya ke satu tempat.

   YANG DILEPAS, DENGAN SADAR: nama tingkat tidak lagi persis sejajar dengan kolom yang ia
   namai. Itu sifat yang dibangun v363. Ia dilepas karena sejak pemilik meminta tanda '›'
   kembali (16 Agu), STRUKTUR jejaknya dibawa pemisahnya — dan kesejajaran yang dibayar
   dengan judul terpotong 95% tidak membeli apa pun yang bisa dibaca orang. */
/* DUA KEADAAN, DAN PEMILIK YANG MEMUTUSKAN BENTUKNYA — 18 Agu 2026 (v386).
   Ia melihat kedua bentuk berdampingan dan menjawab: "secara default saya mau tetap di atas
   masing-masing kolom, tapi begitu digeser-geser, berubah jadi bentuk kedua supaya bisa kebaca
   kalau ada tulisan kepotong."

   ISTIRAHAT  sejajar dengan kolomnya — sifat v363 yang v385 sempat lepas seluruhnya
   DIBUKA     ikut isinya, jadi rantainya lebih panjang daripada bilah dan BISA digulir

   Ini menyelesaikan hal yang tidak diselesaikan kedua bentuk sendirian. v384 sejajar dan judul
   panjang tidak pernah bisa dibaca; v385 terbaca dan kesejajarannya hilang di tiap layar,
   sepanjang waktu, untuk sesuatu yang dibutuhkan beberapa detik.
     >> Sebuah sifat yang cuma dibutuhkan saat orang MENCARI sesuatu tidak perlu dibayar
        sepanjang waktu oleh orang yang tidak sedang mencari.

   Dan urutannya penting: tanpa keadaan DIBUKA, `overflow-x:auto` di atas baris yang lebarnya
   persis selebar wadahnya tidak pernah punya apa pun untuk digulir — jadi isyaratnya bukan
   gulirannya melainkan SENTUHANNYA (pointerdown/wheel), lihat bukaJejak() di app.js. */
.tb-col{flex:0 0 calc(100% / var(--tb-n,1));min-width:0;display:block}
:root.tb-buka .tb-cols > .tb-col{flex:0 0 auto;width:max-content}
/* TANPA text-overflow. Aturan pemilik, disebut dua kali dan disahkan AD sebagai MUTLAK:
   di bilah jejak titik-titik TIDAK PERNAH boleh muncul. Muat -> utuh. Tidak muat -> BISA
   DIGESER (sentuh bilahnya dan rantainya jadi selebar isinya), bukan dipotong dengan titik.
     >> Titik-titik adalah aplikasi yang mengaku tahu sesuatu lalu menolak mengatakannya. Di
        sebuah daftar ia sopan; di jejak ia menghapus satu-satunya keterangan tentang di mana
        orang itu sedang berdiri.
   `overflow:hidden` TETAP: ia yang menahan huruf di dalam selnya saat istirahat. Yang dibuang
   cuma titik-titiknya — dan tests/sapu_jejak_titik.mjs yang menuntut ia tidak pernah perlu. */
.tb-lbl{display:block;min-width:0;padding:0 var(--sp-14);font-size:16px;font-weight:700;letter-spacing:-.01em;color:var(--text);white-space:nowrap;overflow:hidden}
/* Sel terakhir memberi ruang untuk apa pun yang mengambang di kanan. Tanpa ini namanya lewat
   di bawah dan terbaca setengah. Di label, bukan di sel: di sel ia menggeser tiap nama di
   kirinya — itu cacat 57,34px yang diukur hari ini.

   RUANGNYA DITURUNKAN, TIDAK DIKETIK. Dua tetapan di bawah masing-masing hidup di satu tempat,
   dan jarak bebasnya dihitung dari keduanya. Mengetik "332px" di sini berarti angka ketiga yang
   akan berpisah dari kedua angka itu pada rilis yang mengubah salah satunya. */
:root{--tb-actions-w:96px;--tb-search-w:300px}
/* CADANGAN KANAN = IKON SAJA, TIDAK IKUT KOTAK CARI — BUTIR 168 (v385).
   Bentuk lama menambahkan --tb-search-w ke padding ini saat layar lebar. Akibatnya lebar
   rantai jejak BERGANTUNG pada apakah kotak carinya sedang mundur — sementara keputusan
   "kotaknya mundur" diambil dengan mengukur lebar rantai itu. Dua pengukuran yang saling
   menjadi masukan satu sama lain tidak punya jawaban yang tetap.
     >> Ukuran yang dipakai untuk MEMUTUSKAN sesuatu tidak boleh ikut berubah oleh keputusan
        itu. Kalau ia ikut berubah, yang kita punya bukan pengukuran melainkan gema.
   Sekarang cadangannya tetap (ikon saja), keputusan mundurnya diambil dari lebar rantai yang
   tidak lagi bergerak, dan yang menjaga huruf tidak lewat di bawah kotak cari adalah
   clip-path yang lebarnya DIUKUR. */
.tb-cols > .tb-col:last-child > .tb-lbl{padding-right:var(--tb-actions-w)}
/* SAAT ISTIRAHAT cadangannya IKUT kotak cari lagi — dan pengembalian ini punya sebab yang
   terukur, bukan nostalgia. Di keadaan istirahat sel terakhir selebar slotnya, jadi ujung
   tulisannya berhenti 96px sebelum tepi bilah — yaitu 20px MELEWATI ikon aksi di 1426px
   (terukur rig MEMAKAI: "tinta ruas TERAKHIR berhenti sebelum ikon aksi 1100,05 <= 1080").
   clip-path memang menyembunyikannya, tapi yang disembunyikan tetap bertabrakan:
     >> Potongan gambar menutupi tabrakan; ia tidak mencegahnya. Yang mencegah tabrakan cuma
        ruang yang benar-benar disediakan.
   Kekhawatiran melingkar yang membuatnya dicabut di v385 TIDAK kembali: cadangan ini cuma
   berlaku saat ISTIRAHAT, dan keputusan "kotak cari mundur" cuma diambil saat DIBUKA — di
   sana cadangannya tetap (ikon saja), jadi lebar rantai tidak lagi berubah oleh keputusan
   yang memakainya. */
/* CADANGANNYA MEMAKAI ANGKA YANG SAMA DENGAN POTONGANNYA — satu sumber, bukan dua.
   19 Agu 2026, pemilik: "kok 'Update...' harusnya tertulis 'Update History' juga masih cukup".
   Diukur di layar Account, keadaan `Spotious › Account › Update History`:

       1426px   --tb-clip-w  90px   <- potongan IKUT mengerut (diukur saat lari)
                padding-right 396px  <- TIDAK ikut (300 + 96, dua TETAPAN dijumlahkan)
                gugus tindakan yang SUNGGUH dirender: 74px
                sia 322px · tinta 522 vs kotak 457 -> TERPOTONG

   Komentar di bawah aturan clip-path menulis "Pasangan, seperti padding di atas." Pasangannya
   cuma separuh ada: potongannya diukur, cadangannya dijumlahkan dari tetapan.
     >> Sepasang aturan yang disebut sepasang di komentar dan tidak sepasang di kode akan
        berpisah pada keadaan yang belum pernah dilihat siapa pun — dan yang tidak diperbarui
        DIAM, sebab tidak ada galat untuk ruang yang disisihkan terlalu banyak.
   Tetapannya tetap jadi cadangan terakhir: sebelum ukurJejak() pertama, --tb-clip-w belum ada.
   HANYA di keadaan ISTIRAHAT. Saat DIBUKA selnya `width:max-content`, jadi padding kanan ikut
   menentukan lebar rantai — dan lebar rantai itulah masukan keputusan yang menghasilkan
   --tb-clip-w. Aturan `tb-buka` di bawah tetap memakai TETAPAN, dan itu yang memutus gemanya. */
:root.lebar .tb-cols > .tb-col:last-child > .tb-lbl{padding-right:var(--tb-clip-w, calc(var(--tb-search-w) + var(--tb-actions-w)))}
:root.lebar.tb-buka .tb-cols > .tb-col:last-child > .tb-lbl{padding-right:var(--tb-actions-w)}
/* =========================================================================
   BARIS JEJAK BERAKHIR DI TEMPAT IKON AKSI DIMULAI — TIDAK PERNAH DI BAWAHNYA
   =========================================================================
   Terukur di Release, jendela 500px, keadaan `Chats › Kevin Brawijaya`:

       .tb-cols      kiri  56  kanan 500   <- sampai TEPI JENDELA
       .top-actions  kiri 418  kanan 492
       tumpang tindih 82px · ruas terakhir kotaknya berakhir di 584

   `.top-actions` position:absolute, jadi ia MENGAMBANG di atas baris jejak. Padding kanan di
   atas cuma menjaga posisi ISTIRAHAT; begitu barisnya digeser — atau begitu namanya cukup
   panjang — hurufnya lewat di bawah ikon cari dan lonceng.

     >> Tidak ada aturan yang menghentikan baris itu sebelum ikonnya. Yang menentukan tertutup
        atau tidak cuma PANJANG SEBUAH NAMA — dan panjang nama ditentukan pelanggan, bukan kita.

   Dan inilah batas aturan gambar, ditemukan aturan gambar itu sendiri dua hari sesudah
   dipasang: gambar di lebar yang sama TIDAK memperlihatkan apa pun tertutup, karena nama di
   sana kebetulan pendek.

     >> Gambar membuktikan apa yang terlihat pada SATU isi. Ia tidak bisa membuktikan sebuah
        ATURAN. Yang menutup kelas ini pernyataan GEOMETRI. (Asisten Direktur, 17 Agu 2026)

   KENAPA `clip-path`, DAN BUKAN MENYEMPITKAN `.tb-cols`

     Lebar `.tb-cols` adalah 100% yang dibagi `--tb-n` untuk tiap sel, dan pembagian itulah yang
     membuat tiap nama duduk tepat di atas kolomnya. Menyempitkan kotaknya akan memindahkan
     TIAP nama dari kolomnya — menukar satu cacat dengan cacat yang lebih besar.

     clip-path mengubah yang DIGAMBAR, bukan yang dihitung. Grid-nya tetap selebar layar; yang
     berakhir di tepi ikon adalah barisnya. Dan uji-sentuh ikut terpotong, jadi tidak ada ruas
     yang bisa diketuk di tempat ia tidak terlihat.

   ANGKANYA SATU, DAN SUDAH ADA. Ia memakai cadangan yang sama dengan padding di atas, jadi
   tidak ada angka kedua yang harus dijaga tetap sama — dan cadangan itu SELALU >= lebar
   tombolnya yang sungguhan (terukur 82px di 400px), jadi potongannya tidak pernah kurang. */
/* LEBAR POTONGANNYA DIUKUR, BUKAN DIJUMLAHKAN DARI DUA TETAPAN — BUTIR 168 (v385).
   Bentuk lama menjumlahkan --tb-search-w + --tb-actions-w. Terukur 18 Agu 2026 di 958px:
   potongannya jatuh di x=562 sementara kotak cari baru mulai di x=612 — LIMA PULUH PIKSEL
   dibuang, tepat di layar yang sedang kekurangan piksel. Dua tetapan yang dijumlahkan tidak
   pernah tahu tentang jarak, margin, dan pembungkus di antara keduanya.
     >> Angka yang diturunkan dari dua angka lain menjawab pertanyaan tentang TATA LETAK
        tanpa pernah melihat tata letaknya.
   ukurJejak() sekarang mengukur tepi KIRI gugus kanan yang sebenarnya dan menerbitkannya.
   Nilai cadangan di bawah dipakai pada gambar pertama, sebelum JS sempat mengukur. */
.tb-cols{clip-path:inset(0 var(--tb-clip-w, var(--tb-actions-w)) 0 0)}
:root.lebar .tb-cols{clip-path:inset(0 var(--tb-clip-w, calc(var(--tb-search-w) + var(--tb-actions-w))) 0 0)}
.tb-lbl-ws{display:flex;align-items:center;gap:var(--sp-6);padding-left:var(--sp-6);padding-right:var(--sp-6);font-weight:400}
.tb-col-now{color:var(--text)}
/* Wajah ringkas workspace di slot rel — dipakai hanya saat kolom paling kiri bukan Home.
   Ukurannya diikat ke --rail-w supaya ia selalu muat di slotnya, apa pun nilai var itu nanti:
   satu angka, satu sumber, seperti sisa baris ini. */
.tb-ws-ava{display:flex;align-items:center;justify-content:center;width:100%;height:100%}
.tb-ws-ava .co-logo{width:var(--tbws-ava);height:var(--tbws-ava);border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;letter-spacing:-.02em}
.tb-ws-ava:hover .co-logo{filter:brightness(.96)}
/* Breadcrumb lives IN the title bar now (feedback #79 v2). It fills the space left of the
   actions and scrolls when long — JS pins it to the last crumb, so a deep path reads from
   where-you-are and you slide left to reach the ancestors. */
/* =========================================================================
   --gulir-sengaja — SEBUAH PENYATAAN, DIBACA ALAT UKUR
   =========================================================================
   Invarian "nol yang bisa digeser ke samping" benar untuk hampir semua hal, dan SALAH
   untuk segelintir baris yang memang tumbuh mengikuti data orang: remah jejak, strip
   chip saring, tab, dan blok kode di dokumentasi. Baris-baris itu digulir dengan sengaja
   dan sudah punya gradasi memudar di ujungnya, supaya matanya tahu ada lanjutannya.

   Sampai hari ini alat ukur tidak bisa membedakannya, jadi ia melaporkan MERAH atas
   rancangan yang benar. Alat yang berteriak atas hal yang benar akan didiamkan, dan alat
   yang didiamkan tidak menjaga apa pun.

   YANG SENGAJA TIDAK DILAKUKAN: menulis daftar nama kelas di dalam berkas ujinya. Daftar
   seperti itu hidup jauh dari yang didaftarnya, dan berpisah darinya pada hari seseorang
   mengganti nama kelas. Penyataannya ditaruh DI ELEMENNYA, di sebelah alasannya, dan alat
   ukur membacanya dari getComputedStyle — jadi ia tidak bisa basi tanpa ketahuan.

   Ia BUKAN cara mendiamkan alat ukur: tiap penggulung yang menyatakan diri tetap DICETAK
   berikut lebarnya di tiap larian. Yang menyatakan sesuatu dengan keliru akan melihatnya
   sendiri, tiap kali.
   ========================================================================= */
/* .top-crumbs DIBUANG: bilah atas tidak lagi memikul rantai yang tumbuh mengikuti kedalaman,
   jadi ia tidak perlu menggulung mendatar. Penggulung yang tidak punya isi yang tumbuh adalah
   penggulung yang suatu hari dipakai lagi oleh orang yang mengira ia berarti sesuatu. */
.crumb{flex:none;font-size:13px;color:var(--muted);padding:var(--sp-x3) var(--sp-x7);border-radius:7px;white-space:nowrap;max-width:190px;overflow:hidden;text-overflow:ellipsis;line-height:1.55}
button.crumb{cursor:pointer;transition:background .12s,color .12s}
button.crumb:hover{background:var(--hover);color:var(--text)}
/* TUGAS-168 — .crumb-cur / .crumb-sep / .crumb-wsbadge DIBUANG bersama jalur globalnya.
   Judul tiap tingkat sekarang duduk di bilah atas SEJAJAR dengan kolomnya (.tb-col), tempat
   yang memang milik judul tanpa memakan tinggi kolomnya. Aturan yang ditinggalkan tanpa pemakainya akan dipakai lagi suatu hari oleh orang
   yang mengira ia masih berarti sesuatu. */
/* While a horizontal strip is being mouse-dragged (feedback #88): grab cursor, no smooth-scroll
   fighting the drag, and children ignore the pointer so nothing hovers/toggles mid-drag. */
.dragging-x{cursor:grabbing;scroll-behavior:auto;scroll-snap-type:none}
.dragging-x *{pointer-events:none}
/* Nama workspace penuh + chevron (membuka info workspace). Di KEDALAMAN BERAPA PUN sejak
   TUGAS-168: bilah atas tidak lagi memikul jalur, jadi ia tidak perlu mengecil untuk
   memberi tempat. */
.crumb-ws{display:flex;align-items:center;gap:var(--sp-x5);max-width:none;font-weight:600;font-size:16px;color:var(--muted);letter-spacing:-.02em;padding:var(--sp-x5) var(--sp-8) var(--sp-x5) var(--sp-6)}
.crumb-ws .crumb-ws-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:52vw}
.crumb-ws>svg{color:var(--faint);flex:none}
.brand{display:flex;align-items:center;gap:var(--sp-10);font-weight:600;font-size:16px}
.brand .mark{width:32px;height:32px;border-radius:9px;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center}
.brand-wrap{position:relative;display:flex;align-items:center;gap:var(--sp-x7)}
/* Penanda beta. Kecil dan tenang dengan sengaja: ia menerangkan, bukan meminta maaf.
   flex:none supaya nama workspace yang panjang memendekkan dirinya sendiri, bukan
   menggencet penanda ini sampai hurufnya patah. */
.beta-pill{flex:none;font-size:9.5px;font-weight:800;letter-spacing:.7px;padding:var(--sp-x3) var(--sp-x7);border-radius:6px;
  background:var(--accent-weak);color:var(--accent-text);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);
  cursor:pointer;line-height:1.2;transition:background .12s}
.beta-pill:hover{background:color-mix(in srgb,var(--accent) 18%,transparent)}
/* BUTIR 207 — pil lingkungan (Preview / Demo) di BILAH ATAS, bersebelahan dengan cip BETA.
   Bentuknya sengaja SEIRAMA dengan .beta-pill — tinggi baris, radius, dan ukuran huruf yang
   sama — supaya bilah atas tidak jadi barisan lencana yang masing-masing berbicara sendiri.
   Yang membedakan cuma warnanya, dan warnanya yang memang harus berbeda: pil ini menjawab
   "aku sedang di mana", bukan "produk ini sedang apa".
   `flex:none` + `white-space:nowrap` supaya ia tidak pernah ikut menyusut atau melipat saat
   remah panjang; di layar sempit yang mengalah remahnya, bukan penanda lingkungannya. */
.env-pill{flex:none;font-size:9.5px;font-weight:800;letter-spacing:.5px;padding:var(--sp-x3) var(--sp-8);border-radius:6px;
  line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none;margin-left:var(--sp-6)}
/* WARNA DAN LATARNYA BERPASANGAN DI SATU ATURAN, dan itu bukan kerapian: alat ukur kontras
   (butir 51) memasangkan `color` dengan `background` yang ditemukannya di aturan yang sama.
   Menaruh warna huruf di aturan dasar dan latarnya di aturan pengubah membuat alat itu
   mengukur putih di atas putih — 1,00:1 — lalu memerah untuk cacat yang tidak ada. Ia BENAR
   berbunyi: aturan dasarnya memang tidak punya latar, dan pil tanpa kelas pengubah memang
   akan jadi putih-di-atas-putih.
   Gradien lama (#14b8a6 -> #0ea5e9 dan #f59e0b -> #f97316) DIGANTI warna tunggal yang lebih
   gelap, karena putih di atas keduanya tidak sampai 4,5:1. Lencana lama boleh lolos: ia
   mengambang, dekoratif, pointer-events:none. Pil ini duduk di bilah atas sebagai antarmuka
   sungguhan, jadi ia tunduk pada syarat yang sama dengan chip lain. */
.env-pill.env-preview{background:#b45309;color:#fff}
.env-pill.env-demo{background:#0f766e;color:#fff}
:root[data-theme="dark"] .env-pill{opacity:.92}
.env-pill b{font-weight:800}
.env-pill i{font-style:normal;font-weight:700;opacity:.88}
.env-pill i::before{content:" \00b7 "}
/* Di layar tersempit yang mengalah KETERANGANNYA, bukan nama lingkungannya. Tanpa ini pil
   terpotong di tengah kata di 320px — dan label terpotong terbaca seperti kerusakan. */
@media (max-width:400px){.env-pill i{display:none}}
/* Di pintu depan ia memuat tanggalnya juga, jadi tidak sesempit yang di bilah atas. */
.beta-pill-auth{letter-spacing:.4px;font-size:10px;padding:var(--sp-x3) var(--sp-x9);cursor:default;align-self:center}
.beta-pill-auth:hover{background:var(--accent-weak)}
.brand-wrap>.brand{gap:var(--sp-6);padding:var(--sp-6) var(--sp-8) var(--sp-6) var(--sp-10);border-radius:10px;max-width:56vw}
.brand-wrap>.brand:hover{background:var(--surface-2)}
.brand-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.brand-wrap>.brand>svg:last-child{color:var(--faint);flex-shrink:0}
.co-backdrop{position:fixed;inset:0;z-index:100}
.co-menu{position:fixed;top:calc(var(--topbar-total) + 4px);left:12px;z-index:101;width:288px;max-width:calc(100vw - 24px);max-height:calc(100dvh - var(--topbar-total) - 20px);overflow-y:auto;-webkit-overflow-scrolling:touch;background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:0 14px 44px rgba(0,0,0,.18);padding:var(--sp-8)}
.co-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);padding:var(--sp-8) var(--sp-10) var(--sp-x5)}
.co-item{width:100%;display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-8) var(--sp-10);border-radius:10px;text-align:left;color:var(--muted)}
.co-item:hover{background:var(--surface-2)}
.co-item.on{background:var(--accent-weak)}
.co-item-body{flex:1;min-width:0}
.co-item-name{display:block;font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.co-item-role{display:block;font-size:12px;color:var(--muted);text-transform:capitalize}
.co-item.on>svg{color:var(--accent-text)}
.co-sep{height:1px;background:var(--line);margin:var(--sp-6) var(--sp-4)}
.co-link{width:100%;display:flex;align-items:center;gap:var(--sp-x9);padding:var(--sp-x9) var(--sp-10);border-radius:10px;font-size:14px;font-weight:500;color:var(--accent-text)}
.co-link:hover{background:var(--surface-2)}
.co-code{font-size:12px;color:var(--muted);padding:var(--sp-x9) var(--sp-10) var(--sp-4)}
.co-code b{color:var(--text);letter-spacing:1px}
.co-form{display:flex;flex-direction:column;gap:var(--sp-x9);padding:var(--sp-4) var(--sp-6) var(--sp-8)}
.co-form input{border:1px solid var(--line);border-radius:10px;padding:var(--sp-x9) var(--sp-12);font-size:14px;outline:none;background:var(--surface)}
.co-form input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.co-form .btn-primary{justify-content:center}
.topbar .bell .badge{position:absolute;top:2px;right:2px}
/* Denyut "baru" di lonceng — ada update yang belum sempat dilihat sejak lonceng terakhir dibuka.
   Dua sinyal terpisah, tiap-tiap satu aturan: denyut = "baru datang" (hilang saat dibuka),
   angka = "masih perlu ditangani" (hilang saat ditangani). Denyut inilah kelincahan; angkanya
   yang sekarang bisa diikuti. Hormati prefers-reduced-motion. */
.topbar .bell.has-new .badge{animation:bellPulse 1.8s ease-out infinite}
@keyframes bellPulse{0%{box-shadow:0 0 0 0 var(--accent-weak)}70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}
@media (prefers-reduced-motion:reduce){.topbar .bell.has-new .badge{animation:none}}
/* inline top-bar search */
.brand-wrap{flex:none;min-width:0}
.brand-name{max-width:118px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.topsearch-wrap{flex:1;min-width:0;display:flex;align-items:center;gap:var(--sp-x7);margin:0 var(--sp-8);border:1px solid var(--line);border-radius:20px;padding:0 var(--sp-6) 0 var(--sp-12);background:var(--surface-2);height:38px}
.topsearch-wrap svg{color:var(--muted);flex:none}
.topsearch{flex:1;min-width:0;border:none;background:none;outline:none;font-size:14px;color:var(--text);height:100%}
.topsearch-x{flex:none;display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;border:none;background:none;color:var(--muted);cursor:pointer}
.topsearch-x:hover{background:var(--surface);color:var(--text)}
.topsearch-panel{position:fixed;top:calc(var(--topbar-total) + 4px);left:12px;right:12px;z-index:101;max-height:72dvh;overflow-y:auto;background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:0 14px 44px rgba(0,0,0,.2);padding:var(--sp-6)}
/* v364 — PENCARIAN SELALU IKON, DAN TIGA ATURAN MATI DIBUANG BERSAMA FUNGSINYA.
   Sampai v363 bilah atas memuat MEDAN pencarian yang lebarnya diatur fitTopbarSearch(); ia
   melipatnya jadi ikon dengan menambahkan .tb-searchcollapsed saat sempit. Fungsi itu dibuang
   di v364 — bilah atasnya sekarang memikul nama kolom, bukan medan — tapi aturan CSS-nya
   TIDAK ikut dibuang. Akibatnya `.topsearch-btn{display:none}` berlaku selamanya, karena satu-
   satunya yang pernah menyalakannya adalah kelas yang tidak ada lagi yang memasang:
   TOMBOL PENCARIAN HILANG SAMA SEKALI dari aplikasi.

   Tidak satu pun gerbang melihatnya. Yang melihatnya GAMBAR — aturan BARU-18, di rilis
   pertama yang terikat olehnya.

     >> Aturan yang syaratnya cuma bisa dinyalakan oleh kode yang sudah dihapus bukan aturan
        yang tidur; ia aturan yang menyala TERUS, dengan cabang yang salah.

   Sekarang ikonnya berdiri sendiri, tanpa kelas dan tanpa syarat. Saat ditekan ia membuka
   medan selebar bilah (.topsearch-wrap.open), yang tidak pernah ikut dibuang. */
/* SATU SENDI, DUA RUPA — dan sendinya yang sudah punya nama (isDesktop(), diterbitkan render()
   sebagai kelas `lebar` di <html>). Bukan @media dengan angkanya sendiri: ambang kedua yang
   hari ini sama dengan yang pertama akan berpisah pada rilis yang mengubah salah satunya. */
.topsearch-btn{flex:none;display:inline-flex}
.topsearch-inline{display:none}
:root.lebar .topsearch-inline{display:flex;flex:0 0 auto;width:var(--tb-search-w);max-width:var(--tb-search-w);margin:0;height:34px}
:root.lebar .topsearch-btn{display:none}
/* v365 — PENGUKURNYA MENUKAR, TIDAK MENYEMBUNYIKAN. Satu kelas, satu pasang aturan: kotak
   mundur DAN ikon maju bersama-sama. Kalau ukurJejak() dibuang suatu hari tanpa membuang
   kelas ini, yang tersisa kotak yang selalu tampil — bukan layar yang kehilangan keduanya.
   Itu persis cacat yang lahir di v364, dan bentuk aturannya yang menentukan, bukan ingatan. */
:root.lebar.tb-sempit .topsearch-inline{display:none}
:root.lebar.tb-sempit .topsearch-btn{display:inline-flex}
/* Kotak pencarian mundur jadi ikon -> potongannya ikut mundur. Pasangan, seperti padding di atas. */
:root.lebar.tb-sempit .tb-cols{clip-path:inset(0 var(--tb-clip-w, var(--tb-actions-w)) 0 0)}
.topsearch-inline .topsearch{min-width:0}
.topsearch-inline .topsearch-x{flex:none}
.topbar-search{gap:var(--sp-8)}
.topsearch-wrap.open{flex:1;margin:0;max-width:none}
@media (min-width:900px){.topsearch-panel{left:auto;right:12px;width:420px}}
.stage{flex:1;min-height:0;display:flex;overflow:hidden}
/* The composer lives in here. padding-bottom must clear the home indicator,
   otherwise the message box is drawn underneath it and looks cut off. */
.taskbar{flex-shrink:0;min-height:calc(var(--topbar-h) + var(--sab));border-top:1px solid var(--line);background:var(--surface);padding:var(--sp-8) var(--sp-14) calc(var(--sp-8) + var(--sab));display:flex;align-items:center;gap:var(--sp-10)}
.taskbar.empty{display:none}
/* Tombol utama di taskbar TIDAK BOLEH tergencet. Ia tindakan pokok layar itu; kalau
   "New task" patah jadi dua baris, yang rusak bukan cuma rupanya — tinggi taskbar ikut
   berubah dan isi di atasnya bergeser. flex:none menahannya, nowrap menjaga tulisannya. */
.taskbar>.btn{flex:none;white-space:nowrap}
/* Chip saring: memakai sisa ruang, dan digulir mendatar kalau tidak muat.
   justify-content SENGAJA dibiarkan di awal — flex-end pada wadah yang bisa digulir
   membuat bagian awalnya tidak bisa dijangkau, jebakan yang sama seperti tombol Screenshot
   yang melayang keluar kartu. */
.tb-chips{--gulir-sengaja:1;flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:var(--sp-8);
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.tb-chips::-webkit-scrollbar{display:none}
/* BUTIR PAPAN 81 — "berapa yang tidak kamu lihat".
   Nada TENANG dengan sengaja: ia keterangan, bukan peringatan. Kalau ia semerah lencana
   pekerjaan-menunggu, ia akan menuntut perhatian di layar yang dibuka setiap hari, dan
   orang akan belajar mengabaikannya — lalu satu-satunya kalimat yang menjelaskan kenapa
   daftarnya pendek jadi tak terbaca lagi. Jalan keluarnya menempel di barisnya sendiri. */
.tk-hidden{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-x9) var(--sp-16);font-size:12.5px;color:var(--muted);
  background:var(--surface-2);border-bottom:1px solid var(--line)}
.tk-hidden svg{flex:none;color:var(--faint)}
.tk-hidden>span{flex:1;min-width:0}
.tk-hidden-go{flex:none;font-size:12.5px;font-weight:700;color:var(--accent-text);background:var(--accent-weak);
  border:0;border-radius:20px;padding:var(--sp-4) var(--sp-x11);cursor:pointer}
.tk-hidden-go:active{transform:scale(.96)}
/* BUTIR 118 (saudara) — tiga chip status HARUS MUAT, bukan digulir.
   Diukur di 360px: 60+59+42 = 161px isi + 2 jarak 8px = 177px, di ruang 147px. "Semua"
   terpotong keluar layar TANPA satu tanda pun — .tv-bar setidaknya punya gradasi memudar
   di ujungnya, baris ini tidak.
   Bedanya dengan .tv-bar penting, dan itu yang menentukan perbaikannya: .tv-bar TUMBUH
   mengikuti data orang, jadi ia memang harus digulir. Baris ini isinya TETAP TIGA
   selamanya. Baris berbatas yang tidak muat itu masalah UKURAN, bukan masalah gulungan —
   jadi yang benar membuatnya muat, bukan memberinya gradasi supaya terpotongnya terlihat
   sopan. Dipadatkan 34px (jarak 8->6, padding 13->8): 143px di ruang 147px. */
.tb-chips{gap:var(--sp-6)}
.tb-chips>.chip{flex:none;padding-left:var(--sp-8);padding-right:var(--sp-8)}
/* Ajakan pasang di bilah bawah layar utama. Keterangannya menyusut lebih dulu kalau
   ruangnya sempit — tombolnya yang harus utuh, bukan penjelasannya. */
.tb-install{flex:none;white-space:nowrap}
.tb-install-note{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:12px;color:var(--muted)}

/* ---------- shared rows ---------- */
.list-row{width:100%;display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-x13) var(--sp-16);border-bottom:1px solid var(--line);cursor:pointer;text-align:left;transition:background .12s}
.list-row:hover{background:var(--surface-2)}
.list-row.active{background:var(--accent-weak);box-shadow:inset 3px 0 0 var(--accent)}
.lr-body{flex:1;min-width:0}
.lr-title{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lr-sub{font-size:13px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:var(--sp-2)}
/* BUTIR PAPAN 78 — lencana hitung di sebuah .list-row, dan baris keterangannya saat ada isinya.
   Dimodelkan pada .co-item-badge yang sudah ada (bentuk, bobot, warna) supaya tidak lahir dialek
   lencana baru; bedanya cuma tak memakai margin-left:auto — .lr-body sudah flex:1, jadi lencana
   ini duduk sendiri di kanan dengan tanda panah tetap di ujung.
   Warnanya --danger-solid seperti kembarannya: ia bukan hiasan, ia pekerjaan yang menunggu. */
.lr-count{flex:none;min-width:19px;height:19px;padding:0 var(--sp-x5);border-radius:10px;background:var(--danger-solid);color:#fff;font-size:11px;font-weight:800;line-height:19px;text-align:center;margin-right:var(--sp-2)}
.lr-sub-todo{color:var(--danger);font-weight:600}
/* Pratinjau baris chat sadar-jenis: ikon kecil + label (mirip WhatsApp). */
.lr-pv{display:inline-flex;align-items:center;gap:var(--sp-x5);min-width:0;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}
.lr-pv>svg{flex:none;opacity:.85}
.lr-pv-del{font-style:italic;opacity:.85}
/* Logo WhatsApp (berwarna) di dalam tombol — jangan menyusut di tata letak flex. */
.wa-ic{flex:none;vertical-align:middle}
/* Kolom, bukan baris: jam di atas, lencana/pin/bisu di bawah. Dulu baris ini either/or —
   ada yang belum dibaca berarti jamnya hilang (temuan CEO 31 Jul). align-items:flex-end
   supaya keduanya rata kanan berapa pun lebar angkanya. min-height pada baris bawah menjaga
   tinggi baris percakapan TETAP SAMA antara yang punya lencana dan yang tidak — tanpa itu
   daftar chat bergoyang tiap kali sebuah percakapan dibaca. */
.lr-meta{font-size:12px;color:var(--faint);flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end;justify-content:center;gap:var(--sp-4)}
.lr-meta-b{display:flex;align-items:center;gap:var(--sp-6);min-height:18px}
.lr-avas{display:inline-flex;align-items:center}
.lr-avas .avatar{margin-left:-9px;box-shadow:0 0 0 2px var(--surface)}
.lr-avas .avatar:first-child{margin-left:0}
.ava-more{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--surface-2);color:var(--muted);font-weight:600;margin-left:-9px;box-shadow:0 0 0 2px var(--surface);flex:none}
.due{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:12px;color:var(--muted);margin-top:var(--sp-x3)}
.due svg{opacity:.7}
.due.overdue{color:var(--due-late);font-weight:600}
.due.due-today{color:var(--due-today);font-weight:600}
.due.due-soon{color:var(--text);font-weight:500}
.count-pill{font-size:12px;font-weight:600;color:var(--muted);background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:var(--sp-2) var(--sp-8)}
.panel-head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-14) var(--sp-16);border-bottom:1px solid var(--line);min-height:56px}
.panel-head h2{font-size:16px;font-weight:600}

/* ---------- DESKTOP/TABLET: responsive column cascade ---------- */
.cascade{display:flex;flex:1;min-width:0;height:100%;overflow:hidden;background:var(--surface-2)}
/* ---------- company rail (Slack-style workspace switcher) ---------- */
.co-rail{flex:0 0 var(--rail-w);display:flex;flex-direction:column;align-items:center;gap:var(--sp-6);padding:var(--sp-10) 0;background:var(--bg);border-right:1px solid var(--line);overflow-y:auto;overflow-x:hidden}
.co-rail::-webkit-scrollbar{width:0;height:0}
.co-home{width:44px;height:44px;border-radius:13px;display:flex;align-items:center;justify-content:center;background:var(--accent);color:#fff;flex:none}
.co-home:hover{filter:brightness(1.08)}
.co-rail-sep{width:26px;height:1px;background:var(--line);margin:var(--sp-2) 0;flex:none}
.co-rail-list{display:flex;flex-direction:column;align-items:center;gap:var(--sp-6);width:100%;flex:1 1 auto;min-height:0}
.co-ava{position:relative;width:100%;height:46px;display:flex;align-items:center;justify-content:center;flex:none}
.co-logo{width:40px;height:40px;border-radius:13px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;letter-spacing:-.01em;transition:border-radius .15s}
.co-ava:hover .co-logo{border-radius:10px}
.co-ava.on .co-logo{box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--accent)}
/* Per-workspace unread badge on the sidebar avatar (only on workspaces you're not in). */
/* BUTIR 143 — `right` dihitung dari rel, bukan dipatok. .co-ava selebar rel sedangkan yang
   dilencanai .co-logo 40px di tengahnya; angka mati 9px menaruh lencana 3px di luar tepi kanan
   logo saat rel 64, dan akan menariknya MASUK ke dalam logo saat rel menyempit. Rumus ini
   menghasilkan 9px persis di rel 64 dan menjaga overhang 3px yang sama di lebar rel mana pun. */
.co-ava-badge{position:absolute;top:2px;right:calc((var(--rail-w) - 40px) / 2 - 3px);min-width:17px;height:17px;padding:0 var(--sp-4);border-radius:9px;background:var(--danger-solid);color:#fff;font-size:10px;font-weight:800;line-height:17px;text-align:center;box-shadow:0 0 0 2px var(--bg);pointer-events:none}
.co-item-badge{margin-left:auto;min-width:19px;height:19px;padding:0 var(--sp-x5);border-radius:10px;background:var(--danger-solid);color:#fff;font-size:11px;font-weight:800;line-height:19px;text-align:center;flex:none}
.co-ava.on::before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:4px;height:24px;border-radius:0 4px 4px 0;background:var(--accent)}
.co-add{width:44px;height:44px;border-radius:13px;display:flex;align-items:center;justify-content:center;background:var(--surface);color:var(--accent-text);border:1px dashed var(--line);flex:none}
.co-add:hover{background:var(--accent-weak);border-color:var(--accent)}
/* company switcher inside the mobile Home rail */
.co-rail-m{gap:var(--sp-6)}
.co-rail-m .co-ava.on .co-logo{box-shadow:0 0 0 2px var(--surface-2),0 0 0 4px var(--accent)}
/* ---------- onboarding (join / create a workspace) ---------- */
.onb-card{max-width:400px}
.onb-title{font-size:22px;font-weight:700;letter-spacing:-.02em;margin-bottom:var(--sp-4)}
.onb-form{margin-top:var(--sp-16)}
.onb-form>label{display:block;font-size:13px;font-weight:600;color:var(--muted);margin-bottom:var(--sp-6)}
.onb-row{display:flex;flex-direction:column;gap:var(--sp-10)}
.onb-row input{width:100%;padding:var(--sp-x11) var(--sp-12);border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--text)}
.onb-row input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.onb-row .btn{width:100%;justify-content:center}
/* Optional discount-code field (refCreateField) — same full-width styling wherever it appears,
   so it never falls back to a narrow default input that clips its own placeholder. */
.ref-code-field{width:100%;box-sizing:border-box;margin-top:var(--sp-10);padding:var(--sp-x11) var(--sp-12);border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--text);font:inherit;letter-spacing:.04em}
.ref-code-field:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.ref-code-field::placeholder{text-transform:none;letter-spacing:normal;color:var(--faint)}
.onb-or{display:flex;align-items:center;text-align:center;color:var(--faint);font-size:12px;margin:var(--sp-16) 0 var(--sp-2)}
.onb-price{display:flex;gap:var(--sp-x7);align-items:flex-start;margin:var(--sp-10) var(--sp-2) 0;font-size:12px;line-height:1.45;color:var(--muted);background:var(--hover);border-radius:10px;padding:var(--sp-x9) var(--sp-x11)}
.onb-price svg{flex:none;margin-top:var(--sp-1);color:#12b76a}
.onb-price b{color:var(--text)}
.onb-or::before,.onb-or::after{content:"";flex:1;height:1px;background:var(--line)}
.onb-or span{padding:0 var(--sp-12);text-transform:uppercase;letter-spacing:.06em}
/* #392 — Akordeon Gabung/Buat: satu terbuka, satu terlipat, biar tak salah pencet tombol Buat. */
.onb-acc{border:1px solid var(--line);border-radius:12px;margin-top:var(--sp-12);overflow:hidden;transition:border-color .12s,box-shadow .12s}
.onb-acc.open{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.onb-acc-head{width:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);padding:var(--sp-14);background:var(--surface);border:0;cursor:pointer;font-family:inherit;font-size:15px;font-weight:600;color:var(--text);text-align:left}
.onb-acc-head:hover{background:var(--hover)}
.onb-acc-head>svg{flex:none;color:var(--muted)}
.onb-acc-body{margin-top:0;padding:0 var(--sp-14) var(--sp-14)}
.co-link.co-danger{color:var(--danger)}
.co-link.co-danger:hover{background:rgba(159,18,57,.1)}
/* workspace settings (rename + members) */
.co-menu-wide{width:302px;max-height:76dvh;overflow-y:auto}
.co-flabel{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--faint);display:flex;align-items:center;gap:var(--sp-6);margin:var(--sp-2) var(--sp-2) var(--sp-6)}
.co-info{padding:var(--sp-2) var(--sp-2) var(--sp-8);font-size:14px}
.co-info b{display:block;margin-top:var(--sp-2)}
.co-rename{padding:var(--sp-2)}
.co-rename input{width:100%;padding:var(--sp-x9) var(--sp-x11);border:1px solid var(--line);border-radius:9px;background:var(--surface);color:var(--text);margin-bottom:var(--sp-8)}
.co-rename input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.co-rename .btn{width:100%;justify-content:center}
.co-member{display:flex;align-items:center;gap:var(--sp-x9);padding:var(--sp-6) var(--sp-4)}
.co-member-body{display:flex;flex-direction:column;min-width:0}
.co-member-name{font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.co-member-role{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.co-mem-actions{display:flex;gap:var(--sp-2);margin-left:auto;flex:none}
.co-mem-btn{width:28px;height:28px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--muted);flex:none}
.co-mem-btn:hover{background:var(--surface-2);color:var(--text)}
.co-mem-btn.danger:hover{background:rgba(159,18,57,.12);color:var(--danger)}
/* "Joined the company on" — join date per member (#77/#67). */
.co-member-wrap{display:flex;flex-direction:column}
.co-member-joined{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:11.5px;color:var(--muted);margin-top:var(--sp-2)}
.co-member-joined svg{flex:none;color:var(--accent-text)}
.co-member-joined.is-none{color:var(--faint);font-style:italic}
.co-member-joined.is-none svg{color:var(--faint)}
.ws-start-set{display:flex;flex-direction:column;gap:var(--sp-x3);margin-top:var(--sp-14)}
.ws-start-form{margin-top:var(--sp-14);display:flex;flex-direction:column}
.ws-start-hint{display:flex;align-items:center;gap:var(--sp-x5);margin-top:var(--sp-8);font-size:12px;color:var(--muted);line-height:1.45}
.ws-start-hint svg{flex:none;color:var(--faint)}
.ws-memstart-form{margin:var(--sp-2) 0 var(--sp-10) var(--sp-x41);padding:var(--sp-x11) var(--sp-12);background:var(--surface-2);border:1px solid var(--line);border-radius:12px;display:flex;flex-direction:column;gap:var(--sp-x9)}
.ws-memstart-acts{display:flex;gap:var(--sp-6);justify-content:flex-end;flex-wrap:wrap}
.co-copy{display:flex;align-items:center;gap:var(--sp-6);width:100%;text-align:left;cursor:pointer;border-radius:8px}
.co-copy:hover{background:var(--surface-2)}
.co-copy b{color:var(--text)}
.co-copy svg{margin-left:auto;color:var(--faint);flex:none}
.co-copy:hover svg{color:var(--accent-text)}
/* Companies section detail */
.ws-detail{max-width:560px}
.ws-head{display:flex;align-items:center;gap:var(--sp-14);margin-bottom:var(--sp-16)}
.ws-head-b{min-width:0}
.ws-name{font-size:20px;font-weight:700;line-height:1.2;overflow:hidden;text-overflow:ellipsis}
.ws-role{font-size:13px;color:var(--muted);margin-top:var(--sp-2)}
.ws-actions{margin-bottom:var(--sp-16)}
.ws-switch{width:100%;justify-content:center}
.ws-detail .co-rename{margin-bottom:var(--sp-14)}
.ws-detail .co-code{margin:var(--sp-6) 0 var(--sp-4)}
.ws-sep{height:1px;background:var(--line);margin:var(--sp-20) 0 var(--sp-14)}
.ws-danger{width:100%;justify-content:center}
.ws-danger-zone{display:flex;flex-direction:column;gap:var(--sp-10)}
.ws-hint{font-size:13px;color:var(--muted);margin-top:var(--sp-10);line-height:1.55}
/* message edit/delete */
.bubble{position:relative}
.msg-acts{position:absolute;top:50%;transform:translateY(-50%);display:flex;gap:var(--sp-1);opacity:0;transition:opacity .12s;z-index:1}
.bubble:hover .msg-acts{opacity:1}
.bubble.me .msg-acts{right:100%;margin-right:var(--sp-x3)}
.msg-act{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--faint);background:var(--surface);border:1px solid var(--line)}
.msg-act:hover{color:var(--text);background:var(--surface-2)}
.bubble.them .msg-acts{left:100%;margin-left:var(--sp-x3)}
/* Di perangkat sentuh ikon aksinya DISEMBUNYIKAN, bukan dibuat setengah tembus.
   Dulu opacity:.5 — artinya tiga tombol samar menempel di SETIAP pesan, seumur hidup
   layar itu, dan tak satu pun bisa ditekan dengan yakin. Di ponsel jalannya sudah ada
   dan lebih baik: tekan-lama membuka menu penuh, geser untuk membalas. */
@media (hover:none){.msg-acts{display:none}}
/* Pemisah sebelum baris yang tidak bisa dibatalkan. */
.mm-sep{height:1px;background:var(--line);margin:var(--sp-6) var(--sp-8)}
/* Penghitung karakter: diam sampai mendekati batas. */
.co-count{position:absolute;right:6px;bottom:calc(100% + 4px);font-size:11px;font-weight:600;color:var(--faint);background:var(--surface);padding:var(--sp-1) var(--sp-x7);border-radius:20px;border:1px solid var(--line);opacity:0;transition:opacity .15s;pointer-events:none}   /* di ATAS kotak ketik, bukan menindih teksnya */
.co-count.on{opacity:1}
.co-count.warn{color:var(--due-late)}
.rxns{display:flex;flex-wrap:wrap;gap:var(--sp-4);margin-top:-3px}
.rxns.me{align-self:flex-end;justify-content:flex-end}
.rxns.them{align-self:flex-start}
.rxns.them.grp{margin-left:var(--sp-x36)}
.rxn{display:inline-flex;align-items:center;gap:var(--sp-x3);padding:var(--sp-2) var(--sp-8) var(--sp-2) var(--sp-6);border-radius:999px;background:var(--surface-2);border:1px solid var(--line);font-size:12px;line-height:1.7;cursor:pointer;transition:transform .08s}
.rxn:active{transform:scale(.94)}
.rxn.mine{background:var(--accent-weak);border-color:var(--accent)}
.rxn-e{font-size:13px}
.rxn-c{font-weight:600;color:var(--muted)}
.rxn.mine .rxn-c{color:var(--accent-text)}
.rxn-backdrop{position:fixed;inset:0;z-index:200}
.rxn-pop{position:fixed;left:50%;bottom:calc(84px + var(--sab));transform:translateX(-50%);z-index:201;display:flex;gap:var(--sp-4);padding:var(--sp-8);background:var(--surface);border:1px solid var(--line);border-radius:999px;box-shadow:0 12px 40px rgba(0,0,0,.24)}
.rxn-pick{width:44px;height:44px;border-radius:50%;font-size:24px;display:flex;align-items:center;justify-content:center;transition:transform .1s,background .12s}
.rxn-pick:hover{background:var(--surface-2);transform:scale(1.15)}
.rxn-more{color:var(--muted)}
.rxn-more:hover{color:var(--text)}
.emo-backdrop{position:fixed;inset:0;z-index:440;background:rgba(17,20,28,.4);animation:cfFade .12s ease}
.emo-panel{position:fixed;z-index:441;left:50%;top:50%;transform:translate(-50%,-50%);width:344px;max-width:calc(100vw - 28px);max-height:min(70dvh,460px);display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:0 24px 64px rgba(0,0,0,.28);animation:cfPop .15s cubic-bezier(.2,.9,.3,1.15);overflow:hidden}
.emo-head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-12) var(--sp-14) var(--sp-6);font-size:15px;font-weight:700}
.emo-scroll{overflow-y:auto;padding:0 var(--sp-12) var(--sp-12);-webkit-overflow-scrolling:touch}
.emo-cat{font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);margin:var(--sp-12) var(--sp-4) var(--sp-6)}
.emo-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:var(--sp-2)}
.emo-btn{height:38px;display:flex;align-items:center;justify-content:center;font-size:22px;line-height:1;background:none;border:none;border-radius:9px;cursor:pointer;transition:background .1s}
.emo-btn:hover{background:var(--surface-2)}
/* BUTIR 143 — `inline-flex` membuat penanda ini duduk SEBARIS dengan isi pesannya, jadi yang
   terbaca "↱ ForwardedAnd the updated user flows" — penanda dan kalimat orang menyatu.
   `margin-bottom:2px` yang sudah ada di sini membuktikan maksud aslinya memang satu baris
   sendiri; marginnya cuma tak pernah berlaku selama ia inline. `flex` memberi baris itu. */
.fwd-label{display:flex;align-items:center;gap:var(--sp-x3);font-size:11px;font-style:italic;color:var(--muted);margin-bottom:var(--sp-2);width:fit-content}
.bubble.me .fwd-label{color:rgba(255,255,255,.82)}
.fwd-label svg{opacity:.85}
.star-ind{display:inline-flex;vertical-align:middle;margin-right:var(--sp-x3)}
.star-ind svg{color:#e8a91a}
/* Banner sematan. Garis bawahnya ada di pembungkus, bukan di tombolnya, supaya tetap utuh
   melintasi tombol "lihat semua" di ujung kanan. */
.pin-wrap{display:flex;align-items:stretch;background:var(--surface-2);border-bottom:1px solid var(--line)}
.pin-banner{display:flex;align-items:center;gap:var(--sp-x9);flex:1;min-width:0;padding:var(--sp-8) var(--sp-14);background:none;border:none;text-align:left;cursor:pointer}
.pin-banner:hover{background:var(--surface)}
.pin-banner>svg{color:var(--accent);flex:none}
.pin-body{display:flex;flex-direction:column;min-width:0}
.pin-label{font-size:11px;font-weight:700;color:var(--accent-text)}
.pin-snip{font-size:13px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Penunjuk posisi: ruas terpisah sampai 6 pin, di atas itu jalur dengan penanda sebanding. */
.pin-bars{display:flex;flex-direction:column;gap:var(--sp-2);width:3px;height:28px;flex:none}
.pin-seg{flex:1;border-radius:2px;background:var(--line)}
.pin-seg.on{background:var(--accent)}
.pin-track{position:relative;background:var(--line);border-radius:2px;overflow:hidden}
.pin-thumb{position:absolute;left:0;width:100%;border-radius:2px;background:var(--accent);transition:top .18s ease}
.pin-more{flex:none;display:flex;align-items:center;justify-content:center;width:42px;background:none;border:none;border-left:1px solid var(--line);color:var(--muted);cursor:pointer}
.pin-more:hover{background:var(--surface);color:var(--text)}
/* Baris di panel: satu tombol lebar untuk melompat, satu tombol kecil untuk melepas. */
.pin-row{display:flex;align-items:center;gap:var(--sp-2);border-bottom:1px solid var(--line)}
.pin-row:last-child{border-bottom:none}
.pin-row .pin-row-main{flex:1;min-width:0;border-bottom:none}
.pin-row-off{flex:none;margin-right:var(--sp-6);color:var(--muted)}
.pin-row-off:hover{color:var(--danger)}
.find-bar{display:flex;align-items:center;gap:var(--sp-6);padding:var(--sp-8) var(--sp-12);background:var(--surface-2);border-bottom:1px solid var(--line)}
.find-ico{display:inline-flex;color:var(--muted);flex:none}
.find-input{flex:1;min-width:0;border:none;background:none;font:inherit;font-size:14px;color:var(--text);outline:none}
.find-count{font-size:12px;color:var(--muted);flex:none;min-width:38px;text-align:center}
.find-nav{width:30px;height:30px}
.find-mark{background:#ffe08a;color:#18181b;border-radius:3px;padding:0 var(--sp-1)}
.bubble.me .find-mark{background:#ffd24d}
.bubble.find-cur{outline:2px solid var(--accent);outline-offset:2px}
.bubble.find-cur .find-mark{background:#ff9f43}
.mm-backdrop{position:fixed;inset:0;z-index:250;background:rgba(17,20,28,.35)}
.mm-sheet{position:fixed;z-index:251;left:50%;bottom:calc(16px + var(--sab));transform:translateX(-50%);width:280px;max-width:calc(100vw - 28px);max-height:calc(100dvh - 32px);overflow-y:auto;-webkit-overflow-scrolling:touch;background:var(--surface);border:1px solid var(--line);border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,.28);padding:var(--sp-6);animation:cfPop .14s ease}
.mm-row{display:flex;align-items:center;gap:var(--sp-12);width:100%;padding:var(--sp-x11) var(--sp-14);border-radius:10px;font-size:14.5px;color:var(--text);background:none;border:none;text-align:left;cursor:pointer}
.mm-row:hover{background:var(--surface-2)}
.mm-row svg{color:var(--muted);flex:none}
.mm-row.danger,.mm-row.danger svg{color:var(--danger)}
.fwd-modal{width:360px;padding-bottom:var(--sp-12)}
.fwd-list{max-height:min(50vh,360px);overflow-y:auto;margin-top:var(--sp-8);display:flex;flex-direction:column;gap:var(--sp-2)}
.fwd-row{display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-x9) var(--sp-8);border-radius:11px;background:none;border:none;text-align:left;cursor:pointer}
.fwd-row:hover{background:var(--surface-2)}
.fwd-name{flex:1;font-size:14px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fwd-row>svg:last-child{color:var(--faint)}
.star-panel{display:flex;flex-direction:column}
.star-head-title{display:inline-flex;align-items:center;gap:var(--sp-8)}
.star-head-title svg{color:#e8a91a}
.star-scroll{overflow-y:auto;padding:var(--sp-8) var(--sp-10) var(--sp-12);flex:1}
.star-item{display:block;width:100%;text-align:left;padding:var(--sp-x11) var(--sp-12);border-radius:12px;background:none;border:none;cursor:pointer;border-bottom:1px solid var(--line)}
.star-item:hover{background:var(--surface-2)}
.star-item-top{display:flex;justify-content:space-between;gap:var(--sp-10);margin-bottom:var(--sp-x3)}
.star-item-who{font-size:13.5px;font-weight:600}
.star-item-conv{font-size:12px;color:var(--muted);flex:none}
.star-item-snip{font-size:13.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.star-empty{display:flex;flex-direction:column;align-items:center;gap:var(--sp-10);padding:var(--sp-x44) var(--sp-20);color:var(--faint);text-align:center}
.star-empty svg{color:var(--line)}
.composer-wrap{display:flex;flex-direction:column;gap:var(--sp-x7);width:100%}
.reply-bar{display:flex;align-items:center;gap:var(--sp-x9);padding:var(--sp-x7) var(--sp-12);background:var(--surface-2);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:10px}
/* #276 — strip "N foto siap dikirim": foto yang dipilih tapi belum terkirim, tersimpan per-percakapan */
.attach-draft{display:flex;align-items:center;gap:var(--sp-10);width:100%;text-align:left;padding:var(--sp-x7) var(--sp-8);background:var(--surface-2);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:10px;cursor:pointer}
.attach-draft:hover{background:var(--line)}
.attach-draft-thumbs{flex:none;display:flex;align-items:center}
.attach-draft-thumbs img{width:32px;height:32px;border-radius:7px;object-fit:cover;margin-left:-8px;border:2px solid var(--surface-2);box-shadow:0 1px 2px rgba(0,0,0,.12)}
.attach-draft-thumbs img:first-child{margin-left:0}
.attach-draft-more{margin-left:-8px;width:32px;height:32px;border-radius:7px;background:var(--accent-weak);color:var(--accent-text);border:2px solid var(--surface-2);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}
.attach-draft-lbl{flex:1;min-width:0;display:flex;align-items:center;gap:var(--sp-6);font-size:12.5px;color:var(--muted)}
.attach-draft-lbl>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attach-draft-x{flex:none;width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--muted);background:none;border:0;cursor:pointer}
.attach-draft-x:hover{background:var(--surface);color:var(--text)}
.reply-bar-ic{color:var(--accent-text);flex:none;display:flex}
.reply-bar-body{display:flex;flex-direction:column;min-width:0;flex:1}
.reply-bar-sender{font-size:12px;font-weight:700;color:var(--accent-text)}
.reply-bar-snip{font-size:12.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.reply-bar-x{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--muted);flex:none}
.reply-bar-x:hover{background:var(--surface);color:var(--text)}
.msg-quote{display:flex;flex-direction:column;gap:var(--sp-1);width:100%;text-align:left;padding:var(--sp-x5) var(--sp-x9);margin-bottom:var(--sp-x5);background:rgba(0,0,0,.06);border-left:3px solid currentColor;border-radius:7px;cursor:pointer;overflow:hidden}
.bubble.me .msg-quote{background:rgba(255,255,255,.18)}
/* BUTIR 155 — kutipan balasan sekarang punya bentuk untuk tiap jenis pesan, jadi susunannya
   berubah dari satu kolom jadi [gambar kecil][teks]. Teksnya dibungkus .mq-tx supaya ia tetap
   menumpuk sendiri (nama di atas, isi di bawah) di sebelah gambarnya.
   min-width:0 di .mq-tx WAJIB: tanpa itu elipsis di .mq-snip tidak pernah bekerja di dalam
   flex — anaknya menolak menyusut di bawah lebar isinya, dan nama berkas panjang akan
   melebarkan seluruh gelembung alih-alih dipotong. */
.msg-quote{flex-direction:row;align-items:center;gap:var(--sp-x7)}
.mq-tx{display:flex;flex-direction:column;gap:var(--sp-1);min-width:0;flex:1 1 auto}
.mq-thumb{width:34px;height:34px;flex:none;border-radius:5px;object-fit:cover;background:rgba(0,0,0,.08)}
.mq-sender{font-size:11px;font-weight:700;opacity:.85}
.mq-snip{font-size:12px;opacity:.8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:var(--sp-4)}
.mq-snip svg{flex:none;opacity:.9}
.reply-bar-snip{display:flex;align-items:center;gap:var(--sp-4);min-width:0}
.reply-bar-snip svg{flex:none;opacity:.9}
/* shared task / event cards inside a chat bubble */
/* BUTIR 143 — cacat yang sama seperti .poll-card, dan ia sekeluarga: lantai lebar tetap
   di dalam .bubble yang max-width-nya 74%. Kartu tugas/acara/absen meluber keluar
   gelembungnya di setiap ponsel; yang terpotong justru sisi kanan tempat waktu dan panah
   berada. `min(210px,100%)` menjaga lebar nyamannya di tempat yang lapang dan menyerah
   pada gelembungnya di tempat yang sempit. */
.chat-card{display:flex;align-items:center;gap:var(--sp-10);width:100%;min-width:min(210px,100%);max-width:290px;text-align:left;padding:var(--sp-x9) var(--sp-10);margin:var(--sp-1) 0 var(--sp-x5);background:var(--surface);border:1px solid var(--line);border-radius:12px;cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}
.chat-card:hover{box-shadow:0 3px 10px rgba(0,0,0,.09)}
.chat-card:active{transform:scale(.985)}
.cc-ico{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:9px;background:var(--accent-weak);color:var(--accent)}
.cc-main{display:flex;flex-direction:column;gap:var(--sp-1);min-width:0;flex:1}
.cc-kind{font-size:10.5px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;color:var(--faint)}
.cc-title{font-size:13.5px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}
.cc-meta{display:flex;align-items:center;gap:var(--sp-x5);font-size:11.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cc-meta svg{flex:none;opacity:.85}
.cc-go{flex:none;color:var(--faint);display:flex}
.cc-done .cc-title{text-decoration:line-through;opacity:.6}
.bubble-card{padding:var(--sp-4)}
.bubble-card .bt{padding:0 var(--sp-6) var(--sp-2)}
.task-src{display:flex;align-items:center;gap:var(--sp-6);font-size:12px;color:var(--muted);background:var(--accent-weak);padding:var(--sp-x7) var(--sp-10);border-radius:9px;margin-bottom:var(--sp-12)}
.task-src svg{flex:none;color:var(--accent)}
/* cross-workspace markers */
.lr-title.has-tag{display:flex;align-items:center;overflow:visible}
.lr-title.has-tag .lr-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.ws-tag{flex:none;display:inline-flex;align-items:center;gap:var(--sp-x3);margin-left:var(--sp-x7);padding:var(--sp-1) var(--sp-8) var(--sp-1) var(--sp-6);font-size:10.5px;font-weight:600;color:var(--muted);background:var(--surface-2);border:1px solid var(--line);border-radius:20px;white-space:nowrap;max-width:130px;overflow:hidden}
.ws-tag svg{flex:none;opacity:.75}
.foreign-banner{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-x7) var(--sp-14);background:var(--surface-2);color:var(--muted);font-size:12.5px;font-weight:500;border-bottom:1px solid var(--line)}
.foreign-banner svg{flex:none;opacity:.8}
/* #363 — grup lintas-workspace ("shared"): chip ungu di daftar chat + banner transparansi di kepala
   percakapan + chip "contact" di pemilih anggota grup. Ungu = penanda "menyeberang workspace". */
.ws-tag-shared{color:#7c3aed;background:#f3ecff;border-color:#e2d4fb}
:root[data-theme="dark"] .ws-tag-shared{color:#c4b5fd;background:#7c3aed26;border-color:#7c3aed4d}
.shared-banner{background:#f3ecff;color:#7c3aed;border-bottom-color:#e2d4fb}
:root[data-theme="dark"] .shared-banner{background:#7c3aed1a;color:#c4b5fd;border-bottom-color:#7c3aed4d}
.chk-x{flex:none;margin-left:auto;padding:var(--sp-1) var(--sp-8);font-size:10px;font-weight:600;color:#7c3aed;background:#f3ecff;border-radius:20px}
:root[data-theme="dark"] .chk-x{color:#c4b5fd;background:#7c3aed26}
/* peek at a deleted message */
.bubble-del{display:flex;flex-direction:column;gap:var(--sp-4);align-items:flex-start}
.peek-btn{display:inline-flex;align-items:center;gap:var(--sp-4);background:none;border:none;padding:0;font:inherit;font-size:11.5px;font-weight:600;color:var(--accent);cursor:pointer;opacity:.9}
.peek-btn:hover{opacity:1;text-decoration:underline}
.peek-btn svg{flex:none}
.bubble-revealed{border:1px dashed var(--line)}
.bubble.me.bubble-revealed{border-color:rgba(255,255,255,.4)}
.revealed-tag{display:flex;align-items:center;gap:var(--sp-4);font-size:10.5px;font-weight:600;opacity:.7;margin-bottom:var(--sp-x3)}
.revealed-tag svg{flex:none}
/* activity center */
.ws-activity{width:100%;justify-content:center}
.act-panel .star-scroll{padding:var(--sp-6) 0}
.act-row{display:flex;gap:var(--sp-x11);align-items:flex-start;padding:var(--sp-x11) var(--sp-16);border-bottom:1px solid var(--line)}
.act-ic{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:9px;margin-top:var(--sp-1)}
.act-ic svg{flex:none}
.act-body{min-width:0;flex:1}
.act-line{font-size:13.5px;color:var(--text);line-height:1.4}
.act-line b{font-weight:650}
.act-time{font-size:11.5px;color:var(--faint);margin-top:var(--sp-2)}
.nt-item.nt-unread{background:var(--accent-weak)}
.nt-ico{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:var(--accent-weak);color:var(--accent)}
.nt-ico svg{flex:none}
.nt-item .nt-strong{font-weight:500;color:var(--text);white-space:normal;line-height:1.35;overflow:hidden;text-overflow:clip;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.nt-item.nt-unread{align-items:flex-start}
.nt-time{display:block;font-size:11px;color:var(--faint);margin-top:var(--sp-2)}
/* Butir 77 — penanda "ini milik workspace lain", di baris notifikasi, SEBELUM ditekan.
   Sepi dengan sengaja: ia keterangan, bukan peringatan. Menekannya tetap membawa orangnya ke
   sana; label ini cuma membuat perpindahan workspace jadi hal yang ia setujui, bukan hal yang
   tiba-tiba terjadi padanya. */
.nt-ws{display:inline-flex;align-items:center;gap:var(--sp-x3);margin-left:var(--sp-8);padding:0 var(--sp-6);border-radius:20px;
  background:var(--surface-2);color:var(--muted);font-size:10.5px;font-weight:600;vertical-align:middle}
.nt-ws>svg{flex:none;opacity:.8}
.cl-entry.cl-shipped{background:var(--accent-weak);border:1px solid var(--line);border-bottom:1px solid var(--line);border-radius:12px;padding:var(--sp-12) var(--sp-14);margin-bottom:var(--sp-16)}
.cl-shipped .cl-head{margin-bottom:var(--sp-6)}
.cl-shipped .cl-title{display:flex;align-items:center;gap:var(--sp-6);color:var(--accent-text)}
.cl-shipped .cl-title svg{flex:none}
/* feedback hub */
.fb-sub{font-size:13.5px;color:var(--muted);margin:-6px 0 var(--sp-14);line-height:1.5}
.fb-new{width:100%;justify-content:center;margin-bottom:var(--sp-14)}
.fb-form{background:var(--surface-2);border:1px solid var(--line);border-radius:12px;padding:var(--sp-12);margin-bottom:var(--sp-14);display:flex;flex-direction:column;gap:var(--sp-x9)}
.fb-form input,.fb-form textarea{width:100%;border:1px solid var(--line);border-radius:9px;padding:var(--sp-x9) var(--sp-x11);font:inherit;font-size:14px;background:var(--surface);color:var(--text)}
/* #101 — "ide serupa" saat mengetik judul feedback (dedup di sumbernya) */
.fb-sim{border:1px solid var(--line);border-radius:10px;background:var(--surface);overflow:hidden}
.fb-sim-h{display:flex;align-items:center;gap:var(--sp-6);padding:var(--sp-x7) var(--sp-10);font-size:12px;color:var(--muted);background:var(--accent-weak)}
.fb-sim-h svg{flex:none;stroke:var(--accent-text)}
.fb-sim-row{display:flex;align-items:center;gap:var(--sp-8);width:100%;text-align:left;padding:var(--sp-8) var(--sp-10);border:0;border-top:1px solid var(--line);background:none;cursor:pointer;font-size:13px;color:var(--text)}
.fb-sim-row:hover{background:var(--surface-2)}
.fb-sim-v{flex:none;display:inline-flex;align-items:center;gap:var(--sp-2);font-size:12px;font-weight:700;color:var(--muted)}
.fb-sim-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fb-sim-st{flex:none;font-size:11px;font-weight:600}
.fb-form textarea{min-height:66px;resize:vertical}
/* Tiga tombol (Screenshot, Cancel, Submit) tidak muat berjajar di kolom selebar ponsel.
   Tanpa flex-wrap mereka meluber KE LUAR kartunya — dan karena justify-content:flex-end,
   luberannya keluar lewat sisi kiri, jadi tombolnya tampak melayang di luar kotak. */
.fb-form-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--sp-8)}
.fb-list{display:flex;flex-direction:column;gap:var(--sp-x9)}
.fb-card{display:flex;gap:var(--sp-x11);align-items:stretch;background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:var(--sp-x11) var(--sp-12)}
.fb-card.open{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
.fb-vote{flex:none;display:flex;flex-direction:column;align-items:center;gap:var(--sp-1);min-width:46px;padding:var(--sp-6) var(--sp-4);border:1px solid var(--line);border-radius:10px;background:var(--surface-2);color:var(--muted);cursor:pointer;font-weight:700;font-size:13px;transition:all .12s}
.fb-vote svg{flex:none}
.fb-vote:hover{border-color:var(--accent);color:var(--accent)}
.fb-vote.on{background:var(--accent-weak);border-color:var(--accent);color:var(--accent)}
.fb-card-main{flex:1;min-width:0;text-align:left;background:none;border:none;padding:0;cursor:pointer;font:inherit}
.fb-card-body{display:flex;flex-direction:column;gap:var(--sp-x3);min-width:0}
.fb-card-title{font-size:14.5px;font-weight:650;color:var(--text);line-height:1.35;display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-x7)}
.fb-badge{font-size:10.5px;font-weight:700;padding:var(--sp-1) var(--sp-8);border-radius:20px;white-space:nowrap}
.fb-card-desc{font-size:13px;color:var(--muted);line-height:1.45;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.fb-card-meta{display:flex;align-items:center;gap:var(--sp-x5);font-size:11.5px;color:var(--faint);margin-top:var(--sp-2)}
.fb-card-meta svg{flex:none;opacity:.8}
.fb-detail{margin:-2px 0 var(--sp-4);padding:var(--sp-12);background:var(--surface-2);border:1px solid var(--line);border-top:none;border-radius:0 0 12px 12px}
.fb-status-set{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-bottom:var(--sp-12)}
.fb-st-btn{font-size:11.5px;font-weight:600;padding:var(--sp-4) var(--sp-10);border:1px solid var(--line);border-radius:20px;background:var(--surface);color:var(--muted);cursor:pointer}
.fb-st-btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
.fb-comments{display:flex;flex-direction:column;gap:var(--sp-x11);margin-bottom:var(--sp-x11)}
.fb-comment{display:flex;gap:var(--sp-x9)}
.fb-comment-b{min-width:0;flex:1}
.fb-comment-h{display:flex;align-items:baseline;gap:var(--sp-8);font-size:12px}
.fb-comment-h b{font-weight:650;color:var(--text)}
.fb-comment-h span{color:var(--faint);font-size:11px}
.fb-comment-t{font-size:13.5px;color:var(--text);line-height:1.45;margin-top:var(--sp-1);word-wrap:break-word}
.fb-nocom,.fb-cload{font-size:13px;color:var(--faint);padding:var(--sp-2) 0 var(--sp-10)}
.fb-comment-form{position:relative;display:flex;gap:var(--sp-8);align-items:flex-end}   /* jangkar untuk .co-count, sama seperti .composer */
.fb-comment-form textarea{flex:1;min-width:0;border:1px solid var(--line);border-radius:20px;padding:var(--sp-8) var(--sp-x13);font:inherit;font-size:13.5px;background:var(--surface);color:var(--text);resize:none;overflow-y:auto;max-height:120px;line-height:1.45}
.fb-comment-form textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak);outline:none}
.fb-comment-form .btn-sm{flex:none;width:34px;height:34px;padding:0;justify-content:center;border-radius:50%}
.fb-empty{display:flex;flex-direction:column;align-items:center;gap:var(--sp-10);color:var(--faint);padding:var(--sp-x34) var(--sp-20);text-align:center;font-size:14px}
/* event visibility */
.seg-vis button{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-x5)}
.seg-vis button svg{flex:none}
.field-hint{font-size:11.5px;color:var(--faint);margin-top:var(--sp-x7);line-height:1.45}
.ev-private{display:inline-flex;vertical-align:middle;margin-left:var(--sp-6);color:var(--faint)}
.ev-private svg{flex:none}
/* swipe a bubble to reply */
.chat-body .bubble{transition:transform .18s cubic-bezier(.2,.7,.3,1)}
.chat-body .bubble.swiping{transition:none}
.chat-body .bubble.swiping::before{content:'';position:absolute;left:-30px;top:50%;width:24px;height:24px;margin-top:-12px;border-radius:50%;background-color:var(--accent-weak);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' viewBox='0 0 24 24' fill='none' stroke='%234f46e5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 17 4 12 9 7'/%3E%3Cpath d='M20 18v-2a4 4 0 0 0-4-4H4'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:center;opacity:calc(var(--swipe,0)/55)}
.msg-flash{animation:msgflash 1.3s ease}
@keyframes msgflash{0%,100%{outline:2px solid transparent;outline-offset:2px}30%{outline:2px solid var(--accent);outline-offset:2px}}
/* var(--faint) itu abu redup yang dirancang untuk latar TERANG. Di bubble milik sendiri —
   yang berlatar merah brand penuh — hasilnya nyaris tak terbaca (laporan klien 1 Agu,
   diverifikasi CEO: rgb(107,107,116) di atas merah). Bubble lawan bicara berlatar abu terang
   jadi aman; varian milik-sendiri yang terlewat saat pass kontras v253/v267.
   Warnanya kini MENGIKUTI varian bubble-nya, bukan satu nilai untuk semua latar. */
.msg-gone{display:inline-flex;align-items:center;gap:var(--sp-6);font-style:italic;color:var(--faint)}
/* Putih PENUH, bukan rgba(255,255,255,.88). Alpha .88 di atas merah brand #C93A22 hanya
   menghasilkan 4,29:1 — di bawah ambang AA 4.5, jadi ia akan mengulang persis kegagalan
   yang sedang diperbaiki, cuma lebih halus. Putih penuh: 5,11:1. Kesan "redup" tetap
   didapat dari italic dan ikon tongnya, bukan dari mengorbankan keterbacaan. */
.bubble.me .msg-gone{color:#fff}
.bubble-del{opacity:1}                 /* opacity .8 dulu menggerus kontras yang baru dibetulkan */
.bubble.me.bubble-del{opacity:1}
.msg-gone svg{flex:none}
/* group info modal */
.chat-title{font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chat-info{flex:none;color:var(--muted)}
.chat-info:hover{color:var(--accent-text)}
/* BUTIR 137 — tombol lebarkan/sempitkan obrolan.
   Keadaan MENYALA harus terbaca sekejap tanpa membaca ikonnya: latar pastel --accent-weak
   yang sama dipakai di seluruh aplikasi untuk "sedang aktif", plus --accent-text di atasnya
   (pasangan yang memang dibuat untuk itu — 5,50 dan 8,33 di gerbang kontras butir 51).
   Ikonnya bergeser 1px saat ditekan: gerak sekecil itu tidak mengalihkan perhatian, tapi ia
   membalas sentuhan — dan tombol yang tidak membalas terasa seperti tombol yang tidak jalan. */
.chat-wide{flex:none;color:var(--muted);transition:color .15s ease,background-color .15s ease}
.chat-wide:hover{color:var(--accent-text)}
.chat-wide.on{color:var(--accent-text);background:var(--accent-weak)}
.chat-wide.on:hover{background:var(--accent-weak);color:var(--accent)}
.chat-wide:active svg{transform:scale(.88)}
.chat-wide svg{transition:transform .12s ease}
.gi-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:101;width:342px;max-width:calc(100vw - 32px);max-height:82vh;max-height:calc(100dvh - 28px);overflow-y:auto;-webkit-overflow-scrolling:touch;background:var(--surface);border:1px solid var(--line);border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,.28);padding:var(--sp-6) var(--sp-14) var(--sp-14)}
.gi-head{display:flex;align-items:center;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);padding:var(--sp-10) 0 var(--sp-6)}
/* chat attachments */
.attach-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:10px;color:var(--muted);cursor:pointer;flex:none}
.attach-btn:hover{background:var(--surface-2);color:var(--text)}
.att-img{display:block;margin-top:var(--sp-4);border-radius:12px;overflow:hidden;max-width:240px;line-height:0;padding:0;border:none;background:var(--surface-2);cursor:zoom-in;-webkit-appearance:none}
.att-img img{display:block;width:100%;height:auto;max-height:280px;object-fit:cover}
.bubble.me .att-img{margin-left:auto}
.att-file{display:inline-flex;align-items:center;gap:var(--sp-10);margin-top:var(--sp-4);padding:var(--sp-8) var(--sp-12) var(--sp-8) var(--sp-8);background:var(--surface);border:1px solid var(--line);border-radius:12px;color:var(--text);text-decoration:none;max-width:250px}
.att-file:hover{background:var(--surface-2)}
.att-file svg{flex:none}
.af-ico{width:32px;height:32px;border-radius:8px;background:var(--accent-weak);color:var(--accent-text);display:flex;align-items:center;justify-content:center;flex:none}
.att-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500}
.bubble.me .att-file{background:rgba(255,255,255,.16);border-color:transparent;color:#fff}
.bubble.me .af-ico{background:rgba(255,255,255,.22);color:#fff}
.bubble-att{padding-bottom:var(--sp-6)}

/* ---------- image compose (crop + caption) + lightbox ---------- */
/* ---- WhatsApp-style photo composer (multi-photo, optional crop) ---- */
.ic-backdrop{position:fixed;inset:0;z-index:200;background:rgba(0,0,0,.6)}
.ic-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:201;width:440px;max-width:calc(100vw - 24px);max-height:calc(100dvh - 40px);background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:0 20px 60px rgba(0,0,0,.34);padding:var(--sp-6) var(--sp-14) var(--sp-14);display:flex;flex-direction:column}
.ic-head{display:flex;align-items:center;gap:var(--sp-6);padding:var(--sp-4) 0 var(--sp-8)}
.ic-head .ic-title{flex:1;text-align:center;font-weight:700;font-size:14px;color:var(--text)}
.ic-crop-btn.on{background:var(--accent-weak);color:var(--accent-text)}
.ic-stagewrap{flex:1;display:flex;justify-content:center;align-items:center;background:var(--surface-2);border-radius:14px;padding:var(--sp-10);overflow:hidden;min-height:180px}
.ic-stage{position:relative;line-height:0;touch-action:none;-webkit-user-select:none;user-select:none;max-width:100%}
.ic-stage img{display:block;max-width:100%;max-height:46vh;object-fit:contain;border-radius:8px;pointer-events:none}
.ic-modal.cropping .ic-stage img{border-radius:0}
.ic-crop{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 100vmax rgba(0,0,0,.5);cursor:move;touch-action:none}
.ic-h{position:absolute;width:16px;height:16px;background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.5)}
.ic-h.nw{left:-9px;top:-9px;cursor:nwse-resize}.ic-h.ne{right:-9px;top:-9px;cursor:nesw-resize}.ic-h.sw{left:-9px;bottom:-9px;cursor:nesw-resize}.ic-h.se{right:-9px;bottom:-9px;cursor:nwse-resize}
.ic-hint{text-align:center;font-size:12px;color:var(--muted);margin:var(--sp-8) 0 var(--sp-2)}
.ic-reset{background:none;border:none;color:var(--accent-text);font-size:12px;font-weight:600;cursor:pointer;padding:var(--sp-2) var(--sp-4)}
.ic-strip{--gulir-sengaja:1;display:flex;gap:var(--sp-8);overflow-x:auto;padding:var(--sp-10) var(--sp-2) var(--sp-4);-webkit-overflow-scrolling:touch}
.ic-thumb{position:relative;flex:none;width:52px;height:52px;border-radius:10px;overflow:hidden;border:2px solid transparent;padding:0;background:var(--surface-2);cursor:pointer}
.ic-thumb.on{border-color:var(--accent)}
.ic-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.ic-thumb-x{position:absolute;top:1px;right:1px;width:17px;height:17px;border-radius:50%;background:rgba(0,0,0,.6);color:#fff;display:flex;align-items:center;justify-content:center}
.ic-add{flex:none;width:52px;height:52px;border-radius:10px;border:1.5px dashed var(--line);background:var(--surface-2);color:var(--muted);display:flex;align-items:center;justify-content:center;cursor:pointer}
.ic-cap-row{display:flex;align-items:center;gap:var(--sp-8);margin-top:var(--sp-8)}
.ic-caption{flex:1;min-width:0;border:1px solid var(--line);border-radius:22px;padding:var(--sp-10) var(--sp-14);font-size:14px;outline:none;background:var(--surface);color:var(--text)}
.ic-caption:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.ic-send{flex:none;position:relative;width:44px;height:44px;border-radius:50%;border:none;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 2px 8px var(--accent-weak)}
.ic-send:disabled{opacity:.85;cursor:default}
.ic-send-n{position:absolute;top:-4px;right:-4px;min-width:18px;height:18px;padding:0 var(--sp-4);border-radius:9px;background:var(--surface);color:var(--accent-text);border:1.5px solid var(--accent);font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center}
.ic-send-prog{font-size:12px;font-weight:700;letter-spacing:.02em}
@media (max-width:520px){
  .ic-modal{width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;top:0;left:0;transform:none;border-radius:0;border:none;padding:var(--sp-6) var(--sp-14) calc(var(--sp-14) + env(safe-area-inset-bottom))}
  .ic-stage img{max-height:none}
}
/* ---- virtual name card (share to WhatsApp) ---- */
.nc-wrap{display:flex;flex-direction:column;align-items:center;gap:var(--sp-16);padding:var(--sp-x18) var(--sp-16) var(--sp-x26)}
.nc-loading{padding:var(--sp-40);text-align:center;color:var(--muted)}
.nc-card{position:relative;width:100%;max-width:360px;border-radius:22px;padding:var(--sp-x22) var(--sp-20) var(--sp-x18);color:#fff;overflow:hidden;box-shadow:0 12px 34px rgba(0,0,0,.2);background:linear-gradient(150deg,var(--accent),#3a2f9e)}
.nc-top{display:flex;align-items:center;gap:var(--sp-14)}
.nc-card .nc-ava{width:66px;height:66px;flex:none;box-shadow:0 2px 10px rgba(0,0,0,.25);border:2px solid rgba(255,255,255,.6)}
.nc-id{min-width:0}
.nc-name{font-size:20px;font-weight:800;line-height:1.15;word-break:break-word}
.nc-role{font-size:13px;opacity:.92;margin-top:var(--sp-x3);word-break:break-word}
.nc-line{display:flex;align-items:center;gap:var(--sp-8);margin-top:var(--sp-16);font-size:15px;font-weight:600;background:rgba(255,255,255,.16);padding:var(--sp-x9) var(--sp-12);border-radius:12px}
.nc-line .wa-ic{width:18px;height:18px;flex:none}
.nc-qr{margin:var(--sp-16) auto var(--sp-6);width:170px;background:#fff;border-radius:16px;padding:var(--sp-12) var(--sp-12) var(--sp-8);display:flex;flex-direction:column;align-items:center}
.nc-qr svg{width:146px;height:146px;display:block}
.nc-qr-cap{font-size:11px;color:#333;font-weight:600;margin-top:var(--sp-6)}
.nc-brand{text-align:center;font-weight:800;letter-spacing:.06em;opacity:.85;margin-top:var(--sp-10);font-size:13px}
.nc-actions{display:flex;flex-direction:column;gap:var(--sp-10);width:100%;max-width:360px}
.nc-actions .btn{width:100%;justify-content:center}
.nc-share .wa-ic{width:18px;height:18px;flex:none;margin-right:var(--sp-2)}
.nc-hint{display:flex;gap:var(--sp-8);align-items:flex-start;max-width:360px;font-size:12px;color:var(--muted);line-height:1.5}
.nc-hint svg{flex:none;margin-top:var(--sp-1)}
/* public profile link row (tap to copy) */
.nc-linkrow{display:flex;align-items:center;gap:var(--sp-12);width:100%;max-width:360px;background:var(--surface-2);border:1px solid var(--line);border-radius:14px;padding:var(--sp-10) var(--sp-14);cursor:pointer;text-align:left;color:var(--text);transition:border-color .12s,transform .1s}
.nc-linkrow:hover{border-color:var(--accent)}
.nc-linkrow:active{transform:scale(.99)}
.nc-linktxt{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-2)}
.nc-linklbl{display:flex;align-items:center;gap:var(--sp-6);font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.nc-linklbl svg{flex:none}
.nc-url{font-size:13px;font-weight:600;color:var(--accent-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nc-linkrow>svg{flex:none;color:var(--muted)}
/* public link controls (rotate / turn off / turn on) */
.nc-linkbox{display:flex;flex-direction:column;gap:var(--sp-8);width:100%;max-width:360px}
.nc-linkctl{display:flex;gap:var(--sp-8)}
.nc-ctl{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--sp-6);height:38px;border-radius:11px;border:1px solid var(--line);background:var(--surface);color:var(--text);font-size:13px;font-weight:600;cursor:pointer;transition:border-color .12s,transform .1s}
.nc-ctl:hover{border-color:var(--accent)}
.nc-ctl:active{transform:scale(.98)}
.nc-ctl svg{flex:none}
.nc-linkoff{display:flex;align-items:center;gap:var(--sp-10);width:100%;max-width:360px;background:var(--surface-2);border:1px solid var(--line);border-radius:14px;padding:var(--sp-8) var(--sp-8) var(--sp-8) var(--sp-14)}
.nc-offtxt{flex:1;display:flex;align-items:center;gap:var(--sp-8);font-size:13px;font-weight:600;color:var(--muted)}
.nc-offtxt svg{flex:none}
.nc-ctl-on{flex:none;height:38px;padding:0 var(--sp-16);background:var(--accent-weak);border-color:transparent;color:var(--accent-text)}
.nc-ctl-on:hover{border-color:transparent}
.nc-actions2{display:flex;gap:var(--sp-10);width:100%;max-width:360px}
.nc-actions2 .btn{flex:1;min-width:0;justify-content:center}
/* group admin badges */
.gi-badge{display:inline-block;margin-left:var(--sp-6);padding:var(--sp-1) var(--sp-x7);border-radius:8px;font-size:10px;font-weight:700;letter-spacing:.02em;vertical-align:middle;background:var(--accent-weak);color:var(--accent-text)}
.gi-badge-owner{background:#fff3d6;color:#8a5a00}
:root[data-theme="dark"] .gi-badge-owner{background:#4a3a12;color:#f0c674}
.lightbox{position:fixed;inset:0;z-index:300;background:rgba(0,0,0,.9);display:flex;align-items:center;justify-content:center;padding:var(--sp-20);cursor:zoom-out}
.lightbox img{max-width:100%;max-height:100%;border-radius:8px;object-fit:contain}
.lb-close{position:fixed;top:calc(16px + var(--sat));right:16px;z-index:301;width:42px;height:42px;border-radius:50%;background:rgba(0,0,0,.5);color:#fff;display:flex;align-items:center;justify-content:center;border:none;cursor:pointer}
.lb-close:hover{background:rgba(0,0,0,.75)}
.cf-backdrop{position:fixed;inset:0;z-index:400;background:rgba(17,20,28,.42);animation:cfFade .12s ease}
.cf-modal{position:fixed;z-index:401;left:50%;top:50%;transform:translate(-50%,-50%);width:340px;max-width:calc(100vw - 40px);max-height:calc(100dvh - 32px);overflow-y:auto;-webkit-overflow-scrolling:touch;background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:0 24px 64px rgba(0,0,0,.28);padding:var(--sp-20) var(--sp-20) var(--sp-x18);animation:cfPop .15s cubic-bezier(.2,.9,.3,1.15)}
.cf-title{font-size:17px;font-weight:700;color:var(--text)}
.cf-body{margin-top:var(--sp-8);font-size:14px;line-height:1.55;color:var(--muted)}
.cf-actions{display:flex;gap:var(--sp-10);justify-content:flex-end;margin-top:var(--sp-20)}
/* Confirm dialogs were text-only until the export flow needed a password box.
   Give a field inside one room to breathe, and drop its own bottom margin —
   .cf-actions already supplies the gap below. */
.cf-modal .field{margin-top:var(--sp-16);margin-bottom:0}
.cf-actions .btn{min-width:92px;justify-content:center;padding:var(--sp-10) var(--sp-14)}
/* Bergaris, bukan berisi. Aksen dan bahaya kini sama-sama merah; membedakannya
   hanya lewat rona menuntut mata yang jeli, dan tombol hapus bukan tempat untuk itu.
   Beda BENTUK terbaca lebih cepat daripada beda warna. */
.btn-danger{background:transparent;color:var(--danger);border-color:var(--danger);box-shadow:none;font-weight:600}
.btn-danger:hover{background:var(--danger-solid);color:#fff}
.btn-danger:hover{background:var(--danger-solid);filter:brightness(.93)}
/* Amber: aksi merusak tapi tak sefinal Hapus (mis. Kosongkan/Reset workspace) */
.btn-warn{background:transparent;color:#b45309;border-color:#f59e0b;box-shadow:none;font-weight:600}
/* TUGAS-195 (separuh "tombol tidak terbaca") — putih di atas amber-500 terukur 2,15:1, di
   bawah ambang teks BESAR sekalipun (3:1). Dan ini tombol RESET WORKSPACE: kendali paling
   berbahaya di aplikasi, tulisannya paling tidak terbaca justru saat kursor ada di atasnya.
   Amber-700 (#b45309) memberi 5,02:1 — lulus AA untuk teks biasa — dan ia angka yang SUDAH
   dipakai varian tema gelap tepat di bawah, jadi keduanya berhenti berbeda tanpa alasan. */
.btn-warn:hover{background:#b45309;color:#fff}
:root[data-theme="dark"] .btn-warn{color:#fbbf24;border-color:#b45309}
:root[data-theme="dark"] .btn-warn:hover{background:#b45309;color:#fff}
@keyframes cfFade{from{opacity:0}to{opacity:1}}
@keyframes sheetUp{from{opacity:0;transform:translateX(-50%) translateY(12px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}
@keyframes cfPop{from{opacity:0;transform:translate(-50%,-46%) scale(.96)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}
.bubble.me.bubble-edit,.bubble.them.bubble-edit{background:var(--surface);border:1px solid var(--line);color:var(--text);box-shadow:none;width:min(82%,440px)}
.msg-edit-box{display:flex;flex-direction:column;gap:var(--sp-8)}
.msg-edit-ta{width:100%;resize:none;border:1px solid var(--line);border-radius:10px;padding:var(--sp-x9) var(--sp-x11);font:inherit;font-size:14px;background:var(--surface);color:var(--text);line-height:1.4;outline:none}
.msg-edit-ta:focus{border-color:var(--accent)}
.msg-edit-actions{display:flex;gap:var(--sp-8);justify-content:flex-end}
.pf-wrap{width:100%}
.pick-field{display:flex;align-items:center;gap:var(--sp-x9);width:100%;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--text);font:inherit;font-size:14px;text-align:left;cursor:pointer;transition:border-color .12s,box-shadow .12s}
.pick-field:hover{border-color:var(--accent)}
.pick-field:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak);outline:none}
.pick-field svg{color:var(--muted);flex:none}
.pf-val{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-val.pf-ph{color:var(--faint)}
.dp-backdrop,.tp-backdrop{position:fixed;inset:0;z-index:420;background:rgba(17,20,28,.4);animation:cfFade .12s ease}
.dp-panel,.tp-panel{position:fixed;z-index:421;left:50%;top:50%;transform:translate(-50%,-50%);width:320px;max-width:calc(100vw - 32px);max-height:calc(100dvh - 32px);overflow-y:auto;-webkit-overflow-scrolling:touch;background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:0 24px 64px rgba(0,0,0,.28);padding:var(--sp-14);animation:cfPop .15s cubic-bezier(.2,.9,.3,1.15)}
.dp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--sp-8)}
.dp-title{font-size:15px;font-weight:700}
.dp-nav{width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--muted);background:none;border:none;cursor:pointer}
.dp-nav:hover{background:var(--surface-2);color:var(--text)}
.dp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:var(--sp-2)}
.dp-dow{margin-bottom:var(--sp-2)}
.dp-dowc{text-align:center;font-size:11px;font-weight:600;color:var(--faint);padding:var(--sp-4) 0}
.dp-cell{height:38px;border-radius:9px;font-size:13.5px;color:var(--text);background:none;border:none;cursor:pointer;transition:background .1s}
.dp-cell:hover{background:var(--surface-2)}
.dp-cell.today{font-weight:700;color:var(--accent-text)}
.dp-cell.sel{background:var(--accent);color:#fff;font-weight:600}
.dp-cell.sel:hover{background:var(--accent)}
.dp-cell.blank{pointer-events:none}
.dp-foot{display:flex;gap:var(--sp-8);justify-content:flex-end;margin-top:var(--sp-10);border-top:1px solid var(--line);padding-top:var(--sp-10)}
.dp-quick{padding:var(--sp-x7) var(--sp-x13);border-radius:9px;font-size:13px;font-weight:500;color:var(--accent-text);background:var(--accent-weak);border:none;cursor:pointer}
/* Butir 38 — "Hapus" di pemilih tanggal. Ditandai berbeda dari "Hari ini"/"Batal" karena ia
   MENGHAPUS sesuatu, bukan memilih atau membatalkan. Cukup berbeda untuk tidak tertekan tanpa
   sengaja, cukup sepi untuk tidak terbaca sebagai tindakan berbahaya — ia memang tidak. */
/* BUTIR 130 — dulu var(--danger-text, #c0392b). Cadangannya selalu yang terpakai karena
   --danger-text tidak pernah ada, dan cadangan itu MEMANG warna yang dimaksud: --danger
   (#9f1239) terlalu keras untuk tombol yang catatannya sendiri minta "cukup sepi". Jadi yang
   dicabut namanya, bukan warnanya — token yang tidak ada membuat berkas ini mengaku punya
   sistem warna di tempat ia sebenarnya memakai satu angka. Mode gelap sudah punya barisnya
   sendiri di bawah, jadi tak ada yang hilang. */
.dp-clear{margin-right:auto;color:#c0392b}
.dp-clear:hover{background:rgba(192,57,43,.08)}
:root[data-theme="dark"] .dp-clear{color:#fca5a5}
:root[data-theme="dark"] .dp-clear:hover{background:rgba(192,57,43,.22)}
/* Date picker: tappable month/year title → year grid, so old dates aren't a hundred clicks. */
.dp-title-btn{border:none;background:none;font-family:inherit;font-size:15px;font-weight:700;color:var(--text);cursor:pointer;display:inline-flex;align-items:center;gap:var(--sp-x3);padding:var(--sp-4) var(--sp-x9);border-radius:8px}
.dp-title-btn:hover{background:var(--surface-2)}
.dp-title-btn svg{color:var(--muted)}
.dp-ygrid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-6);padding:var(--sp-4) var(--sp-2) var(--sp-2)}
.dp-ycell{height:46px;border:none;background:var(--surface-2);border-radius:11px;color:var(--text);font-size:14.5px;font-weight:600;cursor:pointer;transition:background .1s,color .1s}
.dp-ycell:hover{background:var(--accent-weak);color:var(--accent-text)}
.dp-ycell.sel{background:var(--accent);color:#fff}
.dp-quick:hover{filter:brightness(.97)}
.tp-head{display:flex;align-items:center;justify-content:space-between;font-size:15px;font-weight:700;margin-bottom:var(--sp-8);padding-left:var(--sp-4)}
/* Butir 53 — baris ketik jam bebas. Kotaknya dibuat sepersis .field input supaya panel ini tak
   terasa punya bahasa desain sendiri; tombol Pakai memakai bentuk .dp-quick yang sudah ada. */
.tp-type{display:flex;gap:var(--sp-8);align-items:center;margin-bottom:var(--sp-6)}
.tp-type input{flex:1;min-width:0;border:1px solid var(--line);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:15px;font-weight:600;letter-spacing:.3px;text-align:center;outline:none;background-color:var(--surface);color:var(--text)}
.tp-type input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.tp-type input::placeholder{font-weight:400;letter-spacing:0;color:var(--faint)}
:root[data-theme="dark"] .tp-type input{background-color:var(--surface-2)}
.tp-use{flex:none;padding:var(--sp-10) var(--sp-x15);border-radius:10px;font-size:13px;font-weight:600;color:#fff;background:var(--accent);border:none;cursor:pointer;transition:filter .12s,opacity .12s}
.tp-use:hover:not(:disabled){filter:brightness(.95)}
.tp-use:disabled{opacity:.4;cursor:default}
.tp-hint{font-size:11.5px;color:var(--faint);padding:0 var(--sp-4) var(--sp-8);transition:color .15s}
.tp-hint.tp-bad{color:var(--danger)}
.tp-sub{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);padding:0 var(--sp-4) var(--sp-6);border-top:1px solid var(--line);padding-top:var(--sp-x9)}
.tp-list{max-height:236px;overflow-y:auto;display:flex;flex-direction:column;gap:var(--sp-2);-webkit-overflow-scrolling:touch}
.tp-opt{padding:var(--sp-10) var(--sp-12);border-radius:9px;font-size:14px;color:var(--text);background:none;border:none;text-align:center;cursor:pointer;transition:background .1s;display:flex;align-items:center;justify-content:center;gap:var(--sp-8)}
.tp-opt:hover{background:var(--surface-2)}
.tp-opt.sel{background:var(--accent);color:#fff;font-weight:600}
/* Lencana "diketik". Di dalam baris terpilih ia BERGARIS, bukan pil putih transparan: diukur di
   Chrome, wash putih 24% di atas aksen memberi 3,48:1 — di bawah 4,5:1 yang kita pegang. Teks
   putih penuh dengan garis tipis memberi 5,11:1, sama dengan teks jamnya sendiri, dan kebetulan
   juga lebih rapi: satu blok warna, bukan dua. */
.tp-tag{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;padding:var(--sp-1) var(--sp-x7);border-radius:999px;background:var(--accent-weak);color:var(--accent-text);border:1px solid transparent}
.tp-opt.sel .tp-tag{background:transparent;color:#fff;border-color:rgba(255,255,255,.6)}
.btn-sm{padding:var(--sp-6) var(--sp-12);font-size:13px;border-radius:9px;min-width:0}
.top-actions{position:absolute;right:var(--sp-8);top:var(--sat);bottom:0;display:flex;align-items:center;gap:var(--sp-2)}
.sr-backdrop{position:fixed;inset:0;z-index:150;background:rgba(0,0,0,.4)}
.sr-panel{position:fixed;z-index:151;left:50%;top:50%;transform:translate(-50%,-50%);width:520px;max-width:calc(100% - 32px);max-height:calc(100dvh - 28px);background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:0 24px 64px rgba(0,0,0,.28);display:flex;flex-direction:column;overflow:hidden;animation:cfPop .15s cubic-bezier(.2,.9,.3,1.15)}
.sr-head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-12);padding:var(--sp-14) var(--sp-16);border-bottom:1px solid var(--line);flex:none}
.sr-head .star-head-title{font-size:15px;font-weight:700}
.sr-top{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-12) var(--sp-14);border-bottom:1px solid var(--line);color:var(--muted)}
.sr-top input{flex:1;border:none;background:none;outline:none;font-size:15px;color:var(--text)}
.sr-results{overflow-y:auto;padding:var(--sp-6)}
.sr-hint{padding:var(--sp-x28) var(--sp-x18);text-align:center;color:var(--faint);font-size:14px}
.sr-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);padding:var(--sp-10) var(--sp-12) var(--sp-4)}
.sr-item{width:100%;display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-x9) var(--sp-12);border-radius:10px;text-align:left;transition:background .12s}
.sr-item:hover{background:var(--surface-2)}
.sr-body{flex:1;min-width:0;display:flex;flex-direction:column}
.sr-title{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sr-sub{font-size:12.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sr-ic{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex:none;background:var(--surface-2)}
.sr-item>svg:last-child{color:var(--faint);flex:none}
/* contacts: friend requests + add/search */
/* 16px, not 12px — everything below it (.section-label, .list-row) sits at 16,
   so the button was starting 4px to the left of its own list. */
.contacts-top{padding:var(--sp-12) var(--sp-16) var(--sp-2)}
.contacts-top .btn{width:100%;justify-content:center}
.btn-sm{padding:var(--sp-6) var(--sp-x11);font-size:13px;border-radius:9px}
.req-row,.cs-row{cursor:default}
.req-row:hover,.cs-row:hover{background:none}
.req-actions,.cs-action{display:flex;gap:var(--sp-6);flex:none;margin-left:auto;align-items:center}
.cs-tag{font-size:12px;color:var(--muted);background:var(--surface-2);border:1px solid var(--line);border-radius:20px;padding:var(--sp-x5) var(--sp-10);white-space:nowrap}
.contact-search{display:flex;align-items:center;gap:var(--sp-8);margin:var(--sp-8) var(--sp-12) var(--sp-2);padding:0 var(--sp-12);background:var(--surface);border:1px solid var(--line);border-radius:10px}
.contact-search .cs-ico{color:var(--faint);flex:none;display:flex;align-items:center}
.contact-search input{flex:1;min-width:0;border:none;background:none;padding:var(--sp-x11) 0;outline:none;color:var(--text);font-size:14px}
/* notifications dropdown */
.nt-menu{position:fixed;top:calc(var(--sat) + 52px);right:12px;z-index:101;width:328px;max-width:calc(100vw - 24px);max-height:72dvh;overflow-y:auto;background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:0 14px 44px rgba(0,0,0,.18);padding:var(--sp-6)}
.nt-head{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);padding:var(--sp-10) var(--sp-12) var(--sp-6)}
.nt-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--faint);padding:var(--sp-8) var(--sp-12) var(--sp-4)}
.nt-item{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-8) var(--sp-12);border-radius:10px;width:100%;text-align:left}
.nt-click{cursor:pointer}
.nt-click:hover{background:var(--surface-2)}
.nt-body{flex:1;min-width:0;display:flex;flex-direction:column}
.nt-invite .nt-accept{flex:none;white-space:nowrap;padding:var(--sp-6) var(--sp-12)}
.nt-title{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nt-sub{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nt-empty{padding:var(--sp-x22) var(--sp-16);text-align:center;color:var(--muted);font-size:14px}
/* Switch Unread | Updates di panel notifikasi */
.nt-seg{display:flex;gap:var(--sp-x5);padding:var(--sp-6) var(--sp-8) var(--sp-8)}
.nt-seg button{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--sp-6);padding:var(--sp-x7) var(--sp-10);border:1px solid var(--line);border-radius:9px;background:var(--surface);color:var(--muted);font-size:13px;font-weight:600;cursor:pointer;transition:background .12s ease,color .12s ease}
.nt-seg button.on{background:var(--accent-weak);border-color:transparent;color:var(--accent-text)}
.nt-seg-n{display:inline-flex;align-items:center;justify-content:center;min-width:17px;height:17px;padding:0 var(--sp-x5);border-radius:9px;background:var(--accent);color:#fff;font-size:11px;font-weight:700}
/* butir 108 — baris aksi "Tandai semua sudah dibaca" di tab Pembaruan.
   Ditaruh di ATAS daftar, bukan di bawah: dengan 30 baris, tombol di bawah berarti menggulung
   dulu untuk bisa membereskan sesuatu — dan yang ingin dibereskan justru daftar yang panjang.
   Warnanya sekunder dengan sengaja; ia jalan keluar, bukan aksi utama. */
.nt-actrow{display:flex;justify-content:flex-end;padding:var(--sp-6) var(--sp-12) var(--sp-2)}
.nt-allread{display:inline-flex;align-items:center;gap:var(--sp-x5);font-size:12px;font-weight:600;color:var(--accent-text);background:var(--accent-weak);border:none;border-radius:999px;padding:var(--sp-x5) var(--sp-x11);cursor:pointer;font-family:inherit}
.nt-allread:hover{filter:brightness(.96)}
.nt-allread svg{flex:none}
/* Lebar kolom TIDAK diatur di sini — sengaja.
   flex:1 1 0 dengan min-width:0 berarti semua kolom membagi lebar dengan persis, tanpa sisa.
   Yang memutuskan berapa kolom (dan karenanya seberapa lebar masing-masing) adalah
   desktopStage(), yang memilih jumlah slot supaya lebarnya sedekat mungkin ke lebar ponsel.
   Satu sumber kebenaran, bukan dua angka yang harus dijaga tetap sama. */
.col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;border-right:1px solid var(--line);background:var(--surface)}
/* BUTIR 137 — kolom yang dilebarkan memakai DUA satuan, bukan sebuah angka piksel.
   Angka mati (880px) akan meninggalkan remah abu-abu di ujung kanan begitu lebar layar bukan
   kelipatannya — persis cacat yang butir 143 hapus. Dengan dua satuan, layar tetap terbagi
   habis: CORE.tahapKaskade() sudah menghitung jumlah slotnya termasuk satuan tambahan ini.
   DUA, tidak pernah lebih: "cukup 2 kolom aja jangan lebih, itu titik nyamannya." */
/* v364 — SATU ATURAN, DUA PEMBACA. Sel bilah atas milik kolom yang dilebarkan wajib memakai
   satuan yang SAMA; kalau tidak, tiap nama di KANANNYA bergeser dari kolomnya begitu sebuah
   kolom di-FLIP. Ditulis sebagai satu aturan dan bukan dua yang kebetulan sama-sama '2',
   karena dua angka yang harus sejajar dan masing-masing punya angkanya sendiri akan berpisah. */
.col.col-wide{flex:2 2 0}
/* Tingkat "lebar" dulu MEMATOK dua slot. Di bawah aturan ikut-isi ia jadi LANTAI, bukan
   patokan: ia tetap selebar dua slot saat isinya lebih sempit, dan boleh melebihinya saat
   isinya lebih lebar. Mematoknya lagi akan mengembalikan pemotongan yang baru saja dicabut,
   di satu tingkat saja — dan cacat yang cuma tersisa di satu tempat adalah cacat yang tidak
   ada yang percaya lagi. */
.tb-col.tb-col-wide{flex:0 0 calc(200% / var(--tb-n,1))}
/* Saat dibuka ia jadi LANTAI, bukan patokan: tetap selebar dua slot kalau isinya lebih sempit,
   boleh melebihinya kalau isinya lebih lebar. */
:root.tb-buka .tb-cols > .tb-col.tb-col-wide{flex:0 0 auto;min-width:calc(200% / var(--tb-n,1))}
/* Placeholder boleh melar mengisi sisa ruang — lebih baik ajakan yang ramah daripada
   bidang abu-abu kosong. Ia bukan kolom isi, jadi tidak terikat lebar ponsel. */
.col-ph{min-width:0}
/* Slot sisa. Ikut kelas .col supaya lebarnya PERSIS sama dengan kolom isi — layar
   selalu terbagi habis jadi kolom-kolom seukuran, tidak pernah menyisakan remah sempit. */
/* DISENGAJA — keputusan PEMILIK, 16 Agu 2026: ruang untuk kedalaman yang akan datang.
   Jangan menyembunyikannya, jangan melebarkan kolom untuk menutupnya. Alasan lengkapnya di
   CORE.tahapKaskade() (assets/js/core.js), berikut peringatan bahwa dua orang sudah salah
   membacanya sebagai kerusakan pada hari yang sama. */
.col-empty{background:var(--surface-2)}
.col-ph{background:var(--surface-2)}
.col-ph .empty{color:var(--faint)}
/* #398 — Ruang kosong harus MUNDUR, bukan maju.
   Di mode terang --surface-2 (#fafafa) memang lebih gelap dari --surface (#fff), jadi slot sisa
   otomatis mundur. Di mode gelap skalanya terbalik: --surface-2 (#202027) justru LEBIH TERANG dari
   --surface (#17171c) — akibatnya kolom kosong menyala lebih terang daripada kolom isinya, dan
   monitor lebar terbaca seperti papan mati yang mencolok. Di gelap kita pakai --bg (paling gelap)
   supaya sisanya tenggelam ke belakang, sama seperti perilakunya di mode terang. */
:root[data-theme="dark"] .col-empty,
:root[data-theme="dark"] .col-ph,
:root[data-theme="dark"] .cascade{background:var(--bg)}
.col-brand{flex:0 0 60px;display:flex;flex-direction:column;align-items:center;padding:var(--sp-12) 0;background:var(--surface-2);border-right:1px solid var(--line);cursor:pointer}
.col-brand:hover{background:var(--line)}
/* overflow-x:hidden WAJIB eksplisit. Menyetel satu sumbu jadi auto membuat sumbu lainnya
   dihitung auto juga oleh peramban — jadi kolom ini diam-diam bisa menggulir mendatar.
   Akibatnya: saat bubble digeser untuk membalas, isinya melewati tepi dan scrollbar
   mendatar berkedip muncul selama gerakan (laporan klien 1 Agu). Dalam keadaan diam
   scrollWidth == clientWidth, itu sebabnya cuma "berkedip" dan sulit ditangkap. */
/* TUGAS-168 (koreksi pemilik, 16 Agu 2026) — .col-head DIBUANG SELURUHNYA.
   v363 menaruh nama tingkat di kepala tiap kolom. Pemilik: "ngabis2in tempat". Ia benar —
   di layar yang isinya daftar, tinggi itu baris. Namanya sekarang duduk di bilah atas,
   sejajar dengan kolomnya (.tb-col), dan kolomnya kembali nol tinggi terpakai untuk nama. */
.col-body{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}
.col-foot{flex-shrink:0;min-height:calc(var(--topbar-h) + var(--sab));border-top:1px solid var(--line);background:var(--surface);padding:var(--sp-8) var(--sp-14) calc(var(--sp-8) + var(--sab));display:flex;align-items:center;gap:var(--sp-10)}
/* #391 — Judul besar di badan kolom disembunyikan: judulnya kini dipikul breadcrumb di bilah
   atas (crumb terakhir yang disorot), jadi tak dobel & kolom lebih lega. Pengecualian .col-keep
   untuk judul SPESIFIK yang bukan sekadar nama bagian (mis. tanggal di tampilan hari). */
.col-body .detail-pad > h1:first-child,
.m-pane .detail-pad > h1:first-child{display:none}
.col-body .detail-pad > h1.col-keep,
.m-pane .detail-pad > h1.col-keep{display:block}
/* BUTIR 168 — JUDUL DOBEL. Aturan di atas sudah benar sejak #391, dan ia menjaga SATU bentuk
   pembungkus: .detail-pad. Layar yang isinya sebuah <form> memakai pembungkus lain, jadi
   judulnya lolos. Diukur di 1426px:

       Account > Edit profile       jejak "Edit profile"     · badan "Edit profile"
       Account > Change password    jejak "Change password"  · badan "Change password"
       Workspaces > New             jejak "New"              · badan "Create workspace"

   Dua yang pertama kata per kata sama, dua kali di layar yang sama — persis yang #391 ada
   untuk menghapus. Yang ketiga bukan judul dobel melainkan JEJAKNYA yang miskin, dan itu
   dibetulkan di crumbLabel() supaya judulnya pindah, bukan supaya judul kedua dibiarkan
   hidup sebagai tambalan.

   Sebabnya bukan aturan ini kurang panjang, melainkan ia menyebut BENTUK MARKUP: pembungkus
   berikutnya akan lolos lagi dengan cara yang persis sama. Selama belum ada bentuk ketiga yang
   terukur, ia diperluas ke <form> dengan perilaku yang SAMA PERSIS — satu perilaku di dua
   pembungkus, bukan dua perilaku yang suatu hari berbeda. */
.col-body form > h1:first-child,
.m-pane form > h1:first-child{display:none}
.col-body form > h1.col-keep,
.m-pane form > h1.col-keep{display:block}
/* Panel-head yang cuma berisi alat (navigasi bulan kalender / sort tugas), tanpa judul lagi. */
.panel-head-tools{min-height:0;padding:var(--sp-10) var(--sp-16)}
.panel-head-end{justify-content:flex-end}
/* BUTIR 118 — dua kendali di baris kepala Tugas: pengelompokan di KIRI (dibaca lebih dulu),
   pengurutan di KANAN. Keduanya di baris yang TIDAK menggulir, jadi "Daftar" terlihat di
   tiap lebar berapa pun banyaknya daftar yang dipunyai orang.

   BUTIR 143 — ditambah `flex-wrap:wrap`, dan itu BUKAN pembatalan aturan di atas.
   Yang dilarang butir 118 adalah LABEL di dalam tombol patah dua baris ("Due date" jadi
   "Due" / "date"), karena tinggi barisnya lalu berubah tanpa ada yang memutuskan. Yang
   dilakukan di sini kebalikannya: seluruh segmen pindah utuh ke baris kedua, tak satu huruf
   pun patah.

   Diukur 9 Agu 2026 di layar 320px, sebelum perubahan:

       tersedia 256px   dibutuhkan 272px (Inggris) / 287px (Indonesia)

   Kekurangan itu membuat .m-pane — SELURUH daftar tugas, bukan cuma kepalanya — bisa digeser
   ke samping. Menyempitkan rel saja tidak menutupnya: rel 56 memberi 264px, rel 48 pun cuma
   272px, masih kurang 15px untuk Indonesia. Karena itu kepalanya sendiri yang harus bisa
   mengalah.

   Kenapa `wrap` dan bukan memangkas padding tombol: pangkasan menghasilkan angka yang pas
   HARI INI, untuk DUA bahasa yang kebetulan ada sekarang. `wrap` mengukur dirinya sendiri —
   ia benar untuk panjang label apa pun, bahasa apa pun, ukuran huruf sistem apa pun, tanpa
   satu pun media query. Harganya: di 320px kepala Tugas jadi dua baris. Itu pertukaran yang
   dipilih sadar — satu baris tambahan di ponsel tersempit lebih baik daripada daftar yang
   diam-diam bisa digeser ke samping.

   `max-width:max-content` menjaga segmennya TIDAK melar saat ia sendirian di baris kedua;
   tanpa itu `flex:1 1 auto` akan menarik ketiga tombol selebar kolom. */
.panel-head-split{justify-content:space-between;gap:var(--sp-8);flex-wrap:wrap}
.panel-head-split .tv-manage{flex:none}
.panel-head-split .seg-sort{flex:1 1 auto;max-width:max-content}
.menu-row.active{background:var(--accent-weak)}

/* ---------- MOBILE sliding ---------- */
.m-shell{display:flex;width:100%;height:100%}
.m-rail{cursor:pointer;width:var(--rail-w);flex-shrink:0;background:var(--surface-2);border-right:1px solid var(--line);display:flex;flex-direction:column;align-items:center;padding:var(--sp-12) 0;gap:var(--sp-10);overflow-y:auto}
.m-rail[data-act]{cursor:pointer}
.m-back{display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);font-size:10px;color:var(--faint);width:100%;padding:var(--sp-2) 0}
/* BUTIR 143 — dua ukuran, satu aturan, dipakai KEDUA rel:
       AKSI    44px  .co-home · .co-add · .m-mini   (lantai sasaran sentuh)
       IDENTITAS 40px  .co-logo · .m-mini-ava       (avatar; sisa 8px tiap sisi di rel 56)
   Sebelum ini .m-mini 42 dan .m-mini-ava 38 — keduanya di bawah lantai 44 sejak awal, dan
   menyempitkan rel dengan mempertahankan PROPORSI akan menurunkannya lagi ke 37/33. Jadi
   angkanya dinaikkan, bukan diproporsikan. Avatar sengaja berhenti di 40 dan tidak ikut 44:
   di 44 ia menyisakan 6px tiap sisi dan rel chat berubah jadi dinding lingkaran; 40 adalah
   angka yang sudah dipakai .co-logo, jadi kedua rel akhirnya berbicara ukuran yang sama. */
.m-mini{position:relative;width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;color:var(--faint)}
/* BUTIR PAPAN 78 — lencana hitung di atas ikon rel. Kembaran .co-ava-badge yang sudah dipakai
   rel workspace, termasuk cincin var(--bg)-nya: tanpa cincin itu angka merah menempel langsung
   ke ikonnya dan keduanya saling memakan bentuk. pointer-events:none supaya lencananya tidak
   pernah menelan ketukan yang dimaksudkan untuk tombolnya. */
.m-mini-badge{position:absolute;top:-2px;right:-3px;min-width:17px;height:17px;padding:0 var(--sp-4);border-radius:9px;background:var(--danger-solid);color:#fff;font-size:10px;font-weight:800;line-height:17px;text-align:center;box-shadow:0 0 0 2px var(--bg);pointer-events:none}
.m-mini.on{background:var(--accent-weak);color:var(--accent-text)}
.m-mini-ava{width:40px;height:40px;font-size:12px;background:var(--surface);border:1px solid var(--line);color:var(--muted)}
.m-mini-ava.on{outline:2px solid var(--accent);color:var(--accent-text)}
.m-rail .m-mini:active,.m-rail .m-back:active{transform:scale(.92)}
.m-rail .m-mini:not(.on):hover{background:var(--line)}
/* Rel bisa panjang (ratusan chat/kontak) hingga menggulung; tombol kembali DIBEKUKAN di puncaknya
   supaya jalan "naik satu tingkat" selalu terjangkau tanpa harus menggulung ke atas dulu. Baris
   lainnya tetap menggulung; latar solid + bayangan menutup ikon yang lewat di baliknya. */
.m-rail .m-back{position:sticky;top:0;z-index:3;background:var(--surface-2);padding-top:var(--sp-x7);padding-bottom:var(--sp-x15);box-shadow:0 6px 7px -6px rgba(15,23,42,.22)}
/* #390 — tutup celah 12px di ATAS tombol kembali yang dibekukan. Sticky menempel di tepi
   dalam-padding kolom, jadi padding-atas rel jadi pita tembus tempat avatar mengintip saat
   digulir. Palang solid selebar rel menutup pita itu. */
.m-rail .m-back::before{content:'';position:absolute;left:0;right:0;top:-12px;height:12px;background:var(--surface-2)}
.m-focus{flex:1;background:var(--surface);overflow:hidden;display:flex;flex-direction:column;min-width:0}
.m-pane{flex:1;overflow-y:auto;animation:slideIn .24s ease;min-height:0}
.m-pane.back{animation:slideBack .24s ease}
.m-pane.noanim{animation:none}
@keyframes slideIn{from{transform:translateX(20%);opacity:.3}to{transform:translateX(0);opacity:1}}
@keyframes slideBack{from{transform:translateX(-12%);opacity:.3}to{transform:translateX(0);opacity:1}}

/* ---------- BUTIR PAPAN 80 — transisi kolom desktop ----------
   Sampai v299 `.col` tidak punya SATU PUN aturan animasi: di layar lebar, membuka sebuah
   percakapan berarti seluruh layar digambar ulang seketika. Tak ada yang memberi tahu mata
   bahwa sesuatu terbuka DI SEBELAH KANAN dari yang sedang dilihat — padahal itu seluruh
   model layoutnya, dan ponsel sudah punya isyaratnya sejak lama lewat .m-pane.

   Tiga gerak untuk tiga kejadian, dan pembedanya bukan selera:
     col-dive  kolom baru datang dari kanan  -> geser masuk; matanya mengikuti kedalaman
     col-back  kolom terdalam ditutup        -> geser kecil dari kiri; lebih pendek, karena
                                                kembali seharusnya terasa lebih ringan
                                                daripada masuk
     col-swap  kedalaman sama, isi berganti  -> HANYA pudar, tidak bergeser sedikit pun.
               Memberi gerak geser di sini adalah berbohong dengan animasi: matanya diberi
               tahu "kamu berpindah tingkat" padahal tingkatnya sama persis.

   Jaraknya sengaja kecil (14% / 8%, bukan 100%): kolom itu SUDAH ada di tempatnya, gerak
   ini cuma memberi arah — bukan mengantar kolomnya dari luar layar. Animasi yang menempuh
   seluruh lebar layar terasa lambat pada pemakaian kedua, dan orang memakai ini ratusan
   kali sehari. `.cascade` sudah overflow:hidden, jadi tak ada yang bocor ke kanan.

   Kurvanya ease-out (.22,.61,.36,1): cepat di awal, melambat di akhir — gerak yang terasa
   "tiba", bukan "meluncur". Linear terasa mekanis; ease-in-out terasa malas di awal. */
/* ============================================================================
   KONTRAK GERAK — ANGKANYA DI SINI, DAN GERBANGNYA MENEGAKKAN ANGKA INI
   ============================================================================
   Asisten Direktur menulis kontraknya "180-220 ms panel · 120-160 ms pop-up". Yang tayang
   240 ms, dan gerbangnya menegakkan aturan yang BERBEDA dan lebih longgar (<= 0,30 detik).
   Jadi 240 disahkan tanpa ada yang pernah memutuskannya.

   AD mencabut angka 180-220 pada 15 Agustus 2026 dengan alasan yang layak dicatat: ia
   menuliskannya tanpa pernah mengukurnya. 240 ms tidak salah — yang salah adalah gerbang
   yang menegakkan aturan yang berbeda dari naskah yang melahirkannya, karena itu membuat
   naskahnya jadi hiasan dan pelanggarannya tidak akan pernah berbunyi.

     >> Gerbang yang lebih longgar daripada aturannya bukan gerbang yang lunak —
        ia gerbang yang menjaga aturan yang BERBEDA, diam-diam.

   Baris di bawah DIBACA MESIN oleh tests/js/run.js (cari "KONTRAK GERAK"). Mengubah salah
   satu angka tanpa mengubah pasangannya di CSS membuat gerbangnya merah, ke dua arah.

   KONTRAK-GERAK panel-masuk=240 panel-kembali=190 panel-tukar=150 popup-min=120 popup-maks=160 chat-lebar=220
   ============================================================================ */
.col-dive{animation:colDive .24s cubic-bezier(.22,.61,.36,1)}
.col-back{animation:colBack .19s cubic-bezier(.22,.61,.36,1)}
.col-swap{animation:colSwap .15s ease-out}
@keyframes colDive{from{transform:translateX(14%);opacity:0}to{transform:none;opacity:1}}
@keyframes colBack{from{transform:translateX(-8%);opacity:.35}to{transform:none;opacity:1}}
@keyframes colSwap{from{opacity:.3}to{opacity:1}}

/* Orang yang meminta gerak dikurangi tetap mendapat perpindahannya — cuma tanpa gerak.
   Dan .m-pane ikut di sini: ponsel beranimasi sejak lama TANPA pernah menghormati
   permintaan ini. Itu bukan bagian dari butir 80, itu lubang yang kebetulan terlihat
   saat menuliskan barisnya, dan menutupnya lebih murah daripada mencatatnya. */
@media (prefers-reduced-motion:reduce){
  .col-dive,.col-back,.col-swap,.m-pane,.m-pane.back{animation:none}
}

/* ============================================================================
   v390 — MELEBARKAN / MENYEMPITKAN OBROLAN: LEBARNYA YANG BERGERAK, BUKAN ISINYA
   ============================================================================
   Melebarkan obrolan BUKAN navigasi: tingkatnya sama, isinya sama, yang berubah cuma
   berapa satuan lebar satu bingkai pakai. Karena itu ia TIDAK memakai .col-dive/.col-back
   /.col-swap — ketiganya menceritakan perpindahan tingkat, dan menceritakannya di sini
   adalah berbohong dengan animasi.

   Yang dianimasikan `flex-grow`: angka yang sama yang CORE.tahapKaskade() bagi-bagikan.
   Tiap keadaan antaranya adalah TATA LETAK SUNGGUHAN, jadi tidak ada huruf yang melar —
   berbeda dengan FLIP, yang menganimasikan dengan scaleX dan melarkan teks kolom obrolan
   selama seluruh animasi.

   Kelasnya dipasang PADA WADAHNYA dan cuma selama animasi berjalan. Transisi yang menetap
   akan ikut menghaluskan perubahan lebar yang BUKAN pelebaran — mengubah ukuran jendela,
   misalnya — dan menggambar ulang layar yang sama tidak boleh beranimasi.
   ============================================================================ */
.cascade.kas-lebar > .col{transition:flex-grow .22s cubic-bezier(.22,.61,.36,1)}
/* BILAH REMAH IKUT, DAN DENGAN KURVA SERTA DURASI YANG SAMA PERSIS.
   Ia menerbitkan pembagian yang sama dengan kolom di bawahnya (--tb-n = jumlah slot, ruas
   yang dilebarkan 200%/n). Sampai 22 Agustus ia MELOMPAT ke lebar barunya seketika sementara
   kolomnya meluncur 220 md — terukur: pada frame pertama sesudah diketuk, ruasnya sudah
   457/913 sementara kolomnya masih 457/457/457.
     >> Dua baris yang menggambarkan pembagian yang sama harus berpindah bersama; kalau satu
        melompat dan satu meluncur, selama 220 md keduanya menyebut batas yang berbeda.
   Geometrinya berbeda (kolom membagi dengan flex-grow, ruas remah dengan flex-basis), jadi
   sifat yang ditransisikan pun berbeda. Yang WAJIB sama cuma kurva dan durasinya — kalau
   tidak, keduanya berpindah bersama tapi tidak sejalan, dan itu lebih mengganggu daripada
   melompat. */
.tb-cols.kas-lebar > .tb-col{transition:flex-basis .22s cubic-bezier(.22,.61,.36,1)}
.tb-cols > .tb-col-hantu{overflow:hidden;pointer-events:none}
/* Klon kolom yang PERGI ke rel, hidup hanya selama animasi. `overflow:hidden` supaya isinya
   ikut terpotong saat lebarnya menuju nol, bukan tumpah ke tetangganya; `pointer-events:none`
   supaya ia tidak pernah bisa diketuk — ia gambar, bukan kolom. */
.cascade > .col-hantu{overflow:hidden;pointer-events:none}
@media (prefers-reduced-motion:reduce){
  .cascade.kas-lebar > .col,.tb-cols.kas-lebar > .tb-col{transition:none}
}

/* ============================================================================
   TUGAS-80 syarat 4 — POP-UP IKUT MENGHORMATI "KURANGI GERAK"
   ============================================================================
   Sampai 15 Agustus 2026 blok di atas mencakup PANEL saja. Empat belas blok
   prefers-reduced-motion tersebar di berkas ini, dan tidak satu pun menyentuh lapisan
   pop-up: modal, lembar, menu konteks, pemilih tanggal/jam, panel emoji, kartu peta.

   Yaitu tempat yang justru PALING SERING muncul tiba-tiba di tengah layar.

   Ini bukan kerapian. Orang yang menyalakan setelan itu biasanya menyalakannya karena
   gerak membuatnya pusing atau mual — dan kita menghormatinya di perpindahan kolom, lalu
   melemparkan modal yang meletus dari tengah layar tepat di depan matanya.

   Latarnya (cfFade) IKUT dimatikan, bukan disisakan: pudar-masuk selama 0,12 detik pada
   bidang gelap seukuran layar penuh adalah perubahan kecerahan besar, dan itu justru salah
   satu pemicu yang paling sering disebut.

   Yang TIDAK ikut di sini, dan itu keputusan: .btn-spin (baris ~3050) sudah punya
   perlakuannya sendiri — diperlambat, bukan dihentikan, karena penunggu yang berhenti
   berputar terbaca sebagai aplikasi yang menggantung.
   ============================================================================ */
@media (prefers-reduced-motion:reduce){
  .cf-backdrop,.cf-modal,
  .emo-backdrop,.emo-panel,
  .mm-sheet,
  .dp-backdrop,.tp-backdrop,.dp-panel,.tp-panel,
  .sr-panel,
  .picker-back,.picker-sheet,
  .mapc-back,.mapc,
  .mm-backdrop,.sr-backdrop,
  .gi-modal,
  .co-menu,
  .mm-sheet.mm-pop{animation:none}
}

/* ---------- home menu (mobile) ---------- */
.home-h{padding:var(--sp-x22) var(--col-teks) var(--sp-12)}
.home-h .hi{font-size:20px;font-weight:700}
.home-h .date{color:var(--muted);font-size:14px;margin-top:var(--sp-2)}
/* Marginnya DITURUNKAN, tidak diketik: chrome kartu (garis 1px + padding) menonjol keluar ke
   selokan, supaya ISI kartu mendarat di --col-teks yang sama dengan baris menu di bawahnya.
   var(--sp-1) di sini adalah tebal garisnya, bukan sebuah langkah jarak — ia ditulis lewat
   skala karena gerbang skala membaca deklarasi ini, dan 1px telanjang di sebuah margin tidak
   bisa ia bedakan dari jarak yang lupa diskalakan. */
.today-card{margin:var(--sp-2) calc(var(--col-teks) - var(--sp-6) - var(--sp-1)) var(--sp-14);background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:var(--sp-6);box-shadow:0 1px 2px rgba(0,0,0,.04)}
.today-head{font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);padding:var(--sp-x9) var(--sp-10) var(--sp-x5)}
.td-item{width:100%;display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-x9) var(--sp-10);border-radius:11px;text-align:left;color:inherit;transition:background .12s}
.td-item:hover{background:var(--surface-2)}
.td-item>svg:last-child{color:var(--faint);flex-shrink:0}
.td-time{flex-shrink:0;width:40px;font-size:12px;font-weight:600;color:var(--muted);display:flex;align-items:center;justify-content:center}
.td-time.td-task{color:var(--accent-text)}
.td-dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}
.td-dot.ev{background:var(--accent)}
.td-dot.tk{background:#e8811a}
.td-title{flex:1;min-width:0;font-size:14px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.td-item .due{margin-top:0;flex-shrink:0}
/* Jalan keluar dari daftar, bukan baris ke-N di dalamnya — jadi bentuknya berbeda: tanpa
   kolom jam, tanpa titik berwarna, dan dipisah garis tipis supaya terbaca sebagai kaki kartu.
   Hitungannya (kiri) memakai warna keterangan karena ia FAKTA; ajakannya (kanan) memakai
   --accent-text karena ia yang bisa ditekan. Diukur dengan mesin kontras proyek sendiri:
   6,06:1 terang, 9,61:1 gelap. */
.td-more{border-top:1px solid var(--line);border-radius:0 0 11px 11px;margin-top:var(--sp-2);padding-top:var(--sp-x11)}
.td-more .td-title{color:var(--muted);font-weight:500;flex:1;min-width:0}
.td-more-go{flex:none;color:var(--accent-text);font-weight:600;font-size:13px}
.td-more>svg:last-child{color:var(--accent-text)}
.menu-row{width:100%;display:flex;align-items:center;gap:var(--sp-14);padding:var(--sp-x15) var(--col-teks);border-bottom:1px solid var(--line);text-align:left;transition:background .12s}
.menu-row:hover{background:var(--surface-2)}
.menu-ico{width:44px;height:44px;border-radius:12px;background:var(--accent-weak);color:var(--accent-text);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.menu-row .t{font-size:15px;font-weight:600}
.menu-row .s{font-size:13px;color:var(--muted);margin-top:var(--sp-1)}
/* color-coded icons (foreground everywhere; soft tint behind the rounded menu squares) */
.ic-chats{color:#0a5cbf}.ic-tasks{color:#177245}.ic-calendar{color:#a4661a}.ic-contacts{color:#0e6f77}.ic-companies{color:#4553b3}.ic-account{color:#5b3fb0}.ic-attendance{color:#0e766e}
.ic-edit{color:#0e6f77}.ic-card{color:#4553b3}.ic-password{color:#c0392b}.ic-whatsnew{color:#b23a5b}.ic-notifications{color:#b45309}.ic-feedback{color:#7c3aed}.ic-developer{color:#be185d}.ic-devices{color:#2563a8}
.menu-ico.ic-chats{background:#e7f1fd}.menu-ico.ic-tasks{background:#e9f7ef}.menu-ico.ic-calendar{background:#fdf0e2}.menu-ico.ic-contacts{background:#e6f6f7}.menu-ico.ic-companies{background:#ececfb}.menu-ico.ic-account{background:#f0ecfb}.menu-ico.ic-attendance{background:#dcf5f0}
.menu-ico.ic-edit{background:#e6f6f7}.menu-ico.ic-card{background:#ececfb}.menu-ico.ic-password{background:#fdecea}.menu-ico.ic-whatsnew{background:#fbeaf0}.menu-ico.ic-notifications{background:#fdf0e2}.menu-ico.ic-feedback{background:#f2ecfd}.menu-ico.ic-developer{background:#fce7f0}.menu-ico.ic-devices{background:#e7f1fd}
.ic-report{color:#177245}.ic-insights{color:#7c3aed}.ic-managers{color:#0e6f77}.ic-more{color:#b45309}
.menu-ico.ic-report{background:#e9f7ef}.menu-ico.ic-insights{background:#f2ecfd}.menu-ico.ic-managers{background:#e6f6f7}.menu-ico.ic-more{background:#fdf0e2}
/* Verifikasi dua langkah. Tanpa aturan ini ia jatuh ke --accent-weak/--accent-text,
   yaitu merah aksen aplikasi — warna yang disediakan untuk tindakan utama, bukan untuk
   satu baris menu. Hijau dipilih karena maknanya aman, bukan karena giliran warna. */
.ic-twofactor{color:#177245}
.menu-ico.ic-twofactor{background:#e9f7ef}
/* Ikon menu yang dulu tak punya pasangan warna sendiri jatuh ke --accent-weak/--accent-text —
   di mode gelap itu jadi kotak cokelat gelap yang nyaris tak terbaca (Guide, FAQ, Storage, dll).
   Diberi pasangan pastel + warna seperti ikon lainnya, supaya tetap colourful & jelas di terang & gelap. */
.ic-docs{color:#2563a8}.menu-ico.ic-docs{background:#e7f1fd}
.ic-faq{color:#7c3aed}.menu-ico.ic-faq{background:#f2ecfd}
.ic-storage{color:#0e766e}.menu-ico.ic-storage{background:#dcf5f0}
.ic-referral{color:#be185d}.menu-ico.ic-referral{background:#fce7f0}
.ic-activity{color:#b45309}.menu-ico.ic-activity{background:#fdf0e2}
.ic-deverrors{color:#c0392b}.menu-ico.ic-deverrors{background:#fdecea}
.ic-devreferral{color:#be185d}.menu-ico.ic-devreferral{background:#fce7f0}

/* ---- Devices / sessions ---- */
.dev-sess-list{margin-top:var(--sp-6)}
.dev-sess{display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-12) var(--sp-2);border-bottom:1px solid var(--line)}
.dev-sess-ic{width:38px;height:38px;flex:none;border-radius:10px;background:#e7f1fd;color:#2563a8;display:flex;align-items:center;justify-content:center}
.dev-sess-b{flex:1;min-width:0;display:flex;flex-direction:column}
.dev-sess-name{font-size:14.5px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:var(--sp-8);flex-wrap:wrap}
.dev-sess-cur{font-size:11px;font-weight:700;color:#177245;background:#17724518;padding:var(--sp-1) var(--sp-8);border-radius:20px}
.dev-sess-sub{font-size:12px;color:var(--muted);margin-top:var(--sp-2)}
/* ---------- contacts ---------- */
.contact-invite{display:flex;align-items:center;gap:var(--sp-12);margin:var(--sp-4) var(--sp-12) var(--sp-12);padding:var(--sp-12) var(--sp-14);background:var(--accent-weak);border:1px solid var(--line);border-radius:14px}
.contact-invite .ci-ico{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:11px;background:var(--surface);color:var(--accent-text);flex:none}
.contact-invite .ci-text{display:flex;flex-direction:column;flex:1;min-width:0}
.contact-invite .ci-title{font-weight:600;font-size:13.5px;color:var(--text)}
.contact-invite .ci-sub{font-size:12px;color:var(--muted)}
.ci-code{display:inline-flex;align-items:center;gap:var(--sp-6);font-weight:700;letter-spacing:.06em;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--surface);color:var(--accent-text);border:1px solid var(--accent);border-radius:9px;padding:var(--sp-x7) var(--sp-10);flex:none}
.ci-code:hover{background:var(--accent-weak)}

/* ---------- chat ---------- */
.chat-top{position:sticky;top:0;z-index:3;background:var(--surface)}
.chat-head{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-10) var(--sp-16);border-bottom:1px solid var(--line);background:var(--surface)}
.chat-body{padding:var(--sp-16);display:flex;flex-direction:column;gap:var(--sp-8)}
.bubble{max-width:74%;padding:var(--sp-x9) var(--sp-x13);border-radius:16px;font-size:14px;line-height:1.45;word-wrap:break-word;box-shadow:0 1px 2px rgba(0,0,0,.05)}
.bubble .who{display:block;font-size:11px;font-weight:600;color:var(--accent-text);margin-bottom:var(--sp-x3)}
/* butir 110 — tiap orang punya warna nama sendiri, warna yang SAMA dengan avatarnya
   (CORE.warnaOrang() memilih indeksnya untuk keduanya, jadi tak mungkin berbeda).
   Warnanya PENGUAT, bukan tanda satu-satunya: namanya tetap ditulis penuh dan avatarnya
   tetap berhuruf awal, karena sekitar satu dari dua belas laki-laki sulit membedakan warna.
   Semua nilai di bawah DIUKUR terhadap latar bubble (.bubble.them = --surface-2) di kedua
   tema dan digerbang butir 51. Deret mode gelap ADA karena warna gelap avatar dipakai apa
   adanya di sana cuma 2,13–2,98:1 — DIUKUR, bukan dikira.
   Terang (di atas #fafafa): 6,10 · 5,70 · 5,51 · 5,74 · 7,23 · 5,65 · 5,21 · 7,28
   Gelap  (di atas #202027): 7,31 · 7,53 · 7,16 · 7,59 · 6,99 · 7,48 · 7,13 · 7,70 */
.bubble.them .who-c0{color:#0a5cbf}
.bubble.them .who-c1{color:#177245}
.bubble.them .who-c2{color:#b23a5b}
.bubble.them .who-c3{color:#8d5712}
.bubble.them .who-c4{color:#5b3fb0}
.bubble.them .who-c5{color:#0e6f77}
.bubble.them .who-c6{color:#c0392b}
.bubble.them .who-c7{color:#465569}
:root[data-theme="dark"] .bubble.them .who-c0{color:#7db1f5}
:root[data-theme="dark"] .bubble.them .who-c1{color:#5ec48f}
:root[data-theme="dark"] .bubble.them .who-c2{color:#f28fab}
:root[data-theme="dark"] .bubble.them .who-c3{color:#e0a75a}
:root[data-theme="dark"] .bubble.them .who-c4{color:#b39ef0}
:root[data-theme="dark"] .bubble.them .who-c5{color:#57bfc9}
:root[data-theme="dark"] .bubble.them .who-c6{color:#f0938a}
:root[data-theme="dark"] .bubble.them .who-c7{color:#a8b4c4}
/* ============================================================================
   BUTIR 144 — GELEMBUNG GRUP DIBERI WARNA AKSEN PENGIRIMNYA, TIPIS SAJA.
   ============================================================================
   Permintaan pemilik 7 Agu: "di grup chat, chat bubble memang harus berwarna sesuai aksen
   user nya deh. warnanya tipis-tipis aja, itu sangat membantu lho." Alasannya benar: di grup,
   mata mencari SIAPA lebih dulu daripada APA, dan warna melakukannya lebih cepat daripada nama.

   DIPASANG DI KELAS YANG SUDAH ADA, bukan di kelas baru. `.who-cN` sudah menentukan warna nama
   pengirim, dan nomornya berasal dari CORE.warnaOrang() yang sama dengan warna avatar (butir
   110). Menggantungkan latar gelembung pada kelas itu juga berarti wajah, nama, dan gelembung
   satu orang TIDAK MUNGKIN memilih warna berbeda — mereka satu sumber, bukan tiga yang harus
   dijaga tetap sama.

   KENAPA color-mix DAN BUKAN DELAPAN WARNA BARU: "tipis-tipis aja" adalah syarat, bukan
   selera, dan cara paling aman memenuhinya adalah menurunkan latarnya DARI warna nama yang
   kontrasnya sudah diukur — bukan memilih delapan warna baru dengan mata. 8% di terang dan
   14% di gelap; gelap butuh lebih banyak karena selisih terangnya sendiri lebih kecil.

   AMBANGNYA DIJAGA ALAT, BUKAN MATA. Latar ini menggeser kontras teks pesan (var(--text)) dan
   kontras nama pengirim di atasnya. Keduanya diukur gerbang kontras butir 51 yang membaca
   app.css apa adanya dan mengomposisikan alfa — bukan diperiksa dengan melihat layar.
   ============================================================================ */
.bubble.them.who-b0{background:color-mix(in srgb,#0a5cbf 8%,var(--surface-2))}
.bubble.them.who-b1{background:color-mix(in srgb,#177245 8%,var(--surface-2))}
.bubble.them.who-b2{background:color-mix(in srgb,#b23a5b 8%,var(--surface-2))}
.bubble.them.who-b3{background:color-mix(in srgb,#8d5712 8%,var(--surface-2))}
.bubble.them.who-b4{background:color-mix(in srgb,#5b3fb0 8%,var(--surface-2))}
.bubble.them.who-b5{background:color-mix(in srgb,#0e6f77 8%,var(--surface-2))}
.bubble.them.who-b6{background:color-mix(in srgb,#c0392b 8%,var(--surface-2))}
.bubble.them.who-b7{background:color-mix(in srgb,#465569 8%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b0{background:color-mix(in srgb,#7db1f5 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b1{background:color-mix(in srgb,#5ec48f 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b2{background:color-mix(in srgb,#f28fab 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b3{background:color-mix(in srgb,#e0a75a 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b4{background:color-mix(in srgb,#b39ef0 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b5{background:color-mix(in srgb,#57bfc9 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b6{background:color-mix(in srgb,#f0938a 14%,var(--surface-2))}
:root[data-theme="dark"] .bubble.them.who-b7{background:color-mix(in srgb,#a8b4c4 14%,var(--surface-2))}
.bubble .btxt{white-space:pre-wrap}
.btxt code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;background:rgba(0,0,0,.06);padding:var(--sp-1) var(--sp-x5);border-radius:5px}
.bubble.me .btxt code{background:rgba(255,255,255,.22)}
.btxt del{opacity:.75}
.mention{color:var(--accent-text);font-weight:600}
.bubble.me .mention{color:#fff;text-decoration:underline;text-underline-offset:2px}
.mention.me{background:var(--accent-weak);color:var(--accent-text);border-radius:5px;padding:0 var(--sp-x3)}
.bubble.me .mention.me{background:rgba(255,255,255,.26);color:#fff;text-decoration:none}
.composer-wrap{position:relative}
.mention-pop{position:absolute;left:10px;right:10px;bottom:calc(100% + 6px);z-index:60;background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:0 12px 40px rgba(0,0,0,.18);padding:var(--sp-x5);max-height:220px;overflow-y:auto}
.mention-opt{display:flex;align-items:center;gap:var(--sp-10);width:100%;padding:var(--sp-8) var(--sp-10);border-radius:10px;background:none;border:none;text-align:left;cursor:pointer}
.mention-opt:hover{background:var(--surface-2)}
.mention-name{font-size:14px;font-weight:500}
.mic-btn{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--muted);background:none;border:none;cursor:pointer;flex:none}
.loc-btn{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--muted);background:none;border:none;cursor:pointer;flex:none}
.loc-btn:hover{color:#0e766e}
.cc-ico-loc{background:#0e766e1a;color:#0e766e}
/* ---- Check-in ↔ task (visit proof) ---- */
.cc-ico-ci{background:#7c3aed1a;color:#7c3aed}
/* Ikon ini adalah CADANGAN saat selfie tak boleh dilihat — dan sejak v266 ia jauh lebih sering
   muncul, karena check-in ber-selfie tersembunyi kini tetap dibagikan (hanya fotonya yang tidak).
   Di mode gelap violet-600 di atas tint 10%-nya sendiri cuma 2,92:1 — di bawah ambang 3:1 untuk
   objek grafis, terukur di Chrome. Dinaikkan ke violet-400, pola yang sama dipakai spanduk
   pasang-PWA (#0284c7 → #38bdf8) beberapa baris di bawah: 5,87:1. */
:root[data-theme="dark"] .cc-ico-ci{background:#7c3aed26;color:#a78bfa}
.cc-selfie{flex:none;width:38px;height:38px;border-radius:9px;overflow:hidden;border:1px solid var(--line);background:var(--line)}
.cc-selfie img{width:100%;height:100%;object-fit:cover;display:block}
/* task form: "Needs check-in" toggle — mengembangkan .chk-inline (lihat catatan di sana) jadi
   dua baris (judul + petunjuk). Spesifisitas .field label.chk-inline.chk-ci (0,3,1) sengaja
   lebih tinggi dari .field label.chk-inline (0,2,1) supaya menang tanpa bergantung urutan berkas. */
.field label.chk-inline.chk-ci{align-items:flex-start}
.field label.chk-inline.chk-ci input{margin-top:var(--sp-1)}
.chk-ci-txt{display:flex;flex-direction:column;gap:var(--sp-2);min-width:0}
.chk-ci-name{display:flex;align-items:center;gap:var(--sp-6);font-weight:600;font-size:14px;color:var(--text)}
.chk-ci-name svg{color:#7c3aed;flex:none}
.chk-ci-hint{font-size:12px;color:var(--muted);line-height:1.4;font-weight:400}
/* capture: "for which task?" picker */
.ci-task{margin:var(--sp-2) 0 var(--sp-10)}
.ci-task-sel{display:flex;align-items:center;gap:var(--sp-8);width:100%;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:11px;background:var(--surface);color:var(--text);font-size:14px;cursor:pointer;text-align:left}
.ci-task-sel .ci-task-ic{display:grid;place-items:center;width:26px;height:26px;border-radius:8px;background:#7c3aed1a;color:#7c3aed;flex:none}
.ci-task-lbl{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ci-task-sel>svg:last-child{flex:none;color:var(--faint)}
.ci-task-sel.has .ci-task-lbl{font-weight:600}
.ci-task-list{margin-top:var(--sp-6);border:1px solid var(--line);border-radius:11px;overflow:hidden;max-height:206px;overflow-y:auto;background:var(--surface)}
.ci-task-opt{display:flex;align-items:center;gap:var(--sp-x9);width:100%;padding:var(--sp-10) var(--sp-12);background:none;border:none;border-bottom:1px solid var(--line);color:var(--text);font-size:13.5px;cursor:pointer;text-align:left}
.ci-task-opt:last-child{border-bottom:none}
.ci-task-opt:hover{background:var(--surface-2)}
.ci-task-opt.on{background:var(--accent-weak)}
.ci-opt-title{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ci-task-opt>svg{flex:none;color:var(--accent)}
.ci-none-dot{width:9px;height:9px;border-radius:50%;border:1.5px solid var(--faint);flex:none}
/* task detail: visits (visit proof) */
.visits-box{margin-top:var(--sp-14)}
.vs-label{display:flex;align-items:center;gap:var(--sp-6)}
.vs-label svg{color:#7c3aed}
.visit-row{display:flex;align-items:flex-start;gap:var(--sp-x11);padding:var(--sp-x9) 0;border-top:1px solid var(--line)}
.visit-thumb{width:52px;height:52px;flex:none;border-radius:10px;overflow:hidden;border:1px solid var(--line);padding:0;background:var(--line);cursor:pointer}
.visit-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.visit-thumb-none{display:flex;align-items:center;justify-content:center;color:var(--faint);cursor:default}
.visit-main{display:flex;flex-direction:column;gap:var(--sp-x3);min-width:0;flex:1}
.visit-who{display:flex;align-items:center;gap:var(--sp-x7);font-weight:600;font-size:14px;color:var(--text)}
.visit-meta{display:flex;align-items:center;gap:var(--sp-x5);font-size:12px;color:var(--muted)}
.visit-meta svg{flex:none;opacity:.85}
.visit-loc{display:inline-flex;align-items:center;gap:var(--sp-4);align-self:flex-start;font-size:12px;color:#0e766e;background:#0e766e14;border:none;padding:var(--sp-x3) var(--sp-8);border-radius:7px;cursor:pointer}
.visits-empty{font-size:13px;color:var(--muted);padding:var(--sp-8) 0 var(--sp-4);line-height:1.5}
/* ---- Chat polls ---- */
/* BUTIR 143 — `min-width:236px` di sini adalah sebab angka polling terpotong di HP.
   Kartu polling tinggal di dalam .bubble yang max-width-nya 74%. Di lebar 360 gelembungnya
   195px; kartu yang menuntut 236px meluber 54px KELUAR dari gelembungnya dan keluar layar,
   dan yang pertama terpotong adalah .poll-opt-pct karena ia paling kanan — "0%" terbaca
   "0'". Di 320 luberannya 83px dan angkanya hilang sama sekali.
   `min(236px,100%)` menjaga lebar nyaman yang memang dimaksud SELAMA ruangnya ada, dan
   menyerah pada wadahnya saat tidak. Angka polling adalah satu-satunya isi polling yang
   berarti; ia tidak boleh jadi bagian pertama yang dibuang. */
.poll-card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:var(--sp-12);min-width:min(236px,100%);max-width:320px}
.bubble.me .poll-card{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.25)}
.poll-q{display:flex;gap:var(--sp-x7);font-weight:700;font-size:14px;margin-bottom:var(--sp-10);line-height:1.35}
.poll-q svg{flex:none;margin-top:var(--sp-2);color:var(--accent)}
.bubble.me .poll-q svg{color:#fff}
.poll-opt{position:relative;display:block;width:100%;text-align:left;border:1px solid var(--line);background:var(--surface-2);border-radius:9px;padding:0;margin-bottom:var(--sp-x7);overflow:hidden;cursor:pointer}
.bubble.me .poll-opt{border-color:rgba(255,255,255,.25);background:rgba(255,255,255,.1)}
.poll-opt-bar{position:absolute;left:0;top:0;bottom:0;width:0;background:var(--accent-weak);transition:width .35s cubic-bezier(.2,.7,.3,1)}
.bubble.me .poll-opt-bar{background:rgba(255,255,255,.22)}
.poll-opt.voted .poll-opt-bar{background:var(--accent);opacity:.2}
.poll-opt-row{position:relative;display:flex;align-items:center;gap:var(--sp-x7);padding:var(--sp-x9) var(--sp-x11);font-size:13.5px}
.poll-opt-check{width:15px;flex:none;color:var(--accent);display:inline-flex}
.bubble.me .poll-opt-check{color:#fff}
.poll-opt-label{flex:1;min-width:0}
.poll-opt-pct{flex:none;font-weight:700;font-size:12.5px;color:var(--muted)}
.bubble.me .poll-opt-pct{color:rgba(255,255,255,.85)}
.poll-foot{font-size:11.5px;color:var(--muted);margin-top:var(--sp-2)}
.bubble.me .poll-foot{color:rgba(255,255,255,.75)}
.poll-form{display:flex;flex-direction:column;gap:var(--sp-10)}
.poll-form input[name="question"],.poll-form textarea{width:100%;border:1px solid var(--line);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:14px;font-family:inherit;outline:none;background:var(--surface);color:var(--text)}
.poll-form input:focus,.poll-form textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.poll-form textarea{resize:vertical}
.poll-multi{display:flex;align-items:center;gap:var(--sp-8);font-size:13px;color:var(--muted);cursor:pointer}
.poll-multi input{width:16px;height:16px;accent-color:var(--accent)}
/* ---- Tagihan Terbuka (open-book billing) ---- */
.ic-billing{color:#3b6d11}
.menu-ico.ic-billing{background:#eaf3de}
/* Who-can-see-this badges — one colour + icon per role, used on every restricted area */
.vis-badge{display:inline-flex;align-items:center;gap:var(--sp-x5);font-size:10.5px;font-weight:700;padding:var(--sp-x3) var(--sp-8);border-radius:20px;white-space:nowrap;vertical-align:middle;line-height:1.5}
.vis-badge svg{flex:none}
/* Wujud ringkas untuk BARIS DAFTAR: ikon saja, bulat, tanpa kata.
   Kata-katanya pindah ke title + aria-label — jadi ia tetap bisa dibaca pembaca layar dan
   tetap muncul saat kursor berhenti di atasnya, tapi tidak lagi memakan judul barisnya. */
.vis-badge.vis-mini{gap:0;padding:var(--sp-x3);border-radius:50%;margin-left:var(--sp-x7);flex:none}
.vis-dev{color:#6d28d9;background:#7c3aed1a;border:1px solid #7c3aed33}
.vis-owner{color:#b45309;background:#f59e0b1a;border:1px solid #f59e0b33}
.vis-mgr{color:#0f766e;background:#14b8a61a;border:1px solid #14b8a633}
:root[data-theme="dark"] .vis-dev{color:#c4b5fd;background:#7c3aed26;border-color:#7c3aed4d}
:root[data-theme="dark"] .vis-owner{color:#fbbf24;background:#f59e0b26;border-color:#f59e0b4d}
:root[data-theme="dark"] .vis-mgr{color:#5eead4;background:#14b8a626;border-color:#14b8a64d}
.vis-me{color:#4a5261;background:#64748b1a;border:1px solid #64748b33}
:root[data-theme="dark"] .vis-me{color:#cbd5e1;background:#64748b30;border-color:#64748b55}

/* ---- BUTIR 186 — "siapa yang bisa melihat ini", diturunkan dari penjaganya ----
   Bentuknya .vis-badge yang sudah ada; yang baru cuma warnanya. Sengaja SEKELUARGA:
   ini konsep yang sama persis dengan lencana lama, cuma sumbernya yang berbeda (lencana
   lama ditulis tangan per layar, yang ini diturunkan server dari penjaganya).

   DUA tingkat MEMAKAI WARNA LENCANA LAMA YANG ARTINYA SAMA, bukan warna baru:
     .who-pengurus = teal .vis-mgr  ("Attendance managers only")
     .who-sendiri  = slate .vis-me  ("Only you can see this")
   Audiens yang sama dengan dua warna berbeda akan terbaca sebagai dua aturan berbeda.

   Kontras dihitung di atas latar EFEKTIF (alpha ditumpuk ke --surface), bukan di atas
   warna alpha-nya — terang 4,99–6,94 · gelap 7,67–9,82, ambang 4,5. Gerbangnya hidup di
   tests/js/run.js, yang dijalankan skrip promosi TIAP rilis: angka yang ditulis di komentar
   membeku, angka yang dihitung tiap rilis tidak. Kalau salah satu warna di bawah diubah
   dan pasangannya tidak, rilisnya berhenti. */
.who-ws{color:#1d4ed8;background:#3b82f61a;border:1px solid #3b82f633}
.who-pengurus{color:#0f766e;background:#14b8a61a;border:1px solid #14b8a633}
.who-terlibat{color:#4338ca;background:#6366f11a;border:1px solid #6366f133}
.who-peserta{color:#047857;background:#10b9811a;border:1px solid #10b98133}
.who-sendiri{color:#4a5261;background:#64748b1a;border:1px solid #64748b33}
:root[data-theme="dark"] .who-ws{color:#93c5fd;background:#3b82f626;border-color:#3b82f64d}
:root[data-theme="dark"] .who-pengurus{color:#5eead4;background:#14b8a626;border-color:#14b8a64d}
:root[data-theme="dark"] .who-terlibat{color:#a5b4fc;background:#6366f126;border-color:#6366f14d}
:root[data-theme="dark"] .who-peserta{color:#6ee7b7;background:#10b98126;border-color:#10b9814d}
:root[data-theme="dark"] .who-sendiri{color:#cbd5e1;background:#64748b30;border-color:#64748b55}
/* Baris tersendiri — dipakai kalau pilnya berdiri sebagai keterangan sebuah medan/layar,
   bukan menempel di judul. `min-width:0` supaya kalimat panjang ("Kamu dan semua peserta
   obrolan ini") boleh memotong barisnya di kolom 360px, bukan melebarkan kolomnya. */
.who-row{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-top:var(--sp-8);min-width:0}
.who-row .vis-badge{white-space:normal;text-align:left}
/* Muncul dengan tenang saat kalimatnya berganti (mis. centang "sembunyikan" ditekan) —
   pergantian audiens adalah kabar, dan kabar yang muncul tanpa gerakan mudah terlewat. */
@media (prefers-reduced-motion:no-preference){
  .who-row .vis-badge{animation:whoIn .18s ease-out both}
}
@keyframes whoIn{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}

/* ---- Label & catatan pribadi tentang seorang kontak ----
   Delapan warna, latar pastel + teks gelap senada. Ubinnya membawa latarnya sendiri, jadi
   pasangannya sama di terang maupun gelap — pola yang sama seperti .menu-ico. Tiap
   pasangan sudah diperiksa: 5,31–6,77 (ambang 4,5). */
.lbl-chip{display:inline-flex;align-items:center;font-size:12px;font-weight:650;padding:var(--sp-x3) var(--sp-10);border-radius:20px;white-space:nowrap;line-height:1.55;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.lbl-sm{font-size:10.5px;padding:var(--sp-1) var(--sp-x7);font-weight:700}
.lbl-c0{background:#eceef2;color:#4a5261}
.lbl-c1{background:#e7f1fd;color:#0a5cbf}
.lbl-c2{background:#e9f7ef;color:#177245}
.lbl-c3{background:#fdf0e2;color:#8a5510}
.lbl-c4{background:#fdecea;color:#b3271a}
.lbl-c5{background:#f2ecfd;color:#6b31d6}
.lbl-c6{background:#e6f6f7;color:#0e6f77}
.lbl-c7{background:#fbeaf0;color:#a92e56}

.note-card{margin:var(--sp-16) var(--sp-16) 0;padding:var(--sp-14);border:1px solid var(--line);border-radius:14px;background-color:var(--surface-2)}
.note-head{display:flex;align-items:center;gap:var(--sp-x7);font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:var(--sp-10)}
.note-head svg{flex:none;color:var(--accent-text)}
.note-head>span{margin-right:auto}
.note-text{margin:var(--sp-8) 0 0;font-size:14px;line-height:1.5;color:var(--text);white-space:pre-wrap;word-break:break-word}
.note-empty{margin:0;font-size:13px;color:var(--muted)}
.note-edit-btn{margin-top:var(--sp-12)}
.note-card form{display:flex;flex-direction:column}
.note-card input[type="text"],.note-card #note-label,.note-card textarea{width:100%;padding:var(--sp-x9) var(--sp-x11);border:1px solid var(--line);border-radius:10px;background-color:var(--surface);color:var(--text);font:inherit;font-size:14px;outline:none}
.note-card #note-label:focus,.note-card textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.note-card textarea{resize:vertical;min-height:70px;line-height:1.5}
.note-card .co-flabel{margin:var(--sp-10) 0 var(--sp-x5)}
.note-card .co-flabel:first-of-type{margin-top:0}
.note-acts{display:flex;gap:var(--sp-10);justify-content:flex-end;margin-top:var(--sp-14)}
.note-acts .btn{min-width:88px;justify-content:center}

/* Contoh warna. Centangnya hanya terlihat pada yang terpilih — bentuk, bukan cuma
   warna, supaya yang buta warna tetap bisa melihat mana yang aktif. */
.lbl-sws{display:flex;flex-wrap:wrap;gap:var(--sp-8);margin:var(--sp-x9) 0 var(--sp-2)}
.lbl-sw{width:30px;height:30px;border-radius:9px;border:2px solid transparent;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;transition:transform .12s ease,border-color .12s ease}
.lbl-sw svg{opacity:0}
.lbl-sw.on{border-color:currentColor}
.lbl-sw.on svg{opacity:1}
.lbl-sw:hover{transform:scale(1.08)}
.lbl-sw:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* Penyaring label di atas daftar kontak. */
.lbl-filter{--gulir-sengaja:1;display:flex;gap:var(--sp-x7);overflow-x:auto;padding:var(--sp-2) 0 var(--sp-10);scrollbar-width:none}
.lbl-filter::-webkit-scrollbar{display:none}
.lbl-filter .lbl-chip{cursor:pointer;border:1.5px solid transparent;flex:none}
.lbl-filter .lbl-chip.on{border-color:currentColor}
.lbl-filter .lbl-all{background-color:var(--surface-2);color:var(--muted)}

/* ---- Daftar tugas (bentuk Wunderlist) ---- */
/* Satu baris yang digulir mendatar. Di Wunderlist daftar itu hal yang dilihat orang
   sepanjang hari; menyembunyikannya di balik menu membuat orang berhenti berpindah. */
.tv-bar{--gulir-sengaja:1;display:flex;gap:var(--sp-x7);overflow-x:auto;padding:var(--sp-2) 0 var(--sp-10) var(--sp-16);scrollbar-width:none;-webkit-overflow-scrolling:touch}
.tv-bar::-webkit-scrollbar{display:none}
.tv-chip{flex:none;display:inline-flex;align-items:center;gap:var(--sp-x5)}
.tv-chip svg{flex:none}
.tv-n{font-size:11px;font-weight:700;opacity:.75;margin-left:var(--sp-x3)}
/* Tepi kanan yang memudar: 725px isi deret ini tersembunyi di kolom 453px, dan tanpa
   tanda apa pun orang tidak tahu ada yang bisa digulir. Diukur di demo, bukan diduga. */
.tv-bar{position:relative;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent)}
/* BUTIR 195 — "+ List" BERHENTI TERLIHAT SEPERTI SLOT KOSONG (v386).
   Pemilik menulisnya 10-11 Agustus ("button +newlist itu kok kayak bukan button ya") dan
   menagihnya lagi 18 Agustus ("harusnya button warna orange").

   Sebabnya bukan selera, dan bukan cuma `dashed`:
     .chip       garis --line tipis · teks --muted · latar --surface
     .tv-manage  border-style:dashed
   >> Garis putus-putus abu-abu muda dengan teks abu-abu adalah bahasa rupa universal untuk
      "tempat kosong yang bisa diisi", bukan untuk "tekan saya". Matanya membacanya dengan
      BENAR: itu memang bukan tombol, itu slot.
   Dan lebih tajam lagi: `Select` di sebelahnya memakai .chip bergaris SOLID — jadi di baris
   yang sama, tombol yang MEMBUAT sesuatu bergaris lebih lemah daripada tombol yang cuma
   MENGUBAH MODE. Fungsi dan bobotnya terbalik.

   `.chip.tv-manage`, BUKAN `.tv-manage` — dan ini diukur, bukan gaya penulisan.
   `.chip` ditulis 1.370 baris DI BAWAH sini dengan kekhususan yang SAMA (satu kelas), jadi
   `background` dan `color`-nya menang atas apa pun yang ditulis di sini. Percobaan pertama
   memakai `.tv-manage{...}` dan gambarnya kembali nyaris tak berubah — cuma bayangan tipis.
     >> Dua aturan berkekhususan sama diputuskan URUTAN, dan urutan adalah sifat berkas, bukan
        sifat aturannya. Yang menuliskannya di tempat yang salah kalah tanpa satu pun galat.
   Penyakit yang sama sudah tercatat di .tb-col/.tb-col-lalu, di berkas ini juga.

   var(--accent), BUKAN #C93A22: menulis heksanya harfiah membuatnya benar di mode terang dan
   salah di mode gelap — dan mode gelap baru saja dipakai jadi bahan video promosi.

   BENTUK TERISI dipilih PEMILIK sesudah melihat dua bentuk berdampingan (terisi vs lembut
   --accent-weak), di kedua tema. Alternatif lembutnya satu baris kalau dua tombol aksen di
   satu layar suatu hari terasa berebut. */
.chip.tv-manage{border-style:solid;background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600;box-shadow:0 1px 2px rgba(201,58,34,.28)}
.chip.tv-manage:hover{background:var(--accent-text);border-color:var(--accent-text)}

/* ---- Grup daftar yang bisa dilipat (hanya tampilan "Semua") ----
   Header memakai warna pastel daftar (lbl-c0..7) sebagai latar — lbl-c cuma memberi
   background+warna teks, jadi tata letaknya diatur di sini. Chevron & hitungan mewarisi
   currentColor dari header, jadi ikutnya ikut berwarna sesuai daftar.
   Keadaan lipat diingat per-perangkat (localStorage). */
/* Jarak antar-grup HARUS satu angka, entah grup di atasnya dilipat atau terbuka.
   Karena itu pemisahnya hidup di margin-ATAS tiap header (14px), bukan di bawah — margin
   bawah yang lama (6px) di-"collapse" dengan atas header berikutnya jadi cuma 12px yang
   kesempitan saat dilipat. Jarak header->tugasnya kini dipegang oleh padding-atas badan,
   jadi ia tidak ikut memengaruhi jarak antar-grup saat badan itu hilang (terlipat). */
.tg-head{display:flex;align-items:center;gap:var(--sp-x9);width:calc(100% - 32px);border:none;cursor:pointer;
  padding:var(--sp-x9) var(--sp-12);margin:var(--sp-14) var(--sp-16) 0;border-radius:11px;font-size:12.5px;font-weight:700;
  text-align:left;line-height:1.4;letter-spacing:.01em}
.tg-head:first-child{margin-top:var(--sp-2)}
.tg-head:active{transform:scale(.992)}
.tg-chev{display:inline-flex;flex:none;opacity:.9;transition:opacity .15s}
.tg-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tg-n{flex:none;font-size:11.5px;font-weight:800;opacity:.72;min-width:16px;text-align:right}
.tg-body{padding:var(--sp-x5) 0 0}

/* Baris kedua di tiap tugas: tenggat + lencana daftar + hitungan komentar/berkas.
   Sebelumnya semua ini berdesakan di kanan dan memakan lebar JUDUL — diukur: judul
   butuh 424px, cuma dapat 72px. */
.lr-sub2{display:flex;align-items:center;gap:var(--sp-x7);flex-wrap:nowrap;overflow:hidden;min-width:0;margin-top:var(--sp-2)}
.lr-sub2>.due,.lr-sub2>.lr-sub{flex:none;white-space:nowrap}
/* Nama daftar dipotong, bukan dibiarkan mendorong yang lain keluar. 140px cukup untuk
   dikenali; selebihnya kalah penting dari tenggat di sebelahnya. */
/* display:inline-block, BUKAN inline-flex warisan .lbl-chip: text-overflow:ellipsis
   tidak berlaku pada wadah flex, jadi namanya terpotong di tengah kata tanpa titik-titik
   dan terlihat seperti kerusakan, bukan seperti pemotongan yang disengaja. */
.lr-sub2 .lbl-chip{display:inline-block;flex:0 1 auto;min-width:0;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lr-sub2 .t-mini{flex:none}
/* Bintang menempel di judul sekarang, bukan mengambang di tengah baris. */
.lr-title .t-star{margin-left:var(--sp-6);vertical-align:-1px}

/* Tanda di baris tugas. --amber-ish untuk bintang: warnanya sendiri, bukan aksen app,
   supaya "penting" tidak tertukar dengan "terpilih". */
.t-star{display:inline-flex;color:#BA7517}
:root[data-theme="dark"] .t-star{color:#FAC775}
.t-mini{display:inline-flex;align-items:center;gap:var(--sp-x3);font-size:11px;color:var(--muted)}
.t-mini svg{flex:none}

.t-h1{display:flex;align-items:flex-start;gap:var(--sp-10)}
.t-star-btn{flex:none;margin-left:auto;background:none;border:none;padding:var(--sp-4);cursor:pointer;color:var(--faint);border-radius:8px;transition:color .14s ease,transform .12s ease}
.t-star-btn:hover{color:#BA7517;transform:scale(1.1)}
.t-star-btn.on{color:#BA7517}
:root[data-theme="dark"] .t-star-btn.on,:root[data-theme="dark"] .t-star-btn:hover{color:#FAC775}
.t-star-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.meta-btn{width:100%;text-align:left;background:none;border:none;font:inherit;color:inherit;cursor:pointer;border-radius:10px;padding:var(--sp-8) var(--sp-6);margin-left:-6px}
.meta-btn:hover{background-color:var(--surface-2)}
.meta-btn>svg:last-child{margin-left:auto;color:var(--faint)}

/* Bagian lampiran & komentar di detail tugas. */
.task-sec{margin-top:var(--sp-x22);padding-top:var(--sp-16);border-top:1px solid var(--line)}
.task-sec-h{display:flex;align-items:center;gap:var(--sp-x7);font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:var(--sp-10)}
.task-sec-h svg{flex:none;color:var(--accent-text)}

.tf-row{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-8) 0;border-bottom:1px solid var(--line)}
.tf-row:last-of-type{border-bottom:none}
.tf-thumb{width:40px;height:40px;flex:none;border:none;padding:0;border-radius:8px;overflow:hidden;cursor:pointer;background:var(--surface-2)}
.tf-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.tf-ico{width:40px;height:40px;flex:none;border-radius:8px;background-color:var(--surface-2);display:flex;align-items:center;justify-content:center;color:var(--muted)}
.tf-b{display:flex;flex-direction:column;min-width:0;flex:1}
.tf-name{font-size:14px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tf-sub{font-size:12px;color:var(--muted)}
.tf-add{margin-top:var(--sp-12);display:inline-flex;align-items:center;gap:var(--sp-6);cursor:pointer}

.cmt{display:flex;gap:var(--sp-10);padding:var(--sp-x9) 0}
.cmt-b{flex:1;min-width:0}
.cmt-h{display:flex;align-items:baseline;gap:var(--sp-8);font-size:12px;color:var(--muted)}
.cmt-h b{font-size:13px;color:var(--text);font-weight:600}
/* Target sentuh 34px: ikonnya tetap 13px tapi area tekannya diperbesar dengan padding,
   lalu ditarik dengan margin negatif supaya tidak menambah tinggi baris komentar. */
.cmt-x{margin:-8px -8px -8px auto;background:none;border:none;padding:var(--sp-10);cursor:pointer;color:var(--faint);border-radius:8px;display:inline-flex;align-items:center;justify-content:center}
.cmt-x:hover{color:var(--danger)}
.cmt-t{font-size:14px;line-height:1.5;margin-top:var(--sp-2);white-space:pre-wrap;word-break:break-word}
.cmt-del{display:flex;align-items:center;gap:var(--sp-x7);font-size:13px;color:var(--faint);font-style:italic;padding:var(--sp-x9) 0}
.cmt-form{position:relative;display:flex;gap:var(--sp-8);margin-top:var(--sp-12);align-items:flex-end}   /* jangkar untuk .co-count */
.cmt-form textarea{flex:1;min-width:0;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:10px;background-color:var(--surface);color:var(--text);font:inherit;font-size:14px;outline:none;resize:none;overflow-y:auto;max-height:120px;line-height:1.45}
.cmt-form textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
/* Keterangan tugas: satu-satunya teks banyak-baris yang dulu TIDAK pre-wrap, jadi paragraf
   yang diketik orang tampil sebagai satu blok. Disamakan dengan .cmt-t / .note-text / .btxt. */
.task-desc{font-size:14px;color:var(--muted);line-height:1.6;white-space:pre-wrap;word-break:break-word}

/* Kelola daftar. */
/* Dalam alur biasa, bukan position:absolute: .detail-pad tidak punya position:relative,
   jadi yang absolut akan mengambil patokan dari leluhur yang tidak terduga — dan itu
   berbeda-beda tergantung kolom mana yang sedang tergambar. */
.lm-head{display:flex;align-items:center;gap:var(--sp-10)}
.lm-head h1{margin:0;flex:1}
.lm-close{flex:none}
.lm-row{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-10) 0;border-bottom:1px solid var(--line)}
.lm-main{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-x3);background:none;border:none;padding:0;cursor:pointer;font:inherit;text-align:left}
.lm-sub{font-size:12px;color:var(--muted)}
/* Bagian "Diarsipkan" di layar Lists — header sekunder yang bisa dibuka. Sengaja
   selaras dengan .lm-row (padding mendatar 0), bukan meminjam .arch-header milik chats
   yang punya padding sendiri dan akan menjorok ganda di dalam .detail-pad. */
.lm-arch-header{display:flex;align-items:center;gap:var(--sp-x9);width:100%;padding:var(--sp-12) 0;margin-top:var(--sp-6);
  border:none;border-top:1px solid var(--line);background:none;cursor:pointer;font:inherit;
  font-size:13px;font-weight:600;color:var(--muted);text-align:left}
.lm-arch-header>span{flex:1;text-align:left}
.lm-arch-header:hover{color:var(--text)}
.lm-arch-header svg{flex:none}
/* Baris terarsip: bagian namanya tidak bisa diklik (daftarnya di luar penyaring aktif),
   jadi kursornya biasa dan chip-nya sedikit diredupkan untuk menandai keadaan non-aktif. */
.lm-main-ro{cursor:default}
.lm-arch .lbl-chip{opacity:.75}
/* Mode susun-ulang: tombol di header menyala saat aktif; tombol naik/turun yang nonaktif
   (di ujung daftar) diredupkan dan tidak bisa ditekan. */
.lm-reorder.on{color:var(--accent);background:var(--accent-weak)}
.co-mem-btn:disabled{opacity:.28;cursor:default}
.lm-reorder-row .lm-main-ro{padding:var(--sp-2) 0}
#lm-name{width:100%;padding:var(--sp-10) var(--sp-12);margin-top:var(--sp-x5);border:1px solid var(--line);border-radius:10px;background-color:var(--surface);color:var(--text);font:inherit;font-size:14px;outline:none}
#lm-name:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}

/* Anggota daftar tugas — bagian di bawah form edit. Inilah yang membuat daftar bisa
   dibagikan; sebelumnya endpoint-nya ada tapi tanpa tombol. */
.lm-mem{margin-top:var(--sp-x22);padding-top:var(--sp-16);border-top:1px solid var(--line)}
.lm-mem-row{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-x7) 0}
.lm-mem-name{flex:1;min-width:0;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lm-add{margin-top:var(--sp-10);display:inline-flex;align-items:center;gap:var(--sp-6)}

/* ---- Verifikasi dua langkah ---- */
.tf-qr{display:flex;justify-content:center;margin:var(--sp-16) 0 var(--sp-6)}
/* Latar putih dipatok di dalam SVG-nya sendiri: QR di atas latar gelap tidak terbaca
   pemindai mana pun, dan tema gelap tidak boleh mengubah itu. */
.tf-qr svg{border-radius:12px;max-width:100%;height:auto;box-shadow:0 1px 3px rgba(0,0,0,.12)}
.tf-manual{margin-top:var(--sp-14)}
.tf-secret{display:flex;align-items:center;gap:var(--sp-x9);width:100%;justify-content:center;padding:var(--sp-x11) var(--sp-12);margin:var(--sp-6) 0 var(--sp-4);border:1px dashed var(--line);border-radius:12px;background-color:var(--surface-2);color:var(--text);cursor:pointer;font:inherit}
.tf-secret code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px;letter-spacing:.06em;word-break:break-all}
.tf-secret:hover{border-color:var(--accent)}
#tf-code{width:100%;margin-top:var(--sp-x5);padding:var(--sp-x11) var(--sp-12);border:1px solid var(--line);border-radius:10px;background-color:var(--surface);color:var(--text);font:inherit;font-size:19px;letter-spacing:.28em;text-align:center;outline:none}
#tf-code:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
#tf-confirm .co-flabel{display:block;margin-top:var(--sp-16)}
.tf-acts{display:flex;gap:var(--sp-10);flex-wrap:wrap;margin-top:var(--sp-x18)}
.tf-acts .btn{justify-content:center}
.tf-done{display:flex;align-items:center;gap:var(--sp-x9);padding:var(--sp-12) var(--sp-14);margin:var(--sp-4) 0 var(--sp-12);border-radius:12px;background-color:#e9f7ef;color:#177245;font-weight:600;font-size:14px}
:root[data-theme="dark"] .tf-done{background-color:#14b8a626;color:#5eead4}
.tf-done svg{flex:none}
.tf-warn{display:flex;align-items:flex-start;gap:var(--sp-8);margin-top:var(--sp-12)}
.tf-warn svg{flex:none;margin-top:var(--sp-2);color:var(--accent-text)}
/* Kode pemulihan: monospace dan berjarak, karena ia dibaca dari layar lalu diketik
   ulang — kadang dari kertas. */
.tf-codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:var(--sp-8);margin:var(--sp-14) 0 var(--sp-4)}
.tf-codes code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px;letter-spacing:.06em;padding:var(--sp-x9) var(--sp-10);border:1px solid var(--line);border-radius:10px;background-color:var(--surface-2);text-align:center}
.vis-row{margin:-6px 0 var(--sp-14);display:flex;flex-wrap:wrap;gap:var(--sp-6)}
.lr-title .vis-badge,.star-head-title .vis-badge{margin-left:var(--sp-x7)}
.bl-sec-lbl{font-size:11.5px;font-weight:700;color:var(--muted);letter-spacing:.04em;text-transform:uppercase;margin-top:var(--sp-6)}
.bl-cmp-oth .bl-cmp-ini{background:transparent;border:1px dashed var(--line);color:var(--muted);font-size:11px}
.bl-cmp-note{font-size:12px;color:var(--muted);padding:var(--sp-2) 0 0 var(--sp-x44)}
/* Billing plan (fixed-price mode) */
.bl-card.bl-plan{text-align:center;padding:var(--sp-20) var(--sp-16)}
.bl-plan-label{font-size:12px;color:var(--muted)}
.bl-plan-amt{font-size:34px;font-weight:750;color:var(--accent-text);line-height:1.15;margin:var(--sp-2) 0}
.bl-plan-sub{font-size:12.5px;color:var(--muted)}
.bl-mode-seg{display:flex;gap:var(--sp-6);background:var(--surface-2);padding:var(--sp-4);border-radius:11px;margin-bottom:var(--sp-4)}
.bl-mode-seg button{flex:1;border:none;background:none;color:var(--muted);font-size:12.5px;font-weight:650;padding:var(--sp-8) var(--sp-6);border-radius:8px;cursor:pointer;transition:background .15s,color .15s}
.bl-mode-seg button.on{background:var(--surface);color:var(--accent-text);box-shadow:0 1px 3px #0000001f}
.bl-card{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:var(--sp-14) var(--sp-16);margin-bottom:var(--sp-14)}
/* Peringatan riwayat grup: cukup terbaca untuk dilihat sebelum menekan Add, cukup tenang
   untuk tidak terasa seperti pesan galat. Ini pemberitahuan, bukan larangan. */
.gi-hist{display:flex;align-items:flex-start;gap:var(--sp-x7);margin:var(--sp-6) 0 var(--sp-10);padding:var(--sp-8) var(--sp-10);
  border-radius:9px;background:var(--surface-2);color:var(--muted);font-size:12px;line-height:1.45}
.gi-hist svg{flex:none;margin-top:var(--sp-1)}
/* ---- perkiraan harga di layar bikin workspace ---- */
.pn{margin:var(--sp-10) var(--sp-2) var(--sp-12);padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:11px;background:var(--surface-2)}
/* --muted, bukan --faint: kartunya duduk di atas --surface-2 yang lebih gelap dari putih,
   dan --faint di sana cuma 4,31 — di bawah ambang 4,5. Sama persis dengan kasus jam di
   gelembung stiker dulu: warna redup yang aman di atas putih belum tentu aman di atas
   latar kartu. */
.pn-h{display:flex;align-items:center;gap:var(--sp-6);font-size:11px;font-weight:700;text-transform:uppercase;
  letter-spacing:.4px;color:var(--muted)}
.pn-b{margin-top:var(--sp-6);font-size:13px;line-height:1.5;color:var(--text)}
.pn-b b{font-size:15px;font-weight:700}
/* Keterangan tambahan dibuat tenang tapi tetap terbaca — orang membacanya sekali,
   waktu memutuskan, dan setelah itu tidak pernah lagi. */
.pn-sub{display:block;margin-top:var(--sp-4);font-size:12px;line-height:1.45;color:var(--muted)}
/* ---- riwayat pembayaran (pemilik workspace) ---- */
.ph-row{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-x9) 0;border-bottom:1px solid var(--line)}
.ph-row:last-child{border-bottom:none}
.ph-m{flex:0 0 auto;font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
/* Nominal mengambil sisa ruang dan rata kanan, jadi digitnya sejajar antar baris. */
.ph-a{flex:1 1 auto;text-align:right;font-size:13.5px;font-weight:600;color:var(--text);
  font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
.ph-proof{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:28px;height:28px;
  border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--muted);cursor:pointer}
.ph-proof:hover{background:var(--surface-2);color:var(--text)}
/* v345 — LENCANA DI BARIS RIWAYAT TIDAK BOLEH MELIPAT, DAN NOMINALNYA TIDAK BOLEH DIGENCET.
 *
 * Ditemukan dengan MATA di __demo.html, bukan oleh alat ukur — dan alat ukur saya sendiri
 * yang pertama melewatkannya: ia menanyakan "apakah lencananya LUBER" (scrollWidth >
 * clientWidth) dan jawabannya tidak, karena teksnya tidak luber. IA MELIPAT. Baris
 * "Menunggu verifikasi" di kolom 370px pecah jadi dua baris DAN mendorong nominalnya pecah
 * jadi "Rp" / "120.000" — barisnya jadi lebih tinggi dari tetangganya dan angkanya berhenti
 * sejajar. Bentuk Inggris ("Awaiting review") muat, jadi ia akan lolos diam-diam selamanya.
 *
 * `.bl-badge` aslinya dirancang untuk kartu paket, di mana `margin-top:6px` benar. Di dalam
 * baris flex ini margin itu menjatuhkannya 6px dari tengah, dan `flex` bawaan membiarkannya
 * menyusut sampai melipat. Ketiganya dibetulkan di sini SAJA — kartu paket tidak disentuh.
 *
 * Percobaan pertama cuma `white-space:nowrap`, dan itu MENUKAR cacat dengan cacat: lencananya
 * berhenti melipat lalu keluar 52px dari tepi kartu di 320px. Yang benar bukan memaksa satu
 * baris — di 320px "Menunggu verifikasi" memang TIDAK MUAT di sebelah bulan dan nominal, dan
 * memaksanya cuma memindahkan kerusakan ke tempat lain.
 *
 * Jadi barisnya yang boleh MELIPAT, elemennya tidak. Di lebar yang cukup semuanya tetap satu
 * baris seperti sekarang; di lebar sempit lencananya turun utuh ke baris kedua, rata kanan.
 * Tidak ada kata yang pecah, tidak ada angka yang pecah, tidak ada yang keluar dari kartu. */
.ph-row{flex-wrap:wrap}
.ph-row .bl-badge{flex:0 0 auto;margin-top:0;white-space:nowrap;margin-left:auto}
.ph-row .ph-a{white-space:nowrap;overflow-wrap:normal}
/* Alasan penolakan ditaruh sebagai baris tersendiri, bukan disingkat di dalam baris:
   ini satu-satunya keterangan yang memberi tahu orang APA yang harus dia perbaiki. */
.ph-note{display:flex;align-items:flex-start;gap:var(--sp-6);padding:0 0 var(--sp-x9) 0;margin-top:-4px;
  font-size:12px;line-height:1.45;color:var(--danger)}
.ph-note svg{flex:none;margin-top:var(--sp-2)}
@media (max-width:400px){.ph-m{font-size:12.5px}.ph-a{font-size:13px}}
/* ---- untung/rugi ---- */
/* Hijau dan merah dipakai untuk untung/rugi, TAPI tidak sendirian: angka rugi selalu
   didahului tanda minus. Sekitar 8% laki-laki sulit membedakan merah dan hijau, dan
   angka yang artinya cuma bisa dibaca dari warnanya berarti tidak terbaca oleh mereka. */
.pnl-up{color:#177245}
.pnl-down{color:#c0392b}
:root[data-theme="dark"] .pnl-up{color:#4ade80}
:root[data-theme="dark"] .pnl-down{color:#f87171}
.pnl-now-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-10) var(--sp-14);padding:var(--sp-2) 0 var(--sp-12);border-bottom:1px solid var(--line);margin-bottom:var(--sp-10)}
.pnl-now-grid>div{display:flex;flex-direction:column;gap:var(--sp-2);min-width:0}
.pnl-now-grid span{font-size:12px;color:var(--muted)}
.pnl-now-grid b{font-size:16px;font-weight:650;color:var(--text);overflow-wrap:anywhere}
.pnl-head,.pnl-row{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-x7) 0;font-size:13px}
.pnl-head{font-size:11.5px;text-transform:uppercase;letter-spacing:.4px;color:var(--faint);
  border-bottom:1px solid var(--line);padding-bottom:var(--sp-6)}
.pnl-row{border-bottom:1px solid var(--line)}
.pnl-row:last-child{border-bottom:none}
.pnl-m{flex:1 1 0;min-width:0;color:var(--muted)}
.pnl-m i{font-style:normal;font-size:11px;color:var(--accent-text)}
/* Angka rata kanan supaya digitnya sejajar dan gampang dibandingkan sekilas. */
.pnl-in,.pnl-out,.pnl-net{flex:0 0 auto;min-width:82px;text-align:right;font-variant-numeric:tabular-nums}
.pnl-out{color:var(--muted)}
.pnl-out i,.pnl-m i.none{font-style:normal;color:var(--faint);font-size:11.5px}
.pnl-net{font-weight:650}
.pnl-now{background:var(--surface-2);border-radius:8px;padding-left:var(--sp-8);padding-right:var(--sp-8)}
.pnl-tot{border-top:2px solid var(--line);border-bottom:none;font-weight:650;margin-top:var(--sp-2)}
.pnl-tot .pnl-m{color:var(--text)}
/* Pratinjau tagihan di layar pengaturan harga. */
.bl-preview{display:flex;gap:var(--sp-8);margin:var(--sp-2) 0 var(--sp-10)}
.bl-preview>div{flex:1;display:flex;flex-direction:column;gap:var(--sp-2);align-items:center;
  padding:var(--sp-8) var(--sp-6);border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}
.bl-preview span{font-size:11.5px;color:var(--muted)}
.bl-preview b{font-size:13.5px;font-weight:650;color:var(--text)}
@media (max-width:400px){.pnl-in,.pnl-out,.pnl-net{min-width:70px;font-size:12.5px}}
.bl-card-h{display:flex;align-items:center;gap:var(--sp-8);flex-wrap:wrap;font-size:13px;font-weight:700;color:var(--text);margin-bottom:var(--sp-10)}
.bl-row{display:flex;justify-content:space-between;gap:var(--sp-10);font-size:14px;padding:var(--sp-x5) 0;color:var(--text)}
.bl-row span:first-child{color:var(--muted)}
/* v346 — NOMINALNYA TIDAK PERNAH PECAH; YANG MELIPAT LABELNYA.
 *
 * Saudara persis dari perbaikan .ph-row di v345, ditemukan dengan mata di layar yang SAMA
 * satu baris di bawahnya. Di 370px bahasa Indonesia, "Diskon referral (8 × Rp 2.500)"
 * mendorong nominalnya pecah jadi "−Rp" / "20.000": angka yang terbelah dua baris berhenti
 * bisa dibaca sekilas, dan kolom angka di kartu tagihan berhenti sejajar.
 *
 * Label boleh melipat — ia kalimat. Angka tidak — ia satu benda. `min-width:0` wajib supaya
 * label yang panjang benar-benar boleh menyusut alih-alih memaksa barisnya melebar. */
.bl-row span:first-child{min-width:0}
.bl-row span:last-child{flex:0 0 auto;white-space:nowrap}
.bl-sub{border-top:1px solid var(--line);margin-top:var(--sp-4);padding-top:var(--sp-x9);font-weight:600}
.bl-total{border-top:2px solid var(--line);margin-top:var(--sp-4);padding-top:var(--sp-x9);font-weight:750;font-size:15px}
.bl-total span:first-child{color:var(--text)}
/* Selama beta: "To pay" = Rp 0, angka asli dicoret di sebelahnya (gambaran biaya nanti). */
.bl-free{color:#177245;font-weight:800}
.bl-free s{color:var(--muted);font-weight:400;margin-right:var(--sp-6)}
.bl-plan-amt.bl-plan-free{color:#177245}
:root[data-theme="dark"] .bl-free,:root[data-theme="dark"] .bl-plan-amt.bl-plan-free{color:#4ade80}
.bl-note{font-size:12.5px;color:var(--muted);margin-top:var(--sp-8);font-style:italic}
/* BUTIR 159 — kalimat yang menjelaskan tagihan. Ia bukan catatan kaki: ia jawaban atas
   pertanyaan pertama yang orang punya waktu melihat angka yang lebih besar dari jumlah
   timnya. Jadi ukurannya ukuran badan teks, bukan ukuran keterangan. */
.bl-why{display:flex;gap:var(--sp-8);align-items:flex-start;margin-top:var(--sp-10);padding:var(--sp-10) var(--sp-12);
  border-radius:12px;background:var(--surface-2);border:1px solid var(--line);
  font-size:13px;line-height:1.55;color:var(--text)}
.bl-why svg{flex:none;margin-top:var(--sp-2);color:var(--muted)}

/* v357 — TAWARAN "KEMBALI KE GRATIS" DI LAYAR BACA-SAJA.
 *
 * Ia SENGAJA TIDAK berwarna merah dan tidak memakai bentuk peringatan. Orang yang membacanya
 * sedang terkunci; layar itu sudah cukup keras. Tawaran ini jalan keluar, bukan hukuman kedua,
 * jadi bentuknya hangat: latar hijau lembut yang sama dengan lencana "lunas".
 *
 * Baris batasnya (`.bl-offer-limit`) lebih redup DAN tetap terbaca — ia yang mencegah tawaran
 * ini jadi jebakan, jadi ia tidak boleh jadi cetakan kecil yang orang lewati. */
/* Warnanya diturunkan dari `--good`, token yang sudah dipakai lencana "lunas" — bukan nama
   token baru dengan nilai cadangan. Gerbang butir 130 menangkap percobaan pertama saya:
   `var(--ok-bg, …)` yang tokennya tidak pernah ada, jadi cadangannya yang bekerja dan nama
   tokennya cuma hiasan. Warna yang mengaku ikut tema tapi tidak ikut tema akan berbeda dari
   sekitarnya pada hari temanya digeser. */
.bl-offer{background:color-mix(in srgb, var(--good) 8%, transparent);
  border:1px solid color-mix(in srgb, var(--good) 26%, transparent)}
.bl-offer-h{display:flex;gap:var(--sp-8);align-items:center;font-size:15px;font-weight:650;
  color:var(--text);letter-spacing:-.01em;margin-bottom:var(--sp-8)}
.bl-offer-h svg{flex:none;color:var(--good)}
.bl-offer-b{margin:0 0 var(--sp-10);font-size:13.5px;line-height:1.55;color:var(--text)}
.bl-offer-limit{display:flex;gap:var(--sp-x7);align-items:flex-start;font-size:12.5px;color:var(--muted)}
.bl-offer-limit svg{flex:none;margin-top:var(--sp-2);color:var(--muted)}
.bl-offer .btn{width:100%;display:flex;gap:var(--sp-8);align-items:center;justify-content:center}

/* v357 — DASAR TAGIHAN DAN RAMALAN, DUA BENTUK YANG BERBEDA JAUH.
 *
 * Kegagalan yang ditutup di sini bukan kurang keterangan melainkan salah penonjolan: layar
 * menuliskan puncak siklus berjalan (9) tepat di sebelah tagihan yang menagih 4, dengan bentuk
 * yang sama, jadi tidak ada cara membedakan mana yang uang. Perintah AD: angka yang MENAGIH
 * paling menonjol; puncak berjalan hadir sebagai ramalan, dengan namanya sendiri, lebih kecil.
 *
 * `.bl-basis` karena itu tegas dan gelap; `.bl-forecast` sengaja lebih kecil dan lebih redup,
 * dengan ikon panah maju yang mengatakan arah waktunya sebelum kalimatnya sempat dibaca. Dua
 * bentuk yang tidak bisa tertukar walau dilihat sekilas.
 *
 * TIDAK dimiringkan, dan `font-style:normal` di bawah ditulis dengan sengaja: `.bl-note`
 * mewarisi miring dari induknya, dan ramalan ini kalimat panjang — teks miring panjang di
 * layar kecil lebih lambat dibaca, tepat pada kalimat yang paling perlu terbaca. */
.bl-basis{margin-top:var(--sp-10);font-size:14px;font-weight:650;color:var(--text);letter-spacing:-.01em}
.bl-forecast{display:flex;gap:var(--sp-x7);align-items:flex-start;margin-top:var(--sp-6);font-style:normal;
  font-size:12.5px;line-height:1.5;color:var(--muted)}
.bl-forecast svg{flex:none;margin-top:var(--sp-2);color:var(--accent);opacity:.95}

/* BUTIR 158 — panel go-live. Ia duduk di dalam kartu setelan tagihan, jadi ia meminjam
   bahasa kartu itu dan tidak memperkenalkan bentuk baru: sudut 14px, garis --line,
   latar --surface-2. Yang membedakan cuma warna tepinya, dan itu memang tugasnya. */
.bl-golive{border:1px solid var(--line);border-radius:14px;padding:var(--sp-12) var(--sp-14);margin-bottom:var(--sp-12);
  background:var(--surface-2)}
.bl-golive.warn{border-color:#f59e0b66;background:#f59e0b12}
.bl-golive.ok{border-color:#10b98166;background:#10b98110}
.bl-golive-h{display:flex;align-items:center;gap:var(--sp-8);font-size:13px;font-weight:700;color:var(--text);margin-bottom:var(--sp-8)}
.bl-golive-h .gl-x{margin-left:auto;background:none;border:0;padding:var(--sp-4);border-radius:8px;
  color:var(--muted);cursor:pointer;line-height:0;transition:background .15s ease,color .15s ease}
.bl-golive-h .gl-x:hover{background:var(--surface);color:var(--text)}
.bl-golive p{font-size:12.5px;line-height:1.55;color:var(--muted);margin:0 0 var(--sp-10)}
.bl-golive>.btn{width:100%}
.bl-golive>.btn[disabled]{opacity:.45;cursor:not-allowed}
.gl-sum{font-size:12.5px;color:var(--muted);margin-bottom:var(--sp-8);line-height:1.6}
.gl-sum b{color:var(--text);font-weight:700}
.gl-ok{color:#059669}
.gl-bad{color:#dc2626}
.gl-tbl{width:100%;border-collapse:collapse;font-size:12px;margin-bottom:var(--sp-10)}
.gl-tbl th{text-align:left;font-weight:600;color:var(--muted);padding:var(--sp-4) var(--sp-6) var(--sp-6);
  border-bottom:1px solid var(--line);white-space:nowrap}
.gl-tbl td{padding:var(--sp-x5) var(--sp-6);border-bottom:1px solid var(--line);color:var(--text);
  vertical-align:top;word-break:break-word}
.gl-tbl tbody tr:last-child td{border-bottom:0}
.gl-tbl td:first-child{font-weight:600;max-width:120px}
.gl-note{font-size:12px;color:var(--muted);margin:var(--sp-8) 0 0;font-style:italic}
/* Keadaan menempel pada tanggalnya, jadi tabel ini tiga kolom dan tidak ada yang perlu
   disembunyikan di layar sempit (butir 143). Menyembunyikan kolom keadaan adalah percobaan
   pertama saya, dan ia menyembunyikan satu-satunya hal yang panel ini ada untuk dikatakan. */
.gl-tbl td i{display:block;font-style:normal;font-size:11px;margin-top:var(--sp-1);white-space:normal}
/* Tanggal tidak boleh patah dua baris di 360px — ia satu benda, bukan dua. */
.gl-tbl td:nth-child(n+2),.gl-tbl th:nth-child(n+2){white-space:nowrap}
.gl-tbl tr.bad td{background:#dc26260d}
.gl-tbl tr.bad td:first-child{box-shadow:inset 2px 0 0 #dc2626}
.bl-info{display:flex;gap:var(--sp-8);align-items:flex-start;background:var(--surface-2);border:1px solid var(--line);border-radius:12px;padding:var(--sp-x11) var(--sp-x13);font-size:13px;line-height:1.5;color:var(--muted);margin:0 0 var(--sp-14)}
.bl-info svg{flex:none;margin-top:var(--sp-1);color:var(--accent-text)}
.bl-empty{font-size:13.5px;color:var(--muted);padding:var(--sp-6) 0}
.bl-mine .bl-mine-top{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--sp-12)}
.bl-mine-label{font-size:12px;color:var(--muted)}
.bl-mine-pct{font-size:30px;font-weight:750;color:var(--accent-text);line-height:1.1}
.bl-mine-sub{font-size:12px;color:var(--muted)}
.bl-mine-amt{font-size:20px;font-weight:750;color:var(--text)}
.bl-badge{display:inline-block;margin-top:var(--sp-6);font-size:11px;font-weight:700;padding:var(--sp-2) var(--sp-x9);border-radius:20px}
.bl-badge.pending{color:#b45309;background:#f59e0b1a}
.bl-badge.approved{color:#177245;background:#17724522}
.bl-badge.rejected{color:#c0392b;background:#c0392b1a}
.bl-badge.warn{color:#b45309;background:#f59e0b1a}
.bl-badge.beta{color:#6d28d9;background:#7c3aed18}
/* Mode gelap: teks lencana harus lebih terang agar kontras ≥4.5 di atas permukaan gelap. */
:root[data-theme="dark"] .bl-badge.approved{color:#4ade80}
:root[data-theme="dark"] .bl-badge.pending,
:root[data-theme="dark"] .bl-badge.warn{color:#fbbf24}
:root[data-theme="dark"] .bl-badge.rejected,
:root[data-theme="dark"] .bl-badge.unpaid{color:#f87171}
:root[data-theme="dark"] .bl-badge.beta{color:#a78bfa}
.bl-breakdown{margin:var(--sp-14) 0 var(--sp-2);padding-top:var(--sp-10);border-top:1px solid var(--line)}
.bl-row.bl-credit span{color:#177245}
.bl-row.bl-credit span:last-child{font-weight:700}
:root[data-theme="dark"] .bl-row.bl-credit span{color:#4ade80}
.bl-credit-amt{font-size:26px;font-weight:750;color:var(--accent-text);margin:var(--sp-2) 0 var(--sp-4)}
.bl-topup-btn{width:auto;margin-top:var(--sp-12);background:var(--surface-2);color:var(--text);border:1px solid var(--line)}
.bl-topup-btn:hover{border-color:color-mix(in srgb,var(--accent) 40%,var(--line))}
/* Sakelar utama billing_live (khusus pengembang) */
.bl-live-row{display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-12) var(--sp-14);border-radius:14px;margin-bottom:var(--sp-12)}
.bl-live-row.off{background:#7c3aed14}
.bl-live-row.on{background:#17724518}
.bl-live-txt{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:var(--sp-2)}
.bl-live-txt b{font-size:14px;color:var(--text)}
.bl-live-txt span{font-size:12px;color:var(--muted);line-height:1.35}
.bl-live-toggle{flex:none;width:52px;height:30px;border-radius:20px;border:none;background:var(--line);position:relative;cursor:pointer;transition:background .18s;padding:0}
.bl-live-toggle.on{background:#177245}
.bl-live-knob{position:absolute;top:3px;left:3px;width:24px;height:24px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:left .18s}
.bl-live-toggle.on .bl-live-knob{left:25px}
.bl-pay{margin-top:var(--sp-12);width:100%;justify-content:center}
.bl-paid-note{margin-top:var(--sp-12);font-size:13px;color:var(--muted);display:flex;align-items:center;gap:var(--sp-6)}
/* v343 — warnanya dulu dipatok tanpa pasangan gelap: 3,00:1 dan 3,28:1 di tema gelap,
   di layar yang justru memberitahu orang soal uangnya. Sekarang lewat token yang punya
   dua nilai, satu per tema. Dijaga gerbang kontras di tests/js/run.js. */
.bl-paid-note.ok{color:var(--good-text)}
.bl-paid-note.bad{color:var(--danger)}
/* v348 — kalimat "kami belum bisa memastikan" + tombol memeriksanya.
   KELAS SENDIRI, bukan flex-wrap pada .bl-paid-note: menyalakan pelipatan di aturan INDUK
   akan membuat kalimat panjang di varian .ok melompat ke baris kedua dan meninggalkan
   ikonnya sendirian di baris pertama. Yang butuh susunan menurun cuma varian ini. */
.bl-paid-note.bl-unsure{flex-direction:column;align-items:stretch;gap:var(--sp-8);line-height:1.45}
.bl-paid-note.bl-unsure .bl-recheck{width:100%;justify-content:center;margin:0}
/* v348 — ongkos iPaymu disebut sebelum orangnya pergi. flex-start supaya ikonnya sejajar
   dengan BARIS PERTAMA kalimat, bukan dengan tengah blok yang melipat tiga baris di 320px. */
.bl-fee-note{margin:var(--sp-8) 0 0;font-size:12px;line-height:1.45;color:var(--muted);display:flex;align-items:flex-start;gap:var(--sp-6)}
.bl-fee-note svg{flex:none;margin-top:var(--sp-2)}
.bl-proof{margin-top:var(--sp-10);width:64px;height:64px}
.bl-cmp{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-6) 0}
.bl-cmp-ini{width:34px;height:34px;flex:none;border-radius:9px;background:var(--surface-2);color:var(--muted);font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center}
.bl-cmp.mine .bl-cmp-ini{background:var(--accent);color:#fff}
.bl-cmp-bar-wrap{flex:1;height:9px;border-radius:5px;background:var(--surface-2);overflow:hidden}
.bl-cmp-bar{display:block;height:100%;border-radius:5px;background:var(--line)}
.bl-cmp.mine .bl-cmp-bar{background:var(--accent)}
.bl-cmp-pct{flex:none;width:46px;text-align:right;font-size:12.5px;font-weight:600;color:var(--muted)}
.bl-cmp.mine .bl-cmp-pct{color:var(--accent-text)}
.bl-form{display:flex;flex-direction:column;gap:var(--sp-x9)}
.bl-field{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);font-size:13.5px;color:var(--muted)}
.bl-field input{width:150px;border:1px solid var(--line);border-radius:9px;padding:var(--sp-8) var(--sp-10);font-size:14px;outline:none;background:var(--surface);color:var(--text);text-align:right}
.bl-form > input{width:100%;text-align:left;border:1px solid var(--line);border-radius:9px;padding:var(--sp-x9) var(--sp-x11);font-size:14px;outline:none;background:var(--surface);color:var(--text)}
.bl-field input:focus,.bl-form input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.bl-items{display:flex;flex-direction:column;gap:var(--sp-8)}
.bl-item-row{display:flex;align-items:center;gap:var(--sp-8)}
.bl-item-label{flex:1;min-width:0;border:1px solid var(--line);border-radius:9px;padding:var(--sp-8) var(--sp-10);font-size:14px;outline:none;background:var(--surface);color:var(--text)}
.bl-item-amt{width:118px;flex:none;border:1px solid var(--line);border-radius:9px;padding:var(--sp-8) var(--sp-10);font-size:14px;outline:none;background:var(--surface);color:var(--text);text-align:right}
.bl-item-label:focus,.bl-item-amt:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.bl-item-del{flex:none;width:32px;height:32px;border-radius:8px;border:none;background:var(--surface-2);color:var(--muted);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,color .15s}
.bl-item-del:hover{background:#c0392b1a;color:#c0392b}
.bl-add-item{align-self:flex-start;border:1px dashed var(--line);background:none;color:var(--accent-text);font-size:13px;font-weight:600;padding:var(--sp-x7) var(--sp-12);border-radius:9px;cursor:pointer;transition:border-color .15s,background .15s}
.bl-add-item:hover{border-color:var(--accent);background:var(--accent-weak)}
.bl-pct-row{display:flex;gap:var(--sp-10);margin-top:var(--sp-2)}
.bl-pct-row .bl-field{flex:1;justify-content:space-between}
.bl-pct-row .bl-field input{width:78px}
.bl-first-note,.ex-note{display:flex;align-items:flex-start;gap:var(--sp-x7);margin-top:var(--sp-12);font-size:12.5px;color:var(--accent-text);background:var(--accent-weak);border-radius:9px;padding:var(--sp-x9) var(--sp-x11);line-height:1.4}
.bl-first-note svg{flex:none;margin-top:var(--sp-1)}
.pay-banner{display:flex;align-items:center;gap:var(--sp-x11);text-align:left;margin:0 var(--sp-14) var(--sp-14);padding:var(--sp-12) var(--sp-14);border-radius:14px;border:1px solid #f59e0b55;background:linear-gradient(180deg,#fff7ed,#fffbeb);color:#92400e;cursor:pointer;transition:transform .12s,box-shadow .15s}
.pay-banner:hover{transform:translateY(-1px);box-shadow:0 6px 18px #f59e0b26}
.pay-banner:active{transform:translateY(0)}
.pay-banner-ic{flex:none;width:34px;height:34px;border-radius:10px;background:#f59e0b22;color:#b45309;display:flex;align-items:center;justify-content:center}
.pay-banner-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-1)}
.pay-banner-b b{font-size:14px;font-weight:700}
.pay-banner-b span{font-size:12px;color:#a16207}
.pay-banner>svg{flex:none;color:#d97706}
:root[data-theme="dark"] .pay-banner{background:linear-gradient(180deg,#2a2010,#211a0c);border-color:#f59e0b44;color:#fbbf24}
:root[data-theme="dark"] .pay-banner-b span{color:#d9a441}
:root[data-theme="dark"] .pay-banner-ic{background:#f59e0b22;color:#fbbf24}
/* Ajakan tanggal lahir — baris lembut merah-jambu, bukan modal. Ikon hadiah warna-warni,
   tombol "x" terpisah untuk menunda. Soft pastel, sejalan dengan bahasa desain. */
.bd-banner{display:flex;align-items:stretch;margin:0 var(--sp-14) var(--sp-14);border-radius:14px;border:1px solid #ec489955;background:linear-gradient(180deg,#fdf2f8,#faf5ff);overflow:hidden;transition:transform .12s,box-shadow .15s}
.bd-banner:hover{transform:translateY(-1px);box-shadow:0 6px 18px #ec489922}
.bd-banner-main{flex:1;min-width:0;display:flex;align-items:center;gap:var(--sp-x11);text-align:left;padding:var(--sp-12) var(--sp-8) var(--sp-12) var(--sp-14);background:none;border:0;cursor:pointer;color:#9d174d}
.bd-banner-ic{flex:none;width:34px;height:34px;border-radius:10px;background:#ec489922;color:#db2777;display:flex;align-items:center;justify-content:center}
.bd-banner-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-1)}
.bd-banner-b b{font-size:14px;font-weight:700}
.bd-banner-b span{font-size:12px;color:#a3477a}
.bd-banner-main>svg{flex:none;color:#db2777;opacity:.65}
.bd-banner-x{flex:none;width:42px;display:flex;align-items:center;justify-content:center;background:none;border:0;border-left:1px solid #ec489933;color:#be185d99;cursor:pointer;transition:background .15s,color .15s}
.bd-banner-x:hover{background:#ec489914;color:#be185d}
:root[data-theme="dark"] .bd-banner{background:linear-gradient(180deg,#2a1524,#22131f);border-color:#ec489944}
:root[data-theme="dark"] .bd-banner-main{color:#f9a8d4}
:root[data-theme="dark"] .bd-banner-b span{color:#d98bb5}
:root[data-theme="dark"] .bd-banner-ic{background:#ec489922;color:#f472b6}
/* #286 — ikon spanduk pasang PWA: biru-teal "install", beda dari merah-jambu ulang tahun */
.install-banner .bd-banner-ic{background:#0ea5e922;color:#0284c7}
:root[data-theme="dark"] .install-banner .bd-banner-ic{background:#0ea5e926;color:#38bdf8}
:root[data-theme="dark"] .bd-banner-x{border-left-color:#ec489933;color:#f472b699}
:root[data-theme="dark"] .bd-banner-x:hover{background:#ec489918;color:#f9a8d4}
.ic-legal{color:#0891b2}
/* ---------- data export + legal ----------
   Both of these used a translucent background (#0891b21f). Every other menu
   icon in the app uses a SOLID pastel, and on a dark surface a 12% tint reads
   as no background at all — so these two looked flat and colourless sitting
   next to proper pastel circles. Solid pastels now, same as the rest.
   They also both used the same cyan while appearing in the same list, which
   looked like a mistake: legal moves to indigo, export keeps the cyan. */
/* Indigo TETAP, bukan var(--accent-text). Komentar di atas sudah menulis niatnya —
   "legal moves to indigo" — tapi kodenya memakai warna aksen workspace, jadi dua hal salah
   sekaligus: ikonnya bukan indigo (ia ikut warna workspace, bentrok dengan ubin indigo-nya
   sendiri), dan di mode GELAP --accent-text memutih sementara ubinnya tetap pastel terang →
   terang di atas terang, terukur 1,53:1 di Chrome. Satu-satunya baris Akun yang gagal dari 18.
   #4f46e5 adalah indigo yang memang sudah dipakai di tempat lain (lihat .doc-item nth-child),
   jadi ini mengembalikan niat aslinya sekaligus menutup celahnya: 5,17:1 di kedua mode. */
.ic-legal{color:#4f46e5}
.menu-ico.ic-legal{background:#e8e7fb}
.ic-export{color:#0891b2}
.menu-ico.ic-export{background:#ddf3f9}

/* ---------- danger zone ---------- */
/* Honeypot: off-screen, not display:none. Automated form-fillers skip fields that are
   hidden outright but happily fill ones that are merely out of view — which is the
   whole point. Never seen, never tabbed to, never read aloud. */
.hp-field{position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none}
.att-dl{display:inline-flex;align-items:center;gap:var(--sp-x7);margin:0 0 var(--sp-14);text-decoration:none;color:var(--accent-text);background:var(--accent-weak);border:1px solid transparent}
.att-dl:hover{border-color:var(--accent)}
/* Butir 25 — dibaca SEBELUM tombol unduh ditekan, jadi ia duduk tepat di bawahnya. */
.att-prov-note{display:flex;align-items:flex-start;gap:var(--sp-8);margin:0 0 var(--sp-14);padding:var(--sp-10) var(--sp-12);border-radius:12px;background:#f59e0b14;border:1px solid #f59e0b33;font-size:12.5px;line-height:1.45;color:var(--text)}
.att-prov-note svg{flex:none;margin-top:var(--sp-1);color:#b45309}
.ic-acctdel{color:var(--danger)}
.menu-ico.ic-acctdel{background:#fdecea}
:root[data-theme="dark"] .menu-ico.ic-acctdel{background:#c0392b26}
.lr-danger .lr-title{color:var(--danger)}

/* ---------- workspaces counting down to deletion ---------- */
.ic-pending{color:#b45309}
.menu-ico.ic-pending{background:#fdf0e2}
.pd-row{display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-x13) var(--sp-16);border-bottom:1px solid var(--line);text-align:left}
.pd-row .lr-body{flex:1;min-width:0}
.pd-acts{display:flex;gap:var(--sp-x7);flex-shrink:0}
/* The last week reads red, not amber. Up to then it is a reminder; after that the
   data really is about to go, and the row should stop looking like routine housekeeping. */
.pd-urgent .ic-pending{color:var(--danger)}
.pd-urgent .menu-ico.ic-pending{background:#fdecea}
.pd-urgent .lr-sub{color:var(--danger);font-weight:600}
@media(max-width:560px){
  .pd-row{flex-wrap:wrap}
  .pd-acts{width:100%;padding-left:var(--sp-48)}
}
.ex-list{margin-top:var(--sp-2);border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--surface)}
.ex-list .list-row:last-child{border-bottom:none}
.auth-consent{margin:var(--sp-14) 0 0;font-size:12px;line-height:1.5;color:var(--muted);text-align:center}
.auth-consent a,.auth-legal a{color:var(--accent-text);text-decoration:none;font-weight:600}
.auth-consent a:hover,.auth-legal a:hover{text-decoration:underline}
.auth-legal{margin:var(--sp-x18) 0 0;text-align:center;font-size:12.5px;color:var(--muted)}
/* Ajakan membuktikan alamat email. Sengaja memakai nada aksen, bukan merah:
   ini pekerjaan yang belum selesai, bukan kesalahan yang dilakukan orangnya. */
.vf-banner{display:flex;align-items:flex-start;gap:var(--sp-x11);margin:0 var(--sp-14) var(--sp-14);padding:var(--sp-12) var(--sp-14);
  border-radius:14px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);
  background:var(--accent-weak);color:var(--accent-text);flex-wrap:wrap}
.vf-banner>svg{flex:none;margin-top:var(--sp-2)}
.vf-b{flex:1 1 190px;min-width:0;display:flex;flex-direction:column;gap:var(--sp-x3)}
.vf-b b{font-size:14px;font-weight:700}
.vf-b span{font-size:12.5px;line-height:1.45;opacity:.92}
.vf-resend{flex:none;white-space:nowrap;align-self:center}
.vf-resend[disabled]{opacity:.6;cursor:default}
/* #405 — MARGIN YANG DITULIS UNTUK SATU WADAH IKUT TERBAWA KE WADAH LAIN.
   Margin mendatar var(--sp-14) di .vf-banner adalah ganti rugi untuk homeMenu(), yang
   badannya TIDAK berpadding. Di dalam .onb-card padding itu sudah dibayar kartunya, jadi
   ganti ruginya dibayar dua kali: banner jadi 28px lebih sempit daripada kedua pintu tepat
   di bawahnya, tepi kirinya meleset 14px. Diukur: banner w=278 vs pintu w=306 (400px);
   318 vs 346 (1166px) — selisih sama di dua lebar, jadi ia margin, bukan pembulatan.

     >> Nilai yang mengganti rugi sifat sebuah wadah wajib mati begitu wadahnya berganti.

   Bawahnya 18px, bukan 14px, dengan alasan sendiri: celah banner->pintu dulu 14px sementara
   celah pintu->pintu 12px — nyaris sama, jadi ketiganya terbaca sebagai satu tumpukan
   sederajat. Peringatan bukan sederajat dengan pintu; ia berdiri sendiri di atas keduanya.
   Ditulis berlingkup .onb-card supaya menu utama tidak ikut berubah sehari sebelum pembekuan. */
.onb-card .vf-banner{margin:0 0 var(--sp-x18)}
/* #405 — KARTU YANG LEBARNYA DIPATOK 400px TIDAK PUNYA RUANG UNTUK TATA LETAK BERDAMPINGAN,
   TAPI FLEX-WRAP MEMBUATNYA MENCOBA. .vf-b memakai flex:1 1 190px, jadi selama badan banner
   masih >= 190+18+11 tombolnya bertahan di samping teks. Di dalam .onb-card badan itu 318px
   di layar lebar dan 278px di ponsel — jadi ambangnya jatuh PERSIS di antara keduanya:

     1166px  tombol di samping  -> kolom teks 190px -> kalimatnya 4 baris
      400px  tombol turun       -> kolom teks 278px -> kalimatnya 3 baris

   Kartunya sama, lebarnya sama-sama dipatok 400px, orangnya melihat dua susunan berbeda —
   dan yang layarnya LEBIH LEBAR justru dapat kolom teks lebih sempit.

     >> Titik patah yang jatuh di dalam rentang yang dipatok sendiri bukan keluwesan,
        melainkan dua rancangan yang dikirim ke dua orang tanpa alasan yang bisa mereka lihat.

   Grid dua kolom memilih satu susunan untuk semua lebar, dan — ini alasan memilih grid, bukan
   margin — indent tombolnya DITURUNKAN dari lebar ikon yang sebenarnya, bukan dari angka 18
   yang disalin dari ic('mail', 18) di app.js. Angka salinan begitu akan diam-diam meleset
   pada hari seseorang mengubah ukuran ikonnya. Di sini tidak ada yang bisa meleset.
   Berlingkup .onb-card: menu utama tidak ikut berubah sehari sebelum pembekuan. */
.onb-card .vf-banner{display:grid;grid-template-columns:auto 1fr;align-items:start;row-gap:var(--sp-x9)}
.onb-card .vf-b,.onb-card .vf-resend{grid-column:2}
.onb-card .vf-resend{justify-self:start;align-self:start}
.pay-banner.locked{background:linear-gradient(180deg,#fef2f2,#fff5f5);border-color:#c0392b55;color:#991b1b}
.pay-banner.locked .pay-banner-ic{background:#c0392b1f;color:#c0392b}
.pay-banner.locked .pay-banner-b span{color:#b45309}
.pay-banner.locked>svg{color:#c0392b}
.pay-banner.trial{background:linear-gradient(180deg,var(--accent-weak),#f5f3ff);border-color:var(--accent)55;color:#3730a3}
.pay-banner.trial .pay-banner-ic{background:var(--accent)1f;color:var(--accent)}
.pay-banner.trial .pay-banner-b span{color:#5b53c7}
.pay-banner.trial>svg{color:var(--accent)}
:root[data-theme="dark"] .pay-banner.locked{background:linear-gradient(180deg,#2b1414,#231010);border-color:#c0392b66;color:#fca5a5}
:root[data-theme="dark"] .pay-banner.locked .pay-banner-b span{color:#e8a08f}
:root[data-theme="dark"] .pay-banner.trial{background:linear-gradient(180deg,#1e1b3a,#191733);border-color:color-mix(in srgb,var(--accent) 40%,transparent);color:#ffd9cf}
:root[data-theme="dark"] .pay-banner.trial .pay-banner-b span{color:#a5b4fc}
/* Visible only to screen readers — announces state without showing text on screen */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* Loading skeleton — shown the moment a screen is opened, before its data lands */
.sk-wrap{padding:var(--sp-14) var(--sp-16);display:flex;flex-direction:column;gap:var(--sp-16)}
.sk-row{display:flex;align-items:center;gap:var(--sp-12)}
.sk-ava{width:42px;height:42px;border-radius:12px;flex:none;background:var(--surface-2)}
.sk-lines{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-8)}
.sk-l{height:11px;border-radius:6px;background:var(--surface-2);display:block}
.sk-l.w1{width:52%}
.sk-l.w2{width:78%}
.sk-ava,.sk-l{position:relative;overflow:hidden}
.sk-ava::after,.sk-l::after{content:"";position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,rgba(255,255,255,.35),transparent);animation:skshine 1.25s infinite}
:root[data-theme="dark"] .sk-ava::after,:root[data-theme="dark"] .sk-l::after{background:linear-gradient(90deg,transparent,rgba(255,255,255,.07),transparent)}
@keyframes skshine{100%{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){.sk-ava::after,.sk-l::after{animation:none}}
/* Printable invoice */
.bl-inv-btn{display:inline-flex;align-items:center;gap:var(--sp-6);margin-top:var(--sp-12);border:1px solid var(--line);background:var(--surface);color:var(--text);font-size:12.5px;font-weight:650;padding:var(--sp-x7) var(--sp-12);border-radius:9px;cursor:pointer;transition:border-color .15s,background .15s}
.bl-inv-btn:hover{border-color:var(--accent);background:var(--accent-weak);color:var(--accent-text)}
.inv-wrap{position:fixed;inset:0;z-index:80;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:var(--sp-20) var(--sp-14) 96px;overflow:auto;overscroll-behavior:contain}
.inv-sheet{width:100%;max-width:640px;background:#fff;color:#1a1a1a;border-radius:12px;padding:var(--sp-x34) var(--sp-x34) var(--sp-x28);box-shadow:0 18px 50px #00000040;font-size:13.5px;line-height:1.5}
.inv-top{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--sp-20);padding-bottom:var(--sp-x18);border-bottom:2px solid #1a1a1a}
.inv-issuer{font-size:18px;font-weight:750}
.inv-issuer-i{margin-top:var(--sp-4);font-size:12px;color:#555;white-space:pre-wrap;line-height:1.45}
.inv-title{text-align:right;font-size:20px;font-weight:750;letter-spacing:.08em}
.inv-no{margin-top:var(--sp-x3);font-size:11.5px;font-weight:600;letter-spacing:0;color:#555}
.inv-meta{display:flex;flex-wrap:wrap;gap:var(--sp-x22);margin:var(--sp-x18) 0 var(--sp-20)}
.inv-meta span{display:block;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#777}
.inv-meta b{font-size:13.5px;font-weight:650}
.inv-tbl{width:100%;border-collapse:collapse}
.inv-tbl th{text-align:left;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#777;padding:0 0 var(--sp-8);border-bottom:1px solid #ddd}
.inv-tbl td{padding:var(--sp-14) 0;vertical-align:top;border-bottom:1px solid #eee}
.inv-tbl .r{text-align:right;white-space:nowrap}
.inv-sub{display:block;margin-top:var(--sp-x3);font-size:11.5px;color:#666}
.inv-tbl tfoot td{border-bottom:none;border-top:2px solid #1a1a1a;font-size:16px;font-weight:750;padding-top:var(--sp-12)}
.inv-status{display:inline-block;margin-top:var(--sp-x18);padding:var(--sp-x5) var(--sp-14);border-radius:6px;font-size:12px;font-weight:750;letter-spacing:.08em}
.inv-status.ok{color:#177245;background:#17724518;border:1px solid #17724555}
.inv-status.due{color:#c0392b;background:#c0392b12;border:1px solid #c0392b55}
.inv-payto{margin-top:var(--sp-14);padding:var(--sp-x11) var(--sp-x13);border-radius:8px;background:#f6f6f8;font-size:12.5px;white-space:pre-wrap;line-height:1.5}
.inv-payto span{display:block;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#777;margin-bottom:var(--sp-x3)}
.inv-foot{margin-top:var(--sp-x22);padding-top:var(--sp-14);border-top:1px solid #eee;font-size:11px;color:#888;font-style:italic}
.inv-actions{position:fixed;left:0;right:0;bottom:0;display:flex;gap:var(--sp-10);justify-content:center;padding:var(--sp-14) var(--sp-14) calc(var(--sp-14) + var(--sab));background:var(--surface);border-top:1px solid var(--line)}
@media print{
  body *{visibility:hidden!important}
  .inv-sheet,.inv-sheet *{visibility:visible!important}
  .inv-wrap{position:static;padding:0;overflow:visible}
  .inv-sheet{position:absolute;left:0;top:0;max-width:none;box-shadow:none;border-radius:0;padding:0;margin:0}
  .inv-backdrop,.inv-actions{display:none!important}
  @page{margin:16mm}
}
.srv-bar.q-ok span{background:#177245}
.srv-bar.q-warn span{background:#f59e0b}
.srv-bar.q-bad span{background:#c0392b}
.q-note{display:flex;align-items:flex-start;gap:var(--sp-x7);margin-top:var(--sp-10);font-size:12.5px;line-height:1.45}
.q-note.warn{color:#b45309}
.q-note.bad{color:#c0392b}
.q-note svg{flex:none;margin-top:var(--sp-1)}
:root[data-theme="dark"] .q-note.warn{color:#fbbf24}
:root[data-theme="dark"] .q-note.bad{color:#f87171}
/* Base theming for EVERY form control.
   Written with :where() so it carries zero specificity: any existing rule still wins,
   but a newly added field can never fall back to the browser default (white box on a
   dark background). This closes the whole class of bug rather than one field. */
:where(.app input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range])),
:where(.app textarea),
:where(.app select){
  /* background-color, bukan shorthand: aturan dropdown bersama di bawah memasang
     background-image untuk panahnya, dan shorthand di sini akan mengosongkannya. */
  background-color:var(--surface);color:var(--text);border:1px solid var(--line);
  border-radius:9px;padding:var(--sp-x9) var(--sp-x11);font:inherit;font-size:14px;outline:none;
}
:where(.app input::placeholder),:where(.app textarea::placeholder){color:var(--muted);opacity:1}
/* Long lists: skip painting rows that are off-screen. The browser reserves the space
   but does no layout/paint work for them, so a list of thousands scrolls as smoothly as
   a list of ten. contain-intrinsic-size keeps the scrollbar honest while they're skipped. */
.cl-entry,.fb-card,.list-row,.att-rec,.bl-pay-row,.act-row,.dev-row,.msg-row{
  content-visibility:auto;
}
.cl-entry{contain-intrinsic-size:auto 180px}
.fb-card{contain-intrinsic-size:auto 120px}
.list-row,.bl-pay-row,.dev-row{contain-intrinsic-size:auto 64px}
.att-rec{contain-intrinsic-size:auto 96px}
.act-row{contain-intrinsic-size:auto 56px}
/* Jump back to the newest item (screens where newest sits at the top). */
.col-body,.m-pane{position:relative}
.jump-top{position:fixed;right:20px;bottom:calc(84px + var(--sab));z-index:60;width:42px;height:42px;border-radius:50%;
  border:1px solid var(--line);background:var(--surface);color:var(--accent-text);
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  box-shadow:0 6px 18px #0000002e;opacity:0;pointer-events:none;transform:translateY(8px);
  transition:opacity .18s,transform .18s}
.jump-top.show{opacity:1;pointer-events:auto;transform:translateY(0)}
@media(min-width:900px){.jump-top{bottom:28px}}
.jump-top:hover{background:var(--accent-weak);border-color:var(--accent)}
/* Collapsed "shipped from your feedback" block */
.cl-shipped>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--sp-8)}
.cl-shipped>summary::-webkit-details-marker{display:none}
.cl-shipped>summary::after{content:"";margin-left:auto;width:8px;height:8px;border-right:2px solid var(--muted);
  border-bottom:2px solid var(--muted);transform:rotate(45deg);transition:transform .18s}
.cl-shipped[open]>summary::after{transform:rotate(-135deg)}
.cl-count{font-size:11px;font-weight:700;color:var(--accent-text);background:var(--accent-weak);
  padding:var(--sp-2) var(--sp-8);border-radius:20px}
/* In-app legal documents */
.lg-tabs{display:flex;gap:var(--sp-6);margin:var(--sp-4) 0 var(--sp-x18);background:var(--surface-2);padding:var(--sp-4);border-radius:11px}
.lg-tab{flex:1;border:none;background:none;color:var(--muted);font-size:13px;font-weight:650;padding:var(--sp-x9) var(--sp-6);border-radius:8px;cursor:pointer;transition:background .15s,color .15s}
.lg-tab.on{background:var(--surface);color:var(--accent-text);box-shadow:0 1px 3px #0000001f}
.lg-body{font-size:14.5px;line-height:1.65;color:var(--text)}
.lg-body h2{font-size:17px;margin:var(--sp-x26) 0 var(--sp-8);padding-top:var(--sp-14);border-top:1px solid var(--line)}
.lg-body h2:first-child{border-top:none;padding-top:0;margin-top:var(--sp-4)}
.lg-body h3{font-size:15px;margin:var(--sp-x18) 0 var(--sp-6)}
.lg-body p{margin:var(--sp-x9) 0}
.lg-body ul,.lg-body ol{padding-left:var(--sp-20);margin:var(--sp-x9) 0}
.lg-body li{margin:var(--sp-x5) 0}
.lg-body code{background:var(--accent-weak);color:var(--accent-text);padding:var(--sp-1) var(--sp-6);border-radius:5px;font-size:.9em}
.lg-tbl{width:100%;border-collapse:collapse;margin:var(--sp-14) 0;font-size:13.5px}
.lg-tbl th,.lg-tbl td{text-align:left;padding:var(--sp-8) var(--sp-x9);border-bottom:1px solid var(--line);vertical-align:top}
.lg-tbl th{font-size:11px;text-transform:uppercase;letter-spacing:.03em;color:var(--muted)}
.q-set{margin-top:var(--sp-14);padding-top:var(--sp-12);border-top:1px solid var(--line)}
.q-set .bl-field{margin-bottom:var(--sp-8)}
.q-set .bl-field input{width:110px}
.q-commit{display:flex;align-items:flex-start;gap:var(--sp-8);flex-wrap:wrap;margin-top:var(--sp-12);padding-top:var(--sp-12);border-top:1px solid var(--line);font-size:12.5px;color:var(--muted);line-height:1.5}
.q-commit b{color:var(--text)}
.q-commit.bad{color:#c0392b}
.q-commit.bad b{color:#c0392b}
:root[data-theme="dark"] .q-commit.bad,:root[data-theme="dark"] .q-commit.bad b{color:#f87171}
/* Where to transfer */
.bl-payto-box{margin-top:var(--sp-12);padding:var(--sp-x11) var(--sp-x13);border-radius:11px;background:var(--accent-weak);border:1px solid var(--accent);border-color:color-mix(in srgb,var(--accent) 30%,transparent)}
.bl-payto-h{display:flex;align-items:center;gap:var(--sp-6);font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--accent-text)}
.bl-payto-v{margin-top:var(--sp-x5);font-size:14px;font-weight:600;color:var(--text);white-space:pre-wrap;word-break:break-word;line-height:1.45}
.bl-payto-copy{display:inline-flex;align-items:center;gap:var(--sp-x5);margin:var(--sp-2) 0 -4px -8px;border:none;background:none;padding:var(--sp-x9) var(--sp-8);font-size:12px;font-weight:650;color:var(--accent-text);cursor:pointer;border-radius:8px}
.bl-payto-copy:hover{text-decoration:underline}
.bl-payto{width:100%;border:1px solid var(--line);border-radius:9px;padding:var(--sp-x9) var(--sp-x11);font-size:14px;font-family:inherit;outline:none;background:var(--surface);color:var(--text);resize:vertical}
.bl-payto:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
/* Developer income summary */
.bl-money{display:flex;gap:var(--sp-8);margin-bottom:var(--sp-10)}
.bl-money-c{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-1);padding:var(--sp-10) var(--sp-x11);border-radius:11px;border:1px solid var(--line)}
.bl-money-l{font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.03em}
/* v343 — nominal TIDAK BOLEH patah di tengah. "Rp 540.000" pecah jadi dua baris
   begitu angkanya bertambah satu digit; ukurannya yang mengalah, bukan angkanya. */
.bl-money-v{font-size:16px;font-weight:750;color:var(--text);line-height:1.25;
  white-space:nowrap;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
@media(max-width:1100px){.bl-money-v{font-size:14.5px}}
.bl-money-s{font-size:11px;color:var(--muted)}
.bl-money-c.ok{background:#17724514;border-color:#17724533}
.bl-money-c.ok .bl-money-v{color:#177245}
.bl-money-c.wait{background:#f59e0b14;border-color:#f59e0b33}
.bl-money-c.wait .bl-money-v{color:#b45309}
.bl-money-c.bl-due{background:#c0392b14;border-color:#c0392b33}
.bl-money-c.bl-due .bl-money-v{color:#c0392b}
:root[data-theme="dark"] .bl-money-c.ok .bl-money-v{color:#4ade80}
:root[data-theme="dark"] .bl-money-c.wait .bl-money-v{color:#fbbf24}
:root[data-theme="dark"] .bl-money-c.bl-due .bl-money-v{color:#f87171}
.bl-badge.unpaid{color:#c0392b;background:#c0392b1a}
.bl-pay-ini{width:44px;height:44px;flex:none;border-radius:11px;background:var(--surface-2);color:var(--muted);font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center}
@media(max-width:520px){.bl-money{flex-direction:column}}
/* VPS storage health */
.srv-card{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:var(--sp-14) var(--sp-16);margin-bottom:var(--sp-14)}
.srv-h{display:flex;align-items:center;gap:var(--sp-8);flex-wrap:wrap;font-size:13px;font-weight:700;color:var(--text);margin-bottom:var(--sp-10)}
.srv-bar{height:10px;border-radius:6px;background:var(--surface-2);overflow:hidden}
.srv-bar span{display:block;height:100%;border-radius:6px;background:#177245;transition:width .4s}
.srv-card.warn .srv-bar span{background:#f59e0b}
.srv-card.bad .srv-bar span{background:#c0392b}
.srv-nums{font-size:13px;color:var(--muted);margin-top:var(--sp-8)}
.srv-nums b{color:var(--text)}
/* #84 — Storage breakdown & cleanup (owner) */
.stg-total{text-align:center;padding:var(--sp-16) 0 var(--sp-6)}
.stg-total-n{font-size:34px;font-weight:800;letter-spacing:-.02em;color:var(--text);line-height:1.1}
.stg-total-l{font-size:13px;color:var(--muted);margin-top:var(--sp-x3)}
.stg-list{display:flex;flex-direction:column;gap:var(--sp-x15);margin:var(--sp-14) 0 var(--sp-4)}
.stg-row{display:flex;gap:var(--sp-12);align-items:flex-start}
.stg-ic{flex:0 0 auto;width:38px;height:38px;border-radius:11px;display:grid;place-items:center}
.stg-ic svg{width:18px;height:18px}
.stg-blue{background:#e6f0ff;color:#2563eb}
.stg-amber{background:#fff2d9;color:#d97706}
.stg-teal{background:#d9f5ef;color:#0f9d8f}
.stg-body{flex:1 1 auto;min-width:0}
.stg-top{display:flex;justify-content:space-between;align-items:baseline;gap:var(--sp-8)}
.stg-label{font-weight:650;color:var(--text)}
.stg-bytes{font-weight:700;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}
.stg-track{height:8px;border-radius:5px;background:var(--surface-2);overflow:hidden;margin:var(--sp-6) 0 var(--sp-4)}
.stg-fill{display:block;height:100%;border-radius:5px;transition:width .4s}
.stg-fill-blue{background:linear-gradient(90deg,#60a5fa,#2563eb)}
.stg-fill-amber{background:linear-gradient(90deg,#fbbf24,#d97706)}
.stg-fill-teal{background:linear-gradient(90deg,#5eead4,#0f9d8f)}
.stg-sub{font-size:12px;color:var(--faint)}
.stg-empty{text-align:center;padding:var(--sp-x34) var(--sp-12);color:var(--muted)}
.stg-empty svg{opacity:.5;margin-bottom:var(--sp-6)}
.stg-reclaim{margin-top:var(--sp-x18)}
.stg-reclaim .bl-card-h{display:flex;align-items:center;gap:var(--sp-x7)}
.stg-reclaim-note{font-size:13px;color:var(--muted);line-height:1.5;margin:var(--sp-2) 0 var(--sp-12)}
.stg-reclaim-note b{color:var(--text)}
.stg-days{display:flex;gap:var(--sp-8);margin-bottom:var(--sp-12)}
.stg-day{flex:1;padding:var(--sp-x9) var(--sp-6);border:1.5px solid var(--line);background:var(--surface);border-radius:10px;font-weight:600;font-size:13px;color:var(--muted);cursor:pointer;transition:.15s}
.stg-day.on{border-color:var(--danger);color:var(--danger);background:var(--surface-2)}
.stg-clear-btn{width:100%;display:flex;align-items:center;justify-content:center;gap:var(--sp-x7)}
.stg-owner-only{display:flex;align-items:center;gap:var(--sp-6);font-size:13px;color:var(--faint);margin:var(--sp-2) 0 0}
.stg-quota{width:100%;display:flex;align-items:center;gap:var(--sp-x9);margin-top:var(--sp-16);padding:var(--sp-x13) var(--sp-14);border:1px solid var(--line);border-radius:12px;background:var(--surface);color:var(--muted);font-size:13px;text-align:left;cursor:pointer;transition:.15s}
.stg-quota>span{flex:1}
.stg-quota:hover{background:var(--surface-2)}
:root[data-theme="dark"] .stg-blue{background:#16273f;color:#7db1f5}
:root[data-theme="dark"] .stg-amber{background:#3a2c12;color:#fbbf24}
:root[data-theme="dark"] .stg-teal{background:#123230;color:#5eead4}
.srv-grid{display:flex;gap:var(--sp-8);margin-top:var(--sp-12)}
.srv-grid>div{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-x9) var(--sp-10);border-radius:10px;background:var(--surface-2)}
.srv-grid span{font-size:10.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em;font-weight:700}
.srv-grid b{font-size:14px;color:var(--text)}
.srv-spark{display:block;width:100%;height:44px;margin-top:var(--sp-12);color:var(--accent);opacity:.85}
.srv-advice{display:flex;align-items:flex-start;gap:var(--sp-x7);margin-top:var(--sp-12);font-size:12.5px;line-height:1.45;color:var(--muted)}
.srv-advice svg{flex:none;margin-top:var(--sp-1)}
.srv-card.warn .srv-advice{color:#b45309}
.srv-card.bad .srv-advice{color:#c0392b}
:root[data-theme="dark"] .srv-card.warn .srv-advice{color:#fbbf24}
:root[data-theme="dark"] .srv-card.bad .srv-advice{color:#f87171}
/* v343 — GRID, bukan flex, supaya kalimat bukti dapat barisnya SENDIRI selebar kartu.
   Dengan flex ia berdesakan di kolom tengah bersama nama dan nominal, dan di kolom 360px
   "iPaymu confirmed this payment" pecah jadi tiga baris di sebelah dua tombol. Ketahuan
   dengan MERENDER data yang sepanjang data sungguhan, bukan dengan membaca CSS-nya. */
.bl-pay-row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;
  column-gap:var(--sp-12);row-gap:var(--sp-x3);padding:var(--sp-x9) 0;border-bottom:1px solid var(--line)}
.bl-pay-b{flex:1;min-width:0;display:flex;flex-direction:column}
.bl-pay-amt{font-size:12.5px;color:var(--muted)}
.bl-rev-actions{display:flex;gap:var(--sp-6);flex:none}
/* BUTIR 138 / v343 — satu baris yang mengatakan APA yang membuat pembayaran ini pantas
   disetujui. Warnanya memakai token yang sudah ada (ok/bad), bukan warna baru: barisnya
   harus terbaca sebagai bagian dari sistem, bukan sebagai tempelan peringatan. */
.bl-pay-ev{grid-column:1/-1;display:flex;align-items:center;gap:var(--sp-x5);
  font-size:11.5px;line-height:1.35;min-width:0}
.bl-pay-ev svg{flex:none}
.bl-pay-ev.ok{color:var(--good-text)}
.bl-pay-ev.bad{color:var(--danger)}
.mic-btn:hover{background:var(--surface-2);color:var(--accent)}
.rec-bar{display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-8) var(--sp-12)}
.rec-cancel{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--danger);background:none;border:none;cursor:pointer;flex:none}
.rec-cancel:hover{background:var(--surface-2)}
.rec-dot{width:11px;height:11px;border-radius:50%;background:var(--danger);flex:none;animation:recPulse 1s ease-in-out infinite}
@keyframes recPulse{0%,100%{opacity:1}50%{opacity:.25}}
.rec-time{font-size:15px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;min-width:44px}
.rec-hint{flex:1;font-size:13px;color:var(--muted)}
.rec-send{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--accent);color:#fff;border:none;cursor:pointer;flex:none;box-shadow:0 1px 3px rgba(201,58,34,.4)}
/* BUTIR 143 — sekeluarga dengan .poll-card dan .chat-card: lantai tetap di dalam gelembung 74%. */
.voice-msg{display:flex;align-items:center;gap:var(--sp-10);min-width:min(190px,100%);padding:var(--sp-x3) var(--sp-2)}
.voice-play{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--accent);color:#fff;border:none;cursor:pointer;flex:none}
.bubble.me .voice-play{background:#fff;color:var(--accent)}
.voice-wave{flex:1;height:4px;border-radius:3px;background:var(--line);position:relative;overflow:hidden}
.bubble.me .voice-wave{background:rgba(255,255,255,.4)}
.voice-prog{position:absolute;left:0;top:0;bottom:0;width:0;background:var(--accent);border-radius:3px}
.bubble.me .voice-prog{background:#fff}
.voice-time{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums;flex:none;min-width:30px}
.bubble.me .voice-time{color:rgba(255,255,255,.85)}
.conv-row{display:flex;align-items:center;position:relative;border-bottom:1px solid var(--line)}
.conv-row:hover{background:var(--surface-2)}
.conv-row.active{background:var(--accent-weak)}
.conv-open{flex:1;min-width:0;display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-x11) var(--sp-14);background:none;border:none;text-align:left;cursor:pointer;color:inherit;font:inherit}
.conv-menu-btn{opacity:0;flex:none;width:32px;height:32px;margin-right:var(--sp-8);border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--muted);background:none;border:none;cursor:pointer;transition:opacity .12s}
.conv-row:hover .conv-menu-btn{opacity:.7}
.conv-menu-btn:hover{opacity:1;background:var(--surface)}
@media (hover:none){.conv-menu-btn{opacity:.5}}
.conv-ic{display:inline-flex;color:var(--muted);margin-right:var(--sp-4)}
.conv-ic svg{opacity:.7}
.unread-dot{display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--accent)}
.badge.muted{background:var(--muted)}
.lr-time{font-size:12px;color:var(--faint)}
.conv-row.unread .lr-title{font-weight:700}
.arch-header{display:flex;align-items:center;gap:var(--sp-10);width:100%;padding:var(--sp-x11) var(--sp-14);border:none;border-bottom:1px solid var(--line);background:none;color:var(--muted);font-size:14px;font-weight:600;cursor:pointer}
.arch-header:hover{background:var(--surface-2)}
.arch-header>span{flex:1;text-align:left}
.typing-row{padding:var(--sp-2) var(--sp-4) var(--sp-8)}
.typing-bubble{display:inline-flex;align-items:center;gap:var(--sp-8);background:var(--surface-2);border:1px solid var(--line);border-radius:14px;padding:var(--sp-x9) var(--sp-x13)}
.typing-name{font-size:12px;font-weight:600;color:var(--muted)}
.typing-dots{display:inline-flex;gap:var(--sp-4)}
.typing-dots i{width:7px;height:7px;border-radius:50%;background:var(--faint);display:inline-block;animation:typingBounce 1.2s infinite}
.typing-dots i:nth-child(2){animation-delay:.2s}
.typing-dots i:nth-child(3){animation-delay:.4s}
@keyframes typingBounce{0%,60%,100%{transform:translateY(0);opacity:.5}30%{transform:translateY(-4px);opacity:1}}
.chat-title{flex:1;display:flex;flex-direction:column;justify-content:center;line-height:1.25;min-width:0}
.ct-name{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chat-status{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Hijau "online". #12b76a cuma 2.62:1 di atas putih — jelas di bawah 4.5. Digelapkan ke
   #0a7d4f (5.17) untuk mode terang; di mode gelap hijau terang justru kontras tinggi di
   atas latar gelap, jadi dikembalikan terang di sana. */
.pres-online{color:#0a7d4f;font-weight:600}
:root[data-theme="dark"] .pres-online{color:#12b76a}
.ava-online{position:relative;display:inline-flex;flex:none}
.online-dot{position:absolute;right:-1px;bottom:-1px;width:11px;height:11px;border-radius:50%;background:#12b76a;border:2px solid var(--surface)}
.msg-tick{display:inline-flex;vertical-align:middle;margin-left:var(--sp-x3);color:var(--faint)}
.bubble.me .msg-tick{color:rgba(255,255,255,.72)}
.bubble.me .msg-tick.read{color:#8fd7ff}
.msg-tick svg{margin-top:-1px}
.mi-modal{width:340px}
.mi-body{max-height:min(50vh,340px);overflow-y:auto;margin-top:var(--sp-6)}
.mi-sec{margin-top:var(--sp-10)}
.mi-label{display:flex;align-items:center;gap:var(--sp-x7);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted);margin-bottom:var(--sp-6)}
.mi-label svg{color:var(--muted)}
.mi-label.mi-read svg{color:#0a7ea4}
.mi-row{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-6) var(--sp-4)}
.mi-name{font-size:14px}
.mi-empty{padding:var(--sp-20);text-align:center;color:var(--muted)}
.disap-banner{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-x7) var(--sp-14);background:var(--accent-weak);color:var(--accent-text);font-size:12.5px;font-weight:500;border-bottom:1px solid var(--line)}
.disap-banner svg{flex:none}
.mm-title{padding:var(--sp-8) var(--sp-14) var(--sp-4);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.mm-gap{display:inline-block;width:17px;flex:none}
.icon-btn.chat-info.on{color:var(--accent)}
.bubble.them{background:var(--surface-2);border:1px solid var(--line);align-self:flex-start;border-bottom-left-radius:5px}
.bubble.me{background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 84%,#fff),var(--accent));color:#fff;align-self:flex-end;border-bottom-right-radius:5px}
.bubble .bt{display:block;font-size:10px;opacity:.6;margin-top:var(--sp-x3)}
.bubble.grouped{margin-top:-6px}
/* butir 111 — avatar dijajarkan ke ATAS bubble, bukan ke bawah.
   Avatar cuma digambar di pesan PERTAMA sebuah rentetan (sesudahnya .mrow-sp yang kosong),
   jadi ia milik baris pertama bubble itu — dan baris pertama itulah tempat namanya berada.
   Dengan flex-end, pada pesan panjang wajah dan nama pemiliknya terlempar ke dua ujung
   bubble yang berjauhan, dan mata harus menempuh seluruh pesan untuk menghubungkannya.
   DIUKUR di demo: pesan tiga baris menaruh avatar 41px di bawah namanya sendiri. */
.mrow{display:flex;align-items:flex-start;gap:var(--sp-8);align-self:flex-start;max-width:82%}
.mrow .bubble{max-width:100%}
.mrow.grouped{margin-top:-6px}
.mrow .bubble.grouped{margin-top:0}
.mrow-ava,.mrow-sp{width:28px;flex:none}
.msg-link{color:#fff;text-decoration:underline;text-underline-offset:2px}
.bubble.them .msg-link{color:var(--accent-text)}
.bubble .att-img,.bubble .att-file{margin-top:var(--sp-6)}
.day-sep{text-align:center;margin:var(--sp-12) 0 var(--sp-4)}
.day-chip{display:inline-block;background:var(--surface-2);color:var(--muted);font:inherit;font-size:11px;font-weight:600;padding:var(--sp-x3) var(--sp-12);border-radius:999px;border:1px solid transparent;cursor:pointer;transition:border-color .12s,color .12s}
.day-chip:hover{border-color:var(--line);color:var(--text)}
/* "Disimpan di [workspace]" — baris transparansi storage di puncak DM */
.chat-host-note{display:flex;align-items:center;justify-content:center;gap:var(--sp-x5);margin:var(--sp-12) var(--sp-12) var(--sp-6);color:var(--muted);font-size:11px;line-height:1.4;text-align:center}
.chat-host-note svg{flex:none}
.chat-host-note b{color:var(--text);font-weight:600}
.unread-sep{display:flex;align-items:center;gap:var(--sp-10);margin:var(--sp-10) var(--sp-2) var(--sp-4);color:var(--accent-text);font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.6px}
.unread-sep::before,.unread-sep::after{content:"";flex:1;height:1px;background:var(--accent);opacity:.5}
.jump-latest{position:fixed;right:18px;bottom:calc(82px + var(--sab));z-index:20;width:40px;height:40px;border-radius:50%;background:var(--surface);border:1px solid var(--line);box-shadow:0 4px 16px rgba(0,0,0,.18);color:var(--accent-text);display:flex;align-items:center;justify-content:center;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .18s,transform .18s}
.jump-latest.show{opacity:1;transform:none;pointer-events:auto}
.jump-latest:hover{background:var(--surface-2)}
.composer{position:relative;display:flex;gap:var(--sp-8);align-items:flex-end;width:100%}   /* jangkar untuk .co-count */
.composer textarea,.composer input{flex:1;min-width:0;border:1px solid var(--line);border-radius:20px;padding:var(--sp-x9) var(--sp-x15);font-size:14px;line-height:1.4;outline:none;font-family:inherit;background:var(--surface);color:var(--text)}
/* min-height pins the empty box to exactly one line (14px × 1.4 + 18px padding
   + 2px border = 40px). Without it the box is sized by rows="1" alone, so if the
   placeholder ever wraps the second line is drawn outside the box and clipped. */
.composer textarea{resize:none;min-height:40px;max-height:120px;overflow-y:auto}
.composer textarea:focus,.composer input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.attach-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;flex:none;border-radius:50%;color:var(--muted);background:none;border:none;cursor:pointer}
.attach-btn:hover{color:var(--accent-text);background:var(--surface-2)}
/* chat header kebab menu (top-right) + composer attach sheet (bottom-left) */
.chat-menu{top:calc(var(--topbar-total) + 6px);right:10px;left:auto;min-width:210px;width:max-content}
.attach-sheet{min-width:200px;width:max-content}   /* posisinya diatur positionPopMenu() */
/* Attach menu — grid tile colorful (gaya WhatsApp), tetap popup dekat tombol + (bukan sheet
   layar-penuh) supaya di desktop tetap patuh model kolom. Ikon monokrom (currentColor): tiap
   tile mengatur warna ikon + lingkaran pastel sewarna, jadi tak pernah bentrok. */
.attach-grid{padding:var(--sp-12) var(--sp-12) var(--sp-10)}
.attach-grid-in{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-2)}
.attach-tile{display:flex;flex-direction:column;align-items:center;gap:var(--sp-x7);padding:var(--sp-10) var(--sp-6) var(--sp-x7);background:none;border:0;border-radius:14px;cursor:pointer;transition:background .12s,transform .08s;-webkit-tap-highlight-color:transparent}
.attach-tile:hover{background:var(--hover)}
.attach-tile:active{transform:scale(.94)}
.attach-tile-ic{width:50px;height:50px;border-radius:50%;display:flex;align-items:center;justify-content:center}
.attach-tile-label{font-size:12px;line-height:1.1;text-align:center;color:var(--muted)}
.at-photo{color:#2f7ae5;background:rgba(47,122,229,.14)}
.at-file{color:#7c5cff;background:rgba(124,92,255,.14)}
.at-loc{color:#ef4444;background:rgba(239,68,68,.13)}
.at-checkin{color:#f79009;background:rgba(247,144,9,.16)}
.at-poll{color:#12b76a;background:rgba(18,183,106,.14)}
.send{width:40px;height:40px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.send:hover{background:var(--accent-text)}
.send:disabled{opacity:.5}

/* ---------- detail generic ---------- */
.detail-pad{padding:var(--sp-x22);max-width:760px}
.detail-pad h1{font-size:22px;font-weight:700;margin-bottom:var(--sp-14);line-height:1.25}
.meta-row{display:flex;align-items:center;gap:var(--sp-10);color:var(--muted);font-size:14px;margin-bottom:var(--sp-10)}
/* butir 105 — akhir pengulangan, di belakang labelnya. Diredupkan karena ia keterangan
   atas kalimat di sebelahnya, bukan fakta yang berdiri sendiri. */
.meta-until{color:var(--muted)}
.meta-row.overdue{color:var(--due-late);font-weight:500}
.meta-row.due-today{color:var(--due-today);font-weight:500}
.progress{height:7px;border-radius:5px;background:var(--line);overflow:hidden;margin:var(--sp-14) 0 var(--sp-20)}
.progress>i{display:block;height:100%;background:var(--accent)}
/* padding:0 16px lines the label up with .list-row, which is padded 13px 16px.
   Without it the label sat flush against the edge while every row beside it was
   indented — the misalignment is what read as "cramped", more than the gap did.
   Inside .detail-pad the container is already padded 22px, so it resets to 0
   rather than indenting twice. */
.section-label{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);margin:var(--sp-24) 0 var(--sp-12);padding:0 var(--sp-16)}
/* Butir 78 — judul kelompok yang menuntut tindakan. Warnanya SAMA dengan lencana yang
   mengantar orang ke sini, jadi angka di lencana dan angka di judul terbaca sebagai satu hal,
   bukan dua kebetulan. Hanya warnanya yang berubah; bentuk, ukuran dan jaraknya tetap milik
   .section-label supaya tidak lahir dialek judul baru. */
.sl-todo{color:var(--danger)}
.detail-pad .section-label{padding:0}
/* First label in a scroll pane has no row above it to breathe away from. */
.col-body > .section-label:first-child,.m-pane > .section-label:first-child{margin-top:var(--sp-16)}
.sub-item{display:flex;align-items:center;gap:var(--sp-6)}
.sub-toggle{flex:1;min-width:0;display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-x9) 0;font-size:14px;cursor:pointer;background:none;border:none;text-align:left;color:inherit}
.sub-del{opacity:0;flex-shrink:0;display:flex;align-items:center;justify-content:center;padding:var(--sp-x5);border-radius:6px;background:none;border:none;color:var(--muted);cursor:pointer;transition:opacity .15s ease,color .15s ease}
.sub-item:hover .sub-del{opacity:.6}
.sub-del:hover,.sub-del:focus-visible{opacity:1;color:var(--danger)}
@media (hover:none){.sub-del{opacity:.5}}
.check{width:20px;height:20px;border-radius:6px;border:2px solid var(--line);display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0}
/* MASUKAN-173 — baris tugas berhenti jadi SATU tombol.
   Wadahnya kini <div>; isinya dua kendali. Yang dijaga di sini: GEOMETRINYA TIDAK BOLEH
   BERGESER. Butir ini tentang apa yang terjadi saat ditekan, bukan tentang tampilannya —
   dan sebuah perbaikan perilaku yang diam-diam menggeser tiap baris tugas akan terbaca
   sebagai dua perubahan, satu di antaranya tidak diminta siapa pun.

   Karena itu: padding & gap tetap milik .list-row, .lr-open memakai gap yang sama supaya
   .lr-body dan .lr-meta duduk persis seperti dulu, dan sasaran ketuk kotaknya dilebarkan
   dengan padding yang DITARIK KEMBALI oleh margin negatif senilai sama — 40x40 untuk jari,
   nol piksel untuk tata letak. Angkanya ditulis SEKALI (--ketuk) dan keduanya diturunkan
   darinya; ditulis terpisah, keduanya akan berpisah dan barisnya bergeser diam-diam. */
/* ============================================================================
   MASUKAN-172 — MODE PILIH
   ============================================================================
   Di mode pilih SELURUH BARIS memilih, jadi kotaknya bukan kendali sendiri melainkan
   penanda di dalam tombol barisnya (.lr-check-in, bukan .lr-check). Bedanya bukan
   penamaan: .lr-check punya sasaran ketuk 40x40 sendiri karena ia tombol; di sini
   sasaran ketuknya SELURUH baris, dan memberi kotaknya sasaran kedua di dalamnya akan
   membuat ketukan di kotak dan ketukan di judul terasa seperti dua hal berbeda.

   Jarak dan warnanya diturunkan dari yang sudah ada — nol angka baru dikarang. */
.lr-pick .lr-open{gap:var(--sp-12)}
.lr-check-in{flex:none;display:flex;align-items:center;justify-content:center}
.lr-pick.picked{background:var(--accent-weak)}
.lr-pick.picked .lr-title{font-weight:600}

/* Bilah kaki mode pilih. Ia menggantikan kaki layar biasa seluruhnya, jadi ia memakai
   lebarnya penuh dan tidak bersaing dengan apa pun. */
.pick-bar{display:flex;align-items:center;gap:var(--sp-8);flex:1 1 auto;min-width:0}
.pick-n{font-weight:700;font-size:14px;white-space:nowrap}
.pick-bar .btn-ghost{background:transparent;border-color:transparent;color:var(--accent-text);
  padding-left:var(--sp-6);padding-right:var(--sp-6);white-space:nowrap}
.pick-bar .btn-ghost:hover{background:var(--hover)}

.lr-check{--ketuk:var(--sp-10);flex:none;display:flex;align-items:center;justify-content:center;
  padding:var(--ketuk);margin:calc(-1 * var(--ketuk));background:none;border:0;border-radius:10px;
  color:inherit;cursor:pointer}
.lr-check:hover .check{border-color:var(--accent)}
.lr-check:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* PADDINGNYA TETAP MILIK .list-row, dan .lr-open MEREGANG mengisi tingginya. Percobaan
   pertama memindahkan padding tegaknya ke sini — dan ratchet warisan langsung berbunyi:
   637 -> 638, karena 13px itu nilai warisan dan saya baru saja menuliskannya untuk KEDUA
   kalinya. Ratchet-nya benar; jawabannya bukan menaikkan angkanya melainkan tidak menambah
   angka sama sekali. `align-self:stretch` memberi sasaran ketuk setinggi barisnya tanpa
   menyebut satu ukuran pun. */
.lr-open{flex:1;min-width:0;align-self:stretch;display:flex;align-items:center;gap:var(--sp-12);
  background:none;border:0;text-align:left;color:inherit;font:inherit;cursor:pointer}
.lr-open:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.check.done{background:var(--accent);border-color:var(--accent)}
.sub-item.done .lbl{color:var(--faint);text-decoration:line-through}
.task-actions{display:flex;gap:var(--sp-8);margin:var(--sp-16) 0 var(--sp-4)}
.task-done-btn{flex:1;justify-content:center;gap:var(--sp-6)}
.task-icon-btn{flex:none}
.prio-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:var(--sp-x7);vertical-align:middle;flex:none}
.prio-chip{display:inline-flex;align-items:center;gap:var(--sp-6);font-size:12.5px;font-weight:600;padding:var(--sp-x3) var(--sp-x11) var(--sp-x3) var(--sp-x9);border-radius:999px}
.prio-chip .prio-dot{margin-right:0}
.sort-lbl{display:inline-flex;align-items:center;gap:var(--sp-6);font-size:12px;color:var(--muted);flex:none}

/* ---------- calendar ---------- */
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr)}
.cal-dow{text-align:center;font-size:12px;color:var(--faint);padding:var(--sp-10) 0;font-weight:600}
.cal-cell{aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-x3);font-size:14px;cursor:pointer;border-radius:10px;margin:var(--sp-2);transition:background .12s}
.cal-cell:hover{background:var(--surface-2)}
.cal-cell.today{background:var(--accent);color:#fff;font-weight:600}
.cal-cell.active{outline:2px solid var(--accent)}
.cal-cell.blank{cursor:default}
.cal-dots{display:flex;gap:var(--sp-x3);align-items:center;height:5px}
.cal-dot{width:5px;height:5px;border-radius:50%;background:var(--accent)}
.cal-dot.task{background:#e8a13c}
.cal-cell.today .cal-dot{background:#fff}
.cal-cell.today .cal-dot.task{background:#fff;opacity:.6}
.cal-task{width:100%;display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-12) var(--sp-16) var(--sp-12) var(--sp-x13);border:none;border-bottom:1px solid var(--line);border-left:3px solid #e8a13c;background:none;cursor:pointer;text-align:left;font:inherit}
.cal-task:hover{background:var(--surface-2)}
.cal-task .ct-title{flex:1;font-size:14px;color:var(--text)}
.cal-task .ct-title.done{color:var(--faint);text-decoration:line-through}
.cal-legend{display:flex;align-items:center;gap:var(--sp-6);justify-content:center;font-size:11px;color:var(--faint);padding:var(--sp-2) 0 var(--sp-12)}
.cal-legend .cl-dot{width:7px;height:7px;border-radius:50%;background:var(--accent)}
.cal-legend .cl-dot.tk{background:#e8a13c;margin-left:var(--sp-12)}
/* ---- month grid with multi-day event bars (Gantt-style) ---- */
/* #325 (A7) — kalender bulan MENGISI tinggi kolom: flex kolom, tiap minggu berbagi sisa tinggi rata,
   jadi tak ada ruang kosong menganga di bawah grid. */
/* `min-height:100%` DICABUT. Ia berarti "setinggi penggulungnya", padahal di dalam penggulung
   yang sama ada bilah bulan setinggi 57px DI ATASNYA — jadi kisinya selalu 57px lebih tinggi
   daripada ruang yang tersisa. Akibatnya kalender selalu menggulung sedikit, baris minggu
   terakhir selalu terpotong, dan apa pun yang mengambang di bawah (pita DEMO/PRATINJAU) mendarat
   tepat di atasnya. DIUKUR sebelum: scrollHeight 616 vs clientHeight 559. Yang benar bukan
   persentase melainkan flex: penggulungnya jadi kolom flex, bilah bulan ukuran alaminya, kisi
   mengisi SISANYA. Sesudah: kisi 113→574, tak ada gulungan sama sekali. */
/* butir 112 — SEMUA angka kisi minggu hidup DI SINI dan cuma di sini. CORE.kisiMinggu()
   menyusun grid-template-rows dan min-height dari sifat-sifat ini lewat var(), jadi tak ada
   satu pun angka yang harus dijaga tetap sama di dua berkas. Uji js/run.js menuntut tiap
   var(--…) yang disebut JS benar-benar ada di blok ini. */
.cal-month{padding:0 var(--sp-8) var(--sp-14);--cdn:27px;--cbar:18px;--cmore:16px;--crow:2px;--cwmin:62px;display:flex;flex-direction:column;box-sizing:border-box;overflow-x:hidden;overflow-y:auto}
.m-pane:has(>.cal-month),.col-body:has(>.cal-month){display:flex;flex-direction:column;overflow:hidden}
.m-pane>.cal-month,.col-body>.cal-month{flex:1;min-height:0}
/* BUTIR 174 + B-5 — SATU SEBAB, BUKAN DUA TAMBALAN.
 *
 * Aturan ini dulu berbunyi `:root.has-envrib .col-body,…{padding-bottom:calc(--envrib-h + 12px)}`
 * dan ia BENAR — untuk satu elemen mengambang saja, yang cuma ada di Preview & Demo. Di Release
 * `.has-envrib` tak pernah ada, jadi bilah "Versi baru sudah siap" dan bilah unggahan menutupi
 * baris terakhir kolom persis seperti dulu pitanya menutupi — cacat yang sama, penjaga yang
 * kebetulan tidak berlaku di sana.
 *
 * Menambah `:root.has-updbar` di sebelahnya akan menutup gejala berikutnya dan meninggalkan
 * gejala ketiga. Yang salah bukan daftarnya kurang panjang; yang salah adalah CSS diminta tahu
 * SIAPA yang sedang mengambang. Ia tidak pernah bisa tahu — itu keadaan jalan, bukan bentuk.
 *
 * Sekarang CSS cuma menyediakan satu tempat, dan JS yang mengisinya dengan angka yang ia UKUR:
 * terbitkanRuangMelayang() menghitung bagian yang benar-benar bertindih — di KEDUA sumbu —
 * lalu menuliskannya di panelnya masing-masing. Nilai di :root cuma lantainya: panel yang tak
 * tertindih apa pun mewarisi 0px, dan kalau JS tak pernah jalan aturan ini tak berbunyi apa-apa.
 *
 * Per panel, bukan satu angka bersama, dan itu diukur: di 1906px bilah pembaruan duduk di
 * 808..1098 sementara kolom tengah di 426..795 — satu angka bersama memberi kolom itu 210px
 * ruang kosong untuk sesuatu yang tidak pernah melintasinya.
 *
 * Aturannya DIPERLUAS, bukan dicabut: yang dijaga perintah AD (isi kolom tidak boleh tertutup
 * elemen mengambang) sekarang berlaku di Release juga, bukan cuma di Preview & Demo. */
.col-body,.m-pane{padding-bottom:var(--float-stack-h)}
/* Lengket karena .cal-month kini boleh menggulir (butir 112): tanpa ini, bulan yang isinya
   melebihi layar akan menggulung nama harinya keluar, dan kisi tujuh kolom tanpa kepala
   adalah kisi yang tidak bisa dibaca. z-index 3 supaya ia di atas bar (2) dan sel (1). */
.cal-dow-row{display:grid;grid-template-columns:repeat(7,1fr);flex:none;position:sticky;top:0;z-index:3;background:var(--surface)}
.cal-week{display:grid;grid-template-columns:repeat(7,1fr);border-top:1px solid var(--line);min-height:var(--cwmin);row-gap:var(--crow);flex:1 1 0}
.cal-week:last-child{border-bottom:1px solid var(--line)}
.cal-daycol{position:relative;z-index:1;display:block;border:none;background:transparent;padding:0;cursor:pointer;border-right:1px solid var(--line);overflow:hidden}
.cal-daycol:nth-child(7n){border-right:none}
/* Sel di luar bulan ini menampilkan tanggal bulan sebelah, bukan kekosongan. `opacity:.35` yang
   lama dicabut: ia meredupkan SELURUH sel termasuk garis kisinya, dan angka apa pun di dalamnya
   akan ikut hilang. Warnanya dipatok eksplisit dan DIUKUR (1 Agu 2026), bukan opacity yang
   hasilnya tak pernah diketahui: terang #a4a4ae = 2,37:1 · gelap #666670 = 2,85:1 — jelas ada,
   jelas nomor dua di bawah tanggal bulan ini (6,32:1 terang / 8,03:1 gelap). Angka ini memang
   pelengkap, bukan isi: ia aria-hidden dan tak bisa diketuk. */
.cal-daycol.blank{cursor:default;background:var(--surface-2)}
.cal-daycol.blank .cal-dn{color:#a4a4ae;font-weight:500}
:root[data-theme="dark"] .cal-daycol.blank .cal-dn{color:#666670}
.cal-daycol:hover:not(.blank){background:var(--surface-2)}
/* Hari terpilih dulu HANYA diberi --accent-weak: 1,10:1 di atas putih, 1,15:1 di gelap — orang
   tak bisa melihat hari mana yang sedang dibuka. WCAG 1.4.11 meminta 3:1 untuk penanda status,
   dan isian selembut itu takkan pernah sampai tanpa merusak bahasa pastelnya. Jadi isiannya
   ditahan dan penandanya dipindah ke cincin --accent: DIUKUR 5,11:1 di terang, 3,50:1 di gelap. */
.cal-daycol.active{background:var(--accent-weak);box-shadow:inset 0 0 0 2px var(--accent)}
/* Pinned to the top-right, out of flow — a native <button> centres block content, which was
   dropping the day number into the middle of the cell and under the event bars. */
.cal-dn{position:absolute;top:4px;right:6px;font-size:11px;font-weight:600;color:var(--muted);line-height:1;pointer-events:none}
.cal-daycol.today .cal-dn{top:3px;right:4px;color:#fff;background:var(--accent);border-radius:50%;width:20px;height:20px;line-height:20px;text-align:center}
.cal-bar{position:relative;z-index:2;margin:var(--sp-1) var(--sp-2);height:calc(var(--cbar) - 2px);line-height:calc(var(--cbar) - 2px);border-radius:4px;font-size:10px;font-weight:600;overflow:hidden;white-space:nowrap;padding:0 var(--sp-6);border:none;text-align:left;cursor:pointer;background:var(--accent-weak);color:var(--accent-text)}
.cal-bar-t{overflow:hidden;text-overflow:ellipsis;display:block}
.cal-bar-leave{background:repeating-linear-gradient(45deg,var(--surface-2),var(--surface-2) 4px,var(--line) 4px,var(--line) 5px);color:var(--muted);pointer-events:none}
.cal-bar-task{background:transparent;color:#b45309;border:1px dashed #e8a13c;line-height:calc(var(--cbar) - 4px);pointer-events:none}
:root[data-theme="dark"] .cal-bar-task{color:#f0b866;border-color:#7a5418}
.cal-bar.clipL{border-top-left-radius:0;border-bottom-left-radius:0;margin-left:0}
.cal-bar.clipR{border-top-right-radius:0;border-bottom-right-radius:0;margin-right:0}
/* align-self:start, bukan end. Treknya kini minmax(var(--cmore),1fr) — ia MELAR bersama
   selnya, jadi "end" akan melempar penandanya ke dasar sel dan menjauhkannya dari bilah
   yang ia hitung; di kolom desktop yang tinggi jaraknya bisa puluhan piksel dan hubungannya
   putus. "start" menempelkannya tepat di bawah bilah terakhir — sama seperti kalender mana
   pun yang pernah dipakai orang. */
.cal-more{font-size:10px;font-weight:600;color:var(--muted);padding:var(--sp-1) var(--sp-6) 0;pointer-events:none;align-self:start;cursor:default;z-index:2}
/* All-day events hide the time fields; the date then takes the full row width. */
/* butir 105 — "Berulang sampai" hanya muncul kalau memang berulang. Pola yang sama persis
   dengan .all-day di bawah: satu kelas di <form>, satu aturan di sini, tanpa render ulang.
   Ditulis sebagai :not() supaya keadaan BAWAANNYA tersembunyi — form yang lahir tanpa kelas
   apa pun (mis. dari jalur baru yang lupa menambahkannya) menyembunyikannya, bukan
   memamerkan medan yang tak berarti. Arah amannya ke bawah. */
.form:not(.has-recur) .f-recur-until{display:none}
.form.all-day .ev-time{display:none}
.form.all-day .ev-dtrow{grid-template-columns:1fr}
.rf-hidden{display:none}
/* Event create: before a start date is picked, hide the times & end row for a clean first view. */
.ev-row{border-bottom:1px solid var(--line);border-left:3px solid var(--accent)}
/* #325 (F2) — ringkasan acara yang bisa diketuk (jam · judul · lokasi + chevron). */
.ev-head{display:flex;gap:var(--sp-14);width:100%;padding:var(--sp-14) var(--sp-16) var(--sp-14) var(--sp-x13);border:0;background:transparent;text-align:left;cursor:pointer;align-items:flex-start;font:inherit;color:inherit}
.ev-head:hover{background:var(--surface-2)}
.ev-chev{flex:none;color:var(--faint);align-self:center;transition:transform .15s ease}
.ev-chev.open{transform:rotate(180deg)}
.ev-row.ev-leave{display:flex;gap:var(--sp-14);padding:var(--sp-14) var(--sp-16) var(--sp-14) var(--sp-x13)}
.ev-detail{padding:0 var(--sp-16) var(--sp-14) 66px}
.ev-actions{display:flex;gap:var(--sp-2);flex:none;align-items:flex-start;margin-top:var(--sp-12)}
.ev-time{font-size:13px;font-weight:600;color:var(--accent-text);width:52px;flex-shrink:0}
.ev-main{flex:1;min-width:0}
.ev-title{font-size:14px;font-weight:600}
.ev-loc{font-size:12px;color:var(--muted);margin-top:var(--sp-1);display:flex;align-items:center;gap:var(--sp-x5)}
.ev-loc svg{flex:none}
.ev-att{display:flex;align-items:center;gap:var(--sp-8);margin-top:var(--sp-x9)}
.ev-avs{display:flex}
.ev-avs .avatar{margin-left:-7px;box-shadow:0 0 0 2px var(--surface)}
.ev-avs .avatar:first-child{margin-left:0}
.ev-att-txt{font-size:12px;color:var(--muted)}
.rsvp{display:flex;gap:var(--sp-8);margin-top:var(--sp-10)}
.rsvp-btn{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:12px;font-weight:600;padding:var(--sp-x5) var(--sp-12);border-radius:8px;border:1px solid var(--line);color:var(--muted);background:var(--surface)}
.rsvp-btn.yes{background:var(--good);border-color:var(--good);color:#fff}
.rsvp-btn.no{background:var(--danger-solid);border-color:var(--danger-solid);color:#fff}

/* ---------- forms ---------- */
.form{padding:var(--sp-x22);max-width:460px}
.form h1{font-size:20px;font-weight:700;margin-bottom:var(--sp-x18)}
.field{margin-bottom:var(--sp-14)}
/* #401 (F1) — Kolom yang ditolak. Warna SAJA tak cukup (buta warna, layar redup, sinar matahari),
   jadi labelnya ikut berubah warna DAN tepinya menebal — dua isyarat, bukan satu. */
.field-bad > label{color:var(--danger)}
.field-bad input,.field-bad textarea,.field-bad .pick-field,.field-bad .picker-btn{
  border-color:var(--danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--danger) 18%,transparent)}
.field-bad input:focus,.field-bad textarea:focus,.field-bad .pick-field:focus,.field-bad .picker-btn:focus{
  border-color:var(--danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--danger) 28%,transparent)}
.field label{display:block;font-size:13px;font-weight:600;color:var(--muted);margin-bottom:var(--sp-6)}
.field input,.field textarea,.field select{width:100%;border:1px solid var(--line);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:14px;outline:none;background-color:var(--surface)}
.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.field textarea{resize:vertical;min-height:80px}
.field select{background-color:var(--surface)}   /* panahnya dari aturan bersama di atas */
/* Pemilih kustom (pengganti <select> native). Tombolnya dibuat sepersis .field input
   supaya menyatu; panelnya bottom-sheet bergaya app, bukan dropdown OS. */
.picker{display:block;position:relative}
.picker-btn{width:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-8);border:1px solid var(--line);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:14px;background-color:var(--surface);color:var(--text);cursor:pointer;text-align:left}
.picker-btn:hover{border-color:var(--muted)}
.picker-btn>svg{flex:none;color:var(--faint)}
.picker-val{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
:root[data-theme="dark"] .picker-btn{background-color:var(--surface-2)}
.picker-back{position:fixed;inset:0;z-index:250;background:rgba(17,20,28,.35);animation:cfFade .12s ease}
.picker-sheet{position:fixed;z-index:251;left:50%;bottom:calc(16px + var(--sab));transform:translateX(-50%);width:min(360px,calc(100vw - 24px));max-height:60dvh;overflow-y:auto;background:var(--surface);border:1px solid var(--line);border-radius:16px;box-shadow:0 12px 40px rgba(0,0,0,.22);padding:var(--sp-6);animation:sheetUp .16s cubic-bezier(.2,.7,.3,1)}
.picker-opt{width:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);padding:var(--sp-x13) var(--sp-14);border:none;background:none;color:var(--text);font-size:15px;cursor:pointer;border-radius:10px;text-align:left}
.picker-opt:hover{background:var(--surface-2)}
.picker-opt.on{color:var(--accent);font-weight:600}
.picker-opt>svg{flex:none;color:var(--accent)}
.field input:-webkit-autofill,.field input:-webkit-autofill:focus{-webkit-box-shadow:0 0 0 40px var(--surface) inset;-webkit-text-fill-color:var(--text);caret-color:var(--text)}
/* Dua ruas berdampingan — TAPI hanya kalau kolomnya memang cukup lebar.
   Kolom di aplikasi ini selebar ponsel (360–460px), dan .form memakan 44px untuk padding.
   Jadi ruang isinya sekitar 316px pada kolom tersempit; dibagi dua tinggal 152px per ruas.
   Pemilih prioritas butuh sekitar 240px untuk memuat tiga pilihannya, jadi ia meluber —
   itulah "High" yang terpotong jadi "Hig".

   Yang membuatnya meluber, bukan menyusut, adalah aturan bawaan flexbox: anak flex punya
   min-width:auto, artinya ia MENOLAK mengecil di bawah lebar isinya. flex:1 terlihat seperti
   izin untuk menyusut, padahal bukan. Dua hal diperbaiki sekaligus di sini:
     • flex-basis 240px + flex-wrap  -> menumpuk sendiri kalau tidak muat berdampingan
     • min-width:0                   -> kalaupun berdampingan, ia menyusut, tidak meluber */
/* Keterangan kecil di bawah sebuah medan. Menjelaskan syarat yang tidak kelihatan —
   misalnya pengingat yang butuh tenggat — sebelum orang menemukannya lewat kekecewaan. */
/* BUTIR 102 — margin-bottom ditambahkan, dan angkanya DIUKUR bukan dikira.
   .f-hint adalah elemen TERAKHIR schedFields(), dan ia berdiri di luar pembungkus .field —
   jadi jarak antar-medan tidak berlaku padanya. Diukur di 360px: label medan yang mengikuti
   sebuah .f-hint dapat jarak 0px, sementara label medan lain di formulir yang sama dapat
   14px. Selama tidak ada medan sesudah pengingat, tak seorang pun melihatnya; butir 102
   menaruh satu di sana. 14px menyamakannya dengan tetangganya, bukan menciptakan angka baru. */
.f-hint{margin-top:var(--sp-6);margin-bottom:var(--sp-14);font-size:11.5px;line-height:1.45;color:var(--faint)}
/* ---- cuti / izin ---- */
.lv-card{background:var(--surface);border:1px solid var(--line);border-radius:13px;padding:var(--sp-12) var(--sp-x13);margin-bottom:var(--sp-x9)}
.lv-top{display:flex;align-items:flex-start;gap:var(--sp-10)}
.lv-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-2)}
.lv-h{font-size:14px;font-weight:600;color:var(--text)}
.lv-when{font-size:12.5px;color:var(--muted)}
.lv-reason{font-size:12.5px;color:var(--muted);line-height:1.45;margin-top:var(--sp-x3)}
.lv-by{font-size:11.5px;color:var(--faint);margin-top:var(--sp-4);line-height:1.45}
.lv-badge{flex:none;align-self:flex-start;font-size:10.5px;font-weight:700;padding:var(--sp-x3) var(--sp-x9);border-radius:20px;white-space:nowrap}
/* Menunggu = amber, disetujui = hijau, ditolak = merah aksen, ditarik = kelabu.
   Ditarik sengaja TIDAK merah: membatalkan pengajuan sendiri bukan penolakan, dan
   mewarnainya seperti kegagalan membuat orang enggan menariknya saat rencananya berubah. */
.lv-pending{background:color-mix(in srgb,var(--due-today) 16%,transparent);color:var(--due-today)}
/* Butir 51 — teksnya dulu var(--good) (#16a34a) dan terukur 2,78:1 di atas isiannya sendiri.
   Isiannya TIDAK diubah: warna itu yang membuat lencana ini terbaca sebagai "disetujui" dari
   sudut mata. Yang diubah teksnya, ke hijau tua yang sudah dipakai .dev-tag-paid — jadi tak ada
   warna sekali-pakai baru. Terukur 5,03:1 di terang, 7,60:1 di gelap. */
.lv-approved{background:color-mix(in srgb,var(--good) 15%,transparent);color:#177245}
:root[data-theme="dark"] .lv-approved{color:#34d399}
.lv-declined{background:var(--accent-weak);color:var(--accent-text)}
.lv-cancelled{background:var(--surface-2);color:var(--faint)}
.lv-acts{display:flex;flex-wrap:wrap;gap:var(--sp-8);justify-content:flex-end;margin-top:var(--sp-10)}
/* BUTIR 117 — LAMPIRAN (OPSIONAL).
   Tidak ada satu pun aturan di sini yang membuat keadaan KOSONG terlihat kurang: tombolnya
   memakai gaya tombol biasa, bukan kotak putus-putus "seret berkas ke sini" yang menganga
   dan meminta diisi. Kotak kosong yang menganga adalah tanda bintang yang menyamar, dan
   keputusan CEO angka 2 melarangnya — orang yang mengajukan izin keluarga tidak boleh
   terbaca seperti orang yang menahan surat dokter.
   Pratinjaunya 46px, seukuran .att-thumb: satu bahasa ukuran dengan foto absensi, karena
   ini memang jalur yang sama. */
.lv-att{margin-top:var(--sp-2)}
.lv-att-add{display:inline-flex;align-items:center;gap:var(--sp-x7);align-self:flex-start}
.lv-att-pick{display:flex;align-items:center;gap:var(--sp-10);flex-wrap:wrap}
.lv-att-pv{width:46px;height:46px;flex:none;border-radius:9px;object-fit:cover;border:1px solid var(--line);display:block}
.lv-att-nm{font-size:12.5px;color:var(--muted);flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Di kartunya, lampiran adalah KEPING yang diketuk — bukan gambar kecil yang tergambar
   sendiri. Alasannya keputusan CEO angka 3: dokumen medis satu tingkat di atas selfie, dan
   siapa yang melihatnya harus dipilih dengan sengaja. Gambar yang tergambar sendiri di
   daftar yang digulir terlihat oleh siapa pun yang melirik layar pengurus, tanpa satu pun
   tindakan; keping menuntut satu ketukan. */
.lv-att-chip{display:inline-flex;align-items:center;gap:var(--sp-6);margin-top:var(--sp-8);padding:var(--sp-x5) var(--sp-10);border-radius:999px;
  /* align-self: induknya (.lv-body) kolom flex, jadi tanpa baris ini keping melar
     selebar kartu dan berhenti terbaca sebagai keping. Terlihat di rig, bukan di kode. */
  align-self:flex-start;
  border:1px solid var(--line);background:var(--surface-2);color:var(--muted);font-size:12px;font-weight:600;cursor:pointer}
.lv-att-chip:hover{border-color:var(--accent);color:var(--accent-text)}
.lv-att-chip svg{color:var(--accent);flex:none}
/* BUTIR PAPAN 78 — kartu yang ditunjuk notifikasinya.
   Cincin aksen, bukan latar berwarna: latar akan berkelahi dengan lencana status di dalam
   kartunya sendiri (menunggu = amber, disetujui = hijau), dan sorot yang mengubah arti warna
   kartunya bukan sorot melainkan kebingungan. Cincinnya berdenyut dua kali lalu berhenti —
   ia menjawab "yang mana?", dan setelah dijawab ia tak punya urusan lagi.
   Gerakannya dimatikan penuh untuk yang meminta lebih sedikit gerakan; cincinnya tetap ada,
   jadi jawabannya tidak ikut hilang. */
.lv-card.lv-focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
@media (prefers-reduced-motion:no-preference){
  .lv-card.lv-focus{animation:lvFocus 1.1s ease-out 2}
}
@keyframes lvFocus{
  0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 45%,transparent)}
  70%{box-shadow:0 0 0 9px transparent}
  100%{box-shadow:0 0 0 3px var(--accent-weak)}
}
/* Penanda cuti di laporan absensi — hari yang berketerangan, bukan hari kosong. */
.att-leave{display:inline-flex;align-items:center;gap:var(--sp-x5);font-size:11.5px;font-weight:600;
  padding:var(--sp-2) var(--sp-x9);border-radius:20px;background:color-mix(in srgb,var(--good) 14%,transparent);color:var(--good)}
/* Cuti di kalender: baris yang lebih tenang dari acara sungguhan. Ia keterangan, bukan
   janji temu — dan tidak punya tombol apa pun, jadi tidak boleh tampil seolah bisa ditekan. */
.ev-leave{border-left-color:var(--good);opacity:.92}
.ev-leave .ev-time{display:flex;align-items:center;justify-content:center;color:var(--good)}
.ev-leave .ev-title{font-weight:600}
/* Penanda acara berulang, di sebelah judulnya. */
.ev-recur{display:inline-flex;margin-left:var(--sp-6);color:var(--faint);vertical-align:middle}
/* ---- kartu "apa yang sedang dikerjakan" di profil kontak ---- */
.wk-card{margin:0 0 var(--sp-14);padding:var(--sp-12) var(--sp-x13);border-radius:13px;background:var(--surface-2);border:1px solid var(--line)}
.wk-head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);flex-wrap:wrap;margin-bottom:var(--sp-10)}
.wk-pill{display:inline-flex;align-items:center;gap:var(--sp-x5);font-size:11.5px;font-weight:700;padding:var(--sp-x3) var(--sp-10);border-radius:20px;white-space:nowrap}
/* Butir 51 — kembar .lv-approved di atas, dan gagal karena alasan yang sama persis (2,78:1).
   Diperlakukan sama supaya keduanya tidak pelan-pelan berbeda warna. */
.wk-in{background:color-mix(in srgb,var(--good) 15%,transparent);color:#177245}
:root[data-theme="dark"] .wk-in{color:#34d399}
.wk-leave{background:color-mix(in srgb,var(--due-today) 16%,transparent);color:var(--due-today)}
/* "Belum absen" memakai kelabu biasa, BUKAN merah. Ia keterangan, bukan tuduhan —
   orangnya bisa saja sedang di jalan, atau memang tidak memakai absensi. */
.wk-out{background:var(--surface);color:var(--muted);border:1px solid var(--line)}
.wk-count{font-size:12px;color:var(--muted);white-space:nowrap}
.wk-tasks{display:flex;flex-direction:column;gap:var(--sp-2)}
.wk-task{display:flex;align-items:center;gap:var(--sp-x9);width:100%;text-align:left;padding:var(--sp-8) var(--sp-x9);border-radius:9px;
  background:none;border:none;cursor:pointer;font:inherit;color:var(--text)}
.wk-task:hover{background:var(--surface)}
.wk-task-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13.5px}
.wk-task-d{flex:none;font-size:11.5px;font-weight:600}
.wk-more{font-size:12px;color:var(--muted);padding:var(--sp-6) var(--sp-x9) 0}
.wk-none{font-size:13px;color:var(--muted);padding:var(--sp-2) var(--sp-2) var(--sp-4)}
/* Asal-usul tugas: dari percakapan mana ia lahir. Dibuat seperti baris yang bisa diketuk,
   bukan sekadar keterangan — karena memang membawa orang ke tempat lain. */
.task-origin{display:flex;align-items:center;gap:var(--sp-8);width:100%;margin:var(--sp-2) 0 var(--sp-4);padding:var(--sp-x9) var(--sp-x11);
  border-radius:10px;border:1px solid var(--line);background:var(--surface-2);
  color:var(--muted);font-size:12.5px;font:inherit;font-size:12.5px;cursor:pointer;text-align:left}
.task-origin:hover{background:var(--surface);border-color:var(--accent)}
.task-origin>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.task-origin>svg{flex:none;color:var(--faint)}
/* compact "from chat" chip on a calendar event row */
.ev-origin{display:inline-flex;align-items:center;gap:var(--sp-x5);max-width:100%;margin-top:var(--sp-6);padding:var(--sp-x3) var(--sp-x9);border-radius:8px;border:1px solid var(--line);background:var(--surface-2);color:var(--muted);font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;vertical-align:top}
.ev-origin:hover{border-color:var(--accent);color:var(--accent-text)}
.ev-origin>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ev-origin>svg{flex:none;color:var(--accent)}
/* "Siapa yang tidak ada hari ini" di beranda. Nadanya tenang — ini keterangan, bukan
   peringatan, jadi ia tidak boleh berebut perhatian dengan banner tagihan di dekatnya. */
/* LEBARNYA HARUS DITULIS — tanpanya kartu ini menyeret seluruh kolom.
   <button> tidak melebar mengisi induknya; ia menyusut ke isinya. Saudara-saudaranya semua
   menuliskannya sendiri (.menu-row, .td-item); yang ini lupa, dan lupanya tidak terlihat
   selama nama yang tercetak pendek. Diukur dengan empat nama di 320px: kartunya 910px di
   dalam panel 264px, dan panelnya jadi bisa digeser ke samping 674px. text-overflow:ellipsis
   di .away-n tidak pernah berbunyi, karena tidak ada yang menyempitkannya.

   Bentuk yang sama dengan butir 195: tiap anak harus mengingat sesuatu, dan yang lupa satu
   tidak berbunyi sampai ada yang mengukurnya.

   Dan `width:100%` saja SALAH — percobaan pertama memakainya dan menyisakan gulir 28px,
   persis dua kali margin kartunya: 100% adalah lebar induk, sedangkan margin duduk DI LUAR
   kotak itu. Sisipannya sekarang ditulis SEKALI (--away-sisip) dan lebar maupun margin
   diturunkan darinya, jadi keduanya tidak bisa berpisah. */
.away-card{--away-sisip:var(--sp-14);width:calc(100% - var(--away-sisip) * 2);display:flex;align-items:center;gap:var(--sp-x11);text-align:left;margin:0 var(--away-sisip) var(--sp-14);
  padding:var(--sp-x11) var(--sp-14);border-radius:14px;border:1px solid var(--line);background:var(--surface-2);
  color:var(--text);cursor:pointer;font:inherit;transition:background .12s}
.away-card:hover{background:var(--surface)}
.away-ic{flex:none;display:flex;align-items:center;justify-content:center;width:32px;height:32px;
  border-radius:10px;background:color-mix(in srgb,var(--due-today) 15%,transparent);color:var(--due-today)}
.away-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-1)}
.away-t{font-size:13px;font-weight:700}
.away-n{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.away-card>svg{flex:none;color:var(--faint)}
/* ---- catatan aktivitas ---- */
.ac-row{display:flex;align-items:flex-start;gap:var(--sp-x11);padding:var(--sp-10) var(--sp-2);border-bottom:1px solid var(--line)}
.ac-row:last-child{border-bottom:none}
.ac-ic{flex:none;display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;
  background:var(--surface-2);color:var(--muted)}
.ac-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-2)}
.ac-t{font-size:13.5px;color:var(--text)}
.ac-s{font-size:12.5px;color:var(--muted);line-height:1.45;overflow-wrap:anywhere}
/* Jam ditaruh di kanan dan tenang: yang dicari orang adalah APA yang berubah, jamnya
   cuma penunjuk saat ia sudah menemukan barisnya. */
.ac-time{flex:none;font-size:11.5px;color:var(--faint);padding-top:var(--sp-2)}
/* Centang yang berdiri sendiri di dalam .field, bukan bagian dari .chk-list.
   Dibentuk seperti medan isian lain — berbingkai, setinggi tombol pemilih tanggal — supaya
   ia terbaca sebagai bagian dari formulir, bukan tempelan yang lepas dari bahasanya.

   Ukuran kotak centangnya ditulis di sini juga. Aturan ukuran yang ada sebelumnya melekat
   pada `.chk-list .chk input`, jadi centang di luar daftar mewarisi ukuran bawaan peramban
   — kecil, kelabu, dan berbeda dari semua centang lain di aplikasi. */
/* Pemilihnya `.field label.chk-inline`, bukan `.chk-inline` saja. Ini BUKAN gaya-gayaan:
   di dalam .field sudah ada aturan `.field label` yang menjadikan label kecil, kelabu, dan
   display:block — dan spesifisitasnya (0,1,1) MENGALAHKAN satu kelas (0,1,0).
   Akibatnya display:flex-nya diabaikan diam-diam: kotak centangnya menempel ke tulisan dan
   seluruh barisnya terlihat seperti kesalahan. Ketahuan dengan membuka demo, bukan dengan
   membaca — dan itu pelajarannya. */
.field label.chk-inline{display:flex;align-items:center;gap:var(--sp-x11);width:100%;cursor:pointer;
  font-size:14px;font-weight:500;color:var(--text);margin-bottom:0;text-transform:none;letter-spacing:normal;
  padding:var(--sp-x11) var(--sp-12);border:1px solid var(--line);border-radius:11px;background:var(--surface);
  transition:border-color .12s,background .12s}
.field label.chk-inline:hover{background:var(--surface-2)}
.field label.chk-inline input{flex:none;width:17px;height:17px;accent-color:var(--accent);margin:0}
/* Saat tercentang, bingkainya ikut menyala — status yang terbaca sekilas tanpa
   perlu memeriksa kotak kecilnya. */
.field label.chk-inline:has(input:checked){border-color:var(--accent);background:var(--accent-weak)}
.row2{display:flex;flex-wrap:wrap;gap:var(--sp-12)}
.row2>*{flex:1 1 240px;min-width:0}
.chk-list{display:flex;flex-direction:column;gap:var(--sp-2);max-height:190px;overflow-y:auto;border:1px solid var(--line);border-radius:10px;padding:var(--sp-6)}
.chk-list .chk{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-x7) var(--sp-8);border-radius:8px;cursor:pointer;font-size:14px;font-weight:400;color:var(--text);margin-bottom:0}
.chk-list .chk:hover{background:var(--surface-2)}
.chk-list .chk input{width:16px;height:16px;accent-color:var(--accent);flex-shrink:0}
.chk-name{flex:1;min-width:0}
.form-error{background:#fef2f2;color:var(--danger);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:13px;margin-bottom:var(--sp-14)}

/* ---------- tools in taskbar ---------- */
.chip{padding:var(--sp-x7) var(--sp-x13);border-radius:999px;font-size:13px;font-weight:500;border:1px solid var(--line);color:var(--muted);background:var(--surface)}
.chip.on{background:var(--accent-weak);color:var(--accent-text);border-color:transparent}
.spacer{flex:1}
.cal-nav{display:flex;align-items:center;gap:var(--sp-6)}
.cal-nav b{font-size:14px;font-weight:600;min-width:120px;text-align:center}

/* ---------- account ---------- */
.profile-head{padding:var(--sp-24) var(--sp-16) var(--sp-14);display:flex;flex-direction:column;align-items:center;gap:var(--sp-8);border-bottom:1px solid var(--line)}
/* #364 — kepala profil bisa diketuk untuk lihat profil sendiri (+ Bazi) */
.profile-head-btn{width:100%;border:0;border-bottom:1px solid var(--line);background:none;font:inherit;cursor:pointer;transition:background .12s}
.profile-head-btn:hover{background:var(--surface-2)}
.profile-head-btn:active{transform:scale(.995)}
.profile-head-cta{display:inline-flex;align-items:center;gap:var(--sp-x3);margin-top:var(--sp-2);font-size:12.5px;font-weight:600;color:var(--accent-text)}
.profile-head-cta svg{stroke:currentColor}
.kv{display:flex;justify-content:space-between;padding:var(--sp-x13) 0;border-bottom:1px solid var(--line);font-size:14px}
.kv span:first-child{color:var(--muted)}
.kv span:last-child{font-weight:500}
/* butir 98 — "terkunci" adalah KEADAAN, bukan nilai. Sengaja tidak ikut berat 500 milik
   .kv span:last-child supaya mata yang memindai kolom kanan tidak membacanya sebagai
   sebuah nomor. Kekhususan dinaikkan lewat .ui-rows, BUKAN diserahkan ke urutan baris:
   .kv span.kv-locked kekhususannya seri dengan .kv span:last-child, dan aturan yang menang
   cuma karena ditulis belakangan akan diam-diam kalah begitu ada yang merapikan berkas ini.
   Ikonnya accent, bukan danger: tidak ada yang salah di sini, cuma belum berhak. */
.ui-rows .kv span.kv-locked{display:inline-flex;align-items:flex-start;gap:var(--sp-6);color:var(--muted);font-weight:400;font-size:13px;line-height:1.35;text-align:right;max-width:64%}
/* Ikon indigo, PASANGAN yang sudah dipakai app ini untuk "terlindungi": .vault-lock-ico —
   fitur brankas — memakai #4f46e5 terang / #a5b4fc gelap, dan gembok di sini artinya sama.
   Sengaja BUKAN var(--accent): di Spotious accent adalah merah-bata yang praktis tak bisa
   dibedakan dari danger, dan gembok merah membaca sebagai "ada yang salah". Tidak ada yang
   salah — nomornya memang bukan hakmu. Pasangan gelapnya wajib ikut: warna tingkat-600 yang
   ditinggalkan di mode gelap adalah jebakan yang sudah menggigit enam ikon Panduan (2,36:1). */
.ui-rows .kv span.kv-locked svg{color:#4f46e5;flex:0 0 auto;margin-top:var(--sp-1)}
:root[data-theme="dark"] .ui-rows .kv span.kv-locked svg{color:#a5b4fc}
.cl-entry{padding:var(--sp-16) 0;border-bottom:1px solid var(--line)}
.cl-entry:last-child{border-bottom:none}
/* BUTIR 143 — sekeluarga dengan .att-rec: baris flex tanpa flex-wrap yang isinya tak bisa
   menyusut. Di 320px "v315" + tanggal + judul tidak muat sebaris; pil tanggalnya (punya
   padding, tak bisa menyusut) meluber dan judulnya mendarat di atas butir entri di atasnya.
   Riwayat Pembaruan adalah janji transparansi kita — ia tidak boleh jadi layar yang
   tulisannya bertumpuk. */
.cl-head{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-10);margin-bottom:var(--sp-x9)}
.cl-ver{font-size:11px;font-weight:700;letter-spacing:.3px;color:#fff;background:var(--accent);padding:var(--sp-x3) var(--sp-8);border-radius:6px;flex:none}
.cl-date{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--accent-text);background:var(--accent-weak);padding:var(--sp-x3) var(--sp-x9);border-radius:6px;flex:none}
.cl-title{font-size:15px;font-weight:600;min-width:0;flex:1 1 auto}
/* #385 — bar "versi sekarang + Cek pembaruan" di atas riwayat */
.cl-updcheck{display:flex;align-items:center;gap:var(--sp-12);flex-wrap:wrap;margin:var(--sp-2) 0 var(--sp-x18);padding:var(--sp-x11) var(--sp-14);background:rgba(130,140,160,.08);border:1px solid var(--line);border-radius:12px}
.cl-updcheck-v{font-size:13px;color:var(--muted)}
.cl-updcheck-v b{color:var(--text);font-weight:700}
.cl-updcheck-btn{margin-left:auto;display:inline-flex;align-items:center;gap:var(--sp-6);white-space:nowrap}
.cl-list{list-style:none;display:flex;flex-direction:column;gap:var(--sp-x7)}
.cl-list li{position:relative;padding-left:var(--sp-x18);font-size:14px;color:var(--muted);line-height:1.5}
.cl-list li::before{content:"";position:absolute;left:3px;top:8px;width:5px;height:5px;border-radius:50%;background:var(--accent)}

/* ---------- empty / loading ---------- */
.empty,.loading{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-12);color:var(--faint);text-align:center;padding:var(--sp-20)}
.empty>svg{padding:var(--sp-x15);background:var(--accent-weak);color:var(--accent-text);border-radius:50%;box-sizing:content-box}
.empty-t{font-weight:600;color:var(--muted)}
/* Tombol di keadaan kosong — MENIT PERTAMA. Ia tombol sungguhan, bukan tautan hiasan:
   sasaran ketuknya penuh, dan ia menempuh jalur yang sama dengan tombol mana pun. */
.empty-cta{margin-top:var(--sp-4);min-height:40px;padding:0 var(--sp-20)}
.empty-s{font-size:13px;color:var(--faint);margin-top:-6px;max-width:280px;line-height:1.5}

/* BUTIR PAPAN 45 — kartu "tidak bisa dibaca" di dalam kolom.
   Bentuknya sengaja kembar dengan .empty biasa, bukan warna peringatan sendiri: ini bukan galat
   yang harus ditakuti, ini daftar yang belum sampai. Yang membedakannya cukup ikonnya (refresh,
   bukan ikon isi) dan adanya tombol — dan tombol itulah bedanya yang sesungguhnya, karena ia
   yang mengubah "tidak ada apa-apa di sini" jadi "coba sekali lagi".
   Ikonnya memakai kotak netral, bukan --accent-weak, supaya tidak terbaca sebagai ajakan
   membuat sesuatu (yang persis arti kotak aksen di layar kosong yang normal). */
.empty-fail>svg{background:var(--surface-2, rgba(120,120,128,.12));color:var(--muted)}
.empty-retry{margin-top:var(--sp-4)}
/* Pita kegagalan di atas kisi kalender. Tipis dengan sengaja: kisinya masih benar dan masih
   dipakai, jadi kabar ini tidak boleh memakan ruangnya. */
.cal-fail{display:flex;align-items:center;gap:var(--sp-8);margin:0 0 var(--sp-8);padding:var(--sp-8) var(--sp-10);border-radius:10px;
  background:rgba(245,158,11,.12);color:#92400e;font-size:12.5px;line-height:1.35;flex-wrap:wrap}
.cal-fail>svg{flex:none}
.cal-fail>span{flex:1 1 auto;min-width:0}
:root[data-theme="dark"] .cal-fail{background:rgba(245,158,11,.20);color:#fbbf24}
/* Tombol coba-lagi sebaris, dipakai di pita kalender dan di panel notifikasi. Ia tautan yang
   berperilaku tombol: cukup terlihat untuk ditekan, cukup sepi untuk tidak melawan isi. */
.nt-retry{background:none;border:0;padding:0 var(--sp-2);font:inherit;font-weight:600;color:var(--accent-text);
  cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.nt-retry:hover{opacity:.82}
.nt-fail{color:var(--muted)}
.spinner{width:26px;height:26px;border:3px solid var(--line);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* #402 — pemutar seukuran ikon untuk dipasang DI DALAM tombol yang sedang menunggu.
   Warnanya diambil dari `currentColor` dan sisi atasnya dibuat transparan, jadi satu aturan ini
   benar di tombol polos MAUPUN di tombol primer berlatar aksen — tak ada aturan kedua yang bisa
   ketinggalan saat warna tombol berubah (jebakan yang sudah kita bayar di `--danger` vs
   `--danger-solid`). `flex:0 0 15px` karena .btn adalah inline-flex: tanpa itu lingkarannya
   gepeng saat label tombolnya panjang. */
.btn-spin{width:15px;height:15px;flex:0 0 15px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;opacity:.85;animation:spin .6s linear infinite}
/* Di sini kita SENGAJA tidak memakai `animation:none` seperti animasi lain di berkas ini.
   Yang lain dekoratif (denyut lencana, kilau skeleton, kartu masuk) — mematikannya tak
   menghilangkan informasi apa pun. Pemutar ini SATU-SATUNYA tanda bahwa absennya sedang dikirim;
   mematikannya berarti menghadirkan kembali persis bug yang sedang kita perbaiki, khusus bagi
   orang yang memilih reduced-motion. Jadi ia diperlambat, bukan dihentikan. */
@media (prefers-reduced-motion:reduce){.btn-spin{animation-duration:1.6s}}

/* ---------- auth ---------- */
/* ============================================================
   HALAMAN MASUK
   ============================================================
   Layar pertama yang dilihat orang, dan satu-satunya yang dilihat orang yang BELUM
   percaya pada aplikasi ini. Jadi ia mengerjakan dua hal sekaligus: memudahkan masuk,
   dan terlihat seperti buatan orang yang serius.

   Latarnya dicat dengan dua sapuan pastel yang sangat lembut, bukan warna datar. Ini
   satu-satunya layar yang boleh begitu — di dalam aplikasi, latar yang ramai akan
   berkelahi dengan isinya. */
/* height:100% BUKAN cuma min-height — ini yang menentukan.
   #app tingginya var(--app-h) (menyusut saat papan ketik naik) dan overflow:hidden.
   Dengan min-height saja, .auth MELAR melewati kotak itu dan bagian bawahnya terpotong
   tanpa bisa digulir — menggulir .auth tidak menolong karena yang meluap adalah .auth
   itu sendiri, bukan isinya. Dengan height pasti, .auth persis sebesar kotaknya dan
   isinya yang menggulir di dalamnya.

   Dua hal lain yang dulu membuat isinya tertutup papan ketik di HP:
     overflow:hidden       — isi yang lebih tinggi dari layar dipotong, tanpa jalan menggulir
     align-items:center    — saat isinya lebih tinggi dari wadahnya, flexbox memotong
                             KEDUA ujungnya, dan bagian yang terpotong tidak bisa diraih
   Di tab "Buat akun" formulirnya panjang (nama, email, WhatsApp, tanggal lahir, kata
   sandi, tombol, persetujuan). Begitu papan ketik naik, sisa layarnya tinggal separuh
   dan tombol Buat akun-nya hilang begitu saja.

   Sekarang: bisa digulir, dan penengahannya lewat margin:auto pada anaknya — yang
   memusatkan kalau muat, tapi tidak pernah memotong kalau tidak muat. */
.auth{position:relative;height:100%;min-height:100%;display:flex;align-items:flex-start;justify-content:center;
  padding:var(--sp-24) var(--sp-20) calc(var(--sp-24) + var(--sab));background:var(--bg);
  overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch}
.auth::before,.auth::after{content:'';position:absolute;border-radius:50%;pointer-events:none;
  filter:blur(70px);opacity:.5;will-change:transform;transition:transform .22s ease-out}
.auth::before{width:420px;height:420px;top:-150px;left:-130px;background:#c7d2fe;
  transform:translate3d(calc(var(--mx,0)*20px), calc(var(--my,0)*20px - var(--sy,0px)*0.5), 0)}
.auth::after{width:380px;height:380px;bottom:-140px;right:-120px;background:#fbcfe8;
  transform:translate3d(calc(var(--mx,0)*-24px), calc(var(--my,0)*-18px + var(--sy,0px)*0.4), 0)}
/* a third, slower accent orb for depth */
.auth-fx-orb{position:absolute;z-index:0;width:300px;height:300px;border-radius:50%;pointer-events:none;
  filter:blur(72px);opacity:.4;background:#bbf7d0;top:38%;left:44%;
  transform:translate3d(calc(var(--mx,0)*-12px), calc(var(--my,0)*-12px + var(--sy,0px)*0.2), 0);
  transition:transform .22s ease-out}
:root[data-theme="dark"] .auth-fx-orb{background:#134e33;opacity:.26}
:root[data-theme="dark"] .auth::before{background:#3730a3;opacity:.32}
:root[data-theme="dark"] .auth::after{background:#831843;opacity:.28}

/* ---- liquid glass ----
   Kartunya kaca, bukan kertas: sapuan pastel di belakangnya terbaca samar menembusnya,
   dan warnanya ikut bergeser saat kartunya melintas di atas keduanya.

   Tiga lapis yang membuatnya terasa kaca, bukan sekadar transparan:
     1. backdrop-filter blur + saturate  — yang di belakang menjadi buram DAN lebih pekat;
                                           saturate itu yang membedakan kaca dari kabut
     2. inset highlight di tepi atas     — pantulan cahaya pada bibir kaca
     3. bayangan lebih dalam             — kaca melayang, kertas menempel

   background-color TETAP ADA di bawah backdrop-filter, dan itu disengaja: kalau peramban
   tidak mendukung backdrop-filter (Firefox lama, beberapa peramban dalam aplikasi),
   kartunya jatuh kembali menjadi permukaan padat yang tetap terbaca — bukan kotak
   tembus pandang berisi tulisan yang bertumpuk dengan latarnya.

   @supports memisahkan keduanya supaya kejatuhan itu benar-benar terjadi, bukan
   diandaikan. */
.auth-card{position:relative;z-index:1;width:100%;background:var(--surface);
  border:1px solid var(--line);border-radius:22px;padding:var(--sp-x30) var(--sp-x26) var(--sp-x26);
  box-shadow:0 1px 2px rgba(24,24,27,.04),0 12px 32px rgba(24,24,27,.07),0 32px 64px rgba(24,24,27,.05);
  animation:authIn .34s cubic-bezier(.2,.9,.3,1.05)}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .auth-card{
    background:color-mix(in srgb, var(--surface) 62%, transparent);
    -webkit-backdrop-filter:blur(26px) saturate(180%);
            backdrop-filter:blur(26px) saturate(180%);
    border-color:color-mix(in srgb, #fff 55%, transparent);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.65),
      0 2px 6px rgba(24,24,27,.05),
      0 18px 44px rgba(24,24,27,.10),
      0 40px 80px rgba(24,24,27,.07);
  }
  /* Di tema gelap, kacanya digelapkan bukan diputihkan — kaca yang diterangi di ruangan
     gelap terlihat seperti kertas yang menyala, dan itu justru merusak kesannya. */
  :root[data-theme="dark"] .auth-card{
    background:color-mix(in srgb, var(--surface) 55%, transparent);
    border-color:color-mix(in srgb, #fff 12%, transparent);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.10),
      0 18px 44px rgba(0,0,0,.45),
      0 40px 80px rgba(0,0,0,.35);
  }
  /* Sapuan latarnya dipekatkan sedikit — kaca perlu sesuatu untuk dibiaskan. Tanpa ini
     efeknya nyaris tak terlihat, karena yang diburamkan cuma warna datar. */
  .auth::before,.auth::after{opacity:.62;filter:blur(64px)}
  /* Rel tab ikut jadi kaca supaya kartunya tidak terlihat seperti dua bahan berbeda. */
  .auth-tabs{background:color-mix(in srgb, var(--surface-2) 55%, transparent);
    -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
}
/* Sebagian orang menyalakan "kurangi transparansi" di setelan sistemnya, dan biasanya
   karena buram membuat mereka sulit membaca. Untuk mereka kacanya dipadatkan. */
@media (prefers-reduced-transparency: reduce) {
  .auth-card{background:var(--surface);-webkit-backdrop-filter:none;backdrop-filter:none}
}
@keyframes authIn{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.auth-card{animation:none}}

/* Auth shell: satu kolom di tengah untuk layar sederhana (mis. maintenance).
   Form masuk & landing punya gaya sendiri (.auth-wrap-focus / .auth.lp). */
.auth-wrap{position:relative;z-index:1;width:100%;max-width:392px;display:flex;flex-direction:column;gap:var(--sp-x22);margin:auto 0}   /* auto = di tengah kalau muat, 0 kalau tidak — tanpa memotong */
@media (prefers-reduced-motion:reduce){
  .auth::before,.auth::after,.auth-fx-orb{transition:none}
}

.auth-card .brand{justify-content:center;margin-bottom:var(--sp-4);font-size:19px}
.auth-sub{text-align:center;color:var(--muted);font-size:13.5px;margin-bottom:var(--sp-x22)}

/* ===================== focused sign-in shell (reached from the landing) =====================
   Column layout: the logo sits in normal flow at the top, so a tall form (Create account) can
   never slide underneath it. The card carries no second logo — the top one is enough. */
.auth-focus{flex-direction:column;align-items:center;justify-content:flex-start;padding:0 var(--sp-20) var(--sp-x28)}
.auth-topbar{width:100%;max-width:400px;margin:0 auto;padding:var(--sp-16) var(--sp-2) var(--sp-4);display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10)}
/* Compact EN/ID switch on the signed-out screens (landing nav + auth top bar). */
.lang-mini{display:inline-flex;flex:none;background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:var(--sp-2)}
.lang-mini button{padding:var(--sp-4) var(--sp-10);border-radius:999px;font-size:11.5px;font-weight:700;letter-spacing:.03em;color:var(--muted);border:none;background:none;cursor:pointer;line-height:1.35;transition:background .12s,color .12s}
.lang-mini button.on{background:var(--accent);color:#fff}
.lang-mini button:not(.on):hover{color:var(--text)}
/* Sign-up reminder that name & WhatsApp feed integrations — accurate data matters early. */
.auth-note{display:flex;gap:var(--sp-8);align-items:flex-start;text-align:left;background:var(--accent-weak);border:1px solid color-mix(in srgb,var(--accent) 22%,transparent);border-radius:12px;padding:var(--sp-10) var(--sp-12);margin:var(--sp-2) 0 var(--sp-14);font-size:12.5px;line-height:1.5;color:var(--muted)}
.auth-note svg{flex:none;margin-top:var(--sp-1);color:var(--accent-text)}
.auth-home{display:inline-flex;align-items:center;gap:var(--sp-8);background:none;border:none;font:inherit;font-weight:800;font-size:16px;color:var(--text);cursor:pointer;padding:var(--sp-6) var(--sp-8);border-radius:10px}
.auth-home svg{flex:none}
.auth-home:hover{background:var(--surface-2)}
.auth-wrap-focus{width:100%;max-width:400px;flex-direction:column;margin:auto 0}
.auth-focus .ac-brand{display:none}
@media (min-width:940px){
  .auth-wrap-focus{flex-direction:column;max-width:400px;align-items:stretch;margin:auto auto}
  .auth-wrap-focus .auth-card{position:static;order:0;width:100%}
}

/* ===================== marketing landing ===================== */
.auth.lp{display:block;height:100%;min-height:100%;overflow-y:auto;overflow-x:hidden;padding:0;align-items:stretch}
.auth.lp .auth-fx-orb{position:fixed}
.lp-nav{position:sticky;top:0;z-index:20;background:color-mix(in srgb,var(--bg) 82%,transparent);
  -webkit-backdrop-filter:blur(14px) saturate(150%);backdrop-filter:blur(14px) saturate(150%);border-bottom:1px solid var(--line)}
.lp-nav-in{max-width:1080px;margin:0 auto;display:flex;align-items:center;gap:var(--sp-14);padding:var(--sp-x11) var(--sp-20)}
.lp-logo{display:flex;align-items:center;gap:var(--sp-x9);font-weight:800;font-size:18px;color:var(--text)}
.lp-logo svg{flex:none}
.lp-nav-cta{margin-left:auto;display:flex;align-items:center;gap:var(--sp-8)}
.lp-link{background:none;border:none;font:inherit;font-weight:600;font-size:14px;color:var(--text);cursor:pointer;padding:var(--sp-8) var(--sp-12);border-radius:10px;white-space:nowrap}
/* landing CTAs are compact, not full-width like the app's form buttons.
   .auth.lp beats the .auth .btn-primary{width:100%} form rule on specificity. */
.auth.lp .lp-hero-cta .btn,.auth.lp .lp-final-cta .btn,.auth.lp .lp-price-card .btn,.auth.lp .lp-nav-start{width:auto;flex:0 0 auto}
.lp-link:hover{background:var(--surface-2)}
.lp-nav-start{padding:var(--sp-x9) var(--sp-x18);font-size:14px}
.beta-pill-auth{margin-left:var(--sp-2)}
/* Mobile landing header: one tidy row, no overlap/overflow. The beta pill folds
   away and the actions shrink so brand + EN|ID + Sign in + Get started all fit at
   360px (our narrowest supported width). */
@media (max-width:600px){
  .lp-nav-in{gap:var(--sp-8);padding:var(--sp-x9) var(--sp-x13)}
  .lp-logo{font-size:16px;gap:var(--sp-6);min-width:0}
  .lp-logo svg{width:24px;height:24px}
  .lp-logo .beta-pill-auth{display:none}
  .lp-nav-cta{gap:var(--sp-x5)}
  .lp-nav .lang-mini button{padding:var(--sp-4) var(--sp-8);font-size:11px}
  .lp-link{padding:var(--sp-x7) var(--sp-8);font-size:12.5px}
  .lp-nav-start{padding:var(--sp-8) var(--sp-12);font-size:12.5px}
}
/* BILAH KEPALA HALAMAN DEPAN DI PONSEL TERSEMPIT (320px).
 *
 * TERUKUR, bukan diduga: pada 320px BAHASA INGGRIS tombol utama "Get started" berakhir di
 * x=331 — sebelas piksel DI LUAR layar, jadi ujungnya terpotong. Bahasa Indonesia aman
 * ("Mulai" lebih pendek, berakhir di 307). Identik di v348 dan v349, jadi ia cacat bawaan,
 * bukan buatan rilis mana pun. Halaman tidak bisa digulir menyamping, jadi yang hilang
 * benar-benar hilang — di tombol utama halaman depan, di bahasa yang kita pimpin ke pasar
 * internasional.
 *
 * Isi barisnya: merek + pemilih bahasa + Masuk + Get started. Pada 320px keempatnya menuntut
 * 226px sementara barisnya cuma 201px.
 *
 * KENAPA TIDAK ADA YANG DISEMBUNYIKAN, meski itu jalan termudah dan sudah ada presedennya
 * (lencana BETA memang melangkah mundur di baris atas): diukur, "Masuk" cuma muncul DUA kali
 * di seluruh halaman — di sini, lalu 8.229px ke bawah — jadi menyembunyikannya berarti
 * pengguna lama tidak punya jalan masuk yang terlihat sama sekali. Dan pemilih bahasa cuma
 * ada SATU di seluruh halaman; menyembunyikannya mencabut satu-satunya cara berpindah
 * bahasa, tepat di lebar layar yang paling banyak dipakai pembaca bahasa keduanya.
 * Keduanya satu-satunya wakil fungsinya. Yang tidak punya kembaran tidak boleh melangkah
 * mundur.
 *
 * Jadi ruangnya diambil dari KERAPATAN, dan hanya mendatar: tidak ada tinggi ketuk yang
 * berkurang, tidak ada ukuran huruf yang mengecil. Terkumpul 36px untuk kekurangan 25px —
 * sisa 11px, cukup sampai kira-kira 309px. Tanpa sisa itu ia "pas" hari ini lalu pecah lagi
 * pada terjemahan berikutnya yang satu huruf lebih panjang.
 *
 * Dijaga rig peramban di 320px DUA BAHASA, bukan cuma yang kebetulan pernah salah. */
@media (max-width:360px){
  .lp-nav-in{gap:var(--sp-6);padding:var(--sp-x9) var(--sp-x9)}
  .lp-nav-cta{gap:var(--sp-x3)}
  .lp-nav .lang-mini button{padding:var(--sp-4) var(--sp-x5)}
  .lp-link{padding:var(--sp-x7) var(--sp-x5)}
  .lp-nav-start{padding:var(--sp-8) var(--sp-10)}
  /* Merek turun SATU langkah, bukan hilang. Percobaan pertama berhenti di lima baris di atas
     dan menyisakan 5,6px di 320px Inggris — cukup hari ini, dan pecah lagi pada label yang
     satu huruf lebih panjang. Ukuran huruf tombol utama sengaja TIDAK ikut diturunkan: yang
     boleh mengalah adalah merek yang tetap terbaca, bukan ajakan yang harus terbaca. */
  .lp-nav .lp-logo{font-size:15px}
  .lp-nav .lp-logo svg{width:22px;height:22px}
}
.lp-hero{max-width:1080px;margin:0 auto;padding:var(--sp-x44) var(--sp-20) var(--sp-20);position:relative;z-index:1}
.lp-hero-in{display:flex;flex-direction:column;gap:var(--sp-x28)}
.lp-eyebrow{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--accent-text);margin-bottom:var(--sp-12)}
.lp-h1{font-size:33px;line-height:1.1;letter-spacing:-.025em;font-weight:800;margin:0 0 var(--sp-14);color:var(--text);text-wrap:balance}
.lp-lede{font-size:16px;line-height:1.6;color:var(--muted);margin:0 0 var(--sp-x22);max-width:460px}
.lp-hero-cta{display:flex;flex-wrap:wrap;gap:var(--sp-12);margin-bottom:var(--sp-x18)}
.lp-hero-trust{list-style:none;margin:var(--sp-14) 0 0;padding:0;display:flex;flex-wrap:wrap;gap:var(--sp-8) var(--sp-16)}
.lp-hero-trust li{display:flex;align-items:center;gap:var(--sp-x5);font-size:12px;color:var(--muted);font-weight:600}
.lp-hero-trust li svg{color:#177245;flex:none}
/* BUTIR 55 — baris harga di layar pertama. Sengaja BUKAN keping berwarna dan bukan tombol:
   ia keterangan, dan keping yang menonjol di sebelah tombol utama akan bersaing dengannya —
   persis gerbang fakta yang sudah pernah ditulis untuk halaman ini ("kalimat harga tidak
   berubah jadi keping yang bersaing dengan tombol"). Ikonnya rata-ATAS, bukan rata-tengah,
   karena kalimatnya membungkus jadi dua baris di ponsel dan ikon yang rata-tengah akan
   melayang di tengah paragraf. */
.lp-hero-price{display:flex;align-items:flex-start;gap:var(--sp-x7);margin:var(--sp-12) 0 0;font-size:12.5px;line-height:1.5;color:var(--muted);max-width:46ch}
.lp-hero-price svg{color:var(--muted);flex:none;margin-top:var(--sp-x3)}
.lp-hero-price b,.lp-hero-price strong{color:var(--text)}
.lp-cta-lg{padding:var(--sp-x13) var(--sp-24);font-size:15px;font-weight:700}
.lp-cta-ghost{padding:var(--sp-x13) var(--sp-x22);font-size:15px;background:var(--surface);border:1px solid var(--line);color:var(--text)}
.lp-cta-ghost:hover{border-color:var(--accent)}
.lp-hero-art{display:flex;justify-content:center}
.lp-hero-art .ap-phone{margin:0}
.lp-hero-art .ap-phone{transform:rotateY(calc(var(--mx,0)*3deg)) rotateX(calc(var(--my,0)*-3deg)) translateY(calc(var(--sy,0px)*-0.1));transition:transform .2s ease-out}
/* feature sections */
.lp-feat{max-width:1080px;margin:0 auto;padding:var(--sp-x42) var(--sp-20);display:flex;flex-direction:column;gap:var(--sp-24);position:relative;z-index:1}
.lp-kicker{display:inline-flex;align-items:center;gap:var(--sp-x9);font-size:13px;font-weight:700;color:var(--text);margin-bottom:var(--sp-12)}
.lp-kicker .menu-ico{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:9px}
.lp-h2{font-size:24px;line-height:1.2;letter-spacing:-.02em;font-weight:800;margin:0 0 var(--sp-12);color:var(--text);text-wrap:balance}
.lp-desc{font-size:15px;line-height:1.6;color:var(--muted);margin:0 0 var(--sp-16);max-width:450px}
.lp-points{list-style:none;margin:0;padding:0;display:grid;gap:var(--sp-x9)}
.lp-points li{display:flex;align-items:flex-start;gap:var(--sp-x9);font-size:14px;color:var(--text);line-height:1.45}
.lp-points li svg{flex:none;color:#177245;margin-top:var(--sp-2)}
.lp-feat-art{display:flex;justify-content:center;min-width:0}
/* pricing */
/* Landing — grid fitur ringkas (#274). Kartu pakai --surface/--line (sadar tema); ikon monokrom
   diwarnai per-tile. */
.lp-more{padding:var(--sp-x44) var(--sp-20) var(--sp-6);position:relative;z-index:1}
.lp-more-in{max-width:1040px;margin:0 auto}
.lp-more-head{text-align:center;max-width:620px;margin:0 auto var(--sp-x30)}
.lp-more-head .lp-kicker{justify-content:center}
.lp-more-head .lp-desc{margin-left:auto;margin-right:auto;text-wrap:balance}
.lp-more-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(258px,1fr));gap:var(--sp-14)}
.lp-more-card{display:flex;gap:var(--sp-x13);align-items:flex-start;padding:var(--sp-x18);border-radius:16px;background:var(--surface);border:1px solid var(--line)}
.lp-more-ic{width:44px;height:44px;flex:none;border-radius:12px;display:flex;align-items:center;justify-content:center}
.lp-more-tx b{display:block;font-size:15px;font-weight:600;color:var(--text);margin-bottom:var(--sp-x3)}
.lp-more-tx span{display:block;font-size:13px;line-height:1.5;color:var(--muted)}
.lpm-a{color:#2f7ae5;background:rgba(47,122,229,.13)}
.lpm-b{color:#7c5cff;background:rgba(124,92,255,.13)}
.lpm-c{color:#f79009;background:rgba(247,144,9,.15)}
.lpm-d{color:#12b76a;background:rgba(18,183,106,.13)}
.lpm-e{color:#0ea5a5;background:rgba(14,165,165,.13)}
.lpm-f{color:#ef4444;background:rgba(239,68,68,.12)}
.lpm-g{color:#a855f7;background:rgba(168,85,247,.13)}
.lpm-h{color:#eab308;background:rgba(234,179,8,.16)}
.lp-price{padding:50px var(--sp-20);text-align:center;position:relative;z-index:1}
.lp-price-in{max-width:560px;margin:0 auto}
.lp-kicker-c{justify-content:center}
.lp-price-card{margin-top:var(--sp-20);background:var(--surface);border:1px solid var(--line);border-radius:22px;padding:var(--sp-x30) var(--sp-x26);box-shadow:0 20px 50px rgba(24,24,27,.08)}
:root[data-theme="dark"] .lp-price-card{box-shadow:0 20px 50px rgba(0,0,0,.4)}
.lp-price-amt{font-size:40px;font-weight:800;letter-spacing:-.02em;color:var(--text)}
.lp-price-amt span{font-size:14.5px;font-weight:600;color:var(--muted);margin-left:var(--sp-8);letter-spacing:0}
.lp-price-sub{font-size:14.5px;color:var(--muted);margin:var(--sp-8) auto 0;line-height:1.5;max-width:360px}
.lp-price-fine{font-size:12.5px;color:var(--muted);opacity:.9;margin:var(--sp-12) auto 0;line-height:1.5;max-width:360px}
.lp-price-free{display:inline-flex;align-items:center;gap:var(--sp-6);margin:var(--sp-16) 0 0;font-size:12.5px;font-weight:700;color:var(--accent-text);background:var(--accent-weak);border-radius:999px;padding:var(--sp-6) var(--sp-14)}
.lp-price-pts{max-width:290px;margin:var(--sp-20) auto var(--sp-x22);text-align:left}
/* FAQ di landing — memakai ulang .doc-item/.doc-head dari FAQ in-app, jadi bentuk baris,
   warna ikon bergilir, dan perilaku bukanya identik dengan yang nanti dilihat orang di
   dalam aplikasi. Yang berbeda hanya wadahnya: di layar lebar dua kolom (auto-fit, sama
   seperti .lp-more-grid) supaya 15 pertanyaan tidak jadi satu pita panjang; di ponsel
   satu kolom. align-items:start supaya jawaban yang terbuka melebarkan barisnya sendiri,
   bukan seluruh baris di sebelahnya. */
.lp-faq{padding:var(--sp-x44) var(--sp-20) var(--sp-6);position:relative;z-index:1}
.lp-faq-in{max-width:1040px;margin:0 auto}
/* Ditulis .lp-faq .lp-faq-list, BUKAN .lp-faq-list saja. Aturan .doc-list (display:flex)
   berada JAUH di bawah berkas ini; dengan kekhususan yang sama, yang belakangan menang dan
   grid-nya diam-diam tak pernah berlaku — persis yang terjadi saat pertama ditulis. Dua
   kelas membuatnya menang tanpa bergantung pada urutan baris. */
.lp-faq .lp-faq-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(420px,100%),1fr));gap:var(--sp-10);align-items:start}
.lp-faq-list .doc-head{padding:var(--sp-x13) var(--sp-x15)}
/* final CTA + footer */
.lp-final{max-width:640px;margin:0 auto;padding:54px var(--sp-20) var(--sp-x38);text-align:center;position:relative;z-index:1}
.lp-final .lp-h2{font-size:27px}
.lp-final-cta{justify-content:center;margin-top:var(--sp-20)}
.lp-final .lp-lede{margin-left:auto;margin-right:auto;text-wrap:balance}
.lp-foot{border-top:1px solid var(--line);position:relative;z-index:1}
.lp-foot-in{max-width:1080px;margin:0 auto;padding:var(--sp-x22) var(--sp-20);display:flex;align-items:center;gap:var(--sp-14);flex-wrap:wrap;color:var(--muted);font-size:13px}
.lp-foot .lp-logo{font-size:15px}
/* Empat tautan (dulu dua) — wajib boleh membungkus, kalau tidak di 360px baris terakhir
   terdorong keluar layar. FAQ-nya <button> karena seksinya ada di halaman ini; disamakan
   penuh dengan <a> di sebelahnya supaya tak ada yang terlihat sebagai kontrol berbeda. */
.lp-foot-links{display:flex;gap:var(--sp-16);margin-left:auto;flex-wrap:wrap;row-gap:var(--sp-8)}
.lp-foot-links a,.lp-foot-links button{color:var(--muted);text-decoration:none;background:none;border:0;padding:0;font:inherit;cursor:pointer}
.lp-foot-links a:hover,.lp-foot-links button:hover{color:var(--accent-text)}
/* Blok identitas usaha di footer. Satu baris penuh sendiri, di atas baris hak cipta:
   alamat itu satu kalimat panjang, dan kalau dijejalkan ke baris tautan ia akan membungkus
   berantakan di ponsel. Sengaja TIDAK diredupkan sekaligus — nama usaha dibuat lebih tegas
   daripada barisnya, supaya terbaca sebagai identitas, bukan sebagai catatan kaki. */
.lp-foot-biz{width:100%;display:flex;flex-direction:column;gap:var(--sp-x3);color:var(--muted);font-size:12.5px;line-height:1.55}
/* BUTIR 130 — dulu var(--text-2), sebuah nama yang tidak pernah didefinisikan; warnanya jatuh
   ke inherit, jadi aturan ini praktis cuma menebalkan. Di sini penggantinya --text dan BUKAN
   --muted seperti lima tempat lainnya: induknya (.lp-foot-biz) sudah --muted, jadi --muted akan
   membuat aturan ini tidak melakukan apa-apa. Catatan di atas memintanya terbaca sebagai
   identitas, bukan catatan kaki — dan itu berarti lebih terang dari induknya, bukan sama. */
.lp-foot-biz-n{font-weight:650;color:var(--text)}
.lp-foot-biz a{color:var(--muted);text-decoration:none}
.lp-foot-biz a:hover{color:var(--accent-text)}
.lp-foot-c{width:100%;color:var(--faint);font-size:12px}
/* Bilah atas landing itu sticky setinggi ~71px, jadi apa pun yang digulir "ke atas layar"
   mendarat DI BAWAHNYA dan judulnya terpotong. Ini sudah salah sejak tombol "See what’s
   inside" ada — cuma tak pernah terlihat karena yang tersembunyi kebetulan ruang kosong.
   Diperbaiki di targetnya, bukan di pemanggil scrollIntoView, supaya sasaran lompat
   berikutnya ikut benar tanpa perlu diingat. */
.lp-feat,.lp-faq{scroll-margin-top:92px}
/* reveal-on-scroll for landing sections */
.auth.rev-on .rev{opacity:0;transform:translateY(26px)}
.auth.rev-on .rev.in{opacity:1;transform:none;transition:opacity .6s ease,transform .7s cubic-bezier(.2,.8,.2,1)}
@media (prefers-reduced-motion:reduce){.auth.rev-on .rev{opacity:1;transform:none}}
/* wide: two-column hero + alternating features */
@media (min-width:820px){
  .lp-hero{padding:72px var(--sp-20) var(--sp-40)}
  .lp-hero-in{flex-direction:row;align-items:center;gap:52px}
  .lp-hero-tx{flex:1;min-width:0}
  .lp-hero-art{flex:none;width:360px}
  .lp-hero-art .ap-phone{max-width:320px}
  .lp-h1{font-size:46px}
  .lp-lede{font-size:17px}
  .lp-feat{flex-direction:row;align-items:center;gap:56px;padding:60px var(--sp-20)}
  .lp-feat-tx{flex:1;min-width:0}
  .lp-feat-art{flex:none;width:390px}
  .lp-feat-alt{flex-direction:row-reverse}
  .lp-h2{font-size:29px}
}

/* ===================== phone-framed product screens (v141) ===================== */
.ap-phone{width:300px;max-width:100%;background:linear-gradient(160deg,#e9edf3,#dbe1ea);border-radius:36px;padding:var(--sp-x9);
  box-shadow:0 36px 78px rgba(24,24,27,.20),0 12px 28px rgba(24,24,27,.10)}
:root[data-theme="dark"] .ap-phone{background:linear-gradient(160deg,#33333c,#1b1b21);box-shadow:0 36px 78px rgba(0,0,0,.6),0 12px 28px rgba(0,0,0,.45)}
.ap-scr{background:var(--bg);border-radius:28px;padding:var(--sp-16) var(--sp-14) var(--sp-14);overflow:hidden;position:relative}
.lp-feat-art .ap-phone{width:300px}
/* home */
.pm-bar{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--sp-x13)}
.pm-ws{display:flex;align-items:center;gap:var(--sp-x7);font-size:13px;font-weight:800;color:var(--text)}
.pm-ws-ava{width:22px;height:22px;border-radius:50%;background:linear-gradient(140deg,#5b3fb0,#0a5cbf);color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}
.pm-bell{width:15px;height:15px;border-radius:50%;border:2px solid var(--muted);opacity:.5}
.pm-hi{font-size:16px;font-weight:800;color:var(--text);letter-spacing:-.01em}
.pm-hi-sub{font-size:11px;color:var(--muted);margin-bottom:var(--sp-x13)}
.pm-sec{font-size:10px;font-weight:700;letter-spacing:.07em;color:var(--faint);margin:var(--sp-2) 0 var(--sp-6)}
.pm-trow{display:flex;align-items:center;gap:var(--sp-x9);padding:var(--sp-6) var(--sp-2)}
.pm-chk{width:17px;height:17px;flex:none;border:2px solid var(--line);border-radius:5px}
.pm-trow-t{flex:1;font-size:12.5px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* BUTIR 55 — mockup beranda dulu memasang DUA baris merah "Overdue" sebagai janji pertama
   produk ini. Sekarang satu tugas tuntas dan satu jatuh tempo sore ini.
   MERAHNYA IKUT DIBERESKAN, dan ini bukan hiasan: percobaan pertama saya menyisakan .pm-due
   merah, jadi "16:00" — jam sebuah tugas yang BELUM lewat — tetap tampil semerah kata
   "Overdue" yang baru saja saya buang. Warna yang dipakai untuk keadaan biasa berhenti
   berarti apa-apa saat keadaannya benar-benar buruk. Jadi: bawaannya tenang, hijau untuk
   yang tuntas, dan merah disimpan di .pm-due-late untuk yang memang terlambat. */
.pm-due{font-size:10px;font-weight:700;color:var(--muted)}
.pm-due.pm-due-late{color:#c0392b}
:root[data-theme="dark"] .pm-due.pm-due-late{color:#f19d8e}
.pm-chk.on{background:#177245;border-color:#177245;display:flex;align-items:center;justify-content:center;color:#fff}
.pm-trow-t.pm-done{color:var(--muted);text-decoration:line-through}
.pm-due.pm-due-ok{color:#177245}
:root[data-theme="dark"] .pm-due.pm-due-ok{color:#7fd1a8}
.pm-tile{display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-x9) var(--sp-6);border-radius:12px}
.pm-tile .menu-ico{width:34px;height:34px;flex:none;display:flex;align-items:center;justify-content:center;border-radius:10px}
.pm-tile-tx{flex:1;min-width:0}
.pm-tile-tx b{display:block;font-size:12.5px;color:var(--text)}
.pm-tile-tx i{font-style:normal;font-size:10.5px;color:var(--muted)}
.pm-tile>svg{color:var(--faint);flex:none}
/* chat */
.pm-cbar{display:flex;align-items:center;gap:var(--sp-x9);padding-bottom:var(--sp-10);margin-bottom:var(--sp-x11);border-bottom:1px solid var(--line)}
.pm-cava{width:30px;height:30px;border-radius:50%;background:linear-gradient(140deg,#b23a5b,#7d2740);color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center}
.pm-cid{flex:1}
.pm-cid b{display:block;font-size:12.5px;color:var(--text)}
.pm-cid i{font-style:normal;font-size:10px;color:#177245}
.pm-kebab{width:4px;height:16px;flex:none;background:radial-gradient(circle,var(--muted) 1.4px,transparent 1.7px) center/4px 5.5px repeat-y;opacity:.7}
.pm-day{text-align:center;font-size:10px;color:var(--faint);margin:var(--sp-2) 0 var(--sp-10)}
.pm-b{max-width:82%;padding:var(--sp-8) var(--sp-x11);border-radius:14px;font-size:12px;line-height:1.4;margin-bottom:var(--sp-8)}
.pm-in{background:var(--surface-2);color:var(--text);border-bottom-left-radius:4px}
.pm-out{background:var(--accent);color:#fff;margin-left:auto;border-bottom-right-radius:4px}
.pm-shot{display:flex;align-items:center;gap:var(--sp-8);background:color-mix(in srgb,var(--accent) 86%,#000)}
.pm-shot .menu-ico{width:26px;height:26px;flex:none;display:flex;align-items:center;justify-content:center;border-radius:7px;background:rgba(255,255,255,.92);color:#177245}
.pm-shot-tx b{display:block;font-size:11.5px}
.pm-shot-tx i{font-style:normal;font-size:10px;opacity:.85}
.pm-comp{display:flex;align-items:center;gap:var(--sp-8);margin-top:var(--sp-4);padding:var(--sp-x7) var(--sp-8);background:var(--surface-2);border-radius:16px}
.pm-comp-plus{width:20px;height:20px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:18px;line-height:1}
.pm-comp-in{flex:1;font-size:11.5px;color:var(--faint)}
.pm-comp-send{width:26px;height:26px;flex:none;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center}
/* tasks */
.pm-tsc{display:flex;align-items:center;gap:var(--sp-8);margin-bottom:var(--sp-10)}
.pm-list{font-size:10px;font-weight:700;color:#3b6d11;background:#eaf3de;border-radius:7px;padding:var(--sp-2) var(--sp-8)}
:root[data-theme="dark"] .pm-list{background:#22331a;color:#a5cf7e}
.pm-prio{font-size:9.5px;font-weight:800;color:#c0392b;background:#fdecea;border-radius:7px;padding:var(--sp-2) var(--sp-8);text-transform:uppercase;letter-spacing:.03em}
:root[data-theme="dark"] .pm-prio{background:#3d1a14;color:#f19d8e}
.pm-th{font-size:16px;font-weight:800;color:var(--text);letter-spacing:-.01em;margin-bottom:var(--sp-x11)}
.pm-bar2{height:6px;border-radius:3px;background:var(--line);overflow:hidden}
.pm-bar2 span{display:block;height:100%;border-radius:3px;background:linear-gradient(90deg,#0a5cbf,#7c3aed)}
.pm-prog{font-size:10.5px;color:var(--muted);margin:var(--sp-6) 0 var(--sp-10)}
.pm-sub{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-6) var(--sp-2);font-size:12.5px;color:var(--text)}
.pm-chk2{width:18px;height:18px;flex:none;border:2px solid var(--line);border-radius:6px;display:flex;align-items:center;justify-content:center;color:#fff}
.pm-chk2.on{background:#177245;border-color:#177245}
.pm-done{color:var(--muted);text-decoration:line-through}
.pm-asg{display:flex;align-items:center;margin-top:var(--sp-x11);padding-top:var(--sp-x11);border-top:1px solid var(--line)}
.pm-ava{width:24px;height:24px;border-radius:50%;color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;border:2px solid var(--surface);background:linear-gradient(140deg,#0a5cbf,#5b3fb0)}
.pm-ava2{background:linear-gradient(140deg,#177245,#0e766e);margin-left:-8px}
.pm-ava3{background:linear-gradient(140deg,#b45309,#c0392b);margin-left:-8px}
.pm-asg-tx{font-size:10.5px;color:var(--muted);margin-left:var(--sp-x9)}
/* calendar */
.pm-calh{display:flex;align-items:center;justify-content:center;gap:var(--sp-14);margin-bottom:var(--sp-10);color:var(--text)}
.pm-calh b{font-size:13px;font-weight:800}
.pm-calh svg{color:var(--muted)}
.pm-wkrow{display:grid;grid-template-columns:repeat(7,1fr);margin-bottom:var(--sp-x5)}
.pm-wk{text-align:center;font-size:9px;font-weight:700;color:var(--faint)}
.pm-cgrid{position:relative;display:grid;grid-template-columns:repeat(7,1fr);gap:var(--sp-4)}
.pm-cell{aspect-ratio:1;border-radius:5px;background:var(--surface-2)}
.pm-today{outline:2px solid var(--accent);outline-offset:-2px}
.pm-evt{position:absolute;font-size:8.5px;font-weight:700;color:#fff;border-radius:4px;padding:var(--sp-2) var(--sp-x5);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-sizing:border-box}
.pm-e1{top:30%;left:1%;width:70%;background:#4553b3}
.pm-e2{top:49%;left:42%;width:46%;background:#0e766e}
.pm-e3{top:68%;left:16%;width:56%;background:#b45309}
/* attendance / insights */
.pm-ins-h{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:var(--sp-2)}
.pm-ins-h b{font-size:16px;font-weight:800;color:var(--text)}
.pm-ins-peak{font-size:9.5px;font-weight:700;color:#c0392b}
:root[data-theme="dark"] .pm-ins-peak{color:#f19d8e}
.pm-ins-sub{font-size:10.5px;color:var(--muted);margin-bottom:var(--sp-12)}
.pm-hmwrap{display:grid;gap:var(--sp-4)}
.pm-hmrow{display:grid;grid-template-columns:24px repeat(8,1fr);gap:var(--sp-x3);align-items:center}
.pm-hmd{font-size:9px;color:var(--faint);font-weight:600}
.pm-hm{aspect-ratio:1;border-radius:3px;background:var(--surface-2)}
.pm-hm.l1{background:#fde3cf}.pm-hm.l2{background:#fbbf92}.pm-hm.l3{background:#f19860}.pm-hm.l4{background:#d9702f}
:root[data-theme="dark"] .pm-hm.l1{background:#4a2c17}
:root[data-theme="dark"] .pm-hm.l2{background:#7a4420}
:root[data-theme="dark"] .pm-hm.l3{background:#a85e2b}
:root[data-theme="dark"] .pm-hm.l4{background:#d9702f}
.pm-lgd{display:flex;align-items:center;gap:var(--sp-4);justify-content:flex-end;margin-top:var(--sp-x11);font-size:9.5px;color:var(--muted)}
.pm-lc{width:12px;height:12px;border-radius:3px}
.pm-lc.l1{background:#fde3cf}.pm-lc.l2{background:#fbbf92}.pm-lc.l3{background:#f19860}.pm-lc.l4{background:#d9702f}
/* name card */
.pm-nc{background:linear-gradient(150deg,#b23a5b,#7d2740);border-radius:16px;padding:var(--sp-x15);color:#fff;margin:var(--sp-2)}
.pm-nc-top{display:flex;align-items:center;gap:var(--sp-10);margin-bottom:var(--sp-x11)}
.pm-nc-ava{width:40px;height:40px;border-radius:50%;background:#fff;color:#b23a5b;font-size:14px;font-weight:800;display:flex;align-items:center;justify-content:center}
.pm-nc-id b{display:block;font-size:14px}
.pm-nc-id i{font-style:normal;font-size:10.5px;opacity:.9}
.pm-nc-ph{display:flex;align-items:center;gap:var(--sp-6);background:rgba(255,255,255,.16);border-radius:9px;padding:var(--sp-6) var(--sp-10);font-size:11.5px;font-weight:600;margin-bottom:var(--sp-x11)}
.pm-nc-ph .wa-ic{width:14px;height:14px;flex:none}
.pm-nc-qr{width:94px;height:94px;background:#fff;border-radius:10px;padding:var(--sp-x7);margin:0 auto;display:grid;grid-template-columns:repeat(7,1fr);gap:var(--sp-2)}
.pm-nc-qr span{border-radius:1px}
.pm-nc-qr span.on{background:#7d2740}
.pm-nc-cap{text-align:center;font-size:9.5px;opacity:.85;margin-top:var(--sp-x7)}
.pm-nc-brand{text-align:center;font-weight:800;letter-spacing:.05em;opacity:.85;font-size:11px;margin-top:var(--sp-8)}

.auth-tabs{display:flex;gap:var(--sp-4);background:var(--surface-2);border:1px solid var(--line);
  border-radius:12px;padding:var(--sp-4);margin-bottom:var(--sp-x18)}
.auth-tabs button{flex:1;padding:var(--sp-x9) var(--sp-8);border-radius:9px;font-size:13.5px;font-weight:600;
  color:var(--muted);transition:background .16s ease,color .16s ease,box-shadow .16s ease}
.auth-tabs button:hover{color:var(--text)}
.auth-tabs button.on{background:var(--surface);color:var(--accent-text);
  box-shadow:0 1px 2px rgba(24,24,27,.07),0 1px 1px rgba(24,24,27,.04)}

.auth .btn-primary{width:100%;justify-content:center;padding:var(--sp-12);margin-top:var(--sp-6);font-weight:650;
  transition:transform .12s ease,box-shadow .16s ease,background .16s ease}
.auth .btn-primary:hover{box-shadow:0 6px 18px color-mix(in srgb,var(--accent) 34%,transparent)}
.auth .btn-primary:active{transform:translateY(1px)}

.auth-hint{text-align:center;font-size:12.5px;color:var(--faint);margin-top:var(--sp-x15);line-height:1.5}
.auth-link{color:var(--accent-text);font-size:13px;font-weight:600}
.auth-link:hover{text-decoration:underline}

/* ---- Kata sandi: tombol lihat/sembunyikan ----
   WCAG 2.2 (3.3.8) menyebut ini langsung: bisa melihat apa yang diketik menurunkan
   kegagalan masuk bagi orang yang kesulitan mengetik tepat, dan bagi siapa pun yang
   mengetik kata sandi panjang di papan ketik ponsel. */
.pw-wrap{position:relative;display:flex;align-items:center}
.pw-wrap input{flex:1;min-width:0;padding-right:var(--sp-x44)}
.pw-eye{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:flex;
  align-items:center;justify-content:center;width:36px;height:36px;border-radius:9px;
  border:none;background:none;color:var(--faint);cursor:pointer;transition:color .15s ease,background .15s ease}
.pw-eye:hover{color:var(--muted);background:var(--surface-2)}

/* Peringatan Caps Lock. Sebab paling sering "kata sandi salah" padahal orangnya ingat
   benar — dan satu-satunya yang tidak bisa dilihatnya karena hurufnya tertutup titik. */
.caps-hint{display:none;align-items:center;gap:var(--sp-6);margin:var(--sp-x7) var(--sp-2) 0;font-size:12px;
  font-weight:600;color:#b45309}
.caps-hint.on{display:flex}
:root[data-theme="dark"] .caps-hint{color:#fbbf24}

/* Galat muncul dengan gerakan kecil supaya tertangkap mata — tanpa itu, pesan yang
   menggantikan pesan lain di tempat yang sama mudah tidak disadari sudah berganti. */
.auth .form-error,.auth .form-ok{animation:authMsg .22s ease}
@keyframes authMsg{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.auth .form-error,.auth .form-ok{animation:none}}
.install-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-8);width:100%;min-height:44px;margin-top:var(--sp-14);padding:var(--sp-x11);border-radius:10px;border:1px solid var(--line);background:var(--surface);font-size:14px;font-weight:600;color:var(--text);transition:background .12s,transform .08s}
.install-btn:hover{background:var(--surface-2)}
.install-btn:active{transform:scale(.98)}
.install-btn svg{color:var(--accent-text)}
/* BUTIR 59 — pintu demo di bawah formulir masuk.
   Pemisah "atau" dibuat dari dua garis yang tumbuh mengisi sisa ruang, bukan dari lebar mati:
   kartu masuk melebar dari 320px sampai selebar kartunya di desktop, dan garis berlebar tetap
   akan menggantung jauh dari katanya di salah satu ujung rentang itu.
   Tombolnya SENGAJA bukan btn-primary. Di Demo pun sebagian pengunjung datang untuk masuk ke
   akun mereka sendiri; dua tombol merah bersebelahan membuat orang menekan yang salah. */
.auth-demo{margin-top:var(--sp-x18)}
.auth-demo-or{display:flex;align-items:center;gap:var(--sp-10);margin:0 0 var(--sp-12);color:var(--faint);font-size:11.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em}
.auth-demo-or::before,.auth-demo-or::after{content:"";flex:1 1 0;height:1px;background:var(--line)}
/* min-height 44px — lantai sasaran sentuh yang ditetapkan butir 143. Ia berlaku untuk seluruh
   aplikasi, bukan cuma untuk rel: padding 11px + huruf 14px menghasilkan 40px, dan tombol
   40px di layar yang dibuka orang dengan satu tangan sambil berdiri adalah tombol yang meleset.
   .install-btn di sebelahnya ikut dinaikkan — dua tombol bertinggi beda di kartu yang sama
   terbaca sebagai kekeliruan, dan lantainya memang sama untuk keduanya. */
.auth-demo-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-8);width:100%;min-height:44px;padding:var(--sp-x11);border-radius:10px;border:1px solid var(--accent);background:var(--accent-weak);font-size:14px;font-weight:600;color:var(--accent-text);transition:background .12s,transform .08s}
.auth-demo-btn:hover{background:var(--surface)}
.auth-demo-btn:active{transform:scale(.98)}
.auth-demo-btn svg{color:var(--accent-text);flex:none}
.auth-demo-note{margin:var(--sp-x9) var(--sp-2) 0;font-size:12px;line-height:1.5;color:var(--muted);text-align:center}
.form-ok{background:#ecfdf5;color:var(--good);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:13px;margin-bottom:var(--sp-14)}
:root[data-theme="dark"] .form-ok{background:rgba(22,163,74,.16)}

/* desktop taskbar inner width cap for chat composer */
@media(min-width:900px){
  .taskbar{padding:0 var(--sp-x18) var(--sab)}
}

/* ---------- button/span layout fixes (a11y: interactive = <button>) ---------- */
.menu-row .t,.menu-row .s,.lr-title,.lr-sub{display:block}
.list-row,.menu-row,.cal-cell,.sub-toggle,.sub-del{font:inherit}

/* ---------- keyboard focus ---------- */
button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}

/* #332 — Hasil pencarian berupa pesan. Cuplikannya dua baris: satu baris terlalu sering memotong
   tepat sebelum kata yang dicari, dan hasil yang tak menunjukkan kenapa ia cocok bikin ragu. */
.sr-msg .sr-snippet{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-weight:500;line-height:1.4}
/* ---------- bilah pembaruan (#400) ----------
   Menunggu, tidak menuntut: muncul saat versi baru siap dan diam di situ sampai dipencet.
   Di ATAS toast (z-index) karena ia menetap, sementara toast datang-pergi; ditaruh sedikit lebih
   tinggi dari toast supaya keduanya tak pernah bertumpuk kalau kebetulan muncul bersamaan. */
/* +52px = tepat di atas satu toast. Komentar lama mengaku bilah ini "ditaruh sedikit lebih
   tinggi dari toast supaya tak pernah bertumpuk", padahal keduanya memakai bottom yang SAMA
   PERSIS — jadi kalau muncul bersamaan mereka memang bertumpuk, cuma tertutup beda z-index.
   Sekarang klaim itu benar-benar berlaku. */
.updbar{position:fixed;left:50%;bottom:calc(var(--float-b) + var(--envrib-h) + 52px);transform:translate(-50%,10px);z-index:520;
  display:flex;align-items:center;gap:var(--sp-10);width:max-content;max-width:calc(100vw - 24px);
  padding:var(--sp-x9) var(--sp-x9) var(--sp-x9) var(--sp-14);border-radius:999px;background:var(--surface);border:1px solid var(--line);
  box-shadow:0 10px 34px rgba(0,0,0,.22);opacity:0;transition:opacity .22s ease,transform .22s ease}
.updbar.in{opacity:1;transform:translate(-50%,0)}
.updbar-ic{flex:none;display:flex;color:var(--accent-text)}
.updbar-t{flex:1;min-width:0;font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.updbar-go{flex:none;border:0;border-radius:999px;padding:var(--sp-x7) var(--sp-x15);font-size:13.5px;font-weight:700;cursor:pointer;
  background:var(--accent);color:#fff;transition:filter .15s,transform .1s}
.updbar-go:hover{filter:brightness(1.06)}
.updbar-go:active{transform:scale(.96)}
.updbar-x{flex:none;width:30px;height:30px;border:0;border-radius:50%;background:transparent;color:var(--faint);
  display:flex;align-items:center;justify-content:center;cursor:pointer}
.updbar-x:hover{background:var(--surface-2);color:var(--text)}
@media (max-width:420px){.updbar{left:12px;right:12px;transform:translateY(10px);max-width:none}.updbar.in{transform:translateY(0)}}
/* ---------- butir 114: bilah unggahan ----------
   Bahasa visualnya SENGAJA sama persis dengan .updbar — pil, permukaan, garis, bayangan,
   dan transisi yang sama. Dua bilah mengambang yang tampil berbeda akan terbaca sebagai
   dua sistem, dan aplikasi ini cuma punya satu. */
.unggahbar{position:fixed;left:50%;bottom:calc(var(--float-b) + var(--envrib-h) + 104px);transform:translate(-50%,10px);z-index:515;
  display:flex;align-items:center;gap:var(--sp-x11);width:max-content;min-width:266px;max-width:calc(100vw - 24px);
  padding:var(--sp-x9) var(--sp-x15);border-radius:999px;background:var(--surface);border:1px solid var(--line);
  box-shadow:0 10px 34px rgba(0,0,0,.22);opacity:0;transition:opacity .22s ease,transform .22s ease}
.unggahbar.in{opacity:1;transform:translate(-50%,0)}
.ub-ico{flex:none;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;
  background:color-mix(in srgb,var(--accent) 13%,transparent);color:var(--accent-text)}
.ub-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-x5)}
.ub-nama{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Jalur kosongnya HARUS terlihat. Dengan --surface-2 ia nyaris menyatu dengan pil
   di belakangnya, jadi yang terbaca cuma garis merah yang memanjang tanpa ujung —
   orang bisa melihat bahwa sesuatu bergerak, tapi tidak bisa menilai tinggal berapa.
   Semburat aksen menjaganya tetap sekeluarga warna dengan isiannya. */
.ub-track{height:4px;border-radius:999px;background:color-mix(in srgb,var(--accent) 17%,transparent);overflow:hidden}
.ub-fill{height:100%;width:0;border-radius:999px;background:var(--accent);transition:width .18s ease}
/* tabular-nums supaya angkanya tidak bergoyang tiap persen — ia berubah puluhan kali per unggahan */
.ub-pct{flex:none;font-size:12.5px;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums;min-width:36px;text-align:right}
.unggahbar.ub-tanpa-angka .ub-fill{width:40%;animation:ubGeser 1.15s ease-in-out infinite}
.unggahbar.ub-tanpa-angka .ub-pct{min-width:0}
@keyframes ubGeser{0%{transform:translateX(-110%)}100%{transform:translateX(260%)}}
@media (prefers-reduced-motion:reduce){.unggahbar.ub-tanpa-angka .ub-fill{animation:none;width:100%;opacity:.55}}
@media (max-width:420px){.unggahbar{left:12px;right:12px;transform:translateY(10px);max-width:none;min-width:0}.unggahbar.in{transform:translateY(0)}}

/* ---------- toasts ---------- */
.toasts{position:fixed;left:50%;bottom:calc(var(--float-b) + var(--envrib-h));transform:translateX(-50%);z-index:500;display:flex;flex-direction:column;gap:var(--sp-8);align-items:center;pointer-events:none}
.toast{background:#18181b;color:#fff;padding:var(--sp-10) var(--sp-16);border-radius:10px;font-size:14px;line-height:1.4;box-shadow:0 6px 20px rgba(0,0,0,.22);animation:toastIn .2s ease;max-width:90vw}
.toast.error{background:var(--danger-solid)}
.toast.success{background:var(--good)}
/* Toast dengan aksi (mis. Undo): butuh tata letak berjajar, dan tombolnya HARUS bisa diketuk
   walau kontainer .toasts memakai pointer-events:none. */
.toast.has-action{display:flex;align-items:center;gap:var(--sp-12);padding-right:var(--sp-8)}
.toast-msg{flex:1;min-width:0}
.toast-action{pointer-events:auto;flex:none;background:rgba(255,255,255,.18);color:#fff;border:0;border-radius:7px;padding:var(--sp-x5) var(--sp-12);font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,transform .1s}
.toast-action:hover{background:rgba(255,255,255,.3)}
.toast-action:active{transform:scale(.95)}
.toast.hide{opacity:0;transform:translateY(8px);transition:opacity .25s,transform .25s}
@keyframes toastIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

/* ---------- top loading bar ---------- */
/* Di bawah bilah atas, bukan di tepi paling atas jendela. Di monitor besar tepi atas layar
   berada jauh di luar tempat mata bekerja, dan garis 3px di sana praktis tak pernah terlihat —
   orang menunggu tanpa tahu bahwa sesuatu sedang dimuat. Ditebalkan sedikit juga. */
/* #402 — Kembali ke TEPI PALING ATAS, atas permintaan langsung klien. Yang memindahkannya ke
   bawah topbar dulu (v275) beralasan "di monitor besar mata tidak pernah ke sana" — alasan yang
   diambil dari satu layar 1440px, sementara Spotious mobile-first. Di ponsel tepi paling atas
   justru strip yang paling terlihat, dan bilah tipis di sana adalah konvensi yang sudah dipelajari
   orang dari perambannya sendiri. Di bawah header ia masuk wilayah isi dan mulai terbaca sebagai
   elemen antarmuka, bukan keadaan sesaat.

   `var(--sat)`, BUKAN `0`: di PWA standalone iOS, 0 mendorongnya ke bawah takik/bilah status
   sistem sehingga sebagian bilahnya tak pernah terlihat. `--sat` adalah tepi paling atas milik
   APLIKASI — persis "tepat di bawah bilah status". Di desktop `--sat` bernilai 0, jadi keduanya
   sama di sana; tidak ada cabang desktop/ponsel yang perlu ditulis. Ketebalan 4px dipertahankan. */
.loadbar{position:fixed;top:var(--sat);left:0;right:0;height:4px;z-index:200;overflow:hidden;pointer-events:none}
.loadbar.on::after{content:"";position:absolute;top:0;height:100%;width:35%;background:var(--accent);border-radius:2px;animation:loadmove 1s infinite ease-in-out}
@keyframes loadmove{0%{left:-35%}50%{left:45%}100%{left:100%}}

/* ---------- boot screen ---------- */
.boot{height:100%;display:flex;flex-direction:column;gap:var(--sp-x18);align-items:center;justify-content:center;padding:var(--sp-24);text-align:center}
/* butir 113 — muncul hanya kalau boot sudah menunggu lebih lama daripada boot yang sehat. */
.boot-lama{display:flex;flex-direction:column;align-items:center;gap:var(--sp-14);max-width:300px;animation:bootLamaIn .3s ease both}
.boot-lama p{margin:0;color:var(--muted);font-size:13.5px;line-height:1.55}
@keyframes bootLamaIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.boot-lama{animation:none}}

/* ---------- thin scrollbars ---------- */
::-webkit-scrollbar{width:12px;height:12px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--line);border-radius:10px;border:3px solid transparent;background-clip:padding-box;min-height:40px}
::-webkit-scrollbar-thumb:hover{background:var(--faint);background-clip:padding-box}
::-webkit-scrollbar-button{display:none;width:0;height:0}
::-webkit-scrollbar-corner{background:transparent}
*{scrollbar-width:thin;scrollbar-color:var(--line) transparent;-webkit-tap-highlight-color:transparent}

/* Memberi tahu peramban tema mana yang sedang dipakai.
   INI penyebab dropdown tampil putih menyilaukan di tema gelap. Daftar pilihan yang
   muncul saat sebuah <select> ditekan digambar oleh SISTEM, bukan oleh CSS kita — jadi
   berapa pun banyaknya latar gelap yang kita cat, tanpa baris ini peramban tetap
   menganggap halamannya terang dan menggambar daftarnya putih dengan tulisan hitam.
   Sekalian membetulkan batang gulir, kursor teks, dan tampilan isian otomatis. */
:where(#app select:focus){border-color:var(--accent)}
:root[data-theme="light"]{color-scheme:light}
:root[data-theme="dark"]{color-scheme:dark}

/* ============================================================
   DROPDOWN — satu tampilan untuk seluruh aplikasi
   ============================================================
   Dulu ada TIGA gaya berbeda untuk kontrol yang sama: kontrol bawaan peramban di
   filter absensi, panah SVG di .field select, dan segitiga gradien di pilihan tanggal
   lahir. Ketiganya berdampingan di satu aplikasi.

   Dipakai #app, bukan .app: overlay digambar sebagai saudara .app, bukan anak-nya, jadi
   selector lama tidak pernah menyentuh dropdown di dalam overlay sama sekali. Itu sebab
   pilihan tanggal lahir dulu harus punya gayanya sendiri.

   Dibungkus :where() supaya kekhususannya nol — setiap aturan yang lebih spesifik tetap
   menang, jadi ini menambah dasar tanpa merebut apa pun. */
:where(#app select){
  -webkit-appearance:none;appearance:none;cursor:pointer;
  background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 11px center;padding-right:var(--sp-32);
}
:root[data-theme="dark"] :where(#app select){background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")}

/* ---------- theme: dark palette (JS sets data-theme; System resolves at runtime) ---------- */
:root[data-theme="dark"]{--bg:#0e0e11;--surface:#17171c;--surface-2:#202027;--text:#e8e8ec;--muted:#adadb8;--faint:#8a8a95;--line:#2a2a31;--accent-weak:#3d1a14;--accent-text:#ffa896;--hover:rgba(255,255,255,.07);--good-text:#22c55e;--danger:#ef5f8b;--danger-solid:#c2185b;--due-late:#fbbf24;--due-today:#d9903b;--prio-med:#7db1f5;--prio-low:#9aa0aa;--glass:rgba(28,28,34,.62);--glass-strong:rgba(30,30,36,.8);--glass-brd:rgba(255,255,255,.09);--glass-hi:rgba(255,255,255,.08)}
:root[data-theme="dark"] .btn-danger:hover{background:rgba(159,18,57,.16)}
:root[data-theme="dark"] .form-error{background:rgba(159,18,57,.16)}
:root[data-theme="dark"] .bubble.them{background:var(--surface-2);border-color:var(--line)}
/* background-color, BUKAN shorthand background: — shorthand mengosongkan
   background-image, dan panah dropdown-nya ikut terhapus di tema gelap. */
:root[data-theme="dark"] .composer input,:root[data-theme="dark"] .field input,:root[data-theme="dark"] .field textarea,:root[data-theme="dark"] .field select{background-color:var(--surface-2)}
:root[data-theme="dark"] .auth-tabs button.on{box-shadow:0 1px 3px rgba(0,0,0,.5)}
:root[data-theme="dark"] .toast{background:#2a2a31}
.seg{display:inline-flex;background:var(--surface-2);border:1px solid var(--line);border-radius:10px;padding:var(--sp-x3);gap:var(--sp-2)}
.seg button{padding:var(--sp-x7) var(--sp-14);border-radius:8px;font-size:13px;font-weight:500;color:var(--muted)}
.seg button.on{background:var(--surface);color:var(--text);box-shadow:0 1px 2px rgba(0,0,0,.08)}
.seg button{transition:background .12s,color .12s}
.seg-sort{padding:var(--sp-2);max-width:100%}
/* BUTIR 118 — nowrap WAJIB di sini. Sesudah "Daftar" naik ke baris yang sama, ruang untuk
   pengurutan menyempit dan "Due date" patah jadi dua baris di 360px — tinggi baris kepalanya
   ikut berubah dan isinya bergeser. Paddingnya dirapatkan supaya ketiganya tetap muat satu
   baris pada lebar tersempit yang doktrin kita sendiri tetapkan; label yang patah bukan
   "sedikit sempit", ia rupa yang rusak di layar yang dibuka tiap hari. */
.seg-sort button{padding:var(--sp-6) var(--sp-x9);font-size:12px;white-space:nowrap}
.seg-prio{display:flex;width:100%;padding:var(--sp-x3);min-width:0}
/* min-width:0 + overflow:hidden adalah jaring pengaman, bukan perbaikan utama. Perbaikan
   utamanya ada di .row2 yang sekarang menumpuk, sehingga pemilih ini selalu dapat lebar
   penuh. Ini untuk keadaan yang belum terpikir — label terjemahan yang lebih panjang,
   atau layar yang lebih sempit dari yang pernah kita uji. Menyusut itu jelek; meluber
   keluar kolom itu rusak. */
.seg-prio .prio-opt{flex:1 1 0;min-width:0;overflow:hidden;display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-6);padding:var(--sp-x9) var(--sp-6);font-size:13px;font-weight:500;color:var(--muted);white-space:nowrap;border-radius:8px}
.seg-prio .prio-opt .prio-dot{margin-right:0;opacity:.5;transition:opacity .12s}
.seg-prio .prio-opt.on{background:var(--surface);color:var(--text);box-shadow:inset 0 0 0 1.5px currentColor,0 1px 2px rgba(0,0,0,.06)}
/* Tombol prioritas yang belum dipilih tetap netral; warnanya baru muncul saat aktif. */
.seg-prio .prio-opt{background:none}
.seg-prio .prio-opt:not(.on){color:var(--muted)}
.seg-prio .prio-opt:not(.on) .prio-dot{background:var(--faint)}
.seg-prio .prio-opt.on .prio-dot{opacity:1}

/* ---- Profile photos (avatars with an image + initials fallback) ---- */
.avatar-img{position:relative;overflow:hidden;padding:0}
.avatar-img .avatar-fb{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;border-radius:inherit;font-weight:600}
.avatar-img img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit;z-index:1;background:var(--surface)}
.avatar-edit{display:flex;align-items:center;gap:var(--sp-14);margin-bottom:var(--sp-20)}
.avatar-edit-pic .avatar{box-shadow:0 0 0 3px var(--line)}
.avatar-edit-actions{display:flex;flex-direction:column;gap:var(--sp-8);align-items:flex-start}
.avatar-edit-actions .btn{cursor:pointer}

/* ---- Feedback screenshot ---- */
.fb-shot{display:inline-flex;align-items:center;gap:var(--sp-6);cursor:pointer;margin-right:auto}
.fb-form-actions .spacer{flex:1}
.fb-image-preview{position:relative;margin:var(--sp-2) 0 var(--sp-4);width:fit-content}
.fb-image-preview img{max-width:180px;max-height:150px;border-radius:10px;border:1px solid var(--line);display:block}
.fb-image-x{position:absolute;top:-8px;right:-8px;width:24px;height:24px;border-radius:50%;background:var(--text);color:var(--surface);border:2px solid var(--surface);display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0}
.fb-card-img{flex:0 0 auto;width:46px;height:46px;border-radius:8px;overflow:hidden;border:1px solid var(--line);padding:0;cursor:pointer;background:none;align-self:center}
.fb-card-img img{width:100%;height:100%;object-fit:cover;display:block}

/* ---- Account sign-out row ---- */
.lr-signout{color:var(--danger,#c0392b)}
.lr-signout .menu-ico{color:inherit}

/* ---- Developer tools ---- */
.dev-statgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-10);margin:var(--sp-6) 0 var(--sp-16)}
.dev-stat{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:var(--sp-12) var(--sp-10);text-align:center}
.dev-stat-n{font-size:20px;font-weight:750;color:var(--text);line-height:1.1}
.dev-stat-l{font-size:11.5px;color:var(--muted);margin-top:var(--sp-x3)}
.dev-totals{font-size:13px;color:var(--muted);margin-bottom:var(--sp-6)}
.dev-totals b{color:var(--text);font-size:15px}
.dev-sec-h{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);margin:var(--sp-x18) 0 var(--sp-8)}
.dev-list{display:flex;flex-direction:column;gap:var(--sp-x9)}
.dev-row{display:flex;align-items:center;gap:var(--sp-10)}
.dev-row-name{flex:0 0 34%;min-width:0;font-size:13px;font-weight:550;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dev-bar{flex:1;height:8px;border-radius:6px;background:var(--line);overflow:hidden}
.dev-bar span{display:block;height:100%;background:var(--accent);border-radius:6px}

/* ---- dev overview cards → tappable, each opens a metric detail (v225) ---- */
.dev-stat-btn{position:relative;cursor:pointer;display:block;width:100%;font:inherit;color:inherit;transition:border-color .15s,transform .1s,box-shadow .15s}
.dev-stat-btn:hover{border-color:color-mix(in srgb,var(--accent) 45%,var(--line));box-shadow:0 4px 14px -8px rgba(15,23,42,.25)}
.dev-stat-btn:active{transform:scale(.97)}
.dev-stat-go{position:absolute;top:6px;right:6px;color:var(--faint);opacity:.5;display:flex}
.dev-stat-users{border-top:2.5px solid #2f7ae5}
.dev-stat-active{border-top:2.5px solid #1f9d55}
.dev-stat-workspaces{border-top:2.5px solid #4f46e5}
.dev-stat-conversations{border-top:2.5px solid #0d9488}
.dev-stat-messages{border-top:2.5px solid #7c6cf0}
.dev-stat-attachments{border-top:2.5px solid #e8811a}

/* ---- metric detail: growth chart 7/30/90d + breakdown (v225) ---- */
.dm-view{--dmc:#2f7ae5}
.dm-tone-blue{--dmc:#2f7ae5}.dm-tone-green{--dmc:#1f9d55}.dm-tone-indigo{--dmc:#4f46e5}.dm-tone-teal{--dmc:#0d9488}.dm-tone-violet{--dmc:#7c6cf0}.dm-tone-amber{--dmc:#e8811a}
.dm-head{border-radius:14px;padding:var(--sp-16);color:#fff;background:linear-gradient(135deg,var(--dmc),color-mix(in srgb,var(--dmc) 60%,#111))}
.dm-total{font-size:30px;font-weight:800;line-height:1}
.dm-total-l{font-size:12.5px;opacity:.92;margin-top:var(--sp-4);font-weight:600}
.dm-win-total{font-size:12px;margin-top:var(--sp-x9);background:rgba(255,255,255,.2);display:inline-block;padding:var(--sp-x3) var(--sp-10);border-radius:20px;font-weight:600}
.dm-win{display:flex;gap:var(--sp-6);margin:var(--sp-14) 0 var(--sp-10)}
.dm-win-b{flex:1;padding:var(--sp-8) 0;border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--muted);font-size:13px;font-weight:650;cursor:pointer;transition:background .12s,color .12s,border-color .12s}
.dm-win-b.on{background:var(--dmc);border-color:var(--dmc);color:#fff}
.dm-chart{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:var(--sp-12) var(--sp-12) var(--sp-8)}
.dm-svg{display:block;width:100%;height:116px}
.dm-bar{fill:var(--dmc);opacity:.88}
.dm-axis{display:flex;justify-content:space-between;font-size:10.5px;color:var(--faint);margin-top:var(--sp-6)}
.dm-peak{font-weight:650;color:var(--muted)}
.dm-b-list{display:flex;flex-direction:column}
.dm-b-row{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);padding:var(--sp-x9) var(--sp-4);border-bottom:1px solid var(--line)}
.dm-b-row:last-child{border-bottom:0}
.dm-b-n{font-size:13.5px;color:var(--text);font-weight:550;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dm-b-m{display:flex;align-items:center;gap:var(--sp-8);flex:none;color:var(--muted);font-size:12.5px}
.dm-b-c{font-weight:700;color:var(--text)}
.dm-b-by,.dm-b-at{color:var(--faint)}

/* ---- feedback hub developer cleanup bar (v224) ---- */
.fb-devbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);margin:var(--sp-2) 0 var(--sp-12);padding:var(--sp-8) var(--sp-10);border:1px dashed color-mix(in srgb,#c0392b 40%,var(--line));border-radius:11px;background:color-mix(in srgb,#c0392b 6%,var(--surface))}
.fb-devbar-t{display:inline-flex;align-items:center;gap:var(--sp-x5);font-size:11px;font-weight:650;color:var(--muted);text-transform:uppercase;letter-spacing:.4px}
.fb-clean{color:#c0392b;border-color:color-mix(in srgb,#c0392b 35%,var(--line));gap:var(--sp-6)}
.fb-clean:hover{background:color-mix(in srgb,#c0392b 12%,var(--surface));border-color:#c0392b}
.fb-clean-n{font-weight:750;background:#c0392b;color:#fff;border-radius:20px;padding:0 var(--sp-x7);font-size:11.5px;min-width:18px;text-align:center}

/* ---- member picker name search: task assignee / event invite / group members (v226) ---- */
.mp-search{display:flex;align-items:center;gap:var(--sp-x7);padding:var(--sp-x7) var(--sp-10);margin-bottom:var(--sp-8);border:1px solid var(--line);border-radius:10px;background:var(--surface-2);color:var(--faint)}
.mp-search-in{flex:1;border:0;background:transparent;font-size:14px;color:var(--text);outline:none;min-width:0}

/* ---- contacts label picker: reuse an existing label instead of retyping (v227) ---- */
.lbl-pick{margin:var(--sp-8) 0 var(--sp-4)}
.lbl-pick-h{display:flex;align-items:center;gap:var(--sp-x5);font-size:11.5px;color:var(--faint);margin-bottom:var(--sp-6)}
.lbl-pick-row{display:flex;flex-wrap:wrap;gap:var(--sp-6)}
.lbl-pick-row .lbl-chip{cursor:pointer;border:1.5px solid transparent}
.lbl-pick-row .lbl-chip:hover{filter:brightness(.97);transform:translateY(-1px)}

/* ---- add a contact from another workspace by WhatsApp number (v229, #362) ---- */
.wa-add{margin:var(--sp-x18) var(--sp-12) var(--sp-8);padding:var(--sp-14);border:1px solid var(--line);border-radius:14px;background:color-mix(in srgb,#25d366 6%,var(--surface))}
.wa-add-h{display:flex;align-items:center;gap:var(--sp-8);font-size:14px;font-weight:700;color:var(--text)}
.wa-add-h svg{color:#1faa52}
.wa-add-sub{font-size:12.5px;color:var(--muted);margin:var(--sp-x5) 0 var(--sp-x11);line-height:1.45}
.wa-find{display:flex;gap:var(--sp-8)}
.wa-find input{flex:1;min-width:0;border:1px solid var(--line);border-radius:10px;padding:var(--sp-10) var(--sp-12);font-size:14px;background:var(--surface);color:var(--text)}
.wa-find input:focus{outline:none;border-color:color-mix(in srgb,#25d366 55%,var(--line))}
.wa-find .btn{flex:none;gap:var(--sp-6)}
.wa-loading{padding:var(--sp-12) var(--sp-4);font-size:13px;color:var(--faint)}
.wa-empty{display:flex;align-items:flex-start;gap:var(--sp-8);padding:var(--sp-12) var(--sp-4) var(--sp-2);font-size:12.5px;color:var(--muted);line-height:1.45}
.wa-empty svg{flex:none;color:var(--faint);margin-top:var(--sp-1)}
.wa-hit{margin-top:var(--sp-10);padding:var(--sp-8);border:1px solid var(--line);border-radius:11px;background:var(--surface)}

/* ---- "hide my Bazi" reciprocal toggle on your own Bazi card (v230, #371) ---- */
.bz-privacy{margin-top:var(--sp-12);padding-top:var(--sp-12);border-top:1px solid var(--line)}
.bz-hide{display:flex;align-items:center;gap:var(--sp-12);width:100%;text-align:left;background:transparent;border:0;padding:var(--sp-2) 0;cursor:pointer;color:inherit}
.bz-hide[disabled]{cursor:default;opacity:.6}
.bz-hide-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-2)}
.bz-hide-txt b{font-size:13.5px;font-weight:650;color:var(--text)}
.bz-hide-sub{font-size:11.5px;color:var(--muted);line-height:1.4}
.bz-hide-lock{display:flex;align-items:center;gap:var(--sp-x5);margin:var(--sp-x9) 0 0;font-size:11.5px;color:var(--faint)}
.bz-hide-lock svg{flex:none}
.dev-row-val{flex:0 0 auto;font-size:12.5px;font-weight:600;color:var(--text);text-align:right;min-width:64px}
.dev-row-sub{display:block;font-size:10.5px;font-weight:500;color:var(--faint)}
/* developer directory — all users & all workspaces (read-only browser) */
.dev-dir-search{margin:var(--sp-2) 0 var(--sp-12)}
.dev-dir-count{font-size:12px;color:var(--faint);margin:var(--sp-2) 0 var(--sp-10)}
.dev-dir-list{display:flex;flex-direction:column;gap:var(--sp-6)}
.dev-dir-row{width:100%;text-align:left}
.dev-dir-row .lr-title{display:flex;align-items:center;gap:var(--sp-x7);flex-wrap:wrap}
.dev-tag{display:inline-flex;align-items:center;font-size:10.5px;font-weight:700;padding:var(--sp-1) var(--sp-x7);border-radius:20px;letter-spacing:.2px}
.dev-tag-dev{background:#ececfb;color:#4553b3}
.dev-tag-aff{background:#fce7f0;color:#b83280}
.dev-tag-lock{background:#fdeaea;color:#c0392b}
.dev-tag-paid{background:#e9f7ef;color:#177245}
/* Butir 51 — kembar .att-pend-tag; 4,17:1 -> 5,07:1. */
.dev-tag-trial{background:#fdf0e2;color:#94590f}
.dev-role{display:inline-flex;align-items:center;font-size:10.5px;font-weight:700;padding:var(--sp-1) var(--sp-x7);border-radius:20px;background:var(--surface-2);color:var(--muted)}
.dev-role-owner{background:#fdf0e2;color:#a4661a}
.dev-role-admin{background:#e7f1fd;color:#0a5cbf}
.dev-prof{display:flex;align-items:center;gap:var(--sp-14);margin:var(--sp-2) 0 var(--sp-16)}
.dev-prof-b{min-width:0}
.dev-prof-b h1{margin:0;font-size:22px;line-height:1.15;overflow-wrap:anywhere}
.dev-prof-flags{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-top:var(--sp-6)}
.dev-kv{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--surface)}
.dev-kv-row{display:flex;align-items:flex-start;gap:var(--sp-12);padding:var(--sp-10) var(--sp-x13);border-bottom:1px solid var(--line)}
.dev-kv-row:last-child{border-bottom:0}
.dev-kv-k{flex:0 0 38%;font-size:12.5px;color:var(--muted)}
.dev-kv-v{flex:1;min-width:0;font-size:13px;color:var(--text);text-align:right;overflow-wrap:anywhere}
.dev-ok{color:#177245;font-weight:600}
.dev-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;background:var(--surface-2);padding:var(--sp-1) var(--sp-x7);border-radius:6px;letter-spacing:1px}
.dev-act{margin-top:var(--sp-4)}
.dev-act .btn{width:100%;justify-content:center}
:root[data-theme="dark"] .dev-tag-dev{background:rgba(108,92,240,.22);color:#c4bcf5}
:root[data-theme="dark"] .dev-tag-aff{background:rgba(184,50,128,.22);color:#f0aed0}
:root[data-theme="dark"] .dev-tag-lock{background:rgba(192,57,43,.22);color:#f0a89f}
:root[data-theme="dark"] .dev-tag-paid{background:rgba(23,114,69,.26);color:#9fdcb8}
:root[data-theme="dark"] .dev-tag-trial{background:rgba(164,102,26,.26);color:#e6c193}
:root[data-theme="dark"] .dev-role-owner{background:rgba(164,102,26,.26);color:#e6c193}
:root[data-theme="dark"] .dev-role-admin{background:rgba(10,92,191,.26);color:#a9cdf5}

/* ---- Invite via WhatsApp ---- */
/* BUTIR 199 (v351) — ambang tingkat gratis, tepat di atas baris kode undangan.
   Ia MEMBERI TAHU, bukan memperingatkan: warnanya aksen lembut, bukan merah. Yang terjadi
   bukan kesalahan — orang menambah rekan kerja, dan itu memang yang kita inginkan; yang
   tidak boleh terjadi cuma ia mengetahuinya dari tagihan. Nadanya harus setenang itu. */
.ws-ambang{display:flex;gap:var(--sp-x9);align-items:flex-start;margin:var(--sp-10) 0 var(--sp-2);padding:var(--sp-x11) var(--sp-x13);
  border-radius:14px;background:var(--accent-weak);border:1px solid var(--line);
  font-size:13px;line-height:1.55;color:var(--text)}
.ws-ambang svg{flex:none;margin-top:var(--sp-1);color:var(--accent-text)}
.ws-ambang span{min-width:0}

.ws-invite-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-10);margin:var(--sp-6) 0 var(--sp-4)}
.ws-invite{white-space:nowrap}
/* Reset invite code — secondary security control under the invite row */
.ws-code-reset{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-8) var(--sp-12);margin:var(--sp-2) 0 var(--sp-6)}
.ws-reset-code{white-space:nowrap}
.ws-code-hint{font-size:12.5px;color:var(--muted);line-height:1.5;flex:1 1 200px;min-width:0}

/* ---- Media gallery ---- */
.media-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-4);padding:var(--sp-4)}
.media-cell{position:relative;aspect-ratio:1/1;padding:0;border:0;border-radius:8px;overflow:hidden;cursor:pointer;background:var(--line)}
.media-cell img{width:100%;height:100%;object-fit:cover;display:block}
.media-files{padding:var(--sp-6) var(--sp-4) var(--sp-4)}
.media-files .section-label{padding:var(--sp-8) var(--sp-8) var(--sp-6)}
.media-file{display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-10) var(--sp-10);border-radius:10px;text-decoration:none;color:var(--text)}
.media-file:hover{background:var(--hover,rgba(127,127,127,.08))}
.media-file .mf-b{display:flex;flex-direction:column;min-width:0}
.media-file .mf-name{font-size:14px;font-weight:550;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.media-file .mf-sub{font-size:11.5px;color:var(--muted)}

/* ---- Tap header / member → user info ---- */
.chat-head-main{display:flex;align-items:center;gap:var(--sp-10);min-width:0;flex:1;background:none;border:0;padding:0;margin:0;cursor:pointer;text-align:left;font:inherit;color:inherit}
.chat-head-main:hover .ct-name{text-decoration:underline}
.co-member-main{display:flex;align-items:center;gap:var(--sp-10);min-width:0;flex:1;background:none;border:0;padding:0;margin:0;cursor:pointer;text-align:left;font:inherit;color:inherit}
.co-member-main:hover{opacity:.85}
/* Profil orang. Satu tampilan, dua bingkai: overlay di chat, dan kolom di Contacts.
   Gayanya SENGAJA tidak lagi diikat ke .ui-modal — dulu begitu, dan itu sebabnya markah
   yang sama tampil tanpa gaya sama sekali begitu dipakai di luar modal. Yang berbeda
   antara kedua bingkai hanya jaraknya, di blok .detail-person di bawah. */
.ui-hero{display:flex;flex-direction:column;align-items:center;gap:var(--sp-8);padding:var(--sp-x18) var(--sp-16) var(--sp-6)}
.ui-hero .ui-name{font-size:19px;font-weight:750;color:var(--text)}
.ui-hero .ui-role{font-size:13px;color:var(--muted)}
.ui-hero .ui-actions{display:flex;gap:var(--sp-10);margin-top:var(--sp-12);flex-wrap:wrap;justify-content:center}
.ui-rows{padding:var(--sp-6) var(--sp-16) var(--sp-16)}

/* Bingkai kolom: 22px supaya sejajar dengan .detail-pad di layar detail lain. */
.detail-person{padding-bottom:var(--sp-8)}
.detail-person .ui-hero{padding:var(--sp-x22) var(--sp-x22) var(--sp-6)}
.detail-person .ui-rows{padding:var(--sp-6) var(--sp-x22) 0}
.detail-person .bz-card{margin:var(--sp-16) var(--sp-x22) 0}
.detail-person .dp-foot{padding:var(--sp-x18) var(--sp-x22) var(--sp-x22)}
.detail-person .dp-foot .btn{width:100%;justify-content:center}

/* ---- Company bio / about ---- */
.co-rename-stack{display:flex;flex-direction:column;align-items:stretch;gap:0}
.co-rename-stack textarea{width:100%;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--text);font:inherit;font-size:14px;line-height:1.45;resize:vertical}
.co-rename-stack .btn{align-self:flex-start}
.ws-bio{margin:var(--sp-2) 0 var(--sp-6);padding:var(--sp-12) var(--sp-14);background:var(--surface-2,var(--surface));border:1px solid var(--line);border-radius:10px;font-size:14px;line-height:1.5;color:var(--text);white-space:pre-wrap}
.brand .brand-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.brand svg{opacity:.5;flex:none;margin-left:var(--sp-1)}

/* ---- Attendance ---- */
.att-home{padding:var(--sp-x18) var(--sp-16)}
.att-clock{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-8);width:100%;padding:var(--sp-x26) var(--sp-16);border:0;border-radius:18px;background:linear-gradient(135deg,var(--accent),color-mix(in srgb,var(--accent) 62%,#f5a623));color:#fff;font-size:18px;font-weight:750;cursor:pointer;box-shadow:0 10px 30px rgba(201,58,34,.28)}
.att-clock.out{background:linear-gradient(135deg,#e8811a,#c0392b);box-shadow:0 10px 30px rgba(192,57,43,.28)}
.att-clock:active{transform:scale(.99)}
/* #399 — Garis waktu "hari ini" + penjelas yang bisa dibuka.
   Tiga langkahnya diberi warna berbeda supaya hubungannya (masuk → kunjungan → pulang) terbaca
   sekali lihat; garis vertikal tipis mengikatnya jadi satu urutan, bukan tiga kotak terpisah. */
.att-clock-sub{font-size:12px;font-weight:500;line-height:1.35;opacity:.92;max-width:280px;text-align:center}
.att-day-strip{margin:var(--sp-14) 0;background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:var(--sp-12) var(--sp-14)}
.att-day-h{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--faint);margin-bottom:var(--sp-8)}
.att-step{position:relative;display:flex;align-items:center;gap:var(--sp-10);padding:var(--sp-x7) 0 var(--sp-x7) var(--sp-2);flex-wrap:wrap}
.att-step+.att-step::before{content:'';position:absolute;left:9px;top:-7px;height:14px;width:2px;background:var(--line)}
.att-step>svg{flex:none}
.att-step-l{font-size:13.5px;color:var(--muted)}
.att-step-v{margin-left:auto;font-size:15px;font-weight:750;color:var(--text)}
.att-step-hint{flex-basis:100%;padding-left:var(--sp-x26);font-size:11.5px;color:var(--faint);line-height:1.35}
.att-step-in>svg{color:var(--good)}
.att-step-visit>svg{color:var(--accent-text)}
.att-step-out>svg{color:var(--due-today)}
.att-step-open>svg{color:var(--faint)}
.att-step-open .att-step-v{color:var(--faint)}
.att-day-empty{display:flex;align-items:flex-start;gap:var(--sp-10);font-size:14px;color:var(--text)}
.att-day-empty>svg{flex:none;color:var(--accent-text);margin-top:var(--sp-1)}
.att-day-empty .att-day-hint{display:block;font-size:12.5px;color:var(--muted);margin-top:var(--sp-x3);font-weight:400}
.att-how{margin:0 0 var(--sp-14)}
.att-how-h{display:flex;align-items:center;gap:var(--sp-8);width:100%;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:12px;
  background:var(--surface);color:var(--muted);font-size:13px;font-weight:600;cursor:pointer;text-align:left}
.att-how-h>svg:first-child{flex:none;color:var(--accent-text)}
.att-how-h>svg:last-child{margin-left:auto;flex:none;transition:transform .18s}
.att-how.open .att-how-h>svg:last-child{transform:rotate(180deg)}
.att-how-b{list-style:none;margin:var(--sp-8) 0 0;padding:0 var(--sp-2)}
.att-how-b li{display:flex;align-items:flex-start;gap:var(--sp-x9);padding:var(--sp-x5) 0;font-size:13px;color:var(--muted);line-height:1.45}
.att-how-b li>svg{flex:none;margin-top:var(--sp-2);color:var(--accent-text)}
.att-today{display:flex;gap:var(--sp-10);margin:var(--sp-14) 0}
.att-cell{flex:1;background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:var(--sp-12) var(--sp-14);text-align:center}
.att-cell-l{display:block;font-size:12px;color:var(--muted)}
.att-cell-v{display:block;font-size:18px;font-weight:750;color:var(--text);margin-top:var(--sp-x3)}
.att-office{display:flex;align-items:center;gap:var(--sp-x7);font-size:12.5px;color:var(--muted);padding:var(--sp-8) var(--sp-2)}
.att-office svg{flex:none;opacity:.7}
.att-office-off{color:var(--faint)}
/* BUTIR 131 — penanda petanya BERWARNA, teksnya tetap sepi. Baris ini satu-satunya ikon
   abu-abu di layar Absensi; semua tetangganya (laporan hijau, cuti oranye, analitik ungu)
   berwarna, dan aturan kita menyebut semua ikon wajib berwarna. Yang diwarnai cuma ikonnya:
   kalimatnya memang keterangan sepi, dan mewarnai keduanya akan menaikkan derajat sebuah
   catatan kaki menjadi pengumuman.
   Opacity dikembalikan ke 1 hanya di sini — warna yang diredam 70% bukan warna yang dipilih,
   ia warna yang kira-kira. Biru lembut yang sama dengan .at-photo dan .lpm-a. */
.att-office-off>svg{color:#2f7ae5;opacity:1}
:root[data-theme="dark"] .att-office-off>svg{color:#7db1f5}
.att-menu{margin-top:var(--sp-8)}
.att-consent{font-size:11.5px;color:var(--faint);line-height:1.5;margin-top:var(--sp-14)}
.att-report .att-rec{display:flex;flex-wrap:wrap;gap:var(--sp-12);align-items:center;padding:var(--sp-10) 0;border-bottom:1px solid var(--line)}
.att-thumb{width:46px;height:46px;flex:none;border-radius:9px;overflow:hidden;border:1px solid var(--line);padding:0;background:var(--line);cursor:pointer}
.att-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.att-thumb-none{display:flex;align-items:center;justify-content:center;color:var(--faint);cursor:default}
/* BUTIR 143 — kenapa ada `flex-basis` di sini, dan kenapa ia tidak boleh dihapus.
   Baris absen adalah `flex` berisi empat hal: foto (flex:none 46px), badan teks (ini),
   tombol hapus (flex:none), dan Tolak/Setujui (flex:none). Sebelum ini badan teks yang
   satu-satunya boleh menyusut, jadi di lebar ponsel ia diperas sampai ~30px: "05:00 PM"
   pecah jadi dua baris, "Diabsenkan oleh <nama>" jadi empat baris, catatan jadi SATU
   HURUF PER BARIS, dan pil "Menunggu persetujuan" — yang `inline-flex` dan tak bisa
   menyusut — menembus keluar badan lalu tercetak DI BAWAH tombol Tolak.
   Yang memutuskan gaji orang tidak boleh tertimpa tulisan lain.
   `flex:1 1 200px` menyatakan "aku butuh 200px"; begitu 200px tidak muat bersama
   tombol-tombolnya, barisnya MEMBUNGKUS (lihat flex-wrap di .att-rec) alih-alih
   memeras badan teks. `min-width:0` tetap ada supaya ellipsis di dalamnya tetap bekerja
   kalau bahkan satu baris penuh pun masih kurang. Tidak ada media query: aturannya satu
   untuk semua lebar — persis seperti rel dan kolom. */
.att-rec-b{min-width:0;flex:1 1 200px}
.att-rec-top{display:flex;align-items:center;gap:var(--sp-x7);flex-wrap:wrap;font-size:14px}
/* Waktu absen itu satu satuan — "05:00" dan "PM" tidak pernah dipisah baris. */
.att-rec-top>b{white-space:nowrap}
.att-rec-time{font-size:12px;color:var(--muted);margin-top:var(--sp-2)}
.att-badge{font-size:11px;font-weight:700;padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-badge.in{background:#17724522;color:#177245}
.att-badge.out{background:#c0392b22;color:#c0392b}
/* camera capture */
.att-cam-back{position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:1200}
.att-cam{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:1201;width:min(440px,calc(100vw - 24px));background:var(--surface);border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.4);display:flex;flex-direction:column;max-height:calc(100vh - 24px);max-height:calc(100dvh - 24px)}
/* #390 — kepala & bilah aksi dipaku, badan tengah yang menggulir. Tanpa ini, sheet yang
   lebih tinggi dari layar mendorong tombol "Absen" keluar layar tanpa bisa digulir. */
.att-cam-head{flex:none}
.att-proxy-for{flex:none}
/* BUTIR 195 — SELOKAN KOLOM INI DIMILIKI WADAHNYA, BUKAN TIAP ANAKNYA.
 *
 * Yang dipotret pemilik: lencana "Everyone in this workspace" tidak sejajar tepi kiri dengan
 * checkbox tepat di atasnya. Diukur: meleset 12px. Tapi menambal lencananya akan meninggalkan
 * sebabnya utuh, dan sebabnya sudah melahirkan cacat KEDUA yang belum dilaporkan siapa pun —
 * tiga tepi kiri berbeda di antara saudara yang seharusnya satu garis:
 *
 *     .att-note-in    margin kiri 12px
 *     .att-pub        margin kiri 12px
 *     .att-loc-warn   margin kiri  2px      <- meleset 10px, tak pernah dilaporkan
 *     .who-row        margin kiri  0px      <- yang dipotret pemilik
 *
 * Sebabnya satu: selokannya ditulis DI TIAP ANAK. Selama begitu, tiap anak harus mengingatnya,
 * dan .who-row TIDAK BISA mengingatnya — ia komponen bersama yang dipakai di lima layar dan
 * tidak pernah tahu wadah mana yang sedang memuatnya. Anak berikutnya akan lupa juga.
 *
 * Sekarang wadahnya yang memegangnya, sekali. Anak baru mendapatkannya tanpa tahu apa-apa. */
.att-cam-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;padding:0 var(--sp-12)}
/* Satu-satunya yang menembus selokan: gambarnya. Ia memang harus penuh dari tepi ke tepi —
   4:3-nya dihitung dari lebar DIALOG, dan menyempitkannya akan memotong wajah orang. */
.att-cam-scroll>.att-cam-wrap{margin-left:calc(-1 * var(--sp-12));margin-right:calc(-1 * var(--sp-12));width:calc(100% + var(--sp-12) * 2)}
.att-cam-wrap-shot{max-height:46vh}
.att-cam-head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-12);font-size:15px;font-weight:700;border-bottom:1px solid var(--line)}
/* Lebar SELALU penuh selebar modal. Dulu pakai `aspect-ratio:3/4; max-height:60vh` — saat
   tinggi terpotong max-height, browser MENYUSUTKAN lebarnya untuk menjaga rasio, menyisakan
   margin putih di kanan. Sekarang lebar dipatok 100% dan tinggi dihitung dari lebar modal
   (rasio 3:4), dicap max-height; video object-fit:cover mengisi penuh tanpa celah. */
.att-cam-wrap{position:relative;background:#000;width:100%;height:calc(min(440px, 100vw - 24px) * 4 / 3);max-height:62vh;display:flex;align-items:center;justify-content:center;overflow:hidden}
.att-cam-wrap video,.att-cam-shot{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transform:scaleX(-1)}
.att-cam-err{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-x9);text-align:center;padding:var(--sp-x22);color:#fff;font-size:14px;background:rgba(0,0,0,.58)}
/* #386 — kamera diblokir: ikon + judul + cara menyalakan lagi + tombol Coba lagi, ditumpuk rapi. */
.att-cam-err svg{filter:drop-shadow(0 1px 3px rgba(0,0,0,.4))}
.att-cam-err b{font-size:15px;font-weight:700;line-height:1.35;max-width:300px;color:#fff}
.att-cam-err-help{font-size:12.5px;font-weight:400;line-height:1.5;color:rgba(255,255,255,.85);max-width:320px}
.att-cam-retry{margin-top:var(--sp-x3);background:#fff;color:#111;border:0;box-shadow:0 2px 10px rgba(0,0,0,.28);display:inline-flex;align-items:center;gap:var(--sp-6)}
.att-cam-retry:hover{background:#f1f5f9}
/* #386 — lokasi mati: peringatan lembut di pratinjau selfie (absen tetap bisa dikirim). */
.att-loc-warn{display:flex;gap:var(--sp-10);align-items:flex-start;margin:var(--sp-12) 0 0;padding:var(--sp-x11) var(--sp-x13);border-radius:13px;background:rgba(245,158,11,.10);border:1px solid rgba(245,158,11,.34)}
.att-loc-warn svg{flex:none;margin-top:var(--sp-1)}
.att-loc-warn-t{display:flex;flex-direction:column;gap:var(--sp-x3);min-width:0}
.att-loc-warn-t b{font-size:13px;font-weight:600;color:var(--text);line-height:1.4}
.att-loc-warn-t span{font-size:12px;color:var(--muted);line-height:1.5}
.att-cam-actions{display:flex;gap:var(--sp-10);padding:var(--sp-12);flex:none;border-top:1px solid var(--line);background:var(--surface)}
.att-cam-actions .btn{flex:1;justify-content:center}
.att-cam-actions .att-shoot{flex:2}
.att-note-in{width:100%;margin:var(--sp-12) 0 0;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:10px;font-size:14px;outline:none;background:var(--surface);color:var(--text)}
.att-note-in:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.att-pub{display:flex;align-items:center;gap:var(--sp-x9);margin:var(--sp-10) 0 0;font-size:12.5px;color:var(--muted);cursor:pointer}
.att-pub input{width:16px;height:16px;flex:none;accent-color:var(--accent)}
/* report: visit note, shared/rejected tags, reject control */
.att-pub-tag{display:inline-flex;align-items:center;gap:var(--sp-x3);font-size:11px;font-weight:600;color:var(--accent-text);background:color-mix(in srgb,var(--accent) 12%,transparent);padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-priv-tag{display:inline-flex;align-items:center;gap:var(--sp-x3);font-size:11px;font-weight:600;color:var(--muted);background:var(--surface-2);padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-priv-tag svg{flex:none}
.att-rej-tag{font-size:11px;font-weight:700;color:#c0392b;background:#c0392b1a;padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-report .att-rec{align-items:flex-start}
.att-report .att-rec.rejected{opacity:.55}
.att-report .att-rec.rejected .att-thumb img{filter:grayscale(1)}
/* Catatan pengguna = kutipan, bukan status. Bentuknya sengaja dibuat TIDAK sekeluarga dengan
   .att-rec-loc / .att-rec-proxy di bawahnya, yang keduanya fakta sistem: garis kutip di kiri,
   miring, dan jaraknya lebih lega supaya ia terbaca sebagai blok tersendiri — bukan baris
   berikutnya dalam daftar fakta. Ikonnya balon-pesan (di JS), bukan ikon generik. */
.att-rec-note{display:flex;align-items:flex-start;gap:var(--sp-6);font-size:12.5px;color:var(--muted);
  font-style:italic;margin-top:var(--sp-6);padding-left:var(--sp-x7);border-left:2px solid var(--line)}
.att-rec-note>span{overflow-wrap:anywhere}
.att-rec-loc{display:flex;align-items:center;gap:var(--sp-x5);font-size:12.5px;color:var(--text);margin-top:var(--sp-x3)}
.att-rec-loc svg{flex:none;color:#0e766e}
.att-rec-loc span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* #383 — Baris lokasi & chip "Lokasi tidak biasa" yang bisa DIKETUK → pop-up peta. */
.att-rec-loc-btn{border:0;background:transparent;padding:0;font:inherit;color:var(--text);cursor:pointer;max-width:100%}
.att-rec-loc-btn:hover{color:var(--accent-text)}
.att-rec-loc-btn:hover span{text-decoration:underline}
.att-rec-loc-btn>svg:last-child{color:var(--faint);flex:none}
.att-odd-btn{border:0;cursor:pointer;font:inherit}
.att-odd-btn:hover{filter:brightness(.97)}
.att-odd-btn>svg:last-child{opacity:.7}
/* Pop-up peta satu titik (lokasi absen). Tinggi dibatasi dvh; peta mengisi bagian tengah. */
.mapc-back{position:fixed;inset:0;background:rgba(17,20,28,.5);z-index:1200;animation:cfFade .12s ease}
.mapc{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:1201;width:min(460px,calc(100vw - 24px));height:min(560px,calc(100dvh - 32px));display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.4);animation:cfPop .15s cubic-bezier(.2,.9,.3,1.15)}
.mapc-head{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-x11) var(--sp-8) var(--sp-x11) var(--sp-14);border-bottom:1px solid var(--line);font-weight:650;font-size:14.5px;flex:none}
.mapc-head>svg:first-child{flex:none;color:#0e766e}
.mapc-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mapc-map{flex:1;min-height:200px;background:var(--surface-2)}
.mapc-ext{flex:none;display:flex;align-items:center;justify-content:center;gap:var(--sp-6);padding:var(--sp-12);border-top:1px solid var(--line);color:var(--accent-text);font-weight:600;font-size:13.5px;text-decoration:none}
.mapc-ext:hover{background:var(--surface-2)}
.att-rec-note svg{flex:none;opacity:.7;margin-top:var(--sp-1)}
/* Titip absen — absenkan rekan memakai HP ini (HP rekan mati / ketinggalan). Aksen indigo lembut
   memisahkannya dari absen-sendiri (aksen merah). Selalu tercatat "diabsenkan oleh X". */
.att-proxy-btn{display:flex;align-items:center;gap:var(--sp-10);width:100%;margin-top:var(--sp-10);padding:var(--sp-12) var(--sp-14);border:1px solid color-mix(in srgb,#7c6cf0 26%,var(--line));border-radius:14px;background:color-mix(in srgb,#7c6cf0 10%,var(--surface));color:var(--text);font-size:14px;font-weight:650;cursor:pointer;transition:transform .12s ease,background .12s ease}
.att-proxy-btn:hover{background:color-mix(in srgb,#7c6cf0 15%,var(--surface))}
.att-proxy-btn:active{transform:scale(.99)}
.att-proxy-btn>svg{flex:none}
.att-proxy-btn>span{flex:1;text-align:left}
.att-proxy-btn>svg:last-child{opacity:.5}
.att-proxy{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:1201;width:min(440px,calc(100vw - 24px));max-height:min(80dvh,620px);display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.4)}
.att-proxy-hint{margin:var(--sp-12) var(--sp-16) 0;font-size:12.5px;line-height:1.45;color:var(--muted)}
.att-proxy-search{display:flex;align-items:center;gap:var(--sp-8);margin:var(--sp-12) var(--sp-16) var(--sp-4);padding:var(--sp-x9) var(--sp-12);border:1px solid var(--line);border-radius:10px;background:var(--surface)}
.att-proxy-search:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.att-proxy-search svg{flex:none;opacity:.6}
.att-proxy-search input{flex:1;border:0;outline:none;background:transparent;color:var(--text);font-size:14px;min-width:0}
.att-proxy-host{flex:1;overflow-y:auto;padding:var(--sp-4) var(--sp-8) var(--sp-10)}
.att-proxy-row{display:flex;align-items:center;gap:var(--sp-12);width:100%;padding:var(--sp-x9) var(--sp-8);border:0;border-radius:12px;background:transparent;cursor:pointer;text-align:left;transition:background .12s ease}
.att-proxy-row:hover{background:var(--surface-2)}
.att-proxy-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-1)}
.att-proxy-meta b{font-size:14.5px;font-weight:650;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.att-proxy-role{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.att-proxy-row>svg:last-child{flex:none;opacity:.4}
.att-proxy-empty{display:flex;flex-direction:column;align-items:center;gap:var(--sp-8);padding:var(--sp-x34) var(--sp-20);text-align:center;color:var(--muted);font-size:13.5px}
.att-proxy-empty svg{opacity:.55}
.att-proxy-for{display:flex;align-items:center;gap:var(--sp-10);margin:var(--sp-12);padding:var(--sp-10) var(--sp-12);border-radius:12px;background:color-mix(in srgb,#7c6cf0 13%,var(--surface));border:1px solid color-mix(in srgb,#7c6cf0 24%,var(--line))}
.att-proxy-for span{font-size:12.5px;line-height:1.4;color:var(--text)}
/* TUGAS-169 — spanduk akibat check-in ulang, di dalam overlay kamera.
 * Bentuknya sengaja SAMA PERSIS dengan .att-proxy-for (slot, jarak, radius, tebal garis):
 * keduanya menempati posisi yang sama dan tidak pernah tampil bersamaan, jadi dua bentuk
 * berbeda di satu tempat cuma akan terbaca sebagai dua komponen yang berbeda maknanya.
 * Yang membedakan HANYA warnanya — langit, bukan ungu (titip absen) dan bukan amber
 * (.att-loc-warn = ada yang tidak beres). Ini pemberitahuan, bukan galat: warnanya jangan
 * meminjam warna galat, kalau tidak orang belajar mengabaikan amber. */
.att-again-warn{display:flex;align-items:center;gap:var(--sp-10);margin:var(--sp-12);padding:var(--sp-10) var(--sp-12);border-radius:12px;background:color-mix(in srgb,#38bdf8 13%,var(--surface));border:1px solid color-mix(in srgb,#38bdf8 26%,var(--line));flex:none}
.att-again-warn svg{flex:none;color:#0284c7}
.att-again-warn span{font-size:12.5px;line-height:1.4;color:var(--text)}
.att-proxy-for b{font-weight:700}
.att-rec-proxy{display:flex;align-items:center;gap:var(--sp-x5);font-size:12.5px;color:var(--text);margin-top:var(--sp-x3);font-weight:600}
/* Butir 27 — jejak keputusan titip absen. Sengaja LEBIH REDUP dari .att-rec-proxy: yang di atas
   adalah fakta tentang absennya, yang ini catatan administratif tentang siapa memprosesnya. */
.att-rec-decided{display:flex;align-items:flex-start;gap:var(--sp-x5);font-size:12px;color:var(--muted);margin-top:var(--sp-x3);line-height:1.4}
.att-rec-decided svg{flex:none;margin-top:var(--sp-2);color:#177245}
/* Butir 21 — satu-satunya baris di layar ini yang menyatakan gerbangnya TIDAK dilewati. Ia
   memakai warna peringatan dan berdiri sendiri; menyembunyikannya di antara catatan lain
   sama saja dengan tidak menandainya. */
.att-rec-selfdec{display:flex;align-items:flex-start;gap:var(--sp-x5);font-size:12px;font-weight:600;color:#b45309;background:#f59e0b14;border:1px solid #f59e0b33;border-radius:9px;padding:var(--sp-x5) var(--sp-8);margin-top:var(--sp-4);line-height:1.4}
.att-rec-selfdec svg{flex:none;margin-top:var(--sp-1)}
:root[data-theme="dark"] .att-rec-selfdec{color:#fbbf24;background:rgba(245,158,11,.14);border-color:rgba(245,158,11,.34)}
:root[data-theme="dark"] .att-rec-decided svg{color:#6ee7b7}
.att-rec-proxy svg{flex:none;color:#7c6cf0}
.aw-proxy{display:flex;align-items:center;gap:var(--sp-4);font-size:11.5px;color:#7c6cf0;font-weight:600;margin-top:var(--sp-2)}
.aw-proxy svg{flex:none}
.att-reject{align-self:center;flex:none;font-size:12px;font-weight:600;color:var(--danger);background:none;border:1px solid var(--line);border-radius:8px;padding:var(--sp-x5) var(--sp-10);cursor:pointer}
/* #387 — hapus absen sendiri (ikon tong sampah, nada bahaya) */
.att-del{align-self:center;flex:none;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;background:none;border:1px solid var(--line);border-radius:8px;cursor:pointer}
.att-del:hover{border-color:var(--danger);background:rgba(220,60,60,.07)}
.att-del:active{transform:scale(.94)}
.att-reject:hover{background:#c0392b12;border-color:#c0392b55}
.att-reject.undo{color:var(--accent-text)}
/* Titip absen menunggu persetujuan pengurus (anti-kecurangan) */
/* Butir 51 — #a4661a di atas #fdf0e2 terukur 4,17:1, meleset tipis dan justru itu jenis yang
   paling lama bertahan: cukup dekat sehingga tak pernah terasa salah saat dilihat. 5,07:1. */
.att-pend-tag{display:inline-flex;align-items:center;gap:var(--sp-x3);font-size:11px;font-weight:700;color:#94590f;background:#fdf0e2;padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-pend-tag svg{flex:none}
/* BUTIR 143 — sebuah pil tidak boleh ditembus tulisannya sendiri. `inline-flex` tanpa
   batas lebar membuat isinya menentukan lebar pil; kalau induknya lebih sempit, tulisan
   keluar dari latar pilnya dan mendarat di atas apa pun yang ada di sebelahnya. Dengan
   ini ia membungkus DI DALAM pil (pilnya yang meninggi), bukan keluar dari pil. */
.att-pend-tag,.att-rej-tag{max-width:100%;min-width:0;white-space:normal;overflow-wrap:anywhere}
.att-rec-pending{background:color-mix(in srgb,#e8811a 7%,var(--surface));border-radius:12px}
/* Saat baris membungkus, gugus tombol turun ke barisnya sendiri dan merapat ke kanan —
   bukan menempel di bawah foto, yang membuatnya terbaca seperti milik foto itu. */
.att-approve-row{align-self:center;flex:none;display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-left:auto}
.att-approve{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:12px;font-weight:700;color:#fff;background:linear-gradient(135deg,#15803d,#177245);border:0;border-radius:8px;padding:var(--sp-6) var(--sp-12);cursor:pointer}
.att-approve:active{transform:scale(.97)}
.att-approve svg{flex:none}
:root[data-theme="dark"] .att-pend-tag{background:rgba(164,102,26,.26);color:#e6c193}
/* report: one collapsible summary row per person per day */
.att-group{border-bottom:1px solid var(--line)}
.att-day{display:flex;align-items:center;gap:var(--sp-12);width:calc(100% + 16px);margin:0 -8px;padding:var(--sp-x11) var(--sp-8);border:none;border-radius:10px;background:none;color:var(--text);text-align:left;cursor:pointer}
.att-day:hover{background:var(--surface-2)}
.att-day-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-x3)}
.att-day-top{display:flex;align-items:baseline;gap:var(--sp-8);min-width:0}
.att-day-top b{font-size:14.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.att-day-date{flex:none;font-size:11.5px;color:var(--muted)}
/* #382 — header tanggal yang bisa dilipat di laporan absensi */
.att-datesec{margin:0 0 var(--sp-4)}
.att-datehead{display:flex;align-items:center;gap:var(--sp-8);width:100%;background:none;border:0;cursor:pointer;color:var(--text);text-align:left;padding:var(--sp-12) var(--sp-2) var(--sp-x7)}
.att-datehead:active{transform:scale(.994)}
.att-datehead-chev{display:inline-flex;flex:none;color:var(--muted)}
.att-datehead-d{font-size:12.5px;font-weight:700;letter-spacing:.02em}
/* Butir 20 — hari yang dikelompokkan dengan zona waktu yang DIASUMSIKAN. Sengaja kecil dan
   netral, bukan merah: ini bukan kesalahan, ini keterbatasan data lama yang akan habis sendiri. */
.att-tz-assumed{flex:none;font-size:10px;font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:var(--muted);background:rgba(130,140,160,.14);padding:var(--sp-2) var(--sp-x7);border-radius:20px;margin-left:var(--sp-6)}
.att-datehead-n{margin-left:auto;flex:none;font-size:11px;font-weight:700;color:var(--muted);background:rgba(130,140,160,.14);min-width:20px;text-align:center;padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-datesec-body{display:flex;flex-direction:column;gap:var(--sp-8)}
/* BUTIR PAPAN 79 — pengelompokan per orang.
   Header seksinya membawa avatar; namanya boleh lebih panjang dari sebuah tanggal, jadi ia
   dipotong dengan ellipsis alih-alih mendorong angka hitungannya keluar baris. */
.att-datehead-ava{flex:none}
.att-persec .att-datehead-d{font-size:13.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Kartu di dalam kelompok per orang menyebut TANGGAL, bukan orang — jadi tempat avatarnya
   diisi ikon kalender. Ukurannya disamakan dengan avatar 36px supaya kedua pengelompokan
   punya garis kiri yang sama; kalau tidak, berpindah pengelompokan terasa seperti berpindah
   layar, padahal isinya sama. */
.att-day-cal{flex:none;width:36px;height:36px;border-radius:11px;display:flex;align-items:center;justify-content:center;
  background:var(--accent-weak);color:var(--accent-text)}
.att-day-times{display:flex;align-items:center;gap:var(--sp-6);flex-wrap:wrap}
.att-t{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
.att-t-open{color:var(--muted);font-weight:600}
.att-t-arr{display:inline-flex;color:var(--faint)}
.att-t-arr svg{display:block}
/* Butir 43 — penanda sesi yang berakhir keesokan harinya. Kecil dan tenang: ia
   keterangan pada sebuah jam, bukan peringatan. */
.att-t-nx{font-size:9.5px;font-weight:800;vertical-align:super;margin-left:var(--sp-2);color:var(--muted)}
.att-t-dur{font-size:11px;font-weight:700;color:#177245;background:#17724518;padding:var(--sp-2) var(--sp-8);border-radius:20px}
/* Butir 25 — penanda "sementara". Amber, satu keluarga dengan .att-odd: keduanya berkata
   "perhatikan", bukan "salah". Hijau (.att-t-dur) berarti angka final, jadi sengaja dibedakan. */
.att-t-prov{font-size:11px;font-weight:700;color:#b45309;background:#f59e0b22;padding:var(--sp-2) var(--sp-8);border-radius:20px;white-space:nowrap}
.att-t-none{font-size:12.5px;color:var(--faint)}
.att-day-n{flex:none;min-width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;padding:0 var(--sp-6);font-size:11px;font-weight:700;color:var(--accent-text);background:var(--accent-weak);border-radius:20px}
.att-day-chev{flex:none;display:inline-flex;color:var(--faint);transition:transform .2s cubic-bezier(.2,.7,.3,1)}
.att-group.open .att-day-chev{transform:rotate(180deg)}
.att-day-recs{margin:0 0 var(--sp-10) var(--sp-x17);padding-left:var(--sp-x17);border-left:2px solid var(--accent-weak);animation:attOpen .18s ease}
.att-day-recs .att-thumb{width:40px;height:40px}
.att-day-recs .att-rec:last-child{border-bottom:none}
@keyframes attOpen{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}

/* ---- date of birth: day / month / year ---- */
.bd-row{display:flex;gap:var(--sp-8)}
.bd-sel{flex:1;min-width:0;padding:var(--sp-x11) var(--sp-x30) var(--sp-x11) var(--sp-10);border:1px solid var(--line);border-radius:10px;background-color:var(--surface);color:var(--text);font-size:14px;font-family:inherit;outline:none}
.bd-sel:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
/* Day and year are fixed and narrow so the month keeps enough room for "September". */
.bd-day{flex:0 0 74px}
.bd-year{flex:0 0 86px}

/* ---- Bazi: five elements, five colours ---- */
.el-0{color:#177245}                 /* 木 Kayu  — wood   */
.el-1{color:#c0392b}                 /* 火 Api   — fire   */
.el-2{color:#b45309}                 /* 土 Tanah — earth  */
.el-3{color:#64748b}                 /* 金 Logam — metal  */
.el-4{color:#2563eb}                 /* 水 Air   — water  */
.bz-card{margin:var(--sp-14) var(--sp-16) var(--sp-16);padding:var(--sp-14);border:1px solid var(--line);border-radius:14px;background:var(--surface-2)}
.bz-empty{display:flex;align-items:center;gap:var(--sp-x9);font-size:13px;color:var(--muted)}
.bz-empty svg{flex:none;color:#a855f7}
.bz-head{display:flex;align-items:center;gap:var(--sp-8);font-size:13px;font-weight:700;margin-bottom:var(--sp-12)}
.bz-head>svg{flex:none;color:#a855f7}
.bz-animal{margin-left:auto;font-size:11px;font-weight:700;color:#a855f7;background:#a855f71a;padding:var(--sp-x3) var(--sp-10);border-radius:20px}
.bz-cols{display:flex;gap:var(--sp-8)}
.bz-col{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);padding:var(--sp-10) var(--sp-4);border-radius:12px;background:var(--surface)}
.bz-lbl{font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:var(--sp-4)}
.bz-ch{font-size:26px;line-height:1.15;font-weight:600}
.bz-py{margin-top:var(--sp-x5);font-size:9.5px;line-height:1.45;color:var(--faint);text-align:center}
.bz-dm{margin-top:var(--sp-12);font-size:12.5px;color:var(--muted)}
.bz-dm b{font-size:16px;vertical-align:-1px;margin:0 var(--sp-2)}
.bz-els{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-top:var(--sp-10)}
.bz-el{display:inline-flex;align-items:center;gap:var(--sp-x5);font-size:11px;font-weight:600;padding:var(--sp-x3) var(--sp-x9);border-radius:20px}
.bz-el.el-0{background:#1772451c}
.bz-el.el-1{background:#c0392b1c}
.bz-el.el-2{background:#b453091c}
.bz-el.el-3{background:#64748b1c}
.bz-el.el-4{background:#2563eb1c}
.bz-el>b{font-weight:800}
.bz-el.none{opacity:.4}
/* ---- watak Day Master: kekuatan, kelemahan, sifat ---- */
.bz-nat{margin-top:var(--sp-14);padding:var(--sp-x13) var(--sp-14);border-radius:13px;background:var(--surface-2);border:1px solid var(--line)}
.bz-nat-h{display:flex;align-items:baseline;gap:var(--sp-8);flex-wrap:wrap;margin-bottom:var(--sp-x9)}
.bz-nat-h b{font-size:15px;font-weight:700;color:var(--text)}
.bz-nat-h span{font-size:11.5px;color:var(--faint);text-transform:lowercase}
.bz-nat-s{display:flex;align-items:flex-start;gap:var(--sp-8);font-size:13px;line-height:1.5;color:var(--text);margin-bottom:var(--sp-8)}
.bz-nat-ic{flex:none;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:6px;margin-top:var(--sp-1)}
.bz-up{background:color-mix(in srgb,var(--good) 15%,transparent);color:var(--good)}
.bz-dn{background:color-mix(in srgb,var(--due-today) 18%,transparent);color:var(--due-today)}
.bz-traits{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-top:var(--sp-x11)}
.bz-trait{font-size:11.5px;font-weight:600;padding:var(--sp-x3) var(--sp-10);border-radius:20px;
  background:color-mix(in srgb,var(--good) 12%,transparent);color:var(--good);white-space:nowrap}
/* Sifat yang perlu diwaspadai memakai amber, BUKAN merah. Merah membacanya sebagai
   kesalahan; amber membacanya sebagai sesuatu yang perlu diperhatikan — dan itu memang
   maksudnya. Bentuknya sengaja sama supaya keduanya terbaca setara. */
.bz-trait-w{background:color-mix(in srgb,var(--due-today) 15%,transparent);color:var(--due-today)}
.bz-nat-f{margin-top:var(--sp-x11);padding-top:var(--sp-x9);border-top:1px solid var(--line);
  font-size:11px;line-height:1.5;color:var(--faint);font-style:italic}
.bz-note{display:flex;align-items:flex-start;gap:var(--sp-6);margin-top:var(--sp-10);font-size:11px;line-height:1.5;color:var(--faint)}
.bz-note svg{flex:none;margin-top:var(--sp-1)}
.bz-warn{color:#b45309}

/* ---- Bazi: the reading ---- */
.bz-an{margin-top:var(--sp-14);padding-top:var(--sp-12);border-top:1px solid var(--line)}
.bz-an-h{font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:var(--sp-6)}
.bz-an-row{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin-bottom:var(--sp-12)}
.bz-bar{height:7px;border-radius:20px;background:#64748b22;overflow:hidden;margin-top:var(--sp-2)}
.bz-bar-f{display:block;height:100%;border-radius:20px;background:linear-gradient(90deg,var(--accent),var(--due-today))}
.bz-bar-l{display:flex;justify-content:space-between;font-size:10.5px;color:var(--faint);margin-top:var(--sp-x5)}
.bz-an-t{font-size:12px;line-height:1.55;color:var(--muted);margin-top:var(--sp-x9)}
.bz-gods{display:flex;gap:var(--sp-6);margin-top:var(--sp-12)}
.bz-god{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);padding:var(--sp-8) var(--sp-4);border-radius:10px;background:var(--surface)}
.bz-god-p{font-size:9.5px;font-weight:700;color:var(--faint);text-transform:uppercase;letter-spacing:.03em}
.bz-god-c{font-size:15px;font-weight:600;color:var(--accent-text)}
.bz-god-n{font-size:9.5px;color:var(--muted);text-align:center;line-height:1.3}
.bz-disc{margin-top:var(--sp-12);padding-top:var(--sp-10);border-top:1px solid var(--line)}
/* #384 — Panduan: kartu unsur (warna/arah/musim) + catatan pengembangan diri */
.bz-guide{margin-top:var(--sp-14);padding-top:var(--sp-12);border-top:1px solid var(--line)}
.bz-guide-h{display:flex;align-items:center;gap:var(--sp-6);font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.bz-guide-h svg{color:#a855f7}
.bz-guide-sub{font-size:11.5px;line-height:1.5;color:var(--faint);margin:var(--sp-6) 0 var(--sp-x11)}
.bz-gels{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:var(--sp-8)}
.bz-gel{padding:var(--sp-x11) var(--sp-12);border-radius:12px;background:var(--surface);border:1px solid var(--line);border-left:3px solid currentColor}
.bz-gel-h{display:flex;align-items:center;gap:var(--sp-x7);margin-bottom:var(--sp-8)}
.bz-gel-dot{width:11px;height:11px;border-radius:50%;background:currentColor;flex:none}
.bz-gel-h b{font-size:13px;font-weight:700}
.bz-gel-rows{display:flex;flex-direction:column;gap:var(--sp-4);margin-bottom:var(--sp-x9)}
.bz-gel-row{display:flex;justify-content:space-between;gap:var(--sp-8);font-size:11.5px;line-height:1.4}
.bz-gel-k{color:var(--faint)}
.bz-gel-v{color:var(--text);font-weight:600;text-align:right}
.bz-gel-life{font-size:11.5px;line-height:1.5;color:var(--muted);padding-top:var(--sp-8);border-top:1px solid var(--line)}
.bz-grow{display:flex;align-items:flex-start;gap:var(--sp-x9);margin-top:var(--sp-x9);padding:var(--sp-12) var(--sp-x13);border-radius:12px;background:color-mix(in srgb,#a855f7 8%,var(--surface));border:1px solid color-mix(in srgb,#a855f7 22%,var(--line))}
.bz-grow-ic{flex:none;display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;background:#a855f71f;color:#a855f7}
.bz-grow-b{display:flex;flex-direction:column;gap:var(--sp-x3);min-width:0}
.bz-grow-h{font-size:10.5px;font-weight:700;color:#a855f7;text-transform:uppercase;letter-spacing:.03em}
.bz-grow-t{font-size:12.5px;line-height:1.55;color:var(--text)}

/* ---- Human Resources ---- */
.menu-ico.ic-hr{background:#fbeaf0}
/* developer: module switches + developer list */
.menu-ico.ic-devmodules{background:#e8e7fb}
.menu-ico.ic-devpeople{background:#fce7f0}
.menu-ico.ic-devusers{background:#e7f1fd}
.menu-ico.ic-devspaces{background:#ececfb}
.menu-ico.ic-devops{background:#e0f2fe}
.menu-ico.ic-devtree{background:#dcf5f0}
.menu-ico.ic-polls{background:#f2ecfd}
/* Navigation map (#103) — peta hierarki nav */
.nt-view .nt-stats{display:flex;gap:var(--sp-10);margin:var(--sp-6) 0 var(--sp-14)}
.nt-stat{flex:1;display:flex;flex-direction:column;gap:var(--sp-1);background:var(--surface-2);border:1px solid var(--line);border-radius:13px;padding:var(--sp-x11) var(--sp-x13)}
.nt-stat b{font-size:22px;font-weight:800;color:var(--text);line-height:1.1}
.nt-stat span{font-size:11.5px;font-weight:600;color:var(--muted)}
.nt-legend{display:flex;flex-wrap:wrap;gap:var(--sp-6);margin:0 0 var(--sp-16)}
.ntree{margin-top:var(--sp-2)}
.nt-node{position:relative}
.nt-row{display:flex;align-items:center;gap:var(--sp-x9);min-height:40px;flex-wrap:wrap}
.nt-ico{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:none}
.nt-label{font-weight:650;color:var(--text);font-size:13.5px}
.nt-tag{font-size:9.5px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;padding:var(--sp-2) var(--sp-x7);border-radius:999px;white-space:nowrap}
.nt-cond{display:inline-flex;align-items:center;font-size:11px;font-weight:600;color:var(--muted);background:var(--surface-2);border:1px solid var(--line);padding:var(--sp-1) var(--sp-8);border-radius:999px;white-space:nowrap}
.nt-here{display:inline-flex;align-items:center;gap:var(--sp-x3);font-size:10.5px;font-weight:800;color:#0e766e;background:#dcf5f0;padding:var(--sp-1) var(--sp-8) var(--sp-1) var(--sp-x5);border-radius:999px}
.nt-here svg{color:#0e766e}
.nt-note{font-size:11.5px;color:var(--muted);margin:-3px 0 var(--sp-x3) var(--sp-x37)}
.nt-sub{margin-left:var(--sp-14)}
.nt-sub>.nt-node{padding-left:var(--sp-x22)}
.nt-sub>.nt-node::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--line)}
.nt-sub>.nt-node:last-child::before{bottom:auto;height:20px}
.nt-sub>.nt-node::after{content:'';position:absolute;left:0;top:20px;width:15px;height:2px;background:var(--line);border-radius:2px}
.nt-here-node>.nt-row .nt-label{color:#0e766e}
.nt-tag.k-root{background:#eceff3;color:#475569}
.nt-tag.k-section{background:#ececfb;color:#4553b3}
.nt-tag.k-module{background:#e9f7ef;color:#177245}
.nt-tag.k-detail{background:#e7f1fd;color:#0a5cbf}
.nt-tag.k-form{background:#fdf0e2;color:#a4661a}
.nt-tag.k-screen{background:#f0ecfb;color:#5b3fb0}
.nt-tag.k-sub{background:#dcf5f0;color:#0e766e}
.dev-warn{display:flex;align-items:center;gap:var(--sp-8);margin:0 0 var(--sp-14);padding:var(--sp-10) var(--sp-12);border-radius:11px;font-size:12.5px;font-weight:600;color:#b45309;background:#f59e0b1c}
.dev-warn svg{flex:none}
.dev-mail{display:block;font-size:11.5px;font-weight:400;color:var(--faint);margin-top:var(--sp-1)}
.menu-ico.ic-employees{background:#fbeaf0}
.menu-ico.ic-leave{background:#fdf0e2}
.menu-ico.ic-cashadv{background:#eaf3de}
.menu-ico.ic-overtime{background:#dcf5f0}
/* Sub-modules that are listed but not built yet: visibly inert, never clickable. */
.hr-soon{opacity:.55;cursor:default}
.hr-soon .menu-ico{filter:grayscale(.5)}
.ws-tag-mem{background:var(--surface-2);color:var(--faint)}
.hr-soon-tag{flex:none;font-size:10px;font-weight:700;color:var(--muted);background:var(--surface-2);padding:var(--sp-x3) var(--sp-x9);border-radius:20px}
.m-mini-soon{opacity:.35;pointer-events:none}
.hr-h1{margin:0;padding:var(--sp-16) var(--sp-16) 0;font-size:20px}
.hr-back{display:inline-flex;align-items:center;gap:var(--sp-x5);margin-bottom:var(--sp-14);padding:var(--sp-6) var(--sp-x11) var(--sp-6) var(--sp-8);border:1px solid var(--line);border-radius:20px;background:none;color:var(--muted);font-size:12.5px;font-weight:600;font-family:inherit;cursor:pointer}
.hr-back:hover{background:var(--surface-2);color:var(--text)}
.hr-top{padding:var(--sp-14) var(--sp-16) var(--sp-10);border-bottom:1px solid var(--line)}
.hr-count{display:flex;align-items:baseline;gap:var(--sp-10);flex-wrap:wrap;font-size:13px;color:var(--muted);margin-bottom:var(--sp-10)}
.hr-count b{font-size:20px;font-weight:700;color:var(--text)}
.hr-missing{font-size:11px;font-weight:700;color:#b45309;background:#f59e0b1c;padding:var(--sp-x3) var(--sp-x9);border-radius:20px}
.hr-search{display:flex;align-items:center;gap:var(--sp-8);padding:var(--sp-x9) var(--sp-12);border:1px solid var(--line);border-radius:11px;background:var(--surface-2)}
.hr-search svg{flex:none;color:var(--faint)}
.hr-search input{flex:1;min-width:0;border:none;background:none;outline:none;font-size:14px;font-family:inherit;color:var(--text)}
.hr-row .lr-sub{display:flex;align-items:center;gap:var(--sp-6);flex-wrap:wrap}
.hr-tag{font-size:10px;font-weight:700;color:var(--muted);background:var(--surface-2);padding:var(--sp-2) var(--sp-x7);border-radius:20px}
.hr-tag.owner{color:var(--accent-text);background:color-mix(in srgb,var(--accent) 12%,transparent)}
.hr-shio{flex:none;display:flex;flex-direction:column;align-items:center;gap:var(--sp-1);min-width:44px}
.hr-shio b{font-size:19px;line-height:1.1;font-weight:600}
.hr-shio>span{font-size:9.5px;color:var(--faint)}
.hr-shio-none{justify-content:center;color:var(--faint);opacity:.5}
.hr-hero{display:flex;align-items:center;gap:var(--sp-14);margin-bottom:var(--sp-16)}
.hr-hero-b{min-width:0}
.hr-hero-b h1{margin:0;font-size:20px;line-height:1.25;overflow:hidden;text-overflow:ellipsis}
.hr-hero-role{font-size:13px;color:var(--muted);margin-top:var(--sp-2)}
.hr-acts{display:flex;gap:var(--sp-8);flex-wrap:wrap;margin-bottom:var(--sp-16)}
.hr-detail .bz-card{margin:var(--sp-16) 0 0}
/* attendance managers list + toggle */
.att-mgr-row{display:flex;align-items:center;gap:var(--sp-12);padding:var(--sp-10) 0;border-bottom:1px solid var(--line)}
.att-mgr-name{flex:1;min-width:0;font-size:14.5px;font-weight:600;color:var(--text)}
.att-mgr-tag{font-size:11px;font-weight:700;color:var(--accent-text);background:var(--accent-weak);padding:var(--sp-1) var(--sp-x7);border-radius:20px}
.att-mgr-owner{font-size:12px;color:var(--faint);font-weight:600}
.switch{width:42px;height:24px;flex:none;border-radius:20px;border:0;background:var(--line);position:relative;cursor:pointer;transition:background .18s}
.switch.on{background:var(--accent)}
.switch-dot{position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:transform .18s}
.switch.on .switch-dot{transform:translateX(18px)}
/* insights: hour histogram + top spots */
.att-card{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:var(--sp-14);margin-top:var(--sp-14)}
.att-card-h{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-8);font-size:13px;font-weight:700;color:var(--text);margin-bottom:var(--sp-12)}
.att-peak{font-size:11.5px;font-weight:600;color:var(--accent-text);background:var(--accent-weak);padding:var(--sp-2) var(--sp-8);border-radius:20px;white-space:nowrap}
/* Peta panas absensi (hari × jam). Kolom 1 = label hari, sisanya = jam. Dua ramp warna dipilih
   lewat kelas di KARTU: .hm-io = clock in/out (hangat/oranye), .hm-other = lainnya (teal). */
.hm{display:grid;gap:var(--sp-x3);align-items:center}
.hm-corner{min-width:26px}
.hm-hh{font-size:9px;color:var(--faint);text-align:center;line-height:1;overflow:hidden}
.hm-day{font-size:10px;font-weight:600;color:var(--muted);text-align:right;padding-right:var(--sp-6);white-space:nowrap}
.hm-c{height:24px;border-radius:3px;background:var(--surface-2);transition:transform .1s;border:none;padding:0}
button.hm-c{cursor:pointer}
.hm-c:hover{transform:scale(1.18)}
button.hm-c:active{transform:scale(.9)}
/* ramp hangat — clock in/out */
.hm-io .hm-1{background:#ffe6d0}
.hm-io .hm-2{background:#ffc396}
.hm-io .hm-3{background:#f7935a}
.hm-io .hm-4{background:#e05f2e}
.hm-io .hm-c.peak{box-shadow:inset 0 0 0 2px rgba(64,22,4,.82)}
:root[data-theme="dark"] .hm-io .hm-1{background:#4a2f1c}
:root[data-theme="dark"] .hm-io .hm-2{background:#7c4a24}
:root[data-theme="dark"] .hm-io .hm-3{background:#b3652c}
:root[data-theme="dark"] .hm-io .hm-4{background:#e0742f}
/* ramp teal — check-in lainnya */
.hm-other .hm-1{background:#d6edf0}
.hm-other .hm-2{background:#93d3da}
.hm-other .hm-3{background:#42a9b5}
.hm-other .hm-4{background:#1c7f8b}
.hm-other .hm-c.peak{box-shadow:inset 0 0 0 2px rgba(6,50,56,.82)}
:root[data-theme="dark"] .hm-other .hm-1{background:#173d42}
:root[data-theme="dark"] .hm-other .hm-2{background:#22636b}
:root[data-theme="dark"] .hm-other .hm-3{background:#2c93a0}
:root[data-theme="dark"] .hm-other .hm-4{background:#31c0d0}
.hm-leg{display:flex;align-items:center;justify-content:flex-end;gap:var(--sp-4);margin-top:var(--sp-x11);font-size:10.5px;color:var(--muted)}
.hm-leg .hm-c{width:15px;height:15px;flex:none}
/* sheet "siapa saja absen" (dibuka dari ketuk sel/lokasi) */
.aw-scroll{padding:var(--sp-2) 0}
.aw-row{display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-x9) var(--sp-16);border-top:1px solid var(--line)}
.aw-row:first-child{border-top:0}
.aw-b{flex:1;min-width:0}
.aw-n{font-size:14px;font-weight:600;color:var(--text)}
.aw-m{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.aw-kind{font-size:10.5px;font-weight:700;padding:var(--sp-2) var(--sp-8);border-radius:20px;white-space:nowrap;flex:none}
.awk-in{background:#e9f7ef;color:#177245}
.awk-out{background:#fdecea;color:#c0392b}
.awk-inout{background:#e7f1fd;color:#0a5cbf}
.awk-other{background:var(--surface-2);color:var(--muted)}
:root[data-theme="dark"] .awk-in{background:#12351f;color:#57c98a}
:root[data-theme="dark"] .awk-out{background:#3d1a14;color:#f0917f}
:root[data-theme="dark"] .awk-inout{background:#122a45;color:#7db3f0}
.aw-maps{display:flex;align-items:center;justify-content:center;gap:var(--sp-6);padding:var(--sp-12);border-top:1px solid var(--line);color:var(--accent-text);font-weight:600;font-size:13.5px;text-decoration:none}
.aw-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-x9);padding:var(--sp-40) var(--sp-16);color:var(--muted);font-size:13.5px;font-weight:500;text-align:center}
.aw-empty svg{opacity:.5}
/* pemilih rentang & saklar peta di Insights — melebar penuh, segmen sama lebar */
.hm-seg{display:flex;width:100%;margin-top:var(--sp-12)}
.hm-seg button{flex:1;text-align:center}
/* peta titik panas (Leaflet) */
.att-map{height:340px;border-radius:14px;overflow:hidden;border:1px solid var(--line);margin-top:var(--sp-10);position:relative;z-index:0}
.hm-hint{font-size:12px;color:var(--muted);margin-top:var(--sp-8);text-align:center}
.hm-maplink{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:12px;font-weight:600;color:var(--accent-text);background:var(--accent-weak);padding:var(--sp-4) var(--sp-10);border-radius:20px}
.leaflet-container{font:inherit}
.att-locs{display:flex;flex-direction:column}
.att-loc{display:flex;align-items:center;gap:var(--sp-x11);padding:var(--sp-x9) 0;border-top:1px solid var(--line);color:inherit;text-decoration:none}
.att-loc:first-child{border-top:0}
.att-loc-rank{width:22px;height:22px;flex:none;border-radius:50%;background:var(--accent-weak);color:var(--accent-text);font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center}
.att-loc-b{flex:1;min-width:0;display:flex;flex-direction:column}
.att-loc-co{font-size:13.5px;font-weight:600;color:var(--text)}
.att-loc-n{font-size:12px;color:var(--muted)}
.att-loc-empty{font-size:13px;color:var(--muted);padding:var(--sp-6) 0}
/* unusual time / place flags + date range */
.att-odd{display:inline-flex;align-items:center;gap:var(--sp-x3);font-size:11px;font-weight:700;color:#b45309;background:#f59e0b22;padding:var(--sp-2) var(--sp-8);border-radius:20px}
.att-odd svg{flex:none}
.att-report .att-rec.flagged{background:linear-gradient(90deg,#f59e0b12,transparent);border-radius:10px;padding-left:var(--sp-6);padding-right:var(--sp-6)}
/* ===== v214: titip absen menunggu approval = MENDESAK =====
   Kalau pengurus lupa meng-approve, orangnya "tidak absen" sama sekali. Jadi ini menonjol —
   spanduk di atas + aksen kiri tegas pada baris/hari yang pending — tapi tetap dalam bahasa
   desain (dasar pastel + aksen oranye-merah + ikon berwarna), bukan merah menyala kasar. */
.att-report .att-pend-banner{display:flex;align-items:center;gap:var(--sp-12);width:100%;text-align:left;margin:var(--sp-2) 0 var(--sp-12);padding:var(--sp-12) var(--sp-14);background:linear-gradient(135deg,#fff4ec,#ffe9e3);border:1px solid #f2c7a6;border-left:4px solid #e8621a;border-radius:12px;cursor:pointer;transition:transform .12s,box-shadow .12s}
.att-report .att-pend-banner:hover{box-shadow:0 4px 14px rgba(232,98,26,.16)}
.att-report .att-pend-banner:active{transform:scale(.99)}
.att-pend-banner-ic{flex:none;width:38px;height:38px;border-radius:11px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f0932b,#e8621a)}
.att-pend-banner-ic svg{stroke:#fff}
.att-pend-banner-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-1)}
.att-pend-banner-t b{font-size:14px;color:#7a3d12;line-height:1.25}
.att-pend-banner-t span{font-size:12px;color:#a4661a}
.att-pend-banner-go{flex:none;display:inline-flex;align-items:center;gap:var(--sp-2);font-size:12.5px;font-weight:700;color:#e8621a}
.att-pend-banner-go svg{stroke:#e8621a}
@media (prefers-reduced-motion:no-preference){.att-pend-banner-ic{animation:attPendPulse 2s ease-in-out infinite}}
@keyframes attPendPulse{0%,100%{box-shadow:0 0 0 0 rgba(232,98,26,.35)}50%{box-shadow:0 0 0 6px rgba(232,98,26,0)}}
.att-report .att-rec.att-rec-pending{background:color-mix(in srgb,#e8811a 12%,var(--surface));border-left:3px solid #e8621a;padding-left:var(--sp-x7);border-radius:10px}
.att-group-pend>.att-day{border-left:3px solid #e8621a}
.att-pend-tag{border:1px solid #f0b483}
:root[data-theme="dark"] .att-report .att-pend-banner{background:linear-gradient(135deg,rgba(232,98,26,.15),rgba(232,98,26,.07));border-color:rgba(232,98,26,.42);border-left-color:#f0932b}
:root[data-theme="dark"] .att-pend-banner-t b{color:#ffd9b8}
:root[data-theme="dark"] .att-pend-banner-t span{color:#e6b48a}
:root[data-theme="dark"] .att-pend-banner-go,:root[data-theme="dark"] .att-pend-banner-go svg{color:#f0932b;stroke:#f0932b}
:root[data-theme="dark"] .att-pend-tag{border-color:rgba(240,147,43,.4)}
/* KONTRAS MODE GELAP untuk tiga chip laporan absensi — keluarga cacat yang sama dengan v267.
 * Ketiganya memakai warna teks pekat (#b45309 / #177245) di atas wash tipis. Di mode terang itu
 * benar; di mode gelap wash-nya jatuh ke permukaan gelap sementara warna teksnya tidak ikut
 * disesuaikan, jadi teks gelap duduk di atas latar gelap. Terukur di Chrome sebelum diperbaiki:
 *   .att-t-prov 2,83:1 · .att-odd 2,83:1 · .att-t-dur 2,80:1   (ambang yang kita pegang: 4,5:1)
 * Sesudah: 7,02 · 7,02 · 9,01. `.att-t-prov` baru lahir hari ini di butir 25 — dua yang lain
 * sudah lama ada dan ikut diperbaiki, karena menemukannya lalu memperbaiki punya sendiri saja
 * berarti sengaja meninggalkan cacat yang sudah kita lihat sendiri.
 * `.att-pend-tag` sudah punya varian gelapnya sejak awal; itu pola yang diikuti di sini. */
:root[data-theme="dark"] .att-t-prov,
:root[data-theme="dark"] .att-odd{background:rgba(245,158,11,.22);color:#fbbf24}
:root[data-theme="dark"] .att-t-dur{background:rgba(23,114,69,.30);color:#6ee7b7}
:root[data-theme="dark"] .att-rej-tag{background:rgba(192,57,43,.28);color:#fca5a5}
.att-range{display:flex;align-items:center;gap:var(--sp-8);flex-wrap:wrap;margin:var(--sp-2) 0 var(--sp-16)}
.att-date-btn{display:inline-flex;align-items:center;gap:var(--sp-6);padding:var(--sp-x7) var(--sp-x11);border:1px solid var(--line);border-radius:9px;background:var(--surface);color:var(--text);font-size:13px;font-weight:600;cursor:pointer}
.att-date-btn svg{color:var(--muted);flex:none}
.att-range-sep{color:var(--muted)}
.att-filters{display:flex;gap:var(--sp-8);flex-wrap:wrap;margin-bottom:var(--sp-14)}
/* #397 — filter absensi pakai pemilih bergaya app (.picker), bukan <select> native */
.att-filter{flex:1 1 150px;min-width:150px}
.att-filter .att-filter-btn{font-size:13.5px;font-weight:600;padding:var(--sp-x9) var(--sp-10);border-radius:9px}
.att-filter .att-filter-btn>svg{color:var(--muted)}

/* ---- Feedback: full, selectable detail text ---- */
.fb-tabs{--gulir-sengaja:1;display:flex;gap:var(--sp-6);overflow-x:auto;padding:var(--sp-2) 0 var(--sp-14);-webkit-overflow-scrolling:touch;scrollbar-width:none}
.fb-tabs::-webkit-scrollbar{display:none}
.fb-tab{flex:none;display:inline-flex;align-items:center;gap:var(--sp-6);padding:var(--sp-x7) var(--sp-x13);border-radius:999px;border:1px solid var(--line);background:var(--surface);color:var(--muted);font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}
.fb-tab:hover{background:var(--surface-2)}
.fb-tab.on{background:var(--accent);border-color:var(--accent);color:#fff}
.fb-tab-n{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 var(--sp-x5);border-radius:999px;background:var(--surface-2);color:var(--muted);font-size:11px;font-weight:700}
.fb-tab.on .fb-tab-n{background:rgba(255,255,255,.28);color:#fff}
.fb-full{user-select:text;-webkit-user-select:text;padding:var(--sp-2) var(--sp-2) var(--sp-12);border-bottom:1px solid var(--line);margin-bottom:var(--sp-10)}
.fb-full-title{font-size:15px;font-weight:700;color:var(--text);line-height:1.4;margin-bottom:var(--sp-6);user-select:text;-webkit-user-select:text}
.fb-full-body{font-size:14px;line-height:1.55;color:var(--muted);white-space:pre-wrap;word-break:break-word;user-select:text;-webkit-user-select:text}
.fb-comment-t{user-select:text;-webkit-user-select:text}

/* ---- Unified pop-up / overlay system ---- */
/* One consistent scrim + fade for every modal backdrop. */
.mm-backdrop,.cf-backdrop,.sr-backdrop{background:rgba(17,20,28,.45);animation:cfFade .14s ease}
.co-backdrop{background:rgba(17,20,28,.14)} /* lighter scrim for dropdown-style menus */
/* One consistent panel look: radius, border, shadow, entrance animation. */
.mm-sheet,.cf-modal,.sr-panel,.gi-modal,.co-menu{border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,.30)}
.gi-modal{animation:cfPop .16s cubic-bezier(.2,.9,.3,1.15)}
/* Menu sudut (attach +, kebab, dropdown) TIDAK dipusatkan. cfPop memakai translate(-50%,-50%)
   yang benar untuk modal tengah, tapi pada menu sudut ia menggeser menu ke kiri-atas selama
   animasi lalu menyentak balik saat animasi selesai. coPop hanya opacity + scale (tanpa translate),
   tumbuh dari sudut jangkarnya (transform-origin diatur per-menu). */
.co-menu{animation:coPop .15s cubic-bezier(.2,.9,.3,1.08);transform-origin:top center}
/* Compound selector (dua kelas) agar menang atas .co-menu generik di atas — tanpa ini, karena
   sama-sama satu tingkat spesifisitas dan .co-menu ditulis belakangan, origin per-menu terabaikan.
   Tiap menu tumbuh dari tombol pemicunya sehingga terasa menyatu, bukan mengambang. */
.co-menu.chat-menu{transform-origin:top right}       /* kebab: tumbuh dari titik-tiga (kanan-atas) */
.co-menu.attach-sheet{transform-origin:bottom left}  /* attach +: tumbuh dari tombol + (kiri-bawah) */
@keyframes coPop{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
/* Menu (co-menu/mm-sheet) yang digambar ulang oleh render latar tak boleh mengulang animasi
   masuknya — itu bikin kedip. JS menandai .noanim di semua render kecuali yang tepat sesudah dibuka. */
.co-menu.noanim,.mm-sheet.noanim{animation:none}
/* Consistent rows inside menus. */
.co-menu .co-link,.co-menu .co-code{border-radius:10px}
/* ---- Liquid glass: frosted translucency on floating panels (progressive enhancement) ---- */
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .co-menu,.nt-menu,.mm-sheet,.cf-modal,.gi-modal,.sr-panel,.ui-modal,.fwd-modal{
    background:var(--glass);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
    border:1px solid var(--glass-brd);box-shadow:0 20px 60px rgba(0,0,0,.28),inset 0 1px 0 var(--glass-hi)}
  .topbar,.chat-top{background:var(--glass-strong);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur)}
  .jump-latest{background:var(--glass-strong);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border:1px solid var(--glass-brd)}
  .toast{background:rgba(20,20,24,.74);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur)}
}
/* Context menu (long-press / right-click) opens anchored at the finger, not as a far-away sheet. */
.mm-sheet.mm-pop{left:0;top:0;right:auto;bottom:auto;transform:none;width:max-content;min-width:184px;max-width:min(248px,76vw);transform-origin:top left;animation:mmPop .13s cubic-bezier(.2,.85,.3,1.05)}
@keyframes mmPop{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
/* Stop the native long-press flicker (tap highlight, iOS callout, text-selection handles) fighting our menu. */
.chat-body .bubble,.conv-row{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}
.chat-body .bubble,.chat-body .bubble *,.conv-row,.conv-row *{-webkit-user-select:none;user-select:none}

/* ---- Galat klien (layar Developer) ---- */
.ce-tabs{display:flex;flex-wrap:wrap;gap:var(--sp-8);margin:var(--sp-14) 0 var(--sp-10)}
.ce-tab{padding:var(--sp-6) var(--sp-14);border-radius:20px;border:1px solid var(--line);background:var(--surface);color:var(--muted);font:inherit;font-size:12.5px;font-weight:600;cursor:pointer}
.ce-tab:hover{background:var(--surface-2)}
.ce-tab.on{background:var(--accent);border-color:var(--accent);color:#fff}
.ce-item{border:1px solid var(--line);border-radius:14px;background:var(--surface);margin-bottom:var(--sp-8);overflow:hidden}
.ce-item.ce-done{opacity:.62}
.ce-head{display:flex;align-items:center;gap:var(--sp-10);width:100%;padding:var(--sp-x11) var(--sp-x13);background:none;border:none;text-align:left;font:inherit;color:inherit;cursor:pointer}
.ce-head:hover{background:var(--surface-2)}
.ce-ico{flex:none;display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;background:#f43f5e1c;color:#f43f5e}
/* butir 101 — empat keadaan, empat warna. Ikonnya sendiri yang membawa keadaannya, karena
   di daftar sepanjang layar orang memindai kolom kiri, bukan membaca tiap barisnya. */
.ce-item.ce-done .ce-ico{background:#10b9811c;color:#10b981}
.ce-item.ce-acked .ce-ico{background:#f59e0b1c;color:#f59e0b}
.ce-item.ce-bgnd .ce-ico{background:#0ea5e91c;color:#0ea5e9}
.ce-item.ce-bgnd{opacity:.82}
/* Alasan "diketahui" hidup DI BARIS, bukan di balik ketukan — seluruh gunanya keadaan ini
   adalah memindai daftar dan tahu kenapa sesuatu dibiarkan. */
.ce-why{display:flex;align-items:flex-start;gap:var(--sp-x7);margin:0 var(--sp-x13) var(--sp-x11) 53px;padding:var(--sp-x7) var(--sp-10);border-radius:10px;background:#f59e0b14;color:#b45309;font-size:12px;line-height:1.45}
/* Pasangan gelapnya. Versi pertama mewarisi #b45309 dari tema terang dan terukur 3,10:1 —
   ditangkap gerbang kontras butir 51 sebelum sempat tayang, bukan sesudah. Isian kuningnya
   dipertahankan (ia yang membuat baris ini dikenali dari sudut mata); yang diterangkan
   TULISANNYA, sama seperti lima perbaikan kontras sebelumnya. */
:root[data-theme="dark"] .ce-why{background:#f59e0b1f;color:#fcd34d}
.ce-why svg{flex:none;margin-top:var(--sp-2)}
.ce-acts{display:flex;flex-wrap:wrap;gap:var(--sp-8)}
/* Ringkasan latar: satu angka, bukan puluhan bendera. */
.ce-sum{display:flex;align-items:center;gap:var(--sp-10);width:100%;margin-bottom:var(--sp-10);padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:14px;background:#0ea5e90f;color:inherit;font:inherit;text-align:left;cursor:pointer}
.ce-sum:hover{background:#0ea5e91a}
.ce-sum-ico{flex:none;display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;background:#0ea5e91c;color:#0ea5e9}
.ce-sum-b{display:flex;flex-direction:column;gap:var(--sp-2);min-width:0;flex:1}
.ce-sum-t{font-size:13px;font-weight:600;line-height:1.35}
.ce-sum-s{font-size:11.5px;color:var(--muted)}
.ce-sum>svg:last-child{flex:none;color:var(--muted)}
.ce-body{display:flex;flex-direction:column;gap:var(--sp-x3);min-width:0;flex:1}
.ce-msg{font-size:13.5px;font-weight:600;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ce-sub{display:flex;flex-wrap:wrap;gap:var(--sp-8);font-size:11.5px;color:var(--muted)}
.ce-where{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.ce-ver{font-weight:700;color:#8b5cf6;background:#8b5cf61c;padding:var(--sp-1) var(--sp-x7);border-radius:20px}
.ce-hits{flex:none;font-size:12px;font-weight:700;color:var(--muted);background:var(--surface-2);padding:var(--sp-x3) var(--sp-x9);border-radius:20px}
.ce-detail{padding:0 var(--sp-x13) var(--sp-x13);border-top:1px solid var(--line)}
/* Jejak tumpukan bisa sangat panjang dan tidak boleh melebarkan kolom — dibungkus dan
   digulir sendiri, bukan memaksa seluruh halaman melar ke samping. */
/* BUTIR 107 — judul kecil di atas tiap kotak stack. Muncul HANYA kalau ada dua kotak,
   jadi kehadirannya sendiri sudah memberi tahu bahwa keadaan pertama dan terakhir memang
   berbeda. Tanpa judul, dua kotak berurutan terbaca sebagai satu stack yang kepanjangan. */
/* BUTIR 109 — kalimat Beta di Update History. Pastel hangat, bukan merah: ini bukan
   peringatan, ia janji. Ikonnya tetap berwarna sesuai bahasa desain kita. */
.cl-beta{display:flex;gap:var(--sp-10);align-items:flex-start;margin:0 0 var(--sp-x18);padding:var(--sp-x13) var(--sp-x15);border-radius:14px;background:linear-gradient(135deg,#fef3c7 0%,#fde8d7 100%);color:#7c4a12;font-size:13px;line-height:1.6}
.cl-beta svg{flex:0 0 auto;margin-top:var(--sp-2);color:#f59e0b}
:root[data-theme="dark"] .cl-beta{background:#f59e0b1a;color:#fcd34d}
:root[data-theme="dark"] .cl-beta svg{color:#fbbf24}
.ce-stack-lbl{margin:var(--sp-x11) 0 -4px;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--muted)}
.ce-stack{margin:var(--sp-x11) 0;padding:var(--sp-10) var(--sp-12);max-height:220px;overflow:auto;border-radius:10px;background:var(--surface-2);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.55;white-space:pre-wrap;word-break:break-word;color:var(--muted)}
.ce-meta{display:flex;flex-direction:column;gap:var(--sp-4);font-size:11.5px;color:var(--muted);margin-bottom:var(--sp-x11);word-break:break-word}
/* Butir 51 — putih di atas #f43f5e terukur 3,67:1. Digelapkan satu langkah dalam keluarga
   warna yang sama (rose-500 -> rose-600): 4,70:1, dan tetap terbaca sebagai mawar yang sama. */
.ce-badge{flex:none;min-width:22px;text-align:center;font-size:11.5px;font-weight:700;color:#fff;background:#e11d48;padding:var(--sp-2) var(--sp-x7);border-radius:20px;margin-right:var(--sp-4)}

/* Prioritas & catatan aktivitas — warnanya dari palet, bukan heksa yang ditempel di JS.
   color-mix membuat latar tipisnya, menggantikan trik menyambung "1a" ke heksa. */
.prio-urgent{color:var(--danger);background:color-mix(in srgb,var(--danger) 13%,transparent)}
.prio-high{color:var(--due-today);background:color-mix(in srgb,var(--due-today) 13%,transparent)}
.prio-medium{color:var(--prio-med);background:color-mix(in srgb,var(--prio-med) 13%,transparent)}
.prio-low{color:var(--prio-low);background:color-mix(in srgb,var(--prio-low) 13%,transparent)}
.prio-urgent .prio-dot,span.prio-dot.prio-urgent{background:var(--danger)}
.prio-high   .prio-dot,span.prio-dot.prio-high{background:var(--due-today)}
.prio-medium .prio-dot,span.prio-dot.prio-medium{background:var(--prio-med)}
.prio-low    .prio-dot,span.prio-dot.prio-low{background:var(--prio-low)}
/* #326 (A8) — pita prioritas kiri pada baris tugas (bukan baris lain). Skala: urgent/high tegas,
   medium halus, low samar. :not(.active) agar baris yang dibuka tetap memakai aksen accent-nya. */
.list-row.tp-urgent:not(.active){box-shadow:inset 3px 0 0 var(--danger)}
.list-row.tp-high:not(.active){box-shadow:inset 3px 0 0 var(--due-today)}
.list-row.tp-medium:not(.active){box-shadow:inset 3px 0 0 color-mix(in srgb,var(--prio-med) 55%,transparent)}
.list-row.tp-low:not(.active){box-shadow:inset 2px 0 0 color-mix(in srgb,var(--prio-low) 45%,transparent)}
.act-del{color:var(--danger);background:color-mix(in srgb,var(--danger) 13%,transparent)}
.act-ok{color:var(--good);background:color-mix(in srgb,var(--good) 13%,transparent)}
.act-chg{color:var(--prio-med);background:color-mix(in srgb,var(--prio-med) 13%,transparent)}

/* Rel: ruang kosongnya kembali satu tingkat; ikon di dalamnya punya tujuan sendiri. */
.m-rail>*{cursor:default}
.m-rail button{cursor:pointer}

.cf-input{width:100%;margin:var(--sp-12) 0 var(--sp-2);padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:10px;background-color:var(--surface);color:var(--text);font:inherit;font-size:14px;outline:none}
.cf-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}

/* Pilihan di dalam kotak konfirmasi (mis. peran seseorang di workspace).
   Memakai kotak yang sama, bukan kotak baru: latar, tombol Esc, dan penataannya sudah
   ada dan tidak perlu ditulis dua kali. */
.cf-choices{display:flex;flex-direction:column;gap:var(--sp-8);margin:var(--sp-14) 0 var(--sp-2)}
.cf-choice{display:flex;align-items:center;gap:var(--sp-x11);width:100%;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line);border-radius:12px;background-color:var(--surface);color:var(--text);font:inherit;text-align:left;cursor:pointer;transition:border-color .14s ease,background-color .14s ease,transform .14s ease}
.cf-choice:hover{border-color:var(--accent)}
.cf-choice:active{transform:scale(.985)}
.cf-choice.on{border-color:var(--accent);background-color:var(--accent-weak)}
.cf-choice:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.cf-choice-ico{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--accent-weak)}
.cf-choice-ico.ic-contacts{background:#e6f6f7}.cf-choice-ico.ic-companies{background:#ececfb}.cf-choice-ico.ic-account{background:#f0ecfb}
.cf-choice-b{display:flex;flex-direction:column;min-width:0;flex:1}
.cf-choice-t{font-size:14px;font-weight:600}
/* --muted, bukan --faint: di atas .cf-choice.on latarnya --accent-weak, dan --faint
   tidak sampai 4,5:1 di sana. */
.cf-choice-s{font-size:12px;color:var(--muted);line-height:1.35;margin-top:var(--sp-1)}
/* --accent-text, bukan --accent: di tema gelap --accent di atas --accent-weak cuma
   3,03:1 — lolos ambang grafis tapi tipis untuk satu-satunya tanda "terpilih".
   --accent-text memang dibuat untuk dibaca di atas --accent-weak: 5,50 dan 8,33. */
.cf-choice>svg{color:var(--accent-text);flex-shrink:0}

/* Permintaan izin lihat pesan terhapus, di panel notifikasi. */
.nt-peek{align-items:flex-start;gap:var(--sp-10);padding:var(--sp-x11) var(--sp-12)}
.nt-reason{font-style:italic;color:var(--muted);margin-top:var(--sp-2)}
.nt-peek-acts{display:flex;gap:var(--sp-8);margin-top:var(--sp-x9)}
.nt-peek-acts .btn{padding:var(--sp-x5) var(--sp-14);font-size:12.5px}

/* ---- Mode pemeliharaan ---- */
/* Kotak saklar sengaja TIDAK terlihat seperti baris modul biasa. Mematikan satu modul
   itu pekerjaan terencana; menyalakan ini artinya sedang terjadi sesuatu. */
.mnt-box{border:1px solid var(--line);border-radius:14px;padding:var(--sp-x13) var(--sp-14);margin:var(--sp-14) 0 var(--sp-x18);background:var(--surface)}
.mnt-box.on{border-color:var(--due-late);background:color-mix(in srgb,var(--due-late) 8%,transparent)}
.mnt-row{display:flex;align-items:center;gap:var(--sp-10)}
.mnt-row>svg{flex:none;color:var(--due-late)}
.mnt-lbl{flex:1;font-size:14px;font-weight:600}
.mnt-lbl b{color:var(--due-late)}
.mnt-box .fb-sub{margin:var(--sp-x9) 0 0}
.mnt-note-cur{font-style:italic}
.mnt-box .cf-input{margin:var(--sp-x11) 0 0}
/* Layar yang dilihat pengguna saat pemeliharaan menyala. */
.mnt-card{text-align:center}
.mnt-ico{display:flex;align-items:center;justify-content:center;width:56px;height:56px;margin:var(--sp-14) auto var(--sp-12);border-radius:18px;background:color-mix(in srgb,var(--due-today) 14%,transparent);color:var(--due-today)}
.mnt-title{font-size:18px;font-weight:700;margin-bottom:var(--sp-6)}

/* ==========================================================================
   TIDAK ADA YANG BOLEH LEBIH LEBAR DARI KOLOMNYA
   ==========================================================================
   Dua bug yang ditemukan 20 Juli — pilihan prioritas terpotong jadi "Hig", dan tombol
   Screenshot/Cancel/Submit melayang di luar kartunya — ternyata satu sebab yang sama,
   bukan dua kejadian terpisah:

     Anak dari flex row punya min-width:auto secara bawaan. Artinya ia MENOLAK menyusut
     di bawah lebar isinya. Kalau isinya lebih lebar dari ruang yang ada, ia tidak
     mengecil dan tidak turun baris — ia MELUBER keluar, diam-diam, tanpa scrollbar.

   Aplikasi ini kolomnya selebar ponsel (360–460px) di desktop MAUPUN di HP, jadi kesalahan
   ini muncul di layar besar juga. Ia hanya kelihatan kalau isinya kebetulan cukup panjang —
   itu sebabnya ia lolos sekian lama.

   Penyapuan menemukan 127 baris flex tanpa flex-wrap. Sebagian besar aman: satu ikon
   ditambah satu teks lentur tidak bisa meluber. Yang diperbaiki di bawah adalah dua pola
   yang BISA:

     1. Deretan tombol berteks   -> boleh turun baris
     2. Deretan kolom sejajar    -> boleh menyusut

   Ditulis di sini sebagai satu blok, bukan disebar ke 20 aturan, supaya alasannya tinggal
   di satu tempat dan penyapuan berikutnya cukup membaca bagian ini.
   -------------------------------------------------------------------------- */

/* 1. Deretan tombol — turun baris daripada meluber. */
.cf-actions,.msg-edit-actions,.dp-foot,.task-actions,.rsvp,.att-cam-actions,
.nt-peek-acts,.inv-actions,.fb-status-set{flex-wrap:wrap}

/* 2. Deretan sejajar — menyusut daripada meluber. Yang ini TIDAK diberi flex-wrap:
      tiga kolom Bazi atau tiga ruas tanggal lahir yang turun baris justru terbaca salah,
      lebih baik mengecil bersama-sama. */
.bd-row,.bz-cols,.srv-grid,.bl-money,.att-today,.bl-pct-row,
.auth-tabs,.lg-tabs,.ce-tabs,.bl-mode-seg{min-width:0}
.bd-row>*,.bz-cols>*,.srv-grid>*,.bl-money>*,.att-today>*,.bl-pct-row>*,
.auth-tabs>*,.lg-tabs>*,.ce-tabs>*,.bl-mode-seg>*{min-width:0}

/* ============ Referral / afiliasi (layar Account) ============
   Layar hadiah: kupon berwarna, saldo hijau menonjol, daftar penghasilan & pencairan.
   Colorful tapi tetap estetik; ikut token tema untuk terang & gelap. Mobile-first. */
.rf-wrap{max-width:560px}
.rf-coupon{position:relative;border-radius:18px;padding:var(--sp-x18);color:#fff;overflow:hidden;
  background:linear-gradient(135deg,#7c5cff 0%,#b552e6 55%,#ec5aa8 100%);
  box-shadow:0 10px 30px rgba(124,92,255,.34)}
.rf-coupon::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(120px 120px at 88% -12%,rgba(255,255,255,.35),transparent 70%)}
.rf-coupon-label{display:flex;align-items:center;gap:var(--sp-x7);font-size:12.5px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;opacity:.94}
.rf-coupon-code{width:100%;margin:var(--sp-12) 0 var(--sp-14);display:flex;align-items:center;justify-content:center;gap:var(--sp-12);
  padding:var(--sp-14) var(--sp-16);border-radius:14px;background:rgba(255,255,255,.16);
  border:1.5px dashed rgba(255,255,255,.6);color:#fff;transition:background .12s,transform .08s}
.rf-coupon-code b{font-size:23px;font-weight:800;letter-spacing:.14em;word-break:break-all}
.rf-coupon-code:hover{background:rgba(255,255,255,.26)}
.rf-coupon-code:active{transform:scale(.98)}
.rf-share{width:100%;justify-content:center;background:#fff;color:#7c3aed;border-color:#fff;font-weight:650}
.rf-share:hover{background:#f4f0ff}
.rf-coupon-empty{background:linear-gradient(135deg,#8a8f98,#6b7078);box-shadow:0 8px 22px rgba(0,0,0,.14)}
.rf-hint-sm{margin:var(--sp-8) 0 0;font-size:13px;color:rgba(255,255,255,.92);line-height:1.5}
.rf-form{margin:var(--sp-14) 0}
.rf-code-row{display:flex;gap:var(--sp-8)}
.rf-code-row input{flex:1;min-width:0;border:1px solid var(--line);border-radius:10px;padding:var(--sp-10) var(--sp-12);
  font-size:15px;font-weight:700;letter-spacing:.1em;background:var(--surface);color:var(--text);outline:none}
.rf-code-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.rf-how{display:flex;gap:var(--sp-x9);align-items:flex-start;margin:var(--sp-6) 0 var(--sp-16);padding:var(--sp-x11) var(--sp-x13);border-radius:12px;
  background:var(--surface-2);color:var(--muted);font-size:12.5px;line-height:1.55}
.rf-how svg{flex-shrink:0;margin-top:var(--sp-1);color:#8b5cf6}
.rf-stats{border:1px solid var(--line);border-radius:16px;padding:var(--sp-16);background:var(--surface);box-shadow:0 1px 2px rgba(0,0,0,.04)}
.rf-bal{display:flex;flex-direction:column;gap:var(--sp-2);padding-bottom:var(--sp-12);border-bottom:1px solid var(--line)}
.rf-bal-label{font-size:12.5px;color:var(--muted);font-weight:600}
.rf-bal-amt{font-size:30px;font-weight:800;color:#16a34a;line-height:1.1}
.rf-stat-row{display:flex;gap:var(--sp-12);margin-top:var(--sp-12)}
.rf-stat{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--sp-2)}
.rf-stat-n{font-size:18px;font-weight:750;color:var(--text);word-break:break-all}
.rf-stat-l{font-size:12px;color:var(--muted)}
.rf-wd{margin:var(--sp-16) 0 var(--sp-6)}
.rf-wd-btn{width:100%;justify-content:center}
.rf-wd-locked{display:flex;gap:var(--sp-8);align-items:center;padding:var(--sp-x11) var(--sp-x13);border-radius:12px;background:var(--surface-2);color:var(--muted);font-size:13px;line-height:1.45}
.rf-wd-locked svg{flex-shrink:0;color:#f59e0b}
.rf-wd-form{border:1px solid var(--line);border-radius:16px;padding:var(--sp-16);background:var(--surface)}
.rf-wd-head{display:flex;align-items:center;gap:var(--sp-8);font-weight:700;font-size:15px;margin-bottom:var(--sp-10)}
.rf-wd-head svg{color:#16a34a}
.rf-wd-acts{display:flex;gap:var(--sp-8);margin-top:var(--sp-6)}
.rf-wd-acts .btn{flex:1;justify-content:center}
.rf-sec-h{margin:var(--sp-20) 0 var(--sp-8);font-size:12.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--faint)}
.rf-row{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);padding:var(--sp-x11) 0;border-bottom:1px solid var(--line)}
.rf-row:last-child{border-bottom:0}
.rf-row-main{font-size:15px;font-weight:700;color:var(--text)}
.rf-row-main.rf-earn{color:#16a34a}
.rf-row-side{display:flex;align-items:center;gap:var(--sp-10)}
.rf-row-seats{font-size:12.5px;color:var(--muted)}
.rf-row-date{font-size:12px;color:var(--faint);min-width:74px;text-align:right}
.rf-empty{padding:var(--sp-16) var(--sp-4);color:var(--muted);font-size:13.5px;text-align:center}
.rf-off{display:flex;gap:var(--sp-10);align-items:center;padding:var(--sp-16);border-radius:12px;background:var(--surface-2);color:var(--muted);font-size:14px}
.rf-off svg{color:#8b5cf6;flex-shrink:0}
/* Ajakan developer menyalakan kode referralnya sendiri — kartu lembut, ikon hadiah warna. */
.rf-enroll{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--sp-10);padding:var(--sp-x26) var(--sp-20);border-radius:16px;border:1px solid #8b5cf644;background:linear-gradient(180deg,#faf5ff,#f5f3ff)}
.rf-enroll-ic{width:52px;height:52px;border-radius:15px;display:flex;align-items:center;justify-content:center;background:#8b5cf61f;color:#7c3aed}
.rf-enroll-t{font-size:17px;font-weight:700;color:#5b21b6}
.rf-enroll-p{font-size:13px;line-height:1.6;color:#6b5b95;margin:0;max-width:420px}
.rf-enroll-btn{margin-top:var(--sp-4)}
:root[data-theme="dark"] .rf-enroll{background:linear-gradient(180deg,#1e1b3a,#191733);border-color:#8b5cf655}
:root[data-theme="dark"] .rf-enroll-ic{background:#8b5cf62b;color:#c4b5fd}
:root[data-theme="dark"] .rf-enroll-t{color:#ddd6fe}
:root[data-theme="dark"] .rf-enroll-p{color:#b7abd9}
/* Baris diskon referral di kartu Tagihan & invoice (hijau seperti credit). */
.bl-row.bl-discount span{color:#177245}
.bl-row.bl-discount span:last-child{font-weight:700}
.bl-row.bl-discount svg{display:inline-block;vertical-align:-2px;margin-right:var(--sp-x3)}
/* BUTIR 204 — baris tingkat gratis. Ia menggantikan "0 × Rp 25.000" seluruhnya, jadi ia
   satu-satunya baris di rincian dan boleh berbicara dengan percaya diri: hijau lembut yang
   sama dengan baris diskon, tapi tanpa tanda minus — tidak ada yang dipotong, memang tidak
   ada yang ditagih. */
.bl-row.bl-freetier span{color:#177245}
.bl-row.bl-freetier span:last-child{font-weight:700}
.bl-row.bl-freetier svg{display:inline-block;vertical-align:-2px;margin-right:var(--sp-x3)}
/* Kode referral di Pengaturan workspace. */
.co-refform{margin-top:var(--sp-4)}
.co-refnote{margin:var(--sp-2) 0 var(--sp-8);font-size:12.5px;color:var(--muted);line-height:1.5}
:root[data-theme="dark"] .rf-bal-amt,
:root[data-theme="dark"] .rf-row-main.rf-earn,
:root[data-theme="dark"] .rf-wd-head svg{color:#4ade80}
:root[data-theme="dark"] .bl-row.bl-discount span{color:#4ade80}
:root[data-theme="dark"] .rf-share{background:#241d3a;color:#c7b6ff;border-color:#241d3a}
:root[data-theme="dark"] .rf-share:hover{background:#2c2447}
/* Panel Referral developer: form angka + antrean pencairan. */
.rfd-form{display:flex;flex-direction:column;margin:var(--sp-2) 0 var(--sp-8)}
.rfd-form .btn{align-self:flex-start;margin-top:var(--sp-14)}
.rfq-list{display:flex;flex-direction:column;gap:var(--sp-10)}
.rfq-row{border:1px solid var(--line);border-radius:14px;padding:var(--sp-x13) var(--sp-14);background:var(--surface)}
.rfq-top{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--sp-10)}
.rfq-who{display:flex;flex-direction:column;gap:var(--sp-1);min-width:0}
.rfq-who b{font-size:14.5px;font-weight:700}
.rfq-email{font-size:12px;color:var(--muted);word-break:break-all}
.rfq-amt{font-size:16px;font-weight:800;color:#16a34a;white-space:nowrap}
.rfq-bank{display:flex;gap:var(--sp-x7);align-items:center;margin:var(--sp-8) 0;padding:var(--sp-8) var(--sp-10);border-radius:10px;background:var(--surface-2);font-size:12.5px;color:var(--muted);word-break:break-all}
.rfq-bank svg{flex-shrink:0;color:#8b5cf6}
.rfq-foot{display:flex;justify-content:space-between;align-items:center;gap:var(--sp-10)}
.rfq-date{font-size:12px;color:var(--faint)}
.rfq-acts{display:flex;gap:var(--sp-8)}
.rfq-acts .btn{padding:var(--sp-x7) var(--sp-12);font-size:13px}
.co-refcode{text-transform:uppercase;letter-spacing:.05em}
:root[data-theme="dark"] .rfq-amt{color:#4ade80}

/* Pita lingkungan (Preview/Demo) — penanda tetap, informatif, TAK menghalangi ketukan.
   Hanya muncul di lingkungan non-Release (lihat mountEnvRibbon di app.js). */
/* DULU di tengah-atas, dengan alasan "tengah topbar hampir selalu kosong (judul kiri,
   search/bell kanan)". Alasan itu ternyata cuma berlaku di layar lebar: di 500px breadcrumb
   memanjang sampai ke tengah dan pitanya menimpa judul halaman (temuan CEO 31 Jul).
   Keberatan asli terhadap area bawah — "semua kontrol primer ada di sana" — sekarang sudah
   hilang, karena --float-b menaruh seluruh tumpukan mengambang DI ATAS .col-foot. Jadi
   pitanya turun bergabung ke tumpukan itu: tak ada lagi tebakan soal bagian mana dari
   topbar yang kebetulan kosong di lebar tertentu. */
/* BUTIR 46 — dari tengah-bawah ke KIRI-BAWAH (keputusan CEO 3 Agu malam).
   Di tengah-bawah ia selalu melintasi isi daftar yang sedang digulung setengah jalan: itu
   bukan bug, itu konsekuensi dari duduk di tengah kolom. Tiga sudut lain sudah dihuni —
   kanan-bawah bilah aksi & tombol kirim, kanan-atas pencarian & lonceng, kiri-atas remah
   navigasi (jalur baca pertama). Tersisa kiri-bawah.
   Ia dipatok tepat DI SEBELAH KANAN rel, bukan di atasnya: pitanya ~140px sedangkan rel cuma
   64px, jadi left:8px akan membuatnya menumpang di atas tombol + . pointer-events:none sudah
   ada sejak awal dan TIDAK BOLEH dilepas — ia boleh bersinggungan secara visual, ia tidak
   boleh pernah memakan satu ketukan pun.
   Kalau suatu saat di lebar tersempit ia tetap bertabrakan sampai salah satunya tak terbaca,
   jalan mundurnya sudah disetujui di muka: strip tipis selebar layar di tepi paling bawah,
   tetap pointer-events:none. */
.envribbon{position:fixed;bottom:var(--float-b);left:calc(var(--rail-w) + 10px);z-index:1150;
  pointer-events:none;padding:var(--sp-4) var(--sp-12);border-radius:999px;color:#fff;font-size:10.5px;font-weight:700;
  letter-spacing:.4px;white-space:nowrap;box-shadow:0 4px 14px rgba(0,0,0,.22);opacity:.9;
  max-width:calc(100vw - var(--rail-w) - 20px);overflow:hidden;text-overflow:ellipsis;
  -webkit-user-select:none;user-select:none}
.envribbon.env-preview{background:linear-gradient(135deg,#f59e0b,#f97316)}
.envribbon.env-demo{background:linear-gradient(135deg,#14b8a6,#0ea5e9)}
:root[data-theme="dark"] .envribbon{box-shadow:0 6px 18px rgba(0,0,0,.5);opacity:.9}
/* Dipasang mountEnvRibbon() hanya di Preview/Demo. Di Release nilainya tetap 0px, jadi toast
   dan bilah pembaruan tidak terdorong naik sia-sia untuk pita yang memang tak ada. */
:root.has-envrib{--envrib-h:29px}

/* Panduan (in-app user guide) — accordion colorful */
.doc-view h1{margin-bottom:var(--sp-4)}
.doc-intro{color:var(--muted);font-size:13px;margin:0 0 var(--sp-14)}
.lg-search{position:relative;display:flex;align-items:center;margin:0 0 var(--sp-14)}
.lg-search-ic{position:absolute;left:12px;display:flex;color:var(--muted);pointer-events:none}
.lg-search input{width:100%;padding:var(--sp-x11) var(--sp-x38);border:1px solid var(--line);border-radius:12px;background:var(--surface);color:var(--text);font:inherit;font-size:14px}
.lg-search input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.14)}
.lg-search-x{position:absolute;right:8px;display:flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;border-radius:8px;background:none;color:var(--muted);cursor:pointer}
.lg-search-x:hover{background:rgba(127,127,127,.14)}
.lg-empty{text-align:center;color:var(--muted);padding:var(--sp-x28) var(--sp-12)}
.lg-empty svg{opacity:.45;margin-bottom:var(--sp-4)}
.lg-empty p{margin:0;font-size:14px}
.doc-list{display:flex;flex-direction:column;gap:var(--sp-8)}
.doc-item{border:1px solid var(--line);border-radius:14px;background:var(--surface);overflow:hidden}
.doc-item.open{box-shadow:0 4px 16px rgba(0,0,0,.06)}
.doc-head{display:flex;align-items:center;gap:var(--sp-12);width:100%;padding:var(--sp-12) var(--sp-14);background:none;border:0;cursor:pointer;text-align:left;color:var(--text);font:inherit}
.doc-head .menu-ico{display:flex;align-items:center;justify-content:center;border-radius:10px;flex:0 0 auto}
.doc-title{flex:1;font-size:15px;font-weight:600}
.doc-body{padding:0 var(--sp-16) var(--sp-14)}
.doc-item:nth-child(6n+1) .menu-ico{background:#eef2ff;color:#4f46e5}
.doc-item:nth-child(6n+2) .menu-ico{background:#ecfdf5;color:#059669}
.doc-item:nth-child(6n+3) .menu-ico{background:#fff7ed;color:#ea580c}
.doc-item:nth-child(6n+4) .menu-ico{background:#fdf2f8;color:#db2777}
.doc-item:nth-child(6n+5) .menu-ico{background:#eff6ff;color:#0284c7}
.doc-item:nth-child(6n) .menu-ico{background:#f5f3ff;color:#7c3aed}
/* Mode gelap meratakan enam ubin pastel jadi satu sapuan putih 7% — itu benar, pastel terang
   menyilaukan di atas permukaan gelap. Yang TERLEWAT dulu: warna ikonnya tidak ikut disesuaikan,
   jadi warna tingkat-600 yang dirancang untuk ubin TERANG ditinggalkan di atas ubin GELAP.
   Terukur di Chrome: indigo 2,36:1 dan violet 2,60:1 — di bawah ambang 3:1 untuk objek grafis,
   dan empat sisanya cuma lolos tipis (3,2–4,2). Ini persis jebakan "mode gelap membalik skala"
   di serah-terima: separuh pasangan warna diperbaiki, separuhnya lupa.
   Dinaikkan ke tingkat-400 dari rona yang sama — tetap enam warna berbeda (mandat "semua ikon
   berwarna" utuh), tapi kini 5,5–7,7:1 dan seimbang satu sama lain. Nada yang sama sudah dipakai
   spanduk ulang tahun (#f472b6) dan pasang-PWA (#38bdf8). */
:root[data-theme="dark"] .doc-head .menu-ico{background:rgba(255,255,255,.07) !important}
:root[data-theme="dark"] .doc-item:nth-child(6n+1) .menu-ico{color:#a5b4fc}
:root[data-theme="dark"] .doc-item:nth-child(6n+2) .menu-ico{color:#34d399}
:root[data-theme="dark"] .doc-item:nth-child(6n+3) .menu-ico{color:#fb923c}
:root[data-theme="dark"] .doc-item:nth-child(6n+4) .menu-ico{color:#f472b6}
:root[data-theme="dark"] .doc-item:nth-child(6n+5) .menu-ico{color:#38bdf8}
:root[data-theme="dark"] .doc-item:nth-child(6n) .menu-ico{color:#a78bfa}
/* Operations & handover (dev): code blocks, ordered steps, and callouts inside the accordion */
.ops-view .doc-body pre{--gulir-sengaja:1;margin:var(--sp-8) 0;padding:var(--sp-10) var(--sp-12);background:#f4f5f7;border:1px solid var(--line);border-radius:10px;overflow-x:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;line-height:1.55;color:var(--text);white-space:pre;-webkit-overflow-scrolling:touch}
.ops-view .doc-body code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;background:rgba(0,0,0,.05);padding:var(--sp-1) var(--sp-x5);border-radius:5px;word-break:break-word}
.ops-view .doc-body pre code{background:none;padding:0;border-radius:0;font-size:inherit;word-break:normal}
.ops-view .doc-body ol{margin:var(--sp-8) 0;padding-left:var(--sp-20)}
.ops-view .doc-body ol li{margin:var(--sp-x5) 0;line-height:1.5}
.ops-view .doc-body .ops-cal{display:block;background:#fff7ed;border:1px solid #fed7aa;color:#9a3412;border-radius:10px;padding:var(--sp-x9) var(--sp-12);margin:var(--sp-10) 0;font-size:13px;line-height:1.5}
:root[data-theme="dark"] .ops-view .doc-body pre{background:rgba(255,255,255,.05)}
:root[data-theme="dark"] .ops-view .doc-body code{background:rgba(255,255,255,.09)}
:root[data-theme="dark"] .ops-view .doc-body .ops-cal{background:#2a1c0c;border-color:#7c4a12;color:#fbbf24}
/* Credentials vault (dev) */
.menu-ico.ic-devvault{background:#e9e7fb}
.vault-lock{max-width:420px;margin:var(--sp-8) auto;text-align:center;padding:var(--sp-8)}
.vault-lock-ico{width:56px;height:56px;border-radius:16px;display:flex;align-items:center;justify-content:center;margin:var(--sp-6) auto var(--sp-10);background:#eef2ff;color:#4f46e5}
:root[data-theme="dark"] .vault-lock-ico{background:rgba(99,102,241,.18);color:#a5b4fc}
.vault-lock h3{margin:var(--sp-6) 0}
.vault-lock p{color:var(--faint);font-size:14px;margin:var(--sp-6) 0;text-align:center}
.vault-lock .vault-warn{color:#9a3412;background:#fff7ed;border:1px solid #fed7aa;border-radius:10px;padding:var(--sp-8) var(--sp-10);font-size:13px}
:root[data-theme="dark"] .vault-lock .vault-warn{color:#fbbf24;background:#2a1c0c;border-color:#7c4a12}
.vault-lock .fb-form{margin-top:var(--sp-10);text-align:left}
.vault-err{color:#b91c1c;background:#fef2f2;border:1px solid #fecaca;border-radius:9px;padding:var(--sp-x7) var(--sp-10);font-size:13px;margin:var(--sp-2) 0}
:root[data-theme="dark"] .vault-err{color:#fca5a5;background:#2a1414;border-color:#7f1d1d}
.vault-bar{display:flex;flex-wrap:wrap;gap:var(--sp-8);margin:var(--sp-2) 0 var(--sp-12)}
.vault-list{display:flex;flex-direction:column;gap:var(--sp-10)}
.vault-item{border:1px solid var(--line);border-radius:14px;background:var(--surface);padding:var(--sp-12) var(--sp-14)}
.vault-item.has{box-shadow:0 3px 12px rgba(0,0,0,.05)}
.vault-head{display:flex;align-items:center;gap:var(--sp-12)}
.vault-label{flex:1;font-weight:600;font-size:15px}
.vault-item:nth-child(6n+1) .menu-ico{background:#eef2ff;color:#4f46e5}
.vault-item:nth-child(6n+2) .menu-ico{background:#ecfdf5;color:#059669}
.vault-item:nth-child(6n+3) .menu-ico{background:#fff7ed;color:#ea580c}
.vault-item:nth-child(6n+4) .menu-ico{background:#fdf2f8;color:#db2777}
.vault-item:nth-child(6n+5) .menu-ico{background:#eff6ff;color:#0284c7}
.vault-item:nth-child(6n) .menu-ico{background:#f5f3ff;color:#7c3aed}
:root[data-theme="dark"] .vault-item .menu-ico{background:rgba(255,255,255,.07) !important}
.vault-body{margin-top:var(--sp-10)}
.vault-dots{font-family:ui-monospace,Menlo,Consolas,monospace;letter-spacing:2px;color:var(--faint)}
.vault-empty{letter-spacing:normal;font-style:italic}
.vault-val{margin:0 0 var(--sp-6);padding:var(--sp-x9) var(--sp-x11);background:#f4f5f7;border:1px solid var(--line);border-radius:9px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;white-space:pre-wrap;word-break:break-word;color:var(--text)}
:root[data-theme="dark"] .vault-val{background:rgba(255,255,255,.05)}
.vault-notes{color:var(--faint);font-size:13px;margin:var(--sp-2) 0 var(--sp-6);white-space:pre-wrap}
.vault-acts{display:flex;flex-wrap:wrap;gap:var(--sp-6)}
.vault-none{color:var(--faint);font-size:14px;padding:var(--sp-6) var(--sp-2)}
.vault-add{margin:var(--sp-14) 0 var(--sp-6)}
.vault-foot{color:var(--faint);font-size:12.5px;margin-top:var(--sp-10)}
.vault-edit{border:1px solid var(--line);border-radius:14px;background:var(--surface);padding:var(--sp-14);margin-bottom:var(--sp-14)}
.vault-edit textarea{width:100%;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:13px}
.vault-back{margin-bottom:var(--sp-10)}
.vault-log{display:flex;flex-direction:column;gap:var(--sp-8)}
.vault-log-row{display:flex;justify-content:space-between;gap:var(--sp-10);align-items:baseline;border:1px solid var(--line);border-radius:10px;padding:var(--sp-x9) var(--sp-12);background:var(--surface);font-size:14px}
.vault-log-row.vault-log-fail{border-color:#fecaca;background:#fef2f2}
:root[data-theme="dark"] .vault-log-row.vault-log-fail{border-color:#7f1d1d;background:#2a1414}
.vault-log-act{font-weight:600}
.vault-log-meta{color:var(--faint);font-size:12.5px;text-align:right}
/* Affiliate split slider + dev affiliate rows */
.rf-split{border:1px solid var(--line);border-radius:14px;background:var(--surface);padding:var(--sp-14);margin:var(--sp-12) 0}
.rf-split-h{display:flex;align-items:center;gap:var(--sp-8);font-weight:600;font-size:15px}
.rf-split .rf-hint-sm{margin:var(--sp-6) 0 var(--sp-4)}
.rf-split-vals{display:flex;gap:var(--sp-10);margin:var(--sp-10) 0 var(--sp-6)}
.rf-split-v{flex:1;text-align:center;border-radius:12px;padding:var(--sp-10) var(--sp-6);background:#eff6ff}
.rf-split-v:last-child{background:#ecfdf5}
:root[data-theme="dark"] .rf-split-v{background:rgba(2,132,199,.15)}
:root[data-theme="dark"] .rf-split-v:last-child{background:rgba(5,150,105,.15)}
.rf-split-n{display:block;font-size:19px;font-weight:700;color:#0284c7}
.rf-split-comm{color:#059669}
.rf-split-l{display:block;font-size:12px;color:var(--faint);margin-top:var(--sp-2)}
.rf-range{width:100%;margin:var(--sp-8) 0 var(--sp-2);accent-color:#6366f1}
.rf-split-ends{display:flex;justify-content:space-between;font-size:11.5px;color:var(--faint);margin-bottom:var(--sp-10)}
.rfd-margin{margin:var(--sp-8) 0;padding:var(--sp-8) var(--sp-10);border-radius:9px;background:#f5f3ff;color:#4f46e5;font-size:13px}
:root[data-theme="dark"] .rfd-margin{background:rgba(99,102,241,.15);color:#a5b4fc}
/* Referral: sale price shown read-only (it's the same seat_price you set in Billing). */
.rfd-ro{display:flex;flex-direction:column;gap:var(--sp-x3);margin:var(--sp-2) 0 var(--sp-12)}
.rfd-ro-val{display:flex;align-items:baseline;gap:var(--sp-x9);flex-wrap:wrap}
.rfd-ro-val b{font-size:17px;font-weight:800;color:var(--text)}
.rfd-ro-hint{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:11.5px;color:var(--muted)}
.rfd-ro-hint svg{color:var(--muted)}
/* Billing: discount-code (coupon) card — the code now lives here, not in workspace settings. */
.bl-coupon-h{display:flex;align-items:center;gap:var(--sp-x7);font-weight:700;font-size:14px;color:var(--text);margin-bottom:var(--sp-8)}
.bl-coupon-h svg{color:#b83280}
.bl-coupon-note{font-size:12.5px;color:var(--muted);margin:0 0 var(--sp-10);line-height:1.5}
.bl-coupon-row{display:flex;gap:var(--sp-8)}
.bl-coupon-row input{flex:1;min-width:0;height:40px;border:1px solid var(--line);border-radius:10px;padding:0 var(--sp-12);background:var(--surface-2);color:var(--text);font:inherit;letter-spacing:.04em}
.bl-coupon-row input:focus{outline:none;border-color:var(--accent);background:var(--surface)}
.bl-coupon-row .btn{flex:none}
.rf-aff-split{font-size:12.5px;font-weight:600;color:#059669;white-space:nowrap;margin-left:var(--sp-6)}
:root[data-theme="dark"] .rf-aff-split{color:#5eead4}
.rf-aff-nocode{font-size:12px;color:var(--faint);font-style:italic;margin-left:var(--sp-6)}
.rf-aff-code{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;background:rgba(99,102,241,.12);color:#4f46e5;padding:var(--sp-1) var(--sp-x5);border-radius:5px}
:root[data-theme="dark"] .rf-aff-code{background:rgba(99,102,241,.2);color:#a5b4fc}

/* Bar kemajuan subtugas di baris tugas (permintaan klien 1 Agu). Angka "1/3" sudah ada;
   bar tipis membuat proporsinya terbaca sekilas tanpa membaca angkanya. Tanpa subtugas,
   tanpa bar — baris yang tak punya subtugas tidak boleh kelihatan seperti 0%. */
.tk-bar{display:block;height:3px;border-radius:2px;background:var(--line);overflow:hidden;margin-top:var(--sp-6)}
.tk-bar>span{display:block;height:100%;border-radius:2px;background:var(--good);transition:width .25s ease}

/* BUTIR 125 — dua unduhan berdampingan: Excel yang utama, CSV yang tetap ada.
   `flex:none` pada keduanya supaya baris ini tidak ikut menyusut di 360px; dan
   `white-space:nowrap` karena "Unduh laporan" pecah dua baris di lebar itu —
   pelajaran yang sama dengan label Tenggat di butir 118, satu rilis yang lalu.

   ==========================================================================
   BUTIR 128 — versi pertama aturan ini TAYANG DALAM KEADAAN RUSAK, dan pemilik
   yang melihatnya lebih dulu. Tiga cacat sekaligus, semuanya terukur:
   ==========================================================================
     tinggi   43px vs 29px. `.att-dl` membawa `margin:0 0 14px`, dan di dalam
              flex ber-`align-items:stretch` margin itu meregangkan SAUDARANYA
              setinggi tombol+margin. Jarak bawahnya milik BARISNYA, bukan milik
              satu tombol — jadi sekarang grupnya yang memegangnya.
     garis bawah  `.btn` tidak pernah mereset `text-decoration`. `.att-dl`
              mereset sendiri, jadi cacatnya tak pernah terlihat sampai ada
              <a class="btn"> KEDUA. Sekarang `.btn` yang meresetnya, supaya
              yang ketiga tidak perlu mengingatnya.
     ukuran   12px di sebelah 13px milik `.btn-sm`. Dua ukuran huruf dalam satu
              kendali terbaca sebagai dua jenis benda, bukan dua pilihan.

   Kenapa lolos: saya MEMBACA CSS-nya dan tidak pernah MERENDER-nya. Pagi yang
   sama saya menulis "satu pembaca bukan verifikasi" untuk berkas Excel, lalu
   mengirim tombol yang tidak pernah saya buka sama sekali. */
.att-dl-grup{display:inline-flex;align-items:stretch;gap:var(--sp-8);flex:none;vertical-align:top;margin:0 0 var(--sp-14)}
.att-dl-grup>.btn{flex:none;white-space:nowrap;margin:0;text-decoration:none}
/* Latar SURFACE, bukan transparan: di atas panel, transparan membuatnya terbaca
   seperti kotak kosong yang kebetulan berbingkai, bukan tombol yang bisa ditekan. */
.att-dl-csv{background:var(--surface);border:1px solid var(--line);color:var(--muted);font-size:13px}
.att-dl-csv:hover{color:var(--accent-text);border-color:var(--accent);background:var(--accent-weak)}

/* BUTIR 129 — SATU BARIS FAKTA untuk seluruh rentang, pengganti tanda "+N lembur" per hari.
   Yang dicabut: .att-t-ot, lencana beraksen di setiap kartu hari.

   Nadanya sengaja NETRAL — garis + surface, teks --muted, tanpa satu pun warna status.
   Lencana lama memakai --accent-weak/--accent-text, dan aksen adalah warna yang dipakai
   aplikasi ini untuk "perhatikan ini". Rata-rata bukan kabar baik dan bukan kabar buruk;
   memberinya warna berarti menilainya, dan penilaian itulah yang hari ini dicabut.

   Ikonnya tetap berwarna (--accent): yang tidak boleh menilai adalah ANGKANYA, bukan
   penanda barisnya. */
.att-rata{display:flex;align-items:flex-start;gap:var(--sp-x9);padding:var(--sp-x9) var(--sp-12);margin:0 0 var(--sp-14);border:1px solid var(--line);border-radius:12px;background:var(--surface);font-size:13px;line-height:1.5;color:var(--muted)}
/* flex-start + 2px, BUKAN center. Di 360px kalimatnya membungkus jadi dua baris dan kepingnya
   turun ke baris ketiga; dengan `center` ikonnya berhenti di tengah tumpukan itu — berdiri di
   sebelah kata "lengkap" alih-alih di awal kalimatnya, dan terbaca seperti ikon yang lepas dari
   teksnya. 2px menaruhnya optis di tengah baris PERTAMA (kotak baris 19,5px, ikon 15px).
   Ditemukan dengan MELIHAT tangkapan layarnya di 360, bukan dengan membaca aturannya. */
.att-rata>svg{flex:none;margin-top:var(--sp-2);color:var(--accent)}
/* Membungkus supaya angka kedua ikut membungkus di bawah kalimatnya pada lebar 360, bukan
   memaksa barisnya melebar dan menggeser tata letaknya. */
.att-rata>span{min-width:0;display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--sp-8)}
/* BUTIR 134 — KUALIFIKASI, bukan keterangan tambahan.
   Nadanya sengaja LEBIH REDUP daripada angkanya dan berdiri di baris sendiri: ia bukan nilai
   kedua yang bersaing dengan rata-rata, ia syarat baca angka pertama. Tapi ia TIDAK boleh
   jadi sekecil catatan kaki — orang yang salah membacanya justru orang yang tidak sedang
   mencarinya, jadi ia harus terbaca tanpa diklik dan tanpa dicari.
   display:block memaksanya turun ke baris sendiri di lebar mana pun; di 360 kalimat
   rata-ratanya sudah dua baris, dan menempelkan kualifikasi ini di ekornya membuat keduanya
   terbaca sebagai satu kalimat panjang yang tidak jelas mana subjeknya. */
.att-rata-q{display:block;width:100%;font-size:12px;color:var(--muted);line-height:1.5;margin-top:var(--sp-1)}

/* ============================================================================
   BUTIR 138A — KARTU iPaymu (layar tempel kunci, panel Developer)
   ============================================================================
   Kartu ini dilihat SEKALI oleh satu orang, sambil memegang dasbor iPaymu di jendela
   lain. Jadi yang diutamakan bukan kepadatan melainkan KEJELASAN URUTAN: keadaan dulu,
   baru mode, baru dua kotak, baru dua tombol, baru jawabannya. Orang yang menempel kunci
   sedang membandingkan dua layar; setiap langkah yang harus ia cari adalah langkah yang
   bisa ia lewati.

   Diperiksa dengan dirender, bukan dibayangkan (aturan tim 9 Agu): 360, 400, dan 1386.
   Di 360 tidak ada yang terpotong dan tidak ada yang perlu digeser mendatar. */
.ip-state{display:flex;align-items:flex-start;gap:var(--sp-x9);padding:var(--sp-x11) var(--sp-x13);border-radius:12px;margin-bottom:var(--sp-x13);font-size:13px;line-height:1.55}
/* Ikonnya TIDAK boleh mewarisi .empty>svg (app.css: bulat besar, rata tengah). Karena itu
   kelas keadaannya berawalan ip-, dan ukurannya tetap dipatok di sini sebagai sabuk kedua. */
.ip-state svg{flex:0 0 auto;margin-top:var(--sp-1);width:16px;height:16px;padding:0;background:none;border-radius:0}
/* Tiga keadaan, tiga warna, dan warnanya menyebut sebab — bukan sekadar bagus/jelek.
   Belum-dipasang itu NETRAL, bukan merah: tidak ada yang rusak, fitur lamanya jalan. */
.ip-state.ip-ok{background:#17724514;color:#0f5132;border:1px solid #17724530}
.ip-state.ip-half{background:#b4530914;color:#8a3d05;border:1px solid #b4530930}
.ip-state.ip-empty{background:var(--surface-2,#f6f7f9);color:var(--muted);border:1px solid var(--line)}
[data-theme=dark] .ip-state.ip-ok{background:#17724524;color:#7ee2b0;border-color:#17724545}
[data-theme=dark] .ip-state.ip-half{background:#b4530924;color:#f3c48b;border-color:#b4530945}

.ip-mode{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-12);flex-wrap:wrap;margin-top:var(--sp-2)}
.ip-mode-lbl{font-size:13px;font-weight:600}
.ip-seg{display:inline-flex;background:var(--surface-2,#f1f2f5);border-radius:10px;padding:var(--sp-x3);gap:var(--sp-2)}
.ip-seg-b{border:0;background:transparent;color:var(--muted);font:inherit;font-size:13px;font-weight:600;
  padding:var(--sp-6) var(--sp-14);border-radius:8px;cursor:pointer;transition:background .16s,color .16s,box-shadow .16s}
.ip-seg-b.on{background:var(--surface);color:var(--text);box-shadow:0 1px 3px #0000001f}
/* Produksi menyala MERAH saat terpilih. Kunci sandbox di mode produksi menghasilkan 401
   yang bunyinya persis sama dengan kunci salah ketik — warna ini satu-satunya hal di layar
   yang membedakan keduanya sebelum tombol Uji ditekan. */
.ip-seg-b.danger.on{background:#b91c1c;color:#fff}
.ip-mode-note{font-size:12px;color:var(--muted);line-height:1.55;margin:var(--sp-x7) 0 var(--sp-x3)}
/* .bl-field dirancang untuk ANGKA PENDEK — harga, jumlah hari: lebar 150px, rata kanan.
   Nomor VA 16 digit dan API Key panjang TIDAK muat di dalamnya, dan medan yang memotong
   nilainya adalah medan yang membuat orang tidak bisa memeriksa apa yang baru ia tempel —
   padahal memeriksa itulah satu-satunya alasan nilainya ditampilkan kembali. Ketahuan
   karena dirender di 360, bukan karena dibaca. Jadi medan iPaymu bertumpuk dan selebar
   kartunya. */
.ip-f{flex-direction:column;align-items:stretch;gap:var(--sp-6)}
.ip-f>span{font-weight:600;color:var(--text)}
.ip-f input{width:100%;text-align:left;font-variant-numeric:tabular-nums;letter-spacing:.02em}
.ip-note{display:flex;align-items:flex-start;gap:var(--sp-6);font-size:12px;color:var(--muted);line-height:1.55;margin:var(--sp-2) 0 var(--sp-4)}
.ip-note svg{flex:0 0 auto;margin-top:var(--sp-x3)}

/* Tombol membungkus ke bawah, tidak pernah mengecil sampai labelnya terpotong: di 360
   "Uji koneksi" dan "Simpan kunci" tidak muat bersebelahan, dan tombol yang labelnya
   terpotong adalah tombol yang orang ragu menekannya. */
.ip-actions{display:flex;flex-wrap:wrap;gap:var(--sp-x9);align-items:center;margin-top:var(--sp-4)}
/* TOMBOL MATI HARUS TERLIHAT MATI. app.css tidak punya aturan .btn[disabled] umum — hanya
   satu yang khusus panel go-live — jadi tanpa baris ini "Uji koneksi" terlihat persis sama
   saat ia bisa ditekan dan saat ia tidak. Tombol yang terlihat hidup lalu tidak menjawab
   membuat orang mengira aplikasinya rusak, padahal ia cuma sedang jujur bahwa kuncinya
   belum lengkap. */
.ip-actions .btn[disabled]{opacity:.45;cursor:not-allowed}
.ip-clear{border:0;background:transparent;color:#b91c1c;font:inherit;font-size:13px;font-weight:600;
  padding:var(--sp-8) var(--sp-4);cursor:pointer;text-decoration:underline;text-underline-offset:3px}
.ip-clear:hover{opacity:.8}

/* Jawaban uji ditulis DI SINI, bukan sebagai toast: toast hilang sendiri, dan kalimat yang
   menjelaskan kenapa kunci ditolak perlu dibaca dua kali sambil melihat dasbor iPaymu. */
.ip-result{font-size:13px;line-height:1.6;border-radius:11px;margin-top:var(--sp-2)}
.ip-result:empty{display:none}
.ip-result:not(:empty){padding:var(--sp-10) var(--sp-12);border:1px solid transparent}
.ip-result.busy{background:var(--surface-2,#f6f7f9);color:var(--muted);border-color:var(--line)}
.ip-result.ok{background:#17724514;color:#0f5132;border-color:#17724530}
.ip-result.bad{background:#b91c1c12;color:#991b1b;border-color:#b91c1c2e}
.ip-result.warn{background:#b4530912;color:#8a3d05;border-color:#b453092e}
[data-theme=dark] .ip-result.ok{background:#17724524;color:#7ee2b0}
[data-theme=dark] .ip-result.bad{background:#b91c1c22;color:#fca5a5}
[data-theme=dark] .ip-result.warn{background:#b4530922;color:#f3c48b}
.ip-foot{font-size:12px;color:var(--muted);line-height:1.6;margin:var(--sp-12) 0 0;padding-top:var(--sp-x11);border-top:1px solid var(--line)}

/* BUTIR 138A langkah 2 — pemisah antara "bayar sekarang" dan "transfer manual".
   Garis dengan kata di tengahnya, bukan sekadar jarak: dua tombol bayar yang berdiri
   berdekatan tanpa penjelasan membuat orang berhenti untuk menebak bedanya, dan berhenti
   di layar pembayaran adalah berhenti yang paling mahal di seluruh aplikasi. */
.bl-or{display:flex;align-items:center;gap:var(--sp-10);margin:var(--sp-12) 0 var(--sp-10);color:var(--faint);font-size:12px}
.bl-or::before,.bl-or::after{content:"";flex:1 1 auto;height:1px;background:var(--line)}
.bl-or span{flex:0 0 auto}

/* =============================================================================================
   ABSENSI — CHIP "N SESI" DAN "1 KETUKAN · TANPA PASANGAN"
   =============================================================================================
   Lahir 20 Agustus 2026 bersama laporan absensi berbasis HARI.

   Keduanya sengaja TIDAK merah. Yang dilaporkan bukan kesalahan orangnya: sebuah hari boleh
   punya dua sesi, dan sebuah ketukan tunggal mungkin memang begitu keadaannya. Merah akan
   menuduh; yang dibutuhkan cuma memberi tahu bahwa ada sesuatu di balik baris itu.

     >> Warna yang menuduh membuat orang berhenti membaca isinya dan mulai membela diri.

   Bentuknya MENIRU .att-pend-tag apa adanya — skala jarak yang sama, radius yang sama, berat
   huruf yang sama. Pil yang dibuat sendiri dengan angka sendiri akan berbeda tinggi dari
   tetangganya di baris yang sama, dan bedanya baru terlihat di layar orang lain.

   `att-solo-tag` sedikit lebih hangat daripada `att-sesi-tag` karena ia yang layak dibuka
   lebih dulu: hari itu tidak punya angka jam kerja sama sekali.

   SATU HAL YANG SENGAJA BERBEDA dari .att-pend-tag: `gap` memakai --sp-4, bukan --sp-x3.
   Tingkat kedua skala jarak (--sp-x*) punya ratchet yang HANYA BOLEH MENGECIL, dan memakainya
   di aturan baru akan menaikkannya. Bedanya satu piksel, di celah yang pil ini bahkan tidak
   punya ikon untuk mengisinya.
     >> Ratchet yang dilanggar "cuma sekali, cuma satu piksel" berhenti jadi ratchet.
   ============================================================================================= */
.att-sesi-tag,.att-solo-tag{display:inline-flex;align-items:center;gap:var(--sp-4);font-size:11px;font-weight:700;padding:var(--sp-2) var(--sp-8);border-radius:20px;max-width:100%;min-width:0;white-space:normal;overflow-wrap:anywhere}
.att-sesi-tag{color:#3730a3;background:#eef1fd}
.att-solo-tag{color:#94590f;background:#fdf0e2}
:root[data-theme="dark"] .att-sesi-tag{background:rgba(67,56,202,.26);color:#c3c9f5}
:root[data-theme="dark"] .att-solo-tag{background:rgba(164,102,26,.26);color:#e6c193}

/* Angka "N hari tidak lengkap tidak ikut dihitung" — sebuah TOMBOL, bukan keterangan.
   Perintah AD 20 Agu 2026: angka yang dibuang sebuah rata-rata harus bisa ditekan, sebab
   memberi tahu orang bahwa sembilan hari tidak ikut lalu membiarkannya mencari sendiri
   adalah memberi tahu setengah.
     >> Angka yang menyebutkan pengecualian tanpa jalan menuju pengecualiannya memindahkan
        pekerjaan ke pembacanya, dan pembacanya tidak punya daftarnya. */
.att-rata-x{display:inline;padding:0;border:0;background:none;color:var(--accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}
.att-rata-x:hover{text-decoration-thickness:2px}

/* TUGAS-160 — jalan bagi orang yang datang dengan KODE, di kaki formulir buat-workspace.
   Sengaja tenang: ia bukan ajakan, ia jalan keluar bagi orang yang salah pintu. Sebuah tombol
   sekunder yang seramai tombol utamanya membuat orang berhenti dan menimbang ulang keputusan
   yang baru saja ia ambil.
     >> Pilihan kedua yang ditawarkan sekeras pilihan pertama mengubah sebuah formulir jadi
        sebuah pertanyaan. */
.ws-join-alt{margin-top:var(--sp-16);text-align:center}
.ws-join-link{padding:var(--sp-6);border:0;background:none;color:var(--muted);font:inherit;font-size:14px;text-decoration:underline;text-underline-offset:2px;cursor:pointer}
.ws-join-link:hover{color:var(--text)}
