/* Reset, typography, and the single reading column everything lives in. */
*, *::before, *::after { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

body {
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
}

/* Background: a fixed layer that never scrolls, carrying the page base plus
   three very slow blooms. They are plain radial gradients moved with transform
   only — no filters, no repaints — so they cost nothing while still keeping the
   page from sitting completely still. */
.bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background: var(--page-wash);
}
.bg i {
  position: absolute;
  display: block;
  border-radius: 50%;
  will-change: transform;
}
.bg .b1 {
  width: 68vmax; height: 68vmax; left: -24vmax; top: -28vmax;
  background: radial-gradient(circle, rgba(245, 169, 184, 0.30), rgba(245, 169, 184, 0) 70%);
  animation: drift-a 30s var(--ease) infinite;
}
.bg .b2 {
  width: 58vmax; height: 58vmax; right: -20vmax; top: -20vmax;
  background: radial-gradient(circle, rgba(91, 206, 250, 0.22), rgba(91, 206, 250, 0) 70%);
  animation: drift-b 36s var(--ease) infinite;
}
.bg .b3 {
  width: 54vmax; height: 54vmax; left: 24%; bottom: -32vmax;
  background: radial-gradient(circle, rgba(238, 130, 153, 0.16), rgba(238, 130, 153, 0) 72%);
  animation: drift-c 42s var(--ease) infinite;
}
@keyframes drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(7vmax, 5vmax, 0) scale(1.12); }
}
@keyframes drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.06); }
  50% { transform: translate3d(-6vmax, 7vmax, 0) scale(0.94); }
}
@keyframes drift-c {
  0%, 100% { transform: translate3d(0, 0, 0) scale(0.96); }
  50% { transform: translate3d(5vmax, -6vmax, 0) scale(1.1); }
}

/* Falling decoration. The vertical fall is linear on the outer element and the
   sway/spin eases on the inner one, so the two never fight; both are transform
   only, so the whole field is a handful of composited layers. */
.fall {
  position: absolute;
  top: 0;
  margin-left: -0.5em;      /* centre the glyph on its column */
  line-height: 1;
  user-select: none;
  will-change: transform;
  animation-name: fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.fall i {
  display: block;
  font-style: normal;
  will-change: transform;
  animation-name: sway;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
@keyframes fall {
  from { transform: translate3d(0, -12vh, 0); }
  to { transform: translate3d(0, 108vh, 0); }
}
@keyframes sway {
  from { transform: translate3d(-14px, 0, 0) rotate(-24deg); }
  to { transform: translate3d(14px, 0, 0) rotate(24deg); }
}

h1, h2, h3, p, ul, ol, figure, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; padding: 0; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 8px; }
[hidden] { display: none !important; }

/* One column. It centres at every width, so there is nothing to "adapt": the
   only breakpoint is where the session gets roomy enough to split in two. */
.col {
  width: 100%;
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 20px;
}

main { flex: 1 0 auto; display: flex; flex-direction: column; }

/* View changes are a real change of page, so they read as one: the old screen
   lifts away, then the new one rises in. Only ever one view is in flow, which
   is what keeps the swap from shifting anything. */
.view.is-entering { animation: view-in 420ms var(--ease-out) both; }

/* Elements arrive one after another rather than all at once. JS sets the
   per-element delay; the class only says "come in". */
.rise { animation: rise 480ms var(--ease-spring) both; }
@keyframes rise {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to { opacity: 1; transform: none; }
}
.view.is-leaving { animation: view-out 220ms var(--ease) both; }
@keyframes view-in {
  from { opacity: 0; transform: translate3d(0, 16px, 0) scale(0.985); }
  to { opacity: 1; transform: none; }
}
@keyframes view-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translate3d(0, -10px, 0) scale(0.99); }
}

.stack { display: grid; gap: var(--s-4); }
.stack-sm { display: grid; gap: var(--s-2); }
.row { display: flex; align-items: center; gap: var(--s-3); }
.grow { flex: 1 1 auto; min-width: 0; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.center { text-align: center; }

.title {
  font-size: clamp(26px, 7vw, 34px);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.sub { font-size: 15px; color: var(--text-soft); line-height: 1.7; }
.tiny { font-size: 12.5px; color: var(--text-faint); line-height: 1.6; }

.icon {
  display: block;          /* an inline svg sits on the text baseline, off-centre */
  width: 20px; height: 20px;
  flex: 0 0 auto;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon-sm { width: 16px; height: 16px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
