/* ==========================================================================
   CPCE — 2026-2027 有問有答《基本法》問答比賽 / Basic Law Quiz Competition

   Every number below is a design pixel at the artboard's own scale: the
   designer's export is 1723.7 x 1073.7 pt for the home page and the export is
   1:1, so 1 pt = 1 design pixel. `--u` is one design pixel on screen, which
   makes the whole layout scale with the window instead of at a few breakpoints.

   The measurements come from the PDF geometry (tools/probe-pdf-geometry.py),
   not from eyeballing the previews:
     page margin 36.85 · content 1649.88 · 12 columns of 137.49
     roundel 118.19 at (186.84, 64.84) inside the content box
     home intro box at x 199.00, width 239.79, first line at y 370.06
     menu pill 260.80 x 34.80, pitch 51.19; home at x 1271.95, content pages
     at x 1323.05 (a 51.1 difference the design carries — see PLAN.md §12.4)
     footer band 136.29 tall, bottom of the content box on every page
   ========================================================================== */

/* The design's own type spec (§4.2) is Noto Sans TC / Noto Sans HK, both SIL
   OFL. tools/subset-fonts.py cuts them down to the glyphs the pages use, so
   the site does not depend on what the visitor happens to have installed. */
@font-face {
  font-family: "Noto Sans TC";
  src: url("../fonts/noto-sans-tc.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Noto Sans HK";
  src: url("../fonts/noto-sans-hk.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* One design pixel. The page used to sit inside the artboard's 36.85 px white
     margin, so a design pixel was a 1723.7th of the window; with that margin
     gone the card itself fills the window, and the artwork's own 1649.88 px is
     now the whole width. Scaling against that keeps every position inside the
     card (menu, roundel, columns, type) in exactly the design's proportions
     instead of letting the artwork grow away from them. */
  --u: calc(100vw / 1649.88);

  /* Browser zoom must visibly enlarge readable type and navigation. If text is
     sized directly in --u it shrinks at exactly the same rate as the CSS
     viewport grows, so the physical size never changes. --type-u tracks the
     artboard between 1440 and 1600 CSS px, then stops: below 1440 a browser
     zoom enlarges the type, and above 1600 the type no longer grows away from
     the artwork on very wide screens. */
  --type-u: clamp(0.8728px, var(--u), 0.9698px);

  --white: #ffffff;
  --navy: #003851;
  /* body copy inside the cards and the home intro are black on the artboard */
  --ink: #000000;
  --teal: #2fb5b1;
  --orange: #ec966c;
  --pink: #e280a0;
  --blue: #63b1cd;
  /* the flat field a page continues into past the artwork's icon pattern —
     measured from the designer's home-bg.webp below its last patterned row */
  --field: #9bcaed;
  --green: #8bc63e;
  --btn-green: #5cba8c;
  --btn-blue: #8fc6e4;

  /* The design draws a 36.85 px white margin round the artwork. The client
     asked for it to go on 2026-10-18, so the card is full-bleed — the artwork
     itself already runs to its own edge. */
  --page-margin: 0px;
  --content-w: calc(1649.88 * var(--u));
  --footer-h: calc(136.29 * var(--u));

  /* the menu block is centred on this line of the viewport, as the artboard
     centres it on 53.8 % of the page */
  --menu-centre: 53.8vh;

  font-family: "Noto Sans TC", "Noto Sans HK", "PingFang TC", "Microsoft JhengHei",
    "Heiti TC", system-ui, sans-serif;
  -webkit-text-size-adjust: 100%;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  background: var(--white);
}

body {
  color: var(--navy);
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* `img { display: block }` outranks the user agent's own `[hidden]` rule, so the
   attribute has to be restated for the images and panels the scripts reveal. */
[hidden] {
  display: none !important;
}

a {
  color: var(--navy);
}

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 3px;
}

/* --- utilities ------------------------------------------------------------ */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: 0;
  left: 50%;
  z-index: 20;
  transform: translate(-50%, -120%);
  padding: 0.6em 1.2em;
  background: var(--navy);
  color: var(--white);
  border-radius: 0 0 8px 8px;
  text-decoration: none;
}

.skip-link:focus {
  transform: translate(-50%, 0);
}

/* ==========================================================================
   Page shell
   The artboard is a card inset from a white page by 36.85 design px, with the
   white footer band across its bottom.
   ========================================================================== */

.page {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  padding: var(--page-margin);
  background: var(--white);
}

/* The home page is exactly the artboard's height: the artwork band and the
   footer. Letting it stretch to the window leaves a tall band of empty field
   under the illustration on a big monitor (client, 2026-10-18). */
.page--home {
  min-height: 0;
}

/* The card is at least as tall as that page's artwork, so the artwork is never
   cut short on a page whose content is shorter than the design's canvas.
   `--art-h` is the artwork height in design px; the home page's is the shared
   4172 px top, the short content pages' 5000, the long pages' 9576. */
.page__body {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  min-height: calc(var(--art-h, 4172) * var(--u));
  overflow: clip;
  background: var(--field);
  transform-origin: top left;
}

/* --- artwork -------------------------------------------------------------- */

/* Absolutely placed so the columns can sit on top of it while its own height
   stays the artwork's: the hero, then the long pages' continuation. */
.stage {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  line-height: 0;
  pointer-events: none;
  z-index: 0;
}

.stage__canvas {
  position: relative;
  width: 100%;
  line-height: 0;
}

.stage__art {
  width: 100%;
  height: auto;
}

/* The title and category scene are separate designer layers over a clean
   backdrop. The art box below spans the 2560 x 1557 card whose coordinates were
   used to register every layer; it is separate from the stage's longer icon
   continuation, so percentages stay stable on every viewport. */
.keyvisual__art {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  aspect-ratio: 2560 / 1557;
  pointer-events: none;
}

.keyvisual__title {
  position: absolute;
  top: 5.652%;
  left: 34.063%;
  width: 32.186%;
  height: auto;
}

.keyvisual__scene {
  position: absolute;
  top: 43.610%;
  left: 32.344%;
  width: 35.661%;
  aspect-ratio: 1232 / 821;
  isolation: isolate;
}

.keyvisual__layer {
  position: absolute;
  height: auto;
  transform-origin: 50% 92%;
}

/* Positions are relative to the designer's shared 1232 x 821 scene canvas. */
.keyvisual__board   { top: 10.110%; left:  0.000%; width: 100.000%; }
.keyvisual__plane   { top:  0.000%; left: 32.386%; width:  38.068%; }
.keyvisual__secoprince { top: 38.002%; left: 61.120%; width:  4.870%; z-index: 3; }
.keyvisual__family  { top: 10.962%; left: 68.019%; width:  20.779%; }
.keyvisual__primary { top: 38.855%; left:  2.192%; width:  29.627%; }
.keyvisual__open    { top: 62.850%; left: 39.773%; width:  26.948%; }
.keyvisual__elder   { top: 11.206%; left: 16.071%; width:  14.935%; }
.keyvisual__bear    { top: 21.924%; left: 86.851%; width:  11.526%; }

@media (prefers-reduced-motion: no-preference) {
  @keyframes keyvisual-bob {
    0%,
    100% {
      transform: translate3d(0, 0, 0) rotate(0deg);
    }
    50% {
      transform: translate3d(0, var(--lift), 0) rotate(var(--tilt));
    }
  }

  /* Each group keeps its own label and moves as one, so text never separates
     from its mascot. Different periods and negative delays stop the row from
     pulsing mechanically. The board and headline stay fixed. */
  .keyvisual__layer:not(.keyvisual__board):not(.keyvisual__secoprince) {
    animation: keyvisual-bob var(--period) cubic-bezier(0.42, 0, 0.58, 1) infinite;
    animation-delay: var(--delay);
    will-change: transform;
  }

  .keyvisual__plane { --lift: calc(-3.2 * var(--u)); --tilt:  0.25deg; --period: 5.2s; --delay: -1.4s; }
  .keyvisual__family  { --lift: calc(-2.2 * var(--u)); --tilt: -0.35deg; --period: 6.0s; --delay: -2.6s; }
  .keyvisual__primary { --lift: calc(-2.5 * var(--u)); --tilt:  0.45deg; --period: 4.8s; --delay: -0.6s; }
  .keyvisual__open    { --lift: calc(-2.4 * var(--u)); --tilt: -0.45deg; --period: 5.6s; --delay: -3.1s; }
  .keyvisual__elder   { --lift: calc(-1.8 * var(--u)); --tilt:  0.35deg; --period: 6.4s; --delay: -1.9s; }
  .keyvisual__bear    { --lift: calc(-2.0 * var(--u)); --tilt: -0.45deg; --period: 5.0s; --delay: -2.2s; }
}

/* --- roundel -------------------------------------------------------------- */

.brand {
  position: absolute;
  z-index: 4;
  top: calc(64.84 * var(--u));
  left: calc(186.84 * var(--u));
  display: block;
  width: calc(118.19 * var(--u));
}

.brand img {
  width: 100%;
  /* The roundel is a circular mark; clip it so nothing it carries can ever sit
     outside the circle, whatever the renderer does with its alpha edge. */
  border-radius: 50%;
}

/* --- columns -------------------------------------------------------------- */

.grid {
  position: relative;
  z-index: 1;
  flex: 1 0 auto;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
}

.col--brand-home  { grid-column: 1 / 5; position: relative; }
.col--mid-home    { grid-column: 5 / 9; }
.col--menu-home   { grid-column: 9 / 13; }

.col--brand-page  { grid-column: 1 / 3; position: relative; }
.col--mid-page    { grid-column: 3 / 11; }
.col--menu-page   { grid-column: 11 / 13; }

/* ==========================================================================
   Home page columns
   ========================================================================== */

.home-intro {
  position: absolute;
  left: calc(199.00 * var(--u));
  width: calc(239.79 * var(--u));
  /* The artboard's own text box. The PDF's 406.91 is the first *baseline*, so
     the box sits two thirds of a line higher than the plan first assumed. */
  top: calc(352.02 * var(--u));
  /* 15 px in Chinese, 16 in English — the designer's own type spec */
  font-size: calc(15 * var(--type-u));
  font-weight: 600;
  /* the artboard's 22.14 pt line at 15 pt */
  line-height: 1.476;
  color: var(--ink);
  text-align: justify;
  text-justify: inter-ideograph;
}

/* The English master is a separate document whose intro frame is wider, higher
   and set a point larger (IDML: x 148.40, w 351.60, y 238.00, 16.95 / 24.01). */
body[data-lang="en"] .home-intro {
  left: calc(148.40 * var(--u));
  width: calc(351.60 * var(--u));
  top: calc(238.00 * var(--u));
  font-family: "Noto Sans HK", "Noto Sans TC", system-ui, sans-serif;
  font-size: calc(16.95 * var(--type-u));
  font-weight: 800;
  line-height: 1.4165;
}

.home-intro p {
  margin: 0 0 calc(12.29 * var(--u));
}

body[data-lang="en"] .home-intro p {
  margin-bottom: calc(32.14 * var(--u));
}

.home-intro p:last-child {
  margin-bottom: 0;
}

/* The middle paragraph is knocked out white in both artboards. */
.home-intro p:nth-child(2) {
  color: var(--white);
}

/* ==========================================================================
   Menu
   ========================================================================== */

.menu-col {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--menu-left) * var(--u));
  width: calc(260.80 * var(--type-u));
  z-index: 3;
  /* The column spans the full page for sticky positioning, but its empty space
     must not intercept clicks on content below it — notably the last two footer
     marks. Re-enable pointer events on the menu itself. */
  pointer-events: none;
}

