/* =====================================================================
   SALES ACADEMY — CORE ENGINE UI
   Premium corporate editorial design system.
   Palette: #006885 (primary) · #00AAAD (secondary) · #F15A22 (accent) · #F99D1F (warning)
   Icons: Font Awesome 6 Solid (fa-solid). No custom icon-font dependency.
   ===================================================================== */

:root {
  /* Corporate Color Identity */
  --c-primary: #006885;
  --c-primary-dark: #004d63;
  --c-primary-light: #0089ab;
  --c-secondary: #00AAAD;
  --c-secondary-light: #e3f3f3;
  --c-accent: #F15A22;
  --c-accent-dark: #d2481a;
  --c-warning: #F99D1F;

  --c-bg: #EEF6F8;
  --c-bg-soft: #F5FAFB;
  --c-surface: #FFFFFF;
  --c-text: #0E2027;
  --c-text-muted: #5C7378;
  --c-border: #E1EBED;
  --c-success: #00AAAD;
  --c-danger: #F15A22;

  --gradient-brand: linear-gradient(135deg, var(--c-primary) 0%, var(--c-secondary) 100%);
  --gradient-accent: linear-gradient(135deg, var(--c-accent) 0%, var(--c-warning) 100%);
  --gradient-page: linear-gradient(160deg, #E7F5F6 0%, #F3FAFB 38%, #FCFEFE 70%, #FFFFFF 100%);

  /* UAT-DEFECT-049 (KPI Card Icon Color Contract) — the ONE canonical
     4-color corporate sequence every standard KPI/counting/calculation
     card's icon container rotates through, by card POSITION (never by
     business/semantic status). Aliases onto the SAME corporate variables
     above -- never a duplicated hex value. */
  --kpi-color-1: var(--c-primary);
  --kpi-color-2: var(--c-secondary);
  --kpi-color-3: var(--c-warning);
  --kpi-color-4: var(--c-accent);

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius-xl: 32px;
  --radius-pill: 999px;
  --shadow-sm: 0 2px 10px rgba(0, 40, 52, 0.06);
  --shadow-md: 0 10px 28px rgba(0, 40, 52, 0.10);
  --shadow-lg: 0 20px 48px rgba(0, 40, 52, 0.16);
  --transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  /* Aliases — beberapa modul (mis. induction) sebelumnya membuat token
     "bridge" sendiri (--transition-base / --transition-fast) karena nama
     ini belum ada di core. Didefinisikan resmi di sini supaya modul tidak
     perlu redefinisi token sendiri lagi. */
  --transition-base: var(--transition);
  --transition-fast: var(--transition);
  --sidebar-w: 268px;
  --font-display: 'Sora', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-body);
  background: var(--gradient-page);
  background-attachment: fixed;
  color: var(--c-text);
  -webkit-font-smoothing: antialiased;
}

a { text-decoration: none; color: inherit; }
ul { list-style: none; margin: 0; padding: 0; }
button { font-family: inherit; cursor: pointer; }

h1, h2, h3, h4 { margin: 0 0 4px; font-family: var(--font-display); font-weight: 700; letter-spacing: -0.01em; }

::selection { background: var(--c-secondary); color: #fff; }

:focus-visible {
  outline: 3px solid var(--c-secondary);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---------------------------------------------------------------------
   ICONS — Font Awesome 6 Solid. Sizing/coloring helpers only.
--------------------------------------------------------------------- */
.icon-fa { display: inline-flex; align-items: center; justify-content: center; }
.i-circle {
  width: 1.9em; height: 1.9em; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(0,104,133,0.10); color: var(--c-primary);
}

/* ---------------------------------------------------------------------
   DOODLE / EDITORIAL ACCENTS (subtle, minimal, low-opacity)
--------------------------------------------------------------------- */
.doodle-layer { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 0; }
.doodle-star {
  position: absolute; color: rgba(255,255,255,0.35); font-size: 22px;
}
.doodle-scribble { position: absolute; opacity: 0.18; }
.bg-blob {
  position: absolute; border-radius: 50%;
  filter: blur(30px);
  opacity: 0.35;
}

/* =====================================================================
   APP SHELL / LAYOUT
   ===================================================================== */
.app-shell { display: flex; min-height: 100vh; position: relative; }
.master-home-doodles { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; opacity: .13; }
.master-home-doodles svg { position: absolute; width: 150px; height: 150px; animation: floatSlow 10s ease-in-out infinite; }
.master-home-doodles .doodle-one { top: 13%; right: 7%; }
.master-home-doodles .doodle-two { bottom: 8%; left: 22%; animation-delay: -3s; }
.master-home-doodles .doodle-three { top: 48%; right: 30%; animation-delay: -6s; }
.main-area, .sidebar { position: relative; z-index: 1; }

.sidebar {
  width: 76px;
  background: var(--gradient-brand);
  color: #fff;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; bottom: 0; left: 0;
  z-index: 40;
  transition: width var(--transition), transform var(--transition);
  box-shadow: var(--shadow-lg);
  overflow: visible;
  /* SIDEBAR BEHAVIOR (brief §6): default state CLOSED — baik desktop
     maupun mobile. Body mendapat class .sidebar-open lewat JS (dan lewat
     inline script di <head>, dari sessionStorage, supaya tidak ada flash
     "kebuka dulu baru ketutup" saat reload). */
  transform: none;
}
body.sidebar-open .sidebar { width: var(--sidebar-w); }

.sidebar::before {
  content: '';
  position: absolute;
  width: 220px; height: 220px;
  border-radius: 50%;
  background: var(--gradient-accent);
  opacity: 0.18;
  right: -90px; bottom: -80px;
  filter: blur(6px);
}

.sidebar::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 85% 8%, rgba(255,255,255,0.10) 0, transparent 45%),
    radial-gradient(circle at 10% 92%, rgba(255,255,255,0.08) 0, transparent 40%);
  pointer-events: none;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 86px;
  padding: 16px 14px;
  border-bottom: 1px solid rgba(255,255,255,0.15);
  position: relative;
  z-index: 1;
}
.sidebar-burger {
  width: 46px; height: 46px; flex: 0 0 46px; padding: 0;
  display: grid; place-items: center; border: 1px solid rgba(255,255,255,.3);
  border-radius: 12px; color: #fff; background: rgba(255,255,255,.08); cursor: pointer;
  box-sizing:border-box; line-height:0; align-self:center;
}
.sidebar-burger svg,
.sidebar-user-caret {
  width: 21px;
  height: 21px;
  display: block;
  margin: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

body.sidebar-open .sidebar-brand {
  justify-content: space-between;
}

body.sidebar-open .sidebar-burger {
  width: 38px;
  height: 38px;
  flex-basis: 38px;
  order: 2;

  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  outline: none;
}

.sidebar-burger .icon-close {
  display: none;
}

body.sidebar-open .sidebar-burger .icon-open {
  display: none;
}

body.sidebar-open .sidebar-burger .icon-close {
  display: block;
  width: 17px;
  height: 17px;
  stroke-width: 1.8;
}

.sidebar-burger:hover,
.sidebar-burger:focus,
.sidebar-burger:focus-visible,
.sidebar-burger:active {
  background: transparent;
  border: 0;
  box-shadow: none;
  outline: none;
}

body:not(.sidebar-open) .sidebar-brand .app-logo {
  display: none;
}

.brand-mark img {             /* ← tambahkan blok baru ini */
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Logo utuh (bukan brand-mark kotak) di dalam sidebar-brand, mis. <a class="app-logo"><img></a> */
.sidebar-brand .app-logo {
  display: flex;
  align-items: center;
  width: calc(100% - 48px);
}
.sidebar-brand .app-logo img {
  max-width: 170px;
  width: 100%;
  height: auto;
  max-height: 80px;
  object-fit: contain;
  display: block;
}

.brand-mark {
  width: 40px; height: 40px;
  overflow: hidden;
  background: rgba(255,255,255,0.16);
  border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 14px;
  font-family: var(--font-display);
  backdrop-filter: blur(4px);
}
.brand-mark-lg { width: 64px; height: 64px; font-size: 22px; border-radius: var(--radius-md); }

.brand-text { font-weight: 700; font-size: 16.5px; font-family: var(--font-display); letter-spacing: -0.01em; }
.brand-eyebrow {
  display: block; font-size: 10.5px; letter-spacing: 1.5px; text-transform: uppercase;
  color: rgba(255,255,255,0.6); font-weight: 700; margin-top: 1px;
}

.sidebar-nav { flex: 1; overflow:visible; padding: 18px 10px; position: relative; z-index: 1; }
/*
 * Human UAT Round 4 — sidebar vertical scroll, scrollbar visually hidden.
 *
 * ONLY the fully-expanded (body.sidebar-open) sidebar gets this treatment
 * — the COLLAPSED icon-rail mode's `.sidebar-nav` deliberately keeps
 * `overflow:visible` (the rule above, untouched) because its hover
 * flyouts (`left:58px`/`left:100%`, `position:absolute`) must be able to
 * render OUTSIDE `.sidebar-nav`'s own narrow box — any `overflow` value
 * other than `visible` on an ANCESTOR clips a `position:absolute`
 * descendant that visually extends past the ancestor's own bounds,
 * REGARDLESS of the descendant's own `overflow:visible` — this is exactly
 * why this must be scoped to expanded mode only, per this round's own
 * "do not assume expanded and collapsed use the same overflow strategy"
 * instruction.
 *
 * `min-height: 0` is the classic flexbox fix this specific bug needed:
 * `.sidebar-nav` is a `flex: 1` child of `.sidebar` (a fixed-height flex
 * column, `position: fixed; top:0; bottom:0`) — without `min-height: 0`,
 * a flex item's default `min-height: auto` prevents it from ever
 * shrinking below its OWN CONTENT height, so when a Project's expanded
 * feature list is taller than the space actually available, `.sidebar-nav`
 * simply grew past its allotted space and pushed/overflowed past
 * `.sidebar`'s own fixed box instead of scrolling internally — this is
 * the root mechanism behind "lower menu items become unreachable."
 */
body.sidebar-open .sidebar-nav {
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* legacy Edge/IE */
}
body.sidebar-open .sidebar-nav::-webkit-scrollbar { display: none; /* Chrome/Safari/new Edge (WebKit/Blink) */ }

.sidebar-menu li { margin-bottom: 3px; position:relative; }

.menu-link {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  font-size: 14.2px;
  font-weight: 500;
  color: rgba(255,255,255,0.85);
  transition: background var(--transition), transform var(--transition), padding-left var(--transition);
  position: relative;
}
body:not(.sidebar-open) .sidebar .menu-label,
body:not(.sidebar-open) .sidebar .menu-caret,
body:not(.sidebar-open) .sidebar .menu-caret-btn,
body:not(.sidebar-open) .sidebar .sidebar-user-info,
body:not(.sidebar-open) .sidebar .sidebar-user-caret,
body:not(.sidebar-open) .sidebar .sidebar-submenu { display:none; }
body:not(.sidebar-open) .sidebar .menu-link { justify-content:center; gap:0; }
body:not(.sidebar-open) .sidebar .menu-link i { font-size:17px; }

/* UAT-DEFECT-051 (superseding UAT-DEFECT-050 Part B's own fix): forcing
   the participant sidebar to a permanently-expanded desktop width (with
   the collapse burger hidden) was found to be the ROOT CAUSE of a new
   Human UAT finding -- it made open/collapse/reopen literally
   impossible for participants, rather than merely "different" from the
   admin shell. The participant shell now reuses the EXACT SAME
   `body.sidebar-open` toggle/CSS/persistence mechanism the admin shell
   already uses, with no participant-specific width override at all --
   full behavioral parity by reuse, never a second collapse mechanism.
   The only participant-specific difference is a DEFAULT: a brand-new
   participant session (no persisted sidebar preference yet) starts
   expanded (see the inline bootstrap script in header.php), because a
   narrow icon-only rail reads badly for a menu that only ever has 3
   items -- every subsequent toggle/reload then follows the identical
   shared persisted-state contract an admin session would. */
body:not(.sidebar-open) .sidebar-footer { padding:14px 12px; }
.menu-icon { width:19px; height:19px; flex:0 0 19px; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; }

body:not(.sidebar-open) .sidebar-menu > li > .menu-link::after {
  content: attr(data-label); position:absolute; left:58px; top:50%; transform:translateY(-50%);
  padding:8px 11px; border-radius:8px; background:#173847; color:#fff; white-space:nowrap;
  box-shadow:var(--shadow-md); opacity:0; pointer-events:none; transition:opacity .15s ease; z-index:1002;
}
body:not(.sidebar-open) .sidebar-menu > li:not(.has-children) > .menu-link:hover::after { opacity:1; }
body:not(.sidebar-open) .sidebar-menu > li.has-children:hover > .sidebar-submenu {
  display:block; position:absolute; left:58px; top:0; width:220px; padding:8px; max-height:none;
  background:#173847; border:1px solid rgba(255,255,255,.16); border-radius:12px;
  box-shadow:var(--shadow-lg); overflow:visible; z-index:1001;
}
body:not(.sidebar-open) .sidebar-menu > li.has-children:hover > .sidebar-submenu .menu-label { display:block; }
body:not(.sidebar-open) .sidebar-menu > li.has-children:hover > .sidebar-submenu .menu-link { justify-content:flex-start; gap:11px; padding:10px 12px; }
/*
 * UAT-DEFECT-009 (reopened a 2nd time) — REAL root cause found via direct
 * Playwright browser measurement, not source-code reasoning: the level-1
 * hover-flyout rule above only ever matched `.sidebar-menu > li` (a
 * DIRECT child of the top-level menu, e.g. "Assessment Project" itself).
 * It never matched a Project NODE's own nested `li.has-children` one level
 * further in (e.g. "Project Alpha" sitting inside the "Assessment
 * Project" flyout) — so hovering a Project inside that first flyout to
 * reveal ITS OWN features (Isi Penilaian, Daftar Rapor, ...) had NO
 * matching CSS rule at all and rendered `display:none`, confirmed via
 * `getComputedStyle()` in an actual headless-browser session. In the
 * app's OWN default sidebar state (collapsed/icon-rail — "Default state:
 * CLOSED baik desktop maupun mobile"), a user could see the Project LIST
 * but could never see any single Project's own feature list via hover —
 * this is almost certainly what Human UAT experienced as "submenus
 * disappearing," since the fully-expanded sidebar (`body.sidebar-open`)
 * was never the state most of that testing happened in.
 * Fix: a second-level hover-flyout rule, generic to any nesting depth
 * (descendant combinator, not `>`), positioned to the RIGHT of its own
 * parent flyout (`left:100%`) rather than the sidebar's own left edge.
 */
body:not(.sidebar-open) .sidebar-menu .sidebar-submenu li.has-children { position:relative; }
body:not(.sidebar-open) .sidebar-menu .sidebar-submenu li.has-children:hover > .sidebar-submenu {
  display:block; position:absolute; left:100%; top:0; width:220px; padding:8px; max-height:none;
  background:#173847; border:1px solid rgba(255,255,255,.16); border-radius:12px;
  box-shadow:var(--shadow-lg); overflow:visible; z-index:1002;
}
body:not(.sidebar-open) .sidebar-menu .sidebar-submenu li.has-children:hover > .sidebar-submenu .menu-label { display:block; }
body:not(.sidebar-open) .sidebar-menu .sidebar-submenu li.has-children:hover > .sidebar-submenu .menu-link { justify-content:flex-start; gap:11px; padding:10px 12px; }
.menu-link i.fa-solid, .menu-link i.fa-regular { width: 18px; text-align: center; font-size: 14px; opacity: 0.92; }

.menu-link:hover {
  background: rgba(255,255,255,0.13);
  color: #fff;
  padding-left: 18px;
}

.sidebar-menu li.active > .menu-link {
  background: rgba(255,255,255,0.20);
  color: #fff;
  box-shadow: inset 3px 0 0 var(--c-accent);
}

/* UAT-DEFECT-050 Part B (Asesi participant shell) -- `.is-locked`/
   `.menu-state-badge` had NO styling anywhere before this pass (a
   Human UAT-confirmed gap); `.menu-state-badge` (plain text) is
   replaced by `.menu-state-icon` (an icon + accessible label, per the
   "state communicated by icon, never text/color alone" requirement). */
.menu-link.is-locked { cursor: not-allowed; opacity: 0.62; }
.menu-link.is-locked:hover { background: transparent; padding-left: 14px; }
.menu-state-icon { margin-left: auto; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; opacity: 0.9; }
.menu-state-svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.menu-state-icon--completed { color: var(--c-secondary); }
.menu-state-icon--locked { color: rgba(255,255,255,0.55); }
body:not(.sidebar-open):not(.participant-app) .sidebar .menu-state-icon { display: none; }

/*
 * Human UAT (Assessment Project landing flow): the caret is now its own
 * click/keyboard target (`data-menu-caret`, see app.js's initSubmenuAccordion())
 * separate from the `.menu-link` label, so a parent with its own real route
 * (e.g. "Assessment Project", or a per-Project node) can be clicked to
 * navigate while the caret still expands/collapses its children. The
 * `margin-left:auto` that used to live on `.menu-caret` itself now lives on
 * this wrapper so the caret still sits at the end of the row.
 */
.menu-caret-btn { margin-left:auto; display:inline-flex; align-items:center; justify-content:center; width:26px; height:26px; border-radius:6px; cursor:pointer; }
.menu-caret-btn:hover, .menu-caret-btn:focus-visible { background: rgba(255,255,255,0.14); outline: none; }
.menu-caret { width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; transition:transform var(--transition); }
.sidebar-menu li.has-children.open > .menu-link .menu-caret { transform: rotate(180deg); }

.sidebar-submenu { padding-left: 30px; overflow: hidden; max-height: 0; transition: max-height var(--transition); }
/*
 * UAT-DEFECT-009 (reopened a 2nd time) — 2nd real bug found via direct
 * browser measurement: even in the fully-expanded (`body.sidebar-open`)
 * sidebar, a static `max-height: 500px` ceiling was SMALLER than the
 * actual rendered height of a Project with 11 feature items, several with
 * two-line-wrapping labels ("Bank Soal Pre & Post Test", "Setup Pre &
 * Post Test", "Pre & Post Test Asesi", "Form Field Absensi") — measured
 * scrollHeight 544px vs. the 500px ceiling, silently clipping the last
 * item(s) via this rule's own `overflow: hidden`, even though they were
 * fully present (and marked `.active`-eligible) in the DOM. A max-height
 * this technique relies on only needs to exceed the LARGEST realistic
 * content height — it never forces a collapsed submenu to actually take
 * up this much space — so this is raised generously rather than computed
 * exactly per-Project (which would need JS `scrollHeight` measurement,
 * a bigger change for no real benefit over a safely large static ceiling).
 */
.sidebar-menu li.open > .sidebar-submenu { max-height: 2000px; }

.sidebar-footer { padding: 16px; border-top: 1px solid rgba(255,255,255,0.15); position: relative; z-index: 1; }

.sidebar-user {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-sm);
  text-align: left;
  transition: background var(--transition);
}
.sidebar-user:hover, .sidebar-user[aria-expanded="true"] { background: rgba(255,255,255,0.10); }
.sidebar-user-info { display: flex; flex-direction: column; line-height: 1.3; font-size: 13px; color: #fff; }
.sidebar-user-info strong { font-size: 13.5px; }
.sidebar-user-info { min-width: 0; overflow: hidden; }
.sidebar-user-info strong, .sidebar-user-info small { display:block; max-width:150px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.sidebar-user-info small { opacity: 0.75; }
.sidebar-user-caret { margin-left: auto; font-size: 11px !important; color: rgba(255,255,255,0.7); transition: transform var(--transition); }
.sidebar-user[aria-expanded="true"] .sidebar-user-caret { transform: rotate(180deg); }

/* Dropdown menu akun, dipicu dari avatar di footer sidemenu (bukan lagi di
   topbar). Selector digabung (.dropdown.dropdown-up, bukan .dropdown-up
   saja) supaya spesifisitasnya lebih tinggi dari .dropdown biasa di bawah —
   sebelumnya top/right dari sini malah ketiban .dropdown karena urutannya
   belakangan di file ini walau spesifisitasnya sama, akibatnya dropdown
   nongol di luar layar (di bawah footer sidemenu) dan kelihatan "tidak
   muncul" padahal sebenarnya cuma salah posisi. */
.dropdown.dropdown-up {
  right: 16px; left: 16px;
  top: auto; bottom: calc(100% + 8px);
  transform: translateY(8px) scale(0.97);
}
.dropdown.dropdown-up.show { transform: translateY(0) scale(1); }
body:not(.sidebar-open) .dropdown.dropdown-up {
  position:fixed; left:84px; right:auto; bottom:18px; top:auto; min-width:210px; z-index:1100;
}

.avatar-circle {
  width: 38px; height: 38px;
  border-radius: 50%;
  background: var(--gradient-accent);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700;
  font-family: var(--font-display);
  flex-shrink: 0;
  box-shadow: var(--shadow-sm);
}
.avatar-sm { width: 32px; height: 32px; font-size: 13px; }

.sidebar-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(10, 30, 38, 0.5);
  z-index: 35;
  backdrop-filter: blur(2px);
}

.main-area {
  flex: 1;
  margin-left: 76px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  transition: margin var(--transition);
}
/* Sidebar terbuka: dorong konten (main-area) di DESKTOP saja — di mobile
   sidebar melayang di atas konten (overlay), jadi main-area tidak boleh
   ikut ter-dorong (lihat override di media query 900px di bawah). */
body.sidebar-open .main-area { margin-left: var(--sidebar-w); }

/* ---------------------------------------------------------------------
   TOPBAR — page title on the left, brand LOGO pinned top-right
--------------------------------------------------------------------- */
.topbar {
  height: 80px;
  background: rgba(255,255,255,0.75);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--c-border);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 26px;
  position: sticky;
  top: 0;
  z-index: 20;
}

.topbar-title { font-size: 19px; font-family: var(--font-display); }

.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 18px; }

