/* ==========================================================================
   CPCE — Parent-child Quiz Competition in Commemoration of the
   160th Anniversary of Birth of Dr Sun Yat-sen

   Geometry in this file is calibrated against the V6 design artboards:
   6875 x 4167 px  =  1600 x 970 CSS px  (1 artboard px = 0.2327 CSS px).
   Layer positions were measured from the artboards (see tools/reconstruct.py),
   so percentages below are design values, not eyeballed.
   ========================================================================== */

/* --- webfont ------------------------------------------------------------- */

/* The @font-face for the self-hosted Noto Sans TC subset is off until the
   subset itself is in the package. The rule pointed at
   `assets/fonts/noto-sans-tc-subset.woff2`, which has never existed, so every
   page load asked for a file that 404s and then fell through to the stack in
   :root below — 24 failed requests across the 12 pages, and a second one from
   404.html (found 2026-10-08 by loading every page and logging the responses).

   Nothing on screen changes while it is off: the file was missing, so the
   fallbacks below are what has been rendering all along. To switch the face on,
   drop the subset at `assets/fonts/noto-sans-tc-subset.woff2` and uncomment the
   block — no other edit is needed, and the `?v=` token in tools/build-pages.mjs
   must move with it so returning visitors get the new stylesheet. */

/*
@font-face {
  font-family: "Noto Sans TC";
  src: url("../fonts/noto-sans-tc-subset.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
*/

/* --- tokens -------------------------------------------------------------- */

:root {
  --cream: #fff4e6;
  --paper: #ffffff;
  --navy: #01385f;
  --navy-soft: #03365c;
  --text: #000000;
  --green: #83a952;
  --blue: #2e76a9;
  --orange: #e58229;
  --pink: #cc5592;
  --line: rgba(1, 56, 95, 0.14);
  --card-shadow: 0 0.6vw 1.6vw rgba(1, 56, 95, 0.08);

  /* artwork placement as a percentage of the stage (measured from the artboards) */
  --menu-home-left: 67.1%;
  --menu-home-width: 18.25%;
  --menu-page-left: 78.2%;
  --menu-page-width: 18.5%;
  /* The menu block is centred on this line of the viewport and stays there
     while scrolling: artboard y 1963 of 4167 = 47.1 % of the screen. Desktop
     overrides this to 60 % further down (client, 2026-10-03); the mobile bar
     replaces the menu below 769 px, so this value only shows on desktop. */
  --menu-centre: 47.1vh;
  /* home artboard: 4167 px tall, and its white footer band starts at y 3599 */
  --home-hero: calc(100vw * 4167 / 6875);
  /* The band starts just above the artboard's own line, y 3599 -> 3590 (0.13 %
     of the stage, ~2.5 px at a 1920 window), so that it laps over the bottom
     edge of all three artwork layers: the side panels run well past it and the
     middle panel's bottom edge (+0.5 %, see .stage--home .art--bottom) ends
     ~30 artboard px inside the band. No hairline can show between the artwork
     and the footer at any window size or display scaling. */
  --home-footer-top: calc(100vw * 3590 / 6875);
  --home-footer-h: calc(100vw * 568 / 6875);

  font-family: "Noto Sans TC", "PingFang TC", "Microsoft JhengHei", "Heiti TC",
    "Noto Sans CJK TC", system-ui, sans-serif;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--text);
  font-size: clamp(13px, 1.0625vw, 20px);
  line-height: 1.5;
  overflow-x: hidden;
}

img {
  display: block;
  max-width: 100%;
  /* without this, an img with width/height attributes keeps the attribute
     height when max-width squeezes it (the mobile bar logo went 48 x 236) */
  height: auto;
}

a {
  color: var(--navy);
}

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --- utilities ----------------------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, -120%);
  z-index: 100;
  padding: 0.6em 1.2em;
  background: var(--navy);
  color: #fff;
  border-radius: 0 0 8px 8px;
  transition: transform 0.2s;
}

.skip-link:focus {
  transform: translate(-50%, 0);
}

/* ==========================================================================
   Artwork layer
   One absolutely positioned image per exported artwork layer. Positions are
   percentages of the stage box, which always keeps the artboard aspect ratio.
   ========================================================================== */

.stage {
  position: absolute;
  inset: 0 0 auto 0;
  width: 100%;
  aspect-ratio: 6875 / 4167;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
}

.stage > img {
  position: absolute;
  height: auto;
}

/* home */
/* Both side panels bleed off the edge they hang from, so each is anchored to
   that side. The widths are the artwork's own artboard size: the exports are
   0.5x of the artboard, so left 1305x2/6875 = 37.96 % and right
   1198x2/6875 = 34.85 % — the right panel then ends exactly on the stage's
   right edge. Narrowing either one (client asked for 37.5 % / 33.76 % on
   2026-10-03) opens page background along the right edge and widens the cream
   area the artwork leaves above the footer, because the panels no longer carry
   their own bleed. */
.stage--home .art--left    { left: 0;  top: 0; width: 37.96%; }
.stage--home .art--right   { right: 0; top: 0; width: 34.85%; }
/* bg/home-bottom is trimmed to its artwork, so the box is the artwork: 70 % wide
   from 8 %, which puts the gate and family on the artboard's axis, and bottom
   anchored on the footer band. It runs 0.5 % of the stage past the band's top
   edge (client, 2026-10-03) because ending exactly on it made the browser blend
   the artwork's last row with the page background — a pale hairline between the
   family band and the footer on displays that scale fractionally (1.25x, 1.5x) */
.stage--home .art--bottom  { left: 8%; bottom: calc(13.63% - 0.5%); width: 70%; }
.stage--home .art--banner  { left: 36.95%; top: 0.19%;  width: 26.09%; }

/* content pages */
.stage--page .art--left    { left: 0;      top: 1.34%;  width: 25.69%; }
.stage--page .art--right   { left: 82.98%; top: 0;      width: 17.02%; }
.stage--page .art--bottom  { left: 0;      top: 37.25%; width: 100%; }
/* client, 2026-10-03: 2em lower and wider. The banner is centred in the
   artboard (22.72 % + 54.55 % = 50 %), so the left offset is recomputed for the
   new width — (100 - 57) / 2 = 21.5 % — to keep it centred. Home page keeps its
   own banner rule. */
.stage--page .art--banner  { left: 21.5%;  top: 2em;    width: 57%; }

/* the faint side artwork behind the long pages (exported pre-faded) */
.stage--page .art--long-left  { left: 0;     top: 0; width: 37.41%; }
.stage--page .art--long-right { right: 0;    top: 0; width: 36.31%; }

/* client, 2026-10-03: on content pages each artwork layer dissolves into the
   cream page colour at its bottom edge instead of stopping on a hard line — the
   two side panels and the bottom band fade over their last 5 % of the viewport.
   The top banner was included then and is exempt again since 2026-10-05 (see
   the override below). The home hero keeps its hard edges. */
