/* ==========================================================================
 * ANTRIAN INBOUND FROZEN — DESIGN SYSTEM
 *
 * Token dan tipografi mengikuti NEXUS (astro-sandbox-ops-wh,
 * Docs/Base/ui_ux.md) sejak 2026-10-21 — tujuan migrasi aplikasi ini. Tata
 * letak dan primitifnya masih yang dipinjam dari `outbound-operations-hub`;
 * bagian 40 di akhir berkas menyelaraskan kulitnya ke NEXUS tanpa
 * memindahkan apa pun. Yang ditambahkan di sini hanya yang tidak ada di sana:
 * hitung mundur SLA, kartu antrean, dan input plat nomor.
 *
 * Tidak ada Tailwind CDN. Compiler Tailwind versi CDN menyusun ulang seluruh
 * CSS di dalam browser pada setiap muat halaman (~120 KB JS + blocking paint)
 * dan memang tidak ditujukan untuk produksi. Semua kelas di bawah statis.
 * ========================================================================== */

/* --------------------------------------------------------------------------
 * 1. Token
 *
 * Tema terang adalah nilai dasar di `:root`. Tema gelap didefinisikan dua kali
 * dengan sengaja: lewat `.dark` (pilihan eksplisit operator, tersimpan di
 * localStorage) dan lewat `prefers-color-scheme` untuk pemakai yang belum
 * pernah memilih. `:root:not(.light)` menjaga agar pilihan "terang" yang
 * eksplisit tetap menang di perangkat yang setelan sistemnya gelap.
 * ----------------------------------------------------------------------- */
:root {
  --bg: #f4f7fc;
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --surface-muted: #f8fafc;
  --surface-accent: #eef6ff;
  --line: #e2e8f0;
  --line-strong: #cbd5e1;
  --text: #1e293b;
  --text-soft: #475569;
  /* Skala status tema terang diperdalam satu langkah dari nilai pinjaman
     aslinya. Sebabnya bukan selera: hampir semua pemakainya di aplikasi ini
     adalah teks KECIL — `.fact span` 9px, `.dock-state` 10px, `.sla-note` 10px,
     `.badge` 10px, `.eyebrow` 11px — dan teks kecil menuntut 4,5:1, bukan 3:1.
     Pada nilai lama `--text-muted` hanya mencapai 3,7:1 di atas latar halaman
     dan lencana teal jatuh ke 2,9:1; keduanya masih terbaca di monitor kantor
     dan tidak lagi terbaca di tablet gudang yang berembun di bawah lampu
     dingin. Tema gelap tidak ikut berubah — nilainya sudah lolos. */
  --text-muted: #616f85;
  --accent: #1565c0;
  --accent-soft: #d8eaff;
  /* Tinta di atas bidang penuh warna aksen/teal. Tombol primer dan tombol teal
     memakai warnanya sebagai LATAR, jadi tintanya harus berlawanan arah dengan
     temanya: putih di atas biru tua tema terang, biru sangat gelap di atas biru
     muda tema gelap. Sebelumnya keduanya dipaku `#fff`, dan di tema gelap itu
     berarti teks putih di atas #6fa4ff — 2,5:1 pada tombol yang paling sering
     ditekan di seluruh aplikasi. */
  --accent-ink: #ffffff;
  --teal: #0a7669;
  --teal-soft: #dff7f2;
  --teal-ink: #ffffff;
  --status-normal: #0f744a;
  --status-monitor: #8a6009;
  --status-warning: #b4550c;
  --status-critical: #b82743;
  --shadow: 0 8px 24px rgb(15 23 42 / 8%);
  --radius: 16px;
  --sidebar: 232px;
  --topbar-height: 72px;
  --font-sans: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;
  --glass-bg: rgba(255, 255, 255, 0.7);
  --glass-border: rgba(255, 255, 255, 0.2);
  --transition-smooth: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  /* NEXUS — radius iOS, pil, cincin fokus, dan bayangan permukaan
     (astro-sandbox-ops-wh, Docs/Base/ui_ux.md bagian 1.2). */
  --radius-sm: 10px;
  --radius-xs: 6px;
  --radius-pill: 50px;
  --radius-lg: 24px;
  --shadow-sm: 0 2px 8px rgb(15 23 42 / 5%);
  --focus-ring: 0 0 0 3px rgb(30 136 229 / 18%);
  --primary-gradient: linear-gradient(135deg, #1565c0, #0d47a1);
}

.dark {
  --bg: #09111f;
  --surface: #101a2d;
  --surface-raised: #142139;
  --surface-muted: #0c1729;
  --surface-accent: #13294d;
  --line: #24334b;
  --line-strong: #34455f;
  --text: #eef4ff;
  --text-soft: #b5c0d1;
  --text-muted: #8593aa;
  --accent: #60a5fa;
  --accent-soft: #18335e;
  --accent-ink: #06152f;
  --teal: #3fd0bd;
  --teal-soft: #123b3a;
  --teal-ink: #04241f;
  --status-normal: #42d18d;
  --status-monitor: #e5b94f;
  --status-warning: #ff9850;
  --status-critical: #ff7388;
  --shadow: 0 14px 34px rgb(0 0 0 / 24%);
  --focus-ring: 0 0 0 3px rgb(96 165 250 / 28%);
  --primary-gradient: linear-gradient(135deg, #60a5fa, #3b82f6);
  --glass-bg: rgba(16, 26, 45, 0.7);
  --glass-border: rgba(255, 255, 255, 0.05);
}

@media (prefers-color-scheme: dark) {
  :root:not(.light) {
    --bg: #09111f;
    --surface: #101a2d;
    --surface-raised: #142139;
    --surface-muted: #0c1729;
    --surface-accent: #13294d;
    --line: #24334b;
    --line-strong: #34455f;
    --text: #eef4ff;
    --text-soft: #b5c0d1;
    --text-muted: #8593aa;
    --accent: #60a5fa;
    --accent-soft: #18335e;
    --accent-ink: #06152f;
    --teal: #3fd0bd;
    --teal-soft: #123b3a;
    --teal-ink: #04241f;
    --status-normal: #42d18d;
    --status-monitor: #e5b94f;
    --status-warning: #ff9850;
    --status-critical: #ff7388;
    --shadow: 0 14px 34px rgb(0 0 0 / 24%);
    --focus-ring: 0 0 0 3px rgb(96 165 250 / 28%);
    --primary-gradient: linear-gradient(135deg, #60a5fa, #3b82f6);
    --glass-bg: rgba(16, 26, 45, 0.7);
    --glass-border: rgba(255, 255, 255, 0.05);
  }
}

/* --------------------------------------------------------------------------
 * 2. Dasar
 * ----------------------------------------------------------------------- */
* {
  box-sizing: border-box;
}

/* `hidden` harus menang atas setiap `display` yang dipasang kelas. Tanpa baris
   ini, elemen seperti `.field-error[hidden]` tetap tergambar karena kelasnya
   sudah menetapkan `display: flex`. */
[hidden] {
  display: none !important;
}

html {
  background: var(--bg);
  color-scheme: light;
  -webkit-text-size-adjust: 100%;
}

html.dark {
  color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
  html:not(.light) {
    color-scheme: dark;
  }
}

body {
  margin: 0;
  min-width: 320px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
}

button,
input,
select,
textarea {
  color: inherit;
  font: inherit;
}

button,
a {
  -webkit-tap-highlight-color: transparent;
}

/* Fokus keyboard selalu terlihat: 3px di luar batas kontrol supaya tidak
   tertutup border milik kontrol itu sendiri.

   Cincinnya PEKAT, bukan 42% tembus pandang. Yang lama menghasilkan
   rgb(56,84,133) di atas permukaan gelap — 2,3:1 terhadap latarnya, di bawah
   lantai 3:1 yang diminta WCAG 2.2 (1.4.11). Lapisan kedua berwarna latar di
   dalamnya menjaga cincin tetap terbaca di atas permukaan mana pun. */
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--bg);
}

a {
  color: inherit;
  text-decoration: none;
}

h1,
h2,
h3,
h4,
p {
  margin: 0;
}

h1,
h2,
h3 {
  letter-spacing: -0.035em;
  line-height: 1.18;
}

img {
  max-width: 100%;
}

.eyebrow,
.badge,
.chip,
.mono {
  font-family: var(--font-mono);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Lompat ke konten: tersembunyi sampai difokuskan lewat Tab. */
.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 120;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  transform: translateY(-180%);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

.noscript-banner {
  padding: 16px;
  background: var(--status-critical);
  color: #fff;
  font-weight: 600;
  text-align: center;
}

/* --------------------------------------------------------------------------
 * 3. Kerangka aplikasi
 * ----------------------------------------------------------------------- */
.app-shell {
  display: grid;
  min-height: 100dvh;
  grid-template-columns: var(--sidebar) minmax(0, 1fr);
}

.sidebar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  height: 100dvh;
  min-width: 0;
  flex-direction: column;
  border-right: 1px solid var(--line);
  background: var(--surface);
}

.sidebar.rail {
  width: 74px;
}

.app-shell:has(.sidebar.rail) {
  grid-template-columns: 74px minmax(0, 1fr);
}

.sidebar-head {
  display: flex;
  min-height: var(--topbar-height);
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 14px 14px 14px 18px;
  border-bottom: 1px solid var(--line);
}

.brand-mark {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 11px;
}

.brand-mark > span:last-child {
  display: grid;
  min-width: 0;
}

.brand-mark strong {
  font-size: 16px;
  line-height: 1.1;
}

.brand-mark small {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.brand-grid {
  display: grid;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 5px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-muted);
}

.brand-grid i {
  border-radius: 3px;
  background: var(--accent);
}

.brand-grid i:nth-child(2),
.brand-grid i:nth-child(3) {
  background: var(--teal);
}

.brand-grid i:nth-child(4) {
  background: var(--status-warning);
}

.rail-toggle {
  display: grid;
  width: var(--control-h-sm);
  height: var(--control-h-sm);
  flex: 0 0 auto;
  place-items: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.rail-toggle svg,
.icon-btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.7;
}

.rail-toggle:hover {
  background: var(--surface-muted);
  color: var(--text);
}

.icon-btn {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text-soft);
  cursor: pointer;
}

.icon-btn:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.sidebar nav {
  display: grid;
  align-content: start;
  gap: 3px;
  padding: 14px 10px;
  overflow-y: auto;
}

.nav-link {
  display: flex;
  width: 100%;
  min-height: 44px;
  align-items: center;
  gap: 11px;
  padding: 7px 12px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--text-soft);
  cursor: pointer;
  font: inherit;
  text-align: left;
  white-space: nowrap;
}

.nav-link:hover {
  background: var(--surface-muted);
  color: var(--text);
}

.nav-link.active {
  background: var(--surface-accent);
  color: var(--accent);
  font-weight: 600;
}

.nav-link svg {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.7;
}

/* Lencana hitung pada menu, mis. jumlah antrean yang masih menunggu. */
.nav-link b {
  margin-left: auto;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--accent) 14%, var(--surface));
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 10px;
}

.sidebar.rail .nav-link {
  position: relative;
  justify-content: center;
  padding-inline: 8px;
}

/* Lencana antrean TIDAK ikut disembunyikan: ia dulu ada di daftar ini bersama
   label teksnya, sehingga menciutkan menu — yang dilakukan supervisor justru
   untuk meluaskan papan — ikut membuang hitungan truk menunggu. */
.sidebar.rail .nav-link span,
.sidebar.rail .brand-mark > span:last-child,
.sidebar.rail .sidebar-user {
  display: none;
}

.sidebar.rail .nav-link b {
  position: absolute;
  top: 2px;
  right: 10px;
  display: grid;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  place-items: center;
}

.sidebar-foot {
  display: grid;
  gap: 6px;
  margin-top: auto;
  padding: 12px 10px;
  border-top: 1px solid var(--line);
}

.sidebar-user {
  display: grid;
  min-width: 0;
  gap: 1px;
  padding: 4px 12px 8px;
}

.sidebar-user strong {
  overflow: hidden;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-main {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 25;
  display: flex;
  min-height: var(--topbar-height);
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 11px 22px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in oklab, var(--surface) 92%, transparent);
  backdrop-filter: blur(14px);
}

.topbar-context {
  min-width: 0;
}

.topbar-context strong,
.topbar-context small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar-context strong {
  font-size: 13px;
}

.topbar-context small {
  color: var(--text-muted);
  font-size: 11px;
}

/* Hanya tampil saat sidebar desktop diciutkan: selain itu sidebar (desktop)
   atau chip gudang di judul (ponsel) sudah menyebutnya. */
.topbar-context {
  display: none;
}

@media (min-width: 901px) {
  .app-shell:has(.sidebar.rail) .topbar-context {
    display: grid;
  }
}

.topbar-actions,
.page-actions,
.section-actions,
.table-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Pil status di kiri, kontrol di kanan. */
.topbar-actions {
  min-width: 0;
  flex: 1 1 auto;
  justify-content: flex-end;
}

.topbar-actions .data-mode {
  margin-right: auto;
}

/* Pemilih gudang di topbar.

   Selalu terlihat, di setiap lebar layar: dengan dua gudang aktif, "antrean
   gudang mana yang sedang kulihat" adalah pertanyaan pertama sebelum membaca
   satu kartu pun — dan nama gudang di sidebar tidak terlihat dari ponsel.
   `<select>` asli dipertahankan di dalamnya supaya tablet membuka pemilih
   sistem yang besar; bingkainyalah yang dibuat menyerupai pil status. */
.site-chip {
  display: inline-flex;
  min-height: 36px;
  flex: 0 0 auto;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  border: 1px solid color-mix(in oklab, var(--accent) 35%, var(--line));
  border-radius: 9px;
  background: var(--surface-accent);
  color: var(--text);
  font-size: 12px;
}

.site-chip svg {
  flex: 0 0 auto;
  color: var(--accent);
}

.site-chip b {
  font-family: var(--font-mono);
  font-weight: 700;
}

.site-chip > span {
  overflow: hidden;
  max-width: 12ch;
  color: var(--text-soft);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.site-chip-select {
  position: relative;
  cursor: pointer;
}

.site-chip-select:hover {
  border-color: var(--accent);
}

.site-chip-select:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.site-chip .site-chip-caret {
  color: var(--text-muted);
}

/* Select asli menutupi seluruh chip tanpa terlihat: ketukan di mana pun pada
   chip membuka pemilih sistem, dan teks yang terbaca tetap milik chip. */
.site-chip select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  font-size: 16px;
}

/* Kelompok tombol MEMBUNGKUS; hanya topbar yang tidak, karena tingginya tetap.
   Tanpa ini tiga tombol tema di layar Pengaturan meluber 21 piksel melewati tepi
   kartunya pada layar 320px — dan tombol terakhir, "Ikut sistem", terpotong
   separuh oleh `overflow: hidden` milik `.section`. */
.page-actions,
.section-actions,
.table-actions {
  flex-wrap: wrap;
}

.eyebrow {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Indikator koneksi/sinkronisasi di topbar.
 *
 * Kelas `.sync-control` yang dulu menemani `.data-mode` di sini sudah dihapus:
 * tidak ada satu pun berkas yang pernah merendernya, dan salinan aturan titik
 * `paused`/`offline` miliknya membuat seolah ada dua indikator yang harus
 * dijaga selaras. */
.data-mode {
  display: inline-flex;
  min-height: 36px;
  align-items: center;
  gap: 7px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: 12px;
}

.data-mode i,
.metric-label i {
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--teal);
}

.live-clock {
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
 * 4. Ruang kerja & judul halaman
 * ----------------------------------------------------------------------- */
.workspace {
  width: 100%;
  min-width: 0;
  max-width: 1680px;
  margin: 0 auto;
  padding: 24px;
  /* Topbar melekat di atas dan menutupi 72 piksel pertama halaman. Apa pun yang
     digulir ke dalam pandangan oleh browser — fokus keyboard, tautan lompat,
     pemulihan gulir — karena itu harus berhenti di bawahnya, bukan di baliknya. */
  scroll-margin-top: calc(var(--topbar-height) + 8px);
}

.dashboard-page {
  display: grid;
  min-width: 0;
  gap: 18px;
}

/* Anak grid diberi `min-width: 0` secara menyeluruh.

   Bawaan CSS untuk item grid adalah `min-width: auto`, yang berarti "jangan
   pernah lebih sempit dari isi terlebar di dalammu". Satu anak yang memuat
   sesuatu yang lebar — rel dok berisi sembilan ubin, sebuah tabel, sebuah
   grafik — karena itu MELEBARKAN seluruh kolom alih-alih menggulir sendiri,
   dan seluruh halaman ikut meluber ke samping.

   Terukur di ponsel 390px sebelum baris ini ada: halaman menjadi 820px, dan
   rel dok yang seharusnya menggulir justru mendorong judul halaman keluar
   layar. */
.dashboard-page > * {
  min-width: 0;
}

.page-header {
  display: flex;
  min-width: 0;
  align-items: end;
  justify-content: space-between;
  gap: 24px;
}

.page-header > div:first-child {
  min-width: 0;
  max-width: 860px;
}

.page-header h1 {
  font-size: clamp(24px, 2.6vw, 32px);
}

.page-scope {
  display: inline-block;
  margin-right: 8px;
  padding: 3px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface-muted);
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: 0.5em;
  letter-spacing: 0.06em;
  vertical-align: middle;
}

.dashboard-grid {
  display: grid;
  min-width: 0;
  gap: 16px;
}

.dashboard-grid-main {
  grid-template-columns: minmax(0, 1.65fr) minmax(300px, 0.75fr);
  /* Kolom TIDAK diregangkan menyamai tetangganya. `stretch` adalah bawaan grid,
     dan pada kolom yang berbeda panjang ia menghasilkan kartu berisi udara —
     di Pengaturan, kartu setinggi 370px diregangkan menjadi 1.875px. */
  align-items: start;
}

/* --------------------------------------------------------------------------
 * Kisi Pengaturan
 *
 * Pengaturan bukan "satu kolom utama dan satu pendamping" seperti Pendaftaran:
 * isinya kartu setara yang panjangnya berbeda. Kisi 1,65 : 0,75 menaruh satu
 * kartu pendek di kolom lebar dan menjejalkan enam sisanya ke kolom 334 piksel,
 * tempat judul "Cookie sesi Superset" pecah dua baris dan peringatannya sepuluh.
 * ----------------------------------------------------------------------- */
.settings-grid {
  display: grid;
  min-width: 0;
  gap: 16px;
  /* `min(100%, 320px)`, bukan 320px polos: di kolom yang lebih sempit daripada
     320px, lantai polos memaksa kisinya meluber keluar layar. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  align-items: start;
}

/* Kelompok "Sumber data": kartu berisi isian dan fakta, dua sejajar. Lantai
   420 piksel membuat keduanya menumpuk di tablet potret alih-alih menjadi dua
   kolom yang terlalu sempit untuk kotak isiannya.

   Kartu tidak lagi MELEBAR menyeberangi kolom (`settings-span` dua kolom di
   kisi tiga kolom). Itulah yang dulu meninggalkan kolom ketiga kosong setinggi
   lebih dari seribu piksel di sebelah kartu Akun, Lembar, dan Cookie. */
.settings-grid-two {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
}

/* Kelompok "Aturan & akses": tabel yang memang menuntut lebar penuh. */
.settings-grid-one {
  grid-template-columns: minmax(0, 1fr);
}

/* Tiga pilihan tema setara, jadi kolomnya sama lebar. Dengan fleks, "Ikut
   sistem" dua kali lebih lebar daripada "Gelap" dan ketiganya terbaca sebagai
   tiga hal yang berbeda bobot. */
.theme-picker {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}

.theme-picker .btn {
  min-width: 0;
  padding-inline: 8px;
}

/* --------------------------------------------------------------------------
 * 5. Primitif
 * ----------------------------------------------------------------------- */
.card {
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 0 1px 0 rgb(16 33 58 / 2%);
}

.section {
  overflow: hidden;
}

.section-head {
  display: flex;
  min-height: 60px;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}

.section-head h2 {
  margin-top: 2px;
  font-size: 17px;
}

.section-body {
  min-width: 0;
  padding: 16px;
}

.section-body.flush {
  padding: 0;
}

.section-note {
  margin-top: 12px;
  color: var(--text-muted);
  font-size: 11px;
}

.btn {
  display: inline-flex;
  min-height: 40px;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 8px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.btn svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

.btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}

.btn:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

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

.btn-primary:hover:not(:disabled) {
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 88%, var(--accent-ink));
  color: var(--accent-ink);
}

.btn-teal {
  border-color: var(--teal);
  background: var(--teal);
  color: var(--teal-ink);
}

.btn-teal:hover:not(:disabled) {
  border-color: var(--teal);
  background: color-mix(in oklab, var(--teal) 88%, var(--teal-ink));
  color: var(--teal-ink);
}

.btn-ghost {
  border-color: transparent;
  background: transparent;
}

.btn-danger {
  color: var(--status-critical);
}

.btn-danger:hover:not(:disabled) {
  border-color: var(--status-critical);
  background: color-mix(in oklab, var(--status-critical) 12%, var(--surface));
  color: var(--status-critical);
}

.btn-sm {
  min-height: 34px;
  padding: 5px 10px;
  font-size: 11px;
}

/* Aksi utama di kartu antrean: cukup besar untuk ditekan bersarung tangan. */
.btn-block {
  width: 100%;
  min-height: 48px;
  font-size: 13px;
}

.btn.is-busy {
  cursor: progress;
  opacity: 0.7;
  pointer-events: none;
}

label {
  display: grid;
  min-width: 0;
  gap: 5px;
}

label > span,
.field > span,
.field-caption > span {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

label > small,
.field > small {
  color: var(--text-muted);
  font-size: 10px;
}

.input {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 10px 11px;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text);
}

.input:hover {
  border-color: var(--accent);
}

textarea.input {
  min-height: 76px;
  resize: vertical;
}

select.input {
  appearance: none;
  padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 17px) 50%, calc(100% - 12px) 50%;
  background-repeat: no-repeat;
  background-size: 5px 5px, 5px 5px;
}

/* Kesalahan input ditandai border + ikon + teks, bukan warna saja, supaya
   tetap terbaca oleh operator dengan buta warna. */
.input[aria-invalid="true"] {
  border-color: var(--status-critical);
  background: color-mix(in oklab, var(--status-critical) 6%, var(--surface));
}

/* Penanda medan wajib: empat dari delapan isian pendaftaran wajib, jadi yang
   ditandai adalah yang wajib. */
.req {
  color: var(--status-critical);
  font-weight: 700;
}

