/* ==========================================
   The catalogue: frames, pillars, backdrops, /screens/
   ==========================================
   Loaded by index.html and screens.html only (head_extra), not by the shell:
   /faq/ and the legal pages have no frames on them and should not pay for
   these rules.

   Every colour, radius, font and gutter here is a landing.css token
   (--bg-1, --accent, --rad, --font-d, --pad). Nothing new is invented: the
   catalogue has to look like the rest of the site, not like a bolt-on.
   ========================================== */

/* ---- The frame component (partials/gs_frame.html) ----
   The box is the frame's own aspect ratio, from the manifest's real w/h. That
   is not decoration: step 4 positions hotspots as PERCENTAGES of this box, and
   a box whose ratio is not the image's puts every one of them in the wrong
   place at every width except one. */
.gs-frame { margin: 0; }
.gs-frame-box {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: var(--rad);
  border: 1px solid var(--border-b);
  background: var(--bg-1);
  box-shadow: 0 24px 60px rgba(0,0,0,0.45);
}
.gs-frame-media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.gs-frame-video { position: relative; z-index: 1; }
/* The still sits UNDER the video, filling the same box. When the video is
   removed from the flow (reduced motion, decode failure) the still is simply
   what is left, with no layout change and no flash of empty box. */
.gs-frame-still { position: absolute; inset: 0; z-index: 0; }
.gs-frame-box.no-motion .gs-frame-video { display: none; }
.gs-frame-box.no-motion .gs-frame-still { position: relative; }

.gs-hot-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;   /* empty until step 4; must never eat a click */
}
.gs-frame-caption {
  margin-top: 12px;
  font-size: 13px;
  color: var(--text-d);
  text-align: center;
}

/* A visitor who asked their operating system for less motion gets the still.
   The JS pauses the element as well -- display:none does not reliably stop a
   video downloading or decoding. */
@media (prefers-reduced-motion: reduce) {
  .gs-frame-video { display: none; }
  .gs-frame-still { position: relative; }
}

/* ---- Hero: the loop replaces the two stacked JPGs ---- */
.hero-loop {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 12px;
  border: 1px solid var(--border-b);
  box-shadow: 0 30px 80px rgba(0,0,0,0.55);
  transform: perspective(1400px) rotateY(-6deg);
  transform-origin: left center;
  background: var(--bg-1);
}
.hero-still { display: none; }
.hero-frame.no-motion .hero-loop { display: none; }
.hero-frame.no-motion .hero-still { display: block; }
@media (prefers-reduced-motion: reduce) {
  .hero-loop { display: none; }
  .hero-still { display: block; }
}

/* ---- Walk-through ---- */
.walk-section { padding: var(--pad); }
.walk-frame { max-width: var(--max-w); margin: 28px auto 0; }

/* ---- The walk-through player (static/js/tour.js) ----
   The hotspots are PERMANENTLY outlined (plan 9.1 D10). A clickable frame that
   looks like a screenshot is a screenshot: nobody clicks it. The toggle turns
   the outlines OFF, which is the only direction that needs a control -- ON is
   the state that teaches. */
.gs-tour { max-width: var(--max-w); margin: 0 auto; }
.gs-tour-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.gs-tour-bar .gs-crumb {
  flex: 1 1 220px;
  min-width: 0;
  font-size: 13px;
  color: var(--text-m);
  margin: 0;
  overflow-wrap: anywhere;
}
.gs-btn {
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
  background: var(--bg-2);
  border: 1px solid var(--border-b);
  border-radius: 999px;
  padding: 9px 16px;
  min-height: 40px;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s, color 0.2s;
}
.gs-btn:hover { border-color: var(--accent); color: var(--accent); }
.gs-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gs-btn--go { color: var(--accent); border-color: var(--accent-dim); background: var(--accent-dim); }
.gs-restart { font-size: 12.5px; color: var(--text-d); text-decoration: underline; }
.gs-restart:hover { color: var(--accent); }

/* The frame swaps under a short cross-fade. Reduced motion gets neither the
   fade nor the pulse -- the JS skips the timer as well, so the swap is one
   synchronous paint rather than a fast animation. */
