/* =========================================================
   Moving parts, shared stylesheet
   Art direction: see ../art-direction.md
   ========================================================= */

@font-face {
  font-family: "Instrument Sans";
  src: url("fonts/instrument-sans/InstrumentSans-Variable.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/geist/GeistMono-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* The Black / white look, 24/09/2026 (AB-98, AB-101): the site on a
     near black, the previews on white, the accent in the text colour, no
     glow. A look is three colours, the ground of the site, the ground of
     the previews and the ink written on them; the panel is the site's
     ground 7% towards white. The previews are the components as they
     ship, already in black on white: the site does not recolour them. The
     other looks tried on 24/09, kept to try again (site, previews, ink):
       Graphite / bone   #121210, #f2efe7, #1b1a17
       Slate / mist      #0e1013, #e7eaed, #14171b
       Espresso / sand   #15120e, #e4ddd0, #221c15
       Carbon / stone    #101010, #d4d3cf, #121212
       Black / black     #0c0c0d, #18181a, #ededee
     Since 06/10/2026 (AB-222) the site and the previews wear the amber
     palette of the landing: warm black, cream, ink #15100c. */
  --bg: #0e0b09;
  --panel: #1f1a15;
  --preview-ground: #f4ece0;
  --text: #f4ece0;
  --muted: #b9ac9b;
  --label: #8f8273;

  /* THE tint of the site: everything derives from it, one line to change.
     In the Black / white look it is the text colour. */
  --accent: var(--text);
  --glow-page: color-mix(in srgb, var(--accent) 7%, transparent);
  --glow-low: color-mix(in srgb, var(--accent) 4%, transparent);
  --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);

  /* The AI family: the violet of the Effort slider in Claude's app, worn by
     everything that speaks to an assistant instead of the lime. Icons,
     chevrons and answers take --ai; the chevron on hover takes --ai-strong. */
  --ai: #b8a6ff;           /* value text, icons, chevrons, answers */
  --ai-strong: #dcd6ff;    /* lavender white: the chevron on hover */
  --ai-soft: color-mix(in srgb, var(--ai) 12%, transparent);   /* tinted surfaces, as --accent-soft */
  --ai-line: color-mix(in srgb, var(--ai) 45%, transparent);   /* hover borders, as the lime rows */

  --line: rgba(244, 236, 224, 0.08);
  --line-strong: rgba(244, 236, 224, 0.16);
  --surface: rgba(244, 236, 224, 0.03);
  --surface-high: rgba(244, 236, 224, 0.055);

  /* The page is full width (AB-23): one fluid gutter on each side, the
     content takes everything else. Only running text keeps a measure, and it
     keeps it in `ch` inside its own column, never on the page. */
  --gutter: clamp(16px, 1.6vw, 32px);
  --radius: 14px;

  /* The header (the landing's, landing/assets/shell.css) is a fixed bar about
     71px tall, 65 on a phone. Anything sticky starts below that line, and
     there is one number to change if the bar ever grows. */
  --bar-height: 72px;
  --stick-top: calc(var(--bar-height) + clamp(6px, 0.8vh, 12px));

  /* The two glows of the page background, in one place: a sticky band that
     wants to look like the page paints the very same thing. */
  --page-glow: none;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

::selection { background: var(--accent); color: var(--bg); }

/* The browser's own smoothing is off on purpose: the site scrolls through
   Lenis, which writes the scroll position itself on every frame. Left on, the
   two interpolate one over the other and the page answers late and softly. It
   comes back by itself for anyone whose Lenis never starts, since the boot in
   site.js puts the class below on the root only once it is running. */
html { scroll-behavior: auto; }

/* What Lenis needs from the page, and nothing more. Its own stylesheet also
   carries `.lenis.lenis-smooth iframe { pointer-events: none }`, which would
   be quiet havoc here: this site is built on iframes, the library cards are
   driven by synthetic pointer events, the stage is hovered and clicked, and
   the objects of the gravity footer are picked up by hand. */
html.lenis, html.lenis body { height: auto; }
html.lenis-stopped { overflow: clip; }


body {
  background: var(--bg);
  color: var(--text);
  font-family: "Instrument Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 430;
  font-size: 16px;
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* The colour in the background: two tinted glows, off-centre */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--page-glow);
}

/* Terminal layer: mono, uppercase, GREY.
   The accent only underlines the endings (OK, caret, hovers). */
.mono {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  line-height: 1.9;
}

.ok { color: var(--accent); }
.caret { color: var(--accent); }

a { color: inherit; }

/* =========================================================
   Page frame
   ========================================================= */

/* No maximum width and no centring: the page is the window, minus a gutter. */
.wrap {
  position: relative;
  z-index: 1;
  padding: 0 var(--gutter);
}

/* The library masthead: the title on the left and the count on the right on
   one line, then the sentence and the search field, then a hairline. The
   hairline belongs to the masthead and not to the filter band under it: the
   band keeps a single edge, the one it carries when it sticks. */
.masthead {
  margin: 0 calc(var(--gutter) * -1);
  padding: clamp(104px, 13vh, 152px) var(--gutter) 0;
  border-bottom: 1px solid var(--line);
}

.masthead .eyebrow { color: var(--label); margin-bottom: clamp(12px, 1.6vh, 20px); }

.mast-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: clamp(16px, 3vw, 48px);
}

.masthead h1 {
  font-weight: 440;
  font-size: clamp(40px, 7.4vw, 132px);
  line-height: 0.96;
  letter-spacing: -0.03em;
  min-width: 0;
}

/* The count reads as one number with a word above it, the way a collection is
   numbered on a cover. */
.mast-count {
  flex: none;
  text-align: right;
  line-height: 1;
}

.mast-count .word { display: block; color: var(--label); margin-bottom: 4px; }

.mast-count .n {
  font-weight: 440;
  font-size: clamp(28px, 3.4vw, 58px);
  letter-spacing: -0.03em;
  line-height: 1;
}

.mast-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: clamp(14px, 2vw, 40px);
  padding: clamp(20px, 3vh, 36px) 0 clamp(14px, 2vh, 22px);
}

.masthead .intro {
  color: var(--muted);
  font-size: clamp(14.5px, 1vw, 16px);
  line-height: 1.65;
  max-width: 58ch;
  flex: 1 1 30ch;
}

.mast-search {
  flex: 0 1 clamp(280px, 30vw, 460px);
  min-width: 0;
}

/* The search field, the info line, the category row, the grid and its cards
   live in cards.css, shared with the Library page of the landing. */

/* The button that opens the rest. It carries its own count, so the page never
   hides a filter that is on. */