.topbar-logo { display: flex; align-items: center; gap: 10px; padding-right: 6px; border-right: 1px solid var(--c-border); }
.topbar-logo img { height: 50px; width: auto; display: block; }

.btn-icon {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: transparent;
  display: flex; align-items: center; justify-content: center;
  color: var(--c-text);
  font-size: 15px;
  transition: background var(--transition), transform var(--transition);
}
.btn-icon:hover { background: var(--c-bg); transform: translateY(-1px); }

/* Outline icon treatment for action controls. Navigation icons use native
   stroke-only SVG above; legacy Font Awesome glyphs are rendered as outlines
   instead of opaque block silhouettes. */
.btn > i.fa-solid, .btn-icon > i.fa-solid, .search-box > i.fa-solid,
.dropdown-item > i.fa-solid, .row-actions i.fa-solid {
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: .8px currentColor;
}

/* Tombol toggle sidebar SEKARANG selalu tampil (desktop & mobile) — sesuai
   SIDEBAR BEHAVIOR (brief §6): default closed di semua ukuran layar,
   dibuka/ditutup manual oleh user lewat tombol ini, state dipertahankan
   selama session (sessionStorage, lihat public/assets/js/app.js). */
#toggleSidebar { display: flex; }

.topbar-user-menu { position: relative; }

.dropdown {
  position: absolute;
  right: 0; top: calc(100% + 10px);
  background: var(--c-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  min-width: 190px;
  padding: 8px;
  opacity: 0;
  transform: translateY(-8px) scale(0.97);
  pointer-events: none;
  transition: all var(--transition);
  border: 1px solid var(--c-border);
}
.dropdown.show { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }

.dropdown-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  color: var(--c-text);
  transition: background var(--transition);
}
.dropdown-item i { width: 16px; text-align: center; color: var(--c-text-muted); }
.dropdown-item:hover { background: var(--c-bg); }
.text-danger { color: var(--c-danger); }
.text-danger i { color: var(--c-danger) !important; }

.content { padding: 30px; flex: 1; }

/* =====================================================================
   FOOTER — adapted from BEKAL Portal brand footer (bsa-footer)
   ===================================================================== */
.app-footer {
  background: var(--c-primary-dark);
  color: #fff;
  padding: 30px 0 20px;
  margin-top: auto;
}
.app-footer .footer-container {
  max-width: 1180px; margin: 0 auto; padding: 0 28px;
  display: flex; flex-direction: column; gap: 18px;
}
.app-footer .footer-row1 {
  display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;
}
.app-footer .footer-tagline p { margin: 0; font-size: 12.5px; color: rgba(255,255,255,0.72); line-height: 1.7; text-align: right; }
.app-footer .footer-brand { display: flex; align-items: center; gap: 10px; font-family: var(--font-display); font-weight: 700; }
.app-footer .footer-brand img { height: 26px; }
.app-footer .footer-divider { height: 1px; background: rgba(255,255,255,0.12); }
.app-footer .footer-socials { display: flex; justify-content: center; gap: 16px; font-size: 15px; padding: 4px 0; }
.app-footer .footer-socials a {
  width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255,255,255,0.22);
  transition: background var(--transition), border-color var(--transition), transform var(--transition);
}
.app-footer .footer-socials a:hover { background: var(--c-accent); border-color: var(--c-accent); transform: translateY(-3px); }
.app-footer .footer-bottom {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px;
  padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.10);
  font-size: 11.5px; color: rgba(255,255,255,0.55);
}
.app-footer .footer-legal { display: flex; gap: 18px; }
.app-footer .footer-legal a:hover { color: #fff; }

/* Simple centered footer variant (used on error pages / minimal layouts) */
.app-footer.app-footer--simple { padding: 18px 28px; text-align: center; font-size: 12.5px; background: transparent; color: var(--c-text-muted); border-top: 1px solid var(--c-border); }

/* =====================================================================
   PAGE INTRO / STATS / PANELS
   ===================================================================== */
.page-intro { margin-bottom: 26px; position: relative; }
.page-intro h2 { font-size: 24px; }
.page-intro p { color: var(--c-text-muted); margin: 0; }

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 20px;
  margin-bottom: 28px;
}

