/* GENERATED by scripts/lib/bare-shell.mjs from public/bare/base.css, parts/figures.css, parts/nav.css, parts/footer.css, parts/boxed.css, parts/booking.css, parts/pages.css, parts/mobile.css, parts/layout-polish.css, parts/faq.css, parts/button.css, parts/nav-mobile.css, parts/resource.css, parts/journal.css + the library's badges. Do not edit. */
/* ==== base.css ==== */
/* ===========================================================================
   base.css   the OpusJake brand layer for the single page

   Three instructions from the client govern this file and everything built
   on it: keep oatmeal and indigo, keep the current fonts, keep it simple.
   So there is exactly ONE hue on this site, and exactly THREE typefaces.

   The page moves through seven aesthetic modes. Because hue and typeface are
   fixed, the modes separate through texture, value, density, shadow and
   negative space instead. That constraint is the design, not a limitation
   worked around.
   =========================================================================== */

@font-face { font-family: 'Endless'; src: url('/fonts/endless.ttf') format('truetype'); font-display: swap; }
@font-face { font-family: 'Pixeboy'; src: url('/fonts/pixeboy.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('/fonts/JetBrainsMono-Medium.woff2') format('woff2'); font-weight: 500 600; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('/fonts/JetBrainsMono-Bold.woff2') format('woff2'); font-weight: 700; font-display: swap; }

:root {
  color-scheme: light;

  --paper:   #EDE9DE;
  --card:    #F5F2EA;
  --muted:   #E4DFD1;
  --ink:     #2F2B25;
  --ink-2:   #6C6659;
  --ink-3:   #A8A294;
  --rule:    #2F2B2524;
  --rule-2:  #CFC8B8;

  /* the only hue on the site */
  --indigo:  #4b4aef;

  /* the inverted ground, used by the ledger mode alone */
  --ground:  #1E1B16;

  --sans:  'Endless', system-ui, sans-serif;
  --pixel: 'Pixeboy', ui-monospace, monospace;
  --mono:  'JetBrains Mono', ui-monospace, Menlo, monospace;

  /* 8px grid. Every spacing value in this project is a multiple of --u. */
  --u: 8px;

  /* Radius is a scale with a job per step, not a preference. 0 is structure,
     s is a small object, m is a panel, l is a large surface, pill is anything
     pressable. See DESIGN.md. A value off this scale is a bug. */
  --r-xs:   6px;
  --r-s:    10px;
  /* panels and large surfaces share one tight corner, 8px (Jake, Oct 2026,
     pointing at openai.com's cards); controls keep Astryx's 10px */
  --r-m:    8px;
  --r-l:    8px;
  --r-pill: 999px;

  /* Two elevations. Paper does not float: a content card gets a hairline. */
  --shadow-1: 0 1px 2px rgba(31, 29, 58, 0.14);
  --shadow-2: 0 10px 24px rgba(31, 29, 58, 0.14);
  /* the Apple pair: a tight contact shadow plus a wide, soft ambient one
     (Oct 10: Jake asked for depth on the boxes and buttons, as in Apple's UI) */
  --shadow-card: 0 1px 2px rgba(31, 29, 58, 0.08), 0 12px 28px -8px rgba(31, 29, 58, 0.24);
  --shadow-float: 0 1px 2px rgba(31, 29, 58, 0.10), 0 24px 56px -12px rgba(31, 29, 58, 0.34);

  /* section rhythm, 120px to 200px */
  --section-y: clamp(120px, 14vh, 200px);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

html, body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

body { overflow-x: hidden; }

a { color: var(--indigo); }

/* the contained column. Full bleed sections opt out by not using it. */
.wrap {
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: 32px;
}

/* shared type helpers. Sections may use these but must not restyle them. */
.mono-label {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.wordmark-pixel {
  font-family: var(--pixel);
  font-weight: 400;
  color: var(--indigo);
  letter-spacing: 0.04em;
  /* Pixeboy digits and caps sit ON the baseline and are exactly half an em
     tall, so a 100px font-size draws 50px of ink. Author by visible height
     and derive the size, never by eyeballing line-height. */
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ==== parts/figures.css ==== */
/* ===========================================================================
   figures.css   prefix fg-   the three FIG cards

   Every rule in this file is scoped to a class beginning fg-. Nothing here
   touches :root, a bare element, or a base.css helper.

   The reference is a technical figure plate, so the vocabulary is: hairline
   rules, tiny mono caps, a bordered square well on a slightly recessed
   ground, and two dashed leader annotations. Flat throughout. There is no
   border radius, no shadow and no gradient in this file, and --indigo is the
   only hue that appears.

   Spacing is authored in multiples of --u (8px) with no exceptions.
   =========================================================================== */

/* --- the card ----------------------------------------------------------- */

.fg-fig {
  padding-block: calc(var(--u) * 12);
  background: var(--paper);
}

/* the header row: number hard left, system label hard right */
.fg-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--u) * 2);
  padding-bottom: calc(var(--u) * 2);
}

.fg-num,
.fg-sys {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
}

.fg-num { color: var(--ink-2); }
.fg-sys { color: var(--ink-3); }

/* --- the two column body ------------------------------------------------ */

/* Narrow first. One column, and the figure is placed in row 1 so the plate
   reads above the copy on a phone. Source order is still copy then figure. */
.fg-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: calc(var(--u) * 6);
  margin-top: calc(var(--u) * 6);
}

.fg-copy   { grid-column: 1; grid-row: 2; min-width: 0; }
.fg-figure {
  grid-column: 1; grid-row: 1; margin: 0; min-width: 0;
  /* Cap the well at the same size it holds on desktop, so the narrow
     band cannot inflate the plate into a full bleed square. 58 units. */
  max-width: calc(var(--u) * 58);
  margin-inline: auto;
}

/* --- copy --------------------------------------------------------------- */

.fg-h {
  margin: 0;
  font-family: var(--sans);
  /* Endless ships one weight, so never ask for bold and get a synthetic one. */
  font-weight: 400;
  font-size: clamp(28px, 3.4vw, 44px);
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.fg-p {
  margin: calc(var(--u) * 3) 0 0;
  max-width: 44ch;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-2);
}

/* --- the action --------------------------------------------------------- */

.fg-act {
  display: inline-flex;
  align-items: center;
  gap: var(--u);
  margin-top: calc(var(--u) * 4);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1;
  text-decoration: none;
  color: var(--indigo);
}

/* FIG 01 and FIG 03: a quiet text action on a hairline. */
.fg-act-link {
  padding-bottom: var(--u);
  border-bottom: 1px solid var(--indigo);
  transition: color 180ms ease, border-color 180ms ease;
}

.fg-act-x { transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1); }

/* FIG 02: the client asked for a button here, so this is the one filled
   element in the whole spine. That is the entire reason it reads as the
   loudest thing on the page. */
.fg-act-btn {
  padding: calc(var(--u) * 2) calc(var(--u) * 4);
  border: 1px solid var(--indigo);
  background: var(--indigo);
  color: var(--paper);
  transition: background-color 180ms ease, color 180ms ease;
}

.fg-act:focus-visible { outline: 2px solid var(--indigo); outline-offset: calc(var(--u) / 2); }

/* --- the figure well ---------------------------------------------------- */

/* Square, because every plate is a 240 x 240 grid. Bordered, on a ground one
   step darker than the paper so it reads as recessed, with a generous inner
   band so the subject floats and the annotations have clean ground to sit on. */
.fg-well {
  position: relative;
  aspect-ratio: 1 / 1;
  /* rounded to match the card and the buttons, one radius across the site */
  border-radius: var(--r-l);
  overflow: hidden;
  padding: calc(var(--u) * 5);
  border: 1px solid var(--rule-2);
  background: var(--muted);
}

/* the exact box the plate occupies, and the coordinate space the annotations
   are positioned against */
.fg-frame {
  position: relative;
  width: 100%;
  height: 100%;
}

/* The renderer reads getComputedStyle(canvas).color to pick its ink, so the
   colour declaration here is load bearing, not decoration. Never give this a
   background. */
.fg-plate {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--indigo);
}


/* --- the annotations ---------------------------------------------------- */

/* Two per card, no more. The label always sits in the well's padding band, on
   clean ground, so it is legible whatever the plate is doing underneath. Only
   the dashed leader crosses onto the plate, and it crosses at 28 percent and
   78 percent of the width, well clear of the centre column where all three
   subjects live. These never move and never animate. */
.fg-anno {
  position: absolute;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: var(--ink-3);
  pointer-events: none;
}

.fg-anno::after {
  content: '';
  position: absolute;
  width: 0;
  height: calc(var(--u) * 7);
  border-left: 1px dashed var(--ink-3);
}

/* the terminal tick, so the leader points at something rather than trailing off */
.fg-anno::before {
  content: '';
  position: absolute;
  width: 3px;
  height: 3px;
  background: var(--ink-3);
}

/* upper right label, leader dropping into the plate */
.fg-anno-a { top: calc(var(--u) * -3); right: 0; }
.fg-anno-a::after  { top: calc(100% + var(--u)); left: calc(var(--u) * 3); }
.fg-anno-a::before { top: calc(100% + var(--u) * 8); left: calc(var(--u) * 3); margin-left: -1px; }

/* lower left label, leader rising into the plate */
.fg-anno-b { bottom: calc(var(--u) * -3); left: 0; }
.fg-anno-b::after  { bottom: calc(100% + var(--u)); right: calc(var(--u) * 3); }
.fg-anno-b::before { bottom: calc(100% + var(--u) * 8); right: calc(var(--u) * 3); margin-right: -1px; }

/* On a phone the well is small enough that two leaders crowd the subject, so
   the second one steps back. One annotation is still an annotation. */
@media (max-width: 639px) {
  .fg-anno-b { display: none; }
}

/* --- wide layout -------------------------------------------------------- */

@media (min-width: 900px) {

  .fg-fig { padding-block: calc(var(--u) * 16); }

  /* FIG 02 follows the longest tool-call run. Pull its opening edge up by
     eight grid units (64px), trimming roughly one eighth of the white gap in
     the desktop composition without changing the card's internal spacing. */
  .fg-fig-b { padding-top: calc(var(--u) * 8); }

  .fg-body {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: calc(var(--u) * 10);
    align-items: center;
    margin-top: calc(var(--u) * 9);
  }

  .fg-copy   { grid-row: 1; max-width: 480px; }
  .fg-figure { grid-row: 1; width: 100%; max-width: calc(var(--u) * 58); }

  /* Alternation happens here and only here: the source order never changes,
     the columns do. FIG 01 and FIG 03 put the copy left and the plate right,
     FIG 02 mirrors it. Each figure is pushed to the outer page edge so the
     spine reads as a zigzag rather than a stack of centred boxes. */
  .fg-fig-a .fg-copy,
  .fg-fig-c .fg-copy   { grid-column: 1; }
  .fg-fig-a .fg-figure,
  .fg-fig-c .fg-figure { grid-column: 2; margin-inline-start: auto; }

  .fg-fig-b .fg-copy   { grid-column: 2; }
  .fg-fig-b .fg-figure { grid-column: 1; margin-inline-end: auto; }
}

/* --- scroll reveal ------------------------------------------------------ */

/* IMPORTANT. Everything above is the finished, resting, fully visible state.
   The hidden state below only exists while figures.js is running, because
   only figures.js ever adds .fg-armed. If the script never loads, never
   parses, or bails out, the cards are already correct.

   figures.js reaches .fg-in by four independent routes: a first pass position
   check, an IntersectionObserver, a setInterval position poll, and a plain
   setTimeout hard stop. No two of them share a dependency, so neither the
   observer nor requestAnimationFrame is ever the only path to opacity 1.
   Measured: in a hidden tab neither rAF nor the observer fires at all, and
   the timers are what carry it. */

.fg-armed .fg-head,
.fg-armed .fg-h,
.fg-armed .fg-p,
.fg-armed .fg-act {
  opacity: 0;
  transform: translateY(12px);
}


/* The well fades as one piece. The annotations inside it therefore never
   translate, which is the requirement: they must not move. */
.fg-armed .fg-well,
.fg-armed 
.fg-in .fg-head,
.fg-in .fg-h,
.fg-in .fg-p,
.fg-in .fg-act {
  opacity: 1;
  transform: none;
}

/* ==== parts/nav.css ==== */
/* ===========================================================================
   nav.css   prefix: nv-
   =========================================================================== */

.nv-nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 3);
  padding: calc(var(--u) * 3) calc(var(--u) * 4);
  opacity: 1;
  background: transparent;
  transition: background-color 260ms ease, border-color 260ms ease;
  border-bottom: 1px solid transparent;
}

/* Once the reader is past the hero the bar earns a ground so type does not
   collide with the content running under it. */
.nv-nav.nv-in {
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom-color: var(--rule-2);
}

.nv-brand {
  font-family: var(--pixel);
  font-size: 32px;   /* Pixeboy draws half an em, so this is 16px of ink */
  line-height: 1;
  color: var(--indigo);
  text-decoration: none;
  letter-spacing: 0.04em;
  /* the home link: a 44px target around 16px of ink, the layout unmoved */
  padding-block: 12px;
  margin-block: -12px;
}

.nv-links { margin-left: auto; display: flex; align-items: center; gap: var(--u); }

/* The nav items are buttons, cut to the same shape as the pair in the hero:
   10px radius, a soft fill for the quiet ones and the solid indigo for the
   single call to action. */
.nv-link {
  font: 500 14px/1 var(--sans);
  color: var(--ink);
  text-decoration: none;
  /* FINDING-006: 38px tall missed the 44px target floor */
  padding: 15px calc(var(--u) * 2);
  border-radius: var(--r-s);
  border: 0;
  background: transparent;
  transition: background-color 160ms ease, color 160ms ease;
}

.nv-link-cta {
  background: var(--indigo);
  color: #fff;
}

/* --- the current page ------------------------------------------------------
   Marked at build time with aria-current, so it is right in the first frame
   and means the same to a screen reader. Its look is Astryx's selected ghost
   (a 10% indigo tint, indigo label) and lives in button.css with every other
   button state. */

/* A tint is invisible in forced colours, so restate the state as a border. */
@media (forced-colors: active) {
  .nv-link[aria-current="page"] { border: 2px solid; }
}


@media (max-width: 720px) {
  .nv-nav { padding: calc(var(--u) * 2); gap: calc(var(--u) * 2); }
  .nv-links { gap: calc(var(--u) * 0.5); }
  .nv-link { font-size: 13px; padding: calc(var(--u) * 1.25) calc(var(--u) * 1.5); }
}

/* ==== parts/footer.css ==== */
/* ===========================================================================
   footer.css   prefix: ft-
   A skyline of gradient columns rising out of the PAPER. Same oatmeal ground
   as every other section, so the footer is the page continuing rather than a
   dark slab bolted to the bottom.

   THE BARS FADE INTO THE GROUND AT THEIR TOPS. That is what makes this work on
   a light page: each bar starts as oatmeal, indistinguishable from the paper,
   and saturates downward through near white, lilac, periwinkle and into the
   indigo base. There is no bar edge anywhere near the top, so the skyline has
   no hard silhouette against the page, it emerges.

   That arrangement is also what pays for the type. Copy sits in the UPPER half
   where the bars are still paper coloured, so ink keeps its contrast without a
   single scrim, box or blur. An earlier pass put white type on a dark ground
   with a wash over the bars, which crushed the gradient to purple grey; a
   second pass added per block blurred floors, which drew visible rectangles
   across the skyline. Neither is needed if the bars are light where the words
   are.

   ALIVE THE WAY THE CARDS ARE. No bar moves, no gradient slides. The single
   sheet behind the silhouette runs the same displacement filter as the figure
   cards, so its colour evolves slowly and continuously in place. Subtle by
   construction rather than by tuning a speed down.
   =========================================================================== */

.ft {
  position: relative;
  isolation: isolate;    /* the bars sit at z-index 0. Without this they paint
                            behind body's oatmeal and the footer renders empty,
                            which has cost this project a render cycle before */
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
  /* 96px above the heading, like any section break. It was 320px plus an
     80px margin, which left a 448px hole under the FAQ. The skyline is pinned
     to the bottom edge at a fixed height, so the top padding never changed
     where the bars sit behind the links; it only made the footer taller. */
  padding-block: calc(var(--u) * 12) calc(var(--u) * 12);
  margin-top: 0;
}

/* --- the skyline ----------------------------------------------------------
   ONE SURFACE, WITH THE SILHOUETTE CUT OUT OF IT.

   The previous build gave every bar its own gradient and its own animation, so
   22 rectangles each scanned up and down on their own clock. That is what read
   as sloppy: it is 22 effects, not one material, and nothing in the page above
   behaves that way.

   Now there is a single gradient sheet across the whole band, carrying the
   SAME filter as the figure cards, so the footer is made of the same stuff as
   the boxes above it. The bars are not painted at all: each rectangle is a
   block of PAPER hanging from the top of the band, and what you read as a bar
   is simply where no paper is covering the sheet. The silhouette is therefore
   perfectly crisp while the colour underneath drifts, because the crisp thing
   and the moving thing are different layers.

   Nothing translates and nothing scans. The only motion is the filter's own
   turbulence evolving, which is slow, continuous, and identical in character
   to the card surfaces.
   -------------------------------------------------------------------------- */

.ft-sky {
  position: absolute;
  inset: auto 0 0 0;
  height: 640px;
  display: flex;
  align-items: flex-start;   /* the paper blocks hang from the top */
  overflow: hidden;          /* the filter needs somewhere to bleed */
  z-index: 0;
  pointer-events: none;
}

