/* ==========================================================================
   Shift Game Jam - Abu Dhabi Gaming x Unity
   17-18 October 2026, with November to follow

   Layout utilities come from Tailwind (CDN, configured in index.html).
   This file owns: design tokens, the hero, and every bespoke component.

   HOUSE STYLE - printed event programme, not a SaaS landing page:
     · dark ground, white type, one yellow accent, red for detail marks.
       The client's palette, supplied 2026-09-14; see the token block.
       No gradients anywhere - the one image-based ground is their own
       texture, which is not a gradient but a photographed grid.
     · square corners. No border-radius, no drop shadows, no blur.
     · structure comes from rules (hairlines and heavy bars), never boxes.
     · sections are numbered like a programme; type carries the page.

   IT WAS A CREAM-PAPER SCHEME UNTIL 2026-09-16 and every "paper", "ink" and
   "veil" in the comments below was literal then. The structure survived the
   re-skin unchanged, so those words now name roles rather than colours: the
   ground and the type. Where a measurement was redone for the dark scheme the
   comment says so and gives the new number.

   RTL: the Arabic build ships at ar/index.html. Everything here uses CSS
   logical properties (inline-start/end, padding-inline, text-align: start),
   so <html dir="rtl"> mirrors the layout with no new layout CSS. Type is
   another matter - see the html[lang="ar"] block at the foot of this file,
   and add any new tracked or reading-size component to its lists.
   ========================================================================== */

:root {
  /* ==========================================================================
     THE CLIENT'S DARK PALETTE - Website-theme/, supplied 2026-09-14, applied
     2026-09-16 on user direction. It replaces the warm-paper-and-ink scheme
     this site shipped with; that scheme is gone, not toggleable, and there is
     no light mode.

     THE TOKEN NAMES DID NOT CHANGE, and that is deliberate rather than lazy.
     --paper is the ground and --ink is the type in both schemes, whatever
     colour each happens to be, so the whole stylesheet re-skinned by
     rewriting this block. Renaming them to --bg/--fg would have touched 200
     lines for no behavioural gain and lost the diff. Read --paper as "ground"
     and --ink as "type" and nothing below surprises you.

     The client's own names for these, from `SHIFT GAME JAM WEBSITE COLORS`:

       BACKGROUND 1   #0A0E20   -> --paper      the default section ground
       BACKGROUND 2   #000711   -> --paper-2    banded sections, and the footer
       TITLE / BODY   #FEFEFF   -> --ink        headings, prose, logos
       TITLE HIGHLIGHT #E9E701  -> --accent     the accent line, buttons, links
       LITTLE DETAILS #F52823   -> --alarm      eyebrows, hairlines, TBA boxes

     Their sheet also annotates an OLDER build of this page - three jams,
     "BUILD. COLLABORATE. CREATE.", a "Choose your game jam" screen. It is a
     COLOUR spec, not a layout spec: the layout is the one the 2026-09-12
     review asked for. Do not read the mock as a request to restore any of
     that.
     ========================================================================== */

  /* --- Ground. Both are the client's, and #0A0E20 is the lighter of the two,
         which reverses the old scheme: bands used to go darker, they now go
         deeper-and-flatter. The page texture sits behind both - see
         .bg-grid. --- */
  --paper:         #0A0E20;   /* BACKGROUND 1 */
  --paper-2:       #000711;   /* BACKGROUND 2 - bands */
  --paper-foot:    #181B24;   /* Dark grey grounding for footer */

  /* --- Type. Contrast is measured against --paper, the LIGHTER ground, which
         is the worst case; everything gains a little over --paper-2 and a lot
         over the texture.

         THE MONO LABELS MOVED UP A STEP with the re-skin - spec rows, the
         tally, the countdown units and the plate captions were all --ink-3
         and are now --ink-2. On cream, --ink-3 read as a quiet label; on this
         ground the same relative step reads as switched off, and the client's
         sheet draws those labels the same white as their values. --ink-2
         keeps a step of hierarchy where they went to flat white, which is the
         one place this deviates from the mock, deliberately: the label and
         the value in identical colour lose the label/value relationship that
         the whole spec-row component is built on. --- */
  --ink:           #FEFEFF;   /* 19.1:1 - the client's TITLE/BODY white */
  --ink-2:         #B9BDC9;   /* 10.1:1 - secondary prose */
  --ink-3:         #868A96;   /*  5.5:1 - mono labels, captions */

  /* --- Accent: the client's yellow, used flat, and it is now the only thing
         links, buttons and the hero's highlight line are set in. 14.2:1 on
         --paper as text and 15.4:1 on --paper-2, so it is safe at any size,
         which the old ultramarine was not over the hero footage.

         THE ULTRAMARINE IS GONE (#1F3AB5). Worth knowing that it was the one
         sanctioned nod to the sealed theme, so losing it removes an exposure
         rather than creating one - see THEME-NOTES.private.md. Do not
         reintroduce a blue to "balance" the yellow; the client's sheet has
         three hues and this is the only one that carries interaction.

         --accent-dark is now ONLY a hover/pressed state. It used to also set
         the hero's highlight line, because the true accent could not reach
         3:1 over the footage; yellow reaches 3.33:1 at the veil this band
         carries, so that line takes the real accent now. --- */
  --accent:        #E9E701;   /* TITLE HIGHLIGHT */
  --accent-dark:   #C4C200;   /* hover / pressed only */
  --on-accent:     #000711;   /* the client's button text colour */

  /* --- The client's red. It is NOT an accent and never carries a link or a
         button. Two jobs only: the "little copy details" their sheet names -
         the mono eyebrow above a section heading - and the to-be-announced
         boxes. It was on the hairlines too until the user took it off them;
         see the note on --rule below for why that was right.

         4.6:1 on --paper and 5.0:1 on --paper-2, so it clears 4.5:1 for the
         small bold mono it is used on - but only just, on the lighter ground.
         Do not darken it, do not put it at a lighter weight, and do NOT put
         it over the hero footage: against that band's veil it measures
         1.1:1. See the hero veil note. --- */
  --alarm:         #F52823;   /* LITTLE DETAILS / LINE DETAILS */
  --on-alarm:      #000711;

  /* --- Hairlines, and they are WHITE. The client's sheet draws them in red
         and they shipped red for about an hour on 2026-09-16 before the user
         called it: "change the horizontal red lines, i suggest white".

         That is the right call and it is worth writing down why, because the
         spec sheet says otherwise and someone will come back to it. Their
         mock draws six rules on one screen, in a three-row spec block. This
         page has spec rows, a fact line, a two-day list, six full-bleed
         section edges, a tally, and a 41-row FAQ accordion - red at 1px,
         repeated forty times down a column, stops reading as furniture and
         starts reading as an error state.

         So the red is spent where it is loudest and rarest instead: the
         section eyebrows and the to-be-announced boxes. Both are in the
         client's sheet, both are type rather than structure, and there are
         never more than two on a screen.

         The two weights are the same ones the cream build used, inverted:
         0.16 for separators inside a component, 0.34 for the edge of an
         object (an image, a video, a control). --- */
  --rule:          rgba(254, 254, 255, 0.16);
  --rule-strong:   rgba(254, 254, 255, 0.34);
  --bar:           var(--ink);
  --ring:          var(--accent);

  --font-display:  Anton, "Arial Narrow", Impact, system-ui, sans-serif;
  --font-body:     Archivo, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:     "Space Mono", ui-monospace, Consolas, monospace;

  --shell:         76rem;
}

/* Own the reset. Tailwind's Preflight normally supplies box-sizing, but this
   stylesheet has to stand on its own: .shell is width:100% plus padding, so
   under content-box it overflows the viewport and clips text off the right
   edge on phones. README plans to drop the CDN before launch. */
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, h4, p, dl, dd, figure, blockquote, ol, ul { margin: 0; }
ol, ul { padding: 0; }
img { max-inline-size: 100%; }

html {
  background: var(--paper);
  color-scheme: light;
  scroll-behavior: smooth;
}

/* The nav is fixed, so anchored sections must stop clear of it. */
section[id], footer[id], div[id], span[id] { scroll-margin-block-start: 5.5rem; }

/* AND SO MUST THE FIRST SECTION ON THE PAGE, which is a different problem and
   was missing. The hero sets its own 6.25rem top padding and clears the bar on
   index.html; faq.html has no hero, so its page head is an ordinary
   .section--loose - and clamp(4rem, 7vw, 6rem) resolves to 4rem (64px) on a
   phone, against a bar that is 44px of toggle plus its padding, about 70px.
   The FAQ's eyebrow printed underneath it at every mobile width. It read fine
   on a desktop, where 7vw clears the bar on its own, which is why it survived.

   6.25rem is the hero's own figure, so both pages now open the same distance
   below the bar. It is a floor, not a replacement: a section that already asks
   for more keeps it.

   Scoped to main's first section rather than put on .section--loose, because
   the same class is used mid-page in faq.html where there is no bar to
   clear. */
main > .section:first-of-type { padding-block-start: max(6.25rem, clamp(3rem, 5vw, 4.5rem)); }
main > .section--loose:first-of-type { padding-block-start: max(6.25rem, clamp(4rem, 7vw, 6rem)); }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.02rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  position: relative;
}