.gs-frame-box.gs-swapping { opacity: 0.25; }
.gs-frame-box { transition: opacity 0.16s linear; }

.gs-tour-live .gs-hot-layer { pointer-events: none; }
.gs-hot {
  position: absolute;
  display: block;
  pointer-events: auto;
  border: 1px solid rgba(102,192,244,0.35);
  border-radius: 5px;
  background: rgba(102,192,244,0.05);
  text-decoration: none;
  transition: background 0.18s, border-color 0.18s, box-shadow 0.18s;
}
.gs-hot:hover,
.gs-hot:focus-visible {
  border-color: var(--accent);
  background: rgba(102,192,244,0.18);
  box-shadow: 0 0 0 2px var(--accent-glow);
  outline: none;
}
.gs-hot-label {
  position: absolute;
  left: 0;
  top: 100%;
  margin-top: 5px;
  white-space: nowrap;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent);
  background: rgba(10,14,20,0.9);
  border: 1px solid var(--accent-dim);
  border-radius: 6px;
  padding: 3px 7px;
  opacity: 0;
  transition: opacity 0.18s;
  pointer-events: none;
}
.gs-hot:hover .gs-hot-label,
.gs-hot:focus-visible .gs-hot-label { opacity: 1; }
.gs-hot-pulse { animation: gs-pulse 2.6s ease-in-out infinite; }
@keyframes gs-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(102,192,244,0); }
  50% { box-shadow: 0 0 0 4px rgba(102,192,244,0.22); }
}
.gs-no-outlines .gs-hot {
  border-color: transparent;
  background: transparent;
  animation: none;
}
.gs-no-outlines .gs-hot:hover,
.gs-no-outlines .gs-hot:focus-visible {
  border-color: var(--accent);
  background: rgba(102,192,244,0.14);
}
@media (prefers-reduced-motion: reduce) {
  .gs-hot-pulse { animation: none; }
  .gs-frame-box { transition: none; }
}

/* The phone list. Built at every width and hidden above 700 px, so a rotate or
   a resize never lands on a frame with no visible way out. */
.gs-taps { display: none; }
.gs-tap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 48px;
  padding: 11px 14px;
  margin-top: 8px;
  border: 1px solid var(--border-b);
  border-radius: 11px;
  background: var(--bg-1);
  color: var(--text);
  text-decoration: none;
  font-size: 14.5px;
}
.gs-taps-head {
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--text-d);
  margin: 4px 0 2px;
}
.gs-tap:active { border-color: var(--accent); }
.gs-tap-label { font-weight: 600; }
.gs-tap-to { font-size: 12.5px; color: var(--text-d); text-align: right; }
.gs-taps-none { font-size: 13px; color: var(--text-d); margin-top: 10px; }

.gs-what {
  margin-top: 16px;
  border: 1px solid var(--border);
  border-radius: var(--rad);
  background: var(--bg-1);
  padding: 12px 16px;
}
.gs-what summary {
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-m);
}
.gs-what summary:hover { color: var(--accent); }
.gs-what-text { margin: 10px 0 0; font-size: 14px; line-height: 1.65; color: var(--text-m); }

.gs-node-extra { margin-top: 14px; text-align: center; }
.gs-chip {
  display: inline-block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent);
  border: 1px solid var(--accent-dim);
  background: var(--accent-dim);
  border-radius: 999px;
  padding: 8px 15px;
  text-decoration: none;
}
.gs-chip:hover { border-color: var(--accent); }

/* The player inside the /screens/ lightbox: same component, no page hash.
   It is parsed at the bottom of the page and moved into #lightbox on load, so
   it is display:none until it is inside one -- otherwise a second frame flashes
   under the catalogue on every page load. */
#lightboxTour { display: none; }
.lightbox #lightboxTour { display: block; }
.lightbox .gs-tour {
  width: min(1180px, 92vw);
  max-height: 88vh;
  overflow: auto;
  padding: 16px;
}
.lightbox .gs-tour .gs-what { display: none; }

