/* INTERNAL CONFLICT, SITE V2 SHARED CHROME.
   LIVE FEED brand system, extended. Tokens per Knowledge/gamedev/internal-conflict-brand.md.
   Masters used from files (KERNING LAW). No gradients on letters (FINISH LAW).
   Cuts not fades: 240ms cubic-bezier(.2,.9,.2,1).

   WHY A SHARED FILE. The v1 pages each inlined a copy of the chrome, and the nav order
   drifted between them (home listed four channels, game listed four different ones).
   A rule duplicated across pages WILL drift and silently un-enforce. The chrome now has
   exactly ONE definition, imported by every v2 page; the nav MARKUP still ships statically
   in each page so it works with JS disabled, and the verify suite asserts every page
   carries the identical nav sequence. Drift is a test failure, not a live defect.

   Page-specific CSS stays inline in its page. This file owns only what every page shares. */

:root {
  --black: #0A0C10;
  --white: #EEF1F6;
  --red:   #E8102D;
  --gold:  #FFB400;
  --grey:  #7D8798;
  --blue:  #2456C8;
  --ink60: rgba(10, 12, 16, .60);
  --line:  rgba(238, 241, 246, .16);
  --paper: #E9E0C9;
  --paperink: #26241F;
  /* AUTHORED EASING. Nothing on this site uses a browser default curve. Four named
     curves, each with a job, so motion reads as one hand:
       --cut   the broadcast cut, fast out of the gate and settled early
       --out   long expo tail, for anything that arrives and stays (reveals, marks)
       --swing symmetrical, for state that moves both ways (hover, toggles)
       --snap  a small overshoot, reserved for press feedback and confirmations */
  --cut:   cubic-bezier(.2, .9, .2, 1);
  --out:   cubic-bezier(.16, 1, .3, 1);
  --swing: cubic-bezier(.65, 0, .35, 1);
  --snap:  cubic-bezier(.34, 1.42, .64, 1);
  --dur:   240ms;
  --slow:  620ms;
  --support: "Bahnschrift SemiCondensed", "Bahnschrift", "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
  --mono: Consolas, "IBM Plex Mono", "Courier New", monospace;
}
@font-face {
  font-family: "Anton";
  src: url("/brand/ic/fonts/Anton-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { height: 100%; }
body {
  min-height: 100vh;
  min-height: 100svh;
  background: var(--black);
  color: var(--white);
  font-family: var(--support);
  /* clip, not hidden: hidden makes body a scroll container and kills position:sticky */
  overflow-x: clip;
}
img { max-width: 100%; }
[hidden] { display: none !important; }

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

/* ---------- stage: graded poster held as a still behind everything ---------- */
.stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--black) url("/brand/ic/video/ic-bg-poster.jpg") center / cover no-repeat;
  pointer-events: none;
}
.scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10, 12, 16, .88) 0%, rgba(10, 12, 16, .84) 40%, rgba(10, 12, 16, .84) 70%, rgba(10, 12, 16, .92) 100%);
}
/* The scanlines BREATHE. A CRT never holds a perfectly steady raster, so the line field
   drifts between .50 and .60 over seven seconds. It is slow enough that nobody catches it
   moving and quick enough that the page never looks like a flat print. */
.scanlines {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom,
    rgba(0, 0, 0, .22) 0, rgba(0, 0, 0, .22) 1px, transparent 1px, transparent 3px);
  opacity: .55;
  mix-blend-mode: multiply;
  animation: breathe 7s var(--swing) infinite;
}
@keyframes breathe {
  0%, 100% { opacity: .50; }
  50%      { opacity: .60; }
}
.grain {
  position: absolute; inset: -60%;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='170'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 170px 170px;
  opacity: .09;
  mix-blend-mode: overlay;
  animation: grain .7s steps(1, end) infinite;
  will-change: transform;
}
@keyframes grain {
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(-9%, 4%); }
  40%  { transform: translate(-4%, 12%); }
  60%  { transform: translate(12%, 1%); }
  80%  { transform: translate(-13%, 2%); }
  100% { transform: translate(0, 0); }
}
.vignette {
  position: absolute; inset: 0;
  background: radial-gradient(ellipse 120% 92% at 50% 42%, transparent 56%, rgba(0, 0, 0, .52) 100%);
}

