/* Dipakai bersama index.php & laporan.php — dulu token warna dan chrome
   ini ditulis dua kali di index.html dan laporan.html. */
:root {
  --bg: #262624;
  --bg-card: #30302e;
  --bg-hover: #3a3a37;
  --border: #4a4a46;
  --text: #e5e5e2;
  --text-dim: #a8a89f;
  --accent: #d97757;
  --accent-soft: rgba(217, 119, 87, 0.12);
  /* Hijau kontrol admin (edit/hapus/susun ulang) — beda warna dari --accent
     oranye supaya jelas mana kontrol yang cuma muncul untuk sesi admin,
     dan beda juga dari --done supaya tidak dibaca sebagai status "Selesai". */
  --accent-admin: #57d96d;
  --accent-admin-soft: rgba(87, 217, 109, 0.14);
  /* Hijau "Selesai". #0ca30c, bukan #7ba05b yang dulu: yang lama berjarak
     ΔE 10.1 dari abu "Belum Mulai" — di bawah ambang 15, jadi dua status
     paling sering muncul itu susah dibedakan bahkan dengan penglihatan
     warna normal. Yang ini ΔE 20.1, kontras 4.52:1. */
  --done: #0ca30c;
  /* Kuning "Berjalan". Dulu status ini memakai --accent — warna yang SAMA
     dengan brand, link, dan progress bar — jadi "sedang dikerjakan" tidak
     terbaca sebagai status, melainkan sebagai hiasan halaman. Sekarang punya
     warnanya sendiri: kontras 8.26:1, dan ΔE 11.3 dari hijau (aman untuk
     buta warna deutan/protan). */
  --berjalan: #3987e5;
  --belum: #8a8a84;
  /* Sorotan "halaman yang sedang dibuka". SENGAJA merah, bukan --accent:
     oranye sudah berarti status "Berjalan", jadi kalau sorotannya juga oranye,
     tahap "Belum Mulai" yang sedang dibuka kelihatan seperti sedang berjalan. */
  --fokus: #ef4444;
}

/* ---- Warna per status, SATU sumber ----------------------------------------
   Tiap status menaruh warnanya di --st, lalu titik, bar Gantt, dan kotak status
   sama-sama membacanya. Jadi bentuknya satu template yang sama untuk semua
   status, dan menambah status = tambah satu baris di sini (plus di config.php).
   Kelas .status-* dipakai baris chart, .s-* dipakai kotak status di kartu. */
.status-selesai,     .s-selesai     { --st: var(--done); }
.status-berjalan,    .s-berjalan    { --st: var(--berjalan); }
.status-belum-mulai, .s-belum-mulai { --st: var(--belum); }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  background: var(--bg); color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  line-height: 1.65; padding: 40px 20px 80px;
  /* Pengaman terakhir: SATU kata/link/nama task panjang tanpa spasi yang lolos
     dari overflow-wrap di tempat asalnya tidak akan mendorong seluruh halaman
     melebar ke samping (yang di HP artinya seluruh isi bisa digeser horizontal,
     bukan cuma elemen itu sendiri). Tidak menggantikan overflow-wrap di
     tempatnya masing-masing (itu yang membuat teksnya masih terbaca, bukan
     kepotong) — ini cuma jaring terakhir. */
  overflow-x: hidden;
}
/* 1600px, bukan 900: portal ini hampir selalu dibuka di laptop, dan yang paling
   diuntungkan lebarnya adalah chart Gantt — makin lebar makin banyak rentang
   tanggal yang muat tanpa scroll horizontal. 'max-width' (bukan width) jadi di
   layar sempit ia tetap menyusut sendiri; tidak ada media query baru yang perlu. */
.wrap { max-width: 1600px; margin: 0 auto; }

/* HANYA layar masuk. Ia cuma berisi satu tombol Google — dibentangkan 1600px
   yang ada malah tombol kecil melayang di tengah padang kosong.

   Dulu kelas ini juga dipakai halaman task, halaman laporan, dan panel root —
   jadi waktu .wrap dilebarkan ke 1600px, tiga halaman itu diam-diam tetap 760px
   dan terlihat seperti layout-nya balik sendiri begitu satu task dibuka.
   Sekarang ketiganya memakai .wrap biasa, dan kelas ini tinggal milik layar
   masuk saja. Kalau menambah halaman baru: pakai .wrap, kecuali halaman itu
   memang cuma berisi satu form pendek. */
.wrap.narrow { max-width: 760px; }
.brand { font-size: 13px; letter-spacing: 2px; text-transform: uppercase; color: var(--accent); margin-bottom: 8px; }
/* Baris "siapa yang sedang masuk" + Keluar, tepat di bawah .brand. Sengaja
   redup: ini informasi layanan, bukan bagian dari isi portal. */
.sesi { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; }
.sesi a { color: var(--text-dim); margin-left: 8px; }
.sesi a:hover { color: var(--accent); }
h1 { font-size: clamp(26px, 5vw, 38px); font-weight: 700; letter-spacing: -0.5px; margin-bottom: 4px; }
.subtitle { color: var(--text-dim); font-size: 15px; margin-bottom: 8px; }
/* Rentang tanggal di kepala halaman task. Angkanya rata lebar (tabular-nums)
   supaya dua tanggal di kiri-kanan tanda pisah tidak goyang. */
.rentang {
  color: var(--accent); font-size: 14px; font-weight: 600;
  font-variant-numeric: tabular-nums; margin-bottom: 12px;
}

/* Baris rentang + tombol edit (admin): tombolnya duduk tepat di samping kanan
   tanggal, dipisah jarak — bukan di baris sendiri. Kalau ia memakai satu baris
   penuh, breadcrumb dan seluruh isi di bawahnya ikut turun, jadi tampilan admin
   beda dari tampilan klien.
   flex-wrap: di layar sempit tombolnya boleh turun baris daripada memepet. */
.rentang-baris {
  /* baseline, BUKAN center: begitu formnya dibuka, kotak edit jadi tinggi, dan
     'center' akan menyeret tanggalnya ikut turun ke tengah form. Dengan
     baseline, tanggal tetap sebaris dengan tulisan tombolnya. */
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 6px 14px; margin-bottom: 12px;
}
.rentang-baris .rentang { margin-bottom: 0; }
.edit-rentang { margin-top: 0; }
.progress-line { display: flex; align-items: center; gap: 12px; margin: 18px 0 40px; }
.progress-bar { flex: 1; height: 8px; background: var(--bg-card); border-radius: 99px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--accent); border-radius: 99px; transition: width .6s ease; }
.progress-pct { font-weight: 700; color: var(--accent); font-size: 14px; min-width: 40px; }
h2 { font-size: 20px; font-weight: 600; margin: 44px 0 14px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
p.note { color: var(--text-dim); font-size: 14px; margin-bottom: 14px; }
/* Link di dalam .note (mis. "Buka di Drive") dulu tidak punya warna sendiri,
   jadi jatuh ke biru/ungu bawaan browser — satu-satunya link di portal ini
   yang tidak memakai --accent. Sama polanya dengan .back di bawah. */
p.note a { color: var(--accent); text-decoration: none; }
p.note a:hover { text-decoration: underline; }

/* ---- Gantt container ----
   Layout 2 kolom: nama tahapan (kiri, tetap) + timeline (kanan, bisa di-scroll).
   Scroll horizontal ditangani .gantt-container bawaan Frappe, bukan #gantt-wrap. */
#gantt-wrap { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px;
  padding: 12px; display: flex; align-items: flex-start; position: relative; }
#gantt { min-height: 120px; }
#gantt-wrap .gantt-container { flex: 1 1 auto; min-width: 0; }

/* Tinggi baris & chart HARUS sama. Arahnya sekarang terbalik dari dulu: CSS
   yang memutuskan (--gn-row-h di bawah), app.js membacanya lalu mengirim
   padding = --gn-row-h - bar_height ke Frappe. Jadi tidak ada lagi dua angka
   yang harus diubah bersamaan.
   Header beda urusan dan masih tetap: header_height 50 + 10 = 60px (.gn-head). */
/* Lebar kolom nama IKUT lebar layar, tidak lagi dipatok 190px.

   190px itu angka mati: di layar 1745px ia memaksa nama terpanjang (terukur
   337px) membungkus jadi dua baris — dan karena tinggi baris harus seragam,
   SELURUH baris ikut jadi 52px — padahal 1384px di sebelahnya menganggur.
   Kolomnya melar sampai muat, chart tetap kebagian sisanya.

   clamp: 220px lantai (jangan lebih sempit dari sebelumnya di layar sedang),
   50% mengikuti layar, 720px langit-langit. Di bawah 600px, media query di
   bawah menang dan mengembalikannya ke 120px — di sana chart yang lebih butuh
   ruang.

   50%, naik dari 30%: nama task di portal ini panjang-panjang ("OPTIMASI SEO
   SEBELUM WEBSITE DIDAFTARKAN") dan di 30% mereka terpotong sudah sejak
   tingkat SATU — ellipsis persis di tempat yang membedakan satu task dari
   task lain. Indentasi yang sekarang lebih lebar (--gn-inden) memakan ruang
   itu lagi, jadi dua-duanya harus naik bersama; menaikkan indentasi saja cuma
   memindahkan potongannya lebih ke kiri.

   Setengah untuk nama, setengah untuk chart. Chart masih kebagian ratusan
   piksel di layar biasa, dan yang dibaca duluan di timeline ini memang nama
   task-nya — barnya menyusul. */
