/*! trentpower.fr · authored source */
/*
  trentpower.fr
  screen design system

  Role:
  defines the public editorial interface, trust surfaces and
  responsive layout.

  Constraints:
  - no external assets
  - no analytics, cookies, trackers or third-party embeds
  - active css, js and font filenames revalidate; frozen release
    archives carry immutable dated assets
  - oxblood is an accent, not decoration
*/

/*!
  trentpower.fr css architecture
  cascade order: reset → tokens → base → layout → components → pages → utilities → overrides
  · tokens define decisions (colours, type, spacing, motion)
  · base styles target elements; reset normalises
  · layout positions; components are reusable; pages scope by body[data-page]
  · utilities are minimal helpers; overrides are rare and documented
  @font-face declarations sit in @layer fonts (added implicitly at the
  end of the cascade order — font-face lookups ignore layers anyway).
*/

@layer reset, tokens, base, layout, components, pages, utilities, overrides;

@layer fonts {
/* klim type foundry fonts used under purchased web licences for trentpower.fr.
   display strategy:
   - styles.css declares only the three critical subset @font-face
     entries below. the full editorial weights live in /fonts-full.css
     and are loaded after lcp by /app-enhance.js (which also adds
     `.fonts-loaded` to <html> so the variables in this file resolve
     to the full families on the next style recalculation).
   - until /fonts-full.css arrives, every site-wide
     `font-family: var(--serif|--sans|--mono)` resolves to the critical
     subset, then to a system fallback. this guarantees no full woff2
     enters the lcp critical chain.
   - hero (signifier critical) keeps `font-display: swap` because it
     is the single preloaded font and its arrival is brand-critical.
     the other two critical aliases use `optional`: if the subset is
     not delivered within 100 ms, the system fallback holds for that
     page load — acceptable for nav and small mono labels because
     the fallback stacks were chosen to mirror the brand metrics. */
@font-face { font-family: 'Signifier Critical';   font-style: normal; font-weight: 300; font-display: swap;     src: url('/fonts/subsets/signifier-light-hero.woff2') format('woff2'); }
@font-face { font-family: 'Söhne Critical';       font-style: normal; font-weight: 500; font-display: optional; src: url('/fonts/subsets/soehne-kraftig-nav.woff2') format('woff2'); }
@font-face { font-family: 'Söhne Mono Critical';  font-style: normal; font-weight: 400; font-display: optional; src: url('/fonts/subsets/soehne-mono-buch-labels.woff2') format('woff2'); }

}
@layer tokens {
:root {
  color-scheme: light;
  /* Two-surface model with explicit positive scoping.
     the public profile sits on warm editorial paper. the trust
     system, archive and source surfaces sit on a slightly cooler
     warm grey — the records room of the same artefact. overlays
     use a grey scrim above either surface; the panel itself
     returns to paper.

     every public page declares its surface explicitly via
     <body data-surface="editorial"> or <body data-surface="record">.
     the :root default below is ivory so a page that forgets the
     attribute falls through to the editorial surface, never to
     the record surface — grey is never the global default. */
  /* three paper tones — main paper for the public face, record
     paper for the archive, raised paper for inspected objects.
     Raised-high is reserved for overlay panels that sit above the
     scrim regardless of the underlying page surface. */
  --paper-main:        #FAF7F0;   /* front of house · editorial paper */
  --paper-record:      #E9E5DC;   /* back of house · warm archival grey */
  --paper-raised:      #F7F3EA;   /* card on record surface · paper sheet */
  --paper-raised-high: #FBF8F1;   /* overlay panels (.modal) · highest lift on the record surface */
  --paper-project:     #FFFDF8;   /* homepage editorial insert (what's on in paris card) — warmer than raised-high so it visibly lifts off front-of-house ivory without going stark white */
  /* phase 52 · single canonical archival surface so the verify and
     integrity record cards (plus the command block beneath them) read
     as one verification-system family of paper sheets. */
  --surface-archival:  #F4F1EA;

  --surface-page: var(--paper-main);
  --surface-card: var(--surface-archival);

  --overlay-scrim: rgba(28, 25, 22, 0.46);

  /* rules / hairlines tied to ink density rather than a hard
     opaque grey, so they read consistently on every paper tone.
     phase 39 · softened from 0.14 to ~10% mix for editorial calm.
     phase 45 · further reduced to 8% — dividers now read as
     deliberate registration marks rather than ui wireframe.
     phase 60 · softened a further ~10% (8% → 7%) so the hairlines
     stop competing with typography on dense surfaces (/source/,
     /verify, /integrity). */
  --rule:        color-mix(in srgb, var(--ink) 7%, transparent);
  --rule-strong: rgba(42, 38, 33, 0.22);

  /* ink and accent — semantic names for the new system. */
  --ink:        #211F1C;
  --ink-muted:  #67625B;
  --accent:     #6E1A14;

  /* legacy aliases — older rules (verify-command-block, .modal,
     etc.) reference these names; new code should prefer the
     semantic --paper-* / --ink-* / --rule tokens above. */
  --bg-public:  var(--paper-main);
  --bg-archive: var(--paper-record);
  --bg:         var(--surface-page);
  --bg2:        var(--paper-record);
  --card:       var(--paper-raised-high);

  --fg:  var(--ink);
  /* phase 40 · slightly warmer body register (#5f5a53). same warm
     hue as phase 39 (#5e5952), 1 unit lighter — preserves the
     editorial warmth while keeping aaa-equivalent serif legibility
     on mobile. dark mode --fg2 unchanged (handled in dark block). */
  --fg2: #5F5A53;
  --fg3: #706B66;
  --ac:  var(--accent);
  --ac2: #8B2218;     /* warmer oxblood · hover only */

  /* accent split: --accent is decorative (rules, dividers, focus-
     ring fills, button backgrounds, ::after underline tints). in
     light mode --accent-text aliases --accent (oxblood reads on
     paper at aaa). in dark mode --accent-text is a brighter coral
     so accent-coloured text (link hover, code-string, trust
     bullets, project cta labels, etc.) preserves wcag aa contrast
     on dark paper, while --accent stays deep crimson for
     decorative use. see @media (prefers-color-scheme: dark) below. */
  --accent-text:  var(--accent);
  --accent-hover: var(--ac2);
  /* phase 39 · --bd moved from solid #d8d4cc to a 10% ink mix so
     metadata-row dividers, record-grid hairlines, table edges and
     security-section borders all read as quieter editorial pacing
     rather than wireframe scaffolding. --bd-soft kept at the
     solid lighter tint for surfaces that need a deliberate plate. */
  --bd:        color-mix(in srgb, var(--ink) 10%, transparent);
  --bd-soft:   #E6E1D8;
  /* editorial focus-visible tokens. 45 % alpha oxblood reads as a
     calm typographic accent rather than a browser-debug rectangle.
     2 px ring + 3 px offset gives obvious keyboard visibility
     without dominating the page. component rules consume these via
     var(--focus-colour) etc.; dark-mode overrides land below. */
  --focus-colour:     rgba(110, 26, 20, 0.45);
  --focus-ring-width: 2px;
  --focus-offset:     3px;
  --focus-radius:     0;
  /* phase 46 · semantic radius tokens.
     three categories — cards/archival objects, larger overlay panels,
     and true pill objects (the trust-mark certification strip). every
     literal radius on a card or overlay surface now flows through
     these tokens so the curvature stays intentional and easy to
     refine in one place. layout surfaces (body, main, footer, nav,
     buttons, rules) remain sharp — radius identifies objects, not
     layout. */
  --radius-soft:      7px;
  --radius-panel:     10px;
  --radius-pill:      999px;
  /* Code-token colours · used by .code-str / .code-var inside the
     trust-command and verify-command panels. tokenised so dark mode
     and prefers-contrast can override them without touching the
     component rules. */
  --code-string: #2563EB;
  --code-var:    #7C3AED;
  /* Critical-only stacks. the full editorial family names ('signifier',
     'söhne', 'söhne mono') are intentionally absent until /fonts-full.css
     loads after lcp and adds `.fonts-loaded` to <html>, at which point
     the override at the bottom of this file flips these stacks to put
     the full weights first. keeps every full woff2 out of the lcp
     critical request chain. */
  --serif: 'Signifier Critical', Georgia, 'Times New Roman', serif;
  --sans:  'Söhne Critical', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono:  'Söhne Mono Critical', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

/* explicit positive surface scoping.
   - data-surface="editorial" → /, /privacy/
   - data-surface="record"    → /verify/, /source/, /integrity/,
                                /integrity/releases/, /security/,
                                /security/acknowledgments/, error pages
   - data-surface="archive"   → kept as a synonym of "record" so any
                                cached html still resolves correctly. */
body[data-surface="editorial"] {
  --surface-page: var(--paper-main);
  --surface-card: var(--surface-archival);
  --bg:           var(--paper-main);
}
body[data-surface="record"] {
  --surface-page: var(--paper-record);
  --surface-card: var(--surface-archival);
  --bg:           var(--paper-record);
}
body[data-surface="archive"] {
  --surface-page: var(--paper-record);
  --surface-card: var(--surface-archival);
  --bg:           var(--paper-record);
}

/* Back-of-house pages calm the supporting-text and rule tokens by a
   small amount. primary tokens (--fg / --ink / --link) stay
   unchanged so headings, filenames, hashes, fingerprints and
   action links remain authoritative. the override fires on every
   page that wears the brand-only masthead — that includes privacy,
   verify, integrity, source, releases, security and the error
   pages. the homepage carries no data-masthead attribute and so
   keeps the original (slightly stronger) muted tones.

   Contrast: --fg2 #6c6760 vs --paper-record #e9e5dc ≈ 4.6:1; vs
   --paper-main #faf7f0 ≈ 5.4:1. --fg3 #756f69 vs the same
   backgrounds ≈ 4.0:1 and 4.7:1. both safely above the 3:1 large-
   text bar; --fg3 sits at the aa edge for very small mono labels
   on the warm-grey surface. */
body[data-masthead="brand-only"] {
  --fg2:  #6C6760;
  --fg3:  #756F69;
  /* phase 39 · brand-only masthead pages get an even quieter rule
     register (8% mix) so the brand-only chrome stays nearly
     subliminal against editorial type. */
  --rule: color-mix(in srgb, var(--ink) 8%, transparent);
}

}

/* ─── Accessibility-preference overlays ─────────────────────────
   a third @layer tokens block holds three media-query branches that
   redefine the canonical token set under user preferences. the
   surface variants (body[data-surface=…], body[data-masthead=…])
   reference these tokens via var(); they automatically inherit the
   redefined values without their own overrides.

   - prefers-color-scheme: dark    — warm editorial dark, aaa contrast
   - prefers-contrast: more        — pure black/white, thicker rules
   - dark + contrast (intersect)   — pure white on pure black
   - forced-colors: active         — windows high contrast mode
*/
@layer tokens {

  @media (prefers-color-scheme: dark) {
    :root {
      color-scheme: dark;

      /* warm editorial paper, not pure black. the hue (~33° / 9% / 9%)
         mirrors the light paper-main inverted with the warm cast preserved. */
      --paper-main:        #1B1916;
      --paper-record:      #22201B;
      --paper-raised:      #26231F;
      --paper-raised-high: #2C2925;
      --paper-project:     #1F1D1A;
      /* phase 52 · dark-mode canonical archival surface mirrors light. */
      --surface-archival:  #26231F;

      --overlay-scrim: rgba(0, 0, 0, 0.55);

      /* hairlines flip to ink-on-paper opacities expressed in the
         dark ink hue, so they read consistently on every paper tone.
         phase 39 · softened to 12% (vs 10% in light).
         phase 45 · further reduced to 10% — dark dividers now match
         the same registrational register as the light :root above.
         phase 60 · softened a further ~10% (10% → 9%) in step with
         the light variant; dense surfaces breathe a touch more. */
      --rule:        color-mix(in srgb, var(--ink) 9%, transparent);
      --rule-strong: rgba(240, 234, 224, 0.30);

      /* cream ink — warm, editorial, aaa against #1b1916. */
      --ink:        #F0EAE0;     /* ~14.6:1 vs --paper-main */
      --ink-muted:  #C9C2B7;     /* ~9.6:1  vs --paper-main */

      /* deep crimson — lacquered ink, oxblood leather, archival
         annotation. replaces the earlier bright-coral dark-mode
         accent (#e68675). used for decorative accent: focus-ring
         fills, hover ::after underlines, button backgrounds with
         text on top (aaa contrast white-on-crimson), divider
         tints, glyph fills. text use of accent (color: var(--accent-text))
         migrates to var(--accent-text) below — at #9e2f2a on dark
         paper #1b1916 contrast is ~2.4:1, which fails wcag aa for
         text. the split-token architecture keeps the editorial
         crimson register on decoration and keeps every accent-
         coloured text use AA-compliant. */
      --accent:       #9E2F2A;   /* ~2.4:1 vs --paper-main · decorative only */
      --accent-text:  #D86459;   /* ~4.9:1 vs --paper-main · text only · aa */
      --accent-hover: #B13A34;   /* hover · brightens subtly, not glow */
      --ac2:          #B13A34;   /* legacy alias, anchored on hover hue */

      --fg3:        #A39C92;     /* tertiary mono labels */
      /* phase 39 · --bd moved from solid #3a352f to a 12% ink mix
         (paired with --rule above). --bd-soft retained as a solid
         tone for surfaces that need a plate. */
      --bd:         color-mix(in srgb, var(--ink) 12%, transparent);
      --bd-soft:    #332F2A;

      /* Focus-visible token: anchored on the brighter --accent-text
         hue (#d86459) so the ring composites to ≥3:1 against dark
         paper (wcag 1.4.11 non-text contrast). the deep crimson
         #9e2f2a is too dark for focus rings on dark paper — anchored
         at 2.4:1 raw, which fails 1.4.11 even with high alpha. */
      --focus-colour: rgba(216, 100, 89, 0.85);

      /* Code-token colours — desaturated for editorial calm. */
      --code-string: #99D5A1;    /* mint, ~7.5:1 */
      --code-var:    #C8AEEA;    /* warm violet, ~8.9:1 */
    }

    /* selection contrast on dark paper: paper-on-deep-crimson (the
       light-mode `color: var(--bg)` default) drops to ~2.4:1 in
       dark mode. override to cream-on-deep-crimson (~6.2:1, aa). */
    ::selection {
      color: var(--ink);
    }

    /* Brand-only masthead overrides on dark paper: calm fg2/fg3
       slightly more so they sit gently above body text. */
    body[data-masthead="brand-only"] {
      --fg2:  #B8B0A4;
      --fg3:  #968F86;
      /* phase 39 · brand-only masthead on dark paper softened to
         10% ink mix (matches the brand-only register on light). */
      --rule: color-mix(in srgb, var(--ink) 10%, transparent);
    }
  }

  @media (prefers-contrast: more) {
    :root {
      --ink:        #000000;
      --ink-muted:  #1A1A1A;
      --paper-main: #FFFFFF;
      --paper-record: #F4F2EC;
      --rule:        rgba(0, 0, 0, 0.45);
      --rule-strong: rgba(0, 0, 0, 0.7);
    }
    a {
      text-decoration-thickness: 2px;
      text-underline-offset: 0.18em;
    }
    :focus-visible {
      /* keep: wcag 2.4.7 — increased focus visibility under user-
         requested high contrast must override component-level rings. */
      outline-width: 3px !important;
      outline-offset: 3px !important;
    }
  }

  @media (prefers-color-scheme: dark) and (prefers-contrast: more) {
    :root {
      --ink:        #FFFFFF;
      --paper-main: #000000;
      --paper-record: #0A0A0A;
      --rule:        rgba(255, 255, 255, 0.55);
      --rule-strong: rgba(255, 255, 255, 0.8);
    }
  }

  /* windows high contrast mode (and other forced-colors environments).
     remap key tokens to system colour keywords so user-controlled
     palettes win. forced-color-adjust is intentionally not set;
     interactive controls keep their own visible borders so they remain
     distinguishable from body text. */
  @media (forced-colors: active) {
    :root {
      --ink:        CanvasText;
      --paper-main: Canvas;
      --paper-record: Canvas;
      --paper-raised: Canvas;
      --paper-raised-high: Canvas;
      --accent:     LinkText;
      --rule:       CanvasText;
      --rule-strong: CanvasText;
    }
    a {
      color: LinkText;
    }
    .cite-btn,
    .site-footer__language button,
    .verify-record-action {
      color: ButtonText;
      border-color: ButtonText;
    }
    :focus-visible {
      outline-color: Highlight;
    }
  }

}

@layer layout {
/* Brand-only masthead.
   the full trent power + nav strip is the homepage identity. every
   other page (privacy, the entire record layer, error pages) shows
   only trent power. same height, hairline rule, mobile two-line
   stack — the brand stays exactly as on the homepage; the wayfinding
   list disappears. (Retired: the nav-toggle / nav-links system is gone
   site-wide; the masthead now stands alone in every header.) */

}
@layer base {
/* opentype defaults wherever signifier renders editorial prose:
   kerning, ligatures, contextual alternates, old-style figures. */
.hero-statement, .hero-body,
.principle-title, .principle-body,
.chapter-title, .chapter-detail,
.project-desc, .project-subline,
.preview-editorial,
.page-title, .page-lede, .page-body p, .page-subtitle,
.modal-text {
  font-feature-settings: "kern" 1, "liga" 1, "onum" 1, "calt" 1;
  font-kerning: normal;
  font-variant-numeric: oldstyle-nums proportional-nums;
  text-rendering: optimizeLegibility;
}

/* tabular lining figures wherever evidence renders, so columns of numerals
   align and dates / hashes / file paths read precisely. */
code, .technical-list, .code-path, .fingerprint,
.section-label, .chapter-label, .page-meta,
.preview-meta, .preview-title, .site-footer__colophon,
.nav-mark, .contact-email {
  font-variant-numeric: tabular-nums lining-nums;
}

}
@layer reset {
/* reset & base */

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

::selection {
  background: var(--ac);
  color: var(--bg);
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

}
@layer tokens {
/* anchor offset for the fixed nav. 52px nav height plus a small breathing
   room so section labels land cleanly just under the nav, not flush. */
:root {
  --nav-offset: 60px;
  /* Anchor-offset tokens for native anchor navigation on the home
     section ids. mobile nav-inner height varies with the device —
     ~58 px on non-notched displays and ~102 px on notched iphones
     (env(safe-area-inset-top) + padding + content). the mobile
     value is dynamic: `calc(env(safe-area-inset-top) + 4.5rem)` =
     72 px non-notch / ~116 px notched, landing the section heading
     ~14 px below the nav at every viewport.

     desktop nav is `position: fixed` at 52 px — the 7 rem fixed
     value lands the heading with calm breathing room and doesn't
     need an env() lookup (desktop browsers don't expose a
     meaningful safe-area-inset-top). */
  --anchor-offset-mobile:  calc(env(safe-area-inset-top) + 4.5rem);
  --anchor-offset-desktop: 7rem;

  /* Safe-area-inset tokens — surface env() values as named tokens so
     edge-touching elements (sticky/fixed nav, footer, overlays) can
     refer to them via var() with a sane 0 fallback. `viewport-fit=
     cover` (set in <meta name="viewport">) lets the layout reach the
     full visual viewport including the notch/Dynamic island and the
     ios home-indicator strip; the tokens give us a single point to
     reason about edge clearance.

     use only where the layout actually reaches a viewport edge — the
     brief's guidance is "do not blindly add safe-area padding
     everywhere." most centred / max-width content does not need it. */
  --safe-top:    env(safe-area-inset-top,    0px);
  --safe-right:  env(safe-area-inset-right,  0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left,   0px);
}

}
@layer base {
[id] {
  scroll-margin-top: var(--nav-offset);
}

/* native anchor navigation on the home section ids lives in
   @layer overrides below — bare id selectors (per the brief) are
   legal there. search "anchor landing offsets" to find the
   declarations. */

html, body {
  /* `overflow-x: clip` is the modern variant — does not establish a
     scroll context the way `hidden` does, so positioned descendants
     remain reachable via fragment navigation. older browsers ignore
     `clip` and fall through to the next declaration. */
  overflow-x: hidden;
  overflow-x: clip;
}

/* editorial abbreviation register. native html <abbr title="…">
   gets a quiet dotted underline so the conventional "hover for
   expansion" affordance reads correctly without shouting. browsers
   without dotted-underline support fall through to nothing —
   acceptable degradation. */
abbr[title] {
  text-decoration: underline dotted var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  cursor: help;
}

/* phase 43 · semantic enrichment register.
   `<dfn>` marks the first definitional occurrence of a concept
   (integrity manifest, source mirror, signed release, page record,
   detached signature). browser-default italic is overridden — the
   element should read as a quiet definitional anchor, not as a
   typographic flourish. */
dfn {
  font-style: normal;
  font-weight: 500;
  color: var(--fg);
}

/* `<samp>` marks computed output — sha-256 hashes, pgp fingerprints,
   validation timestamps. mono register so it sits inside the
   technical-mono family with `<code>`, but slightly smaller so the
   eye reads it as derived output rather than authored input. */
samp {
  font-family: var(--mono);
  font-size: 0.92em;
  color: var(--fg);
  overflow-wrap: anywhere;
}

/* `<cite>` marks publication titles in editorial register. browser-
   default italic preserved — in serif body prose this reads as
   published-work convention; in mono surfaces the slant is barely
   perceptible. */
cite {
  font-style: italic;
  color: inherit;
}

/* editorial focus-visible system. calm oxblood ring at 45 % alpha,
   2 px width, 3 px offset. component overrides land in @layer
   components below (text links → typographic underline-on-focus
   instead of a box; buttons / nav controls / ctas → token-based
   outline tightened to the same width and offset).

   :focus:not(:focus-visible) is stripped so a touch tap on ios
   safari doesn't leave a persistent outline behind on the tapped
   element. keyboard users still get :focus-visible. the
   prefers-contrast: more block elsewhere in this file overrides
   these to 3 px solid var(--ac) for high-contrast at users. */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-colour);
  outline-offset: var(--focus-offset);
  border-radius: var(--focus-radius);
}
:focus:not(:focus-visible) {
  outline: none;
}
@media (pointer: coarse) {
  :focus:not(:focus-visible) {
    outline: none;
  }
}

/* inline text links (body copy, hero body, trust-mark, contact
   secondary, footer privacy, page-back) prefer a typographic
   underline-on-focus over a box outline. the underline reads as
   continuous emphasis with the typeface; an outline would look
   like a form control. component selectors enumerated explicitly
   so the typographic treatment doesn't leak into buttons or icon
   controls that need a visible ring. */
.page-body a:focus-visible,
.hero-body a:focus-visible,
.trust-mark a:focus-visible,
.contact-secondary a:focus-visible,
.site-footer__actions a:focus-visible,
.page-back:focus-visible {
  outline: none;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--focus-colour);
}