/* UAT-DEFECT-010 — the 8-KPI-card contract: exactly 4 cards per row on
   desktop widths (2 rows for 8 cards), reflowing at narrower widths via the
   same breakpoints .stat-grid already uses below — no fixed pixel width
   that could overflow a smaller viewport. */
.stat-grid--kpi8 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) {
  .stat-grid--kpi8 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 380px) {
  .stat-grid--kpi8 { grid-template-columns: 1fr; }
}

/* Dashboard Assessment / Daftar Rapor Analytics — the 5 locked charts'
   2-column desktop grid (§30/§31 of the Chart Visualization Contract).
   Collapses to 1 column on tablet/mobile per the required responsive
   behavior -- charts resize with their parent container (Chart.js
   `responsive: true` + `maintainAspectRatio: false`), never a fixed
   canvas pixel size that could overflow. */
.dashboard-chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
@media (max-width: 900px) {
  .dashboard-chart-row { grid-template-columns: 1fr; }
}
.dashboard-chart-card { padding: 20px; min-width: 0; min-height: 0; }

/* Chart Size/Height/Performance Remediation — root cause (confirmed via
   source audit, not assumed): every chart canvas sat directly in normal
   document flow inside a CSS-grid card with NO explicit height anywhere
   in its ancestor chain. Chart.js's `responsive:true` +
   `maintainAspectRatio:false` measures the canvas's PARENT to decide the
   canvas's own size -- with no bounded parent, this is the exact,
   well-documented Chart.js "canvas-in-flex/grid-without-a-sized-wrapper"
   resize trap: the canvas's own rendered size feeds back into the grid
   row's auto-height, which the canvas then measures again on the next
   resize tick, each round growing further (observed as ~76 Chrome
   full-page-capture pages). Fixed at the architecture level, not with a
   CSS height patch alone: every canvas now lives inside a `.chart-viewport`
   wrapper carrying a FIXED, never-computed-from-content pixel height, so
   the canvas has a genuinely stable box to measure and can never feed
   back into its own container's size. `position: relative` is required
   by Chart.js itself (it absolutely-positions its internal size-monitor
   element against the nearest positioned ancestor). */
.chart-viewport {
  position: relative;
  width: 100%;
  height: 320px;
  min-height: 0;
  min-width: 0;
}
.chart-viewport canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}
/* "Beban Penilaian per Asesor" is full-width and may carry many Asesor
   labels -- a modestly taller viewport per §3, but STILL a fixed bound,
   never `height = assessor_count × N`. */
.chart-viewport--wide { height: 340px; }
/* "Distribusi Hasil Assessment" is a compact 3-slice summary chart. */
.chart-viewport--compact { height: 300px; }

@media (max-width: 1200px) {
  .chart-viewport { height: 300px; }
  .chart-viewport--wide { height: 320px; }
}
@media (max-width: 768px) {
  .chart-viewport { height: 280px; }
  .chart-viewport--wide { height: 300px; }
  .chart-viewport--compact { height: 280px; }
}

/* =====================================================================
   UAT-67 — Dashboard & Rapor Information Architecture Redesign
   Both pages now follow a single visual-hierarchy contract: page header
   -> filters -> primary outcomes (Executive/Project Summary) -> score
   summary -> outcome comparison charts -> participant/diagnostic detail
   -> operational detail. These components replace the previous flat
   "many equal-weight KPI cards" pattern so a reviewer can read portfolio/
   project state in ~10 seconds instead of scanning a uniform card wall.
   ===================================================================== */

/* Named section wrapper -- gives every IA section (Executive Summary,
   Outcome Overview, Diagnostic Analytics, Operational Analytics, ...) a
   consistent vertical rhythm and a small-caps label, so the hierarchy is
   visually legible even before a reader looks at any individual number. */
.analytics-section { margin-top: 28px; }
.analytics-section:first-of-type { margin-top: 16px; }
.analytics-section-title {
  font-size: 12.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px;
  color: var(--c-text-muted); margin: 0 0 10px;
}

/* Executive Summary / Project Summary -- exactly 4 primary cards (never
   8 equal-weight cards as before). Reuses .stat-card for visual
   consistency with the rest of the app, but is deliberately its own grid
   modifier so this primary row is never confused with the lower-priority
   metadata/score rows below it. */
.stat-grid--exec { grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 10px; }
@media (max-width: 900px) { .stat-grid--exec { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 420px) { .stat-grid--exec { grid-template-columns: 1fr; } }
/* Daftar Rapor's Project Summary uses 5 primary cards (Peserta/Hasil
   Final/Pending/Kompeten/Belum Kompeten) instead of Dashboard's 4. */
.stat-grid--exec5 { grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 10px; }
@media (max-width: 1100px) { .stat-grid--exec5 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 420px) { .stat-grid--exec5 { grid-template-columns: 1fr; } }

/* Scope metadata -- secondary context (Total Project / Unique People /
   Total Asesor / Total Wilayah) demoted from large KPI cards to one
   compact inline line, per the IA contract's "useful but not equally
   important" treatment. */
.scope-meta { font-size: 13px; color: var(--c-text-muted); margin: 0 0 24px; padding: 0 2px; }
.scope-meta strong { color: var(--c-text); font-weight: 600; }

/* Score Summary bar -- Assessment/Pre-Test/Post-Test averages + delta +
   Attendance as ONE coherent score group (a single bordered strip with
   internal dividers) instead of 4-5 separate equal-sized stat cards. */
.score-summary-bar {
  display: flex; flex-wrap: wrap;
  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); padding: 18px 24px; margin-bottom: 24px;
}
.score-summary-item { flex: 1 1 140px; padding: 6px 22px; border-right: 1px solid var(--c-border); }
.score-summary-item:last-child { border-right: none; }
.score-summary-item--secondary { flex: 1 1 170px; opacity: .88; }
.score-summary-label { display: block; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .4px; color: var(--c-text-muted); margin-bottom: 4px; }
.score-summary-value { font-size: 22px; font-weight: 700; line-height: 1.1; }
.score-summary-sub { display: block; font-size: 12px; color: var(--c-text-muted); margin-top: 3px; }
.score-summary-value--positive { color: var(--c-success); }
.score-summary-value--negative { color: var(--c-danger); }
@media (max-width: 768px) {
  .score-summary-bar { flex-direction: column; padding: 16px 20px; }
  .score-summary-item { border-right: none; border-bottom: 1px solid var(--c-border); padding: 10px 0; }
  .score-summary-item:last-child { border-bottom: none; }
}

/* Single-project-required / no-data states inside a chart card must stay
   compact -- never a full 320px empty chart-viewport box reserved just to
   preserve grid height (§54 of the IA contract). */
.chart-empty-compact { padding: 28px 16px; }
.chart-empty-compact i { font-size: 26px; margin-bottom: 8px; }

.stat-card {
  background: var(--c-surface);
  border-radius: var(--radius-lg);
  padding: 22px;
  display: flex;
  align-items: center;
  gap: 16px;
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--c-border);
  transition: transform var(--transition), box-shadow var(--transition);
}
.stat-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }

.stat-icon {
  width: 52px; height: 52px;
  border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 19px;
  color: #fff;
  flex-shrink: 0;
  /* UAT-DEFECT-049: navigation cards (e.g. `show.php`'s Project submenu,
     the Wilayah detail link cards) reuse `.stat-card`/`.stat-icon` but
     are NOT KPI/counting cards, so they intentionally keep this neutral
     brand-gradient default rather than joining the 4-color KPI rotation
     below. Every genuine KPI/counting/calculation card MUST instead
     carry an explicit `.stat-card--primary/--secondary/--warning/--accent`
     modifier (applied by POSITION, cycling in that exact order — see
     `--kpi-color-1..4` above) so its icon container renders a SOLID
     corporate color, never this gradient. */
  background: var(--gradient-brand);
}
/* UAT-DEFECT-049 — the canonical KPI icon-container color rotation.
   Position 1 = --kpi-color-1 (teal dark), 2 = --kpi-color-2 (teal
   light), 3 = --kpi-color-3 (orange light), 4 = --kpi-color-4 (orange
   dark), repeating for grids of more than 4 cards. Solid colors only —
   never a gradient, tint, or opacity-based background — so the white
   icon glyph (`color: #fff` above) always renders on full corporate
   contrast. */
.stat-card--primary .stat-icon { background: var(--kpi-color-1); }
.stat-card--secondary .stat-icon { background: var(--kpi-color-2); }
.stat-card--warning .stat-icon { background: var(--kpi-color-3); }
.stat-card--accent .stat-icon { background: var(--kpi-color-4); }
/* Modifier ukuran + gradient — sebelumnya di-inline identik di beberapa link-card (roadmap/input.php). */
.stat-icon-lg { width: 56px; height: 56px; font-size: 22px; }
.stat-icon--brand { background: var(--gradient-brand); }
.stat-icon--accent { background: var(--gradient-accent); }

/* UAT-DEFECT-050 Part A (Attendance Form Builder) — native HTML5
   drag-and-drop reorder rows, shared by the Field Builder and any
   dropdown-option manager (Posisi/Jabatan, custom SELECT fields). */
.dnd-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.dnd-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; background: var(--c-bg-soft); border: 1px solid var(--c-border);
  border-radius: var(--radius-md); cursor: grab;
}
.dnd-row.dragging { opacity: 0.4; }
.dnd-row .dnd-handle { color: var(--c-text-muted); font-size: 16px; flex-shrink: 0; }
.dnd-row .dnd-body { flex: 1; min-width: 0; }
.dnd-row .dnd-actions { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }

.stat-body { display: flex; flex-direction: column; }
.stat-value { font-size: 27px; font-weight: 800; font-family: var(--font-display); line-height: 1.1; }
.stat-label { font-size: 12.5px; color: var(--c-text-muted); margin-top: 2px; }

/* Generic content card — sebelumnya didefinisikan ulang secara terpisah
   (nyaris identik) di bas-production.css dan induction-ui.css. Disatukan
   di sini sebagai komponen inti; modul cukup memakai class ini. */
.card {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), transform var(--transition);
}
.card:hover { box-shadow: var(--shadow-md); }
.card-body { padding: 22px; }

.panel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 22px;
}

.panel {
  background: var(--c-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--c-border);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
/* Modifier: .panel dipakai sebagai kartu aksi berbentuk link (ikon + judul +
   deskripsi), sebelumnya di-styling identik lewat inline style di 4+ tempat. */
.panel-link {
  text-decoration: none; color: inherit; padding: 28px;
  display: flex; gap: 18px; align-items: center;
  transition: box-shadow var(--transition), transform var(--transition);
}
.panel-link:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.panel-link h3 { margin: 0 0 4px; }
.panel-link p { margin: 0; font-size: 13.5px; }
.panel-header { padding: 20px 22px; border-bottom: 1px solid var(--c-border); display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.panel-header h3 { font-size: 15.5px; display: flex; align-items: center; gap: 10px; }
.panel-header h3 i { color: var(--c-secondary); font-size: 14px; }
.panel-body { padding: 22px; }

.role-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(0, 104, 133, 0.1);
  color: var(--c-primary);
  font-size: 12.5px;
  font-weight: 600;
  transition: transform var(--transition);
  display: inline-flex; align-items: center; gap: 6px;
}
.chip:hover { transform: scale(1.05); background: rgba(0, 104, 133, 0.16); }
.chip i { font-size: 10.5px; }
/* Modifier untuk .chip yang dipakai sebagai tautan "kembali" di atas judul halaman. */
.chip-back { margin-bottom: 10px; }

.timeline { border-left: 2px solid var(--c-border); padding-left: 16px; }
.timeline li { position: relative; padding-bottom: 14px; font-size: 14px; color: var(--c-text-muted); }
.timeline-dot {
  position: absolute; left: -21px; top: 4px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--c-secondary);
  box-shadow: 0 0 0 3px rgba(0,170,173,0.2);
}
.timeline em { font-style: normal; display: block; font-size: 12px; color: var(--c-text-muted); opacity: 0.8; }

