/* ==========================================================================
   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;

  /* ==========================================================================
     THE TYPE SCALE - added 2026-09-18, client review of the same date.

     "Their biggest issue is the font hierarchy [...] preserve a clear
     H1 > H2 > H3 > body relationship so users can easily scan the page."

     WHAT WAS WRONG. There was no scale. Every component set its own size at
     the point of use, which is survivable while the numbers happen to agree
     and stops being survivable the moment they do not. The measured failure
     was .sub against body: 1.08rem over 1.02rem, a ratio of 1.06. On a
     desktop the display faces and the section rhythm carried the hierarchy
     anyway; on a phone, where .head h2 clamps down to 2rem and there is no
     column structure left to read, a sub-heading was six percent larger than
     the paragraph under it and the page turned into one undifferentiated
     column. That is the mobile screenshot the client sent.

     THE RATIOS ARE THE POINT, not the absolute values. Each step has to be
     visible at the SMALL end of its clamp, because that is where the scale is
     under most pressure and where the client was reading:

       h1 / h2    2.90 / 2.00 = 1.45     hero name over a section heading
       h2 / h2-sub 2.00 / 1.50 = 1.33    section heading over a group heading
       h2-sub/h3  1.50 / 1.30 = 1.15     FAQ page only, see below
       h2 / h3    2.00 / 1.30 = 1.54     section heading over a sub-heading
       h3 / body  1.30 / 1.02 = 1.28     WAS 1.06 - this is the fix
       lede/body  1.10 / 1.02 = 1.08     a lede is body with air, not a size
       body/small 1.02 / 0.875 = 1.17
       small/label 0.875 / 0.72 = 1.22   label is mono caps, a different voice

     --fs-small IS TAILWIND'S .text-sm VALUE (0.875rem), deliberately. The CDN
     build injects its utilities AFTER this stylesheet, so a `.text-sm` rule
     in here loses on source order at equal specificity and cannot be used to
     retune the utility. Matching the number instead means the page has one
     small size whether a given element reaches it through the token or
     through the utility class.

     1.28 for h3/body is the smallest step that reads as a different level at
     16px on a 390px screen. It was checked against 1.18 (too close to bold
     body) and 1.4 (a 1.43rem sub-head crowds the 2rem h2 above it on the same
     screen, which trades one collision for another).

     THE LEDE DELIBERATELY DOES NOT GET ITS OWN LEVEL. It is 1.08x body, which
     is a texture difference rather than a rank - a lede belongs to the
     heading above it and must not compete with the h3s below it. Its
     separation comes from measure, colour and space, not from size.

     --fs-h2-sub EXISTS FOR ONE PAGE. faq.html is six groups of questions under
     a single page title, so it genuinely needs a level between the page
     heading and the question rows; index.html never uses it. It is a narrow
     step over --fs-h3 (1.15) and that is safe only because the two never
     appear in the same block - if a section ever needs both, widen this
     rather than leaning on the position.

     TWO FLOORS ARE CONTRAST FLOORS AND ARE NOT PART OF THIS SCALE:
     .hero__slogan's 1.5rem (see its own note - the accent run is only legal
     as WCAG "large text") and .eyebrow's 0.7rem red (4.6:1 on --paper at
     bold mono, with nothing spare). Neither takes a token. Do not "tidy" them
     into one.

     ADDING A COMPONENT: take a token, do not write a number. If no token
     fits, the component is claiming a level the page does not have, and that
     is the thing to question. The Arabic build multiplies these same tokens
     rather than restating them - see the html[lang="ar"] block at the foot of
     this file, which used to carry fourteen hand-set sizes that had to be
     re-derived by hand every time an English one moved.
     ========================================================================== */
  --fs-h1:    clamp(2.9rem, 9vw, 7rem);      /* hero name only */
  --fs-h2:    clamp(2rem, 5.2vw, 3.4rem);    /* section headings */
  --fs-h2-sub: clamp(1.5rem, 3.4vw, 2.1rem); /* group headings under an h2 */
  --fs-h3:    clamp(1.3rem, 2.2vw, 1.5rem);  /* .sub - was 1.08rem flat */
  --fs-lede:  clamp(1.1rem, 1.6vw, 1.22rem); /* section and hero ledes */
  --fs-body:  1.02rem;                       /* body, prose, spec values */
  --fs-small: 0.875rem;                      /* captions, footnotes */
  --fs-label: 0.72rem;                       /* mono caps labels */

  --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: var(--fs-body);
  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: var(--fs-h3);
}

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: var(--fs-h2); }
.head__lede {
  margin-block-start: 1.15rem;
  max-inline-size: 60ch;
  color: var(--ink-2);
  font-size: var(--fs-lede);
}

