/* Labs index (/labs).
   Part of the /labs stylesheet, split out of one 3,400-line labs.css.

   THE NUMBER IN THE FILENAME IS LOAD-BEARING. stylesheet_link_tag :app
   collects every stylesheet under app/assets and serves them sorted by
   path, so the ordinal prefix is the only thing holding the cascade order
   these rules were written in — and several classes are shared across labs
   (.lp-head, .lab-entry, .lab-block, .dt-table). Renaming a file without
   renumbering it, or inserting one out of sequence, reorders the cascade
   silently.

   Nothing in this comment may contain the two characters that close a CSS
   comment. The first version of it wrote a shell glob here, which closed
   the comment early; the leaked text then parsed as a selector and ate the
   first real rule in all nine files. There is a test for that now.
   ============================================================ */

/* ============================================================
   Labs index (/labs) — a short catalogue of the prototypes.
   Number-rail + content layout, monochrome, hairline rules.
   ============================================================ */
.lab-list { margin-top: clamp(24px, 4vw, 40px); }
.lab-entry {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 0 28px;
  padding: clamp(28px, 4vw, 44px) 0;
  border-top: 1px solid var(--line);
}
.lab-entry:first-child { border-top: none; padding-top: 0; }
@media (max-width: 680px) {
  .lab-entry { grid-template-columns: 1fr; gap: 12px; }
}
/* Grid items default to min-width:auto, which refuses to shrink below the
   intrinsic width of their own contents. Signal Line puts a 950px .dt-scroll
   table in here, so the track grew to fit it and the scroller never engaged —
   the whole page scrolled sideways below about 950px instead. Observatory and
   Monday Brief share this grid, so this covers them before they hit it too. */
.lab-main { min-width: 0; }
.lab-num {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  padding-top: 6px;
}
.lab-main .eyebrow { display: block; margin-bottom: 12px; }
.lab-name {
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: clamp(26px, 3.4vw, 38px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0;
}
.lab-name a { color: var(--ink); border-bottom: 1px solid transparent; transition: border-color 0.2s; }
.lab-name a:hover { border-bottom-color: var(--ink); }

.lab-block { margin-top: 22px; }
/* 12px is the floor, and this label is the only thing naming each group in the
   Signal Line's catalogue — at 11px it was doing structural work in the
   smallest type on the page. */
.lab-block-label {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
.lab-block p {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.7;
  color: var(--ink-muted);
  max-width: 64ch;
  margin: 0;
}
.lab-block p.lab-wide { max-width: none; }   /* span the full column, like the stack block */

.lab-open {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 26px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.03em;
  color: var(--bg);
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 9999px;
  padding: 11px 18px;
  /* The design system's 44px floor applies to every pill unconditionally, and
     this one measured about 40. It is the CTA on the labs index and, since
     2026-09-03, the only action on /labs/signal-line. */
  min-height: 44px;
  transition: gap 0.2s, opacity 0.2s;
}
.lab-open:hover { gap: 12px; opacity: 0.85; }
.lab-open svg { flex: 0 0 auto; }

.lab-block p strong { color: var(--ink); font-weight: 500; }
/* ABSOLUTE, NOT 0.85em. A relative size here inherited whatever its paragraph
   was set to, so one class produced three different sizes on one page — 13.6px
   in a 16px paragraph, 12.75px in a 15px one, and 11.05px in a 13px one, the
   last of them under the system's 12px floor. None of the three is on the
   twelve-size scale, and none of them was authored. */
.lab-block p code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink);
  word-break: break-word;
}

.lab-num svg { display: block; }

/* ============================================================
   Labs index (/labs) — the long form of the homepage's section 01,
   in the same language: a mono stage tick, the lab's name in its
   stage colour, hairline rules, no tinted cards.

   Namespaced .lab-index-* on purpose. The older .lab-entry /
   .lab-block / .lab-name / .lab-num / .lab-list rules above are
   shared with the Observatory, Monday Brief, Signal Line and every
   Proving Ground partial, and .lp-head/.lp-title/.lp-subtitle with
   all of them — so this page gets its own rules rather than
   redefining theirs.

   The five hues are NOT declared here: the entry carries a
   .labs-stage-* class from application.css, which is served on every
   page alongside this file. One definition, both surfaces.
   ============================================================ */
