/* ==========================================================================
   RKS WEB  ·  app.css
   The whole visual system for rksum.my. One file, no framework, no build step.

   Brand hues are fixed by ../../CLAUDE.md and are restated here verbatim.
   Everything else — radii, shadows, type, motion — is web kit, and this file
   is the only place any of it is decided. Never write a raw hex in a page.
   ========================================================================== */

/* ==========================================================================
   FONTS, SERVED FROM THIS DOMAIN

   These were loaded from fonts.googleapis.com until 12 Ogos 2026. That is a
   third party request on every page load, which hands Google the IP address
   and user agent of every visitor, including every applicant reading the
   rubric. The site tells people it collects nothing about them, so it cannot
   also be quietly introducing them to Google.

   Downloaded from Google Fonts and served from assets/fonts/ instead. The
   licence is the SIL Open Font Licence 1.1, which permits this.

   Latin and latin-ext only. Chinese is set in whatever CJK face the reader's
   device already has, because self hosting Noto Sans SC means over a hundred
   subset files, and every device that reads Chinese ships with a good one.
   ========================================================================== */
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-400i-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-400i-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-serif-500i-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-serif-500i-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   1 · TOKENS
   ========================================================================== */
:root {
  /* --- Brand. Fixed. Do not diverge. ------------------------------------- */
  --navy:        #001240;
  --navy-800:    #0A2050;
  --navy-700:    #16305F;
  --gold:        #FDC641;
  --gold-300:    #FFD976;
  --gold-700:    #8A6410;   /* text-safe gold on light grounds, 4.8:1 */
  --hibiscus:    #ED2920;
  /* One step down the same hue so white text on a filled button clears AA
     (4.58:1). The brand red itself only reaches 4.25:1, which fails. Same
     reasoning as --gold-700 — a tint for legibility, not a new hue. */
  --hibiscus-700: #E5231A;
  --hibiscus-900: #8E1710;  /* hibiscus as TEXT on parchment, 7.4:1 */

  /* --- Grounds ------------------------------------------------------------ */
  --parchment:   #FAF7F0;
  --cream:       #F3EEE2;
  --white:       #FFFFFF;
  --ink:         #0B1020;

  /* --- Navy-biased neutrals ---------------------------------------------- */
  --n-700: #333C4F;
  --n-500: #5C6779;
  --n-600: #4E596B;   /* muted TEXT. 6.6:1 on parchment, was 5.35 */
  --n-400: #8A93A6;
  --n-300: #B4BBC9;
  --n-200: #DBDFE7;
  --n-100: #EDEFF4;

  /* --- Applied roles ------------------------------------------------------ */
  --bg:          var(--parchment);
  --bg-2:        var(--cream);
  --card:        var(--white);
  --card-2:      #FFFFFF;
  --text:        var(--ink);
  /* One step darker than the old --n-500. Muted text carries a lot of the
     real content on this site, not just captions, and 5.35:1 was thin for
     someone reading 14px on a phone outdoors. 6.6:1 keeps the hierarchy
     obvious against 17.7:1 body text while being materially easier to read. */
  --text-muted:  var(--n-600);
  --line:        #E5E1D6;
  --line-strong: #D4CFC0;
  --accent:      var(--hibiscus-700);
  --accent-text: var(--hibiscus-900);
  /* Hibiscus for a navy ground. Not theme dependent, because the header strip
     and the footer are navy in every theme: hibiscus-700 is a body-text red
     tuned for parchment and goes muddy on navy. Same value the dark palette
     already uses for accent text, so this is a reuse and not a new hue.
     #FF8C82 on navy is 7.4:1, which carries the small strip type. */
  --accent-on-navy: #FF8C82;
  --glass:       rgba(255, 255, 255, 0.74);
  --glass-line:  rgba(0, 18, 64, 0.09);
  --scrim:       rgba(0, 18, 64, 0.55);

  /* The waiting state of an apply button, and of anything else counting down
     to the window opening. It was a white pill on a parchment page, which is
     the same value as the ground and therefore invisible: the one action the
     page exists for read as a disabled control. A gold wash with navy text
     keeps it unmistakably the primary object while it is still unclickable.
     Gold as a SURFACE, never as text, which is the rule in CLAUDE.md §2. */
  --wait-bg:     #FCEFCE;
  --wait-ink:    var(--navy);
  --wait-line:   var(--gold);

  /* Stable in BOTH themes. Navy panels keep their navy ground in dark mode,
     so text on them must not follow a token that flips. */
  --on-navy:        #F3F5FA;
  --on-navy-muted:  #A9B5CE;
  --on-navy-line:   rgba(255, 255, 255, 0.14);
  --on-navy-glass:  rgba(255, 255, 255, 0.10);

  /* --- Type --------------------------------------------------------------- */
  /* IBM Plex Sans is the face CLAUDE.md 3 names for body text, and they are in Canva as well as Google Fonts, so a poster
     made by an AJK matches the site exactly. It replaces Geist, Geist Mono and Fraunces.

     There is NO monospace on this site. CLAUDE.md 3 names IBM Plex Mono for
     data, and the figures here are set in Plex Sans instead, at weight 600
     with font-variant-numeric: tabular-nums. That keeps every column of marks
     in the rubric and every line of the calculator's working aligned, because
     alignment comes from tabular figures and not from a fixed width face.
     The rule still holds for print. This is the web only.

     One family carries the whole page: hierarchy comes from size and weight,
     not from a second decorative face. That is what every readable reference
     does. Plex Sans also has a genuinely large x-height and open apertures,
     which is what makes it hold up at 14px on a cheap phone in daylight.

     Arial is the named fallback because CLAUDE.md 3 fixes it as the substitute
     an AJK uses in Word, so a document and the site degrade the same way. */
  --f-sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Source Han Sans SC', Arial, sans-serif;
  /* The motto only, twice on the site. Same superfamily, so it harmonises. */
  --f-accent: 'IBM Plex Serif', Georgia, serif;
  --f-accent-zh: 'Songti SC', 'SimSun', 'Source Han Serif SC', 'Noto Serif CJK SC', serif;

  --t-xs:    0.875rem;
  --t-sm:    0.9375rem;
  --t-base:  1rem;
  --t-lg:    clamp(1.0625rem, 0.3vw + 1rem, 1.1875rem);
  --t-xl:    clamp(1.25rem, 0.7vw + 1.1rem, 1.5rem);
  --t-2xl:   clamp(1.5rem, 1.4vw + 1.2rem, 2rem);
  --t-3xl:   clamp(1.875rem, 2.4vw + 1.35rem, 2.875rem);
  --t-4xl:   clamp(2.25rem, 3.6vw + 1.4rem, 3.75rem);
  --t-hero:  clamp(2.625rem, 5.4vw + 1.2rem, 5rem);

  --lh-tight: 1.06;
  --lh-snug:  1.22;
  --lh-body:  1.62;
  --track-display: -0.015em;
  --measure: 64ch;

  /* NOTE, and it is a deliberate departure from CLAUDE.md 3.
     That section says "Uppercase only for short labels, always with
     --track-label letter-spacing". The website no longer uppercases anything,
     so both --track-label and --t-label are gone from this file.

     Why: the eyebrow was set in 11px spaced capitals and it was the single
     least readable thing on the site, on the pages most likely to be read by
     someone stressed, on a phone, in daylight. Spaced capitals defeat word
     shape recognition, which is most of how people read at small sizes, and
     RKS put them on every section heading, every table header and every
     ledger label. Eleven readable public information sites were checked and
     not one of them uses the device.

     The rule still stands for print, where a letterspaced label is set once
     on an A4 page rather than forty times on a scrolling page. If you are
     making a poster or a notice, follow CLAUDE.md. This is the web only. */

  /* --- Space -------------------------------------------------------------- */
  --s1:4px;  --s2:8px;   --s3:12px;  --s4:16px;  --s5:24px;
  --s6:32px; --s7:44px;  --s8:64px;  --s9:88px;  --s10:120px;

  --container: 1240px;
  --container-narrow: 820px;
  --gutter: 20px;
  --nav-h: 66px;
  /* The results strip inside the fixed header. Everything that has to clear
     the header measures --nav-h + --notice-h rather than --nav-h alone, so
     removing the strip is one edit: set this to 0px. */
  --notice-h: 38px;
  --pad-section: var(--s10);

  /* --- Radii. Generous, and the same family everywhere. ------------------- */
  --r-xs:   10px;
  --r-sm:   14px;
  --r-md:   20px;
  --r-lg:   26px;
  --r-xl:   34px;
  --r-2xl:  42px;
  --r-full: 999px;

  /* --- Shadows. Tinted navy, never neutral grey. -------------------------- */
  --sh-xs:  0 1px 2px rgba(0, 18, 64, 0.06);
  --sh-sm:  0 6px 16px -10px rgba(0, 18, 64, 0.30);
  --sh-md:  0 22px 46px -30px rgba(0, 18, 64, 0.42);
  --sh-lg:  0 36px 70px -40px rgba(0, 18, 64, 0.50);
  --sh-cta: 0 16px 32px -14px rgba(229, 35, 26, 0.55);
  --sh-gold:0 16px 32px -14px rgba(253, 198, 65, 0.55);

  --dur: 240ms;
  --dur-slow: 600ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

@media (max-width: 1024px) { :root { --pad-section: var(--s9); } }
@media (max-width: 640px)  { :root { --pad-section: var(--s8); --gutter: 18px; --nav-h: 60px; --notice-h: 34px; } }

/* --- Dark. Designed, not inverted. --------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:          #070B16;
    --bg-2:        #0D1426;
    --card:        #101A31;
    --card-2:      #16223D;
    --text:        #E9ECF3;
    --text-muted:  #98A3B8;
    --line:        #1E2A45;
    --line-strong: #2C3A5C;
    --accent:      #FF5A4E;
    --accent-text: #FF8C82;
    --glass:       rgba(14, 21, 40, 0.78);
    --glass-line:  rgba(255, 255, 255, 0.09);
    --gold-700:    #E7BD64;
    --wait-bg:     #2C2109;
    --wait-ink:    #FBE1A4;
    --wait-line:   rgba(253, 198, 65, 0.55);
  }
}
:root[data-theme="dark"] {
  --bg:          #070B16;
  --bg-2:        #0D1426;
  --card:        #101A31;
  --card-2:      #16223D;
  --text:        #E9ECF3;
  --text-muted:  #98A3B8;
  --line:        #1E2A45;
  --line-strong: #2C3A5C;
  --accent:      #FF5A4E;
  --accent-text: #FF8C82;
  --glass:       rgba(14, 21, 40, 0.78);
  --glass-line:  rgba(255, 255, 255, 0.09);
  --gold-700:    #E7BD64;
  --wait-bg:     #2C2109;
  --wait-ink:    #FBE1A4;
  --wait-line:   rgba(253, 198, 65, 0.55);
}

/* ==========================================================================
   2 · BASE
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--f-sans);
  font-size: 17px;
  font-weight: 400;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5 {
  margin: 0;
  font-weight: 600;
  line-height: var(--lh-snug);
  letter-spacing: var(--track-display);
  text-wrap: balance;
}
p  { margin: 0; }
/* figure carries a user agent margin of 1em 40px, which quietly inset every
   photograph on the site by 40px a side. */
figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; }
li { list-style: none; }
a  { color: inherit; text-decoration: none; }