body {
  font-family: var(--sans);
  background-color: var(--bg);
  /* paper · two fixed background layers sit above the flat hex:
     a soft corner vignette so the page feels held in two hands,
     and a tiny inline svg fractal-noise giving the surface tooth at
     ~4.5% alpha. both are background-attachment:fixed so they do not
     scroll. the vignette uses var(--ink) so dark mode inverts it
     automatically; the noise reads near-identically on cream and on
     near-black. data-url inside a stylesheet bypasses script-src. */
  background-image:
    radial-gradient(ellipse 120% 90% at 50% 50%, transparent 55%,
                    color-mix(in srgb, var(--ink) 4%, transparent) 100%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='260' height='260'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' seed='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.13  0 0 0 0 0.12  0 0 0 0 0.11  0 0 0 0.045 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-attachment: fixed, fixed;
  background-size: auto, 260px 260px;
  background-repeat: no-repeat, repeat;
  color: var(--fg);
  line-height: 1.6;
  font-synthesis: none;
}

}
@layer overrides {
/* Print-only elements never render on screen. print.css restores them
   inside @media print. defined here so the rule applies even though
   print.css is loaded with media="print" and never reaches screen.

   no !important: cascade-layer order makes this rule sit in the
   `overrides` layer, which beats every other named screen layer for
   normal-importance declarations. removing !important is critical so
   that print.src.css's @layer print-overrides {!important} rules can
   override these in print mode — !important across cascade layers
   reverses precedence (earlier layer wins), so a screen-layer
   !important here would beat the print-layer !important and the
   homepage would print as a blank page. */
.print-only,
.print-profile,
.print-trust-sheet,
.print-utility-sheet,
.print-header,
.print-footer,
.print-citation,
.print-meta,
.print-kicker,
.print-title,
.print-seal {
  display: none;
}

}
@layer components {
/* /verify/ public verification route.
   verify.js renders into #verify-root using only textcontent and
   setattribute on safe attributes. Same-origin only. */

/* phase 56 · .verify-kicker rule retired (the html element was
   removed in phase 55; the rule was an orphan). */

/* /verify/ scoped layout
   verify.js renders real <section> children inside #verify-root, which
   would otherwise inherit the global `section { padding: clamp(80px,
   12vh, 160px) 0 }` rule from the homepage hero rhythm. the scoped
   reset below cancels that inheritance and re-establishes a tighter
   editorial rhythm specific to the verification page. */
.verify-page section {
  padding: 0;
  border-bottom: 0;
  min-height: 0;
}

/* editorial column. wider than prose pages so the page record can
   carry presence, but narrow enough to keep line length readable. */
.verify-page .page-body {
  max-width: 880px;
  margin: 0 auto;
  /* top padding tightened from clamp(48,7vw,88) so the kicker sits
     closer to the masthead , /verify/ is a utility-record page, not
     a hero-driven landing. */
  padding: clamp(40px, 6vw, 64px) 0;
}
@media (max-width: 700px) {
  .verify-page .page-body {
    max-width: none;
    padding: 32px 0 48px;
  }
  /* card actions stack vertically on narrow viewports — flex column
     keeps the hairline-underline rhythm of each link intact. */
  .verify-page .verify-record-actions {
    flex-direction: column;
    gap: 0.5rem 0;
    line-height: 1.4;
  }
  .verify-page .verify-record-action { align-self: flex-start; }
  /* evidence line splits "validated YYYY-MM-DD" onto its own line so
     the row reads as three calm lines (source / file·size / validated)
     rather than wrapping mid-row. desktop keeps the inline " · " join. */
  .verify-page .verify-rg-evidence-sep       { display: none; }
  .verify-page .verify-rg-evidence-validated { display: block; }
}

/* hero. compressed so the record card appears sooner. title uses
   the utility-page scale (clamp 42 to 76px), the intro paragraph
   sits close beneath, and the gap to the card is small. */
.verify-page .page-title {
  max-width: 720px;
  font-size: clamp(42px, 7vw, 76px);
  line-height: 0.95;
  margin-bottom: clamp(20px, 3vw, 28px);
}
.verify-page .page-lede {
  max-width: 620px;
  margin-bottom: 0;
}

/* ─────────────────────────────────────────────────────────────
   phase 33 · verify-page micro-grid record system
   ─────────────────────────────────────────────────────────────
   the page record is a signed technical certificate. the layout
   is a definition-list grid where each row pairs a small mono
   uppercase label with a readable value. inspired by swiss
   archival records, museum object labels, financial terminals
   and printed release ledgers. the current-edition panel below
   reuses the same grid via the `--quiet` modifier.
*/
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card {
  margin-block-start: clamp(2rem, 5vw, 3.25rem);
  margin-block-end: clamp(2rem, 6vw, 4rem);
  /* phase 50 · padding compressed to match the integrity record
     card register (clamp 1.2-1.9rem). archival rhythm unifies.
     phase 52 · background promoted to the canonical archival
     surface so verify + integrity cards read as one paper sheet
     family. border colour promoted to the canonical --rule token
     so the divider register matches across pages. */
  width: 100%;
  max-width: 100%;
  min-width: 0;
  padding: clamp(1.2rem, 4vw, 1.9rem);
  background: var(--surface-archival);
  border: 1px solid var(--rule);
  /* phase 39 · 2px outer radius matches the project-card register
     so verify, integrity and sw-reset records read as the same
     family of mounted archival sheets. .record-grid__row stays
     sharp — only the outer container is softened. phase 46 ·
     literal 8px → var(--radius-soft) (7px). */
  border-radius: var(--radius-soft);
  /* paper-raise via a quiet light-mode shadow only; dark mode
     drops the shadow so it doesn't muddy the page. */
  box-shadow: 0 18px 40px rgb(0 0 0 / 0.06);
}
@media (prefers-color-scheme: dark) {
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card {
    /* phase 52 · dark-mode card surface promoted to canonical
       archival surface for verify / integrity parity. */
    background: var(--surface-archival);
    box-shadow: none;
  }
}

:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card,
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card__header,
.verify-intro-panel,
.verify-intro-list,
.integrity-record-dl,
.integrity-rg,
.integrity-record-actions {
  min-width: 0;
}

:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card__header {
  margin-block-end: clamp(1.25rem, 4vw, 2rem);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card__header .eyebrow {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card__header h2 {
  /* the user warned the theatrical 9vw spec; tightened to a
     clamp that still feels composed at desktop without bullying
     the rest of the page. */
  margin: 0.45rem 0 0.4rem;
  font-family: var(--serif);
  font-size: clamp(1.75rem, 6.5vw, 3.25rem);
  font-weight: 300;
  line-height: 0.98;
  letter-spacing: -0.012em;
  color: var(--fg);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-status {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--fg3);
}

:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid {
  margin: 0;
  display: grid;
  /* phase 52 · rule colour unified across verify / integrity. */
  border-top: 1px solid var(--rule);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid__row {
  display: grid;
  grid-template-columns: minmax(7.25rem, 0.34fr) minmax(0, 1fr);
  gap: clamp(1rem, 4vw, 2rem);
  /* phase 51 · row padding tightened 0.85 → 0.8rem.
     phase 52 · rule colour unified to var(--rule). */
  padding-block: 0.8rem;
  border-bottom: 1px solid var(--rule);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid dt {
  font-family: var(--mono);
  font-size: 0.64rem;
  line-height: 1.2;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--fg3);
  /* recede comes from --fg3 + 0.64rem + tracking; no further
     opacity reduction — at this size 0.82 would push the blend
     below aa (4.5:1) in both modes. */
  margin: 0;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid dd {
  margin: 0;
  min-width: 0;
  font-size: clamp(0.9rem, 2.4vw, 1rem);
  /* phase 51/57 · ledger line-height progressively tightened:
     1.5 → 1.35 → 1.28 so the mono metadata rows match the
     integrity .integrity-rg-value register across both cards. */
  line-height: 1.28;
  color: var(--fg);
  overflow-wrap: anywhere;
  word-break: break-word;
}
/* phase 51 · long mono outputs (paths, hashes) wrap cleanly on
   narrow viewports so the value column never overflows the card.
   .record-grid code already had this; samp inherits the same rule. */
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid samp {
  font-family: var(--mono);
  overflow-wrap: anywhere;
  word-break: break-word;
}
/* phase 51 · the verify page's fingerprint as compact ledger hex.
   smaller than .integrity-fingerprint (which has wbr-broken 4-char
   groups inside the larger integrity record card); this one sits
   as a single inline-wrapped hash in the verify card's value
   column. */
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-fingerprint {
  display: block;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--fg);
  overflow-wrap: anywhere;
}
.page-hash {
  overflow-wrap: anywhere;
  word-break: break-word;
  font-size: 0.88rem;
  line-height: 1.45;
  letter-spacing: 0.01em;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent);
  transition: color 0.2s, border-bottom-color 0.2s;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid a:hover,
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid a:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid code {
  font-family: var(--mono);
  font-size: 0.86em;
  background: transparent;
  padding: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-meta {
  display: block;
  margin-block-start: 0.3rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--fg3);
}

/* phase 51 · record-actions refactored as a dot-separator inline
   phrase.
   phase 56 · class renamed to .record-tools; border-top divider
   above the rail retired (spacing alone marks the transition from
   card to actions); register aligned with the integrity inline-
   action vocabulary (.record-inline-action / .copy-fingerprint /
   .trust-code-copy) so the verify rail joins one declarative
   system across the site. button + anchor children now carry the
   .record-inline-action class and inherit styling from the shared
   rule. */
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.55rem;
  /* phase 57 · gap to the card above tightened so the utility rail
     sits as the immediate footer of the card.
     phase 58 · further compressed so the rail reads as a tight
     archival footer rhythm against the last record row above. */
  margin-block-start: 0.35rem;
  font-family: var(--mono);
  font-size: 0.58rem;
  line-height: 1.25;
  letter-spacing: 0.055em;
  color: var(--fg3);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-tools > * {
  display: inline-flex;
  align-items: baseline;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-tools .record-inline-action + .record-inline-action::before {
  content: "·";
  margin-right: 0.55rem;
  opacity: 0.45;
}

:is(.verify-page, .sw-reset-page, .security-page, .source-page) .edition-panel {
  /* phase 56 · further ~25% reduction on both block margins so
     current edition reads as a tight extension of the page record,
     and the page closes immediately afterwards. */
  margin-block-start: clamp(0.9rem, 3vw, 1.9rem);
  margin-block-end: clamp(0.85rem, 2.3vw, 1.4rem);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .edition-panel h2 {
  /* phase 52 · h2 scale reduced so current edition no longer reads
     as section-heavy. it supports the page record above, doesn't
     compete with it. */
  margin: 0 0 0.85rem;
  font-family: var(--serif);
  font-size: clamp(1.1rem, 3.5vw, 1.4rem);
  font-weight: 300;
  line-height: 1.1;
  letter-spacing: -0.008em;
  color: var(--fg);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet {
  color: var(--fg2);
  /* phase 52 · rule colour unified to var(--rule). */
  border-top-color: var(--rule);
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet .record-grid__row {
  /* phase 53 · per-row hairlines retired in the --quiet variant.
     phase 55 · closing :last-child hairline also retired so the
     current-edition panel ends without a floating divider between
     itself and the back link. spacing alone carries the close. */
  padding-block: 0.85rem;
  border-bottom: 0;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet .record-grid__row--fingerprint {
  padding-block-start: 1.05rem;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet dt {
  font-size: 0.6rem;
  /* recede comes from --fg3 + 0.6rem + tracking; opacity reduction
     would drop the blend below aa at this size. */
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet .record-grid__row--fingerprint dt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.45rem;
}
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet dd {
  font-size: clamp(0.78rem, 2vw, 0.88rem);
}

/* mobile stacking · labels above values when the row gets tight. */
@media (max-width: 620px) {
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .verify-card {
    padding: clamp(1.25rem, 6vw, 1.8rem);
  }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid__row {
    grid-template-columns: 1fr;
    gap: 0.35rem;
    padding-block: 0.9rem;
  }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid dt { font-size: 0.62rem; }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid dd { font-size: 0.95rem; }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet .record-grid__row {
    padding-block: 0.7rem;
  }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet .record-grid__row--fingerprint {
    padding-block-start: 0.95rem;
  }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet dt { font-size: 0.58rem; }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid--quiet dd { font-size: 0.84rem; }
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-tools {
    display: grid;
    gap: 0.4rem;
  }
  /* phase 58 · when the utility rail stacks vertically on mobile,
     the dot pseudo before each non-first item would render as a
     leading bullet and read as an accidental list. hide it. */
  :is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-tools .record-inline-action + .record-inline-action::before {
    content: none;
  }
}

/* ─────────────────────────────────────────────────────────────
   verify · other-records + current-edition section structure
   ─────────────────────────────────────────────────────────────
   two distinct sections sit below the page-record card: the
   "other records" sibling-route index and the "current edition"
   panel. spacing alone separates them — no bridging rule, no
   shared border. releases must read as the close of other
   records, never as the lead of current edition.
*/
.verify-page .other-records {
  /* tightened block-end · a visible <hr> divider now carries the
     section break; spacing only completes the rhythm. */
  margin: clamp(2rem, 5vw, 3rem) 0 clamp(1.75rem, 6vw, 2.75rem);
}
/* shared peer-heading register · other records and current edition
   sit as siblings at the same hierarchy level, divided by the
   visible <hr> section-divider below. */
.verify-page .other-records-title,
.verify-page .current-edition .current-edition-title {
  margin: 0 0 1.25rem;
  font-family: var(--serif);
  font-size: clamp(1.55rem, 5vw, 2rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--fg);
  text-transform: none;
}

/* peer-section divider · soft hairline between other records and
   current edition. matched to --rule, with generous bottom margin
   so the divider clearly closes the section above and opens the
   one below rather than floating between them. */
.verify-page .section-divider--after-records {
  margin: 0 0 clamp(2rem, 7vw, 3.25rem);
  border: 0;
  border-top: 1px solid var(--rule);
  height: 0;
}
.verify-page .other-records-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(2.5rem, 12vw, 5rem);
  row-gap: 1rem;
}
.verify-page .other-records-grid li { display: block; }
.verify-page .other-records-grid a {
  display: inline-block;
  width: fit-content;
  color: var(--fg2);
  font-family: var(--mono);
  font-size: 0.86rem;
  line-height: 1.35;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color 0.2s, text-decoration-color 0.2s;
}
.verify-page .other-records-grid a:hover,
.verify-page .other-records-grid a:focus-visible {
  color: var(--accent-text);
  outline: 0;
}
.verify-page .other-records-grid [aria-current="page"] {
  display: inline-block;
  width: fit-content;
  color: var(--fg3);
  text-decoration: none;
  cursor: default;
  font-family: var(--mono);
  font-size: 0.86rem;
  line-height: 1.35;
}

/* current-edition · sits as a fresh section, not as a
   continuation of the other-records list. no top margin — the
   block-end margin on .other-records carries the separation.
   the inner record-grid drops its top hairline so the h2 and
   the rows read as one editorial block. */
.verify-page .edition-panel.current-edition {
  margin-top: 0;
  padding-top: 0;
  margin-block-end: clamp(0.85rem, 2.3vw, 1.4rem);
}
.verify-page .current-edition .record-grid {
  border-top: 0;
}
/* override the shared .record-card .fingerprint-section border so
   edition and signing key read as one continuous record block —
   the visible section break belongs above the h2, not between
   data rows. natural row padding from .record-grid--quiet keeps
   the breathing without painting a line. */
.verify-page .current-edition .fingerprint-section {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
@media (max-width: 700px) {
  .verify-page .other-records {
    margin-block-end: clamp(2.5rem, 9vw, 4rem);
  }
  .verify-page .other-records-grid {
    column-gap: clamp(1.5rem, 10vw, 3rem);
  }
}

/* ─────────────────────────────────────────────────────────────
   legacy verify-page rules (kept for backwards compat with
   archived/frozen content under /integrity/releases/<edition>/
   that may still reference the .verify-thispage / .verify-rg
   class system). the new active /verify/ dom uses .verify-card
   + .record-grid above.
   ─────────────────────────────────────────────────────────────
*/

/* /verify/ reads as three composed objects: hero → record card →
   chooser → back. the card carries the visual weight; the chooser
   sits outside it as quiet secondary nav. related records is gone ,
   the card already shows source mirror and release archive, and the
   chooser links to integrity and releases. */
.verify-page .verify-record-card {
  background: var(--surface-card);
  border: 1px solid var(--rule);
  /* shared card padding scale (matches /integrity/ signed release card
     and /security/ architecture card so the trust system uses one
     paper-card register). */
  padding: clamp(28px, 4.5vw, 48px);
  /* phase 21: card is now the hero object below the lede. tighter
     top margin so the lede flows directly into the card; bottom
     margin opens the gap to the supporting infrastructure (current
     edition) panel that now sits below. */
  margin: clamp(16px, 2.2vw, 24px) 0 clamp(32px, 5vw, 56px);
  max-width: 860px;
  /* subtle paper-raise , restrained shadow, not modern card. */
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.035);
}
.verify-page .verify-record-card .verify-thispage {
  margin: 0;
}

/* ── editorial card internals ──
   the record reads as a publication colophon, not a metadata table.
   page record kicker (mono, restrained) → page title (serif, large)
   → short status line → grouped editorial rows (citation / location /
   evidence / fingerprint / archive) → mono-utility actions footer.
   hairlines separate groups, never rows. */
.verify-page .verify-record-kicker {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.55rem;
}
.verify-page .verify-record-title {
  font-family: var(--serif);
  font-size: clamp(28px, 4.4vw, 38px);
  font-weight: 300;
  letter-spacing: -0.012em;
  line-height: 1.12;
  color: var(--fg);
  margin: 0 0 0.35rem;
}
.verify-page .verify-record-status {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.025em;
  color: var(--fg3);
  margin: 0 0 clamp(24px, 4vw, 32px);
}
.verify-page .verify-rg {
  /* default groups have no top hairline; rules are added selectively
     via .verify-rg--ruled at boundaries the editorial rhythm wants
     (status→citation, evidence→fingerprint, fingerprint→archive). */
  padding: 0.85rem 0 0.6rem;
}
.verify-page .verify-rg--ruled {
  border-top: 1px solid var(--bd);
  padding-top: 1rem;
}
.verify-page .verify-rg-label {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.4rem;
}
.verify-page .verify-rg-value {
  margin: 0;
  color: var(--fg);
  overflow-wrap: anywhere;
  word-break: break-word;
}
.verify-page .verify-rg-value--mono {
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1.55;
}
.verify-page .verify-fingerprint {
  /* Single-line visual abbreviation; full value lives on the
     title/aria-label and is what the copy action returns. */
  white-space: nowrap;
  overflow-wrap: normal;
  word-break: normal;
}
.verify-page .verify-rg-value--serif {
  font-family: var(--serif);
  font-size: clamp(15px, 1.6vw, 17px);
  font-weight: 300;
  line-height: 1.5;
}
.verify-page .verify-rg-link {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
  word-break: break-all;
  overflow-wrap: anywhere;
}
.verify-page .verify-rg-link:hover,
.verify-page .verify-rg-link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.verify-page .verify-rg-value--serif .verify-rg-link {
  border-bottom-width: 1px;
}

.verify-page .verify-record-actions {
  border-top: 1px solid var(--rule);
  padding-top: 0.85rem;
  margin: 1rem 0 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.025em;
  color: var(--fg2);
  line-height: 1.6;
  /* flex + gap, no inline " · " separators (verify.js stopped
     emitting them). wrapped actions never orphan a bullet. */
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
}
.verify-page .verify-record-action {
  appearance: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--fg2);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bd);
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  text-transform: none;
  transition: color 0.2s, border-bottom-color 0.2s;
}
.verify-page .verify-record-action:hover,
.verify-page .verify-record-action:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.verify-page .verify-record-action[data-state="copied"] {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
/* legacy separator class kept hidden so cached html never paints a
   stray bullet; new verify.js doesn't emit them. */
.verify-page .verify-record-actions-sep { display: none; }

/* /verify/ "other records" uses the shared .related-nav grammar
   above; only the unknown-route fallback needs its own bottom margin
   so it doesn't collide with back. */
.verify-page .verify-unknown { margin: 0 0 clamp(36px, 5vw, 64px); }

/* ─────────────────────────────────────────────────────────────
   .trust-disclosure — shared collapsible component
   ─────────────────────────────────────────────────────────────
   one quiet disclosure grammar across /verify/ and /integrity/:
   small mono summary, single right-aligned +/− indicator, hairline
   top rule, no native browser triangle, no duplicated marker.

   Variants:
     .trust-disclosure--compact   /verify/ "choose another record"
     .trust-disclosure--technical /integrity/ "verify release locally"
                                                                  */
.trust-disclosure {
  /* silent utility row — no top hairline (was a section divider).
     quiet vertical margin lets the disclosure sit as a single line
     beneath the card, not as a separate page section. */
  margin: clamp(14px, 2.2vw, 20px) 0 clamp(14px, 2.2vw, 20px);
  padding-top: 0;
}
/* when a disclosure follows the integrity record-card, the paper card
   above already provides the visual anchor; tighten further so the
   "verify release locally" line reads as a small footer of the card.
   phase 57 · gap halved so the accordion reads as an extension of
   the release record card, not a new section beneath it. */
.integrity-record-card + .trust-disclosure {
  margin-top: clamp(3px, 0.8vw, 8px);
}
.trust-disclosure summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 0.7rem;
  line-height: 1.35;
  letter-spacing: 0.025em;
  color: var(--fg2);
  transition: color 0.2s;
}
.trust-disclosure summary::-webkit-details-marker { display: none; }
.trust-disclosure summary::marker                  { content: none; }
.trust-disclosure summary::after {
  /* fine editorial chevron — rotates 90deg when open. replaces the
     earlier +/− marker so the disclosure reads as archival, not as
     a generic accordion control. */
  content: '›';
  display: inline-block;
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 1em;
  line-height: 1;
  color: var(--fg3);
  transform: translateY(-0.06em);
  transition: transform 0.22s ease, color 0.15s;
}
.trust-disclosure[open] > summary::after {
  transform: rotate(90deg) translateX(-0.06em);
}
.trust-disclosure summary:hover,
.trust-disclosure summary:focus-visible {
  color: var(--accent-text);
  outline: 0;
}
.trust-disclosure summary:hover::after,
.trust-disclosure summary:focus-visible::after { color: var(--accent-text); }
.trust-disclosure__label { display: inline; }
/* phase 50 · summary → body gap tightened ~30% so the disclosure
   reads as one continuous technical appendix rather than two
   stacked blocks. */
.trust-disclosure__body  { margin-top: clamp(10px, 1.5vw, 14px); }

/* technical — short note + command block + copy button. */
.trust-disclosure--technical .trust-disclosure__body > p {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--fg2);
  margin: 0 0 1rem;
  line-height: 1.55;
}
.trust-code-block {
  /* a little breathing room above the command block so it reads as
     an archival printout, not a stacked terminal panel. */
  margin: 0;
  padding-top: 0.15rem;
}
.trust-code-block-header {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.45rem;
}
/* phase 55 · standalone .trust-code-copy rules retired. all
   inline-action styling now lives in the combined .record-inline-
   action / .copy-fingerprint / .trust-code-copy rule further down,
   keeping the integrity command-block copy and the fingerprint
   copy in lockstep. */
.trust-code {
  /* phase 50 · command block aligned with the card-object family:
     7px outer radius (var(--radius-soft)). background promoted to
     --surface-card (which now resolves to --surface-archival in
     phase 52). border at canonical --rule. phase 52 · padding
     compressed ~8% so the command slip reads as a terminal printout
     rather than a developer card. */
  margin: 0;
  padding: clamp(0.9rem, 2.75vw, 1.25rem);
  background: var(--surface-card);
  border: 1px solid var(--rule);
  border-radius: var(--radius-soft);
  font-family: var(--mono);
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--fg);
  /* display wraps long urls cleanly; copy returns the original. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  max-width: 100%;
}
.trust-code .code-cmd { color: var(--fg); }
.trust-code .code-var { color: var(--fg2); }
.trust-code .code-str { color: var(--accent-text); }
@media (max-width: 700px) {
  .trust-code { font-size: 0.66rem; padding: 0.85rem 0.95rem; line-height: 1.55; }
}

/* the unscoped .verify-thispage / .verify-related margin rules below
   no longer apply , related records is removed and .verify-thispage's
   spacing is owned by the card. */

/* chooser strip , same calm mono utility-link family as
   .verify-related-strip and .verify-thispage-actions. the current
   record reads as a muted span (no underline, no hover state) so the
   row never points back at itself awkwardly. */
.verify-chooser-strip {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.025em;
  color: var(--fg2);
  margin: 0;
  line-height: 1.6;
}
.verify-chooser-link {
  color: var(--fg2);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.verify-chooser-link:hover,
.verify-chooser-link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.verify-chooser-link--current {
  color: var(--fg3);
  border-bottom: 0;
  cursor: default;
}
.verify-chooser-sep {
  color: var(--fg3);
  margin: 0 0.4em;
}

/* page record , the centrepiece. top + bottom hairlines from the dl
   already render the frame; widen the grid so labels and values have
   room without becoming a card. */
.verify-page .verify-thispage-list { padding: 26px 0 24px; }
@media (min-width: 720px) {
  .verify-page .verify-thispage-row {
    grid-template-columns: 190px minmax(0, 1fr);
    column-gap: 48px;
    row-gap: 16px;
  }
}
.verify-page .verify-thispage-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--fg3);
  text-transform: uppercase;
}
.verify-page .verify-thispage-value      { font-size: 15px; line-height: 1.4; }
.verify-page .verify-thispage-value--mono { font-size: 13px; }

/* connected records , closing nav line, separated by a single hairline
   so it reads as the editorial endpoint, not as a stranded paragraph. */
.verify-page .verify-related {
  border-top: 1px solid var(--bd);
  padding-top: 28px;
}

/* back link , placed, not abandoned. */
/* phase 52 · back link pulls another 15% closer to the closing
   content so the page closes tight rather than floating. */
.verify-page .page-back { margin-top: clamp(16px, 2.5vw, 24px); }

/* Unknown-route fallback , small actions strip mirrors the page-record
   actions strip so the fallback never reads as broken. */
.verify-page .verify-unknown-actions {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.025em;
  color: var(--fg);
  margin: 0.7rem 0 0;
  line-height: 1.5;
}
.verify-page .verify-unknown-action {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.verify-page .verify-unknown-action:hover,
.verify-page .verify-unknown-action:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.verify-page .verify-unknown-actions-sep {
  color: var(--fg3);
  margin: 0 0.4em;
}

/* legacy reset
   the .verify-page section rule above is the load-bearing fix. these
   selectors are retained for any future contributor who reintroduces
   the older div-based render. */
#verify-root,
#verify-root > div,
.verify-command {
  padding: 0;
  border: 0;
}

.verify-root { margin-top: 1rem; }

/* ─────────────────────────────────────────────────────────────
   /integrity/ — the signed-release hub
   ─────────────────────────────────────────────────────────────
   same editorial dna as the /verify/ page record card: warm paper
   background, large serif headline, mono labels, hairline rules,
   oxblood only as accent on hover. the card is the central object;
   the command block is collapsed by default below it; history and
   related sit as quiet closing utilities.                         */

.integrity-record-card {
  background: var(--surface-card);
  border: 1px solid var(--rule);
  /* phase 39 · outer radius matches .verify-card and .project-
     card. internal kickers, dl rows, action rows stay sharp.
     phase 46 · literal 8px → var(--radius-soft) (7px). */
  border-radius: var(--radius-soft);
  /* phase 47 · card padding tightened ~25% so the record reads as
     a compact archival ledger rather than a spaced-out ui panel.
     phase 50 · further compression to compact archival register. */
  width: 100%;
  max-width: min(100%, 720px);
  min-width: 0;
  padding: clamp(1.2rem, 4vw, 1.9rem);
  margin: clamp(24px, 3.5vw, 36px) 0 clamp(18px, 2.5vw, 26px);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.035);
}
.integrity-record-kicker {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.55rem;
}
.integrity-record-title {
  font-family: var(--serif);
  font-size: clamp(28px, 4.4vw, 38px);
  font-weight: 300;
  letter-spacing: -0.012em;
  line-height: 1.12;
  color: var(--fg);
  margin: 0 0 0.35rem;
}
.integrity-record-status {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.025em;
  color: var(--fg3);
  margin: 0 0 clamp(20px, 3vw, 28px);
}

.integrity-record-dl  { margin: 0; padding: 0; }

/* phase 47 · record-row rhythm tightened so the rows read as one
   ledger system, not a spaced list. Phase-45 inter-row 2rem margin
   retired; the symmetric padding-block + ruled border now carries
   the inter-row separation. fingerprint row gets a touch more
   breathing room via .integrity-rg--fingerprint below. */
.integrity-rg {
  /* phase 50 · row padding tightened 0.85 → 0.72rem so the ledger
     reads as a denser archival sheet. fingerprint row keeps its
     touch more breathing via .integrity-rg--fingerprint below. */
  padding-block: 0.72rem;
}
.integrity-rg--ruled {
  border-top: 1px solid var(--bd);
}
.integrity-rg--fingerprint {
  padding-block: 1.05rem 0.95rem;
}
.integrity-rg--fingerprint .integrity-rg-label {
  display: block;
}
/* span exists only so the copy button can sit as a non-uppercased
   sibling on the same flex row — inherits label register. */
.integrity-rg-label-text {
  display: inline-block;
}

.integrity-rg-label {
  font-family: var(--mono);
  /* phase 45 · slightly larger + wider tracking + quieter colour so
     the labels read as quiet archival headings, with the value below
     carrying the weight.
     phase 47 · bottom margin tightened from 0.45 → 0.35.
     phase 50 · further tightened 0.35 → 0.2.
     phase 54 · alpha moved from opacity to color-mix.
     phase 59 · letter-spacing 0.14 → 0.12em so the row labels
     match the dominant label register (kicker, history-heading
     pre-section-kicker, etc.). one tracking value across the
     small-caps system.
     phase 60 · color-mix 78% → plain --fg3. at 0.72rem the mix
     blended below aa contrast in light mode. recede comes from
     size + uppercase + tracking + --fg3 alone. */
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.2rem;
}
.integrity-rg-value {
  font-family: var(--mono);
  font-size: 0.82rem;
  /* phase 47/57 · ledger line-height tightened progressively:
     1.55 → 1.35 → 1.28 so metadata reads as a dense archival
     ledger. */
  line-height: 1.28;
  color: var(--fg);
  margin: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.integrity-rg-desc {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--fg2);
  /* phase 47 · note sits closer to the value above + tighter
     internal leading. */
  margin: 0.18rem 0 0;
  line-height: 1.25;
}
.integrity-rg-link {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
  word-break: break-all;
}
.integrity-rg-link:hover,
.integrity-rg-link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.integrity-rg-sep {
  display: inline-block;
  margin: 0 0.4em;
  color: var(--fg3);
}
/* Cross-row alignment via subgrid at desktop breakpoints. below 720px
   the label/value/description stack as before. at ≥720px with subgrid
   support, the label sits left and the value+description stack on the
   right, with column edges aligned across every record. browsers
   without subgrid keep the stacked layout — no visual regression. */
@media (min-width: 720px) {
  @supports (grid-template-columns: subgrid) {
    .integrity-record-dl {
      display: grid;
      grid-template-columns: 200px minmax(0, 1fr);
      column-gap: 1.4rem;
    }
    .integrity-rg {
      display: grid;
      grid-template-columns: subgrid;
      grid-column: 1 / -1;
      align-items: baseline;
    }
    .integrity-rg-label {
      grid-column: 1;
      align-self: baseline;
      margin-bottom: 0;
    }
    .integrity-rg-value,
    .integrity-rg-desc {
      grid-column: 2;
    }
  }
}
.fingerprint-grid {
  display: grid;
  grid-template-columns: repeat(3, max-content);
  justify-content: start;
  align-items: baseline;
  column-gap: clamp(1.1rem, 5vw, 2rem);
  row-gap: 0.55rem;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
  padding-block: 0.25rem;
  font-family: var(--mono);
  font-size: clamp(1rem, 4.1vw, 1.15rem);
  line-height: 1.35;
  letter-spacing: 0.01em;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-variant-ligatures: none;
  background: transparent;
}
.fingerprint-grid span {
  white-space: nowrap;
}
@media (max-width: 360px) {
  .fingerprint-grid {
    grid-template-columns: repeat(2, max-content);
  }
}
.record-action-row {
  margin-block: 0.25rem 0.75rem;
}
.text-action {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.release-card .fingerprint-section,
.record-card .fingerprint-section {
  margin-top: 1.35rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

.integrity-record-actions {
  border-top: 1px solid var(--rule);
  /* phase 47 · tighter rhythm between the last record row, the
     hairline, and the actions strip below.
     phase 57 · wrapper register aligned with .record-inline-action
     so the separator spans + child anchors all read at one
     typographic level (0.58rem / 0.055em).
     phase 59 · vertical rhythm tightened so the strip sits as a
     close archival footer to the data rows above, matching the
     verify utility rail's compact register (phase 58). */
  padding-top: 0.55rem;
  padding-bottom: 0.14rem;
  margin: 0.55rem 0 0;
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.055em;
  color: var(--fg3);
  line-height: 1.4;
  /* flex + gap, no inline " · " separators — a wrapped action
     can never orphan a bullet at the start of a new line. */
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
}
/* shared utility-action register — copy citation, copy fingerprint,
   open mirror, view manifest, view releases all read as one family. */
.integrity-record-action {
  appearance: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--fg2);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bd);
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  text-transform: none;
  transition: color 0.2s, border-bottom-color 0.2s;
}
.integrity-record-action:hover,
.integrity-record-action:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.integrity-record-action[data-state="copied"] {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
/* legacy separator class kept for any cached html that still emits
   it; a hidden display rule means a stray <span> never paints a
   bullet. new html omits the separators entirely. */
.integrity-record-actions-sep { display: none; }
@media (max-width: 700px) {
  .integrity-record-actions {
    flex-direction: column;
    gap: 0.5rem 0;
    line-height: 1.4;
  }
  .integrity-record-action { align-self: flex-start; }
}

/* compact inline strip variant — download zip · TAR.GZ · view
   manifest · view releases. Single-line on desktop with explicit
   middots between items (so a wrap shows the structure clearly);
   on mobile, keeps the inline flow rather than stacking, so the
   row reads as one calm strip even at 320px. restrained underline
   weight (uses --rule, not --bd) keeps the row quieter than the
   data-bearing dl above it. no top rule on the strip itself —
   .integrity-record-copy above it carries the single rule that
   separates fingerprint hex from the grouped action area. */
.integrity-record-actions.integrity-record-actions--strip {
  display: block;
  margin: 0.25rem 0 0;
  font-size: 0.66rem;
  letter-spacing: 0.02em;
  line-height: 1.7;
}
.integrity-record-actions--strip .integrity-record-action {
  color: var(--fg3);
  border-bottom-color: var(--rule);
}
.integrity-record-actions--strip .integrity-record-action:hover,
.integrity-record-actions--strip .integrity-record-action:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
.integrity-record-actions--strip .integrity-record-actions-sep {
  display: inline;
  margin: 0 0.45em;
  color: var(--fg3);
  border: 0;
}
/* two grouped lines inside the strip — download zip · TAR.GZ on one
   line, view manifest · view releases on another. the grouping by
   purpose (downloads vs nav) gives the strip an intentional rhythm
   instead of a single long string of accidental footnotes. */
.integrity-record-actions--strip .integrity-record-actions-line {
  display: block;
}
.integrity-record-actions--strip .integrity-record-actions-line + .integrity-record-actions-line {
  margin-top: 0.2rem;
}
@media (max-width: 700px) {
  .integrity-record-actions.integrity-record-actions--strip {
    display: block;        /* override the legacy column rule above */
  }
  .integrity-record-actions--strip .integrity-record-action {
    align-self: auto;      /* override the legacy flex-start rule */
  }
}

/* phase 49 · the standalone .integrity-record-copy strip was retired.
   the copy action now sits inline on the fingerprint row's <dt>
   header (see .integrity-rg--fingerprint .integrity-rg-label above
   and the .copy-fingerprint rule further down). */

/* /integrity/ Verify-release-locally disclosure: chrome lives in the
   shared .trust-disclosure rules above. no /integrity/-specific summary
   styling needed — the page just adds the technical variant. */

/* Page-level note linking out to /verify/. a quiet bridge — sits
   close to the disclosure above so it reads as a footnote to local
   verification, not a fresh body paragraph. */
.integrity-page-level-note {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.03em;
  color: var(--fg3);
  margin: clamp(6px, 1vw, 10px) 0 clamp(18px, 2.6vw, 24px);
  line-height: 1.55;
}
.integrity-page-level-note a {
  color: var(--fg);
  border-bottom: 1px solid var(--bd);
  text-decoration: none;
  transition: color 0.2s, border-bottom-color 0.2s;
}
.integrity-page-level-note a:hover,
.integrity-page-level-note a:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}

/* history — small section heading + body + link, no hairlines.
   tighter top margin so the page reads as a continuous editorial
   thread, not three separate modules stacked. mobile reduces ~20%
   so the long card+disclosure+history stack feels finished, not
   bottom-heavy. */
.integrity-history {
  /* phase 57 · top margin further compressed ~15% so the history
     continuation sits as a soft transition, not a section break. */
  margin: clamp(0.9rem, 2.5vw, 1.7rem) 0 clamp(10px, 1.6vw, 16px);
}
.integrity-history-heading {
  /* phase 56 · history label drops to the section-kicker register —
     smaller, wider tracking so the label reads as an editorial
     section marker rather than a competing heading.
     opacity reduction retired — at 0.58rem on this token the blend
     would fall below aa contrast (lighthouse remediation). recede
     now comes from size + tracking + --fg3 alone. */
  font-family: var(--mono);
  font-size: 0.58rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.35rem;
}
.integrity-history-body {
  font-family: var(--serif);
  font-size: clamp(15px, 1.5vw, 17px);
  font-weight: 300;
  color: var(--fg);
  line-height: 1.55;
  /* phase 50 · symmetric block margin pulls the body closer to
     heading above and link below — heading, body, link read as
     one compact archival entry. */
  margin: 0 0 0.45rem;
}
.integrity-history-link {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.integrity-history-link:hover,
.integrity-history-link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}

/* ─────────────────────────────────────────────────────────────
   shared support-navigation pattern — .related-nav
   ─────────────────────────────────────────────────────────────
   single grammar for every secondary link strip across the trust
   pages: small mono label, one quiet hairline top rule, inline
   links with " · " separators. used by /integrity/ related records
   and /verify/ related records. reads as a finishing footer —
   never a content section, never a second module. */
.related-nav,
.integrity-related,
.related-grid {
  /* give the closing support nav one clear breath below the primary
     object so the page ends composed rather than cramped. */
  margin: clamp(16px, 2.8vw, 24px) 0 clamp(10px, 1.8vw, 16px);
}
.related-nav-label,
.integrity-related-label {
  /* Support-nav label register — quieter than the in-card metadata
     labels (page record, manifest, history) so support nav never
     competes with the page-record card or the section it follows.
     title case, gentle tracking, mono very small, fg3. the final
     shelf of the archive — should read as a footnote, not a
     section. */
  font-family: var(--mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: none;
  color: var(--fg3);
  margin: 0 0 0.35rem;
}
.related-nav-list,
.integrity-related-list,
.related-grid-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.8rem 1.5rem;
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--fg3);
  line-height: 1.7;
}
.related-nav-list li,
.integrity-related-list li,
.related-grid-list li { display: block; }
@media (min-width: 760px) {
  .related-nav-list,
  .integrity-related-list,
  .related-grid-list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
.related-nav-list a,
.integrity-related-list a,
.related-grid-list a {
  color: var(--fg2);
  font-family: var(--mono);
  font-size: 0.82rem;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  transition: color 0.2s, text-decoration-color 0.2s;
}
.related-nav-list a:hover,
.related-nav-list a:focus-visible,
.integrity-related-list a:hover,
.integrity-related-list a:focus-visible,
.related-grid-list a:hover,
.related-grid-list a:focus-visible {
  color: var(--accent-text);
  outline: 0;
}
.related-nav-list span[aria-current="page"] {
  color: var(--fg3);
  cursor: default;
}
@media (max-width: 700px) {
  .related-nav-list,
  .integrity-related-list,
  .related-grid-list { font-size: 0.78rem; }
}

/* ── shared command component (used on /verify/ and /integrity/) ──
   header strip (title left + copy button right) sits above the <pre>
   so the button never overlaps the command. Mobile-safe wrapping. */

.verify-command {
  margin: 0 0 1.6rem;
  max-width: 100%;
}

.verify-command:last-of-type {
  margin-bottom: 0.6rem;
}

.verify-command-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem 1rem;
  flex-wrap: wrap;
  margin: 0 0 0.4rem;
}

.verify-command-title {
  font-family: var(--serif);
  font-size: clamp(14px, 1.5vw, 16px);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.3;
  color: var(--fg);
  margin: 0;
  text-transform: none;
}

.verify-command-copy {
  appearance: none;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--fg3);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bd, #D8D4CC);
  border-radius: 0;
  padding: 0.15rem 0;
  cursor: pointer;
  transition: color 0.2s, border-bottom-color 0.2s;
  text-transform: none;
  line-height: 1.3;
}

.verify-command-copy:hover,
.verify-command-copy:focus-visible {
  color: var(--ac, #6E1A14);
  border-bottom-color: var(--ac, #6E1A14);
  outline: 0;
}

.verify-command-copy[data-state="copied"] {
  color: var(--ac, #6E1A14);
  border-bottom-color: var(--ac, #6E1A14);
}

.verify-command-block {
  background: var(--bg2);
  border: 1px solid var(--bd);
  border-radius: 4px;
  padding: 0.85em 1em;
  margin: 0;
  overflow-x: auto;
  max-width: 100%;
}

.verify-command-block code {
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--fg);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-wrap: anywhere;
}

.verify-command-note {
  font-family: var(--serif);
  font-size: clamp(13px, 1.4vw, 15px);
  font-weight: 300;
  line-height: 1.45;
  color: var(--fg2);
  max-width: 64ch;
  margin: 0.6rem 0 1rem;
}

/* general routes mode (no path) + unknown-route fallback. */
.verify-general-body {
  font-family: var(--serif);
  font-size: clamp(15px, 1.6vw, 18px);
  font-weight: 300;
  line-height: 1.55;
  color: var(--fg2);
  max-width: 62ch;
  margin: 0 0 1.5rem;
}

.verify-unknown-path {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--fg2);
  margin: 0 0 0.8rem;
  word-break: break-all;
}

/* trust routes , compact mental-model block
   mirrors the homepage approach-list pattern: mono numerals, signifier
   label, calm serif descriptor. restrained editorial composition; not
   a dashboard, not a sitemap. */
.trust-routes {
  margin: clamp(28px, 4vw, 40px) 0 clamp(20px, 3vw, 28px);
  max-width: 64ch;
}
.trust-routes-heading {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.7rem;
}
.trust-routes-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.trust-routes-item {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr);
  column-gap: 0.9rem;
  padding: 0.65rem 0;
  align-items: baseline;
  border-top: 1px solid var(--bd);
}
.trust-routes-item:last-child {
  border-bottom: 1px solid var(--bd);
}
.trust-routes-num {
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--accent-text);
  line-height: 1.3;
}
.trust-routes-body {
  min-width: 0;
}
.trust-routes-title {
  display: inline-block;
  font-family: var(--serif);
  font-size: clamp(15px, 1.55vw, 17px);
  font-weight: 400;
  color: var(--fg);
  line-height: 1.3;
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.trust-routes-title:hover,
.trust-routes-title:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
/* Current-page state , non-interactive, no underline, slightly muted
   so the row reads as the visitor's current location, not a self-link. */
.trust-routes-title--current {
  color: var(--fg3);
  border-bottom: 0;
}
.trust-routes-desc {
  display: block;
  margin: 0.18rem 0 0;
  font-family: var(--serif);
  font-size: clamp(13px, 1.4vw, 14.5px);
  font-weight: 300;
  color: var(--fg2);
  line-height: 1.45;
}

/* verify
   Page-level record card for citation, source mirror, fingerprint and
   archived release. hero, white record card, quiet record selector,
   back link. no command blocks; release verification lives on
   /integrity/. */

/* section heading shared across verify-* sub-sections. editorial
   signifier, same family as the privacy/integrity/security subheads
   so /verify/ feels native to that family. */
.verify-section-heading {
  font-family: var(--serif);
  font-size: clamp(18px, 2.2vw, 22px);
  font-weight: 300;
  letter-spacing: -0.012em;
  line-height: 1.2;
  color: var(--fg);
  margin: 0 0 0.5rem;
  text-transform: none;
}
.verify-section-heading--small {
  font-size: clamp(15px, 1.7vw, 17px);
}

/* short serif intro paragraph that sits below a section heading. */
.verify-section-intro {
  font-family: var(--serif);
  font-size: clamp(14px, 1.5vw, 16px);
  font-weight: 300;
  line-height: 1.5;
  color: var(--fg2);
  margin: 0 0 1.1rem;
  max-width: 64ch;
}

/* ── Section: this page ──
   editorial public record. no per-row borders , let typography and
   spacing carry the rhythm. identity rows then evidence rows; combined
   file row; page fingerprint with inline copy button. status strip
   above the list replaces the standalone verification chain section. */
/* .verify-thispage block-rhythm is owned by the scoped `.verify-page
   .verify-thispage` clamp() rule near the top of this file. */

/* inline status strip , three short mono chips, fg3, no badges, no
   icons. sits between the section heading and the metadata list. */
.verify-thispage-status {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.025em;
  color: var(--fg3);
  margin: -0.1rem 0 0.7rem;
  line-height: 1.4;
}
.verify-thispage-status-sep {
  color: var(--fg3);
  opacity: 0.7;
}
.verify-thispage-status-chip {
  white-space: nowrap;
}

.verify-thispage-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: 0;
  padding: 0.7rem 0;
  border-top: 1px solid var(--bd);
  border-bottom: 1px solid var(--bd);
}
.verify-thispage-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.05rem;
  padding: 0.45rem 0;
}
@media (min-width: 720px) {
  .verify-thispage-row {
    grid-template-columns: 200px 1fr;
    column-gap: 1.4rem;
    padding: 0.5rem 0;
    align-items: baseline;
  }
}
.verify-thispage-label {
  margin: 0;
  font-family: var(--serif);
  font-size: 0.92rem;
  font-weight: 400;
  color: var(--fg3);
  line-height: 1.4;
}
.verify-thispage-value {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(14px, 1.55vw, 16px);
  font-weight: 300;
  color: var(--fg);
  line-height: 1.4;
  min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.verify-thispage-value--mono {
  font-family: var(--mono);
  font-size: 0.85em;
  word-break: break-all;
}
.verify-thispage-link {
  font-family: inherit;
  font-size: inherit;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  word-break: break-all;
  overflow-wrap: anywhere;
  transition: color 0.2s, border-bottom-color 0.2s;
}
.verify-thispage-link:hover,
.verify-thispage-link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}

/* page fingerprint row , value stacks: hash on its own line, then a
   small copy fingerprint button below. same pattern as the cite-overlay
   hash treatment so it never overlaps. */
.verify-thispage-fingerprint-value {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  align-items: flex-start;
}
.verify-thispage-fingerprint {
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--fg2);
  word-break: break-all;
  overflow-wrap: anywhere;
  background: transparent;
  width: 100%;
}
.verify-thispage-fingerprint-copy {
  appearance: none;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  color: var(--fg3);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bd);
  padding: 0.1rem 0;
  cursor: pointer;
  transition: color 0.2s, border-bottom-color 0.2s;
  text-transform: none;
}
.verify-thispage-fingerprint-copy:hover,
.verify-thispage-fingerprint-copy:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.verify-thispage-fingerprint-copy[data-state="copied"] {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}