/* the sheet: one gradient, one filter, the same material as .fg-copy */
.ft-sky::before {
  content: '';
  position: absolute;
  inset: -25%;
  z-index: 0;
  /* Built like a CARD, not like a rainbow: a quiet ramp with two soft blooms
     sitting on it, which is exactly what .fg-copy does. The previous version
     was a ten stop spectrum including a hard cyan band, and a spectrum with
     hard bands is what turned into a visible wave the moment it was displaced.
     Warm notes are blooms, so they read as light on a surface rather than as
     stripes in a flag. */
  background-image:
    radial-gradient(36% 15% at 72% 11%, rgba(250, 196, 158, 0.9) 0%, transparent 100%),
    radial-gradient(30% 13% at 26% 17%, rgba(244, 172, 204, 0.72) 0%, transparent 100%),
    linear-gradient(180deg,
      #EDE9DE 0%,
      #F3DCCA 10%,
      #EFC7B0 20%,
      #E7C2CE 32%,
      #D2CBEE 44%,
      #B3B6F0 54%,
      #8F97EE 63%,
      #6470EC 73%,
      #4553E8 84%,
      #2E3ACB 100%);
  filter: url(#oj-footer);
}

/* THE COLUMN EDGES. With one continuous sheet behind the silhouette there is
   nothing separating one rectangle from the next below the bar line, so the
   skyline reads as a single blob with a stepped top. A one pixel paper rule on
   every column boundary puts the verticals back, drawn over the sheet at full
   band height so the edge runs the whole way down. Aligned to 100%/22 because
   that is exactly where the flex items divide. */
.ft-sky::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: repeating-linear-gradient(90deg,
    transparent 0,
    transparent calc(100% / 22 - 1px),
    var(--paper) calc(100% / 22 - 1px),
    var(--paper) calc(100% / 22));
}

/* each rectangle is PAPER, not colour: it masks the sheet above the bar line */
.ft-sky i {
  position: relative;
  z-index: 1;
  flex: 1 1 0;
  /* half a pixel of overlap each side. 22 flex items at fractional widths land
     on subpixel boundaries and the sheet shows through as a hairline between
     every pair, which reads as gaps between the rectangles. */
  margin-inline: -0.5px;
  height: calc(100% - var(--h, 60%));
  background: var(--paper);
}

/* GRAIN over the whole footer. On a light ground this has to MULTIPLY, not
   overlay: overlay against a pale surface returns almost nothing, which is the
   same arithmetic that made the card grain invisible the first time it was
   built here. */
.ft-grain {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  opacity: 0.42; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'>\
<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='2'/>\
<feColorMatrix type='saturate' values='0'/></filter>\
<rect width='140' height='140' filter='url(%23n)' opacity='.5'/></svg>");
}

/* the faintest lift under the copy half, so a bar that breathes upward can
   never quite reach the type. No box, no blur, no edge. */
.ft::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg,
    var(--paper) 0%,
    var(--paper) 44%,
    color-mix(in srgb, var(--paper) 82%, transparent) 56%,
    color-mix(in srgb, var(--paper) 30%, transparent) 68%,
    transparent 80%);
}

/* --- content -------------------------------------------------------------- */

.ft-in { position: relative; z-index: 2; }

.ft-h {
  font-family: var(--sans); font-weight: 400;
  font-size: clamp(28px, 3.6vw, 50px);
  line-height: 1.06; letter-spacing: -0.02em;
  margin: 0 0 calc(var(--u) * 7); color: var(--ink);
}

.ft-cols {
  display: grid; grid-template-columns: 1fr; gap: calc(var(--u) * 5);
  padding-bottom: calc(var(--u) * 7);
}
@media (min-width: 760px) { .ft-cols { grid-template-columns: repeat(3, 1fr); gap: calc(var(--u) * 6); } }

.ft-col { display: flex; flex-direction: column; gap: calc(var(--u) * 1.5); align-items: flex-start; }
.ft-lab {
  font: 500 10px/1 var(--mono); letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: var(--u);
}
.ft-col a {
  font-family: var(--sans); font-size: 15px; color: var(--ink);
  text-decoration: none; transition: color 160ms ease;
}
.ft-col a:focus-visible { color: var(--indigo); text-decoration: underline; }
@media (hover: hover) and (pointer: fine) {
  .ft-col a:hover { color: var(--indigo); text-decoration: underline; }
}

/* --- Elsewhere: a row per platform, its mark in a tile on the right --------
   The tile is a small 8px box like every other box on the site, in the card
   ground with a hairline. Pressing pushes the tile in (the site's press),
   then the link goes (press.js delays the navigation one beat so the push is
   seen). The word keeps the column's type; nothing underlines. */
.ft-col { align-items: stretch; }
.ft-col .ft-lab { align-self: flex-start; }
.ft-el {
  display: flex;
  align-items: center;
  justify-content: space-between;   /* the word left, the tile at the right edge */
  gap: calc(var(--u) * 1.5);
  width: 100%;
  min-height: 36px;
  text-decoration: none;
  color: var(--ink);
  -webkit-tap-highlight-color: transparent;
}
.ft-col .ft-el:hover, .ft-col .ft-el:focus-visible, .pg-links .ft-el:hover { text-decoration: none; color: var(--ink); }
.ft-el-t span { display: block; margin-top: 2px; font: 500 10px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3); }
/* on /about the list keeps its hairlines between rows; the word stays the
   list's 15px sans (the list styles every span as a mono handle, so the
   label span is restated) */
.pg-links .ft-el { align-items: center; padding: calc(var(--u) * 1.25) 0; }
.pg-links .ft-el .ft-el-t { font: 400 15px/1.3 var(--sans); letter-spacing: 0; text-transform: none; color: var(--ink); white-space: normal; }
.pg-links .ft-el .ft-el-t span { font: 500 10px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3); }
.ft-el-i {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--rule);
  border-radius: var(--r-l);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-1);
  transition: background-image 125ms cubic-bezier(0.24, 1, 0.4, 1),
              box-shadow 125ms cubic-bezier(0.24, 1, 0.4, 1),
              transform 125ms cubic-bezier(0.24, 1, 0.4, 1);
}
.ft-el-i svg { width: 18px; height: 18px; }
.ft-el-i svg:not([fill="none"]) { fill: currentColor; }   /* the Instagram mark is strokes */
@media (hover: hover) and (pointer: fine) {
  .ft-el:hover .ft-el-i { background-image: linear-gradient(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.05)); }
}
@media not (pointer: coarse) {
  .ft-el:active .ft-el-i {
    background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
    box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
    transform: scale(0.98);
  }
}
.ft-el[data-press="on"] .ft-el-i,
.ft-el:focus-visible .ft-el-i {
  background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
  box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
  transform: scale(0.98);
}
.ft-el:focus { outline: none; }
@media (forced-colors: active) { .ft-el:focus-visible .ft-el-i { outline: 2px solid CanvasText; } }
/* the footer's phone layout puts the words on paper; the tile reads there as is */
.ft-p { margin: 0; font-family: var(--sans); font-size: 14px; line-height: 1.6;
        color: var(--ink-2); max-width: 34ch; }

.ft-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: calc(var(--u) * 3); flex-wrap: wrap;
  padding-top: calc(var(--u) * 4);
}
/* the button sits at the right end of the bottom row, the fine print at the
   left (Jake); when the row wraps on a phone the button takes its own line,
   still at the right */
.ft-foot .ft-fine { order: 1; }
.ft-foot .ft-pill { order: 2; margin-left: auto; }

/* The site's button, not the reference's. Every action on this site is 10px
   radius, 14px sans at weight 500, padding 12 by 18, indigo for the primary
   one. The reference used a 999px outlined pill; copying it here would have
   put a second button language on the page for the sake of one footer. */
.ft-pill {
  display: inline-flex; align-items: center; gap: calc(var(--u) * 0.75);
  font: 500 14px/1 var(--sans);
  color: #fff; text-decoration: none;
  background: var(--indigo); border: 0;
  border-radius: var(--r-s);
  padding: calc(var(--u) * 1.5) calc(var(--u) * 2.25);
  white-space: nowrap;
  transition: background-color 160ms ease;
}

.ft-fine { font: 500 10px/1 var(--mono); letter-spacing: 0.2em;
           text-transform: uppercase; color: var(--ink-3); }

/* The filter animates through SMIL inside the SVG, which CSS cannot reach, so
   reduced motion is honoured on the SVG element by the page script, the same
   way the card surfaces are. There is no CSS animation here to disable. */

@media (max-width: 720px) {
  .ft { padding-block: calc(var(--u) * 9) calc(var(--u) * 8); }
  .ft-sky, .ft::after { height: 460px; }
  /* 22 columns on a phone is stripes, not a skyline */
  .ft-sky i:nth-child(2n) { display: none; }
}

/* The Status column is desktop only (Jake: off the phone). */
@media (max-width: 899px) {
  .ft-col-status { display: none; }
}
/* On a phone the Elsewhere column runs the full width, so its tiles stand at
   the footer's right edge, in line with the button below. */
@media (max-width: 759px) {
  .ft-col-else { grid-column: 1 / -1; }
}

/* On a phone the foot is ONE row (Jake, Oct 11): the three marks on the left
   and Book a call on the right. The Elsewhere column and the sign-off give
   way to it; desktop keeps both. */
.ft-marks { display: none; }
@media (max-width: 759px) {
  /* only where the page carries the new row: a page built before it keeps
     its Elsewhere column rather than losing its links */
  .ft:has(.ft-marks) .ft-col-else { display: none; }
  .ft:has(.ft-marks) .ft-foot .ft-fine { display: none; }
  .ft-foot { align-items: center; }
  .ft-foot .ft-marks { display: flex; gap: 10px; order: 1; }
  .ft-foot .ft-mark { width: auto; min-height: 0; }
}

/* ==== parts/boxed.css ==== */
/* ===========================================================================
   boxed.css   the alternative treatment
   Loaded only by /site/boxed.html, on top of the same fragments.

   THREE BUGS RAN FOR MANY ROUNDS. ALL ARE NAMED HERE SO THEY DO NOT RETURN.

   0. THE STREAKS STOPPED HALFWAY. The reveal reached zero at card x 0.37 and
      the streaks lived only inside it, so they ended in mid air. A streak that
      crosses the copy has to be under the contrast ceiling, so each streak is
      now a mid-tone BODY that spans the whole card unmasked plus a bright HEAD
      masked to the right, sharing one height. One streak, brightening.

   1. THE MASK WAS A RADIAL GRADIENT. A radial mask is a circle. Every build
      that "put a circle on the right" did so because the reveal itself was
      circular; no amount of retuning the shapes inside it could remove a
      circle that was being drawn by the mask. The reveal is now LINEAR, a long
      soft fade from right to left, so it has no shape of its own at all.

   2. THE BASE WENT NEARLY NAVY. Chasing hue variation in the copy half, the
      base was dropped to #141C52. The reference is a BRIGHT saturated blue and
      white type does not need it dark: #3B49E6 measures L 0.114, which is
      6.4 to 1, far more than the 4.5 required. Dark was never the price of
      legibility here.

   WHAT THE REFERENCE ACTUALLY IS
   A bright blue field with long, soft, near horizontal light streaks flowing
   across it and feathering out toward the middle. So:

     - the streaks are EXTREMELY elongated, 55% by 4% of their tile, which is
       about 1.7 card widths long and 0.13 card heights thick. Anything close
       to round reads as a blob; this reads as flow.
     - they drift UP AND DOWN rather than side to side, on long alternating
       periods, which is the motion in the reference.
     - the reveal fades left over 30 percent of the card, so a streak thins out
       gradually instead of ending at a boundary.

   CONTRAST. White copy needs L <= 0.183 behind it and the glyphs reach x 0.68.
   The base at L 0.114 leaves real headroom, and the linear reveal is solved so
   the streaks are down to a few percent of their strength by the time they
   reach the type.
   =========================================================================== */


/* --- the card ------------------------------------------------------------ */

.fg-copy {
  position: relative;
  isolation: isolate;
  border-radius: var(--r-l);
  padding: calc(var(--u) * 5);
  overflow: clip;   /* clips the streaks like hidden did, without becoming a scroll container */
  background: #3B49E6;
  color: #fff;
  box-shadow: var(--shadow-card);
}

/* Window controls: the same red / yellow / green page dots on every card.
   They sit above the animated field and never accept pointer input. */
.fg-page-dots {
  position: absolute;
  top: calc(var(--u) * 3);
  left: calc(var(--u) * 3);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--u);
  pointer-events: none;
}

.fg-page-dot {
  display: block;
  width: 11px;
  height: 11px;
  border: 1px solid rgba(0, 0, 0, 0.18);
  border-radius: 50%;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.32),
              0 1px 2px rgba(15, 20, 80, 0.22);
}