/* Links inside a sentence. Stripping decoration globally is right for the nav
   and for buttons and wrong here: a link in running text that looks like text
   is not a link. Scoped to the containers that actually hold prose so it never
   reaches a nav item or a card that is itself a link. */
.prose a, .lede a, .body-sm a, .body-xs a, .faq__a a, .stage__p a, .hero__note a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--accent-text) 45%, transparent);
  transition: text-decoration-color var(--dur) var(--ease);
}
.prose a:hover, .lede a:hover, .body-sm a:hover, .body-xs a:hover,
.faq__a a:hover, .stage__p a:hover, .hero__note a:hover {
  text-decoration-color: currentColor;
}
img, svg { max-width: 100%; display: block; }
/* `display: block` above outranks the user agent's `[hidden] { display: none }`,
   which left both burger icons painted at once. hidden must win. */
[hidden] { display: none !important; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

[id] { scroll-margin-top: calc(var(--nav-h) + var(--notice-h) + var(--s5)); }

::selection { background: var(--gold); color: var(--navy); }

/* Chinese needs looser leading and no Latin letterspacing. */
html[lang="zh"] body { line-height: 1.82; }
html[lang="zh"] h1, html[lang="zh"] h2,
html[lang="zh"] h3, html[lang="zh"] h4 { letter-spacing: 0; line-height: 1.35; }
html[lang="zh"] .kicker { letter-spacing: 0; }
html[lang="zh"] .motto  { font-family: var(--f-accent-zh); font-style: normal; }

/* ==========================================================================
   3 · LAYOUT PRIMITIVES
   ========================================================================== */
.wrap   { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.wrap-n { width: 100%; max-width: var(--container-narrow); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--pad-section); }
.section--tight { padding-block: var(--s9); }
.section--cream { background: var(--bg-2); }
.prose { max-width: var(--measure); }

.stack   { display: flex; flex-direction: column; }
.gap-2 { gap: var(--s2); } .gap-3 { gap: var(--s3); } .gap-4 { gap: var(--s4); }
.gap-5 { gap: var(--s5); } .gap-6 { gap: var(--s6); } .gap-7 { gap: var(--s7); }
.gap-8 { gap: var(--s8); }

.grid { display: grid; gap: var(--s4); }
/* A grid item defaults to min-width:auto, so its widest unbreakable descendant
   can force the whole track wider than the phone. One long button label did
   exactly that and pushed the page 17px past a 360px viewport. */
.grid > *, .stats > *, .people > * { min-width: 0; }
@media (min-width: 720px)  { .md\:cols-2 { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (min-width: 1000px) { .lg\:cols-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
                             .lg\:cols-4 { grid-template-columns: repeat(4, minmax(0,1fr)); } }

/* ==========================================================================
   4 · TYPE ROLES
   ========================================================================== */
.kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  font-family: var(--f-sans);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--accent-text);
}
.kicker::before {
  content: "";
  width: 26px;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  flex: none;
}
.kicker--gold  { color: var(--gold-700); }
/* Used once, on the sign in card while checking is down. accent-text rather
   than accent, because a kicker is small text on a light ground and only the
   darker of the two clears 4.5:1 there. */
.kicker--accent{ color: var(--accent-text); }
.kicker--onnavy{ color: var(--gold); }
.kicker--muted { color: var(--text-muted); }

.h-hero { font-size: var(--t-hero); font-weight: 700; line-height: var(--lh-tight); letter-spacing: -0.02em; }
.h1     { font-size: var(--t-4xl); font-weight: 700; letter-spacing: -0.02em; }
.h2     { font-size: var(--t-3xl); font-weight: 700; }
.h3     { font-size: var(--t-xl);  font-weight: 600; }
.h4     { font-size: var(--t-lg);  font-weight: 600; }

.lede      { font-size: var(--t-lg); color: var(--text-muted); line-height: 1.58; }
.body-sm   { font-size: var(--t-sm); color: var(--text-muted); }
.body-xs   { font-size: var(--t-xs); color: var(--text-muted); }
.num       { font-family: var(--f-sans); font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0; }
.hl        { color: var(--accent-text); }
.hl-gold   { color: var(--gold-700); }

.motto {
  font-family: var(--f-accent);
  font-style: italic;
  font-weight: 500;
  letter-spacing: 0;
}

/* ==========================================================================
   5 · BUTTONS
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 50px;
  padding-inline: var(--s7);
  border-radius: var(--r-full);
  border: 1px solid transparent;
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  white-space: nowrap;
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease),
              border-color var(--dur) var(--ease), color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn svg { width: 16px; height: 16px; flex: none; }

.btn--primary {
  background: var(--hibiscus-700);
  color: #FFFFFF;
  box-shadow: var(--sh-cta);
}
.btn--primary:hover { background: var(--hibiscus); }

.btn--gold {
  background: var(--gold);
  color: var(--navy);
  box-shadow: var(--sh-gold);
}
.btn--gold:hover { background: var(--gold-300); }

/* The application window gate. A locked button has no href, so it is already
   unclickable and unfocusable; this only makes that visible.

   It is deliberately NOT greyed out. For the days before the form opens this
   is the single most important object on the page, and a grey pill reads as
   "broken" rather than "not yet". Gold ground, navy text, a live dot: it
   still says wait, but it says wait about something that is coming. */
.btn.is-locked,
.btn.is-locked:hover {
  background: var(--wait-bg);
  color: var(--wait-ink);
  border-color: var(--wait-line);
  box-shadow: none;
  transform: none;
  cursor: default;
  font-variant-numeric: tabular-nums;
}
.btn.is-locked svg { display: none; }

/* The dot. Text is written with textContent, so there is no element to hang
   this on and it has to be a pseudo element. */
.btn.is-locked::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: var(--accent);
  flex: none;
  animation: waitpulse 2.4s var(--ease) infinite;
}
@keyframes waitpulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) {
  .btn.is-locked::before { animation: none; }
}

.btn--quiet {
  background: var(--card);
  color: var(--text);
  border-color: var(--line-strong);
  box-shadow: var(--sh-xs);
}
.btn--quiet:hover { border-color: var(--text-muted); }

.btn--glass {
  background: var(--on-navy-glass);
  color: var(--on-navy);
  border-color: rgba(255, 255, 255, 0.26);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.btn--glass:hover { background: rgba(255, 255, 255, 0.20); }

.btn--sm { min-height: 44px; padding-inline: var(--s5); font-size: var(--t-xs); }

/* Narrow phones. A pill button keeps its label on one line by default, which
   is right until the label is long and the screen is 360px wide, at which
   point the button decides the width of everything around it. Here it wraps. */
@media (max-width: 430px) {
  .btn { padding-inline: var(--s5); white-space: normal; text-align: center; line-height: 1.3; }
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 44px;          /* tap target; the text alone is only 23px tall */
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--accent-text);
  transition: gap var(--dur) var(--ease);
}
.link-arrow:hover { gap: var(--s3); }
.link-arrow svg { width: 15px; height: 15px; }

/* ==========================================================================
   6 · SURFACES
   ========================================================================== */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  padding: var(--s6);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.card--lift:hover { transform: translateY(-4px); box-shadow: var(--sh-md); border-color: var(--line-strong); }
.card--flat { box-shadow: none; }
.card--navy {
  background: var(--navy);
  border-color: var(--on-navy-line);
  color: var(--on-navy);
}
.card--navy .body-sm, .card--navy .lede, .card--navy .body-xs { color: var(--on-navy-muted); }

.panel-navy {
  background: var(--navy);
  color: var(--on-navy);
  border-radius: var(--r-2xl);
  overflow: hidden;
}
.panel-navy .lede, .panel-navy .body-sm, .panel-navy .body-xs { color: var(--on-navy-muted); }