.stage--page > img {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 5vw), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 5vw), transparent 100%);
}

/* client, 2026-10-05: the content pages' top banner — page-top.webp — is exempt
   again and keeps the artwork's own hard bottom edge, so only the two side
   panels and the bottom band dissolve into the cream. This lifts the blanket
   fade at every width; the home hero's banner never carried one. */
.stage--page .art--banner {
  -webkit-mask-image: none;
          mask-image: none;
}

/* The bottom band is the one layer whose artwork runs right to its own edge, and
   the white footer band covers it from 87.5 % of its height (artboard y 3590 of
   the 37.25 % -> 93.1 % span it occupies). The fade is placed to finish there,
   so the illustration dissolves into the cream just above the footer instead of
   being cut by it (client, 2026-10-03). */
.stage--page .art--bottom {
  -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent 87.5%);
          mask-image: linear-gradient(to bottom, #000 70%, transparent 87.5%);
}

/* ==========================================================================
   Page shell
   ========================================================================== */

.page {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

.page__body {
  position: relative;
  /* keep the artwork column at the artboard's height; the footer band below it
     takes up whatever height is left, so tall windows never open a gap between
     the columns' background and the band */
  flex: 0 0 auto;
  /* client, 2026-10-06: on the two long pages the faint side artwork runs past
     the stage (`.stage { overflow: visible }` below), and whatever fell past the
     footer band showed as artwork below it. Clipping the page body stops every
     layer at the footer's top edge, which is where the footer's own white band
     would hide it anyway. `overflow: clip` rather than `hidden`: `hidden` would
     make this element the menu's scroll container and break its sticky
     behaviour, while `clip` only clips. */
  overflow: clip;
}

.grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  align-items: stretch;
  width: 100%;
}

.grid--home {
  min-height: var(--home-hero);
}

.grid--content {
  padding-top: var(--head-gap, 32%);
  /* the short artboards are 4167 tall like the home page, so the content
     column keeps the artboard's height and the footer band starts at y 3599 */
  min-height: var(--home-hero);
}

.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; }
.col--mid-page    { grid-column: 3 / 11; }
.col--menu-page   { grid-column: 11 / 13; }

/* --- logo ---------------------------------------------------------------- */

/* Page-level, because every artboard puts the badge at the same spot on the
   page top-left, whatever the column's head gap is. The badge is 445 artboard
   px = 103.5 CSS px at the 1600 reference; the asset carries 8 px of
   transparent margin on each side of its 236 px box. */
.brand-logo {
  position: absolute;
  z-index: 4;
  top: 2.33vw;
  left: 2.67vw;
  display: block;
  width: 6.94vw;
}

.brand-logo img {
  width: 100%;
  height: auto;
}

/* ==========================================================================
   Home page
   ========================================================================== */

/* Left column: logo at the top, then the intro copy level with the banner.
   V6 artboard — the copy box runs x 420–2195 of the 2292 wide column and its
   first line sits at y 1108 of the 4167 tall home artboard (= 16.1vw, tuned to
   15.85vw so the rendered first line lands on that y). */
.home-intro {
  position: absolute;
  left: 18.25%;
  right: 4.25%;
  top: 15.85vw;
  text-align: justify;
  color: var(--navy-soft);
  font-size: clamp(13px, 1vw, 19px);
  /* client, 2026-10-06: the intro copy goes up a weight */
  font-weight: 600;
  line-height: 1.5;
  text-shadow: 0 0 0.5em var(--cream), 0 0 0.25em var(--cream);
}

.home-intro p {
  margin: 0 0 0.5em;
}

.home-intro p:last-child {
  margin-bottom: 0;
}

/* The intro copy sits over the left-column artwork, so on desktop the client
   wants a frosted panel behind it (client, 2026-10-01). Mobile keeps the plain
   copy, because there it sits on the cream page instead of on artwork. */
@media (min-width: 769px) {
  .home-intro {
    background-color: transparent;
    backdrop-filter: none;
  }

  /* client, 2026-10-07: the panel stops where the copy stops — on the last line
     of each paragraph, that means on the last word. It is therefore painted by
     the paragraph's own inline box (the `.home-intro__panel` span) instead of
     by a block-level pseudo-element: an inline box's background follows the
     text line by line, so the shape ends at the last word at every viewport
     width and in both languages, with nothing in the stylesheet having to know
     where the line broke. A block-level panel cannot do that — its width is the
     copy box's width, which is what left the old rectangle hanging past the
     last word.

     `box-decoration-break: clone` repeats the padding, and with it the fill, on
     each line fragment. Consecutive fragments then overlap by ~0.1em and the
     panel reads as one shape; under the default `slice` the middle lines would
     lose their vertical padding and show as separate ribbons. The two
     paragraphs keep a gap between them, so their panels stay two shapes rather
     than one panel with a break in it. */
  .home-intro p {
    margin-bottom: 1.3em;
  }

  .home-intro__panel {
    /* client, 2026-10-08: the panel hugs the copy more tightly — 0.3em of fill
       on all four sides instead of 0.42em top/bottom and 0.8em left/right. */
    padding: 0.3em;
    /* The padding would otherwise shift the copy right and narrow every line,
       so it is cancelled on both sides: the text keeps the copy box's measured
       left/right edges (the first line still lands on the artboard's x) and the
       panel is what sticks out. Line breaks are unchanged by the pair — checked
       against the unwrapped copy at 1024/1280/1600 px, and re-measured for the
       0.3em pair. */
    margin: 0 -0.3em;
    -webkit-box-decoration-break: clone;
            box-decoration-break: clone;
    /* client, 2026-10-07: 30 % rather than 62 %, so the artwork shows through
       more; client, 2026-10-08: down again to a 15 % fill, the lightest the
       panel has been. The backdrop blur keeps the copy legible either way. The
       blur does apply to an inline box broken across lines — verified against
       hard stripes behind it, not assumed. */
    background-color: rgba(255, 255, 255, 0.15);
    -webkit-backdrop-filter: blur(3px);
            backdrop-filter: blur(3px);
    border-radius: 0.6em;
  }
}

/* ==========================================================================
   Side menu (desktop)
   ========================================================================== */

/* client, 2026-10-03: the menu block hangs 60 % of the viewport height down
   instead of the artboard's 47.1 %. The content pages' `--menu-centre-shift`
   is built from this token, so they follow it. */
@media (min-width: 769px) {
  :root {
    --menu-centre: 60vh;
  }
}