/* THE PAGE GROUND IS THE CLIENT'S TEXTURE - assets/bg-grid.webp, from
   Website-theme/, applied 2026-09-16 on user direction ("background").

   It replaces the inline-SVG paper tooth that used to sit here: one
   turbulence at 0.3 with mix-blend-mode: multiply, which existed to stop a
   flat cream reading as screen-default. Multiplying near-black by noise does
   nothing you can see, so that layer was pure cost on this scheme.

   FIXED, NOT SCROLLING, and that is what the element is for. The texture is a
   single 1920x1080 frame with one soft glow in it - tile it and the glow
   repeats in a visible band; stretch it down a 6000px page and the grid turns
   to mush. Pinned to the viewport it is sized once, the grid stays at its
   native pitch, and the content scrolls over it. A `background-attachment:
   fixed` on <body> would do the same thing on paper but is the one value iOS
   Safari has never handled - it silently falls back to scroll and sizes to
   the document, which is exactly the stretch this avoids.

   z-index: 0 puts it under the section grounds, which is why the non-banded
   sections below are TRANSPARENT rather than painted: they are how it shows
   through. .shell and .foot are z-index 2 and sit above it either way.

   WEBP, AND THAT IS MEASURED. The source is a 2.0 MB PNG of a 1px dot grid
   over near-black. JPEG at a comparable size smears the grid - visible at
   16x amplification, invisible at 1x, but it is the only detail the file
   carries. WebP at quality 82 is 95 KB and holds the pitch. Re-encode from
   the PNG in Website-theme/, never from this file.

   It is decorative and behind everything, so it gets no alt route and no
   reduced-motion case - it does not move. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--paper-2) url("../bg-grid.webp") center / cover no-repeat;
}

h1, h2, h3, h4, .display {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 0.94;
  letter-spacing: 0.002em;
  text-transform: uppercase;
  text-wrap: balance;
}

/* Sub-headings inside prose stay in the body face - Anton at small sizes is a
   shout, not a heading. */
h3.sub, .sub {
  font-family: var(--font-body);
  font-weight: 700;
  text-transform: none;
  letter-spacing: -0.005em;
  line-height: 1.3;
  font-size: 1.08rem;
}

p { text-wrap: pretty; }

::selection { background: var(--accent); color: var(--on-accent); }

:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 3px;
}

/* Kills the ~300ms double-tap-to-zoom wait on tappable controls, so a tap on
   the nav toggle or an FAQ row registers immediately. Scoped to the controls
   themselves, so pinch-zoom on the page is untouched - which matters here,
   because the rules and the FAQ are dense reading and the mono labels sit at
   0.68rem. */
:where(a, button, summary, [tabindex]) { touch-action: manipulation; }

/* --- Shell -------------------------------------------------------------- */
.shell {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  /* Published as a variable so a full-bleed child inside the shell - the
     photo rail in #last - can pull back out to the viewport edge and pad the
     same amount back in, without the number being written twice. */
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  padding-inline: var(--gutter);
}

/* Sections do not share one rhythm. Uniform vertical padding down a whole
   page is itself a tell. */
.section        { padding-block: clamp(3rem, 5vw, 4.5rem); position: relative; z-index: 2; }
.section--tight { padding-block: clamp(2.25rem, 3.5vw, 3rem); }
.section--loose { padding-block: clamp(4rem, 7vw, 6rem); }
/* Section grounds use the client's palette: --paper (#0A0E20, BACKGROUND 1)
   and --paper-2 (#000711, BACKGROUND 2). The header texture is kept in .hero
   where the client spec requested "BACKGROUND HEADER TEXTURE ATTACHED", while
   content sections have solid grounds so text and FAQ answers are clean,
   high-contrast, and comfortable to read without dot-grid interference. */
.section         { background: var(--paper); }
.section--band   { background: rgba(0, 7, 17, 0.88); }
.section--edge  { border-block-start: 1px solid var(--rule); }

/* ==========================================================================
   SECTION HEAD - the headline alone, with an optional lede beneath. No label
   and no bar above it: the full-bleed .section--edge hairline is what
   separates one section from the next.
   ========================================================================== */
/* h1 as well as h2: faq.html is a page in its own right, so its title is the
   document heading rather than a section heading, and it must not come out a
   different size from the section headings under it. */
.head :is(h1, h2) { font-size: clamp(2rem, 5.2vw, 3.4rem); }
.head__lede {
  margin-block-start: 1.15rem;
  max-inline-size: 60ch;
  color: var(--ink-2);
  font-size: 1.06rem;
}

/* --- Section eyebrow ----------------------------------------------------
   A mono label above a heading, in the same family as .specs dt
   and .day__head - the page's one label voice. It exists for #last, where
   client copy v2 replaced a heading that dated the section ("The last one we
   ran") with one that does not ("See what students created"). The year has to
   survive that, and the hero has nothing to lend it: its headline is bare type
   on the veil and its lede's translucent card is sized for three paragraphs.
   IT IS THE CLIENT'S RED since 2026-09-16. Their sheet marks this exact slot
   "LITTLE COPY DETAILS #F52823" - the mono label above a section heading is
   the one piece of type they put in red, and it is the reason the red is in
   the palette at all. It measures 4.6:1 on --paper and 5.0:1 on --paper-2, so
   it clears 4.5:1 for small bold mono on both grounds, but only just on the
   lighter one: do not lower the weight, do not shrink it, and do not put it
   on any ground lighter than --paper.

   THE HERO'S KICKER IS THE EXCEPTION and stays white - over the hero footage
   this red measures 1.1:1 and no amount of veil fixes it, because the red is
   darker than the veil needs the ground to be. See the hero veil note.

   It exists for #last, where client copy v2 replaced a heading that dated the
   section ("The last one we ran") with one that does not ("See what students
   created"). The year has to survive that.                                  */
.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--alarm);
  margin-block-end: 0.9rem;
}

/* ==========================================================================
   HERO - type-led, on paper, with the games reel showing through it.

   The hairline drafting grid that used to sit here is gone (client direction
   2026-09-08): with the footage visible, the grid read as clutter ruled over a
   picture. What replaces it is nothing - the reel is the ground now.

   The band stays paper with ink type. That is the constraint the treatment
   below is built around, and it is a hard one: dark type over a picture means
   the picture has to stay light everywhere the type runs, so the reel is
   graded with its blacks lifted rather than washed out with a thick veil.
   Lifting in the encode buys visibility; veiling would only have bought
   contrast. See README.md -> The hero loop.
   ========================================================================== */
.hero {
  position: relative;
  isolation: isolate;
  /* TRANSPARENT, so the page texture behind it is the hero's ground where the
     footage has been masked out - see body::before. The client's sheet
     annotates this band "BACKGROUND HEADER TEXTURE ATTACHED", and this is how
     it gets attached: the video sits on the texture, not on a flat colour. */
  background: transparent;
  /* Sized by its content, not the viewport: on a laptop this lands just under
     one screen, so nothing is cut and no dead band opens under the CTAs. */
  padding-block: 6.25rem 2rem;
  overflow: hidden;
}

.hero > .shell { position: relative; z-index: 2; }

/* --- The reel layer ------------------------------------------------------
   Deliberate exceptions to the house style, both contained here and both
   recorded in DESIGN.md: the only blur on the page, and the only gradient.

   The blur is soft focus, not obliteration - client direction is that the
   footage should read as gameplay. It has to stop short of legible, though:
   this is the January 2026 jam's games playing uncaptioned behind copy about
   2026, and nothing in frame may be identifiable as a particular game or carry
   readable on-screen text. 768px wide, baked sigma 2, plus the 5px pass below
   is where that line sits. See CONTENT.md -> The games reel.

   The picture is at full strength behind the headline, which is 48px+ Anton
   and needs only 3:1, and is masked away before the fact strip so the small
   label type at the foot of the band sits on clean paper.                    */
.hero__reel {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 84%);
}

.hero__reel video {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Only the upscale is hidden here. The grade is baked into the encode: the
     footage's blacks are lifted to 46% and it is desaturated and warmed to the
     paper, which is what lets it sit under ink type at all - untouched, a
     night frame would leave the lede at 2:1, and a cold grey frame would turn
     the paper ground grey for a second or two. */
  filter: blur(2.5px);
  transform: scale(1.06);
}

/* THE VEIL, AND IT IS INVERTED. This band carries text over moving footage,
   so something has to sit between them. On the cream scheme that was a PAPER
   veil lifting dark frames so BLACK type could read over them, tuned against
   the darkest pixel in the clip. The dark scheme reverses every term: the type
   is white and yellow, so the veil is the client's #000711 and the binding
   constraint is the BRIGHTEST pixel, not the darkest.

   RE-MEASURED 2026-09-16, and this is the measurement. Across all 383 frames
   of hero-loop.mp4 the brightest luma is Y=227 of 255 (ffmpeg signalstats,
   YMAX), and one frame averages Y=190 - it is a bright clip, which is why the
   old scheme got away with a thin veil and this one cannot. Against sRGB 0.890
   the requirements are:

     white --ink, prose, 4.5:1        -> needs 0.478
     white --ink, display, 3:1        -> needs 0.344
     yellow --accent, display, 3:1    -> needs 0.442
     red --alarm, anything            -> UNREACHABLE

   SO IT IS FLAT 0.52, with a little headroom over the 0.478 the strictest
   line needs. What that buys, measured at the flat value:

     .hero__kicker   small, white                       4.9:1
     .hero__title    display, white                      4.9:1
     .hero__slogan   24px bold, white                    4.9:1
     .hero__slogan span  24px bold, yellow               3.6:1
     .hero__lede     small, white                        4.9:1
     .hero__meta     small, white                        4.9:1

   NO RED MAY GO IN THIS BAND. --alarm measures 1.1:1 against this veil and
   cannot be fixed by thickening it: the red is darker than the veil needs the
   ground to be, so more veil moves them closer together, not further apart.
   That is why .hero__kicker is white here while every other eyebrow on the
   site is red. It is the one place the client's palette cannot be applied as
   drawn, and it is a measurement, not a preference.

   FLAT, NOT RAMPED, for the same reason it was flat before: prose sits beside
   the headline above 62rem rather than below it, so display type and reading
   type share the same horizontal strip and the stricter of the two wins
   everywhere. Flat also means no breakpoint - the ramp needed one, because
   the accent line rode up the band as the viewport narrowed.

   The bottom stops still ramp toward opaque, and they are unchanged in shape
   from the cream build: that half of the band carries the CTA and the
   countdown as the mask fades the footage out.

   Re-measure after any re-encode of the loop; the sweep is in README.md ->
   The hero loop. If the clip is ever regraded darker, this veil can come down
   and the footage comes back for free. */