@media (max-width: 700px) {
  /* D5: a 0.05-high hotspot is nine pixels on a phone. Rows instead. */
  .gs-hot-layer { display: none; }
  .gs-taps { display: block; }

  /* D12: THE CONTROLS GO UNDER THE FRAME. Above the frame they were the first
     thing on the section and the picture they act on was off the bottom of a
     phone screen, so Back and Next were two buttons with no subject. Ordering
     the flex column moves the whole bar -- breadcrumb included -- to sit with
     the tap rows it belongs to, without touching the markup, which the
     homepage and the /screens/ lightbox share. Anything not listed keeps
     order 0 and stays above the frame. */
  .gs-tour { display: flex; flex-direction: column; }
  .gs-tour .gs-frame { order: 1; }
  .gs-tour-bar { order: 2; gap: 8px; margin: 12px 0 0; }
  .gs-taps { order: 3; }
  .gs-what { order: 4; }
  .gs-node-extra { order: 5; }

  /* D12: breadcrumb and "Start over" share ONE row -- a phone's walk-through
     is breadcrumb -> [Back][Next] -> the tap list with a single caption
     ("pick where to go next", painted by tour.js), and a separate full-width
     "Start over" row used to be a fourth text element between the buttons and
     the list. `order` puts both on the FIRST line the flex-wrap draws.

     REGRESSION (measured at 560px): `flex: 1 1 auto` on the crumb only sets
     its GROW/SHRINK basis, not a floor -- with a short breadcrumb like "Start
     menu" the crumb + restart left just enough of the line free for Back's
     `calc(50% - 4px)` to squeeze on too, giving "Start menu | Start over |
     Back" on row 1 and a lone full-width Next on row 2. A forced break is the
     only thing that is not sensitive to how long the breadcrumb text is: a
     zero-size flex item with `flex-basis: 100%` always consumes the rest of
     line 1, so nothing after it can share that line regardless of width.
     Back/Next get an explicit `order` strictly after the break (equal to each
     other, so source order still puts Back first). */
  .gs-tour-bar .gs-crumb { flex: 1 1 auto; min-width: 0; order: -1; }
  .gs-tour-bar .gs-restart { flex: 0 0 auto; order: -1; text-align: right; }
  .gs-tour-bar::after { content: ""; flex-basis: 100%; order: 0; }
  /* Two-up: Back and Next are one decision, and a full-width Next stacked on a
     full-width Back is a column of buttons a thumb has to read. */
  .gs-tour-bar .gs-back,
  .gs-tour-bar .gs-next { flex: 1 1 calc(50% - 4px); order: 1; }
  /* D12: the outline toggle turns off something a phone never sees -- the
     overlay hotspots are display:none at this width. */
  .gs-tour-bar .gs-outline-toggle { display: none; }
}

/* ---- Pillars ---- */
.pillars-section {
  padding: var(--pad);
  background: var(--bg-1);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.pillar-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  margin-top: 34px;
}
.pillar {
  display: flex;
  flex-direction: column;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--rad);
  overflow: hidden;
  transition: border-color 0.25s, transform 0.25s, box-shadow 0.25s;
}
.pillar:hover {
  border-color: var(--accent);
  transform: translateY(-4px);
  box-shadow: 0 12px 32px rgba(0,0,0,0.35);
}
.pillar-shot {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: #000;
  cursor: zoom-in;
  font: inherit;
  color: inherit;
}
.pillar-shot img,
.pillar-shot video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 19 / 9;
  object-fit: cover;
}
.pillar-shot:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pillar-body { padding: 20px 22px 22px; display: flex; flex-direction: column; flex: 1; }
.pillar-body h3 { font-family: var(--font-d); font-size: 21px; margin-bottom: 8px; }
.pillar-body p { color: var(--text-m); font-size: 14px; line-height: 1.65; }
.pillar-state {
  margin-top: 12px;
  font-size: 12px;
  color: var(--text-d);
  line-height: 1.55;
}
.pillar-chip {
  align-self: flex-start;
  margin-top: 16px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent);
  border: 1px solid var(--accent-dim);
  background: var(--accent-dim);
  border-radius: 999px;
  padding: 6px 13px;
  text-decoration: none;
  transition: border-color 0.2s;
}
.pillar-chip:hover { border-color: var(--accent); }