/* --- 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.                                  */
/* NOT --fs-label, and the 0.02rem difference is the reason this is worth a
   line: the label token is a scale step and can move, this number is a
   contrast floor. Bold mono red on --paper measures 4.6:1 with nothing
   spare - see --alarm. If --fs-label ever drops, this must not follow it. */
.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__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.
   It is the one place the client's palette cannot be applied as drawn, and it
   is a measurement, not a preference. The band carried a white .hero__kicker
   for exactly this reason until 2026-09-18, when the chip was removed
   outright; the rule survives it. Nothing red goes in this band - not an
   eyebrow, not a hairline, not a TBA pill.

   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, and the block it heads ---------------------
   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 UNITY MARK IS PART OF IT NOW - user direction 2026-09-18, with the
   client's revised key art. That artwork adds Unity to the cartridge: the
   white SHIFT GAME JAM plaque, a hairline under it, and the Unity logo on the
   dark cartridge body below. This is that arrangement, composed from the two
   logo files the site already ships rather than cut out of the 1920x1080 PNG.

   COMPOSED, NOT CROPPED, and the user asked for it that way. A crop would
   have been a third raster lockup to keep in step with the other two, pinned
   to whatever resolution the export happened to be, and re-rendered as a
   picture of two logos that are already on the page as logos. unity-white.svg
   is a vector and stays sharp at any size; the hairline is a border rather
   than pixels; and if the client reissues either mark, the file is swapped
   once and every use of it follows. The only raster left is the plaque
   itself, which has always been one.

   The rule is --rule-strong rather than the key art's red. Red at 1px is the
   thing the user took off the hairlines across the whole site on 2026-09-16
   - see --rule - and one red line inside a logo lockup would be the only one
   left on the page.

   The ceiling is the plaque 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 {
  inline-size: clamp(8rem, 17vw, 13.5rem);
  flex-shrink: 0;
}
.reg__mark img { display: block; }
.reg__mark-lockup { inline-size: 100%; block-size: auto; }
/* Unity sits in the band under the plaque, centred on it, at the proportion
   the key art uses - a little under three fifths of the plaque's width,
   measured off the supplied artwork rather than guessed. The border is the
   band's top edge. */
.reg__mark-unity {
  inline-size: 58%;
  block-size: auto;
  margin: 0.85rem auto 0;
  padding-block-start: 0.85rem;
  border-block-start: 1px solid var(--rule-strong);
  box-sizing: content-box;
}