.text-muted { color: var(--c-text-muted); }
.text-success { color: #178a68; }

/* =====================================================================
   BUTTONS
   ===================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 22px;
  border-radius: var(--radius-sm);
  border: none;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  transition: transform var(--transition), box-shadow var(--transition), background var(--transition);
}
.btn i { font-size: 13px; }
.btn-block { width: 100%; }
.btn-sm { padding: 8px 14px; font-size: 13px; }
.btn-primary {
  background: var(--gradient-accent);
  color: #fff;
  box-shadow: 0 6px 18px rgba(241, 90, 34, 0.32);
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(241, 90, 34, 0.42); }
.btn-primary:active { transform: translateY(0); }

.btn-secondary { background: var(--c-primary); color: #fff; box-shadow: 0 6px 16px rgba(0,104,133,0.25); }
.btn-secondary:hover { transform: translateY(-2px); background: var(--c-primary-dark); }

.btn-outline { background: transparent; color: var(--c-primary); border: 1.5px solid var(--c-border); }
.btn-outline:hover { border-color: var(--c-primary); background: rgba(0,104,133,0.06); }

.btn-danger-outline { background: transparent; color: var(--c-danger); border: 1.5px solid rgba(227,69,58,0.35); }
.btn-danger-outline:hover { background: rgba(227,69,58,0.08); border-color: var(--c-danger); }

.btn-ghost { background: transparent; color: var(--c-text-muted); }
.btn-ghost:hover { background: var(--c-bg); }

/* =====================================================================
   FORMS (shared: login + admin CRUD)
   ===================================================================== */
.form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
/* Setup Pre & Post Test (Section F) -- a checkbox field has no separate
   label line above it, unlike a text-label + <select>/<input> field in the
   same grid row; aligning it to the row's BOTTOM (rather than the top,
   .form-group's default) keeps the checkbox itself level with the other
   fields' actual input controls instead of floating noticeably higher. */
.form-group--checkbox { justify-content: flex-end; }
.form-group label { font-size: 13.5px; font-weight: 600; color: var(--c-text); }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 4px 20px; }
.form-grid--5 { grid-template-columns: repeat(5, 1fr); }
.form-grid--2 { grid-template-columns: 1fr 1fr; gap: 12px; }
/* Human UAT (Question Create section width) — .form-grid--2's fixed
   1fr/1fr split (unlike the generic .form-grid's auto-fit(minmax(...))
   which naturally collapses on its own) has no built-in narrow-viewport
   stacking; first real usage here (Bank Soal's Kode Soal + Kategori row)
   needs it, so it's added as a reusable rule for any future .form-grid--2
   consumer too -- same breakpoint/pattern already used by
   .rapor-identity-grid above. */
@media (max-width: 640px) {
  .form-grid--2 { grid-template-columns: 1fr; }
}
/* Setup Assessment UI/CSS remediation -- Row 1 of the Assessment tab's
   configuration form (Skala Min / Skala Max / Passing Threshold): the
   first two fields are intentionally narrower (numeric scale bounds),
   Passing Threshold gets the remaining wider space. CSS Grid fractional
   units only -- no manual pixel widths, absolute positioning, or
   translateX hacks. Narrow tablet collapses to a 2-column row (Skala
   Min|Max) with Passing Threshold spanning full width below it; mobile
   stacks all three, mirroring .form-grid--2's own breakpoint pattern. */
.form-grid--assessment-primary { grid-template-columns: minmax(120px, 0.7fr) minmax(120px, 0.7fr) minmax(220px, 1.6fr); gap: 12px; }
@media (max-width: 900px) {
  .form-grid--assessment-primary { grid-template-columns: 1fr 1fr; }
  .form-grid--assessment-primary > :last-child { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .form-grid--assessment-primary { grid-template-columns: 1fr; }
  .form-grid--assessment-primary > :last-child { grid-column: auto; }
}
/* Bank Soal UX remediation §16-17: each answer-option row keeps its radio
   button visually locked to its own text input via a fixed-width label
   wrapper (the radio + its A/B/C/D letter act as one unit), so which radio
   belongs to which answer is unambiguous — never relies on loose flex gap
   alignment alone. Presentation only; does not touch correct-answer
   persistence (still the `correct_choice` radio value/index). */
.qb-choice-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.qb-choice-radio-label { display: flex; align-items: center; gap: 6px; font-weight: 600; flex-shrink: 0; cursor: pointer; }
.qb-choice-radio-label input[type="radio"] { width: 18px; height: 18px; accent-color: var(--c-primary); cursor: pointer; }
/* Ikon status besar di tengah panel (mis. status Pre-Test/Post-Test) —
   sebelumnya inline identik 6× di satu file (asesi-landing.php). */
.status-icon-xl { font-size: 34px; margin-bottom: 12px; display: block; }
.status-icon-xl--success { color: var(--c-success); }
.status-icon-xl--muted { color: var(--c-text-muted); }
.status-icon-xl--primary { color: var(--c-primary); }
.panel-body--empty { text-align: center; padding: 30px 20px; }
.form-hint { font-size: 12px; color: var(--c-text-muted); margin-top: -2px; }

input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="tel"],
input[type="number"], input[type="date"], input[type="datetime-local"], input[type="url"],
select, textarea, .form-control {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--c-border);
  font-size: 14.2px;
  font-family: var(--font-body);
  transition: border-color var(--transition), box-shadow var(--transition);
  background: var(--c-surface);
  color: var(--c-text);
  width: 100%;
}
input:focus, select:focus, textarea:focus, .form-control:focus {
  border-color: var(--c-secondary);
  box-shadow: 0 0 0 4px rgba(0,170,173,0.15);
  outline: none;
}
/* Human UAT (Auto-generated question code visual state) — a field that is
   genuinely server-generated/read-only (never a client-editable input,
   regardless of `readonly` vs `disabled` -- neither is trusted server-side
   anyway) must LOOK unmistakably read-only, not merely "an input that
   happens to be disabled" per the browser's own inconsistent default
   styling. Reusable for any future server-generated/computed field, not
   just Kode Soal. */
.field-readonly, input[readonly].field-readonly {
  background: var(--c-bg-soft);
  color: var(--c-text-muted);
  cursor: not-allowed;
  border-color: var(--c-border);
}
.field-readonly:focus { box-shadow: none; border-color: var(--c-border); }

.input-with-icon { position: relative; }
.input-with-icon input { padding-left: 42px; }
.input-with-icon i.field-icon {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--c-text-muted); font-size: 14px; pointer-events: none;
}
.input-with-icon .toggle-password { left: auto; right: 8px; pointer-events: auto; }
.password-input-wrap input { padding-left:14px; padding-right:46px; }

.toggle-password {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  background: none; border: none; color: var(--c-text-muted);
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: background var(--transition);
}
.toggle-password:hover { background: var(--c-bg); }

.form-row-between { display: flex; justify-content: space-between; align-items: center; font-size: 13.5px; margin-bottom: 22px; }
.checkbox-label { display: flex; align-items: center; gap: 6px; color: var(--c-text-muted); }
.link-muted { color: var(--c-secondary); font-weight: 600; transition: color var(--transition); }
.link-muted:hover { color: var(--c-primary); }

.alert {
  padding: 13px 16px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  margin-bottom: 18px;
  display: flex; align-items: center; gap: 10px;
  animation: shake 0.4s ease;
}
/* UAT-DEFECT-053: `.alert`'s own `display: flex` (an AUTHOR rule) silently
   overrode the browser's built-in `[hidden] { display: none }` UA rule --
   author styles always win over UA styles regardless of specificity, so
   any `<div class="alert ..." hidden>` rendered as an empty, padded,
   colored box instead of actually being hidden (the root cause of the
   "empty red alert on initial page load" Human UAT finding, e.g.
   Absensi's `#camera-error`). This is the ONE fix, applied once here --
   every `.alert` anywhere in the app that legitimately uses the `hidden`
   attribute is corrected by it, never a per-page patch. */
.alert[hidden] { display: none; }
.alert i { font-size: 15px; }
.alert-error { background: rgba(227, 69, 58, 0.1); color: var(--c-danger); border: 1px solid rgba(227,69,58,0.25); }
.alert-success { background: rgba(31, 169, 127, 0.1); color: var(--c-success); border: 1px solid rgba(31,169,127,0.25); }
.alert-info { background: rgba(0, 104, 133, 0.08); color: var(--c-primary); border: 1px solid rgba(0,104,133,0.2); }

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}

/* =====================================================================
   ADMIN — USER MANAGEMENT (table, badges, toolbar)
   ===================================================================== */
.toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; margin-bottom: 20px;
}
.toolbar-left { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.search-box { position: relative; min-width: 240px; }
.search-box i { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--c-text-muted); font-size: 13px; }
.search-box input { padding-left: 38px; }

/* Baris filter Account List, Email Queue, dan Audit Log. */
.filter-row {
  display: flex;
  align-items: flex-end;
  flex-wrap: nowrap;
  gap: 12px;
  width: 100%;
}
.filter-row .search-box { flex: 1 1 280px; min-width: 210px; }
.filter-row .search-box input { width: 100%; }
.filter-row select { flex: 0 1 170px; min-width: 130px; }
.filter-date-field {
  display: flex;
  flex: 0 1 165px;
  min-width: 145px;
  flex-direction: column;
  gap: 5px;
}
.filter-date-field label {
  color: var(--c-text-muted);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}
.filter-date-field input { width: 100%; }
.filter-row > .btn { flex: 0 0 auto; }
.audit-action-toolbar { margin-top: -4px; }
.audit-reset-button { margin-left: auto; }

@media (max-width: 1100px) {
  .filter-row { flex-wrap: wrap; }
  .filter-row .search-box { flex: 1 1 100%; }
  .filter-row select,
  .filter-date-field { flex: 1 1 180px; }
}

/*
 * UAT-DEFECT-047 (Global UI Layout / KPI Card / Section / Pagination
 * Remediation): every independent top-level content block now carries
 * its own trailing margin, so two adjacent sections (e.g. the Kriteria
 * Penilaian .table-card immediately followed by the "Buat Versi Baru"
 * .toolbar on Setup Assessment) never visually touch/merge, without
 * every view having to remember an inline `style="margin-top:..."`
 * patch. This is the ONE shared section-spacing rule -- do not
 * reintroduce per-page margin overrides for this purpose.
 */
.table-card { background: var(--c-surface); border-radius: var(--radius-lg); border: 1px solid var(--c-border); box-shadow: var(--shadow-sm); overflow: hidden; margin-bottom: 24px; }
.table-wrap { overflow-x: auto; }

/* UAT-DEFECT-052 (Daftar Peserta Wilayah layout): a toolbar living
   INSIDE a `.table-card` (title + optional secondary controls, ABOVE
   the table, inside ONE bordered card) -- distinct from the bare
   page-level `.toolbar` (used OUTSIDE any card on Account List/Email
   Queue/Audit Log, left completely untouched by this fix). Root cause
   of the reported edge-collision: `.table-wrap` (a horizontal-SCROLL
   wrapper meant only for the `<table>` itself) has zero padding by
   design, so a `.toolbar` nested inside it inherited none -- this
   class is applied to a toolbar living as a `.table-card` child
   INSTEAD of inside `.table-wrap`, giving it its own padding and a
   divider border, without padding ever reaching the table itself. */
.card-toolbar { padding: 18px 22px 16px; border-bottom: 1px solid var(--c-border); }
.card-toolbar h3 { margin: 0; }
.card-toolbar-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }

/* Search + primary action sharing one row (e.g. Daftar Asesi) -- the
   search form flexes to fill the available width, the action button
   never shrinks and only wraps to its own line when space genuinely
   runs out (mobile), never as an arbitrary/uncontrolled reflow. */
.card-toolbar-search-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 14px; }
.card-toolbar-search-row form { flex: 1 1 280px; min-width: 0; }
.card-toolbar-search-row .search-box { width: 100%; min-width: 0; }
.card-toolbar-search-row .search-box input { width: 100%; }
.card-toolbar-search-row > .btn { flex: 0 0 auto; }
@media (max-width: 560px) {
  .card-toolbar-search-row > .btn { width: 100%; }
}
.data-table { width: 100%; border-collapse: collapse; min-width: 720px; }
.data-table thead th {
  text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--c-text-muted); font-weight: 700; padding: 14px 18px;
  background: var(--c-bg-soft); border-bottom: 1px solid var(--c-border);
}
.data-table tbody td { padding: 14px 18px; font-size: 14px; border-bottom: 1px solid var(--c-border); vertical-align: middle; }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr { transition: background var(--transition); }
.data-table tbody tr:hover { background: var(--c-bg-soft); }
/* Setup Pre & Post Test -- Alokasi Kategori Total row (Human UAT: the Total
   row visually appeared detached/misaligned). Root cause: `<tfoot>` cells
   inherited ZERO of `.data-table tbody td`'s padding/border rules (a
   `<tfoot>` is never a `<tbody>`), so the Total row had no padding at all
   and no visual separation from the rows above it. Fixed at the genuine
   semantic table-footer level (the markup already used <tfoot> correctly)
   -- never an absolute-position/translateX/manual-margin overlay hack. */
