/* ==========================================================================
   FILAMENTAL — SITE DESIGN SYSTEM
   One stylesheet. Every page reads from it. Change a value here, it changes
   everywhere, which is the whole point.

   Sections:
     0. Fonts           IBM Plex Sans and Mono, self-hosted, no third-party request
     1. Tokens          colour, type, space, the rail, and the derived header
     2. Base            page shell, the rail grid, one left edge
     3. Typography      the rail label and header-statement hierarchy
     4. Controls        buttons, and one chip vocabulary
     5. Surfaces        ruled compartments, and the observation field
     6. Chrome          nav, footer
     7. Motion          all animation lives here, and nowhere else
     8. Long-form prose the utility pages only, scoped under .prose
     9. Help            search field, tips panel, topic list
    10. Pricing         the two plans
    11. Download        platform lists, install lines, checksums
    12. Spaces          the build sequence and the capability list
    13. FAQ             the question block that closes a sector page
    14. Changelog       generated from the application own release notes
    15. Closing plate   the last block on 23 pages, and the download ask
   ========================================================================== */


/* 0. FONTS =================================================================
   Self-hosted on purpose. Loading these from fonts.googleapis.com sends every
   visitor's IP address to Google before a single word renders, which is not
   something a site with our privacy policy should be doing. These files are
   served from our own origin and the site makes no third-party request at all.

   TWO families as of 2026-08-22, per docs/DESIGN-REGISTER.md section 5. Outfit,
   Manrope, Gruppo and JetBrains Mono are gone with the previous register. IBM
   Plex Sans carries display and body; IBM Plex Mono carries every signal
   element: anchor labels, nav, buttons and readouts.

   Sans is one variable file per subset covering 300 to 600. Mono has no
   variable cut on Google Fonts (a wght@400..500 request 400s), so it ships as
   two static weights. 400 is nav and body signal, 500 is labels, buttons and
   the active state. Do not add a third: the register uses exactly these two.

   latin and latin-ext only. The site is English; cyrillic, greek and vietnamese
   are not shipped. Each file's unicode-range means a browser fetches only the
   subsets a page actually needs. See assets/fonts/README.md.

   font-display:swap means text paints immediately in the fallback and reflows
   when the face arrives. Never let a webfont hold up the first paint.
   ========================================================================== */