.fg-page-dot-red    { background: #ff5f57; }
.fg-page-dot-yellow { background: #febc2e; }
.fg-page-dot-green  { background: #28c840; }

/* THE PALETTE. Shared by all three cards, defined once, on purpose. */
.fg-copy::before,
.fg-copy::after {
  content: '';
  position: absolute;
  inset: -55%;
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;

  /* bodies: cross the whole card, all under the L 0.183 ceiling */
  --fg-m1: rgba( 83,  97, 236, 0.85);  /* L 0.164   4.9 to 1 */
  --fg-m2: rgba( 68,  99, 228, 0.85);  /* L 0.158   5.1 to 1 */
  --fg-m3: rgba( 96,  88, 224, 0.85);  /* L 0.148   5.3 to 1 */
  /* heads: too bright to cross the copy, so masked to the right */
  --fg-s1: rgba(198, 194, 246, 0.72);  /* lavender   */
  --fg-s2: rgba(176, 206, 244, 0.66);  /* pale blue  */
  --fg-s3: rgba(242, 208, 194, 0.62);  /* pale peach */
  --fg-s4: rgba(236, 234, 250, 0.70);  /* near white */

  filter: var(--fg-liquid, url(#oj-liquid-a));
}

/* the bright blue ground, with two very wide soft swells in it so the blue
   itself is never flat */
.fg-copy::before {
  background-image:
    radial-gradient(78% 4%   at 50% 50%, var(--fg-m1) 0%, var(--fg-m1) 10%, transparent 100%),
    radial-gradient(72% 5%   at 50% 50%, var(--fg-m2) 0%, var(--fg-m2) 9%,  transparent 100%),
    radial-gradient(75% 3.5% at 50% 50%, var(--fg-m3) 0%, var(--fg-m3) 10%, transparent 100%),
    radial-gradient(60% 22%  at 50% 50%,
      rgba(90, 108, 244, 0.45) 0%, rgba(90, 108, 244, 0.45) 22%, transparent 100%),
    linear-gradient(100deg, #2F3CD8 0%, #3B49E6 52%, #4655EE 100%);
  background-size: 150% 150%, 150% 150%, 150% 150%, 150% 150%, 100% 100%;
  animation: var(--fg-tide) var(--fg-tide-dur) ease-in-out infinite alternate
             var(--fg-tide-delay, 0s);
}

/* the light streaks. LINEAR reveal, not radial: a radial reveal is a circle. */
.fg-copy::after {
  background-image:
    radial-gradient(55% 4%  at 50% 50%, var(--fg-s1) 0%, var(--fg-s1) 12%, transparent 100%),
    radial-gradient(48% 5%  at 50% 50%, var(--fg-s2) 0%, var(--fg-s2) 10%, transparent 100%),
    radial-gradient(52% 3.5% at 50% 50%, var(--fg-s3) 0%, var(--fg-s3) 12%, transparent 100%),
    radial-gradient(44% 4.5% at 50% 50%, var(--fg-s4) 0%, var(--fg-s4) 10%, transparent 100%);
  background-size: 150% 150%;
  -webkit-mask-image: linear-gradient(to right,
    rgba(0,0,0,0) 44%, rgba(0,0,0,0.05) 52%, rgba(0,0,0,0.18) 58%,
    rgba(0,0,0,0.44) 64%, rgba(0,0,0,0.74) 70%, #000 77%, #000 100%);
  mask-image: linear-gradient(to right,
    rgba(0,0,0,0) 44%, rgba(0,0,0,0.05) 52%, rgba(0,0,0,0.18) 58%,
    rgba(0,0,0,0.44) 64%, rgba(0,0,0,0.74) 70%, #000 77%, #000 100%);
  animation: var(--fg-flow) var(--fg-flow-dur) ease-in-out infinite alternate
             var(--fg-flow-delay, 0s);
}

/* Paths in CARD coordinates, converted once: at inset -55% with a 150 percent
   tile, a centre at card p needs background-position (1.025 - p) / 0.0105.
   The streaks move UP AND DOWN: X barely changes, Y sweeps. */
@keyframes fg-flow-a {
  0%   { background-position: 26% 74%, 31% 45%, 21% 29%, 29%  7%; }
  50%  { background-position: 26% 60%, 31% 58%, 21% 16%, 29% 20%; }
  100% { background-position: 26% 55%, 31% 64%, 21% 10%, 29% 23%; }
}
@keyframes fg-flow-b {
  0%   { background-position: 29% 62%, 24% 33%, 33% 18%, 26% 80%; }
  50%  { background-position: 29% 48%, 24% 47%, 33%  8%, 26% 66%; }
  100% { background-position: 29% 41%, 24% 54%, 33%  4%, 26% 59%; }
}
@keyframes fg-flow-c {
  0%   { background-position: 23% 36%, 34% 68%, 27% 12%, 31% 52%; }
  50%  { background-position: 23% 51%, 34% 54%, 27% 25%, 31% 38%; }
  100% { background-position: 23% 58%, 34% 47%, 27% 31%, 31% 31%; }
}

/* the two blue swells drift the other way, so the ground is never still */
@keyframes fg-tide-a {
  0%   { background-position: 50% 74%, 50% 45%, 50% 29%, 70% 22%, 50% 50%; }
  50%  { background-position: 50% 60%, 50% 58%, 50% 16%, 34% 55%, 50% 50%; }
  100% { background-position: 50% 55%, 50% 64%, 50% 10%, 58% 78%, 50% 50%; }
}
@keyframes fg-tide-b {
  0%   { background-position: 50% 62%, 50% 33%, 50% 18%, 36% 64%, 50% 50%; }
  50%  { background-position: 50% 48%, 50% 47%, 50%  8%, 68% 30%, 50% 50%; }
  100% { background-position: 50% 41%, 50% 54%, 50%  4%, 44% 14%, 50% 50%; }
}
@keyframes fg-tide-c {
  0%   { background-position: 50% 36%, 50% 68%, 50% 12%, 56% 40%, 50% 50%; }
  50%  { background-position: 50% 51%, 50% 54%, 50% 25%, 26% 74%, 50% 50%; }
  100% { background-position: 50% 58%, 50% 47%, 50% 31%, 64% 20%, 50% 50%; }
}

/* PER CARD: MOVEMENT ONLY. No colour below this line. */
.fg-fig-a .fg-copy::before,
.fg-fig-a .fg-copy::after {
  --fg-liquid: url(#oj-liquid-a);
  --fg-tide: fg-tide-a;  --fg-tide-dur: 61s;
  --fg-flow: fg-flow-a;  --fg-flow-dur: 37s;
}
.fg-fig-b .fg-copy::before,
.fg-fig-b .fg-copy::after {
  --fg-liquid: url(#oj-liquid-b);
  --fg-tide: fg-tide-b;  --fg-tide-dur: 73s;  --fg-tide-delay: -29s;
  --fg-flow: fg-flow-b;  --fg-flow-dur: 47s;  --fg-flow-delay: -19s;
}
.fg-fig-c .fg-copy::before,
.fg-fig-c .fg-copy::after {
  --fg-liquid: url(#oj-liquid-c);
  --fg-tide: fg-tide-c;  --fg-tide-dur: 53s;  --fg-tide-delay: -41s;
  --fg-flow: fg-flow-c;  --fg-flow-dur: 31s;  --fg-flow-delay: -23s;
}

@media (prefers-reduced-motion: reduce) {
  .fg-copy::before,
  .fg-copy::after { animation: none; }
}


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

/* The type on the card is WHITE, full stop. Stated explicitly rather than left
   to inheritance: an earlier edit removed this block and the heading fell back
   to the dark ink token on a saturated field, which measures 2.60. */
.fg-copy,
.fg-copy .fg-h,
.fg-copy .fg-num,
.fg-copy .fg-sys { color: #fff; }

.fg-copy .fg-p { color: rgba(255, 255, 255, 0.94); }

/* Capped as a share of the CARD, not in ch: a ch cap is relative to font size,
   so 15ch on the heading resolved to 88% of the card and freed nothing.
   Measured, the glyphs stop at x = 0.68, which is what the reveal is solved
   against. */
.fg-copy .fg-h { max-width: 70%; }
.fg-copy .fg-p { max-width: 72%; }


/* --- action -------------------------------------------------------------- */

.fg-copy .fg-act-link,
.fg-copy .fg-act-btn {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 0.75);
  border: 0;
  border-bottom: 0;
  border-radius: var(--r-s);
  background: #fff;
  color: #2438C8;
  font: 500 14px/1 var(--sans);
  padding: calc(var(--u) * 1.5) calc(var(--u) * 2.25);
  white-space: nowrap;
  transition: background-color 160ms ease, color 160ms ease;
}

/* The pill used to run the full inner width of the card with its label hugged
   left, so "Join Opus Lab" trailed ~200px of empty white. It hugs its own text
   now, which is the whole of direction A's geometry change. */
.fg-copy .fg-act-link,
.fg-copy .fg-act-btn { align-self: flex-start; width: auto; }

/* The mark is an ASCII arrow in JetBrains Mono, not the Endless glyph: it reads
   as the terminal trace the rest of the site is built around. Endless does have
   its own U+2192, this is a choice rather than a workaround. */
.fg-copy .fg-act-x {
  font: 500 13px/1 var(--mono);
  letter-spacing: -0.06em;
  padding-left: 2px;
}

/* Hidden at rest, slides in on hover. The span keeps its box at all times, so
   revealing it never changes the pill's width and nothing reflows.
   Gated on (hover: hover): on a touch screen there is no hover, so the mark
   would never appear at all and the pill would just read as a label. */
@media (hover: hover) {
  .fg-copy .fg-act-x {
    opacity: 0;
    transform: translateX(-5px);
    transition: opacity 160ms ease,
                transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1);
  }
  .fg-copy .fg-act:hover .fg-act-x,
  .fg-copy .fg-act:focus-visible .fg-act-x { opacity: 1; transform: none; }
}

/* figures.css slides the mark 4px right on hover; that fights the reveal. */
@media (prefers-reduced-motion: reduce) {
  .fg-copy .fg-act-x { transition: none; transform: none; }
}


/* --- layout -------------------------------------------------------------- */

@media (min-width: 900px) {
  .fg-body { align-items: stretch; grid-auto-rows: minmax(464px, auto); }
  .fg-figure { display: flex; align-items: center; }
  .fg-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: calc(var(--u) * 6);
  }
}

/* The ambient bloom is off here. */
.gl::before { display: none; }

/* ==== parts/booking.css ==== */
/* ===========================================================================
   booking.css
   The booking dialog: up to two questions name the call, then a calendar of
   open times, the visitor's details, a confirm step (the only place a price
   appears) and Stripe. Booked on opusjake.ai itself, see booking.js and
   src/lib/booking. Same window register as the library dialog.
   =========================================================================== */

.bk { position: fixed; inset: 0; z-index: 210; display: flex;
      align-items: center; justify-content: center; padding: calc(var(--u) * 3); }
.bk[hidden] { display: none; }

/* Open and close. A dialog that appears with no transition reads as a glitch,
   one that takes its time reads as slow: in over 200ms from 96% and clear on a
   strong ease-out, out in 150ms, because leaving is faster than arriving. The
   origin stays centred: a modal belongs to the viewport, not to its button.
   display is held for the exit with allow-discrete; an engine without it just
   closes instantly, which is how it closed before. */
.bk { transition: display 150ms allow-discrete; }
.bk[hidden] { pointer-events: none; }
.bk-scrim { transition: opacity 200ms ease; }
.bk-card { transition: opacity 200ms cubic-bezier(0.23, 1, 0.32, 1),
                      transform 200ms cubic-bezier(0.23, 1, 0.32, 1); }
@starting-style {
  .bk-scrim { opacity: 0; }
  .bk-card { opacity: 0; transform: scale(0.96); }
}
.bk[hidden] .bk-scrim { opacity: 0; transition-duration: 150ms; }
.bk[hidden] .bk-card { opacity: 0; transform: scale(0.98);
                     transition-duration: 150ms; transition-timing-function: ease-out; }

.bk-scrim { position: absolute; inset: 0;
            background: color-mix(in srgb, var(--ink) 42%, transparent);
            backdrop-filter: blur(3px); }

.bk-card {
  position: relative;
  width: min(640px, 100%);
  max-height: min(88vh, 820px);
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-l);
  box-shadow: var(--shadow-float);
  padding: calc(var(--u) * 4) calc(var(--u) * 4) calc(var(--u) * 3);
}

.bk-head { display: flex; align-items: center; justify-content: space-between;
           gap: calc(var(--u) * 2); padding-bottom: calc(var(--u) * 3); }
.bk-eyebrow { font: 500 11px/1 var(--mono); letter-spacing: 0.18em;
              text-transform: uppercase; color: var(--ink-2); }
/* the header is the close button alone now; it keeps to the right */
.bk-head .bk-close { margin-left: auto; }
.bk-close {
  flex: none;
  font: 500 11px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--indigo); background: none;
  border: 1px solid var(--indigo); border-radius: var(--r-s);
  padding: calc(var(--u) * 1.25) calc(var(--u) * 2); cursor: pointer;
  transition: color 140ms ease, background-color 140ms ease;
}


.bk-step[hidden] { display: none; }
.bk-q { font: 400 clamp(24px, 3.2vw, 32px)/1.15 var(--sans); color: var(--ink);
        margin: 0 0 calc(var(--u) * 1); letter-spacing: -0.01em; }
.bk-sub { font: 400 15px/1.5 var(--sans); color: var(--ink-2); margin: 0 0 calc(var(--u) * 3); }

.bk-opts { display: grid; gap: var(--u); margin: 0; padding: 0; list-style: none; }
.bk-opt {
  display: flex; align-items: center; justify-content: space-between; gap: calc(var(--u) * 2);
  width: 100%; text-align: left;
  font: 400 16px/1.35 var(--sans); color: var(--ink);
  background: var(--card); border: 1px solid var(--rule-2); border-radius: var(--r-m);
  padding: calc(var(--u) * 2) calc(var(--u) * 2.5); cursor: pointer;
  transition: border-color 140ms ease, background-color 140ms ease;
}
.bk-opt small { font: 400 12px/1.4 var(--mono); color: var(--ink-3); display: block; margin-top: 2px; }
.bk-opt-x { font: 500 15px/1 var(--mono); white-space: nowrap; color: var(--indigo); transition: transform 160ms ease; }
.bk-opt:focus-visible { border-color: var(--indigo); background: var(--paper); outline: none; }
@media (hover: hover) and (pointer: fine) {
  .bk-opt:hover { border-color: var(--indigo); background: var(--paper); }
}
@media (hover: hover) and (pointer: fine) {
  .bk-opt:hover .bk-opt-x { transform: translateX(4px); }
}

/* --- the result card ------------------------------------------------------ */
.bk-res { border: 1px solid var(--rule-2); border-radius: var(--r-m); background: var(--card);
          padding: calc(var(--u) * 3); }
.bk-res-row { display: flex; flex-wrap: wrap; gap: var(--u); margin-bottom: calc(var(--u) * 2); }
.bk-tag {
  /* Astryx Token (sm): 20px, 6px corner, 8px pad, 12px/20 at 500, no outline */
  display: inline-flex; align-items: center; height: 20px; padding: 0 8px;
  border-radius: var(--r-xs, 6px); font: 500 12px/20px var(--sans); letter-spacing: normal;
}
.bk-tag-time { background: #4b4aef33; color: var(--indigo); }
.bk-tag-price { background: #61c45433; color: #2f7a27; }
.bk-tag-free { background: #f4bf4f33; color: #8a6300; }
.bk-res-h { font: 400 22px/1.2 var(--sans); color: var(--ink); margin: 0 0 var(--u); }
.bk-res-p { font: 400 15px/1.5 var(--sans); color: var(--ink-2); margin: 0 0 calc(var(--u) * 3); }
.bk-go {
  display: inline-flex; align-items: center; gap: var(--u);
  font: 500 12px/1 var(--mono); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--paper); background: var(--indigo); border: 1px solid var(--indigo);
  border-radius: var(--r-s); padding: calc(var(--u) * 1.75) calc(var(--u) * 3);
  text-decoration: none; cursor: pointer;
  transition: color 140ms ease, background-color 140ms ease;
}
.bk-note { font: 400 12px/1.5 var(--mono); color: var(--ink-3); margin: calc(var(--u) * 2) 0 0; }

.bk-foot { display: flex; justify-content: space-between; align-items: center; gap: var(--u);
           margin-top: calc(var(--u) * 3); padding-top: calc(var(--u) * 2);
           border-top: 1px solid var(--rule); }
.bk-back, .bk-alt {
  font: 500 11px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-2); background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: none;
}
.bk-back:focus-visible, .bk-alt:focus-visible { color: var(--indigo); }
@media (hover: hover) and (pointer: fine) {
  .bk-back:hover, .bk-alt:hover { color: var(--indigo); }
}
.bk-back[hidden] { display: none; }

@media (max-width: 640px) {
  .bk { padding: calc(var(--u) * 1.5); align-items: flex-end; }
  .bk-card { padding: calc(var(--u) * 3) calc(var(--u) * 2.5) calc(var(--u) * 2.5); border-radius: var(--r-l); }
}

/* --- the booker: cal.diy's booking page, in the dialog ------------------------
   Matched to the Bare reskin of the cal.diy build (cal.opusjake.ai, run
   locally from ~/Claude Code/cal-diy), measured off its rendered page:
   squared corners, hairline dividers, Endless for words and JetBrains Mono for
   anything the machine says (dates, times).

     card       the dialog widens to 1060px, card ground, 1px hairline
     columns    event 280 | month | times 260 (one column on a phone)
     day        a square tile, mono 13/500; open = the muted tile,
                chosen = indigo with a paper number, today = an indigo dot
     weekday    12/16 500, uppercase, 0.1em tracking
     time       a full-width outlined box, 36px, mono 13/500
     field      32px, card ground, 1px #CFC8B8, squared, label 14/500 above
     confirm    indigo, paper label, 32px, squared; Back is quiet text
*/
.bk-card.bk-wide {
  width: min(1060px, 100%);
  padding: 0;
  border-radius: 0;
  background: var(--card);
  border-color: color-mix(in srgb, var(--ink) 12%, transparent);
}
.bk-wide .bk-head { padding: 14px 24px; border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, transparent); }
.bk-wide .bk-step { margin: 0; }
/* cal.diy's corners are square, so the dialog's own buttons follow in here */
.bk-wide .bk .bk-close, .bk-card.bk-wide .bk-close { border-radius: 0; }
.bk-wide .bk-foot { margin: 0; padding: 12px 24px; border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent); }

.bkr { display: grid; grid-template-columns: 280px minmax(0, 1fr) 260px; }
.bkr.bkr-form-mode { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr); }
.bkr > * { min-width: 0; padding: 24px; }
.bkr > * + * { border-left: 1px solid color-mix(in srgb, var(--ink) 8%, transparent); }

/* the event */
.bkr-avatar {
  display: grid; place-items: center;
  width: 24px; height: 24px; border-radius: 50%; overflow: hidden;
  background: var(--muted); color: var(--ink-2); font: 500 10px/1 var(--mono);
}
.bkr-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bkr-host { margin: 10px 0 0; font: 500 14px/20px var(--sans); color: var(--ink-2); }
.bkr-title { margin: 4px 0 12px; font: 600 20px/28px var(--sans); color: var(--ink); outline: none; }
.bkr-desc { margin: 0 0 24px; font: 400 15px/24px var(--sans); color: var(--ink); }
.bkr-meta { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.bkr-meta li { display: flex; gap: 10px; align-items: flex-start; font: 400 15px/20px var(--sans); color: var(--ink); }
.bkr-meta svg { width: 16px; height: 16px; flex: none; margin-top: 2px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }

/* the month */
.bkr-cal-bar { display: flex; align-items: center; gap: 4px; margin-bottom: 20px; }
.bkr-month { flex: 1; font: 400 16px/24px var(--sans); color: var(--ink-2); }
.bkr-month b { font-weight: 600; color: var(--ink); }
.bkr-nav {
  display: grid; place-items: center; width: 32px; height: 32px; padding: 0;
  border: 0; border-radius: 0; background: transparent; color: var(--ink); cursor: pointer;
}
.bkr-nav svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.bkr-nav:disabled { color: var(--ink-3); cursor: default; }
.bkr-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 3px; }
.bkr-wd { padding-bottom: 12px; text-align: center; font: 500 12px/16px var(--sans); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink); }
.bkr-day {
  position: relative;
  aspect-ratio: 1;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font: 500 13px/1 var(--mono);
  cursor: default;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 125ms ease, border-color 125ms ease, color 125ms ease, box-shadow 125ms ease;
}
.bkr-day-open { background: var(--muted); cursor: pointer; }
.bkr-day-on { background: var(--indigo); color: var(--card); }
.bkr-today::after {
  content: ""; position: absolute; left: 50%; bottom: 16%;
  width: 4px; height: 4px; margin-left: -2px; border-radius: 50%; background: var(--indigo);
}
.bkr-day-on.bkr-today::after { background: var(--card); }
.bkr-msg { margin: 12px 0 0; min-height: 20px; font: 400 13px/20px var(--sans); color: var(--ink-2); }
.bkr-msg:empty { margin: 0; min-height: 0; }

/* the day's times */
.bkr-times-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 16px; }
.bkr-dayh { font: 400 16px/24px var(--sans); color: var(--ink-2); }
.bkr-dayh b { font-weight: 600; color: var(--ink); }
.bkr-seg { display: inline-flex; padding: 3px; gap: 2px; border-radius: 8px; background: var(--muted); }
.bkr-seg-b {
  height: 26px; padding: 0 8px; border: 0; border-radius: 6px; background: transparent;
  font: 500 13px/1 var(--sans); color: var(--ink-2); cursor: pointer;
}
.bkr-seg-b[aria-pressed="true"] { background: var(--card); color: var(--ink); box-shadow: var(--shadow-1); }
.bkr-slots { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; max-height: 420px; overflow-y: auto; }
.bkr-slot {
  position: relative;
  width: 100%; height: 36px; padding: 0 10px;
  border: 1px solid var(--rule-2); border-radius: 0;
  background: var(--card); color: var(--ink);
  font: 500 13px/1 var(--mono); letter-spacing: 0.02em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 125ms ease, background-image 125ms ease, box-shadow 125ms ease;
}

/* the form */
.bkr-form { display: grid; gap: 16px; align-content: start; }
.bkr-field { display: grid; gap: 8px; }
.bkr-label { font: 500 14px/20px var(--sans); color: var(--ink); }
.bkr-input {
  width: 100%; height: 32px; padding: 0 12px;
  border: 1px solid var(--rule-2); border-radius: 0;
  background: var(--card); color: var(--ink);
  font: 400 14px/20px var(--sans);
  transition: border-color 125ms ease, box-shadow 125ms ease;
}
.bkr-textarea { height: 80px; padding: 8px 12px; resize: vertical; }
.bkr-input::placeholder { color: var(--ink-3); }
.bkr-input:focus { outline: none; border-color: var(--indigo); box-shadow: inset 0 0 0 1px var(--indigo); }
.bkr-fine { margin: 0; font: 400 12px/18px var(--sans); color: var(--ink-2); }
.bkr-actions { display: flex; justify-content: flex-end; gap: 8px; }
.bkr-back, .bkr-confirm {
  position: relative;
  height: 32px; padding: 0 12px; border-radius: 0; cursor: pointer;
  font: 500 14px/1 var(--sans);
  -webkit-tap-highlight-color: transparent;
  transition: background-image 125ms ease, box-shadow 125ms ease, opacity 125ms ease;
}
.bkr-back { border: 0; background: transparent; color: var(--ink-2); }
.bkr-confirm { border: 1px solid var(--indigo); background: var(--indigo); color: var(--card); }
.bkr-confirm:disabled { opacity: 0.6; cursor: default; }

/* states: hover borders like cal.diy; a press and keyboard focus push in */
@media (hover: hover) {
  .bkr-day-open:not(.bkr-day-on):hover, .bkr-slot:hover { border-color: var(--indigo); }
  .bkr-nav:not(:disabled):hover, .bkr-back:hover, .bkr-seg-b:hover { background-image: linear-gradient(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.05)); }
  .bkr-confirm:not(:disabled):hover { background-image: linear-gradient(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.08)); }
}
@media not (pointer: coarse) {
  .bkr-day-open:active, .bkr-slot:active, .bkr-nav:not(:disabled):active, .bkr-back:active, .bkr-confirm:not(:disabled):active {
    background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
    box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
  }
}
:is(.bkr-day-open, .bkr-slot, .bkr-nav, .bkr-back, .bkr-confirm, .bkr-seg-b)[data-press="on"] {
  background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
  box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
}
:is(.bkr-day, .bkr-slot, .bkr-nav, .bkr-back, .bkr-confirm, .bkr-seg-b):focus { outline: none; }
:is(.bkr-day, .bkr-slot, .bkr-nav, .bkr-back, .bkr-confirm, .bkr-seg-b):focus-visible {
  background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
  box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
}
@media (forced-colors: active) {
  :is(.bkr-day, .bkr-slot, .bkr-nav, .bkr-back, .bkr-confirm, .bkr-seg-b):focus-visible { outline: 2px solid CanvasText; }
  .bkr-day-on { border-color: CanvasText; }
}
/* a thumb needs 44px; the drawn controls stay cal.diy's size */
@media (pointer: coarse) {
  :is(.bkr-nav, .bkr-back, .bkr-confirm)::before {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: max(100%, 44px); height: max(100%, 44px); transform: translate(-50%, -50%);
  }
  .bkr-input { font-size: 16px; } /* iOS zooms into anything smaller */
}

.bk-hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.bk-err { margin: 0; font: 400 13px/20px var(--sans); color: #a50c25; }
.bk-err:empty { display: none; }
.bk-link { padding: 0; border: 0; background: none; font: inherit; color: var(--indigo); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }

/* stacked, the way cal.diy stacks on a phone: event, month, times */
@media (max-width: 899px) {
  .bkr, .bkr.bkr-form-mode { grid-template-columns: minmax(0, 1fr); }
  .bkr > * + * { border-left: 0; border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent); }
  .bkr > * { padding: 20px; }
  .bkr-slots { max-height: none; }
  .bk-wide .bk-head, .bk-wide .bk-foot { padding-inline: 20px; }
}
@media (max-width: 640px) {
  .bk:has(.bk-wide) { padding: 0; align-items: stretch; }
  .bk-card.bk-wide { max-height: none; height: 100%; border: 0; }
  .bkr-actions { flex-direction: row-reverse; justify-content: stretch; }
  .bkr-confirm { flex: 1; height: 40px; }
  .bkr-back { height: 40px; }
}

/* each step moves focus to its heading so a screen reader announces it; that
   is not a control, so it draws no ring */
.bk .bk-q:focus { outline: none; }

/* --- email: the work-with-me form ------------------------------------------
   The live site's fields, as Astryx fields: label above, a 40px field on the
   10px corner, card ground, the hairline turning indigo on focus. */
.bk-wm-step { display: block; margin-bottom: calc(var(--u) * 1.5); color: var(--ink-3); }
.bk-wm { display: grid; gap: calc(var(--u) * 2); margin-top: calc(var(--u) * 2); }
.bk-f { display: grid; gap: 6px; min-width: 0; }
.bk-f-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: calc(var(--u) * 1.5); }
.bk-f-l { font: 500 13px/20px var(--sans); color: var(--ink); }
.bk-f-i {
  width: 100%;
  height: 40px;
  padding: 0 calc(var(--u) * 1.5);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-s);
  background: var(--card);
  color: var(--ink);
  font: 400 15px/20px var(--sans);
  transition: border-color 125ms ease, box-shadow 125ms ease;
}
.bk-f-area { height: auto; min-height: 112px; padding-block: 10px; resize: vertical; }
.bk-f-sel {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236C6659' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px;
}
.bk-f-i::placeholder { color: var(--ink-3); }
.bk-f-i:focus { outline: none; border-color: var(--indigo); box-shadow: inset 0 0 0 1px var(--indigo); }
@media (forced-colors: active) { .bk-f-i:focus { outline: 2px solid CanvasText; } }
@media (pointer: coarse) { .bk-f-i { font-size: 16px; } } /* iOS zooms into anything smaller */
.bk-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.bk .bk-opt[aria-pressed="true"] { border-color: var(--indigo); }
@media (max-width: 420px) { .bk-f-row { grid-template-columns: minmax(0, 1fr); } }