.filters-toggle {
  margin-left: auto;
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 14px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.filters-toggle:hover { color: var(--text); border-color: var(--line-strong); }
.filters-toggle svg { width: 13px; height: 13px; flex: none; }

.filters-toggle[data-active="true"],
.filters-toggle[aria-expanded="true"] {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
  background: var(--accent-soft);
}

.filters-toggle .badge-count {
  min-width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  line-height: 1;
}

/* The extra filters, as a drawer under the row rather than a floating card:
   it belongs to the band, it opens where the button is, and nothing about it
   has to be positioned against a corner of the window. */
.filters-panel {
  display: grid;
  gap: clamp(12px, 1.6vh, 18px);
  margin-top: clamp(10px, 1.2vh, 14px);
  padding-top: clamp(10px, 1.2vh, 14px);
  border-top: 1px solid var(--line);
  max-height: calc(100vh - var(--bar-height) - 150px);
  overflow-y: auto;
}

.filter-set {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

.filter-set .set-title { color: var(--label); }

.filter-set .chips {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(5px, 0.5vw, 8px);
}

.filters-clear {
  justify-self: start;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  color: var(--label);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 2px 0;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.filters-clear:hover { color: var(--accent); border-bottom-color: var(--accent); }

.filter-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.filter-group > .group-title {
  color: var(--label);
  margin-right: 2px;
}

.chip {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 13px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
  white-space: nowrap;
}

.chip:hover { color: var(--text); border-color: var(--line-strong); }

.chip[aria-pressed="true"] {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
  background: var(--accent-soft);
}

.chip .count { opacity: 0.55; margin-left: 6px; }

.to-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* =========================================================
   Buttons
   ========================================================= */

.button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  /* Declared, because without it a <button> takes `normal` from the browser
     and an <a> inherits the page: the same class then measured 32.5px on
     Replay and 39.95px on Fullscreen, side by side in the same row. The value
     clears the 11px arrow some of them carry. And a label never wraps: with
     two lines "Complete demo" stood at 59.89px, nearly twice its neighbour. */
  line-height: 16px;
  white-space: nowrap;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 9px 14px;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.button:hover {
  color: var(--text);
  border-color: var(--line-strong);
  background: var(--surface-high);
}

.button.accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.button.accent:hover { background: var(--accent-soft); color: var(--accent); }

/* =========================================================
   Component page
   ========================================================= */

/* The hero is what pushes the stage under the fold: every pixel spent here is
   a pixel of demo the reader has to scroll for. */
.detail-head { padding: clamp(88px, 10vh, 140px) 0 28px; }

.breadcrumb { color: var(--label); margin-bottom: 20px; display: flex; align-items: center; gap: 8px; }
.breadcrumb a { text-decoration: none; transition: color 0.2s ease; }
.breadcrumb a:hover { color: var(--accent); }

.detail-head h1 {
  font-weight: 440;
  font-size: clamp(36px, 4.6vw, 62px);
  line-height: 1.05;
  letter-spacing: -0.022em;
  max-width: 15ch;
}

.detail-head .description {
  color: var(--muted);
  font-size: 16px;
  line-height: 1.66;
  max-width: 68ch;
  margin-top: 22px;
}

.actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 22px 0 0; }

/* The stage keeps a screen's proportions: a demo built for a browser
   plays cramped in a short band, and its blocks pinned to the top and
   bottom end up overlapping. */
.stage {
  position: relative;
  border: 1px solid var(--line-strong);
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
  overflow: hidden;
  background: var(--bg);
  /* Explicit, and not `auto`: once `max-height` bites, an auto width gets
     derived from the height through the ratio, and the stage shrinks away
     from the sides of its own frame. */
  width: 100%;
  /* As tall as a browser window with its bookmarks bar, less the floating
     masthead and the control bar under the frame: the demo is seen the way a
     visitor sees a page, not through a letterbox. (Benjamin, 14/09/2026:
     "bien trop petit… à peu près la taille d'un écran avec barre de favoris") */
  height: calc(100vh - 180px);
  max-height: calc(100vh - 180px);
  /* `min-height` beats `max-height` in CSS, so a flat floor wins over the
     88vh ceiling on a short window: the stage grows past the fold and the
     demo looks cropped. The floor has to fold away by itself. */
  min-height: min(560px, 72vh);
}

/* The demo is laid out at 125% of the frame and scaled back to 80%: the
   whole page fits, with its proportions, the way a screenshot of a screen
   fits a smaller screen. Pointer events go through the transform. */
.stage iframe {
  width: 125%;
  height: 125%;
  border: 0;
  display: block;
  transform: scale(0.8);
  transform-origin: 0 0;
}

.stage-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 0 14px;
  color: var(--label);
}

.stage-bar .to-right { gap: 8px; }

/* The spec below the documentation. One column since AB-23: the facts that
   used to sit beside it moved up into the right column of the Documentation
   section, where the actions are, and the reading column keeps its measure. */
.detail-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  padding: clamp(34px, 5vh, 54px) 0 clamp(50px, 8vh, 80px);
  align-items: start;
}

/* The art direction page still lays a panel of notes beside its text, so the
   two column form survives where a page actually brings the second column. */
.detail-columns:has(.side-spec) {
  grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
  gap: clamp(28px, 3vw, 44px);
}

.side-spec {
  position: sticky;
  top: var(--stick-top);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.side-spec .block .label { color: var(--label); }
.side-spec .block .value { font-size: 14px; line-height: 1.55; color: var(--muted); }
.side-spec .block ul { list-style: none; font-size: 14px; line-height: 1.6; color: var(--muted); }

@media (max-width: 1040px) {
  .detail-columns:has(.side-spec) { grid-template-columns: minmax(0, 1fr); gap: 34px; }
  .side-spec { position: static; }
}

/* Spec body, rendered from markdown */
.prose { max-width: 76ch; }
.prose h2 {
  font-weight: 470;
  font-size: 23px;
  letter-spacing: -0.015em;
  margin: 44px 0 16px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
}
.prose h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.prose h3 { font-weight: 470; font-size: 17px; margin: 26px 0 10px; }
.prose p, .prose li { color: var(--muted); font-size: 15px; line-height: 1.68; }
.prose p { margin-bottom: 14px; }
.prose ul, .prose ol { margin: 0 0 16px 20px; }
.prose li { margin-bottom: 8px; }
.prose strong { color: var(--text); font-weight: 520; }
.prose code {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 0.88em;
  color: var(--text);
  background: var(--surface-high);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 1px 5px;
}
.prose table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 20px;
  font-size: 14px;
}
.prose th, .prose td {
  text-align: left;
  padding: 10px 14px 10px 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  color: var(--muted);
}
.prose th {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  color: var(--label);
}
.prose blockquote {
  border-left: 2px solid var(--line-strong);
  padding-left: 16px;
  margin: 0 0 16px;
}

/* =========================================================
   Narrow screens
   ========================================================= */

@media (max-width: 900px) {
  .mast-foot { align-items: flex-start; }
  .mast-search { flex: 1 1 100%; }
}

@media (max-width: 640px) {
  .masthead { padding-top: clamp(92px, 12vh, 120px); }
  .mast-line { flex-wrap: wrap; align-items: flex-end; }
  .mast-count { text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}


/* ==========================================================
   Integration: one section under the stage, three tabs, and
   snippets short enough to read without scrolling.
   ========================================================== */

/* No overflow clipping here: it would turn this box into a scroll container
   and the sticky column inside it would stop sticking. */
.integrate {
  margin-top: 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--panel) 42%, transparent);
}

.integrate-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}

/* The one button that is not in the terminal register: it carries another
   product's logo, so it borrows that product's shape instead of ours. */
.wf-button {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  border: 0;
  border-radius: 8px;
  padding: 9px 14px;
  background: #146ef5;
  color: #fff;
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: background 0.16s ease, transform 0.16s ease;
}

.wf-button:hover { background: #0f5ad4; }
.wf-button:active { transform: translateY(1px); }
.wf-button svg { width: 17px; height: 17px; flex: none; }
.wf-button[data-state="done"] { background: var(--accent); color: var(--bg); }

/* A segmented control, not a row of file tabs: the three routes are three
   ways of doing the same thing, so they read as one object with one part lit
   rather than as three separate headings. */
.tabs {
  --tabs-pad: 4px;
  position: relative;
  display: inline-flex;
  gap: 2px;
  padding: var(--tabs-pad);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
}

/* The selection is a single box that travels from one tab to the next, placed
   by script on the measured tab. The padding of the track is what keeps it off
   the hairline, so it reads as a selection laid inside a track rather than as
   a button that does not fit. */
.tab-pill {
  position: absolute;
  left: 0;
  top: var(--tabs-pad);
  bottom: var(--tabs-pad);
  width: 0;
  border-radius: 8px;
  background: var(--surface);
  pointer-events: none;
}

/* Position and width only: they travel without laying the row out again. The
   transition is armed by the script once the pill has been placed a first
   time, so nothing slides in from the left edge when the page opens. */
.tabs[data-pill="ready"] .tab-pill {
  transition: transform 280ms cubic-bezier(0.22, 0.8, 0.28, 1),
              width 280ms cubic-bezier(0.22, 0.8, 0.28, 1),
              background-color 200ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .tabs[data-pill="ready"] .tab-pill { transition: none; }
}

.tab {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--label);
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 11.5px;
  padding: 11px 17px;
  cursor: pointer;
  transition: color 0.16s ease;
}

.tab svg { width: 14px; height: 14px; flex: none; }

.tab:hover { color: var(--muted); }

/* The selected tab only changes the colour of its text: its ground is the
   pill, which is already there. */
.tab[aria-selected="true"] { color: var(--text); }

/* The Webflow blue only once the panel is the one being read, so the control
   does not look like it has a permanent favourite. */
.tab[aria-selected="true"][data-panel="webflow"] svg { color: #4353ff; }

.panel {
  padding: 24px 20px 26px;
  background: var(--bg);
}

.panel[hidden] { display: none; }

.steps { list-style: none; counter-reset: step; display: grid; gap: 20px; }

/* minmax(0, 1fr): without it the column sizes to the longest line of a
   snippet, and one minified block widens the whole page. */
.steps > li { counter-increment: step; display: grid; grid-template-columns: minmax(0, 1fr); gap: 9px; }

.step-title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 15px;
  letter-spacing: -0.01em;
}

.step-title::before {
  content: counter(step);
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 10px;
  display: grid;
  place-items: center;
  transform: translateY(1px);
}

.step-note {
  margin-left: 30px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
  max-width: 62ch;
}

.step-note code {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 12px;
  color: var(--accent);
}

.snippet {
  position: relative;
  margin-left: 30px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #100c09;
}

.snippet pre {
  margin: 0;
  padding: 14px 92px 14px 16px;
  overflow-x: auto;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 12px;
  line-height: 1.75;
  color: var(--text);
  white-space: pre;
  tab-size: 2;
}

.snippet-copy {
  position: absolute;
  top: 9px;
  right: 9px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg);
  color: var(--label);
  font-family: "Geist Mono", ui-monospace, monospace;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  padding: 5px 9px;
  cursor: pointer;
  transition: color 0.16s ease, border-color 0.16s ease;
}