@font-face{ font-family:'IBM Plex Sans'; font-style:normal; font-weight:300 600; font-display:swap;
  src:url(../fonts/ibmplexsans-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF }

@font-face{ font-family:'IBM Plex Sans'; font-style:normal; font-weight:300 600; font-display:swap;
  src:url(../fonts/ibmplexsans-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD }

@font-face{ font-family:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap;
  src:url(../fonts/ibmplexmono-400-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF }

@font-face{ font-family:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap;
  src:url(../fonts/ibmplexmono-400-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD }

@font-face{ font-family:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap;
  src:url(../fonts/ibmplexmono-500-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF }

@font-face{ font-family:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap;
  src:url(../fonts/ibmplexmono-500-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD }

/* 1. TOKENS ================================================================
   Nothing below this block should contain a raw colour or a raw pixel size.
   If you want the site lighter, cooler, tighter, roomier, change it here.

   Rewritten 2026-08-22 for the register in docs/DESIGN-REGISTER.md. Read that
   file before changing anything here. The short version: the site is a
   scientific observation instrument, not a marketing page with a dark theme.

   TWO RULES GOVERN THIS BLOCK AND THEY ARE NOT STYLE PREFERENCES.

   1. Derived, not chosen (section 12). Where two numbers have to agree, one is
      calculated from the other. The bar height is not "66px", it is the mark
      plus its air. Change --mark-size and the header stays correct. The last
      build shipped proportion errors precisely because the wordmark, the bar
      and the button were each picked independently.

   2. Colour is placed by rule, not by taste (section 4). The palette is the
      hydrogen emission spectrum and each line has one job. This is not a
      decorative story, it is what decides where a colour is allowed to appear.
      Do not "tidy" these rules away.
   ========================================================================== */

:root{
  /* --- GROUND. One light cool grey. Laboratory, not archival.

         There is exactly one light ground and it does not alternate. The old
         build had three dark grounds cycling down the page so no two sections
         touched, which is a way of separating sections with colour. Sections
         are separated by hairlines and whitespace now. The -deep and -raised
         aliases are held here only until block 2 stops asking for them. --- */
  --colour-ground: #E4E7E9;

  /* --- OBSERVATION DARK. Reserved, not removed.

         A dark field on this site MEANS observation. It contains the product,
         the graph, a rendering of structure, or the literal text we hand to a
         model. Light is where we talk, dark is where we show. A reader learns
         that rule without being told, and every screenshot gains force from it.

         Never use it as a section separator, as decoration, or as a footer
         just because footers are dark. If you cannot say what is being
         observed inside it, it should not be dark. --- */
  --colour-observed:        #0B0D10;
  --colour-observed-rule:   #1B2026;   /* the gap between two views */

  /* There is no surface token, and that is the point. Nothing on this site is
     a filled or bordered container: compartments are made by alignment, shared
     baselines and hairlines that run to the edge rather than closing around
     content. --colour-surface, -raised, --colour-card, --colour-bezel and
     --colour-canvas are all deleted. If you find yourself wanting one back, you
     are drawing a card. */

  /* --- HAIRLINES. 1px, and they run to the edge of the container rather than
         around content. These are the only separator the site has. --- */
  --colour-line:        #C9CFD4;
  --colour-line-soft:   #D8DDE1;
  --colour-line-bright: #AEB6BD;   /* the one that has to be noticed */

  /* --- INK ON LIGHT. All three clear 4.5:1 on the ground above.

         The ramp is deliberately compressed and that is not an oversight. On a
         light ground with a real contrast gate there is very little room
         between "secondary" and "fails", so hierarchy here comes from SIZE AND
         WEIGHT rather than from greying text out. A quiet label is quiet
         because it is 11px mono uppercase, not because it is pale. That is
         also how the reference objects do it: a Braun panel does not fade its
         labels, it makes them small.

         The study at docs/register-study.html uses #8A9299 for the third step,
         which measures 2.54:1 and fails. Measured, not assumed. Do not put it
         back. --- */
  --colour-text:           #14171A;   /* 14.48:1 */
  --colour-text-secondary: #565E66;   /*  5.30:1 */
  --colour-text-muted:     #61666B;   /*  4.67:1 */

  /* --- INK ON THE OBSERVATION DARK. Same rule, other ground. --- */
  --colour-observed-text:      #EDEFF1;   /* 16.88:1 */
  --colour-observed-secondary: #98A2AA;   /*  7.49:1 */
  --colour-observed-muted:     #758089;   /*  4.82:1 */

  /* --- THE MARKER. H-alpha, 656.3nm. The only colour in the chrome.

         It means "this one" and it always means the same thing: the active nav
         item, the single point of attention in a view, an error. It is used
         sparingly. If you cannot say what a red mark is pointing at, remove it.

         Colour is never a surface fill. No coloured bands, no coloured nav, no
         coloured buttons. The chartreuse #D4FF00 that used to live here is
         retired, not parked: it fails contrast on a light ground and reads as
         highlighter pen.

         TWO tokens now, not three, and the red changed on 2026-08-26 when the
         brand was set from Scott's Affinity artboard. Stated in HSL because
         that is how he works in colour.

           --brand-red   hsl(0 100% 40%)   #CC0000
           --brand-ink   hsl(0   0% 10%)   #1A1A1A

         The old marker was #CE1B24 and needed a separate darker cut, #C41A22,
         to clear 4.5:1 as text on the light ground. The brand red measures
         4.74:1 there by itself, which is both better than the old mark's
         4.44:1 and enough for text, so the two collapsed into one and
         --colour-marker-ink is gone. It was already referenced nowhere.

         Rendering 656.3nm into sRGB is an approximation either way, so this is
         still H-alpha; it is a rounder tone-mapping of the same line.

         --colour-marker-lit CANNOT collapse with them. The brand red measures
         only 3.31:1 on the observation dark, which fails for text, so the
         lighter cut stays and is the one thing here that is not the brand
         value. Do not "unify" it. */
  --brand-red: hsl(0 100% 40%);   /* #CC0000 */
  --brand-ink: hsl(0 0% 10%);     /* #1A1A1A */

  --colour-marker:     var(--brand-red);   /* mark AND text on ground: 4.74:1 */
  --colour-marker-lit: #F7202B;            /* text on observed:        4.81:1 */

  /* --- OBSERVED COLOUR. The product's own identity, and dark fields ONLY.

         H-beta 486.1nm and H-delta 410.2nm. These must never appear on the
         light ground: cyan measures 1.14:1 there and violet 2.60:1, so they are
         very nearly invisible, and the restriction is what keeps the chrome and
         the observation cleanly separated. Measured, not asserted. --- */
  --colour-cyan:   #67EDEB;   /* 13.80:1 on the observation dark */
  --colour-purple: #9981D9;   /*  6.02:1 on the observation dark */

  /* These two are the observation field's palette and they appear on exactly
     one block of the site, the parallax hero in section 8. If you find them
     anywhere else, that is a bug rather than a variation. */

  /* --- TYPE. Two families, both IBM Plex. See block 0. --- */
  --font-display:  'IBM Plex Sans', system-ui, sans-serif;
  --font-body:     'IBM Plex Sans', system-ui, sans-serif;
  --font-signal:   'IBM Plex Mono', ui-monospace, monospace;
  --font-wordmark: var(--font-display);

  /* Statements are set light at large sizes with tight tracking and leading
     near 1.0. Plex needs far less negative tracking than Outfit did: -.035em
     on Plex closes the counters up and it stops reading as an instrument. */
  --type-statement: clamp(2.9rem, 6.2vw, 4.7rem);
  --type-heading-2: clamp(1.75rem, 3.2vw, 2.6rem);
  --type-heading-3: 1.375rem;
  --type-lede:      19px;
  --type-body:      17px;
  --type-small:     15px;
  --type-stat:      clamp(2.6rem, 4.4vw, 3.8rem);

  --weight-statement: 300;
  --weight-body:      400;
  --weight-signal:    500;

  --leading-tight: 1.02;
  --leading-body:  1.55;

  --tracking-statement: -.024em;

  /* --- SIGNAL. Every mono element on the site is one of these three sizes.

         Uppercase, tracked open, and small. This is the vocabulary that makes
         the page read as labelled rather than written: anchor labels, nav,
         buttons, readout keys, view titles, versions and coordinates. --- */
  --type-signal:       11.5px;   /* nav items and buttons */
  --type-signal-label: 11px;     /* rail anchor labels */
  --type-signal-small: 10.5px;   /* readout keys, view titles */
  --tracking-signal:   .13em;    /* nav and buttons */
  --tracking-label:    .16em;    /* labels and keys */

  /* --- OPTICAL: SITTING THE RAIL LABEL ON THE HEADING'S CAP LINE.

         align-items:start puts the label's BOX top level with the heading's
         BOX top, and a heading's cap line sits a long way below its box top
         because of half-leading plus the gap between the em box and the cap.
         At statement size that is nearly 19px, so the label floats.

         The drop is a function of the heading's own size and leading, so it is
         calculated rather than nudged by eye, and it stays correct if the type
         scale moves. The multiplier is half-leading plus Plex Sans's cap gap of
         roughly .24em: (line-height - 1) / 2 + .24.

           statement  leading 1.02  ->  .25
           heading-2  leading 1.08  ->  .28
           label      leading 1.45  ->  .465                            --- */
  --cap-drop-statement: calc(var(--type-statement)     * .25);
  --cap-drop-heading:   calc(var(--type-heading-2)     * .28);
  --cap-drop-label:     calc(var(--type-signal-label)  * .465);

  /* --- NUMERALS ARE PART OF THE DESIGN.

         Versions, counts, sizes, wavelengths and coordinates go on show, and
         they are set with tabular figures so a column of them lines up. This is
         half of what makes Braun, Nomos and Teenage Engineering read as
         instruments rather than as products. Apply --figures-tabular to
         anything numeric that sits in a column. --- */
  --figures-tabular: tabular-nums;

  /* --- SPACE. One scale, used for everything. --- */
  --space-1:4px;  --space-2:8px;   --space-3:12px;  --space-4:16px;
  --space-5:24px; --space-6:32px;  --space-7:48px;  --space-8:64px;
  --space-9:96px; --space-10:128px;--space-11:192px;

  /* --- LAYOUT.

         The rail is the structural change. Every section's mono anchor label
         sits in a fixed column OUTSIDE the text column, so the labels form one
         continuous edge down the page and the prose keeps a real measure.

         --layout-measure is the prose column BESIDE the rail, not the whole
         container. The old --layout-container-narrow was a 760px box with the
         label inside it; put a 200px rail in that and the text is 560px wide,
         which is a column of fragments. So the narrow wrapper is derived now:
         rail plus gap plus measure, and it can only ever be right. --- */
  --layout-rail:      200px;
  --layout-measure:   68ch;
  --layout-gutter:    48px;
  --layout-container: 1320px;
  --layout-section-y: clamp(64px, 7vw, 104px);

  /* --- HEADER, DERIVED (section 7).

         The old header failed because the wordmark, the bar height and the
         button were each chosen independently. Here the mark is the only
         chosen number and everything else follows it.

         The wordmark matches the bar's padding rather than the mark's height.
         At a full 24px it optically outweighs the mark standing next to it,
         and the brand has to outrank the button without shouting at its own
         logo. --- */
  --mark-size: 24px;
  --bar-pad:   21px;
  --layout-nav-height: calc(var(--mark-size) + var(--bar-pad) * 2);   /* 66px */
  --type-wordmark:     var(--bar-pad);                                /* 21px */

  /* --- CORNERS. Square, or 2px at most.

         No pill buttons, no rounded cards. --radius-small, -medium, -large and
         -pill are all deleted along with the last rule that asked for one. --- */
  --radius:      0;
  --radius-mark: 2px;

  /* --- ELEVATION IS DELETED, NOT RESTYLED.

         "Light falls from above, so anything raised catches its top edge,
         carries a downward sheen and casts below" was the previous build's
         stated rule. It is consumer OS chrome and it is gone: no drop shadows,
         no inset sheens, no glows, no lift.

         There are no --elev, --sheen, --wash or --edge-catch tokens any more.
         They were held at `none` through the conversion so the rules still
         asking for them resolved to nothing visible rather than becoming
         invalid, and they went with the last of those rules. --- */

  /* --- MOTION. Every duration and distance on the site is one of these.

         Motion informs, never decorates, and all of it lives in block 7. The
         hero light no longer breathes, because there is no hero light: it was
         a property of the dark build. --motion-rise comes down from 14px, as
         a reveal that travels far reads as an entrance on a light ground. --- */
  --motion-quick:  140ms;
  --motion-ease:   260ms;
  --motion-slow:   600ms;
  --motion-rise:   8px;
  --motion-curve:  cubic-bezier(.22,.61,.36,1);
}


/* 2. BASE =================================================================
   The rail lives here, and it is the structural change of the whole register.
   ========================================================================= */

*, *::before, *::after{ box-sizing:border-box }

body{
  margin:0;
  background:var(--colour-ground);
  color:var(--colour-text);
  font-family:var(--font-body);
  font-size:var(--type-body);
  line-height:var(--leading-body);
  -webkit-font-smoothing:antialiased;
}

img, svg{ max-width:100%; height:auto }

/* ONE CONTAINER, ONE LEFT EDGE.

   .wrap and .wrap-narrow are the same width now, and that is the point. The
   old build made narrow a smaller BOX, so a prose section and a wide section
   started at two different x positions and the page had two left edges. Here
   both share the container and the rail, and narrow differs only in how wide
   its CONTENT column is allowed to get. The labels down the rail form one
   continuous edge, and so does the text beside them. */
.wrap, .wrap-narrow{
  width:100%; max-width:var(--layout-container);
  margin-inline:auto; padding-inline:var(--layout-gutter);
}
.layer{ position:relative; z-index:2 }

/* THE RED EDGE, added 2026-08-25.

   A single marker-red rule down the left of the viewport, full height, present
   on every page and every scroll position.

   IT IS A DELIBERATE EXCEPTION TO TWO RULES IN THIS FILE, both a few dozen
   lines up: "colour is never a surface fill, no coloured bands", and "if you
   cannot say what a red mark is pointing at, remove it". Scott's call, from a
   brand composition he built in Affinity, and the reasoning is that the site
   had reserved exactly one accent colour and then spent almost none of it. A
   palette with an accent nobody sees is not restraint, it is an unused
   declaration. DESIGN-REGISTER.md section 4 carries the decision.

   IT IS AN EDGE, NOT A BAND, and the distinction is what keeps it honest. It
   lives outside the container, in the gutter, so it never becomes a coloured
   surface behind anything and never pushes content. Take it away and not one
   pixel of layout moves.

   WHY FIXED AND WHY ABOVE THE NAV. Fixed, so it is one continuous edge rather
   than something that scrolls away. z-index 200 puts it over the sticky nav
   (z-index 100), so the line runs unbroken past the header instead of being
   interrupted by it, which is what the source composition does. pointer-events
   is none so it can never eat a click at the window edge.

   It narrows below 900px, where --layout-gutter drops from 48px to 24px and a
   6px rule would start crowding the text. */
:root{ --edge-width:6px }
body::before{
  content:""; position:fixed; inset-block:0; left:0;
  width:var(--edge-width); background:var(--colour-marker);
  z-index:200; pointer-events:none;
}
@media (max-width:900px){ :root{ --edge-width:4px } }

/* THE RAIL (section 6).

   Every section wrapper is a two column grid: a fixed rail carrying the mono
   anchor label, and the content column beside it. The label sits OUTSIDE the
   text column rather than above it, which is what stops a page reading as a
   stack of headed blocks and starts it reading as an instrument with its
   parts named.

   Content is pinned to column 2 and the label to column 1 row 1, so the
   markup order does not have to change: an .anchor-title written first in the
   source still lands in the rail beside the heading that follows it. */
:is(.section, .hero) > .wrap,
:is(.section, .hero) > .wrap-narrow{
  display:grid;
  grid-template-columns:var(--layout-rail) minmax(0,1fr);
  column-gap:var(--space-7);
  align-items:start;
}
:is(.section, .hero) > .wrap-narrow{ grid-template-columns:var(--layout-rail) minmax(0,var(--layout-measure)) }

:is(.section, .hero) > .wrap > *,
:is(.section, .hero) > .wrap-narrow > *{ grid-column:2; min-width:0 }
:is(.section, .hero) > .wrap > .anchor-title,
:is(.section, .hero) > .wrap-narrow > .anchor-title{ grid-column:1; grid-row:1 }

/* Some sections carry the label one level deeper, inside the block that also
   carries the reveal animation. That block becomes its own rail grid spanning
   both columns of its parent, which puts its label in exactly the same column
   as every other label on the page.

   It is done this way rather than with display:contents because a
   display:contents element generates no box, and .reveal needs a box to
   animate. Flattening the wrapper would have silently switched the scroll
   reveal off on 27 blocks. */
:is(.section, .hero) > .wrap > :has(> .anchor-title){
  grid-column:1 / -1;
  display:grid;
  grid-template-columns:var(--layout-rail) minmax(0,1fr);
  column-gap:var(--space-7);
  align-items:start;
}
:is(.section, .hero) > .wrap > :has(> .anchor-title) > *{ grid-column:2; min-width:0 }
:is(.section, .hero) > .wrap > :has(> .anchor-title) > .anchor-title{ grid-column:1; grid-row:1 }

/* THERE IS NO .centre. Centre-aligned page content is the single strongest
   generic-template signal on the old build, and removing it is the one change
   that did most to stop this reading as a template. It was on 53 blocks and it
   is on none. Do not reintroduce it, and do not reach for text-align:center on
   a page: the only centring left on the site is a label inside a button. */

/* A section is one movement of the argument, separated by a hairline that runs
   to the edge of the container rather than around anything.

   The three alternating ground colours are gone, and so are .section-deep and
   .section-raised. Cycling depth down the page is separating sections with
   colour, and this register separates them with a 1px rule and whitespace. */
.section{
  position:relative;
  padding-block:var(--layout-section-y);
  background-color:var(--colour-ground);
  border-top:1px solid var(--colour-line);
}


/* 3. TYPOGRAPHY ===========================================================
   Two classes carry the whole hierarchy rule: a quiet anchor-title saying what
   the section is, and one dominant header-statement making its argument.
   One header-statement per section. Never two competing at equal weight.

   That rule is the best thing in the old build and it survives the register
   change intact. What changes is where the anchor sits and what it is set in.
   ========================================================================= */

/* THE RAIL LABEL. Mono, small, tracked open, and NOT pale.

   It reads as quiet because it is 11px uppercase mono in a column of its own,
   not because it has been greyed towards the ground. See the ink ramp note in
   block 1. The bottom margin is gone: it is no longer sitting above anything. */
.anchor-title{
  font-family:var(--font-signal); font-size:var(--type-signal-label); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  line-height:1.45; margin:0;
}
.anchor-title{ color:var(--colour-text-muted) }

/* The label drops onto the cap line of whichever heading it labels. Two rules
   rather than one because the two heading scales have different cap drops, and
   both are calculated from the type scale in block 1. */
:is(.section, .hero) > .wrap:has(> .header-statement) > .anchor-title,
:is(.section, .hero) > .wrap-narrow:has(> .header-statement) > .anchor-title,
:is(.section, .hero) > .wrap > :has(> .header-statement) > .anchor-title{
  padding-top:calc(var(--cap-drop-statement) - var(--cap-drop-label));
}
:is(.section, .hero) > .wrap:has(> .section-heading) > .anchor-title,
:is(.section, .hero) > .wrap-narrow:has(> .section-heading) > .anchor-title,
:is(.section, .hero) > .wrap > :has(> .section-heading) > .anchor-title{
  padding-top:calc(var(--cap-drop-heading) - var(--cap-drop-label));
}

/* Statements are set light at large sizes with leading near 1.0. Plex takes
   far less negative tracking than Outfit did: at -.035em the counters close up
   and it stops reading as an instrument. */
.header-statement{
  font-family:var(--font-display); font-size:var(--type-statement); font-weight:var(--weight-statement);
  line-height:var(--leading-tight); letter-spacing:var(--tracking-statement);
  color:var(--colour-text); max-width:15ch; margin:0 0 var(--space-7);
}
.section-heading{
  font-family:var(--font-display); font-size:var(--type-heading-2); font-weight:var(--weight-statement);
  line-height:1.08; letter-spacing:-.018em; max-width:22ch; margin:0 0 var(--space-6);
  color:var(--colour-text);
}
.sub-heading{
  font-family:var(--font-display); font-size:var(--type-heading-3); font-weight:var(--weight-signal);
  line-height:1.28; letter-spacing:-.01em; margin:0 0 var(--space-3);
  color:var(--colour-text);
}

.lede{ font-size:var(--type-lede); line-height:1.62; color:var(--colour-text-secondary); max-width:56ch; margin:0 0 var(--space-7) }
.body-copy{ font-size:var(--type-body); line-height:var(--leading-body); color:var(--colour-text-secondary); max-width:66ch; margin:0 }
/* Two body paragraphs in a row read as one block otherwise: .body-copy is
   margin:0 because it is nearly always alone in its compartment, and .card-top
   is a flex column with no gap. Consecutive ones take the same rhythm as
   .prose p. Sitewide this matches three places, all on the home page. */
.body-copy + .body-copy{ margin-top:var(--space-4) }
.note{ font-size:var(--type-small); line-height:1.5; color:var(--colour-text-muted); max-width:60ch; margin:0 }

/* A signal term: a single word that tells the right reader this is for them,
   and reads straight past everyone else. Mono, because that is what every
   other signal element on this site is set in. */
.signal-term{
  font-family:var(--font-signal); font-size:var(--type-signal); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-signal);
  color:var(--colour-text-muted); margin:0 0 var(--space-5);
}

/* A number on show. Tabular, because these sit in columns and a column of
   proportional figures does not line up. Weight 300 is the floor of the
   variable range: the old 200 silently clamped to it anyway. */
.stat-number{
  font-family:var(--font-display); font-size:var(--type-stat); font-weight:var(--weight-statement);
  letter-spacing:-.03em; line-height:1; color:var(--colour-text);
  font-variant-numeric:var(--figures-tabular); margin:0 0 var(--space-4);
}

/* The ownership argument: one numbered sequence, not four scattered lines. */
.numbered{ display:grid; grid-template-columns:56px minmax(0,1fr); gap:var(--space-5); padding-block:var(--space-5); border-top:1px solid var(--colour-line); align-items:baseline }
.numeral{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  letter-spacing:var(--tracking-label); color:var(--colour-text-muted);
  font-variant-numeric:var(--figures-tabular);
}


/* 4. CONTROLS =============================================================
   Square, mono-labelled, and one chip vocabulary rather than three.

   The old build had rounded status pills, outlined rectangles and circled
   numerals all on one page, which is three ways of saying the same thing. A
   label on this site is one shape: a rectangle with a mono uppercase caption,
   or no shape at all.

   Every control here is DERIVED from its own label. Button height is not a
   number, it is the label's line box plus its air, so changing the signal size
   in block 1 keeps the buttons proportionate instead of breaking them.
   ========================================================================= */

/* The label sets the height. --btn-pad-y is the air above and below the line
   box; --btn-pad-x is that air at the ratio the study uses, which reads as a
   control rather than a tag. */
.btn{
  --btn-pad-y:14px;
  --btn-pad-x:calc(var(--btn-pad-y) * 1.85);
  display:inline-flex; align-items:center; justify-content:center;
  padding:var(--btn-pad-y) var(--btn-pad-x);
  border-radius:var(--radius);
  border:1px solid var(--colour-line-bright);
  background-color:transparent;
  color:var(--colour-text);
  font-family:var(--font-signal); font-size:var(--type-signal); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-signal);
  line-height:1; text-decoration:none;
}
.btn:hover{ border-color:var(--colour-text) }

/* THE SOLID INK BUTTON.

   The only filled mass on the light ground, and therefore the most emphatic
   thing on any page it appears on, without spending a colour to be so. This is
   what replaces the chartreuse slab as the download ask: the emphasis comes
   from mass and isolation rather than from fill. Section 4 is explicit that
   colour is never a surface fill, so it cannot come from anywhere else.

   .btn-ink was the same button under a second name, kept only because the nav
   used one and the pages used the other. One button, one name. */
.btn-accent{
  background-color:var(--colour-text); color:var(--colour-ground);
  border-color:var(--colour-text);
}
.btn-accent:hover{ border-color:var(--colour-text) }

/* GRID ITEMS DO NOT COLLAPSE THEIR MARGINS.

   Worth knowing, because it caught this rule. Section content became grid items
   when the rail went in, so a .lede with 48px below it followed by a .cta-row
   with 48px above it now measures 96px instead of the 48 it measured for the
   whole of the previous build. The button row drifted away from the argument it
   belongs to on every page at once.

   The margin-top stays, because a .cta-row after a .body-copy (which carries no
   bottom margin) still needs it. What changes is that anything sitting directly
   above one gives up its own bottom margin. */
.cta-row{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-4); margin-top:var(--space-7) }
.header-statement:has(+ .cta-row),
.section-heading:has(+ .cta-row),
.lede:has(+ .cta-row){ margin-bottom:0 }

/* THE ONE CHIP.

   .badge, .badge-tag and .chip were three different objects doing one job.
   They are the same object now: a mono uppercase caption. .chip carries a
   hairline because it names a thing; .badge does not, because it sits inline
   in a sentence and a box around it would read as a second control. */
.chip, .badge{
  display:inline-flex; align-items:center; gap:var(--space-2);
  align-self:flex-start;
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  color:var(--colour-text-muted);
  background-color:transparent; border-radius:var(--radius);
}
.chip{
  padding:6px 10px; border:1px solid var(--colour-line);
  margin-bottom:var(--space-5);
}
.badge{ padding:0; border:0; margin-bottom:var(--space-7) }

/* A status mark, not a decoration: it says the MCP server is present. Square,
   because nothing on this site is round, and marker red because it is the one
   thing in that line the reader is being pointed at. */
.badge-dot{ width:6px; height:6px; border-radius:0; background:var(--colour-marker) }

/* A value inside a badge: the version number the badge exists to state. */
.badge-tag{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  font-variant-numeric:var(--figures-tabular);
  color:var(--colour-text); background:transparent;
  padding:0; border-radius:0;
}


/* 5. SURFACES =============================================================
   BENTO WITHOUT BOX OUTLINES (section 6).

   Nothing on this site is a bordered or filled container. Compartments are
   made by alignment, shared baselines and whitespace, and separated by
   hairlines that run to the edge of their compartment rather than closing
   around the content. The difference sounds small and it is most of the
   register: a bordered card is an object sitting ON a page, a ruled compartment
   is part of the page.

   The 12 column grid stays exactly as it was. It was never the problem, and it
   is what makes the compartments line up.
   ========================================================================= */

.bento{
  display:grid; grid-template-columns:repeat(12,minmax(0,1fr));
  column-gap:var(--space-7); row-gap:var(--space-8);
  margin-top:var(--space-8);
}
.span-3{grid-column:span 3} .span-4{grid-column:span 4} .span-5{grid-column:span 5}
.span-6{grid-column:span 6} .span-7{grid-column:span 7} .span-8{grid-column:span 8}
.span-12{grid-column:span 12}

/* A compartment. One hairline across the top, and air. The rule is what says
   "a new thing starts here", and because it stops at the compartment's own
   width rather than wrapping around it, the compartments read as divisions of
   one surface instead of as separate objects.

   min-height is gone with the fill: it existed to stop cards of different
   content lengths looking ragged, which is a problem boxes have and ruled
   compartments do not. Their tops align, and that is enough. */
.card{
  position:relative;
  display:flex; flex-direction:column; gap:var(--space-5);
  padding:var(--space-5) 0 0;
  border-top:1px solid var(--colour-line);
  background-color:transparent;
}
.card-top{ display:flex; flex-direction:column; align-items:flex-start }
.card-foot{ margin-top:auto; padding-top:var(--space-4) }

/* .card-lit and .card-accent are both gone. The first was a chartreuse point
   source falling across one card per section, the second a whole card filled
   with the accent. There is no light source on a laboratory ground, and colour
   is never a surface fill. .accent-block went with them; see block 15. */

/* THE OBSERVATION FIELD.

   A capture of the application, placed in the content column. Dark, because
   dark means observation: this is the product being shown rather than talked
   about, and it is the only asset no competitor can copy.

   THE BEZEL IS GONE ENTIRELY, 2026-08-27 on Scott's call, and this is the
   second and last step of a retreat. It was once a 10px frame with a lit top
   edge and a chartreuse glow beneath, which was the elevation rule wearing an
   instrument costume; it then survived as a hairline plus a 1px dark ring,
   which on a light ground read as a black outline drawn around every capture.
   Block 3 of the register says hairlines run to the edge of the container
   rather than around content, and that ring was doing exactly the forbidden
   thing. Nothing is lost: the ground change from light to dark is what stops a
   screenshot reading as a flat rectangle pasted onto the page, and it was
   always doing that work on its own. Do not put a border back on .stage.

   NO GRATICULE HERE, and it was tried first. Section 6 allows one inside a dark
   observation field "that content visibly aligns to", and a capture fills this
   field edge to edge, so nothing aligns to anything and the grid is pure
   ornament, which is the one thing that section does not permit. It belongs in
   the hero's parallax field, where the nodes sit on real coordinates and the
   reader can see that they do. */
.stage{ position:relative; margin:var(--space-9) 0 0 }
.stage-inner{
  position:relative; display:block; overflow:hidden;
  background:var(--colour-observed);
}
.stage-shot{ display:block; width:100%; height:auto }

/* EVERY CAPTURE CARRIES A TITLE, and this is a rule rather than a habit
   (Scott, 2026-08-27). A screenshot dropped into a column with nothing said
   about it reads as orphaned: the reader has to work out what they are looking
   at and which of two similar views this one is, and most will not bother. So
   a capture is a <figure>, and the <figcaption> names it and, where it earns
   the line, says what to look at.

   THE CAPTION LEADS. It sits above the image, like .view figcaption in the
   observation field, because a label the reader meets first tells them what to
   look for; a label underneath only tells them what they should have seen.

   The hairline is a border-top running the full width of the container, which
   is the .numbered pattern and the register's rule in block 3: hairlines run to
   the edge rather than closing around content. It gives the block a place to
   start. Do not turn it into a box around the caption. */
.stage-caption{
  display:flex; flex-wrap:wrap; align-items:baseline;
  column-gap:var(--space-5); row-gap:var(--space-2);
  margin:0 0 var(--space-4); padding-top:var(--space-4);
  border-top:1px solid var(--colour-line);
}
.stage-caption-title{
  flex:none;
  font-family:var(--font-signal); font-size:var(--type-signal-label); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  line-height:1.45; color:var(--colour-text);
}
.stage-caption-note{
  font-size:var(--type-small); line-height:1.5;
  color:var(--colour-text-muted); max-width:60ch; margin:0;
}

/* MOTION IN A STAGE, added 2026-08-26 for the two recorded fly-throughs.

   This is a silent looping clip, which is an image that moves. It is
   deliberately NOT a player: no controls, no audio track in the file at all,
   no chrome. The rule that video is never embedded in the site is about
   players and about the YouTube channel being a second destination, and a
   background loop does not breach it. Do not add controls here.

   HOW IT LOADS, and this is the whole reason the markup looks like it does.
   The site carries no behavioural JavaScript, so none of this is scripted.
   Every <source> is gated on `(prefers-reduced-motion: no-preference)`, so a
   visitor who has asked their system for less motion matches NO source, the
   element resolves to no resource, and nothing is downloaded at all. They get
   the poster still and pay nothing for the clip. The width query then picks
   the 1280 encode on small screens, so a phone never pulls the 1920 file.

   The aspect ratio is fixed at 12/5 because the source is letterboxed 2.4:1
   footage cropped to its live area. It must stay in step with the encodes, or
   the poster and the clip will disagree and the block will jump on load. */
.stage-motion{
  display:block; width:100%; height:auto;
  aspect-ratio:12/5; object-fit:cover;
  background-color:var(--colour-observed);
}
@media (prefers-reduced-motion: reduce){
  /* Belt and braces. The source gating above already means there is nothing
     to play; this covers a browser that ignores `media` on <source> and
     resolves one anyway. The poster remains visible through the element. */
  .stage-motion{ animation:none }
}

.rule{ width:100%; height:1px; background:var(--colour-line); border:0 }

/* .logo-strip and .logo-item are gone. The strip listed seven names in a row,
   Windows through LM Studio, and it mixed platforms, applications and AI tools,
   so it read as a logo wall of unlike things with no way to tell which name
   meant what. It is three named claims in a .readout now: see index.html.

   The reason it was never a customer logo wall still holds and is worth keeping
   here: there are no customers yet, and a borrowed logo reads as exactly that. */


/* THE OBSERVATION FIELD (section 8).

   The hero's parallax pair, and the only place on the site where cyan and
   violet appear. That is the rule rather than a habit: they measure 1.14:1 and
   2.60:1 on the light ground and are effectively invisible there, and the
   restriction is what keeps the chrome and the observation cleanly separated.
   Light is where we talk, dark is where we show.

   The graticule lives here and nowhere else. Section 6 allows exactly one
   ornament and only inside a field that content visibly aligns to, which these
   nodes do: coordinates are what the product is about. It was tried on .stage
   and removed, because a capture fills that field edge to edge and nothing
   aligns to anything.

   The pair is a 1px gap over the field's own colour, so the two views are
   divided by a hairline rather than boxed. Two boxes side by side would read as
   two pictures; one field split down the middle reads as one instrument
   showing the same thing twice, which is the entire argument. */
.observation{
  margin-top:var(--space-9);
  padding:var(--space-6) var(--space-6) var(--space-5);
  background:var(--colour-observed);
  border:1px solid var(--colour-line);
}
.observation-head{ margin-bottom:var(--space-5) }
.observation-label{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  color:var(--colour-observed-muted); margin:0 0 var(--space-3);
}
.observation-title{
  font-family:var(--font-display); font-size:var(--type-heading-3); font-weight:var(--weight-statement);
  line-height:1.25; letter-spacing:-.01em;
  color:var(--colour-observed-text); margin:0; max-width:40ch;
}

.views{
  display:grid; grid-template-columns:1fr 1fr;
  gap:1px; background:var(--colour-observed-rule);
}
.view{ margin:0; padding:0 var(--space-4); background:var(--colour-observed) }
.view figcaption{
  display:flex; justify-content:space-between; align-items:baseline;
  margin-bottom:var(--space-2);
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  font-variant-numeric:var(--figures-tabular);
  color:var(--colour-observed-muted);
}
.view svg{ display:block; width:100%; height:auto; overflow:visible }

/* THREE VIEWS, AND VIEWS THAT CARRY A PHOTOGRAPH RATHER THAN AN SVG.

   .views was built for the hero's parallax pair, where two views of one
   structure prove depth. The customisation row is the same object doing the
   same job: several views of one structure, where the thing held constant is
   the argument. So it extends .views instead of inventing a second grid.

   The row collapses to one column early. Three app windows side by side stop
   being legible long before the container does, and below that width the
   argument survives as a vertical sequence: the reader still scans identical
   frames and finds nothing moved. */
.views-3{ grid-template-columns:repeat(3,1fr) }
.view img{ display:block; width:100%; height:auto; margin-bottom:var(--space-4) }
/* .view's side padding exists to inset an SVG that draws to its own edges. A
   screenshot has its own frame already, and at three across every pixel of
   column width is doing work, so the padding comes off when the view carries
   a photograph. */
.views-3 .view{ padding:0 }
.views-3 .view figcaption{ padding:0 var(--space-2) }

@media (max-width: 900px){
  .views-3{ grid-template-columns:1fr }
}

/* The SVG carries no colour of its own. Every value it draws comes from the
   token block, so the emission-line rules cannot be worked around by editing a
   generated file. */
.view .grat  { stroke:var(--colour-cyan); stroke-opacity:.055; stroke-width:1 }
.view .edge  { stroke:var(--colour-cyan); stroke-width:1 }
.view .node.cyan   { fill:var(--colour-cyan) }
.view .node.violet { fill:var(--colour-purple) }
.view .corona{ fill:none; stroke-width:1 }
.view .corona.cyan   { stroke:var(--colour-cyan) }
.view .corona.violet { stroke:var(--colour-purple) }

/* The marker, lifted to the cut that clears 4.5:1 on the dark field. It points
   at the node that measurably moved furthest, which is the nearest one, and
   that is the only thing red says on this site. */
.view .mark{ stroke:var(--colour-marker-lit); stroke-width:1.2 }

.observation-note{
  margin:var(--space-5) 0 0; max-width:62ch;
  font-size:var(--type-small); line-height:1.6;
  color:var(--colour-observed-secondary);
}
.observation-note .delta{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  font-variant-numeric:var(--figures-tabular);
  color:var(--colour-marker-lit); margin-right:var(--space-3);
}

/* One above the other below the pair's own breakpoint. Two 180px views side by
   side on a phone would put the whole argument below the readable threshold,
   and the argument is the point. */
@media (max-width:720px){
  .views{ grid-template-columns:1fr }
  .observation{ padding:var(--space-5) var(--space-4) var(--space-4) }
}


/* THE PRACTICE DIRECTORY, for the In practice hub. Added 2026-08-16.

   A hub is a NAVIGATION page, not a content page, and the bento of equal cards
   it replaced was built like the latter: the name of each practice sat in a
   .chip, so the single most important word on a directory page was also the
   quietest thing on it. Here the name leads and the description is demoted to
   supporting text, which is the right way round when the reader's only job is
   to recognise their own work and click it. That argument survives the register
   change unchanged; only the container goes.

   A row rather than a card, for two reasons. It scales: eleven practices as a
   grid of cards is a wall, as a list it stays scannable. And it leaves a real
   slot on the right for a capture, so images drop in later as a pure insert.
   Rows with no image simply do not render .practice-shot and the text column
   takes the full width, so a mixed state is not a broken state.

   Deboxed, the list becomes what a directory in this register should be: a
   ruled table of entries. The hairline between rows does the separating, and
   the last row closes the list. */
.practice-list{ display:flex; flex-direction:column; margin-top:var(--space-8) }

.practice-row{
  position:relative; display:grid; gap:var(--space-5) var(--space-8);
  grid-template-columns:1.05fr 1fr; align-items:baseline;
  padding:var(--space-6) 0;
  border-top:1px solid var(--colour-line);
  background-color:transparent; text-decoration:none;
}
.practice-row:last-child{ border-bottom:1px solid var(--colour-line) }
.practice-row:has(.practice-shot){ grid-template-columns:1fr .85fr .9fr }

.practice-name{
  font-family:var(--font-signal); font-size:var(--type-signal-label); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  color:var(--colour-text-muted); margin:0 0 var(--space-3);
}
.practice-line{
  font-family:var(--font-display); font-size:clamp(1.5rem,2.4vw,2rem); font-weight:var(--weight-statement);
  line-height:1.08; letter-spacing:-.018em;
  color:var(--colour-text); margin:0; max-width:20ch;
}
.practice-who{ font-size:var(--type-small); color:var(--colour-text-secondary); margin:0 0 var(--space-5) }
.practice-go{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  color:var(--colour-text-muted);
}
a.practice-row:hover .practice-go{ color:var(--colour-text) }

.practice-shot{ display:block; width:100%; height:auto; border-radius:var(--radius); border:1px solid var(--colour-line) }

@media (max-width:900px){
  .practice-row, .practice-row:has(.practice-shot){ grid-template-columns:1fr }
  .practice-line{ max-width:none }
}


/* 6. CHROME ===============================================================
   Section 7, and every number in it is derived from --mark-size in block 1.

   The old header failed because the wordmark, the bar height and the button
   were each chosen independently, so no two of them agreed about how big the
   brand was. Here the mark is the only chosen number.

   THE BRAND OUTRANKS THE BUTTON, NEVER THE REVERSE. The download control is
   the most important thing on the site commercially and it still has to be
   visually lighter than the wordmark, because a bar where the button shouts
   over the logo reads as a landing page rather than as a product.
   ========================================================================= */

/* The bar is the ground colour with a 1px bottom rule. It is not a coloured
   slab and it is not a floating pill.

   The full-bleed chartreuse nav was the single loudest thing on the old build,
   it never scrolled away, and it sat directly above a product shot in cyan,
   purple, red and orange. Two loud colour systems, and the chrome won, because
   the chrome was the one that never moved. The product visual is the only asset
   no competitor can copy and it was being outshouted by a navigation bar. */
.nav{
  position:sticky; top:0; z-index:100;
  background:var(--colour-ground); color:var(--colour-text);
  border-bottom:1px solid var(--colour-line);
}
.nav-inner{
  display:flex; align-items:center; gap:var(--space-6);
  height:var(--layout-nav-height);
}

/* THE LOCKUP, 2026-08-26. One image, not a drawn mark beside live text.

   It is Scott's Affinity artwork exported outlined, and it is a single asset
   because the wordmark carries two things live text on this site could not:
   the fi LIGATURE, and a RED TITTLE sitting on top of it.

   WHY LIVE TEXT COULD NOT DO IT, since the previous attempt is still worth
   knowing about. Two separate things were suppressing the ligature here:

     1. The i was wrapped in its own <span> so the dot could be painted red,
        and browsers do not form ligatures across an element boundary.
     2. This rule set letter-spacing:.005em, and a non-zero letter-spacing
        suppresses common ligatures outright.

   An earlier note in this file recorded, correctly, that the string measured
   the same width with ligatures on and off, and concluded the face does not
   ligate fi. It does. The measurement was taken through the letter-spacing.
   Measured again 2026-08-26: at 0 letter-spacing the string is 1.9px narrower
   at 200px with ligatures on.

   That note also warned that a true fi ligature DELETES THE TITTLE, so the red
   dot would silently vanish. Not in this face: IBM Plex Sans's fi glyph has two
   contours, the merged f-and-stem plus a tittle of its own at x 393..491,
   y 637..740 per 1000 em. The dot survives the ligature. Scott's artwork lays
   a red rounded rect over exactly that tittle, at x 1282.58 against the glyph's
   own 1283.18, 8.25 wide against 8.38.

   Sized by HEIGHT so --mark-size still governs: the artwork is cropped tight to
   its ink, and the mark is the tallest thing in it, so height here IS the mark
   size and --layout-nav-height stays correct. Width follows the viewBox at
   about 6.1:1, which is 146px at the nav's 24px. */
.nav-logo{
  display:flex; align-items:center; margin-right:auto; text-decoration:none;
}
.nav-lockup{ display:block; flex:none; height:var(--mark-size); width:auto }


.nav-links{ display:flex; align-items:center; gap:30px }

/* Nav items are signal, not prose. They sit at ink-secondary and go to full
   ink on hover, so the bar has one live state rather than an opacity ramp. */
.nav-link{
  font-family:var(--font-signal); font-size:var(--type-signal); font-weight:400;
  text-transform:uppercase; letter-spacing:var(--tracking-signal);
  color:var(--colour-text-secondary); text-decoration:none; opacity:1;
  padding:calc(var(--bar-pad) + 1px) 0 var(--bar-pad);
  border-bottom:2px solid transparent;
}
.nav-link:hover{ color:var(--colour-text) }

/* THE ACTIVE ITEM, and one of only two places red appears in the whole design.
   It means "this one", which is exactly what an active nav item is. The 2px
   rule is a mark rather than text, so it is judged against the 3:1 rule for
   non-text and measures 4.44:1 on the ground. */
.nav-link.is-active{ color:var(--colour-text); border-bottom-color:var(--colour-marker) }

/* The download control. Square, solid ink, mono label, and deliberately set at
   the same signal size as the nav items rather than larger: its weight comes
   from being the only filled mass in the bar. */
.nav-links .btn{ --btn-pad-y:11px }

/* The mobile menu, with no JavaScript. A visually-hidden checkbox drives the
   drawer through a sibling selector, which means the link list exists exactly
   once in the markup: there is no second copy to drift out of step.

   The checkbox is clipped rather than display:none on purpose. Clipped, it
   still takes keyboard focus, so the menu opens with Space like any other
   control. display:none would remove it from the tab order entirely.

   Everything here is inert above 767px. The rules that switch it on live in
   the responsive block at the foot of the file. */
.nav-toggle{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; white-space:nowrap; border:0; clip-path:inset(50%);
}
.nav-burger{
  display:none; position:relative; width:40px; height:40px; margin-right:-8px;
  align-items:center; justify-content:center;
  border-radius:var(--radius); cursor:pointer;
}
.burger-bars, .burger-bars::before, .burger-bars::after{
  position:absolute; left:50%; width:20px; height:1px; margin-left:-10px;
  background:var(--colour-text); border-radius:0;
}
.burger-bars{ top:50%; margin-top:-1px }
.burger-bars::before{ content:""; top:-6px }
.burger-bars::after { content:""; top:6px }

/* Open: the bars become a cross. This is state, not decoration: it is how the
   control says it now closes rather than opens. */
.nav-toggle:checked ~ .nav-burger .burger-bars{ background:transparent }
.nav-toggle:checked ~ .nav-burger .burger-bars::before{ top:0; transform:rotate(45deg) }
.nav-toggle:checked ~ .nav-burger .burger-bars::after { top:0; transform:rotate(-45deg) }

.nav-toggle:focus-visible ~ .nav-burger{ outline:2px solid var(--colour-text); outline-offset:2px }

/* padding-bottom was 0 because the old hero ran straight into a dark horizon
   gradient and the two were meant to touch. There is no gradient now, so the
   last thing in the hero sat 12px off the next section's hairline. */
.hero{ position:relative; padding-block:var(--space-9); background:var(--colour-ground) }
/* .hero-grid, .hero-glow and .hero-horizon were three stacked layers of the
   dark build: a masked graticule, a chartreuse point source and a fade into
   black. Rules and elements are both gone now. The graticule idea survives, but
   section 6 puts it INSIDE a dark observation field where content visibly
   aligns to it, which is the one ornament this register justifies. */

/* The footer is on the light ground and hairline separated (section 9). It is
   not dark: a dark band on this site means observation, and a footer observes
   nothing. That was the whole reason the old one was dark, and it is not a
   reason.

   CHROME STARTS AT THE CONTAINER EDGE, CONTENT STARTS AT THE RAIL EDGE, and
   that is deliberate rather than an oversight. The nav wordmark and the footer
   links both sit at the container edge while every section of content sits 248px
   in, so the chrome reads as a frame around the content rather than as more of
   it. Do not rail the footer to "line it up": lining it up is what would break
   the distinction.

   The links are set in mono because they are navigation, and every navigational
   and labelling element on this site is mono. */
.footer{
  position:relative; background:var(--colour-ground);
  border-top:1px solid var(--colour-line);
  padding:var(--space-8) 0 var(--space-7);
}
.footer-cols{ display:flex; flex-wrap:wrap; gap:var(--space-4) var(--space-7) }
.footer-link{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:400;
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  color:var(--colour-text-muted); text-decoration:none;
}
.footer-link:hover{ color:var(--colour-text) }


/* 7. MOTION ===============================================================
   All animation on the site lives in this block and nowhere else.

   The rule it follows (Vega): motion must do a job. Reveal marks a boundary
   and sets reading order. Hover states say "this is clickable". The glow
   Nothing here exists to delight.

   Hover LIFT is gone with the elevation rule. A control on this site answers a
   hover by changing its rule colour, not by rising towards the reader: there is
   no light, no shadow and nothing for an element to rise above.

   To calm the whole site down, change --motion-* in the tokens block.
   To remove any single behaviour, delete its rule below. Nothing else breaks.
   ======================================================================== */

/* --- Hover: affordance only, on things you can actually click ------------

   All of these used to transition transform and box-shadow as well, and the
   button lifted 1px. Neither property changes on this site any more, so what
   is left is the one thing that does: the rule colour. */
.btn{ transition:border-color var(--motion-ease) var(--motion-curve),
                 color var(--motion-ease) var(--motion-curve) }

.nav-link{ transition:color var(--motion-quick) var(--motion-curve) }
.footer-link{ transition:color var(--motion-quick) var(--motion-curve) }

/* Only compartments that are links get a hover state. One that does nothing
   should not pretend it does. The rule brightens; nothing moves. */
a.card, a.practice-row, a.help-item{
  color:var(--colour-text); text-decoration:none;
  transition:border-color var(--motion-ease) var(--motion-curve),
             color var(--motion-ease) var(--motion-curve);
}
a.card:hover, a.practice-row:hover, a.help-item:hover{ border-color:var(--colour-line-bright) }

/* --- The tips panel cycles ----------------------------------------------
   Ten tips sharing one panel. This is the tips *being readable* rather than
   decoration: showing them any other way costs ten lines of vertical space
   that would push the topic list off the screen.

   Each tip is visible for its own tenth of the cycle. The base state is
   opacity 0 with no fill-mode, so during its start delay a tip stays hidden —
   `backwards` would apply the 0% frame and show all ten at once.

   With reduced motion they become an ordinary static list. Nothing is ever
   only reachable by waiting. */
@keyframes tipCycle{
  0%      { opacity:0; transform:translateY(6px) }
  1.5%, 9%{ opacity:1; transform:none }
  10.5%   { opacity:0; transform:translateY(-6px) }
  100%    { opacity:0; transform:translateY(-6px) }
}
.tip{ opacity:0; animation:tipCycle 60s linear infinite; animation-delay:calc(var(--i) * 6s) }

@media (prefers-reduced-motion: reduce){
  .tips{ min-height:0 }
  .tip{ position:static; opacity:1; transform:none; animation:none; margin-bottom:var(--space-3) }
  .tip:last-child{ margin-bottom:0 }
}

/* --- The hero light no longer breathes ----------------------------------
   Removed 2026-08-22 with the register change. A light source that never moves
   reads as a flat gradient rather than as light, which is why it drifted, but
   there is no light source on a laboratory ground: the argument is made by the
   observation field now. The .hero-glow element goes with the hero rebuild. */


/* --- Reveal on scroll ---------------------------------------------------
   Uses scroll-driven CSS animation: no JavaScript, and in browsers that do
   not support it the content is simply visible, which is the correct
   fallback. Never hide content behind a script. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    @keyframes reveal{
      from{ opacity:0; transform:translateY(var(--motion-rise)) }
      to  { opacity:1; transform:translateY(0) }
    }
    .reveal{
      animation:reveal var(--motion-slow) var(--motion-curve) both;
      animation-timeline:view();
      animation-range:entry 5% cover 22%;
    }
    /* Stagger a row of cards so the eye is led across rather than hit at once */
    .reveal-stagger > *{
      animation:reveal var(--motion-slow) var(--motion-curve) both;
      animation-timeline:view();
      animation-range:entry 5% cover 26%;
    }
    .reveal-stagger > *:nth-child(2){ animation-range:entry 8% cover 30% }
    .reveal-stagger > *:nth-child(3){ animation-range:entry 11% cover 34% }
  }
}

/* --- The override that outranks everything above ------------------------- */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
}


