/* ============================================================================
   site.css — Focal Comics · shared rules that apply at EVERY width
   ----------------------------------------------------------------------------
   Companion to mobile.css. The split between the two is deliberate:

     · mobile.css — mobile only. Every rule lives inside @media (max-width: …),
                    so it provably cannot affect desktop.
     · site.css   — all viewports, desktop included. Put a rule here only when
                    it genuinely needs to apply everywhere.

   NEW PAGES: link both, in this order, before </head>:

       <link rel="stylesheet" href="site.css">
       <link rel="stylesheet" href="mobile.css">

   (members/ pages use ../site.css and ../mobile.css)
   ========================================================================== */

/* ===========================================================================
   SHARED CHROME — lifted out of the per-page <style> blocks on 52 pages.
   These 50 rules were byte-identical everywhere they appeared, and no page
   redefines any of their selectors, so nothing can be relying on a later
   inline copy winning. Edit them here and every page follows.

   This block sits at the TOP of the file on purpose: the rules below it are
   overrides that are meant to beat the page chrome, exactly as they did when
   the chrome lived inline ahead of this stylesheet.
   ========================================================================= */
.page-hero { position: relative; z-index: 1; padding: 8rem 1.5rem 2.5rem; text-align: center; }
.page-hero .kick { font-family: 'Oswald', sans-serif; font-weight: 500; font-size: .74rem; letter-spacing: .5em; text-transform: uppercase; color: var(--teal-lt); margin-bottom: 1rem; }
.page-hero h1 { font-family: 'Oswald', sans-serif; font-weight: 700; font-size: clamp(2.4rem, 7vw, 4.2rem); letter-spacing: .04em; text-transform: uppercase; background: linear-gradient(100deg, var(--gold-dk), var(--gold) 30%, var(--gold-lt) 50%, var(--gold) 70%, var(--gold-dk)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.page-hero .rule { width: min(260px, 60vw); height: 2px; margin: 1.3rem auto 0; background: linear-gradient(90deg, transparent, var(--gold) 25%, var(--teal) 75%, transparent); }
.contact-section { position: relative; z-index: 1; padding: 2rem 1.5rem 5rem; }
.ci-label { font-family: 'Oswald', sans-serif; font-weight: 600; font-size: .7rem; letter-spacing: .35em; text-transform: uppercase; color: var(--teal-lt); margin-bottom: .6rem; }
.ci-title { font-family: 'Oswald', sans-serif; font-weight: 700; font-size: clamp(1.6rem, 4vw, 2.3rem); line-height: 1.1; letter-spacing: .02em; text-transform: uppercase; color: var(--paper); margin-bottom: 1.1rem; }
.ci-title span { color: var(--gold); }
.ci-body { color: rgba(241,236,224,.68); font-size: 1rem; line-height: 1.7; margin-bottom: 2.2rem; }
.contact-card { display: flex; gap: .9rem; align-items: flex-start; background: linear-gradient(160deg, var(--panel), var(--ink-2)); border: 1px solid var(--line); border-radius: 10px; padding: 1.1rem 1.2rem; margin-bottom: 1rem; }
.cc-ic { width: 22px; height: 22px; color: var(--gold); flex-shrink: 0; margin-top: .1rem; }
.cc-label { font-family: 'Oswald', sans-serif; font-weight: 600; font-size: .68rem; letter-spacing: .22em; text-transform: uppercase; color: var(--gold-lt); margin-bottom: .25rem; }
.cc-val { font-size: .92rem; color: rgba(241,236,224,.7); line-height: 1.5; }
.cc-val a { color: var(--teal-lt); text-decoration: none; transition: color .2s; }
.cc-val a:hover { color: var(--gold-lt); }
.cc-soon { font-size: .58rem; font-family: 'Oswald', sans-serif; letter-spacing: .16em; text-transform: uppercase; color: var(--ink); background: var(--gold); padding: .1rem .4rem; border-radius: 3px; margin-left: .4rem; vertical-align: middle; }
.social-row { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.4rem; }
.social-btn { font-family: 'Oswald', sans-serif; font-weight: 500; font-size: .68rem; letter-spacing: .16em; text-transform: uppercase; color: var(--paper); text-decoration: none; border: 1px solid var(--line); border-radius: 100px; padding: .45rem 1rem; transition: border-color .2s, color .2s, background .2s; cursor: pointer; }
.social-btn:hover { border-color: var(--gold); color: var(--gold-lt); background: rgba(224,187,82,.06); }
.form-wrap { position: relative; background: linear-gradient(160deg, var(--panel), var(--ink-2)); border: 1px solid var(--line); border-radius: 14px; padding: 2rem 1.8rem; }
.form-title { font-family: 'Oswald', sans-serif; font-weight: 700; font-size: clamp(1.4rem, 3vw, 1.9rem); letter-spacing: .03em; text-transform: uppercase; color: var(--paper); margin-bottom: 1.5rem; }
.form-title span { color: var(--gold); }
@media (max-width: 520px) { .form-row { grid-template-columns: 1fr; } }
.form-group { margin-bottom: 1.1rem; }
.form-group label { display: block; font-family: 'Oswald', sans-serif; font-weight: 500; font-size: .66rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); margin-bottom: .45rem; }
.form-group input, .form-group select, .form-group textarea { width: 100%; background: rgba(0,0,0,.35); border: 1px solid var(--line); border-radius: 7px; padding: .75rem .9rem; color: var(--paper); font-family: 'Rajdhani', sans-serif; font-weight: 500; font-size: 1rem; transition: border-color .2s, box-shadow .2s; }
.form-group textarea { min-height: 130px; resize: vertical; }
.form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 3px rgba(31,154,166,.16); }
.form-group select option { background: var(--ink-2); }
.form-consent { font-size: .82rem; line-height: 1.5; color: rgba(241,236,224,.5); margin: .2rem 0 1rem; }
.form-submit { width: 100%; cursor: pointer; font-family: 'Oswald', sans-serif; font-weight: 600; font-size: .9rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ink); border: 0; padding: .95rem; border-radius: 7px; background: linear-gradient(100deg, var(--gold-dk), var(--gold) 30%, var(--gold-lt) 50%, var(--gold) 70%, var(--gold-dk)); background-size: 200% auto; transition: filter .2s, transform .15s, background-position .4s; }
.form-submit:hover { filter: brightness(1.08); transform: translateY(-1px); background-position: 100% center; }
.form-note { margin-top: 1rem; font-size: .78rem; color: var(--muted); text-align: center; }
.hp { position: absolute; left: -9999px; width: 0; height: 0; opacity: 0; }
.form-success { display: none; text-align: center; padding: 2rem 1rem; }
.form-success.show { display: block; }
.form-success .fs-title { font-family: 'Oswald', sans-serif; font-weight: 700; font-size: 1.5rem; letter-spacing: .04em; text-transform: uppercase; color: var(--gold); margin-bottom: .6rem; }
.form-success .fs-sub { color: rgba(241,236,224,.7); font-size: 1rem; line-height: 1.6; }
.agegate { position:fixed; inset:0; z-index:5000; display:none; align-items:center; justify-content:center; padding:1.5rem; background:rgba(6,7,10,.86); backdrop-filter:blur(6px); }
.agegate.show { display:flex; }
.ag-card { position:relative; max-width:440px; width:100%; background:linear-gradient(160deg,var(--panel),var(--ink-2)); border:1px solid var(--line); border-radius:14px; padding:2.2rem 1.8rem 1.8rem; text-align:center; box-shadow:0 24px 70px rgba(0,0,0,.6); }
.ag-badge { display:inline-block; font-family:'Oswald',sans-serif; font-weight:700; font-size:.9rem; letter-spacing:.1em; color:var(--ink); background:var(--teal-lt); padding:.25rem .7rem; border-radius:6px; margin-bottom:1rem; }
.ag-card h2 { font-family:'Oswald',sans-serif; font-weight:700; font-size:1.4rem; letter-spacing:.04em; text-transform:uppercase; color:var(--gold); margin-bottom:.9rem; }
.ag-card p { color:rgba(241,236,224,.78); font-size:.98rem; line-height:1.6; margin-bottom:.8rem; }
.ag-card .ag-confirm { color:var(--paper); font-weight:600; }
.ag-actions { display:flex; flex-direction:column; gap:.6rem; margin-top:1.4rem; }
.ag-yes { font-family:'Oswald',sans-serif; font-weight:600; font-size:.82rem; letter-spacing:.14em; text-transform:uppercase; color:var(--ink); border:none; border-radius:7px; padding:.85rem; cursor:pointer; background:linear-gradient(100deg,var(--gold-dk),var(--gold-lt)); transition:filter .2s; }
.ag-yes:hover { filter:brightness(1.08); }
.ag-no { font-family:'Oswald',sans-serif; font-weight:500; font-size:.72rem; letter-spacing:.16em; text-transform:uppercase; color:var(--muted); background:none; border:1px solid var(--line); border-radius:7px; padding:.7rem; cursor:pointer; transition:color .2s,border-color .2s; }
.ag-no:hover { color:var(--paper); border-color:rgba(224,187,82,.5); }