.data-table tfoot td { padding: 14px 18px; font-size: 14px; font-weight: 600; border-top: 2px solid var(--c-border); background: var(--c-bg-soft); vertical-align: middle; }
/* UAT-DEFECT-074 -- one shared numeric-column alignment utility (Modal
   Detail Rapor's Bobot/Jumlah Soal/Benar/Point/Skor columns), rather than
   inventing a one-off inline style per table. */
.data-table .text-right { text-align: right; }

.user-cell { display: flex; align-items: center; gap: 12px; }
.user-cell strong { display: block; font-size: 14px; }
.user-cell small { color: var(--c-text-muted); font-size: 12.5px; }

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.2px;
}
.badge i { font-size: 8px; }
.badge-active { background: rgba(31,169,127,0.12); color: #178a68; }
.badge-inactive { background: rgba(92,115,120,0.12); color: var(--c-text-muted); }
.badge-suspended { background: rgba(227,69,58,0.12); color: var(--c-danger); }
.badge-pending { background: rgba(249,157,31,0.14); color: #b9740c; }
.badge-warning { background: rgba(249,157,31,0.14); color: #b9740c; }
.badge-success { background: rgba(31,169,127,0.12); color: #178a68; }
.badge-danger { background: rgba(241,90,34,0.12); color: var(--c-accent); }
/* Badge netral non-pill-status, mis. label "via Role" pada matrix permission
   (sebelumnya inline <style> di dalam view partial user_management). */
.badge-neutral, .badge-role-inherited {
  background: var(--c-bg-soft); color: var(--c-text-muted);
  border-radius: var(--radius-pill); padding: 2px 10px; font-weight: 600; font-size: 11px;
  display: inline-block;
}

.row-actions { display: flex; align-items: center; gap: 8px; }
.row-actions .btn-icon { width: 34px; height: 34px; font-size: 13px; background: var(--c-bg-soft); }
.row-actions .btn-icon:hover { background: var(--c-border); }
.row-actions .btn-icon.danger { color: var(--c-danger); }

.empty-state { text-align: center; padding: 60px 20px; color: var(--c-text-muted); }
.empty-state i { font-size: 38px; color: var(--c-border); margin-bottom: 14px; }

/* Generic checkbox-management Bulk Action bar (moved here from
   user-management.css so any module's own bulk-select UI -- e.g.
   Question Bank's bulk status change -- can reuse it without depending on
   a different module's stylesheet). */
.bulk-action-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: rgba(0,104,133,0.06); border: 1px solid var(--c-primary);
  border-radius: var(--radius-sm); padding: 10px 16px; margin-bottom: 14px;
}
.bulk-action-bar select {
  padding: 8px 12px; border-radius: 8px; border: 1px solid var(--c-border);
  font-size: 13px; background: var(--c-surface);
}
.bulk-action-bar strong,
.bulk-action-bar span[id$="-bulk-count"] { font-weight: 700; color: var(--c-primary); }

/* Generic tab-bar (moved here from user-management.css, same reasoning as
   .bulk-action-bar above): Setup Assessment/Exam Config's own
   setup-assessment-tabs.php partial and Bank Soal's new PRE-TEST/POST-TEST
   tabs (§04-06) both use this convention, and neither of those modules'
   views load user-management.css — so this was actually UNSTYLED on those
   pages before this move (a pre-existing latent gap, not something this
   remediation introduced; fixed as a side effect of relocating the rule to
   where every module's views can reach it, exactly like bulk-action-bar). */
.um-tabs {
  display: flex; gap: 10px; margin-bottom: 18px; flex-wrap: wrap;
}
.um-tabs .tab-btn {
  padding: 10px 20px; border-radius: var(--radius-sm);
  border: 1px solid var(--c-border); background: var(--c-surface);
  color: var(--c-text-muted); font-size: 14px; font-weight: 600;
  cursor: pointer; transition: all var(--transition);
  display: inline-flex; align-items: center; gap: 8px; text-decoration: none;
}
.um-tabs .tab-btn:hover { background: var(--c-bg-soft); color: var(--c-primary); }
.um-tabs .tab-btn.active {
  background: var(--c-primary); border-color: var(--c-primary); color: #fff;
}
.um-tabs .tab-btn .tab-count {
  background: rgba(255,255,255,0.25); border-radius: var(--radius-pill);
  padding: 1px 8px; font-size: 12px; font-weight: 700;
}
.um-tabs .tab-btn:not(.active) .tab-count {
  background: var(--c-bg-soft); color: var(--c-text-muted);
}

/* Human UAT (Bank Soal toolbar alignment) — module-scoped wrapper so
   PRE-TEST/POST-TEST and Import CSV/Preview Course share ONE row on
   desktop via the existing generic .toolbar (flex, space-between,
   flex-wrap) instead of two separately-stacked blocks. .question-bank-
   toolbar-tabs resets .um-tabs's own margin-bottom since it now sits
   INSIDE .toolbar rather than as a standalone block above it. */
.question-bank-toolbar-tabs { margin-bottom: 0; }
.question-bank-toolbar-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Human UAT (Category section too narrow) — .table-card itself has no
   built-in padding (its inner sections, e.g. the question list below,
   each supply their own), so this supplies the header/body padding the
   old .form-card used to give for free. Deliberately module-scoped
   (.question-bank-category-section-*) rather than a generic .table-card
   change, per this remediation's own CSS-scope contract. */
.question-bank-category-section-header { font-weight: 600; padding: 16px 20px; border-bottom: 1px solid var(--c-border); }
.question-bank-category-section-body { padding: 16px 20px; }

/* Human UAT (Question Create section width/layout alignment) — same
   reasoning as .question-bank-category-section above: .table-card itself
   has no built-in padding, so this supplies what the generic .form-card
   (720px-capped, now replaced) used to give for free -- matching its own
   original 26px padding value so the visual density is unchanged, only
   the outer width constraint is removed. */
.question-bank-question-form { padding: 26px; }

.pagination { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 6px; padding: 18px; }
.pagination a, .pagination span {
  min-width: 34px; height: 34px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--c-text-muted); border: 1px solid var(--c-border);
}
.pagination a:hover { background: var(--c-bg-soft); }
.pagination .active,
.pagination .pagination__page.active { background: var(--c-primary); color: #fff; border-color: var(--c-primary); }
/*
 * UAT-DEFECT-047: Previous/Next controls, shared by every paginated
 * screen via render_pagination() (core/helpers.php). A disabled control
 * is a <span aria-disabled="true">, never a clickable <a> to page 0/past
 * the last page -- visually dimmed and non-interactive rather than
 * simply hidden, so its position in the control group stays stable.
 */
.pagination__previous, .pagination__next { color: var(--c-text-muted); }
.pagination__previous[aria-disabled="true"],
.pagination__next[aria-disabled="true"] { opacity: .4; cursor: not-allowed; background: var(--c-bg-soft); }

/* Human UAT (Isi Penilaian readiness/authorization gate) — generic
   disabled-button visual language, reusing the SAME opacity/cursor
   convention the pagination controls above already established (never a
   new one-off style). Applies to a real <button disabled> as well as an
   <a> standing in for a disabled action (via aria-disabled, since <a> has
   no native disabled attribute) -- pointer-events:none additionally
   prevents an aria-disabled <a> from still being clickable/navigable. */
.btn[disabled], .btn.is-disabled, .btn[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; pointer-events: none; filter: grayscale(0.3);
}

.form-card { background: var(--c-surface); border-radius: var(--radius-lg); border: 1px solid var(--c-border); box-shadow: var(--shadow-sm); padding: 26px; max-width: 720px; margin-bottom: 24px; }
/* UAT-DEFECT-073 — Isi Penilaian's own content (Live Preview cards, Post-Test
   category table, the Assessment criteria table) genuinely needs the full
   available desktop width and was being squeezed into the generic 720px
   single-column form cap meant for simple narrow forms (login, quick edits).
   A dedicated modifier avoids widening EVERY other .form-card on the site. */
.form-card--wide { max-width: 1100px; }
.role-checklist { display: flex; flex-wrap: wrap; gap: 10px; }
.role-checkbox {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 14px; border: 1.5px solid var(--c-border); border-radius: 999px;
  font-size: 13px; cursor: pointer; transition: all var(--transition);
}
.role-checkbox:has(input:checked) { background: rgba(0,104,133,0.08); border-color: var(--c-primary); color: var(--c-primary); font-weight: 600; }

/* Permission Matrix (Role Management) */
.matrix-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
.matrix-module {
  border: 1.5px solid var(--c-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--c-bg-soft);
}
.matrix-module-header {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 12px 16px;
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-border);
  font-weight: 700; font-size: 13.5px;
}
.matrix-module-header i { color: var(--c-secondary); margin-right: 8px; }
.matrix-select-all { font-size: 11.5px; font-weight: 600; color: var(--c-secondary); cursor: pointer; background: none; border: none; }
.matrix-select-all:hover { color: var(--c-primary); }
.matrix-permissions { padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; }
.matrix-permission-item { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.matrix-permission-item input { width: 16px; height: 16px; accent-color: var(--c-primary); flex-shrink: 0; }
.matrix-permission-item label { cursor: pointer; }
.matrix-permission-item small { display: block; color: var(--c-text-muted); font-size: 11px; }
.matrix-empty { padding: 12px 16px; font-size: 12.5px; color: var(--c-text-muted); }

.role-level-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  background: rgba(0,104,133,0.1); color: var(--c-primary);
  font-size: 11.5px; font-weight: 700;
}
.role-protected { color: var(--c-warning); font-size: 11.5px; display: inline-flex; align-items: center; gap: 5px; }

/* =====================================================================
   ROADMAP MODULE — wallboard, channel drill-down, status badges
   ===================================================================== */
.roadmap-topbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; }
.roadmap-clock { text-align: right; }
.roadmap-clock .rc-date { font-size: 13px; color: var(--c-text-muted); }
.roadmap-clock .rc-time { font-size: 20px; font-weight: 800; font-family: var(--font-display); color: var(--c-primary); }
.btn-toggle-slideshow.active { background: var(--c-danger); color: #fff; }
.btn-toggle-slideshow.active .dot { background: #fff; }
.btn-toggle-slideshow .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c-danger); display: inline-block; margin-right: 6px; animation: pulseDot 1.4s infinite; }
@keyframes pulseDot { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }

.rm-status { display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border-radius: 999px; font-size: 11px; font-weight: 700; }
.rm-status i { font-size: 7px; }
.rm-status--ongoing { background: rgba(0,170,173,0.14); color: #007f82; }
.rm-status--upcoming { background: rgba(249,157,31,0.16); color: #b9740c; }
.rm-status--waiting { background: rgba(249,157,31,0.10); color: #b9740c; opacity: 0.8; }
.rm-status--completed { background: rgba(31,169,127,0.14); color: #178a68; }
.rm-status--cancel { background: rgba(227,69,58,0.14); color: var(--c-danger); }
.rm-status--uncompleted { background: rgba(92,115,120,0.14); color: var(--c-text-muted); }
.rm-status--none { background: transparent; color: var(--c-border); }

.channel-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px; }
.channel-card {
  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-lg);
  padding: 18px; cursor: pointer; transition: transform var(--transition), box-shadow var(--transition);
  box-shadow: var(--shadow-sm);
  /* Komponen ini selalu dipakai sebagai <a> (link card) — sebelumnya setiap
     pemanggil menambahkan text-decoration/color/display via inline style
     yang identik di setiap tempat. Dipindah ke sini sekali untuk semua. */
  text-decoration: none; color: inherit; display: block;
}
.channel-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--c-secondary); }
.channel-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.channel-card-head strong { font-size: 16px; font-family: var(--font-display); }
.channel-card-nums { display: flex; gap: 16px; }
.channel-card-nums div { text-align: center; }
.channel-card-nums .n { font-size: 20px; font-weight: 800; font-family: var(--font-display); display: block; }
.channel-card-nums .l { font-size: 10.5px; color: var(--c-text-muted); text-transform: uppercase; letter-spacing: 0.4px; }
.channel-card-nums .n.total { color: var(--c-text); }
.channel-card-nums .n.completed { color: var(--c-success); }
.channel-card-nums .n.uncompleted { color: var(--c-accent); }

.month-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.month-card {
  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-md);
  padding: 14px; cursor: pointer; transition: transform var(--transition), box-shadow var(--transition);
}
.month-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--c-secondary); }
.month-card-head { display: flex; justify-content: space-between; font-size: 12.5px; font-weight: 700; margin-bottom: 8px; }
.month-card-head .m-name { color: var(--c-primary); }
.month-progress { height: 6px; border-radius: 999px; background: var(--c-bg-soft); overflow: hidden; margin-top: 8px; }
.month-progress-bar { height: 100%; background: var(--gradient-brand); border-radius: 999px; transition: width 0.4s ease; }