.sidemenu {
  position: relative;
  z-index: 3;
  /* This is the box the sticky menu travels in. Content pages push their columns
     down by the head gap, so the menu is pulled back up by `--menu-centre-shift`
     to sit on the same line as on the home page — and the box has to grow by the
     same amount, so that its bottom still lands on the column's bottom. With a
     plain `height: 100%` the negative margin moved the box up without lengthening
     it, which ended the travel early; the sticky element was then clamped to the
     box's bottom and, because it carries `translateY(-50%)` of its own height,
     ended up half a menu higher — off the top of the screen (client, 2026-10-06,
     measured at y -52 on competition-details at 1920 x 1080). */
  /* the fallback must carry a unit: `calc(100% - 0)` is invalid, and the whole
     declaration would be dropped on the home page, which sets no shift */
  height: calc(100% - var(--menu-centre-shift, 0px));
  margin-top: var(--menu-centre-shift, 0px);
}

/* client, 2026-10-06: on the two long pages the menu has to keep travelling past
   the grid — which ends where the footer starts — so that it is still on screen
   when the page is scrolled to its end. The extra travel reaches into the white
   band's strip; the page body clips that strip, so nothing shows there. */
body.page--long .sidemenu {
  height: calc(100% - var(--menu-centre-shift, 0px) + var(--home-footer-h));
}

.sidemenu__inner {
  position: sticky;
  top: var(--menu-centre);
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 0.55vw;
  align-items: flex-start;
  padding-block: 1vw;
}

.sidemenu__link {
  display: block;
  width: var(--menu-width);
  transform-origin: left center;
  transition: transform 0.3s ease;
  will-change: transform;
}

.sidemenu__link img {
  width: 100%;
  height: auto;
}

.sidemenu__link:hover {
  animation: menu-pop 0.3s ease both;
}

.sidemenu__link:active {
  transform: scale(0.9);
}

@keyframes menu-pop {
  0%   { transform: scale(1); }
  60%  { transform: scale(1.2); }
  100% { transform: scale(1.1); }
}

.col--menu-home .sidemenu__inner { --menu-width: 54.75%; margin-left: 0; }
/* width: 100% keeps the -31% margin from widening the box, so the buttons stay
   the artboard's 1254 px wide instead of hanging off the right edge */
.col--menu-page .sidemenu__inner { --menu-width: 111%; margin-left: -20%; width: 100%; }

/* ==========================================================================
   Content pages
   ========================================================================== */

.title-pill {
  width: 28.7%;
  min-width: 200px;
  margin: 0 auto;
}

.title-pill img {
  width: 100%;
  height: auto;
}

.lede {
  margin: 3.2% auto 0;
  /* client, 2026-10-06: the lede's copy is centred, the way every content
     artboard sets it under the title pill. It used to be justified, which only
     looked right because the copy carried hard line breaks; those are gone, so
     the paragraph now wraps on its own and each line centres itself. */
  text-align: center;
  /* client, 2026-10-07: centred, but the copy must not run across the
     illustration. On the artboards the block is 318-383 CSS px wide at the
     1600 reference — 30-36 % of the 1067 px middle column — so 39 % keeps the
     copy inside the clear area between the two walking pairs while still
     leaving the shorter lines (video-sharing, 383 px) on one line and letting
     the longer ones wrap onto two, exactly as the mockups draw them. The
     artboards' own figures sit at x 419-558 and x 1024-1233 of 1600, so a
     416 px block centred on 800 (592-1008) clears them both. */
  max-width: 39%;
  color: var(--navy-soft);
  font-size: clamp(14px, 1.5625vw, 30px);
  /* client, 2026-10-06: the content pages' lede goes up a weight too */
  font-weight: 600;
  line-height: 1.45;
  text-shadow: 0 0 0.45em var(--cream), 0 0 0.25em var(--cream);
}

.lede p {
  margin: 0;
}

/* client, 2026-10-06: every paragraph of running copy is set flush to both
   edges — desktop and mobile, Chinese and English. Only running text is
   included: the section titles, the prize pills, the menu and the footer keep
   their own alignment, and so does the `.lede` above, which is centred.
   `text-justify` is the legacy value for CJK, where the browser must stretch
   between ideographs rather than between words. */
.home-intro,
.home-intro p,
.category p,
.card > p,
.prizes__note,
.bullets li,
.schedule dd {
  text-align: justify;
  text-justify: inter-ideograph;
}

/* --- big category buttons ------------------------------------------------- */

.cta-pair {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 4%;
  margin-top: 4.4%;
}

.cta-pair a {
  display: block;
  width: 14.1%;
  min-width: 120px;
  transform-origin: center;
  transition: transform 0.3s ease;
}

.cta-pair--details a {
  width: 18.4%;
  min-width: 150px;
}

.cta-pair img {
  width: 100%;
  height: auto;
}

.cta-pair a:hover {
  animation: menu-pop 0.3s ease both;
}

.cta-pair a:active {
  transform: scale(0.92);
}

/* --- white content card (competition details / related websites) --------- */

.card {
  position: relative;
  width: 58.75%;
  margin: 0 auto;
  padding: 3.4% 4%;
  background: var(--paper);
  border: 3px solid var(--orange);
  border-radius: 2.2vw;
  box-shadow: var(--card-shadow);
  color: var(--text);
}

.card::after {
  content: "";
  position: absolute;
  inset: 0.2% 0.6%;
  border: 2px dashed rgba(204, 85, 146, 0.55);
  border-radius: 1.8vw;
  pointer-events: none;
}

.card--details::after {
  border-color: rgba(229, 130, 41, 0.6);
}

.card > * {
  position: relative;
  z-index: 1;
}

/* --- details typography -------------------------------------------------- */

.section-title {
  margin: 0 0 0.5em;
  color: var(--navy);
  font-size: clamp(17px, 1.875vw, 34px);
  font-weight: 700;
  line-height: 1.25;
}

.section-title__en {
  display: block;
  font-size: 0.72em;
  font-weight: 700;
}

.category {
  margin: 0 0 1em;
  padding-left: 1.5em;
  position: relative;
  /* The bullet centres on the name's line (see .category::before), so the
     name's size and the halved line box that centres it are declared once here;
     the mobile block overrides both. The copy beside the name is always
     smaller, so the name's line box is what the category's first row measures
     from. */
  --cat-name-fs: clamp(15px, 1.45vw, 26px);
  --cat-name-half: 0.75;
  /* client, 2026-10-03: as in the artboard the category name and its copy share
     one row — the name in its own column, the copy beside it with every wrapped
     line aligned to the copy's left edge. The column is 410 artboard px in the
     Chinese artboard (name starts x 1669, copy x 2079) = 5.96 % of the width,
     and 797 px in the English one = 11.6 %. Mobile keeps the stacked layout. */
  display: grid;
  grid-template-columns: 5.96vw minmax(0, 1fr);
  align-items: baseline;
}

body[data-lang="en"] .category {
  grid-template-columns: 11.6vw minmax(0, 1fr);
}