/* --- CANON: the ten power-type colours -----------------------------------------
   Fixed by F.C. Marrow, 4 Aug 2026. These are canon, not decoration: the
   games, the card game and the reader all key off them, so a power type is the
   same colour wherever it appears and this block is where that is decided.
   Change a value here and it changes everywhere, which is the point — do not
   hardcode a power-type colour anywhere else.

   Each is given twice: a hex for straightforward use, and the same colour as
   an unwrapped RGB triple, because the site's idiom is rgba() at whatever
   opacity a glow or a wash needs — rgba(var(--lus-arcane-rgb), .14).

   Tuned two ways. Every one has to stay legible as small text on #0a0b0d, and
   every one has to stay separable from its nearest neighbour: Elemental red
   against Kinetic's terracotta, Dynamic's saturated blue against Cosmic's pale
   sky, Harmonic's warm white against Mechanical's cool grey. Those three pairs
   are the ones that collapse if anybody "cleans up" the values later.
   ------------------------------------------------------------------------- */
:root {
  --lus-genetic:    #3ec46f;  --lus-genetic-rgb:    62,196,111;   /* green            */
  --lus-divine:     #e0bb52;  --lus-divine-rgb:     224,187,82;   /* gold — the site's */
  --lus-kinetic:    #b06a41;  --lus-kinetic-rgb:    176,106,65;   /* reddish brown    */
  --lus-harmonic:   #f2efe6;  --lus-harmonic-rgb:   242,239,230;  /* white            */
  --lus-arcane:     #e569b4;  --lus-arcane-rgb:     229,105,180;  /* pink             */
  --lus-dynamic:    #4f8ef2;  --lus-dynamic-rgb:    79,142,242;   /* blue             */
  --lus-elemental:  #e8452f;  --lus-elemental-rgb:  232,69,47;    /* red              */
  --lus-abyssal:    #9d63e8;  --lus-abyssal-rgb:    157,99,232;   /* purple           */
  --lus-cosmic:     #74c9f7;  --lus-cosmic-rgb:     116,201,247;  /* light blue       */
  --lus-mechanical: #b9c4cf;  --lus-mechanical-rgb: 185,196,207;  /* silver           */
}