/* ==== parts/pages.css ==== */
/* ===========================================================================
   pages.css   prefix: pg-
   The four pages that are not the scroll: Resources, Lab, Contact, About.

   They share the scroll's furniture rather than inventing their own. The nav,
   the tokens, the button shapes and the boxed card surface all come from the
   files that were already here; this adds only what a page of content needs
   that a scroll of figures did not: a page head, a list, a two column split.

   RHYTHM IS OWNED BY THE FRAGMENT, not by a shell. That is the rule the
   assembler was built on and these follow it, so nothing here sets padding on
   a container it does not own.
   =========================================================================== */

/* --- page head ------------------------------------------------------------ */

.pg-head {
  padding-block: calc(var(--u) * 16) calc(var(--u) * 8);
}

.pg-eyebrow {
  font: 500 12px/1 var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: block;
  margin-bottom: calc(var(--u) * 3);
}

.pg-h1 {
  font-family: var(--sans);
  font-weight: 400;
  font-size: clamp(34px, 4.4vw, 58px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  margin: 0;
  max-width: 18ch;
  text-wrap: balance;
}

.pg-lede {
  margin: calc(var(--u) * 3) 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 62ch;
}

/* --- resource list -------------------------------------------------------- */

.pg-list { list-style: none; margin: 0; padding: 0; }

.pg-item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--u) calc(var(--u) * 4);
  align-items: baseline;
  padding-block: calc(var(--u) * 4);
  border-bottom: 1px solid var(--rule-2);
}

/* a lesson's mark: the same 36px tile as the Elsewhere icons, in a column of
   its own, so a list without marks keeps its two columns */
.pg-item:has(> .pg-item-ico) { grid-template-columns: auto 1fr auto; }
.pg-item-ico {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: start;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--rule);
  border-radius: var(--r-l);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}
.pg-item-ico svg { width: 18px; height: 18px; }
/* Each mark in its own colour: the library's resource-type pairs (a pastel
   fill, the dark of the same hue for the stroke), cycling down the list
   (Jake, Oct 10). The tile keeps its depth and loses its hairline, the way
   the badges have none. */
