/* ============================================================
   LAB
   Two pieces stacked down the page, each in an outlined box of
   the same size, each with a mono label on the box's left edge.
   Ordinary vertical scrolling — no pinning, no hijacking.
   ============================================================ */

body.lab { background: var(--bg); }

/* Sideways, and the browser's own: a scroll container with snap points rather
   than a pinned track being translated under a wheel listener. Nothing to
   measure, nothing to hijack, and the trackpad, wheel, keyboard, scrollbar and
   shift-scroll all behave the way they should.

   The second piece is deliberately left part-visible at rest. A row that ends
   exactly at the viewport edge looks like the whole page; the piece cut off by
   the edge is the only thing telling you there is more. */
body.lab .lab-page {
  --peek-gap: 70px;
  --box-w: min(880px, 72vw);
  display: flex; flex-direction: row; align-items: center;
  gap: var(--peek-gap);
  height: 100vh;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  /* Side padding centres whichever piece you are on, rather than parking it
     against a gutter: the first sits in the middle of the screen at rest, and
     scrolling to the end centres the second the same way. The peek is then
     whatever the gap leaves over on the right — which is the point, since the
     piece cut off by the edge is the only thing saying there is more. */
  padding: calc(var(--nav-h) + var(--sp-24))
           max(var(--gutter), calc((100vw - var(--box-w)) / 2))
           var(--sp-24);
  /* the container owns the vertical space exactly, so the page itself never
     grows a vertical scrollbar next to the horizontal one */
  box-sizing: border-box;
}
body.lab .lab-page > .lab-piece { flex: none; scroll-snap-align: center; }

/* The column IS the box's width, so the label lands on its left edge rather
   than the page's. --box-w feeds a division below, so it has to stay an
   absolute length: a percentage there makes the scale meaningless. */
/* --box-w is declared on .lab-page (the centring maths needs it) and inherited
   here. The mobile override still sets it on .lab-piece, which wins locally. */
.lab-piece {
  margin: 0; display: flex; flex-direction: column; width: var(--box-w);
}

body.lab .lab-label {
  font-style: normal;                    /* figcaption is italic in base.css */
  font-family: var(--font-mono); font-size: 14px; line-height: 16px;
  letter-spacing: -.04em; text-transform: uppercase; color: var(--fg);
  margin: 0 0 var(--sp-16); text-align: left;
}
body.lab .lab-label .note { color: var(--muted); }

/* The label is a row now: name on the left, the control that opens the piece on
   the right. The name MUST stay wrapped in .lab-label-text — a bare text node
   beside the .note span makes two anonymous flex items, and space-between then
   throws the name and its note to opposite ends of the box. */
body.lab .lab-label {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-16);
  /* the floor is the control's height, so a piece with a button and a piece
     without one have the same row and their frames start level */
  min-height: 24px;
}
body.lab .lab-label-text { min-width: 0; }

/* House style, same language as the gallery arrows: hairline box, zero radius,
   one ink, no fill. Crop-mark glyph rather than diagonal arrows. */
.lab-expand {
  /* 24, and the label row carries a 24 floor to match — the button used to be
     44 at every width, which made the first piece's label row 44 tall against
     the second's 16 and started its frame 28px lower. The 44px hit box is a
     TOUCH requirement, so it is claimed in the touch block below, where the
     pieces are stacked and no two rows have to agree. */
  flex: none; width: 24px; height: 24px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: 0;
  background: transparent; color: var(--fg); cursor: pointer;
  transition: border-color .25s ease;
}
.lab-expand:hover, .lab-expand:focus-visible { border-color: var(--fg); }
.lab-expand svg { width: 12px; height: 12px; display: block; }
/* 1, not 1.25: the glyph sits inside a 1px hairline box, and a stroke heavier
   than its own container reads as a different weight of line to the rest of the
   system. Mitre joins rather than round — the house has right angles. */
.lab-expand svg path {
  fill: none; stroke: currentColor; stroke-width: 1;
  stroke-linecap: butt; stroke-linejoin: miter;
}