/* --- Wearing the canon colours ---------------------------------------------
   lustre.html's ten cards and the lustre panel on heroes.html were both built
   against one teal --accent plus a few hardcoded rgba(31,154,166) glows. Each
   surface now gets handed its own reading's colour instead, which is why the
   values above are tokens rather than literals — this is the only place that
   has to know how a card is painted.

   --lus-rgb is the local hook: everything that used to hardcode the teal now
   reads it, so a card, its border, its glow and its name all move together.
   Fallback stays teal for anything that has not been given a reading.

   Not recoloured: the emblem art (.lu-ic, .lu-emb, .lustre-emblem) is a webp
   set as a background-image from inline markup, so CSS cannot remap it to a
   mask from out here. The jump strip's emblems ARE masks and do recolour. */
#lu-genetic, .lustre-frame[data-lus="genetic"], .sc-lus-genetic {
  --accent: var(--lus-genetic); --accent-dim: rgba(var(--lus-genetic-rgb), .55);
  --lus-rgb: var(--lus-genetic-rgb);
}
#lu-divine, .lustre-frame[data-lus="divine"], .sc-lus-divine {
  --accent: var(--lus-divine); --accent-dim: rgba(var(--lus-divine-rgb), .55);
  --lus-rgb: var(--lus-divine-rgb);
}
#lu-kinetic, .lustre-frame[data-lus="kinetic"], .sc-lus-kinetic {
  --accent: var(--lus-kinetic); --accent-dim: rgba(var(--lus-kinetic-rgb), .55);
  --lus-rgb: var(--lus-kinetic-rgb);
}
#lu-harmonic, .lustre-frame[data-lus="harmonic"], .sc-lus-harmonic {
  --accent: var(--lus-harmonic); --accent-dim: rgba(var(--lus-harmonic-rgb), .55);
  --lus-rgb: var(--lus-harmonic-rgb);
}
#lu-arcane, .lustre-frame[data-lus="arcane"], .sc-lus-arcane {
  --accent: var(--lus-arcane); --accent-dim: rgba(var(--lus-arcane-rgb), .55);
  --lus-rgb: var(--lus-arcane-rgb);
}
#lu-dynamic, .lustre-frame[data-lus="dynamic"], .sc-lus-dynamic {
  --accent: var(--lus-dynamic); --accent-dim: rgba(var(--lus-dynamic-rgb), .55);
  --lus-rgb: var(--lus-dynamic-rgb);
}
#lu-elemental, .lustre-frame[data-lus="elemental"], .sc-lus-elemental {
  --accent: var(--lus-elemental); --accent-dim: rgba(var(--lus-elemental-rgb), .55);
  --lus-rgb: var(--lus-elemental-rgb);
}
#lu-abyssal, .lustre-frame[data-lus="abyssal"], .sc-lus-abyssal {
  --accent: var(--lus-abyssal); --accent-dim: rgba(var(--lus-abyssal-rgb), .55);
  --lus-rgb: var(--lus-abyssal-rgb);
}
#lu-cosmic, .lustre-frame[data-lus="cosmic"], .sc-lus-cosmic {
  --accent: var(--lus-cosmic); --accent-dim: rgba(var(--lus-cosmic-rgb), .55);
  --lus-rgb: var(--lus-cosmic-rgb);
}
#lu-mechanical, .lustre-frame[data-lus="mechanical"], .sc-lus-mechanical {
  --accent: var(--lus-mechanical); --accent-dim: rgba(var(--lus-mechanical-rgb), .55);
  --lus-rgb: var(--lus-mechanical-rgb);
}

