/* ============================================================================
   THE BASE STYLESHEET — the shell every page stands in.
   ============================================================================

   Font loading, typography, links, the page shell, the header region and the
   footer. Nothing here is specific to one page.

   Every value in this file comes from tokens.css. There is not one hex colour,
   type size, spacing step or breakpoint number written below — the build
   refuses to finish if one appears.
   ========================================================================= */


/* ── The fonts ────────────────────────────────────────────────────────────

   Self-hosted, from the site's own backup. Eighteen files: Source Sans 3 in
   nine weights with the italic of each, and Raleway at Medium and Bold for the
   menu. No request leaves for Google Fonts, and Source Sans Pro — the retired
   predecessor the live site still drags in — does not exist here.

   All eighteen are DECLARED and only the used ones are DOWNLOADED: a browser
   fetches a @font-face file when something on the page actually asks for that
   family, weight and style. So the whole family is available to later pages
   without a second round of plumbing, and a page that uses two faces costs two
   files.

   `font-display: swap` shows the words in the fallback immediately and swaps
   when the real face arrives. On a site that is white text on a blue field,
   text that never appears is a far worse failure than text that shifts.       */

@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-ExtraLight.woff2")      format("woff2"); font-weight: 200; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-ExtraLightItalic.woff2") format("woff2"); font-weight: 200; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-Light.woff2")           format("woff2"); font-weight: 300; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-LightItalic.woff2")     format("woff2"); font-weight: 300; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-Regular.woff2")         format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-Italic.woff2")          format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-Medium.woff2")          format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-MediumItalic.woff2")    format("woff2"); font-weight: 500; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-SemiBold.woff2")        format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-SemiBoldItalic.woff2")  format("woff2"); font-weight: 600; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-Bold.woff2")            format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-BoldItalic.woff2")      format("woff2"); font-weight: 700; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-ExtraBold.woff2")       format("woff2"); font-weight: 800; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-ExtraBoldItalic.woff2") format("woff2"); font-weight: 800; font-style: italic; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-Black.woff2")           format("woff2"); font-weight: 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/SourceSans3-BlackItalic.woff2")     format("woff2"); font-weight: 900; font-style: italic; font-display: swap; }