/* compact actions strip beneath the page-record dl. same restrained
   mono utility-link style as .verify-related-strip, so the two strips
   sit as a calm pair at the bottom of /verify/. */
.verify-thispage-actions {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.025em;
  color: var(--fg);
  margin: 0.7rem 0 0;
  line-height: 1.5;
}
.verify-thispage-action {
  appearance: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--fg);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bd);
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  text-transform: none;
  transition: color 0.2s, border-bottom-color 0.2s;
}
.verify-thispage-action:hover,
.verify-thispage-action:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.verify-thispage-action[data-state="copied"] {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
.verify-thispage-actions-sep {
  color: var(--fg3);
  margin: 0 0.4em;
}

/* ── Section: verify locally , closing line ── */
.verify-local-closing {
  font-family: var(--serif);
  font-size: clamp(13px, 1.4vw, 14.5px);
  font-weight: 300;
  line-height: 1.5;
  color: var(--fg2);
  margin: 1.2rem 0 0;
  max-width: 64ch;
}

/* ── proof summary card ──
   compact two-column dl , label column content-led on desktop, fixed
   on mobile so long urls and hashes don't crush labels. status row at
   the top carries a thin oxblood mark (when the page is found). one
   hairline above and below the dl, no per-row dividers. */
/* ── "verify locally" , 2 command blocks with descriptions ── */
/* .verify-local block-rhythm is owned by the scoped `.verify-page
   .verify-local` clamp() rule. */
.verify-local-desc {
  font-family: var(--serif);
  font-size: clamp(13px, 1.4vw, 14.5px);
  font-weight: 300;
  color: var(--fg2);
  line-height: 1.45;
  margin: 0 0 0.55rem;
  max-width: 60ch;
}
.verify-local-desc--second {
  margin-top: 1.4rem;
}

/* ── related records , compact link strip ──
   .verify-related block-rhythm + top hairline owned by the scoped
   `.verify-page .verify-related` rule. */
.verify-related-strip {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.025em;
  color: var(--fg);
  margin: 0;
  line-height: 1.5;
}
.verify-related-link {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.verify-related-link:hover,
.verify-related-link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
.verify-related-sep {
  color: var(--fg3);
  margin: 0 0.4em;
}

/* "route not in map" calm notice (renderunknown). */
.verify-unknown {
  margin: 0 0 1.8rem;
}

@media (max-width: 600px) {
  .verify-command-block {
    padding: 0.75em 0.9em;
  }
  .verify-command-block code {
    font-size: 11px;
    line-height: 1.55;
  }
  .verify-local-desc--second { margin-top: 1.1rem; }
}

/* accessibility */

/* subtle highlight for keyboard-navigated sections */
:target {
  outline-offset: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  font-family: var(--mono);
  font-size: 12px;
  padding: 8px 16px;
  background: var(--fg);
  color: var(--bg);
  text-decoration: none;
}

.skip-link:focus {
  left: 8px;
  top: 8px;
}

/* legacy :focus-visible rule replaced by the editorial focus-visible
   system in @layer base above. keep .skip-link's focus override
   only — it needs to remain prominent so keyboard users can spot
   the bypass. */

:focus:not(:focus-visible) {
  outline: none;
}

/* layout */

.site {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 clamp(24px, 5vw, 80px);
}

/* Safe-area bottom on the main content surface so the last in-flow cta
   (e.g. the homepage `view project` button) never sits beneath ios
   safari's bottom chrome when the user has not yet scrolled to retract
   it. the footer carries its own safe-area-inset-bottom via .footer. */
main.site {
  padding-bottom: env(safe-area-inset-bottom);
}

section {
  padding: clamp(80px, 12vh, 160px) 0;
  border-bottom: 1px solid var(--bd);
}

section:last-of-type {
  border-bottom: none;
}

/* hero → approach divider · the hero is a <header>, so the generic
   section { border-bottom } rule never paints a rule below it. add
   a top hairline on the first section so the hero-to-approach
   transition reads as resolved as the section-to-section breaks
   that follow. matched to the same --bd token + clamp padding the
   rest of the spine uses. */
.home-profile > section:first-of-type {
  border-top: 1px solid var(--bd);
}

/* nav
   ── the nav family lives inside @layer components so the homepage
   disclosure rules in @layer pages (later in cascade order) can win
   normally for the open-state layout (display:grid · text-align:right)
   without needing !important. per css cascade layers level 5, normal
   declarations cascade later-layer-wins; !important declarations
   cascade earlier-layer-wins, with unlayered author rules sitting
   above all layered author rules. leaving these rules unlayered would
   silently override @layer pages every time. ── */
@layer components {
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: var(--bg);
  border-bottom: 1px solid var(--bd);
  /* Safe-area top padding so the nav row sits below the ios notch /
     dynamic island in `viewport-fit=cover` mode on non-home pages.
     homepage @layer pages override (mobile) sets its own padding-
     block via env(safe-area-inset-top) — that wins via cascade. */
  padding-top: var(--safe-top);
}

.nav-inner {
  max-width: 1200px;
  margin: 0 auto;
  /* horizontal safe-area padding: the inset-left/right tokens are
     non-zero on landscape iphone (notch on one side); max() with the
     existing clamp() keeps the editorial register on every other
     orientation while preventing the nav from slipping under the
     notch in landscape. */
  padding-block: 0;
  padding-inline-start: max(clamp(24px, 5vw, 80px), var(--safe-left));
  padding-inline-end:   max(clamp(24px, 5vw, 80px), var(--safe-right));
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* phase 40 · 52 → 54px gives a touch more vertical breathing
     so the header reads as deliberate rather than utilitarian.
     within the 60px --nav-offset buffer, anchor scrolling stays
     correct. */
  height: 54px;
}

/* the wordmark is identity · söhne mono carries the archival voice.
   phase 40 · tracking 0.10 → 0.09em pulls the wordmark slightly
   tighter so it reads as set type rather than spread-out caps. */
.nav-mark {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fg);
  text-decoration: none;
}

/* nav-toggle / nav-links / nav-toggle-glyph / nav-toggle-label —
   retired site-wide. the masthead is the only header element now;
   the homepage scrolls naturally and discovery is by reading. */

/* narrow phones: tighten the masthead horizontally so the stacked
   trent / power lockup sits cleanly against the page edge. */
@media (max-width: 600px) {
  .nav-inner {
    padding-inline: 15px;
    height: 58px;
    align-items: center;
  }
  .nav-mark {
    display: block;
    max-width: 4.55rem;
    white-space: normal;
    line-height: 1.08;
    font-size: 11px;
    letter-spacing: 0.09em;
  }
  .nav-mark span { display: block; }
}
@media (max-width: 390px) {
  .nav-inner {
    padding-inline: 13px;
  }
  .nav-mark {
    max-width: 4.35rem;
    font-size: 10.5px;
    letter-spacing: 0.08em;
  }
}

}
/* end @layer components — masthead. */