.pg-item:nth-child(5n + 1) .pg-item-ico { color: #00458c; background: #c4ddfb; border-color: transparent; }   /* prompts blue */
.pg-item:nth-child(5n + 2) .pg-item-ico { color: #700084; background: #eccef3; border-color: transparent; }   /* skills purple */
.pg-item:nth-child(5n + 3) .pg-item-ico { color: #0c5700; background: #c5e5c0; border-color: transparent; }   /* guides green */
.pg-item:nth-child(5n + 4) .pg-item-ico { color: #584400; background: #f8da9d; border-color: transparent; }   /* playbooks amber */
.pg-item:nth-child(5n + 5) .pg-item-ico { color: #6e3500; background: #fad0b5; border-color: transparent; }   /* templates orange */
.pg-item:has(> .pg-item-ico) .pg-item-title,
.pg-item:has(> .pg-item-ico) .pg-item-blurb { grid-column: 2; }
.pg-item:has(> .pg-item-ico) .pg-item-meta { grid-column: 3; }

/* the About page's "What I work on": the same list in a half-width column,
   one step tighter */
.pg-list-work .pg-item { padding-block: calc(var(--u) * 2.5); }
.pg-list-work .pg-item:first-child { padding-top: 0; }
.pg-list-work .pg-item-title { font-size: 17px; }
.pg-list-work .pg-item-blurb { font-size: 14px; }
.pg-list-work .pg-item-ico { width: 32px; height: 32px; }
.pg-list-work .pg-item-ico svg { width: 16px; height: 16px; }

.pg-item-title {
  font-family: var(--sans);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  grid-column: 1;
}
.pg-item-title:focus-visible { color: var(--indigo); }
@media (hover: hover) and (pointer: fine) {
  .pg-item-title:hover { color: var(--indigo); }
}

.pg-item-blurb {
  grid-column: 1;
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 62ch;
}

.pg-item-meta {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  gap: calc(var(--u) * 1.5);
  align-items: center;
  font: 500 12px/1 var(--mono);
  color: var(--ink-3);
  white-space: nowrap;
}

/* the kind tag. One hue per kind, borrowed from the categorical palette the
   React build used, so a reader who saw that one is not relearning anything. */
.pg-kind {
  /* Astryx Token (sm): 20px, 6px corner, 8px pad, 12px/20 at 500, a pastel
     fill with its dark text, no outline; the theme's categorical pairs */
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 var(--u);
  border-radius: var(--r-xs, 6px);
  font: 500 12px/20px var(--sans);
  letter-spacing: normal;
  text-transform: none;
}
.pg-kind-guide    { color: #00458c; background-color: #c4ddfb; }
.pg-kind-playbook { color: #700084; background-color: #eccef3; }
.pg-kind-prompt   { color: #0c5700; background-color: #c5e5c0; }
.pg-kind-template { color: #6e3500; background-color: #fad0b5; }

.pg-new {
  color: var(--indigo);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* --- two column split ----------------------------------------------------- */

.pg-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: calc(var(--u) * 6);
  padding-block: calc(var(--u) * 8);
}

.pg-col-h {
  font-family: var(--sans);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0 0 calc(var(--u) * 3);
}

.pg-p {
  margin: 0 0 calc(var(--u) * 2);
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 62ch;
}

/* --- the chart: what it is not, what it is ---------------------------------- */

/* A real table, so the two columns' rows stay level however the lines wrap.
   The marks are the agent run's landed discs (calls.css): a soft tint disc
   carrying the darker stroke, the check in its green and the cross in its red. */
.pg-chart {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  margin: 0 0 calc(var(--u) * 8);
}
.pg-chart th, .pg-chart td {
  position: relative;
  text-align: left;
  vertical-align: top;
  padding: calc(var(--u) * 2) calc(var(--u) * 2.5) calc(var(--u) * 2) 0;
  border-bottom: 1px solid var(--rule-2);
}
.pg-chart th {
  font-family: var(--sans);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--ink);
  padding-top: 0;
}
.pg-chart td {
  padding-left: 30px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-2);
}
/* the second column starts past a hairline */
.pg-chart th + th, .pg-chart td + td { border-left: 1px solid var(--rule-2); padding-right: 0; }
.pg-chart th + th { padding-left: calc(var(--u) * 2.5); }
.pg-chart td + td { padding-left: calc(var(--u) * 2.5 + 30px); }
.pg-chart td + td .pg-mark { left: calc(var(--u) * 2.5); }
.pg-mark {
  position: absolute;
  left: 0;
  top: calc(var(--u) * 2 + 2px);
  width: 18px; height: 18px;
  border-radius: 50%;
}
.pg-mark::before {
  content: '';
  position: absolute;
  inset: 0;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: 11px 11px;
          mask-size: 11px 11px;
}
.pg-mark-check { background: #BCD5B7; }
.pg-mark-check::before { background: #3A7342; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.6 9.8 17.4 19 7.2'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.6 9.8 17.4 19 7.2'/%3E%3C/svg%3E"); }
.pg-mark-x { background: #E6C4BE; }
.pg-mark-x::before { background: #9E3B2F; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.2 7.2 16.8 16.8M16.8 7.2 7.2 16.8'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.2 7.2 16.8 16.8M16.8 7.2 7.2 16.8'/%3E%3C/svg%3E"); }
@media (max-width: 599px) {
  .pg-chart th { font-size: 15px; }
  .pg-chart td { font-size: 14px; }
}

.pg-ticks { list-style: none; margin: 0; padding: 0; }
.pg-ticks li {
  position: relative;
  padding-left: calc(var(--u) * 3);
  margin-bottom: calc(var(--u) * 1.5);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-2);
}
.pg-ticks li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.52em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--indigo);
}

/* Section boundaries are carried by SPACE, not by rules. A hairline between
   every section made the page read as a stack of panels; the vertical rhythm
   already does that job and does it without drawing anything. Rules that
   divide ITEMS inside a list are a different thing and stay.
   -------------------------------------------------------------------------- */

/* --- story beats (About) --------------------------------------------------
   The original About layout, restored: each beat is a plate in WINDOW CHROME
   paired with its copy, and the pair alternates sides down the page.

   The chrome is the point. A bare ASCII image is a picture; the same image
   behind a title bar with traffic lights is a SCREEN, which is what makes the
   Go board and the Hokusai read as things being looked at through a machine
   rather than decoration dropped on the page. The title is the artefact's own
   catalogue number in mono, alphago.1978 and kanagawa.1831, so the chrome
   carries information instead of just being a frame.

   Traffic lights are the ONE place the palette allows a radius, and the one
   place it allows a hue that is not indigo. They are 8px and they are the
   whole reason the window reads instantly.
   -------------------------------------------------------------------------- */

.pg-beat {
  display: grid;
  grid-template-columns: 1fr;
  gap: calc(var(--u) * 4);
  align-items: center;
  padding-block: calc(var(--u) * 9);
}

.pg-beat-copy .pg-year {
  font: 500 11px/1 var(--mono);
  letter-spacing: 0.28em;
  color: var(--ink-3);
  display: block;
  margin-bottom: calc(var(--u) * 3);
}
.pg-beat-h {
  font-family: var(--sans);
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.1;
  margin: 0 0 calc(var(--u) * 2);
  color: var(--ink);
}
.pg-beat-copy .pg-p { max-width: 52ch; margin: 0; }

/* --- the window ----------------------------------------------------------- */

/* No frame and no window bar: the plates sit straight on the paper and
   feather out at their edges, the way the closing figure does. */
.pg-win {
  background: none;
  border: 0;
}

/* On the About page the first beat follows the page head directly: the
   head's own bottom padding is the gap, not the head's plus the beat's. */
.pg-head + .pg-body > .wrap > .pg-beat:first-child { padding-top: calc(var(--u) * 2); }

/* the only radius and the only non-indigo hues in the whole system */

.pg-win canvas { display: block; width: 100%; height: auto; color: var(--ink); }
/* The panel photograph as a plate: the whole frame, nothing cropped, with
   the hero portrait's edge (a hard clip at the same 8px corner, no dissolve).
   The shared 640px height cap would cover-crop a tall plate, so this one
   keeps its own shape: the cap transfers to the width through aspect-ratio. */
.pg-win canvas.pg-plate-photo {
  width: auto; max-width: 100%;
  height: auto; max-height: min(640px, 78vh);
  margin-inline: auto;
  border-radius: var(--r-l); clip-path: inset(0 round var(--r-l));
}
/* On a phone the column is the width and the plate runs the whole of it.
   Every .pg-win is a <figure> still wearing the browser's 40px side margins;
   only this one drops them, so the other plates stay as they are. */
@media (max-width: 899px) {
  .pg-win:has(> .pg-plate-photo) { margin-inline: 0; }
  .pg-win canvas.pg-plate-photo { width: 100%; max-height: none; }
}
/* The moving plates run in the one indigo, the way the figures on the
   homepage do. The portrait stays ink: the hero portrait is ink too. */
.pg-win canvas.pg-live { color: var(--indigo); }

@media (min-width: 900px) {
  .pg-beat { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: calc(var(--u) * 8); }
  .pg-beat .pg-win  { grid-column: 1; grid-row: 1; }
  .pg-beat .pg-beat-copy { grid-column: 2; grid-row: 1; }
  /* every other beat swaps sides, so the eye crosses the page as it descends */
  .pg-beat-flip { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .pg-beat-flip .pg-win { grid-column: 2; }
  .pg-beat-flip .pg-beat-copy { grid-column: 1; }
}

/* --- closing card --------------------------------------------------------- */

/* Reuses the scroll's card surface wholesale: same .fg-copy, same filter, so
   the pages end on the same material the scroll is made of. */
.pg-close { padding-block: calc(var(--u) * 10) calc(var(--u) * 16); }

@media (min-width: 900px) {
  .pg-split { grid-template-columns: 1fr 1fr; gap: calc(var(--u) * 10); }
  /* .pg-beat and .pg-year are NOT set here. They were, and because this block
     sits after the beat rules above it silently won, collapsing every window
     to a 96px sliver while the flipped beat kept its own columns and looked
     fine. Two media blocks at the same breakpoint, both claiming the same
     selector, is the whole bug. The beat layout lives in one place now. */
}

@media (max-width: 720px) {
  .pg-head { padding-block: calc(var(--u) * 12) calc(var(--u) * 6); }
  .pg-item { grid-template-columns: 1fr; }
  .pg-item-meta { grid-column: 1; grid-row: auto; }
  .pg-item:has(> .pg-item-ico) { grid-template-columns: auto 1fr; }
  .pg-item:has(> .pg-item-ico) .pg-item-meta { grid-column: 2; grid-row: auto; }
}

/* ==== parts/mobile.css ==== */
/* ===========================================================================
   mobile.css   loaded LAST, the phone layout for the Bare homepage

   Rebuilt from the rules the FIRST opusjake.ai settled on, whose mobile format
   was itself picked from a five-way design-shotgun (2026-07-26) and whose
   reasoning is written into aqua.css and app.html. Taken from there:

     1. Brand + three links + a CTA DO NOT FIT a phone. aqua.css: "brand +
        three tabs + the CTA overrun a 360px viewport by 40px", so it dropped
        the section links and kept brand + CTA. Everything stays in the DOM.
     2. Gutters are 16-18px, not desktop's 32.
     3. svh, never vh or %. iOS grows the % viewport as the address bar hides,
        which slides anything anchored to it.
     4. 44px is the touch-target floor and the type gives way before it does.
     5. Inputs are 16px minimum or iOS force-zooms the page on focus.
     6. Scale the WHOLE type system down together: "every element is sized
        against the 31px headline the way desktop sizes against its 42px one,
        so the hierarchy holds instead of flattening."
     7. Height-aware, not just width-aware. A 568px-tall phone cannot carry the
        same scale as an 844px one: on the first site the taller scale "pushed
        the buttons 68px past the fold and forced them to wrap".
   =========================================================================== */

/* --- 1. nav: brand and CTA only ------------------------------------------- */

@media (max-width: 640px) {
  .nv-nav {
    flex-wrap: nowrap;
    gap: var(--u);
    padding: calc(var(--u) * 1.5) calc(var(--u) * 2);
    padding-right: max(16px, env(safe-area-inset-right));
    padding-left: max(16px, env(safe-area-inset-left));
  }
  .nv-brand { font-size: 17px; flex: none; }

  /* The links stay in the DOM: they are real internal links to /blog and the
     library, and the footer carries them visibly further down. */
  .nv-links { flex: 0 0 auto; margin-left: auto; gap: 0; min-width: 0; }
  .nv-link:not(.nv-link-cta) { display: none; }

  .nv-link-cta {
    display: inline-flex;
    align-items: center;
    min-height: 44px;              /* touch-target floor, type gives way first */
    padding-inline: calc(var(--u) * 2);
    font-size: 14px;
    white-space: nowrap;
  }
}

/* --- 2. hero -------------------------------------------------------------- */

@media (max-width: 720px) {
  .hr-hero {
    padding-block: calc(var(--u) * 3) calc(var(--u) * 5);
    align-items: center;
  }
  .hr-grid   { text-align: center; }
  .hr-copy   { align-items: center; }
  .hr-canvas { margin-inline: auto; }
  .hr-title, .hr-sub { margin-inline: auto; }
  /* .hr-copy is a centred flex column, so its children shrink-wrap: the row
     took 286px of the 339px between the gutters and the primary label
     overflowed by 13px. Stretch it to the full measure and the labels fit
     without shaving type. */
  .hr-actions {
    justify-content: center; flex-wrap: nowrap; gap: calc(var(--u) * 1.5);
    width: 100%; align-self: stretch;
  }

  /* The pair hugs its labels at the live site's own phone dimensions - its
     hero gel spec is height:48px, padding:0 18px, font-size:14.5px - instead
     of splitting the row 50/50. Stretched pills read as bars; the gel skin
     wants the capsule proportions it has on opusjake.ai. At 320px the pair
     measures ~270px against 284px of column, so it fits without a step. */
  .hr-actions .hr-btn {
    /* height, padding and label size are Astryx's lg button (button.css) */
    flex: 0 0 auto;
    justify-content: center;
    text-align: center;
    white-space: nowrap;
  }
}

/* Tall phones (iPhone 12 and up): the fuller scale. */
@media (max-width: 720px) and (min-height: 680px) {
  .hr-canvas { max-height: 42svh; width: auto; max-width: 100%; }
  .hr-title  { font-size: 31px; }
  .hr-sub    { font-size: 15.5px; max-width: 34ch; }
}

/* Short phones (SE at 568px): the compact set. The taller scale pushes the
   buttons past the fold here, which is the trade the first site documented. */
@media (max-width: 720px) and (max-height: 679px) {
  .hr-canvas { max-height: 30svh; width: auto; max-width: 100%; }
  .hr-title  { font-size: 26px; }
  .hr-sub    { font-size: 14.5px; max-width: 32ch; }
}

/* --- 3. figure bands ------------------------------------------------------ */

@media (max-width: 720px) {
  .fg-fig  { padding-block: calc(var(--u) * 5); }
  .fg-body { gap: calc(var(--u) * 3); }
  .fg-copy {
    padding: calc(var(--u) * 4);
    padding-top: calc(var(--u) * 8);
  }
  .fg-head { padding-bottom: calc(var(--u) * 2); }
}

/* --- 4. iOS focus-zoom guard, KEEP LAST -----------------------------------
   iOS Safari force-zooms when a focused control computes under 16px. Every
   focusable text control is listed so no rule above can undercut it. */
@media (max-width: 720px) {
  .lbm input,
  .lbm select,
  .lb-input { font-size: 16px; }
}

/* --- 5. ONE gutter, not two ----------------------------------------------
   .wrap carries desktop's 32px inline padding at every width. Adding a phone
   gutter to .hr-hero and .fg-fig on top of it double-guttered the page: at
   375px the content column measured 275px, with 50px of dead margin each side.
   That is why the hero read as cramped and why the CTA labels would not fit
   their buttons.

   The gutter belongs in one place, and it is the nav's: 16px. At 18 the
   content sat 2px inside the wordmark above it, off the 8px grid. */
@media (max-width: 720px) {
  .wrap { padding-inline: calc(var(--u) * 2); }
}

/* --- 6. no sideways pan, at the root -------------------------------------
   base.css sets `overflow-x: hidden` on BODY only. Chrome contains the page
   that way; iOS Safari does not reliably, and the document pans sideways even
   though desktop measures scrollWidth === viewport. That is a page cut off on
   the left with the nav button cut off on the right.

   `clip` rather than `hidden` on the root: `hidden` makes html a scroll
   container, which breaks position:sticky and can hijack scroll anchoring.
   Both axes are named explicitly so vertical scrolling is untouched. */
html {
  overflow-x: clip;
  max-width: 100%;
}

/* The visually hidden crawler block lays its text out on one nowrap line that
   runs to roughly x=1129. Its own overflow:hidden contains it, but belt and
   braces: it must never be able to widen the document. */
.seo-copy { clip-path: inset(50%); max-width: 1px; }

/* Nothing in the page may exceed the viewport on a phone. */
@media (max-width: 720px) {
  .wrap, .fg-fig, .hr-hero, .tc-wrap, .fq-wrap, .ft { max-width: 100%; }
  img, canvas, svg, video { max-width: 100%; }
}

/* --- 7. the GPU budget ----------------------------------------------------
   Three card surfaces each run filter: url(#oj-liquid-*), an SVG displacement
   driven by SMIL-animated feTurbulence, continuously, plus the footer's own
   turbulence sheet, plus a backdrop blur on the nav. Phone GPUs cannot carry
   four live filter surfaces: that is the jank, and overloaded SVG filters are
   also iOS Safari's other known source of black-rectangle rendering.

   The gradients themselves stay: the ramp and the blooms live in background
   layers, so the cards keep their colour and only the liquid warp goes. */
@media (max-width: 720px) {
  .fg-copy::before,
  .fg-copy::after { filter: none; }
  .ft-sky::before { filter: none; }
  .nv-nav { backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* --- 8. the footer on phones ----------------------------------------------
   Below 760px .ft-cols collapsed to one column, so all four lists stacked down
   the left edge and the skyline sat under empty paper. The columns now run
   ACROSS as a 2x2 grid, the type steps down, and the whole block is pushed
   low enough to sit inside the skyline band the way the desktop footer does. */
@media (max-width: 720px) {
  /* The sky is anchored to the footer's bottom edge, so padding alone can
     never move content into it: growing the footer moves the band down by the
     same amount. The band itself grows instead: the content block runs 491px,
     so a 560px band holds all of it with air above the heading. */
  .ft { padding-block: calc(var(--u) * 9) calc(var(--u) * 6); }
  .ft-sky, .ft::after { height: 560px; }
  .ft-h { font-size: 24px; margin-bottom: calc(var(--u) * 4); }
  .ft-cols {
    grid-template-columns: repeat(2, 1fr);
    gap: calc(var(--u) * 4) calc(var(--u) * 3);
    padding-bottom: calc(var(--u) * 4);
  }
  .ft-lab { font-size: 9px; letter-spacing: 0.22em; }
  .ft-col a, .ft-p { font-size: 13px; }
  .ft-p { line-height: 1.5; }
  .ft-foot { padding-top: calc(var(--u) * 3); gap: calc(var(--u) * 2); }
  .ft-fine { font-size: 9px; letter-spacing: 0.16em; }
}

/* ==== parts/layout-polish.css ==== */
/* Tighter reading rhythm, with the complete ASCII plates kept in view. */
.hr-hero {
  /* Keep the first screen devoted to the hero, including on mobile. */
  min-height: 100vh;
  min-height: 100dvh;
  padding-block: calc(var(--u) * 6) calc(var(--u) * 7);
}

.hr-canvas {
  width: min(100%, 464px);
  height: auto;
  max-height: none;
  justify-self: center;
}

.fg-fig { padding-block: calc(var(--u) * 7); }
.fg-body { margin-top: calc(var(--u) * 2); }
.fg-head { padding-bottom: 0; }
.tc-wrap { padding-block: calc(var(--u) * 4); }
.tc-join-solo { padding-block: var(--u) 0; }
.tc-join { padding-bottom: calc(var(--u) * 2); }
.tc-head { padding-bottom: calc(var(--u) * 2); }
.tc-row { padding-block: var(--u); }
.tc-gen { margin-top: calc(var(--u) * 2); padding-top: calc(var(--u) * 2); }
.tr-wrap { padding-block: calc(var(--u) * 5); }
.tr-label { margin-bottom: calc(var(--u) * 3); }
.fq-wrap { padding-block: calc(var(--u) * 6); }
.fq-head { margin-bottom: calc(var(--u) * 3); }

/* Supporting text is still quieter than headings, but remains readable. */
.mono-label,
.fg-num,
.fg-sys,
.lb-eyebrow,
.gr-label,
.gr-meta,
.lb-bar,
.lm-url,
.tr-label,
.fq-eyebrow {
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.08em;
  color: var(--ink-2);
}

.lm-url { overflow-wrap: anywhere; }
.lbm .lbm-close { min-height: 44px; font-size: 12px; letter-spacing: 0.08em; }
.tc-target, .tc-stat, .tc-ms, .tc-gen, .tc-join span { color: var(--ink-2); }
.tc-count, .tc-join span { font-size: 12px; letter-spacing: 0.06em; line-height: 1.5; }
.tc-add { color: #307438; }
.tc-del { color: #a43c32; }

.hr-actions .hr-btn:focus-visible,
.lbm :is(a, button, input, select):focus-visible,
.fq-q:focus-visible,
.ft a:focus-visible {
  outline: 3px solid var(--indigo);
  outline-offset: 4px;
}

.fg-copy .fg-act:focus-visible {
  outline: 3px solid var(--paper);
  outline-offset: 4px;
}

.fg-copy .fg-act { min-height: 44px; }

/* Compact cards: 400px tall at full width (the old 6/5 ratio), and free to
   grow past that. A fixed ratio with overflow:hidden clipped the top of the
   offer card's headline once its copy and the plan strip outgrew the box. */
.fg-fig .fg-copy {
  width: min(100%, 480px);
  min-height: min(400px, 83.33vw);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@media (min-width: 900px) {
  .hr-grid { grid-template-rows: auto; }
  .hr-canvas { width: 100%; }
  .hr-title {
    --hr-mark-height: clamp(50.92px, 6.968vw, 91.12px);
    /* Let the full name use the outer margin, keeping 32px at the screen edge. */
    width: calc(100% + max(0px, (100vw - 1200px) / 2));
  }
  /* the greeting at the title size, the mark at the mark height: the
     proportion Jake chose over the mark-led one (Oct 10) */
  .hr-title .hr-lead { font-size: 1em; }
  .hr-title .hr-wm {
    font-size: min(var(--hr-mark-height), 12.4cqi);
    height: 1em;
  }
  /* Keep the reference's three-line copy space with the current shorter text. */
  .hr-sub { margin-top: var(--u); min-height: 3lh; }

  .fg-body { margin-top: 0; }
  .fg-fig .fg-figure { width: 66%; max-width: 720px; }
  .fg-copy { padding-block: calc(var(--u) * 6) calc(var(--u) * 4); }
}

@media (min-width: 721px) and (max-width: 899px) {
  .hr-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: calc(var(--u) * 4); }
  .hr-copy { grid-column: 2; grid-row: 1; align-self: center; }
  .hr-actions { gap: var(--u); }
  .hr-actions .hr-btn { padding-inline: calc(var(--u) * 2); }
}

@media (max-width: 899px) {
  .fg-fig { padding-block: calc(var(--u) * 4); }
  .fg-body { gap: calc(var(--u) * 2); }
  /* the ASCII leads, the blue card follows (Jake, Oct 2026): the picture is
     what stops the thumb, the card is what it reads after */
  /* On a phone the card hugs its words: 48px above (the window lights live
     there), 24px around. The 400px floor is a desktop composition; here it
     left a one-line card mostly empty blue. */
  .fg-copy { grid-row: 2; padding: calc(var(--u) * 6) calc(var(--u) * 3) calc(var(--u) * 3); }
  .fg-fig .fg-copy { min-height: 0; }
  .fg-fig .fg-copy { justify-self: center; }
  /* The plates match the copy card's width; a 280px cap made them read as
     thumbnails under a full-width card. */
  .fg-figure { grid-row: 1; width: min(100%, 480px); }
  .fg-copy .fg-h, .fg-copy .fg-p { max-width: 100%; }

  /* --- the ASCII in the box, the words on the paper (Jake, Oct 2026) ----------
     The blue tile holds the plate, drawn in paper white, with the window dots
     at its top left; headline, body and button sit under it on the oatmeal,
     the way an image card carries its title beneath it. */
  .fg-well {
    position: relative;
    isolation: isolate;
    aspect-ratio: 10 / 11;        /* 48px of dots above a square plate */
    padding: calc(var(--u) * 6) calc(var(--u) * 2) calc(var(--u) * 2);
    border: 0;
    border-radius: var(--r-l);
    overflow: clip;
    background: linear-gradient(100deg, #2F3CD8 0%, #3B49E6 52%, #4655EE 100%);
    box-shadow: var(--shadow-card);
  }
  /* the window dots, drawn rather than added: the figure has no dots element */
  .fg-well::before {
    content: "";
    position: absolute;
    top: calc(var(--u) * 3);
    left: calc(var(--u) * 3);
    width: 49px;
    height: 11px;
    z-index: 2;
    background:
      radial-gradient(circle at 5.5px 5.5px, #ff5f57 5px, transparent 5.8px),
      radial-gradient(circle at 24.5px 5.5px, #febc2e 5px, transparent 5.8px),
      radial-gradient(circle at 43.5px 5.5px, #28c840 5px, transparent 5.8px);
    filter: drop-shadow(0 1px 1px rgba(15, 20, 80, 0.22));
    pointer-events: none;
  }
  /* one soft light streak across the top right, no liquid filter on phones */
  .fg-well::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
      radial-gradient(60% 28% at 78% 24%, rgba(198, 194, 246, 0.42), transparent 70%),
      radial-gradient(40% 22% at 92% 60%, rgba(242, 208, 194, 0.30), transparent 70%);
  }
  /* a square tile; the plate stays square inside it, centred under the dots */
  .fg-well { aspect-ratio: 1 / 1; }
  .fg-frame { position: relative; z-index: 1; width: auto; height: 100%; aspect-ratio: 1 / 1; margin-inline: auto; }
  .fg-plate { color: var(--paper); }

  .fg-fig .fg-copy {
    padding: 0;
    min-height: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;   /* the words are not a box; the tile and the button carry the depth */
    color: var(--ink);
    overflow: visible;
  }
  .fg-copy::before, .fg-copy::after, .fg-copy .fg-page-dots { display: none; }
  /* the measure of an image card's caption (openai.com on a phone): 24px
     under the tile, a regular-weight 24px title, a 17px grey line, the
     button 24px under that, and 48px of air before the next section */
  .fg-fig { padding-block: calc(var(--u) * 6); }
  .fg-body { gap: calc(var(--u) * 3); }
  .fg-copy .fg-h { color: var(--ink); font-size: 24px; font-weight: 400; line-height: 1.25; letter-spacing: -0.01em; }
  .fg-copy .fg-p { color: var(--ink-2); font-size: 17px; line-height: 1.5; margin-top: calc(var(--u) * 1.5); }
  .fg-copy .fg-act { margin-top: calc(var(--u) * 3); }
  .fg-p { margin-top: calc(var(--u) * 2); }
  .fg-act { margin-top: calc(var(--u) * 3); }
  /* Keep the bright streaks to the right when copy spans the narrow card. */
  .fg-copy::after { opacity: 0.22; }
}

/* The phone hero, Oct 10 (final order, Jake's): portrait, then the claim,
   then the pair. One block, centred in the first screen and sitting high
   (more air under it than over it). Spacing follows proximity: the claim and
   its buttons are one group, so they sit closer together (24px) than the
   portrait sits to the claim (32px). The claim is the headline (Endless,
   22 to 24px, three balanced lines); the bar already carries OPUSJAKE, so the
   h1 stays in the document for readers and crawlers, visually hidden.
   Nothing of the next section shows on landing. */
@media (max-width: 720px) {
  .hr-hero { min-height: 100svh; align-items: center; padding-block: calc(var(--nv-h, 81px) + var(--u) * 3) calc(var(--u) * 14); }
  .hr-grid { display: flex; flex-direction: column; align-items: center; gap: calc(var(--u) * 4); }
  .hr-canvas { order: 0; width: auto; max-width: 100%; max-height: 40svh; margin-inline: auto; }
  .hr-copy { order: 1; display: flex; flex-direction: column; align-items: center; width: 100%; }
  .hr-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; margin: 0; }
  .hr-copy .hr-sub {
    order: 0;
    width: 100%; max-width: none; min-height: 0;
    margin: 0 0 calc(var(--u) * 3);
    font-size: clamp(22px, 6.2vw, 24px); line-height: 1.2; letter-spacing: -0.015em;
    color: var(--ink);
    text-wrap: balance;
  }
  .hr-actions { order: 1; flex-wrap: nowrap; }
  .tc-wrap { padding-block: calc(var(--u) * 3); }
  .tc-join-solo { padding-block: var(--u) 0; }
  .tc-gen { font-size: 12px; line-height: 1.5; }
}
/* short phones (SE, 8): the portrait gives up some height */
@media (max-width: 720px) and (max-height: 700px) {
  .hr-canvas { max-height: 32svh; }
}

/* ==== parts/faq.css ==== */
/* ===========================================================================
   faq.css   prefix: fq-
   Hairlines, no boxes, one open at a time is NOT enforced: forcing a single
   open panel means a reader comparing two answers has to keep reopening one of
   them. Native details lets them hold both.

   The marker is a plus that becomes a minus. It is drawn with two 1px bars and
   a rotation rather than a glyph, so it is exactly aligned at every size and
   there is no font dependency for a piece of interface.
   =========================================================================== */

/* 10u, not 14u. Every band on the page pairs 128px against 80px, which makes
   each seam 208px; the FAQ's 112px was the one value that did not belong to
   that pair and it made the figure-to-FAQ seam 240px, the only seam on the
   page that differed from the four above it. */
.fq-wrap { padding-block: calc(var(--u) * 10); }

.fq-head { margin-bottom: calc(var(--u) * 5); }
.fq-eyebrow {
  font: 500 10px/1 var(--mono);
  letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--ink-3); display: block; margin-bottom: calc(var(--u) * 2);
}
.fq-h {
  font-family: var(--sans); font-weight: 400;
  font-size: clamp(28px, 3.2vw, 42px);
  line-height: 1.06; letter-spacing: -0.02em; margin: 0; color: var(--ink);
}

.fq-list { border-top: 1px solid var(--rule-2); }
.fq-item { border-bottom: 1px solid var(--rule-2); }

.fq-q {
  display: flex; align-items: center; justify-content: space-between;
  gap: calc(var(--u) * 3);
  padding: calc(var(--u) * 3) 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--sans); font-size: clamp(16px, 1.7vw, 19px);
  font-weight: 400; letter-spacing: -0.01em; color: var(--ink);
  transition: color 160ms ease;
}
.fq-q::-webkit-details-marker { display: none; }
@media (hover: hover) and (pointer: fine) {
  .fq-q:hover { color: var(--indigo); }
}
.fq-q:focus-visible { outline: 2px solid var(--indigo); outline-offset: 3px; }

/* the plus, drawn not typed */
.fq-x { position: relative; flex: none; width: 13px; height: 13px; }
.fq-x::before, .fq-x::after {
  content: ''; position: absolute; left: 0; top: 6px;
  width: 13px; height: 1px; background: var(--ink-2);
  transition: transform 240ms cubic-bezier(0.2, 0.9, 0.2, 1);
}
.fq-x::after { transform: rotate(90deg); }
.fq-item[open] .fq-x::after { transform: rotate(0deg); }
.fq-item[open] .fq-q { color: var(--indigo); }
.fq-item[open] .fq-x::before,
.fq-item[open] .fq-x::after { background: var(--indigo); }

.fq-a { padding: 0 0 calc(var(--u) * 4); }
.fq-a p {
  margin: 0; max-width: 56ch;  /* FINDING-005: 62ch rendered ~77 real characters */
  font-family: var(--sans); font-size: 15px; line-height: 1.65; color: var(--ink-2);
}

@media (prefers-reduced-motion: reduce) {
  .fq-x::before, .fq-x::after { transition: none; }
}

@media (max-width: 720px) {
  .fq-wrap { padding-block: calc(var(--u) * 9); }
  .fq-q { font-size: 16px; gap: calc(var(--u) * 2); }
}

/* ==== parts/button.css ==== */
/* ===========================================================================
   button.css   every button on the site, as Astryx 0.6.7 draws its Button

   Replaces gel.css (the Aqua gloss pills). The values are not eyeballed: they
   were read off a real @astryxdesign/core 0.6.7 <Button>, rendered in the
   OpusJake theme from opusjake-landing (the Bare mockups were built on
   Astryx), at rest, under hover, under a press and under keyboard focus.

     height        sm 28 · md 32 · lg 36 (--size-element-*); the hero pair is
                   lg scaled whole by 4/3 (48)
     shape         10px radius (--radius-element), no border, no shadow
     label         14px / 20px, weight 500, 8px gap to an icon, 12px inline pad
     primary       #4b4aef, white label (--color-accent / --color-on-accent)
     secondary     6% ink tint, ink label (--color-neutral)
     ghost         transparent, ink label
     hover         a 5% ink layer over the fill, only where hover exists
     press         a 10% layer and scale(0.98), 125ms, ease (0.24, 1, 0.4, 1)

   One change from stock Astryx, at Jake's call: no focus ring. A button that
   is pressed, or holds keyboard focus, is PUSHED IN instead: the 10% layer
   plus an inset shadow falling from the top edge (the light on this site
   comes from above), so the surface reads as below its surround. The nav's
   current page wears the same well. Forced-colours mode drops box shadows,
   so a real outline comes back there.
     disabled      50% opacity, no hover, no press

   Touch is Astryx's own press model, not :active. On a phone :active paints
   the moment a finger lands and can outlive the start of a scroll, so under a
   coarse pointer the press is written by press.js as data-press: "on" once
   the touch is believed (150ms held, under 10px of travel, no scroll), a
   100ms flash for a quick tap, then "fading" for the 200ms release.

   Loaded after mobile.css and layout-polish.css, so it owns the SIZE as well
   as the skin. The component sheets keep layout only (where a button sits);
   every state rule a button has lives here and nowhere else.
   =========================================================================== */

/* the release fades a number, because gradients do not interpolate */
@property --btn-press {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

:root {
  --btn-h-sm: 28px;
  --btn-h-md: 32px;
  --btn-h-lg: 36px;
  --btn-radius: 10px;
  --btn-accent: #4b4aef;
  --btn-neutral: rgb(0 0 0 / 0.06);
  --btn-hover: rgb(0 0 0 / 0.05);
  --btn-pressed: rgb(0 0 0 / 0.1);
  /* pushed in: a soft dark lip under the top edge, a 1px light one at the foot */
  --btn-inset: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
  /* at rest a filled button sits ON the page: a contact shadow under it and a
     1px line of light along its top edge, the macOS push button's depth */
  --btn-shadow: var(--shadow-1), inset 0 1px 0 rgb(255 255 255 / 0.18);
  --btn-fast: 125ms;
  --btn-ease: cubic-bezier(0.24, 1, 0.4, 1);
}

/* --- shape: every button ---------------------------------------------------- */

.hr-actions .hr-btn,
.rs-btn,
.fg-copy .fg-act,
.ft .ft-pill,
.nv-nav .nv-link,
.bk .bk-go,
.bk .bk-close,
.lbm .lbm-close,
.cm-send,
.library .sort-submit,
.nv-nav .nv-menu-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--btn-h, var(--btn-h-md));
  /* never shorter than its size: a column flex parent would otherwise squeeze
     it (min-height 0 collapsed the Lab card's button to 7px) */
  min-height: var(--btn-h, var(--btn-h-md));
  /* but free to narrow, so a long label truncates instead of overflowing */
  min-width: 0;
  max-width: 100%;
  padding: 0 12px;
  border: 0;
  border-radius: var(--btn-radius);
  font: 500 14px/20px var(--sans, 'Endless', system-ui, sans-serif);
  letter-spacing: normal;
  text-transform: none;
  text-shadow: none;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: none;
  background-image: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform: scale(1);
  transition-property: background-image, background-color, color, opacity, transform, box-shadow;
  transition-duration: var(--btn-fast);
  transition-timing-function: var(--btn-ease);
}

/* --- size: page actions are large, chrome is medium -------------------------- */

.fg-copy .fg-act,
.ft .ft-pill,
.bk .bk-go { --btn-h: var(--btn-h-lg); }

/* The hero pair is the page's first decision, so it is bigger, but it is the
   SAME SHAPE as every other button (the library's pair is the reference): the
   lg button scaled whole, not stretched. Height, corner, padding, gap and
   label all grow by one factor, --btn-k, in px per lg px. Raising the height
   alone (48px with lg's 10px corner and a 16px label) read as squarer and
   emptier than the library's buttons; Jake caught it.

   4/3 on desktop: 48 tall, 13.3 corner, 16 padding, 16 label. On a phone the
   factor gives way before the pair would wrap: the widest pair (Opus Lab's,
   252 lg px with padding) plus the 12px gap must fit inside the 16px gutters,
   so k = (100vw - 44px) / 252, capped at 4/3. That is 4/3 from 380px up and
   about 47px tall at 375. Below 44px tall, the touch area still is 44. */
.hr-actions .hr-btn {
  --btn-k: 1.3333px;
  --btn-h: calc(36 * var(--btn-k));
  padding-inline: calc(12 * var(--btn-k));
  gap: calc(8 * var(--btn-k));
  border-radius: calc(10 * var(--btn-k));
  /* the label does not scale with the rest: at 4/3 it ran 18.7px and read
     loud for a button (Jake). 12 lg px of label is 16px on desktop. */
  font-size: calc(12 * var(--btn-k));
  line-height: calc(20 * var(--btn-k));
}
/* On a phone the pair is the lg button itself, the same size as the card
   buttons and the footer's (Jake, Oct 10: "the same size as the rest of the
   buttons"); the 4/3 scale is a desktop thing. */
@media (max-width: 720px) {
  .hr-actions .hr-btn { --btn-k: 1px; font-size: 14px; line-height: 20px; }   /* lg's own label, not the scaled 12 */
}
/* the resource pages (/r/<slug>): page actions are lg, a code block's Copy is sm */
.rs-btn { --btn-h: var(--btn-h-lg); }
.rs-btn.rs-btn-sm { --btn-h: var(--btn-h-sm); padding: 0 10px; font-size: 13px; line-height: 20px; }
/* the library's search pair is the lg button itself */
.library .hr-actions .hr-btn { --btn-k: 1px; }
.library .sort-submit { --btn-h: var(--btn-h-sm); }

/* the send button is Astryx's ChatSendButton: an icon-only md primary, round */
.cm-send { width: var(--btn-h-md); padding: 0; border-radius: 50%; flex: none; }
.cm-send::after { content: none; }
.cm-send svg { width: 16px; height: 16px; filter: none; }

/* --- variants ------------------------------------------------------------- */

.hr-actions .hr-btn-solid,
.rs-btn-primary,
.nv-nav .nv-link-cta,
.bk .bk-go,
.cm-send {
  background-color: var(--btn-accent);
  color: #fff;
  box-shadow: var(--btn-shadow);
}

.hr-actions .hr-btn-soft,
.rs-btn-secondary,
.bk .bk-close,
.lbm .lbm-close,
.library .sort-submit {
  background-color: var(--btn-neutral);
  color: var(--ink, #2F2B25);
  box-shadow: var(--btn-shadow);
}

/* the quiet nav items are Astryx's ghost: no fill until hovered */
.nv-nav .nv-link:not(.nv-link-cta),
.nv-nav .nv-menu-toggle {
  background-color: transparent;
  color: var(--ink, #2F2B25);
}
/* the header's one call to action is the primary, set a step apart from the
   links it ends (the Astryx mockup's "Work with me") */
.nv-nav .nv-link-cta { margin-left: var(--u); }
/* the phone menu button: a ghost icon button, and PUSHED IN while its menu
   is open, the site's sign for "on" */
.nv-nav .nv-menu-toggle { width: var(--btn-h-md); padding: 0; flex: none; }
.nv-nav .nv-menu-toggle[aria-expanded="true"],
.nv-nav .nv-menu[open]:not(.nv-js) .nv-menu-toggle {
  background-image: linear-gradient(var(--btn-pressed), var(--btn-pressed));
  box-shadow: var(--btn-inset);
}
.nv-nav .nv-link[aria-current="page"] {
  background-color: color-mix(in srgb, var(--btn-accent) 10%, transparent);
  color: var(--btn-accent);
  box-shadow: var(--btn-inset);
}

/* On blue (the gradient cards, the footer's skyline band) the button is the
   one light object: a white fill with a deep indigo label, the colours the card
   buttons had before the gel. A flat indigo button on an indigo band has no
   edge. */
.fg-copy .fg-act,
.rs-btn-onblue,
.ft .ft-pill {
  background-color: #fff;
  color: #2438C8;
  box-shadow: var(--btn-shadow);
}
/* on a phone the card's words sit on paper, so its button is the primary */
@media (max-width: 899px) {
  .fg-copy .fg-act { background-color: var(--btn-accent); color: #fff; }
}

/* --- states ----------------------------------------------------------------- */

/* keyboard focus: pushed in, no ring */
:is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
    .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle):focus {
  outline: none;
}
:is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
    .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle):focus-visible {
  background-image: linear-gradient(var(--btn-pressed), var(--btn-pressed));
  box-shadow: var(--btn-inset);
}
@media (forced-colors: active) {
  :is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
    .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle):focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }
}

@media (hover: hover) {
  :is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
      .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle):where(:not(:disabled, [aria-disabled="true"])):hover {
    background-image: linear-gradient(var(--btn-hover), var(--btn-hover));
  }
}

/* a mouse press */
@media not (pointer: coarse) {
  :is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
      .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle):where(:not(:disabled, [aria-disabled="true"])):active {
    background-image: linear-gradient(var(--btn-pressed), var(--btn-pressed));
    box-shadow: var(--btn-inset);
    transform: scale(0.98);
  }
}

/* a finger press, written by press.js */
:is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
    .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle)[data-press="on"] {
  background-image: linear-gradient(var(--btn-pressed), var(--btn-pressed));
  box-shadow: var(--btn-inset);
  transform: scale(0.98);
}
:is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
    .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle)[data-press="fading"] {
  background-image: linear-gradient(rgb(0 0 0 / calc(var(--btn-press) * 0.1)),
                                    rgb(0 0 0 / calc(var(--btn-press) * 0.1)));
  animation: btn-release 200ms ease-out forwards;
}
@keyframes btn-release { from { --btn-press: 1; } to { --btn-press: 0; } }

:is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
    .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle):is(:disabled, [aria-disabled="true"]) {
  opacity: 0.5;
  cursor: default;
  transform: none;
}
/* the empty composer: Astryx disables the send button rather than greying it */
.cm-send[disabled] { color: #fff; background-color: var(--btn-accent); border: 0; }

/* A finger needs 44px even where the button draws 28 to 36. The hit area
   grows invisibly around the drawn shape; nothing in the layout moves. */
@media (pointer: coarse) {
  :is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
      .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle)::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: max(100%, 44px);
    height: max(100%, 44px);
    transform: translate(-50%, -50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  :is(.hr-actions .hr-btn, .rs-btn, .fg-copy .fg-act, .ft .ft-pill, .nv-nav .nv-link,
      .bk .bk-go, .bk .bk-close, .lbm .lbm-close, .cm-send, .library .sort-submit,
    .nv-nav .nv-menu-toggle) {
    transition-duration: 0s;
  }
}

/* --- rows that act like buttons: the dialog's answers ----------------------- */

/* Astryx's list rows answer a press with the same 10% layer and no scale. */
@media not (pointer: coarse) {
  .bk .bk-opt:active { background-image: linear-gradient(var(--btn-pressed), var(--btn-pressed)); }
}
.bk .bk-opt[data-press="on"] { background-image: linear-gradient(var(--btn-pressed), var(--btn-pressed)); }
.bk .bk-opt[data-press="fading"] {
  background-image: linear-gradient(rgb(0 0 0 / calc(var(--btn-press) * 0.1)),
                                    rgb(0 0 0 / calc(var(--btn-press) * 0.1)));
  animation: btn-release 200ms ease-out forwards;
}

/* ==== parts/nav-mobile.css ==== */
/* ===========================================================================
   nav-mobile.css   the header on phones, plus the skip link

   Phones get three things in the bar: the wordmark, "Book a call" (the one
   action that is worth a tap from anywhere) and a menu button. The menu is
   Astryx's dropdown, so it speaks the same language as every button on the
   site (button.css):

     the button    a ghost icon button; while the menu is open it stays
                   PUSHED IN, the site's sign for "on"
     the panel     a light card under the bar, 14px radius, the float shadow
     the rows      44px tall, icon and label, the 5% / 10% layers on hover and
                   press, the current page pushed in like the desktop nav

   Motion follows Emil Kowalski's rules for a popover. It grows out of the
   button it hangs from (transform-origin at the top right, not the centre),
   from 96% and clear rather than from nothing, on a strong ease-out in 200ms,
   and it leaves faster than it came (140ms). The glyph turns from two lines
   into a cross on the on-screen-movement curve. Nothing staggers: this is a
   menu people open many times a visit.

   Without JavaScript the native details toggle still opens it (instantly).
   With it, nav.js keeps the details open and drives .nv-open, so the panel
   can animate closed too; a closed panel is visibility:hidden, so its links
   leave the tab order.
   =========================================================================== */

/* desktop has its own links; the menu exists only on phones */
.nv-menu { display: none; }

.skip-link {
  position: fixed;
  top: 12px;
  left: 16px;
  z-index: 100;
  padding: 12px 20px;
  border-radius: var(--r-s);
  background: var(--paper);
  color: var(--ink);
  transform: translateY(-160%);
}
.skip-link:focus { transform: none; }
#top { scroll-margin-top: 88px; }
#top:focus { outline: none; }
#contact { scroll-margin-top: 112px; }

@media (max-width: 640px) {
  :root { --nv-h: 57px; }   /* the bar's height on phones: 16 + 32 + 16 + the hairline */
  .nv-nav { gap: var(--u); background: var(--paper); }
  .nv-brand { font-size: 25px; }

  /* the bar keeps its call to action; the other links move into the menu */
  .nv-nav .nv-links { display: flex; margin-left: auto; gap: 0; }
  .nv-nav .nv-link:not(.nv-link-cta) { display: none; }

  .nv-menu {
    display: block;
    flex: none;
    position: static;
  }
  .nv-menu-toggle { list-style: none; }
  .nv-menu-toggle::-webkit-details-marker { display: none; }
  .nv-menu-toggle::marker { content: ""; }

  /* two lines that cross when the menu opens */
  .nv-menu-glyph { width: 16px; height: 16px; overflow: visible; }
  .nv-menu-glyph path {
    transform-box: fill-box;
    transform-origin: center;
    transition: transform 200ms cubic-bezier(0.77, 0, 0.175, 1);
  }
  .nv-menu.nv-open .nv-mg-a,
  .nv-menu[open]:not(.nv-js) .nv-mg-a { transform: translateY(2.5px) rotate(45deg); }
  .nv-menu.nv-open .nv-mg-b,
  .nv-menu[open]:not(.nv-js) .nv-mg-b { transform: translateY(-2.5px) rotate(-45deg); }

  /* --- the panel ---------------------------------------------------------- */
  .nv-menu-panel {
    position: absolute;
    top: calc(100% + var(--u));
    right: max(16px, env(safe-area-inset-right));
    width: min(280px, calc(100vw - 32px));
    padding: 4px;
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: var(--r-m);
    box-shadow: var(--shadow-1), var(--shadow-2);
    transform-origin: top right;
    /* closed */
    opacity: 0;
    transform: scale(0.96) translateY(-4px);
    visibility: hidden;
    pointer-events: none;
    transition:
      opacity 140ms ease-out,
      transform 140ms ease-out,
      visibility 0s linear 140ms;
  }
  .nv-menu.nv-open .nv-menu-panel,
  .nv-menu[open]:not(.nv-js) .nv-menu-panel {
    opacity: 1;
    transform: none;
    visibility: visible;
    pointer-events: auto;
    transition:
      opacity 200ms cubic-bezier(0.23, 1, 0.32, 1),
      transform 200ms cubic-bezier(0.23, 1, 0.32, 1),
      visibility 0s;
  }

  .nv-items { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }

  /* --- the rows ------------------------------------------------------------ */
  .nv-item {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 1.5);
    height: 44px;
    padding: 0 calc(var(--u) * 1.5);
    border-radius: var(--r-s);
    font: 500 15px/20px var(--sans);
    color: var(--ink);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: background-image 125ms cubic-bezier(0.24, 1, 0.4, 1),
                box-shadow 125ms cubic-bezier(0.24, 1, 0.4, 1);
  }
  .nv-item svg { width: 18px; height: 18px; flex: none; color: var(--ink-2); }
  .nv-item[aria-current="page"] {
    color: var(--indigo);
    background-color: color-mix(in srgb, var(--indigo) 10%, transparent);
    box-shadow: var(--btn-inset, inset 0 2px 4px rgb(0 0 0 / 0.24));
  }
  .nv-item[aria-current="page"] svg { color: var(--indigo); }
  .nv-item:focus { outline: none; }
  .nv-item:focus-visible {
    background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
    box-shadow: var(--btn-inset, inset 0 2px 4px rgb(0 0 0 / 0.24));
  }
  @media (hover: hover) {
    .nv-item:hover { background-image: linear-gradient(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.05)); }
  }
  @media not (pointer: coarse) {
    .nv-item:active { background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1)); }
  }
  .nv-item[data-press="on"] {
    background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
    box-shadow: var(--btn-inset, inset 0 2px 4px rgb(0 0 0 / 0.24));
  }
  @media (forced-colors: active) {
    .nv-item:focus-visible { outline: 2px solid CanvasText; }
    .nv-item[aria-current="page"] { border: 2px solid; }
  }

  #contact { scroll-margin-top: 88px; }
}

@media (max-width: 360px) {
  .nv-brand { font-size: 22px; }
}

@media (prefers-reduced-motion: reduce) {
  .nv-menu-panel, .nv-menu-glyph path { transition-duration: 0s !important; transition-delay: 0s !important; }
}

/* ==== parts/resource.css ==== */
/* ===========================================================================
   resource.css   prefix: rs-
   The resource pages, /r/<slug> (prerendered by scripts/build-resource-page.mjs).

   They were the last Aqua surface on the site (DESIGN.md, "Three page
   languages"). Now they are Bare: the homepage's nav, footer, tokens, Astryx
   buttons (button.css owns every .rs-btn state) and the blue card, plus what a
   long written guide needs that a page of figures did not: a reading column, a
   contents rail, prompt blocks you can copy, and a way on to the next resource.

   Endless ships ONE weight, so nothing here asks for bold: emphasis is a step
   up in value (ink-2 copy, ink for strong), never a synthetic weight.
   Type stays on the scale: 11 · 12 · 13 · 15 · 17 · 22 · 30, then clamp().
   =========================================================================== */

.rs { font-synthesis: none; }
/* the resource's type colour: the library pastel's own text colour,
   used for small marks only: section numbers, the contents marker, quote rules */
.rs-prompts   { --rs-accent: #00458c; }
.rs-skills    { --rs-accent: #700084; }
.rs-guides    { --rs-accent: #0c5700; }
.rs-playbooks { --rs-accent: #584400; }
/* base.css hides horizontal overflow on body, which makes body a scroll
   container and quietly kills position: sticky (the contents rail). clip
   guards the same overflow without becoming one. */
body:has(> main.rs) { overflow-x: clip; }

/* --- head --------------------------------------------------------------------- */

.rs-head { padding-block: calc(var(--u) * 16) calc(var(--u) * 5); }
.rs-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--u);
  margin-bottom: calc(var(--u) * 3);
}
.rs-crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--u);
  align-items: center;
  margin: 0;
}
.rs-crumbs a { color: inherit; text-decoration: none; border-bottom: 1px solid transparent; }
.rs-crumbs a:focus-visible { border-bottom-color: currentColor; outline: none; }
@media (hover: hover) and (pointer: fine) {
  .rs-crumbs a:hover { color: var(--ink-2); border-bottom-color: currentColor; }
}
.rs-h1 { max-width: 20ch; }
.rs-meta {
  margin: calc(var(--u) * 3) 0 0;
  font: 400 12px/1.6 var(--mono);
  letter-spacing: 0.04em;
  color: var(--ink-3);
}
.rs-meta a { color: var(--ink-2); text-decoration: none; }
.rs-meta span { white-space: nowrap; }
.rs-meta i { font-style: normal; }
.rs-apps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--u) calc(var(--u) * 2);
  margin-top: calc(var(--u) * 3);
}
.rs-apps-k { font: 500 11px/1 var(--mono); letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3); }
.rs-app { display: inline-flex; align-items: center; gap: var(--u); font: 400 15px/1 var(--sans); color: var(--ink); }
.rs-actions {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--u) * 1.5);
  margin-top: calc(var(--u) * 4);
}

