/* ============================================================================
   THE TOKEN LAYER — every design value on the site, declared once, here.
   ============================================================================

   This file is the single source for colour, type, spacing, layout and the
   seven screen sizes. Nothing else in this repo writes a hex colour, a type
   size, a spacing step or a breakpoint number. If a value needs to change, it
   changes on one line in this file and every page follows.

   WHY THIS FILE EXISTS AT ALL — read ELEMENTOR-CRUST.md item 12. On the
   Elementor site the same values were retyped into a settings field on every
   surface, and somebody lost the leading `c` off `clamp(`. `lamp(...)` is not a
   CSS function, so the browser silently threw the whole declaration away. It
   happened 32 times across 16 published pages and nobody saw it for months,
   because a discarded declaration looks exactly like a design choice.

   Two things stop that happening again, and both matter:

     1. VALUES LIVE HERE, ONCE. There is nowhere to retype them, so there is
        nothing to mistype 32 times.
     2. `node site/build.mjs` REFUSES TO BUILD on the mistake. It rejects any
        CSS function it does not recognise (so `lamp(` fails by name), any
        `var(--…)` whose token is not declared in this file, any hex colour
        written outside this file, and any media query on a width that is not
        one of the seven below. A typo becomes a red build, not a quiet loss.

   Values come from two sources captured independently: the live Elementor kit
   (ELEMENTOR-CRUST.md, ASSET-SET.md) and the Figma file (vjadoOL6ey89a3PtHU8uRQ,
   pages "Colours and type" and "Parts of the page"). MOST agree; where they
   disagree, or where only one of them carries the value at all, that is noted
   on the line itself rather than smoothed over.
   ========================================================================= */


/* ── The seven screen sizes ───────────────────────────────────────────────

   Confirmed by Joe, 2026-09-10, as four; widened to seven by Joe's ruling of
   2026-09-17 ("add all three"), executed 2026-09-18:

       mobile    600px and below     (this file's base — no query)
       414       menu gap only       (live's "Pro Max" band, 414–480)
       481       menu sizes only     (live's tablet menu starts here, not 601)
       tablet    601px and up        (logo, grid columns, type steps)
       769       menu sizes only     (live's laptop menu starts here, not 1025)
       laptop    1025px and up
       desktop   1367px and up

   Two systems ran at once on the live site (crust item 3): the widget
   breakpoints 601 / 1025 / 1367, which the logo, the content column and the
   type steps follow, and the menu's own set, 414 / 481 / 769 / 1025. The
   first build collapsed the menu onto the widget set and drew it wrong across
   three bands. Joe's reason for carrying both, 2026-09-17: "the goal here is
   a way more exact fit than is common on websites, because Michael is a
   print designer." So the three menu widths are steps of their own, carrying
   menu tokens and nothing else; the widget widths keep everything else.

   A NOTE ON THE ONE VALUE THAT CANNOT BE A TOKEN. CSS custom properties do
   not work inside an `@media` condition — `@media (min-width: var(--tablet))`
   is invalid and is silently ignored. So these six numbers must appear as
   literals in every media query on the site. That is the one place the "write
   it once" rule cannot hold, which is exactly why the build checks it: any
   media query on a width other than 414, 481, 601, 769, 1025 or 1367 fails
   the build. The three custom properties below are the widget set, kept so
   JavaScript can read them if it ever needs to; the three menu widths have
   none, on purpose — they are steps, not a fourth vocabulary.              */

:root {
  --bp-tablet: 601px;
  --bp-laptop: 1025px;
  --bp-desktop: 1367px;
}


/* ── Colour ───────────────────────────────────────────────────────────────
   The seven below appear in BOTH the Figma "Colours and type" page (node 2:2)
   and the live kit, with the same value. The five menu blobs after them do not
   — see the note there.                                                       */