.hero__reel::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(0, 7, 17, 0.52) 0%,
      rgba(0, 7, 17, 0.52) 55%,
      rgba(0, 7, 17, 0.70) 74%,
      rgba(0, 7, 17, 0.94) 100%);
}

/* Reduced motion: site.js removes the <video> outright, so the poster frame
   would go with it. The still is restored here as a background instead - same
   picture, nothing moving, no 1 MB fetch. */
@media (prefers-reduced-motion: reduce) {
  .hero__reel {
    background: url("../video/hero-loop-poster.jpg") center / cover no-repeat;
  }
}

/* --- Type over the picture ----------------------------------------------
   Two components move a step darker inside the hero, and only inside it.
   --ink-2 is 7:1 on flat paper but only 2.6:1 over the reel's darkest frames,
   so the lede takes full --ink even though it sits on the intro card; the
   countdown labels take --ink-2 rather than --ink-3 for the same reason, and
   they have no card. Everything below the mask - the fact strip, the CTAs - is
   on clean paper and keeps the page's normal values.                        */
.hero__lede           { color: var(--ink); }
.hero .countdown__lbl { color: var(--ink-2); }

/* --- Stacked hero is a centred hero -------------------------------------
   Below the two-column breakpoint the lockup, the copy and the CTA sit in one
   narrow column, and left-aligning a 290px plaque above a full-width
   paragraph leaves the mark looking dropped in rather than placed. Centred,
   the column reads as one block. The fact strip goes with it: half-centred,
   half-flush is the version that looks like a mistake.

   Everything here is text-align and justification only - no logical property
   is flipped, so the Arabic build centres identically.                      */
@media (max-width: 47.99rem) {
  .hero { text-align: center; }
  .hero .countdown__units { justify-content: center; }
  .hero__meta { justify-content: center; }
  .hero__lede { margin-inline: auto; }
}

/* --- The lockup in #register --------------------------------------------
   It moved out of the hero on 2026-09-09 and sits at the top of #register's
   right-hand column, above the registration block. It was briefly beside the
   section heading instead, which read badly: the plaque is portrait, and a
   tall block next to one line of display type looked wedged in rather than
   placed. This column's top was empty - the heading and lede above it run
   full width, so the column starts level with the spec list - and a vertical
   mark belongs in a vertical space.

   The ceiling is the asset, not the design: the file is 515px wide, so past
   16rem its hard edges soften on a 2x screen, and the clamp stops below that.
   Raise it when a vector or a larger export arrives - see index.html.       */
.reg__mark {
  display: block;
  inline-size: clamp(8rem, 17vw, 13.5rem);
  block-size: auto;
  margin-block-end: clamp(1.75rem, 4vw, 2.75rem);
}

/* --- The kicker chip ----------------------------------------------------
   Restored on 2026-09-09 with the old hero. Mono caps on an opaque
   --paper-2 ground, which is the point: it sits in the top fifth of the band
   over footage running at close to full strength, and on its own ground it is
   14.63:1 whatever the frame behind it does. It also carries the event name,
   which is what keeps the client's "the opening should be a game jam name"
   note satisfied now that the headline is a strapline again - see
   index.html.                                                               */
.hero__kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper-2);
  border: 1px solid var(--rule-strong);
  padding: 0.4rem 0.75rem;
}

/* --- The headline is the event name -------------------------------------
   Review 2026-09-12: "Make Shift Game Jam the primary H1." Three lines of
   strapline became one line of name, and the strapline moved down to
   .hero__slogan below.

   IT STILL WRAPS TO TWO LINES ON A NARROW PHONE, and that is fine - "Shift
   Game Jam" breaking after "Shift" is a wrap, not a broken headline, which is
   what the three-clause version could never risk. text-wrap: balance from the
   h1 base rule now does something here for the first time, because there are
   no explicit <br>s left to override it.

   The size ceiling came down from 9rem to 7rem. Two words of Anton at 9rem
   overran 1584px, and the old value was set for three short clauses stacked,
   not one long phrase on one line.                                          */
.hero__title {
  font-size: clamp(2.9rem, 9vw, 7rem);
  margin-block: 1.25rem 0;
  letter-spacing: -0.005em;
}

/* --- The strapline, demoted ---------------------------------------------
   Copy v2's "Build a game. Test your skills. Learn by doing." It was the h1
   until 2026-09-16; the review asked for the event name in its place and
   offered "move the current slogan to supporting text or remove it". The
   user chose to move it, and set it as uppercase Anton at weight 400.

   THE 1.5rem FLOOR IS A CONTRAST FLOOR, NOT A TASTE ONE. Do not lower it.

   The accent run in here is --accent over the hero veil, which measures
   3.74:1. That clears 3:1 but not 4.5:1, so this line is only legal while it
   counts as WCAG "large text" - and for a weight-400 face that means 24px,
   full stop. The 18.66px shortcut needs a bold face and Anton ships one
   weight.

   It shipped at a 1.35rem (21.6px) floor for about an hour, which put every
   viewport below ~1091px at 21.6-22.5px: not large text, so the yellow half
   of the line owed 4.5:1 and delivered 3.74:1. It failed on every phone and
   tablet and passed on a desktop, which is the worst way for this to be
   wrong. Measured at 360/390/430/768/1024/1091/1200/1440/1600.

   1.5rem is the smallest floor that is correct. The 1.75rem ceiling and the
   2.2vw ramp are untouched; on a phone the change is 2.4px.

   If this line ever has to be smaller, the accent span goes back to --ink -
   white is 4.87:1 here and legal at any size. The other lever is taking the
   veil to 0.546, and that is 45% of the footage for one line. See the veil
   note above.                                                               */
.hero__slogan {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.2vw, 1.75rem);   /* 1.5rem floor: see above */
  font-weight: 400;
  text-transform: uppercase;
  line-height: 1.15;
  letter-spacing: 0.02em;
  color: var(--ink);
  margin-block-start: 0.8rem;
  text-wrap: balance;
}
/* --accent, NOT --accent-dark, and that is the reverse of what this rule said
   on the cream scheme. There, the true accent could not reach 3:1 over the
   footage and --accent-dark was the compromise that could. On the dark scheme
   --accent-dark is a HOVER state - a deeper yellow - and deeper is exactly
   wrong over a veil this reader has to see through: it measures 2.7:1 against
   the brightest frame where the real accent measures 3.72:1. The rule kept
   the old token through the re-skin and was caught by measuring, not by eye.
   24px bold is large text, so 3:1 is the floor and the real accent clears it. */
.hero__slogan span {
  display: inline-block;
  color: var(--accent);
}


/* NO COLOUR HERE. It is set in the "type over the picture" block above, and
   this rule used to re-declare --ink-2 at equal specificity 200 lines further
   down the file - so it won on source order, and the lede rendered a step
   light over the footage while the comment up there said it took full --ink.
   That is not a cosmetic difference: --ink-2 over this band is 2.2:1 where
   --ink is 5.07:1. Do not put a colour back in here.

   No card either, since the old hero came back on 2026-09-09 - the veil
   carries the prose. See the veil note above.                               */
.hero__lede {
  max-inline-size: min(46ch, 100%);
  font-size: clamp(1.05rem, 2vw, 1.22rem);
  margin-block-start: clamp(1.35rem, 2.6vw, 1.9rem);
}
/* One paragraph since the 2026-09-12 review, but the rule stays: the Arabic
   build and any future copy may run to two, and a second paragraph with no
   space above it is worse than an unused selector. Tighter than body prose -
   a lede is one continuous introduction, not a list of points. */
.hero__lede > p + p { margin-block-start: 0.85rem; }

/* --- Headline and lede side by side ------------------------------------
   User direction 2026-09-09: put the reading copy beside the headline on a
   desktop and stop wasting the width. Below 62rem this is a plain block and
   the two stack in source order, which is the order they read in.

   IT IS FLEX, NOT GRID, AND THE LEFT COLUMN SIZES ITSELF. Since 2026-09-16
   that column is .hero__head - the name and the strapline under it - not the
   headline alone. It is flex-shrink: 0 around a nowrap headline, so it takes
   exactly the width "Shift Game Jam" needs at the current size and the name
   never breaks across two lines on a desktop. The lede is flex: 0 1 44ch, so
   it takes the rest and yields when space runs short. That division of labour
   is deliberate: a wrapped event name is a broken headline, a slightly
   narrower measure is not.

   THE STRAPLINE IS ALLOWED TO WRAP inside that column and usually does -
   it is the longer of the two lines at every size. max-inline-size holds it
   to roughly the name's own width so the block stays a block rather than
   pushing the lede off the row.

   The row got much easier when the headline went from three clauses to two
   words: the old measurements (573px of headline at 1584px, down to 359px at
   992px, against a fixed 492px lede) were what made this tight. "Shift Game
   Jam" at the same sizes is narrower than any of them, in both builds -
   Arabic runs 0.86 of the size through its own override in the ARABIC BUILD
   block and its 44ch of Cairo is the wider measure, ~548px preferred. Checked
   at 992, 1004, 1024, 1104, 1264, 1424 and 1584 in both builds, no overflow.

   WHY 62rem. Below it the left column cannot carry display type any larger
   than the stacked headline already gets, so two columns stop being a gain.
   Be aware the size steps DOWN at the breakpoint - 108px stacked at 984px
   against 55px in two columns at 1008px - because the stacked headline gets
   the full width and 11vw. That discontinuity is the cost of the layout and
   it was accepted knowingly; raising the breakpoint trades desktop density
   for it.

   align-items: center, not start: the lede is much the taller of the two
   (~150 words against three lines), so anchoring the headline to the top of
   a column half again its height left it stranded. Centred, the two read as
   one band.

   Logical properties and plain flex, so the Arabic build mirrors this and
   puts the headline on the right without a second rule.                     */