.rule-gold { height: 1px; background: var(--gold); border: 0; opacity: 0.9; }
.rule      { height: 1px; background: var(--line); border: 0; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 6px 14px;
  border-radius: var(--r-full);
  border: 1px solid var(--line-strong);
  background: var(--card);
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-muted);
}
.pill--gold { border-color: rgba(253,198,65,.6); background: rgba(253,198,65,.14); color: var(--gold-700); }
.pill--accent { border-color: rgba(229,35,26,.28); background: rgba(229,35,26,.08); color: var(--accent-text); }
.pill--glass {
  background: var(--on-navy-glass);
  border-color: rgba(255,255,255,.26);
  color: var(--on-navy);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* ==========================================================================
   7 · NAVIGATION
   ========================================================================== */

/* ── the results strip ───────────────────────────────────────────────────
   Solid navy inside the glass header, so it reads as a notice rather than as
   another row of navigation. It is the only element on the site that is
   navy on every page, which is the point: an applicant who lands on the
   houses page still sees when they find out.

   Fixed height, and it must not wrap. Under 640px the date drops out and the
   label shortens; what survives at 360px is the clock and the arrow, which
   is the part that is actually load bearing. */
.notice {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  height: var(--notice-h);
  padding-inline: var(--gutter);
  background: var(--navy);
  color: var(--on-navy);
  border-bottom: 1px solid var(--gold);
  font-size: var(--t-xs);
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  text-decoration: none;
}
.notice__dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--gold);
}
/* Gold on navy is 11.5:1, so it carries small text safely. It is doing the
   same job here as the hairline does elsewhere: marking what to read first. */
.notice__lab { font-weight: 600; color: var(--gold); }
.notice__clock {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.notice__clock:empty { display: none; }
.notice__when { color: var(--on-navy-muted); }
/* The separator is drawn rather than typed, so no language has to carry a
   middot in its string and no screen reader announces one. */
.notice__when::before {
  content: "";
  display: inline-block;
  width: 1px;
  height: 0.85em;
  margin-inline: var(--s3) var(--s2);
  vertical-align: -0.1em;
  background: rgba(255,255,255,.28);
}
.notice__go {
  flex: none;
  display: inline-flex;
  margin-inline-start: var(--s1);
  color: var(--gold);
  transition: transform var(--dur) var(--ease);
}
.notice__go svg { width: 14px; height: 14px; }
.notice--link:hover .notice__go { transform: translate(2px, -2px); }
.notice--link:hover .notice__when { color: var(--on-navy); }
.notice:focus-visible { outline: 2px solid var(--gold); outline-offset: -3px; }

/* Once the results are out the strip stops being a countdown and becomes an
   instruction, so the dot goes hibiscus and stops being decoration. */
.notice[data-state="out"] .notice__dot { background: var(--accent); }

/* Checking is down. Hibiscus is the one warm accent and a service outage is
   exactly the single status it is reserved for. The dot pulses, because a
   static red dot in a navy strip reads as decoration and this one is not.
   Text stays parchment on navy at 16.2:1: the hibiscus is the signal, never
   the words. */
.notice--down { border-bottom-color: var(--accent); }
.notice--down .notice__dot { background: var(--accent); animation: noticePulse 2s var(--ease) infinite; }
.notice--down .notice__lab { color: var(--accent-on-navy); }
.notice--down .notice__when { color: var(--on-navy); }
@keyframes noticePulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) {
  .notice--down .notice__dot { animation: none; }
}

@media (max-width: 640px) {
  .notice { font-size: 12px; gap: var(--s1); }
  .notice__when { display: none; }
  /* The outage message IS the strip, so it survives where the countdown date
     does not. Without this the phone reader gets a red dot and no reason. */
  .notice--down { gap: var(--s2); white-space: normal; line-height: 1.3; text-align: center; }
  .notice--down .notice__when { display: inline; }
  .notice--down .notice__when::before { display: none; }
  .notice--down .notice__lab { display: none; }
}

.nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 60;
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(18px);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.nav[data-scrolled="true"] {
  border-bottom-color: var(--glass-line);
  box-shadow: 0 14px 34px -28px rgba(0, 18, 64, 0.5);
}
.nav__in {
  max-width: var(--container);
  margin-inline: auto;
  padding: 8px var(--gutter);
  display: flex;
  align-items: center;
  gap: var(--s4);
  min-height: var(--nav-h);
}

/* The crest, with the name set beside it. CLAUDE.md keeps the crest off
   anything under 128px when it must carry the name alone; here the wordmark
   carries the name and the crest is the mark, which is the sanctioned pairing. */
.brand { display: flex; align-items: center; gap: 11px; flex: none; min-height: 44px; }
.brand__crest { width: 42px; height: 42px; object-fit: contain; }
.brand__text { display: flex; flex-direction: column; line-height: 1.05; }
.brand__wordmark {
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text);
}
.brand__sub {
  font-family: var(--f-sans);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-muted);
  margin-top: 1px;
}
@media (max-width: 349px) { .brand__sub { display: none; } }

/* gap was s6 with five links. Publishing the rubric took the bar to eight, and
   at s6 the row ran into the language switch on a 1280px laptop. The desktop
   breakpoint moved with it, from 1060 to 1180: below that the burger holds
   everything, which is the honest failure rather than a nav that wraps. */
.nav__links { display: none; align-items: center; gap: var(--s4); margin-inline: auto; }
.nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--text-muted);
  transition: color var(--dur) var(--ease);
}
.nav__link:hover { color: var(--text); }
.nav__link[aria-current="page"] { color: var(--accent-text); font-weight: 600; }
.nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 2px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}

.nav__right { display: flex; align-items: center; gap: var(--s2); margin-left: auto; }

.langs {
  display: inline-flex;
  align-items: center;
  padding: 3px;
  gap: 2px;
  border-radius: var(--r-full);
  border: 1px solid var(--line-strong);
  background: var(--card);
}
.langs__btn {
  min-height: 38px;
  padding-inline: 13px;
  white-space: nowrap;
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--text-muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.langs__btn:hover { color: var(--text); }
.langs__btn[aria-pressed="true"] { background: var(--navy); color: var(--on-navy); }

.nav__burger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border-radius: var(--r-full);
  color: var(--text);
  transition: background var(--dur) var(--ease);
}
.nav__burger:hover { background: rgba(0, 18, 64, 0.06); }
.nav__burger svg { width: 21px; height: 21px; }
/* The icon swap is CSS, driven by the state the button already exposes to
   assistive tech. It was JS setting `hidden` on the <svg>, which silently did
   nothing: `hidden` is an HTMLElement property, so on an SVG it becomes a
   plain expando and never reaches the DOM. Both icons painted at once. */
.nav__burger [data-icon="close"] { display: none; }
.nav__burger[aria-expanded="true"] [data-icon="close"] { display: block; }
.nav__burger[aria-expanded="true"] [data-icon="open"]  { display: none; }

.nav__cta { display: none; }

/* Nine links, so the full row needs more space than it did with eight.
   Below this the burger sheet carries the same list. */
@media (min-width: 1240px) {
  .nav__links { display: flex; }
  .nav__burger { display: none; }
  .nav__cta { display: inline-flex; }
}
/* 700, not 560. While the window is shut this button carries a live countdown
   rather than the word Apply, which is several times wider, and at 560 the row
   of brand + languages + button + burger overflowed the viewport. Below this
   the burger holds the bar and the hero button is a scroll away. */
@media (min-width: 700px) { .nav__cta { display: inline-flex; } }

/* Mobile sheet */
.sheet {
  display: none;
  margin: var(--s2) var(--gutter) 0;
  padding: var(--s3);
  border-radius: var(--r-xl);
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-md);
}
.sheet[data-open="true"] { display: block; animation: sheet-in 280ms var(--ease) both; }
@media (min-width: 1180px) { .sheet, .sheet[data-open="true"] { display: none; } }
@keyframes sheet-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

.sheet__link {
  display: flex;
  align-items: center;
  min-height: 50px;
  padding-inline: var(--s4);
  border-radius: var(--r-md);
  font-size: var(--t-base);
  font-weight: 500;
  color: var(--text);
}
.sheet__link[aria-current="page"] { background: rgba(229,35,26,.09); color: var(--accent-text); font-weight: 600; }
.sheet__foot { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); display: flex; justify-content: center; }

.page { padding-top: calc(var(--nav-h) + var(--notice-h)); }

/* ── The outage band ──────────────────────────────────────────────────────
   First thing inside <main> on every page while checking is down. Full bleed
   navy so it reads as a state of the whole site rather than a note on one
   section, with the accent carried by a top rule and the kicker only. Body
   copy stays parchment on navy at 16.2:1; the accent never carries a
   sentence. Sized so it is the first and last thing a phone reader sees
   before the hero. Delete nothing to remove it, set PORTAL_DOWN False. */
.alert {
  background: var(--navy);
  color: var(--on-navy);
  border-top: 3px solid var(--accent);
  border-bottom: 1px solid var(--gold);
}
.alert__in { padding-block: var(--s6); max-width: var(--container-narrow); }
.alert__k {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-family: var(--f-sans);
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--accent-on-navy);
  margin: 0;
}
.alert__k::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent-on-navy);
  flex: none;
  animation: noticePulse 2s var(--ease) infinite;
}
.alert__h {
  font-size: var(--t-2xl);
  font-weight: 700;
  line-height: var(--lh-snug);
  text-wrap: balance;
  color: var(--gold);
  margin: var(--s3) 0 0;
}
.alert__p, .alert__n {
  max-width: var(--measure);
  margin: var(--s4) 0 0;
  font-size: var(--t-sm);
  line-height: var(--lh-body);
}
.alert__n { color: var(--on-navy-muted); }
@media (prefers-reduced-motion: reduce) {
  .alert__k::before { animation: none; }
}

.skip {
  position: absolute;
  left: var(--s4);
  top: -100px;
  z-index: 100;
  padding: var(--s3) var(--s5);
  border-radius: var(--r-full);
  background: var(--navy);
  color: var(--on-navy);
  font-size: var(--t-sm);
  font-weight: 600;
  transition: top var(--dur) var(--ease);
}
.skip:focus { top: var(--s3); }