/* Full screen, the iframe is given the real viewport instead of being scaled
   down inside a box. A landing page seen at its own dimensions is the point of
   the control — keeping the transform would just show the same small render on
   a bigger black ground. */
.lab-shot:fullscreen,
.lab-shot:-webkit-full-screen { width: 100vw; height: 100vh; border: 0; }
.lab-shot:fullscreen iframe,
.lab-shot:-webkit-full-screen iframe {
  transform: none; width: 100vw; height: 100vh;
}

/* One box, one size, both pieces. 16:9 because the AI Scouts frame is. */
.lab-box {
  --frame-w: 1440; --frame-h: 810;
  position: relative; overflow: hidden;
  width: var(--box-w);
  height: calc(var(--box-w) * var(--frame-h) / var(--frame-w));
  border: 1px solid var(--border);
  background: var(--bg);
}

/* ---- the AI Scouts hero ------------------------------------------------ */
/* Scaled, not resized: shrinking the frame reflows the page inside it to a
   narrow viewport, which shows a different design rather than a smaller one. */
/* The box wears a still of the hero's own meadow, so the grass is there before
   the (lazy) iframe has even started loading — never beige, never flat green. */
.lab-shot { background: #355c30 url(../lab/ai-scouts-poster.webp) center top / cover no-repeat; }
.lab-shot iframe {
  position: absolute; top: 0; left: 0;
  width: calc(var(--frame-w) * 1px);
  height: calc(var(--frame-h) * 1px);
  border: 0; display: block;
  transform: scale(calc(var(--box-w) / (var(--frame-w) * 1px)));
  transform-origin: 0 0;
}

/* ---- paint a figure ---------------------------------------------------- */
.lab-make {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--sp-24);
  padding: var(--sp-24);
}

/* The flow that made these, tucked into the corner. Absolute so it sits over
   the box's own centred column rather than pushing it off centre. */
/* A source note, so it stays small — at 30% it competed with the figure and a
   generated result ran underneath it. At 18% the corner is clear: its left edge
   sits at 706 in an 880 box, and the result is capped below to stop short. */
.lab-make picture:has(.lab-source) {
  position: absolute; top: var(--sp-16); right: var(--sp-16);
  width: 18%; z-index: 1; display: block;
}
.lab-source {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--border);
  cursor: zoom-in;
  /* pinned to the corner it lives in, so it grows down and to the left
     rather than pushing out of the box */
  transform-origin: 100% 0;
  transition: transform .25s cubic-bezier(.22,1,.36,1), border-color .25s ease;
}
.lab-source:hover, .lab-source:focus-visible {
  transform: scale(1.6);
  border-color: var(--fg);
  z-index: 3;                          /* over a result, while it is enlarged */
}

/* the figure with an arrow either side; the arrows only appear once there is a
   set to step through, and hold their space so nothing shifts when they do */
/* full width, or .lab-art's percentage resolves against a shrink-to-fit
   parent and the figure collapses to nothing */
.lab-stack { display: flex; align-items: center; justify-content: center;
  gap: var(--sp-16); width: 100%; }
.lab-step {
  flex: none; width: 44px; height: 44px; padding: 0;   /* was 32: 44px hit box */
  border: 1px solid var(--border); border-radius: 0;
  background: transparent; color: var(--fg);
  font-family: var(--font-mono); font-size: 14px; line-height: 1;
  cursor: pointer;
}
.lab-step:hover:not(:disabled) { border-color: var(--fg); }
.lab-step:disabled { opacity: .3; cursor: default; }
.lab-step[hidden] { display: block; visibility: hidden; }

/* two layers, as on the homepage */
.lab-art { position: relative; width: 34%; aspect-ratio: 1 / 1; flex: none; }
.lab-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
  /* Centred on the BODY, not on the drawing: the cast shadow runs off to the
     left, so a centred image puts the figure right of centre. In the default
     figure the body's centre sits 10.7% of the width right of the image's, so
     an 11% shift lands him in the middle. The box itself does not move, so
     the arrows either side and the bar below hold their places; and since
     every result, painted or default, lands in these same two layers, new
     figures get the same offset (Josh, 5 Sep 2026: near enough is fine). */
  transform: translateX(-11%); }