#gantt-names { flex: 0 0 clamp(220px, 50%, 720px); }
/* Pojok kiri atas — sejajar dengan header tanggal di sebelah kanan. Dua baris
   (label + rentang aktif) DITUMPUK di dalam tinggi yang SAMA (60px) — bukan
   ditambah tingginya — supaya tetap sinkron dengan header_height + 10 di atas.
   Kalau ANGKA 60px ini berubah, header_height di app.js harus ikut berubah. */
#gantt-names .gn-head {
  /* 60px = header_height + 10 milik Frappe. --gn-geser dipasang app.js SESUDAH
     chart digambar: Frappe menaruh bar pertama di (header_height + padding),
     sedangkan baris nama mulai tepat di bawah header ini — selisihnya
     padding/2 - 10, yang kebetulan ~0 pada padding bawaan 18 tapi jadi 6px
     begitu barisnya ditinggikan. Diukur, bukan dihitung dari rumus dalaman
     pustaka: kalau versi Frappe-nya berganti rumus, pengukuran tetap benar. */
  height: calc(60px + var(--gn-geser, 0px)); display: flex; flex-direction: column; justify-content: center;
  align-items: flex-start; gap: 2px;                          /* = header_height + 10 */
}
#gantt-names .gn-head-label {
  font-size: 13px; font-weight: 600; letter-spacing: .5px;
  color: var(--text-dim); text-transform: uppercase;
}
/* Rentang aktif: lebih kecil & lebih redup daripada labelnya, sama pola dengan
   .t-meta di bawah nama task (kartu "Task Hari Ini") — bukan warna baru. */
#gantt-names .gn-head-tanggal {
  font-size: 11px; color: var(--text-dim); opacity: .8;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
#gantt-names .gn-row {
  /* Tingginya dari variabel, dan app.js MEMBACANYA dari sini lalu menyetel
     padding Frappe supaya bar ikut setinggi itu. Jangan tulis angkanya lagi di
     JS — pelajaran yang sama dengan --gn-inden. */
  height: var(--gn-row-h); display: flex; align-items: center; gap: 8px;
  border-bottom: 1px solid var(--border); font-size: 13px;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  color: var(--text); text-decoration: none;                 /* row = link ke halaman tahap */
  transition: color .15s;
}
#gantt-names a.gn-row:hover { color: var(--accent); }

/* ---- Dua baris OTOMATIS ---------------------------------------------------
   Dipasang app.js kalau ADA SATU SAJA nama yang tidak muat satu baris di lebar
   kolom saat ini. Sengaja semua-atau-tidak: bar di chart digambar Frappe pada
   posisi (urutan × tinggi baris) yang seragam, jadi tinggi yang berbeda-beda
   per baris akan membuat nama dan bar melenceng mulai dari baris pertama yang
   lebih tinggi. Kalau satu butuh dua baris, semua ikut dua baris.

   52px = dua baris 13px (line-height 1.25 ≈ 16px) + ruang napas, dan app.js
   mengirim padding = 52 - bar_height ke Frappe supaya barisnya sama persis. */
#gantt-names.gn-2baris { --gn-row-h: 52px; }
#gantt-names.gn-2baris .gn-row { white-space: normal; }
#gantt-names.gn-2baris .gn-name {
  white-space: normal; line-height: 1.25;
  /* line-clamp: nama yang bahkan dua baris pun tidak cukup tetap dipotong rapi
     dengan elipsis, bukan meluber keluar baris dan menabrak baris di bawahnya. */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
#gantt-names .gn-name { overflow: hidden; text-overflow: ellipsis; }
#gantt-names .gn-row.undated .gn-name { color: var(--text-dim); font-style: italic; }
/* Titik status per baris SUDAH DIHAPUS. Alasannya ruang: di ponsel kolom ini
   cuma 120px, dan titik + gap memakan 16px (~13%) untuk informasi yang sudah
   disampaikan WARNA NAMA (.status-selesai/.status-berjalan/.undated di bawah).
   Di baris dalam makin mahal — level 3 sudah kena indent 42px, jadi 58px dari
   120px habis sebelum satu huruf nama pun muncul. Hirarki tetap terbaca dari
   tebal + KAPITAL (.gn-induk) dan indentasi (.gn-l*). */

/* Hirarki: baris induk = judul grup (tebal, huruf kecil-kapital), anaknya
   menjorok ke dalam. Ditandai dari kolom No di CSV ("2" induk, "2.1" anak),
   jadi tidak ada penanda apa pun yang perlu ditulis di nama tahap.
   Induk = baris yang PUNYA anak, jadi "2.1" di hirarki tiga level tetap tampil
   sebagai grup walau ia sendiri menjorok. */
#gantt-names .gn-row.gn-induk {
  font-weight: 700; font-size: 12px; letter-spacing: .4px; text-transform: uppercase;
}
/* Indentasi per kedalaman. HANYA padding-left: baris di kolom ini disejajarkan
   1:1 dengan baris bar di SVG, jadi tinggi/padding-vertikal/line-height/ukuran
   font TIDAK boleh ikut berubah — begitu berubah, nama dan barnya meleset. */
/* SATU angka untuk indentasi, dan app.js MEMBACANYA dari sini lewat
   getComputedStyle — dulu 14px ditulis dua kali (di sini dan sebagai `const
   INDEN` di app.js) dengan komentar "samakan dengan .gn-l*". Dua tempat yang
   harus diubah bersamaan itu jebakan: yang lupa disamakan tidak bikin error,
   cuma bikin seret-jadikan-sub-task meleset satu tingkat.

   7px = kira-kira selebar SATU karakter pada font baris ini (13px) — jadi tiap
   tingkat semahal satu huruf, bukan dua. Alasannya ruang ponsel: kolom ini
   120px di sana, dan indentasi 14px/tingkat menghabiskan 42px di level 3 —
   sepertiga kolom — sebelum satu huruf nama pun muncul. Dengan 7px, level 3
   cuma 21px, dan level 6 (42px) masih sama dengan biaya level 3 yang LAMA.

   Ditulis px, bukan 1ch, dengan alasan yang konkret: custom property dibaca
   app.js lewat getComputedStyle, dan nilai custom property dikembalikan apa
   adanya sebagai token ("1ch") — bukan hasil hitungan piksel. parseFloat("1ch")
   = 1, jadi seret-jadikan-sub-task akan meleset jauh. Mengukurnya lewat elemen
   bayangan bisa, tapi menambah kerapuhan persis di bagian yang aturan ini
   justru dibuat untuk menyederhanakan.

   DUA NILAI, bukan satu: 24px di layar lebar, 7px di ponsel (lihat @media
   (max-width: 600px) di bawah). Angka 7px lahir dari batasan PONSEL — kolom
   nama cuma 120px di sana, jadi tiap tingkat harus semurah satu huruf. Tapi
   batasan itu ikut terbawa ke layar 1400px, tempat kolomnya bisa 420px dan
   hirarki tiga tingkat jadi nyaris rata kiri: hemat yang tidak menghemat apa
   pun, cuma bikin susunan lebih susah dibaca.

   app.js tidak perlu tahu ada dua nilai: ia membaca --gn-inden lewat
   getComputedStyle SETIAP KALI seretan dimulai, jadi yang dibacanya selalu
   nilai yang sedang berlaku di lebar layar saat itu. */
/* min-width:0 WAJIB, dan ini akar Bug 4 — bukan bug scroll, tapi bug tata letak.

   Flex item bawaannya min-width:auto, artinya "jangan menyusut di bawah lebar
   minimum isimu". Karena .gn-row memakai white-space:nowrap, lebar minimum itu
   = nama tahap TERPANJANG. Nilai itu mengalahkan flex-basis:120px di bawah,
   jadi di ponsel kolom nama membengkak (terukur 354px di layar 400px) sampai
   LEBIH LEBAR dari #gantt-wrap sendiri — dan .gantt-container tersisa 0px.

   Chart selebar nol memang tidak bisa digulir: bukan gulirannya yang rusak,
   memang tidak ada apa pun untuk digeser. Dengan min-width:0, flex-basis
   dihormati, ellipsis di .gn-name mulai bekerja, dan sisa lebarnya kembali ke
   chart (120px nama + ~201px chart ≈ 5 kolom tanggal, dari sebelumnya 1).

   .gantt-container sudah punya min-width:0 sejak dulu (lihat di atas); yang
   tertinggal justru kolom ini. */
#gantt-names { --gn-inden: 24px; min-width: 0; --gn-row-h: 38px; }
#gantt-names .gn-row.gn-l1 { padding-left: calc(1 * var(--gn-inden)); }
#gantt-names .gn-row.gn-l2 { padding-left: calc(2 * var(--gn-inden)); }
#gantt-names .gn-row.gn-l3 { padding-left: calc(3 * var(--gn-inden)); }
#gantt-names .gn-row.gn-l4 { padding-left: calc(4 * var(--gn-inden)); }
/* Kedalaman TIDAK dibatasi kode (level = jumlah titik di kolom No), tapi CSS-nya
   harus ada per tingkat. Dulu berhenti di .gn-l4: baris tingkat 5 digambar rata
   kiri, jadi terbaca seperti task top-level padahal ia anak. Dua baris ini yang
   menutup itu. Lewat tingkat 3 nama mulai terpotong (kolomnya 190px, 120px di
   ponsel), jadi 1.1.1 tetap kedalaman yang paling enak dipakai. */