.snippet-copy:hover { color: var(--text); border-color: var(--line-strong); }
.snippet-copy[data-state="done"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* The knobs table: name, default, and what it does. Nothing else. */
.knobs { margin-left: 30px; width: 100%; border-collapse: collapse; font-size: 13px; }

.knobs th {
  text-align: left;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  color: var(--label);
  padding: 0 14px 8px 0;
  border-bottom: 1px solid var(--line);
}

.knobs td {
  padding: 8px 14px 8px 0;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  vertical-align: top;
}

.knobs td:first-child {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 12px;
  color: var(--text);
  white-space: nowrap;
}

.knobs td:nth-child(2) {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 12px;
  color: var(--accent);
  white-space: nowrap;
}

.panel-intro {
  margin-bottom: 20px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
  max-width: 68ch;
}

.panel-intro strong { color: var(--text); font-weight: 500; }

@media (max-width: 700px) {
  .snippet, .knobs, .step-note, .step-sub, .steps .action { margin-left: 0; }
  .panel { padding: 20px 12px 22px; }
}


/* ==========================================================
   Integration, second pass: the actions stay on screen beside
   the steps, and a snippet unfolds instead of scrolling.
   ========================================================== */

/* The page in two columns (14/09/2026): preview and documentation left, the
   rail right, sticky from the top of the preview to the foot of the page. */
.page-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(236px, 21vw, 360px);
  gap: clamp(18px, 2.2vw, 36px);
  align-items: start;
}

.page-main { min-width: 0; }
.page-columns > .integrate-rail { margin-top: clamp(10px, 1.4vh, 16px); }

.integrate-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(18px, 2.2vw, 36px);
  align-items: start;
  padding: clamp(20px, 2.2vw, 28px) clamp(14px, 1.6vw, 22px);
  background: var(--bg);
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
}

.integrate-panels { min-width: 0; }

/* The body carries the padding now, so the panel adds none of its own. */
.integrate .panel { padding: 0; background: none; }

/* The column of actions. It belongs to the section, so it wears the same
   hairline, the same ground and the same terminal label as everything else
   here rather than floating over the page. */
.integrate-rail {
  position: sticky;
  top: var(--stick-top);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(15px, 1.8vh, 21px);
  padding: clamp(15px, 1.4vw, 19px);
  border: 1px solid var(--line);
  border-radius: 12px;
  background: color-mix(in srgb, var(--panel) 48%, transparent);
}

/* The rail's own actions are rows, built at the end of this file (AB-27). */
.integrate-rail .act { width: 100%; }

/* The resource details, under the three actions and behind a hairline: what
   the thing is, when it arrived, what it reacts to. The techniques left the
   rail with AB-27: they are a filter, not a fact anyone reads here. */
.rail-facts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(10px, 1.2vh, 14px);
  padding-top: clamp(14px, 1.7vh, 19px);
  border-top: 1px solid var(--line);
}

.rail-facts .row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 5px;
}

.rail-facts .key { color: var(--label); }

.rail-facts .val {
  font-size: clamp(13px, 0.95vw, 14px);
  line-height: 1.5;
  color: var(--text);
}

.rail-facts .val.mono { font-size: 11px; color: var(--muted); }

.rail-facts .set {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.rail-facts .set span {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  line-height: 1.7;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 8px;
}

/* A button and the line that says what it does. The line is also where the
   action answers: a label that rewrites itself makes the button jump. */
.action { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(6px, 0.7vh, 9px); }

.action-note {
  color: var(--label);
  font-size: clamp(11.5px, 0.82vw, 12.5px);
  line-height: 1.5;
  max-width: 56ch;
}

.action-note[data-state="done"] { color: var(--accent); }

/* In a step the button keeps its own width; only the column stretches it. */
.steps .action,
.panel-aside .action { margin-left: 30px; justify-items: start; }
.panel-aside .action { margin-left: 0; }
.action.secondary .act { background: none; }

/* The block under the steps, for someone who already built their section. */
.panel-aside {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(10px, 1.2vh, 14px);
  margin-top: clamp(26px, 3.4vh, 40px);
  padding-top: clamp(18px, 2.2vh, 26px);
  border-top: 1px solid var(--line);
}

.panel-aside .label { color: var(--label); }
.panel-aside .panel-intro { margin-bottom: 0; }

/* A snippet shows fourteen lines and unfolds. Scrolling inside a box that is
   itself inside a scrolling page is a trap, and Copy takes everything either
   way. The height is counted in the snippet's own lines, not in pixels. */
.snippet { --snippet-lines: 14; }

/* The AI prompt is prose around code: its long lines wrap instead of running
   off the edge, and it sits flush under the one sentence of the tab, with no
   step to align with. Copy still takes the text as it is. */
.snippet.is-prose { margin-left: 0; }

.snippet.is-prose pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.snippet.clipped pre {
  max-height: calc(var(--snippet-lines) * 1.75em + 28px);
  overflow-y: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 46px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 46px), transparent);
}

.snippet-more {
  display: block;
  width: 100%;
  border: 0;
  border-top: 1px solid var(--line);
  border-bottom-left-radius: 10px;
  border-bottom-right-radius: 10px;
  background: var(--surface);
  color: var(--muted);
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  padding: clamp(8px, 0.9vh, 11px) 0;
  cursor: pointer;
  transition: color 0.16s ease, background 0.16s ease;
}

.snippet-more:hover { color: var(--accent); background: var(--surface-high); }

/* Narrow: the column becomes a plain block above the steps. Sticking it to
   the top of a phone screen would eat the page it is meant to serve. */
@media (max-width: 900px) {
  /* One column: the preview, then the rail, then the documentation. The
     wrapper dissolves so the grid can order the rail between the two. */
  .page-columns { grid-template-columns: 1fr; }
  .page-main { display: contents; }
  .page-main > .stage-frame { order: 1; }
  .page-columns > .integrate-rail { position: static; order: 2; margin-top: 0; }
  /* What it does comes after the actions, the questions after everything. */
  .page-main > .integrate { order: 3; }
  .page-main > .detail-columns { order: 4; }
  .page-main > .about { order: 5; }
  .page-main > .questions { order: 6; }
}

@media (max-width: 700px) {
  .steps .action { margin-left: 0; }
  .steps .action .act,
  .panel-aside .action .act { width: 100%; justify-content: flex-start; }
}


/* ==========================================================
   Detail page, second pass: fewer lines, clearer actions.
   ========================================================== */

/* The breadcrumb is the way back, so it has to look like a control rather
   than like a caption. */
.breadcrumb a {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 11px;
  color: var(--muted);
}

.breadcrumb a:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.breadcrumb .here { padding: 4px 0; }

/* A drawn arrow, not the font's: the mono glyph sits low against capitals.
   Flex centres it on the label's x-height. */
.breadcrumb a { display: inline-flex; align-items: center; gap: 6px; }
.breadcrumb .back-arrow { width: 11px; height: 11px; flex: none; }

/* One framed block for the demo: the controls belong to it instead of
   floating above its corner. */
/* The stage used to break out of the column, wider than everything under it.
   Seen next to the Documentation block it read as a mistake rather than as a
   feature, so it now takes the width of the page like the rest: same left
   edge, same right edge, one column from top to bottom. Only the width
   changed; the height rules below are untouched. */
/* The window sits on a ground of its own, a shade lighter than the page,
   with air around it and a shadow under it: a preview standing on a desk,
   not a border drawn around a piece of the page. (Benjamin, 14/09/2026:
   "on dirait juste un border… c'est le même fond que le site") */
.stage-frame {
  margin-top: clamp(10px, 1.4vh, 16px);
  width: 100%;
  padding: clamp(16px, 1.8vw, 28px);
  border-radius: calc(var(--radius) + 8px);
  background:
    radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.05) 1px, transparent 1.5px) 0 0 / 22px 22px,
    var(--panel);
  border: 1px solid var(--line);
}

.stage-chrome, .stage { box-shadow: 0 28px 70px rgba(0, 0, 0, 0.42); }
.stage-chrome { box-shadow: none; }

.stage-frame .stage { min-height: min(520px, 68vh); }

/* The window bar over the frame: what a browser draws above a page, reduced
   to the three lights, an address and the scale. It is the sign that says
   "this is a preview of a page", which a bare dark rectangle never said. */
.stage-chrome {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1vw, 14px);
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--panel);
  color: var(--label);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.stage-lights { display: inline-flex; gap: 6px; }
.stage-lights i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--line-strong);
}

.stage-address {
  flex: 1;
  min-width: 0;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 4px 12px;
  border-radius: 6px;
  background: var(--surface-high);
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0.02em;
  font-size: 11px;
}

.stage-scale { white-space: nowrap; }
@media (max-width: 700px) { .stage-scale { display: none; } }
/* Under a short window the frame keeps a floor, so it never turns into a
   band. */