@font-face { font-family: "Raleway"; src: url("/fonts/Raleway-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "Raleway"; src: url("/fonts/Raleway-Bold.woff2")   format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }

/* THE RALEWAY 600 NOTE THAT USED TO SIT HERE WAS WRONG, AND THE MENU IS NOW
   BUILT, SO HERE IS WHAT IS ACTUALLY TRUE.

   It said: the live menu goes semibold on phones, only 500 and 700 were
   extracted, a 600 request will "silently synthesise from 500, which is not the
   same letterform", so pull Raleway SemiBold out of the archive first. Both
   halves fail on inspection, and this session inspected instead of reasoning:

     · There is no SemiBold to pull. The 2026-07-31 archive holds exactly two
       Raleway files, Raleway-Medium.woff2 and Raleway-Bold.woff2, at
       wp-content/uploads/2024/10/.
     · The live kit's own @font-face block (uploads/elementor/css/post-367.css)
       declares Raleway at 500 and at 700 and at nothing else. The live site is
       in precisely the position this repo is.
     · No synthesis happens in either place. CSS font matching for a requested
       weight ABOVE 500 searches the declared weights at or above it first, so
       `font-weight: 600` finds the declared 700 and uses the real Raleway Bold
       file.

   So the phone menu renders in Raleway Bold today, and writing 600 below
   reproduces that exactly, from a file already in this repo. Nothing to pull,
   nothing to rule. */


/* ── Reset, kept to what is actually needed ───────────────────────────────  */

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

body, h1, h2, h3, p, figure, blockquote, ul, ol { margin: 0; }

img, picture, video { display: block; max-width: 100%; height: auto; }


/* ── The page ─────────────────────────────────────────────────────────────

   White words on blue. The background is set on the html element as well as
   body so it fills the viewport when the page is shorter than the screen.     */

html {
  background: var(--page-background);
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--page-background);
  color: var(--body-text);
  font-family: var(--font-body);
  font-size: var(--body-size);
  font-weight: 400;
  line-height: var(--body-line-height);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
}


/* ── The shell ────────────────────────────────────────────────────────────

   Three regions stacked inside a site maximum, each holding a content column.
   The footer is NOT pushed to the bottom on a short page: on the live site it
   sits directly under the content and the page blue continues below it
   (measured on /email/ at 375 in a 900px window, 2026-09-18 — live's
   copyright line 500px from the top, the first build's privacy link at 840).
   Matching that is one line here:
   the main region does not grow.                                             */

.site {
  width: 100%;
  max-width: var(--site-max);
  padding-inline: var(--space-md);
  display: flex;
  flex-direction: column;
  flex: 1;
  align-items: center;
}

.container {
  width: var(--content-width);
  max-width: 100%;
}

.site-main {
  width: 100%;
  display: flex;
  justify-content: center;
  /* No padding under the content. Measured on the live site 2026-09-18 on
     the front page and a book page at eight widths: the footer starts
     exactly where the last block ends, and the footer's own padding is all
     the room there is. The first build spent --space-xl here — 48px at 375
     rising to 86px at 1440 — room live does not use. */
  padding-block-end: 0;
}


/* ── The hidden heading ───────────────────────────────────────────────────

   Every page carries exactly one h1, first in the document, and on this site
   every one of them is hidden — the visual identity is the logo, and the
   design has no visible page titles.

   Crust item 9 records three ways that went wrong on the live site: a hidden
   h2 placed AFTER the grid it labels, one page whose h1 was a saved global
   widget with a book title hard-coded into it, and two pages with no heading
   at all. All three came from hiding a heading using the page builder's own
   mechanism, which made the heading a piece of layout rather than a piece of
   document structure.

   Here it is one class. The heading is always present and always first for
   anything reading the document — a screen reader, a crawler, a reading-mode
   view — and whether it is painted on screen is a single class on a single
   element. The Figma "Page title" component models the same choice as a
   true/false switch: remove `visually-hidden` and the title appears, correctly
   sized, with nothing else to change.                                         */

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

.page-title {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--page-title-size);
  line-height: var(--page-title-line-height);
  color: var(--body-text);
}


/* ── Headings and text ───────────────────────────────────────────────────  */

/* h3 is drawn exactly as h2. /about/'s questions are h3 under their
   "Frequently Asked Questions" h2 (element 6b) — a structural fix to the
   heading outline that must not change how the page looks. */
h2, h3, .subhead {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--subhead-size);
  line-height: var(--subhead-line-height);
  color: var(--subhead);
}

/* A two-level heading carries a thin line beneath it. One on the site:
   /about/'s "Frequently Asked Questions". READ FROM LIVE, 2026-10-16: the
   label is white, bold, and its 1px white line is the widget's own bottom
   border, directly under the heading's line box with no padding between —
   4px below the letters at 1440, touching them on a phone. The build had
   drawn it in the yellow subhead colour with the kit's blue --rule and
   --space-xs of padding above the line. */
.subhead--ruled {
  color: var(--subhead-ruled);
  border-block-end: var(--rule-width) solid var(--subhead-ruled);
}

/* From 601 up live's label widget carries one more widget margin under its
   line (computed 18.5px at 601, 20.6px at 1440; none at 375), so the first
   question sits a full step further down there. */
@media (min-width: 601px) {
  .subhead--ruled { margin-block-end: var(--section-step); }
}

p + p { margin-block-start: var(--space-sm); }

/* No element carries .secondary since 2026-10-16 — the book publisher line
   was its one user and live draws that line white. Kept, with its token, for
   the record rather than deleted in a match-live fix. */
.secondary { color: var(--secondary-text); }