/* --- the reading grid: column + contents rail ----------------------------------- */

.rs-grid {
  display: grid;
  grid-template-columns: minmax(0, 720px);
  gap: calc(var(--u) * 10);
  padding-bottom: calc(var(--u) * 12);
}
@media (min-width: 1080px) {
  .rs-grid { grid-template-columns: minmax(0, 720px) 232px; justify-content: space-between; }
}

.rs-hero {
  margin: 0 0 calc(var(--u) * 6);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-m);
  overflow: hidden;
  background: var(--card);
}
.rs-hero img { display: block; width: 100%; height: auto; }

/* --- the article ---------------------------------------------------------------- */

.rs-article {
  min-width: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.7;
  color: var(--ink-2);
}
.rs-article > :first-child { margin-top: 0; }
.rs-article p { margin: 0 0 calc(var(--u) * 2.5); }
.rs-article strong, .rs-article b { font-weight: 400; color: var(--ink); }
.rs-article em { font-style: italic; }
.rs-article a { color: var(--indigo); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* a section: the PDF's page. A hairline and real air, then the heading. */
.rs-article h2 {
  margin: calc(var(--u) * 10) 0 calc(var(--u) * 3);
  padding-top: calc(var(--u) * 5);
  border-top: 1px solid var(--rule-2);
  font: 400 30px/1.2 var(--sans);
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
  scroll-margin-top: calc(var(--u) * 12);
}
.rs-article h2 .rs-num {
  display: block;
  margin-bottom: calc(var(--u) * 1.5);
  font: 500 12px/1 var(--mono);
  letter-spacing: 0.12em;
  color: var(--rs-accent, var(--ink-3));
}
.rs-h2-row { display: flex; align-items: center; gap: calc(var(--u) * 2); }
.rs-h3-row { display: flex; align-items: center; gap: calc(var(--u) * 1.5); }
.rs-article h3 {
  margin: calc(var(--u) * 5) 0 calc(var(--u) * 1.5);
  font: 400 22px/1.3 var(--sans);
  letter-spacing: -0.01em;
  color: var(--ink);
  scroll-margin-top: calc(var(--u) * 12);
}
.rs-article h4 {
  margin: calc(var(--u) * 4) 0 var(--u);
  font: 400 17px/1.4 var(--sans);
  color: var(--ink);
}

.rs-article :is(ul, ol):not(.rv, .rv-steps, .rv-hub-spokes) { margin: 0 0 calc(var(--u) * 2.5); padding-left: calc(var(--u) * 3); }
.rs-article :is(ul, ol):not(.rv, .rv-steps, .rv-hub-spokes) > li { margin: var(--u) 0; padding-left: 4px; }
.rs-article ol.rv-steps > li > p { margin: 0; }
.rs-article li > p { margin: 0; }
.rs-article ul li::marker { color: var(--ink-3); }
.rs-article ol li::marker { font: 500 13px/1 var(--mono); color: var(--ink-3); }

.rs-article code {
  font: 400 0.82em/1 var(--mono);
  padding: 2px 6px;
  border-radius: var(--r-xs);
  background: var(--muted);
  color: var(--ink);
  overflow-wrap: anywhere;
}
.rs-article a code { color: inherit; }

.rs-article blockquote {
  margin: 0 0 calc(var(--u) * 2.5);
  padding: 2px 0 2px calc(var(--u) * 2.5);
  border-left: 2px solid var(--rs-accent, var(--ink));
  color: var(--ink);
}
.rs-article blockquote p:last-child { margin-bottom: 0; }

.rs-article hr { border: 0; border-top: 1px solid var(--rule-2); margin: calc(var(--u) * 5) 0; }
.rs-article img:not(.rv-ico img) { max-width: 100%; height: auto; border-radius: var(--r-m); }

/* a prompt or a command: a paper card you copy from, not a terminal */
.rs-code {
  margin: 0 0 calc(var(--u) * 3);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-m);
  background: var(--card);
  overflow: hidden;
}
.rs-code-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--u);
  padding: 6px 6px 6px calc(var(--u) * 1.5);
  border-bottom: 1px solid var(--rule-2);
}
.rs-code-bar .resource-badge { height: 22px; font-size: 12px; }
.rs-code pre {
  margin: 0;
  padding: calc(var(--u) * 2) calc(var(--u) * 2.5);
  font: 400 13px/1.7 var(--mono);
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  tab-size: 2;
}
.rs-code-diagram pre { white-space: pre; overflow-wrap: normal; overflow-x: auto; }
.rs-code pre code { font: inherit; padding: 0; background: none; border-radius: 0; color: inherit; }