.te-list { display: flex; flex-direction: column; gap: 12px; max-height: 620px; overflow-y: auto; }
.te-card { background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-md); padding: 14px; cursor: pointer; transition: box-shadow var(--transition); }
.te-card:hover { box-shadow: var(--shadow-sm); border-color: var(--c-secondary); }
.te-card-tags { display: flex; gap: 6px; margin-bottom: 8px; flex-wrap: wrap; }
.te-tag { font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: rgba(0,104,133,0.08); color: var(--c-primary); text-transform: uppercase; }
.te-card-title { font-weight: 700; font-size: 13.5px; margin-bottom: 3px; }
.te-card-meta { font-size: 11.5px; color: var(--c-text-muted); display: flex; flex-direction: column; gap: 2px; }
.te-card-meta span { display: flex; align-items: center; gap: 6px; }

/* Generic modal (dipakai untuk semua popup detail/list roadmap) */
.rm-modal { display: none; position: fixed; inset: 0; background: rgba(10,30,38,0.55); backdrop-filter: blur(3px); z-index: 100; align-items: center; justify-content: center; padding: 20px; }
.rm-modal.show { display: flex; animation: fadeIn 0.2s ease; }
.rm-modal-card { background: var(--c-surface); border-radius: var(--radius-lg); padding: 28px; max-width: 640px; width: 100%; max-height: 85vh; overflow-y: auto; position: relative; box-shadow: var(--shadow-lg); }
.rm-modal-card.rm-modal-lg { max-width: 820px; }
.rm-modal-close { position: absolute; right: 18px; top: 16px; font-size: 22px; color: var(--c-text-muted); cursor: pointer; background: none; border: none; }
.rm-modal-title { font-size: 19px; margin-bottom: 4px; padding-right: 30px; }
.rm-modal-sub { color: var(--c-text-muted); font-size: 13px; margin-bottom: 18px; }
.rm-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 20px; }
.rm-detail-item label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; color: var(--c-text-muted); font-weight: 700; margin-bottom: 3px; }
.rm-detail-item div { font-size: 14px; }
.rm-list-item { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--c-border); border-radius: var(--radius-sm); margin-bottom: 8px; cursor: pointer; transition: background var(--transition); }
.rm-list-item:hover { background: var(--c-bg-soft); }
.rm-list-item .rli-main strong { display: block; font-size: 13.5px; }
.rm-list-item .rli-main small { color: var(--c-text-muted); font-size: 11.5px; }
.rm-loading { text-align: center; padding: 30px; color: var(--c-text-muted); }
.rm-loading i { font-size: 22px; margin-bottom: 8px; display: block; }

/* Channel Roadmap page — grid 12 bulan per program */
.rm-program-row { background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-md); padding: 16px; margin-bottom: 14px; }
.rm-program-head { margin-bottom: 12px; }
.rm-program-head .rp-tags { display: flex; gap: 6px; margin-bottom: 6px; }
.rm-program-head strong { font-size: 15px; display: block; }
.rm-program-head small { color: var(--c-text-muted); }
.rm-month-strip { display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px; }
.rm-month-cell {
  border: 1px solid var(--c-border); border-radius: 8px; padding: 8px 4px; text-align: center; cursor: pointer;
  transition: transform var(--transition);
}
.rm-month-cell:hover { transform: translateY(-2px); }
.rm-month-cell .mc-m { font-size: 10px; font-weight: 700; color: var(--c-text-muted); }
.rm-month-cell .mc-s { font-size: 9px; font-weight: 700; margin-top: 3px; }

@media (max-width: 700px) {
  .rm-month-strip { grid-template-columns: repeat(4, 1fr); }
  .rm-detail-grid { grid-template-columns: 1fr; }
}

/* Login master layout — diselaraskan dengan master-login-layout.zip dan
   anotasi visual master-login-layout.jpg. */