figure { display: flex; flex-direction: column; gap: var(--space-sm); }

figcaption {
  font-style: italic;
  font-size: var(--caption-size);
  line-height: var(--caption-line-height);
  color: var(--caption);
}


/* ── Links ────────────────────────────────────────────────────────────────

   Upright and underlined, in the accent blue. On the live site three different
   sources set the link colour and the winner was decided by an `!important`;
   here it is one declaration.

   UPRIGHT, NOT ITALIC — MEASURED 2026-09-17 (element 6b). The first build set
   every link italic. On the live site no stylesheet italicises a link: the
   kit's link rule sets colour, family, size, weight and line-height. The six
   interview links on /links/ and the privacy page's link are upright. A link
   is italic live only where the editor wrapped its text in <em> — which on
   the book pages is nearly every link, so book-page.css sets them italic. */

a {
  color: var(--link);
  text-decoration: underline;
  text-underline-position: from-font;
}

a:hover, a:focus-visible { color: var(--caption); }

a:focus-visible {
  outline: 2px solid var(--body-text);
  outline-offset: 2px;
}


/* ── The skip link ─────────────────────────────────────────────────────────

   Added by element 6b (2026-09-17): the first thing a keyboard reaches, and
   it jumps past the logo and menu to <main>. Off-screen until focused, then
   drawn at the top-left of the page in the body colours, with the same focus
   ring every link has. Nothing about the page changes for a mouse user.      */

.skip-link {
  position: absolute;
  left: var(--space-sm);
  top: var(--space-sm);
  transform: translateY(-200%);
  padding: var(--space-xs) var(--space-sm);
  background: var(--body-text);
  color: var(--page-background);
  font-style: normal;
  z-index: 1;
}

.skip-link:focus-visible {
  transform: none;
  color: var(--page-background);
  outline-color: var(--body-text);
}


/* ── The header region ────────────────────────────────────────────────────

   The logo on the left, the menu on the right, on one row across the content
   column, both vertically centred.

   READ FROM THE LIVE SITE, 2026-09-17 (element 6a). The first build set the
   logo above the menu, both centred — Figma's proposal, because the capture
   never recorded the arrangement. The live header measured at eleven widths
   from 1440px down to 320px says otherwise: the logo's left edge sits on the
   content column's left edge and the last blob's right edge on its right edge,
   one row, at every width down to 375px. Only at 320px, where 120px of logo and
   177px of menu no longer fit in the 280px column, does the menu drop under the
   logo — and both then sit on the LEFT, not centred. flex-wrap with
   space-between does exactly that without a breakpoint: a line holding one
   item puts it at the start.

   NO GAP BETWEEN LOGO AND MENU, MATCHING LIVE (Joe, 2026-09-17: "stop
   spending the four px"). Live's header container sets its row and column
   gaps to 0. The first build spent --space-xs here, and once the menu took
   its live size at 481px (tokens.css, the 481 step) those 4px were exactly
   what made 120px of logo plus 286px of menu miss a 408.84px column at 481
   and 482px, dropping the menu under the logo on those two widths. Measured
   on a scratch build, then fixed by removing the gap rather than widening
   the column, which is a value live and this site agree on.

   The logo links to the books page, which is also the home page. On the live
   site this one element carried THREE link values, two of them wrong, one
   pointing at a URL that does not exist (crust item 8). Here it has one.       */

.site-header {
  width: var(--content-width);
  max-width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0;
  padding-block: var(--space-lg);
}

.site-logo {
  display: block;
  width: var(--logo-width);
  text-decoration: none;
}

.site-logo img {
  width: 100%;
  aspect-ratio: var(--logo-ratio);
}