:root {
  --page-background: #005A8E;  /* the blue the whole site sits on              */
  --body-text:       #FFFFFF;  /* all ordinary words                           */
  --subhead:         #FFCB05;  /* every section heading                        */
  --link:            #1DA1F2;  /* links inside text                            */
  --caption:         #BDDAF2;  /* picture captions, and link hover             */
  --secondary-text:  #CCCCCC;  /* in the kit; no element draws it since 2026-10-16 (below) */
  --rule:            #5B98BF;  /* in the kit; no element draws it since 2026-10-16 (below) */
  --subhead-ruled:   #FFFFFF;  /* /about/'s ruled "Frequently Asked Questions" and its line */
  --facebook:        #3B5998;  /* the footer's Facebook mark, as live draws it */

  /* The five hand-painted menu blobs. On the live site these were CSS
     backgrounds keyed to WordPress post IDs — rebuild the menu and every blob
     vanished (crust item 4). Here they are named for the page they belong to.
     The menu itself is built in the next element; these are its colours.

     WHERE THESE COME FROM, AND WHY AN EARLIER NOTE HERE WAS WRONG TWICE.
     Every other colour above is written down in BOTH the live kit and the
     Figma file. These five are written down in the Figma file only — no TEXT
     in the capture carries them, and ASSET-SET.md names the blobs merely
     purple, orange, teal, red and green. An earlier version of this note went
     on to call them "unconfirmed against the live site". THAT WAS WRONG: the
     capture corroborates them in PIXELS rather than prose. The blob artwork
     itself is in assets/menu/*.png, pulled from the live site, and each Figma
     hex is a sample of its own file — dominant colour within 2 (about), 3
     (links), 4 (books) and 5 (talks) units, and 17 (email), against artwork
     that is hand-painted texture rather than flat fill. Nothing needs checking
     against the live site.

     THE THING TO CARRY FORWARD INSTEAD: a blob is a PAINTING, not a colour.
     These hexes are a representative sample for text, borders and fallback.
     The blobs themselves are the PNGs. Do not reproduce a blob with one of
     these values.                                                            */
  --menu-about: #A95798;  /* purple */
  --menu-books: #FE741B;  /* orange */
  --menu-talks: #00ACCC;  /* teal   */
  --menu-links: #FE2A24;  /* red    */
  --menu-email: #58C053;  /* green  */
}


/* ── Type ─────────────────────────────────────────────────────────────────

   Two families, and only two:

     Source Sans 3 — everything on the site
     Raleway       — the five menu words, and nothing else

   THE FONT QUESTION, SETTLED. The Notion design system says "Source Sans 3
   body, Raleway headings". That half-sentence is wrong about headings and the
   evidence is not close:

     · The live kit sets every heading in Source Sans 3 (ASSET-SET.md, "Type"
       — read from kit post 367, which is what the site actually renders, not
       from documentation). Raleway appears on one style: the menu.
     · The Figma type specimens say the same thing independently — page title
       Source Sans 3 SemiBold, section heading Source Sans 3 Bold, and
       "Menu word — Raleway Medium".
     · Joe ruled on 2026-09-10 that Raleway is the menu font, against Michael's
       own source art, which sets the nav words in AvenirLTStd.

   Two captures taken from different artefacts by different sessions agree, and
   Joe's ruling agrees with both. Raleway is the menu font. It is not a
   headings font.

   AND CRUST ITEM 6 IS A DIFFERENT QUESTION THAN IT LOOKS. "The site loads two
   nearly identical fonts and only wants one" is about Source Sans 3 versus
   Source Sans PRO — the retired predecessor, dragged in by a stale link-hover
   setting and one caption on /about/. Elementor's cache held 84 Source Sans
   Pro files against 14 Source Sans 3 for a font the design never wanted.
   Source Sans Pro is the one that disappears. Raleway stays, at two weights.

   So: 18 .woff2 files ship — 16 Source Sans 3 and 2 Raleway — and no request
   ever leaves for Google Fonts.                                              */