.rs-table { margin: 0 0 calc(var(--u) * 3); overflow-x: auto; }
.rs-article table { width: 100%; border-collapse: collapse; font-size: 15px; line-height: 1.5; }
.rs-article th {
  padding: var(--u) calc(var(--u) * 1.5) var(--u) 0;
  border-bottom: 1px solid var(--ink-3);
  font: 500 11px/1.4 var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: left;
  color: var(--ink-3);
  vertical-align: bottom;
}
.rs-article td {
  padding: calc(var(--u) * 1.5) calc(var(--u) * 1.5) calc(var(--u) * 1.5) 0;
  border-bottom: 1px solid var(--rule-2);
  vertical-align: top;
}

/* the image pairs some guides draw (input, arrow, output). The PDF styles
   them inline; the page rewrites those styles to these classes. */
.rs-pair {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: calc(var(--u) * 2);
  margin: 0 0 calc(var(--u) * 3);
}
.rs-fig { margin: 0; text-align: center; min-width: 0; flex: 1 1 0; max-width: 300px; }
.rs-fig-frame {
  padding: 6px;
  border: 1px solid var(--rule-2);
  border-radius: var(--r-m);
  background: var(--card);
}
.rs-fig-img { display: block; width: 100%; height: auto; border-radius: calc(var(--r-m) - 3px); }
.rs-fig-cap {
  margin-top: var(--u);
  font: 500 11px/1.4 var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.rs-pair-arrow { flex: none; padding-bottom: calc(var(--u) * 5); font: 400 22px/1 var(--mono); color: var(--ink-3); }
.rs-article p:has(> .rs-btn:only-child) { margin: calc(var(--u) * 1) 0 calc(var(--u) * 3); }
.rs-article a.rs-btn { text-decoration: none; }

/* --- contents rail ---------------------------------------------------------------- */

.rs-toc { display: none; }
@media (min-width: 1080px) {
  .rs-toc { display: block; }
  .rs-toc-in {
    position: sticky;
    top: calc(var(--u) * 12);
    max-height: calc(100vh - var(--u) * 16);
    overflow-y: auto;
  }
}
.rs-toc-h {
  margin: 0 0 calc(var(--u) * 2);
  font: 500 11px/1 var(--mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.rs-toc ol { list-style: none; margin: 0; padding: 0; border-left: 1px solid var(--rule-2); }
.rs-toc li { margin: 0; }
.rs-toc ol a {
  display: flex;
  align-items: center;
  gap: var(--u);
  margin-left: -1px;
  padding: 6px 0 6px calc(var(--u) * 2);
  border-left: 1px solid transparent;
  font: 400 15px/1.35 var(--sans);
  color: var(--ink-3);
  text-decoration: none;
  transition: color 120ms ease, border-color 120ms ease;
}
.rs-toc ol a[aria-current="true"] { color: var(--ink); border-left: 2px solid var(--rs-accent, var(--ink)); padding-left: calc(var(--u) * 2 - 1px); }
@media (hover: hover) and (pointer: fine) {
  .rs-toc ol a:hover { color: var(--ink); }
}
.rs-toc-foot { margin-top: calc(var(--u) * 3); display: grid; gap: var(--u); justify-items: start; }

/* --- after the guide: the Lab card, the newsletter, the next resources ------------------ */

.rs-end {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: calc(var(--u) * 3);
  padding-block: calc(var(--u) * 4) calc(var(--u) * 12);
  border-top: 1px solid var(--rule-2);
}
@media (min-width: 900px) {
  .rs-end { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: stretch; }
}
.rs-door { display: grid; min-width: 0; }
/* the Lab card's own drift (boxed.css sets these per homepage figure; this card is not one) */
.rs-door .fg-copy::before,
.rs-door .fg-copy::after {
  --fg-liquid: url(#oj-liquid-b);
  --fg-tide: fg-tide-b;  --fg-tide-dur: 73s;  --fg-tide-delay: -29s;
  --fg-flow: fg-flow-b;  --fg-flow-dur: 47s;  --fg-flow-delay: -19s;
}
/* the homepage sizes this card for its figure band (480px, a grid cell of its own); here it fills its half */
.rs-end .fg-copy { width: auto; max-width: none; height: 100%; margin: 0; place-self: stretch; min-height: 300px; display: flex; flex-direction: column; justify-content: flex-end; }
.rs-end .fg-copy .fg-h { color: #fff; }
.rs-end .fg-copy .fg-p { color: rgba(255, 255, 255, 0.86); max-width: 36ch; }
.rs-end .fg-copy .rs-btn { align-self: flex-start; margin-top: calc(var(--u) * 4); }

.rs-card {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: calc(var(--u) * 5);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-l);
  background: var(--card);
}
.rs-card-k {
  margin: 0 0 calc(var(--u) * 2);
  font: 500 11px/1 var(--mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.rs-card-h { margin: 0; font: 400 clamp(28px, 3.4vw, 44px)/1.12 var(--sans); letter-spacing: -0.02em; color: var(--ink); }
.rs-card-p { margin: calc(var(--u) * 2) 0 0; font-size: 17px; line-height: 1.6; color: var(--ink-2); max-width: 40ch; }
.rs-form { display: flex; flex-wrap: wrap; gap: var(--u); margin-top: calc(var(--u) * 4); }
.rs-field {
  flex: 1 1 220px;
  min-width: 0;
  height: var(--btn-h-lg, 36px);
  padding: 0 12px;
  border: 1px solid var(--rule-2);
  border-radius: var(--r-s);
  background: var(--paper);
  font: 400 15px/1 var(--sans);
  color: var(--ink);
  outline: none;
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.06);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.rs-field::placeholder { color: var(--ink-3); }
.rs-field:focus { border-color: var(--ink-3); box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.12); }
.rs-note { margin: calc(var(--u) * 1.5) 0 0; font: 400 12px/1.5 var(--mono); color: var(--ink-3); }
.rs-note[data-state="ok"] { color: var(--ink); }

.rs-more { padding-bottom: calc(var(--u) * 8); }
.rs-more-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--u) * 2);
  margin-bottom: calc(var(--u) * 4);
}
.rs-more-h { margin: 0; font: 400 30px/1.2 var(--sans); letter-spacing: -0.02em; color: var(--ink); }
.rs-more-all { font: 500 12px/1 var(--mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--indigo); text-decoration: none; }
.rs-more-list { list-style: none; margin: 0; padding: 0; display: grid; gap: calc(var(--u) * 3); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .rs-more-list { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.rs-item {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: calc(var(--u) * 3);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-m);
  background: var(--card);
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease;
}
.rs-item:focus-visible { outline: none; border-color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .rs-item:hover { border-color: var(--ink-3); }
  .rs-item:hover .rs-item-t { color: var(--indigo); }
}
.rs-item-tags { display: flex; }
.rs-item-t { margin: calc(var(--u) * 2) 0 0; font: 400 22px/1.25 var(--sans); letter-spacing: -0.01em; color: var(--ink); transition: color 120ms ease; }
.rs-item-p {
  margin: var(--u) 0 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.rs-item-u { margin-top: auto; padding-top: calc(var(--u) * 2); font: 400 12px/1.4 var(--mono); color: var(--ink-3); }

/* --- phones ---------------------------------------------------------------------- */

@media (max-width: 720px) {
  .rs-head { padding-block: calc(var(--u) * 12) calc(var(--u) * 4); }
  .rs-actions .rs-btn { flex: 1 1 auto; }
  .rs-article { font-size: 17px; }
  .rs-article h2 { margin-top: calc(var(--u) * 7); padding-top: calc(var(--u) * 4); font-size: 26px; }
  .rs-hero { margin-bottom: calc(var(--u) * 4); }
  .rs-pair { gap: var(--u); }
  .rs-card { padding: calc(var(--u) * 4) calc(var(--u) * 3); }
  /* The homepage turns a .fg-copy into words on paper on a phone (its tile
     carries the colour there). This card has no tile, so it keeps its blue. */
  .rs-end .fg-copy { min-height: 0; padding: calc(var(--u) * 8) calc(var(--u) * 3) calc(var(--u) * 4); }
  .rs-end .fg-copy::before, .rs-end .fg-copy::after, .rs-end .fg-copy .fg-page-dots { display: block; }
  .rs-end .fg-copy .fg-page-dots { display: flex; }
  .rs-end .fg-copy .fg-h { color: #fff; font-size: 28px; }
  .rs-end .fg-copy .fg-p { color: rgba(255, 255, 255, 0.86); }
}

/* ==== parts/journal.css ==== */
/* ===========================================================================
   journal.css   prefix: jn-
   The blog (/blog, /blog/<slug>) and the newsletter (/newsletter and its
   issues), rebuilt in Bare on Oct 11 2026. They were the last two other
   worlds under the nav: the iPod skeuomorph and the Aqua window. A post is
   a resource page's article (rs- classes, resource.css); this file adds only
   what a stream of writing needs: the shelves, a lead story, a grid of
   posts, the issue frame.

   Shelf colours are the library's pastel pairs, one per shelf, always on a
   badge with its shape (DESIGN.md, colour jobs): Agents purple, Retrieval &
   data green, Prompting & context blue, Production amber.
   =========================================================================== */

/* --- shelves ------------------------------------------------------------------- */

.badge-shelf-agents     { color: #700084; background-color: #eccef3; }
.badge-shelf-retrieval  { color: #0c5700; background-color: #c5e5c0; }
.badge-shelf-prompts    { color: #00458c; background-color: #c4ddfb; }
.badge-shelf-production { color: #584400; background-color: #f8da9d; }
.rs-shelf-agents     { --rs-accent: #700084; }
.rs-shelf-retrieval  { --rs-accent: #0c5700; }
.rs-shelf-prompts    { --rs-accent: #00458c; }
.rs-shelf-production { --rs-accent: #584400; }

/* topic tags under a post's head: the quiet file token */
.jn-topics { display: flex; flex-wrap: wrap; gap: var(--u); margin-top: calc(var(--u) * 3); }

/* --- a post's own furniture ------------------------------------------------------- */

.rs-article figure { margin: calc(var(--u) * 5) 0; }
.rs-article figure svg { display: block; width: 100%; height: auto; }
.rs-article figure img { display: block; width: 100%; border: 1px solid var(--rule-2); }
.rs-article figcaption {
  margin-top: calc(var(--u) * 1.5);
  font: 400 12px/1.5 var(--mono);
  color: var(--ink-3);
  text-align: center;
}
.jn-faq { margin-top: calc(var(--u) * 8); }
.jn-faq .fq-head { margin-bottom: calc(var(--u) * 3); }

/* --- listings: the lead story, then the grid ------------------------------------- */

.jn-tabs { display: flex; flex-wrap: wrap; gap: var(--u); margin-top: calc(var(--u) * 4); }
.jn-tabs a.resource-badge { height: 28px; padding: 0 10px; font-size: 13px; }
.jn-tabs a.resource-badge .jn-n { font: 400 11px/1 var(--mono); opacity: .7; margin-left: 4px; }
.jn-tabs a.resource-badge[aria-current="true"] { box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2); }

.jn-feature {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: calc(var(--u) * 4);
  align-items: center;
  margin-bottom: calc(var(--u) * 6);
  padding: calc(var(--u) * 3);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-l);
  background: var(--card);
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease;
}
@media (min-width: 900px) { .jn-feature { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); padding: calc(var(--u) * 3); } }
.jn-feature-img, .jn-card-img {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: calc(var(--r-m) - 1px); border: 1px solid var(--rule-2); background: var(--muted);
}
.jn-feature-k { display: flex; flex-wrap: wrap; align-items: center; gap: var(--u); }
.jn-feature-t { margin: calc(var(--u) * 2) 0 0; font: 400 clamp(26px, 2.6vw, 34px)/1.15 var(--sans); letter-spacing: -0.02em; color: var(--ink); transition: color 120ms ease; text-wrap: balance; }
.jn-feature-p { margin: calc(var(--u) * 1.5) 0 0; font-size: 17px; line-height: 1.6; color: var(--ink-2); }
.jn-meta { margin-top: calc(var(--u) * 2); font: 400 12px/1.4 var(--mono); color: var(--ink-3); }
.jn-feature-go { margin-top: calc(var(--u) * 3); }

.jn-grid { list-style: none; margin: 0; padding: 0 0 calc(var(--u) * 12); display: grid; gap: calc(var(--u) * 3); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 720px) { .jn-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1080px) { .jn-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.jn-grid > li[hidden] { display: none; }
.jn-card {
  display: flex; flex-direction: column; height: 100%;
  padding: calc(var(--u) * 1.5) calc(var(--u) * 1.5) calc(var(--u) * 2.5);
  border: 1px solid var(--rule-2); border-radius: var(--r-m); background: var(--card);
  color: inherit; text-decoration: none; transition: border-color 120ms ease;
}
.jn-card-body { display: flex; flex-direction: column; align-items: flex-start; flex: 1; padding: calc(var(--u) * 2) calc(var(--u) * 1) 0; }
.jn-card-t { margin: calc(var(--u) * 1.5) 0 0; font: 400 20px/1.3 var(--sans); letter-spacing: -0.01em; color: var(--ink); transition: color 120ms ease; }
.jn-card-p {
  margin: var(--u) 0 0; font-size: 15px; line-height: 1.55; color: var(--ink-2);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.jn-card .jn-meta { margin-top: auto; padding-top: calc(var(--u) * 2); }
:is(.jn-feature, .jn-card):focus-visible { outline: none; border-color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  :is(.jn-feature, .jn-card):hover { border-color: var(--ink-3); }
  :is(.jn-feature, .jn-card):hover :is(.jn-feature-t, .jn-card-t) { color: var(--indigo); }
}
.jn-sub { margin-bottom: calc(var(--u) * 6); }
@media (min-width: 900px) {
  .jn-sub .rs-card { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 440px); column-gap: calc(var(--u) * 6); align-items: end; }
  .jn-sub .rs-card > :not(.rs-form, .rs-note) { grid-column: 1; }
  .jn-sub .rs-form { grid-column: 2; grid-row: 1 / span 3; align-self: end; margin-top: 0; }
  .jn-sub .rs-note { grid-column: 2; }
}
.jn-sub .rs-card-h { font-size: clamp(24px, 2.4vw, 30px); }

/* --- an issue: the email exactly as sent, framed on the page ------------------------- */

.jn-mail-wrap { padding-bottom: calc(var(--u) * 6); }
.jn-mail {
  max-width: 680px;
  border: 1px solid var(--rule-2);
  border-radius: var(--r-l);
  overflow: hidden;
  background: #FCFBF8;
  box-shadow: var(--shadow-card);
}
.jn-pager { display: flex; flex-wrap: wrap; justify-content: space-between; gap: calc(var(--u) * 2); max-width: 680px; margin-top: calc(var(--u) * 4); }
.jn-pager a { display: flex; flex-direction: column; gap: 4px; max-width: 48%; color: inherit; text-decoration: none; }
.jn-pager a.jn-next { text-align: right; margin-left: auto; }
.jn-pager .k { font: 500 11px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3); }
.jn-pager .t { font: 400 17px/1.35 var(--sans); color: var(--ink); }
@media (hover: hover) and (pointer: fine) { .jn-pager a:hover .t { color: var(--indigo); } }

@media (max-width: 720px) {
  .jn-feature { padding: calc(var(--u) * 1.5); gap: calc(var(--u) * 2); }
  .jn-feature > div { padding: 0 calc(var(--u) * 1) calc(var(--u) * 1.5); }
  .jn-pager a { max-width: 100%; }
}
.jn-feature-k .pg-eyebrow { margin: 0; }
.jn-feature[hidden] { display: none; }
/* the FAQ's own heading, not an article section: no rule, no section air */
.rs-article .jn-faq h2.fq-h { margin: 0; padding: 0; border: 0; font-size: clamp(28px, 3.2vw, 42px); }

/* ==== src/styles/library.css (badges) ==== */
.resource-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 24px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--r-xs, 6px);
  font: 500 12px/20px var(--sans);
  letter-spacing: normal;
  white-space: nowrap;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-image 125ms cubic-bezier(0.24, 1, 0.4, 1);
}
/* a type tag is also a filter link: Astryx's 5% hover and 10% press layers */
@media (hover: hover) {
  a.resource-badge:hover { background-image: linear-gradient(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.05)); }
}
@media not (pointer: coarse) {
  a.resource-badge:active { background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1)); }
}
a.resource-badge[data-press="on"] { background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1)); }
a.resource-badge:focus { outline: none; }
a.resource-badge:focus-visible {
  background-image: linear-gradient(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.1));
  box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.2);
}
/* 44px for a thumb around a 24px tag; nothing moves */
@media (pointer: coarse) {
  a.resource-badge::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: max(100%, 44px);
    height: 44px;
    transform: translate(-50%, -50%);
  }
}
.resource-badge svg {
  width: 14px;
  height: 14px;
  flex: none;
}
/* One hue per type group, so the shelf is scannable by colour before it is
   read. The pairs are the OpusJake Astryx theme's own categorical tokens
   (--color-background-* / --color-text-*), all AA. */
