/* platformgate.css - THE PLATFORM CHOOSER and its header switcher.
   =========================================================================
   A fleet feature, OFF by default, switched on by one manifest block
   (`web.platformgate`). See assemble.py::platformgate_config for the switch
   and frontend/js/platformgate.js for the runtime half.

   WHY IT EXISTS (New Age Dinos, 2026-08-24): one domain now carries TWO
   servers - the Legacy island the site was built for and a new Evrima
   island. A visitor's first question is no longer "what is this" but "which
   one am I here for", and a site that answers that with a nav entry buried
   among nine others has not answered it.

   DIRECTION CONTRACT
   ---------------------------------------------------------------------------
   THESIS: the choice is the WHOLE SCREEN, once, and then it is a pill.
     Nothing else is on the page while it is up, because a chooser competing
     with a hero is a chooser people scroll past.
   IT IS AN INSTRUMENT, NOT A SPLASH (rebuilt 2026-08-24 on the owner's
     "make it look more professional and advanced"). The first build was two
     words on flat charcoal with a hairline between them and a small outlined
     ENTER - honest, and unfinished-looking. What carries "professional" is
     not more colour, it is EVIDENCE OF A SYSTEM: a registration frame on the
     stage, an index numeral per plate, one tracked micro-label, a seam with
     real light in it, and motion that arrives and leaves on purpose. Every
     one of those is house furniture this site already owns (terminal.css's
     tick marks and tracking), spent here at full size.
   THE SEAM IS DIAGONAL, and that is the one structural move. Two rectangles
     side by side read as a table of options; a diagonal cut reads as one
     surface torn in half, which is what two eras of one game are. The cut
     is drawn with `clip-path` on each half, so the two panels genuinely
     interlock rather than being two boxes with a slanted graphic between.
   ★ THE SEAM IS NOW LIT, AND IT IS LIT PER HALF. A single seam element
     centred in the grid would desync the moment hover moves the columns -
     the cut belongs to the panels, so each half draws its own narrow
     parallelogram along its OWN cut with the identical run. They meet on one
     line at every width and at every hover state by construction.
   HOVER WIDENS THE PANEL rather than lighting it: the grid columns move,
     so the panel the pointer is on takes territory from the other one. The
     accent glow rides along, but the SIZE is the signal - it survives a
     colourblind reader and a dimmed screen.
   ONE WORD AND ONE LINE. The display face carries the era's name in caps;
     underneath it is a single short sentence and nothing else. Design copy
     is short (house law) - a paragraph here would be an essay standing
     between a player and the server they came for. The index numeral is a
     COUNTER, not copy: no owner types it and no translator has to.
   THE ART IS A SILHOUETTE, not a photograph: it is `--art-*` owner data
     drawn at low contrast behind the word, so the word always wins. An owner
     who supplies none gets an empty span, and the plate is designed to be
     complete without it - the frame, the numeral and the seam are the
     picture.

   NO-JS: `platformgate.js` is what removes the gate, so with scripting off
     the gate stays up and IS the page - two big links to the two platforms,
     which is exactly the fallback the design asks for. Nothing here hides
     content behind a reveal that may never run. ★ Every ARRIVAL animation is
     keyed on `html.pgate-open`, which only the script sets, so a reader with
     no scripting gets the finished picture rather than a plate parked at
     opacity 0 waiting for a frame that never comes.

   THE PRICE OF THE LAYER: tokens only (this sheet invents no colour and is
   scanned for it by tests/frontend/run_all.py::TOKEN_SHEETS), no !important,
   and every selector is under `.pgate` / `.pswitch` so a site without the
   feature is unaffected by the sheet even if it somehow shipped.
   ========================================================================= */

/* ---- the stage --------------------------------------------------------- */
/* Fixed rather than absolute: the document behind it may be any height, and
   a chooser that scrolls off is a chooser that can be scrolled past. */
.pgate {
  /* THE CUT'S RUN, IN ONE PLACE. Both halves and both seam bars read it, and
     the phone breakpoint overwrites it once - so the geometry can never
     disagree with itself the way four hand-typed 3.2vw would. */
  --pg-run: 3.2vw;
  --pg-seam: 3px;
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background:
    radial-gradient(120% 78% at 50% 0%, var(--tm-lift), transparent 62%),
    radial-gradient(90% 60% at 50% 118%, var(--tm-lift-soft), transparent 70%),
    var(--bg-deep);
  overscroll-behavior: contain;
  transition: opacity .34s ease;
}