/* section 1 · hero / positioning */

.hero {
  min-height: 100vh;
  min-height: 100svh; /* ios safari ≥15.4 retracts chrome via the small viewport unit */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* widened from 52px so the hero text always clears the nav at every
     width (52px desktop nav, 58px mobile nav, plus a small breathing
     budget). with sticky positioning the nav reserves flow space, so
     this padding-top is partly belt-and-braces — keeps text away from
     the very top edge even when the nav scroll-hides. */
  padding-top: clamp(60px, 8vh, 80px);
  padding-bottom: clamp(60px, 10vh, 120px);
  /* no border-bottom: the trust-mark sits inside the hero in dom
     flow after .hero-body, so a 1px line here would cut between
     hero text and the trust mark. section-to-section separation
     comes from the `section { border-bottom }` rule on
     .section-approach below. */
}

/* hero eyebrow. the "trent power" label is rendered via CSS-generated
   content so reader view extracts only one author signal (from the
   article body / metadata), not a duplicate eyebrow text node. the
   <div class="hero-name"> in html is empty + aria-hidden. */
.hero-name::before {
  content: "Trent Power";
}
.hero-name {
  font-family: var(--mono);
  font-size: clamp(11px, 1.2vw, 13px);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg3);
  margin-bottom: clamp(24px, 4vh, 48px);
}

.hero-statement {
  font-family: var(--serif);
  font-size: clamp(42px, 10vw, 82px);
  font-weight: 300;
  line-height: 0.98;
  letter-spacing: -0.035em;
  color: var(--fg);
  max-width: 920px;
  margin-bottom: clamp(32px, 5vh, 56px);
}

.hero-body {
  font-family: var(--serif);
  font-size: clamp(15px, 1.6vw, 19px);
  font-weight: 300;
  line-height: 1.65;
  color: var(--fg2);
  /* phase 40 · ~5% wider measure (580 → 610px). on mobile this
     gives the hero paragraph fewer awkward short-line wraps; on
     desktop it stays well below the 720px page-title cap so the
     editorial hierarchy is preserved. */
  max-width: 610px;
}

/* hero entrance · single reveal contract (phase 37)
   ──────────────────────────────────────────────────────────
   default: hero pieces start invisible and lifted. when js is
   off (no .js class on <html>), the override below paints them
   at rest immediately so no-JS visitors see the hero. when js
   is on and html.enhanced has been added (after DOMContentLoaded
   in app.js), each piece animates up in sequence with the trust
   mark participating as the last beat. reduced-motion users see
   the resting state regardless. */
/* hero reveal · gated on html.js (set inline pre-paint by the language
   bootstrap, runs synchronously before the first paint frame). hiding
   and the reveal keyframe are now driven by the same class, so a
   failed or slow app.js cannot leave the hero stuck invisible — the
   animation starts in the same paint tick the hide begins. when js
   is disabled entirely the hide rule never matches and the hero
   paints resting, no fallback rule needed. */
html.js .hero-name,
html.js .hero-statement,
html.js .hero-body,
html.js .trust-mark {
  opacity: 0;
  transform: translateY(14px);
}
html.js .hero-name {
  animation: revealUp 700ms cubic-bezier(.2,.7,.2,1) forwards;
}
html.js .hero-statement {
  animation: revealUp 800ms cubic-bezier(.2,.7,.2,1) 90ms forwards;
}
html.js .hero-body {
  animation: revealUp 850ms cubic-bezier(.2,.7,.2,1) 180ms forwards;
}
html.js .trust-mark {
  animation: revealUp 850ms cubic-bezier(.2,.7,.2,1) 270ms forwards;
}
/* hero entrance keyframe · animates from the initial-hidden state
   set above (opacity: 0; transform: translateY(14px)) to resting.
   referenced by each of the four hero pieces with a staggered delay. */
@keyframes revealUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hero-name,
  .hero-statement,
  .hero-body,
  .trust-mark {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
}

/* ─── trust mark ──────────────────────────────────────────
   bordered editorial tag under the hero body. reads as an
   intentional certification badge, not three plain links.
   sits inside <header class="hero"> in dom order after
   .hero-statement and .hero-body. participates in the
   `html.enhanced` hero reveal sequence defined above. links
   to /privacy/, /source/, /integrity/ at link-inherit colours
   (never default blue). */
.trust-mark {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  width: fit-content;
  max-width: 100%;
  margin-block-start: clamp(1.2rem, 3vw, 1.8rem);
  /* phase 40 · slightly more present so the certification strip
     reads as deliberate rather than apologetic.
     phase 46 · radius elevated to var(--radius-pill) (999px).
     phase 49 · reversed back to var(--radius-soft) (7px).
     phase 53 · subtle background tint promotes the trust mark
     from bordered text strip to archival label — visually matches
     the verify / integrity card surface (--surface-archival) so
     the mark reads as a small object from the same paper family. */
  background: var(--surface-archival);
  padding: 0.6rem 0.8rem;
  border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
  border-radius: var(--radius-soft);
  font-family: var(--mono);
  font-size: 0.7rem;
  line-height: 1.2;
  letter-spacing: 0.07em;
  color: var(--fg3);
}
.trust-mark a {
  color: inherit;
  text-decoration: none;
}
.trust-mark a:visited {
  color: inherit;
}
.trust-mark a + a::before {
  content: "·";
  display: inline-block;
  margin-inline: 0.55rem;
  opacity: 0.55;
  color: currentColor;
  /* phase 39 · optical-centre the dot between cap heights;
     matches the same pattern used on the footer actions row. */
  transform: translateY(-0.02em);
}
.trust-mark a:hover,
.trust-mark a:focus-visible {
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.trust-mark a:focus-visible {
  outline: 1px solid currentColor;
  outline-offset: 0.25rem;
}
@media (max-width: 700px) {
  .trust-mark {
    font-size: 0.64rem;
    letter-spacing: 0.06em;
  }
}

/* section labels */

/* section labels render in söhne mono. söhne mono does not ship `smcp` /
   `c2sc` opentype features in this licence cut, so we use measured css
   uppercase + restrained tracking rather than faux small caps. */
.section-label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--fg3);
  margin-bottom: clamp(40px, 6vh, 72px);
}

/* the single static oxblood mark on the page: the section index numeral. */
.section-label .label-num {
  color: var(--accent-text);
  margin-right: 8px;
}

/* section 2 · focus & approach */

.principles {
  display: grid;
  gap: 0;
  list-style: none;
}

/* principles, trajectory chapters, and project cards are visible by default
   so the page renders correctly without javascript. the `.js` class
   (added by the inline language script before first paint) opts into the
   scroll-reveal behaviour. */
.principle {
  padding: clamp(28px, 4vh, 48px) 0;
  border-bottom: 1px solid var(--bd-soft);
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

.js .principle {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.js .principle.visible {
  opacity: 1;
  transform: translateY(0);
}

.principle:last-child {
  border-bottom: none;
}

.principle-title {
  font-family: var(--serif);
  font-size: clamp(22px, 3vw, 30px);
  font-weight: 400;
  line-height: 1.2;
  color: var(--fg);
  letter-spacing: -0.018em;
}

.principle-body {
  font-family: var(--serif);
  font-size: clamp(14px, 1.4vw, 17px);
  font-weight: 300;
  line-height: 1.6;
  color: var(--fg2);
  max-width: 560px;
}

@media (min-width: 768px) {
  .principle {
    grid-template-columns: 1fr 1fr;
    gap: clamp(24px, 4vw, 80px);
    align-items: baseline;
  }
}

/* section 3 · trajectory */

.trajectory-grid {
  display: grid;
  gap: 0;
}

/* trajectory · scroll-reveal initial state. the IntersectionObserver
   in app.js promotes each .trajectory-item to .visible as it enters
   the viewport. gating on .js means the items are visible by default
   when scripting is unavailable — no orphaned hidden cards. */
.js .trajectory-item {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.js .trajectory-item.visible {
  opacity: 1;
  transform: translateY(0);
}

/* legacy .chapter-* typography retired — the new .trajectory-item /
   .trajectory-kicker / .trajectory-title / .trajectory-detail rules
   in the trajectory chronology block downstream now carry the same
   register. anchors to #clienteling-definition still resolve to the
   <dfn> in the principles list above. */

/* trajectory · resilient editorial chronology. three breakpoint
   modes share one dom — a vertical archival chronology under 760 px,
   a two-column editorial grid at 760–1099 (label + rail on the left,
   items stacked 2×2 on the right), and a four-column horizontal
   timeline at 1100 and above with a rail running across the top of
   each column. all layout sits inside css grid + flex; no viewport-
   percentage anchoring, no transform hacks, no per-card absolute
   placement. custom properties handle the few tunable knobs. */

.section-trajectory {
  --trajectory-line:    var(--bd-soft);
  --trajectory-marker:  var(--fg3);
  --trajectory-current: var(--accent);
  --trajectory-muted:   var(--fg3);
  --trajectory-gap:     clamp(1.5rem, 3.5vw, 4rem);
  --trajectory-card-max: 22rem;
}

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

.trajectory-item {
  min-width: 0;
  position: relative;
  /* belt-and-braces marker reset · ios safari occasionally paints
     decimal markers even when the parent ol has list-style: none.
     setting list-style on the li and emptying ::marker kills the
     "1. 2. 3." numbering for good. */
  list-style: none;
}
.trajectory-item::marker {
  content: "";
}

.trajectory-year {
  display: block;
  font: 500 11px/1.2 var(--mono);
  letter-spacing: 0.04em;
  color: var(--trajectory-muted);
  font-variant-numeric: tabular-nums lining-nums;
  margin: 0;
}
.trajectory-item--current .trajectory-year {
  color: var(--trajectory-current);
  font-weight: 500;
}

.trajectory-marker {
  display: block;
  width: 0.55rem;
  height: 0.55rem;
  border: 1px solid var(--trajectory-marker);
  border-radius: 999px;
  background: var(--bg);
  box-sizing: border-box;
}
.trajectory-item--current .trajectory-marker {
  width: 0.7rem;
  height: 0.7rem;
  background: var(--trajectory-current);
  border-color: var(--trajectory-current);
}

.trajectory-card {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  max-width: var(--trajectory-card-max);
  margin: 0;
  padding: 0;
}

.trajectory-kicker {
  font: 500 10px/1.1 var(--mono);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--trajectory-muted);
  margin: 0;
}

.trajectory-title {
  font: 400 clamp(15px, 1.4vw, 17px)/1.3 var(--serif);
  letter-spacing: -0.012em;
  color: var(--fg);
  margin: 0;
  text-wrap: balance;
}

.trajectory-org {
  font: 500 10px/1.2 var(--mono);
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--trajectory-current);
  margin: 0;
}

.trajectory-detail {
  font: 300 clamp(13px, 1vw, 14px)/1.55 var(--serif);
  color: var(--fg2);
  margin: 0;
}

.trajectory-period {
  font: 500 9.5px/1.4 var(--mono);
  letter-spacing: 0.06em;
  color: var(--trajectory-muted);
  font-variant-numeric: tabular-nums lining-nums;
  margin: 2px 0 0;
  white-space: nowrap;
}
.trajectory-period time { font: inherit; color: inherit; }

/* ── mobile · vertical archival chronology ───────────────────
   rail is a left border on the list; markers pull into the gutter
   over the rail. a subtle border-top separates each row. spacing
   is generous but never oversized. */
@media (max-width: 759.98px) {
  .trajectory-list {
    margin: clamp(2.4rem, 9vw, 3.6rem) 0 0;
    padding: 0 0 0 1.25rem;
    border-left: 1px solid var(--trajectory-line);
  }
  .trajectory-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.5rem;
    padding: 0 0 clamp(1.6rem, 6vw, 2.2rem) clamp(1rem, 3.5vw, 1.4rem);
  }
  .trajectory-item + .trajectory-item {
    border-top: 1px solid var(--trajectory-line);
    padding-top: clamp(1.6rem, 6vw, 2.2rem);
  }
  .trajectory-year {
    order: 1;
  }
  .trajectory-marker {
    position: absolute;
    top: clamp(1.6rem, 6vw, 2.2rem);
    left: calc(-1.25rem - 0.275rem);
  }
  .trajectory-item:first-child .trajectory-marker {
    top: 0;
  }
  .trajectory-item--current .trajectory-marker {
    left: calc(-1.25rem - 0.35rem);
  }
  .trajectory-card {
    order: 2;
    gap: 0.45rem;
  }
  .trajectory-title {
    font-size: clamp(15px, 4vw, 17px);
    max-width: 24rem;
  }
  .trajectory-detail {
    font-size: clamp(13px, 3.7vw, 15px);
    max-width: 32rem;
  }
}

/* ── tablet · two-column editorial grid (760–1099) ────────────
   the section label sits in a quiet left rail; the four items
   stack 2×2 on the right. no horizontal axis (it would clip at
   this width); the chronology reads as a structured editorial
   index. */
@media (min-width: 760px) and (max-width: 1099.98px) {
  .section-trajectory {
    display: grid;
    grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr);
    column-gap: clamp(2rem, 4vw, 3.5rem);
    align-items: start;
  }
  .section-trajectory > .section-label {
    grid-column: 1;
    margin: 0;
    padding-right: clamp(1rem, 2vw, 2rem);
    border-right: 1px solid var(--trajectory-line);
    min-height: 2rem;
  }
  .trajectory-list {
    grid-column: 2;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: clamp(1.5rem, 3vw, 2.5rem);
    row-gap: clamp(1.8rem, 3.5vw, 2.8rem);
    margin: 0;
  }
  .trajectory-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    row-gap: 0.55rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--trajectory-line);
  }
  .trajectory-year {
    grid-row: 1;
    margin: 0;
  }
  .trajectory-marker {
    grid-row: 2;
    justify-self: start;
    margin: 0;
  }
  .trajectory-card {
    grid-row: 3;
    max-width: 24rem;
  }
}

/* ── desktop · four-column horizontal chronology (≥ 1100) ─────
   rail is a single horizontal hairline across the top of the list
   via ::before. each item is a column: year above marker above card.
   marker margins are calibrated so the dot's vertical centre lands
   on the rail regardless of font metrics. */
@media (min-width: 1100px) {
  .trajectory-list {
    position: relative;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: var(--trajectory-gap);
    margin: clamp(3rem, 7vw, 5rem) 0 0;
  }
  .trajectory-list::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 2.4rem;
    border-top: 1px solid var(--trajectory-line);
  }
  .trajectory-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
  }
  .trajectory-year {
    margin: 0 0 1rem;
  }
  .trajectory-marker {
    margin: 0 0 1.25rem;
    /* year-bottom + 1rem gap + 0.55rem dot puts dot centre at the rail
       (rail sits at top: 2.4rem on the list). */
    align-self: flex-start;
  }
  .trajectory-item--current .trajectory-marker {
    margin: -0.075rem 0 1.175rem;
  }
  .trajectory-card {
    max-width: var(--trajectory-card-max);
  }
}

/* ── print · existing print profile owns the trajectory ─────── */
@media print {
  .trajectory-list { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .trajectory-item { animation: none; transition: none; }
}

/* section 4 · personal projects */

.project-card {
  /* editorial insert on the homepage — its own --paper-project tone
     so it lifts visibly off the front-of-house ivory and reads as
     a sheet placed on the page, never as a record-layer card.
     phase 39 · 2px outer radius softens the object quality just
     enough to read as a mounted archival sheet rather than a
     mathematically sharp web card. internal rules, metadata
     rows and buttons stay sharp. phase 46 · literal 8px →
     var(--radius-soft) (7px). */
  background: var(--paper-project);
  border: 1px solid var(--rule);
  border-radius: var(--radius-soft);
  padding: clamp(36px, 8vw, 48px) clamp(28px, 8vw, 44px) clamp(34px, 9vw, 52px);
  margin-top: clamp(40px, 7vh, 64px);
  position: relative;
}

@media (min-width: 768px) {
  .project-card { padding: clamp(48px, 5vw, 72px); }
}

.js .project-card {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}

.js .project-card.visible {
  opacity: 1;
  transform: translateY(0);
}

.project-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 3px;
  height: 100%;
  background: var(--ac);
}