@media (max-height: 640px) { .stage, .stage-frame .stage { height: 520px; max-height: none; } }

/* Controls in a slim bar under the frame, outside the rounded box. Laid over
   the demo they covered whatever the component draws in its own top corners,
   and the bar costs one row of chrome rather than a working button. */
.stage-tools {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  /* The same expression the frame pads itself with, so the air over the row
     equals the air under it at every width. A flat 16px was right only at the
     narrow end: the frame computes 25.92px at 1440, which put the controls
     16px under the preview and 26px over the floor, closer to the preview
     than to the edge. */
  margin-top: clamp(16px, 1.8vw, 28px);
  color: var(--label);
}

/* No breakpoint: when the row runs out of width the right group drops to a
   line of its own, still against the right edge, and the version chips keep
   the left. Nothing gets squeezed and nothing overflows on a phone. */
.stage-tools .to-right { margin-left: auto; display: flex; gap: 8px; }

/* The version chips stand on the same line as the stage buttons, so they take
   the same height: 16px of line, 9px of padding, 1px of border, 36px in all.
   Left alone they measured 26.5px against 36 and floated inside the row.
   Scoped to this row on purpose: the same class draws the Filters pills on
   the library, which keep their own size. */
.stage-tools .chip { line-height: 16px; padding-block: 9px; }

/* The glyph was a text arrow sitting on the baseline, half a line above the
   label. Drawn, it centres on the label like every other icon here. */
#fullscreen { gap: 7px; }
#fullscreen .ext-arrow { width: 11px; height: 11px; flex: none; }

/* The complete demo is not one more version of the page in the frame, so it
   must not look like one. The version chips are outlined and the stage buttons
   are outlined; this one is filled, which is the only filled control of the
   row: what it opens is a richer page, and the rank is read before the label.
   AB-81, 17/09/2026. */
#complete {
  gap: 7px;
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