/* ---------- broadcast chrome ---------- */
.chrome {
  position: fixed;
  top: 0; left: 0; right: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  padding: 18px 22px 0;
  z-index: 6;
}
/* CRT FLICKER, CHROME ONLY. The broadcast furniture sits on the tube; the page content
   does not. A 0.04 swing in brightness on an eleven second cycle, which reads as a live
   signal rather than a broken one. Confined here on purpose: flicker on body copy is a
   headache, flicker on a station bug is the station. */
.chrome { animation: crt 11s steps(1, end) infinite; }
@keyframes crt {
  0%, 96%   { opacity: 1; }
  96.5%     { opacity: .965; }
  97%       { opacity: 1; }
  98.5%     { opacity: .978; }
  99%       { opacity: 1; }
}

/* ---------- the mark in the chrome ----------
   KERNING LAW. The letterforms below are the brand master paths, copied from the file at
   D:\GameDev\IC\assets\brand\logo, never retyped or re-spaced. They are inlined instead of
   linked so the star can carry the tally blink, which an <img> cannot.

   TWO LOCKUPS, ONE RULE. The full horizontal wordmark reads at desktop chrome width. Below
   900px the row cannot hold 180px of wordmark next to a wishlist chip, so it hands over to
   the IC bug, the master's own short lockup. Both are plainstar masters: the star renders
   here between 18 and 28px, far under the 56px floor where the detailed star holds up. */
.bug { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; min-width: 0; }
.bug a { display: block; }
.bug svg { display: block; width: auto; }
.bug .wordmark { height: 22px; }
.bug .buglock { height: 26px; display: none; }
.bug .tally { animation: tally .9s steps(2, jump-none) infinite; transform-origin: center; }
@keyframes tally { from { opacity: 1; } to { opacity: .08; } }
.bug a:hover .tally { animation-duration: .45s; }
.bug .live-label {
  font-family: var(--support);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .22em;
  color: var(--white);
  padding-left: 12px;
  border-left: 1px solid var(--line);
  line-height: 1;
}

/* ---------- channel nav: the page switcher lives in the broadcast chrome ----------
   ORDER IS LOAD BEARING and asserted by the verify suite across every page:
   HOME, THE GAME, PLAYTESTING, FAQ, SUPPORT, then the account chip. ROADMAP and THE ALPHA
   merged into PLAYTESTING; their routes still resolve so no shared link ever dies. */
.channels {
  display: flex;
  gap: 7px;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
}
.channels a {
  position: relative;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .16em;
  color: var(--grey);
  text-decoration: none;
  border: 1px solid var(--line);
  background: rgba(10, 12, 16, .72);
  padding: 6px 11px;
  white-space: nowrap;
  overflow: hidden;
  transition: color var(--dur) var(--cut), border-color var(--dur) var(--cut);
}
/* the hover is a switcher wipe: a red bar crosses the button from the left rather than a
   colour fade, because this chrome cuts, it does not dissolve */
.channels a::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--cut);
}
.channels a:hover::before, .channels a:focus-visible::before { transform: scaleX(1); }
.channels a.on::before { transform: scaleX(1); }
.channels a:hover { color: var(--white); border-color: var(--white); }
.channels a:active { transform: translateY(1px); }
.channels a:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }
.channels a.on { color: var(--red); border-color: var(--red); }
/* the account chip reads as a separate control, not another page */
.channels a.acct { color: var(--white); border-color: rgba(238, 241, 246, .38); margin-left: 5px; }
.channels a.acct:hover { border-color: var(--white); }

/* WISHLIST chip: the one permanently-lit control in the chrome. Signal red, always
   present, never the same weight as a page link. */