.badge-prompts   { color: #00458c; background-color: #c4ddfb; }
.badge-skills    { color: #700084; background-color: #eccef3; }
.badge-guides    { color: #0c5700; background-color: #c5e5c0; }
.badge-playbooks { color: #584400; background-color: #f8da9d; }
/* The format tags stay quiet: they say what the file is, not what it is for.
   Astryx's neutral is a 6% ink tint, which stays warm on the oatmeal. */
.badge-file      { color: var(--ink-2); background-color: rgb(0 0 0 / 0.06); }

/* ==== scripts/lib/resource-visuals.mjs ==== */
.rs { --rv-ink: var(--ink); --rv-ink-2: var(--ink-2); --rv-ink-3: var(--ink-3); --rv-card: var(--card); --rv-line: var(--rule-2);
  --rv-wire: var(--rs-accent, var(--ink-3)); --rv-accent: var(--rs-accent, var(--ink-3)); --rv-sans: var(--sans); --rv-mono: var(--mono);
  --rv-r: var(--r-m); --rv-code: var(--muted); }

.rv-ico { display: inline-flex; align-items: center; justify-content: center; flex: none; vertical-align: middle;
  width: var(--rv-s); height: var(--rv-s); border-radius: calc(var(--rv-s) * 0.24); background: #fff;
  box-shadow: 0 0 0 1px var(--rv-line, rgba(0,0,0,.12)), 0 1px 2px rgba(31,29,58,.10); overflow: hidden; }
.rv-ico img { display: block; width: 70%; height: 70%; object-fit: contain; }
.rv-ico-tile img { width: 100%; height: 100%; object-fit: cover; }
.rv-ico-16 { --rv-s: 16px; } .rv-ico-20 { --rv-s: 20px; } .rv-ico-24 { --rv-s: 24px; }
.rv-ico-32 { --rv-s: 32px; } .rv-ico-48 { --rv-s: 48px; } .rv-ico-64 { --rv-s: 64px; }
.rv-ico-mono { background: var(--rv-card); font: 500 calc(var(--rv-s) * 0.42)/1 var(--rv-mono); color: var(--rv-ink-2); }
.rv-cell { display: inline-flex; align-items: center; gap: 8px; }
.rv-lead .rv-ico { margin: -3px 7px 0 0; }

/* hub: the centre, its wires, the spokes */
.rv-hub { display: grid; grid-template-columns: minmax(0, 168px) 72px minmax(0, 1fr); align-items: stretch;
  margin: 8px 0 28px; padding: 20px; border: 1px solid var(--rv-line); border-radius: var(--rv-r); background: var(--rv-ground, transparent); }
.rv-hub-center { align-self: center; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
  padding: 20px 12px; border: 1px solid var(--rv-line); border-radius: var(--rv-r); background: var(--rv-card); }
.rv-hub-name { font: 400 22px/1.2 var(--rv-sans); color: var(--rv-ink); }
.rv-hub-center-text .rv-hub-name { font-size: 26px; }
.rv-hub-note { font: 500 11px/1.4 var(--rv-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--rv-ink-3); }
.rv-hub-wires { width: 100%; height: 100%; overflow: visible; }
.rv-hub-wires path { fill: none; stroke: var(--rv-wire); stroke-width: 1.5; vector-effect: non-scaling-stroke; stroke-dasharray: 4 4; }
.rv-hub-spokes { list-style: none; margin: 0; padding: 0; display: grid; grid-auto-rows: minmax(56px, 1fr); gap: 10px; }
.rv-spoke { position: relative; display: flex; align-items: center; gap: 12px; margin: 0; padding: 10px 14px;
  border: 1px solid var(--rv-line); border-radius: var(--rv-r); background: var(--rv-card); }
.rv-spoke::before { content: ""; position: absolute; left: -5px; top: 50%; width: 8px; height: 8px; margin-top: -4px;
  border-radius: 50%; background: var(--rv-wire); }
.rv-spoke-t { display: flex; flex-direction: column; min-width: 0; }
.rv-spoke-t b { font: 400 17px/1.3 var(--rv-sans); color: var(--rv-ink); }
.rv-spoke-t span { font: 400 14px/1.45 var(--rv-sans); color: var(--rv-ink-2); }

/* flow: numbered cards and the arrows between them */
.rv-flow { list-style: none; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 28px;
  margin: 8px 0 28px; padding: 0; counter-reset: none; }
.rv-step { position: relative; display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 14px 14px 16px;
  border: 1px solid var(--rv-line); border-radius: var(--rv-r); background: var(--rv-card); }
.rv-step + .rv-step::before { content: ""; position: absolute; left: -24px; top: 50%; width: 20px; height: 1.5px; background: var(--rv-wire); }
.rv-step + .rv-step::after { content: ""; position: absolute; left: -9px; top: 50%; margin-top: -4px;
  border: 4.5px solid transparent; border-left: 6px solid var(--rv-wire); border-right: 0; }
.rv-step-k { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
.rv-step-n { font: 500 12px/1 var(--rv-mono); letter-spacing: .08em; color: var(--rv-accent); }
.rv-step-t { font: 400 17px/1.3 var(--rv-sans); color: var(--rv-ink); overflow-wrap: anywhere; }
.rv-step-p { font: 400 14px/1.45 var(--rv-sans); color: var(--rv-ink-2); }
.rv-step code, .rv-spoke code { font: 400 .86em/1 var(--rv-mono); padding: 2px 5px; border-radius: 5px; background: var(--rv-code, rgba(0,0,0,.06)); color: var(--rv-ink); }
/* five steps or more: a numbered grid, three across, read left to right */
.rv-flow-long { grid-auto-flow: row; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-columns: auto; gap: 12px; }
.rv-flow-long .rv-step + .rv-step::before, .rv-flow-long .rv-step + .rv-step::after { display: none; }

/* setup steps: a numbered rail */
ol.rv-steps { list-style: none; counter-reset: rv; margin: 4px 0 24px; padding: 0; }
ol.rv-steps > li { position: relative; counter-increment: rv; margin: 0; padding: 0 0 18px 48px; }
ol.rv-steps > li::before { content: counter(rv, decimal-leading-zero); position: absolute; left: 0; top: 0;
  width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%;
  border: 1px solid var(--rv-line); background: var(--rv-card); font: 500 12px/1 var(--rv-mono); color: var(--rv-accent); }
ol.rv-steps > li::after { content: ""; position: absolute; left: 15px; top: 34px; bottom: 4px; width: 1px; background: var(--rv-line); }
ol.rv-steps > li:last-child { padding-bottom: 0; }
ol.rv-steps > li:last-child::after { display: none; }
ol.rv-steps > li::marker { content: none; }

.rv-ico img { border-radius: 0; }
@media (max-width: 720px) {
.rv-hub { grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 16px; }
.rv-hub-center { flex-direction: row; justify-content: flex-start; text-align: left; padding: 14px; }
.rv-hub-center .rv-ico-64 { --rv-s: 48px; }
.rv-hub-wires { display: none; }
.rv-hub-spokes { grid-auto-rows: auto; padding-left: 20px; border-left: 1.5px dashed var(--rv-wire); margin-left: 24px; }
.rv-spoke::before { left: -26px; }
.rv-flow, .rv-flow-long { grid-auto-flow: row; grid-template-columns: none; grid-auto-columns: auto; gap: 14px; }
.rv-flow-long .rv-step + .rv-step::before { display: block; }
.rv-flow .rv-step + .rv-step::before { left: 22px; top: -14px; width: 1.5px; height: 12px; }
.rv-flow .rv-step + .rv-step::after { display: none; }
}