/* 8. LONG-FORM PROSE ======================================================
   Only the utility pages use this: privacy, terms, cancel, affiliates, help.

   Everything above is built for a marketing page, where every block is placed
   deliberately and nothing simply flows. These pages are the opposite — they
   are continuous documents that need ordinary paragraphs, lists and tables,
   and they are read rather than scanned.

   All of it is scoped under .prose so none of it can leak onto a marketing
   page and start styling things that were composed by hand.
   ======================================================================== */

.prose{ max-width:72ch }
.prose > :last-child{ margin-bottom:0 }

.prose h2{
  font-family:var(--font-display); font-size:var(--type-heading-3); font-weight:500;
  line-height:1.25; letter-spacing:-.02em; color:var(--colour-text);
  margin:var(--space-8) 0 var(--space-4);
}
.prose h3{
  font-family:var(--font-display); font-size:1.125rem; font-weight:500;
  line-height:1.3; color:var(--colour-text); margin:var(--space-7) 0 var(--space-3);
}
.prose h2:first-child, .prose h3:first-child{ margin-top:0 }

.prose p{ font-size:var(--type-body); line-height:var(--leading-body); color:var(--colour-text-secondary); margin:0 0 var(--space-5) }
.prose strong{ color:var(--colour-text); font-weight:600 }
.prose a{ color:var(--colour-text); text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:3px }