@media (min-width: 62rem) {
  .hero__cols {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: clamp(2rem, 3.5vw, 3.5rem);
    margin-block-start: clamp(1.5rem, 2.5vw, 2.25rem);
  }

  /* The column, not the headline, is the flex child now - see the note
     above.

     inline-size: min-content IS DOING SOMETHING SPECIFIC and a fixed width
     here is wrong. A flex item defaults to max-content, which for this column
     means the STRAPLINE on one unbroken line - far wider than the name above
     it, and it pushed the lede off the row. Capping it instead (it was
     15ch for an afternoon) is worse: the headline is white-space: nowrap, so
     it simply overflowed the cap and printed straight through the lede.

     min-content resolves to the largest min-content of the children. The
     nowrap headline's min-content is its full width; the strapline's is its
     longest word. So the column is exactly as wide as "Shift Game Jam" needs,
     and the strapline wraps inside it. Change either and the column follows
     on its own. */
  .hero__head {
    flex-shrink: 0;
    inline-size: min-content;
  }

  .hero__title {
    font-size: clamp(3.2rem, 5.5vw, 6.25rem);
    white-space: nowrap;
    margin-block: 0;
    line-height: 0.92;
  }

  /* Sits snug beside the headline; measure capped at 44ch for optimal reading */
  .hero__lede {
    flex: 0 1 44ch;
    margin-block-start: 0;
  }
}

/* --- Hero fact line ------------------------------------------------------
   Review 2026-09-12: "Place the date, venue, free entry and registration
   button immediately after the description." Three facts on one wrapping
   line, where a four-cell <dl class="facts"> grid used to be - that grid, and
   its mono labels, are gone from both builds. The labels went with it on
   purpose: DATES / LOCATION / ENTRY above "17-18 October 2026" told the
   reader nothing the values did not.

   EACH FACT IS ONE <span>, AND THE SEPARATORS ARE GENERATED. That is not
   tidiness: the first version had the dots as their own elements between the
   facts, and at 390px the row wrapped with a dot stranded at the end of the
   first line, pointing at nothing. As ::before on every span but the first,
   a separator can only ever appear between two facts on the same line - when
   a fact wraps, its dot goes with it to the start of the next line, which is
   why the whole row stacks below 48rem instead (see the media query). The
   dates were also two <time> elements with the flex gap between them, which
   put 11px of air either side of the en dash; they are one span now.

   Generated content is not read by a screen reader here (no content-visibility
   or alt semantics needed) - the facts read as three phrases, which is what
   they are.

   The rule below closes it the way the old strip's rule did. Nothing here
   sits over the veil's strong half - see the veil note - so these take the
   page's normal ink values.                                                 */
.hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.7rem;
  font-weight: 600;
  font-size: clamp(1rem, 1.7vw, 1.1rem);
  color: var(--ink);
  border-block-end: 1px solid var(--rule-strong);
  padding-block-end: 1.1rem;
  margin-block-start: clamp(1.4rem, 2.6vw, 2rem);
}
.hero__meta > span + span::before {
  content: "\00b7";  /* middle dot, escaped: this file has no @charset */
  /* Not --ink-3: these sit at ~50% of the band, over the footage at full
     strength, where --ink-3 measures 1.42:1 and simply is not there. */
  color: var(--ink);
  margin-inline-end: 0.7rem;
}

/* STACKED, AND WITHOUT SEPARATORS, ON A PHONE. This is the same breakpoint
   that centres the hero, and it is the honest answer to the wrapping problem
   above: three short centred lines read as a list, where a two-line row with
   a dot hanging off the end of the first reads as a mistake. It costs about
   40px of height, which the shortened hero can afford - the CTA still clears
   a 390x844 viewport with room. */
@media (max-width: 47.99rem) {
  .hero__meta {
    flex-direction: column;
    /* align-items on the base rule is baseline, which in a column means the
       INLINE axis - it would shrink-wrap each fact to the start while the
       rest of the hero is centred. */
    align-items: center;
    gap: 0.45rem;
  }
  .hero__meta > span + span::before { content: none; }
}

/* The review's reassurance line. It was --ink-2 - a step down from the facts
   above it, because it answers a worry rather than being a fact to scan - and
   on the dark scheme it cannot be.

   MEASURED: this line sits at 57-69% of the hero band, where the reel's mask
   is still 71-91% opaque, so it is over footage at close to full strength.
   --ink-2 against the veil's worst case is 2.62:1. It takes full --ink at
   4.87:1 instead and loses the step down; the smaller size is what carries
   the hierarchy now. Everything below it - the countdown head, the start time
   - IS past the mask fade and keeps --ink-2 at 10.2:1 on clean ground. */
.hero__reassure {
  color: var(--ink);
  font-size: 0.97rem;
  margin-block-start: 0.9rem;
}

/* ==========================================================================
   BUTTONS - square, flat, no lift.
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.9rem 1.5rem;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
  transition: background-color 140ms linear, border-color 140ms linear, color 140ms linear;
  min-block-size: 2.75rem;   /* 44px touch target */
}
.btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }

/* THE CLIENT'S "BUTTON STYLE 2": transparent box, accent outline, accent
   text - their sheet gives all three values. It used to be a white-ish
   hairline with ink text, which on this ground read as a disabled control
   sitting next to .btn--off. Hover fills to the accent and the label flips to
   the dark, so the pair reads as one family with .btn rather than two. */
.btn--ghost {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}
.btn--ghost:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.btn--lg { padding: 1.05rem 1.9rem; font-size: 0.88rem; }
.btn svg { flex: none; }

/* --- Registration not open yet -------------------------------------------
   Client direction 2026-09-07: only the jam currently taking entries gets a
   live registration link, so nobody signs up for all three at once. November
   and December carry this instead.

   Applied to a <span>, never a disabled <a>: there is no destination to
   disable, and a span is not focusable, so it cannot become a tab stop that
   goes nowhere. It keeps the .btn box, which is the point - the three jam
   columns still line up, and the difference between "go" and "not yet" is
   carried by weight rather than by a missing element.

   --ink-3 on --paper is 5.2:1; #jams sits on --paper, so this is the value
   that applies. Do not reuse it on a .section--band, where it drops to
   4.6:1 and this type is too small for that. */
.btn--off {
  background: transparent;
  color: var(--ink-3);
  border-color: var(--rule);
  cursor: default;
}
.btn--off:hover {
  background: transparent;
  border-color: var(--rule);
  color: var(--ink-3);
}

/* Arrow glyphs point along the reading direction, so flip them under RTL. */
[dir="rtl"] .btn .glyph-arrow { scale: -1 1; }

/* ==========================================================================
   COUNTDOWN - a scoreboard line, not four glass tiles.
   ========================================================================== */
/* --- Countdown ----------------------------------------------------------
   LABELLED AND BELOW THE BUTTON since 2026-09-16. Review 2026-09-12: "Reduce
   or remove the seconds-based countdown. If you keep it, label it 'Event
   starts in'; right now it's confusing, as it implies a registration
   deadline." Four bare numbers under a hero read as a deadline; the heading
   is what says which. The seconds cell is gone from the markup, which is also
   why the numbers can now be larger - three cells have the width four had.

   .countdown__head and .countdown__when are both hidden by site.js when the
   timer is replaced by its live/over message. See the note there.           */
.countdown { margin-block-start: 1.9rem; }
.countdown__head {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-block-end: 0.7rem;
}
/* The timer's target in words - the only text source for the start time now
   that the fact line carries dates rather than the kickoff hour. It must not
   be removed without putting the time back in .hero__meta. */