.category::before {
  content: "";
  position: absolute;
  left: 0;
  /* client, 2026-10-05: centred on the name's line box, which is where the
     artboard draws the diamond (its centre sits on the name's centre, measured
     off both artboards). The old 0.45em put the diamond ~19 artboard px high
     at 1600, and the offset grew as the window narrowed; this expression holds
     the centre at every width, and it stays on the name's line when the copy
     beside it wraps to two lines (Youth Category). The diamond keeps the size
     it has always had — 0.62em of the body font (client, 2026-10-05: the
     attempt to shrink it to the artboards' ~29 artboard px was reverted). */
  top: calc(var(--cat-name-fs) * var(--cat-name-half) - 0.31em);
  width: 0.62em;
  height: 0.62em;
  background: var(--green);
  transform: rotate(45deg);
}

.category--child .category__name { color: var(--green); }
.category--youth .category__name { color: var(--blue); }

.category--youth::before {
  background: var(--blue);
}

.category__name {
  display: block;
  font-size: var(--cat-name-fs);
  /* matches the inherited line box (1.5 desktop, 1.6 mobile) that the bullet's
     centre is computed from, so that value lives in one place */
  line-height: calc(var(--cat-name-half) * 2);
  font-weight: 600;
}

/* the Chinese pages are Chinese-only (client, 2026-09-29): the bilingual
   mirror copy that the artboards put on the English pages is dropped */
body[data-lang="zh"] .section-title__en,
body[data-lang="zh"] .category__name span,
body[data-lang="zh"] .category__en {
  display: none;
}

.category__name span {
  color: var(--text);
  font-size: 0.78em;
  font-weight: 600;
}

.category p {
  margin: 0;
  font-size: clamp(13px, 1.0625vw, 19px);
  font-weight: 400;
}

/* prize table */

.prizes {
  margin: 0.6em 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  /* every row generates boxes into this one grid (display: contents below), so
     all the pills are one width and all the values line up, however long the
     longest label is */
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 4%;
  row-gap: 0.35em;
  align-items: center;
}

.prizes__row {
  display: contents;
}

/* Artboard: the pill is 758 artboard px = 176 CSS px wide at the 1600
   reference (7.6em of the 23.2 px label), its text starts 1.1em in, the left
   corners are square and the right corners round. The odd rows are solid
   orange with white text, the even rows bare orange text (client, 2026-09-29). */
.prizes__label {
  min-width: 7.6em;
  /* client, 2026-10-03: 0.1em of air above and below the label inside the pill
     (was 0.5em, and the margin that briefly replaced it is gone) */
  padding: 0.1em 0.6em 0.1em 1.1em;
  border-radius: 0 0.4em 0.4em 0;
  text-align: left;
  white-space: nowrap;
  font-weight: 800;
  font-size: clamp(13px, 1.45vw, 26px);
  line-height: 1.15;
}

.prizes__row:nth-child(odd) .prizes__label {
  background: var(--orange);
  color: #fff;
}

.prizes__row:nth-child(even) .prizes__label {
  background: var(--paper);
  color: var(--orange);
}

.prizes__label small {
  /* client, 2026-10-03: the place count is trimmed to 0.75em; the Chinese pages
     keep it on the same row as the name, the English pages drop it to its own
     row (see the data-lang="en" rule below) */
  display: inline;
  font-size: 0.75em;
  font-weight: 800;
}

/* client, 2026-10-03: the English column stops at 9.5em (220 px at the 1600
   reference) instead of stretching to the longest name ("Outstanding Awards",
   275 px), so the pills sit narrower and that one name wraps inside its pill.
   The cap is above the widest name that has to stay whole ("2nd Runner-up",
   173 px + 1.7em of padding = 212 px) and the Chinese labels all fit inside the
   7.6em minimum, so their pills keep the artboard's width. */
body[data-lang="en"] .prizes__label {
  max-width: 9.5em;
  white-space: normal;
}

/* client, 2026-10-03: on the English pages "(1 place)" sits on the row under the
   name; the Chinese 「（1名）」 keeps the full-width bracket and stays inline.
   The block lines up with the name because the pill's own left padding already
   indents both, so the old hair of side space is not needed. */
body[data-lang="en"] .prizes__label small {
  display: block;
  margin-left: 0;
}

.prizes__value {
  font-size: clamp(13px, 1.0625vw, 19px);
}

.prizes__note {
  position: relative;
  margin: 0.9em 0 0;
  /* client, 2026-10-03: no bullet on the notes by default, so no indent for one
     — `prizes__note--lead` below adds both for the "How to join" callout */
  padding-left: 0;
  font-size: clamp(12px, 0.95vw, 17px);
}

/* The orange dart the bullet lists use, in front of the notes the artboard
   draws with a bullet. Only the "How to join" lead-in wears it (client,
   2026-10-03); it is the same dart as `.bullets li`, so the two line up in the
   left margin. The note carries `prizes__note--lead` for the indent it needs. */
.prizes__note--bullet::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 0.62em;
  height: 0.62em;
  background: var(--orange);
  transform: rotate(45deg);
}

/* client, 2026-10-03: the "How to join" lead-in is a callout, not a footnote —
   it reads at body-copy-plus size in bold (22 px at the 1600 artboard against
   the notes' 15.2 px), so it also carries the 1.15em indent that clears the dart
   and lines its text up with the `.bullets` copy below. */
.prizes__note--lead {
  padding-left: 1.15em;
  font-size: clamp(14px, 1.375vw, 22px);
  font-weight: 800;
}

/* rule list, result list, schedule, enquiry */

.bullets {
  margin: 0.4em 0 0;
  padding: 0 0 0 1.5em;
  list-style: none;
}

.bullets li {
  position: relative;
  margin-bottom: 0.4em;
  font-size: clamp(13px, 1.0625vw, 19px);
}

.bullets li::before {
  content: "";
  position: absolute;
  left: -1.4em;
  top: 0.5em;
  width: 0.62em;
  height: 0.62em;
  background: var(--orange);
  transform: rotate(45deg);
}

.divider {
  margin: 5% auto;
  width: 100%;
  height: auto;
  /* the design's comb rule (resource/divider.png); the artboard draws it at
     twice the asset's size, i.e. the full width of the card */
  aspect-ratio: 1875 / 21;
  border: 0;
  background: url("../img/details/divider.webp") center / 100% 100% no-repeat;
}

.schedule {
  margin: 0.4em 0 0;
  display: grid;
  gap: 0.5em;
}