.wish {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .16em;
  color: var(--white);
  text-decoration: none;
  border: 1px solid var(--red);
  background: rgba(232, 16, 45, .16);
  padding: 6px 12px;
  white-space: nowrap;
  flex: 0 0 auto;
  transition: background-color var(--dur) var(--cut);
}
.wish:hover { background: var(--red); }
.wish:active { transform: translateY(1px); }
.wish:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }
.wish .dot {
  width: 7px; height: 7px; background: var(--red); flex: 0 0 auto;
  animation: tally .9s steps(2, jump-none) infinite;
}
/* ---------- the channel row: the war's channels, in the chrome ----------
   Icon buttons, not labels: they sit beside the wishlist chip on every page and they are
   the same square as the nav chips are tall, so the row reads as one piece of furniture.
   The Discord button ships hidden and is armed by the server, exactly like the footer
   tile, so an unset invite is an absent button rather than a dead link. */
.chsocials { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.chsocials a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  border: 1px solid var(--line);
  background: rgba(10, 12, 16, .72);
  color: var(--grey);
  transition: color var(--dur) var(--cut), border-color var(--dur) var(--cut), background-color var(--dur) var(--cut);
}
.chsocials a svg { width: 14px; height: 14px; display: block; fill: currentColor; }
.chsocials a:hover { color: var(--white); border-color: var(--white); background: rgba(232, 16, 45, .18); }
.chsocials a:active { transform: translateY(1px); }
.chsocials a:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }

/* ---------- the SOUND chip: the score, opt in only ----------
   The site ships SILENT. Nothing is fetched, nothing is decoded and no play() is ever
   attempted until a visitor presses this, which is both the browser's rule and ours.
   Pressed, the chip lights and EXPANDS into its own small mixer: a track name and a
   mono-styled volume slider. Both the on/off state and the volume are remembered, so
   the choice survives a page change instead of being asked again on every navigation.
   Same furniture language as the channel row: 28px tall, one hairline, no chrome of
   its own. */
.sound { display: flex; align-items: center; gap: 0; flex: 0 0 auto; }
.sound-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 28px;
  padding: 0 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .16em;
  color: var(--grey);
  background: rgba(10, 12, 16, .72);
  border: 1px solid var(--line);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur) var(--cut), border-color var(--dur) var(--cut), background-color var(--dur) var(--cut);
}
.sound-btn:hover { color: var(--white); border-color: var(--white); background: rgba(232, 16, 45, .18); }
.sound-btn:active { transform: translateY(1px); }
.sound-btn:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }
.sound.on .sound-btn { color: var(--white); border-color: var(--red); background: rgba(232, 16, 45, .16); }

/* the level meter doubles as the state: three flat bars while muted, three moving
   bars while the bed is running */
.sound-btn .eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 11px; }
.sound-btn .eq i { display: block; width: 2px; height: 3px; background: currentColor; }
.sound.on .sound-btn .eq i { background: var(--red); animation: eqbar .9s var(--cut) infinite alternate; }
.sound.on .sound-btn .eq i:nth-child(1) { animation-duration: .62s; }
.sound.on .sound-btn .eq i:nth-child(2) { animation-duration: .93s; }
.sound.on .sound-btn .eq i:nth-child(3) { animation-duration: .77s; }
@keyframes eqbar { from { height: 3px; } to { height: 11px; } }

/* the mixer only exists once the bed is running: an off chip is one chip */
.sound-panel {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-left: 0;
  background: rgba(10, 12, 16, .72);
}
.sound-track {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .12em;
  color: var(--grey);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur) var(--cut);
}
.sound-track:hover { color: var(--white); }
.sound-track:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }

/* a mono fader, not a browser slider */
.sound-vol {
  -webkit-appearance: none; appearance: none;
  width: 62px; height: 12px;
  background: none;
  cursor: pointer;
}
.sound-vol:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }
.sound-vol::-webkit-slider-runnable-track { height: 2px; background: var(--line); }
.sound-vol::-moz-range-track { height: 2px; background: var(--line); }
.sound-vol::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 4px; height: 12px; margin-top: -5px; border: 0; border-radius: 0; background: var(--red);
}
.sound-vol::-moz-range-thumb { width: 4px; height: 12px; border: 0; border-radius: 0; background: var(--red); }