.prose ul{ margin:0 0 var(--space-5); padding-left:var(--space-5) }
.prose li{ font-size:var(--type-body); line-height:var(--leading-body); color:var(--colour-text-secondary); margin-bottom:var(--space-2) }
.prose li::marker{ color:var(--colour-text-muted) }

/* Tables scroll inside their own box rather than widening the page. A legal
   summary table is the one place this site has genuinely tabular data. */
.prose-scroll{ overflow-x:auto; margin:0 0 var(--space-6) }
.prose table{ width:100%; min-width:520px; border-collapse:collapse; font-size:var(--type-small) }
.prose th, .prose td{ text-align:left; padding:var(--space-3) var(--space-4); border-bottom:1px solid var(--colour-line); vertical-align:top }
.prose th{
  font-family:var(--font-signal); font-size:11px; font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; color:var(--colour-text-muted);
}
.prose td{ color:var(--colour-text-secondary) }
.prose td:first-child{ color:var(--colour-text) }

/* The summary a reader actually reads before the document they do not. */
.prose-lead{ margin-bottom:var(--space-8) }
.prose-lead li{ color:var(--colour-text) }

/* Code. Only the two AI reference documents carry any, and it is the literal
   text handed to a model — so it must be copyable and exact, never reflowed. */
.prose code{
  font-family:var(--font-signal); font-size:.875em;
  padding:1px 5px; border-radius:var(--radius);
  background:transparent; border:1px solid var(--colour-line);
  color:var(--colour-text);
}
.prose pre{
  overflow-x:auto; margin:0 0 var(--space-6); padding:var(--space-5);
  border-radius:var(--radius);
  background:var(--colour-observed); border:1px solid var(--colour-line);
}
.prose pre code{
  display:block; padding:0; border:0; background:none;
  font-size:14px; line-height:1.6; white-space:pre;
  color:var(--colour-observed-secondary);
}