/* ==========================================================================
   8 · HERO

   Reference: Samara's Backyard hero (mobbin.com/sites/sections/19d48f9c…).
   Its structure is a quiet statement on the left, hard specifications on the
   right, one enormous piece of type as the hero object, and the photograph
   bleeding underneath. It is a prefab housing company, so the job is the same
   as ours: show the building, state the numbers, do not oversell.

   Two deliberate departures from it.

   Samara sets its giant type OVER the photograph. Ours cannot: the top band
   of the RKS 1 photograph contains near-black foliage, measured at 1.14:1
   against navy, so type there would be unreadable in places. The type sits on
   parchment instead and the photograph runs full width beneath it, which is
   also what §9 of CLAUDE.md requires — no text on a photo without a scrim.

   The giant object is RM170 rather than a product name. For a student who
   found out this morning they have nowhere to live, the rent is the fact that
   decides it, and it absorbs the old separate stat bar so the whole hero is
   one band instead of two.
   ========================================================================== */
.hero { padding-top: var(--s8); }
@media (min-width: 900px) { .hero { padding-top: var(--s9); } }

/* ── the statement / specification split ────────────────────────────────── */
.hero__top { display: grid; gap: var(--s6); }
@media (min-width: 900px) {
  .hero__top {
    /* 1.3 is the ceiling. Wider and the aside drops below the width the Malay
       button labels need, and the two buttons stack. */
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    column-gap: var(--s10);
    /* Centred, not bottom aligned. The headline used to be three lines and
       filled this row; at two lines an end alignment leaves a hole above it
       that reads as a mistake. Centring holds whatever the language does to
       the line count, which is the point, since Malay takes three lines and
       Chinese takes two. */
    align-items: center;
  }
  /* The space under the kicker is only needed while the two columns are
     stacked. Once they sit side by side it is half a line of false centring. */
  .hero__top .hero__h { margin-top: 0; }
}
/* One sentence, wrapped by the browser. The measure stays at 26ch so the
   headline breaks into two or three lines rather than running the width of
   the column, and the size steps down from --t-4xl, because "RKS" alone is
   wide at 60px and a wall of it is not a statement. */
.hero__h {
  margin-top: var(--s5);
  max-width: 26ch;
  /* Ceiling is 3.1rem. It was set when the headline was a three line lockup
     whose longest Malay line sat five pixels short of wrapping; the headline
     is one sentence now and wraps on purpose, but the band, the aside and the
     photograph below are all tuned to a hero of this height, so the ceiling
     stays until the whole composition is re-measured. */
  font-size: clamp(2rem, 2.9vw + 1.25rem, 3.1rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.02em;
  /* The global heading balance stays on. Measured at 1440: the name needs 673px
     against a 610px column, so it breaks whatever we do here, and balance breaks
     it as "Rumah Kediaman / Siswa UM." rather than orphaning "UM." on its own
     line. Below 1240 the column shrinks faster than the type, so there is no
     size worth having that keeps the name whole in the two column range. */
}
.hero__aside { display: flex; flex-direction: column; gap: var(--s5); }
.hero__sub { max-width: 42ch; font-size: var(--t-lg); color: var(--text-muted); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s3); }
.hero__note { max-width: 46ch; }

/* ── the figure band. The hinge of the page. ────────────────────────────── */
.hero__band {
  margin-top: var(--s8);
  padding-top: var(--s5);
  border-top: 1px solid var(--gold);
  display: grid;
  gap: var(--s6);
  align-items: end;
}
@media (min-width: 760px) {
  .hero__band { grid-template-columns: auto 1fr; column-gap: var(--s9); }
}

/* The countdown is the hero object now, not the rent. For this reader the
   fact that decides everything is whether the form is open yet; the price
   is a detail they check second. The rent moved down into the fact row.

   Mono digits and tabular figures so the seconds column does not jitter. */
.hero__count { display: flex; flex-direction: column; gap: var(--s3); }
.hero__countlab {
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--gold-700);
}
.hero__clock {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  /* Plex Sans, not Plex Mono. A clock has one hard requirement, which is that
     the seconds column must not shift left and right on every tick, and that
     comes from tabular-nums rather than from a monospace. Plex Sans has real
     tabular figures, so the digits are locked to one width while the face
     stays the same one the rest of the page is set in. */
  font-family: var(--f-sans);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--text);
}
.cd { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.cd__n {
  font-size: clamp(2.75rem, 10vw, 6.5rem);
  font-weight: 600;
  line-height: 0.85;
  letter-spacing: -0.02em;
}
.cd__u {
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-muted);
}
.cd__sep {
  font-size: clamp(2rem, 7vw, 4.5rem);
  font-weight: 400;
  line-height: 0.9;
  color: var(--gold);
  transform: translateY(-0.06em);
}
.hero__when { font-size: var(--t-sm); color: var(--text-muted); }

/* The clock changes what it is counting to three times. Each time the label
   changes with it, and the label turns hibiscus for the two phases where the
   number is a deadline rather than an announcement, so the change of meaning
   is never silent. Once results are out there is nothing left to count and
   the zeros go quiet. */
.hero__count[data-state="open"] .hero__countlab,
.hero__count[data-state="results"] .hero__countlab { color: var(--hibiscus-900); }
.hero__count[data-state="out"] .cd__n,
.hero__count[data-state="out"] .cd__sep { color: var(--text-muted); }

.hero__facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s4);
  padding-bottom: var(--s2);
}
@media (max-width: 480px) { .hero__facts { gap: var(--s3); } }
.hero__fact { display: flex; flex-direction: column; gap: 2px; }
.hero__factv {
  font-family: var(--f-sans);
  font-size: var(--t-xl);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.hero__factl {
  font-size: var(--t-xs);
  line-height: 1.35;
  color: var(--text-muted);
}

/* The one number a reader has to add up for themselves, said in words rather
   than repeated as a fourth figure. Two RM170s in one band read as a mistake. */
.hero__deposit {
  margin-top: var(--s5);
  max-width: 56ch;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* ── the photograph. Nothing is ever set on top of it. ──────────────────── */
.hero__shot {
  margin-top: var(--s8);
  border-radius: var(--r-2xl);
  overflow: hidden;
  background: var(--bg-2);
}
@media (max-width: 640px) { .hero__shot { border-radius: var(--r-xl); } }
.hero__shot img {
  width: 100%;
  height: clamp(240px, 46vw, 560px);
  object-fit: cover;
  object-position: center 58%;   /* keeps the house, drops empty sky */
}
.hero__cap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  margin-top: var(--s4);
  font-size: var(--t-xs);
  color: var(--text-muted);
}

/* Page heroes on the inner pages: navy panel, no photograph. */
.phero { padding: var(--s5) 0 0; }
.phero__frame {
  position: relative;
  max-width: 1400px;
  margin-inline: auto;
  border-radius: var(--r-2xl);
  background: var(--navy);
  color: var(--on-navy);
  padding: var(--s9) var(--s6) var(--s8);
  overflow: hidden;
}
@media (min-width: 720px)  { .phero__frame { padding: var(--s10) var(--s8) var(--s9); } }
@media (max-width: 640px)  { .phero__frame { border-radius: var(--r-xl); } }
.phero__frame::after {
  /* A single soft gold bloom, bottom right. The only decorative gradient on
     the site, and it never sits under text. */
  content: "";
  position: absolute;
  right: -140px; bottom: -180px;
  width: 460px; height: 460px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(253,198,65,.20), transparent 68%);
  pointer-events: none;
}
.phero__h { max-width: 18ch; margin-top: var(--s5); color: var(--on-navy); position: relative; }
.phero__sub { max-width: 56ch; margin-top: var(--s5); color: var(--on-navy-muted); font-size: var(--t-lg); position: relative; }

/* ==========================================================================
   9 · STATS
   ========================================================================== */
.stats { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--s6) var(--s5); }
@media (min-width: 900px) { .stats { grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--s7); } }
.stat { border-top: 1px solid var(--line-strong); padding-top: var(--s4); }
.stat__v {
  font-family: var(--f-sans);
  font-size: clamp(2rem, 3vw + 1.2rem, 3rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.stat__l { margin-top: var(--s3); font-size: var(--t-sm); font-weight: 600; letter-spacing: 0; }
.stat__s { margin-top: 5px; font-size: var(--t-xs); color: var(--text-muted); line-height: 1.5; }

.stats--onnavy .stat { border-top-color: var(--gold); }
.stats--onnavy .stat__v { color: var(--gold); }
.stats--onnavy .stat__l { color: var(--on-navy); }
.stats--onnavy .stat__s { color: var(--on-navy-muted); }

/* ==========================================================================
   10 · HOUSE CARDS
   ========================================================================== */
.house {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
}
.house__media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--bg-2);
  overflow: hidden;
}
.house__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-slow) var(--ease); }
.house--lift:hover .house__media img { transform: scale(1.045); }

/* RKS 2 and RKS 6 have no photographs and RKS 5 has two at low resolution.
   The empty state says so rather than showing a grey rectangle. */
.house__empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  padding: var(--s5);
  text-align: center;
  background:
    repeating-linear-gradient(135deg, transparent 0 11px, rgba(0,18,64,.045) 11px 12px),
    var(--bg-2);
}
.house__empty svg { width: 26px; height: 26px; color: var(--text-muted); opacity: .55; }
.house__empty p { font-size: var(--t-xs); color: var(--text-muted); max-width: 24ch; line-height: 1.5; }