.chrome-right { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; }
.clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  letter-spacing: .06em;
  color: var(--grey);
  line-height: 28px;
  white-space: nowrap;
}

/* ---------- channel flip: the page transition is a hard cut, not a fade ----------
   Leaving a page slams a scanline bar and a black frame over the viewport for 240ms,
   the way a broadcast switcher cuts between sources. Arriving, it lifts. Reduced motion
   and JS-off both skip it entirely and the link is an ordinary navigation. */
.flip {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--black);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}
.flip::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 3px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: center;
}
/* VHS TRACKING. The cut is not a clean black frame, it is a tape head finding the next
   source: two soft bands roll up the field while the red bar snaps across. It lasts 200ms
   and is gone. Quiet enough that it reads as texture, not as an effect. */
.flip::before {
  content: "";
  position: absolute;
  inset: -20% 0;
  background: repeating-linear-gradient(to bottom,
    rgba(238, 241, 246, .00) 0, rgba(238, 241, 246, .00) 46px,
    rgba(238, 241, 246, .055) 48px, rgba(238, 241, 246, .12) 54px,
    rgba(238, 241, 246, .055) 60px, rgba(238, 241, 246, .00) 62px);
  opacity: 0;
}
body.flipping .flip { opacity: 1; visibility: visible; }
body.flipping .flip::before { opacity: 1; animation: tracking 200ms linear; }
body.flipping .flip::after { transform: scaleX(1); transition: transform 200ms var(--cut); }
body.flip-in .flip { opacity: 0; visibility: visible; transition: opacity 200ms var(--cut); }
body.flip-in .flip::before { opacity: 1; animation: tracking 220ms linear reverse; }
@keyframes tracking {
  from { transform: translateY(30%) translateX(0); }
  60%  { transform: translateY(-14%) translateX(2px); }
  to   { transform: translateY(-40%) translateX(0); }
}

