/* =========================================================================
   PeluangKampus.com — shared design system
   One stylesheet for the whole app (replaces 3 copies of inline CSS).
   Layered on top of Bootstrap 5. Typeface: Plus Jakarta Sans (Tokotype, ID).
   ========================================================================= */

:root {
  --primary: #2563eb;
  --primary-dark: #1d4ed8;
  --primary-deep: #1e40af;
  --primary-soft: #e6edfa;
  --accent: #0ea5a3;
  --accent-soft: #d9edeb;

  /* Cobalt is the one accent (tasteskill §4.2). Teal stays only as a chart's
     second series; green, amber and red only for state. Purple and pink are
     retired and resolve to cobalt, so config/quiz.json and older rules that
     still name them stop adding colours without breaking. */
  --c-blue: #2563eb;
  --c-purple: var(--primary);
  --c-teal: #0ea5a3;
  --c-amber: #f59e0b;
  --c-pink: var(--primary);
  --c-green: #16a34a;

  --warn: #f59e0b;
  --danger: #ef4444;
  /* The neutral ramp is warm grey rather than Slate, and it starts below paper
     white: one hue (LCH 75deg) at a chroma low enough to read as grey instead of
     cream, with the pale end pulled down about 4 L*. A white page under a white
     UI is what made this glare; the ink came warm to match, so the type reads as
     dark brown-grey on paper rather than navy on a lightbox. */
  --ink: #292521;
  --muted: #6b645c;
  --line: #e4ddd6;
  --surface: #fbf7f3;
  --bg: #f2ede7;

  /* The edge of a field. #878079 is 3.7:1 on --surface, 3.4:1 on --bg and 3.1:1
     on --surface-2, over the 3:1 a control's boundary needs (WCAG 1.4.11).
     --line is 1.3:1, which would leave every input a box with no visible edge.
     It came down a step with the ground: the old #7f8ea3 measured 2.9:1 on the
     sunk surface once that surface stopped being near-white. */
  --field-line: #878079;

  /* One corner system (tasteskill §4.4), followed everywhere: pills for things
     you press (buttons, tabs, toggles), 20px for cards and panels, 12px for
     inputs, tags and small tiles. The four old sizes keep their names so
     nothing that reads them breaks; they resolve to these two. */
  /* Cards went from 16px to 20px with the floating bar: under a capsule the
     tighter corner read as a different, older system. */
  --radius-sm: 0.75rem;
  --radius: 1.25rem;
  --radius-lg: 1.25rem;
  --radius-xl: 1.25rem;

  /* Shadows are cast in the ground's own family. A blue-black shadow on warm
     paper reads as a grey smudge, which is most of what made the old page feel
     cold; --shadow-rgb is the triplet every hand-written box-shadow uses too,
     so they all warm and darken together. */
  --shadow-rgb: 56, 45, 32;
  --shadow-sm: 0 2px 6px rgba(var(--shadow-rgb), 0.06);
  --shadow-md: 0 12px 30px rgba(var(--shadow-rgb), 0.09);
  --shadow-lg: 0 30px 70px rgba(var(--shadow-rgb), 0.13);
  --shadow-primary: 0 10px 22px rgba(37, 99, 235, 0.22);
}

* {
  font-family: "Plus Jakarta Sans", system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Global overflow guards: no horizontal scroll on mobile.

   The body clips rather than hides. `hidden` on both elements made the body a
   scroll container of its own (html's value goes to the viewport, so the
   body's stays on the body), one that never scrolls, and every
   `position: sticky` on the site was measured against it: the top bar, the
   report card beside its page, the card studio's controls all scrolled away
   with the page. `clip` cuts the overflow off without creating that
   container. A browser too old to know it keeps `hidden` and behaves as
   before. */
html, body { max-width: 100vw; }
html { overflow-x: hidden; }
body { overflow-x: hidden; overflow-x: clip; }

/* Where the top bar stays on screen, a jump to an anchor (or a script's
   scrollIntoView) stops clear of it. */
@media (min-width: 992px) { html { scroll-padding-top: 5.5rem; } }
body {
  background: var(--bg);
  color: var(--ink);
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-tap-highlight-color: transparent;
}

/* Force scrollbar always visible to prevent layout shift between pages. */
html { overflow-y: scroll; }

/* While a dialog is open (the sign-in dialog, pkConfirm, pkDialog) nothing
   behind it moves. Bootstrap's modal hides the body's overflow and pads the
   body by the scrollbar's width, for a scrollbar it expects to disappear. Here
   the scrollbar belongs to <html> and never did, so the padding pushed the
   whole page 6px left, and the page behind still scrolled with the wheel
   (measured 14 Oct 2026). So: the scrollbar's space is reserved for good
   (scrollbar-gutter) instead of by forcing a scrollbar, the scroll is stopped
   on <html> where it lives, and Bootstrap's padding is cancelled. A browser
   without scrollbar-gutter keeps the forced scrollbar and only loses the
   padding, which is what moved the page. */
@supports (scrollbar-gutter: stable) {
  html { overflow-y: auto; scrollbar-gutter: stable; }
  html:has(> body.modal-open) { overflow-y: hidden; }
}
body.modal-open { padding-right: 0 !important; }
/* Modernised scrollbar (still visible, just nicer). */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--surface-2); }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 6px; border: 2px solid var(--surface-2); }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }

h1, h2, h3, h4, h5, h6 { letter-spacing: -0.02em; }

/* Icons: Phosphor, one weight for the whole site (tasteskill §3.C). The
   font sits a touch high on a text baseline without this, the way
   Bootstrap Icons used to set it for us. */
.ph { vertical-align: -0.125em; }

a { color: var(--accent-ink); text-decoration: none; transition: color 0.2s ease; }
a:hover { color: var(--primary-dark); }

.text-primary { color: var(--accent-ink) !important; }
.text-secondary { color: var(--muted) !important; }
.bg-primary { background-color: var(--primary) !important; }

/* ---- Brand wordmark ---------------------------------------------------- */
.brand { font-weight: 800; letter-spacing: -0.03em; }
.brand .dot { color: var(--ink); }

/* =========================================================================
   NAVIGATION
   Three pieces, all here (views/partials/navbar.php, assets/js/nav.js).

     The bar    From 992px a capsule that floats under the top edge and stays
                there while the page passes behind it. Below that it is a
                plain row that scrolls away: a phone gets one pinned thing,
                and that is the dock.
     The dock   Phones: a floating capsule of four places, and beside it the
                analysis as a button of its own.
     The sheet  Phones: the rest, rising from the bottom edge.

   The floating surfaces share one material: the card colour let through, the
   page blurred behind it, a hairline edge and a lit top rim. This is the
   web's frosted glass (backdrop-filter), an approximation of the look and
   not anybody's official material. It turns solid on a slow phone (.pk-lite)
   and where the browser cannot blur, and nearly so for a reader who asked
   for less transparency.

   The bar keeps the space the old one took (71px on a wide screen, 62px on a
   phone), because other rules are sized to it.
   ========================================================================= */
:root {
  /* Clear glass (15 Oct 2026): the page's own colour let through, lightly
     softened, and one even hairline for an edge. White on the light page,
     the card colour on the dark one. A first pass with a lighter grey slab,
     a bright top line, a dark bottom line and a sheen read as embossed
     plastic ("too much emboss", "too frosted"); no bevel, no sheen. */
  --glass-tint: #ffffff;
  --glass: color-mix(in srgb, var(--glass-tint) 52%, transparent);
  --glass-thick: color-mix(in srgb, var(--glass-tint) 76%, transparent);
  --glass-edge: color-mix(in srgb, var(--ink) 11%, transparent);
  --glass-rim: transparent;
  /* Clear glass for a control that sits on the page itself (a secondary
     button, a tab group, the search): barely a fill, a crisp hairline. */
  --glass-clear: rgba(255, 255, 255, .5);
  --glass-clear-hover: rgba(255, 255, 255, .78);
  --glass-line: color-mix(in srgb, var(--ink) 13%, transparent);
  --glass-blur: saturate(170%) blur(10px);
  /* What lights up under a link, a tab or a control. */
  --nav-lit: color-mix(in srgb, var(--ink) 9%, transparent);
  --nav-lit-accent: color-mix(in srgb, var(--primary) 18%, transparent);
  /* From the bottom edge to the top of the dock: 60px of it, 10px under it. */
  --dock-clear: calc(70px + env(safe-area-inset-bottom, 0px));
  /* The thicker glass of anything that opens over the page, not only the
     bar's own menus: a dropdown's list, a toast, the cookie notice, a dialog.
     One name, so what a slow phone and a reader who wants less transparency
     get is said once ("the same surfaces, solid", below). */
  --glass-panel: var(--glass-thick);
  --glass-lift: 0 24px 50px -24px rgba(var(--shadow-rgb), 0.6);
}
:root[data-theme="dark"] {
  --glass-tint: var(--surface);
  --glass-edge: rgba(255, 255, 255, .1);
  --glass-clear: rgba(255, 255, 255, .04);
  --glass-clear-hover: rgba(255, 255, 255, .08);
  --glass-line: rgba(255, 255, 255, .12);
}

/* ---- the bar ------------------------------------------------------------ */
/* The gap above the capsule is the bar's own padding: as a margin on the
   capsule it collapsed through and pushed the whole bar down instead. */
.pk-nav { position: sticky; top: 0; z-index: 1030; height: 71px; padding-top: 10px; pointer-events: none; }
.pk-nav-inner {
  pointer-events: auto;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 0.75rem;
  height: 54px; padding: 0 0.5rem 0 0.65rem; min-width: 0;
  border: 1px solid var(--glass-edge); border-radius: 999px;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-rim), 0 0 0 rgba(var(--shadow-rgb), 0);
  transition: background-color 0.25s ease, box-shadow 0.25s ease;
}
/* Once the page has moved under it the capsule casts a shadow: at the top of
   a page there is nothing behind it to stand off from. */
.pk-nav.is-scrolled .pk-nav-inner {
  background: color-mix(in srgb, var(--glass-tint) 64%, transparent);
  box-shadow: inset 0 1px 0 var(--glass-rim), 0 16px 36px -18px rgba(var(--shadow-rgb), 0.55);
}
/* What scrolls under the bar fades into the page's ground before it reaches
   the top edge. Without it a line of text is read crisp in the strip above
   the capsule and beside it, which is what made the bar look laid on the
   page instead of floating over it. Only once the page has moved: at the top
   there is nothing to fade, and the landing's hero runs up under the bar. */