/* Same --page-top as .lp-head and the hero. This page had its own smaller formula (72px against
   their 96px) for no reason anyone chose. */
.lab-index-head {
  padding-block: var(--page-top) 0;
}
.lab-index-title {
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: clamp(32px, 4.8vw, 54px);
  line-height: 1.05;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin-top: 16px;
  max-width: 20ch;
  text-wrap: balance;
}
.lab-index-title em { font-style: italic; }
.lab-index-sub {
  margin-top: 22px;
  color: var(--ink-muted);
  font-size: clamp(16px, 1.6vw, 17px);
  line-height: 1.55;
}
.lab-index-sub-data {
  color: var(--ink);
  font-weight: 500;
  border-bottom: 1px solid var(--line);
  transition: border-color 0.2s;
}
.lab-index-sub-data:hover { border-bottom-color: var(--ink); }

/* THE RAIL WIDENED FROM 96px TO 236px (2026-09-03), in two steps and for two
   reasons: first to 200px to carry the lab's mark under its stage tick, then to 236
   when the built-from list joined it. At 200 the longest entries wrapped mid-phrase
   — "PostgreSQL + Voyage AI / embeddings" — and a facts column that breaks its own
   facts in half is worse than a wider one.

   The mark is the same partial /customer-intelligence renders in its cards, so a
   reader arriving from that page meets the same object twice, in the same order:
   stage, mark, name, prose, link. */
.lab-index-entry {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 40px;
  padding: clamp(30px, 4vw, 46px) 0;
  border-bottom: 1px solid var(--line-soft);
  /* position for the :target cue below, which is an absolutely-positioned
     pseudo-element and would otherwise resolve against the page. */
  position: relative;

  /* EVERY ENTRY IS A FRAGMENT TARGET (the footer links two of them), and .nav is
     `position: sticky; top: 0` — 44px min-height plus 12px of padding either side
     plus its 1px border, so 69px of header sits over any anchor that lands at 0.
     80 is that plus a hair; the entry's own top padding supplies the rest of the
     breathing room, which is why this is not larger. Same figure at every width:
     the header's height does not change. */
  scroll-margin-top: 80px;
}

/* THE ARRIVAL CUE. Six entries look alike, so landing among them says nothing
   about which one you were sent to.

   PAINTED STATICALLY, FADED BY ANIMATION — in that order, and the order is the
   whole point. site_00_foundation.css sets `* { animation: none !important }`
   under reduced motion, so a cue that exists only as an animation is invisible to
   those visitors while screenshotting perfectly. Declared this way the animation
   is the only thing they lose: the cue simply persists while the fragment is in
   the URL, which is the gentler equivalent that carve-out asks for.

   Opacity only, on a pseudo-element, rather than animating the rule's own colour —
   the same shape as every other transition on this site, and one paint rather
   than a colour interpolation per frame.

   `--stage` is the entry's own light hue, already set on it by `stage_class`, so
   the cue is the lab's colour and no hue enters the system. It is a stroke here
   and never a `color`, which colour_vocabulary_test enforces.

   Nothing is featured when no fragment is set: this is a response to the
   visitor's own navigation, not the editorial ranking that was removed from the
   lab cards when one of six carried a permanent rule and a "Start here" pill. */
