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

/* Custom elements are transparent to layout: the .wiki-layout grid has to see
   <aside> and <main> as its own children, not the <dc-sidebar> wrapper. */
dc-nav, dc-sidebar, dc-footer { display: contents; }

html {
  position: relative;
  scroll-behavior: smooth;
  /* Kills a stray horizontal scroll without creating a scroll container.
     `clip` keeps the sticky nav working where `hidden` would not. */
  overflow-x: clip;
}

@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;
  }
}

/* Cross-document view transitions. router/router.js swaps <body> for internal
   links, so this rule covers what it does not handle: the first load, an entry
   from outside, JS disabled. Progressive enhancement, no fallback needed. */
@view-transition { navigation: auto; }
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 200ms;
  animation-timing-function: var(--ease-drop);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* The lamp over the module cell, shared by the hero kicker dot and by the
   module cell drawn in the capacity tile. One definition. */
@keyframes cell-breathe {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* Brand mark fill: the box, its lid line and the module plate, drawn in the nav
   wordmark and in the hero. Defined once, used at both sizes. */
.brand-mark-back { fill: var(--primary-text); }
.brand-mark-front { fill: var(--accent-text); opacity: 0.8; }

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 170ms var(--ease-drop), color 170ms var(--ease-drop);
  -webkit-font-smoothing: antialiased;
}

/* Archivo Black ships one weight, 400, and any weight above it triggers a faux
   bold that clogs the rendering. Hence 400 and a line-height that lets two
   lines of display type sit side by side without touching. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -0.025em;
  text-wrap: balance;
  margin: 0 0 var(--space-3);
  line-height: 1.08;
}
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 li { margin-bottom: var(--space-2); }
.prose strong { color: var(--ink); }
.prose a { color: var(--primary-text); text-decoration: underline; text-underline-offset: 3px; }
.prose a:hover, .prose a:focus-visible { text-decoration-thickness: 2px; }

a { color: inherit; text-decoration: none; }
/* Underlined at rest, not on hover: a link sitting inside a run of text and
   told apart by colour alone disappears for anyone who does not see it. */
.link {
  color: var(--primary-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.link:hover, .link:focus-visible { text-decoration-thickness: 2px; }

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

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

/* A share of the viewport rather than one frozen number. Capped at 1180px the
   page used 36% of a 3300px screen and stopped responding at exactly the width
   with the most room to use. The reading measure is protected where it belongs,
   on .prose and .mech__body, not by starving the whole layout.
   Three terms: a fixed gutter so a phone keeps its 24px, a proportional one so
   a wide screen keeps air, and the ceiling. */
.container {
  width: min(calc(100% - 2 * var(--space-4)), 94%, 1720px);
  margin-inline: auto;
}

/* Scroll reveal. Content is visible by default; fx/reveal.js adds
   .reveal-ready to arm the transform, then .in-view to run it. No opacity:0
   guard in the stylesheet, so a page with no JS shows in full at first paint.
   Three amplitudes: a heading climbs further than a paragraph, a table row
   barely moves. */
.reveal        { transition: opacity 360ms var(--ease-drop), transform 360ms var(--ease-drop); }
.reveal--wide  { transition: opacity 600ms var(--ease-slide), transform 600ms var(--ease-slide); }
.reveal--tight { transition: opacity 240ms var(--ease-latch), transform 240ms var(--ease-latch); }

.reveal-ready:not(.in-view)               { opacity: 0; transform: translateY(12px); }
.reveal--wide.reveal-ready:not(.in-view)  { opacity: 0; transform: translateY(22px); }
.reveal--tight.reveal-ready:not(.in-view) { opacity: 0; transform: translateY(5px); }

/* The three selectors, not just the first: an amplitude adds a class, so both
   variants outweigh the base rule and would win here. Left unnamed, every
   element carrying one stays at opacity 0 when the preference is on. */
@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; }
}

/* The language gate at the root: no nav, no footer, one choice. */
body.lang-gate {
  min-height: 100vh;
  display: grid;
  place-items: center;
  text-align: center;
}
.lang-gate__mark {
  font-family: var(--font-display);
  font-size: var(--text-h1);
  margin: 0 0 var(--space-4);
}
.lang-gate__choice {
  list-style: none;
  margin: 0; padding: 0;
  display: flex;
  gap: var(--space-4);
  justify-content: center;
}
.lang-gate__choice a {
  color: var(--primary-text);
  font-family: var(--font-mono);
  border-bottom: 2px solid transparent;
  padding-bottom: var(--space-1);
}
.lang-gate__choice a:hover,
.lang-gate__choice a:focus-visible { border-bottom-color: var(--primary-text); }