:root {
  --font-body: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-menu: "Raleway", var(--font-body);

  /* Ordinary words. The live kit's clamp, which the design system also gives.
     16px on a phone rising to 18px. Figma says "18 down to 16" — same range. */
  --body-size: clamp(1rem, 0.969rem + 0.125vi, 1.125rem);
  --body-line-height: clamp(1.75rem, 1.688rem + 0.25vw, 2rem);

  /* The page title. Every page has one; on this site every one is hidden (see
     base.css and crust item 9). Figma: "32 on a big screen, 22 on a phone".
     Expressed as one clamp so there is one declaration rather than one per
     screen size.                                                             */
  --page-title-size: clamp(1.375rem, 1.14rem + 1vw, 2rem);
  --page-title-line-height: 1.2;

  /* Section headings (h2) and picture captions step per screen size rather
     than sliding, because that is how the live kit sets them. The steps are
     redeclared in the widget-width media queries at the foot of this file —
     a few lines in one file, not a value retyped onto sixteen surfaces.      */
  --subhead-size: 1rem;              /* 16px — mobile   */
  --subhead-line-height: 1.375rem;   /* 22px            */
  --caption-size: 1rem;              /* 16px — mobile   */
  --caption-line-height: 1.375rem;   /* 22px            */

  /* The menu. NOW IN USE — the five painted blobs and their words are built in
     base.css. The sizes below come from the live kit's own custom_css, read
     from the 2026-07-31 dump this session rather than from a summary of it.

     THE LIVE SITE SIZES THE MENU ON ITS OWN BREAKPOINT SET, and since
     2026-09-18 this file carries that set as steps of its own. The kit's
     custom_css switches the menu at 414/481/769/1025 (read from live's CSS
     through the CSSOM, element 6c). The bands, all now matched:

         live  ≤413       ->  base      blob 33  word 10  gap 3   dot 3 @2
         live  414–480    ->  414 step                   gap 6
         live  481–768    ->  481 step  blob 50  word 14  gap 9   dot 4 @4
         live  769–1024   ->  769 step  blob 60  word 18  gap 21  dot 5 @10
         live  ≥1025      ->  laptop                     gap 24

     The 601 and 1367 widget steps carry no menu token: live's menu does not
     move at either width. Measured on both sites at fifteen widths from 375
     to 1440 on 2026-09-18: identical at every one.                          */
  --menu-size: 0.625rem;             /* 10px — mobile   */

  /* The menu word's weight. The live site asks for 600 on phones and 500
     everywhere else.
     THE 600 IS NOT A PROBLEM AND NOTHING NEEDS PULLING OUT OF THE ARCHIVE.
     base.css carried a note saying a 600 request "will silently synthesise
     from 500, which is not the same letterform", and that Raleway SemiBold
     should be pulled from the backup before the menu was built. Both halves
     are wrong, and this session checked rather than reasoned:
       · The archive holds exactly two Raleway files — Raleway-Medium.woff2 and
         Raleway-Bold.woff2, at uploads/2024/10/. There is no SemiBold to pull.
       · The live kit's own @font-face block declares Raleway at 500 and 700
         and nothing else — the same two faces this site ships.
       · CSS font matching for a requested weight ABOVE 500 searches the
         declared weights at or above it first. 700 is declared, so 600
         resolves to the real Raleway Bold face. No synthesis happens, here or
         on the live site.
     So writing 600 below reproduces exactly what a phone renders today, using
     a font file that is already in the repo. There is no decision here for
     anyone to make.                                                          */
  --menu-weight: 600;                /* mobile — resolves to the Bold face */

  /* The painted blob behind each word, and the white you-are-here dot under
     it. Sizes are the live `.menu-item` box; the artwork is 53–55px, so the
     laptop and desktop step upscales it slightly. That is what the live site
     does too, and it is the same 1x asset ceiling the capture flagged — noted
     once, not worked around. */
  --menu-blob-size: 33px;            /* mobile */
  /* The gap between blobs at 413px and below. Live's next bands (6px from
     414, 9px from 481, 21px from 769, 24px from 1025) are the steps below.
     Before 2026-09-18 this site drew 3px all the way to 600px — Joe saw the
     difference on his phone against the site Michael signed off on. */
  --menu-gap: 3px;                   /* mobile */
  --menu-dot-size: 3px;              /* mobile */
  --menu-dot-offset: 2px;            /* mobile — up from the blob's bottom */

  /* THE FOOTER — READ FROM LIVE'S FOOTER TEMPLATE, 2026-09-18 (post-396.css).
     Figma said "12, in capitals" and that is what shipped first; live draws
     0.6em of the body — 9.6px on a phone rising to 10.4px on a desktop — in
     capitals, white, on the body's own line-height. The copyright is tracked
     0.2em and the privacy link 1px; two values on live, so two here. The
     Facebook mark is a 9px glyph with 0.6em of padding on a blue square.
     Joe's ruling, 2026-09-18: match the existing site, the 14px house floor
     notwithstanding — it is Michael's design. */
  --footer-size: 0.6em;              /* of the body: 9.6 → 10.4px */
  --footer-tracking: 0.2em;          /* the copyright line */
  --footer-link-tracking: 1px;       /* the privacy link */
  --footer-icon-size: 9px;           /* the Facebook glyph */
  --footer-icon-padding: 0.6em;      /* of the glyph: 5.4px, so a 19.8px square */
  --footer-padding: 1rem;            /* mobile — above and below the row; steps at 601, 1025, 1367 */

  /* THE /email/ IDENTITY ROW — READ FROM LIVE'S PAGE CSS, 2026-09-18
     (post-2003.css, container eb9b1ff and its three text widgets). Live lays
     "Michael Hall", "author / illustrator" and the address out as a CSS grid
     of three columns: equal thirds from 1025px, `.75fr .75fr 1fr` from 601 to
     1024, and one column (a stack) to 600px. The middle line is centred and
     the address right-aligned in the row; all three start-aligned in the
     stack. Every line carries the same bottom margin — a clamp that is 18px
     on a phone and 20.6px at 1440 — and the row carries it again beneath
     itself, before the illustration. The first build stacked the three lines
     at every width with 4px between them; Joe's ruling of 2026-09-18 is to
     match live. The illustration below the row carries its own space above
     it — live's image widget pads 2rem on a phone, 3rem from 601, 4rem from
     1367 — and the same line margin beneath it before the footer. Five
     tokens: the columns step at 601 and 1025, the two alignments at 601, the
     figure space at 601 and 1367; the line space is one clamp that scales.
     Live's widgets also carry a container margin beneath each line (a second
     clamp, capped at 18.75px) that collapses under the line margin at every
     width and is not carried. */
  --contact-columns: 1fr;                                  /* mobile — a stack */
  --contact-role-align: start;                             /* "author / illustrator" */
  --contact-address-align: start;                          /* the address */
  --contact-line-space: clamp(1.125rem, 1.063rem + 0.25vw, 1.5rem); /* 18 → 20.6px */
  --contact-figure-space: 2rem;                            /* mobile — above the illustration */
}