/* --- Registration: the mark and the copy beside it -----------------------
   Client review 2026-09-18, on the mobile build: "the registration and body
   of text should be next to the logo, there is dead space next to the logo".

   They are right, and it is worth being precise about where. This block sits
   in the right-hand column of a two-column section. At 64rem and up that
   column is about 35rem wide and the mark, the heading and the paragraph
   stack down it with nothing to spare - which is the layout that was signed
   off and is unchanged. Below 64rem the section collapses to ONE column, the
   block goes full width, and a 13.5rem-at-most portrait plaque is suddenly
   sitting alone on a 24rem line with two thirds of it empty. The narrower the
   screen, the worse it looked, which is the opposite of what a responsive
   layout is meant to do.

   SO THE STACK INVERTS, and that reads backwards until you see why: side by
   side on the SMALL screens, stacked on the large one. The deciding factor is
   not the viewport, it is the measure available to this block - full page
   width when the section is one column, half of it when the section is two -
   and those run in opposite directions as the viewport grows. 63.99rem is the
   section's own breakpoint (Tailwind lg:grid-cols-[1fr_1fr]); keep them
   together or this block will pair up while it is still in a narrow column.

   THE BUTTON AND THE FINE PRINT ARE NOT IN HERE. They stay full width below,
   because a primary CTA squeezed into a 60% column is a smaller tap target
   for no reason, and because the button's label is long enough to wrap to
   three lines in that space.

   THE PLAQUE COMES DOWN A SIZE WHILE IT IS PAIRED, to clamp(6rem, 22vw, 9rem)
   from the stacked clamp(8rem, 17vw, 13.5rem). At 390px - the width the
   client was reading on - the stacked floor of 8rem would leave about 200px
   for the paragraph beside it, which is 25 characters a line and sets prose
   as a ragged strip. At 6rem the paragraph gets 234px and roughly 29, which
   is the low end of comfortable but is prose. The mark is a supporting
   element in this pairing; the copy is what has to be readable.

   Under 22rem it stacks again - 352px, below the 360px of the narrowest
   phone worth serving, so this is a guard rather than a layout. The old
   floor was 26rem, which was simply wrong: 416px is WIDER than the 390px and
   402px phones this was written for, so every common handset fell through to
   the stacked case and the dead space the client reported was still there.
   Check this number against a real phone width, not against a round rem
   value.                                                                    */
.reg { display: block; }
@media (min-width: 22rem) and (max-width: 63.99rem) {
  .reg {
    display: flex;
    align-items: flex-start;
    gap: clamp(1.25rem, 4vw, 2rem);
  }
  .reg__mark { inline-size: clamp(6rem, 22vw, 9rem); }
  /* The heading is optically level with the top of the plaque rather than
     mathematically level with it: .sub has leading above its cap height. */
  .reg__body { margin-block-start: -0.2rem; }
}
/* Stacked - both the desktop column and the sub-22rem guard. The mark needs
   its own space under it only when something follows it in the flow. */
