/* KEEP. Resets and the .container gutter, whose side padding is written with
   padding-inline. A layout class landing on that same element must add
   padding-block only, never the padding shorthand, or the gutter collapses to
   zero and text touches both screen edges below 1180px, the way it shipped
   once already. */
*, *::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
   <site-sidebar> wrapper tag around them. */
site-nav, site-sidebar, site-footer, site-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: router/router.js intercepts
   same-origin clicks and swaps <body> in place, but this rule still earns
   its place for whatever it does not handle (first load, external entries,
   JS disabled) by cross-fading the browser's own full navigation instead of
   a hard reload flash. Progressive enhancement, so 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-soft);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Shared attention pulse: reused by the dot in the home eyebrow, the
   active stage of a wiki diagram (components/wiki/diagrams.css) and the
   timeline's critical stage. One definition only. */
@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* Brand mark fill, the two-path reticle drawn in the nav wordmark
   (chrome/site-chrome.js, BRAND_MARK_SVG): a single definition of what each path
   renders as. */
.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;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  transition: background-color 180ms var(--ease-soft), color 180ms var(--ease-soft);
  -webkit-font-smoothing: antialiased;
}

/* --font-display = Archivo Black: one weight only (400), already the heaviest
   cut of the family. Any weight above 400 triggers a faux-bold that clogs the
   rendering; a line-height under 1.1 makes two-line headings overlap. Hence
   400 + 1.1 here. */
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); }

/* Scroll-reveal : le contenu est visible par défaut ; JS ajoute .reveal-ready
   qui arme la transformation, puis .in-view la déclenche. Pas de garde
   opacity:0 : les pages s'affichent entièrement sans JS et au premier paint.
   Trois amplitudes, choisies selon ce qui apparaît : un titre monte de plus
   loin qu'un paragraphe, une ligne de tableau à peine. */
.reveal        { transition: opacity 380ms var(--ease-soft), transform 380ms var(--ease-soft); }
.reveal--wide  { transition: opacity 620ms var(--ease-glide), transform 620ms var(--ease-glide); }
.reveal--tight { transition: opacity 260ms var(--ease-snap), transform 260ms var(--ease-snap); }

.reveal-ready:not(.in-view)               { opacity: 0; transform: translateY(10px); }
.reveal--wide.reveal-ready:not(.in-view)  { opacity: 0; transform: translateY(18px); }
.reveal--tight.reveal-ready:not(.in-view) { opacity: 0; transform: translateY(4px); }

/* Les trois sélecteurs, pas seulement le premier : une amplitude ajoute une
   classe, donc les deux variantes pèsent plus lourd que la règle de base et
   passeraient devant elle ici. Sans les nommer, 93 éléments restent à
   opacity 0 quand la préférence est active. */
@media (prefers-reduced-motion: reduce) {
  .reveal-ready:not(.in-view),
  .reveal--wide.reveal-ready:not(.in-view),
  .reveal--tight.reveal-ready:not(.in-view) { opacity: 1; transform: none; }
}