/* ── The menu ─────────────────────────────────────────────────────────────

   Five hand-painted blobs, each with its lowercase word in white on top, and a
   small white dot under whichever one you are looking at.

   THE BLOB BELONGS TO THE NAV ITEM, BY NAME. On the live site the five blobs
   are CSS backgrounds keyed to WordPress menu-item POST IDs — `.menu-item-2015`
   is about, `.menu-item-2017` is books, and nothing in the CSS says so except a
   comment (crust item 4). Rebuild the menu in WordPress and every id changes
   and every blob disappears. Here the class carries the name: `.nav-about`
   loads about.png. There is no id to go stale and nothing to break.

   The old machinery behind that was a PHP plugin swapping PNG backgrounds via
   body classes keyed to those same post IDs. It does not survive, and it does
   not need to — none of what it did is happening here.

   A BLOB IS A PAINTING, NOT A COLOUR. The five --menu-* hexes in tokens.css are
   a sample of each artwork, for text, borders and fallback. They are never used
   to draw a blob; a blob is always its own PNG.                                */

.site-nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--menu-gap);
}

/* Each word sits centred on its blob. The anchor IS the blob: a fixed square
   the artwork fills, with the word centred inside it, so the whole shape is the
   click target rather than just the five or six letters. */
.site-nav a {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--menu-blob-size);
  height: var(--menu-blob-size);
  background-position: center center;
  background-repeat: no-repeat;
  background-size: contain;
  font-family: var(--font-menu);
  font-weight: var(--menu-weight);
  font-size: var(--menu-size);
  font-style: normal;
  line-height: 1;
  text-align: center;
  text-transform: lowercase;
  color: var(--body-text);
  text-decoration: none;
}

.nav-about { background-image: url("/assets/menu/about.png"); }
.nav-books { background-image: url("/assets/menu/books.png"); }
.nav-talks { background-image: url("/assets/menu/talks.png"); }
.nav-links { background-image: url("/assets/menu/links.png"); }
.nav-email { background-image: url("/assets/menu/email.png"); }

/* The live site keeps the word white on hover and changes nothing else — the
   blob is already the affordance. Focus still gets the visible ring every other
   link on the site gets, from the rule further up this file. */
.site-nav a:hover { color: var(--body-text); }

/* ── You are here ─────────────────────────────────────────────────────────

   A centred white dot under the word. Not an underline: the underline→bullet
   change queued on 2026-05-23 had already shipped by the time the site was
   captured, so the bullet is what the live site does today and this is
   match-exactly rather than a change.

   IT HANGS OFF `aria-current`, WHICH WAS ALREADY IN THE MARKUP. That attribute
   is what tells a screen reader which link is the page you are on, and painting
   the dot from it means there is exactly one source of truth for "which page is
   this". A second mechanism — a body class, an `is-active` class — could one day
   disagree with it, which is how the live site ended up with three link
   mechanisms on one book cover.

   THE DOT PAINTS ONLY FOR `aria-current="page"`. The front page's `books`
   link IS the page and carries "page"; a book detail page's `books` link is
   its PARENT and carries `aria-current="true"`, which still tells a screen
   reader "you are in here" but paints nothing. READ FROM THE LIVE SITE,
   2026-09-17 (element 6a): on /books-toc/red/ the live `books` item carries
   Elementor's `current-page-ancestor` but not `elementor-item-active`, and its
   ::after has no content — no dot on a book page. The first build painted one
   on the value-blind `[aria-current]`, on the guess that showing it was the
   more useful behaviour; match-exactly says otherwise. shell.mjs explains
   which value goes where. */

.site-nav a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: var(--menu-dot-offset);
  transform: translateX(-50%);
  width: var(--menu-dot-size);
  height: var(--menu-dot-size);
  border-radius: 50%;
  background: var(--body-text);
  pointer-events: none;
}