.house__body { padding: var(--s5) var(--s5) var(--s6); display: flex; flex-direction: column; gap: var(--s3); flex: 1; }
.house__top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); }
.house__name { font-size: var(--t-xl); font-weight: 700; letter-spacing: 0; }
.house__meta { display: flex; flex-wrap: wrap; gap: var(--s2); }
.house__spec { margin-top: auto; padding-top: var(--s4); border-top: 1px solid var(--line); font-family: var(--f-sans); font-variant-numeric: tabular-nums; font-weight: 600; font-size: var(--t-xs); color: var(--text-muted); }

/* ==========================================================================
   11 · LISTS  (provided / not provided, rules, steps)
   ========================================================================== */
.checklist { display: flex; flex-direction: column; gap: var(--s3); }
.checklist li { display: flex; gap: var(--s3); font-size: var(--t-sm); line-height: 1.5; }
.checklist svg { width: 18px; height: 18px; flex: none; margin-top: 2px; }
.checklist--yes svg { color: var(--gold-700); }
.checklist--no  svg { color: var(--text-muted); }
.checklist--no li { color: var(--text-muted); }

.steps { counter-reset: step; display: flex; flex-direction: column; }
.step {
  counter-increment: step;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s5) 0 var(--s5) var(--s8);
  border-bottom: 1px solid var(--line);
}
.step:first-child { border-top: 1px solid var(--line); }
.step::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: calc(var(--s5) + 2px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--r-full);
  background: rgba(229,35,26,.09);
  color: var(--accent-text);
  font-family: var(--f-sans); font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
  font-weight: 600;
}
.panel-navy .step { border-color: var(--on-navy-line); }
.panel-navy .step::before { background: rgba(253,198,65,.16); color: var(--gold); }

/* ==========================================================================
   11b · ROWS  (rent table, and anything else with an item / figure / when
   shape). A real <table> collapses badly at 360px; this keeps the figure
   beside its label on wide screens and stacks honestly on narrow ones.
   ========================================================================== */
.rows { border-top: 1px solid var(--line); }
.row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s1) var(--s5);
  padding: var(--s5) 0;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 620px) {
  .row { grid-template-columns: 1.5fr auto; align-items: baseline; }
  .row__w { grid-column: 1 / -1; }
}
.row__k { font-size: var(--t-sm); font-weight: 600; letter-spacing: 0; }
.row__v { font-size: var(--t-base); font-weight: 500; color: var(--text); }
.row__w { font-size: var(--t-xs); color: var(--text-muted); }

/* ==========================================================================
   12 · FAQ
   ========================================================================== */
.faq { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  width: 100%;
  padding: var(--s5) 0;
  text-align: left;
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: 0;
  list-style: none;
  cursor: pointer;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q svg { width: 18px; height: 18px; flex: none; color: var(--text-muted); transition: transform var(--dur) var(--ease); }
.faq__item[open] .faq__q svg { transform: rotate(45deg); }
.faq__a { padding-bottom: var(--s5); font-size: var(--t-sm); color: var(--text-muted); line-height: 1.6; max-width: var(--measure); }

/* --------------------------------------------------------------------------
   Timestamp on a changelog entry.

   To the minute, not just the date. Five changes were made to a live rubric
   on the morning the form opened, and a reader deciding whether the version
   they read yesterday is the version they are being scored against needs to
   know which came first and how close to 11:00 the last one landed. A date
   alone cannot answer that.

   Tabular figures so the times form a column the eye can run down. Plex Sans,
   not a monospace face, because this site has none on purpose.
   -------------------------------------------------------------------------- */
.chlog__t {
  display: block;
  margin-bottom: var(--s2);
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* ==========================================================================
   12b · THE UPDATE LIST
   A changelog written as four collapsible rows, one per change. Reference:
   Lemon Squeezy's numbered chrome free rows, with Dropbox's expand to body
   text. Cards were the first attempt and they were wrong: four boxes of
   equal weight give a reader no way in, and the whole point of this block
   is that a reader can scan four titles and open only what affects them.

   Rules rather than boxes, and the rule is the gold hairline that CLAUDE.md
   4 names as the workhorse device. Numerals are Plex Sans 600 with tabular
   figures, because this site has no monospace on purpose.
   ========================================================================== */
.upd { border-top: 1px solid var(--gold); }
.upd__item { border-bottom: 1px solid var(--gold); }

.upd__q {
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  padding: var(--s5) 0;
  text-align: left;
  list-style: none;
  cursor: pointer;
}
.upd__q::-webkit-details-marker { display: none; }
.upd__q:hover .upd__t { color: var(--accent); }
.upd__item[open] .upd__t { color: var(--accent); }

/* Tabular figures keep 01 to 04 the same width, so the titles line up. */
.upd__n {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--gold-700);
  padding-top: 2px;
}
.upd__t {
  flex: 1;
  font-weight: 600;
  font-size: var(--t-base);
  line-height: 1.45;
  transition: color var(--dur) var(--ease);
}

/* A plus that becomes a cross. One glyph, no icon font, no second asset. */
.upd__i { flex: none; width: 20px; height: 20px; margin-top: 2px; color: var(--text-muted); transition: transform var(--dur) var(--ease); }
.upd__item[open] .upd__i { transform: rotate(45deg); color: var(--accent); }

.upd__body {
  padding: 0 0 var(--s6);
  max-width: var(--measure);
  /* Indented to the title, so the number column stays a spine down the list. */
  padding-inline-start: calc(var(--s4) + 2.2ch);
}
/* Same idiom as .kicker: no uppercase, no letterspacing. The site does not
   uppercase anything, and a 12px uppercase label in Malay reads badly. */
.upd__lbl {
  display: block;
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-muted);
  margin-bottom: var(--s2);
}
.upd__lbl--fix { color: var(--gold-700); }
.upd__p { font-size: var(--t-sm); line-height: 1.65; }
.upd__p + .upd__lbl { margin-top: var(--s5); }

@media (max-width: 560px) {
  .upd__body { padding-inline-start: 0; }
  .upd__q { gap: var(--s3); }
}

/* ==========================================================================
   13 · PEOPLE
   ========================================================================== */
.people { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--s4); }
@media (min-width: 720px)  { .people { grid-template-columns: repeat(3, minmax(0,1fr)); } }
@media (min-width: 1000px) { .people { grid-template-columns: repeat(4, minmax(0,1fr)); } }
.people--board { grid-template-columns: repeat(1, minmax(0,1fr)); }
@media (min-width: 640px)  { .people--board { grid-template-columns: repeat(3, minmax(0,1fr)); } }

.person { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.person__media { position: relative; aspect-ratio: 1 / 1; background: var(--bg-2); overflow: hidden; }
.person__media img { width: 100%; height: 100%; object-fit: cover; }
.person__initials {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, var(--navy-700), var(--navy));
  color: var(--gold);
  font-family: var(--f-sans); font-variant-numeric: tabular-nums;
  font-size: clamp(1.5rem, 4vw, 2rem);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.person__body { padding: var(--s4) var(--s4) var(--s5); display: flex; flex-direction: column; gap: 6px; }
.person__name { font-size: var(--t-sm); font-weight: 600; letter-spacing: 0; line-height: 1.3; }
.person__role {
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--accent-text);
}
.person--board .person__role { color: var(--gold-700); }

/* A visible, labelled link rather than a clickable card. One clickable card
   among twenty is not a pattern a reader can learn, and a link with a word in
   it is the only version a keyboard or a screen reader can find. */
.person__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--accent-text) 40%, transparent);
  transition: text-decoration-color var(--dur) var(--ease);
}
.person__link:hover { text-decoration-color: currentColor; }
.person__link svg { width: 13px; height: 13px; flex: none; fill: currentColor; }

/* ==========================================================================
   14 · FOOTER
   ========================================================================== */
.footer { background: var(--navy); color: var(--on-navy); padding-block: var(--s9) var(--s6); }
.footer a { color: var(--on-navy-muted); transition: color var(--dur) var(--ease); }
.footer a:hover { color: var(--on-navy); }
.footer__top { display: grid; gap: var(--s7); padding-bottom: var(--s7); border-bottom: 1px solid var(--on-navy-line); }
@media (min-width: 640px)  { .footer__top { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (min-width: 1040px) { .footer__top { grid-template-columns: 1.5fr 1fr 1.15fr 1.15fr; gap: var(--s7); } }
/* height:auto is load bearing. The <img> carries width and height attributes
   so the browser can reserve space, and those map to presentational CSS. With
   only `width` constrained, the attribute height won and the two crests came
   out squashed tall.

   The 300px floor is a brand rule, not taste. CLAUDE.md forbids the crest
   below 128px, and the RKS crest occupies 45.1% of this lockup, so anything
   under 284px pushes it past that. 300 leaves a margin. */
.footer__lockup {
  width: 100%;
  height: auto;
  max-width: 300px;
  min-width: 284px;
}
@media (max-width: 359px) { .footer__lockup { min-width: 0; } }
.footer__label {
  font-family: var(--f-sans);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--gold);
  margin-bottom: var(--s4);
}
.footer__links { display: flex; flex-direction: column; font-size: var(--t-sm); }
.footer__links a { display: inline-flex; align-items: center; min-height: 40px; }
.footer__links p { padding-block: var(--s2); }
.footer__motto {
  font-size: var(--t-2xl);
  color: var(--gold);
  padding-block: var(--s7);
  border-bottom: 1px solid var(--on-navy-line);
}
.footer__legal { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); width: 100%; }
.footer__legal a { text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: rgba(255,255,255,.28); }
.footer__legal a:hover { text-decoration-color: currentColor; }
.footer__fine { padding-top: var(--s5); display: flex; flex-wrap: wrap; gap: var(--s3) var(--s6); font-size: var(--t-xs); color: var(--on-navy-muted); }