/* editorial cards opt out of the accent rail. */
.project-card--editorial::before { content: none; display: none; }
.project-card--editorial { border-left: 1px solid var(--rule); }

.project-name {
  font-family: var(--mono);
  /* phase 40 · slightly larger and tighter so the project title
     reads as the clear visual anchor of the card (söhne mono is
     only available in 400/500 — no heavier weight option, so the
     hierarchy bump comes from size + tracking instead of weight).
     stays mono caps, stays at full --fg ink. */
  font-size: clamp(15px, 1.7vw, 19px);
  font-weight: 500;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--fg);
  margin-bottom: 18px;
}

.project-desc {
  font-family: var(--serif);
  font-size: clamp(19px, 5vw, 22px);
  font-weight: 400;
  line-height: 1.58;
  letter-spacing: -0.01em;
  color: var(--fg2);
  max-width: 32ch;
  margin-bottom: 34px;
}

.project-subline {
  font-family: var(--serif);
  font-size: clamp(14px, 1.6vw, 16px);
  font-style: italic;
  font-weight: 400;
  line-height: 1.55;
  color: var(--fg2);
  margin-top: 0;
  margin-bottom: 28px;
}

.project-link {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-text);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  border-bottom: 1px solid var(--bd);
  transition: border-color 0.2s, gap 0.2s;
}

.project-link:hover {
  border-color: var(--accent-text);
  gap: 12px;
}

/* Defensive: anywhere a typographic mark like → is rendered, force it
   through söhne mono so font-fallback can't substitute the glyph. */
.arrow {
  font-family: var(--mono);
}

.project-link .arrow {
  transition: transform 0.2s;
}

.project-link:hover .arrow {
  transform: translateX(2px);
}

/* button variant · replaces inline style="" for csp compliance */
.project-link-btn {
  margin-top: 24px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--bd);
  cursor: pointer;
}

/* project preview · mimics the app ui */
.project-preview {
  margin-top: 34px;
  border-top: 1px solid var(--bd);
  padding-top: 26px;
}

.preview-header {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  margin-bottom: 16px;
}

.preview-items {
  display: grid;
  gap: 0;
  list-style: none;
}

.preview-item {
  padding: 10px 0;
  border-bottom: 1px solid var(--bd-soft);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
}

.preview-item:last-child {
  border-bottom: none;
}

.preview-title {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  color: var(--fg);
}

.preview-meta {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--fg3);
  white-space: nowrap;
}

.preview-editorial {
  font-family: var(--serif);
  font-style: italic;
  font-size: 11px;
  color: var(--fg3);
  margin-top: 2px;
}

.project-preview-caption {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--fg3);
  margin-top: 14px;
  line-height: 1.5;
}

.preview-tier {
  font-size: 9px;
  letter-spacing: 0.08em;
  padding: 1px 5px;
  border: 1px solid var(--bd);
  color: var(--fg3);
  margin-right: 6px;
}

.tier-walk  { border-color: var(--bd); color: var(--fg3); }
.tier-metro { border-color: var(--accent-text); color: var(--accent-text); }

/* contact */

.section-contact {
  padding: clamp(80px, 12vh, 160px) 0 clamp(60px, 8vh, 100px);
}

@media (max-width: 767px) {
  .section-contact {
    padding-top: clamp(64px, 9.5vh, 112px);
  }
}

/* small editorial sentence above the email — gives the contact
   section a reason to write rather than three abrupt anchors.
   serif, muted, with a narrow measure so it reads as a brief
   abstract, not a paragraph. */
.contact-note {
  font-family: var(--serif);
  font-size: clamp(15px, 1.5vw, 17px);
  font-weight: 300;
  line-height: 1.55;
  color: var(--fg2);
  max-width: 32rem;
  margin: 0 0 clamp(1.1rem, 3vw, 1.5rem);
}

.contact-address {
  font-style: normal;
  margin: 0;
}

.contact-email {
  font-family: var(--mono);
  font-size: clamp(14px, 2vw, 22px);
  font-weight: 400;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--bd);
  padding-bottom: 4px;
  transition: border-color 0.2s;
  /* Long-text resilience: at 200% zoom on a 320 px viewport the rendered
     mailto could otherwise overflow the column. word-break stays normal
     so spaces in the readable form are preserved. */
  overflow-wrap: anywhere;
  word-break: normal;
}

.contact-email:hover {
  border-color: var(--accent-text);
}

/* Long-token resilience for narrow viewports / 200% zoom. mono url
   and project-preview titles can each overrun their column at 320px
   width without `overflow-wrap: anywhere`. fingerprint grids are
   component-controlled and therefore excluded here. */
.preview-title {
  overflow-wrap: anywhere;
  word-break: normal;
}

.integrity-record-status,
.verify-status,
.record-meta,
.integrity-rg-value,
.integrity-rg-desc,
.integrity-rg-link,
.integrity-rg-value code,
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid dd,
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid a,
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid code,
:is(.verify-page, .sw-reset-page, .security-page, .source-page) .record-grid samp,
.verify-intro-row dd,
.verify-intro-row dd code,
.source-entry-name a,
.source-entry-meta {
  max-width: 100%;
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: normal;
  font-variant-ligatures: none;
}

.record-fingerprint,
.record-fingerprint bdi {
  overflow-wrap: anywhere;
  word-break: break-all;
  font-variant-ligatures: none;
}

.contact-secondary {
  margin-top: 20px;
}

.contact-secondary a {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg3);
  text-decoration: none;
  letter-spacing: 0.04em;
  transition: color 0.2s;
}

.contact-secondary a:hover {
  color: var(--fg);
}

/* Mobile-only contact refinement: email gains a touch more presence
   without becoming a headline; linkedin link stays secondary. */
@media (max-width: 767px) {
  .contact-email {
    font-size: clamp(15.5px, 4.3vw, 18px);
    letter-spacing: 0.01em;
  }
  .contact-secondary a {
    font-size: 11px;
  }
}

/* footer · centred editorial colophon (phase 37 stabilisation)
   ───────────────────────────────────────────────────────────────
   one footer system. one cite trigger contract. no border line.
   centred on every breakpoint, calm and balanced — reads as an
   intentional colophon rather than a left rail of metadata.

   markup:
       <footer class="site-footer">
         <div class="site-footer__inner">
           <small class="site-footer__colophon">…</small>
           <nav class="site-footer__language" aria-label="Language">
             <ul class="site-footer__language-list"><li><button>…</button></li>…</ul>
           </nav>
           <nav class="site-footer__actions" aria-label="Footer">
             <button class="site-footer__action" data-cite-open>…</button>
             <a class="site-footer__action" href="/privacy/">Privacy</a>
           </nav>
         </div>
       </footer>

   `.cite-btn` is retained as a styling alias on the cite button
   element (the l8 touch-target validator looks for a standalone
   `.cite-btn { min-height: 44px }` rule in compiled css).
   phase 36 had already deleted every legacy `.footer-*` selector;
   this rewrite keeps that purge in place.
*/
/* footer · four elements, centred, vertically stacked.
   ── seal · · · — the typographer's return, mono, very wide tracking,
      fg3 at 55% opacity. screen-reader hidden.
   ── colophon — mono publication imprint: year, name, city/country
      separated by nbsp pairs (no commas between the three tokens),
      one comma kept inside the paris/france cluster.
   ── language switcher — serif, full language names in their own
      language. active = ink; alternate = fg3 lifting to accent on
      hover with a hairline border-bottom. italic slash separator.
   ── actions rail (optional) — mono, dot-separated. cite & verify ·
      privacy. omitted on data-surface="record" or "archive". the
      home page and the privacy page are the only two surfaces that
      keep the rail.
   tokens: --bg / --fg / --fg3 / --accent / --bd-soft / --mono / --serif. */
.site-footer {
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
  border-top: 1px solid var(--bd-soft);
  padding: 34px 28px calc(30px + env(safe-area-inset-bottom));
  text-align: center;
  color: var(--fg3);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* seal — three middle dots stretched apart by 0.8em letter-spacing,
   color reads as fg3 dialled back to 55% opacity. aria-hidden because
   the dots are a typographic flourish, not content. */
.site-footer__seal {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.8em;
  /* trailing letter-spacing pushes the visual centre right by 0.8em;
     pull the block back so · · · is optically centred. */
  padding-inline-start: 0.8em;
  color: color-mix(in srgb, var(--fg3) 55%, transparent);
  margin: 0;
  line-height: 1;
  user-select: none;
}

/* colophon — publication imprint. nbsp pairs already in the markup
   so we don't need word-spacing tricks. tabular numerals keep 2026
   from drifting between editions. */
.site-footer__colophon {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--fg3);
  font-variant-numeric: tabular-nums lining-nums;
  margin: 0;
  line-height: 1.4;
}

/* language switcher — serif, weight 300, full names. the alternate
   sits in fg3 and lifts to accent on hover with a 1 px border-bottom
   underline. the slash separator is italic 14 px in fg3 at 50%
   opacity so it reads as punctuation, not as a control. */
.site-footer__language {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: 0;
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 300;
  line-height: 1.2;
  letter-spacing: 0;
  margin: 0;
}
.site-footer__language button {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: var(--fg3);
  font: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding: 0 6px;
  /* 44×44 touch target carried by the row, not by per-button box
     geometry — letter-spacing + padding-inline keep the buttons
     tap-friendly without forcing a chrome line-box. */
  min-height: 44px;
  min-width: 44px;
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease;
}
.site-footer__language button[aria-pressed="true"] {
  color: var(--fg);
}
.site-footer__language button:hover,
.site-footer__language button:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
  outline: 0;
}
.site-footer__language button:focus-visible {
  outline: 1px solid var(--focus-colour, currentColor);
  outline-offset: 0.3rem;
}
.site-footer__lang-sep {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14px;
  line-height: 1;
  color: color-mix(in srgb, var(--fg3) 50%, transparent);
  margin-inline: 4px;
  user-select: none;
}

/* actions rail — mono publication metadata. middle-dot separator at
   50% opacity. follows the language switcher hover rule: color to
   accent, hairline border-bottom on. */
.site-footer__actions {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--fg3);
  margin: 0;
  line-height: 1.3;
}
.site-footer__actions a,
.site-footer__actions button {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding: 0;
  min-height: 30px;
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease;
}
.site-footer__actions a:hover,
.site-footer__actions a:focus-visible,
.site-footer__actions button:hover,
.site-footer__actions button:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
  outline: 0;
}
.site-footer__actions a:focus-visible,
.site-footer__actions button:focus-visible {
  outline: 1px solid var(--focus-colour, currentColor);
  outline-offset: 0.3rem;
}
.site-footer__actions-sep {
  margin-inline: 8px;
  color: color-mix(in srgb, currentColor 50%, transparent);
  user-select: none;
}

/* l8 touch-target validator: dedicated `.cite-btn` rule with
   min-height 44px must appear in compiled css. the cite button
   no longer carries the class but the rule stays so the validator
   sees it. */
.cite-btn { min-height: 44px; }

/* forced-colours / windows high-contrast */
@media (forced-colors: active) {
  .site-footer__language button:focus-visible,
  .site-footer__actions a:focus-visible,
  .site-footer__actions button:focus-visible {
    outline: 2px solid CanvasText;
  }
}

/* quiet settle animation when the language row updates */
@keyframes footerLanguageSettle {
  from { opacity: 0.72; transform: translateY(4px); }
  to   { opacity: 1;    transform: none; }
}
.site-footer__language.language-updated {
  animation: footerLanguageSettle 260ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .site-footer__language.language-updated { animation: none; }
}


/* privacy page */

.page {
  padding-top: clamp(80px, 12vh, 140px);
  padding-bottom: clamp(60px, 8vh, 100px);
}

.page-title {
  font-family: var(--serif);
  font-size: clamp(40px, 10.5vw, 58px);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--fg);
  margin-bottom: clamp(32px, 5vh, 56px);
}

.page-body {
  max-width: 580px;
}

.page-lede {
  font-family: var(--serif);
  font-size: clamp(24px, 6.3vw, 30px);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--fg);
  max-width: 31ch;
  margin-bottom: 1.6em;
}

.page-body p {
  font-family: var(--serif);
  font-size: clamp(17px, 4.6vw, 20px);
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: -0.008em;
  color: var(--fg2);
  max-width: 34ch;
  margin-bottom: 1.4em;
}

@media (min-width: 768px) {
  .page-lede   { font-size: clamp(24px, 2.6vw, 30px); max-width: 50ch; }
  .page-body p { font-size: clamp(18px, 2vw, 21px); line-height: 1.65; max-width: 62ch; }
}

.page-body p:last-child {
  margin-bottom: 0;
}

/* refined editorial underlines under signifier text. quiet by default,
   oxblood on hover/focus, thin and offset so they don't visually weigh
   down large serif type. */
.page-body a {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--bd);
  text-underline-offset: 0.14em;
  border-bottom: none;
  transition: text-decoration-color 0.2s;
}

.page-body a:hover,
.page-body a:focus-visible {
  text-decoration-color: var(--accent-text);
}

.page-meta {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg3);
  letter-spacing: 0.06em;
}

/* technical evidence · file paths, hashes, fingerprints, verification
   resources. mono, slightly tabular, wraps cleanly on narrow viewports.
   does not inherit the editorial old-style figures. */
.technical-list {
  list-style: none;
  margin: 24px 0 32px;
  padding: 0;
  font-family: var(--mono);
  font-size: clamp(13px, 3.4vw, 15px);
  line-height: 1.7;
  letter-spacing: 0.02em;
  color: var(--fg2);
}

.technical-list li {
  padding: 4px 0;
}

.technical-list a,
.code-path {
  font-family: var(--mono);
  font-size: inherit;
  color: var(--fg);
  word-break: break-word;
  overflow-wrap: anywhere;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--bd);
  text-underline-offset: 0.18em;
  border-bottom: none;
  transition: text-decoration-color 0.2s;
}

.technical-list a:hover,
.technical-list a:focus-visible {
  text-decoration-color: var(--accent-text);
}

/* /integrity/releases/ — release register.
   the page is a quiet archival index, not a download surface. two
   groups (current release / archive) each carrying release rows.
   no cards, no boxes — hairline rules between rows do the work.
   vertical rhythm tightened ~25-30% from the first-pass values so
   the page reads as a finished register on mobile rather than a
   sparse list. whitespace remains the hierarchy; it is just no
   longer being used as emptiness. */
/* releases page sections opt out of the generic section { padding +
   border-bottom } rule — three nested <section> elements (release-
   index + two release-groups) were each inheriting 80-160px of
   vertical padding and a hairline border-bottom, producing large
   empty voids and unnecessary horizontal rules between the intro,
   current release, archive, and related records. .release-index
   and .release-group only exist on the releases page so class
   specificity (0,1,0) beats the generic section selector (0,0,1)
   without needing the body[data-page=…] layer wrap. */
.release-index {
  /* intro → current release · compact editorial gap. */
  padding: 0;
  border-bottom: 0;
  margin: clamp(3rem, 9vw, 4.5rem) 0 0;
}

.release-group {
  /* current → archive · slightly larger than the in-card rhythm so
     the two groups read as peer sections, but no full section-block
     void between them. */
  padding: 0;
  border-bottom: 0;
  margin: clamp(3rem, 9vw, 4.5rem) 0 0;
}
.release-group:first-child { margin-top: 0; }

.release-group-label {
  /* shared metadata-label register — matches page record, manifest,
     history across the trust system. title-case mono, very small,
     muted; never competes with the release titles below it.
     border-top + padding-top retired — the rule was floating between
     sections and fragmenting the page. spacing alone separates
     current release from archive. */
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 1rem;
}

/* related-records → archive gap · with the generic section padding
   retired, .related-nav's default 16-24px top margin reads as
   cramped against the archive card above. scope via the unique
   adjacent-sibling on the releases page (.release-index is only
   emitted there) so we beat the shared .related-nav margin on
   specificity (0,2,0 > 0,1,0) without leaving the unlayered tier. */
.release-index + .related-nav {
  margin-top: clamp(4rem, 11vw, 6rem);
}

.release-row {
  /* phase 42 · each release is now an archival object — framed,
     padded, subtly lifted off the page surface so the register
     reads as a release registry of preserved snapshots rather
     than a loose list of rows. matches the verify-card / project-
     card / integrity-record-card object family. internal
     hierarchy (date / title / meta / actions) stays sharp.
     phase 46 · literal 8px → var(--radius-soft) (7px). */
  background: color-mix(in srgb, var(--surface-page) 92%, white 8%);
  border: 1px solid var(--rule);
  border-radius: var(--radius-soft);
  padding: clamp(1.4rem, 4vw, 2rem);
  margin-bottom: clamp(0.85rem, 2vw, 1.25rem);
}
.release-row:last-child { margin-bottom: 0; }

.release-row--current {
  /* current release gets a touch more presence than the archive
     rows — slightly heavier title, marginally more breathing room. */
  padding-bottom: clamp(1.5rem, 4.5vw, 2.2rem);
}

.release-date {
  /* shared metadata-label register again — date sits as the small
     uppercase locator above the human-readable title. */
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg3);
  font-variant-numeric: tabular-nums lining-nums;
  margin: 0 0 0.3rem;
}

.release-title {
  font-family: var(--sans);
  font-style: normal;
  font-weight: 400;
  font-size: clamp(15px, 3.6vw, 17px);
  line-height: 1.35;
  color: var(--fg);
  margin: 0 0 0.25rem;
}
.release-row--current .release-title {
  font-size: clamp(16px, 3.8vw, 18px);
}

.release-meta {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.04em;
  color: var(--fg3);
  margin: 0;
  line-height: 1.5;
}

/* compact action strip — download zip · download TAR.GZ · checksums
   & signature · view release. inline middots between actions, wraps
   cleanly on narrow screens; restrained underline weight (--rule)
   so the row reads as a footnote to the release title above. */
.release-actions {
  margin: 0.7rem 0 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.025em;
  line-height: 1.7;
  color: var(--fg3);
}
.release-action {
  color: var(--fg2);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.release-action:hover,
.release-action:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
/* phase 42 · actions separator now rendered as ::before pseudo on
   each sibling after the first (matches the .site-footer__actions
   and .trust-mark separator pattern). the legacy .release-actions-sep
   <span aria-hidden="true">·</span> elements are no longer emitted
   in current html, but the rule is kept to hide any cached html
   that still includes them. */
.release-actions > * + *::before {
  content: "·";
  display: inline-block;
  margin-inline: 0.42rem;
  opacity: 0.45;
  transform: translateY(-0.02em);
  color: currentColor;
}
.release-actions-sep { display: none; }


@media (max-width: 700px) {
  .release-row--current { padding-bottom: clamp(1.3rem, 5vw, 1.8rem); }
  /* mobile stacks each action on its own line — middots are hidden
     and links sit as a clean four-line list. the wrapping behaviour
     of an inline strip becomes unnecessary, and "checksums &
     signature" is given room to breathe on the smallest screens. */
  .release-actions {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.66rem;
    line-height: 1.5;
  }
  .release-action { align-self: flex-start; }
  .release-actions > * + *::before { display: none; }
}

.fingerprint {
  display: inline-block;
  font-family: var(--mono);
  font-size: clamp(13px, 3.4vw, 15px);
  line-height: 1.8;
  letter-spacing: 0.08em;
  color: var(--fg);
  overflow-wrap: anywhere;
  word-break: break-all;
  user-select: text;
  -webkit-user-select: text;
}

/* designed proof list · three numbered verification artefacts on the
   integrity page. söhne mono for numerals and file paths, signifier for
   descriptions, hairline rule between items. the oxblood accent only on
   the proof numeral. */
.proof-list {
  margin: 24px 0 28px;
  /* real <ol> for reader view extraction; .proof-num spans carry the
     visual numbering, so suppress browser default markers + padding. */
  list-style: none;
  padding: 0;
}

.proof-item {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, auto) minmax(0, 1fr);
  align-items: baseline;
  column-gap: clamp(10px, 2vw, 16px);
  padding: 14px 0;
  border-top: 1px solid var(--bd-soft);
  color: inherit;
}

.proof-list .proof-item:last-of-type {
  border-bottom: 1px solid var(--bd-soft);
}

.proof-num {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums lining-nums;
}

/* only the file path is a link. the description is descriptive prose
   and never underlines. */
.proof-path {
  font-family: var(--mono);
  font-size: clamp(12px, 3vw, 14px);
  letter-spacing: 0.02em;
  color: var(--fg);
  overflow-wrap: anywhere;
  word-break: break-word;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-bottom-color 0.2s;
}

.proof-path:hover,
.proof-path:focus-visible {
  border-bottom-color: var(--accent-text);
}

.proof-desc {
  font-family: var(--serif);
  font-size: clamp(14px, 3.4vw, 16px);
  line-height: 1.5;
  color: var(--fg2);
  text-decoration: none;
  border: 0;
}

@media (max-width: 600px) {
  .proof-item {
    grid-template-columns: 1.8rem minmax(0, 1fr);
    grid-template-rows: auto auto;
    row-gap: 4px;
  }
  .proof-num {
    grid-row: 1 / span 2;
    align-self: start;
    padding-top: 0.3em;
  }
}

/* fingerprint block. signifier label, mono fingerprint code, restrained
   copy button below. button keeps a minimum width so the copied state
   does not shift its placement and never wraps to the same line as the
   fingerprint. */
.fingerprint-block {
  display: grid;
  gap: 12px;
  margin: 24px 0 32px;
}

.fingerprint-label {
  font-family: var(--serif);
  font-size: clamp(19px, 5vw, 24px);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--fg);
  margin: 0;
}

.fingerprint {
  display: block;
  user-select: text;
  -webkit-user-select: text;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* phase 49 · copy fingerprint as an inline editorial action.
   phase 50 · quieter still — smaller, lighter, subordinate to the
   fingerprint itself. opacity 0.72 by default; lifts to 1 on hover/
   focus so the action remains discoverable. js hook (.copy-
   fingerprint[data-copy-target]) and i18n bindings preserved.
   text-transform cancels the parent .integrity-rg-label uppercase
   cascade; opacity here multiplies with the parent 0.78 — combined
   effective opacity ~0.56 at rest, lifting to 0.78 on interaction. */
/* phase 49 · copy fingerprint as an inline editorial action.
   phase 54 · restored text-decoration: underline pattern, explicit
   font-weight + line-height. parent .integrity-rg-label opacity
   moved to color-mix so the button's declared opacity renders
   directly.
   phase 55 · .record-inline-action joined as a shared class so
   copy fingerprint + copy command + any future inline action sit
   in one declarative register. existing .copy-fingerprint and
   .trust-code-copy selectors remain in the combined rule for
   compatibility with the cite.js js bindings. */
.record-inline-action,
.copy-fingerprint,
.trust-code-copy {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  font-family: var(--mono);
  font-size: 0.58rem;
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: 0.055em;
  text-transform: none;
  color: var(--fg2);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--fg2) 42%, transparent);
  text-underline-offset: 0.18em;
  cursor: pointer;
  transition: color 0.2s, text-decoration-color 0.2s;
}
.record-inline-action:hover,
.record-inline-action:focus-visible,
.copy-fingerprint:hover,
.copy-fingerprint:focus-visible,
.trust-code-copy:hover,
.trust-code-copy:focus-visible {
  color: var(--fg);
  text-decoration-color: currentColor;
  outline: 0;
}
.record-inline-action[data-state="copied"],
.copy-fingerprint[data-state="copied"],
.trust-code-copy[data-state="copied"] {
  color: var(--accent-text);
  text-decoration-color: var(--accent-text);
}