.prose hr{
  height:1px; border:0; margin:var(--space-7) 0;
  background:var(--colour-line);
}
.prose blockquote{
  margin:0 0 var(--space-5); padding-left:var(--space-5);
  border-left:1px solid var(--colour-line-bright);
}
.prose blockquote p{ margin:0; color:var(--colour-text) }


/* 9. HELP =================================================================
   The help hub: a search field, a rotating tips panel, and the topic list.
   Topic pages themselves need nothing beyond .prose.
   ======================================================================== */

.help-top{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,380px); gap:var(--space-7); align-items:start; margin-bottom:var(--space-9) }
.help-search-col{ display:flex; flex-direction:column; gap:var(--space-4) }
.help-search{ display:flex; flex-direction:column; gap:var(--space-3) }
.help-search .anchor-title{ margin:0 }

.field{
  width:100%; padding:14px 16px; border-radius:var(--radius);
  border:1px solid var(--colour-line-bright);
  background-color:transparent;
  color:var(--colour-text); font-family:var(--font-body); font-size:var(--type-body);
}
.field::placeholder{ color:var(--colour-text-muted) }
.field:focus{ outline:2px solid var(--colour-text); outline-offset:2px; border-color:var(--colour-text) }

/* The tips panel. Fixed height because the tips are stacked on top of one
   another — without it the panel would collapse to nothing. */