.lab-art .shadow { z-index: 0; }
.lab-art .front  { z-index: 2; }

/* Painting. The house has one ink, five opacities and no gradients, so the wait
   is the drawing itself going quiet and breathing — the same pulse the status
   line uses, on the same 1.4s. A sweeping band was a second vocabulary for one
   state, and the note and the shut field already say what is happening. */
/* Its own keyframes, not the note's: an animation on opacity beats the static
   declaration outright, so borrowing lab-pulse would have pulsed him at .45-1
   and undimmed the very thing being dimmed. Same 1.4s, lower range. */
.lab-make.is-painting .lab-art img { animation: lab-quiet 1.4s ease-in-out infinite; }
@keyframes lab-quiet { 0%, 100% { opacity: .16 } 50% { opacity: .38 } }
@media (prefers-reduced-motion: reduce) {
  .lab-make.is-painting .lab-art img { animation: none; opacity: .3; }
}

/* A result replaces the artwork inside .lab-art, so it lands at exactly the
   size and position of the default figure — nothing here resizes it. These
   rules now serve only the out-of-turns video. */
.lab-out { display: contents; }
.lab-plate { margin: 0; min-height: 0; display: flex; }
.lab-plate video {
  display: block; min-height: 0; max-height: 100%;
  width: auto; max-width: min(520px, 100%);   /* stops short of the corner note */
  object-fit: contain;
}

/* ---- the ask ------------------------------------------------------------
   The Figma frame draws this as a soft card: Open Sans, #FFFBF3, an 11px
   radius, a drop shadow and a pill button. That is a different language from
   the rest of the site, so it is rebuilt in the house one — hairline box, zero
   radius, no shadow, one ink at its opacities, mono for the label. The prefix
   still reads into the field, which was the frame's good idea. */
.lab-ask { display: flex; flex-direction: column; align-items: center; flex: none; }

.lab-bar {
  display: flex; align-items: stretch;
  width: min(360px, 100%); height: 44px;
  border: 1px solid var(--border);
  border-radius: 0;                      /* house: zero radius, everywhere */
  background: transparent;
  transition: border-color .25s ease;
}
/* Three steps on one ink, all existing tokens: at rest the box outline (.3),
   under the cursor --border-strong (.55), which the house already defines as
   the weight that "bounds controls, not dividers", and full ink on focus.
   Ordered so focus wins while hovering a focused field, and both the error and
   spent rules sit later still — an errored field stays inked, and a field with
   no turns left does not light up under a cursor that cannot use it. */
.lab-bar:hover { border-color: var(--border-strong); }
.lab-bar:focus-within { border-color: var(--fg); }

.lab-bar-fields { display: flex; align-items: stretch; flex: 1 1 auto; min-width: 0; }

/* Optically centred, not geometrically. The label is uppercase, so it carries no
   descenders — but its line box still reserves the descender space below the
   baseline, and centring THAT box leaves the caps sitting high by about half of
   it. Measured at 1.29px in the 44px bar; 1px is the whole-pixel correction, and
   `top` rather than padding so it shifts the ink without touching the layout.
   Any all-caps label centred in a fixed-height box has this; mixed-case text
   does not, which is why the input beside it needs no such nudge. */
.lab-bar-prefix {
  position: relative; top: 1px;
  display: flex; align-items: center; flex: none;
  /* Even both sides. It was 16/12, which read as 1.74px right of centre — the
     cell shrink-wraps its text, so unequal padding IS the off-centring. 16 to
     match the input's own padding, so the whole control uses one value. */
  padding: 0 var(--sp-16);
  border-right: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 12px; line-height: 16px;
  letter-spacing: -.04em; text-transform: uppercase; color: var(--sublabel);
  white-space: nowrap;
}
.lab-bar input {
  flex: 1 1 auto; min-width: 0;
  border: 0; background: transparent; border-radius: 0;
  padding: 0 var(--sp-16);
  font-family: var(--font-body); font-size: 15px; line-height: 20px;
  color: var(--fg);
}
.lab-bar input::placeholder { color: var(--muted); }
.lab-bar input:focus { outline: none; }