/* ==========================================================================
   15 · MOTION
   Two behaviours only: a staged hero reveal on load, and a single fade-and-rise
   as a block scrolls in. Elements are visible by default, so a failed script
   can never leave the page blank.
   ========================================================================== */
/* The hidden start state is opt-IN, and only app.js may opt in, by stamping
   html.js-anim once it has an observer running. Every other path — script
   blocked, script errored, old browser, reduced motion — leaves the content
   plainly visible. Defaulting .reveal to opacity 0 would mean one bad script
   tag hides the entire site below the hero. */
.reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: no-preference) {
  html.js-anim .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 700ms var(--ease), transform 700ms var(--ease);
  }
  html.js-anim .reveal.is-in { opacity: 1; transform: none; }

  .rise { animation: rise 620ms var(--ease) both; }
  .d1 { animation-delay: 70ms; }  .d2 { animation-delay: 140ms; }
  .d3 { animation-delay: 210ms; } .d4 { animation-delay: 280ms; }
  .d5 { animation-delay: 350ms; }
}
@keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   16 · UTILITIES
   ========================================================================== */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.center { text-align: center; }
.mt-2 { margin-top: var(--s2); } .mt-3 { margin-top: var(--s3); }
.mt-4 { margin-top: var(--s4); } .mt-5 { margin-top: var(--s5); }
.mt-6 { margin-top: var(--s6); } .mt-7 { margin-top: var(--s7); }
.mt-8 { margin-top: var(--s8); } .mt-9 { margin-top: var(--s9); }
.figure { border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line); }
.figure img { width: 100%; height: 100%; object-fit: cover; }
.figure figcaption { padding: var(--s3) var(--s4); font-size: var(--t-xs); color: var(--text-muted); background: var(--card); }

/* A map is evidence, not a crop. It has to be shown whole or it stops being
   a map, so this one variant opts out of the object-fit cover above. */
.figure--map { background: var(--card); }
.figure--map img { height: auto; object-fit: contain; }

/* ==========================================================================
   17 · THE THREE DATES

   Open, close, results. The whole session hangs on these and until now they
   were scattered across a countdown, a FAQ answer and a footer line. One
   strip, one place, in the data face because they are data.
   ========================================================================== */
.stages { display: grid; gap: var(--s6); }
@media (min-width: 760px) { .stages { grid-template-columns: repeat(3, 1fr); gap: var(--s7); } }

.stage { padding-top: var(--s5); border-top: 2px solid var(--line-strong); }
.stage--now { border-top-color: var(--gold); }

.stage__d {
  font-family: var(--f-sans); font-weight: 600;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.stage--now .stage__d { color: var(--accent-text); }
.stage__h {
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: 0;
  margin-top: var(--s2);
}
.stage__p { font-size: var(--t-sm); color: var(--text-muted); margin-top: var(--s3); max-width: 34ch; }

/* ==========================================================================
   18 · THE RUBRIC

   Band tables. A real <table> is the honest element for this and it is also
   unreadable at 360px: six criteria, forty five bands, two columns of very
   uneven width. This is the same shape as §11b .rows, tuned tighter, because
   most applicants meet the rubric on a phone.

   The label takes all the width it needs and wraps; the mark never moves off
   the right edge, in tabular figures, so a column of marks reads as a column.

   Reference: Assembly's Compare Plan Details
   (mobbin.com/sites/sections/2199bf2b-ab23-46ed-b1be-f2fb10d7ece2) and
   Browserbase's pricing comparison
   (mobbin.com/sites/sections/e44a18da-8d88-40ed-a067-f064f9fc1622). Both do
   the same three things and we take all three: hairline rules instead of a
   boxed grid, no zebra striping, and a small uppercase heading row naming
   what the columns hold.

   The departure is the column count. Every published example of this pattern
   is a matrix, three or four plans against a list of features. A rubric has
   only one plan, so this is two columns, band against marks, which is what
   lets it stay legible at 360px where a matrix cannot. */
.crit { display: flex; flex-direction: column; }
.crit__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s5);
}
.crit__id { min-width: 0; }
.crit__malay {
  margin-top: var(--s2);
  font-size: var(--t-xs);
  color: var(--text-muted);
}
/* The criterion name in Malay, printed under the English one so the term an
   applicant will hear from the committee is always on the page. In Malay the
   heading above it already says exactly this, so it would print twice. */
html[lang="ms"] .crit__malay { display: none; }
.crit__w {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  padding-left: var(--s4);
  border-left: 1px solid var(--line);
}
.crit__wn {
  font-size: clamp(1.75rem, 5vw, 2.25rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0;
  color: var(--gold-700);
}
.crit__wl {
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-muted);
}
.crit__sub { margin-top: var(--s5); max-width: 62ch; }
.crit__note { margin-top: var(--s4); max-width: 62ch; }
.crit__doc {
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}

.band { margin-top: var(--s5); border-top: 1px solid var(--line-strong); }
.band__head,
.band__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2) var(--s5);
  align-items: baseline;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--line);
}
.band__head {
  padding-block: var(--s3);
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-muted);
}
.band__head .band__v { font-weight: 500; }
.band__k { font-size: var(--t-sm); line-height: 1.45; }
.band__v {
  font-family: var(--f-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-sm);
  font-weight: 600;
  text-align: right;
  min-width: 4ch;
}
/* The row the calculator landed on, echoed back so a reader can see which
   line of the published table their answer picked. */
.band__row[data-hit="true"] {
  background: rgba(253, 198, 65, 0.16);
  box-shadow: inset 3px 0 0 var(--gold);
  padding-inline: var(--s3);
  margin-inline: calc(var(--s3) * -1);
  border-radius: var(--r-xs) var(--r-xs) 0 0;
}

/* ==========================================================================
   19 · TRANSPARENCY LEDGER
   Rubric version, publication date, the three application instants, beds by
   list. It goes on the homepage and on the rubric, and it is built as a plain
   description list so that post-results figures can be added as more items
   without touching this stylesheet.
   ========================================================================== */
.ledger {
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--card);
  padding: var(--s5) var(--s6) var(--s6);
  box-shadow: var(--sh-xs);
}
.ledger__h {
  font-family: var(--f-sans);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--gold-700);
}
.ledger__grid {
  display: grid;
  gap: var(--s4) var(--s6);
  margin: var(--s5) 0 0;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.ledger__item dt { font-size: var(--t-xs); color: var(--text-muted); line-height: 1.4; }
.ledger__item dd {
  margin: 5px 0 0;
  font-size: var(--t-sm);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.ledger__note {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--text-muted);
  max-width: 62ch;
}

/* ==========================================================================
   20 · THE CALCULATOR
   A form and a worked answer. Nothing here submits, stores or transmits.

   Reference: incident.io's Cost Calculator
   (mobbin.com/sites/sections/a89d203c-0a40-48a4-a442-e632fb3784e7).
   Three things taken from it. The input groups are NUMBERED, because unlike
   most numbered sections this really is a sequence: had kifayah cannot be
   built until the household is entered, so the order carries information
   rather than decorating it. The result panel is given a border the rest of
   the site never uses, so output is never mistaken for another input card.
   And the running total ends on one emphasised line rather than trailing off.

   One deliberate departure. incident.io sets each label beside a narrow input
   on a single row, which is denser and right for a desktop tool. Ours stacks
   the label above a full width control, because most people meeting this page
   are on a phone and several of these labels run to two lines there.
   ========================================================================== */
.calc { display: grid; gap: var(--s7); }
@media (min-width: 1040px) {
  .calc { grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr); gap: var(--s8); align-items: start; }
  .calc__out { position: sticky; top: calc(var(--nav-h) + var(--notice-h) + var(--s4)); }
}

/* The output is not another card. Gold keyline, used nowhere else. */
.calc__card {
  border-color: var(--gold);
  box-shadow: var(--sh-md);
}

form[data-calc] { counter-reset: fs; }
.fieldset { border: 0; margin: 0; padding: 0; min-width: 0; counter-increment: fs; }
.fieldset + .fieldset { margin-top: var(--s6); padding-top: var(--s6); border-top: 1px solid var(--line); }
.fieldset__h {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-family: var(--f-sans);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--gold-700);
  padding: 0;
}
.fieldset__h::before {
  content: counter(fs);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  flex: none;
  border-radius: var(--r-full);
  background: rgba(253, 198, 65, .22);
  color: var(--gold-700);
  letter-spacing: 0;
}
.fieldset__s { margin-top: var(--s3); font-size: var(--t-xs); color: var(--text-muted); max-width: 56ch; }
.fields { display: grid; gap: var(--s5); margin-top: var(--s5); }
@media (min-width: 560px) { .fields--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field--wide { grid-column: 1 / -1; }
.field__l { font-size: var(--t-sm); font-weight: 600; letter-spacing: 0; }
.field__h { font-size: var(--t-xs); color: var(--text-muted); line-height: 1.45; }
.field input,
.field select {
  width: 100%;
  min-height: 48px;
  padding: 0 var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--card);
  color: var(--text);
  font-family: var(--f-sans);
  font-size: var(--t-sm);
  line-height: 1.4;
}
.field input {
  font-family: var(--f-sans); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.field input:focus, .field select:focus { border-color: var(--text-muted); }
.field select {
  padding-right: var(--s7);
  appearance: none;
  -webkit-appearance: none;
  /* A neutral mid grey chevron. It is the one decoration on the page that
     cannot take a token, since a background image has no currentColor, and
     --n-400 is the value that stays visible on both grounds. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238A93A6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s4) center;
  background-size: 18px 18px;
}
.field select option { color: #0B1020; background: #FFFFFF; }

/* Disabled, which on this site means one thing only: the mark lookup before
   the figures are published. It has to read as not yet rather than broken. */
.field input:disabled,
.field select:disabled {
  opacity: .5;
  cursor: not-allowed;
  background: var(--bg-2);
}

/* ── the answer ───────────────────────────────────────────────────────── */
.score { display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap; }
.score__n {
  font-family: var(--f-sans);
  font-size: clamp(3rem, 11vw, 4.5rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.score__d { font-size: var(--t-lg); color: var(--text-muted); }
/* Before an income has been entered there is no score, and a confident 0.0
   would be a claim rather than a blank. */
.score__n[data-blank="true"] { opacity: .22; }

.work { margin-top: var(--s6); border-top: 1px solid var(--line-strong); }
.work__row { padding: var(--s4) 0; border-bottom: 1px solid var(--line); }
.work__top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); }
.work__n { font-size: var(--t-sm); font-weight: 600; letter-spacing: 0; min-width: 0; }
.work__m {
  flex: none;
  font-family: var(--f-sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-sm);
  font-weight: 600;
}
.work__m span { color: var(--text-muted); }
.work__in { display: grid; gap: 3px; margin-top: var(--s3); }
.work__in p {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s3);
  font-size: var(--t-xs);
  color: var(--text-muted);
  line-height: 1.45;
}
.work__in b {
  font-family: var(--f-sans);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text);
  text-align: right;
}
.work__sum {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  margin-top: var(--s5);
  padding: var(--s4) var(--s5);
  border-radius: var(--r-xs);
  background: rgba(253, 198, 65, .14);
  font-size: var(--t-base);
  font-weight: 600;
}
.work__sum .num { font-size: var(--t-xl); font-weight: 500; color: var(--gold-700); }