#gantt-names .gn-row.gn-l5 { padding-left: calc(5 * var(--gn-inden)); }
#gantt-names .gn-row.gn-l6 { padding-left: calc(6 * var(--gn-inden)); }
/* Tahap yang sudah SELESAI diredupkan: urutannya sudah turun ke bawah
   (wl_urut_selesai), dan diredupkan supaya pekerjaan yang belum kelar yang
   menonjol. Titik statusnya sengaja TIDAK ikut redup — warna hijaunya justru
   penanda "beres". */
/* Hijau, BUKAN diredupkan. Dulu nama tahap yang selesai dijadikan abu supaya
   mundur ke belakang — tapi hasilnya cuma titik kecil yang hijau, sementara
   teks yang justru dibaca duluan tetap abu, sama persis dengan "Belum Mulai".
   Sekarang seluruh barisnya sewarna: titik, nama, dan bar. */
#gantt-names .gn-row.status-selesai .gn-name { color: var(--done); }
/* Pasangannya untuk "Berjalan". Tanpa baris ini, tahap yang sedang berjalan cuma
   dapat titik 8px berwarna di samping teks abu — warna birunya praktis tidak
   terlihat, sementara baris "Selesai" di atas terbaca satu warna penuh. Jadi
   yang paling perlu menonjol (yang sedang dikerjakan) justru paling samar. */
#gantt-names .gn-row.status-berjalan .gn-name { color: var(--berjalan); }
/* Bar "Selesai" TIDAK diredupkan lagi. opacity .5 dulu dipasang supaya
   pekerjaan yang belum kelar yang menonjol, tapi efek sampingnya: hijau
   setengah transparan di atas permukaan gelap jatuh ke abu-abu — persis
   warna "Belum Mulai". Yang mau dibedakan malah jadi sama. Nama tahap di
   kolom kiri ikut hijau (lihat .gn-row.status-selesai .gn-name), jadi satu
   baris selesai terbaca sebagai satu kesatuan warna. */
.gantt .bar-wrapper.status-selesai { opacity: 1; }

/* Tahap yang halamannya sedang dibuka. MERAH (--fokus), bukan oranye: oranye
   sudah dipakai status "Berjalan", jadi sorotan oranye membuat tahap yang belum
   mulai terlihat seperti sedang berjalan. Merah tidak dipakai status mana pun,
   jadi artinya cuma satu — "ini halaman yang sedang kamu buka".
   Titik & bar tetap memakai warna statusnya sendiri. */
#gantt-names .gn-row.gn-fokus {
  background: color-mix(in srgb, var(--fokus) 14%, transparent);
  box-shadow: inset 4px 0 0 var(--fokus);
}
#gantt-names .gn-row.gn-fokus .gn-name { color: var(--fokus); font-weight: 700; }
/* Menang atas peredupan Selesai: tahap yang sedang dibuka tetap jelas walaupun
   statusnya sudah selesai. */
#gantt-names .gn-row.gn-fokus.status-selesai .gn-name { color: var(--fokus); }
.gantt .bar-wrapper.fokus { opacity: 1; }

/* ---- Susun ulang lewat seret & jatuhkan (admin) ---------------------------
   position:relative supaya garis penanda bisa ditempatkan relatif ke kolom. */
#gantt-names[data-susun] { position: relative; }
/* Pegangan seret. Dibuat samar dan baru jelas waktu barisnya disentuh: kolom
   ini dibaca jauh lebih sering daripada disusun ulang, jadi titik-titik pegangan
   di tiap baris tidak boleh ikut ramai waktu orang cuma membaca. */
/* Baris = <a href>, dan link BISA DISERET BAWAAN BROWSER tanpa atribut apa pun.
   Drag bawaan itu merebut gerakan pointer dan membatalkan penyusun di app.js.
   Dimatikan di dua tempat sekaligus (di sini + preventDefault('dragstart') di
   app.js) karena -webkit-user-drag tidak didukung semua browser. */
#gantt-names[data-susun] .gn-row,
#desk-list[data-susun] .desk-item,
#desk-list[data-susun] .desk-item img {
  -webkit-user-drag: none; user-drag: none;
}
#gantt-names .gn-grip {
  flex-shrink: 0; width: 9px; height: 14px; cursor: grab; opacity: 0;
  transition: opacity .15s;
  background: radial-gradient(circle, var(--text-dim) 1px, transparent 1.2px);
  background-size: 4px 4px;
  /* touch-action:none SUDAH DICABUT. Dulu ada supaya jari yang turun di sini
     menyeret dan bukan menggulir — tapi jari sekarang tidak menyeret sama
     sekali (lihat pasangSeret di app.js). Kalau dibiarkan, petak ini jadi
     titik mati di layar sentuh: tidak menyeret (JS menolak) DAN tidak
     menggulir (CSS menolak) — persis di sebelah nama task, tempat ibu jari
     paling sering mendarat waktu menggulir timeline panjang. */
}
#gantt-names[data-susun] .gn-row:hover .gn-grip { opacity: .75; }
#gantt-names .gn-grip:active { cursor: grabbing; }
/* Baris yang sedang diangkat — termasuk seluruh keturunannya, karena subpohon
   selalu ikut pindah utuh. */
#gantt-names .gn-row.susun-angkat { opacity: .4; }
/* Garis tempat baris akan mendarat. Kirinya digeser mengikuti kedalaman yang
   akan didapat, jadi "jadi sub-task atau tidak" kelihatan SEBELUM dilepas. */
.susun-tanda {
  position: absolute; right: 0; height: 2px; margin-top: -1px;
  background: var(--accent-admin); pointer-events: none; z-index: 3;
}
.susun-tanda::before {
  content: ""; position: absolute; left: -3px; top: -3px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent-admin);
}
.susun-tip { margin: 8px 0 0; }

/* ---- Susun ulang bagian deskripsi lewat seret & jatuhkan (admin) --------
   Sama pola dengan #gantt-names di atas, tapi datar (tidak ada level/indent)
   karena bagian deskripsi tidak berhirarki. .susun-tanda & .susun-tip di atas
   dipakai ULANG apa adanya — sudah generik, tidak terikat ke #gantt-names. */
#desk-list[data-susun] { position: relative; }
.desk-item { position: relative; }
/* Absolute, bukan flex child seperti .gn-grip: .deskripsi-judul sendiri sudah
   flex (judul + panah, justify-content:space-between), jadi menambah anak
   flex baru di situ akan mengganggu tata letak itu. */
.desk-grip {
  position: absolute; left: -14px; top: 12px; width: 9px; height: 14px;
  cursor: grab; opacity: 0; transition: opacity .15s;
  background: radial-gradient(circle, var(--text-dim) 1px, transparent 1.2px);
  background-size: 4px 4px;
  /* touch-action ikut dicabut — alasan sama dengan .gn-grip di atas. */
}
#desk-list[data-susun] .desk-item:hover .desk-grip { opacity: .75; }
.desk-grip:active { cursor: grabbing; }

/* ---- Layar sentuh: pegangan DISEMBUNYIKAN ---------------------------------
   Kebalikan dari aturan sebelumnya. Dulu pegangan justru dibuat selalu
   terlihat & dilebarkan di sini, supaya seret-susun tidak jadi fitur tak
   terlihat di layar sentuh (di sana 'hover' tidak pernah terjadi).

   Sekarang jari tidak bisa menyeret sama sekali (app.js), jadi pegangan yang
   tetap tampil cuma jadi tombol bohong: kelihatan bisa dipegang, ditarik,
   lalu tidak terjadi apa-apa — dan orangnya menyimpulkan portalnya rusak,
   bukan bahwa fiturnya memang tidak ada di ponsel.

   display:none, bukan opacity:0 — biar petaknya tidak ikut menyita lebar
   kolom nama, yang di ponsel cuma 120px dan sudah paling sempit. */
@media (hover: none) {
  #gantt-names .gn-grip,
  .desk-grip { display: none; }
}
.desk-item.susun-angkat { opacity: .4; }

/* Di HP urutan kepentingannya terbalik dari desktop: yang dibaca duluan adalah
   NAMA task, bukan berapa banyak tanggal yang muat. Jadi chart-nya yang dikunci
   — selebar beberapa kolom saja, angkanya dari app.js (--gn-chart-w, =
   JUMLAH_TANGGAL × lebar kolom bawaan pustaka) — dan kolom nama ambil SISANYA.

   Dulu kebalikannya: nama dipatok 120px dan chart mengambil sisa, sehingga nama
   terpotong parah sementara belasan tanggal kosong berjajar di sebelahnya.

   Cadangan 76px (= 2 × 38, skala harian) cuma dipakai kalau app.js belum
   sempat menyetel variabelnya. */
@media (max-width: 600px) {
  /* Indentasi kembali ke 7px HANYA di sini — kira-kira selebar satu karakter
     pada font baris ini (13px), jadi tiap tingkat semahal satu huruf. Di kolom
     120px, 24px/tingkat menghabiskan 72px di tingkat 3 (60% kolom) sebelum satu
     huruf nama pun muncul; 7px cuma 21px. Alasan lengkapnya di catatan
     --gn-inden di atas. */
  #gantt-names { flex: 1 1 auto; --gn-inden: 7px; }
  #gantt-wrap .gantt-container { flex: 0 0 var(--gn-chart-w, 76px); }
}

/* Frappe Gantt dark-theme overrides.
   Kelas .status-* di bawah dipasang dari kolom Status di CSV — jadi warna
   bar diatur dari CSV. Status baru butuh satu aturan tambahan di sini. */