.menu {
  position: sticky;
  top: var(--menu-centre);
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  /* The artboard's pills are 260.80 x 34.80 at a 51.19 pitch, but the resource
     pack's artwork is drawn 89/560 of its width tall — 41.44 at the artboard's
     width — and the client confirmed on 2026-10-08 that the 560 x 89 shape is the
     intended one. So the pill keeps its own proportion, the column keeps the
     artboard's width, and the gap is what is left of the pitch. */
  gap: calc(9.74 * var(--type-u));
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: auto;
}

.menu__link {
  display: block;
  width: 100%;
  text-decoration: none;
  will-change: transform;
}

.menu__link img {
  width: 100%;
  height: auto;
}

/* The English Competition Details pill is missing from the resource pack, so the
   label-free plate carries its label as text until the artwork arrives. */
.menu__link--labelled {
  position: relative;
}

.menu__label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--white);
  font-size: calc(19 * var(--type-u));
  font-weight: 800;
  line-height: 1;
  text-shadow: 0 calc(0.5 * var(--u)) calc(1 * var(--u)) rgba(0, 0, 0, 0.18);
  pointer-events: none;
}

/* ==========================================================================
   Footer
   ========================================================================== */

/* ==========================================================================
   Content pages
   Everything below is measured from the Competition Details artboard, in design
   px: the ribbon at y 827.00, the card 843.86 wide starting at 930.58 with a
   50.14 / 29.87 / 47.05 padding, sections 41 apart, headings 21 px #004386.
   ========================================================================== */

.title-pill {
  width: calc(366.24 * var(--type-u));
  /* A shade lower than the artboard's 827 u (client, 2026-10-18). */
  margin: calc(845.00 * var(--u)) auto 0;
}

.title-pill img {
  width: 100%;
  height: auto;
}