.countdown__when {
  color: var(--ink-2);
  font-size: 0.92rem;
  margin-block-start: 0.7rem;
}
.countdown__units {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem clamp(0.85rem, 3.5vw, 1.6rem);
}
.countdown__unit { display: flex; align-items: baseline; gap: 0.4rem; }
.countdown__num {
  font-family: var(--font-display);
  /* Small floor so all three units hold one line on a 390px phone. The
     ceiling rose with the seconds cell's removal. */
  font-size: clamp(2.1rem, 8vw, 4rem);
  line-height: 0.9;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.countdown__lbl {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.countdown__live {
  font-family: var(--font-display);
  font-size: 1.5rem;
  text-transform: uppercase;
  color: var(--accent);
}

/* --- "To be announced" marker ------------------------------------------- */
.tba {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* THE CLIENT'S "BOX STYLE 1" - red box, dark text. It was the accent
     yellow for an hour during the re-skin, which put a to-be-announced
     placeholder in the same colour as the register button: the loudest thing
     on the screen saying "we do not know yet". Red is what their sheet draws
     it in, and it is the right register for it. */
  color: var(--on-alarm);
  background: var(--alarm);
  padding: 0.24rem 0.6rem;
}

/* --- Spec list: label/value pairs, no box ------------------------------- */
.specs { display: grid; gap: 0; }
.specs > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1.5rem;
  border-block-start: 1px solid var(--rule);
  padding-block: 0.85rem;
}
.specs > div:first-child { border-block-start: 0; padding-block-start: 0; }
.specs dt {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
/* THE AUTO MARGIN IS WHY THIS IS NOT justify-content: space-between, which is
   what it used to be. space-between only separates items that share a line.
   These rows wrap (some values are long, and the Arabic build sets them a
   size up), and a value that wraps onto a line of its own is then the only
   item on that line - where space-between puts it at the START, i.e. hard
   against the label side, while every value that did NOT wrap sits at the
   end. The Arabic register list showed it plainly: "اليوم الأول" wrapped and
   its value jumped to the right edge while the rest of the column stayed
   left. Reported 2026-09-09.

   margin-inline-start: auto pushes the value to the end of its line whether
   it shares one with the label or not, so the value column stays a column in
   both builds and at any content length. Logical, so RTL mirrors it.       */
.specs dd {
  font-weight: 600;
  margin-inline-start: auto;
}

/* ==========================================================================
   JAMS - the three events of the series, in #jams and again in #details.

   The client sitemap calls these "three selectable event cards". They are not
   cards: the house style has no boxes, no radius and no shadow anywhere, and
   what the brief actually needs is three units a reader can tell apart and
   choose between. One heavy 2px rule per event does that - the same weight
   .day__head and .panel__head use - so the three read as three columns of a
   programme rather than three tiles. See DESIGN.md -> House style.

   Everything inside reuses existing components (.specs, .tba, .btn), which is
   also what makes the RTL mirror and the Arabic type sizing free.
   ========================================================================== */
.jams {
  margin-block-start: 2.5rem;
  display: grid;
  gap: clamp(1.9rem, 4vw, 2.75rem);
  grid-template-columns: 1fr;
}
@media (min-width: 52rem) { .jams { grid-template-columns: repeat(3, 1fr); } }

.jam {
  border-block-start: 2px solid var(--bar);
  padding-block-start: 1.1rem;
  /* Column flex so the CTA can be pushed to the foot of the block: the three
     descriptions run to different lengths and the buttons should still line
     up across the row. Items stretch by default; only the button opts out,
     so paragraphs and spec lists keep the full column width. */
  display: flex;
  flex-direction: column;
}
.jam__month {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.jam__name { font-size: 1.5rem; margin-block-start: 0.55rem; }
/* Named rather than matched as `.jam > p`: a descendant-plus-type selector
   outranks a single class, so `.jam > p` also captured .jam__month above and
   set the mono label at body size. */
.jam__desc {
  color: var(--ink-2);
  font-size: 0.98rem;
  margin-block-start: 0.75rem;
}
.jam .specs { margin-block: 1.35rem 1.4rem; }
/* auto pushes the button to the foot of the column so the three line up; the
   gap above it comes from the spec list's own bottom margin. align-self stops
   it stretching to the full column width. */
.jam .btn {
  margin-block-start: auto;
  align-self: flex-start;
}

/* --- Plain marked list -------------------------------------------------- */
.marks { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.55rem; }
.marks li {
  position: relative;
  padding-inline-start: 1.05rem;
  color: var(--ink-2);
}
.marks li::before {
  content: "•";
  position: absolute;
  inset-inline-start: 0;
  color: var(--accent);
  font-weight: 700;
}

/* ==========================================================================
   SCHEDULE - milestone rail. Deliberately no clock times: only the 9:00 AM
   kickoff is confirmed, so nothing else is invented.
   ========================================================================== */

/* --- The two-day summary ------------------------------------------------
   The review's own two lines, which replaced four paragraphs of per-day
   prose on 2026-09-12. Two rows rather than two columns: they are a sequence,
   and a sequence read left to right on a desktop and top to bottom on a
   phone changes shape halfway down the page for no gain.                    */
.days {
  display: grid;
  gap: 0;
  max-inline-size: 52rem;
}
.days > div {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: 0.6rem 1.25rem;
  border-block-start: 1px solid var(--rule);
  padding-block: 1.1rem;
}
.days > div:last-child { border-block-end: 1px solid var(--rule); }
.days dt {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.days dd { font-size: 1.05rem; color: var(--ink); }
/* Below 34rem the label and the sentence stop fitting on one row. */
@media (max-width: 33.99rem) {
  .days > div { grid-template-columns: 1fr; }
}

/* --- "View the full schedule" -------------------------------------------
   Review 2026-09-12: "Put the detailed schedule behind 'View the full
   schedule' or below the short summary." Native <details>, matching the FAQ:
   it opens with JavaScript off, it is in the DOM for find-in-page, and the
   browser owns the keyboard and the ARIA. Do not replace it with a scripted
   toggle.                                                                   */
.schedule > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  padding: 0.8rem 1.25rem;
}
.schedule > summary::-webkit-details-marker { display: none; }
.schedule > summary:hover { color: var(--accent); border-color: var(--accent); }
.schedule > summary .chev { transition: transform 0.18s ease; flex-shrink: 0; }
.schedule[open] > summary .chev { transform: rotate(180deg); color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .schedule > summary .chev { transition: none; }
}

.day__head {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  border-block-end: 2px solid var(--bar);
  padding-block-end: 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.day__head b { color: var(--accent); }
.day__head span { color: var(--ink-3); }

.rail { list-style: none; padding: 0; margin-block-start: 1.35rem; }
.rail li {
  position: relative;
  padding-inline-start: 1.5rem;
  padding-block-end: 1.15rem;
}
.rail li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.52rem;
  inline-size: 0.5rem;
  block-size: 0.5rem;
  background: var(--rule-strong);
}
.rail li[data-mark="start"]::before,
.rail li[data-mark="end"]::before { background: var(--accent); }
.rail li:last-child { padding-block-end: 0; }
.rail strong { font-weight: 700; display: block; }
.rail span { color: var(--ink-2); font-size: 0.95rem; }

/* ==========================================================================
   FAQ - native <details> so it works with zero JS and is keyboard
   accessible by default. `name` makes the group mutually exclusive where
   supported, and harmlessly independent everywhere else.
   ========================================================================== */
.faq details { border-block-start: 1px solid var(--rule); }
.faq details:last-child { border-block-end: 1px solid var(--rule); }
.faq summary {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding-block: 1.15rem;
  cursor: pointer;
  font-weight: 700;
  font-size: 1.04rem;
  list-style: none;
  transition: color 140ms linear;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--accent); }
.faq summary .q {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  color: var(--accent);
  padding-block-start: 0.24rem;
  flex: none;
  /* Fixed width so Q1 and Q11 share one left edge. */
  min-inline-size: 2.4rem;
}
.faq summary .chev {
  margin-inline-start: auto;
  flex: none;
  color: var(--ink-3);
  transition: transform 180ms ease;
}
.faq details[open] summary .chev { transform: rotate(180deg); color: var(--accent); }
.faq details[open] { background: rgba(254, 254, 255, 0.02); }
.faq .answer {
  padding-inline-start: 3.4rem;
  padding-block-end: 1.3rem;
  color: var(--ink);
  line-height: 1.65;
  max-inline-size: 70ch;
}
.faq .answer ul { list-style: none; margin-block-start: 0.6rem; display: grid; gap: 0.3rem; }
.faq .answer li { padding-inline-start: 1.3rem; position: relative; }
.faq .answer li::before {
  /* A round bullet, not an em dash - user direction 2026-09-13: the
     long dash read as machine-written wherever it showed on the page. */
  content: "•";
  position: absolute;
  inset-inline-start: 0;
  color: var(--accent);
}
@media (max-width: 30rem) {
  .faq .answer { padding-inline-start: 0; }
}

/* --- Segmented FAQ: the category index -----------------------------------
   faq.html only. Six audience groups is more than fits above the fold, so the
   page opens with a jump list to them.

   Built as chips in the page's one label voice (.eyebrow, .specs dt,
   .day__head) rather than as a sidebar: a sticky rail would have to survive
   RTL, the fixed nav and a 390px phone, and this list is six items that never
   grows. The count beside each label is the honest thing to show - it is the
   reason a visitor picks one group over another.

   Not a <nav aria-label> wrapper by accident: these are in-page links to
   landmark sections, so the whole thing is announced as navigation and a
   screen reader can skip it in one move. */
.faqnav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-block-start: 1.9rem;
}
.faqnav a {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  background: rgba(254, 254, 255, 0.02);
  padding: 0.5rem 0.8rem;
  transition: color 140ms linear, border-color 140ms linear, background-color 140ms linear;
}
.faqnav a:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(233, 231, 1, 0.06);
}
.faqnav a b { color: var(--accent); font-weight: 700; }

/* --- Segmented FAQ: a group heading --------------------------------------
   Smaller than a .head h2 on purpose. Six of them down one page at the
   section-head size would each read as the start of a new page; at 1.5rem
   they read as what they are, six divisions of one list. The .eyebrow above
   carries the audience label. */
.faqgroup__title { font-size: clamp(1.5rem, 3.4vw, 2.1rem); }

/* ==========================================================================
   NAV
   ========================================================================== */
/* Always opaque. It used to fade in past 24px of scroll, which also meant the
   mobile panel opened over a transparent bar at the top of the page; a bar that
   is always painted removes that failure mode entirely. If the fade is ever
   reinstated, the panel needs its own opaque-background hook again. */
.nav {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 50;
  /* The DEEPER ground, not the section ground: this is a plate that sits over
     the page texture at every scroll position, and the flatter tone is what
     reads as a plate rather than as a gap in the texture. */
  background: var(--paper-2);
  border-block-end: 1px solid var(--rule-strong);
}
.nav__inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding-block: 0.8rem;
}
.nav__links { display: none; align-items: center; gap: 1.6rem; margin-inline-start: auto; }