/* ---------- shared type + rules ---------- */
.slate {
  display: flex;
  align-items: stretch;
  margin-bottom: 26px;
  animation: slatein 420ms var(--cut) both;
}
@keyframes slatein { from { transform: translateX(-14px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
.slate .chip {
  background: var(--red);
  color: var(--white);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .2em;
  display: flex;
  align-items: center;
  padding: 0 14px;
  white-space: nowrap;
}
.slate h1 {
  font-family: "Anton", var(--support);
  font-weight: 400;
  font-size: clamp(34px, 6vw, 58px);
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--white);
  line-height: 1;
  padding: 8px 18px;
  background: var(--ink60);
  border: 1px solid var(--line);
  border-left: none;
}
.lede {
  font-size: clamp(16px, 2.2vw, 19px);
  line-height: 1.6;
  color: #C9CFD9;
  max-width: 620px;
  text-wrap: balance;
}
.lede strong { color: var(--white); font-weight: 600; }

.section-rule {
  margin: 72px 0 0;
  display: flex;
  align-items: center;
  gap: 16px;
}
.section-rule .bar { height: 2px; background: var(--red); flex: 0 0 44px; }
.section-rule h2 {
  font-family: "Anton", var(--support);
  font-weight: 400;
  font-size: clamp(22px, 3.4vw, 30px);
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--white);
}
.section-note {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .12em;
  color: var(--grey);
  text-transform: uppercase;
}

/* ---------- kinetic section titles ----------
   Words land one after another from under a clipped line, the way a title card types on.
   The clip is what makes it read as a broadcast title rather than a fade: nothing is ever
   seen half transparent, it rises out of a hard edge. */
[data-kinetic] .w {
  display: inline-block;
  opacity: 0;
  transform: translateY(.62em) skewY(2deg);
}
.in [data-kinetic] .w, [data-kinetic].in .w {
  opacity: 1;
  transform: translateY(0) skewY(0);
  transition: opacity 420ms var(--out), transform 520ms var(--out);
}

/* ---------- reveal ----------
   STAGGER. A group sets --stagger on itself and each child gets an --i; the delay is the
   product. No JS timers, no per-element inline styles beyond the index, and the whole
   cascade collapses to zero under reduced motion. */
/* GATED ON THE SCRIPT. A reveal starts invisible and is revealed by an observer, so on a
   page with JavaScript switched off it would start invisible and STAY there: whole bands
   of real copy rendered as blank space. The armed state therefore only exists when the
   head switch has declared that a script is running to undo it. Pages that use .reveal
   MUST set the js class in the head, before first paint. */
.js .reveal { opacity: 0; transform: translateY(26px); }
.reveal.in {
  opacity: 1; transform: translateY(0);
  transition:
    opacity var(--slow) var(--out) calc(var(--i, 0) * var(--stagger, 0ms)),
    transform var(--slow) var(--out) calc(var(--i, 0) * var(--stagger, 0ms));
}
/* the sideways variant: used where a block should arrive from the spine, not from below */
.reveal.from-left { transform: translateX(-24px) translateY(0); }
.reveal.from-left.in { transform: none; }

/* ---------- buttons ----------
   Three states with intent, not one hover colour:
     rest   the control is available
     hover  a red field wipes in from the left under the label, and the label steps up 1px
     press  the whole control drops 1px and the wipe holds, so a click has a floor
   The wipe is a pseudo element so the label never re-renders and never shifts colour
   mid-transition. Every curve here is authored. */
.btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  font-family: "Anton", var(--support);
  font-size: 17px;
  letter-spacing: .01em;
  text-transform: uppercase;
  height: 52px;
  padding: 0 26px;
  border-radius: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transition:
    transform 160ms var(--snap),
    background-color var(--dur) var(--swing),
    border-color var(--dur) var(--swing);
}
/* z-index -1 inside the button's own isolated stacking context is the one value that
   paints the wipe OVER the button's background and UNDER its label. A positive value
   would cover the words; without isolation the negative value would escape behind the
   section. Both were tried; this is the pair that works. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 320ms var(--out);
}
.btn:hover::before, .btn:focus-visible::before { transform: scaleX(1); }
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(1px); transition-duration: 60ms; }
.btn:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }
.btn-red { background: var(--red); color: var(--white); border: 1px solid var(--red); }
/* on the red button the wipe is the DARKER field, so hover still reads as a change */
.btn-red::before { background: #A50A1F; }
.btn-ghost { background: transparent; color: var(--white); border: 1px solid var(--line); }
.btn-ghost:hover { border-color: var(--red); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .38; cursor: not-allowed; pointer-events: none; }
.cta-row {
  margin-top: 28px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.micro {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .10em;
  color: var(--grey);
}

/* ---------- CAPTURE SLOTS ----------
   Every capture-dependent asset on the site is one of these. The poster ships now, the
   clip drops in later at /internal-conflict/media/v2/<slot>.mp4 with NO code change:
   the script reads data-slot and only attaches a source if the file resolves. A missing
   clip is invisible, never a black box. Contract documented in media/v2/SLOTS.md. */
.slot {
  position: relative;
  overflow: hidden;
  background: #05070A;
  border: 1px solid var(--line);
}
.slot > img, .slot > video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.slot > video { position: absolute; inset: 0; opacity: 0; transition: opacity 420ms var(--cut); }
.slot.playing > video { opacity: 1; }
.slot .slot-grade {
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    repeating-linear-gradient(to bottom, rgba(0,0,0,.20) 0, rgba(0,0,0,.20) 1px, transparent 1px, transparent 3px),
    radial-gradient(ellipse 118% 92% at 50% 44%, transparent 54%, rgba(0,0,0,.55) 100%);
  mix-blend-mode: multiply;
}
.slot .slot-id {
  position: absolute;
  left: 10px; bottom: 9px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .18em;
  color: rgba(238, 241, 246, .5);
  text-transform: uppercase;
  pointer-events: none;
  text-shadow: 0 1px 0 rgba(10, 12, 16, .9);
}

/* ---------- GRIT KIT ----------
   Texture, not a costume. The site is a live modern broadcast; these are the marks that
   say a human physically handled the paper inside it. Every one of them is low opacity by
   rule, and none of them is ever the reason you notice a section.

   .paper       a photocopied zine surface: toner speckle plus a faint second-generation
                blotch, laid over whatever background the element already had.
   .taped       two pieces of tape across the top corners, angled and translucent.
   .stapled     one staple in the top left.
   .stencil     a worn spray-stencil edge on a red stamp, chewed by a mask rather than
                drawn, so no two elements wear the same way. */
.paper {
  position: relative;
}
.paper::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: .30;
  mix-blend-mode: overlay;
  background-image:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='z'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23z)'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 70% 50% at 18% 12%, rgba(255, 255, 255, .07), transparent 70%),
    radial-gradient(ellipse 60% 44% at 84% 86%, rgba(0, 0, 0, .16), transparent 72%);
  background-size: 220px 220px, 100% 100%, 100% 100%;
}
.paper > * { position: relative; z-index: 2; }