.lab-index-entry:target::before {
  content: "";
  position: absolute;
  /* Sits 14px outside the measure so the rule is beside the entry rather than on
     top of its first column. Nothing is drawn between the two edges. */
  inset: 0 -14px;
  z-index: -1;
  /* A RULE, NOT A WASH (Jason, 2026-09-06). This carried a tinted background over
     the whole entry as well, which even at 7% made one of six a filled tile —
     the featured-card treatment that was deliberately removed from these labs,
     arriving by another route. The rule alone points at the entry without
     restyling it, and it is the only thing on the page drawn in that hue at that
     weight, so nothing else competes with it. */
  border-left: 3px solid var(--stage);
  animation: lab-index-target 1.6s ease-out forwards;
}
/* 1.6s total: solid for the first 45% (~0.7s), gone by the end. A cue is meant to
   catch the eye on arrival and get out of the way — the first version held for
   2.75s and faded over 2.25s, which is long enough to read as a state the entry
   is in rather than as a response to the click (Jason, 2026-09-06).

   THE HOLD IS WHAT MUST NOT GO TO ZERO. The animation starts when :target
   applies, which on a same-page click is while the smooth scroll is still
   travelling — so a cue that is already fading before the entry arrives is one
   nobody sees. ~0.7s covers that trip. A cross-page visit positions instantly and
   sees the whole thing. */
@keyframes lab-index-target {
  0%, 45% { opacity: 1; }
  100%    { opacity: 0; }
}
.lab-index-entry.is-dataset {
  --stage: color-mix(in srgb, var(--ink) 30%, transparent);
  --stage-ink: var(--ink);
  border-bottom: none;
}
/* The bar is a flex sibling of the word, never an absolutely-positioned marker —
   at left:0 it lands on top of the first letter, and "CONNECT" makes that obvious. */
.lab-index-tick {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: start;
  padding-top: 12px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--stage-ink);
}
.lab-index-tick i {
  flex: 0 0 auto;
  width: 18px;
  height: 2px;
  border-radius: 1px;
  background: var(--stage);
}

/* THE PLATE. The stage's own light, mixed against the page ground and multiplied
   by --hero-k, which is the same arithmetic .lab .dw and the home doors use — a
   percentage of a light hue is still light on a dark ground without it. The
   is-dataset entry has no --stage, so both mixes fall back to the page ground and
   the plate is a plain framed panel there rather than a mis-tinted one. */
/* THE RAIL IS THE LAB'S CARD, AND IT IS STICKY (2026-09-03). It held a tick and a
   mark and nothing else, which left 380 to 459px of empty column on every entry and
   2,797px down the page, measured. It now carries everything a reader compares
   BETWEEN labs — stage, mark, kind, what it is built from, the way in — while the
   main column carries only what is read WITHIN one. That sorting is the layout.

   Sticky because an entry is 500 to 640px tall, so at the foot of one the mark and
   the link had scrolled off. `top` clears the fixed header. It goes static at 760px,
   where the rail is a full-width block above the name and pinning it would hold it
   over the prose it introduces. */
.lab-index-rail {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-self: start;
  position: sticky;
  top: 88px;
}
.lab-index-mark {
  display: grid;
  place-items: center;
  height: 140px;
  padding: 14px;
  border: 1px solid color-mix(in srgb, var(--stage, var(--bg)) calc(32% * var(--hero-k, 1)), var(--bg));
  background:
    linear-gradient(103deg,
      color-mix(in srgb, var(--stage, var(--bg)) calc(16% * var(--hero-k, 1)), var(--bg)),
      color-mix(in srgb, var(--stage, var(--bg)) calc(24% * var(--hero-k, 1)), var(--bg)) 48%,
      color-mix(in srgb, var(--stage-lt, var(--bg)) calc(42% * var(--hero-k, 1)), var(--bg)));
  --fig-stroke: var(--stage-draw, var(--ink-muted));
  --fig-hi: var(--stage, var(--ink-muted));
}
.lab-index-mark svg { width: 100%; height: 100%; }

.lab-index-name {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(28px, 3.4vw, 38px);
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--stage-ink);
  margin: 0 0 18px;
}
.lab-index-name a { color: inherit; border-bottom: 1px solid transparent; transition: border-color 0.2s; }
.lab-index-name a:hover { border-bottom-color: currentColor; }

/* IN THE RAIL NOW, not beside the name. As a flex sibling of the h2 it was a second
   string competing with the lab's title at the page's largest type; here it is one of
   four facts and reads as one. It keeps --stage-ink: coloured mono names the lab. */