/* ---- The backdrops pillar IS the grid (D9), not a picture of one ---- */
/* Full width, not `span 2`: nine cards in a three-column grid put this one
   last, and spanning two left a card-sized hole beside it that reads as a
   failed image. It also gives 29 thumbnails room to be legible. */
.pillar--backdrops { grid-column: 1 / -1; }
/* D13: FIVE COLUMNS, not as many 112 px cells as fit. Twenty-nine thumbnails
   at ten-across were a texture -- no scene was legible, and the shelf read as
   a swatch strip rather than as twenty-nine places to play. Five across a
   full-width pillar is ~210 px a cell, which is close to the 475 px source and
   wide enough to recognise. */
.backdrop-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 12px;
  padding: 18px 22px 4px;
}
.backdrop-cell { position: relative; margin: 0; }
.backdrop-cell img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 19 / 9;
  object-fit: cover;
  border-radius: 7px;
  border: 1px solid var(--border);
}
.backdrop-cell figcaption {
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--text-d);
  line-height: 1.3;
  overflow-wrap: anywhere;
}
/* D13: a CORNER DOT, not a word. Fourteen "REACTIVE" pills over twenty-nine
   small pictures were the loudest thing in the shelf, and the shelf is meant
   to be the pictures. The word stays in the markup and is clipped rather than
   removed -- it is the only thing telling a screen reader which scenes react,
   and the cell's aria-label (backdrops.js) says it in a sentence. */
.backdrop-pill {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 9px;
  height: 9px;
  padding: 0;
  border-radius: 50%;
  background: var(--accent);
  border: 1px solid rgba(10,14,20,0.9);
  box-shadow: 0 0 0 1px var(--accent-dim);
  overflow: hidden;
  text-indent: -999px;
  white-space: nowrap;
  font-size: 0;
  line-height: 0;
  color: transparent;
}

/* ---- "What you need" ---- */
.need-section { padding: 56px 24px 0; }
.need-strip {
  max-width: var(--max-w);
  margin: 0 auto;
  border: 1px solid var(--border);
  border-radius: var(--rad);
  background: var(--bg-1);
  padding: 26px 28px;
}
.need-strip h2 { font-family: var(--font-d); font-size: 24px; margin-bottom: 16px; }
.need-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin: 0 0 14px;
  padding: 0;
}
.need-list li {
  position: relative;
  padding-left: 16px;
  font-size: 14.5px;
  color: var(--text-m);
}
/* A CSS marker rather than an <i class="fa-*">: icons.css is a ~30-glyph
   hand-built sheet and anything not in it renders as nothing at all. */
.need-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.need-note { font-size: 13px; color: var(--text-d); line-height: 1.6; margin: 0; }

/* ==========================================
   /screens/ -- the full catalogue
   ========================================== */
.cat-layout {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 24px 80px;
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr);
  gap: 40px;
  align-items: start;
}
/* D10: this is the /stats/ rail, ported (`.stats-rail` in site.css). Same
   sticky offset, same numbered counter, same single left axis drawn by the
   LINKS rather than by the nav -- a border on the nav plus a border on each
   link gives two vertical lines a few pixels apart, which is what this page
   had. Keep the two in step: a reader moving between /stats/ and /screens/
   should not notice they changed component. */