.gantt .grid-background { fill: var(--bg-card); }
.gantt .grid-header { fill: var(--bg-card); stroke: var(--border); }
.gantt .grid-row { fill: var(--bg-card); }
.gantt .grid-row:nth-child(even) { fill: #343432; }
.gantt .row-line, .gantt .tick { stroke: var(--border); }
.gantt .today-highlight { fill: var(--accent-soft); opacity: 1; }
.gantt .bar { fill: #6b6b66; stroke: none; }          /* default = Belum Mulai */
.gantt .bar-progress { fill: #8a8a84; }
/* Nama tahapan ada di kolom kiri (#gantt-names), jadi label di bar dimatikan. */
.gantt .bar-label, .gantt .bar-label.big { display: none; }
/* Tahap tanpa tanggal: bar disembunyikan, tapi baris grid-nya tetap ada
   supaya tetap sejajar dengan kolom nama di kiri. */
.gantt .bar-wrapper.no-date { display: none; }
/* ---- Tahap TERBUKA: mulai diketahui, deadline belum ada -------------------
   Ujung barnya BUKAN deadline — cuma "besok", yang maju sendiri tiap hari
   (lihat wl_akhir_efektif() di lib.php). Karena itu tidak boleh digambar
   seperti bar biasa: klien yang menolak memberi deadline justru paling mudah
   salah membaca ujung yang padat sebagai tanggal yang dijanjikan.

   Jadi: isinya dibuat transparan dengan garis tepi putus-putus, plus panah di
   ujung kanan yang artinya "masih lanjut". Warna statusnya tetap dipakai
   sebagai warna garis, jadi Berjalan/Belum Mulai masih terbaca.

   stroke-dasharray di SVG, bukan border-style: bar frappe-gantt itu <rect>. */
.gantt .bar-wrapper.terbuka .bar {
  fill: transparent;
  stroke: currentColor;
  stroke-width: 1.5px;
  stroke-dasharray: 4 3;
}
/* Progress bar ikut disembunyikan: tahap yang belum punya ujung tidak punya
   persentase yang jujur untuk digambar. */
.gantt .bar-wrapper.terbuka .bar-progress { display: none; }

/* Panah "masih lanjut" di ujung kanan bar. Digambar dari ::after milik
   wrapper-nya supaya tidak perlu menyentuh markup frappe-gantt. */
.gantt .bar-wrapper.terbuka { color: #8a8a84; }
.gantt .bar-wrapper.terbuka.status-berjalan { color: var(--berjalan); }
.gantt .bar-wrapper.terbuka.status-selesai  { color: var(--done); }

/* Baris induk = ringkasan rentang grup, bukan pekerjaan tersendiri. Barnya
   dibuat tipis & redup supaya jelas ini "payung" dan tidak bersaing dengan bar
   anaknya. transform-box: fill-box perlu supaya transform-origin dihitung dari
   kotak bar-nya sendiri, bukan dari titik asal SVG. */
/* Bar tahap yang sedang dibuka: isinya tetap warna STATUS, cuma garis tepinya
   merah — jadi "sedang dibuka" dan "statusnya apa" terbaca terpisah. */
.gantt .bar-wrapper.fokus .bar { stroke: var(--fokus); stroke-width: 2.5px; }
.gantt .bar-wrapper.induk .bar,
.gantt .bar-wrapper.induk .bar-progress {
  transform-box: fill-box; transform-origin: center; transform: scaleY(.4);
  opacity: .6;
}
.gantt .lower-text, .gantt .upper-text { fill: var(--text-dim); }
/* Satu aturan untuk SEMUA status: warnanya dari --st milik .status-* di
   .bar-wrapper. Custom property diwarisi, jadi .bar di dalamnya ikut membacanya. */
.gantt .bar-wrapper .bar { fill: var(--st, var(--belum)); }
.gantt .bar-wrapper .bar-progress { fill: var(--st, var(--belum)); }

/* ---- tab pemilih timeline ----
   Sekarang <a href="index.php?t=..."> biasa, bukan tombol JS. */
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.tab {
  background: var(--bg-card); border: 1px solid var(--border); color: var(--text-dim);
  padding: 7px 14px; border-radius: 99px; font-size: 13px; font-weight: 600;
  cursor: pointer; font-family: inherit; text-decoration: none; display: inline-block;
  transition: background .15s, color .15s, border-color .15s;
}
.tab:hover { background: var(--bg-hover); color: var(--text); }
.tab.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

/* Tanggal yang punya laporan — dibikin jelas beda dari tanggal biasa.
   Kotak transparan .date-hit yang bikin area kliknya besar (setinggi header). */
.gantt .lower-text.has-report { fill: var(--accent); font-weight: 700; }
.gantt .date-hit { fill: transparent; cursor: pointer; }
.gantt .date-hit:hover + .lower-text, .gantt .lower-text.has-report:hover { fill: #e89070; }
.gantt .date-underline { stroke: var(--accent); stroke-width: 2; pointer-events: none; }
/* Semua bar adalah link ke halaman tahapnya. */
.gantt a .bar-wrapper.clickable .bar { cursor: pointer; }
.gantt a:hover .bar-wrapper.clickable .bar { filter: brightness(1.18); }
.gantt a { text-decoration: none; }

/* ---- legend ---- */
.legend { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 12px; font-size: 13px; color: var(--text-dim); }
.legend span { display: flex; align-items: center; gap: 6px; }
.dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }

/* ---- task list (mobile-friendly fallback, always shown under gantt) ---- */
.tasks { display: grid; gap: 10px; margin-top: 18px; }
.task {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px 14px;
  text-decoration: none; color: var(--text);
  transition: background .15s, border-color .15s;
}
.task:hover { background: var(--bg-hover); border-color: var(--accent); }
.task .arrow { align-self: center; }
/* Kartu tahap yang sudah selesai: diredupkan & namanya dicoret, jadi terbaca
   sebagai "beres" sekilas. Urutannya sudah turun ke bawah lewat wl_urut_selesai.
   Redup hanya sampai disentuh — hover mengembalikannya supaya tetap terbaca. */
.task.t-done { opacity: .6; }
.task.t-done:hover { opacity: 1; }
.task.t-done .t-name { text-decoration: line-through; color: var(--text-dim); }
.task.current { border-color: var(--accent); }
/* Kotak status: SATU template, warnanya dari --st. Dulu tiap status punya nilai
   rgba sendiri dengan kepekatan berbeda-beda (.18/.12/.15), jadi kotaknya tidak
   sebanding satu sama lain. Sekarang semua status memakai resep yang sama —
   Selesai jadi kotak hijau, Berjalan oranye, Menunggu kuning, Belum Mulai abu —
   dengan latar, garis tepi, dan tulisan yang diturunkan dari satu warna. */
.t-status {
  flex-shrink: 0; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .5px;
  padding: 3px 9px; border-radius: 99px; margin-top: 2px;
  color: var(--st, var(--belum));
  background: color-mix(in srgb, var(--st, var(--belum)) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--st, var(--belum)) 40%, transparent);
}
.t-name { font-weight: 600; font-size: 15px; }
.t-meta { font-size: 13px; color: var(--text-dim); margin-top: 2px; }

/* ---- link cards ---- */
.links { display: grid; gap: 12px; margin-top: 16px; }
.link-card {
  display: flex; align-items: center; gap: 14px;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px;
  padding: 16px 18px; text-decoration: none; color: var(--text);
  transition: background .15s, border-color .15s;
}
.link-card:hover { background: var(--bg-hover); border-color: var(--accent); }
.link-num { flex-shrink: 0; width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent); border-radius: 8px; font-weight: 700; font-size: 14px; }
.link-label { font-weight: 500; font-size: 15px; }
.link-desc { font-size: 13px; color: var(--text-dim); margin-top: 2px; }
.arrow { margin-left: auto; color: var(--text-dim); font-size: 18px; }

#err { background: rgba(192,0,0,.12); border: 1px solid #c00000; color: #f0b0b0;
  padding: 12px 16px; border-radius: 10px; font-size: 14px; margin-bottom: 16px; }
#err code { background: rgba(0,0,0,.35); padding: 2px 6px; border-radius: 4px; font-size: 13px; }

/* Pesan hasil ganti nama / hapus task. Bentuknya mengikuti #err supaya kegagalan
   di portal ini selalu tampil sama, cuma warnanya yang berbeda per jenis. */
.flash { padding: 12px 16px; border-radius: 10px; font-size: 14px; margin-bottom: 16px;
  border: 1px solid; }
.flash-error { background: rgba(192,0,0,.12); border-color: #c00000; color: #f0b0b0; }
.flash-ok { background: rgba(0,140,90,.12); border-color: var(--done); color: var(--text); }

/* ---- halaman laporan ---- */
.back { display: inline-block; color: var(--accent); text-decoration: none; font-size: 14px; margin-bottom: 24px; }
.back:hover { text-decoration: underline; }
/* ---- breadcrumb (halaman task) ----
   Duduk paling atas (di bawah brand, sebelum judul) — konteks pertama yang
   dibaca. Tiap segmen (Timeline / grup induk / task sekarang) KOTAK SENDIRI-
   SENDIRI (sudut 8px, latar + garis masing-masing) — bukan satu bar besar
   membungkus semuanya, dan SENGAJA bukan border-radius 999px (pil penuh):
   waktu isinya harus melipat ganda baris (nama task panjang), sudut sepenuh
   itu jadi benjolan aneh di tiap ujung — sudut kecil aman untuk kotak satu
   baris MAUPUN yang kepaksa jadi beberapa baris.
   .sep (›) polos di antaranya, tidak ikut dikotakkan, supaya tetap kebaca
   sebagai pemisah bukan kotak kosong.
   justify-content:center + flex-wrap:wrap = di layar sempit kotak-kotak ini
   turun ke baris berikutnya dan baris itu ikut ditengahkan, bukan terpotong. */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 6px 8px; margin: 4px auto 32px; font-size: 13px;
}
/* .brand cuma punya margin-bottom:8px bawaan — cukup waktu breadcrumb belum
   ada, tapi kotak-kotak breadcrumb sekarang jauh lebih "berat" secara visual
   (latar + garis sendiri) daripada baris teks polos yang dulu di situ, jadi
   8px kerasa mepet. Di-scope ke sini (bukan menaikkan margin-bottom .brand)
   supaya index.php yang .brand-nya TIDAK diikuti breadcrumb tidak ikut
   berubah. */
.brand + .crumbs { margin-top: 20px; }
/* h1 (judul task) global TIDAK punya margin-top sama sekali (cuma margin-
   bottom, lihat aturan h1 di atas) — dulu itu benar karena breadcrumb ada DI
   BAWAH judul. Sekarang breadcrumb duduk tepat di atasnya, jadi tanpa jarak
   sendiri judulnya nempel ke kotak breadcrumb terakhir. Di-scope ke sini
   (bukan menambah margin-top ke aturan h1 global) supaya halaman lain yang
   masih memakai h1 tanpa breadcrumb di depannya tidak ikut berubah. */
.crumbs + h1 { margin-top: 14px; }
/* Pengaman nama task/portal yang panjang (mis. "HTML + WordPress Sites"):
   flex item BAWAANNYA menolak menciut di bawah lebar isinya sendiri walau
   .crumbs sudah flex-wrap — itu yang bikin satu segmen panjang mendorong
   seluruh pil (dan halaman) melebar ke samping di HP. min-width:0 melepas
   penolakan itu, overflow-wrap:anywhere membiarkan browser memenggal kalau
   memang masih kepanjangan setelah menciut, max-width:100% menjaganya tidak
   pernah melebihi lebar layar sendiri. */
.crumbs a, .crumbs .crumb-now {
  display: inline-block; min-width: 0; max-width: 100%; overflow-wrap: anywhere;
  padding: 5px 11px; border-radius: 8px;
}
/* Link (leluhur): garis tepi bertinta accent, bukan --border netral — supaya
   kelihatan "berwarna & bisa diklik", bukan kotak abu-abu polos. Latarnya tetap
   dibiarkan bening: dibedakan dari .crumb-now lewat ISI (border tipis vs latar
   penuh), bukan cuma teks bold vs tidak, yang gampang kelewat dibaca sekilas. */
.crumbs a {
  color: var(--accent); text-decoration: none;
  background: transparent; border: 1px solid rgba(217, 119, 87, .35);
}
.crumbs a:hover { background: var(--accent-soft); border-color: var(--accent); }
.crumbs .sep { color: var(--text-dim); }
/* Halaman yang sedang dibuka: latar penuh --accent-soft + garis tepi --accent
   solid, bukan cuma teks tebal di atas kotak netral yang sama dengan link —
   itu yang bikin breadcrumb-nya kelihatan "tanpa warna" sebelumnya. */
.crumbs .crumb-now {
  color: var(--accent); font-weight: 700;
  background: var(--accent-soft); border: 1px solid var(--accent);
}
/* scroll-margin-top biar judul tidak mepet ujung layar saat dituju via anchor */
.rep { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px;
       padding: 20px 22px; margin-bottom: 16px; scroll-margin-top: 20px; }
/* Di laporan.php, .rep adalah <details> (accordion): padding pindah ke summary
   & .todo, jadi kontainernya sendiri nol. Di halaman task .rep tetap <div>
   biasa dengan padding di atas. */
details.rep { padding: 0; }
.rep:target { border-color: var(--accent); }
/* summary = baris judul yang bisa diklik untuk buka/tutup. list-style:none
   membuang segitiga bawaan; kita pasang segitiga sendiri lewat .rep-judul. */
.rep > summary { list-style: none; cursor: pointer; padding: 18px 22px;
                 display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rep > summary::-webkit-details-marker { display: none; }
.rep-judul { font-size: 17px; font-weight: 600; }
/* Segitiga penanda buka/tutup, berputar saat details terbuka. */
.rep-judul::before { content: '\25B8'; display: inline-block; margin-right: 8px;
                     color: var(--text-dim); transition: transform .15s; }
.rep[open] > summary .rep-judul::before { transform: rotate(90deg); }
.rep .when { font-size: 13px; color: var(--text-dim); }
.rep .todo { color: var(--text-dim); font-size: 14px; font-style: italic;
             margin: 0; padding: 0 22px 20px; }
/* Isi accordion di halaman task (laporan + feedback). Padding pindah ke sini
   karena details.rep sendiri padding-nya nol. */
.rep-body { padding: 0 22px 20px; }
.rep-body > :first-child { margin-top: 0; }

/* ---- laporan di luar rentang tahap ----
   Tanggal yang tidak lagi masuk rentang tahap (mis. setelah Mulai digeser)
   tidak dihapus, cuma diturunkan ke bagian bawah. Dibedakan lewat garis
   putus-putus + badge, BUKAN warna merah: ini bukan error, cuma bukan bagian
   dari rentang yang sekarang. */
.luar-judul { font-size: 16px; font-weight: 600; color: var(--text-dim);
              margin: 34px 0 6px; padding-top: 18px; border-top: 1px dashed var(--border); }
.rep.luar { border-style: dashed; }
.rep.luar .rep-judul { color: var(--text-dim); }
.rep-badge { font-size: 11px; letter-spacing: .3px; text-transform: uppercase;
             /* --accent, bukan warna status: --waiting sudah dihapus, dan warna
                status memang disediakan khusus untuk status — dipinjam untuk
                badge, ia jadi berbohong soal keadaan sesuatu. --accent sekarang
                bebas karena Berjalan sudah punya warnanya sendiri. */
             color: var(--accent); border: 1px solid var(--accent);
             border-radius: 999px; padding: 1px 8px; }

/* ---- deskripsi lengkap tahap (di halaman task) ----
   Heading di-scope di sini supaya tidak ikut garis bawah h2 global. */
/* Judul "Deskripsi N" — BUKAN h2 polos bergaris bawah seperti Timeline/Berkas/
   Laporan di bawahnya: sengaja dibuatkan bar sendiri (latar --bg-hover, beda
   dari --bg-card kotak isinya) yang menempel LANGSUNG di atas .deskripsi
   (margin-bottom 0, sudut bawah persegi) — jadi kelihatan sebagai SATU kartu
   dua tingkat (judul + isi), bukan judul lepas yang kebetulan ada kotak di
   bawahnya. Ditengahkan (text-align:center) biar sepadan sama gaya pil
   breadcrumb yang juga ditengahkan di atasnya. */
/* Bar judul dobel sebagai PEMICU accordion (<label for=desk-buka-…>): judul di
   kiri, panah di kanan — bukan lagi ditengahkan, karena sekarang ada dua ujung
   yang harus dikenali sekali lihat (apa isinya, dan apakah sedang terbuka).
   border-radius PENUH (8px semua sudut) di keadaan tertutup, karena tanpa
   .deskripsi menyusul di bawahnya (disembunyikan total, lihat .desk-buka di
   bawah) bar ini berdiri sendiri sebagai satu kartu utuh, bukan kepala dari
   kartu yang lebih besar. */
.deskripsi-judul {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  text-align: left; font-size: 15px; font-weight: 700;
  background: var(--bg-hover); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 16px; margin: 10px 0 0;
  cursor: pointer; user-select: none;
}
/* 44px (bekas jarak "section baru" waktu cuma ada satu bar judul selalu
   tampil) dipertahankan HANYA di bagian pertama, supaya masih ada jarak jelas
   dari chart/tombol admin di atasnya. Bagian ke-2 dst. dari task yang sama
   ditumpuk rapat (10px) — itulah "compact" yang diminta: banyak bagian tidak
   lagi berarti banyak jarak kosong ikut menumpuk. */
#desk-list > .desk-item:first-child > .deskripsi-judul { margin-top: 28px; }
.deskripsi-judul:hover { border-color: var(--accent); }
/* Sudut bawah diratakan HANYA waktu terbuka, supaya menyatu dengan kotak
   .deskripsi yang menyusul (lihat .desk-buka:checked ~ .deskripsi di bawah). */
.desk-buka:checked + .deskripsi-judul { border-radius: 8px 8px 0 0; }
/* Panah elemen sendiri (bukan ::after di teks judul) supaya bisa diputar
   tanpa ikut memiringkan tulisannya — sama pola dengan .desk-lagi::before
   yang dulu dipakai. flex:none supaya tidak ikut menyusut waktu judul panjang. */
.dj-panah { flex: none; display: inline-block; font-size: 13px; line-height: 1;
  color: var(--accent); transition: transform .15s ease; }
.dj-panah::before { content: "\25b8"; }
.desk-buka:checked + .deskripsi-judul .dj-panah { transform: rotate(90deg); }
/* Teks panjang TIDAK ikut melebar sampai 1600px. Chart senang dibentangkan,
   paragraf tidak: satu baris selebar layar laptop bikin mata kehilangan awal
   baris berikutnya. Jadi kolom bacanya dipatok — angkanya kira-kira 90 karakter,
   masih longgar tapi belum menyiksa. Yang dipatok cuma blok teks; chart, kartu,
   dan daftar berkas tetap memakai lebar penuh. */
/* Disembunyikan TOTAL sampai bar judulnya (.deskripsi-judul) diklik — lihat
   .desk-buka:checked di bawah. Dulu cuma teksnya (.desk-isi) yang dilipat
   5 baris, sisanya (waktu diperbarui, form edit, kotak komentar) selalu
   tampil; sekarang task dengan banyak bagian deskripsi bisa menumpuk banyak
   layar kalau semuanya selalu terbuka, jadi SATU bagian yang benar-benar mau
   dibaca — bukan sekadar cuplikan — yang tampil sekali klik. */
.deskripsi { display: none; }
.desk-buka:checked + .deskripsi-judul + .deskripsi {
  display: block;
  margin: 0 0 8px; padding: 16px 18px;
  background: var(--bg-card); border: 1px solid var(--border);
  border-top: 0; border-radius: 0 0 8px 8px;
}
/* Kolom baca dipatok di ELEMEN TEKSnya, bukan di pembungkusnya. Form edit
   (Quill) juga tinggal di dalam .deskripsi, jadi waktu patokannya dipasang di
   pembungkus, editornya ikut terjepit 78ch padahal justru itu yang paling
   butuh selebar halaman. Sekarang paragraf tetap enak dibaca, editor lega.

   Patokannya dipasang di anak langsung .desk-isi (pembungkus isi yang dilipat),
   bukan di anak langsung .deskripsi — form edit tinggal di luar .desk-isi, jadi
   alasan di atas tetap berlaku. */
.deskripsi .desk-isi > p, .deskripsi .desk-isi > h1, .deskripsi .desk-isi > h2,
.deskripsi .desk-isi > h3, .deskripsi .desk-isi > ul, .deskripsi .desk-isi > ol,
.deskripsi .desk-isi > blockquote { max-width: 78ch; }

/* Gambar yang ditempel lewat editor kaya (lihat wl_bersihkan_html + skrip
   Quill): tidak pernah lebih lebar dari kotaknya, tinggi ikut menyesuaikan
   supaya rasio aslinya tidak gepeng. */
.desk-isi img, .lap-teks img, .komentar-teks img, .rich-area .ql-editor img {
  max-width: 100%; height: auto; display: block; border-radius: 6px; margin: 8px 0;
}

/* .desk-isi sendiri tidak perlu style lagi — dulu di sini yang memotong 5
   baris + memudar (line-clamp + mask-image); sekarang seluruh .deskripsi
   sudah tersembunyi/tampil sekaligus (lihat di atas), jadi isinya selalu
   dirender utuh begitu bagian ini dibuka. */
.deskripsi h1 { font-size: 23px; font-weight: 700; letter-spacing: -.3px; margin: 22px 0 8px; }
.deskripsi h2 { font-size: 18px; font-weight: 600; margin: 20px 0 8px; padding: 0; border: 0; }
.deskripsi h3 { font-size: 15px; font-weight: 600; margin: 16px 0 6px; color: var(--text); }
.deskripsi h1:first-child, .deskripsi h2:first-child, .deskripsi h3:first-child { margin-top: 0; }
.deskripsi p { margin: 8px 0; }
.deskripsi ul, .deskripsi ol { margin: 8px 0; padding-left: 24px; }
.deskripsi li { margin: 3px 0; }
/* overflow-wrap: link panjang (mis. URL utuh dari wl_autolink_html, bukan
   teks pendek) tidak punya spasi buat browser membungkusnya — tanpa ini ia
   mendorong .deskripsi lebih lebar dari layar HP dan seluruh halaman ikut
   ter-geser ke samping. Sama pola dengan .komentar-teks a. */
.deskripsi a { color: var(--accent); overflow-wrap: anywhere; }
.deskripsi strong { color: var(--text); font-weight: 600; }
.deskripsi code { background: rgba(0,0,0,.28); padding: 1px 6px; border-radius: 4px;
  font-size: 13px; color: var(--text); }
/* Laporan hasil edit admin: HTML rapi dari editor (header/list/link). Tipografinya
   disamakan dengan .deskripsi supaya h2/h3/ul tampil rapi di dalam laporan juga. */
.lap-teks { margin: 8px 0; }
.lap-teks h1 { font-size: 21px; font-weight: 700; margin: 18px 0 8px; padding: 0; border: 0; }
.lap-teks h2 { font-size: 18px; font-weight: 600; margin: 16px 0 8px; padding: 0; border: 0; }
.lap-teks h3 { font-size: 15px; font-weight: 600; margin: 14px 0 6px; color: var(--text); }
.lap-teks p { margin: 8px 0; }
/* padding-left (bukan margin-left) supaya penanda bullet berada DI DALAM kotak
   list, tidak menggantung keluar ke tepi kartu. */
.lap-teks ul, .lap-teks ol { margin: 8px 0; padding-left: 24px; }
.lap-teks li { margin: 3px 0; }
.lap-teks a { color: var(--accent); overflow-wrap: anywhere; }
.lap-teks strong { color: var(--text); font-weight: 600; }
.lap-teks code { background: rgba(0,0,0,.28); padding: 1px 6px; border-radius: 4px;
  font-size: 13px; color: var(--text); }
.lap-teks > :first-child { margin-top: 0; }

/* ---- Kontrol edit admin (deskripsi / laporan / komentar) ----
   Hanya muncul untuk sesi admin. <details> dipakai sebagai toggle tanpa JS:
   klik "Edit" membuka textarea + tombol simpan. */
.diperbarui, .diedit { font-size: 12px; color: var(--text-dim); margin-top: 6px; }
.diedit { display: inline-block; margin-left: 8px; font-style: italic; }
.edit-box { margin-top: 12px; }
.edit-box > summary { list-style: none; cursor: pointer; display: inline-block; }
.edit-box > summary::-webkit-details-marker { display: none; }
/* padding vertikal 6px, bukan 3px: dengan 3px tombolnya setipis garis teks dan
   susah dikenali sebagai tombol — apalagi di ponsel, di mana area sentuhnya jadi
   jauh lebih kecil dari yang nyaman. */
.edit-btn { font-size: 13px; font-weight: 600; color: var(--accent-admin);
  background: var(--accent-admin-soft); border: 1px solid var(--accent-admin);
  border-radius: 7px; padding: 6px 12px; display: inline-block; }
.edit-btn:hover { background: var(--accent-admin); color: #1a1a18; }
.edit-box[open] > summary .edit-btn { background: rgba(0,0,0,.2); }
.edit-box form { margin-top: 10px; }
.edit-box textarea { width: 100%; box-sizing: border-box; background: var(--bg-card);
  color: var(--text); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; font: inherit; font-size: 14px; resize: vertical; }
.edit-box input[type=date] { background: var(--bg-card); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 7px 10px;
  font: inherit; font-size: 14px; margin-bottom: 8px; }
/* Kolom URL folder Drive. Dulu tidak disebut sama sekali, jadi ia jatuh ke input
   putih bawaan browser di tengah tema gelap — dan karena lebarnya ikut ukuran
   bawaan (~20 karakter), URL Drive yang panjang terpotong hampir seluruhnya.
   width:100% + box-sizing supaya muat selebar formnya, dan text-overflow:ellipsis
   supaya sisa yang tetap tidak muat berakhir dengan "…" bukan terpenggal mentah. */
.edit-box input[type=url] { width: 100%; box-sizing: border-box;
  background: var(--bg-card); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
  font: inherit; font-size: 14px; text-overflow: ellipsis; }
.edit-box input[type=url]::placeholder { color: var(--text-dim); }
/* Kolom judul bagian deskripsi (lihat form edit_deskripsi / "+ Tambah
   deskripsi"). Latar SENGAJA beda dari textarea isi di bawahnya (--bg-hover,
   bukan --bg-card) — sebelumnya keduanya sama-sama --bg-card, jadi menyatu
   dan tidak kelihatan sebagai DUA kolom terpisah (judul vs isi). --bg-hover
   dipilih supaya nyambung ke .deskripsi-judul (bar judul di kepala kartu,
   juga --bg-hover) — jadi jelas: "field ini yang isinya jadi judul bar itu". */
.edit-box input[type=text].judul-bagian { width: 100%; box-sizing: border-box;
  background: var(--bg-hover); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
  font: inherit; font-size: 14px; font-weight: 600; margin-bottom: 8px; }
.edit-box input[type=text].judul-bagian::placeholder { color: var(--text-dim); font-weight: 400; }
/* flex-wrap: sejak ada tombol "Hapus tanggal", barisnya berisi dua tombol +
   keterangan — di layar HP itu tidak muat sebaris. */
.edit-aksi { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 8px; }
.edit-hint { font-size: 12px; color: var(--text-dim); }
/* Sekunder, BUKAN merah: merah dipesan untuk "Hapus task" yang benar-benar
   membuang barisnya. Mengosongkan tanggal tidak menghapus apa pun — tasknya,
   laporannya, dan komentarnya semua tetap ada. Jadi bentuknya garis tepi saja,
   supaya Simpan tetap yang paling menonjol; warnanya baru menyala waktu disentuh.
   Ditulis .aksi-tanggal .btn-hapus-tanggal (0,2,0), bukan .btn-hapus-tanggal
   sendirian (0,1,0): resep tombol bersama memakai `.aksi-tanggal button` (0,1,1)
   yang lebih kuat, jadi versi pendeknya tidak pernah berlaku. */
.aksi-tanggal .btn-hapus-tanggal {
  background: transparent; border-color: var(--border); color: var(--text-dim);
}
.aksi-tanggal .btn-hapus-tanggal:hover {
  background: var(--accent-admin-soft); border-color: var(--accent-admin); color: var(--accent-admin);
}
/* "Hapus tanggal" tampil tepat di bawah dua kolom tanggal — tapi di HTML ia
   ditulis PALING AKHIR, sesudah Simpan. Alasannya ada di wl_form_tanggal():
   Enter di kolom tanggal memicu tombol submit pertama, dan itu harus Simpan,
   bukan penghapus. Jadi urutan tampilnya diatur di sini, bukan di HTML.
   .task-form sudah display:grid, jadi `order` berlaku untuk anak langsungnya:
   yang tidak disebut tetap 0 dan tampil sesuai urutan tulis (Mulai, Selesai,
   lalu tombol ini). */
.aksi-tanggal { justify-self: start; }
.edit-rentang .baris-status { order: 1; }
.edit-rentang .edit-aksi    { order: 2; }
/* Dipakai "+ Tambah laporan" DAN "+ Tambah sub-task". Jarak atas 14px (dulu 4px):
   yang sub-task mendarat persis di bawah chart Gantt, jadi 4px membuatnya
   menempel ke scrollbar chart-nya. */
.add-lap, .add-desk { margin: 14px 0 20px; }
.edit-komentar { margin-top: 8px; }
/* Merah, BUKAN sekunder seperti .btn-hapus-tanggal: itu cuma mengosongkan
   tanggal (tidak menghapus apa pun), ini beneran membuang barisnya lewat
   wl_hapus_komentar() — dipesan sama seperti warna "Hapus task" di .edit-hapus.

   .btn-hapus-bagian ("Hapus bagian" di kotak Edit deskripsi) ikut aturan yang
   SAMA, bukan salinan warnanya sendiri: dua-duanya membuang isi permanen, jadi
   dua-duanya harus terbaca sama bahayanya. Kalau warnanya disalin, yang satu
   pasti ketinggalan waktu palet diubah — dan yang ketinggalan justru terbaca
   seperti tombol biasa. */
.btn-hapus-komentar,
.btn-hapus-bagian { background: transparent; border-color: #7a2020; color: #f0b0b0; }
.btn-hapus-komentar:hover,
.btn-hapus-bagian:hover { background: #c00000; border-color: #c00000; color: #fff; }

/* Task maker (home, admin). */
/* Ganti nama & hapus berdampingan. flex-wrap supaya di layar sempit tombolnya
   turun sendiri, bukan menyempitkan formnya. align-items:flex-start: kalau salah
   satu <details> terbuka, yang tertutup tidak ikut ditarik tinggi. */
/* margin-top WAJIB ada: baris ini duduk langsung di bawah kartu status tahap, dan
   tanpa jarak atas tombolnya menempel ke tepi kartu. */
.admin-aksi { display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: flex-start;
  margin: 16px 0 18px; }
.admin-aksi .edit-box { margin-top: 0; }
/* Tombol hapus dibedakan warnanya — ini satu-satunya aksi di portal yang
   memindahkan data keluar dari tempatnya. */
.edit-hapus .edit-btn { color: #f0b0b0; background: rgba(192, 0, 0, .12); border-color: #7a2020; }
.edit-hapus .edit-btn:hover { border-color: #c00000; }
.edit-hapus .edit-aksi button:hover { background: #c00000; border-color: #c00000; }

.task-form { display: grid; gap: 8px; margin-top: 10px; max-width: 440px; }
.task-form input[type=text], .task-form input[type=date], .task-form select {
  background: var(--bg-card); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 14px; }
/* Panah dropdown digambar browser DI DALAM padding kanan, jadi dengan 10px yang
   sama seperti input teks ia menempel ke garis tepi. Cuma <select> yang diberi
   ruang tambahan — input teks & tanggal tidak punya panah, jadi tetap 10px. */
.task-form select { padding-right: 28px; }
.task-form label { font-size: 13px; color: var(--text-dim); display: grid; gap: 4px; }

/* ---- Editor kaya (Quill snow) disesuaikan ke tema gelap ----
   Snow theme aslinya terang; override di sini supaya menyatu dengan portal. */
.rich-area { background: var(--bg-card); }
.ql-toolbar.ql-snow { border: 1px solid var(--border); border-bottom: 0;
  border-radius: 8px 8px 0 0; background: rgba(0,0,0,.18); }
.ql-container.ql-snow { border: 1px solid var(--border); border-radius: 0 0 8px 8px;
  font: inherit; font-size: 14px; }
.ql-editor { min-height: 120px; color: var(--text); }
.ql-editor.ql-blank::before { color: var(--text-dim); font-style: normal; left: 12px; }
/* Quill snow menata <code>/code-block dengan latar TERANG (jadi kotak putih di
   tema gelap). CSS Quill dimuat SETELAH style.css, jadi override di sini perlu
   spesifisitas lebih tinggi (.rich-area di depan) supaya menang tanpa !important. */
.rich-area .ql-editor code, .rich-area .ql-editor pre,
.rich-area .ql-editor .ql-code-block-container, .rich-area .ql-editor .ql-code-block {
  background: rgba(0,0,0,.32) !important; color: var(--text) !important; }
.rich-area .ql-editor blockquote { border-left: 3px solid var(--border); color: var(--text-dim); }
.rich-area .ql-editor a { color: var(--accent); }
.ql-snow .ql-stroke { stroke: var(--text-dim); }
.ql-snow .ql-fill, .ql-snow .ql-stroke.ql-fill { fill: var(--text-dim); }
.ql-snow .ql-picker { color: var(--text-dim); }
.ql-snow.ql-toolbar button:hover .ql-stroke,
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow .ql-toolbar button.ql-active .ql-stroke { stroke: var(--accent); }
.ql-snow.ql-toolbar button:hover .ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-fill { fill: var(--accent); }
.ql-snow .ql-picker-options { background: var(--bg-card); border-color: var(--border); }
.ql-snow .ql-picker.ql-expanded .ql-picker-label { border-color: var(--border); }
.ql-snow a { color: var(--accent); }
/* popup input link */
.ql-snow .ql-tooltip { background: var(--bg-card); border-color: var(--border);
  color: var(--text); box-shadow: 0 6px 20px rgba(0,0,0,.4); }
.ql-snow .ql-tooltip input[type=text] { background: var(--bg); color: var(--text);
  border-color: var(--border); }

/* ---- checklist statis (copy-paste) ----
   Daftar task dengan kotak centang untuk ditempel di konten halaman
   ($DESKRIPSI / $ENTRI). State-nya HARDCODE di HTML: tambahkan atribut
   `checked` = tercentang, hapus = kosong. Tidak tersimpan ke server —
   mencentang saat halaman dibuka hanya visual dan reset saat reload.
   Contoh:
     <ul class="checklist">
       <li><input type="checkbox" checked> Sudah selesai</li>
       <li><input type="checkbox"> Belum selesai</li>
     </ul>
   :has(input:checked) mencoret item yang sudah dicentang secara otomatis,
   jadi tidak perlu class tambahan per item. */
ul.checklist { list-style: none; margin: 12px 0; padding: 0; }
.checklist li { margin: 7px 0; font-size: 15px; line-height: 1.5; }
.checklist input[type="checkbox"] { accent-color: var(--accent); width: 16px; height: 16px;
  margin-right: 9px; vertical-align: -2px; cursor: pointer; }
.checklist li:has(input:checked) { color: var(--text-dim); text-decoration: line-through; }

/* ---- komentar / feedback klien ----
   <details>/<summary>, sama pola dengan .rep: tertutup bawaan, klik judul untuk
   buka. Segitiga & rotasinya juga dipinjam dari .rep-judul (lihat di atas) —
   BUKAN gaya .edit-btn: itu warna admin (oranye), sedangkan kotak ini dilihat
   klien juga. */
.komentar { margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--border); }
.komentar-judul { list-style: none; cursor: pointer; font-size: 12px; font-weight: 700;
  letter-spacing: .5px; text-transform: uppercase; color: var(--text-dim); }
.komentar-judul::-webkit-details-marker { display: none; }
.komentar-judul::before { content: '\25B8'; display: inline-block; margin-right: 6px;
  transition: transform .15s; }
.komentar[open] > .komentar-judul { margin-bottom: 10px; }
.komentar[open] > .komentar-judul::before { transform: rotate(90deg); }
.komentar-item { background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; margin-bottom: 8px; }
.komentar-nama { font-weight: 600; font-size: 13px; }
.komentar-waktu { font-size: 12px; color: var(--text-dim); margin-left: 8px; }
/* Komentar sekarang HTML dari editor Quill (lihat komentar 'html' di
   isi/<slug>.json), bukan string polos lagi — jadi bukan white-space: pre-wrap
   (HTML sudah bawa <p>/<br> sendiri; pre-wrap di atasnya cuma menambah baris
   kosong dari spasi/indentasi sumbernya). Tipografinya disamakan tipis dengan
   .lap-teks, dipangkas ke ukuran komentar (margin lebih rapat, cuma h2/h3 —
   toolbar Quill komentar memang tidak menawarkan h1). Komentar LAMA (belum
   ditandai 'html') tetap lewat nl2br(), yang sudah menghasilkan <br> sendiri —
   jadi tetap rapi tanpa pre-wrap. */
.komentar-teks { font-size: 14px; margin-top: 4px; overflow-wrap: anywhere; }
.komentar-teks a { color: var(--accent); overflow-wrap: anywhere; }
.komentar-teks p { margin: 6px 0; }
.komentar-teks ul, .komentar-teks ol { margin: 6px 0; padding-left: 22px; }
.komentar-teks li { margin: 2px 0; }
.komentar-teks h2, .komentar-teks h3 { font-size: 15px; font-weight: 600; margin: 10px 0 4px; color: var(--text); }
.komentar-teks strong { color: var(--text); font-weight: 600; }
.komentar-teks code { background: rgba(0,0,0,.28); padding: 1px 6px; border-radius: 4px;
  font-size: 13px; color: var(--text); }
.komentar-teks > :first-child { margin-top: 0; }
.komentar-lampiran { display: inline-block; margin-top: 8px; }
.komentar-lampiran .lampiran-thumb { max-width: 180px; max-height: 180px; border-radius: 8px;
  border: 1px solid var(--border); display: block; }
.komentar-berkas { display: inline-block; margin-top: 8px; font-size: 14px; color: var(--accent);
  text-decoration: none; }
.komentar-berkas:hover { text-decoration: underline; }
.komentar-file { display: block; font-size: 12px; color: var(--text-dim); }
.komentar-file input { display: block; margin-top: 4px; font-size: 13px; color: var(--text); }
/* Pengganti kolom "Nama" yang dulu di form komentar: bukan lagi input, cuma
   info siapa yang akan tercatat sebagai pengirim (email Google sesi
   sekarang). overflow-wrap: email panjang tidak boleh mendorong form melebar
   di HP — sama pola dengan .komentar-teks a. */
.komentar-sebagai { font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
.komentar-sebagai strong { color: var(--text); font-weight: 600; }

.komentar-form { display: grid; gap: 8px; margin-top: 12px; }
.komentar-form input, .komentar-form textarea, .unlock input {
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); font-family: inherit; font-size: 14px; padding: 9px 11px; width: 100%;
}
.komentar-form textarea { resize: vertical; }
/* .aksi-tanggal ikut DI SINI, bukan menumpang gaya sendiri: "Hapus tanggal" duduk
   di luar .edit-aksi (urutan DOM-nya dijaga demi tombol Enter — lihat
   wl_form_tanggal), dan tanpa disebut ia jatuh ke tombol abu-abu bawaan browser. */
.komentar-form button, .unlock button, .edit-aksi button, .aksi-tanggal button {
  justify-self: start; font-weight: 600; font-size: 13px; font-family: inherit;
  padding: 8px 18px; border-radius: 99px; cursor: pointer; transition: background .15s;
}
/* Klien: komentar & unlock kode akses, jadi tetap warna aksen (oranye). */
.komentar-form button, .unlock button {
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent);
}
.komentar-form button:hover, .unlock button:hover { background: var(--accent); color: #fff; }
/* Admin: aksi di dalam .edit-box (Simpan) dan "Hapus tanggal", jadi hijau. */
.edit-aksi button, .aksi-tanggal button {
  background: var(--accent-admin-soft); border: 1px solid var(--accent-admin); color: var(--accent-admin);
}
.edit-aksi button:hover, .aksi-tanggal button:hover { background: var(--accent-admin); color: #fff; }

/* Link biasa di dalam kartu laporan (isi $ENTRI) pakai warna aksen. */
.rep a { color: var(--accent); }

/* Berkas Drive yang disematkan langsung di badan laporan (wl_sematkan_drive).
   Bingkainya sengaja sama dengan .drive-preview di atas — dua jalan berbeda
   menuju berkas yang sama tidak boleh terlihat seperti dua fitur berbeda. */
.lap-semat { margin: 12px 0 0; border: 1px solid var(--border); border-radius: 10px;
  overflow: hidden; background: var(--bg-card); }
.lap-semat img { display: block; width: 100%; height: auto; }
/* Sama seperti panel pratinjau: <embed> tidak punya tinggi alami. */
.lap-semat embed { display: block; width: 100%; height: 70vh; border: 0; }
.lap-semat figcaption { padding: 8px 12px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--text-dim); overflow-wrap: anywhere; }
@media (max-width: 600px) { .lap-semat embed { height: 60vh; } }

/* ---- file manager per tahap (lihat + unggah) ----
   Baris mendatar (bukan kotak/grid): ikon-atau-thumbnail kecil di kiri, nama
   di sebelahnya — sebaris seperti file manager biasa, bukan galeri kartu. */
.berkas-grid { display: flex; flex-direction: column; gap: 4px; margin: 12px 0 18px; }
.berkas-item { display: flex; flex-direction: row; align-items: center; gap: 12px;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 12px; text-decoration: none; color: var(--text); transition: border-color .15s; }
.berkas-item:hover { border-color: var(--accent); }
.berkas-thumb { width: 36px; height: 36px; object-fit: cover; border-radius: 4px;
  border: 1px solid var(--border); flex-shrink: 0; }
.berkas-icon { font-size: 20px; line-height: 36px; height: 36px; width: 36px;
  text-align: center; flex-shrink: 0; }
.berkas-name { font-size: 13px; text-align: left; overflow-wrap: anywhere; flex: 1 1 auto; min-width: 0; }
.berkas-waktu { font-size: 11px; color: var(--text-dim); }
/* "Buka di Drive" — satu-satunya jalan mengunggah, jadi ditaruh besar,
   SELEBAR kolom kontennya (bukan pil seukuran teks), dan warnanya SOLID
   --accent — bukan garis tepi tipis seperti tombol aksi kecil lain. Ini
   ajakan utama satu-satunya di bagian ini, jadi sengaja menonjol sendiri. */
.drive-cta { margin: 18px 0 8px; }
.drive-cta a { display: block; width: 100%; box-sizing: border-box; text-align: center;
  background: var(--accent); color: #1a1a18; font-weight: 700; font-size: 15px;
  text-decoration: none; padding: 14px; border-radius: 10px; transition: filter .15s; }
.drive-cta a:hover { filter: brightness(1.08); }
.berkas-form { display: grid; gap: 10px; margin: 12px 0 8px; max-width: 420px; }
.berkas-file { font-size: 13px; color: var(--text-dim); display: grid; gap: 6px; }
/* Kotak "Choose File" bawaan browser diganti jadi tombol bergaya tema.
   ::file-selector-button hanya menata tombolnya; nama file yang dipilih tetap
   ditampilkan browser di sebelahnya (UX-nya jelas, tanpa perlu JS). */
.berkas-file input[type="file"] { font-size: 13px; color: var(--text-dim); }
.berkas-file input[type="file"]::file-selector-button {
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent);
  font-weight: 600; font-size: 13px; font-family: inherit; padding: 7px 14px;
  border-radius: 99px; cursor: pointer; margin-right: 10px; transition: background .15s;
}
.berkas-file input[type="file"]::file-selector-button:hover { background: var(--accent); color: #fff; }
.berkas-form button {
  justify-self: start; background: var(--accent-soft); border: 1px solid var(--accent);
  color: var(--accent); font-weight: 600; font-size: 13px; font-family: inherit;
  padding: 8px 18px; border-radius: 99px; cursor: pointer; transition: background .15s;
}
.berkas-form button:hover { background: var(--accent); color: #fff; }

.unlock { display: flex; gap: 8px; margin: 16px 0; flex-wrap: wrap; }
.unlock input { width: auto; flex: 1 1 220px; }

footer { margin-top: 64px; padding-top: 20px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--text-dim); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
footer a { color: var(--accent); text-decoration: none; }

/* ---- Catatan aktivitas (halaman depan portal) ----------------------------
   Bentuknya sengaja meniru .komentar: <details> tertutup, segitiga yang
   berputar, huruf kapital kecil untuk judulnya. Dua-duanya "kotak yang bisa
   dibuka kalau perlu" di halaman yang sama, jadi kalau bentuknya beda orang
   akan mengira fungsinya beda.

   max-height + overflow-y: dibuka pun ia tidak boleh mendorong footer sejauh
   seratus baris. Isinya menggulir di dalam kotaknya sendiri. */
.log-box { margin-top: 22px; padding-top: 14px; border-top: 1px dashed var(--border); }
.log-judul { list-style: none; cursor: pointer; font-size: 12px; font-weight: 700;
  letter-spacing: .5px; text-transform: uppercase; color: var(--text-dim); }
.log-judul::-webkit-details-marker { display: none; }
.log-judul::before { content: '\25B8'; display: inline-block; margin-right: 6px;
  transition: transform .15s; }
.log-box[open] > .log-judul { margin-bottom: 10px; }
.log-box[open] > .log-judul::before { transform: rotate(90deg); }

.log-list { list-style: none; margin: 0; padding: 0;
  max-height: 320px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg); }
.log-item { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  padding: 7px 12px; border-bottom: 1px solid var(--border); font-size: 13px; }
.log-item:last-child { border-bottom: 0; }
.log-waktu { color: var(--text-dim); font-size: 12px; white-space: nowrap; }
.log-oleh { font-weight: 600; color: var(--text); }
.log-teks { color: var(--text-dim); overflow-wrap: anywhere; flex: 1 1 220px; }

/* Baris 'ghost' = dikerjakan dari luar daftar orang portal ini (admin pusat).
   Biasanya cuma muncul sekali, waktu portalnya dibuat. Dibedakan tipis supaya
   kalau ia muncul LAGI di tengah daftar, matanya langsung menangkap. */
.log-ghost .log-oleh { color: var(--accent); }