/* --- The persistent Register button -------------------------------------
   Review 2026-09-12: "Keep a visible Register button in the mobile header or
   a compact sticky bottom bar; do not hide the only persistent registration
   action inside the menu." It is a sibling of both the desktop list and the
   toggle, so it survives at every width - it is NOT a member of .nav__links
   and must not be moved inside it.

   margin-inline-start: auto pushes it right when .nav__links is display:none
   (below 66rem); above that the list already claims the auto margin and this
   one collapses to nothing, so the button simply follows the links. One rule,
   both layouts, and it mirrors for Arabic on its own.                       */
.nav__cta {
  margin-inline-start: auto;
  padding: 0.6rem 1.05rem;
  min-block-size: 0;
  font-size: 0.72rem;
  white-space: nowrap;
}
/* At 66rem+ the links row owns the spacing; the button just sits after it. */
@media (min-width: 66rem) {
  .nav__cta { margin-inline-start: 1.6rem; }
}
/* The tightest case: lockup + button + burger on a phone. The tracking is
   what is actually expensive here - 0.14em across eight mono capitals is
   about 11px of pure air - so it is the first thing to go, before the
   padding. Measured at 360px, which is the narrowest phone worth serving. */
@media (max-width: 30rem) {
  .nav__cta { padding: 0.55rem 0.7rem; letter-spacing: 0.06em; font-size: 0.68rem; }
}
.nav__links a:not(.btn) {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  transition: color 140ms linear;
}
.nav__links a:not(.btn):hover { color: var(--accent); }
.nav__links .btn { padding: 0.6rem 1.1rem; min-block-size: 0; }

.lockup { display: flex; align-items: center; gap: 0.9rem; }
.lockup img { display: block; }
.lockup__rule { inline-size: 1px; block-size: 1.5rem; background: var(--rule-strong); }

/* No margin-inline-start: auto here any more - .nav__cta sits between this
   and the lockup and claims it. Two auto margins in the row would push the
   button and the burger apart with the gap between them. */