/* ── Spacing ──────────────────────────────────────────────────────────────

   Six steps, each one growing with the screen. ONE of them is asked for by
   name on the live site — `mt-sm` and `mb-sm` sit on eleven surfaces and are
   defined NOWHERE the live site loads, so that spacing has silently done
   nothing for months (crust item 2). The other five steps come from the
   documented scale reproduced in that same crust item, not from live usage.
   Here all six are real.

   The pixel ranges match the Figma spacing scale exactly: 4→8, 8→16, 16→32,
   32→64, 48→96, 64→128.                                                      */

:root {
  --space-xs:  clamp(0.25rem, 0.5vw, 0.5rem);   /*   4 →   8px */
  --space-sm:  clamp(0.5rem,  1vw,   1rem);     /*   8 →  16px */
  --space-md:  clamp(1rem,    2vw,   2rem);     /*  16 →  32px */
  --space-lg:  clamp(2rem,    4vw,   4rem);     /*  32 →  64px */
  --space-xl:  clamp(3rem,    6vw,   6rem);     /*  48 →  96px */
  --space-xxl: clamp(4rem,    8vw,   8rem);     /*  64 → 128px */

  /* THE STEP BETWEEN BLOCKS ON A BOOK PAGE AND A CONTENT PAGE — READ FROM
     LIVE, 2026-10-16. Live stacks each page as widgets, and the space between
     one widget's last line and the next one's first is the widget's own
     bottom margin: 18px on a phone, 20.6px at 1440, the same clamp the /email/
     row carries (--contact-line-space). This build had used --space-lg there
     (32 → 57.6px), which put about twice live's space above every section
     heading. Measured both sides with the same method, text to text above
     each heading: live 27.2px at 1440 and 21 at 375; --space-lg gave 64.2 and
     35; this step gives live's numbers. Live uses the same margin between
     paragraphs and under each heading, so the step is used inside each
     section too (book-page.css, content-page.css). The EO plan's open
     "rhythm" item; Joe's bar for v1, 2026-09-20, is match live.             */
  --section-step: clamp(1.125rem, 1.063rem + 0.25vw, 1.5rem); /* 18 → 20.6px */

  /* THE STEP INSIDE ONE LIVE TEXT BOX — READ FROM LIVE, 2026-10-16. Two of
     live's text boxes hold several paragraphs on the kit's own paragraph
     margin rather than the widget margin at every width: the first three on
     /talks/ and the five interview lines on /links/; the /links/ podcast
     captions sit the same distance above their players. 12px to 600, 13px
     from 601 (computed margin-bottom, read at 375, 601, 1025, 1367 and 1440).
     A third, the two paragraphs under /about/'s "Where did you grow up?",
     does so only from 1367 (content-page.css .close-desktop). Every other
     paragraph on the site, all twelve book pages included, is on
     --section-step. A block marked `close` in a content page's data takes
     this step instead.                                                     */
  --paragraph-step: 0.75rem;                                   /* 12px — to 600 */

  /* UNDER /about/'S IDENTITY VIDEO — READ FROM LIVE, 2026-11-03. Live's video
     widget carries its own 8px bottom margin where every other widget carries
     the step above: 8px from the video's bottom to the caption line under it,
     measured at 375, 1024 and 1440 (getBoundingClientRect, both sides). The
     space above the video is the ordinary step. */
  --video-step: 0.5rem;                                        /* 8px */

  /* THE CONTENT COLUMN ON /about/, /talks/ AND /links/ AT 600 AND BELOW —
     READ FROM LIVE, 2026-11-03. Those three pages sit in a boxed container
     with 32px of padding, so their column is 32px in from each edge of the
     window at every width to 600 (measured at 320, 375, 481 and 600), while
     the header, the footer, the book pages, /email/ and the privacy page stay
     on the site's own edge (20 → 45px). From 601 all of them share the
     content width again. content-page.css applies it. */
  --content-page-gutter: 2rem;                                 /* 32px — to 600 */
}