.req-legend {
  color: var(--text-muted);
}

.field-error {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--status-critical);
  font-size: 11px;
  font-weight: 600;
}

.field-error::before {
  display: grid;
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--status-critical);
  color: #fff;
  content: "!";
  font-size: 10px;
  line-height: 1;
  place-items: center;
}

.check-label {
  display: flex;
  min-height: 44px;
  align-items: center;
  gap: 8px;
  color: var(--text-soft);
  cursor: pointer;
  font-size: 12px;
}

.check-label input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

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

/* Kisi satu kolom untuk kartu yang memang berdiri di kolom pendamping. Dua
   kolom di dalam kartu selebar 300 piksel menyisakan 130 piksel per kotak —
   cukup untuk bingkainya, tidak cukup untuk isinya. */
.form-grid-one {
  grid-template-columns: minmax(0, 1fr);
}

.form-grid-one .span-2 {
  grid-column: span 1;
}

.form-grid .span-2 {
  grid-column: span 2;
}

/* Bilah filter — FLEKS, bukan grid berkolom tetap.
 *
 * Bentuk sebelumnya memaku lima kolom untuk bilah yang isinya berbeda di tiap
 * halaman: papan mengisi tiga, Laporan dan Analitik dua. Kolom yang tidak
 * terisi tetap memakan lebarnya, jadi papan menyisakan dua kolom kosong selebar
 * 300 piksel di tengah bilah dengan chip "8 tiket" terdampar di kolom ketiga —
 * dan Laporan menambalnya dengan dua `<div></div>` kosong, yang hanya
 * memindahkan persoalan. Dengan fleks tiap bilah selebar isinya sendiri. */
.filter-bar {
  display: flex;
  min-width: 0;
  flex-wrap: wrap;
  align-items: end;
  gap: 10px 12px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.filter-bar > label,
.filter-bar > .field {
  min-width: 150px;
  max-width: 320px;
  flex: 1 1 180px;
}

/* Kotak pencarian tumbuh lebih dulu: ia yang paling sering dipakai, dan
   isinya — nomor antrean, vendor, plat, PO — yang paling panjang. */
.filter-bar > label:has(input[type="search"]),
.filter-bar > .field:has(input[type="search"]) {
  max-width: none;
  flex: 3 1 240px;
}

/* Kotak tanggal tidak perlu tumbuh: isinya selalu sepuluh karakter, dan ruang
   yang tersisa lebih berguna bagi tombol prasetel di sebelahnya. */
.filter-bar > .field:has(input[type="date"]) {
  max-width: 190px;
  flex: 0 1 170px;
}

.filter-bar .table-actions {
  flex: 0 0 auto;
  margin-left: auto;
}

/* Cari selebar baris, saringan berjajar rata di bawahnya. Tanpa ini lima
   pemilih Laporan menumpuk dua baris di kanan dan kotak cari turun ke dasar
   baris yang sama — menyisakan petak kosong selebar separuh kartu di kiri
   atas, tepat di tempat mata mulai membaca. */
.filter-bar-stack > label:has(input[type="search"]),
.filter-bar-stack > .filter-more {
  flex: 1 1 100%;
}

.filter-bar-stack .filter-more-body > label {
  flex: 1 1 160px;
}

/* Jumlah hasil: keterangan, bukan kontrol. Sebagai `.chip` setinggi 22px berisi
   huruf 10px, bingkainya menjanjikan sesuatu yang dapat ditekan — padahal ia
   satu-satunya penanda bahwa papan sedang menyembunyikan tiket. */
.filter-count {
  flex: 0 0 auto;
  margin-left: auto;
  padding-bottom: 9px;
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Keadaan kosong yang menyebutkan sebabnya harus menyebutkan jalan keluarnya. */
.empty-action {
  display: flex;
  justify-content: center;
  margin-top: -8px;
  padding-bottom: 18px;
}

.chip,
.badge {
  display: inline-flex;
  max-width: 100%;
  min-height: 22px;
  align-items: center;
  padding: 3px 7px;
  border: 1px solid var(--line-strong);
  border-radius: 7px;
  color: var(--text-soft);
  font-size: 10px;
  line-height: 1;
  white-space: nowrap;
}

.chip + .chip {
  margin-left: 3px;
}

.chip-accent {
  border-color: color-mix(in oklab, var(--accent) 46%, var(--line));
  background: color-mix(in oklab, var(--accent) 7%, var(--surface));
  color: var(--accent);
}

.badge {
  border: 0;
  font-weight: 700;
}

.badge-muted {
  background: color-mix(in oklab, var(--text-muted) 14%, var(--surface));
  color: var(--text-muted);
}

.badge-normal {
  background: color-mix(in oklab, var(--status-normal) 14%, var(--surface));
  color: var(--status-normal);
}

.badge-monitor {
  background: color-mix(in oklab, var(--status-monitor) 16%, var(--surface));
  color: var(--status-monitor);
}

.badge-warning {
  background: color-mix(in oklab, var(--status-warning) 15%, var(--surface));
  color: var(--status-warning);
}

.badge-critical {
  background: color-mix(in oklab, var(--status-critical) 15%, var(--surface));
  color: var(--status-critical);
}

.badge-accent {
  background: color-mix(in oklab, var(--accent) 12%, var(--surface));
  color: var(--accent);
}

.badge-teal {
  background: var(--teal-soft);
  color: var(--teal);
}

.metric-strip {
  display: grid;
  min-width: 0;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.metric-strip-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.metric-strip-four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.metric-strip-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.metric-card {
  position: relative;
  display: grid;
  min-width: 0;
  min-height: 108px;
  align-content: center;
  gap: 5px;
  padding: 17px 18px;
  border-right: 1px solid var(--line);
  background: linear-gradient(
    145deg,
    color-mix(in oklab, var(--metric-tone) 5%, transparent),
    transparent 54%
  );
}

.metric-card::before {
  position: absolute;
  top: 0;
  right: 18px;
  left: 18px;
  height: 2px;
  border-radius: 0 0 999px 999px;
  background: color-mix(in oklab, var(--metric-tone) 75%, transparent);
  content: "";
}

.metric-card:last-child {
  border-right: 0;
}

.metric-label {
  display: flex;
  align-items: center;
  gap: 7px;
  overflow: hidden;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

.metric-label i {
  background: var(--metric-tone);
}

.metric-value {
  overflow: hidden;
  font-family: var(--font-mono);
  /* Diturunkan bersama peralihan ke HH:MM:SS: pada 32 piksel, "00:45:00"
     menuntut 128 piksel sementara kartu metrik di kisi 2x2 ponsel hanya
     menyisakan 144 piksel termasuk padding. */
  font-size: clamp(21px, 2vw, 28px);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1.15;
  text-overflow: ellipsis;
}

.metric-sub {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

/* Tabel riwayat membawa tiga belas kolom; pada laptop 1366 dua ratus empat puluh
   tujuh piksel di antaranya berada di luar pandangan — termasuk vonis SLA, satu-
   satunya alasan tabel ini dibuka. Bayangan di tepi kanan menandai bahwa
   gulungan itu ada; kolom pertama yang tidak ikut tergulir menjaga baris yang
   sedang dibaca tetap bernama. */
.table-scroll {
  position: relative;
  min-width: 0;
  max-width: 100%;
  overflow: auto;
  scrollbar-gutter: stable;
  /* Bayangan menghilang di ujung gulungan: `local` menempel pada isi yang
     tergulir, `scroll` pada kotaknya, dan pasangannya saling menutup. */
  background:
    linear-gradient(to left, var(--surface), transparent 24px) right / 24px 100% no-repeat local,
    linear-gradient(to left, color-mix(in oklab, var(--text) 22%, transparent), transparent 12px)
      right / 12px 100% no-repeat scroll;
}

/* Hanya tabel yang memang menggulir memakai kelas ini. */
.tbl-sticky th:first-child,
.tbl-sticky td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface);
}

.tbl-sticky thead th:first-child {
  z-index: 3;
}

/* Batas kolom beku, supaya isi yang lewat di belakangnya tidak terbaca menyatu. */
.tbl-sticky th:first-child::after,
.tbl-sticky td:first-child::after {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 1px;
  background: var(--line);
  content: "";
}

.tbl th,
.tbl td {
  min-width: 86px;
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: middle;
  /* Sel TIDAK membungkus; `.table-scroll` di sekelilingnya yang menggulir.
     Sebelumnya sel membungkus, dan nama vendor sepanjang "PT Sukanda Djaya
     Diamond Cold Storage Indonesia Tbk" membuat satu baris tabel setinggi enam
     baris teks — sekitar 250 piksel untuk satu tiket, sehingga empat tiket saja
     sudah menghabiskan layar ponsel. */
  white-space: nowrap;
}

/* Kolom teks bebas yang boleh sangat panjang. Ia dipotong dengan ellipsis
   supaya satu nama vendor tidak melebarkan tabel sampai kolom di sebelahnya
   tidak pernah terlihat; nilai penuhnya tetap ada di atribut `title`. */
.tbl td.cell-text {
  max-width: 26ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Sel yang dapat disunting: satu-satunya sel tabel yang menanggapi ketukan. */
.tbl td.is-editable {
  cursor: pointer;
}

.tbl thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-muted);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.tbl tbody tr:last-child > * {
  border-bottom: 0;
}

.tbl tbody tr:hover > * {
  background: var(--surface-muted);
}

.tbl .numeric {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.empty-state {
  display: grid;
  justify-items: center;
  gap: 8px;
  margin: 0;
  padding: 28px 24px;
  border: 1px dashed var(--line-strong);
  border-radius: 10px;
  color: var(--text-muted);
  text-align: center;
}

.empty-state strong {
  color: var(--text);
}

.empty-state svg {
  width: 28px;
  height: 28px;
  fill: none;
  opacity: 0.6;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.5;
}

/* Spanduk galat pada papan.
 *
 * Kegagalan memuat dulu hanya mengisi `state.error` yang tidak pernah
 * dirender, sehingga backend yang bermasalah tampak persis seperti hari yang
 * sepi: papan kosong, tanpa satu pun petunjuk. */
.banner {
  display: grid;
  gap: 6px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--status-critical);
  border-radius: 10px;
  background: color-mix(in oklab, var(--status-critical) 8%, var(--surface));
}

.banner strong {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--status-critical);
  font-size: 14px;
}

.banner p {
  margin: 0;
  max-width: 78ch;
  color: var(--text-soft);
  font-size: 13px;
}

.banner code {
  padding: 2px 6px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--surface-muted);
  font-family: var(--font-mono);
  font-size: 12px;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
 * 6. Dialog & toast
 * ----------------------------------------------------------------------- */
.modal-open {
  overflow: hidden;
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  padding: 20px;
  backdrop-filter: blur(5px);
  background: rgb(3 10 22 / 62%);
  place-items: center;
}

.modal-card {
  width: min(560px, 100%);
  max-height: min(84dvh, 900px);
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  background: var(--surface);
  box-shadow: var(--shadow);
}

.modal-head,
.modal-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 16px;
}

.modal-head {
  border-bottom: 1px solid var(--line);
}

.modal-head h2 {
  margin-top: 3px;
  font-size: 20px;
}

.modal-body {
  max-height: calc(84dvh - 132px);
  overflow: auto;
  padding: 16px;
}

.modal-footer {
  justify-content: flex-end;
  border-top: 1px solid var(--line);
  background: var(--surface-muted);
}

.toast-stack {
  position: fixed;
  bottom: 24px;
  left: 50%;
  z-index: 100;
  display: grid;
  gap: 8px;
  justify-items: center;
  transform: translateX(-50%);
  pointer-events: none;
}

.toast {
  display: flex;
  max-width: min(92vw, 520px);
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  box-shadow: var(--shadow);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
}

.toast svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
}

.toast-success { color: var(--status-normal); }
.toast-error { color: var(--status-critical); }
.toast-info { color: var(--accent); }

/* --------------------------------------------------------------------------
 * 7. Hitung mundur SLA
 *
 * Angka terpenting di aplikasi ini. Ia berdiri sendiri sebagai elemen dengan
 * `data-deadline`/`data-started` supaya ticker satu detik hanya perlu menulis
 * ulang teks di dalamnya — tidak pernah me-render ulang kartu atau tabel.
 * ----------------------------------------------------------------------- */
.sla {
  display: inline-grid;
  gap: 2px;
  min-width: 0;
}

.sla-value {
  font-family: var(--font-mono);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.1;
}

.sla-note {
  color: var(--text-muted);
  font-size: 10px;
  white-space: nowrap;
}

.sla-idle .sla-value { color: var(--text-muted); }
.sla-none .sla-value { color: var(--text-muted); }
.sla-running .sla-value { color: var(--status-normal); }
.sla-warning .sla-value { color: var(--status-warning); }
.sla-breached .sla-value { color: var(--status-critical); }
.sla-met .sla-value { color: var(--status-normal); }
.sla-missed .sla-value { color: var(--status-critical); }

/* Hitung mundur besar di kartu antrean. */
.sla-hero {
  display: grid;
  gap: 4px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-muted);
  text-align: center;
}

.sla-hero .sla-value {
  font-size: clamp(28px, 4vw, 40px);
}

/* Tiket yang belum mulai bongkar TIDAK memakai ukuran hitung mundur: dalam
   empat puluh piksel, "Target 2 jam" yang diam terbaca sama mendesaknya dengan
   "+48:55" yang berdetak di kartu sebelahnya. */
.sla-hero.sla-idle .sla-value,
.sla-hero.sla-none .sla-value {
  font-size: 19px;
  font-weight: 600;
}

.sla-hero.sla-idle,
.sla-hero.sla-none {
  border-style: dashed;
}

.sla-hero.sla-breached {
  border-color: color-mix(in oklab, var(--status-critical) 45%, var(--line));
  background: color-mix(in oklab, var(--status-critical) 9%, var(--surface));
}

.sla-hero.sla-warning {
  border-color: color-mix(in oklab, var(--status-warning) 45%, var(--line));
  background: color-mix(in oklab, var(--status-warning) 9%, var(--surface));
}

/* Denyut hanya pada keadaan terlambat, dan hanya bila pemakai tidak meminta
   gerakan minimal. */
.sla-hero.sla-breached .sla-value {
  animation: sla-pulse 1.6s ease-in-out infinite;
}

/* --------------------------------------------------------------------------
 * 8. Papan antrean
 * ----------------------------------------------------------------------- */
.board-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}

.queue-card {
  display: grid;
  min-width: 0;
  /* KOLOM IMPLISIT SEBUAH GRID BERUKURAN `auto`, DAN `auto` BERARTI MAX-CONTENT.
   *
   * `min-width: 0` pada kartunya sendiri tidak menolong: yang melebar bukan
   * kartunya melainkan KOLOM di dalamnya, yang diukur dari anak terlebar —
   * deretan tombol aksi, atau daftar chip PO. Akibatnya seluruh anak ikut
   * diregangkan ke lebar itu dan menonjol keluar dari kartu; terukur 37 piksel
   * per kartu, yang menjadikan seluruh halaman menggulir ke samping.
   *
   * `minmax(0, 1fr)` memberi kolomnya izin menyusut di bawah lebar isinya,
   * sehingga yang menggulir kembali menjadi elemen yang memang punya
   * penggulungnya sendiri. */
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--queue-tone, var(--line-strong));
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 0 1px 0 rgb(16 33 58 / 2%);
}

.queue-card-head {
  display: flex;
  min-width: 0;
  align-items: start;
  justify-content: space-between;
  gap: 12px;
}

/* IDENTITAS KARTU: NAMA VENDOR DULU, NOMOR ANTREAN SESUDAHNYA.
 *
 * Urutannya pernah terbalik, dan bobot visualnya ikut terbalik: nomor antrean
 * dicetak 21px monospace sementara nama vendor menjadi teks 11px redup di
 * bawahnya. Yang terbaca dari seberang lorong karena itu adalah "REG 3-7" —
 * deret yang dibangkitkan server, tidak pernah diucapkan di lantai, dan tidak
 * menjawab satu pun pertanyaan yang benar-benar ditanyakan di depan papan.
 *
 * Nomornya tidak dibuang: ia turun menjadi chip mono di baris kedua bersama
 * armada dan plat, masih dapat disalin ke radio, tetapi tidak lagi merebut
 * satu-satunya baris yang terbaca dari jauh. */
.queue-ident {
  display: grid;
  min-width: 0;
  gap: 4px;
}

.queue-vendor {
  overflow: hidden;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.queue-sub {
  display: flex;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  color: var(--text-muted);
  font-size: 11px;
}

/* Pemisah dibuat oleh titik yang digambar CSS, bukan oleh karakter di markup:
   satu chip yang kosong tidak boleh meninggalkan titik yang menggantung. */
.queue-sub > span + span::before {
  content: "·";
  margin-right: 8px;
  color: var(--line-strong);
}

.queue-sub .queue-no {
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-muted);
  color: var(--text-soft);
  font-weight: 700;
  letter-spacing: 0.03em;
}

.queue-flags {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 6px;
}