.pk-nav::before {
  content: ""; position: absolute; z-index: -1; inset: 0 0 -1.5rem 0;
  background: linear-gradient(to bottom, var(--bg) 0%, color-mix(in srgb, var(--bg) 86%, transparent) 42%, color-mix(in srgb, var(--bg) 0%, transparent) 100%);
  opacity: 0; pointer-events: none; transition: opacity 0.25s ease;
}
.pk-nav.is-scrolled::before { opacity: 1; }
@media (min-width: 1200px) {
  /* With the room for it, the links sit in the true middle of the capsule. */
  .pk-nav-inner { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
}

.pk-brand { display: inline-flex; align-items: center; gap: 0.55rem; flex: 0 0 auto; min-width: 0; justify-self: start; text-decoration: none; }
.pk-brand > :first-child { flex: 0 0 auto; }
.brand-mark { flex: 0 0 auto; border-radius: 11px; }
/* The word never shrinks and is never cut: nav.js hides it whole when a phone
   genuinely has no room for it, and the rule at 340px is the floor for anyone
   without the script. */
.pk-brand-word { flex: 0 0 auto; font-weight: 800; font-size: 1.14rem; letter-spacing: -0.03em; color: var(--ink); white-space: nowrap; }
.pk-brand-word b { color: var(--accent-ink); font-weight: 800; }

/* The links, and the one lit pill that moves between them. */
.pk-nav-links { position: relative; isolation: isolate; justify-self: center; display: flex; align-items: center; gap: 0.15rem; }
.pk-nav-link {
  position: relative; z-index: 1;
  padding: 0.48rem 0.9rem; border-radius: 999px;
  color: var(--ink-2); font-weight: 600; font-size: 0.92rem; white-space: nowrap;
  transition: color 0.15s ease;
}
.pk-nav-link:hover { color: var(--ink); }
.pk-nav-link.is-active { color: var(--ink); font-weight: 700; }
.pk-nav-links:not(.has-glider) .pk-nav-link:hover,
.pk-nav-links:not(.has-glider) .pk-nav-link.is-active { background: var(--nav-lit); }
.pk-nav-glider {
  position: absolute; z-index: 0; top: 50%; left: 0;
  width: var(--w, 0); height: 2.3rem; border-radius: 999px;
  background: var(--nav-lit);
  transform: translate(var(--x, 0), -50%); opacity: 0; pointer-events: none;
  transition: transform 0.3s cubic-bezier(0.3, 0.7, 0.2, 1), width 0.3s cubic-bezier(0.3, 0.7, 0.2, 1), opacity 0.2s ease;
}
.pk-nav-links.is-lit .pk-nav-glider { opacity: 1; }

.pk-nav-right { display: flex; align-items: center; gap: 0.3rem; flex: 0 0 auto; min-width: 0; justify-self: end; }

.pk-theme {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 2.4rem; height: 2.4rem; border: 0; border-radius: 50%;
  background: none; color: var(--ink-2); font-size: 1.1rem; cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.pk-theme:hover { background: var(--nav-lit); color: var(--ink); }
.pk-theme:focus-visible, .pk-menu-btn:focus-visible, .pk-avatar-btn:focus-visible, .pk-nav-link:focus-visible,
.pk-energy:focus-visible, .pk-dock-tab:focus-visible, .pk-dock-go:focus-visible { outline: 3px solid rgba(37, 99, 235, 0.45); outline-offset: 2px; }

/* A menu's trigger reads as a peer of the links beside it. */
.pk-menu { position: relative; display: inline-flex; }
.pk-menu-btn {
  display: inline-flex; align-items: center; gap: 0.28rem;
  padding: 0.48rem 0.7rem 0.48rem 0.9rem; border: 0; border-radius: 999px;
  background: none; color: var(--ink-2); font: inherit; font-weight: 600; font-size: 0.92rem; cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.pk-menu-btn:hover, .pk-menu.open > .pk-menu-btn { background: var(--nav-lit); color: var(--ink); }
.pk-menu-btn.is-active { color: var(--ink); font-weight: 700; }
.pk-menu-caret { font-size: 0.7rem; transition: transform 0.2s ease; }
.pk-menu.open .pk-menu-caret { transform: rotate(180deg); }

/* The balance and the way to add to it, as one pill and one link. */
.pk-energy {
  display: inline-flex; align-items: center; flex: 0 0 auto; height: 2.4rem;
  border-radius: 999px; background: var(--nav-lit); color: var(--ink); text-decoration: none;
  transition: background-color 0.15s ease;
}
.pk-energy:hover { background: color-mix(in srgb, var(--ink) 14%, transparent); color: var(--ink); }
.pk-credit { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0 0.6rem 0 0.8rem; font-weight: 700; font-size: 0.92rem; font-variant-numeric: tabular-nums; }
.pk-credit .ph-lightning { color: var(--accent-ink); }
.pk-credit b { color: var(--ink); font-weight: 800; }
.pk-credit-lbl { color: var(--muted); font-weight: 600; font-size: 0.84rem; }
.pk-topup {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 1.85rem; height: 1.85rem; margin-right: 0.28rem; border-radius: 50%;
  background: var(--primary); color: #fff; font-size: 0.8rem;
}

.pk-avatar {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--primary); color: #fff; font-weight: 800; font-size: 0.92rem; line-height: 1;
}
.pk-avatar-btn {
  display: inline-flex; align-items: center; gap: 0.2rem;
  padding: 0.18rem 0.45rem 0.18rem 0.18rem; border: 0; border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer;
  transition: background-color 0.15s ease;
}
.pk-avatar-btn:hover, .pk-menu.open > .pk-avatar-btn { background: var(--nav-lit); }
.pk-nav-cta { display: inline-flex; align-items: center; min-height: 2.4rem; padding-inline: 1.1rem; white-space: nowrap; }

/* What a menu opens: a panel of the same material, thicker so its words do
   not have to be read through the page. */
.pk-pop {
  position: absolute; z-index: 40; right: 0; top: calc(100% + 0.9rem);
  width: 15.5rem; padding: 0.45rem;
  border: 1px solid var(--glass-edge); border-radius: 1.15rem;
  background: var(--glass-thick);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-rim), 0 26px 54px -20px rgba(var(--shadow-rgb), 0.6);
  transform-origin: top right;
}
.pk-pop[hidden] { display: none; }
.pk-menu.open > .pk-pop { animation: pkPop 0.2s cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes pkPop { from { opacity: 0; transform: translateY(-6px) scale(0.97); } to { opacity: 1; transform: none; } }
/* Info hangs from a trigger in mid-bar, so from its left; the account menu is
   flush right and hangs from the right. */
.pk-menu--info .pk-pop { left: 0; right: auto; width: 13rem; transform-origin: top left; }
.pk-pop--acct { width: 15rem; }
.pk-pop-head { padding: 0.6rem 0.7rem 0.5rem; }
.pk-pop-name { font-weight: 700; font-size: 0.9rem; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* An address is long and must not stretch the panel. */
.pk-pop-sub { font-size: 0.78rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pk-pop-item { display: flex; align-items: center; gap: 0.65rem; padding: 0.58rem 0.7rem; border-radius: 0.8rem; color: var(--ink); font-weight: 600; font-size: 0.9rem; }
.pk-pop-item i { width: 1.15rem; font-size: 1.05rem; color: var(--muted); }
.pk-pop-item:hover { background: var(--nav-lit); color: var(--ink); }
.pk-pop-item.is-active, .pk-pop-item.is-active i { color: var(--accent-ink); }
.pk-pop-danger, .pk-pop-danger i { color: var(--bad-ink); }
.pk-pop-danger:hover { background: var(--bad-wash); color: var(--bad-ink); }
.pk-pop-div { height: 1px; margin: 0.35rem 0.5rem; background: var(--glass-edge); }

/* Under 1200px the links are not in the true middle and the row is at its
   tightest, so the balance drops its word and keeps its figure. */
@media (max-width: 1199px) { .pk-credit-lbl { display: none; } .pk-credit { padding-right: 0.5rem; } }

/* ---- the dock and the sheet do not exist on a wide screen --------------- */
.pk-dock, .pk-sheet { display: none; }

@media (max-width: 991px) {
  /* The bar as a plain row. `relative`, not `static`, so it keeps its place
     in the stack. */
  .pk-nav { position: relative; height: 62px; padding-top: 0; pointer-events: auto; }
  .pk-nav::before { display: none; }
  .pk-nav-inner,
  .pk-nav.is-scrolled .pk-nav-inner {
    display: flex; gap: 0.6rem; height: 62px; padding: 0 0.75rem;
    border: 0; border-radius: 0; background: none; box-shadow: none;
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .pk-nav-links, .pk-menu, .pk-theme, .pk-topup, .pk-credit-lbl { display: none; }
  .pk-nav-right { margin-left: auto; }
  .pk-credit { padding: 0 0.85rem 0 0.75rem; }

  /* The dock. Its row does not take taps between the capsule and the disc,
     so a page can still be touched through the gaps. */
  .pk-dock {
    position: fixed; z-index: 1040; left: 0; right: 0; bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    display: flex; align-items: center; justify-content: center; gap: 0.6rem;
    padding: 0 0.75rem; pointer-events: none;
  }
  .pk-dock-bar {
    pointer-events: auto; flex: 1 1 auto; min-width: 0; max-width: 25rem; height: 60px;
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 2px; padding: 5px;
    border: 1px solid var(--glass-edge); border-radius: 999px;
    background: var(--glass);
    -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
    box-shadow: inset 0 1px 0 var(--glass-rim), 0 16px 36px -14px rgba(var(--shadow-rgb), 0.6);
  }
  .pk-dock-tab {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
    min-width: 0; padding: 0; border: 0; border-radius: 999px; background: none;
    color: var(--muted); font: inherit; font-size: 0.66rem; font-weight: 700; line-height: 1.2; text-decoration: none;
    -webkit-tap-highlight-color: transparent; cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease, transform 0.12s ease;
  }
  .pk-dock-tab > i { font-size: 1.32rem; line-height: 1; }
  .pk-dock-tab:hover { color: var(--ink); }
  .pk-dock-tab:active { transform: scale(0.94); }
  /* Where you are: the place's own lozenge, in the accent. */
  .pk-dock-tab.is-active { background: var(--nav-lit-accent); color: var(--accent-ink); }
  .pk-avatar--dock { width: 22px; height: 22px; margin-bottom: 1px; font-size: 0.64rem; }

  /* The one thing the site is for, apart from the row. */
  .pk-dock-go {
    pointer-events: auto; flex: 0 0 auto; display: grid; place-items: center;
    width: 60px; height: 60px; border-radius: 50%;
    background: var(--primary); color: #fff; font-size: 1.5rem; text-decoration: none;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 16px 30px -14px rgba(var(--shadow-rgb), 0.7);
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.12s ease;
  }
  .pk-dock-go:hover { color: #fff; }
  .pk-dock-go:active { transform: scale(0.94); }
  .pk-dock-go.is-active { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 0 0 3px var(--bg), 0 0 0 5px var(--primary); }

  /* What a page leaves free at its foot: the dock and a little air. */
  body { padding-bottom: calc(var(--dock-clear) + 14px); }

  /* The sheet: a scrim, and a panel that comes up from the bottom edge on the
     curve a phone's own sheets use. nav.js adds .is-open a frame after it
     un-hides the sheet, which is what lets the panel travel. */
  .pk-sheet {
    position: fixed; inset: 0; z-index: 1050;
    display: flex; align-items: flex-end; justify-content: center;
    background: rgba(0, 0, 0, 0); transition: background-color 0.3s ease;
  }
  .pk-sheet[hidden] { display: none; }
  .pk-sheet.is-open { background: rgba(0, 0, 0, 0.52); }
  .pk-sheet-panel {
    width: 100%; max-width: 30rem; max-height: min(88dvh, 44rem);
    padding: 0 1rem calc(1.1rem + env(safe-area-inset-bottom, 0px));
    border: 1px solid var(--glass-edge); border-bottom: 0; border-radius: 1.75rem 1.75rem 0 0;
    background: var(--surface); box-shadow: 0 -26px 60px -22px rgba(var(--shadow-rgb), 0.65);
    /* It can outgrow a short screen, so it scrolls itself. */
    overflow-y: auto; overscroll-behavior: contain; outline: 0;
    transform: translateY(105%); transition: transform 0.42s cubic-bezier(0.32, 0.72, 0, 1);
  }
  .pk-sheet.is-open .pk-sheet-panel { transform: none; }
  /* While a finger has hold of it, it follows the finger and nothing else. */
  .pk-sheet.is-dragging .pk-sheet-panel { transition: none; }
}

/* ---- inside the sheet ---------------------------------------------------- */
.pk-sheet-top { touch-action: none; }
.pk-sheet-grab { display: block; width: 100%; padding: 0.6rem 0 0.7rem; border: 0; background: none; cursor: grab; }
.pk-sheet-grab::before { content: ""; display: block; width: 2.5rem; height: 0.3rem; margin: 0 auto; border-radius: 999px; background: var(--line-strong); }
.pk-sheet-head { display: flex; align-items: center; min-width: 0; padding: 0 0.25rem 0.9rem; }
.pk-sheet-acct { display: flex; align-items: center; gap: 0.75rem; min-width: 0; }
.pk-avatar--lg { width: 44px; height: 44px; font-size: 1.1rem; }
.pk-sheet-acct-txt { min-width: 0; }
.pk-sheet-acct-name { font-weight: 800; font-size: 1.02rem; letter-spacing: -0.015em; color: var(--ink); line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pk-sheet-acct-mail { font-size: 0.82rem; color: var(--muted); line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.pk-sheet-energy {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 0.8rem 0.8rem 0.8rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2);
  color: var(--ink); text-decoration: none;
}
.pk-sheet-energy-bal { display: inline-flex; align-items: baseline; min-width: 0; font-size: 0.9rem; font-weight: 600; color: var(--muted); }
.pk-sheet-energy-bal i { align-self: center; margin-right: 0.45rem; font-size: 1.15rem; color: var(--accent-ink); }
.pk-sheet-energy-bal b { margin-right: 0.3rem; font-size: 1.4rem; font-weight: 800; letter-spacing: -0.03em; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
.pk-sheet-energy-add { display: inline-flex; align-items: center; gap: 0.3rem; flex: 0 0 auto; padding: 0.45rem 0.9rem; border-radius: 999px; background: var(--primary); color: #fff; font-weight: 700; font-size: 0.86rem; }

/* The account's own places, large enough to hit without looking. */
.pk-sheet-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.6rem; margin-top: 0.6rem; }
.pk-sheet-tile {
  display: flex; flex-direction: column; justify-content: space-between; gap: 1.1rem;
  min-height: 5.5rem; padding: 0.85rem 0.9rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2);
  color: var(--ink); font-weight: 700; font-size: 0.88rem; line-height: 1.2; text-decoration: none;
  transition: transform 0.12s ease;
}
.pk-sheet-tile i { font-size: 1.4rem; color: var(--accent-ink); }
.pk-sheet-tile:hover { color: var(--ink); }
.pk-sheet-tile:active { transform: scale(0.97); }
.pk-sheet-tile.is-active { border-color: var(--accent-line); background: var(--primary-soft); color: var(--accent-ink); }

/* Rows in one ruled box, the way a phone lists its settings. */
.pk-sheet-list { margin-top: 0.6rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2); overflow: hidden; }
.pk-sheet-row {
  display: flex; align-items: center; gap: 0.8rem; width: 100%; min-height: 3.15rem; padding: 0.7rem 1rem;
  border: 0; background: none; color: var(--ink); font: inherit; font-weight: 600; font-size: 0.95rem;
  text-align: left; text-decoration: none; cursor: pointer;
}
.pk-sheet-row + .pk-sheet-row { border-top: 1px solid var(--line); }
.pk-sheet-row > i:first-child { flex: 0 0 auto; width: 1.3rem; font-size: 1.15rem; color: var(--muted); }
.pk-sheet-row > .ph-caret-right { margin-left: auto; font-size: 0.8rem; color: var(--muted); }
.pk-sheet-row:hover { color: var(--ink); }
.pk-sheet-row:active { background: var(--nav-lit); }
.pk-sheet-row.is-active, .pk-sheet-row.is-active > i:first-child { color: var(--accent-ink); }
.pk-sheet-row--danger, .pk-sheet-row--danger:hover, .pk-sheet-row--danger > i:first-child { color: var(--bad-ink); }
.pk-sheet-foot { margin-top: 0.6rem; }

@media (max-width: 480px) { .pk-brand-word { font-size: 1.1rem; } }
/* Only where even the mark and the balance are fighting for room. */
@media (max-width: 340px) { .pk-brand-word { display: none; } }

/* ---- the same surfaces, solid -------------------------------------------
   A backdrop blur is redone for every frame of whatever moves under it, which
   on a slow phone is every frame of a scroll. */
.pk-lite .pk-nav-inner, .pk-lite .pk-dock-bar, .pk-lite .pk-pop { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
/* A reader who asked for less transparency gets less, not none: the surfaces
   go nearly opaque, so nothing has to be read through the page, and keep the
   blur for the little that still shows. Windows reports this whenever its own
   "transparency effects" switch is off, which is a great many laptops. */
@media (prefers-reduced-transparency: reduce) {
  .pk-nav-inner, .pk-nav.is-scrolled .pk-nav-inner, .pk-dock-bar { background: color-mix(in srgb, var(--glass-tint) 80%, transparent); }
  .pk-pop { background: color-mix(in srgb, var(--glass-tint) 86%, transparent); }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .pk-nav-inner, .pk-nav.is-scrolled .pk-nav-inner, .pk-dock-bar, .pk-pop { background: color-mix(in srgb, var(--surface) 96%, transparent); }
  :root { --glass: color-mix(in srgb, var(--surface) 94%, transparent); --glass-panel: color-mix(in srgb, var(--surface) 96%, transparent); }
}
/* And for everything else made of the thick glass (--glass-panel): nearly
   solid for less transparency, solid and unblurred on a slow phone. */
@media (prefers-reduced-transparency: reduce) { :root { --glass: color-mix(in srgb, var(--glass-tint) 80%, transparent); --glass-panel: color-mix(in srgb, var(--glass-tint) 86%, transparent); } }
html.pk-lite { --glass: var(--surface); --glass-panel: var(--surface); }
.pk-lite .ss-panel, .pk-lite .pk-toast, .pk-lite .consent, .pk-lite .pk-modal .modal-content { -webkit-backdrop-filter: none; backdrop-filter: none; }
@media (max-width: 991px) {
  /* The phone's bar is a plain row whatever the material rules above say. */
  .pk-nav-inner, .pk-nav.is-scrolled .pk-nav-inner { background: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  .pk-nav-inner, .pk-nav-glider, .pk-menu-caret, .pk-dock-tab, .pk-dock-go, .pk-sheet, .pk-sheet-panel, .pk-sheet-tile { transition: none; }
  .pk-menu.open > .pk-pop { animation: none; }
}


 


/* Held by nav.js while the mobile menu is open. <html> is the scrolling
   element here, so the lock has to live on it; the body is pinned because that
   is the only form iOS Safari honours. */
html.is-locked { overflow: hidden; }
html.is-locked body { position: fixed; left: 0; right: 0; width: 100%; }

/* Quiz step buttons: back on the left, forward on the right, always on one
   line. The row used to carry flex-wrap, so on a narrow phone the two buttons
   stacked — which put "Berikutnya" underneath "Sebelumnya" and reversed the
   order a forward step should read in. They shrink instead of stacking. */
/* Inline note for the ticket forms. It replaces the flash message the old
   POST-and-redirect carried: nothing navigates any more, so the answer has to
   appear beside the form that asked for it. */
/* ===== Notices ==========================================================
   Three ways the app speaks, by how long the words are needed:

   .pk-toast   confirms something and leaves (saved, signed in, copied);
   .pk-note    stays beside what it is about (a rule, a warning, an error
               that needs reading);
   .field-err  one line under the field it faults.

   The note is the card surface with a hairline, and its meaning is carried
   by one small thing: its icon's colour, or a dot when it has no icon. It
   used to be a tinted slab with a 3px coloured edge, which in the dark theme
   was a block of dark red or dark amber, the loudest thing on the page.
   ========================================================================= */
.pk-note {
  display: flex; align-items: flex-start; gap: .65rem;
  margin: 0 0 1rem;
  padding: .75rem .95rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-size: .9rem; line-height: 1.55;
  --note: var(--muted);
}
.pk-note > i { flex: 0 0 auto; margin: .12rem 0 0 !important; font-size: 1.05rem; color: var(--note); }
/* No icon: a dot says which kind it is. */
.pk-note:not(:has(> i))::before { content: ""; flex: 0 0 auto; width: .45rem; height: .45rem; margin-top: .52rem; border-radius: 50%; background: var(--note); }
.pk-note strong, .pk-note b { color: var(--ink); font-weight: 700; }
.pk-note a { color: var(--accent-ink); font-weight: 600; }
.pk-note ul { margin: .35rem 0 0; padding-left: 1.1rem; }
.pk-note[hidden] { display: none; }

.pk-note--ok   { --note: var(--ok-ink); }
.pk-note--bad  { --note: var(--bad-ink); }
.pk-note--warn { --note: var(--warn-ink); }
.pk-note--info { --note: var(--accent-ink); }

/* ---- toast ---- a line at the top of the screen, under the bar, that says
   something happened and goes. Ink on the page's own surface; the icon is
   the only colour. */
.pk-toasts {
  position: fixed; z-index: 1080; top: calc(70px + .9rem); left: 50%;
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
  width: max-content; max-width: calc(100vw - 1.5rem);
  transform: translateX(-50%); pointer-events: none;
}
.pk-toast {
  display: flex; align-items: flex-start; gap: .55rem;
  max-width: 30rem; padding: .65rem 1rem .65rem .85rem;
  /* Made of what the bar's menus are made of: it appears just under the bar. */
  background: var(--glass-panel); color: var(--ink);
  border: 1px solid var(--glass-edge); border-radius: 1.15rem;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lift);
  font-size: .9rem; font-weight: 600; line-height: 1.45;
  opacity: 0; transform: translateY(-8px);
  transition: opacity .22s ease, transform .22s ease;
}
.pk-toast.is-in { opacity: 1; transform: none; }
.pk-toast > i { flex: 0 0 auto; margin-top: .1rem; font-size: 1.1rem; color: var(--ok-ink); }
.pk-toast--info > i { color: var(--accent-ink); }
.pk-toast--bad > i { color: var(--bad-ink); }
@media (max-width: 991.98px) { .pk-toasts { top: calc(62px + .6rem); } }
@media (prefers-reduced-motion: reduce) { .pk-toast { transition: none; } }

/* ---- a field that is wrong ---- a red edge on the control and one line
   under it; the form's button keeps the count (.form-problems). */
.form-control.is-problem, .ss.is-problem .ss-trigger, .form-check.is-problem .form-check-input { border-color: var(--danger) !important; }
.field-err { margin: .4rem 0 0; font-size: .82rem; font-weight: 600; line-height: 1.45; color: var(--bad-ink); text-align: left; }
.form-problems {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center; gap: .15rem .6rem;
  margin: 0 auto .9rem; max-width: 36rem;
  font-size: .9rem; font-weight: 600; line-height: 1.5; color: var(--bad-ink);
}
.form-problems[hidden] { display: none; }
.form-problems button { padding: 0; border: 0; background: none; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }

/* Tighter, for notices that sit inside a panel rather than above one. */
.pk-note--sm { padding: .6rem .8rem; font-size: .86rem; gap: .55rem; }
.pk-note--sm > i { font-size: .92rem; }

.btn.is-busy { opacity: .65; pointer-events: none; }

.qz-nav { flex-wrap: nowrap; }
.qz-nav > .btn { white-space: nowrap; }
/* Bootstrap's .btn sets display:inline-block, which outranks the UA rule for
   [hidden] — so the back button stayed visible on question 1 once quiz.js
   started toggling it. */
.qz-nav > [hidden] { display: none !important; }
.qz-nav-gap { flex: 1 1 auto; }

/* Moving between questions changes the question and its four options, and
   nothing else. Fading .qz-swap alone keeps the card, the progress bar and the
   buttons still, so the page reads as one screen changing its contents rather
   than a new screen arriving. */
.qz-swap { transition: opacity .13s ease; }
.qz-swap.is-out { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .qz-swap { transition: none; }
}
@media (max-width: 430px) {
  .qz-nav > .btn { padding-left: .8rem; padding-right: .8rem; font-size: .95rem; }
  .qz-nav > .btn-lg { padding-top: .55rem; padding-bottom: .55rem; }
}
/* On a phone the two buttons ride just above the tab bar until the card's own
   foot comes into view. Left where the card ends, "Berikutnya" sat under the
   tab bar on every question with four answers, cut in half, and each of the
   twelve needed a scroll to reach it. The row carries the card's ground so an
   answer passing behind it is covered rather than read through. --dock-clear
   is the top of the dock (see NAVIGATION). */
@media (max-width: 575.98px) {
  .qz-nav {
    position: sticky; bottom: calc(var(--dock-clear) + 0.6rem); z-index: 2;
    margin-left: -0.4rem; margin-right: -0.4rem; padding: 0.5rem 0.4rem;
    background: var(--surface); border-radius: var(--radius-sm);
    box-shadow: 0 -0.9rem 0.8rem -0.5rem var(--surface);
  }
}


/* ---- Cards ------------------------------------------------------------- */
.glass-card,
.card-soft {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.glass-card { border-radius: var(--radius-xl); }
.card-soft { border-radius: var(--radius); box-shadow: var(--shadow-sm); }
.card-soft:hover { box-shadow: var(--shadow-md); }

.icon-pill {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 16px;
  background: var(--primary-soft); color: var(--accent-ink);
}

/* ---- Buttons -----------------------------------------------------------
   Pills, flat, one cobalt. The raised bottom edge and the blue glow under the
   primary are gone (tasteskill §4.2, §9.A); a press is answered by the button
   itself sinking a pixel and shrinking a little (§4.5). Colours go through
   Bootstrap's own variables, so its :active and :disabled rules agree with
   these instead of flashing its default blue. */
.btn {
  border-radius: 999px; font-weight: 700; letter-spacing: -0.01em;
  padding: 0.6rem 1.3rem;
  transition: transform 0.12s cubic-bezier(0.16, 1, 0.3, 1), background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn:active { transform: translateY(1px) scale(0.98); }
.btn:focus-visible { outline: 3px solid rgba(37, 99, 235, 0.35); outline-offset: 2px; box-shadow: none; }

.btn-primary {
  --bs-btn-color: #fff; --bs-btn-bg: var(--primary); --bs-btn-border-color: var(--primary);
  --bs-btn-hover-color: #fff; --bs-btn-hover-bg: var(--primary-dark); --bs-btn-hover-border-color: var(--primary-dark);
  --bs-btn-active-color: #fff; --bs-btn-active-bg: var(--primary-deep); --bs-btn-active-border-color: var(--primary-deep);
  --bs-btn-disabled-color: #fff; --bs-btn-disabled-bg: var(--primary); --bs-btn-disabled-border-color: var(--primary);
  box-shadow: none;
}

/* The secondary button: the card's own surface, a hairline you can see, ink
   text. The same shape is the "back" button on Jelajahi PTN, the quiz and the
   print view. */
.btn-outline-primary,
.btn-outline-secondary {
  --bs-btn-bg: var(--surface); --bs-btn-border-color: var(--line-strong);
  --bs-btn-hover-bg: var(--surface); --bs-btn-hover-border-color: var(--primary); --bs-btn-hover-color: var(--primary);
  --bs-btn-active-bg: var(--primary-soft); --bs-btn-active-border-color: var(--primary); --bs-btn-active-color: var(--primary-deep);
  --bs-btn-disabled-bg: var(--surface); --bs-btn-disabled-border-color: var(--line);
  box-shadow: none;
}
.btn-outline-primary { --bs-btn-color: var(--ink); --bs-btn-disabled-color: var(--muted); }
.btn-outline-secondary { --bs-btn-color: var(--muted); --bs-btn-disabled-color: var(--muted); }

.btn-light { box-shadow: none; }

.btn-lg { padding: 0.95rem 2.1rem; font-size: 1.02rem; }
.btn-sm { padding: 0.4rem 0.9rem; }

/* ---- Forms -------------------------------------------------------------
   A 1px edge at 3:1 instead of a 2px one at 1.1:1 (see --field-line), with the
   padding grown by the pixel the border gave back so no field changes size. */
.form-control, .form-select {
  border: 1px solid var(--field-line);
  border-radius: var(--radius-sm);
  padding: calc(0.72rem + 1px) calc(0.95rem + 1px);
  font-weight: 500;
  background-color: var(--surface);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
/* --muted is 4.8:1: a placeholder is text somebody has to read (tasteskill
   §4.5). It stays lighter in weight than a typed value. */
.form-control::placeholder { color: var(--muted); font-weight: 400; opacity: 1; }
.form-control:hover, .form-select:hover { border-color: var(--muted); }
.form-control:focus, .form-select:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25);
  background-color: var(--surface);
}
.form-label { font-weight: 700; font-size: 0.88rem; color: var(--ink); margin-bottom: 0.4rem; }
.form-text { color: var(--muted); }

/* Input groups (icon + field): the group carries the edge, the field inside
   carries none. */
.input-group { border: 1px solid var(--field-line); border-radius: var(--radius-sm); overflow: hidden; transition: border-color .15s, box-shadow .15s; background: var(--surface); }
.input-group:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25); }
.input-group .input-group-text { background: var(--surface); border: none; color: var(--muted); padding-left: 0.95rem; }
.input-group .form-control { border: none !important; box-shadow: none !important; }
.input-group .form-control:focus { box-shadow: none !important; }
.input-group > .btn { border: none; box-shadow: none; }

/* Custom checkbox: rounded, fills blue with a white check */
.form-check-input {
  width: 1.35em; height: 1.35em; margin-top: 0.1em;
  border: 2px solid var(--field-line); border-radius: 0.5em; background-color: var(--surface);
  cursor: pointer; transition: background-color .15s, border-color .15s;
}
.form-check-input:checked { background-color: var(--primary); border-color: var(--primary); }
.form-check-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25); }
.form-check-input[type="radio"] { border-radius: 50%; }
.form-check-label { cursor: pointer; padding-left: 0.15rem; }

/* Custom select arrow */
.form-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 0.95rem center; background-size: 1rem;
}

/* ---- Alerts & badges --------------------------------------------------- */
.alert { border: none; border-radius: var(--radius); box-shadow: var(--shadow-sm); }
.badge { font-weight: 600; letter-spacing: 0; }

/* ---- Section helpers --------------------------------------------------- */
.section-eyebrow {
  text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.75rem;
  font-weight: 700; color: var(--accent-ink);
}

/* ---- Footer ------------------------------------------------------------
   One hairline, one row, one line of small print, on the page's own ground. */
.app-footer { margin-top: 3rem; padding: 1.4rem 0 1.75rem; border-top: 1px solid var(--line); color: var(--muted); font-size: 0.875rem; }
.foot-row { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem 2rem; }
.foot-brand { font-size: 1rem; font-weight: 800; letter-spacing: -0.03em; color: var(--ink); text-decoration: none; }
.foot-brand b { color: var(--accent-ink); font-weight: 800; }
.foot-links { display: flex; flex-wrap: wrap; gap: 0.3rem 1.5rem; }
.foot-links a { color: var(--ink-2); font-weight: 600; text-decoration: none; }
.foot-links a:hover { color: var(--accent-ink); }
.foot-note { margin: 0.75rem 0 0; font-size: 0.8rem; line-height: 1.55; color: var(--muted); }
}

/* ---- Staggered page-load reveal --------------------------------------- */
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
.reveal { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; }
.reveal-1 { animation-delay: 0.05s; }
.reveal-2 { animation-delay: 0.13s; }
.reveal-3 { animation-delay: 0.21s; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .btn, .glass-card { animation: none !important; transition: none !important; }
}

/* =====================================================================
   Analysis report (shared by apply result + history detail)
   ===================================================================== */
.analysis { display: flex; flex-direction: column; gap: 1rem; }
.analysis-head .score-ring {
  width: 84px; height: 84px; border-radius: 50%;
  background: conic-gradient(var(--primary) calc(var(--val) * 1%), var(--line) 0);
  display: grid; place-items: center; position: relative;
}
.analysis-head .score-ring::before {
  content: ""; position: absolute; inset: 8px; background: var(--surface); border-radius: 50%;
}
.analysis-head .score-ring span { position: relative; font-weight: 800; font-size: 1.5rem; color: var(--accent-ink); }

.analysis-panel.active { display: block; }
@keyframes fadeUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.panel-title { font-size: 1.05rem; font-weight: 700; margin-bottom: 1rem; }

.stat { text-align: center; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm, 12px); padding: 0.75rem; }
.stat-val { font-size: 1.5rem; font-weight: 800; line-height: 1; }
.stat-lbl { font-size: 0.75rem; color: var(--muted); margin-top: 0.25rem; }

.mini { background: var(--surface-2); border-radius: 10px; padding: 0.5rem 0.25rem; color: var(--muted); font-size: 0.72rem; }
.mini .fw-bold { font-size: 0.95rem; display: block; }

.sem-chart { display: flex; align-items: flex-end; gap: 0.6rem; height: 140px; padding-top: 0.5rem; }
.sem-bar { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; }
.sem-fill {
  width: 100%; max-width: 46px; background: linear-gradient(180deg, var(--primary), #4d8bff);
  border-radius: 8px 8px 0 0; display: flex; align-items: flex-start; justify-content: center;
  color: #fff; font-size: 0.72rem; font-weight: 700; padding-top: 4px; min-height: 14px;
}
.sem-x { font-size: 0.75rem; color: var(--muted); margin-top: 0.35rem; }

.ticks { list-style: none; padding-left: 0; margin: 0.5rem 0 0; }
.ticks li { position: relative; padding-left: 1.5rem; margin-bottom: 0.4rem; }
.ticks li::before { content: "\e182"; font-family: "Phosphor"; position: absolute; left: 0; color: var(--accent-ink); }

.phase { border-left: 3px solid var(--primary); padding-left: 1rem; margin-bottom: 1.25rem; }
.phase-head { font-weight: 700; margin-bottom: 0.6rem; }

.todo {
  display: flex; gap: 0.65rem; align-items: flex-start; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px; padding: 0.7rem 0.85rem;
  transition: background 0.15s ease;
}
.todo:hover { background: var(--surface-2); }
.todo input { position: absolute; opacity: 0; pointer-events: none; }
.todo-box {
  flex: 0 0 auto; width: 20px; height: 20px; border: 2px solid var(--line-strong); border-radius: 6px;
  margin-top: 2px; display: grid; place-items: center; transition: all 0.15s ease;
}
.todo-box::after { content: "\e182"; font-family: "Phosphor"; color: #fff; font-size: 0.8rem; opacity: 0; }
.todo.done .todo-box { background: var(--primary); border-color: var(--primary); }
.todo.done .todo-box::after { opacity: 1; }
.todo.done .todo-task { text-decoration: line-through; color: var(--muted); }

.todo-task { font-weight: 600; font-size: 0.92rem; }
.todo-why { font-size: 0.82rem; color: var(--muted); margin-top: 0.15rem; }

.disclaimer { font-size: 0.8rem; color: var(--muted); background: var(--surface-2); border-radius: 10px; padding: 0.6rem 0.8rem; margin: 0; }

/* --- Print ----------------------------------------------------------- */
@media print {
  .app-navbar, .app-footer, .analysis-foot, .d-print-none, [data-print], .btn { display: none !important; }
  body { background: var(--surface) !important; }
  .glass-card, .card-soft { box-shadow: none !important; border: 1px solid #ddd !important; backdrop-filter: none !important; }
  
  .analysis-panel .panel-title { border-bottom: 2px solid #000; padding-bottom: 0.25rem; }
  .analysis { gap: 1.5rem; }
  .todo input { display: none; }
}

/* ===== Chat (follow-up Q&A) =========================================== */
.chat-thread {
  display: flex; flex-direction: column; gap: 0.85rem;
  max-height: 480px; overflow-y: auto; padding: 0.5rem;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px;
  margin-bottom: 0.75rem;
}
.chat-msg { display: flex; gap: 0.5rem; align-items: flex-start; }

.chat-avatar {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; font-size: 0.95rem;
  background: var(--primary-soft); color: var(--accent-ink);
}
.chat-msg--user .chat-avatar { background: var(--primary); color: #fff; }
.chat-bubble {
  background: var(--surface); border: 1px solid var(--line); padding: 0.65rem 0.85rem;
  border-radius: 14px; max-width: 78%; font-size: 0.93rem; line-height: 1.5;
  word-wrap: break-word;
}
.chat-msg--user .chat-bubble { background: var(--primary); color: #fff; border-color: var(--primary); }
.chat-typing { color: var(--muted); font-style: italic; }

.chat-suggestions { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.5rem; }
.chat-chip {
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  padding: 0.3rem 0.75rem; border-radius: 999px; font-size: 0.82rem; cursor: pointer;
  transition: all 0.15s ease;
}
.chat-chip:hover { background: var(--primary); color: #fff; border-color: var(--primary); }

.chat-form textarea { resize: none; overflow-y: auto; max-height: 160px; }

/* Markdown subset inside assistant bubbles. */
.chat-bubble .chat-md-p { margin: 0 0 0.5rem; }
.chat-bubble .chat-md-p:last-child { margin-bottom: 0; }
.chat-bubble .chat-md-list { margin: 0 0 0.5rem; padding-left: 1.15rem; }
.chat-bubble .chat-md-list:last-child { margin-bottom: 0; }
.chat-bubble .chat-md-list li { margin-bottom: 0.25rem; }
.chat-bubble .chat-md-list li::marker { color: var(--accent-ink); font-weight: 700; }
.chat-bubble code {
  background: rgba(0,0,0,0.06); padding: 0 0.3em; border-radius: 4px;
  font-size: 0.92em; color: inherit;
}
.chat-msg--user .chat-bubble code { background: rgba(255,255,255,0.25); }
.chat-bubble strong { font-weight: 700; }
.chat-bubble a { color: inherit; text-decoration: underline; }

/* ===== Explore — Search bar + Browse grid + Detail ===================== */
.explore-search-wrap { max-width: 720px; margin: 0 auto; }
.explore-search {
  position: relative;
  display: flex; align-items: stretch;
  background: var(--surface); border: 2px solid var(--line);
  border-radius: 999px; padding: 6px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.04);
  transition: all 0.2s ease;
}
.explore-search:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 4px rgba(0,98,255,0.1);
}
.explore-search-icon {
  position: absolute; left: 22px; top: 50%; transform: translateY(-50%);
  color: var(--muted); pointer-events: none;
}
.explore-search-input {
  flex-grow: 1; border: 0; outline: 0; background: transparent;
  padding: 0.65rem 0.5rem 0.65rem 3rem; font-size: 1rem;
  min-width: 0;
}
.explore-search-input:focus { outline: 0; box-shadow: none; }
/* A link on the campus list, a button inside one campus: the button needs the
   browser's own face taken off, or the cross sits in a grey bevelled square. */
.explore-search-clear {
  align-self: center; display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; margin-right: 0.25rem; padding: 0;
  border: 0; border-radius: 50%; background: none; color: var(--muted);
  font: inherit; line-height: 1; text-decoration: none; cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.explore-search-clear[hidden] { display: none; }
.explore-search-clear:hover { color: var(--ink); background: var(--surface-2); }
.explore-search-btn {
  background: var(--primary); color: #fff; border: 0;
  padding: 0.55rem 1.5rem; border-radius: 999px;
  font-weight: 600; cursor: pointer; transition: background 0.15s ease;
}
.explore-search-btn:hover { background: var(--primary-dark); }
@media (max-width: 575px) {
  .explore-search-input { padding-left: 2.6rem; font-size: 0.9rem; }
  .explore-search-btn { padding: 0.45rem 1rem; font-size: 0.9rem; }
}

/* A search hit, in the one accent: the tint under it and the accent's ink on
   top, both from the tokens, so it holds in either theme. It was a fixed
   highlighter yellow with fixed dark type, which glared on the dark ground. */
mark { background: var(--primary-soft); color: var(--accent-ink-2); font-weight: 700; padding: 0 0.15em; border-radius: 4px; }

/* Campus cards, redrawn 14 Oct 2026. A matte card in the page: the crest on
   a plate, the name and the town, and a foot with the campus's two figures.
   A card found for its prodi lists them between the two.
   Nothing on it is cobalt. A card is a place to go, not the thing to press,
   and the 3px cobalt bar that ran down its left edge (under the pointer, and
   on every search hit, with a blue border round the card) read as a stripe
   stuck to the side. What answers the pointer is the lit ground the top bar
   uses, a firmer hairline and a small lift. */
.explore-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
  gap: 1rem;
  align-items: stretch;
}
.explore-card {
  display: flex; flex-direction: column; height: 100%;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  text-decoration: none; color: var(--ink);
  transition: background-color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
/* A link under the pointer goes cobalt everywhere else on the site. Not
   here: the name of the campus stays the page's ink. */
.explore-card:hover, .explore-card:focus { color: var(--ink); }
/* Only where there is a pointer to hover with: on a phone the state would
   stay on the card that was just tapped. */
@media (hover: hover) {
  .explore-card:hover {
    background: color-mix(in srgb, var(--ink) 4%, var(--surface));
    border-color: var(--line-strong);
    box-shadow: 0 18px 32px -22px rgba(var(--shadow-rgb), 0.55);
    transform: translateY(-2px);
  }
}
.explore-card:active { transform: none; }
.explore-card:focus-visible { outline: 3px solid rgba(37, 99, 235, 0.45); outline-offset: 2px; }

.explore-card-head { display: flex; align-items: center; gap: 0.9rem; padding: 1.15rem 1.25rem 1.05rem; }
/* A crest is drawn for white paper: dark blue ones went dim on the dark
   card. So it sits on a light plate in both themes (--crest-plate). */
.explore-card-logo {
  flex: 0 0 auto; width: 48px; height: 48px; padding: 5px;
  display: grid; place-items: center;
  background: var(--crest-plate); border: 1px solid var(--line); border-radius: 0.8rem;
}
.explore-card-logo img { width: 100%; height: 100%; object-fit: contain; }
.explore-card-body { flex: 1 1 auto; min-width: 0; }
.explore-card-name { font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; text-wrap: balance; }
.explore-card-meta { color: var(--muted); font-size: 0.82rem; margin-top: 0.15rem; }

/* The prodi a search found, as rows of the card itself: the name, its seats
   and applicants under it, how many get in beside it. They were boxes inside
   a sunk box under a capital-letter heading that repeated the search on every
   card. */
.explore-card-matches { list-style: none; margin: 0; padding: 0 1.25rem; border-top: 1px solid var(--line); }
.explore-card-matches li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 0.75rem; align-items: center;
  padding: 0.6rem 0;
}
.explore-card-matches li + li { border-top: 1px solid var(--line); }
.match-name { grid-column: 1; font-size: 0.9rem; font-weight: 600; line-height: 1.3; }
.match-meta { grid-column: 1; display: flex; flex-wrap: wrap; color: var(--muted); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
.match-meta span + span::before { content: "\00b7"; margin: 0 0.45em; }
/* Signed out a row has its seats and nothing else: beside the name, not
   under it. */
.explore-card-matches .match--seats .match-meta { grid-column: 2; grid-row: 1; }
/* A hit keeps its tint without its padding, so the name stays on the
   column's edge and the tint overhangs it instead. */
.explore-card mark { padding: 0; box-shadow: -0.15em 0 0 var(--primary-soft), 0.15em 0 0 var(--primary-soft); }
.explore-card-matches .rate-pill { grid-column: 2; grid-row: 1 / span 2; font-size: 0.72rem; padding: 0.2rem 0.55rem; white-space: nowrap; }
.explore-card-matches .match-more { display: block; color: var(--muted); font-size: 0.78rem; }

/* The campus in figures: its prodi and its applicants, and at the end how
   many of them get in, in the pill a prodi's rate is in. The foot is pushed
   to the bottom, so the figures of a row of cards line up whatever the
   length of the names above them. */
.explore-card-foot {
  margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 0.7rem 1.25rem 0.8rem; border-top: 1px solid var(--line);
  color: var(--muted); font-size: 0.8rem;
}
.explore-card-stats { display: flex; flex-wrap: wrap; gap: 0.1rem 1rem; font-variant-numeric: tabular-nums; }
.explore-card-stats b { color: var(--ink); font-weight: 700; }
.explore-card-foot .rate-pill { flex: 0 0 auto; font-size: 0.72rem; padding: 0.2rem 0.55rem; white-space: nowrap; }

/* Detail page */
.uni-detail-logo {
  width: 80px; height: 80px; object-fit: contain; background: var(--surface);
  border: 1px solid var(--line); border-radius: 14px; padding: 8px;
}
.uni-detail-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.75rem; margin-top: 1rem;
}
.uni-detail-stat {
  background: var(--surface-2); border-radius: 10px; padding: 0.85rem 1rem;
  text-align: center;
}
.uni-detail-stat-val { font-size: 1.35rem; font-weight: 800; color: var(--accent-ink); line-height: 1.1; }
.uni-detail-stat-lbl { font-size: 0.75rem; color: var(--muted); margin-top: 0.15rem; }

.rate-pill {
  display: inline-block; padding: 0.25rem 0.6rem; border-radius: 999px;
  font-size: 0.78rem; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--warn-wash); color: var(--warn-ink);
}
.rate-pill.heat-low { background: var(--ok-bg); color: var(--ok-ink); }
.rate-pill.heat-mid { background: var(--warn-wash); color: var(--warn-ink); }
.rate-pill.heat-high { background: var(--bad-bg); color: var(--bad-ink); }

/* ===== Quiz =========================================================== */
.quiz-progress {
  height: 8px; background: var(--surface-2); border-radius: 999px; overflow: hidden;
}
.quiz-progress-fill {
  height: 100%; background: var(--primary);
  border-radius: 999px; transition: width 0.4s ease;
}

.quiz-options-paged { display: flex; flex-direction: column; gap: 0.85rem; }
.quiz-option-paged {
  display: block; cursor: pointer; position: relative;
  background: var(--surface); border: 2px solid var(--line); border-radius: 14px;
  transition: all 0.15s ease;
}
.quiz-option-paged input { position: absolute; opacity: 0; pointer-events: none; }
.quiz-option-paged:hover { border-color: var(--accent-line); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.04); }
.quiz-option-paged:has(input:checked) {
  border-color: var(--primary);
  background: linear-gradient(135deg, var(--primary-soft) 0%, var(--surface) 100%);
  box-shadow: 0 4px 16px rgba(0,98,255,0.15);
}
.quiz-option-content {
  display: flex; align-items: center; gap: 1rem; padding: 1rem 1.25rem;
}
.quiz-option-letter {
  flex-shrink: 0; width: 36px; height: 36px; border-radius: 50%;
  background: var(--surface-2); color: var(--accent-ink); display: grid; place-items: center;
  font-weight: 700; font-size: 0.95rem; transition: all 0.15s ease;
}
.quiz-option-paged:has(input:checked) .quiz-option-letter {
  background: var(--primary); color: #fff;
}
.quiz-option-label { flex-grow: 1; line-height: 1.4; }
.quiz-option-check {
  flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%;
  background: var(--primary); color: #fff; display: grid; place-items: center;
  opacity: 0; transform: scale(0.5); transition: all 0.2s ease;
}
.quiz-option-paged:has(input:checked) .quiz-option-check { opacity: 1; transform: scale(1); }
/* On a phone an answer had about 160px of line to itself: the card's padding,
   the option's own, and a column held for a tick only one of them ever shows.
   Answers broke into three and four lines and pushed the button off the
   screen. The filled letter and the border already say which one is chosen. */
@media (max-width: 575.98px) {
  .quiz-options-paged { gap: 0.6rem; }
  .quiz-option-content { gap: 0.75rem; padding: 0.75rem 0.85rem; }
  .quiz-option-letter { width: 32px; height: 32px; font-size: 0.9rem; }
  .quiz-option-check { display: none; }
}

/* Quiz result */

@keyframes pop { from { transform: scale(0.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }

/* ===== Recommendation cards =========================================== */

.rec-card.picked {
  border-color: var(--primary);
  background: linear-gradient(135deg, var(--primary-soft), var(--surface));
  box-shadow: 0 8px 20px rgba(0,98,255,0.18);
}
.rec-card.is-disabled { opacity: 0.45; pointer-events: none; }

.rec-bars .bar { height: 5px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.rec-bars .bar-fill { height: 100%; border-radius: 999px; transition: width 0.4s ease; }
.rec-bars .bar-val { font-size: 0.72rem; font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; color: var(--ink); }

.rec-card.picked .rec-pick-badge { opacity: 1; transform: scale(1); }

/* Sticky pick action bar */

/* ===== History cards ====================================================
   Was: a "Pilihan 1" chip, then the full-caps campus name, then the program,
   run together on one line and wrapped over three. The chip repeated on every
   row to say something the order already says, and the campus led even though
   the program is what was chosen. Now the crest carries the campus, the rank
   is a digit on it, and the program leads. */
.hist-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
/* The title stands alone now, so it does not hold a gap open for a line under it. */
.page-head.hist-head { margin-bottom: 1.1rem; }
.page-head.hist-head h1 { margin: 0; }

.hist-list { display: flex; flex-direction: column; gap: 0.7rem; }

.hist-card {
  display: flex; align-items: center; gap: 0.9rem;
  padding: 0.9rem 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); text-decoration: none;
  transition: border-color 0.14s, box-shadow 0.14s, transform 0.14s;
}
.hist-card:hover {
  border-color: var(--line); box-shadow: var(--shadow-md); transform: translateY(-1px);
}
/* min-width:0 lets the ellipsis actually take effect inside the flex row. */
.hist-card-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.55rem; }

.hist-choice { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }

.hist-crest { position: relative; flex: 0 0 auto; width: 36px; height: 36px; }
.hist-crest img {
  width: 100%; height: 100%; object-fit: contain;
  border-radius: 9px; background: var(--surface); border: 1px solid var(--line); padding: 2px;
}
.hist-crest-ltr {
  display: grid; place-items: center; width: 100%; height: 100%;
  border-radius: 9px; background: var(--primary-soft); color: var(--accent-ink);
  font-weight: 800; font-size: 0.95rem;
}
.hist-rank {
  position: absolute; right: -4px; bottom: -4px;
  width: 16px; height: 16px; border-radius: 50%;
  /* On ink the numeral takes the surface colour: ink is light in the dark theme. */
  background: var(--ink); color: var(--surface);
  font-size: 0.62rem; font-weight: 700; line-height: 16px; text-align: center;
  box-shadow: 0 0 0 2px var(--surface);
}

.hist-choice-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.hist-prodi {
  color: var(--ink); font-weight: 700; font-size: 0.94rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hist-uni {
  color: var(--muted); font-size: 0.8rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The date leads the row, level with the first pick and in a column of its
   own, so the dates run down the list: it is how an old analysis is looked
   for. */
.hist-date {
  flex: 0 0 5.6rem; align-self: flex-start; margin-top: 0.15rem;
  font-size: 0.8rem; font-weight: 600; line-height: 1.25; color: var(--ink-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The chance at a pick, as the report's first card prints it: the figure over
   the word for it. In ink, not the accent: it is a result, not a link. */
.hist-odds {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end;
  padding-left: 0.5rem; line-height: 1.25;
}
.hist-odds b {
  font-size: 1.05rem; font-weight: 800; letter-spacing: -0.02em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.hist-odds i { font-style: normal; font-size: 0.75rem; color: var(--muted); white-space: nowrap; }
/* A report saved before the estimates were computed has no chance to show and
   keeps the score it was listed by, captioned because a bare number says
   nothing. */
.hist-score { flex: 0 0 auto; display: flex; align-items: baseline; gap: 0.22rem; }
.hist-score b { font-size: 1.2rem; font-weight: 800; color: var(--ink); line-height: 1; }
.hist-score i { font-style: normal; font-size: 0.7rem; color: var(--muted); font-weight: 600; }

.hist-go { color: var(--muted); opacity: 0.7; font-size: 0.95rem; flex: 0 0 auto; }
.hist-card:hover .hist-go { opacity: 1; }
.hist-card:hover .hist-go { color: var(--accent-ink); }

@media (max-width: 575px) {
  /* No room for a date column beside two names and their figures: the date
     takes a line of its own above them. */
  .hist-card { padding: 0.8rem 0.85rem; gap: 0.5rem 0.6rem; flex-wrap: wrap; }
  .hist-date { flex: 0 0 100%; margin-top: 0; font-size: 0.75rem; font-weight: 500; color: var(--muted); }
  .hist-card-body { flex: 1 1 0; }
  .hist-crest { width: 32px; height: 32px; }
  .hist-prodi { font-size: 0.9rem; }
  .hist-odds b { font-size: 1rem; }
  /* The arrow is decoration once the whole card is the tap target. */
  .hist-go { display: none; }
}

/* ===== Credit dropdown ================================================ */

/* ===== Sign in (/masuk) ====================================================
   A heading, one line and Google's button, with a campus photograph taking the
   right half from lg up. It was a small card floating on a tall empty page
   over a purple-tinted wash, and it had no H1. */
/* ---- Signing in ---------------------------------------------------------
   One panel (.signin, views/partials/signin.php): the whole of the dialog
   that opens over a page, and the whole of the masuk page behind it. A flex
   column with align-items set, because a stretched chip became a bar as wide
   as the button under it. */
.signin { display: flex; flex-direction: column; align-items: flex-start; }
.signin .auth-h1 { font-size: 2rem; font-weight: 800; line-height: 1.05; letter-spacing: -0.03em; margin: 0 0 .5rem; }
.signin .auth-sub { margin: 0 0 1.5rem; font-size: 1rem; line-height: 1.6; color: var(--muted); }
.signin .btn-google, .signin .pk-note { width: 100%; }
.auth-legal { margin: 1rem 0 0; font-size: .8rem; line-height: 1.55; color: var(--muted); }
.auth-legal a { color: var(--ink-2); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.auth-legal a:hover { color: var(--accent-ink); }

/* The dialog (.pk-modal). Bootstrap's modal does the focus, the backdrop and
   Escape; the surface, the edge and the corner are the site's own. Sign-in
   uses it, and so does every question the site asks (assets/js/notify.js). */
.pk-modal .modal-dialog { max-width: 25rem; }
.pk-modal .modal-content {
  position: relative; padding: 2.25rem 2rem 1.9rem;
  background: var(--glass-panel); color: var(--ink);
  border: 1px solid var(--glass-edge); border-radius: 1.5rem;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lift);
}
.pk-modal-close { position: absolute; top: 1rem; right: 1rem; }
/* A question: its title, a line if it needs one, and the two answers. */
.pk-modal--ask .modal-dialog { max-width: 23rem; }
.pk-modal--ask .modal-content { padding: 1.75rem 1.75rem 1.5rem; }
.pk-modal-title { margin: 0; font-size: 1.2rem; font-weight: 800; line-height: 1.3; letter-spacing: -0.02em; }
.pk-modal-text { margin: .5rem 0 0; font-size: .95rem; line-height: 1.55; color: var(--muted); }
.pk-modal-acts { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1.4rem; }
.pk-modal-acts .btn { padding: .55rem 1.15rem; font-size: .95rem; }
/* On a phone it comes up from the bottom edge, where the thumb is, and
   covers the tab bar it would otherwise sit behind. */
@media (max-width: 575.98px) {
  .pk-modal .modal-dialog { max-width: none; margin: 0; min-height: 100%; align-items: flex-end; }
  .pk-modal .modal-content { border-radius: var(--radius) var(--radius) 0 0; border-width: 1px 0 0; padding: 2rem 1.25rem calc(1.75rem + env(safe-area-inset-bottom)); }
  .pk-modal.fade .modal-dialog { transform: translateY(2.5rem); }
  .pk-modal.show .modal-dialog { transform: none; }
  .pk-modal-acts { flex-direction: column-reverse; }
  .pk-modal-acts .btn { width: 100%; padding: .8rem 1rem; }
}

/* The page: the same panel, alone in the middle of the first screen. */
.auth-page { padding-block: 0; }
.auth-page .container { display: grid; place-items: center; min-height: calc(100dvh - 70px); padding-block: 2.5rem; }
.auth-card {
  width: 100%; max-width: 25rem; padding: 2.25rem 2rem 1.9rem;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
@media (max-width: 991.98px) {
  .auth-page .container { min-height: calc(100dvh - 62px - 84px); }
}

/* ===== The analysis form, signed out ==================================
   The form is open; signing in is asked for when it is submitted. The one
   thing that needs an account earlier is the rapor photo, and this is the
   line that says so, with a button dressed as a link because it posts the
   form (so what is typed so far comes along). */
.rapor-shot-teaser {
  display: flex; align-items: baseline; gap: .5rem;
  margin: 0 0 1rem; font-size: .9rem; line-height: 1.5; color: var(--ink-2);
}
.rapor-shot-teaser > i { flex: 0 0 auto; color: var(--muted); transform: translateY(.1rem); }
.rapor-shot-teaser-go {
  padding: 0; border: 0; background: none;
  font: inherit; font-weight: 700; color: var(--accent-ink);
  text-decoration: underline; text-decoration-color: var(--accent-line); text-underline-offset: 3px;
  cursor: pointer;
}
.rapor-shot-teaser-go:hover { text-decoration-color: currentColor; }
.rapor-shot-teaser-go:focus-visible { outline: 3px solid rgba(37, 99, 235, .35); outline-offset: 2px; border-radius: 4px; }

/* ===== Support tickets ================================================ */
.ticket-card {
  display: block; padding: 0.95rem 1.1rem;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  text-decoration: none; color: inherit;
  transition: all 0.15s ease;
}
.ticket-card:hover { border-color: var(--primary); transform: translateX(2px); box-shadow: 0 6px 18px rgba(0,0,0,0.05); }
.ticket-card-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.ticket-id { font-weight: 700; color: var(--muted); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.ticket-card-subject { font-weight: 600; margin-top: 0.3rem; line-height: 1.35; }
.ticket-card-meta {
  display: flex; flex-wrap: wrap; gap: 0.85rem;
  color: var(--muted); font-size: 0.78rem; margin-top: 0.45rem;
}
.ticket-card-meta i { margin-right: 0.2rem; }

.ticket-badge {
  display: inline-block; padding: 0.2rem 0.65rem; border-radius: 999px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em;
}
.ticket-badge--open     { background: var(--warn-wash); color: var(--warn-ink); }
.ticket-badge--answered { background: var(--ok-bg); color: var(--ok-ink); }
.ticket-badge--closed   { background: var(--surface-2); color: var(--ink-2); }

.ticket-thread { display: flex; flex-direction: column; gap: 0.85rem; }
.ticket-msg {
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  padding: 1rem 1.1rem;
}
.ticket-msg--admin { background: linear-gradient(180deg, var(--primary-soft), var(--surface)); border-color: rgba(0,98,255,0.3); }
.ticket-msg-head { display: flex; align-items: center; gap: 0.65rem; margin-bottom: 0.6rem; }
.ticket-msg-avatar {
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; font-size: 1rem;
  background: var(--surface-2); color: var(--ink); flex-shrink: 0;
}
.ticket-msg--admin .ticket-msg-avatar { background: var(--primary); color: #fff; }
.ticket-msg-author { font-weight: 600; font-size: 0.92rem; }
.ticket-msg-time { color: var(--muted); font-size: 0.75rem; }
.ticket-msg-body { color: var(--ink-2); line-height: 1.65; padding-left: 0.25rem; }

/* ===== Homepage engagement: how-it-works, sample, stats, testimonials = */

@keyframes hiwLine {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Sample analysis card */

.sample-card-tabs .active { color: var(--accent-ink); font-weight: 700; border-bottom: 2px solid var(--primary); padding-bottom: 0.25rem; margin-bottom: -0.85rem; }

/* Stats band */

/* Testimonials */
.testimonial-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 1.25rem;
}
.testimonial-card {
  position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 18px; padding: 1.75rem 1.5rem 1.25rem;
  transition: all 0.2s ease;
}
.testimonial-card:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0,0,0,0.06); border-color: rgba(0,98,255,0.25); }

.testimonial-quote {
  position: relative; z-index: 1;
  color: var(--ink-2); line-height: 1.6; font-size: 0.95rem;
  margin: 0 0 1.25rem;
}
.testimonial-foot {
  display: flex; align-items: center; gap: 0.75rem;
  padding-top: 0.85rem; border-top: 1px solid var(--line);
  flex-wrap: wrap;
}
.testimonial-avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--primary-deep));
  color: #fff; font-weight: 700; font-size: 1.1rem;
  display: grid; place-items: center;
  flex-shrink: 0;
}
.testimonial-meta { flex-grow: 1; min-width: 0; }
.testimonial-name { font-weight: 700; font-size: 0.92rem; }
.testimonial-role { color: var(--muted); font-size: 0.78rem; margin-top: 0.1rem; }
.testimonial-badge {
  font-size: 0.7rem; font-weight: 700;
  padding: 0.25rem 0.65rem; border-radius: 999px;
  letter-spacing: 0.01em;
  flex-shrink: 0;
}

/* ---- Share bar (result exposure) -------------------------------------- */

/* =========================================================================
   Shared components
   ========================================================================= */

/* Icon tile. Flat: the blue and amber 3D gradients and their inner highlights
   were an accent each and a bevel (tasteskill §4.2). */
.ico3d {
  width: 56px; height: 56px; border-radius: var(--radius-sm); display: grid; place-items: center;
  font-size: 1.5rem; flex: 0 0 auto;
  background: var(--primary-soft); color: var(--accent-ink);
}

/* A plain card. It carried a solid offset edge to look pressable, on cards
   nobody can press. */
.card-chunky {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}

/* Compact page header for app pages: small, left-aligned, no decoration.
   The big centred treatment is reserved for the marketing landing page. */
.page-head { margin-bottom: 1.5rem; }
.page-head h1 {
  font-size: 1.5rem; font-weight: 800; letter-spacing: -0.025em;
  margin: 0 0 0.25rem;
}
.page-head p { color: var(--muted); font-size: 0.94rem; margin: 0; max-width: 44rem; }
.page-head .ico3d { display: none; }        /* retire the oversized icon blobs */
.page-head .section-eyebrow { display: none; } /* title already says it */
@media (max-width: 575px) {
  .page-head h1 { font-size: 1.3rem; }
  .page-head p { font-size: 0.9rem; }
}

/* Soft stat / info pill */

/* Empty state */
.empty-state { text-align: center; padding: 3rem 1.5rem; }
.empty-state .ico3d { margin: 0 auto 1.1rem; }
.empty-state h3 { font-weight: 800; margin-bottom: 0.4rem; }
.empty-state p { color: var(--muted); margin-bottom: 1.2rem; }

/* National ranking badge (used in explore + dropdowns) */

/* =========================================================================
   Prodi cards with 5-year sparkline (explore detail)
   ========================================================================= */
.prodi-cards { display: flex; flex-direction: column; gap: 0.6rem; }
.prodi-card {
  background: var(--surface); border: 2px solid var(--line); border-radius: var(--radius);
  padding: 0.9rem 1.1rem; transition: border-color .15s, box-shadow .15s;
}
.prodi-card:hover { border-color: var(--line-strong); box-shadow: var(--shadow-sm); }
.prodi-card-main { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.prodi-card-info { flex: 1 1 220px; min-width: 0; }
.prodi-card-name { font-weight: 700; font-size: 0.95rem; line-height: 1.35; }
.prodi-card-tags { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.35rem; }

.trend-tag {
  display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: 0.72rem; font-weight: 700; padding: 0.2rem 0.55rem; border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
}
      /* more applicants = tougher */
    /* fewer applicants = easier */

.trend-tag--folder { background: var(--primary-soft); color: var(--accent-ink-2); }

.prodi-card-nums { display: flex; align-items: center; gap: 1.25rem; flex: 0 0 auto; }
.pnum { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; min-width: 62px; }
.pnum-val { font-weight: 800; font-size: 1.05rem; line-height: 1; }
.pnum-lbl { font-size: 0.68rem; color: var(--muted); font-weight: 600; white-space: nowrap; }
@media (max-width: 575px) {
  .prodi-card-nums { width: 100%; justify-content: space-between; gap: 0.5rem; }
}

.prodi-card-toggle {
  margin-top: 0.7rem; background: none; border: none; padding: 0.2rem 0;
  color: var(--accent-ink); font-size: 0.8rem; font-weight: 700; cursor: pointer;
}
.prodi-card-toggle .caret { transition: transform .18s; display: inline-block; }
.prodi-card-toggle.open .caret { transform: rotate(180deg); }
.prodi-card-history { margin-top: 0.75rem; padding-top: 0.85rem; border-top: 2px dashed var(--line); }
.prodi-card-history[hidden] { display: none; }

/* Per-year history table inside a prodi card */
.hist-table { width: 100%; margin-top: 1rem; border-collapse: collapse; font-size: 0.84rem; }
.hist-table th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 800; padding: 0.4rem 0.5rem; border-bottom: 2px solid var(--line); }
/* A fixed row height, because the content of a row varies: 2025 and older
   carry a rate pill, 2026 carries the words "belum ada" and no pill at all.
   Letting height follow content made the year rows step unevenly down the
   table — the 2026 row was 6px shorter than the rest. */
.hist-table td { padding: 0.5rem; height: 2.75rem; border-bottom: 1px solid var(--line); font-weight: 600; }
.hist-table td .text-secondary { white-space: nowrap; }
.hist-table tr:last-child td { border-bottom: none; }
.hist-table tr.is-latest td { background: var(--primary-soft); font-weight: 800; }
.hist-table .ri { text-align: right; }
.yr-tag { font-size: 0.62rem; font-weight: 800; background: var(--primary); color: #fff; padding: 0.1rem 0.4rem; border-radius: 999px; margin-left: 0.3rem; vertical-align: middle; }

/* =========================================================================
   Analysis report: mobile overflow fixes
   Long PTN names, badge rows and logo+text flex rows were blowing out of
   their cards (measured clipping of 100-150px on Ringkasan / Pilihan).
   Flex/grid children need min-width:0 before they will shrink.
   ========================================================================= */
.analysis .row > [class*="col-"] { min-width: 0; }
.analysis .d-flex > * { min-width: 0; }
.analysis .d-flex > .flex-shrink-0,
.analysis .d-flex > img,
.analysis .d-flex > .icon-pill { min-width: auto; }

/* Long university / program names wrap instead of forcing the card wider. */
.analysis .result-uni,
.analysis .result-prog,
.analysis .fit-title,
.analysis h3, .analysis h4, .analysis h5 { overflow-wrap: anywhere; }

/* Badge rows wrap as a group, but a short badge shouldn't break mid-phrase. */
.analysis .badge { white-space: nowrap; text-align: left; }
.analysis .badge.badge-wrap { white-space: normal; }

/* Tab strip: show it is scrollable instead of looking chopped off. */

@media (max-width: 575px) {
  .analysis .card-soft { padding: 1rem !important; }
  .analysis .mini { font-size: 0.72rem; }
}

/* University / program names: wrap to 2 lines instead of being truncated with
   an ellipsis. On a phone "INSTITUT TEKNOLOGI SEPU…" is useless to a student. */

@media (max-width: 575px) { .uni-name-wrap { font-size: 0.88rem; } }

/* Reality check + confidence: the honesty layer of the report. */

/* Ringkasan choice card: on phones the logo + big % squeeze the name column,
   so shrink the logo and let the name use the full width below. */
@media (max-width: 575px) {
  .analysis .result-logo { width: 34px; height: 34px; }
  .analysis-panel .card-soft .d-flex.gap-3 { gap: 0.6rem !important; }
  
}

/* =========================================================================
   Report: "wrapped" visual language. Big numbers first, prose second.
   ========================================================================= */

@media (max-width: 575px) { .wrap-stats { grid-template-columns: repeat(2, 1fr); gap: 0.5rem; } }

.wrap-stat {
  position: relative; overflow: hidden;
  border-radius: var(--radius); padding: 1rem 0.75rem; text-align: center;
  background: var(--surface); border: 2px solid var(--line);
  animation: statPop 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.wrap-stats .wrap-stat:nth-child(1) { animation-delay: 0.05s; }
.wrap-stats .wrap-stat:nth-child(2) { animation-delay: 0.13s; }
.wrap-stats .wrap-stat:nth-child(3) { animation-delay: 0.21s; }
.wrap-stats .wrap-stat:nth-child(4) { animation-delay: 0.29s; }
@keyframes statPop {
  from { opacity: 0; transform: translateY(14px) scale(0.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.wrap-stat-val {
  display: block; font-size: 1.65rem; font-weight: 800; line-height: 1;
  letter-spacing: -0.03em; margin-bottom: 0.2rem;
}

.wrap-stat--primary .wrap-stat-val { color: var(--accent-ink); }
.wrap-stat--success .wrap-stat-val { color: var(--c-green); }
.wrap-stat--warning .wrap-stat-val { color: var(--warn-ink); }
.wrap-stat--danger  .wrap-stat-val { color: var(--danger); }
.wrap-stat--secondary .wrap-stat-val { color: var(--muted); }

/* Chance bar under each choice */

@keyframes chanceGrow { from { width: 0; } to { width: var(--w); } }

/* Score ring: count-up reveal so the headline number feels earned */
.analysis-head .score-ring { animation: ringIn 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes ringIn { from { opacity: 0; transform: scale(0.8) rotate(-12deg); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .wrap-stat, .chance-fill, .analysis-head .score-ring { animation: none !important; }
  
}

/* Insight cards: replace bullet lists so the report reads as scannable
   moments instead of a wall of dot points. */

/* Numbered takeaways */
.takeaways { display: flex; flex-direction: column; gap: 0.6rem; }
.takeaway {
  display: flex; gap: 0.75rem; align-items: flex-start;
  background: var(--surface); border: 2px solid var(--line); border-radius: var(--radius-sm);
  padding: 0.75rem 0.9rem; font-size: 0.92rem; line-height: 1.5;
}

.takeaway span:last-child { overflow-wrap: anywhere; }

/* =========================================================================
   REPORT: fewer things on screen, same information
   The page had a summary paragraph, a TL;DR, a stat strip, two rationale
   paragraphs, a reality box and a confidence note all competing above the
   fold. Long prose now folds; what stays is the number and the name.
   ========================================================================= */

/* One share action, styled as a row rather than a bar of buttons. */

/* Fold-away reasoning. Reads as a quiet link until someone wants the detail. */
.why > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: .3rem;
  font-size: .76rem; font-weight: 700; color: var(--accent-ink);
  padding: .15rem 0;
}
.why > summary::-webkit-details-marker { display: none; }
.why > summary::after {
  content: "\e136"; font-family: "Phosphor"; font-size: .7rem;
  transition: transform .18s ease;
}
.why[open] > summary::after { transform: rotate(180deg); }
.why > p {
  margin: .5rem 0 0; font-size: .8rem; line-height: 1.6; color: var(--muted);
}

/* TL;DR: three quiet facts, not three cards. */

/* Reality check: a marked line, not another boxed card. */

/* Panel headings: quieter, no icon clutter. */
.panel-title {
  font-size: .68rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); margin: 1.5rem 0 .75rem;
}
.panel-title i { display: none; }

/* Choice panels: let them breathe. */
.analysis-panel .card-soft { border-radius: 18px; }

/* Inline figures. Replaces rows of stat tiles and badge clusters: same numbers,
   one line, no boxes. */
.facts { display: flex; flex-wrap: wrap; gap: .4rem .5rem; align-items: center; }
.fact {
  font-size: .78rem; color: var(--muted);
  padding: .25rem .6rem; border-radius: 999px;
  background: var(--surface-2, var(--surface-2));
}
.fact b { font-weight: 800; color: var(--ink); }
.facts--sm .fact { font-size: .72rem; padding: .2rem .5rem; }

/* Label/value row, for pairs that used to need a card. */
.kv {
  display: flex; justify-content: space-between; gap: 1rem; align-items: baseline;
  padding: .5rem 0; border-bottom: 1px solid var(--line); font-size: .84rem;
}
.kv:last-of-type { border-bottom: 0; }
.kv span { color: var(--muted); }
.kv b { font-weight: 700; text-align: right; }

/* Plain lists. Replaces note-cards, which framed every single line. */
.plain { list-style: none; padding: 0; margin: 0; }
.plain li {
  position: relative; padding: .35rem 0 .35rem 1.15rem;
  font-size: .84rem; line-height: 1.55;
}
.plain li::before {
  content: ""; position: absolute; left: 0; top: .95em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--primary);
}

.why .plain li { font-size: .8rem; color: var(--muted); }

/* Alternatives: a list, not three cards. */

.alt {
  display: grid; grid-template-columns: auto 1fr; gap: .1rem .85rem;
  align-items: center; padding: .8rem 0; border-bottom: 1px solid var(--line);
}
.alt:last-child { border-bottom: 0; }

/* Takeaways lose their card wrapper. */
.takeaways { display: flex; flex-direction: column; gap: .1rem; }
.takeaway {
  display: flex; gap: .7rem; align-items: flex-start;
  padding: .6rem 0; font-size: .86rem; line-height: 1.55;
  border-bottom: 1px solid var(--line);
}
.takeaway:last-child { border-bottom: 0; }

/* Chart stands alone now that its card is gone. */
.sem-chart { padding: .5rem 0 0; }

.panel-title.mt-0 { margin-top: 0; }

/* The earlier .takeaway rule still painted a card behind each line; the list
   now carries its own separators, so drop the frame. */
.takeaways .takeaway {
  background: none; border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; padding: .6rem 0; box-shadow: none;
}
.takeaways .takeaway:last-child { border-bottom: 0; }

/* =========================================================================
   AROUND THE REPORT
   The report itself is assets/css/report.css, loaded only where a report is
   shown. Until October 2026 this file also carried some 900 lines of it: the
   first build of the chapters and eleven rounds of correction on top, every
   one of them shipped to every page. What is left here is what other pages
   share with the report page: the pill buttons under the card, the PTN crest
   and its lettermark, and the follow-up questions.
   ========================================================================= */

/* ---- the actions under the card: the app's own pill ---- */
.act {
  font-size: .95rem; font-weight: 700; padding: .8rem 1.4rem;
  border: 1.5px solid var(--line); border-radius: 999px; background: var(--surface);
  color: var(--ink); text-decoration: none; cursor: pointer;
  transition: border-color .15s, transform .15s, box-shadow .15s;
}
.act:hover { border-color: var(--primary); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.act--go { background: var(--primary); border-color: var(--primary); color: #fff; }
.act--go:hover { background: var(--primary-dark); border-color: var(--primary-dark); box-shadow: var(--shadow-primary); }

/* ---- PTN crests ---- */
.crest {
  width: 34px; height: 34px; object-fit: contain; flex: 0 0 auto;
  background: var(--surface); border-radius: 9px; padding: 3px;
}

/* Lettermarks wherever a PTN has no crest file. Same box, same weight, so a
   grid of cards stays a grid instead of gaining holes. */
.uni-card-logo--ltr,
.explore-card-logo--ltr,
.uni-detail-logo--ltr {
  display: grid; place-items: center;
  background: var(--primary-soft); color: var(--accent-ink);
  font-weight: 800; line-height: 1;
}
.explore-card-logo--ltr { padding: 0; border-color: transparent; font-size: 1.05rem; }
.uni-detail-logo--ltr { font-size: 1.6rem; border-radius: 14px; }
.crest-ltr {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  border-radius: .5rem; background: var(--primary-soft); color: var(--accent-ink);
  font-weight: 800; line-height: 1;
  width: 2rem; height: 2rem; font-size: .85rem;
}

/* Written for a chapter that is gone, and kept because the admin dashboard's
   trend chips have been laid out on top of these three declarations ever
   since (admin.css sets their colours and size over them). */
.trend { display: inline-flex; align-items: center; gap: .5rem; font-size: .86rem; margin: 0 0 .5rem; }
.trend i { font-size: .95rem; }
.trend--flat { color: rgba(255,255,255,.7); }

/* =========================================================================
   CHAT, on the brand kit
   Reads as the last part of the report rather than a widget bolted under it:
   the same type scale, no card frame, no icon in the heading.
   ========================================================================= */
.qa { max-width: 34rem; margin: 2.5rem auto 0; padding-top: 2rem; border-top: 1px solid var(--line); }
.qa-eyebrow { font-size: .85rem; font-weight: 600; color: var(--muted); margin: 0 0 .6rem; }
.qa-head { font-size: 1.4rem; font-weight: 800; letter-spacing: -.025em; margin: 0 0 .7rem; }
.qa-body { font-size: .95rem; line-height: 1.6; color: var(--muted); margin: 0 0 1rem; }

.chat-thread {
  display: flex; flex-direction: column; gap: 1.25rem;
  max-height: 420px; overflow-y: auto; padding: 0;
  background: none; border: 0; border-radius: 0; margin: 1.25rem 0;
}
.chat-empty { font-size: .9rem; color: var(--muted); padding: .5rem 0; }
.chat-msg { display: flex; gap: .7rem; align-items: flex-start; }

.chat-avatar {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; font-size: .9rem;
  background: var(--primary-soft); color: var(--accent-ink);
}
.chat-msg--user .chat-avatar { background: var(--primary); color: #fff; }
/* Assistant answers read as prose; only the reader's own words get a bubble. */
.chat-bubble {
  background: none; border: 0; padding: 0; max-width: 100%;
  font-size: .95rem; line-height: 1.65; color: var(--ink);
}
.chat-msg--user .chat-bubble {
  background: var(--primary); color: #fff; border-radius: var(--radius-sm);
  padding: .6rem .85rem; max-width: 82%;
}
.chat-typing { color: var(--muted); font-style: italic; }

.chat-suggestions { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: .85rem; }
.chat-chip {
  border: 1.5px solid var(--line); background: var(--surface); color: var(--ink);
  padding: .55rem .9rem; border-radius: 999px; font-size: .85rem; font-weight: 600;
  cursor: pointer; transition: border-color .15s, transform .15s, box-shadow .15s;
}
.chat-chip:hover { border-color: var(--primary); transform: translateY(-1px); box-shadow: var(--shadow-sm); }

.chat-form .input-group { border: 1.5px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--surface); }
.chat-form .input-group:focus-within { border-color: var(--primary); }
.chat-form textarea {
  resize: none; overflow-y: auto; max-height: 160px; border: 0; box-shadow: none !important;
  padding: .85rem 1rem; font-size: .95rem; background: none;
}
.chat-form .btn { border: 0; border-radius: 0; padding: 0 1.15rem; }
.chat-err { font-size: .84rem; color: var(--danger); min-height: 1.2em; margin-top: .4rem; }
.chat-bubble .chat-md-list li::marker { color: var(--accent-ink); }


/* ---- Desktop nav: grouping ---------------------------------------------
   Three or four things you do sit in the bar; the pages you read once sit
   behind Info; who you are and the way out sit behind the avatar. The bare
   logout icon this replaces said nothing about which account it would end. */


/* ---- Sign-in by phone + one-time code --------------------------------- */
.otp-head { text-align: center; margin-bottom: 1.5rem; }
.otp-ico {
  display: inline-grid; place-items: center; width: 54px; height: 54px;
  border-radius: 18px; background: var(--primary-soft); color: var(--accent-ink);
  font-size: 1.5rem; margin-bottom: 0.85rem;
}
.otp-title { font-size: 1.35rem; font-weight: 800; letter-spacing: -0.025em; margin: 0 0 0.35rem; }

/* The +62 is fixed so nobody has to guess whether to type it. */

.phone-field .form-control { border-radius: 0 0.9rem 0.9rem 0; }

/* Six digits, spaced out so the code is easy to read back from WhatsApp. */

/* Sign-in doubles as sign-up, so the phone screen says so. */

/* ---- Google sign-in button --------------------------------------------
   Google's brand guidelines want their mark on a neutral surface, so this
   one deliberately does not use the app's chunky primary style. */
.btn-google {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.7rem;
  padding: 0.85rem 1.4rem; border-radius: 999px;
  background: var(--surface); color: var(--ink); font-weight: 700; font-size: 1rem;
  border: 2px solid var(--line); cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.btn-google:hover:not(:disabled) {
  border-color: var(--line-strong); box-shadow: 0 6px 16px rgba(var(--shadow-rgb),0.10); transform: translateY(-1px);
}
.btn-google:disabled { opacity: 0.65; cursor: default; }
.btn-google-ico { display: inline-flex; flex: 0 0 auto; }

/* The progress note only exists for messages apply.js sets at runtime, such as
   resuming an in-flight job. Empty by default, so it takes no space. */
#analyzeProgressMsg:empty { display: none; }

/* ---- Competitiveness chart (applicants per seat, per year) --------------
   Replaces a raw applicant-count sparkline. Counts alone cannot say whether a
   prodi got harder, because the number of seats moves too; this ratio can. */
.ratio-chart-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.5rem; margin-bottom: 0.5rem;
}
.ratio-chart-title { font-size: 0.76rem; font-weight: 700; color: var(--muted); }

.ratio-chart { display: flex; align-items: flex-end; gap: 0.45rem; height: 104px; }
.ratio-col {
  flex: 1 1 0; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end; height: 100%; gap: 0.25rem;
}
/* The value sits on the bar, so the chart is readable without hovering. */
.ratio-val { font-size: 0.72rem; font-weight: 800; color: var(--ink); line-height: 1; }
/* One calm colour. The bar height carries the comparison; colour would only
   repeat what every bar in a single prodi already says. */
.ratio-bar {
  width: 100%; max-width: 46px; border-radius: 6px 6px 3px 3px;
  background: var(--accent-line); transition: height .3s ease;
}
.ratio-col.is-latest .ratio-bar { background: var(--primary); }
.ratio-yr { font-size: 0.68rem; color: var(--muted); font-weight: 700; }
.ratio-col.is-latest .ratio-yr { color: var(--ink); }

/* ---- Signed-out gate on Jelajahi PTN ----------------------------------
   One notice for the whole page rather than a prompt on every card, which
   would drown the catalogue it is trying to advertise. */
.gate-note {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.85rem 1rem; margin-bottom: 1.25rem;
  background: var(--primary-soft); border-radius: var(--radius);
}
.gate-note-ico {
  flex: 0 0 auto; width: 36px; height: 36px; border-radius: 11px;
  display: grid; place-items: center; font-size: 0.95rem;
  background: var(--surface); color: var(--accent-ink);
}
.gate-note-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.35; }
.gate-note-txt b { font-size: 0.88rem; font-weight: 700; }
.gate-note-txt i { font-style: normal; font-size: 0.78rem; color: var(--muted); }
.gate-note .btn { flex: 0 0 auto; }

/* The locked value sits where a number would, so rows keep their rhythm. */
.stat-lock { color: var(--muted); opacity: 0.5; font-size: 0.85em; }

@media (max-width: 575px) {
  .gate-note { flex-wrap: wrap; }
  .gate-note .btn { width: 100%; }
}

/* ---- Quiz result gate --------------------------------------------------- */
/* Finished the quiz while signed out. Calm, not a wall: the point is that the
   result exists and is one click away, so nothing here shouts. */
.quiz-lock {
  padding: 1.75rem;
  text-align: center;
}
.quiz-lock-mark {
  width: 3rem;
  height: 3rem;
  margin: 0 auto 0.9rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(37, 99, 235, 0.08);
  color: var(--accent-ink);
  font-size: 1.15rem;
}
.quiz-lock-count {
  margin: 0 0 0.25rem;
  color: var(--muted);
  font-size: 0.95rem;
}
.quiz-lock-count b { color: var(--ink); }
.quiz-lock-sub {
  margin: 0 0 1.1rem;
  color: var(--muted);
  font-size: 0.9rem;
}
.quiz-lock-list {
  list-style: none;
  margin: 1.1rem 0 1.4rem;
  padding: 0;
  text-align: left;
  display: grid;
  gap: 0.55rem;
}
.quiz-lock-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: 0.925rem;
  line-height: 1.45;
  color: var(--ink);
}
.quiz-lock-list i {
  color: var(--accent-ink);
  font-size: 0.9rem;
  line-height: 1.5;
  flex: 0 0 auto;
}
.quiz-lock-alt {
  margin: 1rem 0 0;
  text-align: center;
  font-size: 0.9rem;
}
.quiz-lock-alt a { color: var(--muted); }
.quiz-lock--inline { padding: 1.5rem; }

/* ---- hosted checkout embed --------------------------------------------
   The provider's snippet ships height="100%", which collapses to nothing
   unless an ancestor has a definite height. A min-height keeps the frame
   usable on its own; the embed script then resizes it to its real content,
   and if that script is blocked or slow the box stays scrollable rather
   than empty. */
.checkout-embed {
  position: relative;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--bg);
}
.checkout-embed iframe {
  display: block;
  width: 100%;
  min-height: 560px;
  border: 0;
}
@media (max-width: 575.98px) {
  .checkout-embed iframe { min-height: 620px; }
}

/* ---- Tambah Energi -----------------------------------------------------
   Two ways in, each a card with its question as the heading: a code you
   have, and a code you buy. One under the other on a phone; side by side
   from 992px, where the first stays in view while the checkout scrolls. */
.te { display: grid; gap: 1.25rem; }
.te-code { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.te-card { min-width: 0; padding: 1.5rem; }
.te-h {
  display: block; margin: 0 0 0.75rem;
  font-size: 1.05rem; font-weight: 800; letter-spacing: -0.01em; line-height: 1.35; color: var(--ink);
  text-wrap: balance;
}
.te-buy .te-h { margin-bottom: 0.25rem; }
.te-sub { margin: 0 0 1rem; font-size: 0.875rem; line-height: 1.55; color: var(--muted); }
/* The school route, said once and quietly under the field. It was a tinted
   banner with a button of its own between the two cards. */
.te-school {
  display: flex; align-items: flex-start; gap: 0.6rem;
  margin: 0; padding: 0 0.25rem;
  font-size: 0.875rem; line-height: 1.55; color: var(--ink-2);
}
.te-school > i { flex: none; margin-top: 0.2rem; font-size: 1.05rem; color: var(--muted); }
.te-school a { font-weight: 700; white-space: nowrap; }
@media (min-width: 992px) {
  .te { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 1.5rem; align-items: start; }
  .te-code { position: sticky; top: 5.5rem; }
}
@media (max-width: 575.98px) {
  .te-card { padding: 1.15rem; }
}

/* ---- support: solve it before the ticket -------------------------------
   Five questions account for most tickets, and every one of them has an
   answer that needs no human. Sitting above the form, they cost the student
   a click instead of a day. */
.selfhelp { margin-bottom: 1.5rem; }
.selfhelp-h {
  display: flex; align-items: center; gap: .5rem;
  font-size: 1rem; font-weight: 700; color: var(--ink); margin: 0 0 .8rem;
}
.selfhelp-h i { color: var(--warn); }
/* Two independent stacks rather than one grid. Sharing grid rows meant
   opening an item on the left grew the row and pushed the right-hand
   column down — content the reader was not touching moved on them. */
.selfhelp-grid { display: grid; gap: .5rem; align-items: start; }
.selfhelp-col { display: grid; gap: .5rem; align-content: start; }
@media (min-width: 768px) { .selfhelp-grid { grid-template-columns: 1fr 1fr; gap: .75rem; } }

.sh {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); overflow: hidden;
}
.sh > summary {
  display: flex; align-items: center; gap: .5rem;
  padding: .8rem .95rem; cursor: pointer; list-style: none;
  font-size: .9rem; font-weight: 600; color: var(--ink);
  transition: background .15s ease;
}
.sh > summary::-webkit-details-marker { display: none; }
.sh > summary::after {
  content: "\e136"; font-family: "Phosphor";
  margin-left: auto; font-size: .7rem; color: var(--muted);
  transition: transform .2s cubic-bezier(.3,.8,.4,1);
}
.sh[open] > summary::after { transform: rotate(180deg); }
.sh > summary:hover { background: var(--bg); }
.sh[open] > summary { background: var(--primary-soft); color: var(--accent-ink-2); }
.sh > p {
  margin: 0; padding: .2rem .95rem .9rem;
  font-size: .85rem; line-height: 1.6; color: var(--muted);
  animation: shIn .22s cubic-bezier(.2,.7,.3,1) both;
}
.sh > p b { color: var(--ink); font-weight: 700; }
@keyframes shIn { from { opacity: 0; transform: translateY(-.25rem); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .sh > summary::after, .sh > p { transition: none; animation: none; }
}

/* =========================================================================
   SEARCHABLE SELECT
   Moved here from apply.css. The component is used by the analysis form
   and the support ticket form, but its styles only shipped with
   assetGroup "apply" — so on the support page the dropdown initialised,
   worked, and rendered as an unstyled list. A component used by more than
   one page belongs in the shared sheet.
   ========================================================================= */
/* ---- Searchable select ------------------------------------------------- */
.ss { position: relative; }

/* Cards must be positioned so the active one can lift above its siblings
   (each .glass-card forms a stacking context via backdrop-filter). */
.glass-card { position: relative; z-index: 1; }

.ss.open { position: relative; z-index: 20; }

.ss-trigger {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.9rem;
  font-size: 1rem; text-align: left; color: var(--ink);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.ss-trigger:hover:not(:disabled) { border-color: var(--line-strong); }
.ss-trigger:disabled { background: var(--surface-2); cursor: not-allowed; opacity: 0.7; }
.ss.open .ss-trigger { border-color: var(--primary); box-shadow: 0 0 0 0.2rem rgba(0, 98, 255, 0.14); }

.ss-value { display: flex; align-items: center; gap: 0.45rem; min-width: 0; flex: 1; overflow: hidden; }
.ss-value-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ss-value .ss-logo { width: 20px; height: 20px; object-fit: contain; flex: 0 0 auto; }
.ss-value.has-value { color: var(--ink); font-weight: 500; }

.ss-arrow { color: var(--muted); transition: transform 0.2s ease; flex: 0 0 auto; }
.ss.open .ss-arrow { transform: rotate(180deg); }

/* The list a dropdown opens is a panel over the page, like a menu of the bar,
   and is made of the same glass. */
.ss-panel {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; right: 0;
  background: var(--glass-panel);
  border: 1px solid var(--glass-edge);
  border-radius: 1.15rem;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lift);
  overflow: hidden;
  display: none;
}
.ss.open .ss-panel { display: block; animation: rise 0.18s ease both; }
.ss-panel.ss-portal { animation: rise 0.15s ease both; }

.ss-search { padding: 0.6rem; border-bottom: 1px solid var(--line); }
.ss-search input {
  width: 100%; border: 1.5px solid var(--line); border-radius: var(--radius-sm);
  padding: 0.5rem 0.75rem; font-size: 0.95rem; outline: none;
}
.ss-search input:focus { border-color: var(--primary); }

.ss-options { max-height: 260px; overflow-y: auto; padding: 0.35rem; }
.ss-option {
  padding: 0.6rem 0.7rem; border-radius: var(--radius-sm); cursor: pointer;
  display: flex; align-items: center; gap: 0.6rem; font-size: 0.95rem;
}
/* Lit the way a link of the bar is lit, not tinted. */
.ss-option:hover, .ss-option.active { background: var(--nav-lit); }
.ss-option .ss-logo { width: 24px; height: 24px; object-fit: contain; flex: 0 0 auto; }
.ss-option .ss-sub { color: var(--muted); font-size: 0.8rem; }
.ss-empty { padding: 1rem; text-align: center; color: var(--muted); font-size: 0.9rem; }

/* ---- Jelajahi detail: find a prodi inside one campus ------------------ */
.prodi-find { margin: 0 0 1.25rem; }
.prodi-find[hidden] { display: none; }
.prodi-find .explore-search { margin: 0; }
.prodi-find-count {
  margin: .5rem .25rem 0; font-size: .82rem; color: var(--muted); font-weight: 600;
}
.prodi-find-count[hidden] { display: none; }
/* Paging and searching are different modes; explore.js shows one at a time. */
[data-prodi-pager-wrap][hidden] { display: none; }
.prodi-card[hidden] { display: none !important; }

/* The search and the order, side by side: the order used to be a line of grey
   text saying which one the page had picked. A pill, so the select stands
   beside the search pill as its pair. */
.prodi-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin: 0 0 1.25rem; }
.prodi-tools .prodi-find { flex: 1 1 18rem; margin: 0; }
.prodi-tools + .prodi-find-count { margin: -.6rem .25rem 1rem; }
.prodi-sort { flex: 0 0 auto; display: flex; align-items: center; gap: .5rem; margin: 0; }
.prodi-sort .form-select {
  width: auto; min-height: 3.5rem; padding: .7rem 2.75rem .7rem 1.35rem;
  border: 2px solid var(--line); border-radius: 999px;
  font-size: .95rem; font-weight: 600; color: var(--ink);
  box-shadow: 0 4px 16px rgba(0,0,0,0.04);
}
.prodi-sort .btn[hidden] { display: none; }
@media (max-width: 575px) {
  .prodi-sort { flex: 1 1 100%; }
  .prodi-sort .form-select { width: 100%; min-height: 3.1rem; }
}

/* The applicant count keeps its amber, in a shade that can be read:
   Bootstrap's .text-warning measured 1.5:1 on this ground. */
.pnum-val--apps { color: var(--warn-ink); }

/* From a prodi's history to the analysis, with that prodi already chosen. */
.prodi-card-cta { margin-top: .9rem; text-align: right; font-size: .85rem; font-weight: 700; }
.prodi-card-cta a { text-decoration: none; }

/* ---- Jelajahi PTN: easier to use, same page ----------------------------
   Suggestions under the search as you type. The items are the analysis
   form's dropdown options (.ss-option), so the two lists read as one thing.
   The form is lifted above the grid that follows it: both are stacking
   contexts (the reveal animation makes them so), and the later one would
   otherwise paint over the list. */
.explore-search-wrap { position: relative; z-index: 20; }
.explore-suggest {
  position: absolute; z-index: 30; top: calc(100% + 8px); left: 0; right: 0;
  padding: .35rem; text-align: left;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.explore-suggest[hidden] { display: none; }
.explore-suggest .ss-option { color: var(--ink); text-decoration: none; }
.explore-suggest-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.explore-suggest-text > span:first-child { font-weight: 600; }
.explore-suggest-ltr {
  display: inline-grid; place-items: center; flex: 0 0 auto; width: 24px; height: 24px;
  border-radius: .4rem; background: var(--primary-soft); color: var(--accent-ink);
  font-size: .75rem; font-weight: 800;
}
.explore-suggest-ico { flex: 0 0 auto; width: 24px; text-align: center; color: var(--muted); }

/* Province, under the search: a small pill of the same family. The native
   select is what a browser without scripting gets; with it, the site's own
   dropdown stands in its place (see .explore-ss below). */
.explore-filter { display: flex; justify-content: center; margin-top: .75rem; }
.explore-filter .form-select {
  width: auto; max-width: 100%; padding: .45rem 2.5rem .45rem 1.1rem;
  border: 2px solid var(--line); border-radius: 999px;
  font-size: .88rem; font-weight: 600; color: var(--ink);
}
/* Bootstrap gives .form-select a display of its own, which outranks the
   browser's rule for [hidden]: without this the native select would sit
   beside the dropdown that replaced it. */
.explore-filter .form-select[hidden], .prodi-sort .form-select[hidden] { display: none; }

/* ---- The two selects, as the site's own dropdown ----------------------
   The same component as the form fields and the page picker, shaped as a pill
   because here it stands beside a search pill. */
.explore-ss { flex: 0 0 auto; }
.explore-ss .ss-trigger {
  width: auto; border-width: 2px; border-radius: 999px;
  font-weight: 600; color: var(--ink);
}
.explore-ss .ss-value, .explore-ss .ss-value.has-value { font-weight: 600; }
.explore-filter .explore-ss .ss-trigger { min-width: 13.5rem; padding: .45rem .95rem .45rem 1.15rem; font-size: .88rem; }
.prodi-sort .explore-ss .ss-trigger {
  min-width: 14.5rem; min-height: 3.5rem; padding: .7rem 1.15rem .7rem 1.35rem;
  font-size: .95rem; box-shadow: 0 4px 16px rgba(0,0,0,0.04);
}
/* Styled on the panel's own class: opening it moves it to <body>. */
.explore-ss-panel { min-width: 15rem; }
.explore-ss-panel .ss-option { padding: .5rem .7rem; font-size: .9rem; }
.explore-ss-panel .ss-option.active { font-weight: 700; }
@media (max-width: 575px) {
  .prodi-sort .explore-ss { flex: 1 1 auto; }
  .prodi-sort .explore-ss .ss-trigger { width: 100%; min-height: 3.1rem; }
}

/* One jenjang at a time on a campus that has several. */
.jenjang-chips { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1rem; }
.jenjang-chips a {
  display: inline-flex; align-items: baseline; gap: .4rem; padding: .4rem .95rem;
  border: 2px solid var(--line); border-radius: 999px; background: var(--surface);
  font-size: .86rem; font-weight: 700; color: var(--muted); text-decoration: none;
  transition: border-color .15s, color .15s;
}
.jenjang-chips a span { font-size: .82em; font-weight: 600; }
.jenjang-chips a:hover { border-color: var(--line-strong); color: var(--ink); }
.jenjang-chips a[aria-current] { border-color: var(--primary); background: var(--primary-soft); color: var(--accent-ink); }

/* A prodi's history opens from anywhere on the top of its card. */
.prodi-card.has-history .prodi-card-main { cursor: pointer; }

/* A closed prodi's two locks are one link to the way in. Laid out exactly as
   the two figures it stands in for, so open and closed cards line up. */
.pnum-locked {
  display: flex; align-items: center; gap: 1.25rem;
  color: inherit; text-decoration: none; border-radius: .6rem;
}
.pnum-locked:hover .stat-lock, .pnum-locked:focus-visible .stat-lock { color: var(--accent-ink); opacity: 1; }
.pnum-locked:focus-visible { outline: 3px solid rgba(37, 99, 235, .35); outline-offset: 4px; }
@media (max-width: 575px) {
  /* Three even columns, so the pair inside the link sits where the second
     and third figures of an open card sit. */
  .prodi-card-nums { display: grid; grid-template-columns: repeat(3, 1fr); }
  .prodi-card-nums > .pnum:nth-child(1) { justify-self: start; }
  .prodi-card-nums > .pnum:nth-child(2) { justify-self: center; }
  .prodi-card-nums > .pnum:nth-child(3) { justify-self: end; }
  .pnum-locked { grid-column: span 2; display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
  .pnum-locked > .pnum:first-child { justify-self: center; }
  .pnum-locked > .pnum:last-child { justify-self: end; }
}

/* ---- Pager ------------------------------------------------------------
   Previous and next are the moves people actually make, so they are the wide
   targets; the numbers are for jumping. Bootstrap's .pagination was doing this
   job and looked like a framework demo. */
.pgr { margin: 1.5rem 0 .5rem; }
.pgr-row {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: .35rem;
}
.pgr-step {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .45rem .85rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface, #fbf7f3); color: var(--ink);
  font-size: .86rem; font-weight: 700; text-decoration: none;
  white-space: nowrap;
}
.pgr-step:hover { border-color: var(--primary); color: var(--accent-ink); }
.pgr-step.is-off { opacity: .35; pointer-events: none; }
.pgr-step i { font-size: .78rem; }

.pgr-nums { display: inline-flex; align-items: center; gap: .2rem; margin: 0 .3rem; }
.pgr-n {
  display: inline-grid; place-items: center;
  min-width: 2rem; height: 2rem; padding: 0 .35rem;
  border-radius: 999px; color: var(--muted);
  font-size: .84rem; font-weight: 700; text-decoration: none;
  font-variant-numeric: tabular-nums;
}
.pgr-n:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); color: var(--ink); }
/* The page you are on: lit, like the bar's current link. */
.pgr-n.is-on { background: var(--nav-lit); color: var(--ink); }
.pgr-gap { color: var(--muted); padding: 0 .1rem; opacity: .7; }

/* ---- Jump to a page, and what page you are on -------------------------
   The number window is the fast move and the first thing to go when the row
   runs out of width; this is what is left when it does. A real <select> in a
   real form, so it works with scripting off and gets the platform's own
   picker on a phone, which is a better list of 40 pages than anything drawn
   here would be. */
.pgr-foot {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: .5rem 1rem;
  margin-top: .85rem;
}
.pgr-jump {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .8rem; font-weight: 700; color: var(--muted);
}
.pgr-jump-lbl, .pgr-jump-of { white-space: nowrap; }
/* The native select is the no-JS control and the form's field; pager.js hides
   it and builds a SearchableSelect over the top, so what anyone actually sees
   is the same dropdown the analysis form uses. Styled here only for the few
   seconds before that happens, and for whoever has no scripting. */
.pgr-sel {
  appearance: none; -webkit-appearance: none;
  padding: .3rem 1.6rem .3rem .7rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface, #fbf7f3) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5l4 4 4-4'/%3E%3C/svg%3E") no-repeat right .5rem center/.7rem;
  color: var(--ink); font: inherit; font-weight: 800;
  font-variant-numeric: tabular-nums; line-height: 1.4;
  cursor: pointer;
}
.pgr-sel:hover { border-color: var(--line-strong); }
.pgr-sel:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; border-color: var(--primary); }
.pgr-sel[hidden] { display: none; }

/* ---- The page picker, as the site's own dropdown ----------------------
   Same component as the form fields, sized for a row of pills rather than for
   a form row: a pill instead of a rounded rectangle, room for three digits,
   and the panel narrow enough that a list of page numbers does not open a
   dropdown as wide as the article. */
.pgr-ss { flex: 0 0 auto; }
.pgr-ss .ss-trigger {
  width: auto; min-width: 4.1rem;
  padding: .3rem .55rem .3rem .8rem;
  border-width: 1px; border-radius: 999px;
  font-size: .8rem; font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.pgr-ss .ss-value { flex: 0 1 auto; justify-content: flex-start; }
.pgr-ss .ss-value-text { color: var(--ink); }
.pgr-ss .ss-arrow { font-size: .7rem; }
/* Styled on the panel's own class, not as a descendant of .pgr-ss: opening it
   moves it to <body>, so any selector that goes through the trigger's wrapper
   stops matching exactly when it is needed. */
.pgr-ss-panel { min-width: 6rem; }
.pgr-ss-panel .ss-options { max-height: 244px; padding: .3rem; }
.pgr-ss-panel .ss-option {
  padding: .45rem .65rem; font-size: .88rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
  justify-content: center;
}
.pgr-ss-panel .ss-option.active { background: var(--nav-lit); color: var(--ink); }
.pgr-go {
  padding: .3rem .7rem; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface, #fbf7f3); color: var(--ink);
  font-size: .78rem; font-weight: 700; cursor: pointer;
}
.pgr-go:hover { border-color: var(--primary); color: var(--accent-ink); }

.pgr-say {
  margin: 0; text-align: center;
  font-size: .78rem; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 480px) {
  /* The numbers are the first thing to go: prev/next still move the list and
     the selector still reaches every page. Which is also why both grow to a
     44px target here — with the number row gone they are the only two controls
     left, and they measured 36px and 29px. */
  .pgr-nums { display: none; }
  .pgr-step { flex: 1 1 auto; justify-content: center; min-height: 44px; }
  .pgr-sel,
  .pgr-ss .ss-trigger { min-height: 44px; padding-top: .55rem; padding-bottom: .55rem; }
  .pgr-foot { flex-direction: column; gap: .6rem; }
}
[data-prodi-group][hidden] { display: none !important; }

/* An option that cannot be chosen right now. It stays listed and stays
   searchable — "where did UI go" is a worse question than "why is UI grey" —
   but it is visibly out of reach and the sub line says why. */
.ss-option.is-blocked {
  cursor: not-allowed; opacity: .45; filter: grayscale(1);
}
.ss-option.is-blocked:hover { background: transparent; }
.ss-option.is-blocked .ss-sub { color: var(--warn-ink); opacity: 1; font-weight: 600; }

.ss-options::-webkit-scrollbar { width: 8px; }
.ss-options::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 8px; }

/* =========================================================================
   Quiz result — verdict, catalogue programmes, ranking.
   Light surfaces only; colour comes from --cl, set per cluster from the
   brand tokens in quiz.json. The cluster colour carries the icon chip, the
   meter fill and the leading rank bar, never a text background.
   ========================================================================= */

/* The quiz's own notes are .pk-note now; see Notices above. */

/* -- Verdict ------------------------------------------------------------- */

.qz-verdicts { display: grid; gap: 1rem; margin-bottom: 1.25rem; }
.qz-verdicts--split { grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr)); }

.qz-verdict {
  position: relative;
  overflow: hidden;
  padding: 1.6rem 1.5rem 1.4rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.qz-verdict::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--cl, var(--primary));
}
.qz-verdict-top { display: flex; align-items: center; gap: 0.65rem; margin-bottom: 0.9rem; }
.qz-chip {
  display: grid;
  place-items: center;
  width: 2.35rem;
  height: 2.35rem;
  flex: none;
  border-radius: 0.8rem;
  font-size: 1.1rem;
  color: var(--cl, var(--primary));
  background: color-mix(in srgb, var(--cl, var(--primary)) 12%, var(--surface));
}
.qz-eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.qz-title { font-size: 1.45rem; font-weight: 800; letter-spacing: -0.02em; margin: 0 0 0.5rem; }
.qz-blurb { margin: 0 0 1.25rem; color: var(--muted); font-size: 0.95rem; line-height: 1.6; }

.qz-meter { padding-top: 1.1rem; border-top: 1px solid var(--line); }
.qz-meter-top { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
.qz-num { font-size: 1.9rem; font-weight: 800; letter-spacing: -0.03em; line-height: 1; }
.qz-num-suffix { font-size: 0.9rem; font-weight: 600; color: var(--muted); letter-spacing: 0; }
.qz-read { font-size: 0.85rem; font-weight: 600; color: var(--muted); text-align: right; }
.qz-track { height: 0.5rem; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.qz-fill { display: block; height: 100%; border-radius: 999px; background: var(--cl, var(--primary)); }

/* -- Panels (programmes, ranking) ---------------------------------------- */

.qz-panel {
  padding: 1.5rem;
  margin-bottom: 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.qz-panel-head { margin-bottom: 1.15rem; }
.qz-panel-title { font-size: 1.05rem; font-weight: 800; letter-spacing: -0.01em; margin: 0 0 0.3rem; }
.qz-panel-sub { margin: 0; font-size: 0.875rem; line-height: 1.55; color: var(--muted); }

.qz-list { list-style: none; margin: 0; padding: 0; }
.qz-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.8rem 0;
  border-top: 1px solid var(--line);
}
.qz-item:first-child { border-top: 0; padding-top: 0; }
.qz-item-main { min-width: 0; display: flex; flex-direction: column; gap: 0.12rem; }
.qz-item-name { font-weight: 700; font-size: 0.94rem; line-height: 1.35; }
.qz-item-campus { font-size: 0.8rem; color: var(--muted); line-height: 1.35; }
.qz-item-stats { display: flex; align-items: center; gap: 0.9rem; flex: none; }
.qz-stat { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.qz-stat b { color: var(--ink); font-weight: 700; }
.qz-rate {
  min-width: 3.6rem;
  padding: 0.28rem 0.5rem;
  border-radius: 0.6rem;
  background: var(--primary-soft);
  color: var(--accent-ink-2);
  font-size: 0.86rem;
  font-weight: 800;
  text-align: center;
}

.qz-panel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.15rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}
.qz-foot-note { font-size: 0.8rem; line-height: 1.55; color: var(--muted); max-width: 34rem; }
.qz-foot-note strong { color: var(--ink); font-weight: 700; }
.qz-more { font-size: 0.875rem; font-weight: 700; color: var(--accent-ink); text-decoration: none; white-space: nowrap; }
.qz-more:hover { color: var(--primary-dark); text-decoration: underline; }

/* -- Ranking bars -------------------------------------------------------- */

.qz-bars { display: flex; flex-direction: column; gap: 0.55rem; }
.qz-bar { display: grid; grid-template-columns: 1.2rem minmax(0, 11rem) 1fr 2rem; align-items: center; gap: 0.7rem; }
.qz-bar-rank { font-size: 0.78rem; font-weight: 700; color: var(--muted); text-align: right; }
.qz-bar-label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  font-size: 0.85rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.qz-bar-label i { color: var(--cl, var(--muted)); flex: none; }
.qz-bar-track { height: 0.45rem; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.qz-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--line-strong); }
.qz-bar-score { font-size: 0.82rem; font-weight: 700; color: var(--muted); text-align: right; }
/* Only the rumpun the page argues for carries colour; the rest read as
   context, which is what keeps nine bars from looking like a fruit salad. */
.qz-bar.is-lead .qz-bar-label { color: var(--ink); font-weight: 700; }
.qz-bar.is-lead .qz-bar-fill { background: var(--cl, var(--primary)); }
.qz-bar.is-lead .qz-bar-score { color: var(--ink); }

/* -- Next steps ---------------------------------------------------------- */

.qz-cta {
  padding: 1.75rem 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(160deg, var(--primary-soft), var(--surface) 70%);
}
.qz-cta-title { font-size: 1.15rem; font-weight: 800; letter-spacing: -0.01em; margin: 0 0 0.35rem; }
.qz-cta-sub { margin: 0 0 1.15rem; font-size: 0.9rem; line-height: 1.6; color: var(--muted); max-width: 38rem; }
.qz-cta-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.qz-restart { font-size: 0.875rem; font-weight: 600; color: var(--muted); text-decoration: none; padding: 0.4rem 0.5rem; }
.qz-restart:hover { color: var(--ink); text-decoration: underline; }

@media (max-width: 640px) {
  .qz-panel, .qz-verdict, .qz-cta { padding-left: 1.15rem; padding-right: 1.15rem; }
  .qz-item { align-items: flex-start; flex-direction: column; gap: 0.45rem; }
  .qz-item-stats { gap: 0.75rem; }
  /* The label column would squeeze to a couple of glyphs at this width, so
     the bar moves under its own name rather than being truncated away. */
  .qz-bar { grid-template-columns: 1.2rem 1fr 2rem; row-gap: 0.3rem; }
  .qz-bar-track { grid-column: 2 / 3; }
  .qz-cta-row .btn { width: 100%; }
  .qz-restart { width: 100%; text-align: center; }
}

/* -- Quiz intro ----------------------------------------------------------
   The start button sits with the heading, where the eye already is, and what
   the quiz covers sits beside it from lg up. It was one narrow column with the
   button at the foot of a panel and empty page on both sides. */
/* minmax(0, 1fr), not the implicit auto column: an auto column is never
   narrower than its widest unbreakable content, so a long jurusan name in
   "Hasil sebelumnya" pushed the whole page off the right edge of a phone. */
.qz-intro { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.75rem; max-width: 64rem; margin: 0 auto; }
@media (min-width: 992px) {
  .qz-intro { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: 3.5rem; align-items: start; }
}
.qz-intro .page-head p { max-width: 34rem; }
.qz-start { margin: 0 0 1.75rem; }
.qz-side { padding: 1.5rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.qz-side-title { font-size: 1rem; font-weight: 800; letter-spacing: -0.01em; margin: 0 0 1rem; }

.qz-chip--sm { width: 1.85rem; height: 1.85rem; border-radius: 0.6rem; font-size: 0.9rem; }

.qz-rules { list-style: none; margin: 0 0 1.25rem; padding: 0; display: flex; flex-direction: column; gap: 0.7rem; }
.qz-rules li { display: flex; gap: 0.7rem; align-items: flex-start; font-size: 0.925rem; line-height: 1.55; color: var(--ink); }
.qz-rules i { color: var(--accent-ink); font-size: 1rem; line-height: 1.5; flex: none; }

.qz-caveat {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  margin: 0 0 1.5rem;
  padding: 0.85rem 1rem;
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.55;
}
.qz-caveat i { color: var(--muted); flex: none; line-height: 1.5; }

.qz-clusters { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: 0.5rem; }
.qz-cluster {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
  min-width: 0;
}
.qz-cluster > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* -- Previous result recap ----------------------------------------------- */

.qz-prev-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.9rem; }
.qz-prev-when { font-size: 0.8rem; color: var(--muted); }
.qz-prev-list { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.15rem; }
.qz-prev-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.qz-prev-label { font-weight: 700; font-size: 0.95rem; line-height: 1.3; flex: 1; min-width: 0; }
.qz-prev-score { font-size: 1.15rem; font-weight: 800; letter-spacing: -0.02em; flex: none; }
.qz-prev-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }

@media (max-width: 640px) {
  .qz-prev-actions .btn { width: 100%; }
  .qz-start .btn { width: 100%; }
}

/* One accent (tasteskill §4.2). Each cluster used to wear its own hue from
   config/quiz.json (blue, purple, red, teal, green, amber, pink), mixed toward
   ink so the icons cleared 3:1. The label and the icon already say which
   cluster it is, so the colour was decoration. All of it is cobalt now, and
   the --cl the views still set inline is simply not read. */
.qz-chip { color: var(--accent-ink); background: var(--primary-soft); }
.qz-verdict::before { background: var(--primary); }
.qz-fill { background: var(--primary); }
.qz-bar-label i { color: var(--accent-ink); }
.qz-bar.is-lead .qz-bar-fill { background: var(--primary); }

/* The scale explainer sits under the verdict grid rather than inside each
   card: printed once per card it appeared twice on every tie result. */
.qz-scale {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  margin: -0.35rem 0 1.25rem;
  padding: 0 0.25rem;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--muted);
}
.qz-scale i { flex: none; line-height: 1.55; }
.qz-scale em { font-style: normal; font-weight: 700; color: var(--ink); }

.rf-card {
  padding: 1.35rem;
  margin-bottom: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
/* Sentence-case card titles. An uppercase eyebrow in every card made the page
   read like a form. */
.rf-h { margin: 0 0 0.85rem; font-size: 1rem; font-weight: 800; letter-spacing: -0.01em; }

/* -- The code: a ticket, since it gets passed from hand to hand ---------- */

.rf-ticket {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.55rem 0.55rem 1.15rem;
  border: 2px dashed var(--accent-line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.rf-code {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  background: none;
  color: var(--ink);
  font-size: 1.45rem;
  font-weight: 800;
  /* Wide tracking so the code can be read off a screenshot one glyph at a
     time; it gets typed by hand at the other end. */
  letter-spacing: 0.14em;
  overflow-wrap: anywhere;
}
.rf-actions { display: flex; gap: 0.5rem; flex: 0 0 auto; }
.rf-actions .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 44px;
  padding-inline: 1rem;
}
.rf-copy.is-done { background: var(--c-green); border-color: var(--c-green); }
.rf-share[hidden] { display: none; }

.rf-note { margin: 0.75rem 0 0; font-size: 0.85rem; line-height: 1.55; color: var(--muted); }
.rf-note--top { margin: 0 0 0.85rem; }

.rf-count {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 1.1rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: 0.88rem;
  color: var(--muted);
}
.rf-count i { color: var(--accent-ink); font-size: 1.05rem; }
.rf-count b { color: var(--ink); font-weight: 800; }

/* -- Claim form ---------------------------------------------------------- */

.rf-form { display: flex; gap: 0.6rem; }
.rf-input {
  flex: 1 1 auto;
  min-width: 0;
  height: auto;
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius-sm);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.rf-input::placeholder { font-weight: 500; letter-spacing: 0.06em; color: var(--muted); text-transform: none; }
.rf-form .btn { flex: 0 0 auto; min-height: 44px; }

.rf-done {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  margin: 0;
  font-size: 0.925rem;
  line-height: 1.6;
  color: var(--muted);
}
.rf-done i { color: var(--c-green); flex: none; line-height: 1.6; }

@media (max-width: 575px) {
  .rf-card { padding: 1.15rem; }
  /* Stacked on a phone: the code gets the full width, the buttons share a row. */
  .rf-ticket { flex-direction: column; align-items: stretch; gap: 0.7rem; padding: 1rem 0.85rem 0.85rem; text-align: center; }
  .rf-code { font-size: 1.4rem; text-indent: 0.14em; }
  .rf-actions .btn { flex: 1 1 0; }
  .rf-form { flex-direction: column; }
}

/* A price tag that reads as part of the control it sits on, not a badge.
   Used on the analyse button and inline in the chat intro. */
.cost-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  padding: 0.1rem 0.42rem;
  border-radius: 999px;
  /* A tint of the ink itself, so it is there in both themes: a fixed dark
     veil was a chip on paper and nothing at all on the dark ground. */
  background: color-mix(in srgb, var(--ink) 9%, transparent);
  /* Explicit ink, not inherited. Dropped into muted body copy the tag was
     measured at 3.88:1 against its own tint — the tint darkens the ground
     while the inherited grey stays put, so the pair fails where the
     surrounding text passes. */
  color: var(--ink);
  font-size: 0.78em;
  font-weight: 800;
  line-height: 1.5;
  white-space: nowrap;
  vertical-align: 0.05em;
}
.cost-tag i { font-size: 0.9em; }

/* On the primary button the tag is outlined, not filled. Measured: at 12.7px
   it is normal text, so it needs 4.5:1, and any white veil over #2563eb heavy
   enough to read as a chip (alpha 0.22 gives 3.49:1) drops it below that. With
   no fill the label sits straight on the button at 5.17:1, and the border only
   has to clear the 3:1 that non-text elements need. */
.cost-tag--onbtn {
  margin-left: 0.5rem;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.72);
  color: #fff;
}


/* =========================================================================
   History: the tab pair, and the energy ledger under it.
   ========================================================================= */

.hist-tabs {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 1.25rem;
  padding: 0.25rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  width: fit-content;
  max-width: 100%;
}
.hist-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.45rem 1rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.hist-tab:hover { color: var(--ink); }
/* The tab you are on is lit the way the bar's current link is. Cobalt is
   for the thing to press, and a tab that is already open is not one. */
.hist-tab.is-on { background: var(--nav-lit); color: var(--ink); }

/* -- Summary strip ------------------------------------------------------- */

/* What is left, large; what came in and went out, smaller beside it; the way
   to get more at the far end. Three equal figures used to sit here in thirds,
   with the balance last. */
.en-sum {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.9rem 1.75rem;
  padding: 1.1rem 1.25rem;
  margin-bottom: 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.en-stat { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.en-stat-num {
  font-size: 1.15rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.en-stat--now { padding-right: 1.75rem; border-right: 1px solid var(--line); }
.en-stat--now .en-stat-num { font-size: 1.9rem; letter-spacing: -0.03em; line-height: 1; }
.en-sum-add { margin-left: auto; white-space: nowrap; }
/* Deep enough to stay readable at this weight on white: measured 4.8:1 for
   the green and 5.9:1 for the red, where the brand's lighter tones sat at 3.1
   and 3.9. The sign in the markup carries the meaning either way. */
.en-stat-num--in { color: var(--ok-ink); }
.en-stat-num--out { color: var(--bad-ink); }
.en-stat-lbl { font-size: 0.78rem; line-height: 1.4; color: var(--muted); }

/* -- Ledger rows --------------------------------------------------------- */

.en-list {
  list-style: none;
  margin: 0;
  padding: 0 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.en-row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.8rem 0;
  border-top: 1px solid var(--line);
}
.en-row:first-child { border-top: 0; }

.en-ico {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  font-size: 1.05rem;
  color: var(--muted);
}

.en-txt { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; }
.en-what {
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.35;
  /* Refund labels are long; they wrap rather than being cut off. */
  overflow-wrap: anywhere;
}
.en-when { font-size: 0.78rem; color: var(--muted); font-variant-numeric: tabular-nums; }

.en-amt { display: flex; flex-direction: column; align-items: flex-end; gap: 0.1rem; flex: none; }
.en-delta { font-size: 0.95rem; font-weight: 800; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Energy coming in is the rarer row and the one looked for. The sign still
   says it without the colour. */
.en-delta--in { color: var(--ok-ink); }

.en-bal { font-size: 0.72rem; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* With the width for it a movement is one line: what, when, how much, what
   was left. Stacked, fifteen of them ran a screen and a half with the middle
   of every row empty. */
@media (min-width: 768px) {
  .en-row { padding: 0.62rem 0; }
  .en-txt { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 1rem; }
  .en-when { flex: none; font-size: 0.8rem; }
  .en-amt { flex-direction: row; align-items: baseline; gap: 0; }
  .en-delta { min-width: 4.75rem; text-align: right; }
  .en-bal { min-width: 5.75rem; text-align: right; font-size: 0.78rem; }
}

@media (max-width: 560px) {
  .en-list { padding-left: 1rem; padding-right: 1rem; }
  /* The balance and the button share the first line; in and out take the
     second. A grid, because a wrapping row put the button between them. */
  .en-sum { display: grid; grid-template-columns: auto auto 1fr; padding: 1rem; gap: 0.85rem 1.5rem; }
  .en-stat--now { grid-area: 1 / 1 / 2 / 3; padding-right: 0; border-right: 0; }
  .en-stat--now .en-stat-num { font-size: 1.7rem; }
  .en-sum-add { grid-area: 1 / 3; justify-self: end; margin-left: 0; }
  .hist-tab { padding-inline: 0.85rem; }
}

/* An empty history is a line and a button, not a poster: the heading here was
   larger than the page's own title. */
[data-hist-panel] .empty-state { padding: 2.25rem 1.5rem; }
[data-hist-panel] .empty-state h3 { margin-bottom: 0.9rem; font-size: 1.05rem; }

/* Where the number row is on screen and lists every page, "Halaman [1] dari 3"
   under it says the same thing a second time. On a phone the numbers go and
   the selector is what is left, so it stays there. */
@media (min-width: 481px) {
  [data-hist-panel] .pgr--all .pgr-jump { display: none; }
}

/* Bootstrap ships .text-primary on .bg-primary-subtle at 3.93:1, under AA for
   the 12px the badges are actually set at. Same hue, one step darker. */
.badge.bg-primary-subtle.text-primary { color: var(--accent-ink-2) !important; }

/* =========================================================================
   COOKIE NOTICE
   Here rather than in a file of its own because it is on every page. The
   legal pages it links to are styled by read.css, with Info SNBP and Tentang.
   ========================================================================= */

/* ---- cookie notice -------------------------------------------------------
   One sentence, then its two actions set apart from it: a link to the detail
   and the button that puts it away. A strip above the dock on a phone
   (--dock-clear is the dock's top edge); on a desktop a small bar in
   the middle of the bottom edge, as wide as its words. */
.consent {
  position: fixed; z-index: 1045; left: .75rem; right: .75rem;
  bottom: calc(var(--dock-clear) + 0.6rem);
  display: flex; align-items: center; gap: .5rem 1rem; flex-wrap: wrap;
  padding: .7rem .7rem .7rem 1rem;
  background: var(--glass-panel); border: 1px solid var(--glass-edge); border-radius: 1.15rem;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-lift);
  opacity: 0; transform: translateY(12px);
  transition: opacity .2s ease, transform .2s ease;
}
.consent[hidden] { display: none; }
.consent.is-in { opacity: 1; transform: none; }
.consent.is-going { opacity: 0; transform: translateY(12px); }
@media (min-width: 992px) {
  /* No tab bar up here. */
  /* One row, so it takes the bar's own shape: a capsule. */
  .consent { left: 50%; right: auto; bottom: 1.25rem; width: max-content; max-width: calc(100vw - 2.5rem); flex-wrap: nowrap; padding: .5rem .5rem .5rem 1.35rem; border-radius: 999px; transform: translate(-50%, 12px); }
  .consent.is-in { transform: translate(-50%, 0); }
  .consent.is-going { transform: translate(-50%, 12px); }
}
.consent p { flex: 1 1 14rem; margin: 0; font-size: .86rem; line-height: 1.5; color: var(--ink-2); }
.consent-acts { flex: 0 0 auto; display: flex; align-items: center; gap: .35rem; margin-left: auto; }
.consent-acts a { padding: .4rem .7rem; font-size: .84rem; font-weight: 700; color: var(--ink-2); text-decoration: none; border-radius: 999px; }
.consent-acts a:hover { color: var(--accent-ink); background: var(--surface-2); }
.consent button {
  min-height: 38px; padding: .4rem 1.1rem; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line-strong); background: transparent; color: var(--ink);
  font-size: .84rem; font-weight: 700;
}
.consent button:hover { background: var(--surface-2); }
.consent button:focus-visible, .consent-acts a:focus-visible { outline: 3px solid rgba(37, 99, 235, .35); outline-offset: 2px; }
/* The notice's only button is filled. When the banner asks a question, yes
   and no are both real answers and neither is dressed as the afterthought. */
.consent [data-consent-ok] { background: var(--ink); border-color: var(--ink); color: var(--surface); }
.consent [data-consent-ok]:hover { background: var(--ink-2); border-color: var(--ink-2); }
@media (prefers-reduced-motion: reduce) { .consent { transition: none; } }
/* A phone: one slim row above the dock. The two-line card it was, on top of
   the dock, covered about a third of every screen of a first visit. The
   question stays, the reason is left to Detail (the words are in the page). */
@media (max-width: 991px) {
  .consent { left: .6rem; right: .6rem; flex-wrap: nowrap; gap: .4rem; padding: .4rem .4rem .4rem .9rem; border-radius: 1.25rem; }
  .consent p { flex: 1 1 auto; min-width: 0; font-size: .82rem; font-weight: 600; line-height: 1.3; color: var(--ink); }
  .consent-why { display: none; }
  .consent-acts { gap: .1rem; }
  .consent-acts a { padding: .35rem .5rem; font-size: .8rem; }
  .consent button { min-height: 34px; padding: .3rem .8rem; font-size: .8rem; }
}


/* =========================================================================
   SCHOOL PARTNERSHIP
   One statement and one small form, and nothing else: the offer as a
   headline on the left, a card of four fields on the right, the pair centred
   in the screen. Stacked on a phone. Cobalt is on the one phrase of the
   headline and on the button.
   ========================================================================= */
.sk { padding-block: 2rem 3rem; }
.sk-wrap { display: grid; gap: 2.25rem; }
@media (min-width: 992px) {
  /* The screen less the top bar (71px) and the footer with the space above
     it (107px + 48px), so the pair sits in the middle of what is left. */
  .sk { min-height: calc(100dvh - 71px - 155px); display: flex; align-items: center; padding-block: 1.25rem; }
  .sk-wrap { width: 100%; grid-template-columns: minmax(0, 1.2fr) 26rem; gap: 4rem; align-items: center; }
  .sk > .pt-done { width: 100%; }
}
@media (min-width: 1200px) { .sk-wrap { gap: 6rem; } }

.sk-h1 {
  margin: 0; font-size: clamp(2.4rem, 5.4vw, 4.3rem); font-weight: 800;
  line-height: 1.03; letter-spacing: -.045em; color: var(--ink);
}
.sk-h1 em { font-style: normal; color: var(--accent-ink); }
.sk-sub { margin: 1.1rem 0 0; font-size: clamp(1.05rem, 1.6vw, 1.22rem); line-height: 1.5; color: var(--ink-2); }

.sk-form {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.4rem; box-shadow: var(--shadow-sm);
}
@media (min-width: 576px) { .sk-form { padding: 1.9rem; } }
.sk-form-h { margin: 0 0 1.2rem; font-size: 1.08rem; font-weight: 800; letter-spacing: -.02em; color: var(--ink); }
.sk-send { display: block; width: 100%; margin-top: .35rem; min-height: 54px; }
.sk-form-note { margin: .95rem 0 0; text-align: center; font-size: .88rem; color: var(--muted); }
/* The field a script fills and a person never meets. */
.sk-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* Where a student goes instead of the form. */
.sk-student { margin: 1.1rem 0 0; text-align: center; font-size: .92rem; color: var(--muted); }
.sk-student a { font-weight: 700; color: var(--accent-ink); text-decoration: underline; text-decoration-color: var(--accent-line); text-underline-offset: 3px; }
.sk-student a:hover { text-decoration-color: currentColor; }

.pt-field { margin-bottom: 1.05rem; }
/* #b91c1c, not --danger: #ef4444 is 3.8:1 on white, too faint for small text. */
.pt-err { margin: .35rem 0 0; font-size: .8rem; font-weight: 600; color: var(--bad-ink); }
.sk-form .form-control.is-invalid { border-color: var(--danger); }
.sk-form .form-control.is-invalid:focus { box-shadow: 0 0 0 4px #fee2e2; }

.pt-done { text-align: center; padding: 2.5rem 0; }
.pt-done-ico {
  display: inline-grid; place-items: center; width: 3.25rem; height: 3.25rem; margin-bottom: .85rem;
  border-radius: 50%; background: var(--primary-soft); color: var(--accent-ink); font-size: 1.6rem;
}
.pt-done h1 { margin: 0 0 .35rem; font-size: clamp(1.5rem, 3vw, 1.9rem); font-weight: 800; letter-spacing: -.02em; }
.pt-done p { margin: 0 auto 1.5rem; max-width: 22rem; line-height: 1.6; color: var(--muted); }


/* =========================================================================
   THEMES
   One set of tokens, two sets of values. The brand does not move: cobalt is
   #2563eb on both, every filled cobalt surface keeps it, and white type on
   cobalt stays white. What changes is the ground, and the step the accent
   takes when it has to be *text* on that ground: #2563eb reads 3.7:1 on the
   dark page, so --accent-ink lifts to #7aa5ff there; on the warm light page
   it deepens one step to #1d4ed8, because #2563eb only reaches 4.4:1 once the
   ground stops being white.

   Dark follows the reader's system setting. data-theme on <html> overrides it
   either way, which is what the local ?theme= switch in views/layout.php uses.
   ========================================================================= */
:root {
  color-scheme: light;

  /* Secondary prose, the sunk surface under a card, the heavier hairline. */
  --ink-2: #49443e;
  --surface-2: #ebe4de;
  --line-strong: #d0c8c0;
  /* What a campus crest sits on. Crests are drawn for white paper. */
  --crest-plate: #ffffff;

  /* The accent as text or as an icon, which is not the same job as the accent
     as a fill: these two are the ones that have to clear 4.5:1 on the ground.
     #2563eb measures 4.4:1 on --bg and 4.1:1 on --surface-2 now that both are
     warm and a few steps down from white, so as text the accent takes its dark
     step: 5.8:1 on the page, 5.3:1 on the sunk surface. Every filled cobalt
     surface still uses --primary, unchanged. */
  --accent-ink: var(--primary-dark);
  --accent-ink-2: var(--primary-deep);

  /* A hairline in the accent's family, for tinted panels. */
  --accent-line: #cfdcf4;

  /* The ground a photograph is washed with, as an rgb triplet. It has to equal
     --bg: the wash is meant to fade the picture into the page, and a white wash
     on a warm page left a bright halo around both photo panels. Changing it
     costs contrast on the type over the photo, so the alphas in landing.css
     were re-measured against this value. */
  --scrim-rgb: 242, 237, 231;

  /* State, in three steps each: the ink, the pill it sits in, and the wash
     behind a whole note. Green, amber and red mean the same thing in both
     themes; only their lightness swaps.
     The pills and washes came down and warmed with everything else, so green
     and amber had to deepen to hold 4.5:1 on them: #15803d measured 4.3:1 on
     the new green pill and #b45309 4.2:1 on the amber one. Red already had the
     headroom and is untouched. The washes are a real tint now rather than a
     near-white, since a near-white panel on a near-white page was invisible. */
  --ok-ink: #14713a;
  --ok-bg: #ddf3e3;
  --ok-wash: #edf4ea;
  --warn-ink: #a1521a;
  --warn-bg: #f8ebc9;
  --warn-wash: #f8f0df;
  --warn-line: #ecd9a4;
  --bad-ink: #b91c1c;
  --bad-bg: #f8dedb;
  --bad-wash: #f9e9e5;
}

/* Dark is the light theme's own warm grey taken down, not slate navy. It sits
   on LCH hue 75 like the light ramp, at the same L* steps the navy ramp used,
   so the two themes are one palette in two lightnesses (tasteskill §4.2: do
   not move between warm and cool greys inside one project). Measured on the new
   ground: ink 15.5:1, --ink-2 11.3:1, --muted 7.5:1, the accent as text
   #7aa5ff 7.5:1 (6.7:1 on surfaces, 5.6:1 on the accent tint), a field's edge
   5.5:1. Cobalt fills are unchanged. */
/* The default: views/layout.php serves every page with this attribute, and a
   reader who picks light gets data-theme="light", which leaves the :root set
   above in charge. The system setting is not consulted, so the system
   preference gets no copy of these. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --shadow-rgb: 0, 0, 0;
  --bg: #181512;
  --surface: #23201d;
  --surface-2: #1d1a17;
  --crest-plate: #f1ece6;
  --ink: #f1ece6;
  --ink-2: #d1cbc5;
  --muted: #aba59e;
  --line: #37332f;
  --line-strong: #4b4743;
  --field-line: #938d87;

  --primary-soft: #232d46;
  --accent-ink: #7aa5ff;
  --accent-ink-2: #a9c4ff;
  --accent: #3fd6c9;
  --accent-soft: #10312f;

  --warn: #fbbf24;
  --danger: #f87171;
  --c-green: #4ade80;

  --shadow-sm: 0 2px 6px rgba(0, 0, 0, .40);
  --shadow-md: 0 12px 30px rgba(0, 0, 0, .50);
  --shadow-lg: 0 30px 70px rgba(0, 0, 0, .60);
  --accent-line: #243763;
  --scrim-rgb: 24, 21, 18;

  --ok-ink: #4ade80;
  --ok-bg: #123424;
  --ok-wash: #0f2318;
  --warn-ink: #fbbf24;
  --warn-bg: #3a2a08;
  --warn-wash: #241a06;
  --warn-line: #5a4310;
  --bad-ink: #fca5a5;
  --bad-bg: #3b1414;
  --bad-wash: #251010;

  --shadow-primary: 0 10px 22px rgba(0, 0, 0, .45);
}

/* Paper is white whatever the screen is. Printing an app page, or the report's
   own print view, resets every token to the light set, so a reader in dark mode
   does not send a black page to a printer. The attribute selector is repeated
   because it outranks the bare :root that would otherwise answer here. */
@media print {
  :root,
  :root[data-theme="dark"] {
    color-scheme: light;

    --bg: #ffffff;
    --surface: #ffffff;
    --surface-2: #ffffff;
    --ink: #0f172a;
    --ink-2: #334155;
    --muted: #475569;
    --line: #d7dee9;
    --line-strong: #b6c2d4;
    --field-line: #7f8ea3;

    --primary-soft: #eef4ff;
    --accent-ink: #1d4ed8;
    --accent-ink-2: #1e40af;
    --accent-line: #d3e2ff;

    --ok-ink: #15803d;
    --ok-bg: #dcfce7;
    --ok-wash: #ffffff;
    --warn-ink: #b45309;
    --warn-bg: #fef3c7;
    --warn-wash: #ffffff;
    --warn-line: #fde68a;
    --bad-ink: #b91c1c;
    --bad-bg: #fee2e2;
    --bad-wash: #ffffff;

    --scrim-rgb: 255, 255, 255;
    --shadow-rgb: 0, 0, 0;
  }
}

/* =========================================================================
   THE ENTRY PAGES, WARMED UP
   /masuk, /kuis, /kemitraan and /tentang are the pages somebody meets before
   they are a user, so they borrow the landing's manners: a heading with more
   presence, and a tinted panel where the panel is an aside rather than the
   work. Their buttons used to keep a solid bottom edge and sink when pressed;
   beside a bar made of glass that read as a second material, so every button
   on the site is the same flat pill now.
   ========================================================================= */
/* The one corner every card on the site has. */
.pg-warm { --pg-r: var(--radius); }

.pg-warm .qz-side, .pg-warm .qz-panel, .pg-warm .sk-form { border-radius: var(--pg-r); }

/* A heading with a little more presence than a working page's. */
.pg-warm .page-head h1 { font-size: clamp(1.7rem, 3.4vw, 2.15rem); letter-spacing: -0.03em; }
.pg-warm .page-head p { font-size: 1rem; }

/* The aside reads as a tinted panel: what the quiz covers. Body text on the
   tint is --ink-2, because --muted measures 4.2:1 there. */
.pg-warm .qz-side {
  background: var(--primary-soft);
  border-color: var(--accent-line);
}
.pg-warm .qz-cluster { background: var(--surface); border-color: var(--accent-line); }

/* The chip the sign-in page opens with, the same shape the hero uses. */
.auth-chip {
  display: inline-flex; align-items: center; gap: .45rem;
  margin-bottom: 1rem; padding: .4rem .9rem; border-radius: 999px;
  background: var(--primary-soft); color: var(--accent-ink-2);
  font-size: .88rem; font-weight: 700;
}