.schedule__row {
  position: relative;
  /* client, 2026-10-03: the schedule reads as a bullet list like the rules and
     results above it, so the row carries the same dart and the same 1.5em text
     indent as `.bullets li` */
  padding-left: 1.5em;
  display: grid;
  /* client, 2026-10-05: the mockup separates the label from the value with a
     thin vertical bar, so the row is three columns — label, bar, value — and
     the bar (::after below) carries the spacing in its own margins instead of
     a column gap. The value column is a plain `1fr`, not `minmax(0, 1fr)`: its
     automatic minimum is the value's min-content width, so a long label wraps
     instead of squashing the value to nothing and pushing it off the page
     (which `minmax(0, 1fr)` did on a phone, client-facing bug found 2026-10-05). */
  grid-template-columns: minmax(0, auto) auto 1fr;
  column-gap: 0;
  align-items: start;
  font-size: clamp(13px, 1.0625vw, 19px);
  /* half the label's line box, in em, which is what the bar's centre is
     measured from (1.5 desktop / 1.6 mobile body leading) */
  --sched-line-half: 0.75;
}

.schedule__row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 0.62em;
  height: 0.62em;
  background: var(--orange);
  transform: rotate(45deg);
}

/* the mockup's "|" between the label and the value: measured off the artboard,
   a 3 artboard px hairline standing 60 artboard px tall (0.82em beside the
   17 px row type), with 40 artboard px of air before it and 33 after. The
   margins are the artboard's 9.3 / 7.7 CSS px less the 1.45 px and 0.59 px side
   bearings the label's last glyph and the value's first one carry at 1600, so
   the air between the ink matches. It is a grid item of its own, so it keeps
   its place between the two columns when either side wraps.
   client, 2026-10-08: the bar was drawn at the artboard's 0.7 CSS px rounded up
   to a 1 px hairline, which read as too faint against the navy value, so all
   three schedule rows now carry 1.5 px — still a hairline beside the 0.82em
   height, just enough weight to hold its own.

   client, 2026-10-08 (second pass): 1.5 px is not a whole pixel count, and the
   bar was not stable — it came out 1 px wide in some rows and 2 px in others, so
   the hairline doubled in weight from one window width to the next and rows
   disagreed with each other at the same width (tools/out/measure-bar.mjs: 1
   device px at 1600, 2 at 1920; at 125 % display scaling one row 4 device px and
   its neighbours 3).

   The fix is a whole-pixel box in one flat colour, and the measurements say why
   the two obvious alternatives do not work. The browser rounds a box's edges to
   the CSS pixel grid before painting, which is what turned 1.5 px into 1 px or
   2 px per row. Splitting the 1.5 px into a 1 px fill plus a 1 px half-strength
   border, or into a gradient with a hard stop, keeps the 1.5 px on paper but puts
   an internal boundary inside the box at a fractional device pixel: the browser
   quantises that boundary per row and the rows drift apart again (9-23 % ink
   spread at 125 % and 150 % scaling). A single flat colour has no such boundary
   — its ink is area x alpha, so it can only scale and antialias by area and every
   row inks the same, at any width and any scale factor. That makes 2 px the
   choice: 1 px is the hairline the client already asked to thicken, and 1.5 px is
   not reachable without the boundary the fix exists to remove. The em height is
   rounded onto the same grid with `round()`, which is what stopped rows
   disagreeing about the bar's length. */
.schedule__row::after {
  content: "";
  grid-column: 2;
  grid-row: 1;
  /* centred on the label's line, which is what the mockup's single-line rows
     show and what keeps the bar beside the label when the value wraps */
  align-self: start;
  /* a whole pixel count in both directions, so edge rounding cannot change it */
  width: 2px;
  /* the fallback keeps the em height for browsers without `round()`; the second
     declaration snaps it, so the browser cannot round it per row */
  height: 0.82em;
  height: round(0.82em, 1px);
  margin: calc(var(--sched-line-half) * 1em - 0.41em) 0.42em 0 0.46em;
  /* one flat colour, top to bottom: see the note above — any second colour or
     half-strength half inside the box puts back the boundary that made the rows
     disagree */
  background: currentColor;
}

.schedule__row > dt {
  grid-column: 1;
}

/* the artboard sets the schedule label in the body weight, so no bold here
   (client, 2026-10-03) */

.schedule dd {
  margin: 0;
  grid-column: 3;
}

/* --- related websites ---------------------------------------------------- */

.related {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* the row gap has to be a length: a percentage row gap resolves against the
     grid's own height, which is indefinite here, so the rows used to spill out
     of the card's bottom edge */
  row-gap: 2rem;
  column-gap: 6%;
}

.related__item {
  display: block;
}

.related__item img {
  width: 100%;
  /* one size for every card: the widest artwork's ratio (759 x 557 at the 1600
     reference). client, 2026-10-03: the artwork is fitted inside that box
     instead of being cropped — the artworks run 611 to 759 px wide, so the
     narrower ones now show whole, with cream at the sides, and the label pill
     baked into the artwork's bottom edge still sits on the box's bottom edge */
  aspect-ratio: 759 / 557;
  object-fit: contain;
  object-position: center bottom;
}

/* the card artwork carries its own label pill, so the image is the button
   (client, 2026-09-30) — no separate label under it */
.related__item a {
  display: block;
  transform-origin: center;
  transition: transform 0.3s ease;
}

.related__item a:hover {
  transform: scale(1.03);
}

.related__item a:active {
  transform: scale(0.98);
}

/* artboard: 472 artboard px = 110 CSS px of air between the card and the
   footer band, so the frame never sits against the footer (client, 2026-09-30).
   client, 2026-10-08: competition-details gets the same air — its card used to
   end flush on the footer band on both languages, desktop and mobile alike. */
body[data-page="related-websites"] .card,
body[data-page="competition-details"] .card {
  margin-bottom: clamp(1.5rem, 6.7vw, 8rem);
}

/* --- the extra 2/8/2 section on the long pages --------------------------- */

.extra-section {
  position: relative;
  height: 26vw;
  pointer-events: none;
}

/* ==========================================================================
   Footer
   ========================================================================== */

/* --- competition details, desktop only (client tweaks 2026-09-29) --------- */