.queue-facts {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.fact {
  display: grid;
  min-width: 0;
  gap: 2px;
}

/* Selektor anak LANGSUNG. `.fact span` dulu juga mengenai `<span class="mono">`
   yang dipasang `elapsedMarkup()` DI DALAM `<strong>`, sehingga lama tunggu
   driver — satu-satunya angka di kartu yang bergerak tiap detik — digambar
   memakai gaya LABEL: 9px, huruf besar, redup. */
.fact > span {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.fact strong {
  overflow: hidden;
  font-size: 13px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fact strong.mono,
.fact strong .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Pembungkus di dalam `<strong>` mewarisi ukuran dan bobot induknya, jadi
   pembungkus berikutnya tidak mengulang cacat di atas. */
.fact strong > * {
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: none;
}

/* Aksi kartu. Kolomnya MEMBUNGKUS, tidak dipaksa berjajar.
 *
 * `grid-auto-flow: column` memeras berapa pun jumlah tombol ke satu baris
 * selebar sama. Kartu WAITING punya empat, jadi pada kartu 320px tiap tombol
 * mendapat 78 piksel untuk label yang butuh 94 — dan karena tombolnya
 * `white-space: nowrap`, teksnya meluber dan bertumpuk dengan tetangganya:
 * "Catat datang" menabrak "Panggil", "Panggil" menabrak "Mulai bongkar".
 * `auto-fit` memberi tiap tombol lantai selebar label terpanjangnya, dan yang
 * tidak muat turun ke baris berikutnya. */
.queue-actions {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(126px, 1fr));
}

/* Satu tinggi untuk seluruh aksi kartu, dan aksi utama selebar kartu di baris
   pertama. Tombol kecil yang bercampur dengan tombol besar di satu baris
   membuat barisnya setinggi yang terbesar dan tombol kecilnya tampak melayang;
   aksi utama yang berpindah posisi dari kartu ke kartu harus dicari ulang di
   setiap kartu. */
.queue-actions > .btn {
  min-height: var(--control-h);
}

.queue-actions > .btn-primary,
.queue-actions > .btn-teal {
  grid-column: 1 / -1;
}

.queue-po-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Chip PO dengan muatannya: nomor lalu SKU/qty-nya, satu unit agar pelipatan
   baris tidak memisahkannya. */
/* Isi PO boleh turun ke baris berikutnya: di kartu sempit (ponsel, kolom
   papan 300px) teks di sebelah chip dulu terdorong keluar tepi kartu dan
   terpotong, sehingga qty dan SKU per PO tidak pernah terlihat. */
.queue-po-chip {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 8px;
  min-width: 0;
  max-width: 100%;
}

.queue-po-chip small {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
 * 9. Input plat nomor & armada
 * ----------------------------------------------------------------------- */
/* Lebarnya dibatasi supaya ketiga kotak tetap sebanding dengan plat yang
   sebenarnya. Tanpa batas ini kolom tengah mengambil seluruh sisa baris — di
   desktop sekitar 400 piksel untuk empat digit, dengan kode wilayah dan huruf
   seri terdampar jauh di kedua ujungnya, dan bentuk yang seharusnya langsung
   dikenali sebagai plat nomor berhenti terlihat seperti plat nomor. */
.plate-input {
  display: grid;
  max-width: 330px;
  grid-template-columns: 62px minmax(0, 1fr) 78px;
  gap: 6px;
}

.plate-input input {
  min-height: 48px;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: center;
  text-transform: uppercase;
}

.plate-input input:hover {
  border-color: var(--accent);
}

.plate-input input[aria-invalid="true"] {
  border-color: var(--status-critical);
  background: color-mix(in oklab, var(--status-critical) 6%, var(--surface));
}

/* Pratinjau plat — KETERANGAN, bukan medan keempat. Bingkai 2px dan latar
   permukaan membuatnya terbaca sebagai kotak isian yang menolak diketik: ia
   berdiri di bawah tiga kotak plat, seukuran mereka. */
.plate-preview {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 22px;
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.plate-preview::before {
  content: "Tersimpan sebagai";
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Daftar PO terpilih pada form pendaftaran.
 *
 * Bentuk sebelumnya chip berisi nomor PO saja. Vendor, jumlah SKU, dan qty
 * hanya tampil di daftar saran — tepat SEBELUM operator memilih — lalu hilang
 * begitu ia memilih, padahal justru sesudah memilih itulah angkanya perlu
 * dicocokkan dengan surat jalan. */
.po-picker {
  display: grid;
  gap: 10px;
}

.po-list {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.po-row {
  display: grid;
  gap: 4px;
  padding: 10px 12px;
  border: 1px solid color-mix(in oklab, var(--accent) 32%, var(--line));
  border-radius: 11px;
  background: color-mix(in oklab, var(--accent) 5%, var(--surface));
}

/* PO manual tidak punya baris master, jadi ia sengaja TIDAK memakai nada aksen
   yang sama: angka nol di sebelah PO tervalidasi terbaca sebagai "kosong",
   bukan sebagai "belum diperiksa siapa pun". */
.po-row.is-manual {
  border-color: color-mix(in oklab, var(--status-warning) 38%, var(--line));
  background: color-mix(in oklab, var(--status-warning) 6%, var(--surface));
}

.po-row-head {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 8px;
}

.po-row-head strong {
  overflow: hidden;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  letter-spacing: 0.03em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.po-row-remove {
  display: grid;
  width: var(--control-h-sm);
  height: var(--control-h-sm);
  flex: 0 0 auto;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  place-items: center;
}

.po-row-remove:hover {
  background: color-mix(in oklab, var(--status-critical) 12%, transparent);
  color: var(--status-critical);
}

.po-row-remove svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-width: 2;
}

.po-row-vendor {
  overflow: hidden;
  color: var(--text-soft);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Angka PO. `i` adalah labelnya, `b` nilainya — keduanya sengaja pendek supaya
   dua fakta muat berdampingan pada kartu selebar kolom kanan. */
.po-row-facts,
.po-total {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  margin: 0;
}

.po-row-facts span,
.po-total span {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}

.po-row-facts i,
.po-total i {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.po-row-facts b,
.po-total b {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* Kotak jumlah kendaraan duduk sebaris dengan SKU dan Qty. Angkanya di tengah
   supaya dua digit tidak menggantung di tepi kanan seperti kolom angka tabel;
   selector menyebut elemennya agar menang atas `.input-sm` yang rata kanan. */
.po-row-facts .po-row-vehicle input {
  text-align: center;
}

/* Total seluruh PO pada satu tiket: itulah angka yang benar-benar dicocokkan
   dengan muatan truk, karena satu truk kerap membawa beberapa PO. */
.po-total {
  padding-top: 8px;
  border-top: 1px dashed var(--line-strong);
  color: var(--text-soft);
  font-size: 12px;
}

.po-total > span:first-child {
  font-weight: 600;
}

.po-suggestion {
  display: grid;
  gap: 2px;
  padding: 9px 12px;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.po-suggestion:last-child {
  border-bottom: 0;
}

.po-suggestion:hover {
  background: var(--surface-muted);
}

.po-suggestion strong {
  font-family: var(--font-mono);
  font-size: 12px;
}

.po-suggestion small {
  color: var(--text-muted);
  font-size: 11px;
}

/* --------------------------------------------------------------------------
 * 10. Layar masuk
 * ----------------------------------------------------------------------- */
.login-shell {
  display: grid;
  min-height: 100dvh;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.login-art {
  position: relative;
  display: grid;
  align-content: end;
  gap: 10px;
  padding: 48px;
  background: linear-gradient(150deg, var(--surface-accent), var(--surface));
  overflow: hidden;
}

.login-art img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Tirai di sepertiga bawah: tanpa ini, judul dan deskripsi duduk di atas
   ilustrasi penuh warna dan kontrasnya jatuh di bawah ambang WCAG.
 *
 * Ia baru mencapai warna penuh pada 100%, artinya tepat di baris terakhir —
 * dan blok teksnya menempati kira-kira seperlima bawah panel, jadi hampir
 * seluruhnya duduk di atas tirai yang masih tembus pandang. Ilustrasinya
 * kebetulan memuat huruf raksasa sendiri persis di pita itu, sehingga eyebrow
 * "ASTRONAUTS OPERATIONS" dan judul aplikasi terbaca bertumpuk di atas tulisan
 * milik gambar. Tirainya karena itu pekat sebelum blok teks dimulai, bukan
 * sesudahnya. */
.login-art::after {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    transparent 34%,
    color-mix(in oklab, var(--bg) 68%, transparent) 62%,
    var(--bg) 82%
  );
  content: "";
}

.login-art > *:not(img) {
  position: relative;
  z-index: 1;
}

.login-art h1 {
  font-size: clamp(26px, 3vw, 38px);
}

.login-form h2 {
  margin-top: 4px;
  font-size: 26px;
}

.login-art p {
  max-width: 46ch;
  color: var(--text-soft);
}

.login-panel {
  display: grid;
  align-content: center;
  justify-items: center;
  padding: 32px;
}

.login-form {
  display: grid;
  width: min(380px, 100%);
  gap: 14px;
}

.login-form img {
  width: 56px;
  height: 56px;
  object-fit: contain;
}

.login-form label {
  display: grid;
  gap: 6px;
}

/* Tombol "Lihat" duduk DI DALAM baris sandi, bukan di bawahnya: ia milik
   kotak itu, dan di ponsel baris tambahan mendorong tombol Masuk ke bawah
   papan ketik. */
.password-field {
  display: flex;
  gap: 6px;
}

.password-field .input {
  min-width: 0;
  flex: 1 1 auto;
}

.password-field .btn {
  flex: 0 0 auto;
  align-self: stretch;
}

.field-hint {
  color: var(--status-warning);
  font-size: 12px;
}

/* --------------------------------------------------------------------------
 * 11. Memuat
 * ----------------------------------------------------------------------- */
.app-boot {
  display: grid;
  min-height: 60dvh;
  align-content: center;
  justify-items: center;
  gap: 14px;
  color: var(--text-muted);
}

.spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 999px;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* --------------------------------------------------------------------------
 * 12. Lapisan milik aplikasi ini
 *
 * Token di bagian 1 dipinjam apa adanya dari outbound-operations-hub dan tidak
 * boleh menyimpang — dua aplikasi operasional itu harus terlihat sebagai satu
 * keluarga. Yang ditambahkan di sini adalah lapisan yang HANYA masuk akal di
 * gudang beku: keadaan dok, irama gerak, dan skala warna untuk grafik.
 *
 * Semuanya diturunkan dari token yang sudah ada, bukan warna baru yang berdiri
 * sendiri. Menambah palet kedua adalah cara tercepat membuat dua aplikasi
 * berhenti terlihat serumpun.
 * ----------------------------------------------------------------------- */
:root {
  /* Keadaan dok. Nama menyebut arti, bukan warna: `--dock-busy` tetap benar
     bila kelak aksennya berubah. */
  --dock-free: var(--teal);
  --dock-busy: var(--accent);
  --dock-late: var(--status-critical);
  --dock-warn: var(--status-warning);

  /* Gerak. Satu kurva untuk seluruh aplikasi supaya transisi terasa berasal
     dari tangan yang sama. */
  --motion-fast: 120ms;
  --motion-base: 220ms;
  --ease: cubic-bezier(0.2, 0, 0.1, 1);
}

/* `color-scheme` sengaja TIDAK dinyatakan ulang di sini.
 *
 * Bagian ini pernah memuat `:root:not(.light) { color-scheme: light }`, yang
 * lebih spesifik (0,2,0) daripada `html.dark` di bagian 2 (0,1,1) dan berada
 * lebih belakang — jadi skema warna selalu terang, bahkan ketika operator
 * memilih tema gelap. Token CSS tetap gelap, tetapi segala yang digambar
*    browser sendiri tidak: daftar `<select>`, pemilih `datetime-local`, batang
 *    gulir, dan latar autofill muncul putih di atas papan gelap. Bagian 2 sudah
 *    menyatakannya dengan benar; baris di sini hanya dapat merusaknya. */

/* Titik status "langsung" berdenyut pelan — satu-satunya gerak ambien di
   aplikasi, dan ia menyatakan sesuatu yang benar: saluran sedang terbuka. */
.data-mode i.live {
  background: var(--teal);
  animation: live-pulse 2.4s var(--ease) infinite;
}

.data-mode i.paused { background: var(--status-warning); }
.data-mode i.offline { background: var(--status-critical); }

@keyframes live-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(0.82); }
}

/* --------------------------------------------------------------------------
 * 13. Rel dok
 *
 * Elemen tanda tangan aplikasi ini, dan satu-satunya tempat kebolehan visual
 * dibelanjakan.
 *
 * Gudang ini punya sembilan pintu inbound. Itu batas fisiknya: berapa pun
 * panjang antrean di luar, sembilan adalah jumlah truk yang dapat dibongkar
 * bersamaan. Sebelumnya kenyataan itu hanya hadir sebagai satu dropdown filter,
 * padahal ia justru model mental yang dipakai supervisor sepanjang hari —
 * "dok mana yang kosong, dan mana yang sebentar lagi melewati SLA".
 *
 * Rel ini menggambar bangunannya. Satu ubin per pintu, nomornya besar dan
 * monospace supaya terbaca dari seberang ruangan, dan sebuah bar yang menyusut
 * seiring SLA terpakai. Bar itu digerakkan ticker satu detik yang sudah ada;
 * tidak ada timer baru yang ditambahkan.
 * ----------------------------------------------------------------------- */
/* Rel adalah SATU baris, dan itu bukan kebetulan: sembilan pintu gudang ini
   berjajar di satu dinding. Membungkusnya menjadi dua baris — dengan pintu
   kesembilan menggantung sendirian — merusak satu-satunya hal yang membuat
   komponen ini berarti, yaitu bahwa ia menggambar bangunannya.

   SEMBILAN PINTU HARUS MUAT SEKALIGUS, dan sebelumnya tidak pernah muat:
   `minmax(116px, 1fr)` menuntut 1.108 piksel sementara ruang kerja menyediakan
   1.071px pada laptop 1366 dan 985px pada 1280. Rel yang menjawab "dok mana
   yang kosong" dengan satu dok tersembunyi menjawab salah, jadi jumlah kolom
   kini mengikuti `--dock-count` yang dipasang dockRail(). */
.dock-rail {
  display: grid;
  grid-template-columns: repeat(var(--dock-count, 9), minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 4px;
  padding-bottom: 2px;
}

.dock {
  position: relative;
  display: grid;
  gap: 5px;
  overflow: hidden;
  padding: 8px 10px 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--elevation-1);
  transition: border-color var(--motion-base) var(--ease);
}

/* Nomor dan keadaan BERTUMPUK: berjajar, keduanya berebut satu baris selebar
   ubin dan yang mengalah adalah labelnya — "LEWAT S…", justru keadaan yang
   paling mendesak dibaca. */
.dock-head {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.dock-no {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--text);
}

/* Keadaan dibawa WARNA TEKSNYA, bukan hanya garis tepi setebal satu piksel:
   sebelumnya "KOSONG" dan "LEWAT SLA" sama-sama redup dan terlihat identik
   sampai dibaca. Teksnya tetap ada, jadi isyaratnya bukan warna saja. */
.dock-state {
  overflow: hidden;
  min-width: 0;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

.dock-occupant {
  display: grid;
  gap: 1px;
  min-width: 0;
}

/* Sama seperti pada kartu: yang ditanyakan di depan rel ini adalah "dok tiga
   itu truk siapa", bukan "dok tiga itu nomor berapa". Vendor karena itu
   memegang bobot, nomor antrean menemani di bawahnya. */
.dock-vendor {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dock-vendor.is-free {
  font-weight: 400;
  color: var(--text-muted);
}

.dock-queue {
  font-size: 10px;
  color: var(--text-muted);
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Bar SLA. Lebarnya menyusut dari 100% ke 0 seiring waktu terpakai; ticker
   menuliskannya sebagai persen agar tidak perlu menghitung ulang di CSS. */
.dock-bar {
  position: relative;
  height: 5px;
  border-radius: 999px;
  background: var(--surface-muted);
  overflow: hidden;
}

.dock-bar i {
  display: block;
  height: 100%;
  width: var(--dock-progress, 0%);
  border-radius: inherit;
  background: var(--dock-busy);
  transition: width 1s linear, background-color var(--motion-base) var(--ease);
}

.dock-free {
  border-style: dashed;
  border-color: color-mix(in oklab, var(--dock-free) 40%, var(--line));
  background: color-mix(in oklab, var(--dock-free) 5%, var(--surface));
}

.dock-free .dock-no { color: color-mix(in oklab, var(--dock-free) 70%, var(--text)); }
.dock-free .dock-state { color: color-mix(in oklab, var(--dock-free) 80%, var(--text-muted)); }

.dock-busy { border-color: color-mix(in oklab, var(--dock-busy) 40%, var(--line)); }
.dock-busy .dock-bar i { background: var(--dock-busy); }
.dock-busy .dock-state { color: var(--dock-busy); }

.dock-warn { border-color: color-mix(in oklab, var(--dock-warn) 55%, var(--line)); }
.dock-warn .dock-bar i { background: var(--dock-warn); }
.dock-warn .dock-state { color: var(--dock-warn); }

.dock-late {
  border-color: color-mix(in oklab, var(--dock-late) 60%, var(--line));
  background: color-mix(in oklab, var(--dock-late) 7%, var(--surface));
}

.dock-late .dock-bar i { background: var(--dock-late); }
.dock-late .dock-no { color: var(--dock-late); }
.dock-late .dock-state { color: var(--dock-late); }

/* --------------------------------------------------------------------------
 * 14. Grafik
 *
 * SVG sebaris, tanpa pustaka. Grafik di aplikasi ini sederhana — batang, garis,
 * dan histogram — dan pustaka charting mana pun menambah ratusan kilobyte untuk
 * menggambar bentuk yang muat dalam beberapa lusin baris.
 *
 * Warnanya diambil dari skala status yang sama dengan papan, sehingga "merah"
 * berarti hal yang sama di grafik dan di kartu antrean.
 * ----------------------------------------------------------------------- */
.chart {
  display: block;
  width: 100%;
  height: auto;
  /* Kanvas 720×260 yang direntang ke kartu selebar 1.150px menjadi 415px
     tinggi dengan huruf sumbu 16px — satu grafik memenuhi satu layar. */
  max-height: 340px;
  overflow: visible;
}

.chart-grid line {
  stroke: var(--line);
  stroke-width: 1;
}

.chart-axis {
  fill: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
}

.chart-label {
  fill: var(--text-soft);
  font-family: var(--font-sans);
  font-size: 11px;
}

.chart-bar { transition: opacity var(--motion-fast) var(--ease); }
.chart-bar:hover { opacity: 0.75; }

.chart-line {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chart-dot { stroke: var(--surface); stroke-width: 2; }

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-soft);
}

.chart-legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.chart-legend i {
  width: 10px;
  height: 3px;
  border-radius: 999px;
  background: currentcolor;
}

/* Bar di dalam sel tabel. Angka sendirian menuntut pembacanya membandingkan
   sembilan bilangan; bar di sebelahnya membuat urutannya terbaca sekali lihat,
   dan angkanya tetap ada untuk yang perlu menyalinnya. */
.bar-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 132px;
}

.bar-track {
  position: relative;
  flex: 1;
  min-width: 44px;
  height: 6px;
  border-radius: 999px;
  background: var(--surface-muted);
  overflow: hidden;
}

.bar-track i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--teal);
}

.bar-track i.is-critical {
  background: var(--status-critical);
}

.bar-cell b {
  min-width: 52px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Pencacah pembatalan. Angka nol tidak pernah dibuat mencolok; yang bukan nol
   selalu. */
.cancel-count {
  display: inline-block;
  min-width: 22px;
  padding: 1px 6px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--status-critical) 15%, transparent);
  color: var(--status-critical);
  font-weight: 700;
}

/* Daftar alasan pembatalan. */
.reason-list {
  display: grid;
  gap: 9px;
}

/* Label, bar, dan angka berdempet — bukan bar 90px yang terdampar di tepi kanan
   dengan pita kosong selebar empat ratus piksel di antaranya. */
.reason {
  display: grid;
  grid-template-columns: minmax(0, 14em) minmax(0, 1fr) 34px;
  align-items: center;
  gap: 12px;
  font-size: 13px;
}

.reason-label {
  color: var(--text-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reason b {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) {
  .reason {
    grid-template-columns: minmax(0, 9em) minmax(0, 1fr) 30px;
    gap: 8px;
    font-size: 12px;
  }

  .bar-cell {
    min-width: 104px;
  }
}

/* Tooltip grafik. Satu simpul dipakai bersama seluruh grafik, dipasang di
   <body> supaya tidak pernah terpotong oleh container yang meng-overflow. */
/* Spanduk peringatan dan langkah perbaikan di layar Pengaturan. Cookie Superset
   yang mati adalah kegagalan tersering, dan satu-satunya yang benar-benar
   menuntut orang untuk bertindak — jadi ia tampil sebagai instruksi bernomor,
   bukan sebagai pesan galat. */
.banner-warn {
  border-color: color-mix(in oklab, var(--status-warning) 45%, var(--line));
  background: color-mix(in oklab, var(--status-warning) 8%, var(--surface));
}

.steps {
  margin: 8px 0 0;
  padding-left: 20px;
  display: grid;
  gap: 5px;
  color: var(--text-soft);
  font-size: 13px;
}

.sync-result {
  margin-top: 10px;
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-muted);
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: 12px;
}

.sync-result.is-error {
  border-color: color-mix(in oklab, var(--status-critical) 45%, var(--line));
  color: var(--status-critical);
}

/* Tampilan tabel di balik tiap grafik. Bukan sekadar syarat aksesibilitas:
   supervisor yang menyalin angka ke laporan mingguan tidak seharusnya
   membacanya dari batang. */
.chart-table {
  margin-top: 12px;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

.chart-table summary {
  cursor: pointer;
  color: var(--text-soft);
  font-size: 12px;
  list-style: none;
}

.chart-table summary::marker,
.chart-table summary::-webkit-details-marker { display: none; }

.chart-table summary::before {
  content: "▸ ";
  color: var(--text-muted);
}

.chart-table[open] summary::before { content: "▾ "; }
.chart-table summary:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.chart-table .table-scroll { margin-top: 8px; }

.chart-tip {
  position: fixed;
  z-index: 60;
  max-width: 280px;
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface-raised);
  box-shadow: var(--elevation-2);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.45;
  pointer-events: none;
}

.chart-empty {
  display: grid;
  place-items: center;
  min-height: 140px;
  color: var(--text-muted);
  font-size: 13px;
}

/* Kartu statistik ringkas untuk halaman Analitik: satu angka besar dengan
   sebaran p50/p90 di bawahnya. Rata-rata sendirian menyembunyikan ekor, jadi
   ketiganya selalu tampil bersama. */
/* Lantai kolom dinaikkan dari 150px ke 172px bersama peralihan ke HH:MM:SS:
   "00:45:00" delapan karakter monospace pada 26 piksel, yaitu sekitar 112
   piksel ditambah 32 piksel padding. Pada lantai lama angkanya terpotong
   ellipsis — dan durasi yang terpotong tidak dapat dibaca sebagian. */
.stat-row {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(172px, 1fr));
}

.stat {
  display: grid;
  gap: 4px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
}

.stat-label {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.stat-value {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--stat-tone, var(--text));
}

.stat-spread {
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
 * 15. Penajaman papan antrean
 *
 * Kartu antrean dulu memberi bobot visual yang sama kepada semua isinya:
 * nomor, vendor, armada, plat, gate, jam, SKU — semuanya seukuran. Akibatnya
 * tidak ada yang menonjol, dan mata harus membaca kartu alih-alih memindainya.
 *
 * Hierarkinya sekarang tegas: nomor antrean dan hitung mundur SLA dibaca dari
 * jauh, sisanya baru terbaca saat kartu benar-benar diperhatikan.
 * ----------------------------------------------------------------------- */
.queue-card { transition: border-color var(--motion-base) var(--ease), box-shadow var(--motion-base) var(--ease); }

/* Tiket yang sudah lewat tenggat menarik perhatian lewat bidangnya, bukan
   lewat satu garis tipis di tepi. */
.queue-card.is-breached {
  border-color: color-mix(in oklab, var(--status-critical) 45%, var(--line));
  background: color-mix(in oklab, var(--status-critical) 4%, var(--surface));
}

.queue-card.is-warning {
  border-color: color-mix(in oklab, var(--status-warning) 40%, var(--line));
}

/* Pencacah panggilan. Server sudah menghitungnya sejak awal dan tidak pernah
   ditampilkan — padahal "sudah dipanggil 3x" adalah persis tanda driver yang
   tidak merespons dan perlu ditindak. */
.call-count {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--status-warning) 14%, transparent);
  color: var(--status-warning);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
}

/* Pemisah kelompok di dalam daftar antrean: memisahkan yang sedang bongkar dari
   yang menunggu tanpa menambah kotak baru. */
.queue-group,
.group-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 0 0;
  color: var(--text-muted);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.queue-group::after,
.group-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.queue-group b {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--text-soft);
}

/* --------------------------------------------------------------------------
 * 18. Studi waktu
 *
 * Dua tempat, satu kosakata: pita kemajuan pada kartu antrean dan garis waktu
 * di dialog rincian memakai penanda yang sama, sehingga tahap yang menyala di
 * satu tempat dikenali di tempat lain tanpa dipelajari dua kali.
 *
 * Warna hanya membawa DUA arti di sini, dan keduanya struktural: teal untuk
 * waktu yang dipakai bekerja, biru monitor untuk waktu yang dipakai menunggu.
 * Keduanya juga dibedakan oleh posisi dan label, jadi grafiknya tetap terbaca
 * pada layar yang warnanya sudah pudar oleh sinar matahari dok.
 * ----------------------------------------------------------------------- */

/* Pita kemajuan pada kartu antrean. Sengaja setipis mungkin: ia menjawab
   "seberapa jauh" dari jarak beberapa meter, dan tidak boleh bersaing dengan
   hitung mundur SLA yang merupakan satu-satunya angka mendesak di kartu. */
.stage-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  font-size: 11px;
  color: var(--text-muted);
}

.stage-progress > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stage-bar {
  flex: 0 0 64px;
  height: 4px;
  border-radius: 999px;
  background: var(--surface-muted);
  overflow: hidden;
}

.stage-bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--teal);
}

/* -- Garis waktu di dialog rincian ---------------------------------------- */
.stage-timeline {
  display: grid;
  gap: 0;
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
}

.stage-row {
  display: grid;
  /* Kolom kelima adalah SELISIH terhadap stempel sebelumnya. Sampai ia ada,
     dialog ini menampilkan dua belas jam dinding dan nol durasi — sehingga
     pertanyaan yang sebenarnya dibawa orang ke sini ("QA-nya makan waktu
     berapa lama") dijawab dengan mengurangkan di kepala, dua belas kali, per
     truk. Yang terjadi di lapangan adalah tidak seorang pun melakukannya. */
  grid-template-columns: 12px minmax(0, 1fr) 58px 64px 84px;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  border-bottom: 1px solid var(--line);
}

.stage-span {
  text-align: right;
  color: var(--text-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.stage-span.is-first {
  color: var(--line-strong);
  font-style: italic;
}

/* Tahap yang SEDANG berjalan adalah satu-satunya baris yang angkanya bergerak,
   jadi ia satu-satunya yang diberi warna. */
.stage-span.is-running {
  color: var(--status-monitor);
  font-weight: 700;
}

.stage-row.is-running .stage-dot {
  border-color: var(--status-monitor);
  background: var(--surface);
}

.stage-row.is-running .stage-name {
  color: var(--text);
}

.stage-row:last-child {
  border-bottom: 0;
}

/* Titik tahap adalah SATU-SATUNYA penanda yang tidak membawa teks, jadi ia
   dibedakan oleh isian penuh dan bukan hanya oleh warna: cincin kosong berarti
   belum tercatat, bulatan padat berarti sudah. */
.stage-dot {
  width: 10px;
  height: 10px;
  border: 2px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
}

.stage-row.is-done .stage-dot {
  border-color: var(--teal);
  background: var(--teal);
}

.stage-name {
  overflow: hidden;
  font-size: 13px;
  color: var(--text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stage-row.is-done .stage-name {
  color: var(--text);
}

.stage-time {
  text-align: right;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.stage-row.is-done .stage-time {
  color: var(--text);
}

.stage-act {
  display: flex;
  justify-content: flex-end;
}

/* Empat tahap yang tidak dapat distempel dari sini menyebutkan pemiliknya di
   tempat tombolnya berada — kolom kosong terbaca sebagai tombol yang hilang. */
.stage-owner {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.stage-at-field {
  margin-bottom: 14px;
}

.stage-subhead {
  margin: 18px 0 6px;
  font-size: 13px;
  font-weight: 600;
}

.metric-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
}

.muted-inline {
  color: var(--text-muted);
  font-weight: 400;
}

/* -- Ruas waktu ------------------------------------------------------------ */
.seg-list {
  display: grid;
  gap: 2px;
}

.seg-row {
  display: grid;
  grid-template-columns: minmax(0, 15em) minmax(0, 1fr) 96px 68px 40px;
  align-items: center;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.seg-row:last-child {
  border-bottom: 0;
}

/* Ruas yang belum punya satu pun stempel tetap ditampilkan, hanya diredupkan.
   Menghilangkannya membuat daftar terlihat lengkap padahal justru ruas yang
   hilang itulah yang perlu mulai dicatat. */
.seg-row.is-empty {
  opacity: 0.55;
}

.seg-label {
  display: grid;
  min-width: 0;
  gap: 1px;
}

.seg-label b {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.seg-label small {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.seg-figures {
  text-align: right;
  color: var(--text-soft);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* p90 turun ke barisnya sendiri, bukan mengalir setelah mediannya.
 *
 * Sebagai teks sebaris, "00:21:00 p90 01:02:00" (21 karakter monospace)
 * melewati lebar kolomnya dan membungkus di tempat yang berubah-ubah menurut
 * panjang angkanya — kadang setelah "p90", kadang di tengah jamnya. Sebagai
 * blok, kedua angka selalu berdiri di baris yang sama pada setiap ruas. */
.seg-figures small {
  display: block;
  color: var(--text-muted);
}

.seg-total,
.seg-count {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.seg-count {
  color: var(--text-muted);
  font-size: 11px;
}

.bar-track i.is-queue {
  background: var(--status-monitor);
}

.bar-track i.is-touch {
  background: var(--teal);
}

/* Cakupan memakai baris alasan yang sudah ada, ditambah satu kolom rincian:
   "85%" saja tidak dapat dibedakan dari 85% atas dua puluh tiket dan 85% atas
   dua tiket, dan selisih itu menentukan apakah angkanya boleh dipercaya. */
.reason-4 {
  grid-template-columns: minmax(0, 14em) minmax(0, 1fr) 44px 72px;
}

.reason-4 > small {
  text-align: right;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* -- Kotak estimasi -------------------------------------------------------- */
.estimate-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 14px;
  margin-top: 14px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-muted);
}

.estimate-grid .fact strong {
  font-size: 17px;
}

/* Dua angka kesimpulan berdiri lebih tegas daripada rincian per tahap: yang
   dibandingkan orang adalah "berurutan" terhadap "bersamaan", dan sisanya
   hanya menerangkan dari mana keduanya berasal. */
.estimate-grid .fact.is-strong > span {
  color: var(--accent);
}

.estimate-grid .fact.is-strong strong {
  font-size: 21px;
}

@media (max-width: 900px) {
  .seg-row {
    grid-template-columns: minmax(0, 1fr) 62px 40px;
    row-gap: 6px;
  }

  /* Batangnya pindah ke barisnya sendiri di bawah label, selebar kartu: pada
     lebar ponsel, lima kolom menyisakan batang selebar dua puluh piksel yang
     tidak dapat dibandingkan dengan apa pun. */
  .seg-row .bar-track {
    grid-column: 1 / -1;
    order: 1;
  }

  /* Nama ruas juga mengambil baris penuh. Tanpa ini ia berhenti di kolom
     pertama sementara dua kolom di sebelahnya kosong, dan penjelasan ruas —
     satu-satunya kalimat yang menerangkan apa yang diukur — terpotong pada
     seratus sembilan puluh piksel demi ruang yang tidak dipakai siapa pun. */
  .seg-label {
    grid-column: 1 / -1;
  }

  .seg-figures {
    grid-column: 1;
  }

  .reason-4 {
    grid-template-columns: minmax(0, 9em) minmax(0, 1fr) 36px 56px;
    gap: 8px;
    font-size: 12px;
  }

  .stage-row {
    grid-template-columns: 12px minmax(0, 1fr) 52px 56px 76px;
    gap: 8px;
  }
}

/* --------------------------------------------------------------------------
 * 18b. Dua jalur sejajar, ketukan cepat, dan lembar isian
 *
 * Bentuk visual dari tiga keputusan yang dibuat di tempat lain: QA dan
 * menghitung digambar berdampingan karena keduanya memang boleh berjalan
 * bersamaan, tahap berikutnya punya tombolnya sendiri di kartu, dan studi waktu
 * lengkap diisi sebagai lembar jam, bukan dua belas medan tanggal-dan-jam.
 * ----------------------------------------------------------------------- */

/* Dua jalur berdampingan. Bingkainya sengaja terlihat sebagai SATU blok:
   yang ingin dikatakan bukan "dua daftar", melainkan "satu bagian yang
   bercabang dua lalu bertemu lagi". */
.stage-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin: 10px 0;
  padding: 12px;
  border: 1px dashed var(--line-strong);
  border-radius: 12px;
  background: var(--surface-muted);
}

.stage-track {
  min-width: 0;
}

/* Di dalam jalur, baris tahap menumpuk dua baris: nama di atas, jam dan
   tombolnya di bawah.

   Kolom yang sama dengan tulung punggung menyisakan 55 piksel untuk nama —
   "Mulai hitung" terpotong menjadi "Mulai h…", dan tahap yang tidak dapat
   dibaca namanya adalah tahap yang salah diketuk. Lebar dialog tidak dapat
   dinaikkan hanya untuk ini: ia dipakai seluruh dialog lain di aplikasi. */
.stage-track .stage-row {
  grid-template-columns: 12px minmax(0, 1fr) auto;
  row-gap: 1px;
}

.stage-track .stage-name {
  grid-column: 2 / -1;
}

.stage-track .stage-time {
  grid-column: 2;
  text-align: left;
}

/* Selisihnya menempel pada jamnya, di baris kedua yang sama. Kolom tersendiri
   akan menyempitkan nama tahap yang sudah pas-pasan di jalur sesempit ini. */
.stage-track .stage-span {
  grid-column: 3;
  text-align: left;
}

.stage-track .stage-act {
  grid-column: 3;
  grid-row: 1;
}

.stage-track h4 {
  margin: 0 0 4px;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.stage-split-note,
.stage-split > .btn {
  grid-column: 1 / -1;
}

.stage-split-note {
  margin: 0;
  color: var(--text-muted);
  font-size: 11.5px;
}

/* Tahap berikutnya, sekali ketuk. Diberi warna aksen tipis supaya terbaca
   sebagai "yang paling mungkin Anda tekan" tanpa bersaing dengan tombol utama
   kartu, yang tetap milik alur operasional (Mulai bongkar / Selesai bongkar). */
.btn-quick {
  border-color: var(--accent);
  color: var(--accent);
}

.btn-quick:hover {
  background: var(--accent-soft);
}

/* -- Dampak pemicu, terlihat selagi diketik -------------------------------- */
.metric-impact {
  margin-left: 4px;
  color: var(--teal);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
}

.metric-estimate {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
  margin-top: 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-muted);
}

.metric-estimate .fact strong {
  font-size: 17px;
}

.metric-estimate-note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--text-muted);
  font-size: 11.5px;
  line-height: 1.5;
}

.metric-estimate-note .is-over {
  color: var(--status-critical);
}

.metric-estimate-note .is-under {
  color: var(--status-normal);
}

/* -- Lembar isian studi waktu ---------------------------------------------- */
/* Irama kolom yang SAMA di ketiga kelompok lembar isian.
 *
 * `auto-fit` memberi tiap kelompok jumlah kolom yang berbeda: tulang punggung
 * pertama berisi lima medan, jalur QA dua, tulang punggung kedua tiga — jadi
 * lebar kotak berubah dari baris ke baris dan tepi kirinya tidak pernah
 * sejajar. Empat kolom tetap membuat kedua jalur sejajar (dua kolom masing-
 * masing) jatuh persis di atas kolom tulang punggung di atasnya, dan seluruh
 * dua belas kotak berdiri pada kisi yang sama. */
.sheet-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.stage-track .sheet-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 1100px) {
  .sheet-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .sheet-grid,
  .stage-track .sheet-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .sheet-grid,
  .stage-track .sheet-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Nomor urut medan jam. Dua belas kotak yang bentuknya sama persis, sebagian
   bertumpuk dalam dua jalur sejajar, tidak menunjukkan urutan apa pun kepada
   orang yang memegang lembar pengamatan dan mencari "yang kelima". */
.sheet-order {
  display: inline-grid;
  width: 15px;
  height: 15px;
  margin-right: 5px;
  border-radius: 4px;
  background: var(--surface-accent);
  color: var(--text-soft);
  font-size: 10px;
  letter-spacing: 0;
  place-items: center;
  vertical-align: middle;
}

.sheet-field > span {
  display: block;
  min-height: 2.6em;
  line-height: 1.3;
}

.sheet-field input {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Empat jam yang menentukan SLA diberi tepi kiri berwarna, BUKAN latar merah:
   mengisinya adalah tindakan yang sah, hanya tindakan yang harus disadari. */
.sheet-field.is-sla input {
  border-left: 3px solid var(--status-warning);
}

/* Pola tinta yang sama dengan `.badge-warning`: warna status sebagai teks di
   atas campuran tipisnya, bukan blok pekat dengan tinta yang ditebak. */
.sheet-flag {
  margin-left: 4px;
  padding: 0 4px;
  border-radius: 4px;
  background: color-mix(in oklab, var(--status-warning) 18%, var(--surface));
  color: var(--status-warning);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
}

.sheet-delta {
  min-height: 14px;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10.5px;
}

/* Perguliran hari tidak pernah diam-diam: barisnya menyala supaya tanggal yang
   salah ketik terlihat sebagai lompatan sehari, bukan tersimpan sebagai benar. */
.sheet-delta.is-rollover {
  color: var(--status-warning);
  font-weight: 700;
}

.sheet-date {
  max-width: 280px;
  margin-top: 14px;
}

/* Spanduk yang MEMPERINGATKAN, bukan yang melaporkan kegagalan.
 *
 * `.banner` bawaan memakai nada kritis karena ia lahir untuk "data tidak dapat
 * dimuat". Menyunting jam SLA bukan kegagalan — ia tindakan sah yang harus
 * disadari, dan merah membuat orang berhenti membacanya setelah kali ketiga.
 * Nada peringatannya sama dengan lencana SLA pada medan di bawahnya, sehingga
 * keduanya terbaca sebagai satu pesan. */
.banner-quiet {
  border-left-color: var(--status-warning);
  background: color-mix(in oklab, var(--status-warning) 8%, var(--surface));
}

.banner-quiet strong {
  color: var(--status-warning);
}

/* -- Pemilih tiket --------------------------------------------------------- */
.ticket-picks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 10px;
  margin-top: 12px;
}

.ticket-pick {
  display: grid;
  gap: 3px;
  padding: 11px 13px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: var(--surface);
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: var(--transition-smooth);
}

.ticket-pick:hover {
  border-color: var(--accent);
  box-shadow: var(--elevation-1);
  transform: translateY(-1px);
}

.ticket-pick-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.ticket-pick-head strong {
  font-size: 15px;
}

/* Nama vendor adalah highlight hasil pencarian: yang dicari orang adalah
   "truk siapa", bukan nomor antreannya. */
.ticket-pick-vendor {
  color: var(--accent);
  letter-spacing: -0.01em;
}

.ticket-pick:active {
  border-color: var(--accent);
  background: var(--surface-accent);
}

.ticket-pick-sub {
  overflow: hidden;
  color: var(--text-soft);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ticket-pick-meta {
  color: var(--text-muted);
  font-size: 10.5px;
}

/* --------------------------------------------------------------------------
 * 18d. Pemindai, pemilih PO, dan tabel akun
 * ----------------------------------------------------------------------- */

/* -- Pemilih PO ---------------------------------------------------------- */
/* Kotak cari dan tombol pindai berdiri sebaris: keduanya jalan masuk yang sama,
   dan menaruh Pindai di baris tersendiri membuatnya terbaca sebagai langkah
   tambahan alih-alih sebagai pilihan yang setara dengan mengetik. */
.po-search {
  display: flex;
  align-items: end;
  gap: 10px;
}

.po-search > .field {
  flex: 1 1 auto;
  min-width: 0;
}

.btn-scan {
  flex: 0 0 auto;
  /* Setinggi `.input` di sebelahnya: keduanya berdiri sebaris, dan tepi bawah
     yang tidak sejajar terlihat sebagai kesalahan tata letak. Angkanya token,
     bukan 44 piksel yang ditulis tangan — kotak isian sendiri setinggi token. */
  min-height: var(--control-h);
  border-color: var(--accent);
  color: var(--accent);
}

.btn-scan:hover:not(:disabled) {
  background: var(--accent-soft);
}

.po-results {
  display: grid;
  gap: 6px;
}

.po-empty {
  margin: 0;
  color: var(--text-muted);
  font-size: 12px;
}

/* Kalimat tentang master PO di bawah kotak pencarian: berapa PO yang
   tersambung dari Superset, dan apakah sync-nya sedang bermasalah. */
.po-source {
  margin: 0;
  color: var(--text-muted);
  font-size: 11px;
}

/* Keadaan "tidak ketemu" membawa tombolnya sendiri: operator yang sudah
   mengetik nomornya tidak boleh diminta mengetiknya sekali lagi di kotak
   sebelah. */
.po-missing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border: 1px dashed var(--line-strong);
  border-radius: 10px;
  background: var(--surface-muted);
}

.po-missing p {
  margin: 0;
  color: var(--text-muted);
  font-size: 12px;
}

/* -- Pemindai ------------------------------------------------------------ */
.scanner {
  display: grid;
  gap: 12px;
}

/* Rasio 4:3 dipatok lewat aspect-ratio, bukan tinggi tetap: kamera ponsel
   mengirim bingkai dengan rasio yang berbeda-beda, dan tinggi tetap membuat
   sebagian di antaranya tergunting di tengah barcode. */
.scanner-view {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  background: #000;
}

.scanner-view video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Bingkai baca. Ia tidak memotong apa pun — BarcodeDetector membaca seluruh
   bingkai — melainkan memberi tahu ke mana kamera harus diarahkan. */
.scanner-frame {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 72%;
  height: 46%;
  border: 2px solid var(--accent);
  border-radius: 10px;
  box-shadow: 0 0 0 100vmax rgb(0 0 0 / 34%);
  transform: translate(-50%, -50%);
}

.scan-log {
  min-height: 18px;
  margin: 0;
  color: var(--text-soft);
  font-size: 12px;
}

.scan-log.is-ok {
  color: var(--status-normal);
  font-weight: 600;
}

.scan-log.is-warn {
  color: var(--status-warning);
  font-weight: 600;
}

.scan-log.is-error {
  color: var(--status-critical);
  font-weight: 600;
}

.scan-manual {
  padding-top: 4px;
  border-top: 1px solid var(--line);
}

/* -- Jejak pemindaian ---------------------------------------------------- */
/* Satu baris status cukup untuk satu kode dan tidak cukup untuk lima. Truk yang
   membawa delapan PO dipindai delapan kali berturut-turut, dan tiap hasil
   menghapus hasil sebelumnya — sehingga satu-satunya cara memastikan PO keempat
   benar-benar masuk adalah menutup pemindai, memeriksa daftarnya, lalu
   membukanya lagi. Yang benar-benar terjadi di lantai bukan itu. */
.scan-count {
  margin: 0;
  color: var(--status-normal);
  font-size: 12px;
  font-weight: 700;
}

.scan-trail {
  display: grid;
  max-height: 132px;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  gap: 4px;
  list-style: none;
}

.scan-trail-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 5px 8px;
  border-left: 3px solid var(--line-strong);
  border-radius: 0 7px 7px 0;
  background: var(--surface-muted);
  font-size: 12px;
}

.scan-trail-item.is-ok {
  border-left-color: var(--status-normal);
}

/* Manual BERHASIL ditambahkan, dan tetap bukan hal yang sama: barisnya masuk
   dengan jumlah nol dan vendor kosong. Hijau untuk keduanya menyembunyikan
   satu-satunya hal yang perlu diperiksa sekali lagi sebelum Simpan. */
.scan-trail-item.is-warn {
  border-left-color: var(--status-warning);
}

.scan-trail-item.is-error {
  border-left-color: var(--status-critical);
}

.scan-trail-item b {
  flex: 0 0 auto;
  font-size: 12px;
}

.scan-trail-item span {
  overflow: hidden;
  color: var(--text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Senter. Dok inbound gudang beku remang dan barcode surat jalan kerap tercetak
   tipis; tombolnya berada DI DALAM bidang kamera supaya ibu jari yang memegang
   tablet dapat menjangkaunya tanpa memindahkan arah bidikan. */
.scan-torch {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  border: 1px solid rgb(255 255 255 / 35%);
  border-radius: 50%;
  background: rgb(0 0 0 / 45%);
  color: #fff;
  cursor: pointer;
}

.scan-torch.is-on {
  border-color: var(--status-monitor);
  background: var(--status-monitor);
  color: #10213a;
}

/* -- Kontrol penyegaran di bilah atas ------------------------------------- */
/* Tombol-belah: yang sering di depan (segarkan papan), yang jarang dan berat di
   dalam menu (sync master PO). Dua ikon berdampingan tidak dapat dibedakan
   tanpa dibaca; satu tombol untuk keduanya membuat tiap "layarnya belum
   berubah" menarik puluhan ribu baris dari Superset. */
.refresh-control {
  position: relative;
  display: flex;
  flex: 0 0 auto;
  align-items: stretch;
}

.refresh-control.has-menu #force-refresh {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* Chevron selebar 22 piksel adalah satu-satunya kontrol di bawah target sentuh
   di perangkat sentuh. Lebarnya kini mengikuti skala kontrol: 28 piksel dengan
   tetikus, 40 piksel dengan jari. */
.refresh-control.has-menu .refresh-more {
  width: calc(var(--control-h-sm) - 6px);
  min-width: calc(var(--control-h-sm) - 6px);
  margin-left: -1px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  padding: 0;
}

#force-refresh.is-spinning svg {
  animation: refresh-spin 900ms linear infinite;
}

@keyframes refresh-spin {
  to {
    transform: rotate(360deg);
  }
}

.refresh-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  display: grid;
  width: max-content;
  max-width: min(300px, calc(100vw - 32px));
  padding: 6px;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-raised);
  box-shadow: var(--shadow);
}

.refresh-menu button {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 10px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  text-align: left;
}

.refresh-menu button:hover,
.refresh-menu button:focus-visible {
  background: var(--surface-muted);
}

.refresh-menu button svg {
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--text-muted);
}

.refresh-menu button span {
  display: grid;
  gap: 1px;
}

.refresh-menu button b {
  font-size: 13px;
  font-weight: 600;
}

.refresh-menu button small {
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.35;
}

/* -- Tabel aturan SLA ----------------------------------------------------- */
/* Kotak ringkas: tinggi .input bawaan membuat 13 aturan tidak muat satu layar. */
.input.input-sm {
  height: var(--control-h-sm);
  min-height: var(--control-h-sm);
  max-width: 96px;
  padding: 0 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.tbl-sla td {
  padding: 6px 8px;
  vertical-align: middle;
}

/* Pilihan memuat kalimat ("Semua armada"), jangan sampai terpotong. */
.tbl-sla .input-sm {
  width: 100%;
  min-width: 132px;
}

.sla-number {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.tbl-sla .sla-number .input-sm {
  width: 64px;
  min-width: 64px;
}

.sla-number small,
.sla-summary {
  color: var(--text-muted);
  font-size: 12px;
}

.sla-summary {
  min-width: 150px;
  white-space: normal;
}

.tbl-sla tr.is-off td:not(.sla-toggle, .sla-remove) {
  opacity: 0.5;
}

.sla-errors {
  margin: 0;
  padding: 10px 12px 10px 28px;
  border: 1px solid color-mix(in oklab, var(--status-critical) 40%, transparent);
  border-radius: 10px;
  color: var(--status-critical);
  font-size: 12.5px;
}

.sla-active {
  display: grid;
  place-items: center;
}

.sla-active input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

.icon-btn-sm {
  width: 30px;
  height: 30px;
}

.icon-btn-sm svg {
  width: 15px;
  height: 15px;
}

.sla-dirty {
  align-self: center;
  color: var(--status-warning);
  font-size: 12px;
  font-weight: 600;
}

/* Ponsel: tiap aturan menjadi kartu berlabel, bukan tabel 9 kolom yang digeser. */
@media (max-width: 720px) {
  .tbl-sla.is-editing thead {
    display: none;
  }

  .tbl-sla.is-editing tr {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 8px 10px;
    padding: 12px;
    border-bottom: 1px solid var(--line);
  }

  .tbl-sla.is-editing td {
    display: grid;
    gap: 4px;
    padding: 0;
    border: 0;
  }

  .tbl-sla.is-editing td::before {
    content: attr(data-label);
    color: var(--text-muted);
    font-size: 10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  .tbl-sla.is-editing td {
    grid-column: span 3;
  }

  .tbl-sla.is-editing td:has(.sla-number) {
    grid-column: span 2;
  }

  .tbl-sla.is-editing .sla-number .input-sm {
    width: 48px;
    min-width: 48px;
  }

  .tbl-sla.is-editing .sla-summary {
    grid-column: 1 / -1;
  }

  .tbl-sla.is-editing .sla-toggle,
  .tbl-sla.is-editing .sla-remove {
    align-content: end;
    justify-items: start;
  }
}

/* -- Zona berbahaya ------------------------------------------------------- */
/* Kartunya diberi tepi kritis dan diletakkan paling bawah. Ia tidak pernah
   berdampingan dengan tombol yang ditekan sehari-hari, karena satu-satunya
   kesalahan yang tidak dapat diperbaiki di aplikasi ini ada di dalamnya. */
.section-danger {
  border-color: color-mix(in oklab, var(--status-critical) 45%, var(--line));
}

.section-danger .section-head {
  background: color-mix(in oklab, var(--status-critical) 8%, transparent);
}

.section-danger .section-head .eyebrow {
  color: var(--status-critical);
}

/* Kedua daftar di kartu ini adalah KALIMAT, bukan satu nilai. `.fact strong`
   memotongnya dengan ellipsis — bentuk yang benar untuk nama vendor pada ubin
   dok selebar seratus piksel, dan justru salah di sini: yang terpotong adalah
   akhir dari daftar "apa saja yang tetap ada", tepat pada layar yang menghapus
   segalanya. */
.section-danger .fact strong {
  overflow: visible;
  font-size: 12px;
  line-height: 1.45;
  white-space: normal;
}

/* -- Tabel akun ---------------------------------------------------------- */
/* Akun nonaktif tetap tergambar, hanya diredupkan: menghilangkannya membuat
   daftar terlihat lengkap padahal justru akun itulah yang biasanya dicari. */
.tbl tr.is-off td {
  opacity: 0.5;
}

.tbl td.table-actions {
  display: flex;
  gap: 6px;
}

/* --------------------------------------------------------------------------
 * 18c. Isian yang seragam, komposisi, dan ringkasan
 *
 * Tiga hal yang sebelumnya tidak punya rumah, dan karena itu tumbuh berbeda di
 * tiap halaman:
 *
 *   MEDAN. Setiap halaman menulis markup isiannya sendiri, jadi tinggi kotak,
 *   letak keterangan, dan ada-tidaknya penanda wajib berbeda-beda di enam
 *   layar. Sekarang bentuknya datang dari `field()` (js/forms.js) dan gayanya
 *   dari satu blok di sini.
 *
 *   KOMPOSISI. Donat dan batang bagi menjawab "terbuat dari apa", bentuk yang
 *   tidak dimiliki satu pun grafik bersumbu di aplikasi ini.
 *
 *   RINGKASAN. Pita beban per jam dan daftar "perlu perhatian" milik halaman
 *   Ringkasan.
 *
 * Tidak ada satu pun hex baru; seluruh warna tetap datang dari token yang sama
 * dengan outbound-operations-hub.
 * ----------------------------------------------------------------------- */

/* -- Medan -------------------------------------------------------------- */
.field {
  display: grid;
  min-width: 0;
  align-content: start;
  gap: 5px;
}

/* Grup yang tidak dapat dibungkus satu `<label>` — tiga kotak plat nomor —
   memakai `<label>` terpisah di dalamnya. Ia tidak boleh membawa kisi dan
   jaraknya sendiri, karena jarak itu sudah dipegang `.field`. */
.field-caption {
  display: block;
  gap: 0;
}

/* Nomor langkah pada judul kartu.

   Bentuk lama menulisnya sebagai teks eyebrow ("Langkah 1"), dan urutannya
   selalu putus pada kartu yang tidak punya eyebrow — di formulir pendaftaran,
   justru kartu yang memuat tombol Simpan. Lingkaran ini punya tempatnya
   sendiri, jadi setiap kartu dalam sebuah urutan dapat menyandangnya tanpa
   merebut eyebrow, yang tetap menyebutkan ISI kartunya. */
.section-title {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 12px;
}

.step-mark {
  display: grid;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  place-items: center;
}

/* -- Bilah rentang tanggal ---------------------------------------------- */
/* Prasetel berdiri di sebelah kedua kotak tanggal, bukan di bawahnya: rentang
   yang benar-benar diminta orang hanyalah beberapa, dan mengetiknya sebagai dua
   tanggal adalah enam ketukan untuk pertanyaan yang sama setiap pagi. */
.range-presets {
  display: flex;
  flex: 0 1 auto;
  flex-wrap: wrap;
  gap: 6px;
}

.filter-note,
.filter-range-error {
  flex: 1 0 100%;
  margin: 0;
}

.filter-note {
  color: var(--text-muted);
  font-size: 11px;
}

/* -- Donat -------------------------------------------------------------- */
.donut {
  display: grid;
  align-items: center;
  gap: 16px;
  grid-template-columns: minmax(0, 168px) minmax(0, 1fr);
}

.donut-svg {
  width: 100%;
  max-width: 168px;
  height: auto;
}

.donut-arc {
  transition: opacity var(--motion-fast) var(--ease);
}

.donut-arc:hover {
  opacity: 0.72;
}

/* `dominant-baseline` menaruh titik `y` di TENGAH huruf, bukan di garis
   dasarnya. Tanpa itu blok dua baris di dalam lubang donat tergantung terlalu
   tinggi, dan menambalnya dengan menggeser `y` akan meleset lagi begitu ukuran
   hurufnya berubah. */
.donut-center {
  dominant-baseline: middle;
  fill: var(--text);
  font-family: var(--font-mono);
  font-size: 26px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.donut-center-note {
  dominant-baseline: middle;
  fill: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
}

/* Legenda donat menumpuk ke bawah — bukan mengalir mendatar seperti legenda
   grafik lain — karena ia berdiri di kolom sempit di sebelah donatnya. */
.donut-legend {
  flex-direction: column;
  align-items: flex-start;
  margin-top: 0;
  gap: 7px;
}

.donut-legend i {
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

.donut-legend b,
.donut-legend small {
  font-variant-numeric: tabular-nums;
}

.donut-legend small {
  color: var(--text-muted);
}

/* -- Batang bagi -------------------------------------------------------- */
.share {
  display: grid;
  gap: 10px;
}

.share-bar {
  display: flex;
  overflow: hidden;
  height: 34px;
  border-radius: 10px;
  background: var(--surface-muted);
}

/* Persentase ditulis DI DALAM bidangnya hanya bila bidangnya cukup lebar; di
   bawah itu ia turun ke legenda. Teks yang dipaksa masuk ke bidang lima piksel
   adalah teks yang meluber ke bidang sebelahnya. */
.share-slice {
  display: grid;
  width: var(--share);
  min-width: 0;
  background: var(--share-tone);
  color: var(--surface);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  place-items: center;
  transition: opacity var(--motion-fast) var(--ease);
}

.share-slice:hover {
  opacity: 0.82;
}

/* Penanda legenda batang bagi adalah BIDANG, bukan garis. Bentuk garis bawaan
   .chart-legend menerangkan grafik bersumbu, tempat serinya memang digambar
   sebagai garis; di sini yang diterangkan adalah bidang berwarna, dan penanda
   yang bentuknya berbeda dari yang ditunjuknya menuntut satu langkah
   penerjemahan yang tidak perlu. */
.share .chart-legend i {
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

/* -- Pita beban per jam -------------------------------------------------- */
.load-strip {
  display: grid;
  gap: 3px;
  grid-template-columns: repeat(24, minmax(0, 1fr));
}

.load-cell {
  display: grid;
  padding: 7px 2px 5px;
  border-radius: 7px;
  /* Kepekatan berjalan terhadap jam TERSIBUK hari itu, bukan terhadap angka
     tetap: gudang yang sepi dan gudang yang penuh sama-sama perlu menunjukkan
     bentuk harinya sendiri. */
  background: color-mix(in oklab, var(--accent) calc(var(--load, 0) * 74%), var(--surface-muted));
  font-family: var(--font-mono);
  gap: 1px;
  place-items: center;
}

.load-cell b {
  min-height: 13px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.load-cell i {
  color: var(--text-muted);
  font-size: 10px;
  font-style: normal;
}

.load-cell.is-idle b {
  color: var(--text-muted);
}

/* Jam yang sedang berjalan: batas antara "yang sudah terjadi" dan "yang masih
   akan datang". Tanpa penanda ini, pita yang separuh kosong terbaca sebagai
   hari yang sepi alih-alih sebagai hari yang baru separuh berjalan. */
.load-cell.is-now {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* -- Perlu perhatian ---------------------------------------------------- */
.attention-list {
  display: grid;
  margin: 0;
  padding: 0;
  gap: 8px;
  list-style: none;
}

.attention-row {
  display: grid;
  align-items: center;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: 11px;
  background: var(--surface-muted);
  gap: 6px 12px;
  grid-template-columns: minmax(0, 1fr) auto;
}

.attention-breached { border-left-color: var(--status-critical); }
.attention-warning { border-left-color: var(--status-warning); }

.attention-lead {
  display: grid;
  min-width: 0;
  gap: 1px;
}

.attention-lead strong {
  font-family: var(--font-mono);
  font-size: 15px;
}

.attention-lead small {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Keterangan mengambil baris penuh di bawah identitas dan hitung mundurnya.
 *
 * Sebagai kolom kelima ia hanya menyisakan sekitar 120 piksel di kolom
 * pendamping, sehingga "Sudah dipanggil 3 kali tanpa merapat" membungkus tiga
 * baris sementara tetangganya satu — dan enam baris dengan tinggi berbeda-beda
 * tidak lagi dapat dipindai sebagai daftar prioritas. Dua baris tetap membuat
 * tingginya sama berapa pun lebar kolomnya. */
.attention-note {
  display: flex;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  grid-column: 1 / -1;
}

.attention-note b {
  color: var(--text-soft);
  font-size: 11px;
}

.attention-note i {
  min-width: 0;
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11.5px;
  font-style: normal;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -- Kisi halaman Ringkasan ---------------------------------------------- */
.dash-split {
  display: grid;
  align-items: start;
  margin-top: 16px;
  gap: 18px;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
}

.dash-pair {
  align-items: start;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 1100px) {
  .dash-split,
  .dash-pair {
    grid-template-columns: minmax(0, 1fr);
  }

}

@media (max-width: 720px) {
  /* Dua puluh empat kotak selebar empat belas piksel tidak dapat memuat satu
     angka pun; dua baris berisi dua belas kotak membaca sama urutannya. */
  .load-strip {
    grid-template-columns: repeat(12, minmax(0, 1fr));
  }

  .donut {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }

  .donut-legend {
    align-self: start;
  }
}

/* --------------------------------------------------------------------------
 * 19. Lapisan premium — gerak, kedalaman, dan kaca
 *
 * SATU HAL YANG TIDAK DILAKUKAN LAPISAN INI: mengganti palet atau huruf.
 * Keduanya dikunci test/design-system.test.js agar identik dengan aplikasi
 * outbound-operations-hub, dan kesamaan itu bukan kebetulan — kedua aplikasi
 * dipakai orang yang sama pada hari yang sama. Kesan premium di sini datang
 * dari gerak, kedalaman, dan cahaya, bukan dari huruf baru.
 *
 * `backdrop-filter` DITAMBAHKAN PADA SATU ELEMEN SAJA di sini: kartu dialog.
 * Topbar, latar dialog, dan bilah bawah ponsel sudah memilikinya sejak lama, dan
 * lapisan ini sengaja tidak menyentuh angkanya — menimpanya dengan nilai yang
 * hampir sama hanya menghasilkan dua aturan yang saling menutup tanpa ada yang
 * tahu mana yang berlaku.
 *
 * Batasnya disengaja. Blur adalah efek termahal yang dapat diminta dari GPU, dan
 * tablet gudang bukan mesin yang kuat; memasangnya pada setiap kartu berarti
 * papan yang tersendat setiap kali digulir — persis ongkos yang tidak boleh
 * dibayar aplikasi ini. Empat elemen berukuran tetap, dua di antaranya hanya
 * muncul saat diminta, adalah harga yang wajar untuk kedalaman yang terasa.
 *
 * Seluruh animasi memakai `transform` dan `opacity` saja. Keduanya dikerjakan
 * compositor tanpa menyentuh layout, jadi ia tidak menambah pekerjaan CPU pada
 * perangkat yang sedang menjalankan hitung mundur SLA per detik.
 *
 * Blok `prefers-reduced-motion` yang sudah ada mematikan semuanya dengan
 * `!important`; tidak ada satu pun gerak di bawah yang lolos darinya.
 * ----------------------------------------------------------------------- */
:root {
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --elevation-1: 0 1px 2px rgb(15 23 42 / 6%), 0 4px 12px rgb(15 23 42 / 6%);
  --elevation-2: 0 2px 6px rgb(15 23 42 / 8%), 0 12px 28px rgb(15 23 42 / 10%);
}

.dark {
  --elevation-1: 0 1px 2px rgb(0 0 0 / 30%), 0 4px 14px rgb(0 0 0 / 26%);
  --elevation-2: 0 3px 8px rgb(0 0 0 / 36%), 0 16px 36px rgb(0 0 0 / 32%);
}

@media (prefers-color-scheme: dark) {
  :root:not(.light) {
    --elevation-1: 0 1px 2px rgb(0 0 0 / 30%), 0 4px 14px rgb(0 0 0 / 26%);
    --elevation-2: 0 3px 8px rgb(0 0 0 / 36%), 0 16px 36px rgb(0 0 0 / 32%);
  }
}

/* -- Masuknya halaman ------------------------------------------------------
   Animasi masuk dipasang pada HALAMAN, bukan pada kartu.

   Kartu antrean digambar ulang setiap kali ada tiket yang berubah — beberapa
   kali per menit di jam sibuk. Animasi masuk per kartu berarti seluruh papan
   berkedip setiap kali satu truk dipanggil, dan yang tersisa bukan kesan
   premium melainkan layar yang tidak dapat dibaca sambil berjalan. */
/* `backwards`, BUKAN `both`. Dengan `both` transform terakhir animasi
   (matriks identitas) tetap menempel selamanya, dan elemen ber-transform
   menjadi kotak acuan bagi setiap `position: fixed` di dalamnya. Lembar
   filter papan di ponsel karena itu terbuka di dasar halaman — belasan ribu
   piksel di bawah layar — dan tombol Filter tampak tidak berbuat apa-apa. */
.dashboard-page {
  animation: page-in 260ms var(--ease-out) backwards;
}

@keyframes page-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

/* -- Kedalaman ------------------------------------------------------------- */
.card,
.queue-card,
.metric-card,
.stat {
  transition:
    transform 180ms var(--ease-out),
    box-shadow 180ms var(--ease-out),
    border-color 180ms var(--ease-out);
}

.queue-card:hover {
  border-color: var(--line-strong);
  box-shadow: var(--elevation-2);
  transform: translateY(-2px);
}

.dock {
  transition:
    transform 180ms var(--ease-out),
    box-shadow 180ms var(--ease-out),
    border-color var(--motion-base) var(--ease);
}

.dock:hover {
  box-shadow: var(--elevation-1);
  transform: translateY(-2px);
}

/* -- Tombol ---------------------------------------------------------------
   Tekanan yang terasa. Empat piksel perjalanan terlalu banyak untuk jari
   bersarung tangan — satu piksel sudah cukup untuk memberi tahu bahwa
   ketukannya diterima, dan itulah satu-satunya tugas gerak ini. */
.btn {
  transition:
    transform 120ms var(--ease-out),
    box-shadow 120ms var(--ease-out),
    background-color 120ms var(--ease-out),
    border-color 120ms var(--ease-out);
}

.btn:active:not(:disabled) {
  transform: translateY(1px);
}

.btn-primary,
.btn-teal {
  box-shadow: var(--elevation-1);
}

.btn-primary:hover:not(:disabled),
.btn-teal:hover:not(:disabled) {
  box-shadow: var(--elevation-2);
}

/* Fokus keyboard tetap tebal dan tidak pernah ikut dilembutkan: ia satu-satunya
   penanda posisi bagi orang yang tidak memakai tetikus. */
.btn:focus-visible,
.input:focus-visible,
.nav-link:focus-visible,
.ticket-pick:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* -- Kaca ------------------------------------------------------------------
   Hanya kartu dialog yang bertambah; sisanya sudah berkaca sejak lama. Lihat
   catatan di kepala bagian ini. */
.modal-card {
  border: 1px solid var(--glass-border);
  background: var(--glass-bg);
  backdrop-filter: blur(18px) saturate(1.4);
  box-shadow: var(--elevation-2);
  animation: modal-in 200ms var(--ease-out) both;
}

@keyframes modal-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
  }
}


/* -- Denyut tenggat --------------------------------------------------------
   Hanya yang SUDAH terlambat yang berdenyut, dan hanya nilainya.

   Peringatan tiga puluh menit terakhir sengaja tidak ikut: bila dua keadaan
   sama-sama bergerak, tidak ada yang benar-benar mendesak, dan mata berhenti
   membedakan keduanya setelah setengah jam pertama shift. */
.sla-breached .sla-value {
  animation: sla-pulse 2.4s ease-in-out infinite;
}

@keyframes sla-pulse {
  50% {
    opacity: 0.55;
  }
}

/* -- Batang gulir ---------------------------------------------------------- */
* {
  scrollbar-color: var(--line-strong) transparent;
  scrollbar-width: thin;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: 999px;
  background: var(--line-strong);
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
  background-clip: content-box;
}

@media (max-width: 900px) {
  .stage-split {
    grid-template-columns: minmax(0, 1fr);
  }

  .ticket-picks {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --------------------------------------------------------------------------
 * 12. Responsif
 * ----------------------------------------------------------------------- */
/* Hanya pita lima kartu yang dilipat ke tiga kolom; pita empat kartu
   menunggu 720px untuk menjadi 2×2. */
@media (max-width: 1180px) {
  .metric-strip:not(.metric-strip-two, .metric-strip-three, .metric-strip-four) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .metric-strip:not(.metric-strip-two, .metric-strip-three, .metric-strip-four) .metric-card {
    border-bottom: 1px solid var(--line);
  }

  .metric-strip:not(.metric-strip-two, .metric-strip-three, .metric-strip-four) .metric-card:nth-child(3n) {
    border-right: 0;
  }

  .dashboard-grid-main {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 900px) {
  .app-shell,
  .app-shell:has(.sidebar.rail) {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Sidebar di bawah 900px BUKAN laci samping lagi melainkan bilah navigasi
     bawah; bentuknya ada di bagian 16.

     Sisa aturan laci lama sempat bertahan di sini, dan dua di antaranya tidak
     pernah dinyatakan ulang oleh blok bilah bawah: `visibility: hidden` dan
     `pointer-events: none`. Bilahnya karena itu ada di dalam tata letak —
     `.workspace` bahkan menyisakan 76 piksel untuknya — tetapi tidak pernah
     tergambar dan tidak pernah dapat disentuh, sehingga di ponsel dan tablet
     aplikasi ini hanya punya satu halaman: papan antrean. */

  .sidebar.rail .nav-link {
    justify-content: flex-start;
    padding-inline: 12px;
  }

  .sidebar.rail .nav-link span,
  .sidebar.rail .nav-link b,
  .sidebar.rail .brand-mark > span:last-child,
  .sidebar.rail .sidebar-user {
    display: revert;
  }

  .rail-toggle {
    display: none;
  }

  .workspace {
    padding: 16px;
  }

  .login-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .login-art {
    display: none;
  }
}

@media (max-width: 720px) {
  .form-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Kisi yang isinya FAKTA, bukan isian, tetap dua kolom. Satu kolom benar untuk
     kotak isian — mereka butuh lebar penuh — tetapi pasangan label/nilai pendek
     seperti "Tiket dimuat 12" lalu memakan satu baris penuh masing-masing:
     empat fakta menjadi 232 piksel untuk empat angka. */
  .form-grid:not(:has(input, select, textarea)) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .form-grid .span-2 {
    grid-column: auto;
  }

  .metric-strip,
  .metric-strip-four,
  .metric-strip-three {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .metric-strip .metric-card:nth-child(3n) {
    border-right: 1px solid var(--line);
  }

  .metric-strip .metric-card:nth-child(2n) {
    border-right: 0;
  }

  /* Jumlah kartu ganjil menyisakan setengah baris kosong yang terlihat seperti
     kartu rusak. Kartu terakhir melebar penuh agar barisnya tetap rapat. */
  .metric-strip .metric-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    border-right: 0;
    border-bottom: 0;
  }

  .topbar {
    gap: 8px;
    min-height: 62px;
    padding: 9px 12px;
  }

  .live-clock {
    display: none;
  }

  /* Di ponsel pemilih gudang tinggal kodenya: "SRG" cukup membedakan, dan
     nama lengkapnya tetap ada di daftar pilihan saat diketuk. */
  .site-chip > span {
    display: none;
  }

  /* Pil status TETAP tampil, hanya menyusut.

     Sebelumnya ia disembunyikan bersama jam dinding, seolah keduanya sama-sama
     hiasan. Jam memang hiasan; pil ini satu-satunya tempat papan mengatakan
     bahwa ia terputus, bahwa master PO membeku, atau bahwa jam tablet meleset
     sehingga setiap jam kedatangan hari itu salah — dan perangkat yang paling
     sering dipakai di gudang justru yang tidak pernah diberi tahu. Teksnya
     dipotong bila ruangnya habis; titik warna dan `title` tetap utuh. */
  .data-mode {
    max-width: 196px;
    padding: 5px 8px;
    font-size: 11px;
  }

  .data-mode span {
    overflow: hidden;
    min-width: 0;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .board-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Lantai kolom dilonggarkan sedikit di ponsel: kartu selebar layar masih
     memuat dua tombol berdampingan, dan empat tombol bertumpuk sendiri-sendiri
     mendorong kartu berikutnya keluar layar. */
  .queue-actions {
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  }

  .page-header {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
}

/* Perangkat sentuh: target minimal 44px sesuai WCAG 2.5.5. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.001ms !important;
  }
}

/* --------------------------------------------------------------------------
 * 13. Cetak
 *
 * Tiket antrean dicetak di kertas A6 potret dari jendela terpisah, jadi yang
 * perlu ditekan di sini hanya kerangka aplikasi bila operator memakai Ctrl+P.
 * ----------------------------------------------------------------------- */
@media print {
  .sidebar,
  .topbar,
  .toast-stack,
  .skip-link,
  .page-actions,
  .section-actions,
  .filter-bar {
    display: none !important;
  }

  .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .workspace {
    padding: 0;
  }

  .card,
  .queue-card {
    border-color: #999;
    box-shadow: none;
  }
}

/* --------------------------------------------------------------------------
 * 17. Ukuran kontrol — satu skala untuk semuanya
 *
 * Sebelum bagian ini, tinggi kontrol di aplikasi ini ada empat macam yang tidak
 * pernah sengaja dipilih: input 44px, tombol 40px, tombol kecil 34px, tombol
 * ikon 40px, dan ringkasan disclosure 38px. Akibatnya satu baris form yang
 * memuat input dan tombol di sebelahnya tingginya tidak sama — cacat yang
 * terlihat setiap kali dua kontrol bersebelahan, dan tidak pernah terlihat
 * ketika masing-masing diperiksa sendiri.
 *
 * Sekarang ada dua ukuran saja, keduanya token:
 *
 *   --control-h     kontrol utama. Sama dengan tinggi input, sehingga apa pun
 *                   yang berdiri di samping input sejajar dengannya.
 *   --control-h-sm  kontrol sekunder di dalam kartu, tempat empat tombol harus
 *                   muat berdampingan.
 *
 * Pada perangkat sentuh keduanya naik ke 46px. Itu bukan sekadar mengikuti
 * angka pedoman: papan ini disentuh dengan sarung tangan di ruang dingin, dan
 * jari bersarung tidak setepat kursor.
 * ----------------------------------------------------------------------- */
:root {
  --control-h: 40px;
  --control-h-sm: 34px;
}

@media (pointer: coarse) {
  :root {
    --control-h: 46px;
    --control-h-sm: 46px;
  }
}

.btn,
.nav-link,
.input {
  min-height: var(--control-h);
}

/* Kotak isian satu baris setinggi TEPAT satu kontrol, bukan "paling tidak".
   `min-height` saja membiarkan padding dan tinggi baris bawaan menambah dua
   sampai empat piksel: kotak 42-44 piksel berdiri di sebelah tombol 40 piksel
   di setiap bilah filter, dan tepi bawahnya tidak pernah sejajar. */
input.input,
select.input {
  height: var(--control-h);
  padding-block: 0;
}

.btn-sm {
  min-height: var(--control-h-sm);
}

/* Tombol ikon harus BUJUR SANGKAR, dan ukurannya hidup di SATU tempat.
   Sebelumnya lebarnya ditetapkan 40px di aturan dasar sementara perangkat
   sentuh hanya menaikkan tingginya — hasilnya 40 lebar x 46 tinggi, lebih
   sempit dari pedoman pada sumbu yang justru paling sering meleset. */
.icon-btn {
  width: var(--control-h);
  height: var(--control-h);
  min-height: var(--control-h);
}

/* Kontrol yang sebelumnya tidak tersentuh aturan ukuran sama sekali. Semuanya
   diketuk sama seringnya dengan tombol biasa. */
.filter-more > summary,
.chart-table summary,
.po-suggestion {
  min-height: var(--control-h);
}

/* Tombol hapus PO. Ia salah satu yang paling sering meleset, dan satu ketukan
   salah menghapus PO yang benar — jadi lebarnya mengikuti skala kontrol, bukan
   ukuran ikonnya. */
.po-row-remove {
  min-width: var(--control-h-sm);
}

/* -- Papan yang dapat disentuh ---------------------------------------------- */
/* Tombol transparan selebar ubin dok: ketuk dok terisi → ke kartunya; ketuk dok
   kosong → panggil truk berikutnya. Isi ubin tetap terbaca di bawahnya. */
.dock-hit {
  position: absolute;
  z-index: 1;
  inset: 0;
  border: 0;
  border-radius: inherit;
  background: transparent;
  cursor: pointer;
}

.dock-hit:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

.dock:has(.dock-hit:hover) {
  border-color: var(--line-strong);
}

/* Angka ringkasan yang dapat diketuk tetap terlihat seperti kartunya. */
:where(button.metric-card) {
  border: 0;
  border-right: 1px solid var(--line);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

button.metric-card:hover {
  background-color: var(--surface-accent);
}

button.metric-card:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

/* Kartu yang dituju dari dok, atau yang baru berubah: cincin aksen yang memudar. */
.queue-card.is-spotlit,
.queue-card.is-fresh {
  position: relative;
}

.queue-card.is-spotlit::after,
.queue-card.is-fresh::after {
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 0 3px var(--accent);
  content: "";
  pointer-events: none;
  animation: card-ring 1.8s var(--ease-out) forwards;
}

.queue-card.is-fresh::after {
  box-shadow: 0 0 0 2px var(--teal);
}

@keyframes card-ring {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* Petunjuk langkah berikutnya di judul kelompok. */
.queue-group small {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}


/* -- Menu ⋯ di pojok kartu antrean ------------------------------------------ */
.card-more {
  position: relative;
}

.card-more > summary {
  width: 32px;
  height: 32px;
  min-height: 32px;
  list-style: none;
  cursor: pointer;
}

.card-more > summary::-webkit-details-marker {
  display: none;
}

.card-more-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  right: 0;
  display: grid;
  min-width: 180px;
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--elevation-2);
}

.card-more-menu .btn {
  justify-content: flex-start;
  width: 100%;
}

/* -- Penolakan PO --------------------------------------------------------- */
.queue-po-chip.is-rejected .chip {
  opacity: 0.6;
  text-decoration: line-through;
}

.po-rejected-tag {
  color: var(--status-critical);
  font-weight: 700;
}

.reject-list {
  display: grid;
  gap: 6px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}

.reject-po {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
}

.reject-po-main {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 10px;
  cursor: pointer;
}

.reject-po-main span,
.reject-po.is-rejected .reject-po-main {
  display: grid;
  min-width: 0;
}

.reject-po small {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 12px;
  text-overflow: ellipsis;
}

.reject-po.is-rejected {
  border-color: color-mix(in oklab, var(--status-critical) 40%, var(--line));
}

.reject-po.is-rejected small {
  color: var(--status-critical);
}

.reject-po.is-locked {
  opacity: 0.6;
}

.reject-close {
  margin-top: 8px;
  color: var(--status-critical);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
 * 16. Ponsel — dirapatkan, dan navigasinya dipindahkan ke ibu jari
 *
 * Terukur sebelum bagian ini ada: 811 piksel chrome sebelum kartu tiket
 * pertama. Di layar 812 piksel itu berarti satu layar PENUH digulir sebelum
 * operator melihat satu pun tiket — pada aplikasi yang seluruh gunanya adalah
 * melihat tiket.
 *
 * Penyebabnya bukan satu hal besar melainkan empat hal sedang yang menumpuk:
 * bilah filter tiga baris (276px), metrik 2x2 (218px), judul halaman beserta
 * deskripsinya (143px), dan topbar (65px). Masing-masing tampak wajar sendirian.
 *
 * Nadanya tetap: tidak ada warna baru, tidak ada bayangan tambahan, tidak ada
 * animasi. Yang berubah hanya kerapatan dan urutan — dan ke mana ibu jari harus
 * bergerak.
 * ----------------------------------------------------------------------- */
/* Disclosure filter. Di layar lebar ringkasannya disembunyikan dan isinya
   berdiri sejajar dengan kotak pencarian; pelipatan hanya berlaku di ponsel,
   tempat 192 piksel filter yang selalu terbuka mengalahkan tiket yang sedang
   dicari.
 *
 * Ia TIDAK memakai `display: contents`, dan itu pernah dicoba serta gagal dua
 * kali sekaligus. Pertama, keadaan tutup `<details>` tetap berlaku: selama
 * atribut `open` tidak ada, isinya tidak pernah digambar maupun dapat diklik
 * sekalipun kotaknya sudah dilarutkan — jadi papan antrean tidak punya filter
 * status maupun gate sama sekali di atas 720px. Kedua, `display: contents` tidak
 * benar-benar melarutkan `<details>`: browser menyisipkan kotaknya sendiri
 * (`::details-content`) di antara elemen dan isinya, sehingga kedua medan
 * bertumpuk sebagai blok di ujung kanan bilah alih-alih berjajar.
 *
 * Bentuk di bawah tidak bergantung pada keduanya: disclosure-nya sebuah item
 * fleks biasa, dan isinya diatur fleks miliknya sendiri. Yang tersisa untuk
 * JavaScript hanyalah memasang `open` di layar lebar — `open` sebuah atribut,
 * bukan gaya; lihat `wideFilters()` di board.js. */
.filter-more {
  display: block;
  min-width: 0;
  flex: 2 1 300px;
}

.filter-more > summary {
  display: none;
}

.filter-more-body {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 12px;
}

.filter-more-body > label {
  min-width: 128px;
  flex: 1 1 140px;
}

@media (max-width: 720px) {
  .filter-more {
    flex: 1 1 100%;
  }

  .filter-more > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 4px;
    cursor: pointer;
    list-style: none;
    color: var(--text-soft);
    font-size: 12px;
  }

  .filter-more > summary::-webkit-details-marker { display: none; }

  .filter-more > summary::after {
    content: "▾";
    color: var(--text-muted);
    transition: transform var(--motion-fast) var(--ease);
  }

  .filter-more[open] > summary::after {
    transform: rotate(180deg);
  }

  .filter-more > summary b {
    font-family: var(--font-mono);
    font-weight: 700;
    color: var(--text);
  }

  .filter-more > summary span {
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-size: 10px;
    color: var(--text-muted);
  }

  .filter-more > summary:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
    border-radius: 8px;
  }

  .filter-more-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    padding-top: 8px;
  }
}

/* Bilah bawah berlaku sampai 900px, bukan 720px: rentang 721-900 adalah tablet
   portrait — perangkat yang paling butuh navigasi terjangkau ibu jari, dan yang
   justru dulu mendapat laci yang harus dibuka dari pojok kiri atas. */
@media (max-width: 900px) {
  /* -- Navigasi bawah -----------------------------------------------------
     Laci samping menuntut dua ketukan dan jangkauan ke pojok kiri ATAS —
     sudut terjauh dari ibu jari pada perangkat yang dipegang sambil berdiri.
     Bilah bawah menjadikannya satu ketukan di tempat ibu jari sudah berada.
     Tanpa perubahan markup: sidebar yang sama, disusun ulang. */
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: fixed;
    z-index: 40;
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    height: auto;
    flex-direction: row;
    border-top: 1px solid var(--line);
    border-right: 0;
    background: color-mix(in oklab, var(--surface) 94%, transparent);
    backdrop-filter: blur(14px);
    padding: 4px 6px calc(4px + env(safe-area-inset-bottom));
    transform: none;
  }

  /* Merek, tombol ciutkan, dan blok pengguna sudah terwakili di topbar dan di
     halaman Pengaturan; mengulanginya di bilah bawah hanya memakan lebar yang
     dibutuhkan tombol navigasi. */
  .sidebar-head,
  .sidebar-foot {
    display: none;
  }

  /* Lebar penuh dinyatakan ulang: blok 900px masih menyempitkan sidebar untuk
     keadaan `rail` (menu terciut di desktop), dan keadaan itu tidak berlaku
     pada bilah bawah. */
  .sidebar,
  .sidebar.rail {
    width: 100%;
    max-width: none;
    box-shadow: none;
  }

  .sidebar.rail .nav-link span {
    display: inline;
  }

  .sidebar nav {
    display: grid;
    grid-auto-flow: column;
    /* `minmax(0, 1fr)`, BUKAN `1fr`. Yang terakhir berarti `minmax(auto, 1fr)`,
       dan `auto` di sana adalah lebar MIN-CONTENT tombolnya — sehingga delapan
       tombol berlabel menolak menyusut dan yang terakhir terdorong keluar layar
       alih-alih menyempit. Tidak ada galat, tidak ada gulungan: tombolnya
       sekadar tidak ada. */
    grid-auto-columns: minmax(0, 1fr);
    gap: 2px;
    width: 100%;
    padding: 0;
    overflow: visible;
  }

  /* Tujuh halaman atau lebih tidak muat berlabel; ikonnya sendiri sudah
     dibedakan satu per satu, dan namanya tetap dibacakan pembaca layar lewat
     `aria-label`. Lihat catatan pada navMarkup() di js/app.js. */
  .sidebar .nav-dense .nav-link span {
    display: none;
  }

  .sidebar .nav-dense .nav-link {
    min-height: 46px;
  }

  .sidebar .nav-link {
    flex-direction: column;
    gap: 3px;
    justify-content: center;
    min-height: 52px;
    padding: 6px 2px;
    border-radius: 10px;
    font-size: 10.5px;
    line-height: 1.15;
    text-align: center;
  }

  .sidebar .nav-link span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
  }

  /* Lencana antrean menempel pada ikonnya, bukan mengambil kolom sendiri. */
  .sidebar .nav-link b {
    position: absolute;
    top: 4px;
    left: 50%;
    margin-left: 6px;
  }

  .sidebar .nav-link {
    position: relative;
  }

  /* Ruang supaya bilah bawah tidak pernah menutupi isi terakhir. */
  .workspace {
    padding-bottom: calc(76px + env(safe-area-inset-bottom));
  }

  /* Toast di atas bilah bawah, bukan menutupi label navigasinya. */
  .toast-stack {
    bottom: calc(80px + env(safe-area-inset-bottom));
  }

  /* -- Judul halaman ------------------------------------------------------ */
  .page-header h1 {
    font-size: 21px;
  }

  /* Judul dan aksinya membungkus bila tidak muat. Label tombol tetap
     terlihat: Excel dan CSV berbagi ikon unduh yang sama. */
  .page-header {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
  }

  .page-header .page-actions {
    flex: 0 1 auto;
    gap: 6px;
  }

  .page-header .page-actions .btn {
    min-height: var(--control-h-sm);
    padding: 0 10px;
    font-size: 12px;
  }

  /* -- Metrik -------------------------------------------------------------
     DUA BARIS DUA KOLOM, bukan satu baris yang menggulir. Empat kolom 138px
     berarti 576 piksel isi di layar 357 piksel: "Mendekati SLA" dan "Terlambat"
     — kedua angka yang menjadi alasan papan ini dibuka — berada di luar layar.
     Lebar tetap itu masuk akal ketika kartunya masih tiga baris; keterangannya
     kini disembunyikan, jadi dua kolom memberi 175 piksel per kartu. */
  .metric-strip,
  .metric-strip-three,
  .metric-strip-four {
    display: grid;
    /* Blok 720px di atas sudah menyatakan dua kolom beserta pemisahnya; yang
       dibatalkan di sini hanya bentuk menggulir yang dulu menempatinya. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-flow: row;
    gap: 0;
    overflow-x: visible;
  }

  /* Pemisah bawah pada baris pertama, supaya kisi 2x2 terbaca sebagai kisi. */
  .metric-strip .metric-card:nth-child(-n + 2) {
    border-bottom: 1px solid var(--line);
  }

  /* Tinggi minimum ikut turun bersama isinya: 108px dipilih ketika kartu masih
     memuat tiga baris, dan keterangannya disembunyikan tepat di bawah ini.

     Tanpa sudut membulat: sel di dalam satu bingkai yang bersudut tampak
     sebagai kartu-kartu yang saling bertumpuk. */
  .metric-card {
    min-height: 72px;
    gap: 2px;
    padding: 9px 11px;
    border-radius: 0;
  }

  .metric-value {
    font-size: 21px;
  }

  /* Keterangan panjang di bawah angka adalah hal pertama yang dapat dilepas:
     labelnya sudah menyebut apa yang dihitung. */
  .metric-sub {
    display: none;
  }

  /* -- Filter -------------------------------------------------------------
     Pencarian mengambil satu baris penuh karena ia yang paling sering
     disentuh; medan lain berbagi baris berdua. Aturannya menyebut JENIS
     medannya, bukan urutannya: `:first-child` dulu memberi baris penuh kepada
     apa pun yang kebetulan pertama, sehingga di Laporan medan "Dari" melebar
     penuh sementara "Sampai" tinggal setengah — satu rentang tanggal yang
     digambar sebagai dua medan yang tidak sepadan. */
  .filter-bar {
    gap: 8px 10px;
    padding: 10px;
  }

  .filter-bar > label {
    min-width: 132px;
    max-width: none;
    flex: 1 1 calc(50% - 5px);
  }

  .filter-bar > label:has(input[type="search"]) {
    flex: 1 1 100%;
  }

  /* Dari dan Sampai berdampingan: satu rentang, satu baris. */
  .filter-bar > .field:has(input[type="date"]) {
    min-width: 0;
    max-width: none;
    flex: 1 1 calc(50% - 5px);
  }

  .range-presets {
    display: grid;
    flex: 1 1 100%;
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .filter-bar label span {
    font-size: 10px;
  }

  .filter-bar .table-actions {
    flex: 1 1 100%;
    margin-left: 0;
    justify-content: flex-start;
  }

  /* Ringkasan filter dan jumlah hasil berbagi satu baris: masing-masing
     sendirian memakan satu baris penuh untuk dua kata. */
  .filter-more {
    flex: 1 1 auto;
  }

  .filter-bar .filter-count {
    flex: 0 0 auto;
    margin-left: auto;
    padding-bottom: 0;
    align-self: center;
  }

  /* -- Kartu antrean ------------------------------------------------------
     Dirapatkan tanpa mengecilkan dua hal yang harus terbaca dari jauh: nomor
     antrean dan hitung mundur. */
  .queue-card {
    gap: 9px;
    padding: 12px;
  }

  .queue-facts {
    gap: 8px 12px;
  }

  .sla-hero {
    padding: 9px 12px;
  }

  .board-grid {
    gap: 10px;
  }

  /* -- Rel dok ------------------------------------------------------------
     Di bawah 900px rel kembali menggulir: sembilan pintu dibagi pada layar
     390px menghasilkan ubin selebar 36 piksel yang tidak memuat apa pun. */
  .dock-rail {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: minmax(112px, 1fr);
    gap: 6px;
    overflow-x: auto;
    scrollbar-width: thin;
    /* Ubin terakhir tidak berhenti persis di tepi layar: sisa ruang di kanan
       adalah satu-satunya petunjuk bahwa masih ada pintu di baliknya. */
    scroll-padding-inline: 12px;
  }

  .dock {
    padding: 7px 8px 9px;
  }

  .dock-no {
    font-size: 17px;
  }

  /* -- Bagian dan grafik ---------------------------------------------------
     Kanvas grafik mengecil bersama layar (`useGeometry()` di charts.js), jadi
     satu unit SVG kini hampir satu piksel; label dinaikkan satu langkah supaya
     tiba pada ukuran yang sama seperti di desktop. */
  .chart-axis {
    font-size: 11px;
  }

  .chart-label {
    font-size: 12px;
  }

  .section-body {
    padding: 12px;
  }

  .section-head {
    padding: 11px 12px;
  }

  .stat-row {
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  .stat {
    padding: 10px 12px;
  }

  .stat-value {
    font-size: 21px;
  }
}

/* --------------------------------------------------------------------------
 * 26. Penyaringan yang menyatakan dirinya
 *
 * Bilah filter menyembunyikan tiket, dan papan yang menyembunyikan tiket
 * terlihat persis seperti papan yang memang sepi. Selisih itu adalah selisih
 * antara "tidak ada yang perlu dikerjakan" dan "ada yang tidak Anda lihat", dan
 * satu-satunya cara menutupnya adalah membuat penyaringan yang aktif MENYEBUT
 * dirinya di tempat hasilnya dibaca.
 *
 * Chip-nya dapat dilepas satu per satu. Papan yang disaring ke satu gate dan
 * satu armada sekaligus punya dua alasan untuk kosong, dan satu tombol "hapus
 * semua" memaksa keduanya dibuang hanya untuk menguji salah satunya.
 * ----------------------------------------------------------------------- */
.filter-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin: -2px 0 0;
}

/* Cacah di kiri, urutan di kanan — dua `margin-left: auto` membuat cacahnya
   melayang di tengah baris. */
.filter-status .filter-count {
  margin-left: 0;
  padding-bottom: 0;
}

.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 26px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-accent);
  color: var(--text-soft);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.filter-chip:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.filter-chip b {
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1;
}

.filter-chip.is-clear {
  background: transparent;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* -- Tab status papan antrean ------------------------------------------------
   Jalan terlihatnya untuk "status mana yang sedang saya lihat" — kontrol yang
   ditekan puluhan kali per shift, dengan cacah tiket di tiap tabnya. */
.board-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 0;
}

.board-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: var(--control-h-sm);
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-soft);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease), background-color var(--motion-fast) var(--ease),
    color var(--motion-fast) var(--ease);
}

.board-tab:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.board-tab b {
  color: var(--text-muted);
  font-size: 11.5px;
}

.board-tab.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

.board-tab.is-active b {
  color: var(--accent-ink);
  opacity: 0.85;
}

@media (pointer: coarse) {
  .board-tab {
    min-height: 46px;
  }
}

/* Ponsel: satu baris yang digulir, bukan tiga baris tab. */
@media (max-width: 720px) {
  .board-tabs {
    flex-wrap: nowrap;
    margin-inline: -16px;
    padding: 2px 16px 4px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  .board-tabs::-webkit-scrollbar {
    display: none;
  }

  .board-tab {
    flex: 0 0 auto;
  }
}

/* Lebar dasar yang lebih rapat daripada bilah rentang tanggal, karena bilah ini
   memuat LIMA kontrol dan bukan tiga. Dengan lebar dasar bawaan, yang kelima
   terdorong ke baris kedua seorang diri — satu kontrol yang berdiri sendirian
   di bawah empat lainnya terbaca sebagai kontrol yang bukan bagian dari
   kelompok yang sama, dan penyaringan yang tampak terpisah adalah penyaringan
   yang terlupakan. */
.filter-bar-tight > label {
  min-width: 148px;
  max-width: none;
  flex: 1 1 158px;
}

.filter-bar-tight > label:has(input[type="search"]) {
  flex: 2 1 200px;
}





/* Pemilih urutan berdiri di samping cacah hasil, bukan di dalam bilah filter:
   menyaring MENGHILANGKAN baris, mengurutkan tidak menghilangkan apa pun, dan
   menaruh keduanya dalam satu kotak bernama "Filter" mengajarkan bahwa
   mengganti urutan dapat menyembunyikan sesuatu. */
.sort-control {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.sort-control > span {
  color: var(--text-muted);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.sort-control .input {
  width: auto;
  min-width: 176px;
  height: var(--control-h-sm, 32px);
  min-height: 0;
  padding-block: 0;
  font-size: 12px;
}

@media (max-width: 620px) {
  .sort-control {
    width: 100%;
    margin-left: 0;
  }

  .sort-control .input {
    flex: 1;
  }
}


/* --------------------------------------------------------------------------
 * 27. Tabel yang dapat diurutkan
 *
 * Sampai kepala kolomnya dapat diklik, laporan hanya tersusun menurut waktu
 * pembuatan — urutan yang benar untuk membaca kronologi dan salah untuk setiap
 * pertanyaan lain. "Truk mana yang paling lama menunggu bulan ini" karena itu
 * hanya dapat dijawab dengan mengekspor CSV dan membukanya di Excel, yang
 * dalam praktiknya berarti pertanyaan itu tidak pernah dijawab di sini.
 * ----------------------------------------------------------------------- */
.th-sort {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: left;
  cursor: pointer;
}

.th-sort b {
  color: var(--line-strong);
  font-size: 10px;
}

.th-sort:hover {
  color: var(--text);
}

.th-sort.is-active {
  color: var(--accent);
}

.th-sort.is-active b {
  color: var(--accent);
}

/* --------------------------------------------------------------------------
 * 28. Akses per akun
 *
 * Daftar centang, bukan daftar pilihan ganda: yang ditanyakan bukan "pilih
 * satu" melainkan "yang mana saja", dan pertanyaan kedua dijawab paling cepat
 * ketika seluruh pilihannya terlihat sekaligus beserta yang sudah tercentang.
 *
 * Keadaan TERKUNCI diberi bentuknya sendiri — bukan sekadar `disabled` bawaan
 * browser yang memudarkan teks. Kotak yang tercentang tetapi pudar berarti dua
 * hal sekaligus ("boleh" dan "tidak dapat diubah"), dan tanpa penjelasan di
 * dekatnya, yang dibaca orang selalu yang kedua.
 * ----------------------------------------------------------------------- */
.access-editor {
  display: grid;
  gap: 10px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.access-group {
  display: grid;
  gap: 6px;
}

/* Gudang di dialog akun: berdiri di luar "Ikuti bawaan peran", karena gudang
   bukan bagian prasetel peran. Fieldset memberi kelompok kotaknya nama yang
   dibacakan pembaca layar. */
.access-sites {
  display: grid;
  gap: 8px;
  min-width: 0;
  margin: 18px 0 0;
  padding: 16px 0 0;
  border: 0;
  border-top: 1px solid var(--line);
}

.access-sites legend {
  float: left;
  width: 100%;
  margin: 0 0 4px;
  padding: 0;
}

.segmented-choice {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
}

.access-grid {
  display: grid;
  gap: 6px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}

.access-item {
  display: flex;
  align-items: start;
  gap: 8px;
  min-height: 40px;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-muted);
  cursor: pointer;
}

.access-item:has(input:checked) {
  border-color: color-mix(in oklab, var(--accent) 45%, var(--line));
  background: var(--surface-accent);
}

.access-item.is-locked {
  cursor: default;
  opacity: 0.62;
}

.access-item input {
  margin-top: 2px;
  accent-color: var(--accent);
}

.access-item span {
  display: grid;
  min-width: 0;
  gap: 1px;
}

.access-item b {
  font-size: 12px;
  font-weight: 600;
}

.access-item small {
  color: var(--text-muted);
  font-size: 10px;
  line-height: 1.35;
}

/* Penanda di tabel akun: akun yang aksesnya diatur sendiri, bukan mengikuti
   peran. Yang disebut hanya selisihnya, karena menyebut kedelapan halaman pada
   setiap baris menjadikan kolom ini dinding teks yang tidak dapat dipindai. */
.access-tag {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid color-mix(in oklab, var(--accent) 35%, var(--line));
  border-radius: 999px;
  background: var(--surface-accent);
  color: var(--text-soft);
  font-size: 10px;
}

/* --------------------------------------------------------------------------
 * 29. Ringkasan pendaftaran
 *
 * Berdiri tepat di atas tombol Simpan, yang berada di kolom kanan — jauh dari
 * sebagian besar medan yang baru saja diisi. Pada tablet potret kelima langkah
 * bertumpuk, sehingga langkah 1 sudah tergulir hilang ketika tombolnya
 * terlihat, dan pemeriksaan terakhir hanya dapat dilakukan dengan menggulir
 * kembali ke atas. Yang tidak digulir tidak diperiksa: itulah cara plat salah
 * ketik lolos ke antrean dan baru ketahuan ketika drivernya dipanggil.
 * ----------------------------------------------------------------------- */
.register-summary {
  display: grid;
  gap: 8px 14px;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  padding: 12px;
  border: 1px dashed var(--line-strong);
  border-radius: 12px;
  background: var(--surface-muted);
}

/* Kotak vendor berdiri SESUDAH pemilih PO, karena nilainya diturunkan dari PO
   itu. Garis di atasnya menegaskan urutan sebab-akibatnya. */
.register-vendor {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
 * 30. Tiket batal
 *
 * Keadaan kesembilan hitung mundur, dan satu-satunya yang bukan tentang waktu.
 * Sampai ia ada, tiket yang dibatalkan sebelum bongkar jatuh ke keadaan "belum
 * mulai" dan kartunya menuliskan "Target 02:00:00 · Menunggu mulai bongkar"
 * untuk truk yang sudah pulang tanpa membongkar apa pun.
 * ----------------------------------------------------------------------- */
.sla-cancelled .sla-value {
  color: var(--text-muted);
}

.sla-hero.sla-cancelled {
  border-style: dashed;
}

.sla-hero.sla-cancelled .sla-value {
  font-size: 20px;
  letter-spacing: 0.02em;
}

/* Penghitung tahap berjalan pada kartu antrean. Ia satu-satunya angka kedua
   yang bergerak di kartu, jadi ia sengaja lebih redup daripada hitung mundur
   SLA — dua angka yang sama menonjolnya berarti tidak ada yang menonjol. */
.stage-running {
  margin-left: auto;
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 6px;
  background: var(--surface-muted);
  color: var(--text-soft);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.stage-next {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 720px) {
  .register-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .access-grid {
    grid-template-columns: 1fr;
  }
}

/* --------------------------------------------------------------------------
 * 31. Pemilih armada bergambar
 *
 * `<input type="radio">` YANG SUNGGUHAN, DISEMBUNYIKAN TANPA DIHILANGKAN.
 *
 * `display: none` dan `visibility: hidden` mengeluarkan kontrolnya dari urutan
 * Tab dan dari pohon aksesibilitas sekaligus — yang berarti kembali ke grup
 * radio tiruan lewat pintu belakang. Yang dipakai di sini adalah `appearance:
 * none` dengan kotaknya dibiarkan menempati ubinnya: browser tetap menganggapnya
 * kontrol nyata, navigasi panah dan pengumuman "3 dari 11" tetap datang
 * gratis, dan yang terlihat hanyalah ubinnya.
 *
 * Keadaan terpilih ditandai TIGA hal sekaligus — garis tepi, bidang, dan
 * ketebalan huruf — bukan warna saja. Tablet gudang berembun di bawah lampu
 * dingin, dan satu-satunya penanda yang selamat dari layar seperti itu adalah
 * penanda yang tidak bergantung pada satu kanal.
 * ----------------------------------------------------------------------- */
.fleet-field,
.ticket-type-field {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.fleet-field > legend,
.ticket-type-field > legend {
  padding: 0;
  color: var(--text-soft);
  font-size: 12px;
  font-weight: 600;
}

.fleet-grid {
  display: grid;
  gap: 8px;
  margin: 6px 0 8px;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
}

.fleet-tile {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 8px 6px 7px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-muted);
  text-align: center;
  cursor: pointer;
}

.fleet-tile input,
.ticket-type-tile input {
  /* Nol ukuran, dan sengaja BUKAN disembunyikan lewat properti display:
     menyembunyikannya begitu mencabut kontrolnya dari urutan Tab, dan
     bersamanya seluruh alasan memakai radio sungguhan. */
  width: 0;
  height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  -webkit-appearance: none;
  appearance: none;
}

.fleet-tile-art {
  display: block;
  color: var(--text-muted);
}

.fleet-tile b {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.2;
}

.fleet-tile small {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.02em;
}

.fleet-tile:hover {
  border-color: var(--line-strong);
}

.fleet-tile.is-on {
  border-color: var(--accent);
  background: var(--surface-accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.fleet-tile.is-on .fleet-tile-art,
.fleet-tile.is-on b {
  color: var(--accent);
}

.fleet-tile.is-on b {
  font-weight: 700;
}

/* Cincin fokus dipasang pada UBIN, bukan pada kotak radio nol-piksel yang
   tidak dapat dilihat siapa pun. */
.fleet-tile:has(input:focus-visible),
.ticket-type-tile:has(input:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* Tipe tiket: tiga pilihan yang masing-masing mengubah hal yang berbeda —
   deret nomor antrean, urutan panggilan, dan target SLA — jadi ubinnya memuat
   akibatnya, bukan hanya namanya. */
.ticket-type-grid {
  display: grid;
  gap: 8px;
  margin: 6px 0 8px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

.ticket-type-tile {
  display: grid;
  gap: 2px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: 10px;
  background: var(--surface-muted);
  cursor: pointer;
}

.ticket-type-tile b {
  font-size: 12px;
  font-weight: 600;
}

.ticket-type-tile small {
  color: var(--text-muted);
  font-size: 10px;
  line-height: 1.35;
}

.ticket-type-tile:hover {
  border-color: var(--line-strong);
}

.ticket-type-tile.is-on {
  border-color: var(--tile-tone, var(--accent));
  border-left-color: var(--tile-tone, var(--accent));
  background: var(--surface-accent);
}

.ticket-type-tile.is-on b {
  color: var(--tile-tone, var(--accent));
  font-weight: 700;
}

/* --------------------------------------------------------------------------
 * 32. Pemilih kolom laporan
 *
 * Tiga puluh tujuh kolom tidak dapat dinyalakan satu per satu oleh siapa pun.
 * Yang benar-benar dilakukan orang adalah "tampilkan bagian tahapnya" ketika
 * sedang menelusuri di mana waktu habis, lalu mematikannya lagi — jadi
 * sakelarnya per KELOMPOK, dan panelnya tetap terbuka setelah tiap ketukan
 * karena satu sakelar hampir selalu diikuti sakelar berikutnya.
 * ----------------------------------------------------------------------- */
.column-picker {
  position: relative;
}

.column-picker > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--control-h-sm, 32px);
  padding: 0 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-soft);
  font-size: 12px;
  list-style: none;
  cursor: pointer;
}

.column-picker > summary::-webkit-details-marker {
  display: none;
}

.column-picker > summary b {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 11px;
}

.column-picker > summary:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.column-picker[open] > summary {
  border-color: var(--accent);
  color: var(--accent);
}

/* Melayang di atas tabel, bukan mendorongnya turun: panel yang menggeser tabel
   membuat baris yang sedang dibaca melompat pada tiap ketukan sakelar. */
.column-picker-body {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  right: 0;
  display: grid;
  width: min(320px, calc(100vw - 48px));
  gap: 6px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-raised);
  box-shadow: var(--shadow);
}

.column-picker-body .access-item.is-off {
  opacity: 0.7;
}

.column-picker-body .table-actions {
  justify-content: space-between;
  margin-top: 2px;
}

@media (max-width: 720px) {
  .column-picker-body {
    right: auto;
    left: 0;
  }
}

/* Lencana vendor prioritas pada kartu antrean dan rel dok. */
.vip-flag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 6px;
  border: 1px solid color-mix(in oklab, var(--accent) 50%, var(--line));
  border-radius: 999px;
  background: var(--surface-accent);
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

@media (max-width: 420px) {
  /* Empat ubin sebaris pada 390px: gambar tetap terbaca dan sebelas tipe
     selesai dalam tiga baris, bukan enam seperti grid tombol yang lama. */
  .fleet-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
  }

  .fleet-tile {
    padding: 6px 3px 5px;
  }

  .fleet-tile b {
    font-size: 10px;
  }

  .ticket-type-grid {
    grid-template-columns: 1fr;
  }
}

/* Di layar sangat sempit, label navigasi bawah dilepas dan menyisakan ikon.
   Lima label yang saling terpotong lebih sulit dibaca daripada lima ikon. */
@media (max-width: 380px) {
  .sidebar .nav-link span {
    display: none;
  }

  .sidebar .nav-link {
    min-height: 46px;
  }

  .workspace {
    padding-bottom: calc(66px + env(safe-area-inset-bottom));
  }
}

/* --------------------------------------------------------------------------
 * 33. Alur dan waktu
 *
 * Bentuk untuk keputusan yang dibuat di js/: gate yang DIJANJIKAN kepada driver
 * yang dipanggil, tahap yang terlewat, jam kedatangan yang mengikuti jam, hasil
 * simpan yang bertahan sampai truk berikutnya, dan pembatas kelompok yang
 * dipakai Ringkasan dan Pengaturan. Tidak ada hex dan tidak ada gerak baru.
 * ----------------------------------------------------------------------- */

/* Dok yang dijanjikan: garis putus seperti dok kosong — pintunya memang belum
   dibongkar — tetapi bernada aksen, karena sudah ada truk yang menuju ke sana. */
.dock-called {
  border-style: dashed;
  border-color: color-mix(in oklab, var(--dock-busy) 45%, var(--line));
}

.dock-called .dock-state {
  color: var(--dock-busy);
}

.group-divider small {
  color: var(--text-muted);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* Pita tahap MEMBUNGKUS alih-alih memotong: pada kartu selebar ponsel, yang
   terpotong justru nama tahap berikutnya. */
.stage-progress {
  flex-wrap: wrap;
  row-gap: 4px;
}

/* Nama tahap berikutnya boleh membungkus. Aturan lama `span:last-child`
   memaksanya satu baris dan memotongnya — "Berikutnya: Barang turu…" — justru
   pada satu-satunya kalimat pita ini yang menyebut apa yang harus dikerjakan.
   Selektornya sengaja sekhusus aturan itu supaya menang karena urutan. */
.stage-progress > span.stage-next {
  flex: 1 1 12em;
  min-width: 0;
  overflow: visible;
  white-space: normal;
}

.stage-skip {
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 6px;
  background: color-mix(in oklab, var(--status-warning) 14%, var(--surface));
  color: var(--status-warning);
  font-size: 11px;
  font-weight: 700;
}

/* Selisih negatif di dialog Rincian: jam yang mendahului pendahulunya. */
.stage-span.is-negative {
  color: var(--status-critical);
  font-weight: 700;
}

.gate-note {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-left: 3px solid var(--status-warning);
  border-radius: 0 8px 8px 0;
  background: color-mix(in oklab, var(--status-warning) 9%, var(--surface));
  color: var(--text-soft);
  font-size: 12px;
}

.arrival-field {
  display: grid;
  gap: 6px;
}

.arrival-mode {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  color: var(--text-muted);
  font-size: 11px;
}

.arrival-mode.is-live {
  color: var(--teal);
  font-weight: 600;
}

.arrival-mode.is-live i {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: currentColor;
}

/* Hasil simpan: nada normal, bukan kritis — `.banner` bawaan lahir untuk galat. */
.banner-ok {
  border-left-color: var(--status-normal);
  background: color-mix(in oklab, var(--status-normal) 8%, var(--surface));
}

.banner-ok strong {
  color: var(--status-normal);
}

.banner-ok strong .mono {
  font-size: 16px;
  letter-spacing: 0.04em;
}


/* ==========================================================================
 * KAPASITAS KENDARAAN — parkir dan gate (js/capacity.js)
 *
 * Hanya token warna yang sudah ada. Keadaan dibedakan lewat tona status, dan
 * batang parkir mengisi sesuai jumlah truk — tanpa warna baru.
 * ========================================================================== */
.cap-panel {
  display: grid;
  grid-template-columns: minmax(220px, 420px) minmax(0, 1fr);
  align-items: end;
  gap: 10px 24px;
  padding: 12px 14px;
  margin-bottom: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.cap-room {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.cap-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.cap-meter {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  --cap-tone: var(--status-normal);
}

.cap-meter.is-warning { --cap-tone: var(--status-warning); }
.cap-meter.is-critical { --cap-tone: var(--status-critical); }
.cap-meter.is-muted { --cap-tone: var(--text-muted); }

.cap-track {
  position: relative;
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: var(--surface-muted);
  border: 1px solid var(--line);
  overflow: hidden;
}

.cap-track i {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  background: var(--cap-tone);
  transition: width 240ms ease;
}

.cap-count {
  font-size: 16px;
  font-weight: 800;
  color: var(--text);
  white-space: nowrap;
}

.cap-state {
  font-size: 12px;
  font-weight: 700;
  color: var(--cap-tone);
  white-space: nowrap;
}

.cap-meter.is-unlimited .cap-state { color: var(--text-muted); }

.cap-hint {
  margin: 0;
  font-size: 12px;
  color: var(--text-soft);
}

@media (max-width: 720px) {
  .cap-panel { grid-template-columns: 1fr; }
}

/* ==========================================================================
 * RIWAYAT — nama vendor yang dapat diketuk, bilah rentang, dan tabel vendor
 * ========================================================================== */
.vendor-link {
  appearance: none;
  border: 0;
  padding: 0;
  margin: 0;
  background: none;
  font: inherit;
  font-weight: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
  text-underline-offset: 3px;
  /* Sasaran ketuk setinggi kontrol kecil di tablet, tanpa menggeser baris:
     padding vertikal dibalas margin negatif. */
  padding-block: 8px;
  margin-block: -8px;
}

.vendor-link:hover,
.vendor-link:focus-visible {
  color: var(--accent);
  text-decoration-color: currentColor;
}

.vendor-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.board-range {
  display: grid;
  gap: 10px;
}

.board-range .filter-bar {
  margin-bottom: 0;
}

.vendor-history td.is-priority,
.vendor-history tr.is-priority td:first-child {
  font-weight: 700;
}

.metric-value-sm {
  font-size: 15px;
}


/* ==========================================================================
 * TOOLBAR PAPAN — satu baris, panel filter mengambang (tidak mendorong daftar)
 *
 * Tujuannya: daftar kartu harus mulai setinggi mungkin. Semua kontrol filter
 * dipadatkan ke satu baris; panel Gate/Armada/Tipe/Rentang terbuka sebagai
 * lapisan di atas konten, bukan dengan mendorong kartu ke bawah.
 * Aturan global `.filter-bar` tetap milik halaman lain.
 * ========================================================================== */
.board-toolbar {
  position: relative;
  margin-bottom: 6px;
}

.board-toolbar-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.board-toolbar .bt-search {
  flex: 1 1 auto;
  min-width: 0;
}

/* Tinggi dari skala kontrol, bukan angka tetap: 34px di layar biasa, 46px di
   layar sentuh. Angka tetap di sini dulu membuat tiga kontrol yang paling
   sering disentuh di tablet — cari, filter, urutkan — justru yang terkecil. */
.board-toolbar .bt-search .input,
.board-toolbar .sort-control .input {
  height: var(--control-h-sm);
  min-height: var(--control-h-sm);
  padding: 4px 10px;
  font-size: 13px;
}

.board-toolbar .sort-control {
  display: flex;
  width: auto;
  flex: 0 0 auto;
  align-items: center;
  margin: 0;
}

.board-toolbar .sort-control > span {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.board-toolbar .sort-control .input {
  width: 200px;
  max-width: 200px;
}

/* Tombol Filter: ringkas. Ringkasan pilihannya tampil kecil di sebelahnya. */
.board-toolbar .bt-filter {
  position: relative;
  flex: 0 0 auto;
}

.board-toolbar .bt-toggle {
  height: var(--control-h-sm);
  min-height: var(--control-h-sm);
  padding: 4px 10px;
  list-style: none;
  cursor: pointer;
  white-space: nowrap;
}

.board-toolbar .bt-toggle::-webkit-details-marker {
  display: none;
}

.board-toolbar .bt-toggle b {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
  color: var(--text-muted);
}

.board-toolbar .bt-toggle b:empty {
  display: none;
}

.board-toolbar .bt-filter[open] > .bt-toggle {
  border-color: var(--accent);
  color: var(--accent);
}

.board-toolbar .bt-panel-foot {
  display: flex;
  justify-content: flex-end;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

/* Panel mengambang: keluar dari alur, sehingga membukanya tidak mendorong daftar. */
.board-toolbar .bt-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  display: grid;
  gap: 8px;
  width: min(380px, calc(100vw - 24px));
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
}

.board-toolbar .bt-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

.board-toolbar .bt-grid label {
  display: grid;
  gap: 3px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.board-toolbar .bt-grid .input {
  height: 32px;
  min-height: 32px;
  padding: 3px 8px;
  font-size: 12px;
  text-transform: none;
  letter-spacing: 0;
}

/* Rentang riwayat: sebaris di dalam panel. */
.board-range {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 6px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
}

.bt-range-title {
  width: 100%;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.board-range .filter-bar {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}

/* Kotak tanggal punya lebar sendiri. Dengan `min-width: 0` di dalam baris
   flex, keduanya menyusut sampai 50px: tanggalnya terpotong menjadi "10/2",
   ikon kalendernya tertutup, dan ketukan memilih tanggal tidak membuka apa pun. */
.board-range .filter-bar > .field {
  display: grid;
  flex: 1 1 150px;
  gap: 2px;
  min-width: 150px;
}

.board-range .filter-bar .input {
  width: 100%;
  height: 38px;
  min-height: 38px;
  padding: 4px 10px;
  font-size: 13px;
}

/* Prasetel dan Terapkan di baris sendiri, selebar panel. */
.board-range .range-presets,
.board-range .table-actions {
  flex: 1 1 100%;
}

.board-range .range-presets {
  display: flex;
  gap: 3px;
  flex-wrap: wrap;
}

.board-range .range-presets .btn-sm {
  min-height: 34px;
  padding: 4px 10px;
}

.board-range .filter-note,
.board-range .section-note {
  display: none;
}

.board-range .table-actions {
  display: flex;
}

/* Tab status dan hitungan: satu baris tipis. */
.board-tabs {
  gap: 3px;
  margin-bottom: 6px;
}

.board-tabs .board-tab {
  padding: 4px 9px;
  font-size: 12px;
}

.filter-status {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}

.filter-status .filter-count {
  margin: 0;
  font-size: 12px;
}

/* Ponsel: satu baris tetap; ikon Filter, kotak cari, dan urutan yang menyempit. */
@media (max-width: 720px) {
  .board-toolbar-row {
    gap: 5px;
  }
  .board-toolbar .bt-toggle-text,
  .board-toolbar .bt-toggle b {
    display: none;
  }
  /* Urutan TETAP terlihat di ponsel, hanya menyempit. Sebelumnya kotaknya
     dilebarkan nol — tidak terlihat, tetapi masih dapat difokus keyboard dan
     pembaca layar — sementara labelnya tetap selebar baris (aturan 620px di
     bagian lain) dan mendorong halaman 21 piksel melewati tepi layar. */
  .board-toolbar .sort-control {
    flex: 0 1 132px;
    min-width: 96px;
  }
  .board-toolbar .sort-control .input {
    width: 100%;
    max-width: 132px;
    min-width: 0;
    flex: 1 1 auto;
    text-overflow: ellipsis;
  }
  /* Lembar bawah berdiri DI ATAS bilah navigasi ponsel (±56px + area aman),
     bukan di belakangnya: tombol "Selesai" di dasar lembar dulu tertutup
     bilah itu, dan ketukannya membuka halaman lain. */
  .board-toolbar .bt-panel {
    position: fixed;
    top: auto;
    right: 8px;
    left: 8px;
    bottom: calc(66px + env(safe-area-inset-bottom));
    width: auto;
    max-height: calc(100dvh - 140px);
    overflow: auto;
    box-shadow: 0 -8px 32px rgba(15, 23, 42, 0.28);
  }
  .board-toolbar .bt-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* ==========================================================================
 * 40. PENYELARASAN NEXUS
 *
 * Langkah pertama menuju design system NEXUS (astro-sandbox-ops-wh,
 * Docs/Base/ui_ux.md), SENGAJA kecil: tata letak, menu, urutan, dan setiap
 * tombol tetap di tempatnya supaya operator tidak perlu belajar ulang. Yang
 * berganti hanya "kulit" yang dibagi bersama NEXUS — token (bagian 1), radius
 * iOS, input berlatar lembut dengan cincin fokus, lencana berbentuk pil, dan
 * judul berbobot NEXUS. Tinta status tetap yang lebih pekat (lihat CONTRAST
 * di test/design-system.test.js): teks 10px di ruang beku menuntut 4,5:1.
 * ======================================================================== */
.card {
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

.page-header h1 {
  font-weight: 800;
  letter-spacing: -0.01em;
}

.section-head h2 {
  font-size: 15px;
  font-weight: 700;
}

.btn {
  border-radius: var(--radius-sm);
  transition: background-color 0.2s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.2s cubic-bezier(0.16, 1, 0.3, 1), color 0.2s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.btn:focus-visible {
  box-shadow: var(--focus-ring);
}

.btn-primary {
  box-shadow: 0 4px 12px color-mix(in oklab, var(--accent) 22%, transparent);
}

.input {
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-muted);
  transition: border-color 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1),
    background-color 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.input:hover {
  border-color: color-mix(in oklab, var(--accent) 45%, var(--line));
  background: var(--surface);
}

.input:focus,
.input:focus-visible {
  border-color: var(--accent);
  background: var(--surface);
  /* Garis 1px berwarna aksen di balik cincin lembut NEXUS: cincin 18% saja
     hanya ~1,2:1 terhadap putih, terlalu samar untuk pengguna keyboard. */
  box-shadow: 0 0 0 1px var(--accent), var(--focus-ring);
  outline: none;
}

.input[aria-invalid="true"] {
  border-color: var(--status-critical);
}

/* Lencana NEXUS: pil berbingkai tipis dengan latar lembut. Ukurannya tetap
   kecil — ia hidup di kartu antrean yang padat. */
.badge {
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid color-mix(in oklab, currentColor 22%, transparent);
  border-radius: var(--radius-pill);
  letter-spacing: 0.02em;
}

.nav-link {
  border-radius: var(--radius-sm);
}

.tbl thead th {
  font-weight: 700;
  letter-spacing: 0.04em;
}

/* --------------------------------------------------------------------------
 * KPI tile — padanan <KpiCard> NEXUS. Dapat diketuk untuk menyaring.
 * ----------------------------------------------------------------------- */
.kpi-grid {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
}

.kpi-tile {
  position: relative;
  display: grid;
  min-width: 0;
  gap: 4px;
  padding: 14px 16px 13px;
  overflow: hidden;
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  cursor: pointer;
  font: inherit;
  text-align: left;
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.2s, box-shadow 0.2s;
}

.kpi-tile::before {
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--kpi-tone, var(--text-muted));
  content: "";
}

.kpi-tile:hover {
  border-color: color-mix(in oklab, var(--kpi-tone, var(--accent)) 45%, var(--line));
  transform: translateY(-1px);
}

.kpi-tile:focus-visible {
  box-shadow: var(--focus-ring);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.kpi-tile.is-active {
  border-color: var(--kpi-tone, var(--accent));
  background: color-mix(in oklab, var(--kpi-tone, var(--accent)) 7%, var(--surface));
}

.kpi-label {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 6px;
  color: var(--text-soft);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.kpi-label i {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--kpi-tone, var(--text-muted));
}

.kpi-value {
  font-family: var(--font-mono);
  font-size: 28px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1.1;
  color: var(--kpi-tone, var(--text));
}

.kpi-sub {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.kpi-tile.skeleton {
  min-height: 92px;
  cursor: default;
  background: var(--surface-muted);
}

@media (prefers-reduced-motion: reduce) {
  .kpi-tile {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
 * Chip penyaring — padanan <ChipButton> NEXUS.
 * ----------------------------------------------------------------------- */
.chip-row {
  display: flex;
  min-width: 0;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
  scrollbar-width: thin;
}

.state-chip {
  display: inline-flex;
  min-height: 36px;
  flex: 0 0 auto;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-soft);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.state-chip b {
  min-width: 22px;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-muted);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 11px;
  text-align: center;
}

.state-chip:hover {
  border-color: color-mix(in oklab, var(--accent) 50%, var(--line));
  background: var(--surface-accent);
  color: var(--accent);
}

.state-chip:focus-visible {
  box-shadow: var(--focus-ring);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.state-chip.is-active {
  border-color: var(--accent);
  background: var(--primary-gradient);
  box-shadow: 0 4px 12px color-mix(in oklab, var(--accent) 25%, transparent);
  color: var(--accent-ink);
}

.state-chip.is-active b {
  background: color-mix(in oklab, var(--accent-ink) 22%, transparent);
  color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
 * Tabel bertumpuk — tabel di layar lebar, kartu berlabel di ponsel
 * (padanan DataTable + MobileCardList NEXUS, tanpa dua markup).
 * ----------------------------------------------------------------------- */
@media (max-width: 720px) {
  .tbl-stack thead {
    display: none;
  }

  .tbl-stack,
  .tbl-stack tbody {
    display: block;
    width: 100%;
  }

  .tbl-stack tr {
    display: grid;
    gap: 8px 12px;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-bottom: 10px;
    padding: 12px 14px;
    border: 1.5px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
  }

  .tbl-stack td {
    display: grid;
    min-width: 0;
    gap: 2px;
    padding: 0;
    border: 0;
    text-align: left;
    white-space: normal;
  }

  .tbl-stack td::before {
    color: var(--text-muted);
    content: attr(data-label);
    font-family: var(--font-sans);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }

  .tbl-stack td.cell-text,
  .tbl-stack td.cell-wide {
    grid-column: 1 / -1;
  }

  .tbl-stack td {
    overflow-wrap: anywhere;
  }

  .tbl-stack tbody tr:hover > * {
    background: transparent;
  }
}

.muted-copy {
  margin: 0 0 12px;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
}

.tone-warning { color: var(--status-warning); }
.tone-critical { color: var(--status-critical); }

/* --------------------------------------------------------------------------
 * 41. Jadwal Vendor (SC WIP)
 * ----------------------------------------------------------------------- */
.schedule-page {
  display: grid;
  min-width: 0;
  gap: 14px;
}

.sched-next svg {
  transform: scaleX(-1);
}

.sched-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.sched-toolbar .chip-row {
  flex: 1 1 420px;
}

.sched-search {
  flex: 0 1 280px;
  min-width: 200px;
}

.sched-table td small {
  color: var(--text-muted);
  font-size: 11px;
}

.sched-row.is-missed > td:first-child {
  box-shadow: inset 3px 0 0 var(--status-critical);
}

.sched-row.is-late > td:first-child {
  box-shadow: inset 3px 0 0 var(--status-warning);
}

.sched-row.is-late .sched-delay {
  color: var(--status-warning);
  font-weight: 700;
}

@media (max-width: 720px) {
  .sched-search {
    flex: 1 1 100%;
  }

  .sched-row.is-missed {
    border-left: 4px solid var(--status-critical);
  }

  .sched-row.is-late {
    border-left: 4px solid var(--status-warning);
  }

  .sched-row > td:first-child {
    box-shadow: none !important;
  }

  .kpi-grid.sched-kpis {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* JetBrains Mono lebih lebar daripada Inconsolata: pil status yang tidak
     boleh menyusut mendorong pemilih gudang keluar layar pada 375 piksel.
     Teksnya sudah dipotong dengan ellipsis; pilnya kini ikut menyusut. */
  .topbar-actions .data-mode {
    min-width: 0;
    flex: 0 1 196px;
  }

  .kpi-value {
    font-size: 24px;
  }
}

/* --------------------------------------------------------------------------
 * 42. Jadwal Vendor — tab, analisa, ekspor
 * ----------------------------------------------------------------------- */

/* Kontrol segmen NEXUS (SegmentedControl): satu bidang bergaris, tab aktif
   terangkat sebagai kartu putih. */
.seg-tabs {
  display: inline-flex;
  max-width: 100%;
  gap: 4px;
  overflow-x: auto;
  padding: 4px;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-muted);
  scrollbar-width: none;
}

.seg-tab {
  display: inline-flex;
  min-height: 38px;
  flex: 0 0 auto;
  align-items: center;
  gap: 7px;
  padding: 0 14px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-soft);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

.seg-tab svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

.seg-tab:hover {
  color: var(--accent);
}

.seg-tab.is-active {
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  color: var(--accent);
}

.seg-tab:focus-visible {
  box-shadow: var(--focus-ring);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.sched-panel {
  display: grid;
  min-width: 0;
  gap: 14px;
}

.sched-source {
  display: flex;
  flex: 1 1 280px;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}

.export-group {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.section-actions:has(.export-group) {
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Temuan: kalimat jadi dengan garis warna di kiri, satu per baris. */

/* Bilah mendatar: label, jalur bilah, lalu angkanya sebagai teks. */
.conflict-list {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.conflict-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 13px;
}

.conflict-list li span:not(.badge) {
  color: var(--text-soft);
}

@media (max-width: 720px) {
  .sched-source {
    justify-content: space-between;
  }

  .seg-tabs {
    display: flex;
  }

  .seg-tab {
    flex: 1 0 auto;
    justify-content: center;
  }

}

/* ==========================================================================
 * 43. PENYELARASAN NEXUS — LANGKAH KEDUA (mode terang)
 *
 * Masih tanpa memindahkan apa pun: menu, tombol, dan urutan halaman tetap.
 * Yang diselaraskan dengan kit NEXUS (frontend/src/components/ui dan
 * layout/AppLayout.module.css di astro-sandbox-ops-wh):
 *
 *   menu      latar putih, butir 13px/600, aktif = primary-50 + garis 4px
 *             berpendar di kiri (navTabItem.active)
 *   topbar    putih padat bergaris bawah, bukan kaca tembus
 *   tombol    primer bergradien primary-600 → 700 dan terangkat 1px saat
 *             disentuh; sekunder putih → primary-50; bahaya bidang merah lembut
 *   tabel     kepala surface-muted 11px tebal kapital, baris disorot primary-50
 *   panel     judul 15px/800, kepala 16×20px
 *   lencana   pil KAPITAL 11px tebal, seperti StatusPill
 *   modal     tirai slate 60% + blur 6px, kartu 24px
 *
 * Hanya token — tidak ada hex baru — dan tanpa animasi, sesuai aturan lapisan
 * ponsel (test/compact-mobile.test.js).
 * ======================================================================== */
.sidebar {
  box-shadow: var(--shadow-sm);
}

.nav-link {
  position: relative;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-soft);
}

.nav-link:hover {
  background: var(--surface-accent);
  color: var(--accent);
}

.nav-link.active {
  background: var(--surface-accent);
  box-shadow: none;
  color: var(--accent);
  font-weight: 800;
}

.nav-link.active::before {
  position: absolute;
  top: 50%;
  left: -6px;
  width: 4px;
  height: 22px;
  border-radius: 0 4px 4px 0;
  background: var(--accent);
  box-shadow: 0 0 10px color-mix(in oklab, var(--accent) 50%, transparent);
  content: "";
  transform: translateY(-50%);
}

.topbar {
  background: var(--surface);
  backdrop-filter: none;
}

.btn {
  font-weight: 700;
}

/* Hanya tombol sekunder: varian berwarna (teal, pindai, cepat, bahaya)
   menyimpan hover-nya sendiri. */
.btn:not(.btn-primary, .btn-teal, .btn-danger, .btn-ghost, .btn-scan, .btn-quick):hover:not(:disabled) {
  border-color: color-mix(in oklab, var(--accent) 55%, var(--line));
  background: var(--surface-accent);
  color: var(--accent);
}

.btn-primary,
.btn-primary:hover:not(:disabled) {
  border-color: transparent;
  background: var(--primary-gradient);
  color: var(--accent-ink);
}

.btn-primary:hover:not(:disabled) {
  box-shadow: 0 6px 16px color-mix(in oklab, var(--accent) 35%, transparent);
  transform: translateY(-1px);
}

.btn-ghost:hover:not(:disabled) {
  border-color: transparent;
}

.btn-danger {
  border-color: color-mix(in oklab, var(--status-critical) 30%, var(--line));
  background: color-mix(in oklab, var(--status-critical) 7%, var(--surface));
  color: var(--status-critical);
}

.btn-danger:hover:not(:disabled) {
  border-color: var(--status-critical);
  background: var(--status-critical);
  color: var(--accent-ink);
}

.tbl thead th {
  background: var(--surface-muted);
  color: var(--text-soft);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.tbl tbody tr:hover > * {
  background: var(--surface-accent);
}

.section-head {
  padding: 14px 20px;
}

.section-head h2 {
  font-weight: 800;
}

.badge {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Lencana jam/nomor tetap apa adanya: "jadwal 09:45" kapital tidak
   menambah keterbacaan, dan nomor PO tidak boleh berubah bentuk. */
.badge.mono,
.queue-sched .badge,
.po-row-vendor .badge {
  text-transform: none;
}

.modal-backdrop {
  backdrop-filter: blur(6px);
  background: rgb(15 23 42 / 60%);
}

.modal-card {
  border-color: var(--line);
  border-radius: var(--radius-lg, 24px);
  box-shadow: 0 20px 50px rgb(15 23 42 / 18%);
}

.empty-state {
  border-radius: var(--radius);
}

.metric-strip,
.filter-bar,
@media (min-width: 769px) {
  .section-body {
    padding: 18px 20px;
  }
}

@media (max-width: 720px) {
  .nav-link.active::before {
    display: none;
  }

  .section-head {
    padding: 12px 16px;
  }
}

/* --------------------------------------------------------------------------
 * 44. Jadwal Vendor — tata letak ringkas
 *
 * Satu bilah hari, enam kartu angka, dua grafik ringkasan, lalu tab. Rincian
 * hidup di hover (data-tip), bukan di paragraf.
 * ----------------------------------------------------------------------- */
.sched-rangebar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
}

.sched-range {
  display: flex;
  align-items: center;
  gap: 6px;
}

.sched-date .input {
  width: 150px;
}

.sched-range-sep,
.sched-range-text {
  color: var(--text-muted);
  font-size: 12px;
}

.sched-range-error {
  flex: 1 1 100%;
  margin: 0;
}

/* Penyaring: chip status + cari di baris pertama, medan di baris kedua, chip
   penyaring aktif dengan "X dari Y PO" di baris terakhir. */
.sched-filters {
  display: grid;
  gap: 10px;
  padding: 12px 14px;
}

.sched-filters > * {
  min-width: 0;
}

.sched-filters .sched-toolbar {
  margin: 0;
}

.sched-filter-grid {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  gap: 10px 12px;
}

.sched-filter-grid > * {
  min-width: 0;
  flex: 1 1 150px;
}

.sched-filter-grid > .field-pair {
  display: grid;
  flex: 2 1 230px;
  gap: 8px;
  grid-template-columns: 1fr 1fr;
}

.field-pair .input {
  min-width: 0;
}

.sched-active {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 12px;
}

.sched-count {
  margin-right: 4px;
  color: var(--text-soft);
}

.sched-count b {
  color: var(--text);
  font-family: var(--font-mono);
}

.sched-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin: 0;
  color: var(--text-soft);
  font-size: 12px;
}

/* Pilihan ganda (vendor, PIC): tombol berbentuk kotak isian, panel melayang. */
.ms {
  position: relative;
}

.ms-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  cursor: pointer;
  text-align: left;
}

.ms-toggle span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ms-toggle svg {
  flex: 0 0 auto;
  transform: rotate(-90deg);
  color: var(--text-muted);
}

.ms-toggle[aria-expanded="true"] svg {
  transform: rotate(90deg);
}

.ms.is-set .ms-toggle {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}

.ms-panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  display: grid;
  width: max(100%, 280px);
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-raised);
  box-shadow: var(--elevation-2);
}

.ms-panel[hidden] {
  display: none;
}

.ms-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: var(--text-muted);
  font-size: 11px;
}

.ms-list {
  display: grid;
  max-height: 260px;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.ms-list label {
  display: grid;
  align-items: center;
  gap: 8px;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  min-height: 36px;
  padding: 0 6px;
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-size: 13px;
}

.ms-list label:hover {
  background: var(--surface-accent);
}

.ms-list b {
  color: var(--text-muted);
  font-size: 11px;
}

.ms-empty {
  padding: 8px;
  color: var(--text-muted);
  font-size: 12px;
}

.sched-qty-stats {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.rank-row.vol-row {
  grid-template-columns: minmax(120px, 30%) minmax(0, 1fr) 132px;
}

.vol-row .rank-value {
  white-space: nowrap;
}

.vol-row.is-other .rank-name {
  color: var(--text-muted);
  font-style: italic;
}

.chart-ref line {
  stroke: var(--text-muted);
  stroke-dasharray: 4 4;
}

.chart-quad {
  fill: var(--status-critical);
  font-weight: 600;
}

.sched-overview {
  display: grid;
  align-items: start;
  gap: 16px;
  grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
}

.sched-pair {
  display: grid;
  align-items: start;
  gap: 16px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.seg-tabs.is-small {
  padding: 3px;
}

.seg-tabs.is-small .seg-tab {
  min-height: 30px;
  padding: 0 10px;
  font-size: 12px;
}

.chart-foot {
  margin: 6px 0 0;
  color: var(--text-muted);
  font-size: 12px;
}

/* Peringkat: nama, bilah bertumpuk selebar jumlah PO, kepatuhan. */
.rank-bars {
  display: grid;
  gap: 8px;
  margin-bottom: 10px;
}

.rank-row {
  display: grid;
  align-items: center;
  gap: 12px;
  grid-template-columns: minmax(120px, 34%) minmax(0, 1fr) 44px;
  min-height: 26px;
  border-radius: var(--radius-xs);
  font-size: 12px;
  cursor: default;
}

.rank-row:hover {
  background: var(--surface-accent);
}

.rank-name {
  overflow: hidden;
  padding-left: 4px;
  color: var(--text);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rank-track {
  display: flex;
  min-width: 6px;
  height: 12px;
  overflow: hidden;
  gap: 1px;
  border-radius: var(--radius-pill);
}

.rank-track i {
  min-width: 2px;
}

.rank-value {
  padding-right: 4px;
  text-align: right;
  color: var(--text-soft);
}

.kpi-tile .kpi-sub:empty {
  display: none;
}

@media (max-width: 1100px) {
  .sched-overview,
  .sched-pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 720px) {
  .sched-range {
    flex: 1 1 100%;
  }

  .sched-date {
    flex: 1 1 0;
    min-width: 0;
  }

  .sched-date .input {
    width: 100%;
  }

  .rank-row.vol-row {
    grid-template-columns: minmax(96px, 36%) minmax(0, 1fr) 112px;
  }


  .rank-row {
    grid-template-columns: minmax(96px, 40%) minmax(0, 1fr) 40px;
    gap: 8px;
  }
}

/* --------------------------------------------------------------------------
 * 45. Ruang dan ukuran sentuh
 *
 * Hasil pengukuran semua halaman di 1280px dan 375px (2026-10-22):
 *   - teks di bawah 10px dinaikkan ke label mikro NEXUS (10px);
 *   - panel pilihan kolom Laporan meluap 148px keluar layar ponsel — kini
 *     menjadi lembar bawah selebar layar;
 *   - tombol kecil dan kotak tanggal 30–32px di ponsel dinaikkan ke 38–40px,
 *     ukuran yang dapat diketuk bersarung tangan.
 * ----------------------------------------------------------------------- */
.group-divider small {
  font-size: 10.5px;
}

@media (max-width: 720px) {
  .column-picker-body {
    position: fixed;
    top: auto;
    right: 12px;
    bottom: calc(76px + env(safe-area-inset-bottom));
    left: 12px;
    width: auto;
    max-height: 60dvh;
    overflow: auto;
  }

  .btn-sm,
  .board-range .range-presets .btn-sm {
    min-height: 38px;
  }

  input.input,
  select.input {
    height: 40px;
    min-height: 40px;
  }
}

/* Kepala kelompok papan antrean ("Sedang bongkar 3 · hitung mundur SLA
   jalan"). Sebelumnya 13px tinggi dengan jarak 0px ke kartu pertama di
   bawahnya: judul dan papan terbaca sebagai satu gumpalan. Kini label tebal,
   jumlah sebagai pil, petunjuk langkah lebih tenang, dan ruang yang jelas di
   atas (pemisah antar-kelompok) dan di bawah (judul milik kartu di bawahnya). */
.queue-group {
  min-height: 30px;
  margin: 22px 0 12px;
  padding: 0 2px;
  color: var(--text);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.06em;
}

.queue-group:first-child,
.board-tabs + .queue-group,
.filter-count + .queue-group {
  margin-top: 8px;
}

.queue-group b {
  display: inline-flex;
  min-width: 26px;
  height: 22px;
  align-items: center;
  justify-content: center;
  padding: 0 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-accent);
  color: var(--accent);
  font-size: 11px;
  letter-spacing: 0;
}

.queue-group small {
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 500;
}

/* Ponsel: label dan jumlah satu baris, petunjuk di baris sendiri — dulu
   "SUDAH DIPANGGIL" terlipat dua di samping petunjuk yang juga terlipat. */
@media (max-width: 640px) {
  .queue-group {
    flex-wrap: wrap;
    row-gap: 2px;
  }

  /* Garis pemisah tetap di baris label, petunjuk turun ke bawahnya. */
  .queue-group::after {
    order: 1;
  }

  .queue-group small {
    order: 2;
    flex-basis: 100%;
    letter-spacing: 0;
  }
}

/* Subjudul di dalam kartu ("Perlu perhatian", "Stempel tahap", "Tanpa
   jadwal"): 6px ke isinya terbaca menempel. */
.stage-subhead {
  margin: 18px 0 10px;
}

.stage-subhead:first-child {
  margin-top: 0;
}
