:root {
  color-scheme: light dark;

  --bg-color: #f7f7f8;
  /* Background at zero alpha — fading to `transparent` casts grey mid-fade */
  --bg-color-clear: rgba(247, 247, 248, 0);
  --color-highlight: #18181b;
  --color-dim: #d4d4d8;
  /* Vignette ends; the mid stop is --bg-color */
  --canvas-center: #ffffff;
  --canvas-edge: #ececed;
  /* Always darker than the page, in both themes */
  --shadow-color: rgba(0, 0, 0, 0.12);
  /* em = a share of each row's font-size; parallax-driven */
  --shadow-x: 0em;
  --shadow-y: 0em;
  --shadow-blur: 0.06em;
  /* High only because `overlay` self-limits against a near-white page: lands ~8/255 */
  --grain-opacity: 0.85;
  /* Shared by the stage rotation and the corner fade; ANGLE_DEG in script.js must match */
  --tilt: 15deg;
  --font-mono: 'Datatype', 'Courier New', monospace;
  /* Only wght@500 is fetched; any other value would be synthesised bold */
  --font-weight: 500;
}

/* Inverted palette. Each colour mirrors its light-theme lightness, so the ghost
   rows keep their near-invisible whisper and the name its full contrast. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg-color: #0c0c0e;
    --bg-color-clear: rgba(12, 12, 14, 0);
    --color-highlight: #f4f4f5;
    --color-dim: #2a2a2e;
    --canvas-center: #050506;
    --canvas-edge: #17171a;
    /* Still black, not inverted; the near-black page needs far more alpha to register */
    --shadow-color: rgba(0, 0, 0, 0.55);
    /* Lower: overlay has more headroom against near-black than near-white */
    --grain-opacity: 0.65;
  }
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: var(--bg-color);
  color: var(--color-dim);
  font-family: var(--font-mono);
  font-weight: var(--font-weight);
  user-select: none;
}

/* Dissolves the ribbons at the edges; sits above the type, below the grain */
.corner-fade {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  /* At --tilt the gradient runs 0% = bottom-left corner to 100% = top-right,
     leaving the clear band parallel to the type */
  background: linear-gradient(
    var(--tilt),
    var(--bg-color) 0%,
    var(--bg-color-clear) 25%,
    var(--bg-color-clear) 75%,
    var(--bg-color) 100%
  );
}

/* Static print grain: desaturated and alpha-flattened so only luminance varies,
   blended with `overlay` so it modulates the page rather than veiling it.
   The RGB slope stretches near-flat fractalNoise into visible, clumped grain. */
.grain-overlay {
  position: fixed;
  /* Margin for the parallax drift, which is capped at PARALLAX_RANGE (18px) */
  inset: -32px;
  pointer-events: none;
  will-change: transform;
  z-index: 999;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.45' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3' intercept='-1'/%3E%3CfeFuncG type='linear' slope='3' intercept='-1'/%3E%3CfeFuncB type='linear' slope='3' intercept='-1'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23grain)'/%3E%3C/svg%3E");
  background-size: 400px 400px;
  background-repeat: repeat;
}

/* WebKit rasterises feTurbulence at ~1x and scales it up, so the filter blurs on
   retina; a baked tile stays sharp, and 200px maps 1:1 at 3x. script.js adds the
   class after preloading, so the inline SVG grain covers the 195 KB wait. */
@media (min-resolution: 2dppx), (-webkit-min-device-pixel-ratio: 2) {
  .grain-overlay.grain-hd {
    background-image: url("grain.png");
    background-size: 200px 200px;
  }
}

.canvas-wrapper {
  position: relative;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: radial-gradient(circle at 50% 50%,
    var(--canvas-center) 0%, var(--bg-color) 60%, var(--canvas-edge) 100%);
}

.typography-stage {
  position: absolute;
  width: 320vw;
  height: 320vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transform-origin: center center;
  transform: rotate(var(--tilt));
  will-change: transform;
  pointer-events: none;
}

.ribbon-row {
  width: 100%;
  display: flex;
  white-space: nowrap;
  justify-content: center;
  align-items: baseline;
  line-height: 0.8;
  letter-spacing: -0.02em;
  color: var(--color-dim);
  cursor: default;
}

/* Width pinned to the row metric so the reveal's chart glyphs (0–0.84em, off the
   0.5em mono grid) and blanks cannot reflow the ribbon */
.word-item {
  display: inline-block;
  width: var(--word-w, auto);
  flex-shrink: 0; /* an emptied word has zero min-content and would collapse */
  white-space: pre;
  padding: 0;
  color: var(--bg-color);
  /* Soft cast, offset by the parallax; the blur keeps it visible at rest too */
  text-shadow: var(--shadow-x) var(--shadow-y) var(--shadow-blur) var(--shadow-color);
  /* letter-spacing kills ligatures in Blink; only this forces the charts back on */
  font-feature-settings: "liga" 1, "calt" 1;
}

/* Reveal 1 — the ribbons lift out of the background into dim grey */
.typography-stage.is-lit .word-item {
  color: var(--color-dim);
  transition: color 500ms ease-out;
}

/* Reveal 2 — the name darkens, but only once every glyph has settled */
.typography-stage.is-lit.is-named .ribbon-row.center-surname .word-item.highlight,
.typography-stage.is-lit.is-named .ribbon-row.center-firstname .word-item.highlight {
  color: var(--color-highlight);
  transition: color 900ms ease-out;
}

/* Rows below the centre are appended later, so without this they paint over the
   name. Set on the row, not the word: each row's translateX makes it a stacking
   context, which would trap a z-index on the word inside it. */
.ribbon-row.center-surname,
.ribbon-row.center-firstname {
  z-index: 2;
}

.ribbon-row.center-surname .word-item.highlight,
.ribbon-row.center-firstname .word-item.highlight {
  position: relative;
}