/* THE VIGNETTE. One radial, no edges in it, no repeat: the house's SECOND
   ruling on this site's ground was that it carries no repeating geometry at
   all (terminal.css section 2, the owner's "it looks horrible" twice), and
   the chooser is a full-viewport ground like any other. So the stage is lit,
   never ruled - the measured marks live on the PLATES, where they are
   instrumentation on an object rather than wallpaper. */
.pgate::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background: radial-gradient(128% 88% at 50% 46%,
    transparent 42%, var(--veil-mid) 100%);
}

/* The document does not scroll while the choice is open. Set by the script
   on <html>, so it is impossible for the class to outlive the gate. */
html.pgate-open,
html.pgate-open body {
  overflow: hidden;
}

.pgate-head {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: clamp(20px, 4vh, 44px) var(--gut) 18px;
  text-align: center;
  z-index: 2;
}
.pgate-mark {
  width: clamp(52px, 8vw, 78px);
  height: auto;
}
.pgate-title {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: var(--tm-track);
  font-size: var(--tm-h-sec);
  color: var(--fg);
  margin: 0;
}
.pgate-sub {
  font-family: var(--font-ui);
  font-size: var(--fs-1);
  color: var(--fg-mute);
  margin: 0;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
}

/* THE RULE UNDER THE LOCKUP. An accent-lit centre falling away to nothing at
   both ends - one gradient, no repeat. A first draft ruled the house's tick
   pitch across it and it read as a SCRATCHED line rather than a measured
   one: at this width the ticks are sub-pixel and the browser dithers them
   into noise. The mark that says "drawn" is the clean falloff. */
.pgate-head::after {
  content: "";
  width: min(460px, 66vw);
  height: 2px;
  margin-top: 6px;
  background: linear-gradient(90deg,
    transparent, var(--tm-hot) 34%, var(--accent-hi) 50%,
    var(--tm-hot) 66%, transparent);
}

/* ---- the two halves ----------------------------------------------------- */
/* The grid columns ARE the hover feedback. 1fr/1fr at rest; the hovered
   column takes 1.14fr and the transition carries both. `min-height: 0` so a
   short viewport shrinks the panels instead of overflowing the fixed host. */
.pgate-halves {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 0;
  /* The numeral on each plate is a COUNTER: nothing in the markup, nothing
     for an owner to type and nothing for a catalog to translate. */
  counter-reset: pgate;
  transition: grid-template-columns .28s ease;
}
.pgate-halves:has(.pgate-half:first-child:hover),
.pgate-halves:has(.pgate-half:first-child:focus-visible) {
  grid-template-columns: 1.14fr .86fr;
}
.pgate-halves:has(.pgate-half:last-child:hover),
.pgate-halves:has(.pgate-half:last-child:focus-visible) {
  grid-template-columns: .86fr 1.14fr;
}

.pgate-half {
  position: relative;
  display: grid;
  place-content: center;
  gap: 10px;
  padding: clamp(18px, 5vw, 60px);
  text-align: center;
  text-decoration: none;
  color: var(--fg);
  border: 0;
  counter-increment: pgate;
  /* A FACE, NOT A FILL. The plate is lit from its own top edge and sinks to
     the ground, which is the same material every panel on this site is made
     of - and the grain is the house's, not a second texture invented here.
     ★ THE FIRST PASS PUT `--bg` AT THE FOOT AND THE PLATE VANISHED INTO THE
     STAGE: `--bg` and `--bg-deep` are four points apart on this owner, so a
     face that ran down to the ground colour was a face with no bottom edge.
     It sinks to `--surface` instead and the SEPARATION from the stage is the
     top light, which is what makes two plates read as objects standing on a
     ground rather than as two regions of it. */
  background:
    radial-gradient(112% 62% at 50% 0%, var(--tm-lift), transparent 60%),
    var(--grain),
    linear-gradient(178deg, var(--surface-3), var(--surface-2) 46%,
                    var(--surface));
  cursor: pointer;
  overflow: hidden;
  transition: background .28s ease, box-shadow .28s ease,
              transform .3s cubic-bezier(.4, 0, .2, 1), opacity .3s ease;
}