/* One-line philosophy sentence on /source/, sitting between the
   intro lede and the download affordance. same body register as
   the rest of the page-body but a touch quieter; reads as a calm
   catalogue note, not an epigraph. no italic — italics here would
   be the only italic on the page and read as decorative rather
   than systemic. */
.source-about {
  margin: 12px 0 0;
  font-family: var(--serif);
  font-size: clamp(15px, 1.5vw, 17px);
  font-weight: 300;
  color: var(--fg2);
  line-height: 1.55;
}

/* quiet helper line under the philosophy sentence: explains the
   .txt mirror convention in one breath so a first-time visitor
   understands why the catalogue links don't open as HTML/CSS/JS.
   mono register, fg3, smaller than .source-about; sits as a
   footnote, not a second epigraph. */
.source-mirror-note {
  margin: 6px 0 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.025em;
  color: var(--fg3);
  line-height: 1.55;
}

/* phase 61 · .source-mirror-intro retired. the source-mirror
   reading rule is now implicit: each link opens the readable
   .txt mirror. the page lede + per-file descriptions carry the
   reader without a companion gloss. */

/* Group-label count register — "pages, 12". same fg3 mono register
   as the label itself; no extra colour. */
.source-group-count {
  color: var(--fg3);
  font-weight: 400;
}

/* quiet "download the source archive" affordance on /source/. sits
   between the page lede and the directory listing. mono, small, calm —
   no button styling. the links inherit the page's underlined-link
   treatment. */
.source-download {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: clamp(10.5px, 2.7vw, 12px);
  line-height: 1.6;
  color: var(--fg2);
}
.source-download span[data-i18n] {
  /* keep the lede on its own conceptual line by allowing the
     browser to break before the first separator on narrow widths;
     the inline phrase wraps as a single sentence followed by a
     calm list of three downloads rather than a fragmented chain. */
  display: inline;
}
.source-download a {
  color: inherit;
  white-space: nowrap;
  text-decoration-color: color-mix(in srgb, var(--fg2) 42%, transparent);
}
.source-download a:hover,
.source-download a:focus-visible {
  color: var(--fg);
  text-decoration-color: currentColor;
}

/* /source/ — editorial provenance registry.
   the source page is a public inspection ledger, not a directory
   dump. each editorial group renders as a section with a quiet
   mono kicker, a single-line serif gloss, and a <dl> body of
   .source-entry rows. each entry is a two-column micro-grid:
   filename left, mono meta right (kind · size · validated date),
   with an optional short SHA-256 below. typography is mono for
   identifiers and metadata so the page reads as a typeset archival
   catalogue. */

/* widen the page body on /source/ to match /verify/. the catalogue
   is the longest editorial run on the site; the default 580px
   prose column reads as an austere ribbon and over-isolates the
   right field. 880px gives the registry room to breathe without
   becoming a sprawl. */
.source-page .page-body {
  max-width: 880px;
}
@media (max-width: 700px) {
  .source-page .page-body {
    max-width: none;
  }
}

/* the about line that follows the page lede on /source/. names the
   page's editorial taxonomy in one calm sentence so the reader has
   a mental map before scrolling into the registry. matched to the
   lede register (serif, 300, fg2) but slightly smaller and below
   max-width so it reads as continuation, not a new paragraph. */
/* phase 61 · .source-intro_about, .source-fingerprint-note,
   .source-mirror-intro and .source-philosophy retired. /source/
   now opens on a single editorial lede plus the download line,
   and the files themselves carry the page. */

.source-registry-wrap {
  margin-block-start: clamp(28px, 4.5vw, 40px);
  display: grid;
  gap: clamp(2.4rem, 5.5vw, 3.6rem);
}
.source-group-gloss {
  margin: 0 0 1.1rem;
  font-family: var(--serif);
  font-size: clamp(14.5px, 1.8vw, 16px);
  font-weight: 300;
  line-height: 1.55;
  color: var(--fg2);
  max-width: 60ch;
}
.source-group {
  margin: 0;
  padding: 0;
}
.source-group-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0 0.85rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--rule);
}
.source-group-title {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
}
.source-group-count {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  color: var(--fg3);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.source-registry {
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}
/* registry row — a three-area micro-grid:
   row 1: filename (left) · mono meta (right)
   row 2: editorial description spanning the full width
   no per-row border. rhythm comes from generous padding so the
   page reads as an authored ledger, not a directory index. a
   single faint hairline appears between entries via the +
   sibling selector at very reduced opacity. */
.source-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: clamp(0.85rem, 3.5vw, 1.5rem);
  row-gap: clamp(0.32rem, 0.9vw, 0.45rem);
  align-items: baseline;
  padding-block: clamp(0.85rem, 1.8vw, 1.05rem);
}
.source-entry + .source-entry {
  border-block-start: 1px solid color-mix(in srgb, var(--bd-soft) 55%, transparent);
}
.source-entry:last-child {
  border-bottom: 0;
}
.source-entry-name {
  margin: 0;
  min-width: 0;
  font-family: var(--mono);
  font-size: clamp(0.78rem, 2.4vw, 0.86rem);
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.01em;
  color: var(--fg);
  word-break: break-all;
  overflow-wrap: anywhere;
  font-variant-ligatures: none;
}
.source-entry-name a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.source-entry-name a:hover,
.source-entry-name a:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.source-entry-name code {
  font-family: inherit;
  font-size: inherit;
  background: transparent;
  padding: 0;
}
/* quiet "raw" link alongside the reader link */
.source-entry-raw {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg3);
  text-decoration: none;
  margin-inline-start: 0.5rem;
  opacity: 0.7;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.source-entry-raw:hover,
.source-entry-raw:focus-visible {
  color: var(--accent-text);
  opacity: 1;
}
.source-entry-meta {
  margin: 0;
  justify-self: end;
  text-align: right;
  font-family: var(--mono);
  font-size: 0.66rem;
  line-height: 1.45;
  letter-spacing: 0.04em;
  color: var(--fg3);
  opacity: 0.82;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-variant-ligatures: none;
  white-space: normal;
}
.source-entry-meta abbr {
  text-decoration: none;
  border: 0;
  cursor: default;
  color: var(--fg3);
}
.source-entry-sep {
  margin: 0 0.18rem;
  opacity: 0.55;
}
.source-entry-size {
  font-variant-numeric: tabular-nums lining-nums;
}
.source-entry-date {
  color: var(--fg3);
}
.source-entry-hash {
  /* the hash is a citation, not a primary label. soft enough to
     recede when the eye is scanning filenames; selectable and
     full-precision via the title attribute on the <samp>. */
  margin: 0.18rem 0 0;
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.045em;
  color: color-mix(in srgb, var(--fg3) 70%, transparent);
  line-height: 1.4;
}
.source-entry-hash abbr {
  text-decoration: none;
  border: 0;
  cursor: default;
  margin-right: 0.35rem;
  opacity: 0.7;
}
.source-entry-hash samp {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

/* editorial description row — the why-this-file-exists line. sits
   below the filename/meta on its own row, spanning the full grid
   width so the prose has room to breathe. serif register tied to
   the page's narrative copy. width capped at 44ch for editorial
   discipline: short measures force prose to be precise and let
   the registry breathe between filenames and their commentary. */
.source-entry-desc {
  grid-column: 1 / -1;
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(13.5px, 1.65vw, 15px);
  font-weight: 300;
  line-height: 1.5;
  color: var(--fg2);
  max-width: 44ch;
}

/* quiet role label placed inline at the head of the description
   for trust-critical files. text only — no badge, no border, no
   colour wash. mono uppercase tracking signals "this is a system
   role, not editorial prose"; the trailing middot lets the prose
   flow without a hard break. */
.source-entry-role {
  display: inline;
  margin-inline-end: 0.45rem;
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
  opacity: 0.82;
  white-space: nowrap;
}
.source-entry-role::after {
  content: " ·";
  margin-inline-start: 0.1rem;
  color: color-mix(in srgb, var(--fg3) 55%, transparent);
}

/* critical-file row. quietly distinguish the few files that carry
   the trust system (signed manifest, detached signature, public
   signing key, canonical identity record, disclosure surface).
   the brief forbids badges; the distinction is a hair more
   contrast on the role kicker. no extra chrome, no left border —
   restraint is the point. the filename already reads as primary
   ink across the registry, so no override there. */
.source-entry[data-critical="true"] .source-entry-role {
  color: var(--fg2);
  opacity: 1;
}

/* phase 61 · .source-philosophy retired — explanatory density
   removed in favour of the catalogue itself. */

/* editions lineage — a quiet bibliographic ledger of signed
   editions, sitting between the registry and the related-records
   nav. inherits the verify-card chrome via the .source-page
   trust-system selector so it reads as a paper sheet. each
   record is a two-column micro-grid: the edition slug on the
   left, an annotation note + a linked artefact list on the right. */
.source-editions {
  /* phase 60a · extra breathing above the lineage so it reads as
     a publication shelf, not a continuation of the registry. */
  margin-block-start: clamp(2.8rem, 7vw, 4.2rem);
  margin-block-end: 0;
  /* phase 60 · same tightening as .source-verification so the two
     archival panels read as siblings, flush with the registry above. */
  padding: clamp(1rem, 3.4vw, 1.45rem);
}
.source-editions-list {
  margin: 0;
  display: grid;
  gap: 0;
  /* phase 60a · drop the top hairline; the panel's own border and
     the .verify-card__header already establish the upper bound.
     bibliographic, not tabular. */
  border-top: 0;
}
.source-edition {
  display: grid;
  grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr);
  column-gap: clamp(1rem, 3vw, 1.6rem);
  align-items: baseline;
  padding-block: 0.75rem;
  border-bottom: 1px solid var(--rule);
}
.source-edition:last-child {
  border-bottom: 0;
}
.source-edition-id {
  margin: 0;
  font-family: var(--mono);
  /* phase 60 · invert hierarchy. edition slug becomes the dominant
     element of the row, not the artefact list. larger size, full
     primary ink colour, slight weight bump. annotation registers
     fall in beneath. */
  font-size: clamp(0.92rem, 2.4vw, 1.02rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.source-edition-id time {
  color: inherit;
}
/* phase 61 · the right column now stacks a serif annotation note
   above a mono artefact list. annotation says "current signed
   release"; the artefacts are canonical links. publication
   history register, not a tabular row. */
.source-edition-meta {
  margin: 0;
  display: grid;
  gap: 0.35rem;
  min-width: 0;
}
.source-edition-note {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(13.5px, 1.7vw, 15px);
  font-weight: 300;
  font-style: italic;
  line-height: 1.45;
  color: var(--fg2);
  max-width: 44ch;
}
.source-edition-artefacts {
  margin: 0;
  font-family: var(--mono);
  /* phase 60 · drop one tier smaller + reduce opacity so the
     canonical artefact links register as tertiary annotation, not
     a competing label row. */
  font-size: 0.62rem;
  line-height: 1.6;
  letter-spacing: 0.05em;
  color: var(--fg3);
  opacity: 0.82;
  word-break: break-word;
  overflow-wrap: anywhere;
  font-variant-ligatures: none;
}
.source-edition-artefacts a {
  color: var(--fg2);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--fg2) 28%, transparent);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.source-edition-artefacts a:hover,
.source-edition-artefacts a:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}
.source-edition-artefacts code {
  font-family: inherit;
  font-size: inherit;
  background: transparent;
  padding: 0;
}

.source-editions {
  /* phase 60a · softer card edges than verify-card defaults so the
     panel reads as an archival insert, not a ui component. */
  border-color: color-mix(in srgb, var(--ink) 5%, transparent);
  background: color-mix(in srgb, var(--surface-archival) 88%, var(--bg) 12%);
  box-shadow: none;
}

/* phase 61 · related-records nav. closes the page quietly. one
   small uppercase mono kicker, three inline links separated by
   middots. no card, no border, no explanatory paragraph —
   cryptographic trust and page-level provenance live on their
   own surfaces. */
.source-related {
  margin-block-start: clamp(2.4rem, 5vw, 3.2rem);
  margin-block-end: clamp(2rem, 4vw, 2.6rem);
  padding: 0;
}
.source-related-title {
  margin: 0 0 0.75rem;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
}
.source-related-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem 1.1rem;
  font-family: var(--serif);
  font-size: clamp(15px, 1.9vw, 17px);
  line-height: 1.4;
}
.source-related-nav a {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 24%, transparent);
  transition: color 0.2s, border-bottom-color 0.2s;
}
.source-related-nav a:hover,
.source-related-nav a:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: 0;
}

@media (max-width: 540px) {
  .source-entry {
    grid-template-columns: 1fr;
    row-gap: 0.35rem;
    padding-block: 0.95rem;
  }
  .source-entry-meta {
    justify-self: start;
    text-align: left;
  }
  .source-entry-desc {
    max-width: none;
  }
  .source-edition {
    grid-template-columns: 1fr;
    row-gap: 0.2rem;
    padding-block: 0.8rem;
  }
}

/* citation drawer. tiny, non-modal, opened directly above the footer
   cite button and left-aligned to it. position is computed by cite.js
   on open; css only owns shape, not placement. closes on escape,
   outside click, or after any action. no focus trap. */
.cite-drawer {
  position: fixed;
  z-index: 160;
  width: min(220px, calc(100vw - 24px));
  border: 1px solid var(--bd);
  background: var(--card);
  padding: 10px 12px;
  display: grid;
  gap: 6px;
}

.cite-drawer[hidden] {
  display: none;
}

.cite-drawer button,
.cite-drawer a {
  appearance: none;
  background: none;
  border: 0;
  padding: 6px 0;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--fg3);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.cite-drawer button:hover,
.cite-drawer a:hover,
.cite-drawer button:focus-visible,
.cite-drawer a:focus-visible {
  color: var(--fg);
}

/* Verify-live disclosure · gives the summary the same hierarchy as the
   page so it doesn't read as a tiny disclosure widget. */
.verify-disclosure {
  margin: clamp(24px, 5vh, 36px) 0 0;
}

.verify-disclosure summary {
  cursor: pointer;
  list-style: none;
  display: grid;
  grid-template-columns: 1.1em minmax(0, 1fr);
  column-gap: 0.45em;
  align-items: baseline;
  padding: 4px 0;
}

.verify-disclosure summary::-webkit-details-marker {
  display: none;
}

.verify-disclosure summary::before {
  content: "+";
  grid-column: 1;
  font-family: var(--mono);
  font-size: 0.72em;
  color: var(--fg3);
  transform: none;
}

.verify-disclosure[open] summary::before {
  content: "−";
  transform: none;
}

/* Two-line title + subtitle stacks in column 2 */
.verify-disclosure summary .verify-title,
.verify-disclosure summary .verify-subtitle {
  grid-column: 2;
}

.verify-title {
  font-family: var(--serif);
  font-size: clamp(18px, 4.8vw, 22px);
  font-weight: 400;
  color: var(--fg);
  display: inline;
}

.verify-subtitle {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--fg3);
}

.page-subtitle {
  font-family: var(--serif);
  font-size: clamp(14px, 1.4vw, 17px);
  font-weight: 400;
  color: var(--fg);
  margin-top: 2.5em;
  margin-bottom: 0.5em;
}

.page-back {
  display: inline-block;
  /* subtle archival navigation marker, but not so compressed that
     the page feels clipped at the bottom. */
  margin-top: clamp(24px, 4vh, 40px);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg3);
  text-decoration: none;
  letter-spacing: 0.04em;
  opacity: 0.84;
  transition: color 0.2s, opacity 0.2s;
}

.page-back:hover,
.page-back:focus-visible {
  color: var(--fg);
  opacity: 1;
}

pre {
  font-family: var(--mono);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding: 1em 1.2em;
  color-scheme: light;
  -webkit-appearance: none;
  background-color: var(--bg2);
  border: 1px solid var(--bd);
  border-radius: 4px;
  font-size: 0.72em;
  line-height: 1.7;
  margin: 0;
  color: var(--fg);
}

code {
  font-family: var(--mono);
}

.code-cmd { color: var(--accent-text); }
.code-str { color: var(--code-string); }
.code-var { color: var(--code-var); font-weight: 500; }

/* progressive disclosure
   the site uses a single .trust-disclosure component for every
   collapsible section (verify chooser previously, integrity verify-
   release-locally now). the trust-disclosure rules above own all
   the chrome — list-style suppression, the right-aligned +/-
   marker, the hairline rhythm. the legacy generic `summary` rules
   that lived here previously leaked a ▸ triangle and a dotted
   border onto the trust-disclosure, making it read red/technical;
   they are intentionally removed. new <details> elsewhere should
   either use .trust-disclosure or declare their own scoped rules. */

/* access modal */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(31, 30, 28, 0);
  backdrop-filter: blur(0);
  -webkit-backdrop-filter: blur(0);
  display: flex;
  align-items: center;
  justify-content: center;
  /* full-screen overlay: pad each side by max(24px, safe-area-inset)
     so the modal content never sits under the notch / home-indicator
     in `viewport-fit=cover` mode. the cite drawer is JS-positioned
     so it does not need this here. */
  padding-top:    max(24px, var(--safe-top));
  padding-right:  max(24px, var(--safe-right));
  padding-bottom: max(24px, var(--safe-bottom));
  padding-left:   max(24px, var(--safe-left));
  pointer-events: none;
  /* backdrop fades on its own slower curve so the scrim "settles"
     after the paper card has arrived. matches the brief's "paper
     emerging from fog" target. */
  transition:
    background           260ms cubic-bezier(.22,.61,.36,1),
    backdrop-filter      260ms cubic-bezier(.22,.61,.36,1),
    -webkit-backdrop-filter 260ms cubic-bezier(.22,.61,.36,1);
}

.modal-overlay.active {
  /* darker warm scrim so the page recedes noticeably under the modal.
     --overlay-scrim is the single source of truth (both modals consume
     it); bumped to ~0.46 opacity so the publication card reads as a
     lifted artefact rather than floating on a translucent veil. blur
     widened to 6 px to reinforce the fog effect under the scrim. */
  background: var(--overlay-scrim);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: auto;
}

.modal {
  /* the panel sits on paper (the artefact in front of the scrim).
     raised-high paper — slightly lighter than --surface-card so an
     overlay opened above either page surface still reads as a
     lifted sheet, not as a flat plate.
     phase 41 · 2px outer radius matches the radius scope (cards,
     trust mark, verify-card). padding tightened ~15% so the panel
     reads as a denser editorial insert rather than a generously
     spaced ui modal. phase 46 · overlay panels promote to
     var(--radius-panel) (10px) — slightly more present than the
     card radius so the modal reads as a lifted panel, not a card.
     phase 62 · padding tightened a further ~12% so the project
     modal sits as a private studio note rather than a generously
     spaced ui panel. less empty lower space. */
  background: var(--paper-raised-high);
  border: 1px solid var(--rule);
  border-radius: var(--radius-panel);
  box-shadow: none;
  padding: clamp(22px, 3.4vw, 34px);
  max-width: 440px;
  width: 100%;
  position: relative;
  opacity: 0;
  transform: translateY(7px);
  /* paper emerging from fog: opacity rises faster than transform so
     the card materialises before it finishes settling. easing chosen
     to feel decelerating without spring overshoot. */
  transition:
    opacity   150ms cubic-bezier(.22,.61,.36,1),
    transform 200ms cubic-bezier(.22,.61,.36,1);
}

.modal-overlay.active .modal {
  opacity: 1;
  transform: translateY(0);
}

/* editorial modal · no accent rail. the card carries no alert state. */
.modal::before {
  content: none;
  display: none;
}

.modal-label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fg3);
  /* phase 62 · tighter trailing gap so the panel reads as a
     private studio note. */
  margin-bottom: 16px;
}

.modal-text {
  font-family: var(--serif);
  font-size: clamp(17px, 4.6vw, 21px);
  font-weight: 400;
  line-height: 1.5;
  color: var(--fg2);
  /* phase 41 · hairline rule below the body text anchors the cta
     visually so it reads as the action of the panel, not a quiet
     trailing link.
     phase 62 · spacing tightened ~15% so the cta reads as the
     immediate next gesture, not a separated block. */
  margin-bottom: clamp(16px, 3vw, 22px);
  padding-bottom: clamp(16px, 3vw, 22px);
  border-bottom: 1px solid var(--rule);
}

/* phase 62 · the project modal cta is no longer a directional
   "→ trent@trentpower.fr" link. it reads as a two-line studio
   note: a quiet mono label above ("access by request"), the
   email on the second line. less startup, more archival. */
.modal-cta {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--accent-text);
  text-decoration: none;
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 6px 0;
  border-bottom: 1px solid var(--bd);
  transition: border-color 0.2s, color 0.2s;
}

.modal-cta:hover,
.modal-cta:focus-visible {
  border-color: var(--accent-text);
  outline: 0;
}

.modal-cta-label {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fg3);
}

/* visible email below the label — keeps the action self-evident
   on every locale (the email is the same in all five). */
.modal-cta-email {
  font-family: var(--mono);
  overflow-wrap: anywhere;
  word-break: normal;
}

/* legacy text close , no longer rendered; the project modal now uses
   the top-right × pattern shared with the cite overlay. kept as a
   no-op so any cached html still styles cleanly if it appears. */
.modal-close {
  display: block;
  margin-top: 28px;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--fg3);
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px 4px;
  transition: color 0.2s;
}

.modal-close:hover,
.modal-close:focus-visible {
  color: var(--fg);
}

/* top-right × close , single visible close affordance for the project
   modal. mirrors .cite-modal-close-x so the modal family is consistent.
   phase 41 · colour bumped fg3 → fg2 (same warm grey, ~6% darker) so
   the close glyph reads as deliberate authoritative chrome rather than
   a casual tertiary marker. */
.modal-close-x {
  position: absolute;
  top: 0.5rem;
  right: 0.7rem;
  width: 1.8rem;
  height: 1.8rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--sans);
  font-size: 1.3rem;
  line-height: 1;
  color: var(--fg2);
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 0;
  transition: color 0.2s;
  z-index: 1;
}
.modal-close-x:hover,
.modal-close-x:focus-visible {
  color: var(--accent-text);
  outline: 0;
}

/* cite overlay , page record
   same modal family as the view project modal; wider card to fit a
   metadata table and an actions strip. lazily injected by cite.js. */

/* cite modal · publication record card. five elements top-down:
   close × (mobile only) · header (eyebrow + title + lede) ·
   meta <dl> · primary actions (verify · view source) · secondary
   actions (copy citation · print). the card is paper lifted by
   colour, not by chrome — no shadow, no border. */