.lab-index-kind {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--stage-ink);
}

/* WHAT IT IS BUILT FROM, as the list it always was. It was a middot-joined sentence
   in a 64ch prose row; in a 236px rail that wraps into something that reads as prose
   it is not, so Labs::LabSummary::Summary#built_from splits it. The string in the
   seed file is unchanged, because Front Desk's voice tool speaks it. */
.lab-index-built {
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
}
.lab-index-built-label {
  display: block;
  margin-bottom: 7px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.lab-index-built ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.lab-index-built li {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-muted);
}

/* THE ARGUMENT, AT ONE MEASURE. Two paragraphs, no label rail, no hairlines between
   them. The entry used to be three identically-weighted rows, so it had a primary
   (the name) and no secondary at all — squinting returned a title and a field of
   grey. The lede is what the lab DOES, which is the sentence a reader on this page
   is deciding on; the paragraph under it is why it exists.

   The lede is 300 weight rather than 400. At 21px the regular weight reads as a
   short paragraph rather than as a lede, and this face has the range. */
.lab-index-lede {
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: clamp(19px, 1.9vw, 21px);
  line-height: 1.46;
  letter-spacing: -0.006em;
  color: var(--ink);
  max-width: 54ch;
  margin: 0 0 18px;
}
.lab-index-why {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-muted);
  max-width: 62ch;
  margin: 0;
}

/* -----------------------------------------------------------------------------
   STACKED (<=760px). THIS BLOCK HAS TO SIT BELOW EVERY RULE IT OVERRIDES. It was
   written above them, and `position: static` lost to `position: sticky` on equal
   specificity purely on source order — the rail stayed sticky on a phone, where it
   is a full-width block, so it pinned itself over the prose it introduces. Nothing
   raised; the page just behaved wrongly at one width.

   THE RAIL BECOMES `display: contents`, which is the whole trick. Left as a block
   the entry read: stage, mark, kind, built from, ASK A QUESTION, and only then the
   lab's name — a reader was invited to open a lab before being told which one it
   was. Dissolving the rail promotes its children to grid items of the entry, so
   they can be ordered around the prose: identity first, then the argument, then the
   facts and the way in beneath it. Desktop keeps the column; only the order changes.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {
  .lab-index-entry { grid-template-columns: 1fr; gap: 14px; }
  .lab-index-rail { display: contents; }

  .lab-index-tick  { order: 1; }
  .lab-index-mark  { order: 2; height: 132px; }
  .lab-index-main  { order: 3; }
  .lab-index-kind  { order: 4; }
  .lab-index-built { order: 5; }
  .lab-index-figs  { order: 5; }
  .lab-index-open  { order: 6; }

  /* Two facts and a link read across the width once the rail is not a column. */
  .lab-index-figs { flex-direction: row; flex-wrap: wrap; gap: 12px 36px; }
}

/* A text link, not a button. No fill, so the label is always dark type on the page's
   own white — a filled pill put small mono type on a coloured ground and lost it. */
.lab-index-open {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 22px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stage-ink);
  border-bottom: 1px solid color-mix(in srgb, var(--stage-ink) 40%, transparent);
  padding-bottom: 3px;
  transition: gap 0.2s, border-color 0.2s;
}
.lab-index-open:hover { gap: 12px; border-bottom-color: var(--stage-ink); }
.lab-index-open svg { flex: 0 0 auto; }

/* IN THE DATASET'S RAIL, where a lab keeps its stack: these are the facts a reader
   compares it on, and they were the best scanning element on the page while sitting
   in the middle of the prose column. Stacked rather than a wrapping row, because the
   rail is 236px and three figures across it wrapped to one per line anyway. */
.lab-index-figs {
  display: flex;
  flex-direction: column;
  gap: 14px;
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
}
.lab-index-fig b {
  display: block;
  font-family: var(--font-mono);
  font-size: 21px;
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1;
}
.lab-index-fig span {
  display: block;
  margin-top: 5px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
