/* =====================================================================
   admin.ielts.web.id — sistem desain dashboard
   Bootstrap 5.3 menangani grid dan komponen dasar; berkas ini menetapkan
   token warna, kerangka sidebar, dan komponen khas aplikasi.
   ===================================================================== */

:root {
    --primary:        #2563eb;
    --primary-dark:   #1d4ed8;
    --primary-soft:   #eff6ff;
    --accent:         #0d9488;
    --accent-soft:    #f0fdfa;

    --ink:            #0f172a;
    --ink-soft:       #334155;
    --muted:          #64748b;
    --muted-light:    #94a3b8;

    --bg:             #f1f5f9;
    --surface:        #ffffff;
    --surface-alt:    #f8fafc;
    --border:         #e2e8f0;
    --border-strong:  #cbd5e1;

    --success:        #059669;
    --success-soft:   #ecfdf5;
    --warning:        #d97706;
    --warning-soft:   #fffbeb;
    --danger:         #dc2626;
    --danger-soft:    #fef2f2;
    --info:           #0891b2;

    /*
     | Aksen fokus sengaja lebih terang dari --primary: cincin biru tua di
     | atas garis abu-abu nyaris tidak terlihat, dan navigasi keyboard
     | menjadi menebak-nebak.
     */
    --accent-focus:   #2563eb;
    --focus-ring:     rgba(37, 99, 235, .18);

    --radius:         12px;
    --radius-sm:      10px;
    --radius-lg:      16px;
    --shadow-sm:      0 1px 2px rgba(15, 23, 42, .06);
    --shadow:         0 1px 3px rgba(15, 23, 42, .08), 0 1px 2px rgba(15, 23, 42, .04);
    --shadow-lg:      0 10px 25px -5px rgba(15, 23, 42, .1), 0 4px 10px -6px rgba(15, 23, 42, .06);

    --sidebar-w:      256px;
    --topbar-h:       64px;

    /* ── Skala huruf ──────────────────────────────────────────────────
       Disalin apa adanya dari test.ielts.web.id/assets/css/app.css, dengan
       alasan yang sama seperti token warna di atas: kedua sisi aplikasi
       harus terasa satu keluarga. Kalau salah satunya berubah, ubah
       keduanya.

       Dasbor memakai --fs-ui (14px tetap) sebagai ukuran badan, bukan
       --fs-body: ini layar padat data — tabel paket, daftar soal, antrean
       penilaian — dan 14px memang ukuran untuk kepadatan itu. Yang
       MENGALIR di sini hanya judul.

       Bentuk clamp()-nya selalu `rem + vw`, tidak pernah vw murni, supaya
       pembesaran peramban tetap berpengaruh (WCAG 1.4.4). */

    /* Antarmuka — tetap di semua lebar */
    --fs-2xs:  0.6875rem;   /* 11px — lencana, label nav */
    --fs-xs:   0.75rem;     /* 12px — keterangan, meta */
    --fs-sm:   0.8125rem;   /* 13px — label formulir, sel tabel kecil */
    --fs-ui:   0.875rem;    /* 14px — badan dasbor, tombol, tabel */

    /* Isi — 15px di 360px, 16px di 1024px ke atas */
    --fs-body: clamp(0.9375rem, 0.904rem + 0.151vw, 1rem);
    --fs-lead: clamp(1rem, 0.966rem + 0.151vw, 1.0625rem);      /* 16 → 17 */

    /* Kolom isian tidak pernah di bawah 16px — ambang Safari iOS, bukan
       pilihan rancangan. Bootstrap sudah memberi .form-control 1rem, jadi
       token ini di sini untuk kolom yang ditulis sendiri. */
    --fs-input: max(16px, var(--fs-body));

    /* Judul */
    --fs-h3:   clamp(1.0625rem, 1.029rem + 0.151vw, 1.125rem);  /* 17 → 18 */
    --fs-h2:   clamp(1.25rem, 1.182rem + 0.301vw, 1.375rem);    /* 20 → 22 */
    --fs-h1:   clamp(1.5rem, 1.364rem + 0.602vw, 1.75rem);      /* 24 → 28 */
}

* { box-sizing: border-box; }

/* Tanpa ini Safari iOS menggelembungkan teks begitu perangkat diputar ke
   lanskap, dan lebar sidebar yang dipatok px ikut bergeser terhadapnya.
   Pasangannya sudah lama ada di app.css sisi peserta. */
html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: var(--fs-ui);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { color: var(--primary-dark); text-decoration: underline; }

/* ── Kerangka ─────────────────────────────────────────────────────── */

.admin-shell { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-w);
    flex: 0 0 var(--sidebar-w);
    background: var(--ink);
    color: #cbd5e1;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    z-index: 1040;
}

.sidebar-brand {
    height: var(--topbar-h);
    display: flex;
    align-items: center;
    gap: .625rem;
    padding: 0 1.25rem;
    border-bottom: 1px solid rgba(255, 255, 255, .08);
    flex-shrink: 0;
}
.sidebar-brand .brand-mark {
    width: 32px; height: 32px;
    border-radius: var(--radius-sm);
    background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
    display: grid; place-items: center;
    color: #fff; font-weight: 700; font-size: var(--fs-xs);
    flex-shrink: 0;
}
.sidebar-brand .brand-text {
    font-weight: 650; color: #fff; font-size: var(--fs-body);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.sidebar-nav { flex: 1; overflow-y: auto; padding: .875rem .75rem 1.5rem; }
.sidebar-nav::-webkit-scrollbar { width: 6px; }
.sidebar-nav::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .12); border-radius: 3px; }

.nav-label {
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: .06em;
    color: #64748b;
    font-weight: 600;
    padding: 1rem .75rem .375rem;
}
.nav-label:first-child { padding-top: .25rem; }