/* THE SEAM. The left half gives up its right edge, the right half gives up
   its left one, and the two cuts are complements - so the halves meet on one
   line with no gap and no overlap at any width. --pg-run is the run of the
   slant; a fixed pixel run would be a dramatic diagonal on a phone and a
   hairline on a desktop. */
.pgate-half:first-child {
  clip-path: polygon(0 0, 100% 0, calc(100% - var(--pg-run)) 100%, 0 100%);
}
.pgate-half:last-child {
  clip-path: polygon(var(--pg-run) 0, 100% 0, 100% 100%, 0 100%);
  margin-left: calc(var(--pg-run) * -1);
}

/* THE LIGHT IN THE CUT. A 2px parallelogram that follows each half's own
   slant exactly - same run, same direction - so the two lit edges land on
   one line however the columns are sized. The moving highlight is a second
   background layer travelling down the bar; it is a background-position
   animation on a 2px-wide strip, which costs nothing and is the detail that
   reads as a powered instrument rather than a printed one. */
.pgate-half::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  background:
    linear-gradient(180deg,
      transparent 0%,
      var(--tm-hot) 18%,
      var(--accent-hi) 50%,
      var(--tm-hot) 82%,
      transparent 100%),
    linear-gradient(180deg, var(--tm-hot), var(--tm-hot));
  background-size: 100% 46%, 100% 100%;
  background-position: 0 -60%, 0 0;
  background-repeat: no-repeat, no-repeat;
  opacity: .9;
}
.pgate-half:first-child::after {
  clip-path: polygon(
    calc(100% - var(--pg-seam)) 0,
    100% 0,
    calc(100% - var(--pg-run)) 100%,
    calc(100% - var(--pg-run) - var(--pg-seam)) 100%);
}
.pgate-half:last-child::after {
  clip-path: polygon(
    var(--pg-run) 0,
    calc(var(--pg-run) + var(--pg-seam)) 0,
    var(--pg-seam) 100%,
    0 100%);
}

.pgate-half:hover,
.pgate-half:focus-visible {
  background:
    radial-gradient(112% 62% at 50% 0%, var(--tm-warm), transparent 62%),
    var(--grain),
    linear-gradient(178deg, var(--surface-3), var(--surface-2) 46%,
                    var(--surface));
  box-shadow: inset 0 0 0 1px var(--tm-hot),
              inset 0 -120px 160px -90px var(--tm-warm);
  outline: none;
}

/* ★ A HOVER RULE THAT SETS `outline: none` AND SHARES ITS SELECTOR WITH
   :focus-visible TAKES THE KEYBOARD READER'S ONLY MARKER AWAY. The panels
   are the two controls on this screen; a reader arriving on Tab has to be
   able to see which one they are on. Restated as its own rule AFTER the
   shared one so it wins, and drawn as an inset ring because the plate is
   clipped - an outline outside a clip-path is invisible. */
.pgate-half:focus-visible {
  box-shadow: inset 0 0 0 var(--tm-tick-w) var(--accent-hi),
              inset 0 0 0 4px var(--tm-warm),
              inset 0 -120px 160px -90px var(--tm-warm);
}

/* THE INDEX NUMERAL. A GHOST FIGURE BEHIND THE WORD, not a chip in a corner.
   The first pass drew it as a bordered pill pinned to each plate's outer
   corner and the two of them read as debris - two small dark boxes at the
   far edges of a mostly empty screen, which is the opposite of the ask. Set
   at display size behind the era's name it does the job a plate number does
   on real equipment: it says this is one of a numbered set, and it fills the
   upper half of the plate that was empty.

   It is a COUNTER, so no owner types it, no catalog translates it, and it
   cannot disagree with the order the manifest declares. */
.pgate-half::before {
  content: counter(pgate, decimal-leading-zero);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -152%);
  font-family: var(--font-display);
  font-size: clamp(96px, 19vw, 250px);
  line-height: .78;
  letter-spacing: -.02em;
  color: var(--fg);
  opacity: .045;
  pointer-events: none;
  transition: opacity .32s ease, color .32s ease;
  z-index: 0;
}
.pgate-half:hover::before,
.pgate-half:focus-visible::before {
  color: var(--accent-hi);
  opacity: .13;
}

/* THE PLATE'S OWN BRACKETS. Two registration marks on each panel's OUTER
   corners - never the seam corners, where the diagonal would cut them in
   half. Drawn on `.pgate-art`, which is always in the markup (an owner with
   no silhouette ships it empty), so the frame does not depend on artwork
   this owner may never supply. */