.taped::before, .taped::after {
  content: "";
  position: absolute;
  top: -9px;
  width: 62px;
  height: 19px;
  background: rgba(233, 224, 201, .17);
  border-left: 1px solid rgba(233, 224, 201, .10);
  border-right: 1px solid rgba(233, 224, 201, .10);
  pointer-events: none;
  z-index: 3;
}
.taped::before { left: -14px; transform: rotate(-24deg); }
.taped::after  { right: -14px; transform: rotate(23deg); }

.stapled::before {
  content: "";
  position: absolute;
  left: 13px; top: 11px;
  width: 15px;
  height: 3px;
  background: rgba(238, 241, 246, .40);
  box-shadow: 0 4px 0 -1px rgba(0, 0, 0, .55);
  transform: rotate(-7deg);
  pointer-events: none;
  z-index: 3;
}

.stencil {
  -webkit-mask-image:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='60'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.045' numOctaves='2'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.4 1.5'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
  mask-image:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='60'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.045' numOctaves='2'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.4 1.5'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
  -webkit-mask-size: 140px 60px;
  mask-size: 140px 60px;
}

/* ---------- socials row ---------- */
.socials {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 22px;
}
.socials a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--white);
  text-decoration: none;
  border: 1px solid var(--line);
  background: var(--ink60);
  padding: 13px 18px;
  transition: border-color var(--dur) var(--cut);
}
.socials a:hover { border-color: var(--white); }
.socials a:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }
.socials a svg { width: 16px; height: 16px; display: block; fill: currentColor; flex: 0 0 auto; }

/* ---------- dev log watch card: facade only, no third-party request until clicked ---------- */
.watch-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  background: #000;
  overflow: hidden;
}
.watch-btn {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 0; border: 0; background: none; cursor: pointer; display: block;
}
.watch-frame img, .watch-frame iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block;
}
.watch-frame img { object-fit: cover; }
.watch-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 76px; height: 76px;
  background: var(--ink60);
  border: 1px solid var(--white);
  transition: background var(--dur) var(--cut), border-color var(--dur) var(--cut);
}
.watch-play::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  transform: translate(-38%, -50%);
  border-left: 24px solid var(--white);
  border-top: 15px solid transparent;
  border-bottom: 15px solid transparent;
}
.watch-btn:hover .watch-play, .watch-btn:focus-visible .watch-play { background: var(--red); border-color: var(--red); }
.watch-btn:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }

/* ---------- footer ---------- */
.foot {
  margin-top: 84px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
  text-align: center;
}
/* THE FOOTER LOCKUP. The wordmark master itself, linked as a file rather than inlined,
   because nothing down here needs to animate. Held at 26px, where the star is well under
   the 56px floor, so it is the plainstar cut. Under it, the one site-wide wink. */