.sidebar .nav-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .5625rem .75rem;
    border-radius: var(--radius-sm);
    color: #cbd5e1;
    font-size: var(--fs-ui);
    font-weight: 500;
    margin-bottom: 2px;
    transition: background .15s, color .15s;
    text-decoration: none;
}
.sidebar .nav-link i { width: 18px; text-align: center; font-size: var(--fs-ui); flex-shrink: 0; opacity: .8; }
.sidebar .nav-link:hover { background: rgba(255, 255, 255, .06); color: #fff; text-decoration: none; }
.sidebar .nav-link:hover i { opacity: 1; }
.sidebar .nav-link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/*
 | Batang aksen di tepi kiri, bukan hanya latar biru. Pada sidebar yang
 | panjang, satu blok warna mudah tertukar dengan tombol yang sedang
 | disentuh kursor; batang tegas menandai "Anda sedang di sini".
 */
.sidebar .nav-link.active {
    background: rgba(37, 99, 235, .16);
    color: #fff;
    font-weight: 600;
}
.sidebar .nav-link.active i { opacity: 1; color: #93c5fd; }
.sidebar .nav-link.active::before {
    content: '';
    position: absolute;
    left: -.75rem; top: 50%;
    transform: translateY(-50%);
    width: 3px; height: 22px;
    border-radius: 0 3px 3px 0;
    background: #60a5fa;
}
.sidebar .nav-link .badge { margin-left: auto; font-size: var(--fs-2xs); }

/* Kaki sidebar: tempat yang mustahil tidak terlihat untuk lencana keadaan. */
.sidebar-foot {
    flex-shrink: 0;
    padding: .875rem 1.25rem 1.125rem;
    border-top: 1px solid rgba(255, 255, 255, .08);
    font-size: var(--fs-2xs);
    color: #64748b;
    line-height: 1.6;
}
.env-badge {
    display: inline-flex; align-items: center; gap: .375rem;
    background: rgba(217, 119, 6, .18);
    color: #fbbf24;
    border: 1px solid rgba(217, 119, 6, .35);
    border-radius: 999px;
    padding: .1875rem .5625rem;
    font-weight: 600;
    letter-spacing: .02em;
    margin-bottom: .375rem;
}

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

.topbar {
    height: var(--topbar-h);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0 1.5rem;
    position: sticky;
    top: 0;
    z-index: 1030;
}
.topbar h1 { font-size: var(--fs-h3); font-weight: 650; margin: 0; color: var(--ink); }
.topbar .spacer { flex: 1; }

.sidebar-toggle {
    display: none;
    background: none; border: none;
    font-size: var(--fs-h2); color: var(--ink-soft);
    padding: .25rem .5rem; cursor: pointer;
}

.user-chip {
    display: flex; align-items: center; gap: .625rem;
    padding: .25rem .625rem .25rem .25rem;
    border-radius: 999px;
    background: var(--surface-alt);
    border: 1px solid var(--border);
}
.user-chip .avatar {
    width: 30px; height: 30px; border-radius: 50%;
    background: var(--primary); color: #fff;
    display: grid; place-items: center;
    font-size: var(--fs-xs); font-weight: 650;
}
.user-chip .meta { line-height: 1.2; }
.user-chip .meta strong { font-size: var(--fs-sm); display: block; }
.user-chip .meta span { font-size: var(--fs-2xs); color: var(--muted); }

.lang-switch { display: flex; gap: 2px; background: var(--surface-alt); padding: 3px; border-radius: var(--radius-sm); border: 1px solid var(--border); }
.lang-switch a { padding: .1875rem .5rem; border-radius: 5px; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }
.lang-switch a:hover { text-decoration: none; color: var(--ink); }
.lang-switch a.active { background: var(--surface); color: var(--primary); box-shadow: var(--shadow-sm); }

/*
 | Isi dibatasi 1440 px dan dipusatkan. Tanpa batas ini, di monitor lebar
 | setiap kartu merentang penuh dan terasa kosong. .admin-main kolom flex,
 | jadi margin otomatis memusatkannya; padding ikut hitungan lebar
 | (box-sizing global). Aturan responsif di bawah hanya mengubah padding.
 */
.admin-content { padding: 1.5rem; flex: 1; width: 100%; max-width: calc(1440px + 3rem); margin-inline: auto; }
.page-head { display: flex; align-items: flex-start; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.page-head .titles { flex: 1; min-width: 240px; }
.page-head h2 { font-size: var(--fs-h2); font-weight: 650; margin: 0 0 .125rem; }
.page-head p { color: var(--muted); margin: 0; font-size: var(--fs-ui); }
.page-head .actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* ── Navigasi sub-halaman pengaturan ──────────────────────────────────
   Dulu tujuh view menulis ulang deretan tombolnya sendiri: urutannya
   berbeda, sebagian berikon sebagian tidak, tak satu pun menandai halaman
   yang sedang dibuka, dan cek super_admin disalin ke lima berkas.

   Tab, bukan tombol: tombol berarti "lakukan sesuatu", sedangkan ini
   perpindahan tempat — dan pita tab punya tempat untuk menyatakan halaman
   mana yang sedang terbuka. */
/*
 | Bilah tab yang menggulir mendatar (detail hasil: sampai enam tab).
 | Bootstrap .nav-tabs bawaan MEMBUNGKUS — di 390px enam tab jadi tiga
 | baris yang memakan sepertiga layar. Hanya untuk bilah yang meminta
 | (.is-scroll); tab halaman kelas tetap membungkus.
 */
.nav-tabs.is-scroll {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;   /* tanpa ini overflow-x memunculkan bilah gulir tegak 1px dari tab aktif */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
}
.nav-tabs.is-scroll .nav-link { white-space: nowrap; }

.settings-nav {
    display: flex;
    gap: .25rem;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--border);
    margin: -.25rem 0 1.25rem;
}
.settings-nav[hidden] { display: none; }

.settings-nav-link {
    display: inline-flex;
    align-items: center;
    gap: .4375rem;
    padding: .5rem .875rem;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    color: var(--muted);
    font-size: var(--fs-ui);
    font-weight: 600;
    white-space: nowrap;
}
.settings-nav-link[hidden] { display: none; }
.settings-nav-link i { font-size: var(--fs-sm); opacity: .8; }
.settings-nav-link:hover { color: var(--ink); background: var(--surface-alt); text-decoration: none; }
.settings-nav-link.active {
    color: var(--primary);
    background: var(--surface);
    border-bottom-color: var(--primary);
}
.settings-nav-link.active i { opacity: 1; }

/* ── Grid kartu ───────────────────────────────────────────────────────
   Jumlah jalur dihitung dari lebar yang tersedia, BUKAN dari jumlah
   kartunya. Itu syaratnya: halaman AI merender 6 kartu penyedia dan
   halaman ChatAI 11, dan keduanya harus rapi tanpa tuning terpisah. Tata
   letak dua kolom yang dipakai sebelumnya menaruh sepuluh kartu di kiri
   dan tiga di kanan — kolom kanan habis setelah 700px sementara kiri
   berlanjut empat ribu piksel lagi.

   min(100%, ...) bukan hiasan: tanpa itu, pada viewport 360px jalur tetap
   dipaksa selebar --col-min dan seluruh halaman menggeser ke samping.

   align-items:start, bukan stretch — alasan yang sama dengan .stat-card di
   bawah: begitu satu kartu penyedia dibuka, kartu di sebelahnya tidak boleh
   ikut melar menjadi kotak kosong. */
.card-grid {
    display: grid;
    gap: 1rem;
    align-items: start;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--col-min, 22rem)), 1fr));
}
.card-grid[hidden] { display: none; }

/* Kartu penyedia dibagi rata dua di desktop: deretan inilah yang
   dibandingkan berdampingan (mana yang aktif, model apa, hasil uji
   terakhir), dan perbandingan menuntut lebar yang SAMA — bukan lebar sisa.
   Di bawah 992px jalurnya kembali dihitung dari ruang yang ada.

   minmax(0, 1fr), bukan 1fr: id model panjang di .mono tidak boleh
   melebarkan jalurnya sendiri dan merusak pembagian 50:50. */