.lu-card, .lustre-frame { --lus-rgb: 31,154,166; }
.lu-card {
  border-color: rgba(var(--lus-rgb), .45);
  background: linear-gradient(160deg, rgba(var(--lus-rgb), .12), rgba(11,13,17,.88));
  box-shadow: 0 0 26px rgba(var(--lus-rgb), .14);
}
.lu-card:hover  { border-color: rgba(var(--lus-rgb), .85); box-shadow: 0 0 42px rgba(var(--lus-rgb), .26); }
.lu-card:target { border-color: rgba(var(--lus-rgb), .9);  box-shadow: 0 0 46px rgba(var(--lus-rgb), .34); }
.lu-card .lu-ic { box-shadow: 0 0 14px rgba(var(--lus-rgb), .2); }
/* the name is the fastest read on the card, so it is what carries the colour */
.lu-card .lu-name { color: var(--accent); }

.lustre-frame {
  border-color: rgba(var(--lus-rgb), .5);
  background: linear-gradient(160deg, rgba(var(--lus-rgb), .14), rgba(11,13,17,.85));
  box-shadow: 0 0 24px rgba(var(--lus-rgb), .16);
}
.lustre-frame .lustre-type { color: var(--accent); text-shadow: 0 0 12px rgba(var(--lus-rgb), .4); }

/* A double reading is two colours, so the panel is two colours rather than
   picking a winner. The wash ramps from one to the other through the ink in
   the middle, the glow splits left and right, and the name is clipped to the
   same ramp — the same background-clip trick the hero titles use.

   The border is a masked ::after rather than border-image, which ignores
   border-radius and would square off the corners. The frame's own border goes
   transparent so the ring replaces it instead of doubling it. Single readings
   never match .is-dual and are left exactly as they were. */
