/* Asset-lab chrome. Loaded AFTER lib/visuals.css so the overrides at the bottom win.
   Nothing here restyles an asset — it only builds the white gallery around them. */

:root {
  --paper:   #ffffff;
  --ink:     #16151c;
  --ink-2:   #55525f;
  --ink-3:   #8b8794;
  --rule:    #e7e5ea;
  --rule-2:  #f2f0f5;
  --brand:   #5e4cff;
  --brand-2: #efecff;
  --pad:     clamp(20px, 4vw, 56px);
  --radius:  14px;
}

* { box-sizing: border-box; }

/* NOT `scroll-behavior: smooth`. The 3D tiles measure themselves every frame, and those
   layout reads cancel the browser's native smooth scroll the instant it starts — a nav
   click moved the page about 3px and stopped. app.js tweens the scroll itself instead. */
html { scroll-padding-top: var(--head-h, 135px); }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 15px/1.55 'Inter', system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---------------- header ---------------- */

.labHead {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255, 255, 255, 0.86);
  -webkit-backdrop-filter: blur(14px) saturate(1.6);
  backdrop-filter: blur(14px) saturate(1.6);
  border-bottom: 1px solid var(--rule);
}
.labHead__in { display: flex; gap: 16px; align-items: flex-start; padding: 22px var(--pad) 14px; }
/* ld-logo.svg is the shipped mark — white bolt on a black tile — so it's used as-is
   rather than reconstructed from bolt.svg with a CSS mask. */
.labHead__mark { flex: none; width: 40px; height: 40px; border-radius: 10px; display: block; }
.labHead h1 { margin: 0 0 2px; font-size: 19px; font-weight: 600; letter-spacing: -0.022em; }
.labHead p { margin: 2px 0 0; font-size: 13.5px; color: var(--ink-2); max-width: 68ch; }
.labHead b { color: var(--ink); font-weight: 600; }

