/* Prœcession Games — studio site. Dark bone-on-ink, gold rules, glass over real art. */
@font-face {
  font-family: 'Cinzel';
  src: url('/fonts/Cinzel.woff2') format('woff2');
  font-weight: 400 900;
  font-display: swap;
}
:root {
  color-scheme: dark;
  --ink: #1c1f17;
  --ink-rgb: 28, 31, 23;
  --ink-2: #23271d;
  --ink-3: #2a2e23;
  --bone: #ece5d8;
  --bone-dim: #c5bcab;
  --gold: #c8a558;
  --bronze: #7d6536;
  --accent: var(--gold);
  --line: rgba(236, 229, 216, .14);
  --glass: rgba(38, 42, 32, .55);
  --wrap: 1180px;
  /* The sticky header's own height, so the arrival plate can start under it instead of beneath it: the
     52px bar mark, .7rem of padding twice and its 1px rule. Every slide is this much shorter than the
     window, so a bar that measures short runs a slide's last rows under the header's glass. This calc is
     the answer for a reader without the script; public/js/site.js measures the header and writes its real
     height here, which is what holds when the header grows — the menu open on a narrow window, a brand
     that wraps, a zoomed page — since no number written here can know that height. */
  --bar: calc(52px + 1.4rem + 1px);
  /* Where the panel's own ink sits inside logo.png's square frame, in that file's pixels: the alpha cut
     in tools/gen-logo.py drops the render's field and leaves the frame's lower third empty, so the
     picture is drawn at its own scale inside a frame of the art's shape and offset by these numbers —
     the empty margin is clipped rather than laid out, and the name lands under the panel instead of
     under the frame it was drawn in. tools/check-deck.py maps the rendered picture back through them,
     so a re-cut plate left with stale numbers fails there rather than under the name. */
  --plate: 1254;
  --plate-art-x: 406;
  --plate-art-y: 51;
  --plate-art-w: 441;
  --plate-art-h: 780;
  --serif: 'Cinzel', 'Iowan Old Style', 'Palatino Linotype', Georgia, 'Liberation Serif', 'DejaVu Serif', serif;
  --text: Georgia, 'Liberation Serif', 'DejaVu Serif', 'Times New Roman', serif;
}
* { box-sizing: border-box; }
/* A heading the picture already spells out: still in the page for structure and for a screen reader,
   never drawn. */
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font: 17px/1.65 var(--text);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  /* The leather: the gilded-hide plate, one file on one fixed layer — no gradient stack left to go
     flat or whitened. It came in as /t/leather-texture-exact-look-4k.svg, which is a base64 wrapper
     around a 1672x941 PNG and not vector art; what ships is that raster at JPEG q88 (a 4.6MB SVG
     that decodes to 3.5MB of PNG is no way to paint every page). `cover` on a fixed element, so the
     plate fills the window at any size and never tiles — ~1.15x upscale at 1080p, soft on a 4K
     screen: a bigger cut is the upgrade path, which is what the SVG's 3840x2160 viewBox claims and
     its pixels do not deliver.
     The veil is the price of a photograph where the field used to be a paint (±10 levels of wander):
     the gilding sits exactly where this page puts type. Measured against the shipped plate, the raw
     picture leaves 15.8% of the pixels under the games statement's eyebrow and 10.7% under its lede
     below 3:1 against bone — cream and gold type dissolving into gold. At .60 that is 0.6% and 0.0%,
     and the veins still read as veins. `python3 tools/measure-field.py` holds that floor, so deepen
     the veil rather than eyeball a busier plate. --ink rides under both as the fallback. */
  background:
    linear-gradient(rgba(var(--ink-rgb), .60), rgba(var(--ink-rgb), .60)),
    url('/img/leather.jpg') center / cover no-repeat var(--ink);
}
.site-header, main, .site-footer { position: relative; z-index: 1; }
img { max-width: 100%; display: block; }
a { color: var(--gold); text-decoration: none; }
a:hover { color: var(--bone); }
h1, h2, h3, .brand-name, .button { font-family: var(--serif); }
h1, h2, h3 { line-height: 1.18; margin: 0 0 .5em; font-weight: 500; }
h1 { font-size: clamp(2.1rem, 4.6vw, 3.4rem); }
h2 { font-size: clamp(1.5rem, 2.6vw, 2.1rem); }
h3 { font-size: 1.28rem; }
p { margin: 0 0 1.1em; }
.wrap { width: min(100% - 2.6rem, var(--wrap)); margin-inline: auto; }
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 1rem; top: 1rem; z-index: 9; background: var(--bone); color: var(--ink); padding: .5rem .8rem; }
.eyebrow {
  font-family: var(--serif);
  font-size: .74rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 .7em;
}
.lede { font-size: 1.12rem; color: #ded5c6; max-width: 62ch; }
.gold { color: var(--gold); }
.note { font-size: .84rem; color: var(--bronze); font-style: italic; margin: .8em 0 0; }
.glass {
  background: var(--glass);
  backdrop-filter: blur(16px) saturate(130%);
  -webkit-backdrop-filter: blur(16px) saturate(130%);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
}
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .glass { background: rgba(38, 42, 32, .94); }
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 5;
  background: rgba(var(--ink-rgb), .72);
  backdrop-filter: blur(14px) saturate(130%);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  border-bottom: 1px solid var(--line);
}
.header-inner { display: flex; align-items: center; gap: 1.4rem; padding: .7rem 0; }
.brand { display: flex; align-items: center; gap: .7rem; color: var(--bone); }
.brand-mark { height: 52px; width: auto; display: block; }
.brand-text { display: flex; flex-direction: column; line-height: 1; }
.brand-name { font-size: 1.12rem; letter-spacing: .14em; text-transform: uppercase; }
.brand-sub { font-family: var(--serif); font-size: .68rem; letter-spacing: .34em; text-transform: uppercase; color: var(--gold); }
.site-nav { margin-left: auto; display: flex; align-items: center; gap: 1.4rem; }
.site-nav a { font-family: var(--serif); font-size: .88rem; letter-spacing: .16em; text-transform: uppercase; color: #d3cabb; }
.site-nav a:hover, .site-nav a.is-active { color: var(--bone); }
.site-nav a.is-active { border-bottom: 1px solid var(--gold); padding-bottom: .2rem; }
.nav-toggle {
  margin-left: auto;
  display: none;
  font-family: var(--serif);
  letter-spacing: .16em;
  text-transform: uppercase;
  font-size: .78rem;
  color: var(--bone);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: .45rem .8rem;
  cursor: pointer;
}

/* Arrival screen: the panel art filling what is left of the viewport, its own ink bleeding into the
   page, with the studio's name and its line struck under it as text. It starts under the sticky header
   rather than behind it, so the picture, the name and the hint centre in the space the reader can
   actually see. */
.gate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(.5rem, 1.8vh, 1.2rem);
  width: 100%;
  container-type: size;
  /* The size the name is drawn at, its line a third of it — in cqh as well as vw, so a short window
     shrinks the type with the picture it names instead of leaving a lockup the picture has outgrown. */
  font-size: clamp(1.4rem, min(5.6vw, 100cqh / 12), 3.6rem);
  /* A definite height, not a floor: the frame below takes its height from the slide's own, and a
     container only as tall as its floor has no free space to hand it. */
  height: calc(100vh - var(--bar));
  height: calc(100svh - var(--bar));
  /* The band the hint rides in, and the only thing that holds the picture out of it: the hint is out
     of flow, so the frame has to leave the room for it. */
  padding: 0 0 clamp(3rem, 8vh, 4.2rem);
  border: 0;
  background: radial-gradient(circle at center, #141414 0, #090909 55%, #000 100%);
  color: inherit;
  cursor: pointer;
}
/* The picture's frame: the art's own shape, as tall as the name, its line and the hint's band leave.
   The picture rides inside at its own scale, offset by the --plate-art-* numbers, so the panel lands
   on the frame's box and its empty margin is clipped. The width follows from the height, so the
   lockup is as big as the window allows and the name sits under the panel rather than under the frame
   the panel was drawn in. ponytail: past ~441:780 of a 500px-wide window the art's sides crop instead
   of the lockup shrinking; give the frame a max-width if a window that shape ever matters. */
.gate-mark {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  aspect-ratio: var(--plate-art-w) / var(--plate-art-h);
  overflow: hidden;
}
.gate-plate {
  position: absolute;
  /* The page's own `img { max-width: 100% }` would clamp this to the frame's width and leave the
     picture at a square's height inside a taller frame: the frame is the crop, not the picture. */
  max-width: none;
  width: calc(var(--plate) / var(--plate-art-w) * 100%);
  /* And its own `height="1254"` attribute would otherwise win over the width the frame gives it. */
  height: auto;
  left: calc(var(--plate-art-x) / var(--plate-art-w) * -100%);
  top: calc(var(--plate-art-y) / var(--plate-art-h) * -100%);
}
/* The studio's name under the picture and the studio's own line under that: the cut carries no
   lettering, so both are drawn — the name at the size the gate is scaled to, the line a third of it
   and tracked wide, the way the top bar sets the same pair beside the mark. */
.gate-title {
  font-family: var(--serif);
  font-size: 1em;
  letter-spacing: .16em;
  /* The gap the last letter's own tracking leaves is half a gap of drift; the indent gives it back. */
  text-indent: .16em;
  text-align: center;
  text-transform: uppercase;
  color: var(--bone);
  will-change: transform;
  text-shadow: 2px 2px 2px rgba(206, 89, 55, 0);
}
.gate-sub {
  font-family: var(--serif);
  font-size: .33em;
  letter-spacing: .34em;
  text-indent: .34em;
  text-align: center;
  text-transform: uppercase;
  color: var(--gold);
}
.gate-hint {
  position: absolute;
  left: 0;
  right: 0;
  /* The picture, the name and the studio's line take all of the slide above it, so what the bottom of
     the screen holds is the band the gate's own padding leaves; the hint sits in that band, under the
     studio's line and never through it. */
  bottom: 1.4rem;
  font-family: var(--serif);
  font-size: .74rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
/* Walked off the plate, the hint leaves with it: the last rows of a slide sit behind the sticky
   header's glass, and a hint left there ghosts over the slide the reader is on. */
.gate.is-walked .gate-hint { opacity: 0; }
.gate:hover .gate-plate { opacity: .9; }
.gate:focus-visible { outline: 2px solid var(--gold); outline-offset: -6px; }
.hero { position: relative; padding: clamp(3.5rem, 9vw, 7rem) 0 clamp(2.5rem, 6vw, 4rem); }
/* The studio mark, sunk behind the hero: the round ring (the render the top bar carries), not the panel
   the arrival slide draws — the deck's first two slides carry the studio's two marks, not one twice.
   The ring's ink sits inside ~76% of mark.png's square frame (tools/cut-mark.py squares it around its
   own ink and pads to the width enso.svg fills), so the box below is the frame, not the mark. */
.hero::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 52%;
  width: min(74vh, 92vw, 720px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: url("/img/mark.png") center / contain no-repeat;
  opacity: .13;
  pointer-events: none;
}
.hero-grid { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: 2.4rem; align-items: center; }
.hero-copy { max-width: 46rem; }
.hero-title { display: flex; flex-direction: column; font-size: clamp(2.6rem, 8vw, 5.4rem); letter-spacing: .06em; text-transform: uppercase; margin-bottom: .5rem; }
.hero-lede { font-size: 1.16rem; color: #ded5c6; max-width: 54ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin: 1.6rem 0 2.2rem; }
.button {
  display: inline-block;
  padding: .7rem 1.3rem;
  border-radius: 6px;
  border: 1px solid transparent;
  font-size: .82rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.button.primary { background: var(--gold); color: #14110c; }
.button.primary:hover { background: var(--bone); color: var(--ink); }
.button.glass { background: var(--glass); border-color: var(--line); color: var(--bone); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
.button.glass:hover, .button.ghost:hover { border-color: var(--gold); color: var(--gold); }
.button.ghost { border-color: var(--line); color: var(--bone-dim); }
.button.small { padding: .5rem .95rem; font-size: .74rem; }
.hero-stats { display: flex; flex-wrap: wrap; gap: 2.4rem; list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); padding-top: 1.4rem; }
.hero-stats li { display: flex; flex-direction: column; }
.hero-stats strong { font-family: var(--serif); font-size: 1.3rem; color: var(--bone); }
.hero-stats span { font-size: .82rem; color: var(--bone-dim); letter-spacing: .06em; }

.section { padding: clamp(2.4rem, 5vw, 4rem) 0; }
.section-head { max-width: 60ch; margin-bottom: 1.8rem; }
.section-head p { color: var(--bone-dim); }

.game-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.4rem; }
.game-grid.compact { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.game-card { overflow: hidden; display: flex; flex-direction: column; --accent: var(--gold); }
.game-card::before { content: ''; height: 2px; background: linear-gradient(90deg, var(--accent), transparent); }
.card-media { position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden; }
.card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s ease; }
.game-card:hover .card-media img, .game-row:hover .row-media img { transform: scale(1.04); }
.card-status {
  position: absolute;
  left: .7rem;
  bottom: .7rem;
  font-family: var(--serif);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: .32rem .6rem;
  border-radius: 4px;
  background: rgba(var(--ink-rgb), .68);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  color: var(--bone);
}
.card-body { padding: 1.2rem 1.3rem 1.4rem; display: flex; flex-direction: column; flex: 1; }
.card-body h3 a { color: var(--bone); }
.card-body h3 a:hover { color: var(--accent, var(--gold)); }
.card-body p { color: #d2c9b8; }
.card-sub { font-family: var(--serif); font-size: .74rem; letter-spacing: .18em; text-transform: uppercase; color: var(--gold); margin: -.4em 0 .7em; }
.card-actions { margin-top: auto; display: flex; flex-wrap: wrap; gap: .6rem; padding-top: .6rem; }

.brand-strip { padding-top: 0; }
.strip-inner { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr); gap: 2rem; padding: 1.8rem; align-items: center; }
.plate-stack { position: relative; display: flex; flex-direction: column; gap: .7rem; }
.plate { position: relative; width: 100%; padding: 0; border: 1px solid var(--line); border-radius: 10px; background: #14170f; overflow: hidden; cursor: pointer; display: block; }
.plate img { display: none; width: 100%; height: auto; object-fit: cover; }
.plate img.is-current { display: block; }
.plate.wide img { aspect-ratio: 12 / 7; }
.plate.square img { aspect-ratio: 1 / 1; }
.plate:hover { border-color: var(--gold); }
.plate:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* The strip is a workspace as much as a showcase: the plate shown carries a × that drops its file from
   the design pack. Top corner, because the plate's own number badge has the bottom one, and it sits
   over the plate rather than inside the plate's button, which is what cycles the strip. */
.plate-drop {
  position: absolute;
  top: .55rem;
  right: .55rem;
  z-index: 1;
  width: 1.7rem;
  height: 1.7rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: rgba(var(--ink-rgb), .82);
  color: var(--bone-dim);
  font: inherit;
  font-size: .95rem;
  line-height: 1;
  cursor: pointer;
}
.plate-drop:hover, .plate-drop:focus-visible { border-color: var(--gold); color: var(--bone); outline: none; }
.mark-caption { display: flex; gap: .7rem; flex-wrap: wrap; margin: 0; font-family: var(--serif); font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; color: var(--bone-dim); }
.mark-hint { color: var(--bronze); }

.two-up { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 1.6rem; align-items: start; }
.panel { padding: 1.5rem 1.6rem; }
.panel h2 { font-size: 1.25rem; }
.panel + .panel { margin-top: 1.2rem; }
.principle { border-left: 2px solid var(--bronze); padding-left: .9rem; color: var(--bone-dim); }
.tech-list { list-style: none; margin: 0; padding: 0; }
.tech-list li { border-bottom: 1px solid var(--line); padding: .55rem 0; color: var(--bone-dim); }
.tech-list li:last-child { border-bottom: 0; }

.page-head { padding: clamp(2.6rem, 6vw, 4.5rem) 0 1rem; }
.page-head.lost { padding-bottom: 4rem; }
.contact-inner { padding: 2rem; text-align: center; }
.contact-inner .button { margin-top: .4rem; }
.footer-brand { display: flex; align-items: center; gap: .8rem; }
.footer-brand p { margin: 0; color: var(--bone-dim); }
.site-footer { margin-top: 3rem; border-top: 1px solid var(--line); background: rgba(var(--ink-rgb), .7); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
.footer-inner { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto; align-items: center; gap: 1.4rem; padding: 1.6rem 0; }
.footer-nav { display: flex; gap: 1.6rem; }
.footer-nav a { font-family: var(--serif); font-size: .8rem; letter-spacing: .2em; text-transform: uppercase; color: var(--bone-dim); }
.footer-nav a:hover { color: var(--gold); }
.footer-meta { display: flex; gap: .5rem; margin: 0; font-size: .76rem; letter-spacing: .08em; color: var(--bone-dim); }

.game-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); overflow: hidden; margin-bottom: 1.6rem; --accent: var(--gold); }
.row-media { display: block; overflow: hidden; min-height: 260px; }
.row-media img { width: 100%; height: 100%; object-fit: cover; }
.row-body { padding: 1.6rem 1.8rem; }
.row-sub { color: var(--gold); font-family: var(--serif); letter-spacing: .12em; text-transform: uppercase; font-size: .74rem; }
.row-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem 1.2rem; margin: 0 0 1.2rem; }
.row-facts dt, .spec dt { font-size: .7rem; letter-spacing: .16em; text-transform: uppercase; color: var(--bronze); }
.row-facts dd, .spec dd { margin: .1rem 0 0; }

.game-hero { position: relative; min-height: min(78vh, 620px); display: flex; align-items: flex-end; }
.hero-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.game-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(var(--ink-rgb), .55) 0%, rgba(var(--ink-rgb), .3) 40%, rgba(var(--ink-rgb), .96) 100%);
}
.hero-overlay { position: relative; z-index: 1; padding-bottom: 2.4rem; padding-top: 5rem; }
.hero-overlay h1 { font-size: clamp(2.4rem, 6vw, 4.2rem); letter-spacing: .04em; text-transform: uppercase; margin-bottom: .2rem; }
.subtitle { font-family: var(--serif); color: var(--gold); letter-spacing: .22em; text-transform: uppercase; font-size: .78rem; }
.tagline { font-size: 1.18rem; max-width: 46ch; color: #e6ded0; }
.hero-meta { display: flex; align-items: center; gap: .6rem; font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; color: var(--bone-dim); margin: .6rem 0 0; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 12px var(--accent); }

.prose .lede { margin-bottom: 1.4rem; }
.spec { position: sticky; top: 5.5rem; }
.feature-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: .9rem; }
.feature-list li {
  padding: 1rem 1.2rem 1rem 2.6rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(38, 42, 32, .4);
  position: relative;
}
.feature-list li::before { content: '◆'; position: absolute; left: 1rem; color: var(--accent, var(--gold)); font-size: .8rem; top: 1.15rem; }

.gallery { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: .9rem; }
.shot { position: relative; display: block; padding: 0; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: #000; cursor: zoom-in; width: 100%; }
.shot img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.shot-hint, .plate-no {
  position: absolute;
  right: .6rem;
  bottom: .6rem;
  font-family: var(--serif);
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--bone);
  background: rgba(var(--ink-rgb), .7);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: .24rem .45rem;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.shot-hint { opacity: 0; transition: opacity .2s ease; }
.shot:hover .shot-hint, .shot:focus-visible .shot-hint { opacity: 1; }

.lightbox { border: 1px solid var(--line); background: rgba(var(--ink-rgb), .96); padding: 0; max-width: min(94vw, 1400px); border-radius: 10px; }
.lightbox::backdrop { background: rgba(4, 4, 4, .82); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.lightbox img { max-height: 84vh; width: auto; margin: 0 auto; }
.lightbox .close {
  position: absolute;
  top: .4rem;
  right: .6rem;
  background: transparent;
  border: 0;
  color: var(--bone);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

.logo-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1.1rem; }
.vector-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.1rem; }
.vector-card { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: #14170f; }
.vector-card img { width: 100%; height: auto; aspect-ratio: 12 / 7; object-fit: cover; }
.logo-card { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--ink-2); }
.logo-card.is-kept { border-color: var(--gold); }
.logo-plate { display: grid; place-items: center; padding: .9rem; background: #1a1e14; }
.logo-plate img { border-radius: 6px; }
.logo-meta { display: flex; justify-content: space-between; padding: .6rem .8rem; border-top: 1px solid var(--line); font-size: .76rem; letter-spacing: .1em; text-transform: uppercase; }
.logo-meta span { color: var(--bone-dim); }

.swatches { list-style: none; margin: 0; padding: 0; }
.swatches li { display: flex; align-items: center; gap: .9rem; padding: .5rem 0; border-bottom: 1px solid var(--line); }
.swatches li:last-child { border-bottom: 0; }
.chip { width: 34px; height: 34px; border-radius: 6px; border: 1px solid var(--line); }
.swatches code { color: var(--bone-dim); font-size: .84rem; }

@media (max-width: 860px) {
  .two-up, .strip-inner, .game-row, .hero-grid, .footer-inner { grid-template-columns: minmax(0, 1fr); }
  .footer-inner { gap: .9rem; }
  .spec { position: static; }
  .site-nav { display: none; margin-left: 0; }
  .site-nav.open { display: flex; flex-direction: column; align-items: flex-start; gap: .9rem; padding: 1rem 0 1.2rem; }
  .header-inner { flex-wrap: wrap; }
  .nav-toggle { display: inline-block; }
  .site-nav.open { flex-basis: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .card-media img { transition: none; }
}

/* The arrival deck. Slides, not a page: each one is exactly what the sticky header leaves of the
   window, so nothing overflows the fold, and the third one hands its spare height to the two boxes
   (the picture shrinks, the caption row does not). Everything inside is sized in clamp() against vh,
   so a short window shrinks the slide instead of growing a scrollbar under it. Space walks down. */
.deck { display: block; }
[data-slide] { display: flex; align-items: center; scroll-margin-top: var(--bar); }
.slide {
  min-height: calc(100vh - var(--bar));
  min-height: calc(100svh - var(--bar));
  padding: clamp(1.2rem, 4vh, 3.2rem) 0;
}
.roster[data-slide] { align-items: stretch; }
/* Walked onto, a slide's own content settles the last 3rem into place on top of the deck's travel:
   the walk itself is the whole screen moving, which the walk in public/js/site.js drives, and the
   content never fades — content that fades is a cut however far the box it sits in travels. The
   animated box is the slide's content, never the slide: the slide's own geometry is what a walk is
   measured against, and a transform on it would move the deck. */
[data-slide].is-arriving > * { animation: slide-down .55s cubic-bezier(.22, .61, .36, 1) both; }
@keyframes slide-down {
  from { transform: translateY(-3rem); }
}
/* The slide being walked off rides the same travel up out of the window. Same rule as the arrival:
   the content accents the move, the slide's own geometry does not move. */
[data-slide].is-leaving > * { animation: slide-up .45s cubic-bezier(.4, 0, .75, .25) both; }
@keyframes slide-up {
  to { transform: translateY(-3rem); }
}

.intro.hero { padding: clamp(1.4rem, 5vh, 4rem) 0; }
.intro .hero-title { font-size: clamp(2.1rem, min(7vw, 9.5vh), 4.8rem); }
.intro .hero-lede { font-size: clamp(.98rem, 2.5vh, 1.16rem); margin-bottom: .6em; }
.intro .hero-actions { margin: clamp(.7rem, 2.5vh, 1.5rem) 0 clamp(1rem, 3vh, 2rem); }
.intro .hero-stats { gap: clamp(1.2rem, 3vw, 2.4rem); padding-top: clamp(.7rem, 2vh, 1.4rem); }
.intro .hero-stats strong { font-size: clamp(1.05rem, 2.6vh, 1.3rem); }
.intro .hero-stats span { font-size: clamp(.72rem, 1.9vh, .82rem); }
.intro .plate-stack { width: min(100%, 32vh); margin-inline: auto; }

.roster-inner { display: flex; flex-direction: column; gap: clamp(.6rem, 2vh, 1.4rem); min-height: 0; }
.roster-grid { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(.7rem, 1.6vw, 1.4rem); }
.game-box { display: flex; flex-direction: column; min-height: 0; overflow: hidden; --accent: var(--gold); }
.game-box::before { content: ''; flex: 0 0 auto; height: 2px; background: linear-gradient(90deg, var(--accent), transparent); }
.box-media { position: relative; display: block; flex: 1 1 auto; min-height: clamp(4.5rem, 16vh, 15rem); overflow: hidden; }
.box-media img, .box-media video { position: absolute; inset: 0; width: 100%; height: 100%; }
/* The wordmark is the whole picture in one box and rides on the sky in the other: transparent art in
   both, so the leather — or the game's own field — shows through it. */
.box-media img { object-fit: contain; padding: clamp(.6rem, 3vh, 2.2rem); }
/* Each box carries its game's own sky behind its wordmark: Rogue Horizon's is the picture that
   build's homepage serves, streamed by /game-art/:slug and drifting the way that page drifts it
   (`homepage-background-drift`, 36s — oversized, so the walk never reaches an edge); Avernal Maw's
   start page has no sky raster at all, so its box carries a clip captured off the running build by
   tools/capture-sky.py. Either way the sky is the game's, not a drawn stand-in. */
.box-media .box-sky { padding: 0; object-fit: cover; object-position: center; }
.box-media .box-sky.is-drift { inset: auto; top: 50%; left: 50%; width: 108%; height: 108%; transform-origin: center; transform: translate3d(-50%, -50%, 0); animation: box-sky-drift 36s linear infinite; will-change: transform; }
@keyframes box-sky-drift {
  0% { transform: translate3d(-50%, -50%, 0) translate3d(-1.4%, -1.0%, 0) rotate(-2.0deg); }
  25% { transform: translate3d(-50%, -50%, 0) translate3d(1.4%, -0.3%, 0) rotate(0.7deg); }
  50% { transform: translate3d(-50%, -50%, 0) translate3d(0.8%, 1.0%, 0) rotate(2.0deg); }
  75% { transform: translate3d(-50%, -50%, 0) translate3d(-1.1%, 0.5%, 0) rotate(-0.7deg); }
  100% { transform: translate3d(-50%, -50%, 0) translate3d(-1.4%, -1.0%, 0) rotate(-2.0deg); }
}
@media (prefers-reduced-motion: reduce) {
  .box-media .box-sky { animation: none; }
  [data-slide].is-arriving > * { animation: none; }
  [data-slide].is-leaving > * { animation: none; }
}
.box-body { display: flex; flex-direction: column; align-items: flex-start; gap: .35rem; flex: 0 0 auto; padding: .7rem .9rem .8rem; border-top: 1px solid var(--line); }
.box-meta { margin: 0; font-family: var(--serif); font-size: .68rem; letter-spacing: .14em; text-transform: uppercase; color: var(--bone-dim); }
/* The game in a sentence under its own release line: the box's caption, not a page's copy, so it is
   sized against the window like everything else on the slide. */
.box-desc { margin: 0; font-size: clamp(.74rem, 1.9vh, .88rem); line-height: 1.45; color: #d2c9b8; }
.box-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

@media (max-width: 860px) {
  .roster-grid { gap: .55rem; }
  .box-body { padding: .5rem .6rem; }
  .box-meta { font-size: .6rem; letter-spacing: .08em; }
  .box-media img { padding: .5rem; }
  .intro .plate-stack { width: min(100%, 26vh); }
}

/* A short window closes up the statement's rhythm and, when it is narrow too, gives up the plate
   gallery: the sentence is the slide, the gallery is an extra, and the slide still has to land
   inside the window rather than below the fold. Measured, not guessed — see tools/ and the README. */
@media (max-height: 780px) {
  .intro .hero-lede { margin-bottom: .4em; }
  .intro .hero-actions { margin: .6rem 0 .9rem; }
  .intro .hero-stats { padding-top: .6rem; }
  .intro .plate-stack { width: min(100%, 26vh); }
}
@media (max-width: 860px) and (max-height: 780px) {
  .intro .plate-stack { display: none; }
}