/* ── Layout ───────────────────────────────────────────────────────────────

   The content container is `clamp(280px, 85vw, 45rem)` — 45rem is 720px, the
   width every page in the Figma file is drawn at — and it sits inside a site
   maximum of 800px.

   The live kit had this inverted (crust item 11): 50rem on desktop but 70rem
   on every smaller screen, so small screens were given the wider maximum. The
   viewport hid it. One width now, and it grows with the screen rather than
   shrinking.                                                                 */

:root {
  --content-width: clamp(280px, 85vw, 45rem);
  --site-max: 800px;

  /* The logo. Sizes from the Figma breakpoint cards, which match the v6.9.0
     header guide exactly: 120 / 195 / 235 / 245. The file is 248 × 40, so the
     aspect ratio is fixed here to stop the header jumping while it loads.    */
  --logo-width: 120px;               /* mobile */
  --logo-ratio: 248 / 40;

  --rule-width: 1px;                 /* the line under a two-level heading: live draws 1px
                                        (0.5px drew 1px on an ordinary screen and a
                                        hairline on a sharp one); 2026-10-16 */
}


/* ── The book grid — the knobs ────────────────────────────────────────────

   The front page is a grid of twelve book covers, and it is the first real
   build of the bento generator Joe designed on 2026-07-28 (OB1 "TARGET
   COMPONENT: KNOB-ABLE BENTO-GRID SYSTEM" and "BENTO SYSTEM DEEP-DIVE";
   ai-os/lexicon.md, "The bento generator"). Michael Hall Studio is one of its
   two named proving cases — the image case. Deziel's mixed-content page is the
   other, and is not built here.

   THE SHAPE OF THE SYSTEM, in one paragraph, because everything below is a
   knob on it. A fixed number of columns, `grid-auto-flow: dense`, and every
   tile declaring its own span. Nothing is ever placed by hand. That is the
   whole point: hand-placing tiles is why the earlier attempts collapsed
   whenever one tile changed, and dense packing is what lets varied proportions
   stay aligned left and right without anybody positioning anything.

   A BOOK NEVER GETS A PIXEL SIZE. It gets a TAG from a small vocabulary, and
   the system holds one span rule per tag. Retagging a book is a one-word edit
   in site/content/books.json; nothing in this file changes.

   ── Knob 1: how many columns, per screen size ──

   Set below and stepped in the media queries at the foot of this file. Laptop
   and desktop are deliberately the same number, and it is worth knowing why:
   the grid lives inside --content-width, which caps at 45rem/720px, so a
   desktop window wider than that makes the page no wider and more columns only
   make the covers smaller. Changing the column count is one number. Making the
   grid wider than the text column is a different and larger change — it means
   taking the grid out of .container, which is not a knob.                     */