/* The had kifayah build up. Indented under K1 because it is the working for
   one criterion rather than a criterion of its own. */
.kif { margin-top: var(--s4); padding: var(--s4) var(--s5); background: var(--bg-2); border-radius: var(--r-xs); }
.kif p {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s3);
  padding: 3px 0;
  font-size: var(--t-xs);
  color: var(--text-muted);
}
.kif p b { font-family: var(--f-sans); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
.kif p.kif--zero { opacity: .45; }
.kif p.kif--sum { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line-strong); color: var(--text); font-weight: 600; }

/* ── the one person, one box rule ────────────────────────────────────────
   The had kifayah table gives each household member exactly one component, so
   entering somebody twice inflates the largest criterion by up to five marks
   out of thirty. This states the rule before the boxes rather than after. */
.rulebox {
  margin-top: var(--s5);
  padding: var(--s4) var(--s5);
  border-radius: var(--r-xs);
  background: var(--bg-2);
  border-left: 3px solid var(--gold);
}
.rulebox__h { font-size: var(--t-sm); font-weight: 600; }
.rulebox__l { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s3); }
.rulebox__l li {
  position: relative;
  padding-left: var(--s4);
  font-size: var(--t-xs);
  color: var(--text-muted);
  line-height: 1.5;
}
.rulebox__l li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 5px; height: 5px;
  border-radius: var(--r-full);
  background: var(--gold-700);
  opacity: .6;
}

/* ── the running headcount ───────────────────────────────────────────────
   Counts out loud rather than policing. It never blocks and never silently
   corrects a figure, because a calculator that quietly changes what somebody
   typed is worse than one that lets them see the disagreement. */
.tally {
  margin-top: var(--s5);
  padding: var(--s4) var(--s5);
  border-radius: var(--r-xs);
  border: 1px solid var(--line-strong);
  background: var(--card);
}
.tally[data-state="ok"]  { border-color: var(--gold); }
.tally[data-state="off"] { border-color: var(--accent); }
.tally__n { display: flex; align-items: baseline; gap: var(--s2); font-size: var(--t-sm); }
.tally__n .num { font-size: var(--t-xl); font-weight: 600; }
.tally__d { margin-top: var(--s2); font-size: var(--t-xs); color: var(--text-muted); line-height: 1.6; }
.tally__warn { margin-top: var(--s3); font-size: var(--t-xs); line-height: 1.5; color: var(--text-muted); }
.tally[data-state="off"] .tally__warn { color: var(--accent-text); }

/* ── the published bands, folded in beside the field they belong to ──────
   So an applicant can see what each band is worth while answering, instead
   of holding the rubric open in a second tab. The row their answer landed on
   is lit by kalkulator.js through the same data-rubric-row hook the rubric
   page uses, so the two can never show different figures. */
.bandbox {
  margin-top: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--bg-2);
}
.bandbox__s {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--accent-text);
  cursor: pointer;
  list-style: none;
}
.bandbox__s::-webkit-details-marker { display: none; }
.bandbox__w { margin-left: auto; font-weight: 500; color: var(--text-muted); }
.bandbox__s svg { width: 15px; height: 15px; flex: none; transition: transform var(--dur) var(--ease); }
.bandbox[open] .bandbox__s svg { transform: rotate(45deg); }
.bandbox__b { padding: 0 var(--s4) var(--s4); }
.bandbox__b .band { margin-top: 0; }
.bandbox__b .band__row { padding-block: var(--s2); }

.calc__noscript {
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  background: var(--card);
  padding: var(--s5);
}

/* ── THE OUTCOME BLOCK ────────────────────────────────────────────── semak
   One of three sentences, printed under an applicant's six marks: offered,
   in the reserve queue, or not offered. Built on the .tally shape rather
   than as a new component, because it is the same object — a boxed
   conclusion at the foot of a working — and the page already teaches that
   shape on the calculator.

   Three states, and only one of them is coloured. Gold marks the offer,
   which is the single status this layout is allowed to raise. The reserve
   and the refusal take the neutral keyline. Hibiscus is deliberately absent:
   the site reserves it for a deadline or an action, and there is nothing
   crueller or less useful than printing "you did not get a place" in red at
   somebody who is reading it on a phone.
   ────────────────────────────────────────────────────────────────────── */
.verdict {
  margin-top: var(--s6);
  padding: var(--s5);
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--bg-2);
}
.verdict b {
  display: block;
  font-size: var(--t-xl);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.verdict p {
  margin-top: var(--s3);
  font-size: var(--t-sm);
  line-height: 1.65;
  color: var(--text-muted);
  max-width: var(--measure);
}
.verdict--yes { border-color: var(--gold); border-left-color: var(--gold); }
.verdict--yes b { color: var(--gold-700); }

/* The resident WhatsApp group, shown under an offer and nowhere else. It sits
   on navy rather than taking a fourth keyline colour, because it is the one
   thing on this card the reader is meant to DO: everything above it is a
   number to read. The group invite never reaches this stylesheet, this script
   or any file the browser can request on its own; the server attaches it to
   the reply only when the record it decrypted is an offer. */
.joinbox {
  margin-top: var(--s5);
  padding: var(--s5);
  border-radius: var(--r-xs);
  background: var(--navy);
  color: var(--parchment);
}
.joinbox .kicker { color: var(--gold); }
.joinbox p { color: var(--parchment); max-width: var(--measure); opacity: .92; }
.joinbox .btn { margin-top: var(--s4); }

/* The one line of feedback the form gives back: "checking", or the reason
   nothing opened. It is a live region, so it is read out as it changes, and
   it sits directly under the button where a thumb already is. Errors take
   the accent because an error IS the one status this layout is raising at
   that moment; the result has not been printed yet, so nothing competes. */
.semak__msg {
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--text-muted);
  max-width: var(--measure);
}
.semak__msg[data-kind="bad"] {
  color: var(--accent-text);
  padding-left: var(--s4);
  border-left: 2px solid var(--accent);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE MARK DISTRIBUTION                                          statistik
   ══════════════════════════════════════════════════════════════════════════
   Reference: Farm Minerals (mobbin.com/sites/sections/40140071-87b7-4000-
   b600-c9bcaf732aaa). A distribution drawn as thin columns where the
   reader's own region is solid and the rest is dimmed, with the tails
   annotated instead of labelled. It is the only chart in that set that
   answers "where am I in this" rather than "what is the total", which is the
   entire job here. Axis furniture, a value above each column and a label
   under it, no gridlines, is Retool's (sections/0447caaa-d953-4051-a666-
   59be614da2ec).

   The columns are a ten track grid with NO gap and the bar inset inside its
   track, so a mark maps to exactly that percentage across the plot. That is
   what lets the cut off line be positioned from its own value rather than
   nudged into place by eye, and it stays correct at any width.

   In the preview state every column is hatched and every figure is a dot.
   Nothing here can print a number that is not in results.json. */
.hist { padding: var(--s6) var(--s5) var(--s5); }
.hist__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}
.hist__meta {
  font-family: var(--f-sans);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: var(--t-xs);
  color: var(--text-muted);
}
.hist__stats { margin-top: var(--s6); gap: var(--s5) var(--s4); }
.hist__stats .stat__v { font-size: var(--t-2xl); }

.hist__plot { position: relative; margin-top: var(--s7); }
.hist__cols {
  position: relative;
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  align-items: end;
  height: 220px;
  padding-bottom: 26px;
  border-bottom: 1px solid var(--line-strong);
}
.hist__col {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 5px;
}
.hist__n {
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--text-muted);
  line-height: 1;
}
.hist__bar {
  display: block;
  width: 72%;
  height: var(--h, 2%);
  border-radius: 2px 2px 0 0;
  background: var(--navy);
  opacity: .22;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease);
}

/* Farm Minerals dims the rest of the distribution to lift one region out of
   it. That only makes sense once a region has been chosen, so the dimming is
   conditional: with no mark entered every column is equal, and the moment one
   is lit the others step back. Browsers without :has simply skip the dimming
   and the gold column still reads. */