.pgate-art::before,
.pgate-art::after {
  content: "";
  position: absolute;
  width: calc(var(--tm-tick) * 2.4);
  height: calc(var(--tm-tick) * 2.4);
  border: 0 solid var(--tm-hair);
  transition: border-color .28s ease;
}
.pgate-art::before {
  top: clamp(14px, 3vh, 32px);
  border-top-width: var(--tm-tick-w);
}
.pgate-art::after {
  bottom: clamp(14px, 3vh, 32px);
  border-bottom-width: var(--tm-tick-w);
}
.pgate-half:first-child .pgate-art::before,
.pgate-half:first-child .pgate-art::after {
  left: clamp(14px, 3vw, 40px);
  border-left-width: var(--tm-tick-w);
}
.pgate-half:last-child .pgate-art::before,
.pgate-half:last-child .pgate-art::after {
  right: clamp(14px, 3vw, 40px);
  border-right-width: var(--tm-tick-w);
}
.pgate-half:hover .pgate-art::before,
.pgate-half:hover .pgate-art::after,
.pgate-half:focus-visible .pgate-art::before,
.pgate-half:focus-visible .pgate-art::after {
  border-color: var(--tm-hot);
}

/* The silhouette. A REAL <img> rather than a background on a token, for two
   reasons that both bite: a background-image needs the URL in an inline
   style attribute (one more thing for a content policy to argue with), and
   an <img> can be lazy, sized, and skipped by a browser that decides not to
   fetch it - a decorative picture must never hold up the word behind it.
   An owner who supplies no art ships an empty span, which is correct rather
   than broken: the word and the line are the content. */
.pgate-art {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.pgate-art img {
  width: min(62%, 380px);
  height: auto;
  margin-top: 12%;
  opacity: .17;
  filter: grayscale(1) contrast(1.4);
  transition: opacity .28s ease, filter .28s ease, transform .5s ease;
}
.pgate-half:hover .pgate-art img,
.pgate-half:focus-visible .pgate-art img {
  opacity: .3;
  filter: grayscale(0) contrast(1.1);
  transform: scale(1.04);
}

.pgate-word {
  position: relative;
  display: block;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: clamp(38px, 7vw, 96px);
  line-height: .94;
  letter-spacing: .01em;
  color: var(--fg);
  margin: 0;
  /* CUT INTO THE PLATE rather than sitting on it: a lit top edge and a sunk
     bottom one, the same two-sided light the panels are made with. */
  text-shadow: 0 var(--tm-tick-w) 0 var(--edge-sink),
               0 calc(var(--tm-tick-w) * -1) 0 var(--edge-top);
  transition: color .28s ease, text-shadow .28s ease;
}

/* THE RULE UNDER THE NAME. The one piece of structure between the word and
   the sentence, so the stack reads as a composed block rather than three
   things that happen to be centred. It WIDENS on hover, which makes the
   panel's own accent travel with the pointer at a size the eye reads before
   it reads any colour. */
.pgate-word::after {
  content: "";
  display: block;
  width: 58px;
  height: 2px;
  margin: 16px auto 2px;
  background: var(--rule-accent);
  opacity: .55;
  transition: width .3s cubic-bezier(.4, 0, .2, 1), opacity .3s ease;
}
.pgate-half:hover .pgate-word::after,
.pgate-half:focus-visible .pgate-word::after {
  width: 128px;
  opacity: 1;
}
.pgate-half:hover .pgate-word,
.pgate-half:focus-visible .pgate-word {
  color: var(--accent-hi);
  text-shadow: 0 var(--tm-tick-w) 0 var(--edge-sink),
               0 0 42px var(--tm-warm);
}
.pgate-line {
  position: relative;
  font-family: var(--font-ui);
  font-size: var(--fs-2);
  color: var(--fg-dim);
  margin: 0;
}

/* THE CALL. An outlined chip at rest that FILLS on hover, with an arrow that
   travels - so the panel says where the click goes before the click happens.
   ★ NO RESTING `background`. A rule that sets a background AND a border is
   describing a SURFACE, and test_house_look.py::EveryCardLikeSurfaceIsRegistered
   refuses a page sheet that owns one without naming it in terminal.css's
   surface register. This is a call to action, not a record, so it must not be
   in that list - and the way to stay out of it is to not draw a plate. The
   hover state fills it, and that selector carries `.pgate-half`, which IS
   registered. */
.pgate-go {
  position: relative;
  justify-self: center;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-top: 20px;
  padding: 13px 30px;
  border: var(--tm-tick-w) solid var(--tm-hot);
  border-radius: var(--r-pill);
  font-family: var(--font-ui);
  font-size: var(--fs-1);
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--accent);
  overflow: hidden;
  transition: color .24s ease, background .24s ease, border-color .24s ease,
              box-shadow .24s ease;
}
/* The arrow. A glyph rather than an SVG because the whole mark is one
   character and an inline <svg> here would be markup an owner's copy has to
   step around. */