/* ── The footer region ────────────────────────────────────────────────────

   READ FROM THE LIVE SITE'S FOOTER TEMPLATE, 2026-09-18 (post-396.css, read
   through element 6d; measured at 375, 481, 601, 769, 1024, 1367 and 1440).
   Three things in one row across the content column, spread edge to edge:
   the copyright line, the privacy link, and the Facebook mark. On a phone the
   row is too narrow for the two lines of text, so the privacy link wraps
   under the copyright, and the mark goes with it to the right. Text is 0.6em
   of the body, in capitals, white, on the body's own line-height.

   The first build drew a centred grey column at 12px with no copyright line
   at all — the line was dropped in the rebuild for no recorded reason, and
   the rest was a Figma reading live does not match. Joe's ruling of
   2026-09-18: match the existing site.

   THE PRIVACY LINK. On the live site it was a text widget wrapped in a
   page-builder "link switcher" — it behaved like a link and read as plain
   text to a screen reader or a crawler. Here it is an anchor, drawn as live
   draws it and underlined only on hover and focus.

   THE FACEBOOK MARK links nowhere on the live site — its anchor has no href
   at all (looked at 2026-09-17, element 6a). It is drawn here exactly as
   live draws it, as a mark and not a link, because it is on the page Michael
   signed off on; where it should point is on his follow-up list.           */

.site-footer {
  width: var(--content-width);
  max-width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  padding-block: var(--footer-padding);
}

.site-footer__copyright,
.site-footer__privacy {
  font-size: var(--footer-size);
  font-style: normal;
  text-transform: uppercase;
  color: var(--body-text);
}

.site-footer__copyright { letter-spacing: var(--footer-tracking); }

.site-footer__privacy {
  letter-spacing: var(--footer-link-tracking);
  text-decoration: none;
}

.site-footer__privacy:hover,
.site-footer__privacy:focus-visible {
  color: var(--body-text);
  text-decoration: underline;
}

.site-social {
  display: flex;
  font-size: var(--footer-icon-size);
  line-height: 1;
  padding: var(--footer-icon-padding);
  background: var(--facebook);
  color: var(--body-text);
}

.site-social svg {
  width: 1em;
  height: 1em;
  fill: currentColor;
}


/* ── Stacks ───────────────────────────────────────────────────────────────
   A page's content is a vertical stack with one spacing step between blocks.  */

.stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-lg);
}

/* Blocks fill the column — except a hidden heading, which must keep the 1px
   box the hiding utility gives it. A layout rule quietly winning against
   `.visually-hidden` on equal specificity is precisely how a "hidden" element
   comes back. Naming the exception here keeps both rules honest. */
.stack > *:not(.visually-hidden) { width: 100%; }


/* ── Page blocks ──────────────────────────────────────────────────────────

   Blocks that sit ON the shell rather than being part of it. Only one so far,
   because only one page is built. When the other pages arrive this section
   either grows a heading of its own or moves to its own file — but it does not
   grow a second copy of any token.

   The contact card, matched to the live page 2026-09-18 (tokens.css says
   what was read). Three lines in a three-column grid from 601px — the name
   at the left, "author / illustrator" centred, the address at the right — and
   a stack below that, each line carrying live's bottom margin. On the live
   site they are three separate widgets faking one row; here it is one grid.
   The address is a real mailto: link (element 6a's fix) where live's is plain
   text nobody can click; that stays. The first build drew the name at 600
   where live draws it at the body's 400.

   The card and the illustration share one stack item (.contact) so the space
   between them is live's — the line margin, then the illustration's own
   padding above it (2 / 3 / 4rem by band) — and not the page stack's step.
   The illustration also carries the line margin beneath it, before the
   footer, as live's image widget does.                                       */

.contact {
  display: flex;
  flex-direction: column;
  gap: var(--contact-line-space);
}

.contact-card {
  display: grid;
  grid-template-columns: var(--contact-columns);
}

.contact-card p {
  margin-block-start: 0;
  margin-block-end: var(--contact-line-space);
}

.contact-card__role    { text-align: var(--contact-role-align); }
.contact-card__address { text-align: var(--contact-address-align); }

.contact figure {
  padding-block-start: var(--contact-figure-space);
  margin-block-end: var(--contact-line-space);
}