.cat-rail {
  position: sticky;
  top: 76px;
  align-self: start;
}
.cat-rail-head {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-d);
  margin-bottom: 10px;
}
.cat-rail ol { list-style: none; margin: 0; padding: 0; counter-reset: rail; }
.cat-rail li { counter-increment: rail; }
.cat-rail a {
  display: block;
  padding: 7px 10px 7px 28px;
  position: relative;
  font-size: 13px;
  line-height: 1.35;
  color: var(--text-m);
  border-left: 2px solid var(--border);
  text-decoration: none;
}
.cat-rail a::before {
  content: counter(rail);
  position: absolute;
  left: 9px;
  color: var(--text-d);
  font-size: 11px;
}
.cat-rail a:hover { color: var(--text); }
.cat-rail a.is-current {
  color: var(--accent);
  border-left-color: var(--accent);
  background: var(--accent-dim);
}
.cat-jump { display: none; }
.cat-jump select {
  width: 100%;
  padding: 11px 12px;
  border-radius: 10px;
  border: 1px solid var(--border-b);
  background: var(--bg-1);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}
.cat-section { margin-bottom: 54px; scroll-margin-top: 78px; }
.cat-section h2 {
  font-family: var(--font-d);
  font-size: 26px;
  margin-bottom: 4px;
}
.cat-count { font-size: 12px; color: var(--text-d); margin-bottom: 18px; }
.cat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 20px;
}
.cat-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--rad);
  overflow: hidden;
  transition: border-color 0.25s, transform 0.25s;
}
.cat-card:hover { border-color: var(--accent); transform: translateY(-3px); }
.cat-shot {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: #000;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
}
.cat-shot:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* D4: the box honours the thumb's OWN aspect ratio (set inline per-card from
   the manifest's real w/h in screens.html), not a fixed 19/9 -- a thumb whose
   real ratio is wider or narrower than 19/9 was cropping real UI ("%", "ited",
   "Level 8 of 11") that the source file has whole. `contain`, not `cover`: with
   the box already matching the media's ratio the two behave the same, and
   `contain` is the one that cannot re-crop a thumb whose manifest w/h is off
   by a little. */
.cat-card img,
.cat-card video {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  background: #000;
  border-bottom: 1px solid var(--border);
}
.cat-body { padding: 14px 16px 18px; }
.cat-body h3 { font-size: 15.5px; margin-bottom: 6px; line-height: 1.35; }
.cat-state { font-size: 12px; color: var(--text-d); line-height: 1.5; margin-bottom: 8px; }
.cat-blurb { font-size: 13px; color: var(--text-m); line-height: 1.6; }

/* Lightbox extras: the prev/next pair for stepping a section. They live in
   screens.html because only that page steps; the shell's lightbox is shared. */
.lb-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10001;
  background: rgba(10,14,20,0.75);
  border: 1px solid var(--border-b);
  color: var(--text);
  border-radius: 50%;
  width: 46px;
  height: 46px;
  font-size: 20px;
  cursor: pointer;
  display: none;
}
.lightbox.active .lb-nav { display: block; }
.lb-prev { left: 18px; }
.lb-next { right: 18px; }
.lb-nav:hover { border-color: var(--accent); color: var(--accent); }
.lb-caption {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 22px;
  text-align: center;
  font-size: 13px;
  color: var(--text-m);
  padding: 0 60px;
  z-index: 10001;
  display: none;
}
.lightbox.active .lb-caption { display: block; }

/* ==========================================
   Narrow
   ========================================== */
@media (max-width: 900px) {
  .pillar-grid { grid-template-columns: repeat(2, 1fr); }
  .backdrop-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .pillar--backdrops { grid-column: 1 / -1; }
  .cat-layout { grid-template-columns: minmax(0, 1fr); gap: 20px; }
  .cat-rail { display: none; }
  .cat-jump { display: block; position: sticky; top: 62px; z-index: 5; padding: 10px 0; background: var(--bg-0); }
}
@media (max-width: 700px) {
  .pillar-grid { grid-template-columns: minmax(0, 1fr); }
  .pillar--backdrops { grid-column: 1 / -1; }
  .backdrop-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 14px 16px 2px; }
  .cat-grid { grid-template-columns: minmax(0, 1fr); }
  .need-list { gap: 8px 0; flex-direction: column; }
  .hero-loop { transform: none; }
  .lb-nav { width: 38px; height: 38px; font-size: 17px; }
  .lb-prev { left: 6px; }
  .lb-next { right: 6px; }
}