.card {
  width: calc(843.86 * var(--u));
  margin: calc(45.98 * var(--u)) auto calc(53.79 * var(--u));
  padding: calc(50.14 * var(--u)) calc(29.87 * var(--u)) calc(47.05 * var(--u));
  border-radius: calc(20 * var(--u));
  background: var(--white);
  color: var(--ink);
  font-size: calc(12.7 * var(--type-u));
  font-weight: 300;
  line-height: 1.42;
  text-align: justify;
  text-justify: inter-ideograph;
}

/* The English Details card is a taller box and the designer sets it 21.55
   higher under the ribbon (artboard 945.88 against the Chinese page's 967.43). */
body[data-lang="en"].page--details .card {
  margin-top: calc(24.43 * var(--u));
}

.section-title {
  margin: calc(41 * var(--u)) 0 calc(6 * var(--u));
  color: #004386;
  font-size: calc(21 * var(--type-u));
  font-weight: 800;
  line-height: 1.25;
  text-align: left;
}

.card > .section-title:first-child {
  margin-top: 0;
}

body[data-lang="en"] .section-title {
  font-size: calc(18 * var(--type-u));
}

/* --- categories ----------------------------------------------------------- */

.categories {
  margin: 0;
  padding: 0;
  list-style: none;
}

.category {
  /* ring, coloured name, description — the columns are the artboard's own:
     the name starts 19.96 in, the description 86.86 (Chinese) or 286.26
     (English) in, and every row lines up on them. */
  position: relative;
  display: grid;
  grid-template-columns: calc(19.96 * var(--type-u)) calc(66.90 * var(--type-u)) minmax(0, 1fr);
  align-items: baseline;
  line-height: 1.6;
}

body[data-lang="en"] .category {
  grid-template-columns: calc(19.96 * var(--type-u)) calc(266.30 * var(--type-u)) minmax(0, 1fr);
}

.category::before {
  content: "";
  grid-column: 1;
  align-self: baseline;
  width: calc(7.22 * var(--u));
  height: calc(7.22 * var(--u));
  margin-left: calc(2.74 * var(--u));
  border: calc(1.3 * var(--u)) solid currentColor;
  border-radius: 50%;
}