.foot-lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-bottom: 26px;
}
.foot-lockup img { height: 26px; width: auto; display: block; opacity: .92; }
.y2k {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .26em;
  color: rgba(125, 135, 152, .72);
  text-transform: uppercase;
}
.legal-links {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .18em;
  color: var(--grey);
}
.legal-links a {
  color: var(--grey);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
  margin: 0 9px;
  white-space: nowrap;
  display: inline-block;
}
.legal-links a:hover { color: var(--white); border-color: var(--white); }
.copyline {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .14em;
  color: var(--grey);
}
.foot .socials { justify-content: center; }
.ue-attrib { margin-top: 22px; }
.ue-attrib img { height: 30px; width: auto; display: block; margin: 0 auto 10px; }

/* ---------- platform row: sits ABOVE the engine attribution ----------
   ORDER IS DELIBERATE (Cody, 2026-09-04): platforms read first because they answer a
   player's question ("where can I get this"), and the engine attribution is a
   contractual credit that belongs at the very bottom with the rest of the fine print.

   WORDING IS "IN DEVELOPMENT FOR", NEVER "AVAILABLE ON". We hold partner status on both
   consoles and have a console build on neither. Steam is the only line that may state a
   destination as real, because the store page exists and is public.

   THE SONY LEGAL LINE IS MANDATORY whenever PlayStation marks are used. Supplied verbatim
   by SIE on 2026-09-04, who also confirmed there is NO approval process and that they do
   not monitor external sites; the requirement is the Corporate Brand Guidelines plus this
   line. The year is ours to keep current.

   NO LOGOS ARE PLACED YET. Official artwork is not in the repo, and a traced or
   approximated mark is a worse trademark position than no mark at all. This row is text
   until the real assets land, at which point they drop into .plat-marks. */
.plat-row { margin-top: 22px; }
.plat-row .plat-line {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--grey);
  margin: 0 0 8px;
}
.plat-row .plat-dests {
  font-size: 14px;
  line-height: 1.7;
  color: #C9CEDA;
  margin: 0;
}
.plat-row .plat-dests a { color: inherit; text-decoration: none; border-bottom: 1px solid rgba(255,255,255,.35); }
.plat-row .plat-dests a:hover { border-bottom-color: #fff; color: #fff; }
.plat-row .plat-dests a:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: 2px; }
.plat-marks { display: flex; justify-content: center; align-items: center; gap: 22px; flex-wrap: wrap; margin-top: 12px; }
.plat-marks img { height: 22px; width: auto; display: block; opacity: .85; }
.plat-legal {
  max-width: 640px;
  margin: 12px auto 0;
  font: 400 9.5px/1.7 var(--mono);
  letter-spacing: .06em;
  color: #7D8798;
}
.ue-lines {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .06em;
  line-height: 1.7;
  color: var(--grey);
  max-width: 640px;
  margin: 0 auto;
}

/* ---------- cookie consent banner: opt-in analytics, equal-prominence choices ---------- */
.consent {
  position: fixed;
  left: 0; right: 0; bottom: 44px;
  z-index: 8;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
  flex-wrap: wrap;
  padding: 16px 22px;
  background: rgba(10, 12, 16, .94);
  border-top: 1px solid var(--line);
}
.consent[hidden] { display: none; }
/* THE BANNER MUST NOT SIT ON THE PAGE'S OWN COPY. It is the only element on the site
   that can cover content it did not lay out around, so while it is up the page gets a
   measured --consent-h to hold its bottom-anchored furniture clear of it. chrome.js
   sets and clears the value; the fallback of 0px is the dismissed state. */
body.has-consent { --consent-h: 96px; }
.consent-copy { max-width: 560px; }
.consent-head {
  display: block;
  font-family: "Anton", var(--support);
  font-weight: 400;
  font-size: 15px;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--white);
}
.consent-sub {
  margin-top: 5px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .05em;
  line-height: 1.6;
  color: var(--grey);
}
.consent-sub a { color: var(--white); text-decoration: none; border-bottom: 1px solid rgba(238, 241, 246, .28); }
.consent-sub a:hover { border-color: var(--white); }
.consent-actions { display: flex; gap: 10px; }
.consent-actions .btn { height: 44px; padding: 0 22px; font-size: 15px; }