.nav__toggle {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border: 1px solid var(--rule-strong);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.nav__toggle .nav__icon--close { display: none; }
.nav__toggle[aria-expanded="true"] .nav__icon--open  { display: none; }
.nav__toggle[aria-expanded="true"] .nav__icon--close { display: block; }

.nav__panel {
  display: none;
  padding-block: 0.25rem 1.25rem;
  border-block-start: 1px solid var(--rule);
}
.nav__panel[data-open="true"] {
  display: block;
  /* Never taller than the space under the bar; Register stays reachable on
     short and landscape viewports. */
  max-block-size: calc(100svh - 4.25rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.nav__panel a:not(.btn) {
  display: block;
  padding-block: 0.7rem;
  border-block-end: 1px solid var(--rule);
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.nav__panel a:not(.btn):hover { color: var(--accent); }

/* Two logos plus a 44px target overflow below ~340px, which `overflow-x:
   hidden` on body was quietly masking. */
/* RAISED FROM 26rem TO 30rem on 2026-09-16, when the Register button became
   a permanent member of this row. The bar now has to fit a two-logo lockup,
   a button and the burger at every phone width, and it did not: at 390px the
   full-size lockup plus the button overran the viewport and gave the whole
   document a horizontal scrollbar. Measured at 360, 390, 414 and 430 with the
   button in place. If anything else joins this row, re-measure at 360. */
@media (max-width: 30rem) {
  .nav__inner { gap: 0.75rem; }
  .lockup { gap: 0.55rem; }
  .nav .lockup img { block-size: auto; }
  .nav .lockup img:first-child { inline-size: 70px; }
  .nav .lockup img:last-child  { inline-size: 84px; }
}

/* ONE MORE TIER FOR 320-360px. At 360 the row above lands within 8px of the
   viewport; at 320 it is 30px over, and because body carries
   overflow-x: hidden that overflow does not show up as a horizontal
   scrollbar - the Register button simply prints over the Unity wordmark. It
   is invisible to any scrollWidth check, so it has to be measured by eye.
   Checked at 320 and 360 with the button in place. */
@media (max-width: 22.5rem) {
  .nav__inner { gap: 0.5rem; }
  .nav .lockup img:first-child { inline-size: 58px; }
  .nav .lockup img:last-child  { inline-size: 70px; }
}

/* Raised from 60rem when Judges and the language toggle joined the bar: at
   960px the five links plus Register plus the toggle no longer clear the
   logo lockup. Below this the panel carries everything instead. */
@media (min-width: 66rem) {
  .nav__links  { display: flex; }
  .nav__toggle { display: none; }
  .nav__panel  { display: none !important; }
}

/* ==========================================================================
   MISC
   ========================================================================== */
.prose-muted { color: var(--ink-2); max-inline-size: 66ch; }
.prose-muted p + p { margin-block-start: 1.05rem; }

.hr-soft { border: 0; border-block-start: 1px solid var(--rule); }

.link-inline {
  color: var(--accent);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.link-inline:hover { color: var(--accent-dark); }

.skip {
  position: absolute;
  inset-block-start: -100%;
  inset-inline-start: 1rem;
  z-index: 100;
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.7rem 1.1rem;
  font-weight: 700;
  text-decoration: none;
}
.skip:focus { inset-block-start: 0; }

/* ==========================================================================
   FOOTER
   IT WAS THE ONE DARK BLOCK ON A CREAM PAGE, and that was its entire reason
   to exist: an inverted colophon closing a light page. On the client's dark
   scheme it has nothing to invert against, so the device is gone and the job
   has to be done another way - user direction 2026-09-16.

   What it does instead: its OWN ground, --paper-foot (#181B24), a step
   lighter than either section ground, plus a full-bleed rule across the top
   at the heaviest hairline weight. That token is the one value in the palette
   that is not on the client's sheet, and it is here because the footer has to
   read as a separate block on a page where everything else is already dark -
   the deepest ground was tried first and simply merged with the section above
   it. The rule was red for the first hour of the re-skin, alongside every
   other rule on the page; it went white with them on user direction.

   Inverting it to a white block was the other option considered. It would
   punctuate harder, but #FEFEFF with dark type is a pairing the client's
   sheet never draws, and putting the loudest value on the page in the least
   important block is backwards.

   The rgba(245, 241, 232, ...) values that used to be in here were cream at
   low alpha - dim light on a dark block, which was correct when the block was
   the exception. They are now plain tokens, because the whole page is that
   block's old ground and --ink-2 / --rule already mean "dimmer than the
   type" everywhere else. One fewer hardcoded colour.
   ========================================================================== */
.foot {
  position: relative;
  z-index: 2;
  background: var(--paper-foot);
  border-block-start: 1px solid var(--rule-strong);
  padding-block: 3rem;
}
.foot .lockup__rule { background: var(--rule-strong); }
.foot .hr-soft { border-block-start-color: var(--rule); }
.foot__dim { color: var(--ink-2); }
/* The footer's links stay white rather than going accent-yellow like every
   other .link-inline on the page. Four yellow links in a colophon pull the
   eye to the least important block on the site; the underline is what says
   they are links, and it takes the accent on hover. */
.foot .link-inline { color: var(--ink); text-decoration-color: var(--rule); }
.foot .link-inline:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* ==========================================================================
   PLATES - event photography, laid out as a programme's picture insert.
   A hairline edge and a mono caption line make each image a plate, not a
   card: nothing is enclosed, nothing lifts off the page, corners stay square.
   Photographs are the one place colour other than the accent is allowed.
   ========================================================================== */
.plates {
  margin-block-start: 2.5rem;
  display: grid;
  gap: clamp(1.4rem, 3vw, 2.25rem);
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) { .plates { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .plates { grid-template-columns: repeat(3, 1fr); } }

/* --- The gallery as a side scroller, below 62rem ------------------------
   Review 2026-09-12 section 3: "turn the image gallery into a side scroller
   with navigation buttons to shorten the landing page on mobile". Six stacked
   photographs were about four phone screens in a section whose job, now that
   it sits directly under the hero, is proof at a glance.

   PURE CSS. Overflow plus scroll-snap, so it works with a thumb, a trackpad,
   shift+wheel and the arrow keys before a line of JavaScript runs. site.js
   only reveals the two buttons. Every photograph stays in the DOM, in source
   order, for find-in-page and for a screen reader.

   THE NEGATIVE MARGINS ARE LOAD-BEARING, not decoration. The rail is inside
   .shell, which has the page's side gutter; without pulling back out to the
   viewport edge and padding the same amount back in, the first plate starts
   inset and the last one stops short, and a rail that does not touch the edge
   of the screen does not read as scrollable. scroll-padding-inline-start
   keeps snapped plates off that same gutter.

   OVERSCROLL-BEHAVIOR-INLINE: contain stops a horizontal swipe that runs out
   of rail from turning into a browser back-gesture.

   Above 62rem every one of these is unset and the grid above takes over:
   on a desktop the six plates fit, and a horizontal scroller would be the
   worse way to show them.                                                   */
@media (max-width: 61.99rem) {
  .plates--rail {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: inline mandatory;
    scroll-padding-inline-start: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-block-end: 0.5rem;
    /* The rail takes keyboard focus (tabindex="0" in the markup) because it
       is a scrollable region; give that focus a visible ring. */
    scrollbar-width: thin;
  }
  .plates--rail > .plate {
    flex: 0 0 min(78%, 20rem);
    scroll-snap-align: start;
  }
  /* Uniform height across mixed source ratios, so the rail does not jog
     vertically as it scrolls. */
  .plates--rail > .plate img { aspect-ratio: 3 / 2; }
}

/* --- Rail buttons -------------------------------------------------------
   Revealed by site.js only when the rail actually overflows, and hidden
   outright on a desktop where the plates are a grid. They are a convenience
   on top of a rail that already scrolls - see the note above.               */
.plates__nav {
  display: flex;
  gap: 0.6rem;
  margin-block-start: 1rem;
}
@media (min-width: 62rem) { .plates__nav { display: none; } }
.plates__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule-strong);
  cursor: pointer;
}
.plates__btn:hover:not(:disabled) { color: var(--paper); background: var(--ink); }
/* Disabled at the ends rather than wrapping round - see site.js. Kept
   visible, not hidden, so the row does not reflow as you scroll. */
.plates__btn:disabled { opacity: 0.35; cursor: default; }
/* RTL: the chevrons are direction-agnostic glyphs pointing left and right,
   so the Arabic build flips the pair rather than the icons. */
html[dir="rtl"] .plates__btn svg { transform: scaleX(-1); }

/* --- The games reel ------------------------------------------------------
   The client's four-minute montage of games from the previous jam. It played
   at the TOP of this section until 2026-09-08, when the reel moved to the
   hero and this player came out; it is back on 2026-09-09 at the user's
   request, and now sits BELOW the plates - which is also where the client's
   copy v2 puts its [VIDEO / LOOP] marker on screen 5.

   Two different jobs, two different encodes, and they must not be confused:
   this is reel-720p.mp4, 34 MB, with sound and controls, something a visitor
   chooses to watch. hero-loop.mp4 is 1.2 MB, silent, cropped and blurred, and
   plays itself. preload="metadata" is what keeps 34 MB off the initial load -
   do not change it to "auto".                                              */
.reel { margin: 0; margin-block-start: clamp(1.4rem, 3vw, 2.25rem); }
.reel video {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--rule-strong);
  /* NOT var(--ink). This is what shows in the box before the poster frame
     decodes, and --ink is white on this scheme - it flashed a white rectangle
     the size of a 16:9 video on a near-black page. */
  background: var(--paper-2);
}
.reel figcaption {
  margin-block-start: 0.65rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.plate { margin: 0; }
.plate img {
  display: block;
  inline-size: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--rule-strong);
  background: var(--paper-2);
}
.plate figcaption {
  margin-block-start: 0.65rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}
/* --- Tally: the previous jam's numbers, set at display size -------------- */
.tally {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(7.5rem, 100%), 1fr));
  column-gap: clamp(1rem, 3vw, 2.25rem);
  border-block-end: 1px solid var(--rule-strong);
  margin-block-start: 2rem;
}
.tally > div { padding-block: 0 1rem; padding-inline-end: 0.5rem; }
.tally dt {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.tally dd {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 0.95;
  margin-block-start: 0.4rem;
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   PEOPLE - judges and mentors. The eight portraits arrive shot in eight
   different studios, on grounds ranging from magenta gradient to paint
   splatter, and they run in full colour: these are the people, and washing
   them out to match the page costs more than the tidier grid is worth.
   Uniform square crops and one hairline edge carry the consistency instead.
   ========================================================================== */
.people {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem) clamp(1rem, 2.5vw, 1.75rem);
  grid-template-columns: repeat(2, 1fr);
  margin-block-start: 2.25rem;
}
@media (min-width: 40rem) { .people { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 64rem) { .people { grid-template-columns: repeat(5, 1fr); } }

.person { margin: 0; }
.person img {
  display: block;
  inline-size: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 1px solid var(--rule-strong);
  background: var(--paper-2);
}
.person figcaption { margin-block-start: 0.7rem; }
.person b {
  display: block;
  font-weight: 700;
  font-size: 0.96rem;
  line-height: 1.25;
}
.person span {
  display: block;
  margin-block-start: 0.28rem;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

/* --- Panel sub-heading: a heavy bar, same weight as the schedule days ---- */
.panel__head {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  border-block-end: 2px solid var(--bar);
  padding-block-end: 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.panel__head b { color: var(--accent); }
.panel__head span { color: var(--ink-3); }

/* --- Language toggle ----------------------------------------------------- */
.lang {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  padding: 0.45rem 0.7rem;
  transition: color 140ms linear, border-color 140ms linear;
}
.lang:hover { color: var(--accent); border-color: var(--accent); }
/* In the mobile panel it sits in the link stack, so it drops the border and
   picks up the same row rhythm as its neighbours. */
.nav__panel .lang {
  display: block;
  border: 0;
  border-block-end: 1px solid var(--rule);
  padding: 0.7rem 0;
}

/* ==========================================================================
   REDUCED MOTION - nothing on this page animates decoratively any more, so
   there is little left to switch off. Kept for scroll jumps and hovers.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* PRINT FORCES A LIGHT SCHEME, and on the dark palette that is no longer a
   two-line override. Nobody should print 30 pages of near-black, and a reader
   who prints the FAQ for the day needs it legible on white - so the tokens
   themselves are redefined for print rather than patching the components that
   use them. Everything downstream follows for free.

   THE ACCENT AND THE RED BOTH HAVE TO MOVE. Yellow #E9E701 on white is 1.4:1
   and effectively invisible on paper; the red is 4.2:1 on white, which passes
   but prints muddy on a mono printer. Links go to black with their underline
   doing the work, and the to-be-announced boxes go to a black outline. The
   red stays only on hairlines, where it is structure rather than text.

   body::before is the page texture and the reel is the video; both are
   dropped rather than printed. */
@media print {
  :root {
    --paper:       #FFFFFF;
    --paper-2:     #FFFFFF;
    --ink:         #000000;
    --ink-2:       #333333;
    --ink-3:       #555555;
    --accent:      #000000;
    --accent-dark: #000000;
    --on-accent:   #FFFFFF;
    --alarm:       #000000;
    --on-alarm:    #FFFFFF;
    --rule:        rgba(0, 0, 0, 0.30);
    --rule-strong: rgba(0, 0, 0, 0.60);
  }
  .nav, .hero__reel, body::before { display: none; }
  body { background: #fff; color: #000; }
  .foot { background: #fff; color: #000; border-block-start-color: rgba(0, 0, 0, 0.6); }
  .foot__dim { color: #333; }
  /* NO ATTEMPT TO FORCE <details> OPEN HERE. The first draft of this block had
     `details > summary { display: none }` next to `details { display: block }`
     to print the schedule expanded - which hides the <summary> of all 41 FAQ
     entries, i.e. every question, and does not reveal a single answer,
     because a closed <details> hides its content through the UA's slot
     behaviour rather than through `display`. CSS cannot open them. If printing
     expanded ever matters, the fix is a `beforeprint` listener in site.js that
     sets `open` on every <details>, and `afterprint` to put them back. */
  /* A red-on-white filled box prints as a grey slab with white text on a mono
     printer. Outline instead, which reads the same either way. */
  .tba { background: transparent; color: #000; border: 1px solid #000; }
}

/* ==========================================================================
   ARABIC BUILD - ar/index.html sets <html lang="ar" dir="rtl">.
   The layout itself needs nothing: everything above is written with logical
   properties, so the mirror is free. Type is another matter, and this block
   is almost entirely about legibility.

   1. FACES. Anton, Archivo and Space Mono carry no Arabic glyphs.
      · Display -> Cairo 900. The first pass used Noto Kufi Arabic, which has
        the right poster stance but is a Kufi face: flattened, low-contrast
        letterforms that are hard work below headline size. Cairo keeps the
        geometric weight and stays readable.
      · Body and labels -> Noto Sans Arabic, the most openly drawn Arabic
        sans available to us. Arabic has no monospace tradition, so the
        "mono" role survives as a size-and-weight distinction, not a face.

   2. SIZE. Arabic needs more optical size than Latin to read as easily: the
      letterforms hang off the baseline, what separates one letter from
      another is a dot or two, and there is no capital-height cue. So body
      copy and every tracked-out label is scaled up here - but the HEADINGS
      ARE NOT. An earlier pass scaled the root instead, which lifted body,
      labels and headlines together and left the Arabic page visibly larger
      than the English one. Sizing the two groups separately keeps the
      display type in exact parity with index.html, which is what the poster
      layout depends on, while the reading sizes go up where they need to.

      Practical consequence: do not put a font-size on html[lang="ar"].

   3. LEADING. Ascenders, descenders and diacritics all run through the same
      band, so lines set at 1.6 collide. Body goes to 1.85, headings 1.22.

   4. LETTER-SPACING. Arabic is cursive: tracking forces gaps between glyphs
      that should join, which reads as broken text rather than as emphasis.
      Every rule above that tracks out a label is reset here. The lists are
      explicit rather than a universal override so they stay greppable - if
      you add a tracked component, add it to both.
   ========================================================================== */
html[lang="ar"] {
  --font-display: Cairo, "Noto Sans Arabic", "Segoe UI", Tahoma, sans-serif;
  --font-body:    "Noto Sans Arabic", "Segoe UI", Tahoma, sans-serif;
  --font-mono:    "Noto Sans Arabic", "Segoe UI", Tahoma, sans-serif;
}

/* --- Headings: parity with the English build ----------------------------
   Cairo's poster weight is 900, where Anton is a single weight served as 400.

   Matching font-size does NOT match apparent size. Anton is a condensed face
   whose caps do nearly all the work inside the em; Arabic set in Cairo runs
   ascenders, descenders and diacritics through the full band, so at the same
   font-size a two-line h2 measured 119px of ink against the English page's
   98px - 21% taller, which is exactly what reads as "the Arabic headings are
   bigger". The display sizes below are therefore scaled to 0.86 and the
   leading pulled in, which lands the two builds on the same block height.

   Measured, not guessed: render both pages and compare the ink extents before
   changing these numbers. */
html[lang="ar"] :is(h1, h2, h3, h4, .display) { font-weight: 900; }
html[lang="ar"] :is(h1, h2, h3, h4, .display) { line-height: 1.14; }

/* 5.2vw / 3.4rem in the English build. */
html[lang="ar"] .head :is(h1, h2) { font-size: clamp(1.72rem, 4.47vw, 2.92rem); }

/* The event-block headings would take the same 0.86 as .head h2 - display
   face, so Cairo runs ~21% taller without it - but only once they are set in
   Arabic. Right now all three are Latin wordmarks (TBC-ARNAME), rendered in
   Anton by the .wordmark rule at the foot of this file, and a Latin run in the
   Latin face needs no compensation: applying 0.86 there just made them 14%
   smaller than the English page for no reason. Dropping .wordmark when Abu
   Dhabi Gaming supplies Arabic names re-arms this automatically. */
html[lang="ar"] .jam__name:not(.wordmark) { font-size: 1.29rem; }

/* The hero headline is Arabic copy now ("BUILD. COLLABORATE. CREATE." from the
   client sitemap), not the Latin wordmark it used to be, so it takes the same
   0.86 display scale as .head h2. Measured at 1440px: three lines came to
   475px of block height against the English page's 406px - 17% taller - and
   0.86 with the leading at 1.1 lands it on 409px. 11vw/9rem x 0.86.

   The :not(.wordmark) guard is what makes this self-correcting: if the client
   ever supplies a Latin wordmark for this slot again, adding the class both
   restores Anton and drops this rule, because a Latin run in the Latin face
   needs no Cairo compensation. */
html[lang="ar"] .hero__title:not(.wordmark) {
  font-size: clamp(2.58rem, 9.46vw, 7.74rem);
}
@media (min-width: 62rem) {
  html[lang="ar"] .hero__title:not(.wordmark) {
    font-size: clamp(2.6rem, 4.8vw, 5.5rem);
    white-space: nowrap;
    line-height: 1.18;
  }
}
/* Looser than the Latin 0.94, and looser than the 1.1 this carried while the
   headline was a Latin wordmark: the strapline's third line opens on تعلّم,
   whose shadda rises above the letter body and collided with the line above
   at 1.1. Arabic display type carrying diacritics needs the room. */
html[lang="ar"] .hero__title    { line-height: 1.22; }

html[lang="ar"] .countdown__live { font-size: 1.3rem; }
html[lang="ar"] .foot .display   { font-size: 1.3rem; }

/* --- Latin-only runs keep the Latin faces -------------------------------
   The Arabic page uses Western numerals, as is standard in the UAE. Three
   components hold nothing BUT numerals, so there is no Arabic in them to set
   and no reason for them to change face: swapping Anton for Cairo there just
   makes the same digits look different on the two pages for no gain. They
   keep the English face, size and leading exactly.

   This does not apply to mixed runs - a date like "17–18 أكتوبر 2026" or a
   label like "س1" has Arabic in it and stays in the Arabic face. */
html[lang="ar"] .countdown__num {
  font-family: Anton, "Arial Narrow", Impact, sans-serif;
  font-weight: 400;
  font-size: clamp(1.85rem, 7vw, 3.6rem);
  line-height: 0.9;
  letter-spacing: normal;
}
html[lang="ar"] .tally dd {
  font-family: Anton, "Arial Narrow", Impact, sans-serif;
  font-weight: 400;
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 0.95;
}

/* --- Reading sizes -------------------------------------------------------
   Cairo carries less ink per em than Archivo, so Arabic prose does need to
   run a little larger than the Latin to read as the same weight of text.
   The question is how much, and the first pass overdid it.

   IT WAS +13% SIZE AND +16% LEADING (1.15rem/1.85 against the Latin
   1.02rem/1.6). Those multiply: an Arabic paragraph stood about 30% taller
   than the same paragraph in English, which is why the Arabic build ran a
   whole screen longer and why the hero, the "what is a game jam" definition
   and the day-two rail copy all read as oversized blocks rather than as
   Arabic set to match. Reported by the user 2026-09-09.

   IT IS NOW +5% SIZE AND +7.5% LEADING, which is the compensation Cairo
   actually needs and no more. Every override below moves by the same ~0.93
   factor so the relationships between them are untouched - this is one scale
   being rebalanced, not ten independent decisions.

   Do not take these to Latin parity. At 1.02rem/1.6 Cairo reads a step light
   against the English page, which is the fault this rule exists to correct;
   the bug was the size of the correction, not the correction.               */
html[lang="ar"] body { font-size: 1.07rem; line-height: 1.72; }

html[lang="ar"] .hero__lede   { font-size: clamp(1.08rem, 2.05vw, 1.26rem); }
html[lang="ar"] .head__lede   { font-size: 1.1rem; }
html[lang="ar"] .prose-muted  { font-size: 1.07rem; }
html[lang="ar"] .hero__meta   { font-size: 1.08rem; }
html[lang="ar"] .specs dd     { font-size: 1.08rem; }
html[lang="ar"] .rail span    { font-size: 1.04rem; }
html[lang="ar"] .jam__desc    { font-size: 1.04rem; }
html[lang="ar"] .faq summary  { font-size: 1rem; }
html[lang="ar"] .person b     { font-size: 0.96rem; }
/* Sub-headings return to the body face in both builds; restore their weight
   after the 900 rule above. Size is left at the English 1.08rem - see the
   heading-parity rule in the label block below. */
html[lang="ar"] :is(h3.sub, .sub) { font-weight: 700; }
/* Tailwind's .text-sm is a Latin reading size; the footer is the only place
   the page uses it for running text. */
html[lang="ar"] .foot .text-sm { font-size: 0.98rem; }

/* --- Label floors -------------------------------------------------------
   WHAT GETS LIFTED AND WHAT DOES NOT. Only two groups are scaled up for
   Arabic: running text, and the micro-labels below. Headings of every kind -
   h1-h4, .sub, .faq summary, .person b - stay at their English size, because
   they are already large enough to read and matching them is what keeps the
   two builds looking like one design. An earlier pass lifted the
   sub-headings too and the Arabic page read as though it were set a size up
   throughout.

   These labels do need it: Latin small caps are legible at 0.64rem, Arabic is
   not - there are no caps, and what separates one letter from another is a
   dot or two that vanishes at that size. Each is the Latin size plus ~18%,
   with a 0.82rem (~13px) floor, so they land readable without growing into
   the headings above them. */
html[lang="ar"] .hero__kicker             { font-size: 0.87rem; }
html[lang="ar"] .eyebrow                  { font-size: 0.82rem; }
html[lang="ar"] .tally dt                 { font-size: 0.82rem; }
html[lang="ar"] .specs dt                 { font-size: 0.78rem; }
html[lang="ar"] .jam__month               { font-size: 0.82rem; }
html[lang="ar"] .countdown__lbl           { font-size: 0.82rem; }
html[lang="ar"] .btn                      { font-size: 0.92rem; }
html[lang="ar"] .btn--lg                  { font-size: 0.98rem; }
html[lang="ar"] .lang                     { font-size: 0.83rem; }
html[lang="ar"] .nav__links a:not(.btn)   { font-size: 0.85rem; }
html[lang="ar"] .nav__panel a:not(.btn)   { font-size: 0.9rem; }
html[lang="ar"] .day__head                { font-size: 0.82rem; }
html[lang="ar"] .panel__head              { font-size: 0.87rem; }
html[lang="ar"] .tba                      { font-size: 0.82rem; }
html[lang="ar"] .plate figcaption         { font-size: 0.82rem; }
html[lang="ar"] .reel figcaption          { font-size: 0.82rem; }
html[lang="ar"] .person span              { font-size: 0.82rem; }
html[lang="ar"] .faq summary .q           { font-size: 0.87rem; }
html[lang="ar"] .faqnav a                 { font-size: 0.82rem; }

/* The six group headings take the same 0.86 as .head :is(h1, h2) - Cairo runs
   ~21% taller than Anton at the same size. */
html[lang="ar"] .faqgroup__title          { font-size: clamp(1.29rem, 2.92vw, 1.81rem); }

/* The FAQ answer hangs off the same gutter as its question label. */
html[lang="ar"] .faq summary .q           { min-inline-size: 2.7rem; }
html[lang="ar"] .faq .answer              { padding-inline-start: 3.7rem; }
@media (max-width: 30rem) {
  html[lang="ar"] .faq .answer { padding-inline-start: 0; }
}

/* --- Tracking reset ------------------------------------------------------ */
html[lang="ar"] :is(
  .hero__kicker,
  .hero__slogan,
  .eyebrow,
  .tally dt,
  .specs dt,
  .countdown__lbl,
  .countdown__live,
  .btn,
  .lang,
  .nav__links a:not(.btn),
  .nav__panel a:not(.btn),
  .day__head,
  .panel__head,
  .tba,
  .plate figcaption,
  .reel figcaption,
  .jam__month,
  .person span,
  .faq summary .q,
  .faqnav a,
  h1, h2, h3, h4, .display
) { letter-spacing: 0; }

/* --- Nav ----------------------------------------------------------------
   The bar swaps from panel to links at 66rem for English. Arabic labels are
   both wider and, after the floors above, larger, so five of them plus
   Register plus the language toggle no longer clear the logo lockup at that
   width. The Arabic build keeps the panel until 72rem. The last rule is what
   re-opens the panel, since the 66rem block hides it with !important. */
@media (max-width: 72rem) {
  html[lang="ar"] .nav__links  { display: none; }
  html[lang="ar"] .nav__toggle { display: grid; }
  html[lang="ar"] .nav__panel[data-open="true"] { display: block !important; }
}

/* The event wordmark stays in Latin script - see TBC-ARNAME in ar/index.html
   - so the one Latin run on the page keeps its own face and tracking. */
html[lang="ar"] .wordmark {
  font-family: Anton, "Arial Narrow", Impact, sans-serif;
  font-weight: 400;
  line-height: 0.94;
  letter-spacing: 0.002em;
}