.category--family::before    { color: #ee86a8; }
.category--primary::before   { color: #f39c64; }
.category--secondary::before { color: #5eb884; }
.category--open::before      { color: #6cbbdc; }

.category__name {
  font-size: calc(18 * var(--type-u));
  font-weight: 800;
  white-space: nowrap;
}

.category--family    .category__name { color: #ee86a8; }
.category--primary   .category__name { color: #f39c64; }
.category--secondary .category__name { color: #5eb884; }
.category--open      .category__name { color: #6cbbdc; }

.category__desc {
  font-size: calc(12.7 * var(--type-u));
  font-weight: 300;
}

body[data-lang="en"] .category__name { font-size: calc(15 * var(--type-u)); }
body[data-lang="en"] .category__desc { font-size: calc(14 * var(--type-u)); }

/* Larger Details copy for readability (client, 2026-10-21). The headings keep
   their original sizes so the content still has a clear hierarchy. */
body.page--details .card {
  --details-copy-size: calc(15.5 * var(--type-u));
  font-size: var(--details-copy-size);
}

body.page--details .category__desc,
body.page--details .prizes__value,
body.page--details .card .note {
  font-size: var(--details-copy-size);
}

body.page--details .prizes__label small {
  font-size: calc(14 * var(--type-u));
}

/* --- prize table ---------------------------------------------------------- */

.prizes {
  margin: 0;
  padding: 0;
  list-style: none;
}

.prizes li {
  /* ring, label + leader, award — the award column starts at 305.66 in, which
     is where the artboard puts it (the leader stops 12 before it). */
  position: relative;
  display: grid;
  grid-template-columns: calc(19.96 * var(--type-u)) calc(285.70 * var(--type-u)) minmax(0, 1fr);
  align-items: baseline;
  min-height: calc(27.09 * var(--u));
}

.prizes li::before {
  content: "";
  grid-column: 1;
  align-self: baseline;
  width: calc(7.22 * var(--u));
  height: calc(7.22 * var(--u));
  margin-left: calc(2.74 * var(--u));
  border: calc(1.3 * var(--u)) solid #008cbb;
  border-radius: 50%;
}

.prizes__mid {
  display: flex;
  align-items: baseline;
  grid-column: 2;
  min-width: 0;
}

.prizes__label {
  flex: 0 0 auto;
  color: #008cbb;
  font-size: calc(18 * var(--type-u));
  font-weight: 800;
  white-space: nowrap;
}

.prizes__label small {
  font-size: calc(12 * var(--type-u));
  font-weight: 800;
}

.prizes__leader {
  flex: 1 1 auto;
  margin: 0 calc(6 * var(--u)) calc(12 * var(--u));
  border-bottom: calc(1.2 * var(--u)) dotted rgba(0, 56, 81, 0.42);
  transform: translateY(calc(-3.5 * var(--u)));
}

.prizes__value {
  grid-column: 3;
  flex: 0 0 auto;
  min-width: 0;
  font-size: calc(12.7 * var(--type-u));
  font-weight: 500;
  white-space: nowrap;
}

body[data-lang="en"] .prizes__value {
  white-space: normal;
}

/* --- how to join ---------------------------------------------------------- */

.card .lead {
  position: relative;
  margin: 0;
  padding-left: calc(19.96 * var(--u));
  color: #008cbb;
  font-size: calc(16 * var(--type-u));
  font-weight: 800;
  line-height: 1.25;
  text-align: left;
  text-decoration-line: underline;
  text-decoration-thickness: calc(1.2 * var(--u));
  text-underline-offset: calc(2.5 * var(--u));
}

.card .lead::before,
.card .note::before {
  content: "";
  position: absolute;
  left: calc(2.74 * var(--u));
  top: calc(4.5 * var(--u));
  width: calc(7.22 * var(--u));
  height: calc(7.22 * var(--u));
  border: calc(1.3 * var(--u)) solid;
  border-radius: 50%;
}

.card .lead::before {
  border-color: #008cbb;
}

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(16.38 * var(--u));
  margin: calc(14.5 * var(--u)) 0 0;
  padding: 0;
  list-style: none;
}

.cta li {
  margin: 0;
  padding: 0;
  list-style: none;
}

.cta img {
  width: calc(223.56 * var(--type-u));
  height: auto;
}

.cta a {
  display: block;
}

.card .note {
  position: relative;
  margin: calc(18.29 * var(--u)) 0 0;
  padding-left: calc(19.96 * var(--u));
  font-size: calc(12.7 * var(--type-u));
  text-align: left;
}

.card .note::before {
  top: calc(4 * var(--u));
  border-color: #004386;
}

/* --- bullet lists --------------------------------------------------------- */

.bullets {
  margin: 0;
  padding: 0;
  list-style: none;
}

.bullets li {
  position: relative;
  margin: 0 0 calc(12.5 * var(--u));
  padding-left: calc(19.5 * var(--u));
}

.bullets li:last-child {
  margin-bottom: 0;
}

.bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(5.4 * var(--u));
  width: calc(7.22 * var(--u));
  height: calc(7.22 * var(--u));
  border: calc(1.3 * var(--u)) solid #004386;
  border-radius: 50%;
}

/* ==========================================================================
   Footer
   ========================================================================== */

.footer {
  --footer-action-height: calc(72 * var(--u));
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  min-height: var(--footer-h);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  padding: 0 calc(24 * var(--u));
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: calc(28.8 * var(--u));
  width: 100%;
}

.footer p {
  margin: 0;
}

.footer__label {
  font-size: calc(14 * var(--type-u));
  font-weight: 700;
  white-space: nowrap;
}

/* Restore the artwork's original design sizes. The larger clickable link boxes
   below do not change these image dimensions. */
.footer__org,
.footer__cpce,
.footer__committee,
.footer__facebook,
.footer__instagram {
  width: auto;
  height: auto;
}

/* Each footer mark is its own flex item, which is what lets the phone layout put
   the CPCE and steering-committee blocks on separate rows. */
.footer__mark {
  line-height: 0;
}

/* The padding and minimum height make every logo link a consistently large
   button target, including the two social marks. */
.footer__mark,
.footer__social {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--footer-action-height);
  padding: calc(6 * var(--u)) calc(12 * var(--u));
  line-height: 0;
  transition: transform 0.18s ease;
}

.footer__mark:hover,
.footer__mark:focus-visible,
.footer__social:hover,
.footer__social:focus-visible {
  transform: translateY(-2px);
}

.footer__org       { width: calc(369.63 * var(--u)); }
.footer__cpce      { width: clamp(180px, 14vw, 230px); }
.footer__committee { width: calc(267.14 * var(--u)); }
.footer__facebook  { width: calc(138.49 * var(--u)); }
.footer__instagram { width: calc(108.73 * var(--u)); }

/* Client follow-up: make the CPCE mark more prominent than the artwork while
   leaving the other footer marks at their measured sizes. */
html[lang="en"] .footer__cpce {
  width: clamp(260px, 20vw, 330px);
  height: auto;
}

/* The artboard's gaps run 26.2–31.7 design px; one 28.8 row gap is within
   2.6 px of each, which is inside the tolerance the overlay diff checks. */

/* ==========================================================================
   The band pages — Join Now, The Quiz Final, Try It Now
   These three put their content straight on the artwork (there is no card):
   a centred line of copy and, on two of them, the buttons the page exists for.
   The artboard's ribbon ink lands at y 864 and the buttons at y 1004.10.
   ========================================================================== */

.band {
  text-align: center;
}

/* Join Now, The Quiz Final and Try It Now end on the blue field with artwork
   buttons directly above the footer band; keep the same clear tail the phone
   layout already uses. */
.page--join .band,
.page--final .band,
.page--try .band {
  margin-bottom: calc(56 * var(--u));
}

.band__lede,
.band__prompt,
.band__notice {
  margin: calc(31.4 * var(--u)) 0 0;
  color: #000000;
  /* the designer's own type spec: a page lede is 18 px */
  font-size: calc(18 * var(--type-u));
  font-weight: 500;
  line-height: 1.6;
}

/* The Quiz Final's notice is the largest copy on the site. */
.band__notice {
  margin-top: calc(57 * var(--u));
  font-size: calc(25 * var(--type-u));
  font-weight: 500;
  line-height: 2.05;
}

.band__notice--link {
  margin-top: calc(22 * var(--u));
  font-size: calc(25 * var(--type-u));
  font-weight: 700;
}

.band__notice--link,
.band__notice--link a {
  color: #000000;
  text-underline-offset: calc(5 * var(--u));
}

/* The three category buttons on Join Now sit on the page, not in a card, and
   the artboard centres the row at x 861.85 with a 16.38 gap. */
.cta--page {
  justify-content: center;
  margin: calc(29.9 * var(--u)) 0 0;
}

.cta--pair {
  gap: calc(40.7 * var(--u));
}

.cta--pair img {
  width: calc(201.60 * var(--u));
}

/* ==========================================================================
   Quiz views — Warm-up and Challenge
   Measured from artboard 6: the card is the same 843.86 box as the other
   content pages but starts at y 959.6, the badge is 171.60 x 51 at 30.5 below
   that, and the option rows run on a 36 design px pitch.
   ========================================================================== */

.card--quiz {
  margin-top: calc(39.20 * var(--u));
  padding: calc(30.20 * var(--u)) calc(40.17 * var(--u)) calc(24.30 * var(--u));
  font-size: calc(14 * var(--type-u));
  text-align: left;
}

/* The challenge's head steps up to leave room for the line under the badge,
   which is what artboard 8 does against artboard 6. */
.page--challenge .card--quiz {
  padding-top: calc(15.40 * var(--u));
}

.quiz__badge {
  width: calc(171.60 * var(--type-u));
  margin: 0 auto;
}

.quiz__badge img {
  display: block;
  width: 100%;
  height: auto;
}

.quiz__note,
.quiz__score {
  margin: calc(4.3 * var(--u)) 0 0;
  color: #000000;
  font-size: calc(18 * var(--type-u));
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
}

.quiz__score-number {
  color: #e83314;
}

.quiz {
  margin: calc(23 * var(--u)) 0 0;
}

/* A line above the list tightens the gap to the first question. */
.page--challenge .quiz,
.card--quiz.is-marked .quiz {
  margin-top: calc(13.55 * var(--u));
}

.q {
  margin: 0;
  padding: 0;
  border: 0;
}

.q + .q {
  margin-top: calc(30.6 * var(--u));
}

.q__legend {
  display: flex;
  gap: calc(17 * var(--u));
  padding: 0;
}

.q__no {
  flex: 0 0 auto;
  align-self: flex-start;
  width: auto;
  height: calc(28 * var(--type-u));
  margin-top: calc(3 * var(--u));
}

.q__text {
  font-size: calc(21 * var(--type-u));
  font-weight: 700;
  line-height: calc(28.8 / 21);
}

body[data-lang="en"] .q__text {
  font-size: calc(20 * var(--type-u));
  line-height: calc(28.8 / 20);
}

.q__rows {
  margin-top: calc(21 * var(--u));
}

.q__row {
  position: relative;
  display: flex;
  align-items: center;
  /* A minimum, not a height. The artboard's options are one line each, and its
     rows run on a 36 design px pitch, so a one-line row is still exactly that.
     But the English option text sets longer in Noto Sans HK than in the
     designer's face (§12b, "known and accepted"), and a fixed height let the
     second line run straight through the next row and its rule. */
  min-height: calc(36 * var(--u));
  cursor: pointer;
  border-bottom: calc(1 * var(--u)) solid rgba(0, 56, 81, 0.28);
}

/* The letter sits in its own ring, and the option itself starts 33 px further
   along, which is where the artboard puts the text. */
.q__letter {
  position: absolute;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(31 * var(--u));
  height: calc(31 * var(--u));
  border: calc(1.4 * var(--u)) solid var(--navy);
  border-radius: 50%;
  font-size: calc(15 * var(--type-u));
  color: var(--navy);
}

.q__label {
  padding-left: calc(65.5 * var(--u));
  font-size: calc(18.83 * var(--type-u));
  font-weight: 400;
  line-height: 1.3;
}

body[data-lang="en"] .q__label {
  font-size: calc(19 * var(--type-u));
}

.q__input {
  position: absolute;
  width: calc(31 * var(--u));
  height: calc(31 * var(--u));
  opacity: 0;
  pointer-events: none;
}

/* The participant's pick: a green wash behind the row, and nothing else until
   Submit — the highlight moves with the radio. */
.q__row[data-selected] {
  background: rgba(50, 180, 100, 0.14);
}

.q__row[data-selected] .q__letter {
  border-color: #32b464;
}

.q__mark {
  position: absolute;
  right: 0;
  top: 50%;
  width: calc(26 * var(--u));
  height: calc(26 * var(--u));
  object-fit: contain;
  transform: translateY(-50%);
}

/* The tick or the cross sits at the row's own right-hand end once the answer has
   been marked, so a marked row has to keep that much room for it — an option
   long enough to wrap would otherwise run underneath the mark. Unmarked rows
   keep the artboard's full measure. */
.q__row[data-mark] .q__label {
  padding-right: calc(34 * var(--u));
}

.q__row[data-mark="right"] .q__label,
.q__row[data-mark="right"] .q__letter {
  color: #32b464;
  border-color: #32b464;
  font-weight: 800;
}

.q__row[data-mark="wrong"] .q__label,
.q__row[data-mark="wrong"] .q__letter {
  color: #e83314;
  border-color: #e83314;
  font-weight: 800;
}

.q__row[data-mark="wrong"] {
  background: transparent;
}

.q__row[data-mark] {
  cursor: default;
}

/* Moving over an option enlarges it, the way the 2025-26 build's buttons did
   (framer-motion `whileHover` to scale 1.1 over 0.3 s). A whole row cannot take
   that much — it would lap over the rows above and below and add two lines of
   growth to the card — so the row lifts a little, the option text and its ring
   swell, and the row takes the same green wash the pick uses. Marked rows are
   finished: they do not react. */
@media (prefers-reduced-motion: no-preference) {
  .q__row,
  .q__row .q__letter,
  .q__row .q__label {
    transition: transform 0.18s ease-out, background-color 0.18s ease-out;
  }

  .q__row:hover {
    transform: scale(1.012);
    background-color: rgba(50, 180, 100, 0.08);
    z-index: 1;
  }

  .q__row:hover .q__letter {
    transform: scale(1.14);
  }

  .q__row:hover .q__label {
    transform: scale(1.03);
    transform-origin: left center;
  }

  .q__row[data-mark]:hover,
  .q__row[data-mark]:hover .q__letter,
  .q__row[data-mark]:hover .q__label {
    transform: none;
  }

  .q__row[data-mark]:hover {
    background-color: transparent;
  }
}

.quiz__error {
  margin: calc(18 * var(--u)) 0 0;
  color: #e83314;
  font-size: calc(15 * var(--type-u));
  font-weight: 700;
  text-align: center;
}

.quiz__submit,
.quiz__again {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Submit sets itself below the list; Try Again lives in the action row, where
   its own top margin used to drop it out of line with the button beside it. */
.quiz__submit {
  margin: calc(29 * var(--u)) auto 0;
}

.quiz__again {
  margin: 0;
}

.quiz__submit img,
/* The two action buttons are sized by height, not width: the pack's files are
   the same height but differ in width (and the English switch carries its label
   on two lines), and width-based sizing left them a few pixels apart. */
.quiz__again img,
.quiz__switch img {
  height: calc(48.65 * var(--type-u));
  width: auto;
}

.quiz__score {
  margin: calc(9 * var(--u)) 0 0;
  color: #000000;
  font-size: calc(18 * var(--type-u));
  font-weight: 700;
  text-align: center;
}

.quiz__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(46 * var(--u));
  margin-top: calc(29 * var(--u));
}

.quiz__switch {
  display: block;
  text-decoration: none;
  line-height: 0;
}

/* Fallback pill if a supplied switch artwork is ever absent. */
.quiz__switch-text {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: calc(175.02 * var(--type-u));
  height: calc(48.65 * var(--type-u));
  padding: 0 calc(14 * var(--u));
  border: calc(1.6 * var(--u)) solid var(--navy);
  border-radius: calc(24.33 * var(--type-u));
  color: var(--navy);
  font-size: calc(18 * var(--type-u));
  font-weight: 800;
}

/* "Challenge Questions" does not fit the Chinese pill's width, and a two-line
   label made the row look ragged against Try Again beside it. */
body[data-lang="en"] .quiz__switch-text {
  min-width: calc(224 * var(--type-u));
  white-space: nowrap;
}

/* ==========================================================================
   Modals — the challenge's success card and the invitation that follows
   The plate artwork is 1603 px wide against the artboard's 769.30 design px, so
   a part's own pixel width times 0.479888 is its design width, and every
   percentage below is read against that 769.30 plate: the mascot's offset, each
   piece's width and every gap between them. The English artboards lay the two
   dialogs out at their own spacing and widths, hence the overrides at the end.
   ========================================================================== */

.modal {
  --modal-scale: 0.5625;
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(20 * var(--u));
}

.modal[hidden] {
  display: none;
}

.modal__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 20, 30, 0.62);
}

.modal__plate {
  position: relative;
  /* The plate is 769.30 × 871.5 design px and everything inside it is a
     percentage of its width, so the dialog has one scale factor rather than
     twenty. That is what lets the mobile rule shrink the plate and have the
     content follow it instead of spilling out of the box. The width also yields
     to the window's height, because on a short screen the height runs out first. */
  width: min(
    calc(769.30 * var(--modal-scale) * var(--u)),
    calc((100vh - 40 * var(--u)) * 0.88265 * var(--modal-scale))
  );
  width: min(
    calc(769.30 * var(--modal-scale) * var(--u)),
    calc((100dvh - 40 * var(--u)) * 0.88265 * var(--modal-scale))
  );
  max-width: 100%;
}

.modal__bg {
  width: 100%;
}

.modal__inner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* No padding, so a child's percentage is read against the plate itself. The
     3.900% side padding this used to carry was the boxes' own inset, and it
     silently scaled everything inside down to 92.2% of its designed size. */
  padding: 0;
}

.modal__cong,
.modal__ready {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.modal__cong[hidden],
.modal__ready[hidden] {
  display: none;
}

/* Success card — 挑戰成功！ / Congratulations! */
.modal__cong .modal__art {
  width: 50.464%;
  /* The mascot is already part of popup-bg.webp. Position each headline from
     the top of the plate instead of stacking a second mascot layer above it. */
  margin-top: 47.866%;
}

.modal__cong .modal__art--text {
  width: 64.188%;
  margin-top: 6.425%;
}

.modal__cong .modal__btn {
  margin-top: 5.301%;
}

/* Invitation — 你準備好未？ / Ready to compete for real? */
.modal__ready .modal__art {
  width: 65.310%;
  /* Clear the baked-in mascot's base with a small breathing space. */
  margin-top: 45.650%;
}

.modal__ready .modal__art--text {
  width: 78.035%;
  margin-top: 5.179%;
}

.modal__ready .modal__btn {
  margin-top: 5.303%;
}

/* The button is artwork inside a link. The link carries the artwork's own width
   rather than shrinking to fit it: a shrink-to-fit box holds a percentage-width
   image on its line box's left edge, which drew the button half a button left of
   centre. Keeping the link the artwork's size also keeps the click target it. */
.modal__btn {
  display: block;
  width: 64.935%;
}

.modal__btn-art {
  display: block;
  width: 100%;
}

/* The English master is its own document: same plate, own spacing. */
body[data-lang="en"] .modal__cong .modal__art { width: 79.720%; margin-top: 49.190%; }
body[data-lang="en"] .modal__cong .modal__art--text { width: 45.599%; margin-top: 3.681%; }
body[data-lang="en"] .modal__cong .modal__btn { margin-top: 3.878%; }
body[data-lang="en"] .modal__ready .modal__art { width: 86.706%; margin-top: 45.650%; }
body[data-lang="en"] .modal__ready .modal__art--text { width: 88.826%; margin-top: 4.492%; }
body[data-lang="en"] .modal__ready .modal__btn { margin-top: 3.900%; }

.modal__close {
  position: absolute;
  /* top is a percentage of the plate's height, right of its width. The plate's
     white area curves inside a 100 px radius, so a control hugging the corner
     sat on the navy border — which is why the first build drew nothing there.
     These two put a 40 design px button fully inside the curve. */
  top: 7.4%;
  right: 8.0%;
  width: 5.2%;
  /* ... and it stays a thumb-sized target once the plate is a phone's width. */
  min-width: 30px;
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 56, 81, 0.08);
  box-shadow: inset 0 0 0 1px rgba(0, 56, 81, 0.25);
  cursor: pointer;
}

/* The artboards leave the corner blank, so the dialog had no visible way out at
   all — only Escape and a click on the dimmed page behind it. The control is
   drawn here instead, in the artwork's own ink: a navy cross in a tinted circle,
   the shape the 2025-26 build's popup used. Both bars are read against the
   button, which is square, so either measure lands in the same place. */
.modal__close::before,
.modal__close::after {
  content: "";
  position: absolute;
  left: 29%;
  top: 50%;
  width: 42%;
  height: 9%;
  margin-top: -4.5%;
  border-radius: 999px;
  background: var(--navy);
}

.modal__close::before {
  transform: rotate(45deg);
}

.modal__close::after {
  transform: rotate(-45deg);
}

.modal__close:hover,
.modal__close:focus-visible {
  background: rgba(0, 56, 81, 0.16);
  box-shadow: inset 0 0 0 1px rgba(0, 56, 81, 0.5);
}

body.is-modal-open {
  overflow: hidden;
}

/* ==========================================================================
   Related Websites
   Seven tiles in two columns. Each tile — the screenshot, its pink caption pill
   and the globe — is one PNG in the pack, 699 px wide against the artboard's
   295.55 screenshot, so the file is placed at 322.80 design px: that puts the
   screenshot at 295.6 and the caption's type at 21.2 against the artboard's
   295.55 and 21.9. The columns sit at the artboard's centres (676.5 and 1057.1)
   and the rows pitch 311.
   ========================================================================== */

.card--web {
  margin-top: calc(41.40 * var(--u));
  padding: calc(62.20 * var(--u)) calc(60 * var(--u)) calc(52 * var(--u));
}

.tiles {
  display: grid;
  grid-template-columns: repeat(2, calc(322.80 * var(--u)));
  justify-content: center;
  column-gap: calc(57.80 * var(--u));
  row-gap: calc(54.70 * var(--u));
  margin: 0;
  padding: 0;
  list-style: none;
}

.tile__link {
  display: block;
  text-decoration: none;
}

.tile__art {
  width: 100%;
}

/* ==========================================================================
   The mobile chrome — the hamburger and its panel
   Ported from cpce2025's Menu.jsx (the pill scales 1 → 1.2 → 1.1 on hover, 0.9 on
   tap) and MobileMenu.jsx (bars morph to a cross, a halo pings behind the
   trigger, the panel's items rise in 60 ms apart). Everything below is inside the
   same three guards as the rest of the responsive pass: it only appears under
   768 px, and only animates when motion is welcome.
   ========================================================================== */

.burger,
.mobile-menu {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes burger-ping {
    0%   { transform: scale(0.6); opacity: 0.7; }
    70%  { transform: scale(1.35); opacity: 0; }
    100% { transform: scale(1.35); opacity: 0; }
  }

  @keyframes panel-item-in {
    0%   { opacity: 0; transform: scale(0); }
    60%  { opacity: 1; transform: scale(1.07); }
    100% { opacity: 1; transform: scale(1); }
  }

  /* AnimatePresence's 0.2 s fade on the panel itself. */
  @keyframes panel-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  .burger__button[aria-expanded="true"] .burger__halo {
    animation: burger-ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
  }

  /* cpce2025's MobileMenu.jsx animates each item from scale 0 with
     { stiffness: 260, damping: 20, delay: 0.1 + id / 10 }; the keyframes are
     the spring's overshoot and the delay is the same 0.1 s + 0.1 s per item. */
  .mobile-menu {
    animation: panel-fade-in 0.2s linear both;
  }

  .mobile-menu__item {
    animation: panel-item-in 0.28s cubic-bezier(0.34, 1.4, 0.64, 1) both;
    animation-delay: calc(100ms + var(--i, 0) * 100ms);
  }
}

.burger__button[aria-expanded="true"] .burger__bar:nth-child(1) {
  transform: translateY(calc(6.5 * var(--u))) rotate(45deg);
}

.burger__button[aria-expanded="true"] .burger__bar:nth-child(2) {
  opacity: 0;
}

.burger__button[aria-expanded="true"] .burger__bar:nth-child(3) {
  transform: translateY(calc(-6.5 * var(--u))) rotate(-45deg);
}

/* The text scale is decoupled from the artboard, so the layout needs to reflow
   before readable type outgrows its desktop boxes. On a 1440 px window, 125 %
   browser zoom leaves a 1152 px CSS viewport and keeps the desktop arrangement
   with type 25 % larger; 150 % leaves 960 px and enters the stacked layout. */
@media (max-width: 1100px) {
  :root {
    /* Below the breakpoint the design has no artboard, so the layout stops
       scaling with the window and switches to a normal mobile rhythm. The
       scale stops growing with the card at 520 px CSS. */
    --u: min(calc(100vw / 420), 1.238px);
    /* Mobile keeps the existing 390 px design scale, but the stacked card may
       grow to 520 px without shrinking the type into a caption. */
    --type-u: clamp(0.95px, var(--u), 1.238px);
    --menu-left: 0;
    --menu-centre: 0;
    /* The hero is drawn 120 % of the window wide (client, 2026-10-18), so design
       y 1300 — the last row carrying the illustration, and the top of the flat
       field below it — lands 1300 × 120 / 1649.88 = 94.6vw down the card. The
       title pill and the home intro measure from there. */
    --art-field-top: 94.6vw;
    /* Air between the last line of a page and the footer band. */
    --content-tail: calc(56 * var(--u));
  }

  .page {
    padding: 0;
  }

  .page__body {
    min-height: 0;
    overflow: visible;
  }

  /* The artwork stays anchored to the top of the card at its own aspect ratio,
     and the card simply grows past it on the long pages. */
  /* A fifth larger and centred: the extra width is cropped evenly at the sides,
     so no horizontal scroll appears and the illustration fills the phone better. */
  .stage {
    overflow: clip;
  }

  .stage__canvas {
    width: 120%;
    margin-left: -10%;
  }

  .brand {
    position: static;
    /* Four fifths of 96 u. */
    width: calc(77 * var(--u));
    margin: calc(16 * var(--u)) 0 0 calc(8 * var(--u));
  }

  .grid {
    display: block;
    flex: 0 0 auto;
    /* The stacked layout is a phone layout; above 520 px it centres rather than
       stretching the card wider than the artwork it sits on. */
    max-width: 555px;
    margin: 0 auto;
    padding: 0 calc(14 * var(--u));
  }

  .col--brand-page,
  .col--menu-home,
  .col--menu-page {
    display: none;
  }

  .menu-col {
    display: none;
  }

  .home-intro {
    position: static;
    width: auto;
    /* The brand occupies 93 u above the grid. Pull the intro past that by the
       same 200 u used for the content-page title pills, leaving the copy about
       60-70 px below the banner illustration on a phone. */
    margin: calc(var(--art-field-top) - 200 * var(--u)) calc(20 * var(--u)) var(--content-tail);
    font-size: calc(16 * var(--type-u));
  }

  .title-pill {
    width: calc(300 * var(--u));
    /* Between the two complaints: clear of the illustration by about 70 px on a
       390 px phone, but not parked down on the field line either. -300 u and
       -250 u touched the board; -88 u read as too low. */
    margin-top: calc(var(--art-field-top) - 200 * var(--u));
  }

  /* The home page's intro and the three band pages end on the field: give the
     last line some air before the footer instead of butting it against the
     white. */
  .band {
    margin-bottom: var(--content-tail);
  }

  .card,
  .card--quiz,
  .card--web {
    width: 100%;
    /* A little air between the title pill and the card (client, 2026-10-18). */
    margin-top: calc(14 * var(--u));
    padding: calc(28 * var(--u)) calc(18 * var(--u));
  }

  .card--quiz {
    padding-bottom: calc(24 * var(--u));
  }

  .categories,
  .prizes {
    font-size: calc(13 * var(--type-u));
  }

  .category {
    display: block;
    padding-left: calc(19.96 * var(--u));
  }

  .category::before {
    position: absolute;
    left: calc(2.74 * var(--u));
    top: calc(0.42em);
    margin-left: 0;
  }

  .category__desc {
    display: block;
    /* The description takes the block's own left edge instead of the name's
       indent, which is what made it look stepped in on a phone. */
    margin-left: calc(-19.96 * var(--u));
  }

  /* The prize rows stack instead: label, then the award under it. */
  .prizes li {
    grid-template-columns: calc(19.96 * var(--u)) minmax(0, 1fr);
    height: auto;
    margin-bottom: calc(6 * var(--u));
  }

  .prizes__mid {
    grid-column: 2;
  }

  .prizes__value {
    grid-column: 2;
    min-width: 0;
    white-space: normal;
  }

  .cta,
  .cta--page {
    flex-direction: column;
    align-items: center;
  }

  .cta img,
  .cta--pair img {
    width: calc(240 * var(--u));
  }

  .band__lede,
  .band__prompt,
  .band__notice {
    font-size: calc(17 * var(--type-u));
  }

  .band__notice {
    font-size: calc(19 * var(--type-u));
  }

  /* The quiz keeps its ringed letters but drops the leaders' tight pitch. */
  .q__legend {
    gap: calc(12 * var(--u));
  }

  .q__no {
    height: calc(24 * var(--u));
  }

  .q__text {
    font-size: calc(18 * var(--type-u));
  }

  .q__rows {
    margin-top: calc(12 * var(--u));
  }

  .q__row {
    height: auto;
    min-height: calc(40 * var(--u));
    padding: calc(8 * var(--u)) 0 calc(8 * var(--u)) calc(56 * var(--u));
    align-items: flex-start;
  }

  .q__letter {
    top: calc(50% - calc(15.5 * var(--u)));
  }

  .q__label {
    padding-left: 0;
    font-size: calc(17 * var(--type-u));
  }

  .q__mark {
    right: calc(-4 * var(--u));
  }

  .quiz__actions {
    flex-direction: column;
    align-items: center;
    gap: calc(16 * var(--u));
  }

  .quiz__submit img,
  .quiz__again img,
  .quiz__switch img {
    width: calc(200 * var(--u));
    /* The desktop rule sets the height. Mobile changes the width, so release
       that height or the supplied button artwork is stretched horizontally. */
    height: auto;
    max-width: 100%;
  }

  .quiz__switch-text {
    min-width: calc(200 * var(--u));
    height: calc(52 * var(--u));
    border-radius: calc(26 * var(--u));
    font-size: calc(17 * var(--type-u));
  }

  .tiles {
    grid-template-columns: minmax(0, 1fr);
    row-gap: calc(30 * var(--u));
  }

  .tile__art {
    width: 100%;
  }

  .modal {
    padding: calc(12 * var(--u));
  }

  /* The plate takes the window's width, and the dialog inside it is a percentage
     of the plate, so the whole popup scales down together and nothing spills out
     of the white box — which is what it used to do on a phone. */
  .modal__plate {
    width: min(
      calc(100% * var(--modal-scale)),
      calc((100vh - 24 * var(--u)) * 0.88265 * var(--modal-scale))
    );
    width: min(
      calc(100% * var(--modal-scale)),
      calc((100dvh - 24 * var(--u)) * 0.88265 * var(--modal-scale))
    );
  }

  .footer {
    --footer-action-height: calc(58 * var(--u));
    padding: calc(18 * var(--u)) calc(14 * var(--u));
  }

  .footer__inner {
    gap: calc(20 * var(--u)) calc(24 * var(--u));
  }

  .footer__label {
    flex: 0 0 100%;
    text-align: center;
  }

  .footer__org       { height: calc(63 * var(--u)); width: auto; }
  .footer__cpce      { height: auto; width: clamp(180px, 48vw, 230px); }
  .footer__committee { height: calc(45 * var(--u)); width: auto; }
  .footer__facebook,
  .footer__instagram { height: calc(36 * var(--u)); width: auto; }

  /* The CPCE block and the steering committee's lock-up were sharing a row and
     reading as one crowded block; each gets its own, as the 2025-26 footer did. */
  .footer__mark {
    flex: 0 0 100%;
    display: flex;
    justify-content: center;
  }

  /* --- the hamburger ----------------------------------------------------- */

  .burger {
    position: fixed;
    top: calc(18 * var(--u));
    right: calc(16 * var(--u));
    z-index: 30;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(4 * var(--u));
  }

  .burger__button {
    position: relative;
    width: calc(56 * var(--u));
    height: calc(56 * var(--u));
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--white);
    box-shadow: 0 calc(2 * var(--u)) calc(8 * var(--u)) rgba(0, 56, 81, 0.28);
    cursor: pointer;
  }

  .burger__halo {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: calc(2 * var(--u)) solid var(--pink);
  }

  .burger__ring {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(5 * var(--u));
  }

  .burger__bar {
    display: block;
    width: calc(26 * var(--u));
    height: calc(2.6 * var(--u));
    border-radius: calc(2 * var(--u));
    background: var(--navy);
    transition: transform 0.25s ease, opacity 0.25s ease;
  }

  .burger__caption {
    font-size: calc(11 * var(--type-u));
    font-weight: 700;
    color: var(--navy);
  }

  .mobile-menu {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: calc(28 * var(--u));
    background: rgba(255, 255, 255, 0.97);
  }

  .mobile-menu[hidden] {
    display: none;
  }

  .mobile-menu__list {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(16 * var(--u));
    width: min(100%, calc(340 * var(--u)));
    margin: 0;
    padding: 0;
    list-style: none;
  }

  /* The panel shows the desktop menu's own pills, so there is nothing to draw:
     the artwork is the pill, and .menu__link already sizes it. */
  .mobile-menu__item {
    width: 80%;
  }

  /* The one cue the artwork cannot carry is which page you are on. */
  .mobile-menu__item .menu__link[aria-current="page"] {
    filter: drop-shadow(0 0 calc(4 * var(--u)) rgba(0, 56, 81, 0.55));
  }

}

/* ==========================================================================
   Button motion
   The 2025-26 menu's Framer Motion interaction, translated to CSS: hover
   overshoots to 1.2 and settles at 1.1 over 0.3 s, while a press compresses
   to 0.9. Image buttons and clickable logo/site marks use the same behaviour.
   Hover is limited to devices that can actually hover, and the whole block
   respects the visitor's reduced-motion preference.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  @keyframes button-hover-bounce {
    0% {
      transform: scale(1);
    }

    50% {
      transform: scale(1.2);
    }

    100% {
      transform: scale(1.1);
    }
  }

  :is(
    button,
    [role="button"],
    .menu__link,
    .cta a,
    .footer__mark,
    .footer__social,
    .tile__link,
    .modal__btn,
    .quiz__switch
  ) {
    transform-origin: center;
    transition: transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);
  }

  @media (hover: hover) and (pointer: fine) {
    :is(
      button,
      [role="button"],
      .menu__link,
      .cta a,
      .footer__mark,
      .footer__social,
      .tile__link,
      .modal__btn,
      .quiz__switch
    ):not(:disabled):hover {
      transform: scale(1.1);
      animation: button-hover-bounce 0.3s cubic-bezier(0.42, 0, 0.58, 1) both;
    }
  }

  :is(
    button,
    [role="button"],
    .menu__link,
    .cta a,
    .footer__mark,
    .footer__social,
    .tile__link,
    .modal__btn,
    .quiz__switch
  ):not(:disabled):focus-visible {
    transform: scale(1.1);
    animation: button-hover-bounce 0.3s cubic-bezier(0.42, 0, 0.58, 1) both;
  }

  :is(
    button,
    [role="button"],
    .menu__link,
    .cta a,
    .footer__mark,
    .footer__social,
    .tile__link,
    .modal__btn,
    .quiz__switch,
    .mobile-menu__item .menu__link
  ):not(:disabled):active {
    transform: scale(0.9);
    animation: none;
  }
}

/* ==========================================================================
   404
   ========================================================================== */

.error-page {
  max-width: calc(760 * var(--u));
  margin: 0 auto;
  padding: calc(120 * var(--u)) calc(36.85 * var(--u)) calc(90 * var(--u));
  color: var(--ink);
  text-align: center;
}

.error-page h1 {
  margin: 0 0 calc(18 * var(--u));
  color: #004386;
  font-size: calc(38 * var(--type-u));
  font-weight: 800;
}

.error-page p {
  margin: 0 0 calc(26 * var(--u));
  font-size: calc(18 * var(--type-u));
}

.error-page__links {
  display: flex;
  gap: calc(24 * var(--u));
  justify-content: center;
  flex-wrap: wrap;
}

.error-page__links a {
  color: #008cbb;
  font-weight: 800;
}

/* Stacked desktops and tablets get the 120 %-wide hero. The board inside it
   ends at 54.5 % of the window, so anchoring the pill to the artwork's field
   line (94.6vw) let the gap under the board grow with the hero. This holds the
   gap at a constant 60 px instead, and keeps the home intro on the same line. */
@media (min-width: 751px) and (max-width: 1100px) {
  .title-pill,
  .home-intro {
    margin-top: calc(54.5vw - 93 * var(--u) + 60px);
  }
}