.hist__cols:has(.hist__col[data-you]) .hist__col:not([data-you]) .hist__bar {
  opacity: .11;
}
.hist__k {
  position: absolute;
  bottom: -22px;
  font-size: var(--t-xs);
  color: var(--text-muted);
}
/* Only every other tick is labelled below 560px. Ten numbers under ten
   columns on a phone collide into a grey smear. */
@media (max-width: 559px) {
  .hist__col:nth-child(even) .hist__k { display: none; }
}

.hist[data-state="preview"] .hist__bar {
  opacity: 1;
  background: repeating-linear-gradient(
    135deg,
    var(--line-strong) 0 1.5px,
    transparent 1.5px 6px
  );
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

/* The band the reader is standing in. Solid navy against the dimmed rest,
   which is the Farm Minerals move: emphasis by contrast rather than by hue,
   so it still reads if the accent is unavailable. */
.hist__col[data-you="true"] .hist__bar {
  opacity: 1;
  background: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold-700);
}
.hist__col[data-you="true"] .hist__n { color: var(--gold-700); }
.hist__col[data-you="true"] .hist__k { color: var(--gold-700); font-weight: 600; }

/* The cut off is a threshold, so it is a line across the plot at its own
   mark, not a column. --x is the mark itself, as a percentage. */
.hist__cut {
  position: absolute;
  top: 0;
  bottom: 26px;
  left: var(--x, 50%);
  width: 0;
  border-left: 2px dashed var(--accent);
  opacity: .8;
  pointer-events: none;
}
.hist__cutl {
  position: absolute;
  top: -4px;
  left: 6px;
  white-space: nowrap;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--accent-text);
  background: var(--card);
  padding: 1px 5px;
  border-radius: var(--r-xs);
}
.hist__cut[data-flip="true"] .hist__cutl { left: auto; right: 6px; }

.hist__axis {
  margin-top: var(--s6);
  font-size: var(--t-xs);
  color: var(--text-muted);
  text-align: center;
}
.hist__table { margin-top: var(--s5); }

/* A figure that has not been published yet. Never a zero, never a dash that
   could read as a value, and never blank, which reads as an oversight. */
.pending { opacity: .3; }

/* ── the results state banner ───────────────────────────────────────────── */
.rstate {
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  flex-wrap: wrap;
  padding: var(--s5);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--card);
}
.rstate[data-state="preview"] { border-left-color: var(--line-strong); }
.rstate .body-sm { flex: 1 1 22rem; margin: 0; max-width: var(--measure); }

/* ── the mark lookup output ─────────────────────────────────────────────── */
.mout {
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--line-strong);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.mout__p { font-size: var(--t-sm); color: var(--text-muted); line-height: 1.55; }
.mout__p[data-lead="true"] {
  font-size: var(--t-lg);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.mout[data-state="empty"] .mout__p,
.mout[data-state="locked"] .mout__p { opacity: .7; }
.mout[data-state="range"] .mout__p { color: var(--accent-text); }

/* ── the preview state, said out loud ───────────────────────────────────────
   A chart is the one object on a page that gets screenshotted and forwarded
   without its caption, so the placeholder warning is repeated inside the card
   and again across the plot itself. Both disappear the moment results.json
   says published. */
.hist__ex {
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-top: var(--s6);
  padding: var(--s4) var(--s5);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--bg-2);
}
.hist__ex p { flex: 1 1 18rem; }
.hist__wm {
  position: absolute;
  inset: 0 0 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: rotate(-7deg);
  font-size: clamp(1.05rem, 3vw, 1.55rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-align: center;
  color: var(--text-muted);
  opacity: .38;
  pointer-events: none;
  /* Outlined in the card colour so the line stays readable where it crosses
     the hatching of a tall column. */
  text-shadow:
     1px 0 0 var(--card), -1px 0 0 var(--card),
     0 1px 0 var(--card), 0 -1px 0 var(--card),
     1px 1px 0 var(--card), -1px -1px 0 var(--card);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE BOARD                                                     leadership
   ══════════════════════════════════════════════════════════════════════════
   Reference: Duolingo's technical advisory board (mobbin.com/sites/sections/
   fc796e63-42dd-4777-b6b2-62382c6529da). A portrait beside stacked lines of
   text, hairline between people, no card. Chosen over the photo grid used for
   the committee because every title here runs long in Malay, and a grid of
   fixed tiles either truncates them or leaves ragged holes.

   Two columns at 720px, one below it, and the portrait stays a fixed square
   so a long title never squeezes a face out of shape. */
.board { border-top: 1px solid var(--line-strong); }
.board__row {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s5) 0;
  border-bottom: 1px solid var(--line);
}
.board__media {
  flex: none;
  width: 84px;
  height: 84px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg-2);
}
.board__media img { width: 100%; height: 100%; object-fit: cover; }
.board__body { min-width: 0; }
.board__name { font-size: var(--t-base); font-weight: 600; letter-spacing: 0; line-height: 1.35; }
.board__role { margin-top: 5px; font-size: var(--t-sm); color: var(--text); line-height: 1.45; }
.board__org  { margin-top: 3px; font-size: var(--t-xs); color: var(--text-muted); line-height: 1.45; }

@media (min-width: 720px) {
  .board { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s7); }
  .board__row:nth-child(-n+2) { border-top: 0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE MEDIA PAGE                                                     media
   ══════════════════════════════════════════════════════════════════════════
   Reference: FARFETCH press releases (mobbin.com/sites/sections/fd4997c9-
   21a0-4baf-8455-6020bdd06e60). Image, date, title, and no card chrome at
   all. Here the picture is the content, so the frame gets out of its way.
   Rejected beehiiv's bordered cards and Fruitful's filter chips: one adds a
   box around a box, the other sorts eight items nobody needs to filter. */
.mgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
  gap: var(--s7) var(--s5);
}
.mcard__media {
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--card);
  aspect-ratio: 4 / 3;
}
.mcard__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mcard__media--empty {
  display: grid;
  place-items: center;
  border-style: dashed;
  border-color: var(--line-strong);
  background: var(--bg-2);
}
.mcard__media--empty p { font-size: var(--t-xs); font-weight: 600; color: var(--text-muted); }
.mcard__d { margin-top: var(--s4); font-size: var(--t-xs); color: var(--text-muted); }
.mcard__t { margin-top: 5px; font-size: var(--t-sm); font-weight: 600; line-height: 1.45; }
.mcard--soon .mcard__t { color: var(--text-muted); }

/* An announcement is the graphic plus the notice it carried, so it gets a
   row of its own rather than a tile. The quotation is set on the cream ground
   with a gold rule, the way every other quoted document on this site is. */
.mpost { display: grid; gap: var(--s6); align-items: start; }
@media (min-width: 900px) { .mpost { grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: var(--s8); } }
.mpost__media {
  display: block;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--sh-sm);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.mpost__media:hover { transform: translateY(-3px); box-shadow: var(--sh-md); }
.mpost__media img { width: 100%; height: auto; display: block; }
.mpost__body { min-width: 0; }
.mpost__d { font-size: var(--t-xs); color: var(--text-muted); }
.mpost__q {
  margin-top: var(--s5);
  padding: var(--s5) var(--s5) var(--s5) var(--s6);
  border-left: 2px solid var(--gold);
  background: var(--bg-2);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}
.mpost__q p { font-size: var(--t-sm); line-height: 1.6; color: var(--text); max-width: var(--measure); }
.mpost__q p:first-child { font-weight: 600; }

/* The CV link sits beside LinkedIn where there is one and stands alone where
   there is not, so it takes the same shape as the LinkedIn link rather than
   becoming a button that outranks it. */
.person__link--cv { color: var(--text-muted); }
.person__link--cv:hover { color: var(--accent-text); }

/* Three links on one card is a row, not a stack. Stacked, each new document
   reads as another step to take; in a row they read as what they are, the
   same person in three places. Wraps to two lines on a narrow phone. */
.person__links { display: flex; flex-wrap: wrap; gap: 2px 14px; margin-top: 4px; }
.person__links .person__link { margin-top: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   COMPARISON BARS  ·  statistik.html
   Two bars per row: the whole field of applicants, and the fifty offered a
   place. The gap between them is the argument, so the two bars sit directly
   above each other rather than side by side in separate columns. Reading
   down the list you are reading one gap after another.

   Bars are scaled to the largest value in their own table, not to 100, so a
   two per cent row is still visible. The number is printed on every bar, so
   the scaling changes the picture and never the figure.
   ══════════════════════════════════════════════════════════════════════════ */
.cmp { margin-top: var(--s6); }
.cmp__key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s6);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line-strong);
  font-family: var(--f-sans);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--text-muted);
}
.cmp__lg { display: inline-flex; align-items: center; gap: var(--s2); }
.cmp__sw {
  width: 1.1em;
  height: 0.62em;
  flex: none;
  border-radius: 1px;
}
.cmp__sw--all { background: var(--n-300); }
.cmp__sw--off { background: var(--gold); }

.cmp__rows { list-style: none; margin: 0; padding: 0; }
.cmp__row {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: var(--s2) var(--s5);
  align-items: center;
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--line);
}
.cmp__k {
  font-size: var(--t-sm);
  line-height: 1.4;
}
.cmp__r {
  display: block;
  margin-top: 2px;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--text-muted);
}
.cmp__bars { display: grid; gap: 5px; min-width: 0; }
.cmp__bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-width: 3.6em;
  width: var(--w);
  height: 1.35em;
  padding-inline: 0.5em;
  border-radius: 2px;
}
.cmp__bar--all { background: var(--n-300); }
.cmp__bar--off { background: var(--gold); }
.cmp__v {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--navy);
}

@media (max-width: 40rem) {
  .cmp__row { grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
}