.jla-body .jla-page {
  padding: 32px 20px;
  background: linear-gradient(160deg, #EEF8FA 0%, #E3F2F7 38%, #E9F1FB 70%, #F5FAFF 100%);
}
.jla-body .jla-bg-blob { animation: jlaMasterFloat 16s ease-in-out infinite; }
.jla-body .jla-bg-blob--1 { background: radial-gradient(circle at 30% 30%, #00AAAD, transparent 70%); opacity: .45; }
.jla-body .jla-bg-blob--2 { background: radial-gradient(circle at 60% 40%, #F99D1F, transparent 70%); opacity: .28; animation-delay: -4s; }
.jla-body .jla-bg-blob--3 { background: radial-gradient(circle at 50% 50%, #F15A22, transparent 70%); opacity: .16; animation-delay: -8s; }
.jla-body .jla-shell {
  max-width: 980px;
  min-height: 600px;
  grid-template-columns: 42% 58%;
  border-radius: 28px;
  box-shadow: 0 40px 80px -30px rgba(0,72,92,.35), 0 15px 30px -15px rgba(0,72,92,.25);
}
.jla-body .jla-brand { padding: 40px 36px; background: linear-gradient(155deg, #006885 0%, #00AAAD 100%); }
.jla-body .jla-logo-img { display:block; width:100%; max-width:220px; height:auto; max-height:64px; object-fit:contain; object-position:left center; margin-bottom:64px; filter:drop-shadow(0 4px 10px rgba(0,0,0,.15)); }
.jla-body .jla-brand-title { font-size: 30px; line-height: 1.25; margin: 0 0 16px; }
.jla-body .jla-brand-copy { max-width: 320px; font-size: 13.5px; line-height: 1.7; }
.jla-body .jla-ring { width: 280px; height: 280px; right: -90px; top: auto; bottom: -60px; animation: jlaMasterSpin 40s linear infinite; }
.jla-body .jla-blob--a { width: 180px; height: 180px; top: -50px; right: -50px; background: rgba(249,157,31,.35); filter: blur(30px); animation: jlaMasterFloat 14s ease-in-out infinite; }
.jla-body .jla-blob--b { width: 140px; height: 140px; bottom: 100px; right: -40px; left: auto; background: rgba(241,90,34,.28); filter: blur(24px); animation: jlaMasterFloat 18s ease-in-out infinite -5s; }
.jla-body .jla-blob--c { width: 90px; height: 90px; bottom: -20px; left: 20%; right: auto; background: rgba(255,255,255,.18); filter: blur(18px); animation: jlaMasterFloat 12s ease-in-out infinite -2s; }
.jla-body .jla-form-panel { padding: 44px 48px; }
.jla-body .jla-form-wrap { max-width: 360px; }
.jla-body .jla-corporate-logo { top: 30px; right: 36px; max-width: 120px; max-height: 80px; }
.jla-body .jla-input-shell input { background: #F6FBFC; }
.jla-body .jla-btn-group { width: 210px; margin: 0 auto; }
.jla-body .jla-btn--primary { background: linear-gradient(120deg, #006885 0%, #00AAAD 100%); box-shadow: 0 12px 24px -10px rgba(0,104,133,.55); }
.jla-body .jla-socials a { border-radius: 10px; border: 1px solid rgba(255,255,255,.35); }

@keyframes jlaMasterFloat {
  0%, 100% { transform: translate(0,0) scale(1); }
  50% { transform: translate(20px,-24px) scale(1.06); }
}
@keyframes jlaMasterSpin { to { transform: rotate(360deg); } }

@media (max-width: 860px) {
  .jla-body .jla-shell { display: flex; flex-direction: column; max-width: 440px; min-height: 0; }
  .jla-body .jla-brand { padding: 30px 28px; }
  .jla-body .jla-logo-img { height: auto; max-height: 36px; margin-bottom: 22px; }
  .jla-body .jla-form-panel { padding: 84px 26px 38px; }
}


/* =====================================================================
   AUTH / LOGIN PAGE — structure ported from BEKAL Portal (JLA)
   ===================================================================== */
.jla-body { margin: 0; background: var(--c-bg); }

.jla-page {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  position: relative;
  overflow: hidden;
  background: var(--gradient-page);
}

.jla-bg-blob { position: absolute; border-radius: 50%; filter: blur(50px); opacity: 0.28; z-index: 0; }
.jla-bg-blob--1 { width: 420px; height: 420px; background: var(--c-secondary); top: -140px; left: -120px; }
.jla-bg-blob--2 { width: 380px; height: 380px; background: var(--c-accent); bottom: -160px; right: -100px; opacity: 0.18; }
.jla-bg-blob--3 { width: 260px; height: 260px; background: var(--c-warning); top: 45%; right: 8%; opacity: 0.14; }

.jla-shell {
  position: relative; z-index: 1;
  width: 100%; max-width: 1080px;
  display: grid; grid-template-columns: 1.05fr 1fr;
  background: var(--c-surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  min-height: 640px;
}

.jla-brand {
  background: var(--gradient-brand);
  color: #fff;
  position: relative;
  overflow: hidden;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 44px 48px;
}

.jla-brand-art { position: absolute; inset: 0; z-index: 0; }
.jla-blob { position: absolute; border-radius: 50%; filter: blur(4px); }
.jla-blob--a { width: 260px; height: 260px; background: rgba(255,255,255,0.10); top: -80px; right: -60px; }
.jla-blob--b { width: 200px; height: 200px; background: var(--gradient-accent); opacity: 0.30; bottom: -70px; left: -50px; animation: floatSlow 9s ease-in-out infinite; }
.jla-blob--c { width: 120px; height: 120px; background: rgba(255,255,255,0.08); bottom: 30%; right: 8%; }
.jla-ring { position: absolute; width: 260px; height: 260px; right: -60px; top: 30%; opacity: 0.6; }

@keyframes floatSlow {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-18px) scale(1.04); }
}

.jla-brand-mid {
  position: relative; z-index: 1; max-width: 380px;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.jla-logo-img { height: auto; width: 100%; max-width: 220px; max-height: 40px; object-fit: contain; margin-bottom: 34px; }
.jla-brand-content { position: relative; z-index: 1; }
.jla-brand-logo { height: 80px; width: auto; max-width: 220px; object-fit: contain; margin-bottom: 24px; display: block; }
.jla-brand-tagline { font-size: 22px; line-height: 1.35; color: #fff; margin: 0 0 6px; font-family: var(--font-display); }
.jla-corporate-logo {
  position: absolute;
  top: 30px;
  right: 36px;
  display: block;
  max-width: 120px;
  width: auto;
  height: auto;
  max-height: 80px;
  object-fit: contain;
  margin: 0;
}

.jla-eyebrow { font-size: 11.5px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: rgba(255,255,255,0.72); margin: 0 0 10px; }
.jla-eyebrow--dark { color: var(--c-secondary); }

.jla-brand-title { position: relative; font-size: 34px; margin: 0 0 16px; font-family: var(--font-display); }
.jla-title-glow { position: absolute; width: 140px; height: 60px; background: var(--gradient-accent); opacity: 0.35; filter: blur(24px); left: -20px; top: -10px; }
.jla-title-text { position: relative; }

.jla-brand-copy { position: relative; z-index: 1; color: rgba(255,255,255,0.85); line-height: 1.7; font-size: 14.5px; margin: 0; }

.jla-brand-bottom { position: relative; z-index: 1; }
.jla-socials { display: flex; gap: 12px; margin-bottom: 16px; }
.jla-socials a {
  width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.12); color: #fff;
  transition: background var(--transition), transform var(--transition);
}
.jla-socials a:hover { background: var(--c-accent); transform: translateY(-3px); }
.jla-copyright { font-size: 12px; color: rgba(255,255,255,0.65); margin: 0; }

.jla-form-panel { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px; position: relative; }
.jla-form-wrap { width: 100%; max-width: 380px; padding-top: 8px; }

.jla-form-header { margin-bottom: 28px; }
.jla-form-header h2 { font-size: 27px; margin: 0 0 8px; color: var(--c-primary); }
.jla-form-sub { color: var(--c-text-muted); margin: 0; font-size: 14px; }

.jla-field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 18px; }
.jla-field-label { font-size: 13.5px; font-weight: 600; }
.jla-input-shell { position: relative; display: flex; align-items: center; }
.jla-input-shell input,
.jla-input-shell select {
  width: 100%; padding: 12px 14px 12px 42px; border-radius: var(--radius-sm);
  border: 1.5px solid var(--c-border); font-size: 14.2px; background: var(--c-surface); color: var(--c-text);
}
.jla-input-shell input:focus,
.jla-input-shell select:focus { border-color: var(--c-secondary); box-shadow: 0 0 0 4px rgba(0,170,173,0.15); outline: none; }
.jla-input-shell select { padding-right: 32px; cursor: pointer; appearance: auto; }
.jla-input-icon { position: absolute; left: 14px; color: var(--c-text-muted); font-size: 14px; }
.jla-eye-toggle { position: absolute; right: 14px; color: var(--c-text-muted); font-size: 14px; cursor: pointer; }
.jla-input-shell--modal { margin-bottom: 6px; }

.jla-field-row { display: flex; justify-content: flex-end; margin-bottom: 18px; }

.jla-action-row { display: flex; justify-content: flex-end; margin-bottom: 18px; }
.jla-btn-group { display: flex; flex-direction: column; gap: 10px; }
/* UAT-DEFECT-050 Part B (Asesi Login redesign): the identity-confirmation
   step's "Kembali"/"Ya, Lanjutkan" pair sits side-by-side, unlike the
   single-button `.jla-btn-group` the internal login uses -- overrides
   `body.jla-body .jla-btn-group`'s fixed 210px width (that constraint
   is for a single centered button, not a two-button row). */
body.jla-body .jla-btn-group--split { display: flex; flex-direction: row; width: 100%; max-width: 360px; margin: 0 auto; gap: 10px; }
.jla-btn-group--split .jla-btn--ghost { margin-top: 0; border: 1.5px solid var(--c-border); }

/* UAT-DEFECT-050 Part B: the participant identity-confirmation step's
   responsive key/value summary -- replaces a `.data-table` (min-width
   720px, designed for wide admin list pages with a `.table-wrap` scroll
   ancestor) that never fit inside this narrower login card, per Human
   UAT's "too narrow / table-like" finding. Desktop: label/value on one
   row. Mobile (see media query further down): stacked, so long values
   (email, position, region) wrap instead of clipping or forcing
   horizontal scroll. */
.jla-detail-grid { margin: 0 0 20px; border-top: 1px solid var(--c-border); }
.jla-detail-row {
  display: grid; grid-template-columns: 120px 1fr; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--c-border);
}
.jla-detail-row dt { font-size: 12.5px; font-weight: 600; color: var(--c-text-muted); }
.jla-detail-row dd { margin: 0; font-size: 14px; color: var(--c-text); overflow-wrap: anywhere; word-break: break-word; }
.jla-footnote-link { color: var(--c-secondary); font-weight: 600; }
.jla-footnote-link:hover { color: var(--c-primary); }

.jla-link { color: var(--c-secondary); font-weight: 600; font-size: 13.5px; }
.jla-link:hover { color: var(--c-primary); }

.jla-message { font-size: 13px; margin-bottom: 4px; min-height: 0; }
.jla-message:not(:empty) { padding: 11px 14px; border-radius: var(--radius-sm); background: rgba(227,69,58,0.08); color: var(--c-danger); margin-bottom: 16px; }

.jla-btn {
  width: 100%; padding: 13px 22px; border-radius: var(--radius-sm); border: none;
  font-weight: 700; font-size: 14.5px; cursor: pointer; font-family: var(--font-body);
  transition: transform var(--transition), box-shadow var(--transition), background var(--transition);
}
.jla-btn--primary { background: var(--gradient-accent); color: #fff; box-shadow: 0 8px 20px rgba(241,90,34,0.34); }
.jla-btn--primary:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(241,90,34,0.44); }
.jla-btn--ghost { background: transparent; color: var(--c-text-muted); margin-top: 8px; }
.jla-btn--ghost:hover { background: var(--c-bg); }

.jla-footnote { text-align: center; font-size: 13px; color: var(--c-text-muted); margin: 22px 0 0; }
.jla-footnote-span { text-align: center; font-size: 13px; color: var(--c-text-muted); margin: 2px 0 0; }

/* Modals (OTP / forgot password) */
.jla-modal { display: none; position: fixed; inset: 0; background: rgba(10,30,38,0.55); backdrop-filter: blur(3px); z-index: 100; align-items: center; justify-content: center; padding: 20px; }
.jla-modal.show { display: flex; animation: fadeIn 0.2s ease; }
.jla-modal-card { background: var(--c-surface); border-radius: var(--radius-lg); padding: 34px 30px; max-width: 400px; width: 100%; text-align: center; position: relative; box-shadow: var(--shadow-lg); }
.jla-modal-close { position: absolute; right: 18px; top: 14px; font-size: 22px; color: var(--c-text-muted); cursor: pointer; }
.jla-modal-icon { width: 60px; height: 60px; border-radius: 50%; background: rgba(0,104,133,0.10); color: var(--c-primary); display: flex; align-items: center; justify-content: center; font-size: 24px; margin: 0 auto 18px; }
.jla-modal-icon--success { background: rgba(31,169,127,0.12); color: var(--c-success); }
.jla-modal-card h2 { font-size: 20px; margin-bottom: 8px; }
.jla-modal-card p { font-size: 13.5px; color: var(--c-text-muted); margin: 0 0 20px; }
.jla-modal-input { width: 100%; padding: 12px 14px; border-radius: var(--radius-sm); border: 1.5px solid var(--c-border); font-size: 14px; margin-bottom: 14px; text-align: center; }
.jla-otp-input { font-size: 22px; letter-spacing: 10px; font-weight: 700; }
.jla-otp-timer { font-size: 12.5px; color: var(--c-text-muted); margin-bottom: 12px; }
.jla-hint { font-size: 11.5px !important; color: var(--c-text-muted); margin: -8px 0 16px !important; }

/* =====================================================================
   ERROR PAGES (403 / 404)
   ===================================================================== */
.error-body {
  margin: 0; min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  background: var(--gradient-brand);
  padding: 20px;
  position: relative;
  overflow: hidden;
}
.error-box {
  background: var(--c-surface);
  border-radius: var(--radius-xl);
  padding: 52px 44px;
  text-align: center;
  max-width: 440px;
  box-shadow: var(--shadow-lg);
  position: relative; z-index: 1;
}
.error-box .error-icon { font-size: 30px; color: var(--c-accent); margin-bottom: 12px; }
.error-code {
  font-size: 66px;
  font-weight: 800;
  font-family: var(--font-display);
  background: var(--gradient-accent);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  line-height: 1;
}
.error-box p { color: var(--c-text-muted); margin: 12px 0 26px; }

/* =====================================================================
   UTILITY CLASSES
   Set kecil & sengaja dibatasi — hanya untuk pola spacing/layout yang
   sebelumnya berulang sebagai inline style identik di puluhan file
   (lihat UI-CONSISTENCY-CHANGELOG.md). Bukan utility-first framework;
   komponen tetap jadi pendekatan utama, ini cuma untuk micro-layout.
   ===================================================================== */
.d-flex { display: flex; }
.d-inline-flex { display: inline-flex; }
.flex-1 { flex: 1; }
.flex-wrap { flex-wrap: wrap; }
.gap-8 { gap: 8px; }
.gap-10 { gap: 10px; }
.gap-12 { gap: 12px; }
.cursor-pointer { cursor: pointer; }
.overflow-x-auto { overflow-x: auto; }
.text-uppercase { text-transform: uppercase; }
.fw-700 { font-weight: 700; }
.max-w-100 { max-width: 100%; }
/* Komponen semantik: baris tombol aksi form (Simpan/Batal, dst) — sebelumnya
   berulang sebagai kombinasi inline style / utility class identik di 15 file. */
.form-actions { display: flex; gap: 12px; margin-top: 24px; }

/* UAT-DEFECT-053 (Participant Attendance Camera UX): a camera-centric
   Bukti Absensi component -- the live/idle/captured viewport IS the
   capture surface (click/tap/Enter/Space), replacing the previous
   button-only "Aktifkan Kamera / Ambil Foto / Ambil Ulang" row. Normal
   flow (flexbox) throughout; the only absolute positioning is for the
   two small overlays INSIDE the viewport itself (the capture hint and
   the captured badge), which inherently sit on top of the video/photo
   -- never used to fake the page's own layout structure. */
.camera-viewport-shell { max-width: 480px; margin: 0 auto 14px; }
.camera-idle-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  padding: 48px 20px; border: 2px dashed var(--c-border); border-radius: var(--radius-md);
  background: var(--c-bg-soft); color: var(--c-text-muted); text-align: center;
}
.camera-idle-state i { font-size: 32px; opacity: 0.55; }
.camera-idle-state p { margin: 0; font-size: 13.5px; }
.camera-viewport {
  /* UAT REMEDIATION (camera preview blank after capture): height was
     previously derived ONLY from the in-flow <video> element's own
     rendered (aspect-ratio-based) height -- the <canvas> is
     `position:absolute; inset:0` and cannot establish a height on its
     own. On capture, `video.hidden = true` removes the video from flow
     entirely, so the container collapsed to ~0px height and the
     absolutely-positioned canvas (and its drawn photo) collapsed/clipped
     with it, even though drawImage()/toDataURL() succeeded -- the toast
     said success but nothing was visible. An explicit aspect-ratio makes
     the container's height independent of which child (video or canvas)
     is currently in flow/visible. */
  position: relative; border-radius: var(--radius-md); overflow: hidden; background: #000;
  cursor: pointer; line-height: 0; aspect-ratio: 4 / 3;
}
.camera-viewport:focus-visible { outline: 3px solid var(--c-secondary); outline-offset: 2px; }
.camera-viewport video, .camera-viewport canvas { width: 100%; height: 100%; display: block; object-fit: cover; }
.camera-viewport canvas { position: absolute; inset: 0; }
.camera-capture-hint {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 12px 10px 10px;
  background: linear-gradient(transparent, rgba(0,0,0,.62)); color: #fff; font-size: 13px;
  display: flex; align-items: center; justify-content: center; gap: 6px; pointer-events: none;
}
.camera-viewport.is-captured .camera-capture-hint { display: none; }
.camera-captured-badge {
  position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,.6); color: #fff;
  padding: 6px 11px; border-radius: 999px; font-size: 12px; display: flex; align-items: center; gap: 6px;
}
.camera-actions { display: flex; justify-content: center; gap: 10px; }
.attendance-submit-row { display: flex; justify-content: flex-end; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--c-border); }
@media (max-width: 560px) {
  .attendance-submit-row { justify-content: stretch; }
  .attendance-submit-row .btn { width: 100%; }
}
.required-mark { color: #D93025; font-weight: 800; }
.dummy-module-page { min-height: calc(100vh - 210px); display:grid; place-items:center; text-align:center; }
.dummy-module-page h2 { font-size:clamp(24px,4vw,42px); color:var(--c-primary); }
.dummy-module-page p { margin:8px 0 0; font-size:clamp(16px,2vw,24px); color:var(--c-text-muted); }
/* Modern minimalist outline treatment with consistent solid icon glyphs. */
.btn-icon { border:1px solid var(--c-border); background:rgba(255,255,255,.72); font-size:14px; }
.menu-link i.fa-solid, .menu-link i.fa-regular { font-size:15px; }
/* Sel placeholder "sedang memuat" di dalam tbody yang diisi ulang via JS. */
.table-loading-cell { text-align: center; padding: 24px; }
.span-all { grid-column: 1 / -1; }

.m-0 { margin: 0; }
.mt-0 { margin-top: 0; }
.mt-10 { margin-top: 10px; }
.mt-14 { margin-top: 14px; }
.mt-16 { margin-top: 16px; }
.mt-20 { margin-top: 20px; }
.mt-24 { margin-top: 24px; }
.mb-14 { margin-bottom: 14px; }
.mb-16 { margin-bottom: 16px; }
.mb-18 { margin-bottom: 18px; }
.mb-20 { margin-bottom: 20px; }
.mb-22 { margin-bottom: 22px; }

/* =====================================================================
   MICRO ANIMATIONS
   ===================================================================== */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.fade-in { animation: fadeIn 0.35s ease; }

@keyframes fadeInUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
.fade-in-up { animation: fadeInUp 0.5s ease; }

/* =====================================================================
   RESPONSIVE / MOBILE
   ===================================================================== */
@media (max-width: 1024px) {
  .jla-shell { grid-template-columns: 1fr; max-width: 460px; min-height: auto; }
  .jla-brand { padding: 32px 28px; min-height: auto; }
  .jla-brand-mid { flex: none; }
  .jla-brand-logo { margin-bottom: 16px; }
  .jla-brand-tagline { font-size: 19px; }
  .jla-brand-bottom { margin-top: 22px; }
  .jla-corporate-logo {
    position: static;
    align-self: flex-end;
    margin: 0 0 20px;
  }
  .jla-form-panel { padding: 32px 28px; }
  .jla-form-wrap { padding-top: 0; }
}

/* UAT-DEFECT-050 Part B: below 560px, stack the identity-confirmation
   label/value rows and the Kembali/Ya-Lanjutkan button pair -- never a
   cramped two-column squeeze on a phone-width screen. */
@media (max-width: 560px) {
  .jla-detail-row { grid-template-columns: 1fr; gap: 2px; }
  body.jla-body .jla-btn-group--split { flex-direction: column; max-width: 100%; }
}

@media (max-width: 900px) {
  /* Mobile = Burger Menu (brief §6): sidebar melayang di atas konten lewat
     overlay, jadi main-area TIDAK boleh ikut ter-dorong walau body punya
     class .sidebar-open (beda dari desktop, lihat body.sidebar-open
     .main-area di atas). Overlay-nya sendiri cuma relevan di breakpoint
     ini — desktop tidak pernah menampilkan overlay gelap di belakang sidebar. */
  body.sidebar-open .main-area { margin-left: 0; }
  body.sidebar-open .sidebar-overlay { display: block; animation: fadeIn 0.2s ease; }
  .content { padding: 20px; }
  .topbar { padding: 0 16px; }
  .topbar-logo { display: none; }
  #toggleSidebar { display: flex; }
}

@media (max-width: 560px) {
  .stat-grid { grid-template-columns: 1fr 1fr; }
  .stat-card { padding: 14px; flex-direction: column; align-items: flex-start; gap: 10px; }
  .stat-value { font-size: 21px; }
  .jla-form-panel { padding: 24px; }
  .content { padding: 16px; }
  .topbar-title { font-size: 16px; }
  .error-box { padding: 32px 24px; }
  .toolbar { flex-direction: column; align-items: stretch; }
}

@media (max-width: 380px) {
  .stat-grid { grid-template-columns: 1fr; }
}

/* Health Check status chips */
.chip-success { background: rgba(26,156,92,0.12); color: #1a9c5c; }
.chip-warning { background: rgba(249,157,31,0.14); color: #b9740c; }
.chip-danger  { background: rgba(217,67,63,0.12); color: #d9433f; }

/* =========================================================
   FOOTER — MOBILE CENTERED LAYOUT
   ========================================================= */
@media (max-width: 768px) {

  .app-footer .footer-container {
    padding: 0 20px;
    align-items: center;
    text-align: center;
  }

  /* Row utama: brand + tagline */
  .app-footer .footer-row1 {
    width: 100%;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    gap: 14px;
  }

  /* Logo */
  .app-footer .footer-brand {
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    width: 100%;
  }

  .app-footer .footer-brand img {
    height: 24px;
    width: auto;
  }

  /* Tagline */
  .app-footer .footer-tagline {
    width: 100%;
    text-align: center;
  }

  .app-footer .footer-tagline p {
    text-align: center;
  }

  /* Divider */
  .app-footer .footer-divider {
    width: 100%;
  }

  /* Social media */
  .app-footer .footer-socials {
    width: 100%;
    justify-content: center;
    align-items: center;
  }

  /* Bottom copyright + bantuan */
  .app-footer .footer-bottom {
    width: 100%;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    gap: 8px;
    padding-top: 14px;
  }

  .app-footer .footer-bottom > span {
    width: 100%;
    text-align: center;
  }

  .app-footer .footer-legal {
    justify-content: center;
    align-items: center;
    width: 100%;
  }

  .app-footer .footer-legal a {
    text-align: center;
  }

}

/* Final responsive guard untuk login. Diletakkan paling akhir agar tidak
   ditimpa breakpoint komponen aplikasi yang lebih lama. */
@media (max-width: 1024px) {
  .jla-body .jla-page { min-height: 100svh; padding: 24px 16px; overflow-y: auto; }
  .jla-body .jla-shell { display: flex; flex-direction: column; width: min(100%, 520px); min-height: 0; }
  .jla-body .jla-brand { flex: none; min-height: 220px; padding: 28px 30px 24px; }
  .jla-body .jla-brand-mid { display: block; }
  .jla-body .jla-logo-img { height: auto; max-height: 40px; max-width: 190px; margin-bottom: 22px; }
  .jla-body .jla-brand-title { font-size: 25px; margin-bottom: 8px; }
  .jla-body .jla-brand-copy { margin: 0; font-size: 12.5px; }
  .jla-body .jla-brand-bottom { margin-top: 20px; flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; }
  .jla-body .jla-socials { margin: 0; }
  .jla-body .jla-form-panel { padding: 82px 34px 38px; }
  .jla-body .jla-corporate-logo { position: absolute; top: 24px; right: 28px; max-width: 104px; max-height: 54px; margin: 0; }
}

@media (max-width: 560px) {
  .jla-body .jla-page { align-items: flex-start; padding: 12px; }
  .jla-body .jla-shell { border-radius: 20px; }
  .jla-body .jla-brand { min-height: 180px; padding: 24px 22px 20px; }
  .jla-body .jla-logo-img { height: auto; max-height: 34px; margin-bottom: 16px; }
  .jla-body .jla-eyebrow { margin-bottom: 7px; }
  .jla-body .jla-brand-title { font-size: 22px; }
  .jla-body .jla-brand-copy { display: none; }
  .jla-body .jla-brand-bottom { margin-top: 14px; }
  .jla-body .jla-socials a { width: 32px; height: 32px; }
  .jla-body .jla-copyright { max-width: 180px; text-align: right; }
  .jla-body .jla-form-panel { padding: 76px 22px 30px; }
  .jla-body .jla-corporate-logo { top: 20px; right: 22px; max-width: 90px; max-height: 46px; }
  .jla-body .jla-form-header h2 { font-size: 23px; }
  .jla-body .jla-btn-group { width: min(210px, 100%); }
}

@media (max-width: 380px) {
  .jla-body .jla-brand-bottom { align-items: flex-end; }
  .jla-body .jla-copyright { font-size: 9.5px; }
  .jla-body .jla-form-panel { padding-inline: 18px; }
}

/* =====================================================================
   MODAL & TOAST — canonical global primitives (SCALE Modular WP-4)
   Document 13: no browser alert()/confirm()/prompt() anywhere; every
   confirmation/blocking-decision/error uses this centered, dark-blurred
   modal instead. Triggered via window.openModal()/closeModal()/showToast()
   /confirmAction() in app.js — loaded on every page via footer.php, so
   every module gets these for free.
   ===================================================================== */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(10,30,38,.55); backdrop-filter: blur(3px);
  display: none; align-items: center; justify-content: center; z-index: 200; padding: 20px;
}
.modal-overlay.active { display: flex; }
.modal-box, .modal-content {
  background: var(--c-surface); border-radius: var(--radius-lg);
  width: 100%; max-width: 520px; box-shadow: var(--shadow-lg);
  overflow: hidden; margin: 20px; max-height: 85vh; display: flex; flex-direction: column;
}
.modal-header { padding: 20px 24px; border-bottom: 1px solid var(--c-border); display: flex; align-items: center; justify-content: space-between; }
.modal-header h3 { margin: 0; font-size: 16px; font-weight: 700; }
.modal-body { padding: 22px 24px; overflow-y: auto; }
.modal-footer { padding: 14px 24px; border-top: 1px solid var(--c-border); display: flex; justify-content: flex-end; gap: 10px; }

.toast-stack { position: fixed; top: 20px; right: 20px; z-index: 300; display: flex; flex-direction: column; gap: 10px; }
.toast {
  background: var(--c-surface); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  padding: 12px 16px; min-width: 260px; max-width: 360px;
  display: flex; align-items: flex-start; gap: 10px;
  border-left: 4px solid var(--c-primary); font-size: 13.5px;
}
.toast.success { border-left-color: var(--c-success); }
.toast.error { border-left-color: var(--c-danger); }
.toast.warning { border-left-color: #b8790f; }
.toast.info { border-left-color: var(--c-primary); }
.toast.removing { opacity: 0; transition: opacity var(--transition); }

/* UAT REMEDIATION -- Isi Penilaian: dynamic radio scale (built from Setup
   Assessment's own Skala Min/Max, never hardcoded 1-4) replacing the
   previous unconstrained free numeric input. One row when the viewport has
   room, wraps to multiple rows only when it doesn't -- never a forced
   horizontal scroll just to read a normal score. */
.assessment-scale-table td { vertical-align: middle; }
.assessment-scale-radio-group {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.assessment-scale-radio-option {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; padding: 8px 12px; border: 1px solid var(--c-border); border-radius: var(--radius-sm);
  cursor: pointer; font-weight: 600; font-size: 14px; color: var(--c-text-muted);
  background: var(--c-surface); transition: border-color var(--transition), color var(--transition), background var(--transition);
  user-select: none;
}
.assessment-scale-radio-option input[type="radio"] {
  position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none;
}
.assessment-scale-radio-option:has(input:checked) {
  border-color: var(--c-primary); background: var(--c-primary); color: #fff;
}
.assessment-scale-radio-option:has(input:focus-visible) {
  outline: 3px solid var(--c-secondary); outline-offset: 2px;
}
@media (max-width: 560px) {
  .assessment-scale-radio-option { min-width: 36px; padding: 7px 10px; font-size: 13px; }
}

/* UAT REMEDIATION -- Rapor result visibility: a prominent, full-width
   Kompeten/Belum Kompeten callout placed right after Final Score, reusing
   the SAME semantic success/danger colors as .badge-active/.badge-suspended
   (never a new hardcoded color) at a visual weight that can't be missed. */
.result-status-callout {
  display: flex; align-items: center; gap: 14px; margin: 4px 0 20px;
  padding: 16px 22px; border-radius: var(--radius-md); font-size: 20px; font-weight: 700;
}
.result-status-callout i { font-size: 26px; flex-shrink: 0; }
.result-status-callout.status-competent { background: rgba(31,169,127,0.12); color: #178a68; border: 1px solid rgba(31,169,127,0.35); }
.result-status-callout.status-not-yet-competent { background: rgba(227,69,58,0.12); color: var(--c-danger); border: 1px solid rgba(227,69,58,0.35); }
.result-status-callout.status-incomplete { background: var(--c-bg-soft); color: var(--c-text-muted); border: 1px solid var(--c-border); }
@media (max-width: 560px) {
  .result-status-callout { font-size: 17px; padding: 14px 16px; }
  .result-status-callout i { font-size: 22px; }
}

/* UAT REMEDIATION -- Rapor detail: structured participant identity panel
   (Nama/NPP/Posisi/LOS/Wilayah/Outlet/Asesor/Difinalisasi), replacing the
   previous single loose text sentence. Desktop: readable 2-column grid.
   Mobile: collapses to 1 column. Long values (name/position/outlet/
   assessor) wrap -- never truncated, since this is authoritative identity
   data. */
.rapor-identity-card { margin-bottom: 16px; }
.rapor-identity-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 32px; padding: 14px 20px; margin: 0; }
.rapor-identity-row { display: flex; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--c-border); min-width: 0; }
.rapor-identity-row:last-child, .rapor-identity-row:nth-last-child(2) { border-bottom: none; }
.rapor-identity-row dt { flex: 0 0 140px; font-weight: 600; color: var(--c-text-muted); margin: 0; font-size: 13.5px; }
.rapor-identity-row dd { flex: 1; margin: 0; min-width: 0; overflow-wrap: break-word; word-break: break-word; }
@media (max-width: 640px) {
  .rapor-identity-grid { grid-template-columns: 1fr; }
  .rapor-identity-row:nth-last-child(2) { border-bottom: 1px solid var(--c-border); }
}

/* UAT-DEFECT-074 -- widened again (760px -> 920px): a real 5-column
   Post-Test table (Kategori/Bobot/Jumlah Soal/Benar/Skor) and a real
   4-column Assessment table (Dimensi/Bobot/Point/Skor) both need genuine
   desktop room to render without forcing `.table-wrap`'s own horizontal
   scrollbar on normal desktop widths -- still a bounded, readable maximum
   (never viewport-wide/full-screen); `.table-wrap`'s own `overflow-x:auto`
   remains the only defensive scroll boundary, reserved for genuinely
   narrow viewports. */
.rapor-modal-content { max-width: 920px; }

/* UAT-DEFECT-074 -- Modal Detail Rapor score-summary redesign. ROW 1 (the
   primary outcome, Final Score + Hasil Kompetensi) always renders as an
   exact 2-column grid; ROW 2 (the 3 component scores, in the LOCKED
   Pre-Test/Post-Test/Assessment order) always renders as an exact
   3-equal-column grid on normal desktop width -- both collapse to a single
   column only on genuinely narrow mobile viewports, never introducing
   horizontal scroll for the cards themselves. */
.rapor-primary-result-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 14px 0; }
.rapor-score-component-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin: 0 0 16px; }
/* The Final Score card is the single most important number on the report --
   given deliberately stronger visual weight (larger value type) than every
   other score card, per the explicit "primary outcome" requirement. */
.rapor-final-score-card .stat-value { font-size: 36px; }
/* Hasil Kompetensi reuses the SAME semantic success/danger treatment as
   .result-status-callout (never a new color), reshaped to sit as an equal-
   height card alongside Final Score -- icon + text together, never color
   alone, so the result reads correctly even in print-grayscale. */
.rapor-competency-card { display: flex; align-items: center; gap: 16px; height: 100%; box-sizing: border-box; }
.rapor-competency-card i { font-size: 30px; flex-shrink: 0; }
.rapor-competency-card .rapor-competency-label { display: block; font-size: 12.5px; font-weight: 600; opacity: .8; margin-bottom: 2px; }
.rapor-competency-card .rapor-competency-value { font-size: 22px; font-weight: 800; font-family: var(--font-display); }
@media (max-width: 640px) {
  .rapor-primary-result-grid, .rapor-score-component-grid { grid-template-columns: 1fr; }
}