/* square, not a pill: the house has no rounded controls */
.lab-bar button {
  flex: none; width: 44px;
  background: var(--fg); border: 0; border-radius: 0;
  display: grid; place-items: center; cursor: pointer; padding: 0;
}
.lab-bar button svg { width: 10px; height: 11px; display: block; }
.lab-bar button svg path {
  fill: none; stroke: var(--bg); stroke-width: 1.25;
  stroke-linecap: round; stroke-linejoin: round;
}
.lab-bar button:disabled { opacity: .25; cursor: default; }

/* Not in the frame — the states have to say something, so they sit quietly
   beneath the control rather than inside it. */
body.lab .lab-note, body.lab .lab-count {
  font-family: var(--font-mono); font-size: 12px; line-height: 16px;
  letter-spacing: -.04em; color: var(--sublabel);
  margin: var(--sp-8) 0 0; text-align: center;
}
body.lab .lab-count { margin-top: var(--sp-4); }
/* The note keeps its line even when empty. Collapsing it re-centres the column
   and the figure jumps 12px the moment a status appears. */
body.lab .lab-note { min-height: 16px; }
body.lab .lab-count:empty { display: none; }

/* Refused. One ink and five opacities means no red is available, so the state
   is carried by weight and a mark: the note comes up from sub-label to full ink,
   and the bar takes a second ink hairline along its bottom edge. Inset, so the
   box does not grow by a pixel and shift the figure above it. */
body.lab .lab-note.is-error { color: var(--fg); }
.lab-bar.is-error { border-color: var(--fg); box-shadow: inset 0 -1px 0 var(--fg); }

/* Nothing left to type. Reads as closed rather than merely unfocused. */
.lab-bar input:disabled { color: var(--muted); cursor: not-allowed; }
.lab-bar:has(input:disabled) { border-color: var(--border); opacity: .55; }

.lab-waiting { animation: lab-pulse 1.4s ease-in-out infinite; }
@keyframes lab-pulse { 0%,100% { opacity: .45 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .lab-waiting { animation: none; } }

@media (max-width: 860px) {
  /* Back to a column. A sideways row of 420px boxes on a phone is a worse
     version of scrolling down, and the peek costs more than it explains. */
  body.lab .lab-page {
    flex-direction: column; align-items: center;
    height: auto; overflow: visible;
    gap: var(--sp-48);
    padding: calc(var(--nav-h) + var(--sp-48)) var(--gutter) var(--sp-88);
  }
  .lab-piece { --box-w: min(86vw, 420px); }
  /* 16:9 at this width is 189px tall, which cannot hold the figure and a 47px
     control. Matching heights is a desktop concern — the two stack here anyway,
     so this box grows to its contents and keeps the other as its floor. */
  .lab-make {
    height: auto;
    min-height: calc(var(--box-w) * var(--frame-h) / var(--frame-w));
    gap: var(--sp-16); padding: var(--sp-24) var(--sp-16);
  }
  .lab-art { width: 46%; }
  .lab-make picture:has(.lab-source) { width: 26%; top: var(--sp-8); right: var(--sp-8); }
}

/* Touch: the field and its button are 44px tall. The frame draws them at 47 on
   desktop; under 860 the box padding pulled them to 42. */
@media (max-width: 860px) {
  .lab-bar input, .lab-bar button { min-height: 44px; }
}

/* Touch: the expand control says what it does. A 12px crop-mark glyph on its
   own reads as decoration on a phone; on a pointer device hover and the title
   already explain it. */
@media (pointer: coarse), (max-width: 860px) {
  .lab-expand { width: auto; height: 44px; padding: 0 var(--sp-12); gap: var(--sp-8); display: inline-flex; align-items: center; }
  .lab-expand::after {
    content: "Full screen"; font-family: var(--font-mono); font-weight: 400;
    font-size: 12px; line-height: 16px; letter-spacing: -.04em; text-transform: uppercase;
  }
}