:root {
  --bento-columns: 3;                /* mobile — live is three across to 600px */

  /* ── Knob 2: the gaps — READ FROM LIVE, 2026-09-18 ──
     Live's front page is a plain CSS grid with NO gap of its own
     (post-1997.css, element c56c199: --gap 0, repeat(3, 1fr) to 600px and
     repeat(4, 1fr) above). The space between covers is each image widget's
     own margin, right and bottom, stepped per band:

         ≤600        margin: 0 1rem 01.5rem 0     → 16px across, 24px down
                     (live's own spelling, leading zero and all)
         601–1366    margin: 0 clamp(2rem, 5vw, 3rem) clamp(2rem, 5vw, 3rem) 0
                                                   → 32 at 601, 38.4 at 768,
                                                     48 from 960 to 1366
         ≥1367       margin: 0 2rem 2rem 0        → 32px both ways

     So a cover is one grid cell minus its right margin — column/N − gap —
     and the grid ends one gap short of the column's right edge. Two tokens
     carry it here: the column gap and the row gap, which differ only on a
     phone. Joe's ruling of 2026-09-17 ("more space in between the books")
     was measured at 560px, where live draws 16px against the 8px this file
     used to spend; his ruling of 2026-09-18 is to match live outright. */
  --bento-gap: 1rem;                 /* mobile — across */
  --bento-row-gap: 1.5rem;           /* mobile — down */

  /* ── Knob 3: how many size categories are allowed — THE ONE THAT MATTERS ──

     One dial, rigid to organic:

       1  every book gets an identical box. This IS the uniform-frame
          treatment. The box is the TALL one, so no cover is ever cropped and
          the shorter ones letterbox — Perfect Square most, which is the tell.
       2  square and portrait are told apart. Perfect Square gets its own box;
          the other eleven share the tall one.
       3  the three height buckets are all live. This IS the image-driven
          look. Note what that does and does not mean, measured 2026-09-18:
          the tall box is one tall cover exactly; the square box is 6–10%
          taller than Perfect Square and the standard box 3–4% taller than
          Punch! and Monkey Time, so those three sit at the top of their box
          with that much blue below — as they do on the live site, whose rows
          are as tall as their tallest cover. Three buckets is not twelve
          bespoke boxes.
       4  the feature tile unlocks — two columns wide, for emphasis.

     Uniform and masonry are NOT two implementations to switch between. They
     are this system at two settings, and that is what collapses the open
     question about how the book index should work into one number.

     WHERE THE SETTING ACTUALLY LIVES, STATED PLAINLY BECAUSE AN EARLIER
     VERSION OF THIS NOTE MISLED. CSS cannot branch on a custom property's
     VALUE — there is no `@if var(--x) = 1` — so something has to be a
     selector. The setting is carried as one attribute on one element,
     `data-size-categories` on the grid, and book-grid.css turns it into token
     overrides.

     THE ONE PLACE TO CHANGE IT IS `index.sizeCategories` IN
     site/content/books.json. That is the number the template writes onto the
     grid and therefore the only one the browser ever reads. The token below is
     DOCUMENTATION — it records the shipped default beside the rest of the
     knobs so they can be read in one place, and nothing in book-grid.css
     consumes it. An earlier version of this note said "change both", which
     would have sent someone to edit a value that does nothing. */
  --bento-size-categories: 3;        /* documentation: books.json is the dial */

  /* ── The size vocabulary ──

     A tag is a row span. The unit those spans count is --bento-row below: one
     twenty-seventh of the tallest cover's height plus one row gap (changed
     2026-09-18 — see the note under the spans). So a tile spanning 27 rows is
     exactly one tall cover plus its row gap, and the other spans are the
     smallest whole numbers whose box still holds their cover.

     WHY THESE THREE AND NOT July's tall/wide/square/large-feature. That
     vocabulary does not fit this set. All twelve covers are 200px wide and run
     200–277 tall, so they are portrait-to-square and there is not a wide one
     among them. Measured this session, the twelve fall into three clusters
     with real gaps between them and nothing in between:

         1.000                 Perfect Square                      (1 book)
         1.215 – 1.220         Monkey Time, Punch!                 (2 books)
         1.325 – 1.385         the other nine                      (9 books)

     Three buckets, named for what they are. The spans are those ratios turned
     into whole row steps:

         square    22 steps  ->  a box 6–10% taller than the 1.000 cover
         standard  25 steps  ->  a box 3–4% taller than the 1.215–1.220 covers
         tall      27 steps  ->  1.385 exactly, the cluster at 1.325–1.385

     The art is never cropped — a cover sits inside its box with object-fit:
     contain — so a cover whose own ratio is a little short of its bucket shows
     a hairline of blue rather than losing a millimetre of Michael's drawing.

     A FOURTH TAG EXISTS AND NO BOOK CARRIES IT. `feature` is two columns wide
     and about twice as tall, for a book being given emphasis. It is defined,
     it works, and it is deliberately unassigned — a feature tile measures
     about 353px across at laptop and desktop width, so a 200px cover is drawn
     at about 1.8x its file size. (An earlier note here said "past 2x", which
     overstated it.) Worth knowing that this is a difference of degree, not of
     kind: an ORDINARY tile is about 169px, so on a Retina screen every cover
     on this page is already being drawn at roughly twice its pixels. Larger
     cover art is a whole-grid improvement, not a feature-tile prerequisite —
     the feature tile only makes the existing softness more obvious. Which book (if
     any) gets it is an editorial choice, not a technical one. */
  --bento-row-steps: 27;             /* row steps to one TALL cover plus one row gap */
  --bento-span-square: 22;
  --bento-span-standard: 25;
  --bento-span-tall: 27;             /* = --bento-row-steps */
  --bento-span-feature: 54;          /* two columns wide, so about twice as tall */
  --bento-span-feature-columns: 2;

  /* THE ROW UNIT IS SIZED FROM THE TALLEST COVER, NOT THE SQUARE — changed
     2026-09-18 to match live's rows. Live's grid has no spans: every cover is
     one row, and a row is as tall as its tallest cover plus the bottom
     margin. With the unit set so that 27 steps equal exactly that — the
     tallest cover's height at the current column width, plus the row gap —
     a row of tall covers lands on live's pitch to the pixel at every width.
     The square and standard spans are then the smallest whole numbers whose
     box still holds their cover at every width (checked at 375–1440):
     square 22, standard 25. A shorter cover sits at the top of its box with
     blue below, as it does live (align-items: start there; object-position
     top here). The tallest cover is Little i, 277 × 200.

     TWO LIMITS OF THE MATCH, so nobody is surprised later. Live's rows are
     each as tall as their OWN tallest cover; this ladder is pinned to the
     tallest of all twelve. Today Little i sits in row 1 at four across and
     row 2 at three across, and the rows land within 1.9px of live's at every
     width from 375 to 1440. Reorder the books, retag one, or add a
     thirteenth, and the rows drift further from live wherever Little i is
     not. And below about 329px --content-width's 280px floor takes over
     where live keeps a flat 85%, so at 320px the covers run 2.7px wide of
     live's — a pre-existing floor, not this grid's doing. */
  --bento-tallest-ratio: 1.385;      /* 277 / 200 — Little i, the tallest of the twelve */

  /* ── Derived: the column and the row unit ──

     Both are computed from the knobs above, so changing a knob moves the whole
     grid and nothing needs recalculating by hand. --content-width is the
     grid's real width at every screen size (the shell's own padding never
     squeezes it below its clamp), so this arithmetic is exact rather than
     approximate.

     The column is one grid cell less one gap — live's cover width, since live
     gives every cell a right margin, the last column included, so the grid
     ends one gap short of the column's right edge (book-grid.css does the
     same with max-width). The row unit folds ONE row gap into itself, and the
     grid sets row-gap to zero and puts the gutter inside each tile instead. That is not a flourish: with
     a real row-gap, a tile spanning 27 rows collects 26 gaps and a tile
     spanning 20 collects 19, so tall tiles would grow faster than short ones
     and the vocabulary's ratios would drift apart as the gap changed. With the
     gap inside the tile, a span means the same proportion at every screen
     size. */
  --bento-column: calc(
    (var(--content-width) - var(--bento-columns) * var(--bento-gap))
    / var(--bento-columns)
  );
  --bento-row: calc(
    (var(--bento-column) * var(--bento-tallest-ratio) + var(--bento-row-gap))
    / var(--bento-row-steps)
  );
}