.card-grid.is-providers {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}
@media (min-width: 992px) {
    .card-grid.is-providers { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Ubin kecil (pranala prompt rubrik): isinya satu baris judul + satu baris
   keterangan, jadi jalurnya boleh jauh lebih sempit. */
.card-grid.is-dense { --col-min: 15rem; }

/* Dua kartu pendamping (Kelas & Rak Buku di halaman member) yang duduk di
   kolom 8/12: jalur minimum diturunkan supaya keduanya masih berdampingan
   di desktop; satu anak saja melebar penuh, dan itu yang diinginkan. */
.card-grid.is-sisi { --col-min: 18rem; }

/* Halaman detail member: tiga area. Desktop — Ringkasan dan Tindakan
   menumpuk di kolom kiri, kolom utama (Paket, Kelas, Rak Buku, Riwayat)
   di kanan selebar dua pertiga. Ponsel — Ringkasan dulu, lalu seluruh
   kolom utama, dan Tindakan Akun PALING BAWAH: tombol tangguhkan/hapus
   bukan yang dicari orang yang membuka halaman ini dari ponsel. */
.member-detail {
    display: grid;
    gap: 1rem;
    align-items: start;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "ringkasan" "utama" "tindakan";
}
@media (min-width: 992px) {
    .member-detail {
        grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
        /* auto 1fr: baris pertama setinggi Ringkasan saja. Tanpa ini, tinggi
           kolom utama (yang merentang dua baris) dibagi rata ke kedua baris,
           dan Tindakan Akun terlempar jauh di bawah Ringkasan. */
        grid-template-rows: auto 1fr;
        grid-template-areas: "ringkasan utama" "tindakan utama";
    }
}
.member-detail > .is-ringkasan { grid-area: ringkasan; }
.member-detail > .is-utama     { grid-area: utama; min-width: 0; }
.member-detail > .is-tindakan  { grid-area: tindakan; }
/* Bilah progres kecil di kartu Kelas & Rak Buku. */
.progres-mini { height: 6px; background: var(--border); border-radius: 999px; overflow: hidden; }
.progres-mini > span { display: block; height: 100%; background: var(--primary); border-radius: 999px; }

/* Prosa berlangkah dua kolom yang MENYEIMBANGKAN DIRI (tutorial Gmail).

   Dulu dua .col-lg-6 dengan isinya dibagi tangan: A+B+peringatan di kiri,
   C+D+E di kanan. Pembagian tangan hanya benar untuk satu panjang teks —
   terukur 419px lawan 752px, jadi kolom kiri berakhir 333px lebih awal,
   dan ia akan salah lagi setiap kali satu langkah bertambah atau
   terjemahannya memanjang.

   Multicol menghitung tingginya sendiri, dan urutan bacanya MENURUN di
   kolom kiri lalu menurun di kanan — persis urutan langkah 1..14. Grid
   tidak bisa: urutannya menyamping.

   `columns: 22rem 2` berarti paling banyak dua kolom, masing-masing
   sekurangnya 22rem; di bawah itu ia jatuh sendiri jadi satu kolom, tanpa
   satu pun media query. */
.tutorial-kolom { columns: 22rem 2; column-gap: 2rem; }

/* Yang tidak boleh terbelah di tengah: satu langkah, tabel isian, dan
   kotak peringatan. <ol>-nya SENGAJA dibiarkan boleh terbelah antar
   langkah — justru itu yang membuat tinggi kolomnya bisa seimbang. */
.tutorial-kolom li,
.tutorial-kolom .table-wrap,
.tutorial-kolom .alert { break-inside: avoid; }

/* Judul bagian tidak boleh tertinggal sendirian di kaki kolom. */
.tutorial-kolom > .fw-semibold { break-after: avoid; break-inside: avoid; }

/* Judul bagian di antara dua grid. Bukan <h3>: tingkat judul halaman ini
   sudah dipakai .page-head h2 dan .card-header. */
.section-title {
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--muted);
    margin: .5rem 0 .75rem;
}

/* Baris tombol kirim di kaki formulir. */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin: 1rem 0;
}
.form-actions[hidden] { display: none; }

/* ── Kartu ────────────────────────────────────────────────────────── */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}
.card-header {
    background: transparent;
    border-bottom: 1px solid var(--border);
    padding: .875rem 1.25rem;
    font-weight: 600;
    font-size: var(--fs-body);
    display: flex; align-items: center; gap: .75rem;
}
.card-header .spacer { flex: 1; }
.card-body { padding: 1.25rem; }

/*
 | Kartu satu baris untuk tombol tindakan — badan lebih pipih dari kartu
 | biasa. Tidak menetapkan display di sini: isinya memakai kelas utilitas
 | d-flex, dan kartu ini tidak pernah disembunyikan lewat atribut hidden.
 */
.toolbar-card .card-body { padding: .625rem 1.25rem; }

/*
 | Setinggi isinya — bukan height:100%. Dulu direntangkan setinggi kartu
 | sebaris yang tertinggi; begitu satu kartu memuat tombol, tiga kartu
 | angka di sampingnya menjadi kotak kosong.
 */
.stat-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.125rem 1.25rem;
    box-shadow: var(--shadow-sm);
}
.stat-card .stat-label {
    font-size: var(--fs-xs); color: var(--muted);
    text-transform: uppercase; letter-spacing: .04em; font-weight: 600;
    display: flex; align-items: center; gap: .375rem;
}
.stat-card .stat-value { font-size: 1.875rem; font-weight: 700; line-height: 1.15; margin-top: .375rem; }
.stat-card .stat-note { font-size: var(--fs-xs); color: var(--muted); margin-top: .125rem; }
.stat-card.is-accent { border-left: 3px solid var(--accent); }
.stat-card.is-warning { border-left: 3px solid var(--warning); }
.stat-card.is-danger  { border-left: 3px solid var(--danger); }

/* ── Detail hasil: kartu band SEGAYA halaman hasil peserta ───────────
   Angka yang dilihat admin dan peserta harus tampak sama: kartu bagian
   berbingkai penuh berwarna bagiannya (ielts_warna_skill), lalu hero
   Overall Band. Ukuran & jarak disalin dari app.css peserta. */