.tips-panel{ position:relative; overflow:hidden }
.tips{ position:relative; list-style:none; margin:0; padding:0; min-height:96px }
.tip{
  position:absolute; inset:0; margin:0;
  font-size:var(--type-body); line-height:var(--leading-body); color:var(--colour-text-secondary);
}
.tip strong{
  color:var(--colour-text); font-family:var(--font-signal);
  font-size:var(--type-signal); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-signal);
}

/* THE GROUP HEADINGS TAKE THE RAIL TOO.

   "Getting started", "The studios" and the rest are section labels by another
   name, and the rail only works if every label on the site is in it. These sit
   two levels inside the wrap rather than one, so the general rule in block 2
   does not reach them and they are wired here by name.

   .help-results passes the rail through rather than becoming a column of its
   own: it exists to be the search target, not to be a layout. */
:is(.section, .hero) > .wrap > .help-results{ grid-column:1 / -1 }
.help-group{
  display:grid; grid-template-columns:var(--layout-rail) minmax(0,1fr);
  column-gap:var(--space-7); align-items:start;
  margin-bottom:var(--space-8);
}
.help-group > *{ grid-column:2; min-width:0 }
.help-group > .anchor-title{ grid-column:1; grid-row:1; margin-bottom:0 }
.help-list{ display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:var(--space-4) }
.help-item{
  display:flex; flex-direction:column; gap:var(--space-2);
  padding:var(--space-4) 0 0;
  border-top:1px solid var(--colour-line);
  background-color:transparent; text-decoration:none;
}
.help-item-name{ font-family:var(--font-display); font-size:1.125rem; font-weight:500; color:var(--colour-text) }
.help-item .note{ margin:0 }
.inline-link{ color:var(--colour-text); text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:3px }


/* 10. PRICING =============================================================
   Two plans, side by side, deliberately the same weight. There is no
   comparison table here and there should never be one: the model is one
   price and one decision, and a feature matrix is how that stops being true.
   ======================================================================== */

.plan-price{
  font-family:var(--font-display); font-size:clamp(2.5rem, 4vw, 3.5rem); font-weight:var(--weight-statement);
  letter-spacing:-.03em; line-height:1; color:var(--colour-text);
  font-variant-numeric:var(--figures-tabular); margin:0 0 var(--space-2);
}

.plan-term{ font-family:var(--font-signal); font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:var(--colour-text-muted); margin:0 0 var(--space-6) }

.plan-points{ list-style:none; margin:var(--space-5) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--space-4) }
.plan-points li{
  position:relative; padding-left:var(--space-6);
  font-size:var(--type-body); line-height:var(--leading-body); color:var(--colour-text-secondary);
}
.plan-points li::before{
  content:""; position:absolute; left:0; top:.62em;
  width:6px; height:6px; border-radius:0;
  background:var(--colour-text-muted);
}
.plan-points strong{ color:var(--colour-text); font-weight:600 }