/* ── The screen-size steps ────────────────────────────────────────────────

   Everything that changes by screen size changes HERE and nowhere else. A
   rule elsewhere in the site that wants a different value at a different width
   is a rule that should be a token instead.

   ORDER IS LOAD-BEARING. Every block selects :root and a media query adds no
   specificity, so at a width where two blocks match the LATER one wins. The
   blocks must stay in ascending min-width order — 414, 481, 601, 769, 1025,
   1367 — or a lower step silently overrides a higher one. The build checks
   the widths; it does not check the order. Measure the header after touching
   this section.                                                              */

@media (min-width: 414px) {          /* menu: live's 414–480 gap */
  :root {
    --menu-gap: 6px;
  }
}

@media (min-width: 481px) {          /* menu: live's 481–768 band */
  :root {
    --menu-size: 0.875rem;           /* 14px */
    --menu-weight: 500;              /* the live site drops to 500 here, not at 601 */
    --menu-blob-size: 50px;
    --menu-gap: 9px;
    --menu-dot-size: 4px;
    --menu-dot-offset: 4px;
  }
}

@media (min-width: 601px) {          /* tablet */
  :root {
    --paragraph-step: 0.8125rem;     /* 13px — live's text-box paragraph margin from 601 */
    --subhead-size: 1.0625rem;       /* 17px */
    --subhead-line-height: 1.4375rem;/* 23px */
    --caption-size: 1.0625rem;       /* 17px */
    --caption-line-height: 1.4375rem;/* 23px */
    --logo-width: 195px;             /* the menu does not move at 601 — see the 481 step */
    --footer-padding: 2rem;
    --contact-columns: .75fr .75fr 1fr;                   /* live's 601–1024 row */
    --contact-role-align: center;
    --contact-address-align: end;
    --contact-figure-space: 3rem;
    --bento-columns: 4;              /* live is four across from 601 */
    --bento-gap: clamp(2rem, 5vw, 3rem);      /* live's cover margin, 601–1366 */
    --bento-row-gap: clamp(2rem, 5vw, 3rem);
  }
}