.band-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
    gap: .875rem;
}
.band-card {
    background: var(--surface);
    border: 2px solid var(--muted-light);
    border-radius: var(--radius);
    padding: 1rem;
}
.band-card .skill { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 600; }
.band-card .value { font-size: 1.875rem; font-weight: 700; line-height: 1.1; margin-top: .25rem; font-variant-numeric: tabular-nums; }
.band-card .sub   { font-size: var(--fs-xs); color: var(--muted); }
.band-hero {
    background: linear-gradient(180deg, var(--surface) 0%, var(--surface-alt) 100%);
    border: 2px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 1.75rem;
    text-align: center;
    box-shadow: var(--shadow);
}
.band-grid + .band-hero { margin-top: 1rem; }
.band-overall { font-size: 4rem; font-weight: 800; line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.band-caption { font-size: var(--fs-sm); color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }
.band-label   { font-size: var(--fs-h3); font-weight: 600; margin-top: .375rem; }
.band-hero .meta-row { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; justify-content: center; margin-top: .875rem; }

/* Di bawah hero: kolom utama (jawaban, akses ulasan, bukti mengetik) dan
   kolom sisi (data peserta, keterangan, tindakan). Ponsel menumpuk. */
.hasil-detail {
    display: grid;
    gap: 1rem;
    align-items: start;
    grid-template-columns: minmax(0, 1fr);
    margin-top: 1rem;
}
@media (min-width: 992px) {
    .hasil-detail { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
}
.hasil-detail > div { display: grid; gap: 1rem; min-width: 0; }
.hasil-detail .card { margin-bottom: 0; }
/* Baris label–nilai di kartu sisi: label kelabu di kiri, nilai di kanan. */
.hasil-detail .dl-sisi { display: grid; grid-template-columns: minmax(7rem, 38%) minmax(0, 1fr); gap: .375rem .75rem; margin: 0; font-size: var(--fs-sm); }
.hasil-detail .dl-sisi dt { color: var(--muted); font-weight: 500; }
.hasil-detail .dl-sisi dd { margin: 0; overflow-wrap: anywhere; }

/* ── Layar pengaturan: kartu penyedia ─────────────────────────────────
   Satu kartu per penyedia, dan jumlahnya tidak pernah sedikit: 6 di layar
   AI, 11 di layar ChatAI. Dirender penuh semuanya, layarnya menjadi
   gulungan sepanjang empat ribu piksel yang sebagian besarnya penyedia yang
   bahkan tidak dinyalakan.

   Karena itu kartunya dua lapis: ringkasan yang selalu tampak (nama,
   keadaan, model, hasil uji terakhir) dan rinciannya di dalam <details>
   yang terbuka sendiri hanya untuk penyedia yang aktif. Polanya sudah ada
   di layar ini — pagar topik ChatAI. */
.kartu-penyedia .card-header { padding: .75rem 1rem; }
.kartu-penyedia .card-body   { padding: .875rem 1rem; }

/* Baris meta: model dan hasil uji terakhir. Sengaja DI LUAR <details>
   supaya "terhubung / gagal" terbaca tanpa membuka apa pun, dan supaya
   hasil penekanan tombol Uji (yang ada di dalam) muncul di tempat yang
   tetap terlihat setelah rinciannya ditutup lagi. */
.kartu-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .25rem .625rem;
    min-width: 0;
    margin-top: .375rem;
    font-size: var(--fs-xs);
    color: var(--muted);
}
.kartu-meta[hidden] { display: none; }
.kartu-meta .mono { font-size: var(--fs-2xs); }

/* Dulu <span class="form-hint hasil-uji"> — dan .form-hint membawa
   margin-top:.25rem yang membuatnya melorot terhadap tombol di sebelahnya.
   Kelasnya tetap .hasil-uji: JS di kedua view memilihnya lewat
   .hasil-uji[data-penyedia]. */
.hasil-uji { font-size: var(--fs-xs); color: var(--muted); }

.kartu-rinci { border-top: 1px solid var(--border); margin-top: .625rem; }
.kartu-rinci-toggle {
    display: flex;
    align-items: center;
    gap: .375rem;
    padding: .5rem 0 .125rem;
    cursor: pointer;
    list-style: none;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--primary);
}
.kartu-rinci-toggle[hidden] { display: none; }
.kartu-rinci-toggle::-webkit-details-marker { display: none; }
.kartu-rinci-toggle::after {
    content: '';
    width: .4375rem; height: .4375rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform .15s;
}
.kartu-rinci[open] > .kartu-rinci-toggle::after { transform: rotate(-135deg) translate(-2px, -2px); }
.kartu-rinci-isi { padding-top: .5rem; }

/* Tiga angka ringkas berdampingan. Dulu tiga <div class="col-4"> tanpa
   prefix breakpoint: tetap sepertiga lebar di layar 360px. */
.mini-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 6rem), 1fr));
    gap: .5rem;
    text-align: center;
    margin-bottom: 1rem;
}
.mini-stats[hidden] { display: none; }
.mini-stats .num { font-size: var(--fs-h2); font-weight: 700; line-height: 1.2; }
.mini-stats .lbl { font-size: var(--fs-xs); color: var(--muted); }

/* Ubin pranala prompt rubrik. Dulu dirakit dari border+background inline di
   dalam view — duplikasi .card dengan tangan. */
.prompt-link {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .625rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: inherit;
}
.prompt-link[hidden] { display: none; }
.prompt-link:hover { background: var(--surface-alt); border-color: var(--border-strong); color: inherit; text-decoration: none; }
.prompt-link .isi { flex: 1; min-width: 0; }

/* Kotak hasil diagnosa. TIDAK menetapkan display sama sekali — <div> sudah
   block — supaya atribut hidden bekerja apa adanya. Penjaganya tetap
   ditulis: ia gratis, dan ia menyatakan niatnya. */
.diagnose-box {
    margin-top: 1rem;
    padding: 1rem;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    font-size: var(--fs-sm);
}
.diagnose-box[hidden] { display: none; }

/* ── Kemajuan unggah berkas ────────────────────────────────────────────
   Pembungkusnya berkelas sendiri, bukan .progress Bootstrap yang
   disembunyikan langsung. Bootstrap datang dari CDN: bila CDN-nya tak
   terjangkau, kelas yang menetapkan display ikut hilang bersama penjaga
   [hidden]-nya, dan bilah yang seharusnya tersembunyi justru terlihat
   sejak halaman dibuka. Penjaga di bawah milik kita sendiri. */
.unggah-maju {
    display: grid;
    gap: .375rem;
    margin: .5rem 0;
}
.unggah-maju[hidden] { display: none; }

.unggah-maju .progress { height: .625rem; }

.unggah-maju-teks {
    font-size: var(--fs-2xs);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* Animasi bergaris Bootstrap dimatikan bagi yang memintanya. Angkanya
   tetap bergerak — yang berhenti hanyalah geraknya, bukan kabarnya. */
@media (prefers-reduced-motion: reduce) {
    .unggah-maju .progress-bar,
    .unggah-maju .progress-bar-animated {
        transition: none;
        animation: none;
    }
}

/* Pagar topik ChatAI: teks panjang apa adanya di dalam <details>. */
.fence-box {
    margin: .375rem 0 0;
    padding: .5rem .625rem;
    background: var(--surface-alt);
    border-radius: var(--radius-sm);
    white-space: pre-wrap;
    font-size: var(--fs-2xs);
    line-height: 1.6;
}

/* ── Tabel ────────────────────────────────────────────────────────── */

/*
 | Kepala tabel menempel saat digulir. Pada daftar hasil yang panjang,
 | kehilangan nama kolom setelah baris kesepuluh berarti harus menggulir
 | balik ke atas hanya untuk tahu angka mana yang sedang dibaca.
 */
/*
 | overflow di sini memotong apa pun yang keluar dari kotaknya — termasuk
 | menu dropdown yang dibuka dari dalam baris tabel. Menu semacam itu wajib
 | memakai data-bs-strategy="fixed"; ada pemeriksaan di swa-uji yang
 | menegakkannya.
 */
.table-wrap { overflow: auto; max-height: 70vh; }

/* Tabel pendek (ringkasan pemakaian: paling banyak sebelas baris) tidak
   butuh kepala menempel maupun kotak bergulir sendiri. max-height 70vh di
   atas justru menambah satu bilah gulir di dalam kartu yang sudah muat
   seluruhnya. */
.table-wrap--short { max-height: none; }

.table { margin: 0; font-size: var(--fs-ui); }
.table > thead > tr > th {
    position: sticky; top: 0; z-index: 2;
    background: var(--surface-alt);
    border-bottom: 1px solid var(--border);
    box-shadow: inset 0 -1px 0 var(--border);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--muted);
    font-weight: 600;
    padding: .6875rem .875rem;
    white-space: nowrap;
}
.table > tbody > tr > td { padding: .8125rem .875rem; vertical-align: middle; border-color: var(--border); }
.table > tbody > tr { transition: background .12s; }
.table > tbody > tr:hover { background: var(--primary-soft); }
.table > tbody > tr:last-child > td { border-bottom: none; }
.table .num { font-variant-numeric: tabular-nums; }