@media (min-width: 769px) {
  /* client, 2026-10-01: every content page carries the title pill at the
     competition-details size (42 % of the middle column) and on the same line,
     34 % of the page width down. The pages have different head gaps — the
     grid padding that clears the banner artwork — so the pill cancels its own
     page's gap with a negative margin. Both numbers are percentages of
     different boxes: the head gap is measured against the grid and the margin
     against the middle column, which is 8/12 of it, hence the 1.5.
     Competition Details (43.3 % head gap) works out to the -13.95 % the client
     asked for. */
  .title-pill {
    width: 42%;
    margin-top: calc(51% - 1.5 * var(--head-gap, 32%));
  }

  /* Artboard: the card starts at y 693, i.e. 10.7 % of the middle column below
     the pill. A percentage keeps that as the page scales, where the previous px
     margins did not. */
  body[data-page="competition-details"] .title-pill {
    margin-bottom: 10.7%;
  }

  /* the artboard's card is 940 px wide at the 1600 reference */
  body[data-page="competition-details"] .card--details {
    width: 58.75vw;
  }

  /* Related Websites (client, 2026-09-30): the lower margin keeps the card where
     the artboard puts it, at y ~690; the title pill's line comes from the shared
     rule above */
  body[data-page="related-websites"] .title-pill {
    margin-bottom: 12.1%;
  }

  /* client, 2026-10-01: the related-websites card runs wider than the default
     58.75 % — the AW6 artboard's card is ~75 % of the middle column */
  body[data-page="related-websites"] .card {
    width: 80%;
  }

  /* client, 2026-10-02: the copy and the category buttons sit closer to the
     title pill above them, and the buttons are wider — except on join-now,
     whose artboard pair is 20 % of the middle column */
  .lede {
    margin-top: 2%;
  }

  .cta-pair {
    margin-top: 2%;
  }

  .cta-pair a {
    width: 40%;
  }

  body[data-page="join-now"] .cta-pair a {
    width: 20%;
  }

  /* client, 2026-10-02: the quiz-final page's single "view in Chinese" button
     sits directly under the copy, so it clears the footer band that the short
     pages pin to the artboard line */
  body[data-page="quiz-final"] .cta-pair {
    margin-top: 0;
  }

  body[data-page="competition-details"] .art--long-right {
    left: 75%;
    width: 25%;
  }

  /* client, 2026-10-02: on the two long pages the faint side artwork runs
     below the bottom band instead of from the top of the page. The band is
     37.25 % down and 100 % x 881/2600 x 6875/4167 of the stage tall, so its
     lower edge sits at 93.16 % of the stage. The stage is only as tall as the
     short artboards, so it stops clipping on the long pages — otherwise the
     artwork would be cut off 67 px below the band. */
  body.page--long .stage {
    overflow: visible;
  }

  body.page--long .art--long-left,
  body.page--long .art--long-right {
    top: 93.16%;
  }
}

.footer {
  position: relative;
  z-index: 2;
  background: var(--paper);
  /* artboard: the band is 132 CSS px tall at the 1600 reference = the 65 px logo
     row plus 2.1 % of padding top and bottom. The min-height keeps that
     proportion when the window is taller than the page, so the band cannot grow
     with the viewport. */
  min-height: var(--home-footer-h);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 2.1% 3%;
}

/* The short artboards put the white band over the bottom of the columns (their
   artwork's last strip runs under it), so pull the footer up to artboard y 3599
   instead of leaving the artwork's blank margin showing. The two long pages
   (competition-details, related-websites) follow their content as before. */
body:not(.page--long) .footer {
  margin-top: calc(var(--home-footer-top) - var(--home-hero));
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  /* client, 2026-10-03: the row is 930 CSS px wide at the 1600 reference in the
     mockup, i.e. 58.1 % of the artboard. The mockup's gaps between the four
     blocks are 110/112/133 artboard px = ~1.7 % of the width each. */
  gap: 0 1.7vw;
}

.footer p {
  margin: 0;
}

.footer__coorg {
  display: flex;
  align-items: center;
  /* mockup: 150 artboard px between the 合辦機構 / Co-organiser label and the
     logo block */
  gap: 2.18vw;
  font-size: clamp(9px, 0.82vw, 14px);
  line-height: 1.35;
}

/* Logo sizes are the artboard's own: every block is 0.5x in the export, so the
   width is its artboard width. Measured off the mockups' footer bands —
   合辦機構 block 1542 artboard px (1743 in English), CPCE 705 (1127),
   Facebook 578, Instagram 454 — i.e. 22.44 / 25.35, 10.25 / 16.39, 8.41 and
   6.6 % of the artboard width, each with its own px floor for narrow windows.
   Those floors are the old mobile sizes and do not share the artwork's ratio,
   so under the 769 px breakpoint the footer block at the end of the stylesheet
   gives the three item logos one shared scale instead — otherwise they sit at
   three different heights. */
.footer__coorg img {
  height: auto;
  width: max(188px, 22.44vw);
}

body[data-lang="en"] .footer__coorg img {
  width: max(188px, 25.35vw);
}

.footer__item img {
  height: auto;
}

.footer__item--cpce img { width: max(82px, 10.25vw); }

body[data-lang="en"] .footer__item--cpce img { width: max(82px, 16.39vw); }

/* `src*=` rather than `src$=`: every asset URL now carries a `?v=` cache
   version (see build-pages.mjs), so the extension is no longer the end of the
   string and `$=` matched nothing — which left the two social logos at their
   natural size. */
.footer__item--social img[src*="facebook.webp"]  { width: max(75px, 8.41vw); }
.footer__item--social img[src*="instagram.webp"] { width: max(75px, 6.6vw); }

.footer__item--social a {
  display: block;
}

/* ==========================================================================
   Mobile menu (hamburger) — behaviour ported from reference/MobileMenu.jsx
   ========================================================================== */

.mobile-bar {
  display: none;
}

.hamburger {
  position: relative;
  width: 3.1rem;
  height: 3.1rem;
  padding: 0;
  border: 2px solid var(--navy);
  border-radius: 50%;
  background: var(--paper);
  cursor: pointer;
  z-index: 60;
}

.hamburger::before {
  content: "";
  position: absolute;
  inset: -12%;
  border-radius: 50%;
  background: rgba(46, 118, 169, 0.35);
  animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
  z-index: -1;
}

@keyframes ping {
  0%   { transform: scale(0.9); opacity: 0.7; }
  70%  { transform: scale(1.5); opacity: 0; }
  100% { transform: scale(1.5); opacity: 0; }
}

.hamburger__bars {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  gap: 5px;
}

.hamburger__bars span {
  display: block;
  width: 22px;
  height: 2.5px;
  border-radius: 2px;
  background: var(--navy);
  transition: transform 0.3s ease, opacity 0.2s ease;
}

.hamburger[aria-expanded="true"] .hamburger__bars span:nth-child(1) {
  transform: translateY(7.5px) rotate(45deg);
}

.hamburger[aria-expanded="true"] .hamburger__bars span:nth-child(2) {
  opacity: 0;
}

.hamburger[aria-expanded="true"] .hamburger__bars span:nth-child(3) {
  transform: translateY(-7.5px) rotate(-45deg);
}

.hamburger__label {
  position: absolute;
  left: 50%;
  bottom: -1.5rem;
  transform: translateX(-50%);
  font-size: 0.8rem;
  font-weight: 500;
  color: #5b6b77;
  white-space: nowrap;
}

.mobile-panel {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  padding: 5.2rem 1rem 1.2rem;
  background: var(--cream);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 12px 24px rgba(1, 56, 95, 0.12);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.5rem);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}

.mobile-panel[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.mobile-panel ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.55rem;
}

.mobile-panel li {
  opacity: 0;
  transform: scale(0.8);
}

.mobile-panel[data-open="true"] li {
  animation: menu-item-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  animation-delay: calc(var(--i) * 0.06s + 0.05s);
}