@media (min-width: 769px) {          /* menu: live's 769–1024 band */
  :root {
    --menu-size: 1.125rem;           /* 18px */
    --menu-blob-size: 60px;
    --menu-gap: 21px;
    --menu-dot-size: 5px;
    --menu-dot-offset: 10px;
  }
}

@media (min-width: 1025px) {         /* laptop */
  :root {
    --subhead-size: 1.125rem;        /* 18px */
    --subhead-line-height: 1.5rem;   /* 24px */
    --caption-size: 1.125rem;        /* 18px — read from the live site 2026-09-17 */
    --caption-line-height: 1.5rem;   /* 24px — read from the live site 2026-09-17 */
    --menu-gap: 24px;                /* the only menu value that moves at 1025 */
    --logo-width: 235px;
    --footer-padding: 3rem;
    --contact-columns: 1fr 1fr 1fr;                       /* equal thirds from 1025 */
  }
}

@media (min-width: 1367px) {         /* desktop */
  :root {
    --subhead-size: 1.125rem;        /* 18px */
    --subhead-line-height: 2rem;     /* 32px */
    --caption-size: 1.125rem;        /* 18px */
    --caption-line-height: 2rem;     /* 32px */
    --logo-width: 245px;
    --footer-padding: 4rem;
    --contact-figure-space: 4rem;
    --bento-gap: 2rem;               /* live's cover margin from 1367: a flat 32px */
    --bento-row-gap: 2rem;

    /* The menu does not step again. The live site's last menu change is the
       gap at 1025.

       Neither does --bento-columns: four from 601 is the last step, as live
       has it. --content-width caps at 720px, so a desktop window is not a
       wider grid, only a wider margin either side of the same 720px. The gaps
       do step here — back down to 32px from the 48px of the laptop band,
       which is what live's margins do. */
  }
}


/* ── Three places the two sources do not quite agree ──────────────────────
   Recorded rather than quietly resolved, because a silent pick is how the
   inverted container widths survived for a year.

   1. SUBHEAD AT LAPTOP. The live kit gives 18px at laptop; the Figma
      breakpoint card reads "words 17" there. 18px is encoded, because the kit
      is what the site renders today and matching today is the approval test.
      One pixel, on one breakpoint.

   2. TWO LAPTOP STEPS WERE NOT CAPTURED AT ALL, and both were first CHOSEN.
      ASSET-SET.md records the CAPTION at desktop, tablet and mobile only, and
      the MENU the same way — 18/14/10, no laptop value. Both laptop steps were
      originally set to their desktop size. Neither is disputed by the capture;
      both are simply absent from it, and a token layer needs a value at every
      screen size. Both have since been read from the live site (below).

      MEASURED ON THE LIVE SITE 2026-09-17 (/about/, at 1025, 1100, 1280 and
      1366px): menu words 18px, so the chosen menu value was right; captions
      18px on a 24px line, so the caption SIZE was right and its LINE-HEIGHT
      was not. The caption line-height is now 1.5rem/24px. Desktop (1440px) is
      18px on 32px, as encoded above. Neither laptop value is chosen any more.

      (Two earlier errors in this note, both caught by accuracy checks and both
      worth leaving visible. First, it said the caption was set "so the page can
      render" — wrong; no page built so far contains a figcaption, so nothing
      renders a caption yet. Second, the caption's LAPTOP LINE-HEIGHT was set to
      1.5rem/24px, which is the SUBHEAD's laptop value from the row above it in
      ASSET-SET.md — not the caption's desktop value the note claimed. Now 2rem/
      32px, matching desktop; since 2026-09-17, 1.5rem/24px as measured.)

   3. FOOTER ON A PHONE — SETTLED 2026-09-18 BY READING LIVE. The kit said
      "12px tablet / 0.7em mobile" and Figma a flat 12; live's footer template
      draws 0.6em at every width, and that is what is encoded now. The 12px
      shipped first and was measured against live by element 6d.
   ========================================================================= */