.pgate-go::after {
  content: "\2192";
  display: inline-block;
  transition: transform .24s cubic-bezier(.4, 0, .2, 1);
}
.pgate-half:hover .pgate-go,
.pgate-half:focus-visible .pgate-go {
  color: var(--bg-deep);
  background: var(--accent);
  border-color: var(--accent-hi);
  box-shadow: 0 0 34px -10px var(--tm-hot);
}
.pgate-half:hover .pgate-go::after,
.pgate-half:focus-visible .pgate-go::after {
  transform: translateX(5px);
}

.pgate-foot {
  position: relative;
  z-index: 2;
  padding: 10px var(--gut) clamp(16px, 3vh, 30px);
  text-align: center;
  font-family: var(--font-ui);
  font-size: var(--fs-1);
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--fg-mute);
}

/* ---- arrival ------------------------------------------------------------ */
/* ★ KEYED ON `html.pgate-open`, WHICH ONLY THE SCRIPT SETS. A reader with no
   scripting, or one whose answer is already cookied, never matches these
   rules and therefore never sees a plate parked at opacity 0. The class is
   added before the gate is shown and removed as it goes, so the arrival can
   only ever play on a real first visit. */
@keyframes pgate-rise {
  from { opacity: 0; transform: translateY(26px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes pgate-drop {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pgate-draw {
  from { opacity: 0; transform: scaleY(0); }
  to   { opacity: .9; transform: none; }
}
/* The highlight runs the length of the cut, twice, and stops. A seam that
   pulsed for ever would be a screen that never settles. */
@keyframes pgate-seam {
  from { background-position: 0 -60%, 0 0; }
  to   { background-position: 0 160%, 0 0; }
}

html.pgate-open .pgate-head {
  animation: pgate-drop .5s cubic-bezier(.2, .7, .3, 1) both;
}
html.pgate-open .pgate-half {
  animation: pgate-rise .58s cubic-bezier(.2, .7, .3, 1) both;
}
html.pgate-open .pgate-half:first-child { animation-delay: .08s; }
html.pgate-open .pgate-half:last-child { animation-delay: .16s; }
html.pgate-open .pgate-half::after {
  transform-origin: 50% 0;
  animation:
    pgate-draw .5s cubic-bezier(.2, .7, .3, 1) .3s both,
    pgate-seam 1.5s cubic-bezier(.4, 0, .2, 1) .55s 2;
}
html.pgate-open .pgate-foot {
  animation: pgate-drop .5s cubic-bezier(.2, .7, .3, 1) .34s both;
}

/* ---- the exit ----------------------------------------------------------- */
/* THE CHOICE IS THE LAST THING ON SCREEN. The half that was NOT picked
   leaves along the seam; the picked one takes the screen and dissolves into
   the page it was covering, which is the page the reader is being left on.
   Translate and opacity only, so nothing reflows on the way out.

   ★ THE STAGE ITSELF STILL TRANSITIONS `opacity`, because platformgate.js
   removes the node on ITS OWN transitionend (with a 700ms ceiling behind
   it). A stage with no transition would leave the script waiting on the
   timer every time. */
.pgate[data-pgate="going"] {
  opacity: 0;
  transition: opacity .42s cubic-bezier(.4, 0, .2, 1) .06s;
}
.pgate[data-pgate="going"] .pgate-head,
.pgate[data-pgate="going"] .pgate-foot {
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity .26s ease, transform .34s cubic-bezier(.4, 0, .2, 1);
}
.pgate[data-pgate="going"] .pgate-half {
  transition: transform .46s cubic-bezier(.5, 0, .75, 0),
              opacity .46s ease;
}
.pgate[data-pgate="going"] .pgate-half:first-child {
  transform: translateX(-24%);
  opacity: 0;
}
.pgate[data-pgate="going"] .pgate-half:last-child {
  transform: translateX(24%);
  opacity: 0;
}
/* The one the reader picked does not run away from them. */
.pgate[data-pgate="going"] .pgate-half[data-chosen] {
  transform: scale(1.06);
  opacity: 0;
  transition: transform .5s cubic-bezier(.3, 0, .2, 1), opacity .44s ease .04s;
}

@media (prefers-reduced-motion: reduce) {
  .pgate,
  .pgate-halves,
  .pgate-half,
  .pgate-half::before,
  .pgate-art::before,
  .pgate-art::after,
  .pgate-art img,
  .pgate-word,
  .pgate-word::after,
  .pgate-line,
  .pgate-go,
  .pgate-go::after,
  .pgate[data-pgate="going"],
  .pgate[data-pgate="going"] .pgate-head,
  .pgate[data-pgate="going"] .pgate-foot,
  .pgate[data-pgate="going"] .pgate-half,
  .pgate[data-pgate="going"] .pgate-half[data-chosen] {
    transition: none;
  }
  html.pgate-open .pgate-head,
  html.pgate-open .pgate-half,
  html.pgate-open .pgate-half::after,
  html.pgate-open .pgate-foot {
    animation: none;
  }
  .pgate-half::after { opacity: .9; }
}

/* ---- phones: the seam turns ------------------------------------------- */
/* Two columns 180px wide are two slivers. The halves stack, and the cut
   turns with them - it is still one torn surface, torn the other way. The
   lit bar turns with it, drawn from the same run so the two still meet. */
@media (max-width: 700px) {
  /* ★ THE LIT BAND IS TWICE `--pg-seam`: each plate draws its own half on its
     own side of the cut. Run edge-to-edge across a phone that reads as a
     doubled line rather than one lit seam, so the phone takes the thinner
     bar - the same picture, at the width the screen can carry. */
  .pgate { --pg-run: 5vh; --pg-seam: 2px; }
  .pgate-halves {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr 1fr;
  }
  .pgate-halves:has(.pgate-half:first-child:hover),
  .pgate-halves:has(.pgate-half:first-child:focus-visible),
  .pgate-halves:has(.pgate-half:last-child:hover),
  .pgate-halves:has(.pgate-half:last-child:focus-visible) {
    grid-template-columns: 1fr;
  }
  .pgate-half:first-child {
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--pg-run)), 0 100%);
  }
  .pgate-half:last-child {
    clip-path: polygon(0 var(--pg-run), 100% 0, 100% 100%, 0 100%);
    margin-left: 0;
    margin-top: calc(var(--pg-run) * -1);
  }
  /* The bar is now horizontal, so its gradient and its travel turn with it. */
  .pgate-half::after {
    background:
      linear-gradient(90deg,
        transparent 0%,
        var(--tm-hot) 18%,
        var(--accent-hi) 50%,
        var(--tm-hot) 82%,
        transparent 100%),
      linear-gradient(90deg, var(--tm-hot), var(--tm-hot));
    background-size: 46% 100%, 100% 100%;
    background-position: -60% 0, 0 0;
  }
  /* ★ THE RUN IS ON THE FAR SIDE, NOT THE NEAR ONE. The phone cut descends
     from the RIGHT: the first plate's edge is at 100% height on the left and
     `100% - run` on the right. A first pass put the run on the left instead,
     which drew the bar along the MIRROR of the cut - and the two seam bars
     crossed in the middle of the screen as a bright X. Caught in a rendered
     phone shot; no static gate can see a wrong polygon. */
  .pgate-half:first-child::after {
    clip-path: polygon(
      0 calc(100% - var(--pg-seam)),
      100% calc(100% - var(--pg-run) - var(--pg-seam)),
      100% calc(100% - var(--pg-run)),
      0 100%);
  }
  .pgate-half:last-child::after {
    clip-path: polygon(
      0 var(--pg-run),
      100% 0,
      100% var(--pg-seam),
      0 calc(var(--pg-run) + var(--pg-seam)));
  }
  @keyframes pgate-seam {
    from { background-position: -60% 0, 0 0; }
    to   { background-position: 160% 0, 0 0; }
  }
  html.pgate-open .pgate-half::after {
    transform-origin: 0 50%;
  }
  .pgate[data-pgate="going"] .pgate-half:first-child {
    transform: translateY(-24%);
  }
  .pgate[data-pgate="going"] .pgate-half:last-child {
    transform: translateY(24%);
  }
  .pgate-word { font-size: clamp(34px, 13vw, 62px); }
  .pgate-half { gap: 6px; }
  .pgate-go { margin-top: 14px; padding: 11px 24px; }
  /* The ghost numeral is sized against the plate, and a stacked plate is half
     as tall. At the desktop size it climbs straight off the top of its own
     panel and shows as a clipped stump - so it shrinks and sits closer. */
  .pgate-half::before {
    font-size: clamp(64px, 26vw, 120px);
    transform: translate(-50%, -155%);
    opacity: .05;
  }
  /* Only the OUTER bracket per plate. The inner one lands on the diagonal,
     where the cut takes half of it and leaves a mark that looks broken. */
  .pgate-half:first-child .pgate-art::after,
  .pgate-half:last-child .pgate-art::before {
    display: none;
  }
}

/* ---- the header switcher ----------------------------------------------- */
/* One pill, two segments, in the nav's utility cluster. It is a real pair of
   LINKS, not buttons: with scripting off it still navigates to the other
   platform's landing page, and a middle-click opens it in a tab the way any
   link does. The script upgrades the click to a cookie write plus an
   equivalent-page landing. */
.pswitch {
  position: relative;
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  padding: 2px;
  gap: 2px;
  flex: none;
  overflow: hidden;
}

/* THE THUMB. The lit segment SLIDES between the two rather than blinking
   from one to the other, which is what makes the pill read as a switch with
   a position instead of two links one of which is coloured. It is driven off
   `aria-current`, the same attribute the script already maintains, so there
   is no second piece of state to keep in step. */
.pswitch::before {
  content: "";
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 2px;
  width: calc(50% - 3px);
  border-radius: var(--r-pill);
  background: var(--tm-warm);
  box-shadow: inset 0 0 0 1px var(--edge-accent);
  pointer-events: none;
  transition: transform .3s cubic-bezier(.4, 0, .2, 1);
}
.pswitch:has(a:last-child[aria-current="true"])::before {
  transform: translateX(calc(100% + 2px));
}
/* ★ WITHOUT `:has()` THE THUMB WOULD BE STUCK UNDER THE FIRST SEGMENT and a
   reader on the second platform would be told they are on the first - which
   is worse than no thumb at all. So where the selector is not supported the
   thumb is not drawn and the fill goes back on the link itself, which is
   exactly the picture this pill had before the thumb existed. */
@supports not selector(:has(*)) {
  .pswitch::before { display: none; }
  .pswitch a[aria-current="true"] { background: var(--tm-warm); }
}

.pswitch a {
  position: relative;
  display: block;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  font-family: var(--font-ui);
  font-size: var(--fs-1);
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--fg-mute);
  text-decoration: none;
  white-space: nowrap;
  transition: color .18s ease;
}
.pswitch a:hover {
  color: var(--fg);
}
.pswitch a[aria-current="true"] {
  color: var(--accent-hi);
}

@media (prefers-reduced-motion: reduce) {
  .pswitch::before { transition: none; }
}

/* ---- platform-scoped nav ------------------------------------------------ */
/* Both platforms' links are IN THE DOCUMENT - that is what keeps every page
   reachable from every page (assemble.py::unreachable_pages refuses a build
   whose menu is a subset of itself) and what makes the no-JS answer honest.
   The cookie decides which set is DRAWN, and the attribute that carries the
   decision is written on <html> by the boot script before first paint.

   ★ THE DEFAULT IS BOTH, NOT ONE. With scripting off `data-platform` is
   never written, no rule below matches, and the reader gets every link -
   a fuller menu, not a broken one. Hiding by default and revealing with a
   script is the shape that ships an empty nav to anyone the script fails
   for. */
html[data-platform="legacy"] .nav-links a[data-plat="evrima"],
html[data-platform="legacy"] .foot-links a[data-plat="evrima"],
html[data-platform="evrima"] .nav-links a[data-plat="legacy"],
html[data-platform="evrima"] .foot-links a[data-plat="legacy"] {
  display: none;
}

@media (max-width: 720px) {
  .pswitch a {
    padding: 5px 8px;
  }
}