#complete:hover {
  color: var(--bg);
  background: color-mix(in srgb, var(--accent) 88%, #fff);
  border-color: color-mix(in srgb, var(--accent) 88%, #fff);
}

#complete .ext-arrow { width: 11px; height: 11px; flex: none; }

/* The three things anyone actually wants from this page. They live in the
   sticky column of the integration section. */
.act {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 10px 15px;
  background: none;
  color: var(--text);
  font-family: inherit;
  font-size: 13.5px;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
}

.act:hover { border-color: var(--line-strong); background: color-mix(in srgb, var(--panel) 55%, transparent); }
.act svg { width: 16px; height: 16px; flex: none; }
.act[data-state="done"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

.act.webflow { background: #146ef5; border-color: #146ef5; color: #fff; }
.act.webflow:hover { background: #0f5ad4; border-color: #0f5ad4; }
/* Copied: the Webflow button stays blue and answers in blue, the way the AI
   actions answer in violet. */
.act.webflow[data-state="done"] { background: #0f5ad4; border-color: #0f5ad4; color: #fff; }
.act.webflow + .action-note[data-state="done"] { color: #4d94ff; }

/* The side panel: four facts, nothing else. */
.facts { display: flex; flex-direction: column; gap: 16px; }
.facts .row { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }

.facts .key {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  color: var(--label);
}

.facts .val { font-size: 14px; color: var(--text); }
.facts .val.mono { font-family: "Geist Mono", ui-monospace, monospace; font-size: 12px; }
.facts .val.dim { color: var(--label); }

.facts .chips { display: flex; flex-wrap: wrap; gap: 6px; }

.facts .chips span {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 10px;
  font-family: "Geist Mono", ui-monospace, monospace;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  color: var(--muted);
}

/* The attribute has to win. Any rule setting display on a component (a
   button, a card, a panel) silently beats [hidden], and the element stays on
   screen while the DOM says it is gone. Paid for twice now. */
[hidden] { display: none !important; }

/* Snippet colouring. Five roles, no more: a code block on a documentation
   page is there to be scanned, not to look like an IDE. Attribute names take
   the accent because on this page they are what the reader is hunting for. */
.snippet .t { color: #ef6f6c; }                 /* tag name, css selector */
.snippet .a { color: var(--accent); }           /* attribute, css property */
.snippet .s { color: #e6c88b; }                 /* string */
.snippet .v { color: #cfd3da; }                 /* css value */
.snippet .c { color: var(--label); font-style: italic; }
.snippet .k { color: #ef6f6c; }                 /* js keyword */
.snippet .f { color: #8fd3c1; }                 /* js call */
.snippet .n { color: #e6c88b; }                 /* number */
.snippet .b { color: var(--label); }            /* brackets */


/* ==========================================================
   Documentation, third pass (AB-15). Benjamin, comparing the
   page to Osmo's: the steps did not stand out, nothing
   separated them, and the preview was not aligned with the
   page. One band, steps with real titles and a hairline
   between them, and every snippet in a box with a header bar.
   ========================================================== */

/* The band. The word on the left says what the whole section is, the
   segmented control on the right says which route is on screen. One row, and
   it wraps to two rather than crush the control on a phone. */
.integrate-head {
  padding: clamp(16px, 1.8vw, 24px) clamp(14px, 1.6vw, 22px);
  gap: clamp(12px, 1.4vw, 20px);
}

.integrate-title {
  font-weight: 470;
  font-size: clamp(19px, 1.5vw, 25px);
  letter-spacing: -0.018em;
}

/* Steps that read as steps: a title that names its own number, a hairline
   between them, and enough air that two steps are never mistaken for one
   paragraph with a bold line in it. */
.steps {
  gap: 0;
  /* One space for the rhythm of a step: above and below the hairline, and
     under the title and under the banner. The rest keeps the row gap. */
  --step-space: clamp(28px, 3.4vh, 40px);
  --step-gap: clamp(10px, 1.1vh, 14px);
}

.steps > li {
  gap: var(--step-gap);
  padding: var(--step-space) 0;
}

.steps > li:first-child { padding-top: 0; }
.steps > li:last-child { padding-bottom: 0; }
.steps > li + li { border-top: 1px solid var(--line); }

.step-title {
  display: block;
  font-weight: 470;
  font-size: clamp(16px, 1.15vw, 19px);
  letter-spacing: -0.015em;
  /* Trimmed to its capitals and its baseline, so the space measured from the
     hairline and to what follows is the space seen. */
  line-height: 1.2;
  text-box: trim-both cap alphabetic;
  margin-bottom: calc(var(--step-space) - var(--step-gap));
}

/* The banner is followed by the same space as the title. */
.steps .banner { margin-bottom: calc(var(--step-space) - var(--step-gap)); }

/* The number is written into the title instead of sitting in a bubble beside
   it: the line reads "Step 2: Add custom CSS", which is how it is spoken. */
.step-title::before {
  content: "Step " counter(step) ": ";
  color: var(--accent);
  border: 0;
  width: auto;
  height: auto;
  display: inline;
  transform: none;
  font: inherit;
}

/* The 30px indent was there to clear that bubble. Nothing to clear now. */
.steps .step-note,
.steps .snippet,
.steps .knobs,
.steps .knobs-scroll,
.steps .action { margin-left: 0; }

.steps .step-note { margin-bottom: clamp(4px, 0.6vh, 8px); }

/* AB-50: the two sections of spec.md written for whoever integrates, rendered
   from markdown inside a step of the Code tab. The note keeps its size and
   colour; only what markdown brings with it (paragraphs, a list, bold
   lead-ins, long code names) needs a rule, since the reset strips margins
   and list indents. */
.step-md > * + * { margin-top: 10px; }
.step-md ul, .step-md ol { padding-left: 1.2em; list-style: disc; }
.step-md li + li { margin-top: 9px; }
.step-md li::marker { color: var(--label); }
.step-md strong { color: var(--text); font-weight: 500; }
.step-md code { overflow-wrap: anywhere; }

/* The note that repeats at step 2 and step 3: the code is already inside the
   embed, this is only here for whoever wants it by hand. A neutral surface one
   step above the page, a hairline all the way round and an information mark:
   the accent rule on the left and the [//] tag made it read as a quote. */
.banner {
  display: flex;
  align-items: flex-start;
  gap: clamp(8px, 0.7vw, 11px);
  padding: clamp(10px, 1.2vh, 14px) clamp(12px, 1.2vw, 16px);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-high);
}

/* The mark is the only accent left in the block, and only on its stroke. */
.banner > .info-icon {
  flex: none;
  width: clamp(14px, 1.05vw, 16px);
  height: clamp(14px, 1.05vw, 16px);
  color: var(--accent);
  margin-top: clamp(2px, 0.25vh, 4px);
}

.banner p {
  color: var(--muted);
  font-size: clamp(12.5px, 0.9vw, 13.5px);
  line-height: 1.6;
  max-width: 72ch;
}

.banner-link {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.banner-link:hover { color: var(--accent); border-color: var(--accent); }

/* The "i" beside a step title. Fixed size and a line box of its own, so the
   title keeps its height whether the control is there or not. */
.step-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: clamp(6px, 0.5vw, 9px);
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  color: var(--label);
  line-height: 0;
  vertical-align: middle;
  /* Negative vertical margins keep the circle out of the line box, so the
     title is exactly as tall with the control as without it. */
  margin-block: -5px;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.step-help .info-icon { width: 11px; height: 11px; }

/* The drawn circle would double the one around it. */
.step-help .info-icon circle { display: none; }

.step-help:hover,
.step-help:focus-visible {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}

/* A snippet is an object with a bar of its own: language on the left, the two
   controls on the right. The Copy button no longer floats over the first line
   of code, which is where it used to hide an attribute name. */
.snippet-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: clamp(7px, 0.8vh, 10px) clamp(9px, 0.9vw, 12px);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
}

.snippet-lang { color: var(--label); }
.snippet-tools { display: flex; align-items: center; gap: 6px; }

.snippet-fold,
.snippet-bar .snippet-copy {
  position: static;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg);
  color: var(--label);
  font-family: "Geist Mono", ui-monospace, monospace;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 9.5px;
  padding: 5px 9px;
  cursor: pointer;
  transition: color 0.16s ease, border-color 0.16s ease;
}

.snippet-fold:hover { color: var(--text); border-color: var(--line-strong); }

/* The code sits under a bar now, so it no longer keeps a lane free on the
   right for a floating button. */
.snippet pre { padding: clamp(12px, 1.4vh, 16px); }

/* The settings table. Three columns since AB-63: the attribute, its default
   and what it is. Lower and Higher were two columns more, and a setting that
   takes a list of values has neither, so those rows spilled their options
   across the two cells while the others left one empty. The spec keeps writing
   all three and the AI prompt keeps carrying them. */
.knobs-scroll { overflow-x: auto; }

/* AB-58 then AB-63: the attribute that carries the effect is a table of its
   own, two columns, above the settings table, and the structural marks of the
   component are lines of it. It has no default column to keep clear, so it
   does not need the wide minimum the settings table needs. */
/* AB-63: inside a step, a subtitle separates the attribute that carries the
   effect from the settings that tune it. Two things it has to do. Line up: the
   sentence and the table under it are flush with the step, so the subtitle
   carries no indent of its own. And not be taken for the header of a column,
   which is small grey uppercase mono as well: this one is in full text colour,
   opened by the bracket tag the site already uses and carried by a hairline
   that runs the width of the content. */
.step-sub {
  margin: clamp(16px, 2vh, 22px) 0 clamp(8px, 1vh, 11px);
  padding-top: clamp(7px, 0.9vh, 10px);
  border-top: 1px solid var(--line);
  color: var(--text);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.step-sub::before { content: "[//] "; color: var(--label); }

/* Required or Optional, at the end of what a structural mark does. Two words
   do not deserve a column, and the difference has to be seen: the one that
   cannot be left out is in text colour, the others stay in the terminal grey. */
.need {
  white-space: nowrap;
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--label);
}

.need.is-required { color: var(--text); }

/* Fixed columns, because the second one is a sentence. Left to itself the
   table asks for the width of its longest line, which used to be short, and a
   mark that says what the engine does without it is not: the table grew past
   its scroller and the end of every long line was cut. Fixed columns make the
   sentence wrap instead. */
.knobs.attr { min-width: 0; table-layout: fixed; width: 100%; }
.knobs.attr td:first-child, .knobs.attr th:first-child { width: 34%; }
/* The second column of the settings table is a default, one word that must not
   break; here it is the sentence, so it takes its wrapping back. Without this
   line the sentences ran on a single line and the table scrolled sideways. */
.knobs.attr td:nth-child(2) { color: var(--muted); font-family: inherit; white-space: normal; }
/* The first column keeps the nowrap every settings table gives it, because an
   attribute name must never break in the middle. But a line of this table may
   carry several names at once, the ones the engine writes on the same box, and
   a cell holding four of them measured 611px inside a table laid out at 358:
   it pushed the whole page sideways on a phone, where the scroller is off.
   Wrapping is safe here precisely because a name holds no space, so the line
   can only break after a comma and never inside a name. */
.knobs.attr td:first-child { white-space: normal; }
.knobs { min-width: 460px; }

/* The settings table only: the attribute table has two columns of its own and
   its second one is a sentence. Left unscoped, the 15% written here for the
   Default column landed on that sentence, and fixed layout shared the rest out
   in proportion, which gave the attribute column two thirds of the table. */
.knobs:not(.attr) th:first-child { width: 26%; }
.knobs:not(.attr) th:nth-child(2) { width: 15%; }

.knobs td:nth-child(3) { min-width: 20ch; }

.knobs td { line-height: 1.55; }

/* Narrow: the table becomes one card per setting. A five column table on a
   phone is either unreadable or a horizontal scroll, and both are worse than
   a stack. The word in front of each cell is what keeps it readable. */
@media (max-width: 760px) {
  .knobs-scroll { overflow-x: visible; }
  .knobs, .knobs tbody, .knobs tr, .knobs td { display: block; width: 100%; min-width: 0; }
  .knobs thead { display: none; }

  .knobs tr {
    padding: clamp(12px, 1.6vh, 16px) 0;
    border-bottom: 1px solid var(--line);
  }

  .knobs td { border: 0; padding: 0 0 6px; }
  .knobs td:empty { display: none; }
  .knobs td:first-child, .knobs td:nth-child(2) { display: inline-block; width: auto; padding-right: 10px; }

  /* The attribute table has two columns, and the second one is the sentence,
     not a short default: an inline-block would size it to the whole sentence
     and run it off the screen. It stays a block, on its own line, and the
     attribute above it takes the width it needs. */
  .knobs.attr td:first-child { width: auto; }
  .knobs.attr td:nth-child(2) { display: block; width: 100%; padding-right: 0; }
}

/* The band wraps before the control is crushed. */
@media (max-width: 560px) {
  .integrate-head { align-items: flex-start; }
  .tabs { --tabs-pad: 3px; }
  .tab { padding: 9px 11px; font-size: 11px; gap: 6px; }
}


/* ==========================================================
   Tutorials: the table that compares the three places the code
   can live. The page itself is laid out by the .tut block at
   the end of this file, sidebar on the left, chapter on the
   right, both inside the page gutter.
   The old one column shell (`.tutorials`, capped at 88ch, and
   its `.toc` card) went with the rewrite: nothing wore those
   classes any more, and a capped column is exactly what the
   full width page dropped (AB-23).
   ========================================================== */

/* The comparison of the three places. The first cell names the place, so it
   reads as a heading of its row rather than as one more sentence. */
.places td:first-child {
  color: var(--text);
  font-weight: 520;
  white-space: nowrap;
  padding-right: clamp(14px, 1.4vw, 22px);
}

.places th, .places td { padding-right: clamp(10px, 1.2vw, 18px); }
.places th:last-child, .places td:last-child { padding-right: 0; }

/* Left to itself the browser gives the short columns the width of their
   longest word and crushes "What it covers" into a ladder of one word per
   line. */
.places th:nth-child(1) { width: 17%; }
.places th:nth-child(2) { width: 33%; }
.places th:nth-child(3) { width: 50%; }

/* The one mark the table carries. It sits under the name of the place, on its
   own line, because the first cell is set in `nowrap`. */
.places .rec {
  display: block;
  width: fit-content;
  margin-top: 8px;
  font-size: 10.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: 999px;
  padding: 5px 12px;
}

/* Below that, four columns of prose crush each other: each row becomes a
   small card and every cell says which column it came from. */
@media (max-width: 760px) {
  .places, .places tbody, .places tr, .places td { display: block; width: 100%; }
  .places thead { display: none; }

  .places tr {
    padding: clamp(12px, 1.6vh, 16px) 0;
    border-bottom: 1px solid var(--line);
  }

  .places td { border: 0; padding: 0 0 clamp(8px, 1vh, 12px); }
  .places td:last-child { padding-bottom: 0; }
  .places td:first-child { white-space: normal; padding-right: 0; }

  .places td::before {
    content: attr(data-label);
    display: block;
    font-family: "Geist Mono", ui-monospace, monospace;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--label);
    margin-bottom: 3px;
  }

  .places td:first-child::before { content: none; }
}


/* ==========================================================
   Tutorials, chapter version (AB-21)
   One chapter at a time, a sticky sidebar on the left, a
   clickable summary at the top of each chapter, one diagram
   per step. Everything below is prefixed .tut, so the block
   above stays untouched.
   ========================================================== */

.tut {
  display: grid;
  grid-template-columns: minmax(0, clamp(180px, 17vw, 236px)) minmax(0, 1fr);
  gap: clamp(26px, 3.4vw, 62px);
  align-items: start;
  padding: clamp(22px, 3vh, 40px) 0 clamp(56px, 8vh, 96px);
}

.tut-body { min-width: 0; }

/* ---------- The chapter list ---------- */

.tut-nav { position: sticky; top: var(--stick-top); }
.tut-nav-title { color: var(--label); margin-bottom: clamp(8px, 1.2vh, 14px); }

.tut-nav-list,
.tut-nav-sub { list-style: none; margin: 0; }

.tut-nav-link {
  display: block;
  text-decoration: none;
  color: var(--muted);
  font-size: clamp(14px, 1vw, 15.5px);
  line-height: 1.3;
  padding: clamp(7px, 0.9vh, 10px) 0 clamp(7px, 0.9vh, 10px) clamp(10px, 0.9vw, 14px);
  border-left: 1px solid var(--line);
  transition: color 0.18s ease, border-color 0.18s ease;
}

.tut-nav-link:hover { color: var(--text); border-left-color: var(--line-strong); }

.tut-nav-link[aria-current="true"] {
  color: var(--text);
  border-left-color: var(--accent);
}

/* The sub-chapters only show under the chapter being read: a list of
   twenty entries is a wall, not a map. */
.tut-nav-sub { display: none; }
.tut-nav-item.is-open .tut-nav-sub { display: grid; gap: 1px; margin-bottom: clamp(6px, 0.8vh, 10px); }

.tut-nav-sub a {
  display: block;
  text-decoration: none;
  color: var(--label);
  font-size: clamp(12.5px, 0.85vw, 13.5px);
  line-height: 1.35;
  padding: clamp(5px, 0.6vh, 7px) 0 clamp(5px, 0.6vh, 7px) clamp(22px, 2vw, 28px);
  border-left: 1px solid var(--line);
  transition: color 0.18s ease;
}

.tut-nav-sub a:hover { color: var(--accent); }

/* ---------- A chapter ---------- */

.tut-chapter[hidden] { display: none; }
.tut .prose { max-width: none; }
.tut .prose h2 { margin-top: 0; padding-top: 0; border-top: 0; }
.tut .prose p, .tut .prose li { max-width: 74ch; }
.tut .prose ul, .tut .prose ol { margin-left: clamp(18px, 1.6vw, 24px); }

/* An anchor has to land below the fixed bar, not behind it. */
.tut-chapter,
.tut-sec,
.tut-sec h4[id],
.tut-qa dt[id] { scroll-margin-top: clamp(84px, 10vh, 116px); }

/* Three classes deep on purpose: ".tut .prose p" would win otherwise, and
   this paragraph is the one line that introduces the chapter. */
.tut .prose .tut-lead {
  color: var(--text);
  font-size: clamp(16px, 1.3vw, 18px);
  line-height: 1.55;
  max-width: 60ch;
  margin-bottom: clamp(20px, 2.6vh, 30px);
}

.tut-sec { padding-top: clamp(26px, 3.4vh, 44px); }
.tut-sec + .tut-sec { border-top: 1px solid var(--line); }
.tut-sec h3 { font-size: clamp(19px, 1.6vw, 23px); font-weight: 470; letter-spacing: -0.012em; margin: 0 0 clamp(10px, 1.3vh, 14px); }
.tut-sec h4 { font-weight: 470; font-size: clamp(15px, 1.15vw, 16.5px); margin: clamp(20px, 2.4vh, 28px) 0 8px; }
.tut-kicker { color: var(--label); margin-bottom: 2px; }

/* ---------- The summary at the top of a chapter ---------- */

.tut .prose .tut-summary {
  list-style: none;
  counter-reset: tutline;
  margin: 0 0 clamp(8px, 1vh, 14px) 0;
  padding: 0 clamp(14px, 1.4vw, 22px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

/* The reading measure of the chapter text does not apply to a row of the
   summary, which is a two column line inside its own box. */
.tut .prose .tut-summary li { counter-increment: tutline; margin: 0; max-width: none; }
.tut-summary li + li { border-top: 1px solid var(--line); }

.tut-summary a {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px clamp(10px, 1.2vw, 20px);
  text-decoration: none;
  padding: clamp(10px, 1.3vh, 15px) 0;
}

.tut-summary .name {
  flex: 0 0 clamp(150px, 17vw, 226px);
  color: var(--text);
  font-size: clamp(14px, 1vw, 15.5px);
  font-weight: 500;
  transition: color 0.18s ease;
}

.tut-summary .name::before {
  content: counter(tutline) ". ";
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 0.82em;
  color: var(--accent);
}

.tut-summary .what {
  flex: 1 1 240px;
  color: var(--muted);
  font-size: clamp(13px, 0.95vw, 14.5px);
  line-height: 1.5;
}

.tut-summary a:hover .name { color: var(--accent); }
.tut-summary a:hover .what { color: var(--text); }

/* ---------- Diagrams ---------- */

/* A diagram is framed like a figure and carries no caption: what it shows is
   written in the paragraphs around it (AB-31). */
.tut-fig {
  margin: clamp(18px, 2.4vh, 28px) 0;
  max-width: min(100%, 780px);
}

.tut-fig img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: clamp(12px, 1.4vw, 22px);
}

/* ---------- Custom attributes ---------- */

/* AB-65: a custom attribute is shown here the way a component page shows it,
   a subtitle and a two column table, never in prose. The classes are the ones
   those pages use, so the two cannot drift apart; only two things have to be
   said again. The 30px indent of a numbered step has to go, a tutorial has no
   step number to clear. And `.prose table` is one class plus one element,
   which outweighs the single class of `.knobs`: left alone it would set the
   table back to the prose size. Two classes here, and the table reads the
   same on both pages. */
.tut .step-sub,
.tut .step-note,
.tut .knobs { margin-left: 0; }

.tut .knobs { font-size: 13px; }
.tut .knobs-scroll { margin-bottom: clamp(18px, 2.2vh, 24px); }
.tut .step-note { margin-bottom: clamp(10px, 1.2vh, 14px); }

/* ---------- Questions ---------- */

.tut-qa { margin: 0; }

.tut-qa dt {
  color: var(--text);
  font-size: clamp(15px, 1.15vw, 16.5px);
  font-weight: 500;
  line-height: 1.4;
  margin-bottom: clamp(6px, 0.8vh, 9px);
  padding-top: clamp(16px, 2.2vh, 26px);
}

.tut-qa dt:first-child { padding-top: clamp(8px, 1.2vh, 14px); }

.tut-qa dd {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.68;
  max-width: 74ch;
  padding-bottom: clamp(16px, 2.2vh, 26px);
  border-bottom: 1px solid var(--line);
}

.tut-qa dd strong { color: var(--text); font-weight: 520; }

/* ---------- Narrow: the sidebar becomes a strip ---------- */

@media (max-width: 900px) {
  .tut {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(14px, 2vh, 22px);
    padding-top: 0;
  }

  /* The chapter strip sticks flush under the bar, whatever its height. */
  .tut-nav {
    top: 71px;
    z-index: 30;
    margin: 0 calc(var(--gutter) * -1);
    padding: 10px var(--gutter);
    background: color-mix(in srgb, var(--bg) 86%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
  }

  .tut-nav-title { display: none; }

  /* One scrolling row of chapters. The sub-chapters stay in the clickable
     summary at the top of the chapter, which is right under the strip. */
  .tut-nav-list {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .tut-nav-list::-webkit-scrollbar { display: none; }
  .tut-nav-item { flex: 0 0 auto; }
  .tut-nav-sub,
  .tut-nav-item.is-open .tut-nav-sub { display: none; }

  .tut-nav-link {
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 7px 13px;
    white-space: nowrap;
    font-family: "Geist Mono", ui-monospace, monospace;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 10.5px;
    color: var(--label);
  }

  .tut-nav-link:hover { border-color: var(--line-strong); }

  .tut-nav-link[aria-current="true"] {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, transparent);
    background: var(--accent-soft);
  }

  /* The strip is fixed too, so a target has to clear both bars. */
  .tut-chapter,
  .tut-sec,
  .tut-sec h4[id],
  .tut-qa dt[id] { scroll-margin-top: clamp(136px, 18vh, 168px); }

  .tut-summary .name { flex: 1 1 100%; }
}
@media (max-width: 820px) { .tut-nav { top: 65px; } }


/* ==========================================================
   AB-23. The page is full width, so running text needs its
   own measure: a column that is 1 500 px wide would set a
   sentence of 210 characters. The measure is written in `ch`
   on the text itself, never on the block that holds it, so
   tables, snippets and diagrams still fill the column.
   ========================================================== */

.step-note,
.panel-intro,
.banner p,
.action-note { max-width: 72ch; }

/* The lede already carries its own 60ch above, and a summary row is a two
   column line in a box, not running text: neither is touched here. */
.tut-sec p,
.tut-sec li,
.tut-qa dd { max-width: 72ch; }


/* ==========================================================
   AB-27. The rail, in rows. A full width button with its
   label pinned left leaves a hole on the right, and the same
   button centred puts its label off the reading edge of the
   notes under it. So the line itself becomes the button:
   icon, label, what the action gives, and an arrow that ends
   the line instead of empty space.
   ========================================================== */

.integrate-rail { gap: 0; }

/* Rows touch, so a hairline does the separating the gap used to do. Every
   hairline gets the same air above and below it: the rows' own padding, and
   the facts block under them uses that same value instead of its own. The
   first row starts flush, so the card's padding reads the same on all four
   sides. */
.action.rail + .action.rail { border-top: 1px solid var(--line); }
.integrate-rail .action.rail:first-child .act-row { padding-top: 0; }
.integrate-rail .rail-facts { margin-top: 0; padding-top: clamp(11px, 1.3vh, 15px); }

/* Stretched by the grid, not by a width: a width of 100 % plus the negative
   margin of the primary block below would push the block left instead of
   widening it. */
.integrate-rail .act-row {
  width: auto;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(10px, 0.8vw, 13px);
  padding: clamp(11px, 1.3vh, 15px) 0;
  border: 0;
  border-radius: 0;
  background: none;
  text-align: left;
}

.act-icon {
  display: grid;
  place-items: center;
  width: clamp(28px, 2.1vw, 32px);
  height: clamp(28px, 2.1vw, 32px);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  transition: border-color 0.16s ease, color 0.16s ease;
}

.act-row .act-label {
  display: block;
  font-size: clamp(13px, 0.95vw, 13.5px);
  line-height: 1.35;
  color: var(--text);
}

/* The answer of a row, in full, for a screen reader only: on screen the
   label itself takes it, in a word. */
.action.rail { position: relative; }

.action.rail .action-note {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The label while it answers: the colour of its route. */
.integrate-rail .act-row[data-state="done"] .act-label { color: var(--accent); }
.integrate-rail .act-row.webflow[data-state="done"] .act-label { color: #4d94ff; }
.integrate-rail .act-row[data-act="ai"][data-state="done"] .act-label { color: var(--ai); }
.integrate-rail .act-row.is-locked[data-state="done"] .act-label { color: var(--text); }

.act-go {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--label);
  transition: color 0.16s ease, transform 0.16s ease;
}

.integrate-rail .act-row:hover { background: none; border-color: transparent; }
.integrate-rail .act-row:hover .act-icon { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.integrate-rail .act-row:hover .act-go { color: var(--accent); transform: translateX(3px); }
.integrate-rail .act-row[data-state="done"] { color: inherit; border-color: transparent; background: none; }

/* The three rows sit at the same level: Webflow is one door among three,
   not the one the page pushes. Only the icon keeps the Webflow blue.
   (Benjamin, 14/09/2026: no full blue block in the rail.) */
.integrate-rail .act-row.webflow .act-icon { color: #146ef5; }
.integrate-rail .act-row.webflow:hover .act-icon { color: #146ef5; border-color: color-mix(in srgb, #146ef5 45%, transparent); }
.integrate-rail .act-row.webflow:hover .act-go { color: #146ef5; }
.integrate-rail .act-row.webflow[data-state="done"] { background: none; color: inherit; }
.integrate-rail .act-row.webflow[data-state="done"] .act-icon { color: #146ef5; border-color: color-mix(in srgb, #146ef5 45%, transparent); }
.integrate-rail .act-row.webflow[data-state="done"] .act-go { color: #146ef5; }

/* A click leaves no ring: some browsers draw their default focus ring on a
   clicked button, which here framed the whole row. The keyboard still gets
   one, drawn inside the card. */
.integrate-rail .act-row:focus:not(:focus-visible) { outline: none; }
.integrate-rail .act-row:focus-visible { outline: 1px solid var(--line-strong); outline-offset: 2px; border-radius: 8px; }

/* The answer of an action has to read as an answer even on the plain rows:
   the icon lights with the label. */
.integrate-rail .act-row:not(.webflow)[data-state="done"] .act-icon { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }


/* ==========================================================
   AB-26. The "i" beside a step title answers before it sends
   anyone away: a short title, where this code is allowed to
   live, and the way to the tutorial under it. The box is
   fixed and placed by the script, so a column never cuts it
   and it never leaves a narrow screen.
   ========================================================== */

.step-pop {
  position: fixed;
  z-index: 70;
  width: clamp(240px, 22vw, 300px);
  max-width: calc(100vw - 20px);
  padding: clamp(12px, 1.2vw, 16px);
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--panel);
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45);
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.16s ease, transform 0.16s ease;
}

.step-pop[hidden] { display: none; }

.step-pop.is-open { opacity: 1; transform: translateY(0); pointer-events: auto; }

.step-pop-title {
  font-size: 16px;
  font-weight: 470;
  letter-spacing: -0.015em;
  color: var(--text);
}

.step-pop-text {
  margin-top: clamp(5px, 0.6vh, 8px);
  color: var(--muted);
  font-size: clamp(12.5px, 0.9vw, 13.5px);
  line-height: 1.6;
}

/* The secondary button of the page, stretched to the width of the box. */
.step-pop-act {
  margin-top: clamp(10px, 1.1vh, 14px);
  width: 100%;
  justify-content: flex-start;
  text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
  .step-pop,
  .step-pop.is-open { transform: none; transition: none; }
}


/* ==========================================================
   AI accent: violet
   Everything that speaks to an assistant wears the violet of
   the :root --ai family instead of the lime. The Webflow row
   and tab keep their blue; everything else keeps the lime.
   ========================================================== */

/* ---- The rail row, "Copy the AI prompt" ---- */
.integrate-rail .act-row[data-act="ai"]:hover .act-icon { color: var(--ai); border-color: var(--ai-line); }
.integrate-rail .act-row[data-act="ai"]:hover .act-go { color: var(--ai-strong); transform: translateX(3px); }
.integrate-rail .act-row[data-act="ai"][data-state="done"] .act-icon { color: var(--ai); border-color: var(--ai-line); }

/* ---- The integration tab, "With AI" ---- */
.tab[data-panel="ai"]:hover { color: var(--text); }

.tab[data-panel="ai"]:hover svg,
.tab[aria-selected="true"][data-panel="ai"] svg { color: var(--ai); }

/* The AI colour travels with the pill, since the pill is what carries the
   ground of the selection. */
.tabs[data-on="ai"] .tab-pill { background: var(--ai-soft); }

.tab[aria-selected="true"][data-panel="ai"] { color: var(--text); }

/* ---- The step button, "Copy the AI prompt" in the AI panel ---- */
.act[data-act="ai"]:not(.act-row):hover { border-color: var(--ai-line); }
.act[data-act="ai"]:not(.act-row):hover svg { color: var(--ai); }

/* The answer of the action, in the AI colour, on the button and on the note
   under it. */
.act[data-act="ai"][data-state="done"] { color: var(--ai); border-color: var(--ai-line); }
.act[data-act="ai"] + .action-note[data-state="done"] { color: var(--ai); }

/* ---- The AI chapter of the tutorials ---- */
.tut-nav-item[data-for="ai"] .tut-nav-link:hover { border-left-color: var(--ai-line); }
.tut-nav-item[data-for="ai"] .tut-nav-link[aria-current="true"] { border-left-color: var(--ai); }

#ai .tut-kicker { color: var(--ai); }
#ai .tut-summary .name::before { color: var(--ai); }
#ai .tut-summary a:hover .name { color: var(--ai); }
.tut-nav-item[data-for="ai"] .tut-nav-sub a:hover { color: var(--ai); }

@media (max-width: 900px) {
  .tut-nav-item[data-for="ai"] .tut-nav-link:hover { border-color: var(--ai-line); color: var(--text); }

  .tut-nav-item[data-for="ai"] .tut-nav-link[aria-current="true"] {
    color: var(--ai);
    border-color: var(--ai-line);
    background: var(--ai-soft);
  }
}


/* ==========================================================
   Packs and the tunnel (AB-98)
   A pack is one card on the library holding several: the
   library, the pack page and the page of each item are three
   steps of one tunnel, changing page like the rest of the site.
   ========================================================== */

/* The pack card and the plates of a pack page: see cards.css. */

/* ---- The pack page ---- */
.pack-head .breadcrumb { margin-bottom: clamp(14px, 2vh, 24px); }

/* ---- The page of an item: breadcrumb and neighbours ---- */
.breadcrumb { flex-wrap: wrap; row-gap: 8px; }
.crumb-pack { display: inline-flex; align-items: center; gap: 8px; }
.crumb-pack[hidden] { display: none; }

.pack-pager {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.pack-pager[hidden] { display: none; }

.pager-step {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  text-decoration: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 11px;
  transition: color 0.2s ease, border-color 0.2s ease;
}

a.pager-step:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.pager-step[aria-disabled="true"] { opacity: 0.35; }
.pager-step svg { width: 11px; height: 11px; flex: none; }
.pager-at { color: var(--label); padding: 0 4px; font-variant-numeric: tabular-nums; }

/* What the effect is, between the preview and the documentation (AB-180). */
.about {
  display: grid;
  gap: 10px;
  margin: clamp(40px, 6vh, 72px) 0 0;
  padding: 0 clamp(14px, 1.6vw, 22px);
  max-width: 78ch;
}

.about[hidden] { display: none; }

.about-title {
  font-weight: 470;
  font-size: clamp(19px, 1.5vw, 25px);
  letter-spacing: -0.018em;
  margin-bottom: 4px;
}

.about-lead { color: var(--text); line-height: 1.5; }
.about-text { color: var(--muted); line-height: 1.55; }
.about-date { color: var(--label); margin-top: 6px; }

/* The questions of a component page, folded under the documentation (AB-181).
   The rows are the landing's own (.qa, assets/base.css). */
.questions {
  margin-top: clamp(40px, 6vh, 72px);
  padding: 0 clamp(14px, 1.6vw, 22px);
}

.questions[hidden] { display: none; }

.questions-title {
  font-weight: 470;
  font-size: clamp(19px, 1.5vw, 25px);
  letter-spacing: -0.018em;
  margin-bottom: clamp(14px, 2vh, 22px);
}

.questions .qa button { background: none; border: 0; color: var(--text); font: inherit; font-size: clamp(16px, 1.2vw, 19px); cursor: pointer; padding: 18px 0; }
.questions .qa .ans p { max-width: 68ch; }
.questions .qa .ans code { font-family: "Geist Mono", ui-monospace, monospace; font-size: 0.9em; color: var(--text); }

/* The related effects, a row of plain links above the neighbours (AB-179). */
.related {
  margin: clamp(40px, 7vh, 80px) calc(var(--gutter) * -1) 0;
  padding: clamp(20px, 3vh, 32px) var(--gutter) 0;
  border-top: 1px solid var(--line);
}

.related[hidden] { display: none; }
.related + .pack-next { margin-top: clamp(24px, 4vh, 44px); }
.related-h {
  font-weight: 470;
  font-size: clamp(19px, 1.5vw, 25px);
  letter-spacing: -0.018em;
  margin: 0;
}

.related-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap; }
.related-all { color: var(--muted); font-size: clamp(14px, 1vw, 16px); text-decoration: none; transition: color 0.2s; }
.related-all:hover, .related-all:focus-visible { color: var(--text); }

.related .related-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); padding: clamp(16px, 2.4vh, 26px) 0 0; }
.related-grid .card .summary { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

@media (max-width: 1100px) { .related .related-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* On a phone the four cards are one row the thumb slides, each stopping at
   the gutter, the next one showing its edge. No script: the browser's own
   scroll and its snap points. */
@media (max-width: 700px) {
  .related .related-grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 78%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-bottom: 4px;
    scrollbar-width: none;
  }
  .related .related-grid::-webkit-scrollbar { display: none; }
  .related .related-grid .card { scroll-snap-align: start; }
}

/* The neighbours at the end of the page, two halves on a hairline. */
.pack-next {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: clamp(40px, 7vh, 80px) calc(var(--gutter) * -1) 0;
  border-top: 1px solid var(--line);
}

.pack-next[hidden] { display: none; }

.pack-next-link {
  position: relative;
  display: grid;
  gap: 6px;
  align-content: start;
  padding: clamp(24px, 4vh, 44px) var(--gutter) clamp(28px, 5vh, 56px);
  color: var(--text);
  text-decoration: none;
  transition: background 0.3s ease;
}

.pack-next-link + .pack-next-link {
  border-left: 1px solid var(--line);
  text-align: right;
}

.pack-next-link:hover { background: var(--surface); }
.pack-next-link > .lead { color: var(--label); }

.pack-next-link .t {
  font-weight: 440;
  font-size: clamp(30px, 4.2vw, 64px);
  line-height: 1;
  letter-spacing: -0.03em;
}

.pack-next-link .e { color: var(--muted); }

.pack-next-link .lead { display: inline-flex; align-items: center; gap: 8px; }
.pack-next-link + .pack-next-link .lead { justify-self: end; }

.pack-next-link svg {
  width: 12px;
  height: 12px;
  flex: none;
  transition: color 0.2s ease, transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.pack-next-link:first-child:hover svg { color: var(--accent); transform: translateX(-4px); }
.pack-next-link + .pack-next-link:hover svg { color: var(--accent); transform: translateX(4px); }

/* The side that leads back to the pack rather than to a neighbour. */
.pack-next-link.is-set .t { color: var(--muted); }

@media (max-width: 700px) {
  .pack-pager { margin-left: 0; }
  .pack-next { grid-template-columns: minmax(0, 1fr); }
  .pack-next-link + .pack-next-link { border-left: 0; border-top: 1px solid var(--line); text-align: left; }
  .pack-next-link + .pack-next-link .lead { justify-self: start; }
}

/* ==========================================================
   The Black / white look, 24/09/2026: the previews and the code
   ========================================================== */

/* The previews sit on their ground while their demo loads, as once it is
   there. */
.stage-frame iframe { background: var(--preview-ground); }

/* No yellow in the code either: strings and numbers take the text colour. */
.snippet .s,
.snippet .n { color: var(--text); }

/* ==========================================================
   The lock (AB-129)
   A page whose component the reader may not load: the recording plays
   under a blur in the stage, the three routes sit under the same blur, and
   one card over each says how to get in. The text stays in the page. The
   button borrows the landing's micro-interactions: the label rolls, the
   arrow glides, the pill gives under the click.
   ========================================================== */

.stage-frame.is-locked .stage-tools { display: none; }

.stage-recorded {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--preview-ground);
}
.stage-recorded video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(14px) saturate(0.9);
  transform: scale(1.08); /* the blur would pale the edges */
}
.stage-recorded::after {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 38%, transparent);
}

.lock {
  position: absolute;
  z-index: 5;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(calc(100% - 32px), 400px);
  display: grid;
  justify-items: center;
  gap: clamp(12px, 1.2vw, 16px);
  padding: clamp(24px, 2.4vw, 34px) clamp(20px, 2.4vw, 36px);
  text-align: center;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid var(--line-strong);
  border-radius: calc(var(--radius) + 4px);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
  transition: opacity 0.3s ease;
}
.access-pending .lock { opacity: 0; }

.lock-icon {
  display: grid;
  place-items: center;
  width: clamp(40px, 3.2vw, 46px);
  height: clamp(40px, 3.2vw, 46px);
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--surface-high);
  color: var(--text);
}
.lock-icon svg { width: 46%; height: 46%; }

.lock-text {
  max-width: 24ch;
  font-size: clamp(17px, 1.3vw, 20px);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--text);
  text-wrap: balance;
}

.lock-go {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 42px;
  padding: 0 18px;
  border-radius: 99px;
  background: var(--text);
  color: var(--bg);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: -0.015em;
  white-space: nowrap;
  text-decoration: none;
  transition: background-color 0.18s, transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}
.lock-go-t { position: relative; display: inline-block; overflow: hidden; vertical-align: top; line-height: 1.3; }
.lock-go-t > span { display: block; transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
.lock-go-t > span + span { position: absolute; left: 0; top: 100%; }
.lock-arr { width: 14px; height: 14px; transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
.lock-go:active { transform: scale(0.97); }
.lock-go:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

@media (hover: hover) {
  .lock-go:hover { background: color-mix(in srgb, var(--text) 86%, #fff); }
  .lock-go:hover .lock-go-t > span { transform: translateY(-100%); }
  .lock-go:hover .lock-arr { transform: translateX(4px); }
}

.lock-in {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--label);
}
.lock-in a { color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
.lock-in a:hover { color: var(--text); }

/* The three routes: a blurred excerpt of what is served, the lock over it. */
.integrate.is-locked .integrate-body { position: relative; }
.integrate.is-locked .integrate-panels {
  max-height: clamp(460px, 62vh, 680px);
  overflow: hidden;
  filter: blur(7px);
  opacity: 0.6;
  pointer-events: none;
  user-select: none;
  -webkit-mask-image: linear-gradient(#000 55%, transparent);
  mask-image: linear-gradient(#000 55%, transparent);
}
.integrate.is-locked .integrate-body { min-height: 0; }
/* one small tag over the blurred routes, held inside them whatever their height */
.lock-tag {
  position: absolute;
  z-index: 5;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 14px 0 11px;
  border-radius: 99px;
  border: 1px solid var(--line-strong);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--text);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}
.lock-tag svg { width: 14px; height: 14px; }
/* the routes locked: a padlock for icon, the same row, a little quieter */
.act.is-locked .act-icon { color: var(--muted); }
.act.is-locked .act-label { color: var(--muted); }
.integrate-rail .act-row.is-locked.webflow .act-icon { color: var(--muted); }
.integrate-rail .act-row.is-locked:hover .act-label { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .lock-go-t > span, .lock-arr, .lock-go { transition: none; }
}
