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

/* Custom elements are transparent to layout — CSS Grid (.wiki-layout) must
   see <aside class="wiki-sidebar"> / <main> directly as grid items, not the
   <lb-sidebar> wrapper tag around them. */
lb-nav, lb-sidebar, lb-footer, lb-ledger { display: contents; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
}

/* Native cross-document View Transitions: same static multi-page site (no
   router, no SPA), but the browser cross-fades between navigations instead
   of a hard reload flash. Progressive enhancement — browsers without
   support just keep the current instant navigation, no fallback needed. */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 220ms;
  animation-timing-function: var(--ease-out-expo);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Pulsation d'infection partagée — réutilisée par le point de l'eyebrow
   d'accueil (home.css), l'étape symptomatique de contamination (pillars.css)
   et l'étape critique de la timeline. Une seule définition. */
@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* Remplissage de la marque — le cadre de visée à deux tracés, dessiné dans le
   wordmark de la nav (components.js BRAND_MARK_SVG) : une seule définition de
   ce que rend chaque tracé. */
.brand-mark-back { fill: var(--primary-text); }
.brand-mark-front { fill: var(--accent-text); opacity: 0.82; transform: translate(2px, 2px); }

html {
  position: relative;
  /* Kill any stray horizontal scroll (parallax layers, oversized display
     type, marquee) without creating a scroll container — `clip` keeps the
     sticky nav working, unlike `hidden`. */
  overflow-x: clip;
}
/* Grain: fixed, blended over everything, never intercepts input. Subtle
   analog-horror texture instead of a flat digital surface. */
html::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  background-image: var(--grain);
  opacity: 0.05;
  mix-blend-mode: overlay;
}

body {
  margin: 0;
  /* Halo radial via --page-radial (token thémé) → suit l'override de thème
     sans media query dédiée. */
  background:
    radial-gradient(ellipse 1200px 640px at 50% -8%, var(--page-radial), transparent 62%),
    var(--bg);
  background-attachment: fixed;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  transition: background-color 180ms var(--ease-out-expo), color 180ms var(--ease-out-expo);
  -webkit-font-smoothing: antialiased;
}

/* --font-display = Anton : une seule graisse (400) et de hautes capitales.
   Toute graisse > 400 déclenche un faux-gras qui empâte ; un interligne < 1.1
   fait se chevaucher les titres sur deux lignes. D'où 400 + 1.1 ici. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0 0 var(--space-3);
  line-height: 1.1;
}
h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }
h3 { font-size: var(--text-h3); }

p { text-wrap: pretty; }

.prose { max-width: 68ch; }
.prose p, .prose ul, .prose ol { margin: 0 0 var(--space-3); color: var(--ink); }
.prose strong { color: var(--ink); }
.prose a { color: var(--primary-text); }

a { color: inherit; text-decoration: none; }
.link { color: var(--primary-text); text-underline-offset: 3px; }
.link:hover, .link:focus-visible { text-decoration: underline; }

code, .mono {
  font-family: var(--font-mono);
  font-size: var(--text-mono);
  font-variant-numeric: tabular-nums;
}

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

.container {
  max-width: 1180px;
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.muted { color: var(--muted); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Scroll-reveal: content is visible by default; JS adds .reveal-ready which
   arms the transform, then .in-view triggers it. No opacity:0 gate — pages
   still render fully with JS disabled or on first paint. */
.reveal {
  transition: opacity 480ms var(--ease-out-expo), transform 480ms var(--ease-out-expo);
}
.reveal-ready:not(.in-view) {
  opacity: 0;
  transform: translateY(8px);
}
@media (prefers-reduced-motion: reduce) {
  .reveal-ready:not(.in-view) { opacity: 1; transform: none; }
}