.modal.cite-modal {
  /* shares the .modal family treatment with the homepage view-project
     modal: same background, same 1 px rule border, same panel radius,
     same motion (inherited from .modal). only the cite-specific layout
     overrides live here — text alignment, padding, max-width, internal
     gap. consistency across both modals is the contract. */
  text-align: left;
  padding: clamp(22px, 3.6vw, 32px);
  padding-right: clamp(24px, 4vw, 36px);
  max-width: 26rem;
  width: min(100%, 26rem);
  max-height: 92vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 2.4vw, 20px);
}

/* close × — mobile-only affordance; desktop relies on click-outside +
   escape (the brief's preference). mobile placement is a tiny dot in
   the top-right; opacity stays low until hovered / focused so the
   card surface reads as paper, not as a control panel. */
.cite-modal-close-x {
  position: absolute;
  top: 0.55rem;
  right: 0.6rem;
  width: 1.6rem;
  height: 1.6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  font: 15px/1 var(--mono);
  color: color-mix(in srgb, var(--fg3) 55%, transparent);
  cursor: pointer;
  padding: 0;
  transition: color 180ms ease, opacity 180ms ease;
}
.cite-modal-close-x:hover,
.cite-modal-close-x:focus-visible {
  color: var(--fg2);
  outline: 0;
}
@media (min-width: 700px) {
  /* desktop: the visible × is retired. escape and click-outside
     both close the overlay (wired by app-enhance's TP_OVERLAY).
     keeping it on tap-only surfaces where there is no escape key
     and click-outside is harder with one thumb. */
  .cite-modal-close-x { display: none; }
}

.cite-modal-header {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
}

/* eyebrow · the small-caps publication mark above the title. wider
   tracking, lighter opacity. quieter than the surrounding type. */
.cite-modal-kicker {
  font: 500 9px/1.1 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--fg3) 65%, transparent);
  margin: 0;
}

/* title · serif, slightly smaller than before so the modal does not
   feel like a hero panel. balance hint keeps the mobile wrap clean. */
.cite-modal-title {
  font: 300 clamp(20px, 3.8vw, 24px)/1.22 var(--serif);
  letter-spacing: -0.015em;
  color: var(--fg);
  margin: 0;
  text-wrap: balance;
}

/* lede · softer contrast, one-sentence descriptor, narrower measure. */
.cite-modal-lede {
  font: 300 13.5px/1.5 var(--serif);
  color: var(--fg2);
  margin: 0;
  max-width: 32ch;
}

/* meta · single quiet row reading as an archival record. mono labels
   are no longer uppercase-shouting; the type carries the system
   register without raising its voice. */
.cite-modal-meta {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font: 500 10.5px/1.45 var(--mono);
  letter-spacing: 0.04em;
  color: var(--fg3);
  font-variant-numeric: tabular-nums lining-nums;
}
.cite-modal-meta-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
}
.cite-modal-meta-row dt {
  color: color-mix(in srgb, var(--fg3) 75%, transparent);
  margin: 0;
}
.cite-modal-meta-row dd {
  margin: 0;
  color: var(--fg2);
}

/* actions · two tiers with a subtle hierarchy. primary (verify · view
   source) reads as the inspectable record; secondary (copy citation ·
   print) reads as utility. left-aligned stack; no oversized click
   target; restrained hover. */
.cite-modal-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: clamp(2px, 1vw, 8px) 0 0;
  padding: 0;
}
.cite-modal-actions-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
}
.cite-modal-actions-row--secondary {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid color-mix(in srgb, var(--rule) 35%, transparent);
}
.cite-modal-action {
  appearance: none;
  -webkit-appearance: none;
  font: 400 14px/1.4 var(--serif);
  color: var(--fg);
  background: transparent;
  border: 0;
  text-align: left;
  text-decoration: none;
  padding: 8px 0;
  cursor: pointer;
  border-bottom: 1px solid transparent;
  align-self: flex-start;
  transition: color 180ms ease, border-color 180ms ease;
}
.cite-modal-action--primary {
  color: var(--fg);
  font-weight: 400;
}
.cite-modal-action--secondary {
  font: 400 13px/1.4 var(--serif);
  color: var(--fg2);
}
.cite-modal-action:hover,
.cite-modal-action:focus-visible {
  color: var(--accent);
  border-bottom-color: color-mix(in srgb, var(--accent) 55%, transparent);
  outline: 0;
}
.cite-modal-action[data-state="copied"] {
  color: var(--accent);
}

/* visually-hidden · aria-live region for copy announcements. */
.cite-overlay .visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
  padding: 0;
  margin: -1px;
}

/* reduced motion */

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

  .hero-name,
  .hero-statement,
  .hero-body,
  .trust-mark,
  .principle,
  .trajectory-item,
  .project-card,
  .js .principle,
  .js .trajectory-item,
  .js .project-card {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* print */

@media print {
  .nav { display: none; }
  section { padding: 40px 0; }
  .hero { min-height: auto; }
}

}
@layer components {
/* Security-page architecture diagram. the <picture> swaps to the
   mobile portrait variant under 600px. on desktop the figure widens
   beyond the prose column but stays visually centred in the viewport. */
.architecture-figure {
  /* hug the "1. architecture" summary above; rely on the disclosure's
     own bottom rhythm for the gap below. */
  margin: 0 auto clamp(20px, 3vw, 32px);
}

.architecture-picture {
  display: block;
}

.page-diagram {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
  margin: 0 auto;
}

@media (min-width: 900px) {
  .architecture-figure {
    width: min(92vw, 1120px);
    margin-left: auto;
    margin-right: auto;
    transform: none;
  }
}

/* /security/ as a posture document — open editorial sections with
   real h2 headings, soft hairline rules between them, no accordion
   chrome. phase 62 · section rhythm and sub-section spacing widened
   ~7% and the inter-section rule softened to var(--rule) so spacing,
   not lines, carries the hierarchy on this denser page. */
.security-section {
  margin: clamp(44px, 7vw, 72px) 0;
  padding: clamp(32px, 5vw, 42px) 0 0;
  border-top: 1px solid var(--rule);
  border-bottom: 0;
  min-height: 0;
}
.security-section:first-of-type {
  margin-top: clamp(32px, 4.5vw, 44px);
  padding-top: 0;
  border-top: 0;
}
.security-section-heading {
  font-family: var(--serif);
  font-size: clamp(22px, 4.4vw, 30px);
  font-weight: 400;
  line-height: 1.2;
  color: var(--fg);
  margin: 0 0 clamp(16px, 2.2vw, 24px);
  letter-spacing: -0.005em;
}
.security-section .security-subheading {
  /* quieter subhead — won't compete with the section h2.
     phase 62 · breathing above + below loosened so the subsection
     groupings read as composed rather than compressed. */
  font-family: var(--mono);
  font-size: 0.76rem;
  font-weight: 400;
  letter-spacing: 0.025em;
  color: var(--fg2);
  margin: clamp(1.7rem, 3vw, 2.1rem) 0 clamp(0.55rem, 1vw, 0.7rem);
  text-transform: none;
}
.security-section .security-subheading strong { font-weight: 400; }
/* phase 44 · semantic subsections inside a .security-section.
   threat-model and controls now use real <section> + <h3>
   groupings instead of <p><strong> subheadings. neutralise the
   global `section { padding ; border-bottom }` rule so these
   inner blocks remain flat editorial passages, not nested
   bordered panels. spacing comes from .security-subheading and
   the list rhythm above. */
.security-section .security-subsection {
  padding: 0;
  border-bottom: 0;
  min-height: 0;
  margin: 0;
}
.security-architecture-note {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  color: var(--fg2);
  margin: clamp(18px, 2.5vw, 24px) 0 0;
  line-height: 1.55;
}

/* phase 42 · the bespoke .architecture-card / .architecture-card-dl /
   .architecture-row component family was retired in favour of the
   shared `.verify-card` + `.record-grid` micro-grid (now scoped to
   `.security-page` as well). the security architecture section reuses
   the canonical archival-object pattern, so the visual register is
   one family across verify / sw-reset / security. */

/* Security-page editorial lists · signifier prose with proper specificity
   (no !important needed; .page-body scope wins the cascade). */
.page-body ul.i18n-list {
  font-family: var(--serif);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
  list-style: disc;
  padding-left: 1.25rem;
  margin: 0.5rem 0 1rem;
}
.page-body ul.i18n-list li {
  font-family: var(--serif);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
  margin-bottom: 0.2rem;
}
.page-body details p[data-i18n$="_heading"],
.page-body details strong[data-i18n$="_heading"] {
  font-family: var(--serif);
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--fg2);
  margin: 1rem 0 0.2rem;
}

/* bullets inside .security-section share the same calm refinement
   the prior .security-disclosure block had — list-style:disc, smaller
   marker, refined serif body. phase 62 · inter-item spacing widened
   so the threat-model + controls lists read as composed cadence,
   not stacked rows. */
.page-body .security-section ul.i18n-list {
  padding-left: 1.05rem;
  margin: clamp(0.75rem, 1.5vw, 1rem) 0 clamp(1.35rem, 2.4vw, 1.6rem);
  list-style: disc;
}
.page-body .security-section ul.i18n-list li {
  font-family: var(--serif);
  font-size: clamp(15px, 4vw, 17px);
  line-height: 1.65;
  margin-bottom: clamp(0.5rem, 1vw, 0.65rem);
  color: var(--fg);
}
.page-body .security-section li::marker {
  color: var(--fg3);
  font-size: 0.7em;
}

/* legacy ".trajectory-chapter ul" reset retired — the new trajectory
   chronology has no nested lists. .trajectory-item carries its own
   list-style reset downstream. */

/* ─── language switch · viewport-anchor stabilisation ──
   while a language switch is in flight, suppress all transitions
   and animations so the page does not visibly jump. the
   .is-language-switching class is added by app.js around the
   text-replacement step and removed once layout settles
   (raf × 2). */
}
@layer overrides {
html.is-language-switching {
  scroll-behavior: auto !important;
}

html.is-language-switching .principle,
html.is-language-switching .trajectory-item,
html.is-language-switching .project-card,
html.is-language-switching .hero-name,
html.is-language-switching .hero-statement,
html.is-language-switching .hero-body,
html.is-language-switching .trust-mark {
  transition: none !important;
  animation: none !important;
}

html.is-language-switching .nav {
  transition: none !important;
}

/* ─── source-reader: controlled reader-box width ───────────────
   id hook (#source-view-root) caps the source-reader column at a book-page
   proportion so the code panel never stretches awkwardly on very wide
   viewports. chrome (intro, actions, document map, footer) and the code
   layout share the same max-width so nothing drifts off-axis. lives here
   in @layer overrides because the id selector belongs to the overrides
   register per the css-architecture brief (l4). */
.source-reader-page #source-view-root {
  max-width: 72rem;
  margin-inline: auto;
}

/* ─── anchor landing offsets · home section ids ────────────────
   native anchor navigation lands the section heading below the
   sticky header with elegant breathing room. bare id selectors
   here in @layer overrides per the brief; specificity (1,0,0)
   is fine because the rule is intentionally narrow (four ids,
   one property, no other rule competes for these targets).

   no js scrollintoview, no scrollto, no preventdefault on
   anchor clicks — inithomenav() in app.js closes the menu but
   lets the browser perform native anchor navigation. css owns
   the offset register. */
@media (max-width: 760px) {
  #approach,
  #trajectory,
  #projects,
  #contact {
    scroll-margin-top: var(--anchor-offset-mobile);
  }
}
@media (min-width: 761px) {
  #approach,
  #trajectory,
  #projects,
  #contact {
    scroll-margin-top: var(--anchor-offset-desktop);
  }
}

}
@layer components {
/* ── /source/view/ — source code reader ────────────────────────
   archival inspection surface. warm paper background, mono type,
   muted line numbers. intentionally not ide-like. */

/* source code sizing — single source of truth.
   token classes change colour only; no size, weight, or spacing overrides.
   spacing rhythm — three tokens, used everywhere in this surface so that
   gaps between labels, annotation lines, and code groups read as one
   mathematical system. browser defaults are forbidden.
   the section-divider rhythm uses --source-rule-rise above the rule
   and --source-rule-fall below, so every annotation block (author
   comment, structural tag, generated) breathes identically. */
.source-reader-page {
  --source-code-size: clamp(0.78rem, 2.2vw, 0.92rem);
  --source-code-line-height: 1.55;
  --source-gap-xs: 0.45rem;
  --source-gap-sm: 0.85rem;
  --source-gap-md: 1.4rem;
  --source-rule-rise: 2.75rem;
  --source-rule-fall: 1.25rem;
}
/* the matching reader-box max-width rule lives at the top of this file in
   the existing top-level @layer overrides block (search "#source-view-root").
   ID-keyed selectors must live in @layer overrides per l4 of the css-
   architecture validator. */

/* reader intro: eyebrow + filename + description + meta.
   spacing on this surface uses --source-gap-xs / sm / md only — no
   ad-hoc rem values, no browser defaults. that is the rhythm contract. */
.source-reader-page .reader-intro {
  margin-block-end: var(--source-gap-md);
}
.source-reader-page .reader-intro .eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 var(--source-gap-xs) 0;
}
.source-reader-page .reader-intro h1 {
  font-family: var(--mono);
  font-size: clamp(1rem, 2.5vw, 1.25rem);
  font-weight: 400;
  color: var(--fg);
  margin: 0 0 var(--source-gap-xs) 0;
  line-height: 1.3;
}
.source-reader-page .reader-intro h1 code {
  font-family: inherit;
}
.source-reader-page .reader-description {
  font-family: var(--serif);
  font-size: clamp(0.9rem, 2.2vw, 1rem);
  color: var(--fg2);
  margin: 0 0 var(--source-gap-xs) 0;
  line-height: 1.6;
}
.source-reader-page .reader-meta {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--fg3);
  margin: var(--source-gap-xs) 0 0;
}
.source-reader-page .reader-meta abbr {
  text-decoration: none;
  cursor: default;
}
.source-reader-page .reader-meta-date {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--fg3);
  margin: var(--source-gap-xs) 0 0;
}
.source-reader-page .reader-provenance {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--fg3);
  margin: var(--source-gap-sm) 0 0;
  letter-spacing: 0.01em;
}
.reader-provenance-label {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.6rem;
  opacity: 0.75;
  margin-inline-end: 0.35em;
}
.reader-provenance-link {
  color: var(--fg3);
  text-decoration: none;
}
.reader-provenance-link:hover,
.reader-provenance-link:focus-visible {
  color: var(--fg2);
}

/* reader-intent — one quiet italic-serif conceptual line under the file
   description. interpretive content: hidden in raw mode (see body[data-
   source-mode="raw"] .reader-intent rule above). */
.source-reader-page .reader-intent {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(0.85rem, 2vw, 0.95rem);
  color: var(--fg3);
  margin: var(--source-gap-xs) 0 0;
  line-height: 1.5;
  opacity: 0.85;
}

/* source integrity block — small monospace ledger anchoring the reader
   to a verifiable archival artefact. canonical · edition · sha-256 short
   · signed release. quiet two-column key/value layout, no fills, no rules. */
.source-reader-page .reader-integrity {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: var(--source-gap-sm);
  row-gap: var(--source-gap-xs);
  margin: var(--source-gap-sm) 0 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  color: var(--fg3);
  letter-spacing: 0.04em;
  line-height: 1.4;
}
.source-reader-page .reader-integrity-key {
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  opacity: 0.7;
}
.source-reader-page .reader-integrity-val {
  margin: 0;
  color: var(--fg2);
  word-break: break-all;
}
.reader-integrity-val--mono {
  font-variant-numeric: tabular-nums lining-nums;
}

/* action row: canonical · verify · raw · copy code · source / annotated · wrap lines */
.source-reader-page .reader-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0;
  margin-block-end: var(--source-gap-md);
}
.reader-action {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--fg2);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.15s ease;
}
.reader-action:hover,
.reader-action:focus-visible {
  color: var(--accent);
}
.reader-action + .reader-action::before,
.reader-action + .reader-view-toggle::before,
.reader-view-toggle + .reader-action::before {
  content: ' · ';
  color: var(--fg3);
  pointer-events: none;
}
.reader-action--wrap[aria-pressed="true"] {
  color: var(--accent);
}

/* code shell — horizontal scroll wrapper, never overflows the page.
   on mobile (≤700px) the wrap toggle defaults to on (set by source-view.js),
   so the shell width simply fills the viewport with no inner scroll. on
   desktop, the shell sits inside the 72rem reader-box and any line that
   exceeds the column scrolls within this wrapper, never the page. */
.code-shell {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid color-mix(in srgb, var(--bd-soft) 55%, transparent);
  border-radius: 1px;
  background: var(--surface-archival);
}
.code-reader {
  margin: 0;
  padding-block: clamp(1.15rem, 3vw, 1.6rem);
  /* on mobile the code panel respects the iphone home-bar / notch insets so
     the reader never feels optically pressed against the viewport edge. */
  padding-inline: max(clamp(0.875rem, 3vw, 1.25rem), env(safe-area-inset-left));
  font-family: var(--mono);
  font-size: var(--source-code-size);
  line-height: var(--source-code-line-height);
  background: transparent;
  overflow-x: visible;
}
/* hard size lock — every character in the code panel shares identical metrics.
   token classes may change colour only; no size, weight, letter-spacing, or
   text-transform overrides are permitted. */
.code-reader,
.code-reader code,
.code-reader span,
.code-line,
.line-code,
.line-code * {
  font-family: var(--mono);
  font-size: var(--source-code-size);
  line-height: var(--source-code-line-height);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
.code-reader code {
  display: block;
}
/* line layout: 4ch number column + flexible code column.
   align-items: start keeps line number at top of wrapped lines. */
.code-line {
  display: grid;
  grid-template-columns: 4ch minmax(0, 1fr);
  column-gap: 0.8rem;
  align-items: start;
}
/* line-number is an <a> tag — reset link styles, registration-mark quiet */
a.line-number {
  position: relative;
  display: block;
  text-align: right;
  text-decoration: none;
  color: var(--fg3);
  user-select: none;
  -webkit-user-select: none;
  opacity: 0.55;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums lining-nums;
}
/* §/number crossfade on line hover */
.ln-num,
.ln-sym {
  display: block;
  text-align: right;
  transition: opacity 0.12s;
}
.ln-sym {
  position: absolute;
  inset: 0;
  opacity: 0;
  color: color-mix(in srgb, var(--accent) 55%, var(--fg3));
  font-style: normal;
}
.code-line:hover a.line-number {
  opacity: 0.85;
}
.code-line:hover .ln-num {
  opacity: 0;
}
.code-line:hover .ln-sym {
  opacity: 1;
}
/* wrap state — default off: preserve original line integrity, horizontal scroll allowed.
   when on: wrap within the code column; continuation indents under code, not number. */
.code-reader:not(.is-wrapped) .line-code {
  white-space: pre;
}
.code-reader.is-wrapped .line-code {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.line-code {
  color: var(--fg);
}

/* focus line — warm parchment register, not editor blue.
   the active highlight is interpretive (it is a citation overlay, not
   source content), so it fades after 2s (controlled by source-view.js)
   and collapses to a persistent gutter marker: a 2px paper-warm rule
   along the left edge of each cited line. the citation is still
   locatable without the page shouting. */
.code-line--range-active {
  background: color-mix(in srgb, var(--accent) 8%, color-mix(in srgb, var(--paper) 60%, transparent));
  outline: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
  outline-offset: 1px;
  border-radius: 2px;
  min-width: 100%;
  transition: background-color var(--transition-quiet), outline-color var(--transition-quiet);
}
.code-line--range-marked {
  position: relative;
}
.code-line--range-marked::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: -0.4rem;
  width: 2px;
  background: color-mix(in srgb, var(--accent) 60%, var(--fg3));
  opacity: 0.7;
  pointer-events: none;
}
/* single-line :target retains a subtler version of the active highlight
   so direct hits still feel anchored. js adds .code-line--range-active
   for fade behaviour; this rule covers the cold-load case where only
   the url hash is in play. */
.code-line:target {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  border-radius: 2px;
  outline: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
  outline-offset: 1px;
  min-width: 100%;
}

/* in-source links — when the source viewer detects a routed url inside
   the rendered code (href="/privacy/", https://trentpower.fr/styles.css,
   etc.) it splits the matching text node and inserts an anchor pointing
   at the corresponding source mirror in this same reader. the visual
   register is intentionally quiet: dotted underline, current colour,
   no token bracket. the goal is "this can be opened", not "this is a
   hyperlink". focus state lifts to the accent for one frame so keyboard
   readers can locate the active target. */
a.source-link,
a.source-link:visited {
  color: inherit;
  text-decoration: underline dotted color-mix(in srgb, var(--accent) 35%, var(--bd-soft));
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
a.source-link:hover {
  text-decoration-color: var(--accent);
}
a.source-link:focus-visible {
  outline: 1px solid color-mix(in srgb, var(--accent) 60%, transparent);
  outline-offset: 2px;
  border-radius: 1px;
}

/* selection toolbar — quiet floating control strip that appears only
   when the reader has at least one line selected. lower-right on
   desktop; sticky-bottom on mobile with the iOS home-indicator inset
   honoured. paper background, hairline border, mono labels. never
   glassy, never a code-host ui. */
.source-toolbar {
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-4);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  background: var(--paper);
  border: 1px solid var(--bd-soft);
  border-radius: 12px;
  box-shadow: 0 6px 16px rgba(31, 30, 28, 0.10);
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.2;
  color: var(--fg);
}
.source-toolbar[hidden] {
  display: none;
}
.source-toolbar__count {
  padding-inline: 0.35rem 0.55rem;
  color: var(--fg2);
  letter-spacing: 0.02em;
}
.source-toolbar__btn {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid var(--bd-soft);
  border-radius: 8px;
  padding: 0.32rem 0.7rem;
  font: inherit;
  color: inherit;
  cursor: pointer;
  min-height: 32px;
}
.source-toolbar__btn:hover {
  border-color: color-mix(in srgb, var(--accent) 30%, var(--bd-soft));
}
.source-toolbar__btn:focus-visible {
  outline: 1px solid color-mix(in srgb, var(--accent) 60%, transparent);
  outline-offset: 2px;
}
@media (max-width: 700px) {
  .source-toolbar {
    left: var(--sp-3);
    right: var(--sp-3);
    bottom: calc(env(safe-area-inset-bottom, 0px) + var(--sp-3));
    justify-content: space-between;
  }
  .source-toolbar__btn {
    min-height: 44px;
    padding: 0.4rem 0.8rem;
  }
}

/* visually-hidden aria-live region for selection announcements. the
   toolbar count already carries aria-live="polite" for the running
   selection size; this one is for transient confirmations ("copied 3
   lines", "link copied"), kept off-screen so the layout stays calm. */
.source-announcer {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  border: 0;
  white-space: nowrap;
}

/* section divider — single unified rhythm for every annotation block
   (author comments, structural tags, generated markers). one contract:
       margin-block: var(--source-rule-rise) var(--source-rule-fall)
   so 2.75rem rises above the rule, 1.25rem falls below the label, no
   matter where the marker came from. author / structure / tier variants
   adjust only opacity and letter-spacing — never the rhythm itself. */
.section-divider,
.code-reader .section-divider {
  display: block;
  margin-block: var(--source-rule-rise) var(--source-rule-fall);
  padding-block: var(--source-gap-sm) 0;
  padding-inline: 0;
  border-top: 1px solid color-mix(in srgb, var(--bd-soft) 22%, transparent);
  font-family: var(--mono);
  font-size: 0.68rem;
  line-height: 1.3;
  font-weight: 400;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--fg3);
  user-select: none;
}
.section-divider:first-child {
  margin-block-start: 0;
  border-top: none;
  padding-block-start: 0;
}
/* contrast lifted ~7–10% so labels sit calmly visible rather than
   washed out, while the author/structure differentiation is
   preserved. paint colour stays the same fg3 token; only opacity
   shifts. */