/* 11. DOWNLOAD ============================================================
   Three platform cards, the install one-liners, and the checksums.

   The version numbers, file sizes and hashes in download.html are real and go
   stale on every release. `scripts/refresh-download.mjs` rewrites them in
   place from the GitHub release API — the page stays an ordinary hand-editable
   file, unlike the generated help pages, and only the marked spans move.

   Note what the download links point at: the api.filamental.space redirect
   routes, never a pinned GitHub URL. The worker updates those every release,
   so a link on this page cannot rot even if nobody remembers to run the
   script. The failure mode is a stale *number* next to a working link, which
   is the right way round.
   ======================================================================== */

/* The release line under the hero. */
.dl-meta{
  display:flex; flex-wrap:wrap; gap:var(--space-2) var(--space-5);
  margin-top:var(--space-5);
  font-family:var(--font-signal); font-size:12px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--colour-text-muted);
}
.dl-meta span{ white-space:nowrap }

/* One platform. The head states the architecture honestly, because that is the
   single thing a download page most often hides and most often gets wrong. */
.dl-arch{
  font-family:var(--font-signal); font-size:12px; letter-spacing:.06em;
  color:var(--colour-text-muted); margin:0 0 var(--space-5);
}

.dl-files{ list-style:none; margin:var(--space-4) 0 0; padding:0 }
.dl-file{
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-4);
  padding:var(--space-4) 0; border-top:1px solid var(--colour-line);
}
.dl-file-main{ display:flex; flex-direction:column; gap:2px; min-width:0 }
.dl-file-name{ font-size:var(--type-small); font-weight:600; color:var(--colour-text); text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:3px }
.dl-file-name:hover{ text-decoration:underline; text-underline-offset:3px }
.dl-file-note{ font-size:13px; line-height:1.45; color:var(--colour-text-secondary) }
.dl-file-size{ font-family:var(--font-signal); font-size:12px; color:var(--colour-text-muted); white-space:nowrap }

/* The install one-liners. Each is the exact string that channel's users
   expect to type — a winget user wants a winget line, not a badge. */
.dl-cmds{ display:grid; grid-template-columns:repeat(auto-fit, minmax(320px,1fr)); gap:var(--space-5); margin-top:var(--space-8) }
.dl-cmd{
  display:flex; flex-direction:column; gap:var(--space-3);
  padding:var(--space-5) 0 0;
  border-top:1px solid var(--colour-line);
  background-color:transparent;
}
.dl-cmd-head{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-4) }
.dl-cmd-name{ font-family:var(--font-display); font-size:1.0625rem; font-weight:500; color:var(--colour-text) }
.dl-cmd-os{ font-family:var(--font-signal); font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--colour-text-muted) }
/* Commands scroll rather than wrap: a broken command line is a command that
   does not run when it is pasted. Direct child only, so that a `code` used
   inline inside the note below is not turned into a block. */
.dl-cmd > code{
  display:block; overflow-x:auto; padding:var(--space-4);
  border-radius:var(--radius); border:1px solid var(--colour-line);
  background:var(--colour-observed);
  font-family:var(--font-signal); font-size:13px; line-height:1.6; white-space:pre;
  color:var(--colour-observed-text);
}
.dl-cmd .note{ margin:0 }
/* A command named mid-sentence, not one to be copied. */
.dl-cmd .note code{ font-family:var(--font-signal); font-size:.9em; color:var(--colour-text) }

/* Checksums. Hex strings are the one thing on this site that genuinely cannot
   fit 375px, so they break rather than overflow the page. */
.dl-hashes{ list-style:none; margin:var(--space-5) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--space-4) }
.dl-hash{ display:flex; flex-direction:column; gap:var(--space-1) }
.dl-hash-file{ font-family:var(--font-signal); font-size:12px; color:var(--colour-text-secondary) }
.dl-hash-sum{ font-family:var(--font-signal); font-size:11px; line-height:1.55; color:var(--colour-text-muted); word-break:break-all }


/* 12. SPACES ==============================================================
   The construction argument. Two components: a numbered build sequence, and
   the compressed capability list that closes the page.

   The sequence is an <ol> with its own numerals rather than list-style
   markers, because the number is doing typographic work here: it is the one
   place on the site where the reader is being walked through an order of
   operations, and the steps need to read as steps at a glance.
   ======================================================================== */

.steps{ list-style:none; counter-reset:step; margin:var(--space-8) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--space-6) }
.step{
  counter-increment:step;
  position:relative; padding-left:var(--space-9);
  min-height:var(--space-7);
}
.step::before{
  content:counter(step);
  position:absolute; left:0; top:.42em;
  width:var(--space-7); height:var(--space-7);
  display:flex; align-items:baseline; justify-content:flex-start;
  font-family:var(--font-signal); font-size:var(--type-signal-small);
  font-weight:var(--weight-signal); letter-spacing:var(--tracking-label);
  font-variant-numeric:var(--figures-tabular);
  color:var(--colour-text-muted);
}
.step-name{ display:block; font-family:var(--font-display); font-size:1.25rem; font-weight:500; color:var(--colour-text); margin-bottom:var(--space-2) }
.step p{ margin:0; color:var(--colour-text-secondary); line-height:var(--leading-body) }
.step code{ font-family:var(--font-signal); font-size:.9em; color:var(--colour-text) }

/* The closing capability list. Deliberately dense and unglamorous: the
   narrative sections have already made the argument, and this is the part
   that answers "yes, but does it actually do X" without another ten
   paragraphs of ceremony. */
.caps{ list-style:none; margin:var(--space-7) 0 0; padding:0; columns:2; column-gap:var(--space-7) }
.caps li{
  break-inside:avoid; padding:var(--space-3) 0 var(--space-3) var(--space-5);
  position:relative; font-size:var(--type-small); line-height:1.5; color:var(--colour-text-secondary);
}
.caps li::before{
  content:""; position:absolute; left:0; top:1.05em;
  width:6px; height:6px; border-radius:0; background:var(--colour-text-muted);
}
.caps strong{ color:var(--colour-text); font-weight:600 }


/* 13. FAQ =================================================================
   Sector pages close on a question block, and it needed separating from the
   prose above it. The reason is a change of reading mode rather than
   decoration: everything before it is an argument you follow in order, and
   this is reference you scan for your own question. Set in the same voice as
   the narrative, it reads as more of the same, and the reader carries on
   reading sentences instead of hunting for theirs.

   One inset panel with hairline rules between entries. Six separate cards was
   the obvious alternative and it is wrong: it spends the weight of six raised
   surfaces on what is really one block, and it competes with the Templates
   card further up the page, which does need to draw the eye.

   Questions stay real headings. They are the extractable surface these pages
   exist for, and each is mirrored in the FAQPage JSON-LD.
   ======================================================================== */

.faq{
  margin-top:var(--space-7);
  padding:0;
  background-color:transparent;
  border-top:1px solid var(--colour-line-bright);
  border-bottom:1px solid var(--colour-line);
}
/* The dividers are accent rather than --colour-line. A 1px grey hairline on
   the card ground was very nearly invisible, and the two ways out were to
   thicken it or to change its colour. Scott's call was the colour, which is
   also the better one: a thicker grey rule would start reading as a table
   border, while accent at this weight reads as punctuation. Held at .45 alpha
   because five full-strength acid lines in one panel compete with the anchor
   title and the links, and accent is spent sparingly everywhere else. */
.faq-item{ padding:var(--space-6) 0; border-top:1px solid var(--colour-line) }
.faq-item:first-child{ border-top:0 }
.faq-q{
  font-family:var(--font-display); font-size:1.125rem; font-weight:500;
  line-height:1.3; letter-spacing:-.01em; color:var(--colour-text);
  margin:0 0 var(--space-3);
}
.faq-a{
  font-size:var(--type-body); line-height:var(--leading-body);
  color:var(--colour-text-secondary); margin:0;
}
.faq-a a{ color:var(--colour-text); text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:3px }


/* 14. CHANGELOG ============================================================
   The only page on the site whose job is to prove the thing is still being
   worked on. So the date is not a parenthetical after the version here, it is
   half the content, and it gets its own column.

   Generated by scripts/build-changelog.mjs from the application's own
   CHANGELOG.md, which means nothing in here is hand-written and this block has
   to survive whatever length of bullet a release happens to contain. */

.releases{ display:flex; flex-direction:column }

.release{
  display:grid; grid-template-columns:180px minmax(0,1fr); gap:var(--space-7);
  padding:var(--space-7) 0; border-top:1px solid var(--colour-line);
}
.release:first-child{ border-top:0; padding-top:0 }

/* The stamp holds while its own bullets scroll past, so on a five-item release
   you can still see which version you are reading. It is released by the next
   one arriving, because a grid row is its own containing block. */
.release-stamp{
  position:sticky; align-self:start;
  top:calc(var(--layout-nav-height) + var(--space-5));
}
.release-ver{
  font-family:var(--font-signal); font-size:var(--type-signal-label);
  letter-spacing:.08em; text-transform:uppercase;
  color:var(--colour-text-muted); font-variant-numeric:var(--figures-tabular);
  margin:0 0 var(--space-2);
}
.release-date{ font-size:var(--type-small); color:var(--colour-text-muted); margin:0 }

/* Accent is spent sparingly everywhere else on the site, and this is the one
   place it earns a filled shape: the whole page is a list of near-identical
   blocks, and the single question most visitors arrive with is which of them
   they are on. */
.release-latest{
  display:inline-block; margin-top:var(--space-3);
  font-family:var(--font-signal); font-size:11px;
  letter-spacing:.1em; text-transform:uppercase;
  color:var(--colour-ground); background:var(--colour-text);
  border-radius:var(--radius); padding:3px var(--space-3);
}

.release-list{
  margin:0; padding:0; list-style:none;
  display:flex; flex-direction:column; gap:var(--space-4);
}
.release-list li{
  position:relative; padding-left:var(--space-5);
  font-size:var(--type-body); line-height:var(--leading-body);
  color:var(--colour-text-secondary);
}
/* A rule rather than a disc. These are notes, and a column of eleven bulleted
   lists reads as a specification you are meant to work through. */
