/* Secret Agent
 *
 * Every value here comes from the Build Specification. Nothing is invented.
 *
 * Colour, with contrast against the paper ground measured from the WCAG 2.2
 * relative luminance formula rather than taken on trust:
 *   ink     #26201c   15.15:1   AA pass, AAA pass
 *   brown   #5d413b    8.66:1   AA pass, AAA pass
 *   accent  #9e3524    6.62:1   AA pass, AAA fail (AAA is not the standard)
 *
 * Weights: 400 and 500 only. 600 and 700 would trigger synthetic bold in the
 * licensed families, which is why the specification rules them out.
 *
 * Body size 19px, holding on mobile with no reduction on small screens.
 */

/* --- Fonts -----------------------------------------------------------------
 * Self-hosted, woff2, no CDN (principle 8). The licensed files are not yet in
 * the build; until they are dropped into assets/fonts/ these rules fail and
 * the stack falls through to the system faces below, which is what
 * font-display: swap is for. Migration is: add the files, rebuild.
 *
 * National 2 is confirmed for structural roles (headings, nav, footer).
 * Tiempos Text is confirmed for body prose.
 */

@font-face {
  font-family: "National 2";
  src: url("/assets/fonts/national-2-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "National 2";
  src: url("/assets/fonts/national-2-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* Tiempos Text -- body prose. Regular, Regular Italic (used by <strong>,
 * per the "italic Tiempos Text, not a National 2 Medium switch" ruling),
 * and Medium (standfirst treatment). Licensed files replace the 67-glyph
 * trial subsets -- full character coverage, no more fallback for em dashes,
 * curly quotes, or parentheses. */
@font-face {
  font-family: "Tiempos Text";
  src: url("/assets/fonts/tiempos-text-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Tiempos Text";
  src: url("/assets/fonts/tiempos-text-regular-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Tiempos Text";
  src: url("/assets/fonts/tiempos-text-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

:root {
  --paper:  #faf8f3;
  --ink:    #26201c;
  --brown:  #5d413b;
  --accent: #9e3524;

  --structural: "National 2", system-ui, -apple-system, sans-serif;

  /* Body prose. Tiempos Text confirmed.
   *
   * The fallback face matters more than it looks. The Tiempos TRIAL subset
   * is missing 27 characters the site actually uses -- measured against the
   * built pages, not assumed: 380 em dashes, 324 apostrophes, 173 curly
   * quotes, 149 semicolons, 271 parentheses, 73 colons, 44 question marks
   * and more, 1,498 visible occurrences in total. Every one of them renders
   * in whatever comes next in this stack.
   *
   * National 2 was previously second here, which meant all 1,498 rendered
   * SANS-SERIF mid-sentence inside serif prose -- the exact mid-sentence
   * face switch the Strong-element ruling exists to prevent. Georgia is a
   * serif, is present on effectively every target machine, and covers all
   * 27. So the fallback stays serif throughout.
   *
   * This resolves once the licensed Tiempos files replace the trial subset,
   * at which point the fallback should almost never engage. */
  --body: "Tiempos Text", Georgia, "Times New Roman", serif;

  /* Measure. Hardcoded in px, not rem: a rem value here silently ties
   * container width to the html root font-size, which is a coupling
   * nobody chose and that already drifted once (34rem at 19px computed to
   * 646px, not the 680px actually agreed). 680px stands on its own. */
  --measure: 680px;
}

/* --- Reset ---------------------------------------------------------------- */

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

body, h1, h2, h3, p, ul, li, figure { margin: 0; padding: 0; }

ul { list-style: none; }

html {
  font-size: 19px;                 /* holds on mobile, per specification */
  -webkit-text-size-adjust: 100%;
}

/* 19px read oversized at the 680px measure on wide desktop viewports --
 * confirmed by direct comparison, not assumed. Every size in this sheet
 * is set in rem, so this single root change scales h1/h2/h3/nav/body
 * together and preserves every ratio already tuned above without
 * re-deriving them. Mobile is untouched, per the existing "holds on
 * mobile" ruling -- this only narrows the root at wider viewports. */
@media (min-width: 700px) {
  html { font-size: 17px; }
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-weight: 400;
  line-height: 1.58;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* --- Links ----------------------------------------------------------------
 * The underline is the primary affordance and the warm red is secondary.
 * Colour alone is not sufficient under WCAG 2.2 1.4.1.
 */

a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}

a:hover { text-decoration-thickness: 2px; }

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

/* --- Skip link ------------------------------------------------------------ */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 0.6rem 1rem;
  font-family: var(--structural);
  text-decoration: none;
  z-index: 10;
}

/* Standard accessible-hiding technique -- content stays in the accessibility
 * tree and document outline (screen readers, page-structure tools) but takes
 * no visual space. NOT display:none, which would remove it from both. Used
 * for the homepage's own <h1> (session 5): Paul wants the literal word
 * "Homepage" gone visually, but the page still needs exactly one real h1 for
 * a sound heading structure -- this satisfies both without special-casing
 * the accessibility check itself. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link:focus {
  left: 0;
}

/* --- Header and navigation ------------------------------------------------ */

/* Held to the same 680px measure as main/footer (Paul's call, session 8:
 * the earlier 900px header -- widened so the dropdown nav fit beside the
 * wordmark without wrapping -- put the header out of alignment with the
 * body column below it. This is the fallback session 7 documented for
 * exactly that case: the nav wraps under the wordmark instead. */
.site-header {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 2rem 1.25rem 0.6rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;    /* column-gap reduced from 2rem session 12 --
                              at 2rem the logo + full five-item nav
                              (National 2 Medium, licensed font metrics)
                              overflowed the 680px header by ~7px,
                              forcing a wrap even on desktop. Measured
                              with fontTools against the actual .woff2
                              glyph widths, not assumed. 1.5rem plus the
                              nav-item padding reduction below restores
                              ~27px of real margin. */
  align-items: center;   /* was flex-start -- see note below */
  justify-content: space-between;
  border-bottom: 1px solid var(--brown);
}

/* align-items was flex-start, which tops both flex children (the wordmark
 * image block and the nav <ul>) to the same line, but their box heights
 * differ -- the wordmark is a flush 32px image with line-height:0, while
 * the nav's line-box (from its own font-size/line-height) plus its link
 * padding makes a taller box. Flush-top alignment left the nav sitting
 * visibly high against the wordmark rather than centered on it. Switched
 * to center: confirmed the wordmark image itself is close to vertically
 * symmetric around its glyphs (checked directly against the source file's
 * alpha channel, not assumed from its bounding box), so centering the two
 * flex items against each other lands the nav's own text-center close to
 * the wordmark's optical center rather than its cap-height or baseline. */

.wordmark {
  line-height: 0; /* removes descender gap below the image so it sits
                      on the same baseline as the nav text next to it */
}

.wordmark a {
  display: inline-block;
}

.wordmark img {
  height: 32px;
  width: auto;
  display: block;
}

/* --- Primary navigation: two dropdown menus (Services, About) + three flat
 * links. Promoted to production this session from the session-5 experiment
 * (was scoped under .nav-experimental there so it couldn't leak into the real
 * flat nav; now it IS the real nav, so the scoping is gone and the flat-nav
 * rules it used to fight are removed rather than out-specified). Only the nav
 * rules were carried over -- the experiment file also held orphaned
 * intro-split / about-text / footer-left modules referencing a deleted image
 * and class names the renderer never emits; those were left behind, not
 * merged. Behaviour (open/close, keyboard, outside-click) is in
 * assets/nav.js; with JS off the panels are simply visible and every link
 * still works. */

.site-header nav > ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.15rem;
  font-family: var(--structural);
  font-size: 0.895rem;
  font-weight: 500;
  line-height: 1;                  /* was inheriting body's 1.58, which added
                                       asymmetric leading and pulled the nav's
                                       visual center well below the wordmark's
                                       -- measured directly, not assumed */
  text-transform: uppercase;
  letter-spacing: 0.05em;          /* tracking is a caps convention; nav is caps */
}

.site-header nav li {
  position: relative;
}

/* Flat links and dropdown triggers share one highlight: a rounded rectangle
 * behind the item on hover/focus, matching the dropdown panel's own corner
 * radius rather than a full pill. Accent red measured at 6.62:1 against paper,
 * clearing AA (4.5:1) for normal text. */
.site-header nav a,
.nav-trigger {
  display: inline-block;
  font: inherit;
  line-height: inherit;      /* font: inherit's handling of line-height proved
                                 unreliable in the actual render -- forced
                                 explicitly rather than assumed */
  text-transform: inherit;
  letter-spacing: inherit;
  color: var(--brown);
  text-decoration: none;
  background: none;
  border: none;
  -webkit-appearance: none;  /* iOS Safari applies default button chrome --
                                 including sizing behaviour -- to <button>
                                 elements unless explicitly stripped, even
                                 with background/border/padding already
                                 reset. Likely cause of the brown fill not
                                 reaching the true right edge on a real
                                 iPhone despite every rendering test here
                                 showing it flush -- this class of bug is
                                 specific to real Safari and invisible to
                                 the tooling used to check it. */
  appearance: none;
  cursor: pointer;
  padding: 0.4rem 0.4rem;    /* horizontal reduced from 0.55rem session 12,
                                 alongside the header gap reduction -- see
                                 .site-header for the measured overflow this
                                 fixes. Vertical (0.4rem) unchanged. */
  border-radius: 8px;
  transition: background-color 0.15s ease-out, color 0.15s ease-out;
}

.site-header nav a:hover,
.site-header nav a:focus-visible,
.nav-trigger:hover,
.nav-trigger:focus-visible {
  background: var(--brown);
  color: var(--paper);
  text-decoration: none;
}

.site-header nav a:focus-visible,
.nav-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.site-header nav [aria-current="page"] {
  background: var(--accent);
  color: var(--paper);
}

.nav-trigger[aria-expanded="true"] {
  background: var(--brown);
  color: var(--paper);
}

/* The dropdown panel: same moderate radius as the nav bubbles above. */
/* The dropdown panel: same moderate radius as the nav bubbles above.
 *
 * Restructured session 9, alongside the no-JS fix in render.py. This is
 * now the genuine no-JS baseline: always visible, no hidden state, no
 * animation -- a visitor with JavaScript off sees a plain, fully
 * expanded list under each dropdown trigger, nothing to click to reveal
 * it. Previously this same visual state was reached via the browser's
 * native handling of the `hidden` HTML attribute; render.py no longer
 * emits that attribute at all, so this rule needed to become the
 * unconditional default rather than depend on hidden's absence. */
.nav-panel {
  display: block;
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  min-width: 15rem;
  margin: 0;
  padding: 0.5rem;
  background: var(--paper);
  border: 1px solid var(--brown);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(38, 32, 28, 0.12);
  list-style: none;
  z-index: 20;
}

/* JS-enabled behaviour: collapse by default, ready to animate open.
 * Scoped under .js, added by a tiny inline script in <head> before the
 * stylesheet is even requested -- so this never causes a flash of the
 * open state for JS-on visitors, and never applies at all for JS-off
 * ones, who simply get the always-visible rule above. display:none
 * (not just opacity:0) is deliberate: a panel that's only invisible but
 * still display:block reserves its full layout height, which was the
 * exact bug fixed earlier this session (see the mobile-gap note below,
 * now folded into this same mechanism rather than a separate [hidden]
 * override). display and opacity/transform changing in the same click
 * still animate correctly -- the browser paints the display change
 * first, then transitions opacity/transform within that same update,
 * the same mechanic the old [hidden]-based version relied on. */
.js .nav-panel {
  display: none;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}

/* nav.js toggles this class on click -- see nav.js. Replaces the old
 * :not([hidden]) selector now that hidden is no longer used at all. */
.js .nav-panel.is-open {
  display: block;
  opacity: 1;
  transform: translateY(0);
}

.site-header nav .nav-panel li {
  position: static;
}

/* Real bug, found this session (Paul caught it by actually looking,
 * not from anything my own testing surfaced): this selector's
 * specificity -- one class, one element -- was LOWER than
 * .site-header nav a's (one class, two elements), even though .nav-panel
 * a is the more specific-sounding rule. For every property both rules
 * touch, .site-header nav a won regardless of file order: its
 * `font: inherit` shorthand silently overrode font-family, font-size,
 * AND text-transform back to whatever the outer <ul> sets (National 2,
 * uppercase, 0.895rem) -- so the "serif submenu" from earlier this
 * session was never actually rendering as written, on desktop or
 * mobile, and the hover colour below was very likely wrong the same
 * way. This wasn't a new regression; nothing about .nav-panel a's
 * specificity changed this session, only its declared properties did --
 * the earlier hover/text-transform/font-size rules had presumably lost
 * the same fight for as long as this selector has existed. Fixed by
 * including the ancestor context explicitly so this reliably outranks
 * the general nav-link rule instead of hoping cascade order saves it. */
.site-header nav .nav-panel a {
  display: block;
  width: 100%;
  border-radius: 8px;
  padding: 0.55rem 0.85rem;
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.92rem;
  color: var(--ink);
  background: none;
}

.site-header nav .nav-panel a:hover,
.site-header nav .nav-panel a:focus-visible {
  background: #f0ede5;
  color: var(--ink);
}

.site-header nav .nav-panel [aria-current="page"] {
  background: var(--accent);
  color: var(--paper);
}

/* A cross-page link in the copy awaiting a destination ruling. Rendered as
 * text, not a dead link. (The nav's own former .nav-unresolved state is gone:
 * every nav link now resolves to a real page, so nothing produces it.) */
.link-unresolved {
  color: var(--brown);
  opacity: 0.55;
}

/* --- Page ---------------------------------------------------------------- */

main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 3.5rem 1.25rem 5rem;
}

/* Homepage only (session 5): with the h1 now visually hidden above, the
 * standard top padding left a bigger gap under the header than intended --
 * that padding was originally sized to sit under a visible h1. Tightened
 * specifically here rather than changing `main`'s padding globally, which
 * would also shift the other 19 pages, not asked for. */
main.homepage {
  padding-top: 2.5rem;             /* was 1.75rem, too tight; original (all
                                       other pages) is 3.5rem -- this splits
                                       the difference as a first pass */
}

/* Fluid by default -- explicit width/height attributes on the <img> itself
 * reserve the correct aspect ratio and prevent layout shift while the file
 * loads; this makes the rendered size responsive to the container. */
main img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Shared two-column layout: an image and a block of text side by side at
 * roughly even width, stacking on narrow viewports. First built for the
 * hero (image + tagline); the ABOUT section needed the same arrangement
 * shortly after (image + address paragraph), so the mechanics live here
 * once rather than being copy-pasted a second time -- this project's own
 * "three similar cases, generalise" rule applied a step early, since the
 * two known cases already share 100% of the layout logic. */
.media-split {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.media-split > figure,
.media-split > p {
  flex: 1 1 50%;
  margin: 0;
}

@media (max-width: 700px) {
  .media-split {
    flex-direction: column;
    align-items: stretch;
  }

  /* Dropdown panels are position:absolute, anchored left:0 to their own
   * trigger <li> -- fine on desktop where triggers sit near the left of a
   * single row, but on mobile the nav wraps across several rows and a
   * trigger positioned anywhere but the true left edge sends its panel
   * past the viewport edge, forcing the whole page to scroll horizontally.
   * Below this width, panels drop back into normal flow instead: full
   * width, no floating box, expanding the row beneath the trigger like an
   * accordion. */
  .nav-panel {
    position: static;
    width: 100%;
    min-width: 0;
    margin-top: 0.5rem;
    box-shadow: none;
    transform: none;
  }

  /* Real bug, found this session from an actual iPhone screenshot (not
   * caught by any of this session's own screenshot testing, which never
   * exercises a genuine narrow viewport's flex-basis sizing the way a
   * real device does): nothing here ever gave the <nav> element itself a
   * width. .site-header nav li's width:100% is 100% of nav's own box --
   * and nav, as a flex item with no flex-grow and flex-basis:auto,
   * shrink-wraps to its content's natural size rather than filling the
   * row. In a flex-direction:column list, that natural size resolves to
   * the width of the single widest item (here, the "SERVICES" button),
   * not the viewport. The visible result: nav floats as a narrow column
   * beside the wordmark instead of spanning full width below it. Fixing
   * nav's own width directly here, rather than depending on flex sizing
   * to infer it from children, avoids the whole ambiguity. */
  .site-header nav {
    width: 100%;
  }

  .site-header nav li {
    width: 100%;
  }

  .site-header nav > ul {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
  }

  /* Mobile nav rhythm: rely on generous padding alone, no dividers.
   * Hairline dividers were tried and reverted this session -- reasonable
   * feedback that at any opacity they read as a form/table grid rather
   * than architectural spacing, and that padding alone can carry the
   * rhythm without needing a line to mark every boundary.
   *
   * Touch target trialled at 44px this session (was 48px) -- Apple HIG /
   * WCAG 2.5.5's own cited minimum, one step down from Android Material's
   * 48px. Paul asked to see it, not committed either way yet. min-height
   * plus box-sizing:border-box still guarantee the real tappable area
   * regardless of padding. display:flex + align-items:center centers the
   * text within that box now that padding alone no longer fills it.
   *
   * border-radius deliberately NOT overridden here (was 0 earlier this
   * session, flush edge-to-edge) -- Paul asked for the same rounded
   * highlight desktop already has. Falling through to the base rule's
   * 8px rather than re-declaring it, so the two can't drift apart. The
   * row is still full width; only the background fill on hover/focus/
   * open rounds at its corners, and the header's own side padding
   * (~1.25rem) keeps that rounding well clear of the true screen edge. */
  .site-header nav a,
  .site-header nav .nav-trigger {
    width: 100%;
    min-height: 44px;
    padding: 0.4rem 0.1rem;
    box-sizing: border-box;
    display: flex;
    align-items: center;
  }

  /* Dropdown triggers (Services, About) were visually identical to flat
   * links (Research, Offshore, Contact) -- nothing told a visitor which
   * ones expand and which ones navigate immediately. A plus/minus toggle
   * answers that without needing a label change or extra markup: two bars
   * form a "+" at rest; open state rotates the vertical bar flat and
   * fades it, leaving a single "−". Absolutely positioned within the
   * button rather than flexed alongside the text, so it doesn't depend
   * on the label's width the way the flex/space-between chevron did. */
  .nav-trigger {
    position: relative;
    padding-right: 2rem;      /* reserve room so the label never runs
                                  under the icon at this width */
  }

  .nav-trigger::before,
  .nav-trigger::after {
    content: "";
    position: absolute;
    right: 0.4rem;
    top: 50%;
    width: 12px;
    height: 1.5px;
    background: currentColor;
    transform: translateY(-50%);
    transition: transform 0.2s ease-out, opacity 0.2s ease-out;
  }

  .nav-trigger::before {
    transform: translateY(-50%) rotate(90deg);
  }

  .nav-trigger[aria-expanded="true"]::before {
    transform: translateY(-50%) rotate(0deg);
    opacity: 0;
  }

  /* Submenu children (inside the Services/About dropdown panels): tried
   * as body serif this session (distinguishing "menu" from "sub-menu"
   * typographically), shown to Paul alongside a National-2 comparison
   * build, and settled as National 2 throughout -- same as desktop,
   * keeping one nav typography rule rather than two. No font-family
   * override needed here: falls through to the inherited National 2
   * from .site-header nav > ul, same mechanism desktop already uses.
   *
   * Selector kept strengthened (ancestor-prefixed) from the specificity
   * fix earlier this session even though font-family is no longer being
   * set here -- display/min-height/box-sizing still need to reliably
   * beat the base .nav-panel a rule's own display:block. */
  .site-header nav .nav-panel a {
    font-size: 1rem;
    min-height: 44px;
    display: flex;
    align-items: center;
    box-sizing: border-box;
  }
}


/* .about-photo carries no CSS of its own besides this -- sizing and spacing
 * still come from .media-split above, which the office photo is always
 * wrapped in (session 5). Grayscale + a soft corner radius, session 8. */
.about-photo img {
  border-radius: 14px;
}

/* The homepage hero statement (formerly under the suppressed 'Headline'
 * label -- see render.py). Reads as a headline, not running prose, so it
 * takes the structural face rather than the body face, per the site's own
 * "sans for headings, serif for reading" principle. Medium (500), the
 * heaviest weight actually loaded -- never a synthetic bold. */
.tagline {
  font-family: var(--structural);
  font-weight: 500;
  font-size: 1.7rem;
  line-height: 1.25;
  letter-spacing: -0.012em;
  margin-bottom: 1.75rem;
}

/* ABOUT section (session 5): office photograph beside its address
 * paragraph, same media-split mechanics as the hero. */
.about-split {
  margin-top: 1.5rem;
}

/* The paragraph immediately after .about-split. Its default spacing was tuned
 * for text following text -- coming right after a photo (often taller than the
 * text column beside it) it read as glued to the photo's bottom edge, so this
 * adds breathing room. In v7.5 the following element was the "About →"/email
 * link line; at v7.6 the ABOUT copy split into two paragraphs, so the element
 * that now follows the split is "Secret Agent is led by Paul Osborne." (the
 * address paragraph pairs with the photo inside the split; see render.py).
 * The rule still does the right thing either way -- it spaces whatever
 * paragraph follows the photo -- but the element it lands on has changed. */
/* The homepage's opening intro paragraph -- the one immediately following
 * the bold hero tagline (formerly the 'Second layer' label; see render.py).
 * Reads as a standfirst bridging the hero headline into the running THE
 * FIRM prose below, so it's set slightly larger than standard body text
 * on desktop, per Paul's request. Targeted by adjacent-sibling selector
 * (.tagline + p) rather than a new class: .tagline only ever exists on the
 * homepage hero, so this can't accidentally match anything else, and it
 * means no change was needed to render.py or verify.py's independent
 * census -- the element is still plain <p> as far as both are concerned.
 * Desktop-only (root is 17px there; this sits at 18.5px, inside Paul's
 * 18-19px ask). Not touched on mobile, where root is already 19px and a
 * separate ruling would be needed if a bump is wanted there too. */
@media (min-width: 700px) {
  .tagline + p {
    font-size: 18.5px;
  }
}

.about-split + p {
  margin-top: 1.5rem;
}

/* Session 8 typeface trial (Tiempos Headline vs. National 2 for h1/h2/h3)
 * resolved: National 2 throughout, all heading levels, one consistent
 * structural system rather than a serif/sans split. Reviewed as two full
 * options -- "sans headings + serif body" vs. a Tiempos-headline hybrid --
 * and National settled on for every level, avoiding the seam a partial
 * split kept producing wherever the two systems met on the same page. */
h1 {
  font-family: var(--structural);
  font-weight: 500;
  font-size: 1.95rem;
  line-height: 1.18;
  letter-spacing: -0.012em;
  margin-bottom: 2.4rem;
}

/* '###' in the source: a section heading within a page. Size raised from
 * 1.16rem: the original left an oversized jump from h1 (1.68x) and almost
 * none from h2 to h3 (1.22x) -- visibly out of proportion. 1.4rem brings
 * the h1/h2 step down to 1.39x, close to the h2/h3 step, without touching
 * h1 or h3, both already doing their intended job (h1 as the page title,
 * h3 as a small label). */
h2 {
  font-family: var(--structural);
  font-weight: 500;
  font-size: 1.4rem;
  line-height: 1.28;
  letter-spacing: 0.06em;            /* was unset (0) -- approved session 5
                                         after a single-page test */
  /* Fluid top margin, confirmed correct on mobile. Scaled from the flat
   * 2.9rem (55.1px at the 19px root) down to a proportional mobile
   * minimum, interpolated across the 320-680px viewport range -- 680px
   * matching this file's own container width, not an arbitrary test
   * figure. Desktop top margin was deferred pending the h1 typeface
   * decision (Build Specification); that's resolved now (session 8,
   * National 2 throughout) -- the clamp's own desktop ceiling, 55.1px,
   * already exceeds the 52px an external review separately suggested, so
   * no change was needed there. Bottom margin tightened per that same
   * review: was 1.1rem (~18.7px at the 17px desktop root), now closer to
   * its suggested 14px so the heading grounds to the copy beneath it
   * rather than floating equidistant between the block above and below. */
  margin-top: clamp(36.7px, 20.35px + 5.111vw, 55.1px);
  margin-bottom: 0.85rem;
}

/* '####' in the source: a publication grouping on the Media page. */
h3 {
  font-family: var(--structural);
  font-weight: 500;
  font-size: 0.95rem;
  line-height: 1.35;
  letter-spacing: 0.01em;
  color: var(--brown);
  margin: 2.2rem 0 0.9rem;
}

/* Whole-line bold in the source: a run-in head. Sits tighter to the copy it
 * labels than a section heading does. */
h3.runin {
  font-family: var(--structural);
  font-weight: 500;
  font-size: 1rem;
  color: var(--ink);
  letter-spacing: 0;
  margin: 2.1rem 0 0.55rem;
}

/* Archive entries are links to their reports. Ruled by the copy project:
 * 'the archive entries render as links only' -- no cover images.
 *
 * The site's link treatment is accent red plus an underline. Applied
 * unchanged here it repaints ninety-one headings red down a single page and
 * the archive stops reading as a list of reports and starts reading as a
 * warning. So the heading keeps its ink colour, being a heading first, and
 * takes the underline, being a link second. Nothing new is invented: the
 * underline is the site's own, at the site's own offset and thickness.
 *
 * Hover and focus hand the accent back, so the affordance is the same one
 * every other link on the site uses.
 */
h3.runin a.archive-pdf {
  color: inherit;
  text-decoration-color: var(--brown);
}

h3.runin a.archive-pdf:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

p {
  margin-bottom: 1.5em;              /* was 1.1em (18.7px) -- now closer to
                                         body leading (26.9px), approved
                                         session 5 after a single-page test */
}

p:last-child { margin-bottom: 0; }

/* Progressive enhancement, from the same external review: unsupported
 * browsers simply ignore these and wrap normally, so there's no fallback
 * to write. balance prevents an orphaned short line in a heading; pretty
 * reduces (not eliminates -- it's a browser heuristic, not a guarantee)
 * single trailing words at a paragraph's end. */
h1, h2, h3, .tagline {
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

/* --- Strong ----------------------------------------------------------------
 * Decided, not invented here: within running prose, only one family ever
 * appears. <strong> renders as italic in the body face — never a switch to
 * National 2, and never bold, since only 400/500 are loaded and an
 * unstyled <strong> would otherwise trigger the browser's synthetic bold,
 * exactly what the weight ceiling below is meant to prevent.
 */

strong {
  font-family: inherit;
  font-weight: 400;
  font-style: italic;
}

/* --- Italic blocks -------------------------------------------------------
 * Epigraph and attribution are not distinguished in the parser: the document
 * contains exactly one such pair, and adjacency in CSS decides it without any
 * parse-time judgement. The first italic block in a run carries the rule; a
 * second following it is the attribution and does not.
 */

.italic-block {
  font-style: italic;
  color: var(--brown);
  border-left: 2px solid var(--brown);
  padding-left: 1.25rem;
  margin-bottom: 1.1em;
}

.italic-block + .italic-block {
  border-left-color: transparent;
  font-size: 0.9rem;
  margin-top: -0.5em;
}

/* --- Display notes -------------------------------------------------------
 * The residual whole-line italic, and from v9.0 it means one thing: a note
 * about how to read the page, not a quotation and not part of the argument.
 * Two on the site -- the RHA reading note and the Media catalogue summary.
 *
 * It joins the apparatus family (.archive-intro-note, the About caption,
 * the interview speaker labels): National 2, smaller than body, brown.
 * Quieter than the prose, never louder. It previously carried the italic
 * block's left rule, which said someone was being quoted when nobody was,
 * and gave a reading note more weight on the page than the content above it.
 *
 * The archive health notes are NOT in this class. They keep p.italic-block:
 * the copy project has never ruled on them, and adopting this treatment for
 * them would be a ruling nobody made. */

.note {
  font-family: var(--structural);
  font-size: 0.82rem;
  line-height: 1.6;
  color: var(--brown);
  margin-bottom: 1.4em;
}

/* --- Quotations ----------------------------------------------------------
 * Canon marks every quotation with '>'. render.py emits the first paragraph
 * as p.quote-text and any that follow as p.quote-attrib. Two shapes exist,
 * and they are separated here on shape alone -- no new class from the
 * builder and no parse-time judgement:
 *
 *   epigraph  quotation + attribution   -> .quote-text is NOT an only child
 *   extract   quotation alone           -> .quote-text IS an only child
 *
 * Canon's two extracts also carry a footnote marker and none of the four
 * epigraphs does, so a misclassification would be visible rather than
 * silent.
 *
 * No border on either. Ruled on the rendered page, reversing a ruling made
 * from a description: a left rule brackets nothing on a centred block, and
 * on an essay page the standfirst above already carries one. The centred
 * treatment and its 0 2rem padding are the values the superseded
 * '.essay-rule + .italic-block' rule used, carried over unchanged now that
 * epigraphs are blockquotes rather than italic blocks. */

blockquote {
  margin: 2.4rem 0;
}

blockquote .quote-text {
  font-style: italic;
  text-align: center;
  color: var(--ink);
  margin: 0 0 0.55em;
  padding: 0 2rem;
}

blockquote .quote-attrib {
  text-align: center;
  font-family: var(--structural);
  font-size: 0.88rem;
  color: var(--brown);
  margin: 0;
  padding: 0 2rem;
}

/* Extract: left-aligned at the measure, indented, body type, no centring,
 * no attribution expected. Indented deeper than the epigraph's optical
 * padding, because here the indent is the only thing marking the quotation
 * off from the prose it sits inside. */
blockquote > .quote-text:only-child {
  font-style: normal;
  text-align: left;
  padding: 0 2.25rem;
  margin: 0;
}

/* --- Cross-page links ---------------------------------------------------- */

.link-line {
  font-family: var(--structural);
  font-size: 0.92rem;
  margin-top: -0.7em;                /* pulls the link up against the
                                         one-line description that always
                                         precedes it (homepage pattern:
                                         description paragraph, then its
                                         link), so the two read as one
                                         grouped unit rather than as
                                         successive, unrelated paragraphs */
  margin-bottom: 1.4em;
}

/* --- Prose bullet lists --------------------------------------------------
 * Targets UNCLASSED lists in body copy: the Media page's coverage record and
 * the two essays' bullet lists (the financial-cycle characteristics and the
 * closing SUMMARY). Component lists that carry a class -- .archive-list, and
 * anything added later -- are deliberately excluded.
 *
 * This was `main ul` / `main li`, which reached every list inside <main>
 * regardless of what kind of list it was. The heading said "(Media page)"
 * and was wrong twice over: the essays grew bullet lists after it was
 * written, and the rules also landed on .archive-list, where the dash below
 * printed on top of the month text because .archive-list li's `padding:
 * 0.42rem 0` shorthand zeroes the 1.1rem gutter the dash is positioned in
 * while leaving the dash itself. Found by looking at a rendered mockup;
 * nothing had ever emitted .archive-list before, so it had never shown.
 *
 * Scoped by the absence of a class rather than by page, because there is no
 * page to scope to: Media carries no container class, and narrowing to one
 * would strip these rules from 56 list items across the two essay pages.
 * All six declarations below are prose-list styling and move together --
 * position and padding-left exist only to host the dash, and the three text
 * declarations are sized for reading bullets, not for a listing component.
 */

main ul:not([class]) {
  margin-bottom: 1.6em;
}

main ul:not([class]) li {
  position: relative;
  padding-left: 1.1rem;
  margin-bottom: 0.5em;
  font-size: 0.94rem;
  line-height: 1.5;
}

main ul:not([class]) li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.4rem;
  height: 1px;
  background: var(--brown);
}

/* --- Footnotes ----------------------------------------------------------- */

.fnref a {
  color: var(--accent);
  font-weight: 500;
  text-decoration: none;
  padding: 0 0.08em;
}

.fnref a:hover { text-decoration: underline; }

.footnote {
  font-style: italic;
  color: var(--brown);
  font-size: 0.88rem;
  line-height: 1.55;
  margin-bottom: 0.8em;
}

.fn-back {
  color: var(--accent);
  font-weight: 500;
  font-style: normal;
  text-decoration: none;
}

.fn-back:hover { text-decoration: underline; }

/* --- Essay pages --------------------------------------------------------- */
/* Designed session 10 (mockup). Wayfinding eyebrow, metadata line, standfirst panel,
 * and series block provide wayfinding and context for long-form essays that
 * sit under /paper/. The standfirst panel is ready for when the copy arrives;
 * both essays currently have placeholder text. */

/* 21px reading measure (session 12), confirmed against the design brief:
 * essays get a larger body size than the 17px/19px standard pages, since
 * they are long-form reading (13,000-30,000 words) rather than short
 * scannable service pages. Desktop only, matching the site's existing
 * "19px holds on mobile" convention -- mobile body text is untouched.
 * Scoped to <main class="essay"> so no other page is affected; render.py
 * adds the class from the /paper/ URL prefix. Set in px, not rem, since
 * rem would also rescale the nav/footer/heading sizes that share the root
 * -- only the essay's own paragraphs should grow. p's existing
 * margin-bottom: 1.5em is unitless-relative and scales correctly with
 * this without any extra rule. */
@media (min-width: 700px) {
  main.essay > p:not(.eyebrow):not(.essay-meta):not(.essay-byline):not(.essay-date):not(.footnote) {
    /* 21px -> 18.33px, Paul's read of the rendered page, 17 September.
     * Two typographic points: 1pt is 1.333px in CSS, so this is exactly two
     * and not an approximation of two.
     *
     * NOTE FOR WHOEVER READS THIS NEXT. This now sits BELOW the 19px
     * standard-page body size, inverting the session 12 rationale recorded
     * above -- essays were given a LARGER size precisely because they are
     * 13,000 to 30,000 words of long-form reading.
     *
     * Settled at 19px on Paul's second read: level with the standard pages
     * rather than below them. The session 12 comment above is left standing
     * -- it records why the size was raised, and this records why it came
     * back down. Both are true as of their dates. */
    font-size: 19px;
    line-height: 1.6;
  }
}

/* Wayfinding eyebrow: a single line above the h1 naming the page's orbit
 * ("Services", "About", "The Financial Cycle Trilogy"). Governed copy from
 * the Master Document. Deliberately not a link, so no anchor rule here --
 * see render.py. Replaced the two-segment .crumb at v8.0; the separator
 * span and anchor colour went with it. */
.eyebrow {
  font-family: var(--structural);
  font-size: 0.8rem;
  color: var(--brown);
  margin-bottom: 0.5em;
}

/* Essay h1: centred, with a tighter bottom margin into the byline. */
main.essay h1 {
  text-align: center;
  margin-bottom: 0.5em;
}

/* Byline: centred below the h1, structural face, muted brown.
 * Sits between h1 and the essay-meta line. */
.essay-byline {
  font-family: var(--structural);
  font-size: 0.95rem;
  text-align: center;
  color: var(--brown);
  margin-top: 0.4em;
  margin-bottom: 0;
}

/* Publication date, centred beneath the byline (v8.0). One size down from
 * the byline so the two read as a credit and its date rather than as two
 * equal lines. Excluded from the essay body's 21px rule by class, like the
 * byline above it. */
.essay-date {
  font-family: var(--structural);
  font-size: 0.85rem;
  text-align: center;
  color: var(--brown);
  margin-top: 0.15em;
  margin-bottom: 0;
}

/* "Essay one of three · also available as a PDF." */
.essay-meta {
  font-family: var(--structural);
  font-size: 0.9rem;
  /* Was margin-top: 0, which butted this line against whatever sat above it.
   * Now drops one full line of essay leading -- 18.33px at 1.6 is 29.33px,
   * which is 1.71em at this element's own 17.1px -- so the metadata line
   * starts a line below rather than immediately under. Paul's read of the
   * rendered page: the head of the essay was bunched. */
  margin-top: 1.71em;
  margin-bottom: 1.8em;
  color: var(--brown);
}

.essay-meta a { color: var(--accent); }

/* Standfirst: shaded panel with left border rule. Tiempos Text slightly
 * larger than body, so it reads as an opening and not an aside. Background
 * is a warm tint of the paper colour.
 *
 * The size tracks the essay body and has to be re-checked when that moves.
 * At 1.2rem against a 21px body it was 8.6 per cent larger, which is what
 * 'slightly' meant. When the body came down to 19px on 17 September the
 * same 1.2rem became 20 per cent larger and the panel started reading as a
 * block rather than an opening -- the comment stopped describing the rule.
 * Brought back to 1.1rem, 20.9px, 10 per cent over body. */
.essay-intro {
  background: #f4efe4;
  border-left: 3px solid var(--brown);
  padding: 1.05rem 1.25rem;
  margin: 0 0 2.1em;
}

.essay-intro p {
  font-family: var(--body);
  font-size: 1.1rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}

/* Hairline between the standfirst panel and the body text. Signals "the
 * argument starts here" without competing with the standfirst's own left
 * border rule. 1px, brown structural colour, 35% opacity -- approved as
 * a mockup before being wired into the real build (session 14). */
.essay-rule {
  border: none;
  border-top: 1px solid var(--brown);
  opacity: 0.35;
  margin-bottom: 1.75rem;
}

/* Archive listing on the Research page.
 *
 * Nothing emits these classes yet, nor the .cover-grid / .cov / .cap block
 * below. That is deliberate, not dead code: this is the approved Archive
 * design, wired up when the Research archive page is built. A competing
 * layout (.cover-row-list) was removed 2026-08-13, along with .cmp-note
 * and the .series family. See Build-Specification.md, "Research archive
 * layout". Do not prune these on an emitted-class sweep alone. */
.archive-intro-note {
  font-family: var(--structural);
  font-size: 0.82rem;
  color: var(--brown);
  margin: -0.4em 0 1.1em;
}

.archive-list {
  list-style: none;
  padding: 0;
  margin: 0.2em 0 1em;
}

.archive-list li {
  font-family: var(--structural);
  font-size: 0.92rem;
  padding: 0.42rem 0;
  border-bottom: 1px solid #ece6d8;
  display: flex;
  gap: 0.7rem;
  align-items: baseline;
}

.archive-list li:first-child { border-top: 1px solid #ece6d8; }

.archive-list .m { color: var(--brown); flex: 0 0 8.6em; }

.archive-list .t a { color: var(--accent); }

.archive-more {
  font-family: var(--structural);
  font-size: 0.85rem;
  color: var(--brown);
  margin-top: 1em;
}

/* Cover grid (3-up desktop, 2-up mobile) */
.cover-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.4rem 1.2rem;
  margin: 0.4em 0 1.4em;
}

.cov {
  text-decoration: none;
  color: var(--ink);
}

.cov img {
  width: 100%;
  aspect-ratio: 255 / 360;
  object-fit: cover;
  object-position: top;
  border: 1px solid #e3dccd;
  background: #fff;
  display: block;
  box-shadow: 0 1px 4px rgba(38, 32, 28, 0.10);
}

.cov .cap {
  display: block;
  font-family: var(--structural);
  font-size: 0.82rem;
  margin-top: 0.4rem;
  line-height: 1.3;
}

.cov .cap .m {
  color: var(--brown);
  display: block;
  font-size: 0.75rem;
}

@media (max-width: 520px) {
  .cover-grid { grid-template-columns: repeat(2, 1fr); }
}

/* --- Footer -------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--brown);
  max-width: var(--measure);
  margin: 0 auto;
  padding: 1.6rem 1.25rem 3.5rem;
}

/* Top row: text column (nav links + acknowledgment) on the left, illustration
 * on the right, both starting at the same top edge. Session 8 restructure --
 * previously the acknowledgment sentence ran as its own full-width line
 * below this row (see the session-5 note this replaces); Paul asked for it
 * to sit alongside the illustration instead, in the space beside it rather
 * than dropping underneath. footer-left is itself a column (links, then
 * text) so both pieces of text share one edge with the illustration's
 * left/top corner. */
.footer-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
}

.footer-left {
  display: flex;
  flex-direction: column;
  gap: 1.6rem;             /* session 15: increased from 1.1rem for more
                               breathing room between nav links and the
                               acknowledgement text */
  flex: 1 1 260px;         /* shares space with the illustration; wraps to
                               its own row below 260px combined width, same
                               breakpoint logic as .media-split elsewhere */
  min-width: 0;
}

.footer-links {
  display: flex;
  gap: 1.6rem;             /* session 15: increased from 1.1rem to spread
                               nav links wider within the footer column */
  font-family: var(--structural);
  font-size: 0.82rem;
}

.footer-links a {
  color: var(--brown);
  text-decoration: none;
}

.footer-links a:hover { text-decoration: underline; }

/* Modest size deliberately -- this is a sign-off illustration, not another
 * hero. 260px keeps it well short of the 680px measure so it reads as
 * footer decoration, not a second visual anchor competing with the page's
 * actual content. */
.footer-illustration {
  display: block;
  flex-shrink: 0;
}

.footer-illustration img {
  max-width: 260px;
  width: 100%;
  height: auto;
  display: block;
}

/* Session 15: space between the acknowledgement and the Que sais-je /
 * copyright line. Previously these two paragraphs sat flush (margin-bottom:
 * 0 on both). */
.footer-copy {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.site-footer p {
  font-family: var(--structural);
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--brown);
  margin-bottom: 0;
  max-width: 15rem;
}

/* About page: magnet illustration between paragraphs 8 and 9.
 * Same fluid pattern as main img -- explicit width/height on the <img>
 * prevent layout shift, max-width makes it responsive. */
.about-illustration {
  margin: 2.5em 0;
}

.about-illustration img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Caption. Canon marks the paragraph under an image marker as that image's
 * caption (ruled v8.9); render.py emits it as <figcaption>. No figcaption
 * rule existed anywhere in this stylesheet, so it inherited body <p> and
 * read as prose continuing the page.
 *
 * Ruled by the copy project, session 46:
 *   face   National 2 -- the face this site already uses for apparatus.
 *          One step below body. Not italic: italic carries exactly two
 *          meanings here and a third reopens what S44 closed.
 *   space  groups upward -- space above materially less than space below,
 *          so the caption belongs to the image and not to the next
 *          paragraph.
 *
 * Rendering values at the 19px root:
 *   body        Tiempos Text  1rem      19.00px
 *   caption     National 2    0.85rem   16.15px
 *   above       0.6em of caption size     9.69px
 *   below       figure margin-bottom     47.50px   (2.5em of 19px)
 *   ratio       1 : 4.9 upward
 *
 * Dropped from 0.92rem on Paul's read of the rendered page, 17 September.
 * One typographic point is 1.333px in CSS, so 17.48 -> 16.15 is exactly a
 * point and not an approximation of one. 0.85rem is a size this stylesheet
 * already uses twice, so the scale gains nothing new.
 *
 * The face and the upward grouping are unchanged, so the copy project's
 * S46 ruling still holds in both the parts it specified -- National 2, and
 * space above materially less than space below. Only the step moved.
 */
.about-illustration figcaption {
  font-family: var(--structural);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--brown);
  margin-top: 0.6em;
}

/* --- Mobile footer -------------------------------------------------------
 * When the footer stacks on narrow viewports, centre the text and
 * illustration so they read as a single composed column rather than
 * left-aligned fragments above a centred image. Session 15. */
@media (max-width: 700px) {
  .footer-left {
    align-items: center;
    text-align: center;
  }
  .footer-links {
    justify-content: center;
  }
  .site-footer p {
    max-width: none;
  }
  .footer-top {
    justify-content: center;
  }
  .footer-illustration {
    display: flex;
    justify-content: center;
  }
}

/* --- Contact form --------------------------------------------------------
 * Standard HTML form, no JavaScript required for submission. The #sent
 * wrapper and :target pseudo-class toggle between form and confirmation
 * using pure CSS — when the Worker redirects to /contact/#sent, the form
 * hides and the confirmation message shows. No JS needed.
 *
 * The honeypot field (.form-hp) is hidden from humans but visible to bots.
 * aria-hidden="true" and tabindex="-1" on the input ensure screen readers
 * and keyboard users skip it.
 */

.contact-form {
  margin: 1.5em 0;
}

.form-field {
  margin-bottom: 1.25em;
}

.form-field label {
  display: block;
  font-family: var(--structural);
  font-weight: 500;
  font-size: 0.895rem;
  letter-spacing: 0.02em;
  color: var(--brown);
  margin-bottom: 0.35em;
}

.form-field input,
.form-field textarea {
  display: block;
  width: 100%;
  max-width: var(--measure);
  padding: 0.6em 0.75em;
  font-family: var(--body);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--brown);
  border-radius: 4px;
  box-sizing: border-box;
}

.form-field input:focus,
.form-field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.form-field textarea {
  resize: vertical;
  min-height: 8em;
}

.form-hp {
  position: absolute;
  left: -9999px;
  height: 0;
  overflow: hidden;
}

.form-submit {
  font-family: var(--structural);
  font-weight: 500;
  font-size: 0.895rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.6em 2em;
  color: var(--paper);
  background: var(--brown);
  border: 1px solid var(--brown);
  border-radius: 8px;
  cursor: pointer;
}

.form-submit:hover,
.form-submit:focus {
  background: var(--ink);
  border-color: var(--ink);
}

/* Confirmation toggle: pure CSS via :target. Default state: form visible,
 * confirmation hidden. When URL is /contact/#sent, #sent:target activates
 * and the rules flip. */
.contact-confirmation {
  display: none;
}

#sent:target .contact-form {
  display: none;
}

#sent:target .contact-confirmation {
  display: block;
}

/* --- Quality floor -------------------------------------------------------
 * No animation exists on this site (principle 9: nothing that draws attention
 * to the mechanics). The rule below is here so that if any is ever added, it
 * is disabled for people who have asked for less motion.
 */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}


/* Visible artist credit beside each Oslo Davis illustration (v8.0). The
 * caption copy is exactly the two words "Oslo Davis" -- governed, and the
 * build adds nothing to it. Small structural face, muted, so it reads as
 * an attribution rather than as a caption describing the drawing. */
.artist-credit {
  font-family: var(--structural);
  font-size: 0.7rem;
  color: var(--brown);
  margin-top: 0.35em;
  text-align: right;
}