.labNav { display: flex; gap: 4px; padding: 0 var(--pad) 12px; overflow-x: auto; scrollbar-width: none; }
.labNav::-webkit-scrollbar { display: none; }
.labNav a {
  flex: none; padding: 6px 12px; border-radius: 99px; text-decoration: none;
  font-size: 12.5px; font-weight: 500; color: var(--ink-2); white-space: nowrap;
  border: 1px solid transparent; transition: background .16s, color .16s, border-color .16s;
}
.labNav a:hover { background: var(--rule-2); color: var(--ink); }
.labNav a.is-current { background: var(--brand); color: #fff; border-color: var(--brand); }

/* ---------------- sections ---------------- */

.sect { padding: 56px var(--pad) 8px; scroll-margin-top: var(--head-h, 135px); }
.sect + .sect { border-top: 1px solid var(--rule-2); }
.sect__head { max-width: 74ch; margin-bottom: 26px; }
.sect__n {
  font: 400 10px/1 'Press Start 2P', monospace; letter-spacing: 0.1em;
  color: var(--brand); text-transform: uppercase;
}
/* group header WITHIN a section (e.g. "Customer logos") — same pixel voice as the section
   number, but it has to clear the grid above it */
.sect__sub {
  font: 400 10px/1 'Press Start 2P', monospace; letter-spacing: 0.1em;
  color: var(--brand); text-transform: uppercase;
  margin: 44px 0 18px;
}
.sect__head h2 { margin: 12px 0 6px; font-size: 25px; font-weight: 600; letter-spacing: -0.028em; }
.sect__head p { margin: 0; color: var(--ink-2); font-size: 14px; }

/* `min(Xpx, 100%)` is what makes these safe on a phone: a bare minmax(500px, 1fr) forces a
   500px column on a 390px screen and the whole page scrolls sideways. With min(), the track
   collapses to the container width instead and the tile — and everything inside it, which is
   sized in cqh — simply scales down. */
.grid { display: grid; gap: 22px; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
.grid--wide { grid-template-columns: repeat(auto-fill, minmax(min(500px, 100%), 1fr)); }
.grid--tight { grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); gap: 14px; }
.grid--full { grid-template-columns: 1fr; }
/* very wide, short assets (the nav hint strip, the segment shadow) are unreadable in a
   150px cell — let them take the whole row instead */
.labTile--span { grid-column: 1 / -1; }

/* ---------------- tile ---------------- */

.labTile { margin: 0; display: flex; flex-direction: column; min-width: 0; }
.tile__stage {
  position: relative; overflow: hidden;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: #fff;
  aspect-ratio: var(--ar, 3.5 / 2.62);
}
.tile__stage--dark { background: #0b0b0d; }
/* Portrait screens (the mobile full-fill layouts) — capped to a phone width so a tall
   aspect ratio in a wide column doesn't produce a 1400px-tall tile. `width:100%` is load-
   bearing: `.labTile` is a column flex container, and an auto cross-axis margin cancels the
   default stretch, which collapsed the stage to zero width. */
.tile__stage--phone { width: 100%; max-width: min(300px, 100%); margin-inline: auto; }
.tile__stage--grid {
  background-image: linear-gradient(var(--rule-2) 1px, transparent 1px),
                    linear-gradient(90deg, var(--rule-2) 1px, transparent 1px);
  background-size: 22px 22px;
}
.tile__stage--check {   /* transparency checker, for cut-out art */
  background-image: linear-gradient(45deg, #f4f3f7 25%, transparent 25%, transparent 75%, #f4f3f7 75%),
                    linear-gradient(45deg, #f4f3f7 25%, transparent 25%, transparent 75%, #f4f3f7 75%);
  background-size: 18px 18px; background-position: 0 0, 9px 9px;
}
.tile__stage img.fit { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; padding: 14px; }
.tile__stage img.fill { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.tile__stage img.px { image-rendering: pixelated; }

.tile__cap { display: flex; align-items: baseline; gap: 10px; padding: 11px 2px 0; }
.tile__name { font-size: 13.5px; font-weight: 550; letter-spacing: -0.01em; }
.tile__cap { padding-right: 2px; }
/* Sits in the stage's top-right control row, beside any buttons. */
.tile__meta {
  flex: none; align-self: center;
  font-size: 10.5px; line-height: 1; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  padding: 5px 9px; border-radius: 99px;
  background: rgba(255, 255, 255, 0.92); border: 1px solid var(--rule);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  white-space: nowrap; pointer-events: none;
}
.tile__stage--dark .tile__meta {
  background: rgba(22, 21, 28, 0.72); border-color: rgba(255, 255, 255, 0.16);
  color: rgba(226, 222, 240, 0.72);
}
/* Descriptions are capped at two lines: in a tight grid cell a third line pushes every
   neighbouring tile out of alignment. The copy is written to fit; this is the backstop. */
.tile__note {
  padding: 3px 2px 0; font-size: 12px; color: var(--ink-2);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}

/* replay / control buttons float over the stage */
.tile__ctl { position: absolute; right: 8px; top: 8px; z-index: 8; display: flex; gap: 6px; }
.btn {
  appearance: none; cursor: pointer; font: 500 12px/1 'Inter', sans-serif;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 10px; border-radius: 8px;
  background: rgba(255, 255, 255, 0.92); color: var(--ink-2);
  border: 1px solid var(--rule);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background .15s, color .15s, border-color .15s, transform .1s;
}
.btn:hover { color: var(--brand); border-color: #d7d0ff; background: #fff; }
.btn:active { transform: translateY(1px); }
.btn.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* Icon-only buttons (the ⟳ replay) — the glyph carries the whole control, so it wants to
   be read at a glance rather than sit at label size. */
.btn--icon { padding: 8px; }
.btn--icon svg { display: block; width: 19px; height: 19px; }
/* the wheel turns once when you press it, so the replay reads as an action */
.btn--icon.is-spinning svg { animation: btnSpin .55s cubic-bezier(.4, 0, .2, 1); }
@keyframes btnSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.tile__stage--dark .btn { background: rgba(22, 21, 28, 0.72); border-color: rgba(255, 255, 255, 0.16); color: #d8d4e8; }
.tile__stage--dark .btn:hover { background: rgba(32, 30, 44, 0.92); color: #fff; }
.tile__stage--dark .btn.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* Icon-only buttons (the ⟳ replay) — the glyph carries the whole control, so it wants to
   be read at a glance rather than sit at label size. */
.btn--icon { padding: 8px; }
.btn--icon svg { display: block; width: 19px; height: 19px; }
/* the wheel turns once when you press it, so the replay reads as an action */
.btn--icon.is-spinning svg { animation: btnSpin .55s cubic-bezier(.4, 0, .2, 1); }
@keyframes btnSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

.tileHint { position: absolute; left: 10px; bottom: 9px; z-index: 8; font-size: 11px; color: var(--ink-3); pointer-events: none; }
.tile__stage--dark .tileHint { color: rgba(226, 222, 240, 0.55); }

/* ---------------- 3D ---------------- */

/* 3D tiles hold a plain 2D canvas that the shared WebGL renderer blits into each frame
   (see app.js) — so they need no special stacking, and the white stage shows through the
   transparent clear. */
.tile__stage--3d { background: #fff; }
.tile__stage--3d canvas { display: block; }

/* ---------------- swatches / type ---------------- */

/* The styleguide is reference, not showcase — so it stays quiet: no cards, no borders,
   nothing repeated that the swatch or specimen already shows. */

.sw { display: flex; flex-direction: column; }
.sw__chip { height: 56px; border-radius: 8px; border: 1px solid rgba(0, 0, 0, 0.07); }
.sw__name { margin-top: 7px; font-size: 12.5px; font-weight: 500; }
.sw__val { font: 400 11px/1.4 'JetBrains Mono', monospace; color: var(--ink-3); }

.typeList { display: flex; flex-direction: column; }
.typeRow {
  display: flex; align-items: baseline; gap: 28px;
  padding: 18px 0; border-top: 1px solid var(--rule-2);
}
.typeRow:first-child { border-top: 0; }
.typeRow__label { flex: 0 0 200px; }
.typeRow__name { font-size: 13px; font-weight: 550; }
.typeRow__use { font-size: 12px; color: var(--ink-3); }
.typeRow__spec { min-width: 0; overflow-wrap: anywhere; }

.specList {
  display: grid; grid-template-columns: max-content 1fr; gap: 8px 28px;
  margin: 0; max-width: 640px;
}
.specList dt { font-size: 12.5px; color: var(--ink-3); }
.specList dd { margin: 0; font: 400 12.5px/1.4 'JetBrains Mono', monospace; }

/* Audio row: play button top-left with equal padding all round, title on one line, and
   the description capped at two like every other subtext on the page. */
.aud {
  display: flex; align-items: flex-start; gap: 12px;
  border: 1px solid var(--rule); border-radius: 10px; padding: 14px;
}
.aud__play { flex: none; width: 36px; height: 36px; padding: 0; display: grid; place-items: center; }
.aud__txt { min-width: 0; padding-top: 2px; }
.aud__name {
  font-size: 13px; font-weight: 550;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.aud__meta {
  font-size: 11.5px; color: var(--ink-3); margin-top: 2px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}

/* the wordmark canvas is CSS-scaled up from its low-res raster, hence pixelated */
.wordmarkHost { position: absolute; inset: 0; display: grid; place-items: center; padding: 12%; }
.wordmarkHost .boot-logo { width: 100%; height: auto; image-rendering: pixelated; }

.labFoot { padding: 40px var(--pad) 64px; border-top: 1px solid var(--rule-2); color: var(--ink-3); font-size: 12.5px; }
.labFoot code { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; color: var(--ink-2); }

.err { padding: 12px 14px; border-radius: 10px; background: #fff4f4; border: 1px solid #ffd9d9; color: #a3232c; font: 400 12px/1.5 'JetBrains Mono', monospace; }

/* ================================================================
   OVERRIDES — turn the source's single fixed screen into many tiles
   ================================================================
   lib/visuals.css defines `.gbScreen` (was #screenUI) as a fixed, zero-size, fully
   transparent overlay that the real page projects onto the Game Boy glass with a
   matrix3d. In the lab each one is just a box that fills its tile. Everything else the
   rule carries — container-type:size (the cqh units ALL the visuals are sized in), the
   brand palette custom properties, the font stack — is left untouched. */

.gbScreen.labScreen {
  position: absolute; inset: 0;
  width: auto; height: auto;
  opacity: 1;
  pointer-events: auto;
  border-radius: 0;
  transition: none;
  background: #0b0b0d;          /* the Game Boy glass, which the WebGL screen provides on the real page */
}

/* The title screen is the same story: #boot is a fixed zero-size container on the real page. */
#boot.labScreen {
  position: absolute; inset: 0;
  width: auto; height: auto;
  opacity: 1; z-index: auto;
  border-radius: 0; transition: none;
  background: #0b0b0d;
}
#boot.labScreen .boot-start { pointer-events: none; }

/* The source fades `.view` in on mount (opacity 0 → .show → 1, stepped). In the lab that
   entry transition repeatedly stuck at currentTime 0 — running, never advancing — leaving
   whole screens invisible even with `opacity:1 !important` inline, because a running
   transition outranks it. The tiles are static showcases that don't want an entry fade
   anyway, so the state is pinned open and the transition dropped. */
.gbScreen.labScreen .view {
  opacity: 1;
  transform: none;
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ================================================================
   MIGRATION — abduction demo
   ================================================================
   A purpose-built composition (see app.js), not a lifted screen — but the sprite and the
   beam texture are the real ones, drawn by the source's own code. */

/* A container in its own right, so the logos and labels below size off the TILE's width
   rather than the viewport's. That's what lets this demo shrink correctly on a phone
   without a mobile variant — same trick the source uses with cqh inside .gbScreen. */
.abd { position: absolute; inset: 0; overflow: hidden; container-type: size; }

/* one row along the bottom */
.abd__row {
  position: absolute; left: 4%; right: 4%; bottom: 8%;
  display: flex; justify-content: space-between; align-items: flex-end; gap: 3%;
}

.abd__pill {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center;
  padding: 1.6cqh 0.6cqw; gap: 1.4cqh;
  transition: opacity .25s, transform .35s cubic-bezier(.4, 0, .2, 1);
  /* no housing — the logos sit straight on the starfield */
}

/* Caught in the beam: drifts along the vector to the beam mouth, so it reads as being
   drawn toward Cloudy rather than just floating upward. --px/--py are set per frame. */
.abd__pill.is-pulled {
  transform: translate(var(--px, 0), var(--py, 0)) scale(1.07);
}
.abd__pill img {
  /* the source's own pill proportions: .mpillLogo is 6.4cqh, .mpillName 1.4cqh */
  height: 8.4cqh; max-width: 76%; object-fit: contain; opacity: .95;
  /* NO knockout filter here: the customer marks are already white cut-outs, but the legacy
     BI logos ship on a white plate — brightness(0)+invert flattened those to solid squares. */
}
.abd__pill span {
  font: 400 1.75cqh/1.3 'Press Start 2P', monospace; letter-spacing: 0;
  color: #fff; text-align: center; text-transform: uppercase; white-space: nowrap;
}

/* Abduction uses the SOURCE's own `pillAbduct` keyframes (in lib/visuals.css), not a
   lookalike: 3.4s stepped, wobbling as it rises, shifting green and pixel-dissolving
   through the px-* filters. It reads off --tx/--ty — the delta from the pill to the beam
   mouth — exactly as the real page sets them per frame. */
.abd__pill.is-taken {
  /* The source runs pillAbduct over 3.4s because on the real page several pills lift at
     once in a stagger. Here it's one logo at a time, so that reads as a stall. */
  animation: pillAbduct 1.5s steps(16) forwards;
  pointer-events: none;
}
.abd__pill.is-back { animation: none; opacity: 1; transition: opacity .5s ease; }

.abd__ufo {
  position: absolute; width: 13%; transform: translate(-50%, -50%);
  z-index: 5; pointer-events: none;
}
.abd__cloudy { display: block; width: 100%; height: auto; image-rendering: pixelated; }

/* the beam hangs from the saucer and only fires when a logo is under it */
.abd__beam {
  position: absolute; left: 50%; top: 78%;
  width: 230%; height: 420%;
  transform: translateX(-50%) scaleY(.28); transform-origin: top center;
  transition: transform .4s cubic-bezier(.35, 0, .2, 1), opacity .3s;
  opacity: 0;
}
.abd.is-locked .abd__beam { transform: translateX(-50%) scaleY(1); opacity: 1; }
.abd__ray {
  position: absolute; inset: 0;
  background-image: var(--beam-tex); background-size: 100% 100%; background-repeat: no-repeat;
  image-rendering: pixelated; mix-blend-mode: screen; opacity: .72;
  animation: beamGlow 2.6s ease-in-out infinite;
}
.abd.is-zapping .abd__ray { opacity: 1; filter: brightness(1.5); }


/* Headline + subtext live in the tile caption instead, so the in-screen header comes off
   (along with its ✕, which has nothing to close here). Governance keeps its `.vsub`
   OUTSIDE `.vhead`, so that needs hiding separately. */
/* The source sets `.priceCta { position: static; width: 100% }` so each tier button sits in
   its card's flow — but the base `.cta` rule (absolute, pinned bottom-left) is declared
   later at equal specificity and wins, leaving the buttons flush against the card border.
   Restated here with enough weight to hold. */
.gbScreen .priceBox .priceCta {
  position: static; width: 100%; max-width: 100%;
  inset: auto; box-sizing: border-box;
}

/* Product visual tiles show the imagery on its own: the LEARN MORE / SEE MORE buttons are
   page furniture, not part of the visual.

   Each visual's group also carries a downward Y offset that clears the carousel's header;
   standalone there is no header. Those are zeroed here, and app.js then measures the
   visible artwork and centres the slide on it — hand-tuned numbers per visual rot the
   moment the source layout moves. */
.gbScreen.is-nocta .biGroup  { --bg-y: 0cqh; }
.gbScreen.is-nocta .daGroup  { --dg-y: 0cqh; --pdg-y: 0cqh; }
.gbScreen.is-nocta .mcGroup  { --mcg-y: 0cqh; }
.gbScreen.is-nocta .embGroup { --emg-y: 0cqh; }

.gbScreen.is-nocta .cta,
.gbScreen.is-nocta .vlearn,
.gbScreen.is-nocta .agentCta { display: none; }

/* The pricing/migration card row is absolutely positioned with a top offset that reserves
   room for the headline. With the headline lifted into the caption that space is dead, so
   the row spans the glass and centres instead. */
.gbScreen.is-headless .migRow {
  top: 0; bottom: 0; height: auto;
  /* the row is a COLUMN flex, so vertical centring is justify-content, not align-items */
  display: flex; justify-content: center;
}

.gbScreen.is-headless .vhead,
.gbScreen.is-headless .vsub,
.gbScreen.is-headless .vclose { display: none; }

/* ================================================================
   NARROW SCREENS
   ================================================================
   The visuals themselves need no mobile variant — everything inside a tile is sized in
   cqh, so it scales with the box. All that's needed is for the boxes to fit. */

@media (max-width: 720px) {
  .labHead__in { padding-block: 16px 10px; }
  .labHead h1 { font-size: 17px; }
  .labHead p { font-size: 12.5px; }

  .sect { padding-top: 40px; }
  .sect__head h2 { font-size: 21px; }

  .grid { gap: 16px; }
  .grid--tight { grid-template-columns: repeat(auto-fill, minmax(min(110px, 100%), 1fr)); gap: 10px; }

  /* type specimens stack rather than sitting label-beside-sample */
  .typeRow { flex-direction: column; gap: 10px; }
  .typeRow__label { flex: none; }

  .specList { grid-template-columns: 1fr; gap: 2px 0; }
  .specList dd { margin-bottom: 10px; }

  /* the abduction demo sizes itself off its own container (cqw), so it needs nothing here */
}

/* Container query, not a media query: what matters is how wide the TILE got, which on a
   two-column desktop grid is already only ~half the viewport. Below this the pixel labels
   collide with each other, and the marks read fine on their own. */
@container (max-width: 300px) {
  .abd__pill span { display: none; }   /* below this the pixel labels collide */
}

/* Cloudy's baby form, drawn with the current artwork instead of the old canvas sprite. */
.cloudyArt { display: block; width: 100%; height: auto; image-rendering: pixelated; }