.release-list li::before{
  content:""; position:absolute; left:0; top:.72em;
  width:8px; height:1px; background:var(--colour-line-bright);
}


/* 15. THE CLOSING PLATE ====================================================
   The last block on 23 pages, and the one that carries the site's first job.

   WHAT IT REPLACED. A full-bleed chartreuse slab with a heading, a line of copy
   and two buttons on it. It was the loudest element on every page it appeared
   on, and the register bans its mechanism twice over: colour is never a surface
   fill, and a dark band may not be used as a closing full stop either, because
   dark means observation and a call to action observes nothing.

   Read literally, that removes the loudest thing on the page and leaves the
   download ask quieter than it was. That is not a trade worth making, so the
   emphasis had to come from somewhere the register does allow.

   WHAT IT IS NOW. A specification plate: the thing on the back of an instrument
   that states what the object is. A rule, a statement, the one solid mass this
   design permits, and a readout of literal facts in tabular figures.

   It serves all four of the site's jobs at once, in Scott's own order:

     downloads   the ink button is the only filled mass on a light ground, so it
                 is the most emphatic thing on the page by construction rather
                 than by colour
     buying      the price sits in the same readout as the version and the
                 platforms, stated as a fact rather than argued, so the paid
                 line stops being a separate page you have to go and find
     showcase    placed after an observation field, the return to light is
                 itself the full stop the chartreuse slab used to provide
     AEO / SEO   an answer engine asked what this costs and what it runs on
                 wants a fact table. The old block offered it a sentence about
                 taking a minute, on a chartreuse ground.

   NO RED HERE, and that is deliberate. The test in section 4 is that you must
   be able to say what a red mark points at. On a closing block it would only be
   pointing at "please", which is not a thing. Red stays scarce and keeps
   meaning "the active one".
   ========================================================================= */

.closing{ border-top:1px solid var(--colour-line-bright) }

/* The readout. Keys are signal, values are the answer, and the values are
   tabular because they sit in a column: a column of proportional figures does
   not line up, and lining up is the entire point of a plate.

   auto-fit rather than a fixed four, so a grade of the plate carrying three
   cells is not a broken one. */
.readout{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));
  gap:var(--space-5) var(--space-6);
  margin:var(--space-8) 0 0; padding-top:var(--space-5);
  border-top:1px solid var(--colour-line);
}
.readout > div{ min-width:0 }

/* WHAT THIS COMPONENT IS FOR, in Scott's words: a brand block. "We are sort of
   borrowing credibility from the people we have chosen to integrate with."

   Worth writing down, because it is not what the form suggests. A readout looks
   like a spec table and half of it is doing a spec table's job (version, price,
   platforms). The other half is doing something else entirely: OpenAI,
   Anthropic and Google are not there to be looked up, they are there to be
   recognised. The reader ticks a box they were not conscious of holding.

   TWO CONSEQUENCES.

   The names have to be recognisable rather than precise, so the roster carries
   the company a reader would name (Qwen, Z.AI) rather than the endpoint the app
   talks to (DashScope, Zhipu). The one place precision wins over recognition is
   where a narrow name would imply a narrow capability: "any wikilink Markdown
   folder" rather than "Obsidian", because the second implies you need Obsidian.

   And it is TYPE, never marks. Borrowed credibility normally arrives as a logo
   wall, which this register forbids outright, and a plain list of names is also
   the only version of this that is unambiguously fair use: naming what you
   integrate with is a statement of fact, while reproducing a mark starts
   implying a relationship none of these are in. There is no partnership here.
   The user brings their own key.


   A READOUT IS READ AS EXHAUSTIVE. WRITE IT THAT WAY.

   Scott's rule, and it is the one thing to get right before adding a row: if
   you make a declaration in this form, an omission reads as "not possible"
   rather than as "there were too many to list". A readout is a spec plate, and
   nobody reads a spec plate as a selection.

   So every value here is either a genuinely CLOSED set, or it says out loud
   that it is open. Two of the four rows on the homepage were quietly making a
   claim narrower than the truth:

     Reads: Markdown, Obsidian, Mermaid   said those were the only three things
       it would open. It is IMPORTS, a named feature with a defined set, and it
       works on any wikilink Markdown folder rather than on Obsidian alone.

     Connects to: Claude, Cursor, LM Studio   was the worse one. MCP is a
       standard, so ANY MCP client works, and naming three implied three. That
       is a much weaker claim than the true one, made by accident.

   The provider row is genuinely closed (the table in src-tauri/src/cmd/ai.rs is
   finite) and so is the platform row, so both stay bare lists.

   ONE READOUT, TWO DENSITIES, and the rule for choosing is mechanical rather
   than aesthetic: COLUMNS when every value fits on one line, ROWS when one of
   them is a list.

   The homepage readout is the second kind. "Accepts keys from" names ten
   providers, and in a column that value wraps to three lines while the ones
   beside it sit on one, which is exactly what stops a readout reading as a
   readout: the values no longer line up, and lining up is the whole point.

   Stacked, the key column is fixed so all four keys align, and the value column
   has the full width to itself. It is the same dt and dd underneath, so this is
   a density rather than a second vocabulary. */
.readout-rows{ display:block; padding-top:0; border-top:0 }
.readout-rows > div{
  display:grid; grid-template-columns:200px minmax(0,1fr);
  gap:var(--space-3) var(--space-5); align-items:baseline;
  padding:var(--space-4) 0;
  border-top:1px solid var(--colour-line);
}
.readout-rows dd{ margin:0 }

@media (max-width:640px){
  .readout-rows > div{ grid-template-columns:minmax(0,1fr) }
}
.readout dt{
  font-family:var(--font-signal); font-size:var(--type-signal-small); font-weight:var(--weight-signal);
  text-transform:uppercase; letter-spacing:var(--tracking-label);
  color:var(--colour-text-muted);
}
.readout dd{
  margin:var(--space-2) 0 0;
  font-family:var(--font-signal); font-size:var(--type-small);
  font-variant-numeric:var(--figures-tabular);
  color:var(--colour-text);
}


/* RESPONSIVE ==============================================================
   The bento collapses in two steps: 12 -> halves -> single column.
   ========================================================================= */

/* THE RAIL COLLAPSES BEFORE THE GRID DOES.

   A 200px rail plus a 48px gap costs 248px, which at 900px leaves the prose
   column too narrow to hold a measure. So the rail folds first and the label
   returns to sitting above its content, which is where it was before this
   register and is perfectly legible there.

   Setting --layout-rail to 0 is not enough on its own: a zero-width first
   column still takes the column-gap, so the label would sit in a 0px column
   with 48px of air beside it. The templates collapse to one column as well. */
@media (max-width: 1080px){
  :root{ --layout-rail:0px; --layout-gutter:24px }
  :is(.section, .hero) > .wrap,
  :is(.section, .hero) > .wrap-narrow,
  :is(.section, .hero) > .wrap > :has(> .anchor-title){
    grid-template-columns:minmax(0,1fr);
    column-gap:0;
  }
  :is(.section, .hero) > .wrap > *,
  :is(.section, .hero) > .wrap-narrow > *,
  :is(.section, .hero) > .wrap > :has(> .anchor-title) > *,
  :is(.section, .hero) > .wrap > .anchor-title,
  :is(.section, .hero) > .wrap-narrow > .anchor-title,
  :is(.section, .hero) > .wrap > :has(> .anchor-title) > .anchor-title{ grid-column:1 }
  .help-group{ grid-template-columns:minmax(0,1fr); column-gap:0 }
  .help-group > *,
  .help-group > .anchor-title{ grid-column:1 }
  .anchor-title{ margin-bottom:var(--space-4) }
}

@media (max-width: 991px){
  .span-3,.span-4,.span-5{ grid-column:span 6 }
  .span-7,.span-8{ grid-column:span 12 }
  .nav-links{ gap:var(--space-5) }
  .help-top{ grid-template-columns:minmax(0,1fr) }
}
@media (max-width: 767px){
  .span-3,.span-4,.span-5,.span-6,.span-7,.span-8{ grid-column:span 12 }
  .header-statement{ max-width:none }

  /* .stage-inner's 320px min-height is gone, 2026-08-27. At 375px a capture is
     172px tall and the clip is 136px, so the floor was leaving between 130 and
     184px of bare black hanging under every one of them. That was survivable
     while the field wore a bezel and read as a frame with the picture inside
     it; with the bezel off it is a black slab attached to the bottom of a
     screenshot, which is the same complaint the bezel was removed for. It was
     never guarding against layout shift either: every <img> and the <video>
     carry width and height attributes, so the box is reserved before anything
     loads. Do not reinstate it. */

  /* Two columns of terse list items at 375px would be four words wide. */
  .caps{ columns:1 }

  /* The numeral keeps its size; the gutter does not. At 375px the desktop
     indent left the step text 231px wide, which reads as a column of
     fragments rather than prose. */
  .step{ padding-left:var(--space-8) }

  /* The menu switches on here. The drawer is absolute against .nav, which is
     sticky and so is its own containing block, giving a full-bleed panel that
     sits under the bar rather than inside the 1240px wrap. */
  .nav-burger{ display:flex }
  .nav-links{
    display:none;
    position:absolute; left:0; right:0; top:var(--layout-nav-height);
    flex-direction:column; align-items:stretch; gap:0;
    padding:0 var(--layout-gutter) var(--space-5);
    background:var(--colour-ground);
  }
  .nav-toggle:checked ~ .nav-links{ display:flex }
  .nav-links .nav-link{
    display:block; padding:var(--space-4) 0; font-size:17px; opacity:1;
    border-top:1px solid var(--colour-line);
  }
  .nav-links .btn{ margin-top:var(--space-5); text-align:center }

  /* The version stamp stops being a column and becomes a heading. Sticky has
     to come off with it: at 180px wide it held beside its own list, but as a
     full-width row it would sit on top of the bullets it labels. */
  .release{ grid-template-columns:minmax(0,1fr); gap:var(--space-4) }
  .release-stamp{ position:static }
  .release-latest{ margin-top:var(--space-2) }
}