@keyframes menu-item-in {
  to { opacity: 1; transform: none; }
}

.mobile-panel img {
  width: min(78%, 320px);
  margin: 0 auto;
  height: auto;
}

.mobile-panel a {
  display: block;
}

.mobile-panel__backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(1, 56, 95, 0.18);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s;
}

.mobile-panel__backdrop[data-open="true"] {
  opacity: 1;
  visibility: visible;
}

/* ==========================================================================
   Responsive — the brief's mobile breakpoint is 768px
   ========================================================================== */

@media (max-width: 768px) {
  :root {
    --card-shadow: 0 8px 20px rgba(1, 56, 95, 0.08);
  }

  body {
    font-size: 15px;
    line-height: 1.6;
  }

  /* the two side columns are dropped and their content moves into the middle */
  .grid {
    display: block;
  }

  .col--brand-home,
  .col--brand-page,
  .col--mid-home,
  .col--mid-page {
    display: block;
  }

  /* the middle column carries the intro and the family artwork on the home page,
     and grows so the artwork can reach the footer */
  .col--mid-home {
    display: flex;
    flex-direction: column;
    flex: 1 0 auto;
  }

  .col--menu-home,
  .col--menu-page {
    display: none;
  }

  .sidemenu {
    display: none;
  }

  .mobile-bar {
    position: fixed;
    inset: 0 0 auto;
    z-index: 55;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.55rem 0.9rem;
    background: rgba(255, 244, 230, 0.92);
    backdrop-filter: blur(6px);
    border-bottom: 1px solid var(--line);
  }

  .mobile-bar__logo {
    width: 3rem;
  }

  .mobile-bar__title {
    /* the event title is in the banner artwork right below, so the bar only
       carries the logo and the menu button */
    display: none;
  }

  /* with the title gone the bar is only as tall as the menu button, so the
     選單 / Menu label sits beside it rather than under it */
  .hamburger__label {
    left: auto;
    right: 100%;
    bottom: auto;
    top: 50%;
    transform: translateY(-50%);
    margin-right: 0.55rem;
  }

  /* --- home ------------------------------------------------------------- */
  .stage {
    aspect-ratio: 6875 / 5200;
  }

  .stage--home .art--left,
  .stage--home .art--right {
    display: none;
  }

  .stage--home .art--banner {
    left: 8%;
    top: 4.2rem;   /* just clear of the fixed bar */
    width: 84%;
  }

  /* the family artwork moves into the spacer below the intro, so the three
     pieces stack: banner, intro, family — none of them behind another */
  .stage--home .art--bottom {
    display: none;
  }

  .grid--home {
    min-height: 0;
    /* room for the fixed bar (4.2rem) plus the banner artwork (56.5vw) */
    padding: calc(4.2rem + 59vw) 1.1rem 0;
    display: flex;
    flex-direction: column;
  }

  .brand-logo {
    display: none;
  }

  .home-intro {
    position: static;
    width: min(100%, 34rem);
    margin: 0 auto;
    padding: 0;
    text-align: justify;
    font-size: 0.98rem;
    line-height: 1.7;
    text-shadow: none;
  }

  .home-spacer {
    /* the family artwork follows the intro and sits on the footer's top edge;
       it takes up whatever height is left over, so a tall screen opens the gap
       above the artwork instead of leaving one above the footer */
    flex: 1 0 auto;
    min-height: 47.5vw;
    /* home-bottom starts 5 % off the left edge, so the artwork's left side is
       cropped by the screen — its content reads heavier on that side. The
       spacer is inset by the grid's 1.1rem padding, hence the calc. */
    background: url("../img/bg/home-bottom.webp") calc(-5vw - 1.1rem) bottom / 90vw auto no-repeat;
  }

  /* --- content pages ----------------------------------------------------- */
  .stage--page {
    /* client, 2026-10-01: the square stage keeps the banner and lets the
       bottom band show below it instead of being clipped after 52 px */
    aspect-ratio: 5600 / 5600;
    /* the stacked artwork runs past the stage on mobile, so the cream fade goes
       on the stage itself: it dissolves into the page colour over its last 16 %
       instead of being cut off mid-page (client, 2026-10-03) */
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 16vw), transparent 100%);
            mask-image: linear-gradient(to bottom, #000 calc(100% - 16vw), transparent 100%);
  }

  .stage--page .art--left,
  .stage--page .art--right,
  .stage--page .art--long-left,
  .stage--page .art--long-right {
    display: none;
  }

  .stage--page .art--banner {
    left: 3%;
    /* clear the fixed bar: this banner artwork carries the event title, so on
       mobile it starts under the bar instead of behind it */
    top: 4.2rem;
    width: 94%;
  }

  .stage--page .art--bottom {
    left: -34%;
    /* client, 2026-10-02: on mobile the band starts 1rem below the top of the
       stage instead of clearing the fixed bar, so it becomes the backdrop for
       the title and the card. client, 2026-10-03: 40vw instead of 51vw, so the
       band opens higher up the page. client, 2026-10-05: 3.5rem instead of
       1rem, pulling the band's top edge down towards the fixed bar (4.2rem). */
    top: calc(3.5rem + 40vw);
    /* the base rule pins the band to width 100 %, so a max-width alone cannot
       grow it: the band has to carry the 170 % as its width, with the image's
       own 100 % max-width cap lifted to match (client, 2026-10-01) */
    width: 170%;
    max-width: 170%;
  }

  .grid--content {
    /* room for the fixed bar (4.2rem) plus the banner artwork (94 % of the
       viewport wide, 1016 / 1875 tall), so the title pill and everything below
       it follow the banner instead of being drawn over it */
    /* client, 2026-10-02: the bottom padding is what actually holds the page's
       last block off the footer band — a bottom margin on the `.cta-pair`
       collapses out through this box, so it never showed */
    padding: calc(4.2rem + 54vw) 1.1rem 1rem;
  }

  .title-pill {
    /* client, 2026-10-01: 20rem wide. client, 2026-10-05: 46 % of the viewport
       instead. Measured off the band artwork behind it, its two walking pairs
       end at x 112 and start at x 296 of a 390 px viewport, so the 20rem pill
       (x 35-355) hid their bodies; at 46 % the pill (x 105-285) sits in the
       road between them. A percentage rather than a fixed width keeps that
       clearance on other phone widths, because the band is 170 % of the
       viewport and so scales with it. */
    width: 46%;
    /* the base rule's 200 px floor would win over the 46 % on a phone, and the
       200 px pill is exactly what covered the artwork */
    min-width: 0;
    /* client, 2026-10-02: the pill's old 8rem bottom margin was collapsing
       with the copy's top margin and winning, so the copy could never sit as
       close to the pill as the client asked. The bottom margin is gone and the
       gap below is now carried by the `.lede` (1rem) and, on the two long
       pages, by the card's own 5rem below. client, 2026-10-03: the top margin
       was -1em, pulling the pill up under the banner artwork; client,
       2026-10-05: it is now the desktop rule's own expression — 51 % of the
       column less one and a half head gaps.
       client, 2026-10-07: on a phone that expression has to stop reading the
       page's own `--head-gap`. On the two long pages it is 43.3 % and 39.6 %
       (they are positioned for a desktop column), which turns the margin
       negative — -14 % and -8 % of the column — and pulls the pill and the copy
       up over the banner artwork, exactly as the client reported on
       competition-details and related-websites. The mobile columns are all
       full-width, so every page uses the join-now gap (32.1 %) and all six line
       up below the banner: +2.85 % instead of -14 %. */
    margin: calc(51% - 1.5 * 32.1%) auto 0;
  }

  .lede {
    font-size: 1.05rem;
    /* client, 2026-10-02: tighten the copy's own margins — the gap to the
       footer is carried by the blocks around it instead */
    margin-top: 1rem;
    margin-bottom: 0;
    text-shadow: none;
    /* client, 2026-10-05: just a little wider than the title pill above it. At
       full width the copy ran across the band artwork's two walking pairs,
       whose legs sit at x 92 and x 306 of a 390 px viewport at this height; the
       pill is 46 % of the column, so 54 % keeps the copy between them with 8-16
       px to spare (and the base rule's `auto` margins keep it centred). 54 %
       rather than less because the English "Click the below links to" needs
       191 px; below that it breaks with "to" stranded on its own line. */
    width: 54%;
    /* the mobile column is the full viewport, so the desktop rule's 39 %
       (of the 8-column middle column) would squeeze the copy to a third of
       this width; the 54 % above is this breakpoint's own measurement */
    max-width: none;
  }

  /* client, 2026-10-01: room inside the page frame on the two long pages */
  body[data-page="competition-details"] .card,
  body[data-page="related-websites"] .card {
    margin-top: 5rem;
    margin-bottom: 5rem;
  }

  .cta-pair {
    gap: 5%;
    /* client, 2026-10-02: the gap below the pair is the grid's bottom padding */
    margin-top: 1rem;
  }

  .cta-pair a,
  .cta-pair--details a {
    /* client, 2026-10-02: match the desktop pair widths */
    width: 40%;
    min-width: 0;
  }

  /* the join-now page carries two narrow category buttons side by side */
  body[data-page="join-now"] .cta-pair a {
    width: 20%;
  }

  .card {
    width: 100%;
    padding: 1.6rem 1.1rem;
    border-width: 2px;
    border-radius: 1rem;
  }

  .card::after {
    inset: 0.4rem;
    border-radius: 0.8rem;
  }

  .section-title {
    font-size: 1.2rem;
  }

  /* the name and its copy sit on one row on desktop; stacked reads better in a
     390 px column */
  .category {
    display: block;
    /* the bullet centres on the name's line, so the stacked layout overrides
       the name's size (1.05rem) and its line box (1.6, the mobile body
       leading) here rather than on .category__name */
    --cat-name-fs: 1.05rem;
    --cat-name-half: 0.8;
  }

  .category p {
    margin: 0.3em 0 0;
  }

  .category p,
  .bullets li,
  .prizes__value,
  .schedule__row {
    font-size: 0.95rem;
  }

  /* client, 2026-10-05: on a phone the label sits above its value instead of
     beside it. The two columns left the value only as wide as its longest word
     (73 px in the English schedule), so values like "11:59pm on 6 November
     2026 (Friday)" wrapped over five lines. The "|" only separates a label
     from a value that share a line, so the stacked layout drops it. */
  .schedule__row {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0;
    /* the mobile body leading is 1.6, so the bar's centre would move with it —
       kept so the rule still holds if the bar comes back */
    --sched-line-half: 0.8;
  }

  .schedule__row::after {
    content: none;
  }

  .schedule__row > dt,
  .schedule dd {
    grid-column: 1;
  }

  .prizes {
    column-gap: 0.6rem;
    row-gap: 0.6rem;
    align-items: start;
  }

  .prizes__label {
    font-size: 0.9rem;
  }

  .related {
    grid-template-columns: minmax(0, 1fr);
    gap: 2rem;
  }

  /* client, 2026-10-01: the stacked cards sit at 80 % of the column and centred */
  .related__item {
    width: 80%;
    margin: 0 auto;
  }

  /* The mobile card used to stretch the artwork into the box (object-fit:
     fill, client 2026-10-01) so that the whole label pill showed; the base rule
     now fits the artwork inside the box un-distorted, which does the same job,
     so there is no mobile override left here. */

  .extra-section {
    height: 30vw;
  }

  /* --- footer wraps onto two lines --------------------------------------- */
  .footer {
    padding: 1.6rem 1rem 1.4rem;
  }

  /* the columns stack on mobile, so the footer follows the content again */
  body:not(.page--long) .footer {
    margin-top: 0;
  }

  /* on mobile the page column keeps filling the screen and the footer stays as
     tall as its content (the stacked artwork is above the text, not beside it) */
  .page__body {
    flex: 1 0 auto;
  }

  .footer {
    flex: 0 0 auto;
    min-height: 0;
  }

  .footer__inner {
    /* coorg on one line, then CPCE, then the two social logos side by side */
    display: grid;
    grid-template-columns: repeat(2, auto);
    justify-content: center;
    justify-items: center;
    align-items: center;
    gap: 0.85rem 1.2rem;
  }

  .footer__coorg {
    grid-column: 1 / -1;
  }

  .footer__item--cpce {
    grid-column: 1 / -1;
  }

  /* client, 2026-10-06: the three logos keep the artwork's own widths instead
     of the old three px floors (82 / 75 / 75), which drew CPCE, Facebook and
     Instagram at three different heights. The artboard widths are CPCE 705
     (1127 in English), Facebook 578 and Instagram 454, so every width is one
     `--footer-logo-w` scale multiplied by that block's share of 705. The scale
     is the CPCE block's old 82 px floor — the size the stacked footer has
     always used, and the same width the desktop rule's `max()` falls back to
     just above the breakpoint, so the band does not jump when the window
     crosses 768 px. */
  .footer {
    --footer-logo-w: 82px;
  }

  .footer__item--cpce img {
    width: var(--footer-logo-w);
  }

  body[data-lang="en"] .footer__item--cpce img {
    width: calc(var(--footer-logo-w) * 1127 / 705);
  }

  .footer__item--social img[src*="facebook.webp"] {
    width: calc(var(--footer-logo-w) * 578 / 705);
  }

  .footer__item--social img[src*="instagram.webp"] {
    width: calc(var(--footer-logo-w) * 454 / 705);
  }
}

/* ==========================================================================
   Motion preferences
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