.section-divider--author    { opacity: 0.92; }
.section-divider--structure { opacity: 0.78; }

/* density tiers — three editorial weights, sharing the same rhythm.
   tier-1 (major structural anchors: head / main / footer / body / header
   / structured): wider letter-spacing and a touch more font size — reads
   as a chapter-marker register.
   tier-2 (architectural notes): the baseline section-marker register.
   tier-3 lives on the gloss span (italic serif, quieter still) — the
   inline semantic note that follows the marker.
   no tier changes the vertical rhythm: the rise/fall contract is one
   for all three so the system never gains a second spacing register.
   the .code-reader prefix lifts the tier selector above the hard size
   lock on .code-reader span so the tier font-size actually applies. */
.code-reader .section-divider--tier-1 {
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  font-weight: 500;
}
.code-reader .section-divider--tier-2 {
  font-size: 0.68rem;
  letter-spacing: 0.10em;
}

.section-divider-label {
  /* the label itself — inherits all type metrics from the divider.
     content provenance: this text is sourced from the file itself
     (an html comment or a structural tag). it is the canonical layer. */
  display: inline;
}

/* ── collapsible source-section disclosure ─────────────────────────
   each generated annotated section is a real <button> followed by a
   summary span (hidden while expanded) and a body span that wraps the
   code-lines. css here handles the visual treatment; html semantics
   (aria-expanded, aria-controls, hidden) come from the renderer. */

/* button reset — keep the disclosure visually identical to the inert
   .section-divider register; the button affordance lives entirely in
   the chevron + state, never in a "button-shaped" outline. */
.source-marker-toggle {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin-block: var(--source-rule-rise) var(--source-rule-fall);
  width: 100%;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}
.source-marker-toggle:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  outline-offset: 2px;
  border-radius: 1px;
}
/* the chevron — restrained ▾ glyph that rotates -90° to ▸ when the
   section is collapsed. keep the rotation under 120ms (brief) and
   suppress it entirely for users who have asked for reduced motion. */
.source-marker-chevron {
  display: inline-block;
  margin-inline-end: 0.45em;
  font-size: 0.82em;
  opacity: 0.72;
  transform-origin: center;
  transition: transform 0.12s ease;
}
.source-marker-toggle[aria-expanded="false"] .source-marker-chevron {
  transform: rotate(-90deg);
}
@media (prefers-reduced-motion: reduce) {
  .source-marker-chevron { transition: none; }
}

/* the body span wrapping a section's code-lines. renders as a flow
   region (display: block) inside the <pre><code> stream; the hidden
   attribute folds it. in raw mode we override hidden so source content
   is never invisible regardless of the annotated-mode collapse state. */
.source-section-body {
  display: block;
}
body[data-source-mode="raw"] .source-section-body[hidden] {
  display: block;
}

/* Collapsed-state summary — quiet mono line shown only when the
   section above is folded. the renderer pre-fills its text with the
   line range or count. */
.source-section-summary {
  display: block;
  margin-block: calc(var(--source-rule-fall) * -0.5) var(--source-rule-fall);
  padding-inline-start: 1.5em;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--fg3);
  opacity: 0.72;
}
.source-section-summary[hidden] {
  display: none;
}

/* in raw mode and on oversize files: the disclosure controls are
   entirely suppressed. source content (the body) remains visible via
   the override above. */
body[data-source-mode="raw"] .source-marker-toggle,
body[data-source-mode="raw"] .source-section-summary,
body[data-source-large="true"] .reader-view-toggle {
  display: none;
}

/* hover lifts opacity on both the inert dividers and the disclosure
   buttons, so the affordance reads consistently. */
.source-marker-toggle:hover,
.source-marker-toggle:focus-visible {
  opacity: 1;
}
.source-marker-toggle:hover .source-marker-chevron {
  opacity: 1;
}
/* annotation gloss — interpretive layer (tier-3, the editorial micro line).
   provenance contract: this text is generated by the reader, not authored
   in the source file. the em-dash mark and italic-serif treatment make
   the boundary visible to the reader: source-derived labels are mono
   uppercase, generated glosses are serif italic with a leading dash, so
   the eye distinguishes canonical from interpretive at a glance.
   hidden by default; revealed only under body[data-source-mode="annotated"]. */
.section-divider-gloss,
.code-reader .section-divider .section-divider-gloss,
.code-reader code .section-divider .section-divider-gloss {
  display: none;
  margin-inline-start: var(--source-gap-sm);
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg3);
  opacity: 0.82;
  font-size: 0.85rem;
  line-height: 1.45;
}
.section-divider-gloss-mark,
.code-reader .section-divider .section-divider-gloss-mark,
.code-reader code .section-divider .section-divider-gloss-mark {
  /* the em-dash provenance mark — typographic signal that what follows
     is editorial annotation, not source content. */
  opacity: 0.55;
  font-style: normal;
  margin-inline-end: 0.05em;
}
body[data-source-mode="annotated"] .section-divider-gloss,
body[data-source-mode="annotated"] .code-reader .section-divider .section-divider-gloss,
body[data-source-mode="annotated"] .code-reader code .section-divider .section-divider-gloss {
  display: inline;
}
body[data-source-mode="annotated"] .section-divider[data-collapsed="true"] .section-divider-gloss {
  display: none;
}
@media (max-width: 36rem) {
  body[data-source-mode="annotated"] .section-divider-gloss,
  body[data-source-mode="annotated"] .code-reader .section-divider .section-divider-gloss,
  body[data-source-mode="annotated"] .code-reader code .section-divider .section-divider-gloss {
    display: block;
    margin-inline-start: 0;
    margin-block-start: var(--source-gap-xs);
  }
  body[data-source-mode="annotated"] .section-divider[data-collapsed="true"] .section-divider-gloss {
    display: none;
  }
}

/* ── mode separation ─────────────────────────────────────────────
   raw mode is the literal canonical mirror. hide every interpretive
   surface (annotations, section dividers, document map, intent line,
   synthetic spacing) so what the reader sees is the source file as
   bytes, with line numbers + syntax highlighting only.
   annotated mode lifts these surfaces back into view. */
body[data-source-mode="raw"] .section-divider,
body[data-source-mode="raw"] .reader-intent,
body[data-source-mode="raw"] .inspection-path,
body[data-source-mode="raw"] .source-minimap {
  display: none;
}
body[data-source-mode="raw"] .code-line--section-marker {
  /* in raw mode the line that originally carried a divider is just a
     normal source line — no synthetic soften, no synthetic spacing. */
  opacity: 1;
  margin-block-end: 0;
}
body[data-source-mode="raw"] .code-line--comment-lead {
  /* no inserted vertical rhythm in raw mode — preserve literal source
     spacing exactly. the interpretive breathing room belongs to the
     annotated layer. */
  margin-block-start: 0;
}
body[data-source-mode="raw"] .source-reader-layout {
  /* if raw mode hides the minimap, the layout grid should not reserve
     an empty 8rem column either. Re-collapse to single column. */
  grid-template-columns: 1fr;
}
/* the source comment line directly below a divider — soften further in
   annotated mode only, so the marker reads as the dominant entry point
   of the new section. in raw mode the rule above already overrides. */
.code-line--section-marker {
  opacity: 0.7;
  margin-block-end: 0.3em;
}

/* syntax token colours — editorial hierarchy, not ide rainbow.
   only four classes carry colour: tags, attrs, comments, strings.
   keywords, numbers, and punctuation return to neutral fg so the
   reader stays calm over long reading sessions.
   token classes may change colour only. font metrics (size, line-height,
   weight, letter-spacing, text-transform) are forced to inherit so the
   hard size lock further up is never broken by accident or override. */
.tok-tag,
.tok-attr,
.tok-string,
.tok-comment,
.tok-keyword,
.tok-number,
.tok-punctuation {
  font-size: inherit;
  line-height: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}
/* token grammar v1.0 — roles, not languages. each recipe is one
   color-mix derivation from the six root tokens (ink, fg2, fg3,
   accent, code-string, code-var). no new hex values, no greens or
   second blues. weight is reserved for typography; tokens vary
   colour only. */
.tok-tag         { color: color-mix(in srgb, var(--accent) 60%, var(--fg2)); }
.tok-attr        { color: color-mix(in srgb, var(--ink) 78%, var(--accent)); }
.tok-string      { color: color-mix(in srgb, var(--fg3) 55%, var(--fg2)); }
.tok-comment     { color: color-mix(in srgb, var(--fg3) 65%, transparent); }
.tok-keyword     { color: var(--ink); }
.tok-number      { color: var(--code-var); }
.tok-punctuation { color: var(--fg3); }

/* js / json string literals — the only blue/green in the system.
   html attribute values stay neutral via the default .tok-string
   above; the context selector promotes literals inside js or json
   files to the --code-string register. */
.code-reader[data-lang="js"] .tok-string,
.code-reader[data-lang="json"] .tok-string {
  color: var(--code-string);
}


.code-line--comment-technical .tok-comment {
  color: color-mix(in srgb, var(--mono-muted) 82%, var(--fg3) 18%);
  letter-spacing: -0.006em;
}
.code-line--comment-generated .tok-comment {
  color: color-mix(in srgb, var(--mono-muted) 88%, transparent);
  letter-spacing: -0.004em;
}
.code-line--comment-editorial-prose .tok-comment {
  color: color-mix(in srgb, var(--mono-muted) 76%, var(--oxblood-soft) 24%);
  letter-spacing: -0.01em;
  font-style: italic;
  opacity: 0.92;
  line-height: 1.62;
}
.code-line--rhythm-break { margin-block-start: 0.42rem; }
@media (max-width: 42rem) { .code-line--rhythm-break { margin-block-start: 0.52rem; } }
.reader-view-mode,.source-marker-chevron,.source-section-body,.source-section-summary,.section-divider-gloss,.inspection-path-link,.code-line,.line-code,.source-minimap {
  transition: opacity var(--transition-quiet), background-color var(--transition-quiet), border-color var(--transition-quiet);
}
body[data-source-mode="annotated"] .section-divider-gloss { opacity: 0.82; }
body[data-source-mode="raw"] .section-divider-gloss { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .reader-view-mode,.source-marker-chevron,.source-section-body,.source-section-summary,.section-divider-gloss,.inspection-path-link,.code-line,.line-code,.source-minimap,.code-line--range-active {
    transition: none !important;
  }
}
/* semantic highlighting — restrained typographic register, not ide theme.
   colour-only delta, no size/weight changes. the reader teaches
   architecture visually:
     · structural tags  (<header>, <main>, <footer>, …)   – a touch sharper
     · metadata tags    (<meta>, <link>, <title>, …)      – quieter
     · trust attributes (integrity, crossorigin, rel="canonical", …)
       carry a subtle oxblood (--accent) tint
     · accessibility attributes (aria-*, role, tabindex)  – soft highlight
   each treatment is one step away from the base token colour so the
   page never reads as a syntax-highlighter demo. */
/* structural tags read as full ink — these are the architecture of
   the document. */
.tok-tag--structural {
  color: var(--ink);
}
/* head-only tags describe rather than render — quieter. */
.tok-tag--metadata {
  color: color-mix(in srgb, var(--fg2) 55%, var(--fg3));
}
/* trust register: attribute names and promoted string values that
   carry cryptographic / canonical meaning. one oxblood, no shouting. */
.tok-attr--trust,
.tok-string--trust {
  color: color-mix(in srgb, var(--accent) 70%, var(--fg2));
}
/* accessibility register: attribute name and its value read as one
   phrase — same soft-ink register on both sides of the equals sign. */
.tok-attr--a11y {
  color: color-mix(in srgb, var(--ink) 70%, var(--fg2));
}
.tok-string--a11y {
  color: color-mix(in srgb, var(--ink) 60%, var(--fg2));
}

/* breathing room before comment blocks — editorial section dividers */
.code-line--comment-lead {
  margin-block-start: 0.75em;
}
/* range highlight — subtle accent tint for #L12-L24 deep-link selections */
.code-line--range-highlight {
  background-color: color-mix(in srgb, var(--accent) 7%, transparent);
  border-radius: 2px;
}
.code-line--range-highlight a.line-number {
  opacity: 0.72;
}

/* closing editorial footer — the seal line, the validated edition,
   and a single closure row: canonical · verify · plain text · top. */
.source-reader-footer {
  margin-block-start: clamp(2rem, 5vw, 3rem);
  padding-block-start: 1rem;
  border-top: 1px solid color-mix(in srgb, var(--bd-soft) 40%, transparent);
}
.footer-end {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--fg3);
  margin: 0;
  letter-spacing: 0.04em;
  opacity: 0.92;
}
.footer-edition {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--fg3);
  opacity: 0.7;
  margin: 0.25rem 0 0;
}
.footer-links {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--fg3);
  margin: 0.7rem 0 0;
  letter-spacing: 0.04em;
}
.footer-links a {
  color: var(--fg3);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--bd-soft) 60%, transparent);
}
.footer-links a:hover,
.footer-links a:focus-visible {
  color: var(--fg2);
  border-bottom-color: color-mix(in srgb, var(--fg2) 70%, transparent);
}
.footer-links a.back-to-top-inline {
  /* visually identical to the other closure links — no special
     treatment, the position in the row is its own meaning. */
  font-weight: 400;
}
/* legacy class kept for any cached markup; the new closure renders
   the top link inline above. */
.back-to-top {
  display: none;
}

/* loading / error / unknown states */
.reader-loading,
.reader-error {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--fg3);
  margin-block: 1rem;
}
.reader-unknown {
  padding-block: 2rem;
}

/* one-line conceptual sentence under the file description — sits as
   a quiet authorial line, never an instruction. serif italic so the
   reader hears it as the publication's voice, not as ui. high
   specificity so global p reset / font-style: normal cannot win. */
.source-reader-page .reader-intro .reader-intent {
  margin: 0.4rem 0 1rem;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(15px, 1.6vw, 16px);
  line-height: 1.55;
  color: var(--fg3);
  max-width: 54ch;
  opacity: 0.92;
}

/* view mode toggle — source (default) / annotated. quiet inline
   segmented control in the actions row. no fills, no rounded pills,
   no animations — reads as a typographic switch, not a ui control. */
.reader-view-toggle {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  margin-inline-start: 0;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--fg3);
}
.reader-view-toggle-label {
  /* small contextual eyebrow that names what the two buttons control.
     uppercase mono, calm letter-spacing — the user reads "reading mode"
     and the toggle below it, so the action is interpretation, not
     navigation to another file. */
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
  opacity: 0.72;
  margin-inline-end: 0.2rem;
}
.reader-view-mode {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  padding: 0 0.1em;
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--fg3);
  cursor: pointer;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 0.12s, border-bottom-color 0.12s, opacity 0.12s;
}
.reader-view-mode + .reader-view-mode {
  margin-inline-start: 0.1rem;
}
.reader-view-mode + .reader-view-mode::before {
  content: '\B7';
  margin-inline-end: 0.45rem;
  margin-inline-start: 0.05rem;
  opacity: 0.45;
}
.reader-view-mode.is-active,
.reader-view-mode[aria-pressed="true"] {
  color: var(--fg2);
  border-bottom-color: color-mix(in srgb, var(--fg2) 60%, transparent);
}
.reader-view-mode:hover,
.reader-view-mode:focus-visible {
  color: var(--fg2);
  outline: 0;
}

/* document map (inspection path) — rendered above the code shell on
   narrow viewports; hidden on wide viewports where the sticky
   minimap takes over (see :has() rule below).

   on mobile the map reads as a calmly wrapped block of printed
   index terms — each term sits on its own baseline rule, no fills,
   no rounded pills, no hover theatrics. compare with index entries
   in the back of a printed archive: term, hairline, term, hairline. */
.inspection-path {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--fg3);
  letter-spacing: 0.04em;
  margin-block-end: var(--source-gap-md);
  line-height: 1.5;
}
.inspection-path-label {
  margin: 0 0 var(--source-gap-xs);
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--fg3);
  opacity: 0.78;
}
.inspection-path-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--source-gap-md);
  row-gap: 0;
}
.inspection-path-item {
  flex: 0 1 auto;
}
/* index-term link. opacity 0.62 by default keeps every term quieter than the
   surrounding chrome; the .is-current class lifts opacity slightly so the
   eye anchors on the section the reader is presently passing through. no
   sticky-toc affordance, no animation theatrics — purely optical. */
.inspection-path-link {
  display: inline-block;
  padding-block: var(--source-gap-xs);
  color: var(--fg2);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--fg3) 55%, transparent);
  opacity: 0.62;
  transition: color 0.18s, border-bottom-color 0.18s, opacity 0.18s;
}
.inspection-path-link:hover,
.inspection-path-link:focus-visible {
  color: var(--fg2);
  border-bottom-color: color-mix(in srgb, var(--fg2) 70%, transparent);
  opacity: 1;
  outline: 0;
}
.inspection-path-link.is-current {
  opacity: 1;
  color: var(--fg);
  border-bottom-color: color-mix(in srgb, var(--fg) 65%, transparent);
}
.inspection-path-sep {
  /* retained for backward markup compatibility — never rendered in
     the new wrapped index-terms layout. */
  display: none;
}

/* source-reader layout: a single column on every viewport. the inline
   inspection-path above the code carries the document map; there is no
   sticky sidebar form (minimaps signal ide chrome rather than editorial
   publication, so they have been removed deliberately). */
.source-reader-layout {
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
}

/* mobile: safe-area bottom padding; font-size governed by the clamp rule above */
@media (max-width: 36rem) {
  .source-reader-page .page {
    padding-bottom: calc(3rem + env(safe-area-inset-bottom));
  }
}

/* ── /sw-reset/ — local recovery utility ──────────────────────
   the page reuses the editorial record-grid system from /verify/
   via `.sw-reset-page` scope (see :is(.verify-page, .sw-reset-page, .security-page, .source-page)
   rules above). only the local marker tag survives here as a quiet
   eyebrow above the page title. the reset button is rendered as a
   record-actions item so it reads as a quiet mono action, not a
   browser-default form button. */
.sw-reset-marker {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.5rem 0;
}

/* ── /verify/ — supporting infrastructure (current edition) ────
   phase 21 moved this panel below the page record card so it reads
   as supporting infrastructure, not as the page's primary object.
   visual register stays a quiet definition list — mono caps heading,
   hairline borders, restrained spacing, no colour signals. */
.verify-intro-panel {
  border-top: 1px solid var(--bd);
  border-bottom: 1px solid var(--bd);
  padding: 1.25rem 0;
  /* margin tightened against the card above; bottom margin opens
     the gap to the back link that follows. */
  margin: 0 0 clamp(20px, 3vw, 32px);
}
.verify-intro-heading {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg3);
  margin: 0 0 0.6rem 0;
}
.verify-intro-list {
  margin: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: clamp(1rem, 3vw, 2rem);
  row-gap: 0.4rem;
}
.verify-intro-row {
  display: contents;
}
.verify-intro-row dt {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--fg2);
  margin: 0;
}
.verify-intro-row dd {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--fg);
}
.verify-intro-row dd code {
  font-family: var(--mono);
  font-size: inherit;
  background: transparent;
  padding: 0;
}
.verify-intro-row dd a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}
.verify-intro-fpr {
  word-break: break-word;
}
@media (max-width: 540px) {
  .verify-intro-list { grid-template-columns: 1fr; row-gap: 0.5rem; }
  .verify-intro-row dd { padding-bottom: 0.4rem; border-bottom: 1px solid var(--bd); }
  .verify-intro-row:last-child dd { border-bottom: 0; }
}

  /* source reader stabilization pass */
  .reader-view-toggle { flex-wrap: wrap; row-gap: 0.35rem; }
  .reader-view-mode--wrap { white-space: nowrap; }
  @media (max-width: 520px) {
    .reader-view-toggle-label { flex-basis: 100%; margin-inline-end: 0; }
    .reader-view-mode--wrap { flex: 0 0 auto; }
  }
  .source-selection-tool { position: fixed; left: max(0.75rem, env(safe-area-inset-left)); right: max(0.75rem, env(safe-area-inset-right)); bottom: calc(0.75rem + env(safe-area-inset-bottom)); z-index: 20; background: color-mix(in srgb,var(--surface-archival) 94%,transparent); border: 1px solid color-mix(in srgb,var(--bd-soft) 55%,transparent); padding: 0.5rem 0.65rem; font-family: var(--mono); font-size: 0.69rem; }
  .source-selection-tool[hidden] { display:none; }
  .source-selection-tool-label, .source-selection-tool-actions { margin:0; }
  .source-selection-tool-actions button { appearance:none; background:none; border:0; padding:0; color:var(--fg2); font:inherit; }
  .section-divider { margin-block: 1rem 0.45rem; border-top-color: color-mix(in srgb,var(--bd-soft) 14%,transparent); }
  .source-marker-toggle,.source-marker-chevron,.source-section-summary{ display:none !important; }
  .code-line--range-active { outline: 0; border-radius: 0; background: color-mix(in srgb,var(--accent) 9%, var(--paper)); }
  .code-line--range-marked::before { inset-inline-start: -0.32rem; }
  .code-line--range-active:first-of-type { border-top-left-radius:2px;border-top-right-radius:2px; }
  .code-line--range-active:last-of-type { border-bottom-left-radius:2px;border-bottom-right-radius:2px; }
  .source-reader-footer { margin-block: clamp(3rem,6vw,4rem) calc(2.2rem + env(safe-area-inset-bottom)); }
  .footer-rendered-link { margin-top: 1rem; }
}

/* Page-scoped rules collected here so the cascade-layer order
   makes them defeat any same-specificity components rule, and so
   readers can see all body[data-page=…] declarations in one place.

   the .architecture-figure rule was originally nested inside an
   @media (min-width: 900px) block in the components layer; the
   @media wrapper is replicated here to preserve the desktop-only
   responsive guard. */
@layer pages {
  /* tighten before back so the page feels finished rather than
     trailing off. */
  body[data-page="releases"] .page-back {
    margin-top: clamp(14px, 2.1vw, 22px);
  }

  @media (min-width: 900px) {
    body[data-page="security"] .architecture-figure {
      width: min(720px, calc(100vw - 64px));
      max-width: calc(100vw - 64px);
      margin-left: 0;
      margin-right: auto;
      transform: none;
    }
  }

  body[data-page="security"] .page-body { max-width: 720px; }

  /* nav disclosure system retired — the homepage and every other
     page now present only the masthead at the top. no toggle, no
     panel, no responsive disclosure rules. mobile masthead spacing
     is owned by the @layer components @media rules above. */
}

/* ─── utilities ────────────────────────────────────────────────
   minimal accessibility-only helpers. Visually-hidden removes
   content from the page but preserves it for assistive tech —
   used as a target for aria-live status announcements (copy-toast,
   etc.). one declaration; no surface or page scoping. */
@layer utilities {
  .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;
  }
}