.lustre-frame.is-dual {
  border-color: transparent;
  background: linear-gradient(135deg, rgba(var(--lus-a-rgb), .20), rgba(11,13,17,.86) 52%, rgba(var(--lus-b-rgb), .20));
  box-shadow: -7px 0 22px rgba(var(--lus-a-rgb), .16), 7px 0 22px rgba(var(--lus-b-rgb), .16);
}
.lustre-frame.is-dual::after {
  content: ''; position: absolute; inset: 0; border-radius: 8px; padding: 1px; pointer-events: none;
  background: linear-gradient(135deg, var(--lus-a), var(--lus-b));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.lustre-frame.is-dual .lustre-type {
  background: linear-gradient(100deg, var(--lus-a) 18%, var(--lus-b) 82%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  /* a glow behind transparent-filled text prints straight through it */
  text-shadow: none;
}



/* --- Stop the page shifting sideways when a modal opens ---------------------
   Opening an overlay locks the page with body{overflow:hidden}, which removes
   the scrollbar. That widens the viewport by the scrollbar's width, so centred
   content jumps sideways and full-width fixed elements grow. Measured on
   multiverse.html at 1440px opening the world zoom:

       clientWidth   1425 -> 1440   (15px scrollbar removed)
       hero title     443 -> 450    (centred content jumps 7px right)
       nav right     1425 -> 1440   (fixed nav widens 15px)

   Reserving the gutter permanently means losing the scrollbar changes nothing,
   so there is no shift to see. Verified: with this rule the title and nav stay
   at 443 / 1425 through open and close.

   heroes.html is excluded. It sets body{overflow:hidden} for its entire life,
   so it has no scrollbar to lose and cannot jitter — but a reserved gutter
   would pull its full-viewport frame 8px in from the right edge (measured:
   .screen right 1412 -> 1404). :has(.select-bar) identifies that page.

   Harmless on mobile and on any platform with overlay scrollbars, where the
   gutter is zero-width anyway. */
html:not(:has(.select-bar)) {
  scrollbar-gutter: stable;
}


/* --- One page-title height, site-wide --------------------------------------
   Both header families are defined per page, and they had drifted: plain
   .page-hero sat at 8rem (kick 128 / h1 161) while the hub .sp-hero pages sat
   at calc(66px + 5rem) (kick 146 / h1 179), and the centred .sp-hero pages
   floated wherever their content put them — Collaborators 124, Courses 118,
   Spotlight 186. Moving between pages made the title visibly jump.

   Anchoring both families to the same offset gives every page kick 146 / h1
   179. These sheets load after each page's inline <style>, so the longhand
   here beats the shorthand there at equal specificity.

   Deliberately NOT included: .pod-hero and index's .hero. Those are the
   full-screen cinematic headers with an 89.6px title rather than 67.2px —
   a different treatment on purpose, not drift. */
.page-hero { padding-top: calc(66px + 5rem); }
.sp-hero   { justify-content: flex-start; padding-top: calc(66px + 5rem); }

/* --- One title height across the .pod-hero hubs ----------------------------
   The five cinematic hubs — Multiverse, Characters, Lustre, Locations, Lore —
   centre their hero block, so the title lands wherever the rest of the block
   leaves it. Characters carries five category cards and Lustre two quiz
   buttons, which pushed their titles up to 225 and 228; Locations and Lore,
   carrying almost nothing, sat at 311 and 313. Moving between hubs made the
   title jump by nearly 90px.

   Anchoring the block to the top instead of centring it puts every title at
   Multiverse's height, which is the one to match. min-height:100vh still lets
   a hero grow past the viewport where the content needs it, so the taller
   hubs are not clipped on a short screen.

   Lustre is the only hub with no status badge, so the band a badge occupies
   is reserved there — without it, its title rides that much higher than
   everyone else's. The selector fires only when nothing sits between the kick
   and the title, so a badge added to Lustre later switches the reserve off by
   itself. 69px rather than the 53px the badge is worth: this margin collapses
   with the kick's own 1.2rem bottom margin, and only the excess counts. */
.pod-hero { align-items: flex-start; padding-top: 196px; }
.pod-content > .pod-kick + .pod-title { margin-top: 69px; }
@media (max-width: 760px) {
  .pod-hero { padding-top: calc(66px + 5rem); }
}

/* --- Multiverse on a phone: the constellation moves below the copy ---------
   The seven orbs are placed at percentage coordinates across the hero, which
   reads as a constellation on a wide screen and as seven planets sitting on
   top of the title on a narrow one. They are not decoration — each one is the
   tap target that opens its world — so on mobile the field stops being a
   backdrop and becomes a row of worlds under the text.

   Ordered 1 through 7 rather than in DOM order, and the labels come back on:
   scattered they collided, in a grid they are what makes an orb identifiable
   before you tap it. Lives here rather than in the page because it has to beat
   the .pod-hero rule above, which this sheet sets outside any media query. */
@media (max-width: 760px) {
  /* width:100% because a column flex sizes its items to max-content on the
     cross axis — without it the copy stops shrinking and runs off the screen */
  .pod-hero    { flex-direction: column; align-items: center; }
  .pod-content { order: 1; width: 100%; }
  .mv-worlds {
    order: 2; position: static; inset: auto; width: 100%; pointer-events: auto;
    display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start;
    /* the column gap is what holds the row at four: five orbs plus four of
       these no longer fit a 430px screen, four plus three do */
    gap: 2.3rem 2rem; margin: 2.6rem 0 .5rem;
  }
  /* out of absolute placement, so the -50%/-50% centring the bob carries has
     to go with it — see worldBobFlow */
  .mv-worlds .world   { position: static; left: auto; top: auto; transform: none; animation-name: worldBobFlow; }
  .mv-worlds .w-label { display: block; bottom: -1.15rem; }

  .mv-worlds .world:nth-child(7) { order: 1; }  /* Primus  */
  .mv-worlds .world:nth-child(1) { order: 2; }  /* Secundus */
  .mv-worlds .world:nth-child(2) { order: 3; }  /* Concursus */
  .mv-worlds .world:nth-child(3) { order: 4; }  /* Futurus */
  .mv-worlds .world:nth-child(4) { order: 5; }  /* Truculentus */
  .mv-worlds .world:nth-child(5) { order: 6; }  /* Exitus */
  .mv-worlds .world:nth-child(6) { order: 7; }  /* Negativus */
}
@keyframes worldBobFlow { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }

/* store.html is the one page with its own header class. It sat at 7rem with a
   .8rem eyebrow and a 3.4rem-capped title (54.4px against everyone else's
   67.2px). Brought onto the same metrics rather than left as a one-off. */
.head            { padding-top: calc(66px + 5rem); }
.head .eyebrow   { font-size: .74rem; letter-spacing: .5em; }
.head h1         { font-size: clamp(2.4rem, 7vw, 4.2rem); margin: 1rem 0 0; }


/* --- Refreshing must not land you at the bottom of the page ----------------
   Chrome selects a scroll anchor inside these heroes, then compensates as the
   late-loading web fonts reflow them. On a page barely taller than the
   viewport that compensation clamps straight to the bottom: ethos.html at
   1440x900 is a 1107px document, and every F5 landed at y=207 — exactly the
   end of the page — while a first load stayed at 0.

   Bisected against the live site. overflow-anchor is the cause: disabling it
   fixes it, and so does history.scrollRestoration='manual'. Forcing
   scroll-behavior:auto and swapping font-display to swap both still reproduced
   it, so neither is involved.

   Scoped to the headers rather than <html> so anchoring still works where it
   earns its keep — the long manifesto and lore pages. */
.page-hero, .sp-hero, .pod-hero, .head { overflow-anchor: none; }

/* ── Hero scroll cue ─────────────────────────────────────────────────────────
   The "keep going" affordance pinned to the bottom of a full-height hero: a
   label and an animated chevron. It started as inline CSS on the home page and
   now runs on the spotlight hero too, so it lives here rather than being
   copied. Any hero that is position:relative can carry one.

   Markup:
     <a class="hero-scroll" href="#target" aria-label="...">
       Label
       <span class="chev" aria-hidden="true"></span>
     </a>
   ────────────────────────────────────────────────────────────────────────── */
.hero-scroll {
  position: absolute; left: 0; right: 0; bottom: 1.8rem; z-index: 3;
  text-align: center; text-decoration: none;
  font-family: 'Oswald', sans-serif; font-size: .68rem; letter-spacing: .32em;
  text-transform: uppercase; color: rgba(241,236,224,.82);
  /* hero art sits behind this at full opacity on desktop — without a shadow the
     cue disappears into whichever character happens to be underneath it */
  text-shadow: 0 2px 12px rgba(6,7,10,.95), 0 0 30px rgba(6,7,10,.85);
  transition: color .2s;
  animation: fadeUp .6s cubic-bezier(.22,1,.36,1) .95s both;
}
.hero-scroll:hover { color: #f7de92; }
/* The cue is shared, so its entrance animation has to be too — spotlight.html
   had no fadeUp of its own and the element would have sat at opacity 0. */
/* Identical to the home page's definition on purpose. A later sheet redefining
   a keyframe name replaces it outright, so any drift here would silently
   retune every other fadeUp on index.html. */
@keyframes fadeUp { 0% { opacity: 0; transform: translateY(24px); } 100% { opacity: 1; transform: translateY(0); } }
.hero-scroll .chev {
  display: block; margin: .55rem auto 0; width: 14px; height: 14px;
  border-right: 2px solid #e0bb52; border-bottom: 2px solid #e0bb52;
  transform: rotate(45deg); animation: chev 1.8s ease-in-out infinite;
}
@keyframes chev {
  0%, 100% { transform: rotate(45deg) translate(0, 0);   opacity: .5; }
  50%      { transform: rotate(45deg) translate(4px, 4px); opacity: 1; }
}
@media (max-width: 768px) { .hero-scroll { bottom: 1.2rem; } }

/* Motion here is decorative; a bouncing arrow is exactly what someone with
   vestibular sensitivity turned the setting off for. */
@media (prefers-reduced-motion: reduce) {
  .hero-scroll, .hero-scroll .chev { animation: none; }
}


/* --- The Support button is blue, site-wide ----------------------------------
   Support and Join Us sat next to each other in the identical gold pill, so two
   different asks read as one button drawn twice. Support takes the site blue —
   the same teal gradient as "What We Offer" and "What It Gives", lifted from
   .door-teal .door-btn.solid so it is the SAME blue and not merely a blue.
   (#4a90ff was tried first and rejected: it is a true blue nothing else on the
   site uses, so the button read as foreign rather than as part of the set.)

   TO CHANGE THE SHADE, edit the four --support-* values below and nothing else.
   Every Support button on the site is drawn from them.

   Applied by adding `btn-support` next to whatever base class a Support button
   already carries — btn-join (the nav, 79 of them), hero-join, eth-cta, si-btn,
   or door-btn ghost. This sheet loads after every page's inline <style>, so at
   equal specificity these rules win, and longhand beats the base class's
   `border:` shorthand. Only colour is overridden: the geometry, the sheen
   sweep and the hover transitions still come from the base class. */
:root {
  --support:     #1f9aa6;
  --support-lt:  #5fd8e2;
  --support-dk:  #17727b;
  --support-ink: #0a0b0d;
}

.btn-support {
  color: var(--support-ink);
  border-color: var(--support);
  background: linear-gradient(100deg, var(--support-dk), var(--support) 40%, var(--support-lt) 54%, var(--support) 68%, var(--support-dk));
  background-size: 200% auto;
  box-shadow: 0 2px 16px rgba(31, 154, 166, .32);
}

/* Two buttons sit larger and carry a deeper drop — matched so they do not
   flatten against the gold ones beside them. */
.hero-join.btn-support { box-shadow: 0 10px 30px rgba(31, 154, 166, .34); }
.si-btn.btn-support    { box-shadow: 0  8px 26px rgba(31, 154, 166, .34); }

/* The mobile CTA row repaints .btn-join's colour at 0,3,2, so match that
   weight rather than trying to out-specify it from .btn-support alone. */
.nav-links li.cta-mobile a.btn-support { color: var(--support-ink); }

/* The home page "doors" Support is an outline button, not a solid one. It takes
   the teal as an outline so it stays the quieter of the two actions there. */
.door-btn.btn-support {
  color: var(--support-lt);
  border-color: rgba(63, 198, 210, .5);
  background: transparent;
  box-shadow: none;
}
.door-btn.btn-support:hover {
  color: var(--teal-lt);
  border-color: rgba(63, 198, 210, .85);
  background: rgba(31, 154, 166, .10);
}


/* --- Dark is not a preference here ------------------------------------------
   The site has no light theme and no toggle, so the only thing that could turn
   it light is something outside the page: a browser's auto-dark/auto-light
   pass, or an extension. `color-scheme: only dark` is the standard way to say
   "this document supports exactly one scheme" — it stops the UA re-theming
   form controls, scrollbars, and its own injected surfaces, and it opts out of
   Chrome's automatic theming entirely.

   Every page also carries <meta name="color-scheme" content="only dark"> so the
   browser knows before a stylesheet has even parsed. This rule is the belt to
   that meta's braces, and it covers the pages whose own <style> block never had
   the declaration. */
html {
  color-scheme: only dark;
}


/* --- "Tell me when there is something to read" ------------------------------
   The one exit that costs a visitor nothing. Everything else on this site asks
   for money or a Patreon account; this asks for an email and promises to use it
   once. Markup is real HTML rather than injected, so it is crawlable and it
   still renders if the script never loads — only the submit needs JS.
   Wiring is in notify.js. */
.notify {
  max-width: 720px; margin: 0 auto; padding: 2.4rem 1.8rem;
  background: linear-gradient(160deg, rgba(31,154,166,.10), rgba(11,13,17,.86));
  border: 1px solid rgba(31,154,166,.34); border-radius: 14px; text-align: center;
}
.notify h2 {
  font-family: 'Oswald', sans-serif; font-weight: 700;
  font-size: clamp(1.35rem, 3.4vw, 1.9rem); letter-spacing: .03em;
  text-transform: uppercase; color: #f1ece0; margin-bottom: .8rem;
}
.notify p.notify-lead {
  max-width: 48ch; margin: 0 auto 1.5rem; font-size: 1.02rem; line-height: 1.7;
  color: rgba(241,236,224,.76);
}
.notify-form { display: flex; gap: .6rem; justify-content: center; flex-wrap: wrap; }
.notify-form input {
  flex: 1 1 280px; max-width: 360px; min-width: 0;
  background: rgba(255,255,255,.05); border: 1px solid rgba(224,187,82,.28);
  border-radius: 100px; padding: .85rem 1.2rem; color: #f1ece0;
  font-family: 'Rajdhani', sans-serif; font-size: 1rem; font-weight: 500;
}
.notify-form input:focus { outline: none; border-color: rgba(224,187,82,.62); }
.notify-form input::placeholder { color: rgba(241,236,224,.42); }
.notify-form button {
  flex: none; font-family: 'Oswald', sans-serif; font-weight: 600; font-size: .76rem;
  letter-spacing: .16em; text-transform: uppercase; color: #0a0b0d; border: 0;
  border-radius: 100px; padding: .85rem 1.7rem; cursor: pointer;
  background: linear-gradient(100deg, #b88f37, #ddb64f 40%, #f5e089 54%, #ddb64f 68%, #b88f37);
  background-size: 200% auto; box-shadow: 0 2px 16px rgba(224,187,82,.3);
  transition: background-position .4s, filter .2s, transform .15s;
}
.notify-form button:hover { background-position: 100% center; filter: brightness(1.06); transform: translateY(-1px); }
.notify-form button:disabled { opacity: .6; cursor: default; transform: none; }
.notify-ok {
  font-size: 1.02rem; line-height: 1.7; color: #f7de92; margin: 0;
}
.notify-note { margin-top: .9rem; font-size: .88rem; line-height: 1.5; color: rgba(241,236,224,.5); }
.notify-note a { color: #f7de92; }

.notify-wrap { position: relative; z-index: 1; padding: 0 1.5rem 5rem; }


/* --- Reserve every image's box before it loads ------------------------------
   An <img> with no aspect reserved is zero-height until its bytes arrive and
   then snaps to size, which is the stretch-and-jump on first paint. These are
   the images whose ratio is fixed and known, so the box can be final before
   anything has downloaded.

   Deliberately not a blanket img rule: plenty of images here are sized by their
   container with object-fit and a global aspect-ratio would fight that. */
.tier-banner            { aspect-ratio: 920 / 400; }
.sl-soon-img img,
.cbb-soon-img img       { aspect-ratio: 3 / 4; }
.cx-cover               { aspect-ratio: 2 / 3; }
.cx-banner-art img      { aspect-ratio: 3 / 1; }
.tm-shot                { aspect-ratio: 1; }
.catrow-h img           { aspect-ratio: 10 / 1; }
/* and a floor under the rest: an image can never overflow its column. Art that
   is deliberately larger than its box opts out — the multiverse planets are
   drawn at 120% of their orb so the disc overhangs it, and clamping them to
   100% while they are still offset by -10% pushes every planet off-centre. */
img { max-width: 100%; }
.orb .w-art,
.hero-figure { max-width: none; }


/* --- The header collapses before it squeezes the logo ----------------------
   Measured on index.html: the wordmark renders at 0.65-0.94 of its own aspect
   between roughly 1215 and 1265px while the emblem stays true. The desktop nav
   needs more room than it has through that band and the brand is what gives
   way, so the mobile header takes over at 1280 — above the whole band.

   These are the pages' own 1200px rules re-emitted at the wider width. Doing it
   here rather than editing the same media query inside 52 copied headers means
   one place to change it again. The 1200px blocks still exist in the pages and
   are simply redundant now.

   .nav-brand img is flex:none as well, so the logo cannot be compressed even
   if this breakpoint moves. */
.nav-brand img { flex: none; }
@media (max-width: 1280px) {
      .nav-toggle { display: block; justify-self: end; }
      .nav-right { display: none; }
      .nav-links { position: fixed; top: 66px; left: 0; right: 0; flex-direction: column; gap: 0; background: rgba(10,11,13,0.98); border-bottom: 1px solid var(--line); padding: .5rem 0; transform: translateY(-120%); transition: transform .3s ease; height: auto; justify-self: initial; max-height: calc(100vh - 66px); overflow-y: auto; }
      nav.open .nav-links { transform: translateY(0); }
      .nav-links > li { display: block; }
      .nav-links > li > a { width: 100%; justify-content: center; padding: .9rem; }
      .nav-links > li > a::after { display: none; }
      .nav-links .sub { position: static; opacity: 1; visibility: visible; transform: none; display: none; flex-direction: column; gap: 0; background: rgba(0,0,0,.28); border: 0; box-shadow: none; backdrop-filter: none; padding: 0; }
      .nav-links li.open .sub { display: flex; }
      .sub a { width: 100%; justify-content: center; color: var(--muted); padding: .7rem; }
      .nav-links li.open .parent .caret { transform: rotate(180deg); }
      nav.open .nav-toggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
      nav.open .nav-toggle span:nth-child(2) { opacity: 0; }
      nav.open .nav-toggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
    }
@media (max-width: 1280px) {
      .nav-links > li.cta-mobile { display:block; padding:.4rem 1.1rem; }
      .nav-links li.cta-mobile:first-of-type { padding-top:.8rem; }
      .nav-links li.cta-mobile a { width:100%; justify-content:center; padding:.85rem; border-radius:100px; }
      .nav-links li.cta-mobile a::after { display:none !important; }
      .nav-links li.cta-mobile a.btn-join { color:var(--ink); }
      .nav-links li.cta-mobile a.btn-login { color:var(--paper); }
    }


/* --- No phantom menu when the header collapses -----------------------------
   .nav-links is translateY(-120%) with a transform transition on mobile. When
   the media query first matches — resizing past 1280 — that property changes
   from none to -120% and the browser animates it, which looks exactly like the
   menu opening and shutting by itself.

   Applying the closed state with no transition means there is nothing to
   animate at the boundary. Opening still slides, because nav.open carries the
   transition. Closing snaps instead of sliding: a trade worth making, since
   nobody has complained about a fast close and the phantom fired on every
   crossing. */
@media (max-width: 1280px) {
  .nav-links { transition: none; }
  nav.open .nav-links { transition: transform .3s ease; }
}

/* --- "More" is a label, not a destination ----------------------------------
   Every other dropdown parent in the nav is a real page; More is not a
   section, so it carries no href. It is still an <a> so every `.nav-links > li
   > a` rule keeps applying, and role="button" + tabindex keep it reachable by
   keyboard, which an href-less anchor otherwise is not.

   Cursor splits on the breakpoint: below 1280 it is a tap target that opens
   the submenu, above it the submenu opens on hover and there is nothing to
   click, so a pointer would be a lie. */
.nav-nolink { cursor: default; }
@media (max-width: 1280px) { .nav-nolink { cursor: pointer; } }


/* --- Keyboard focus, and a way past the nav ---------------------------------
   There was no focus style anywhere: site.css defined none, and two pages out
   of fifty-one defined their own. Anybody navigating by keyboard could not see
   where they were, on any page. :focus-visible rather than :focus so a mouse
   click never draws a ring.

   The skip link is the other half. Every page puts a seven-item nav with
   dropdowns ahead of the content, and without this a keyboard user tabs
   through all of it on every single page. It is off-screen until focused. */
:focus-visible {
  outline: 3px solid var(--gold, #e0bb52);
  outline-offset: 3px;
  border-radius: 2px;
}
/* components that already draw their own focus treatment keep it */
.notify-form input:focus-visible,
.fc-help-f input:focus-visible { outline-offset: 0; }

.fc-skip {
  position: fixed; top: -120px; left: 14px; z-index: 5000;
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase; text-decoration: none;
  color: #0a0b0d; background: linear-gradient(100deg, #b88f37, #ddb64f 45%, #f5e089 60%, #ddb64f);
  padding: .7rem 1.1rem; border-radius: 100px;
  box-shadow: 0 12px 30px rgba(0,0,0,.5);
  transition: top .18s ease;
}
.fc-skip:focus { top: 14px; }
@media (prefers-reduced-motion: reduce) { .fc-skip { transition: none; } }