/* ── Lencana band ─────────────────────────────────────────────────── */

.band-badge {
    display: inline-flex; align-items: baseline; gap: .25rem;
    padding: .1875rem .5rem;
    border-radius: var(--radius-sm);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: #fff;
    font-size: var(--fs-sm);
}
.band-badge small { font-weight: 500; opacity: .85; font-size: var(--fs-2xs); }

.band-circle {
    width: 84px; height: 84px;
    border-radius: 50%;
    display: grid; place-content: center; text-align: center;
    color: #fff;
    box-shadow: var(--shadow);
}
.band-circle .num { font-size: var(--fs-h1); font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.band-circle .lbl { font-size: var(--fs-2xs); opacity: .9; text-transform: uppercase; letter-spacing: .04em; }

/* ── Bar kriteria ─────────────────────────────────────────────────── */

.criterion { margin-bottom: .875rem; }
.criterion:last-child { margin-bottom: 0; }
.criterion-head { display: flex; align-items: baseline; gap: .5rem; margin-bottom: .25rem; }
.criterion-head .name { font-weight: 600; font-size: var(--fs-sm); }
.criterion-head .score { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }
.criterion-bar { height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
.criterion-bar span { display: block; height: 100%; border-radius: 3px; transition: width .3s; }
.criterion-desc { font-size: var(--fs-xs); color: var(--muted); margin-top: .25rem; }

/* ── Formulir ─────────────────────────────────────────────────────── */

.form-label { font-weight: 600; font-size: var(--fs-sm); margin-bottom: .3125rem; color: var(--ink-soft); }
.form-control, .form-select {
    border-color: var(--border-strong);
    border-radius: var(--radius-sm);
    font-size: var(--fs-ui);
    padding: .5rem .75rem;
    transition: border-color .15s, box-shadow .15s;
}
.form-control::placeholder { color: var(--muted-light); }
.form-control:hover, .form-select:hover { border-color: var(--muted-light); }
.form-control:focus, .form-select:focus {
    border-color: var(--accent-focus);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.form-text { font-size: var(--fs-xs); color: var(--muted); }
.form-hint { font-size: var(--fs-xs); color: var(--muted); margin-top: .25rem; }

/* ── Lebar kolom isian ────────────────────────────────────────────────
   Lebar kolom mengikuti ISI yang diharapkan, bukan lebar kartunya. Kolom
   angka 1–10 selebar 400px memberi tahu mata bahwa ada banyak yang boleh
   diketik di situ — padahal tidak.

   Dinyatakan sebagai max-width, bukan width, jadi di layar sempit kolomnya
   tetap menciut sendiri. */
.field-xs { max-width:  7rem; }   /* angka <= 3 digit */
.field-sm { max-width: 11rem; }   /* angka besar, dropdown pendek */
.field-md { max-width: 20rem; }   /* nama host, daftar pendek */
.field-lg { max-width: 34rem; }   /* kunci API, URL, kalimat */

/* ── Ukuran huruf ─────────────────────────────────────────────────────
   Token --fs-* sudah ada sejak awal, tetapi dua layar pengaturan menuliskan
   angkanya langsung ke atribut style belasan kali — .8125rem, .75rem,
   .7rem — sehingga skalanya tidak bisa diubah dari satu tempat. Tiga kelas
   ini menggantikan semuanya. */
.fs-sm  { font-size: var(--fs-sm); }
.fs-xs  { font-size: var(--fs-xs); }
.fs-2xs { font-size: var(--fs-2xs); }

/* Pemutar archive.org di halaman PEMERIKSAAN admin — tidak pernah di
   halaman pengerjaan tes. Tingginya tetap karena pemutar mereka memakai
   tata letak sendiri dan iframe tidak bisa menyesuaikan tinggi isinya. */
.audio-embed {
    display: block;
    width: 100%;
    max-width: 520px;
    height: 160px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}

.btn {
    border-radius: var(--radius-sm);
    font-weight: 550;
    font-size: var(--fs-ui);
    padding: .5rem .875rem;
    transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
}
/* Satu cincin fokus untuk seluruh aplikasi, termasuk navigasi keyboard. */
.btn:focus-visible, .nav-link:focus-visible, .form-check-input:focus {
    box-shadow: 0 0 0 3px var(--focus-ring);
    outline: none;
}
.btn-primary { background: var(--primary); border-color: var(--primary); }
.btn-primary:hover, .btn-primary:focus { background: var(--primary-dark); border-color: var(--primary-dark); }
.btn-outline-secondary { border-color: var(--border-strong); color: var(--ink-soft); }
.btn-outline-secondary:hover { background: var(--surface-alt); border-color: var(--muted-light); color: var(--ink); }
.btn-sm { font-size: var(--fs-sm); padding: .3125rem .625rem; }

/* ── Lencana status ───────────────────────────────────────────────── */

.pill {
    display: inline-flex; align-items: center; gap: .3125rem;
    padding: .1875rem .5rem;
    border-radius: 999px;
    font-size: var(--fs-2xs); font-weight: 650;
    text-transform: uppercase; letter-spacing: .03em;
}
.pill-success { background: var(--success-soft); color: var(--success); }
.pill-warning { background: var(--warning-soft); color: var(--warning); }
.pill-danger  { background: var(--danger-soft);  color: var(--danger); }
.pill-info    { background: var(--primary-soft); color: var(--primary); }
.pill-muted   { background: var(--surface-alt);  color: var(--muted); }

/* ── Halaman masuk ────────────────────────────────────────────────── */

/*
 | Latar pekat, merek di atas kartu, kartu putih yang mengambang.
 |
 | Kartu terang di atas latar gelap membuat mata langsung jatuh ke satu
 | tempat — tidak ada lagi yang bisa dilihat di halaman ini. Gradien lembut
 | yang dipakai sebelumnya menyebarkan perhatian ke seluruh layar.
 */
.login-page {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 1.5rem;
    background: var(--ink);
}

.login-shell {
    width: 100%;
    max-width: 420px;
    animation: loginRise .45s cubic-bezier(.16, 1, .3, 1) both;
}

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

.login-brand { text-align: center; margin-bottom: 1.75rem; }
.login-brand .brand-mark {
    width: 52px; height: 52px; border-radius: 14px;
    background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
    display: grid; place-items: center;
    color: #fff; font-weight: 700; font-size: var(--fs-h3);
    margin: 0 auto .875rem;
    box-shadow: 0 8px 20px -6px rgba(37, 99, 235, .55);
}
.login-brand .name {
    color: #fff; font-size: var(--fs-h1); font-weight: 650;
    letter-spacing: -.01em; line-height: 1.25;
}
.login-brand .tagline { color: #94a3b8; font-size: var(--fs-body); margin-top: .1875rem; }

.login-card {
    background: var(--surface);
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, .5);
    padding: 2.25rem;
}
.login-card h1 {
    font-size: var(--fs-h3); font-weight: 700; text-align: center;
    margin: 0 0 1.75rem; color: var(--ink);
}

.login-page .form-label { font-weight: 600; }
.login-page .form-control { padding: .75rem .9375rem; }
.login-page .btn-primary { padding: .75rem 1rem; font-weight: 600; }

.login-foot {
    text-align: center; margin-top: 1.5rem;
    font-size: var(--fs-xs); color: #94a3b8; line-height: 1.6;
}

/*
 | Peringatan salah setel di halaman masuk.
 |
 | Justru saat baseURL salah, tidak ada seorang pun yang bisa masuk ke
 | dashboard untuk membacanya di sana. Di sinilah satu-satunya tempat yang
 | pasti terlihat.
 */
.cfg-alert {
    background: #fff7ed;
    border: 1px solid #fed7aa;
    border-radius: var(--radius-sm);
    padding: .875rem 1rem;
    margin-bottom: 1.25rem;
    font-size: var(--fs-sm);
    color: #7c2d12;
}
.cfg-alert + .cfg-alert { margin-top: -.5rem; }
.cfg-alert strong { display: block; margin-bottom: .25rem; }
.cfg-alert code {
    display: block;
    background: var(--ink);
    color: #e2e8f0;
    border-radius: 6px;
    padding: .5rem .625rem;
    margin-top: .5rem;
    font-size: var(--fs-xs);
    overflow-x: auto;
    white-space: pre;
}

/* ── Tabel dan diagram alur lembar soal ───────────────────────────────
   Disalin APA ADANYA dari test.ielts.web.id/assets/css/player.css. Kalau
   salah satunya berubah, ubah keduanya.

   Kenapa ia perlu ada di dasbor sama sekali: admin/question/preview.php
   dan admin/question/form.php merender instruction_html MENTAH, dan di
   situlah tabel serta diagram alur sebuah part digambar. Tanpa salinan
   ini, keduanya tampil telanjang — tanpa garis, tanpa panah — tepat di
   layar tempat admin memeriksa apakah tata letaknya setia pada lembar
   soal aslinya.

   Bootstrap tidak menyentuh kelas ini: .paper-table berdiri sendiri, dan
   sengaja tidak memakai .table bawaan Bootstrap supaya tampilannya sama
   persis dengan yang dilihat peserta. */

.paper-table {
    border-collapse: collapse;
    width: 100%;
    font-size: var(--fs-sm);
}
.paper-table th,
.paper-table td {
    border: 1px solid var(--border-strong);
    padding: .5rem .625rem;
    text-align: left;
    vertical-align: top;
}
.paper-table thead th,
.paper-table tbody th { background: var(--surface-alt); font-weight: 700; }
.paper-table tbody th { white-space: nowrap; }

.paper-scroll { overflow-x: auto; margin: .625rem 0; }
.paper-scroll .paper-table { min-width: 22rem; }

/* Tabel empat kolom berisi kalimat penuh; 22rem di atas disetel untuk tiga
   kolom. Selektornya dobel-kelas supaya kekhususannya menang tanpa
   bergantung pada urutan penulisan. */
.paper-scroll .paper-table.paper-table--wide { min-width: 34rem; }

.paper-flow { list-style: none; margin: .625rem 0 0; padding: 0; }
.paper-flow li {
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: .625rem .75rem;
    font-size: var(--fs-sm);
}

/* Panahnya digambar CSS, bukan diketik ke dalam isinya: karakter panah
   yang ditulis manual dibacakan pembaca layar sebagai kata dan ikut
   tersalin ketika teks soal disalin. */
.paper-flow li + li { margin-top: 2rem; position: relative; }
.paper-flow li + li::before {
    content: "";
    position: absolute; left: 50%; top: -1.625rem;
    width: 2px; height: 1rem;
    transform: translateX(-50%);
    background: var(--border-strong);
}
.paper-flow li + li::after {
    content: "";
    position: absolute; left: 50%; top: -.75rem;
    width: 0; height: 0;
    transform: translateX(-50%);
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 8px solid var(--border-strong);
}

/* ── Lain-lain ────────────────────────────────────────────────────── */

.empty-state { text-align: center; padding: 3rem 1.25rem; color: var(--muted); }
.empty-state i { font-size: 2.25rem; color: var(--border-strong); margin-bottom: .75rem; display: block; }
.empty-state p { margin: 0 0 1rem; }

.alert { border: none; border-radius: var(--radius-sm); font-size: var(--fs-ui); padding: .75rem 1rem; }
.alert-success { background: var(--success-soft); color: var(--success); }
.alert-danger  { background: var(--danger-soft);  color: var(--danger); }
.alert-warning { background: var(--warning-soft); color: var(--warning); }
.alert-info    { background: var(--primary-soft); color: var(--primary); }

.mono { font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; font-size: var(--fs-sm); }

/*
 | Isi peringatan harus boleh menyusut.
 |
 | .alert memakai d-flex, jadi isinya menjadi flex item — dan flex item
 | bawaannya min-width:auto, artinya ia MENOLAK menyusut di bawah lebar
 | intrinsik isinya. Dengan satu baris perintah cron sepanjang ratusan
 | karakter di dalamnya, yang melar bukan kotak kodenya melainkan seluruh
 | halaman: overflow-x milik .cfg-code tidak pernah sempat bekerja.
 |
 | Dipasang pada .alert, bukan pada satu peringatan tertentu, karena pola
 | d-flex + isi lebar dipakai beberapa peringatan lain.
 */
.alert.d-flex > div { min-width: 0; }

/* Ikonnya justru tidak boleh ikut menyusut atau ia menjadi gepeng. */
.alert.d-flex > i { flex-shrink: 0; }

/* Baris config.php di dalam peringatan dashboard — siap disalin. */
.cfg-code {
    background: var(--ink);
    color: #e2e8f0;
    border-radius: var(--radius-sm);
    padding: .625rem .75rem;
    margin: .625rem 0 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--fs-xs);
    line-height: 1.7;
    overflow-x: auto;
}

.copy-url {
    display: inline-flex; align-items: center; gap: .375rem;
    background: var(--surface-alt); border: 1px solid var(--border);
    border-radius: var(--radius-sm); padding: .25rem .5rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--fs-xs); max-width: 100%;
}
.copy-url span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.copy-url button { border: none; background: none; color: var(--muted); padding: 0 .125rem; cursor: pointer; }
.copy-url button:hover { color: var(--primary); }

.sidebar-backdrop {
    display: none;
    position: fixed; inset: 0;
    background: rgba(15, 23, 42, .5);
    z-index: 1035;
}

/* ── Responsif ────────────────────────────────────────────────────── */

@media (max-width: 991.98px) {
    .sidebar {
        position: fixed; left: 0; top: 0; bottom: 0;
        transform: translateX(-100%);
        transition: transform .22s ease;
        height: 100vh;
    }
    .sidebar.open { transform: translateX(0); box-shadow: var(--shadow-lg); }
    .sidebar-backdrop.show { display: block; }
    .sidebar-toggle { display: block; }
    .admin-content { padding: 1.125rem; }

    /* Pita tab pengaturan menggulir, tidak menumpuk jadi lima baris. */
    .settings-nav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
    .settings-nav::-webkit-scrollbar { display: none; }

    /*
     | Tabel bertumpuk sejak TABLET, bukan hanya ponsel — untuk tabel yang
     | ditandai .stack-md.
     |
     | Ringkasan pemakaian AI punya tujuh kolom dan ringkasan ChatAI empat.
     | Di rentang 576–1400px keduanya terjepit di kolom selebar 287–400px:
     | angka terpotong, kepala kolom terbaca menurun satu huruf per baris.
     | Ambang 575.98px tidak pernah tercapai karena yang sempit itu WADAHNYA,
     | bukan layarnya.
     |
     | Opt-in lewat .stack-md, bukan dengan memindahkan ambang .stack-table:
     | kelas itu dipakai 34 view admin, dan memindahkan ambangnya akan
     | mengubah semuanya sekaligus.
     |
     | Aturannya SENGAJA kembar dengan blok .stack-table di media query
     | 575.98px di bawah. CSS tidak bisa memberi kelas kepada elemen, jadi
     | satu-satunya cara memakai dua ambang untuk dua kelompok tabel adalah
     | menulis kelompok aturannya dua kali. Kalau salah satunya diubah, ubah
     | keduanya — ada pemeriksaan di swa-uji yang menuntut kedua blok itu ada.
     */
    .stack-table.stack-md > thead { display: none; }
    .stack-table.stack-md > tbody > tr {
        display: block;
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        margin-bottom: .625rem;
        padding: .375rem .75rem;
        background: var(--surface);
    }
    .stack-table.stack-md > tbody > tr > td {
        display: flex; justify-content: space-between; gap: .75rem;
        flex-wrap: wrap;
        align-items: baseline;
        text-align: right;
        border: none; padding: .4375rem 0;
        border-bottom: 1px dashed var(--border);
    }
    .stack-table.stack-md > tbody > tr > td:last-child { border-bottom: none; }
    .stack-table.stack-md > tbody > tr > td::before {
        content: attr(data-label);
        font-weight: 600; font-size: var(--fs-xs); color: var(--muted);
        flex-shrink: 0;
        text-align: left;
    }
    .stack-table.stack-md > tbody > tr > td[data-label=""] { justify-content: flex-start; text-align: left; }
    .stack-table.stack-md td[data-label=""]::before { content: none; }
}

/*
 | Rentang 992–1200px: sidebar 256px KEMBALI mengambil tempatnya sementara
 | viewport-nya masih sempit. Isi yang tersisa 688–896px, dan itu satu-
 | satunya titik di seluruh aplikasi di mana lebar isi turun justru ketika
 | layarnya membesar. Padding dikurangi supaya kolom tidak kehilangan 1rem
 | lagi di tempat yang paling tidak mampu menanggungnya.
 */
@media (min-width: 992px) and (max-width: 1199.98px) {
    .admin-content { padding: 1.25rem 1rem; }
    .card-body     { padding: 1rem; }
}

/*
 | Rentang 576–991.98px: tablet. Selama ini tidak ditangani sama sekali —
 | berkas ini hanya punya ambang 991.98 (sidebar) dan 575.98 (ponsel). Di
 | sini sidebar sudah off-canvas sehingga isi memakai lebar penuh; yang
 | perlu diatur tinggal kepadatannya.
 */
@media (min-width: 576px) and (max-width: 991.98px) {
    .page-head { margin-bottom: 1rem; }
    .card-grid { gap: .875rem; }
    .field-lg  { max-width: 100%; }
}

@media (max-width: 575.98px) {
    .topbar { padding: 0 1rem; }
    .topbar h1 { font-size: var(--fs-body); }
    .user-chip .meta { display: none; }
    .lang-switch { display: none; }
    .admin-content { padding: .875rem; }
    .stat-card .stat-value { font-size: var(--fs-h1); }
    .page-head h2 { font-size: var(--fs-h3); }
    .card-body { padding: 1rem; }

    /*
     | Tabel bertumpuk: tiap baris menjadi kartu, label kolom diambil dari
     | atribut data-label. Dipakai dengan menambahkan kelas .stack-table
     | pada tabel dan data-label pada setiap <td>.
     */
    .stack-table > thead { display: none; }
    .stack-table > tbody > tr {
        display: block;
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        margin-bottom: .625rem;
        padding: .375rem .75rem;
        background: var(--surface);
    }
    .stack-table > tbody > tr > td {
        display: flex; justify-content: space-between; gap: .75rem;
        /*
         | Isi yang panjang pindah ke barisnya sendiri alih-alih memaksa
         | nama peserta terpecah menjadi dua baris sempit di sebelah kiri.
         */
        flex-wrap: wrap;
        align-items: baseline;
        text-align: right;
        border: none; padding: .4375rem 0;
        border-bottom: 1px dashed var(--border);
    }
    .stack-table > tbody > tr > td:last-child { border-bottom: none; }
    .stack-table > tbody > tr > td::before {
        content: attr(data-label);
        font-weight: 600; font-size: var(--fs-xs); color: var(--muted);
        flex-shrink: 0;
        text-align: left;
    }
    /* Sel tanpa label — kolom tombol — memakai seluruh lebar. */
    .stack-table > tbody > tr > td[data-label=""] { justify-content: flex-start; text-align: left; }

    /* Kepala yang menempel tidak berlaku saat tabelnya sudah menumpuk. */
    .table-wrap { max-height: none; }
    .stack-table td[data-label=""]::before { content: none; }

    /*
     | Lantai jangkauan sentuh.
     |
     | Dinyatakan sebagai min-height pada kelas tombolnya, bukan sebagai
     | padding pada tiap tombol satu per satu: tombol yang dibuat nanti ikut
     | terkena dengan sendirinya, tanpa harus diingat.
     |
     | 32px, bukan 44px — cukup untuk jari sambil menjaga baris tabel tetap
     | padat di layar sempit. Hanya berlaku di ponsel; desktop dan tablet
     | tidak tersentuh sama sekali.
     |
     | .btn-close dibiarkan: itu komponen Bootstrap, dan tombol tutup alert
     | bukan kendali yang orang cari-cari.
     */
    .btn,
    .btn-sm,
    .btn-group > .btn {
        min-height: 32px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /*
     | Tombol pintas yang sengaja dipadatkan lewat py-0 tetap perlu tinggi
     | minimumnya; padding nol tanpa lantai ini menghasilkan sasaran 19px.
     */
    .btn.py-0 { padding-top: .25rem !important; padding-bottom: .25rem !important; }
}

@media print {
    .sidebar, .topbar, .page-head .actions, .no-print { display: none !important; }
    .admin-content { padding: 0; }
    .card { border: none; box-shadow: none; }
}

/* ── Notifikasi toast (swa-host, pengganti SweetAlert2) ───────────────
   Kelas sama dengan aplikasi peserta; hanya posisinya khas dashboard:
   kanan-atas di bawah topbar, tempat toast SweetAlert2 dulu muncul. */

.toast-area {
    position: fixed;
    top: calc(var(--topbar-h) + .75rem);
    right: 1rem;
    z-index: 1320;    /* di atas modal Bootstrap (1055) dan dialog sendiri */
    display: flex;
    flex-direction: column;
    gap: .5rem;
    align-items: flex-end;
    pointer-events: none;
    width: min(92vw, 380px);
}
.toast {
    background: var(--ink);
    color: #fff;
    border-radius: var(--radius-sm);
    padding: .625rem 1rem;
    font-size: var(--fs-ui);
    box-shadow: var(--shadow-lg);
    animation: rise .18s ease-out;
}
.toast.is-error   { background: var(--danger); }
.toast.is-success { background: var(--success); }
.toast.is-warning { background: var(--warning); }
.toast.is-info    { background: var(--info); }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }

/* ── Dialog konfirmasi seragam ────────────────────────────────────────
   Markah dan kelas yang sama dipakai aplikasi peserta; pengganti
   window.confirm() bawaan peramban. */

.dlg-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1300;    /* di atas modal Bootstrap: konfirmasi bisa dipicu dari dalamnya */
    background: rgba(15, 23, 42, .55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    animation: dlgMuncul .15s ease-out;
}
.dlg {
    background: var(--surface);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    width: min(92vw, 26rem);
    padding: 1.25rem 1.25rem 1rem;
    animation: dlgNaik .18s ease-out;
}
.dlg-message {
    white-space: pre-line;
    font-size: var(--fs-body);
    line-height: 1.55;
    color: var(--ink);
}
.dlg-actions {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    margin-top: 1.125rem;
}
.dlg-btn {
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--ink-soft);
    border-radius: var(--radius-sm);
    padding: .5rem .875rem;
    font-size: var(--fs-ui);
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}
.dlg-btn:hover { background: var(--surface-alt); }
.dlg-btn.dlg-ok { background: var(--primary); border-color: var(--primary); color: #fff; }
.dlg-btn.dlg-ok:hover { background: var(--primary-dark); }
.dlg.is-danger .dlg-btn.dlg-ok { background: var(--danger); border-color: var(--danger); }
@keyframes dlgMuncul { from { opacity: 0; } }
@keyframes dlgNaik { from { opacity: 0; transform: translateY(10px) scale(.98); } }

/* ── Titik status cron di dashboard ──────────────────────────────── */

.cron-dot {
    width: .625rem;
    height: .625rem;
    border-radius: 50%;
    display: inline-block;
    flex: none;
}
.cron-dot.is-active { background: var(--success); }
.cron-dot.is-stale  { background: var(--danger); }
.cron-dot.is-never  { background: var(--muted-light); }

/* ── Feedback dwibahasa (ID/EN) + daftar "How to Improve" ────────────── */

.lang-toggle { display: inline-flex; align-items: center; }
.lang-seg {
    display: inline-flex;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 2px;
}
.lang-btn {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--muted);
    font-weight: 700;
    font-size: var(--fs-xs);
    letter-spacing: .02em;
    padding: .25rem .75rem;
    min-height: 28px;
    border-radius: 999px;
    cursor: pointer;
    transition: background .15s, color .15s;
}
.lang-btn.is-active { background: var(--primary); color: #fff; }
.lang-pane[hidden] { display: none; }

.reko-list {
    margin: .25rem 0 0;
    padding: 0;
    list-style: none;
    counter-reset: reko;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.reko-list li {
    counter-increment: reko;
    position: relative;
    padding: .5rem .75rem .5rem 2.5rem;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--fs-ui);
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.reko-list li::before {
    content: counter(reko);
    position: absolute;
    left: .5rem;
    top: .5rem;
    width: 1.5rem;
    height: 1.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--primary);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    border-radius: 999px;
}

/* Tombol WhatsApp (share) hijau khas. */
.btn-wa, .btn-wa:hover { background: #25d366; border-color: #25d366; color: #0b3d1e; }

/* ── Peta pengucapan ──────────────────────────────────────────────────
   Kata diwarnai menurut skor fonem TERUKUR, bukan tebakan model bahasa
   atas transkrip. Paletnya sama dengan salinan di aplikasi peserta,
   supaya penilai dan peserta membaca warna yang sama persis. */
.pron-map { margin-top: .875rem; }

.pron-words {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
    margin: .5rem 0;
    line-height: 2.1;
}

.pron-w {
    padding: .1rem .4rem;
    border-radius: 8px;
    border-bottom: 3px solid transparent;
    background: var(--surface-alt, #f8fafc);
    cursor: help;
}
.pron-w.is-good { border-bottom-color: var(--success); }
.pron-w.is-avg  { border-bottom-color: var(--warning); background: var(--warning-soft); }
/* Kata terburuk diberi latar, bukan sekadar garis: ia yang harus
   ditemukan mata lebih dulu di antara puluhan kata lain. */
.pron-w.is-bad  { border-bottom-color: var(--danger); background: var(--danger-soft); font-weight: 600; }

.pron-ph {
    display: block;
    font-size: var(--fs-2xs);
    letter-spacing: .04em;
    color: var(--muted);
    margin-top: .1rem;
}
.pron-ph b { font-weight: 600; margin-right: .25rem; }
.pron-ph b.is-avg { color: var(--warning); }
.pron-ph b.is-bad { color: var(--danger); }

.pron-worst {
    margin: .375rem 0 0;
    padding-left: 1.125rem;
    font-size: var(--fs-ui);
}

/* ── Rak Buku: editor bab ─────────────────────────────────────────────
   Bilah tombol sisip membungkus ke baris berikutnya di layar sempit; di
   bawah 1200px panel Tulis/Pratinjau menjadi tab (ebook-admin.js mengatur
   .is-aktif), di atasnya keduanya berdampingan. */
.eb-sisip { display: flex; flex-wrap: wrap; gap: .25rem; }
.eb-sisip .btn { white-space: nowrap; }
.eb-tab-editor { display: flex; gap: .5rem; }
.eb-bingkai-pratinjau { overflow-x: auto; }
.eb-bingkai-pratinjau iframe { display: block; margin: 0 auto; transition: width .2s ease; }
@media (max-width: 1199.98px) {
    .eb-panel:not(.is-aktif) { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .eb-bingkai-pratinjau iframe { transition: none; }
}