/* ---------- news ticker ---------- */
.ticker {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: 44px;
  display: flex;
  align-items: stretch;
  background: var(--ink60);
  border-top: 1px solid var(--line);
  z-index: 5;
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.ticker .tag {
  display: flex;
  align-items: center;
  padding: 0 18px;
  font-family: var(--support);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .18em;
  color: var(--gold);
  border-right: 1px solid var(--line);
  white-space: nowrap;
  flex: 0 0 auto;
}
.ticker .crawl-window { flex: 1; overflow: hidden; display: flex; align-items: center; }
.ticker .crawl {
  display: flex;
  flex: 0 0 auto;
  white-space: nowrap;
  will-change: transform;
  animation: crawl 60s linear infinite;
}
.ticker .crawl span {
  font-family: var(--support);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .10em;
  color: var(--gold);
  padding-right: 64px;
}
@keyframes crawl { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---------- small screens ----------
   The chrome carries seven controls now, so below 900px it stacks: bug and clock on the
   top line, the channel row centered beneath it, the wishlist chip with it. Content
   padding grows to clear the taller chrome. Nothing overflows the 390px viewport. */
@media (max-width: 900px) {
  .chrome { flex-wrap: wrap; padding: 14px 14px 0; }
  .chrome-right { order: 2; margin-left: auto; }
  .channels { order: 3; width: 100%; margin-top: 10px; }
  .clock { font-size: 11px; }
  /* the wordmark hands over to the bug: the master's own short lockup for tight rows */
  .bug .wordmark { display: none; }
  .bug .buglock { display: block; }
}
@media (max-width: 700px) {
  /* The in-world clock is atmosphere, and atmosphere does not get to sit on top of the
     one control that matters. Below 700px the bug, the wishlist chip and six nav links
     already fill the row, so the clock stands down rather than overlapping the chip. */
  .clock { display: none; }
}
@media (max-width: 620px) {
  /* the mixer is a desktop nicety; on a phone the chip alone still turns the bed on
     and off, which is the control that matters */
  .sound-panel { display: none; }
  .sound-btn { padding: 0 8px; letter-spacing: .1em; }
  .chsocials a { width: 25px; height: 25px; }
  .chsocials a svg { width: 12px; height: 12px; }
  .bug .buglock { height: 21px; }
  .bug .live-label { font-size: 10px; padding-left: 10px; }
  .foot-lockup img { height: 20px; }
  .channels a { font-size: 10px; padding: 6px 9px; letter-spacing: .12em; }
  .wish { font-size: 10px; padding: 6px 10px; letter-spacing: .12em; }
  .slate { flex-direction: column; align-items: flex-start; }
  .slate .chip { padding: 8px 12px; }
  .slate h1 { border-left: 1px solid var(--line); border-top: none; }
  .cta-row .btn { width: 100%; }
  .ticker .tag { padding: 0 12px; }
  .legal-links a { margin: 0 6px; }
}

/* ---------- reduced motion: no scrub, no flip, everything at final state ---------- */
@media (prefers-reduced-motion: reduce) {
  .bug .tally, .wish .dot { animation: none; }
  .sound.on .sound-btn .eq i { animation: none; height: 11px; }
  .ticker .crawl { animation: none; }
  .grain { animation: none; }
  .scanlines { animation: none; opacity: .55; }
  .chrome { animation: none; }
  .slate { animation: none; }
  .reveal, .reveal.from-left { opacity: 1; transform: none; }
  .reveal.in { transition: none; }
  [data-kinetic] .w { opacity: 1; transform: none; transition: none; }
  .btn, .btn::before, .channels a::before { transition: none; }
  .btn::before { transform: scaleX(0); }
  .btn:hover, .btn:active { transform: none; }
  .flip { display: none; }
  .slot > video { transition: none; }
}