@media (max-width: 21.99rem), (min-width: 64rem) {
  .reg__mark { margin-block-end: clamp(1.75rem, 4vw, 2.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: var(--fs-h1);
  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: var(--fs-lede);
  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: var(--fs-label);
  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;
}

/* --- Narrow screens: the row stacks ---------------------------------------
   Client review 2026-09-18, on the mobile screenshot: the spec list was one
   of the places they read as having no hierarchy.

   The label/value row works by putting the two at opposite ends of a line.
   That needs a line long enough to hold both, and below ~30rem several of
   these do not have one: "Who can enter" against "Students and recent
   graduates", "Theme" against "Revealed at the Day 1 opening session". The
   value wraps, the auto margin above pushes the wrapped remainder to the far
   end, and the result is a label on the left, a ragged block on the right and
   a gap of white down the middle of a list whose whole job is to be scanned.
   That is exactly what the client photographed.

   Stacked, each row is a label with its value under it, flush to the same
   edge, and the mono/serif-weight contrast between them does the work the
   line positions were doing. The auto margin has to be switched off
   explicitly - it is what pushed the value away from the label.

   30rem, not the 34rem .days uses: these values are shorter than the
   schedule's sentences, and holding the two-column form a little longer keeps
   the list compact on a mid-size phone in landscape.                        */
@media (max-width: 30rem) {
  .specs > div { display: block; }
  .specs dt { margin-block-end: 0.3rem; }
  .specs dd { margin-inline-start: 0; }
}

/* --- Prize ladder: the amounts, set at display size, under the spec list --
   THE PRIZES WERE A SPEC ROW UNTIL 2026-09-22 and the amounts are why they
   are not one any more. A row reading "The prize pool is AED 30,000 in Geekay
   vouchers: 1st place AED 15,000, 2nd place AED 10,000, 3rd place AED 5,000"
   is a sentence with four numbers in it, set at 600 weight in the value
   column between "Students and recent graduates" and nothing. It is the
   single strongest reason on the page to register and it read as the eighth
   line of an eight-line list, with the ranking carried entirely by the
   ordinals inside the sentence.

   IT IS NOT ITS OWN BAND, AND IT WAS ONE FOR AN HOUR. The first attempt put
   a full-width three-column board between the register grid and the crash
   course, which read as a section bolted onto the end of #details - a second
   subject after the register button had already closed the first. User
   direction, 2026-09-22: use the space that is already there.

   THE SPACE IS REAL. #details is a two-column grid: spec list on one side,
   the lockup and the register CTA on the other. The right column runs about
   200px deeper than the spec list, so the foot of the left column has been
   empty since the lockup moved into the right one on 2026-09-09. That gap is
   directly below the last spec row and directly beside the register button,
   which is the best position on the page for the reason to press it.

   SO IT STACKS RATHER THAN SPREADS. Three across needs roughly 160px a
   column in half a grid, which will not hold "AED 15,000" at any size worth
   setting it at. Stacked, each place is a row in the idiom the list above
   already established - mono label at the start, value at the end - and the
   ladder reads top to bottom, which is the direction a ladder reads anyway.

   THE BARS ARE THE RANKING. Each place carries a top bar and the bars
   descend - 4px accent, 2px --rule-strong, 1px --rule - so the order is
   legible before a single figure is read. The figures descend with them.
   Either signal alone would do the job; together they make the order
   impossible to misread, which matters because the amounts are 15/10/5 and a
   careless eye reads them as one block of money.

   ONE ACCENT USE, AND IT IS A BAR RATHER THAN TYPE. Yellow already carries
   links, buttons and the hero's highlight line, and a yellow FIGURE a few
   inches from the yellow register button would read as a second control. A
   rule in the accent hue reads as emphasis and cannot be mistaken for
   something to click. The figures are all --ink.

   NO BOXES. DESIGN.md -> House style allows exactly one boxed component, the
   three points in #why, because the client drew the boxes by hand on a
   screenshot. Nobody has asked for a boxed prize tile, and a podium made of
   rectangles is the look the house style exists to avoid. Structure here
   comes from rules, which is what the style says it comes from.

   IT IS AN <ol>. The order is the content, so the markup carries it rather
   than only the labels - and the labels still say "1st place" in words for
   anyone who gets the figures without the bars.                            */
.prizes { margin-block-start: clamp(1.6rem, 3vw, 2.4rem); }

/* The pool total, in the spec row's own idiom - mono label, 600-weight value,
   the same auto margin pushing the value to the end of the line - because it
   IS one: one label, one value, and the ladder below is its breakdown. It
   carries the hairline that closes the spec list, so the two blocks read as
   one column rather than as a list with a widget under it. */
.prizes__pool {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1.5rem;
  border-block-start: 1px solid var(--rule);
  padding-block: 0.85rem;
}
.prizes__pool-lbl {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.prizes__pool-val {
  font-weight: 600;
  color: var(--ink);
  margin-inline-start: auto;
}

.prizes__board {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}

/* No gap between the rows: the bar at the top of each place is the separator,
   exactly as the hairline at the top of each spec row is. Space between them
   would turn three rungs into three objects. */
.prize {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.25rem;
  padding-block: 0.7rem 0.55rem;
}
.prize--1 { border-block-start: 4px solid var(--accent); }
.prize--2 { border-block-start: 2px solid var(--rule-strong); }
.prize--3 { border-block-start: 1px solid var(--rule-strong); }

/* --- The medals ---------------------------------------------------------
   Client artwork, supplied 2026-09-22: the actual medal, on the actual
   lanyard, rendered. It is the prize, not an illustration of the idea of a
   prize, which is the only reason it earns a place in a house style that has
   no decorative imagery.

   THEY DO NOT CARRY THE RANKING AND MUST NOT BE ASKED TO. All three renders
   are the same silver medal on the same ribbon; what differs is the numeral
   embossed on the face, which is a low-contrast relief about 90px across in
   a 1563px original. At the size it sits here that numeral is gone. The bars
   and the figure sizes do the ranking, exactly as they did before the
   artwork arrived, and if the client ever supplies gold/silver/bronze
   variants this comment is the thing to revisit.

   ONE SIZE FOR ALL THREE. Scaling them down by rank was tried and is wrong:
   it is the same physical object at all three places, and a smaller third
   medal reads as a cheaper one, which is a claim about the prize that nobody
   has made.

   CROPPED TO THE DISC. The originals are 1006x1563 and about two thirds of
   that is lanyard - at 54px wide the whole frame puts a 15px medal under
   40px of ribbon. The crop keeps the clasp and a stub of ribbon, which is
   how a medal is drawn anyway, and the working asset is 200px wide and 6KB
   against the original's 2.5MB.

   SCREEN, BECAUSE THE BLACK IS BAKED IN. The supplied PNGs are fully opaque
   - alpha is 255 across every pixel - on a near-black #070709 ground with a
   soft vignette, so there is no transparency to composite with and no clean
   edge to key against without eating the medal's own shadows. mix-blend-mode
   screen drops black to nothing against any darker-or-equal backdrop and
   leaves the silver alone; the ground here is #0A0E20, so the vignette lifts
   it by about 3% where they meet, which is not visible. It also means no
   radius is needed to round off a square crop, which matters: the house
   style has none anywhere.

   The fallback, in a browser without blend modes, is the crop on its own
   black ground - a slightly darker rectangle on a dark page. Acceptable, and
   the reason this is not doing the work with a mask.                       */
.prize__medal {
  flex: none;
  align-self: center;
  inline-size: clamp(3rem, 6vw, 3.9rem);
  block-size: auto;
  mix-blend-mode: screen;
}

.prize__rank {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* Currency and figure are separate spans on one baseline, and the split is
   not cosmetic: it leaves .prize__sum holding nothing but digits, which is
   what lets the Arabic build keep Anton there under the Latin-run rule at the
   foot of this file. Their ORDER differs between the builds - "AED 15,000"
   against "15,000 درهم" - so it is set in the markup of each page rather than
   by reversing a flex row. The auto margin is the .specs dd rule and is there
   for the same reason: it holds the figures in a column of their own whether
   or not the row wrapped. */
.prize__amount {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  margin-inline-start: auto;
}
.prize__cur {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.prize__sum {
  font-family: var(--font-display);
  line-height: 0.95;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* Three steps of about 1.2, which is the smallest ratio that reads as a
   different level - the same finding the type scale is built on. The ceiling
   is held down deliberately: this sits in half a grid column beside a
   registration block, and a figure that outweighs the register button is a
   page shouting its prize at someone who has not been told the dates yet. */
.prize--1 .prize__sum { font-size: clamp(1.9rem, 3.4vw, 2.7rem); }
.prize--2 .prize__sum { font-size: clamp(1.6rem, 2.9vw, 2.25rem); }
.prize--3 .prize__sum { font-size: clamp(1.35rem, 2.4vw, 1.85rem); }

/* The spec rows stack below 30rem and the auto margins come off with them;
   these rows hold their label/value line to the end, because "1st place" and
   "AED 15,000" together are shorter than any spec value that forced that
   breakpoint. What does come off is the last row's floor - see .specs. */
@media (max-width: 30rem) {
  .prizes__pool { display: block; }
  .prizes__pool-lbl { display: block; margin-block-end: 0.3rem; }
  .prizes__pool-val { margin-inline-start: 0; }
}

/* ==========================================================================
   CARDS - the three points in #why, in boxes.

   Client review 2026-09-18: "Can we have the three main points in boxes, so
   it is easier to segment and navigate". They marked all three up by hand on
   the screenshot, drawing the boxes they wanted round each one.

   WHAT WAS THERE was three bare <div>s in a Tailwind grid - a sub-heading and
   a paragraph each, separated by nothing but a 3rem column gap. On a desktop
   the gap does the segmenting; on a phone the grid collapses to one column
   and the only thing between the end of one point and the start of the next
   is 2.5rem of air. Three points became one column of six paragraphs. The
   client read that page on a phone.

   THIS IS THE HOUSE STYLE'S FIRST BOX AND IT STAYS INSIDE IT. DESIGN.md ->
   House style says no boxes, no radius, no shadow, and the .jams note below
   turned down a request for "three selectable event cards" on exactly that
   ground. Two of those three hold here: there is no radius and no shadow, and
   the border is the same 1px --rule-strong hairline that .schedule > summary
   and the media objects already use. What changed is that the client asked
   for the third, twice, in writing and in ink on the page. A hairline
   rectangle is the smallest thing that answers it.

   THE JAMS BLOCK IS STILL NOT CARDS and this rule is not an opening to make
   it so. Those are three mutually exclusive events a visitor chooses between,
   and they read as a programme. These are three parallel statements that have
   to be told apart. Different problem, different answer.

   GROUND. #why is a .section--band, which is rgba(0,7,17,0.88) over the page
   texture. The card sits on --paper, the LIGHTER of the two grounds, so it
   lifts off the band rather than disappearing into it, and the texture stops
   at the card edge - which is most of why the box reads as a box at all.
   Measured: --ink-2 body copy is 10.20:1 on the card against 10.77:1 on flat
   --paper-2, so moving the prose onto the lighter ground costs about half a
   point and leaves it more than twice over the 4.5:1 floor. --ink headings
   are 19.00:1 on the card.

   EQUAL HEIGHTS come from the grid, not from a min-height: the three bodies
   are 4, 2 and 2 lines, and boxes of three different depths look like a bug
   rather than a rhythm. align-items is left at stretch deliberately.        */
.cards {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.5rem);
}
@media (min-width: 40rem)  { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem)  { .cards { grid-template-columns: repeat(3, 1fr); } }

.card {
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: clamp(1.25rem, 3vw, 1.75rem);
}

/* The third card is the odd one out at the two-column width - it sits alone
   on the second row and stretches to the full width, which reads as a
   different kind of object rather than the third of three. Spanning it
   deliberately is worse; letting it size to one column and sit at the start
   keeps all three the same shape. */
@media (min-width: 40rem) and (max-width: 63.99rem) {
  .cards > :last-child:nth-child(odd) { grid-column: 1; }
}

/* ==========================================================================
   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: var(--fs-h3); 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: var(--fs-body);
  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: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.days dd { font-size: var(--fs-body); 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: var(--fs-label);
  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: var(--fs-body); }

/* ==========================================================================
   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: var(--fs-body);
  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: var(--fs-h2-sub); }

/* ==========================================================================
   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 73rem+ the links row owns the spacing; the button just sits after it.
   Keep this width in step with the display swap below. */
@media (min-width: 73rem) {
  .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; }
}
/* NOWRAP IS LOAD-BEARING, not tidying. Without it a label that does not fit
   wraps to two lines inside the bar instead of overflowing, so the bar looks
   survivable at widths where it is not and the breakpoint below cannot be
   found by eye - which is exactly how "Details & preparation" shipped at a
   70rem swap that was 100px too low on 2026-09-18. Wrapping makes the failure
   quiet; nowrap makes it measurable. */
.nav__links a:not(.btn) {
  white-space: nowrap;
  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.

   RAISED AGAIN, 66rem -> 73rem, on 2026-09-18. The client asked for the menu
   and the section headings to read the same words, and the heading that had
   to give was "Practical details & preparation" - so the nav label grew from
   "Details & prep" to "Details & preparation", seven mono capitals plus their
   tracking.

   73rem IS MEASURED, NOT ESTIMATED, and the first attempt at it was wrong.
   It went in at 70rem on arithmetic - count the capitals, multiply by the
   advance plus tracking, add it to the old limit - and that came out about
   100px short. Measuring the bar's intrinsic width instead (set .nav__inner
   to width: max-content and read it back) gives 1071px of content plus 80px
   of shell gutter: 1151px, or 71.91rem. 73rem is that with a little headroom
   for font-loading variance, and it sits either side of the Arabic build's
   own 72rem for unrelated reasons.

   Below this the panel carries everything, and the panel has never been short
   of room. The alternative was keeping the nav abbreviated and letting the
   heading spell it out, which is the exact mismatch the client reported. Do
   not re-abbreviate this label without lowering this number to match, and do
   not change any label without re-measuring. */
@media (min-width: 73rem) {
  .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.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  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.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  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: var(--fs-small);
  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. Set on the TOKENS rather than on the
   selectors, so every display heading takes the 0.86 from one place - Cairo
   runs ~21% taller than Anton at the same size. --fs-h2-sub is the FAQ page's
   group headings, which used to carry an identically-derived rule of their
   own further down this file. */
html[lang="ar"] {
  --fs-h2:     clamp(1.72rem, 4.47vw, 2.92rem);
  --fs-h2-sub: clamp(1.29rem, 2.92vw, 1.81rem);
}

/* 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;
}
/* The prize figures are the third such component, and the currency is a
   separate span precisely so this one holds nothing but digits - "درهم" sits
   outside it and stays in the Arabic face. No font-size here, unlike the two
   rules above: the three sizes are set per rank in the block itself and are
   the same in both builds, so restating them here would be three more numbers
   to keep in step for no gain. */
html[lang="ar"] .prize__sum {
  font-family: Anton, "Arial Narrow", Impact, sans-serif;
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: normal;
}

/* --- 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.               */
/* THE MULTIPLIERS LIVE ON THE TOKENS NOW, not on ten components - rewritten
   2026-09-18 with the type scale. This block used to restate a font-size for
   every reading component in the page, each one the English value times 1.05
   worked out by hand. That is ten places to get wrong every time an English
   size moves, and it had already drifted: .rail span and .jam__desc were
   pinned at 1.04rem against an English 0.95rem and 0.98rem, two different
   multipliers for two components that are the same size in the Latin build.

   Overriding the tokens applies the Cairo compensation once, to everything
   that draws on them, and cannot drift. The factors are unchanged - +5% on
   reading sizes, +18% on mono labels with a 0.82rem floor - and are the
   numbers the note above derives.

   THE HEADING TOKENS ARE NOT REDEFINED HERE, deliberately: headings stay at
   Latin parity, which is the rule the label block below sets out. --fs-h2 is
   the exception and it is not a reading-size decision - it is the Anton/Cairo
   apparent-size compensation, so it sits with the other display rules above.

   The four rules that survive below are genuine exceptions rather than
   arithmetic, and each says why. */
html[lang="ar"] {
  --fs-body:  1.07rem;                          /* 1.02 x 1.05 */
  --fs-lede:  clamp(1.16rem, 1.68vw, 1.28rem);  /* the Latin clamp x 1.05 */
  --fs-small: 0.92rem;                          /* 0.875 x 1.05 */
  --fs-label: 0.85rem;                          /* 0.72 x 1.18, over the 0.82 floor */
}
html[lang="ar"] body { line-height: 1.72; }

/* The fact line is not tokenised in either build - it sits between body and
   lede and is the only thing at that size. Its own +5% therefore has to be
   written out. */
html[lang="ar"] .hero__meta   { font-size: 1.08rem; }
/* Heading parity, both of these - see the label block below. They take the
   Latin size rather than the Arabic body size, so they have to say so now
   that the body size reaches them through a token. */
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"] .eyebrow                  { font-size: 0.82rem; }
html[lang="ar"] .tally dt                 { font-size: 0.82rem; }
html[lang="ar"] .jam__month               { font-size: 0.82rem; }
html[lang="ar"] .countdown__lbl           { font-size: 0.82rem; }
html[lang="ar"] .prize__rank              { font-size: 0.82rem; }
html[lang="ar"] .prize__cur               { 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"] .panel__head              { font-size: 0.87rem; }
html[lang="ar"] .tba                      { font-size: 0.82rem; }
html[lang="ar"] .plate figcaption         { font-size: 0.92rem; }
html[lang="ar"] .reel figcaption          { font-size: 0.92rem; }
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 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__slogan,
  .eyebrow,
  .tally dt,
  .specs dt,
  .prizes__pool-lbl,
  .prize__rank,
  .prize__cur,
  .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 73rem 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 the English swap, and its own
   labels did not change on 2026-09-18 - the Arabic nav and headings already
   read the same words, so only English had to grow. The last rule is what
   re-opens the panel, since the 73rem block hides it with !important.

   THE CEILING TRACKS THE ENGLISH SWAP rather than standing at its own round
   number. It was 72rem when English swapped at 66rem, and English moving to
   73rem on 2026-09-18 opened a 16px band where Arabic had no menu at all:
   .nav__links was still display:none from the base rule, and this block had
   stopped re-opening the panel. 72.99rem closes it. If the English number
   moves again, this one moves with it. */
@media (max-width: 72.99rem) {
  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;
}
