/* tobiinzi.com — IGNITION redesign
   Dark cosmos, huge display type, terminal chrome. Everything animated is scoped
   under html.js so the no-JS page stays a plain, readable document. */

@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("../assets/fonts/space-grotesk-latin-var.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/jetbrains-mono-latin-400.woff2") format("woff2");
}

:root {
  color-scheme: dark;
  --bg: #08080b;
  --fg: #eceaf2;
  --muted: #71727f;
  --line: rgba(236, 234, 242, 0.14);
  /* Accent system: field.js sets both on pick, clears on release. --accent is the
     element's true colour (UI); --accent-text is its AA-contrast text variant. */
  --accent: var(--fg);
  --accent-text: var(--muted);
  --font-display: "Space Grotesk", "Archivo", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);
  /* The singularity's screen anchor — the igniter button and the three.js orb
     sit on this point (JS reads the button's rect). */
  --orb-x: 50vw;
  --orb-y: 50vh;
  /* The enthroned element sits over on the right (~20% in from the right edge),
     backing off only on very narrow viewports so its rings never clip. The field
     reads the throne's resolved position, so the flight, rings and re-tint waves
     all follow. */
  --throne-dx: min(30vw, 50vw - 7rem);
}

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

html {
  background-color: var(--bg);
  scrollbar-width: none;
}
html::-webkit-scrollbar { display: none; }

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--fg);
  font-family: var(--font-display);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
/* Home never scrolls; the Me journey turns document scroll back on. */
.js body[data-view="home"] { overflow: hidden; height: 100vh; height: 100dvh; }

.mono {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: lowercase;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

::selection { background: var(--accent); color: var(--bg); }

/* Freeze animations while the tab is hidden. */
body.frozen *, body.frozen *::before, body.frozen *::after {
  animation-play-state: paused !important;
  transition: none !important;
}

/* ------------------------------------------------------------- backdrop */
#cosmos {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

.vignette {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(ellipse 120% 90% at 50% 45%, transparent 55%, rgba(0, 0, 0, 0.42) 100%);
}

.grain {
  position: fixed;
  inset: -100%;
  z-index: 60;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 0.9s steps(2) infinite;
}
@keyframes grain {
  0% { transform: translate(0, 0); }
  25% { transform: translate(-2%, 3%); }
  50% { transform: translate(3%, -2%); }
  75% { transform: translate(-3%, -3%); }
  100% { transform: translate(2%, 2%); }
}

/* Detonation flash: a radial burst from the orb anchor, GSAP-driven. */
.flash {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle at var(--orb-x) var(--orb-y), rgba(255, 255, 255, 0.95) 0%, rgba(200, 190, 255, 0.35) 18%, transparent 46%);
}

/* ------------------------------------------------------------ preloader */
/* A lone star roams the dark while the count climbs in the centre. When the
   page is ready the count winks out, the veil lifts (the star keeps roaming
   above the revealing page), then the star glides home to centre stage and
   flares into the singularity — it was the orb all along (main.js). */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  pointer-events: none; /* only the veil blocks; the star must never */
}
.js .preloader { display: block; }
.preloader-veil {
  position: absolute;
  inset: 0;
  background: var(--bg);
  pointer-events: auto;
}
.preloader-star {
  position: absolute;
  left: 0;
  top: 0;
  width: 0.6rem;
  height: 0.6rem;
  margin: -0.3rem 0 0 -0.3rem;
  border-radius: 50%;
  background: #fff;
  box-shadow:
    0 0 1rem 0.2rem rgba(224, 214, 255, 0.6),
    0 0 2.4rem 0.7rem rgba(176, 158, 255, 0.22);
  transform: translate(50vw, 50vh) scale(0.55); /* pre-JS resting spot */
  will-change: transform;
}
.preloader-count {
  position: absolute;
  left: 50%;
  bottom: 22%;
  transform: translateX(-50%);
  font-size: 0.72rem;
  letter-spacing: 0.3em;
  color: var(--muted);
}
/* The trailing dots light up one after another — a quiet terminal-style pulse. */
.preloader-count i {
  font-style: normal;
  animation: loading-dot 1.3s ease-in-out infinite;
}
.preloader-count i:nth-child(2) { animation-delay: 0.22s; }
.preloader-count i:nth-child(3) { animation-delay: 0.44s; }
@keyframes loading-dot {
  0%, 100% { opacity: 0.1; }
  35%, 55% { opacity: 1; }
}

/* --------------------------------------------------------------- chrome */
.chrome {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.35rem 1.6rem;
  pointer-events: none;
}
.chrome > * { pointer-events: auto; }
.chrome-top { top: 0; align-items: flex-start; }
.chrome-bottom { bottom: 0; }
/* Booted in by GSAP (autoAlpha); hidden only when JS will actually run the boot.
   visibility:hidden too, so the invisible links can't be clicked or tabbed to. */
.js .chrome { opacity: 0; visibility: hidden; }

.wordmark {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  text-decoration: none;
  text-align: right;
  color: var(--fg);
}
.wordmark-sign {
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.02em;
}
.wordmark-tail {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.18em;
  color: var(--muted);
}

/* Sections stack vertically in the upper-left, echoing the original's sidebar. */
.nav { display: flex; flex-direction: column; align-items: flex-start; gap: 1.1rem; }
.nav-tabs { display: flex; flex-direction: column; align-items: flex-start; gap: 1.1rem; }
.nav-link {
  appearance: none;
  border: none;
  background: none;
  padding: 0.2rem 0;
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.25s ease;
}
.nav-index {
  font-size: 0.58rem;
  color: var(--accent-text);
  transition: color 0.35s ease;
}
.nav-link:hover { color: var(--fg); }
.nav-link:focus-visible {
  color: var(--fg);
  outline: 1px solid var(--accent);
  outline-offset: 5px;
}
.nav-link[aria-selected="true"] { color: var(--fg); }
.nav-link[aria-selected="true"] .nav-label {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 6px;
}

.status {
  margin: 0;
  display: flex;
  gap: 0.7rem;
  align-items: center;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  white-space: nowrap;
  color: var(--accent-text);
  transition: color 0.35s ease;
}
.status-sep { color: var(--line); }

.socials {
  display: flex;
  gap: 1.1rem;
  align-items: center;
}
/* Hidden until the Contact detonation delivers them (contact.js). */
.js .socials { opacity: 0; pointer-events: none; }
.js .socials.is-revealed { opacity: 1; pointer-events: auto; }
/* Ported from the original site: each icon bobs on its own slow wave (staggered
   below), so the row drifts like a gentle ripple instead of sitting static. The
   wave owns the wrapper transform; hover scale lives on the SVG so the two never
   clash. Runs from load on the still-hidden icons, so a revealed icon is already
   mid-drift and never snaps into motion when it fades in. */
.social {
  display: flex;
  color: var(--accent-text);
  transition: color 0.3s ease;
  animation: social-wave 5s ease-in-out infinite;
}
/* Negative delays start each icon already offset in the cycle. */
.social:nth-child(1) { animation-delay: 0s; }
.social:nth-child(2) { animation-delay: -0.7s; }
.social:nth-child(3) { animation-delay: -1.4s; }
.social:nth-child(4) { animation-delay: -2.1s; }
.social:nth-child(5) { animation-delay: -2.8s; }
.social svg { transition: transform 0.2s ease; }
.social:hover { color: var(--fg); }
.social:hover svg,
.social:focus-visible svg { transform: scale(1.15); }
.social:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}
@keyframes social-wave {
  0%, 100% { transform: translateY(-3px); }
  50% { transform: translateY(3px); }
}

/* ----------------------------------------------------------------- hero */
.panel[hidden] { display: none; }

.panel-home {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
}
.js .panel-home { position: fixed; inset: 0; }

/* Monumental-minimal intro: one centred column with the orb as its pivot.
   The name lands word by word above the orb, the mono role scrambles in under
   it, a single invitation waits beneath — and two ultra-faint orbit rings mark
   the centre as the site's axis. Everything here flies out on ignition. */
.hero {
  position: absolute;
  inset: 0;
  z-index: 10;
  pointer-events: none; /* the igniter button owns the clicks */
}

.hero-head {
  position: absolute;
  left: 50%;
  bottom: calc(100% - var(--orb-y) + clamp(7rem, 17vmin, 10rem));
  transform: translateX(-50%);
  width: max-content;
  max-width: 92vw;
  text-align: center;
}
.hero-kicker {
  margin: 0 0 1.2rem;
  color: var(--muted);
  font-size: 0.68rem;
  letter-spacing: 0.3em;
}
.js .hero-kicker { opacity: 0; }
.hero-name {
  margin: 0;
  font-size: clamp(2.4rem, 6.5vw, 4.6rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--fg);
}
.js .hero-name { opacity: 0; } /* shown the instant its words start rising */
.hero-role {
  margin: 1.1rem 0 0;
  color: var(--muted);
  font-size: clamp(0.6rem, 0.55rem + 0.25vw, 0.74rem);
  letter-spacing: 0.3em;
}
.js .hero-role { opacity: 0; }

/* Word masks (built by scripts/split.js): each word rises out of its own clip.
   The padding/margin pair keeps descenders (y, g) from being sheared off. */
.hero-name .word-mask {
  display: inline-block;
  overflow: hidden;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}
.hero-name .word { display: inline-block; will-change: transform; }

/* The invitation, waiting quietly beneath the orb. */
.hero-cta {
  position: absolute;
  left: 50%;
  top: calc(var(--orb-y) + clamp(7rem, 16vmin, 9.4rem));
  transform: translateX(-50%);
  margin: 0;
  color: var(--muted);
  font-size: 0.68rem;
  letter-spacing: 0.24em;
  white-space: nowrap;
}
.js .hero-cta { opacity: 0; }

/* Faint orbit rings around the orb: SVG strokes (dashes follow the arc — no
   flat-chord notches), slowly counter-rotating. A pre-echo of the throne. */
.hero-orbits {
  position: absolute;
  left: var(--orb-x);
  top: var(--orb-y);
  width: clamp(13rem, 32vmin, 17.5rem);
  height: clamp(13rem, 32vmin, 17.5rem);
  transform: translate(-50%, -50%);
  overflow: visible;
  opacity: 0;
}
.hero-orbit {
  fill: none;
  stroke: rgba(236, 234, 242, 0.16);
  stroke-width: 1;
  stroke-dasharray: 4 6;
  transform-box: fill-box;
  transform-origin: center;
  animation: spin 52s linear infinite;
}
.hero-orbit-b {
  stroke: rgba(236, 234, 242, 0.09);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-dasharray: 0.4 7;
  animation: spin-rev 74s linear infinite;
}

/* ------------------------------------------------------ igniter + throne */
/* The singularity button. The rainbow orb itself is drawn by the .orb-gl canvas
   (scripts/orb.js, ported from the original site); the button is the hit area. */
.igniter {
  position: fixed;
  left: var(--orb-x);
  top: var(--orb-y);
  transform: translate(-50%, -50%) scale(1);
  z-index: 12;
  width: 6rem;
  height: 6rem;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: none; /* the orb is rendered by the <canvas> child */
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* visibility:hidden as well: an opacity-0 button is still keyboard-focusable,
   and Enter on the invisible orb would start the charge mid-boot. */
.js .igniter { opacity: 0; visibility: hidden; pointer-events: none; }
.js .igniter.is-live { opacity: 1; visibility: visible; pointer-events: auto; }
.igniter:focus-visible {
  outline: 1px dashed var(--accent);
  outline-offset: 6px;
}

/* First-reveal only: the orb springs up from nothing with a soft back-ease pop
   (main.js drops .orb-intro after it has played). */
.igniter.is-live.orb-intro {
  animation: orb-arrive 1.35s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes orb-arrive {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0); }
  60% { opacity: 1; }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.orb-gl {
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(7.5rem, 18vmin, 10rem);
  height: clamp(7.5rem, 18vmin, 10rem);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* Fallback if WebGL is unavailable (initOrb couldn't start): a soft static
   rainbow disc so the button is still visible and clearly clickable. */
.igniter.no-gl {
  background: radial-gradient(
    circle at center,
    #fff 8%,
    #00ade8 34%,
    #9a619b 58%,
    transparent 72%
  );
}

/* Containment lost: the (charged, white-hot) orb collapses into a single point
   of light — the beat of stillness before the detonation erupts from it. */
.igniter.is-collapsing {
  pointer-events: none;
  animation: igniter-collapse 0.17s cubic-bezier(0.6, 0, 0.9, 0.4) forwards;
}
@keyframes igniter-collapse {
  to { transform: translate(-50%, -50%) scale(0.04); }
}

/* -------------------------------------------------------- ignition FX */
/* Accretion rings: dashed circles spiralling into the singularity. Sized by a
   fixed base (260px) and driven purely by transform, so the infall is cheap. */
.charge-ring {
  position: fixed;
  left: 0;
  top: 0;
  width: 260px;
  height: 260px;
  border-radius: 50%;
  border: 1px dashed rgba(236, 234, 242, 0.5);
  pointer-events: none;
  z-index: 11; /* just beneath the orb, so rings die behind its glow */
  will-change: transform, opacity;
}

/* Infalling sparks: rainbow motes feeding the core (colour set inline). */
.charge-spark {
  position: fixed;
  left: 0;
  top: 0;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 11;
}

/* The containment readout: a quiet HUD percentage tucked right under the orb. */
.charge-readout {
  position: fixed;
  left: var(--orb-x);
  top: calc(var(--orb-y) + 5.4rem);
  transform: translateX(-50%);
  margin: 0;
  z-index: 13;
  color: var(--muted);
  letter-spacing: 0.24em;
  font-size: 0.55rem;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
  white-space: pre;
}
.charge-readout.is-critical {
  color: #e0564c;
  animation: readout-blink 0.42s steps(1) infinite;
}
@keyframes readout-blink {
  50% { opacity: 0.35; }
}

/* Detonation shockwave rings, expanding from the collapse point. Width/height
   are animated (not scale) so the hairline border stays hairline. */
.shock-ring {
  position: fixed;
  left: 0;
  top: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.85);
  pointer-events: none;
  z-index: 49; /* beneath the flash */
  will-change: width, height, opacity;
}

.throne {
  position: fixed;
  left: calc(var(--orb-x) + var(--throne-dx));
  top: var(--orb-y);
  z-index: 11;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
}
.throne.is-visible { opacity: 1; }
/* SVG orbit rings: the 200-unit viewBox maps ~1:1 to px at the default root size,
   so r=76 / r=91 give the same 9.5rem / 11.4rem circles as before — but the dashes
   are stroked along the arc, so no flat-chord notches. */
.throne-rings {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 12.5rem;
  height: 12.5rem;
  transform: translate(-50%, -50%);
  overflow: visible;
  pointer-events: none;
}
.throne-ring {
  fill: none;
  stroke: color-mix(in srgb, var(--accent) 55%, transparent);
  stroke-width: 1;
  stroke-dasharray: 4 5;
  transform-box: fill-box;
  transform-origin: center;
  animation: spin 26s linear infinite;
}
.throne-ring-b {
  stroke: color-mix(in srgb, var(--accent) 30%, transparent);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-dasharray: 0.4 6.5;
  animation: spin-rev 40s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes spin-rev { to { transform: rotate(-360deg); } }

.throne-label {
  position: absolute;
  right: 6.6rem; /* to the LEFT of the orb — reads inward from the right-anchored throne */
  top: -0.4rem;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  white-space: nowrap;
}
.throne-name {
  font-size: 1rem;
  letter-spacing: 0.3em;
  color: var(--accent-text);
  text-transform: uppercase;
}

/* ------------------------------------------------------------ the field */
.field {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30vh;
  z-index: 12;
  pointer-events: none;
}
.type {
  position: absolute;
  left: 0;
  top: 0;
  width: 3.6rem;
  height: 3.6rem;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(14, 14, 20, 0.55);
  cursor: pointer;
  pointer-events: auto;
  opacity: 0;
  will-change: transform;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.field.ready .type { opacity: 1; }
.js .field .type { visibility: hidden; }
.js .field.ready .type { visibility: visible; }
.type img {
  width: 62%;
  height: 62%;
  pointer-events: none;
  transition: opacity 0.16s ease;
}
.type.is-hovered {
  border-color: color-mix(in srgb, var(--type-color) 75%, transparent);
  box-shadow: 0 0 1.4rem 0.1rem color-mix(in srgb, var(--type-color) 38%, transparent);
}
.type:focus-visible {
  outline: 1px solid var(--type-color);
  outline-offset: 4px;
}
/* Mid-flight the button reads as a pure colour orb. */
.type.is-orb {
  border-color: transparent;
  background: radial-gradient(circle, var(--type-color) 0%, color-mix(in srgb, var(--type-color) 55%, transparent) 55%, transparent 75%);
  box-shadow: 0 0 1.6rem 0.3rem color-mix(in srgb, var(--type-color) 55%, transparent);
}
.type.is-orb img { opacity: 0; }
.type.is-active {
  border-color: color-mix(in srgb, var(--type-color) 85%, transparent);
  box-shadow: 0 0 2.6rem 0.4rem color-mix(in srgb, var(--type-color) 42%, transparent);
  background: rgba(10, 10, 16, 0.72);
}

.trail {
  position: fixed;
  left: 0;
  top: 0;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  z-index: 11;
  filter: blur(1px);
}

.pick-hint {
  position: fixed;
  left: 50%;
  top: 30vh; /* upper third, centred */
  transform: translateX(-50%);
  z-index: 12;
  margin: 0;
  color: var(--muted);
  letter-spacing: 0.28em;
  opacity: 0;
  transition: opacity 0.8s ease;
  pointer-events: none;
}
.pick-hint.is-visible { opacity: 1; }
.caret { animation: caret 1.1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

/* -------------------------------------------------------------- journey */
.panel-me { position: relative; }

/* No-JS / no-motion default: a plain readable timeline. */
.journey { position: relative; }
.journey-stage {
  max-width: 46rem;
  margin-inline: auto;
  padding: 9rem 1.6rem 6rem;
}
.scene { position: relative; margin-bottom: 5.5rem; }
.scene-ghost {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: -1;
  font-weight: 700;
  font-size: clamp(7rem, 24vw, 22rem);
  line-height: 1;
  letter-spacing: -0.02em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(236, 234, 242, 0.07);
  user-select: none;
  white-space: nowrap;
}
.scene-date {
  margin: 0 0 0.9rem;
  color: var(--accent-text);
  letter-spacing: 0.26em;
  transition: color 0.35s ease;
}
.scene-title {
  margin: 0 0 1.1rem;
  font-weight: 600;
  font-size: clamp(2.2rem, 6vw, 4.6rem);
  line-height: 1.02;
  letter-spacing: -0.02em;
}
.scene-title .line-mask { display: inline-block; overflow: hidden; vertical-align: top; }
.scene-title .char { display: inline-block; }
.scene-text {
  margin: 0;
  max-width: 34rem;
  color: var(--muted);
  font-size: 1.02rem;
  line-height: 1.8;
}

/* JS mode: a tall scroll track with one pinned, crossfading stage (~1 screen
   of scroll per scene, plus the empty opening screen). */
.js .journey { height: 760vh; }
.js .journey-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
.js .scenes { position: absolute; inset: 0; }
.js .scene {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  /* Anchor the block from a fixed top so the date + title sit at the same place
     in every scene (aligned as you scroll); longer copy just extends lower
     instead of pushing the heading up. */
  justify-content: flex-start;
  align-items: center;
  text-align: center;
  padding: 41vh 1.6rem 0;
  opacity: 0;
  visibility: hidden;
}
.js .scene-ghost { z-index: 0; }
.js .scene-date, .js .scene-title, .js .scene-text { position: relative; z-index: 1; }
.js .scene-text { margin-inline: auto; }

/* Ported from the original site: "Scroll to explore" beside a slow pulsing line,
   sitting low in the first (deliberately empty) screen of the journey. */
.journey-prompt {
  position: absolute;
  left: 50%;
  bottom: 6.5vh;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  transform: translateX(-50%);
  opacity: 1;
  transition: opacity 0.35s ease, transform 0.35s ease;
  will-change: opacity, transform;
  pointer-events: none;
  z-index: 2;
}
.journey-prompt.is-hidden {
  opacity: 0;
  transform: translate(-50%, -0.65rem);
}
.journey-prompt-mark {
  width: 2.6rem;
  height: 1px;
  background: var(--accent-text);
  transition: background 0.35s ease;
  animation: promptPulse 1.6s ease-in-out infinite;
}
@keyframes promptPulse {
  0%,
  100% {
    opacity: 0.28;
    transform: scaleX(0.45);
    transform-origin: left;
  }
  50% {
    opacity: 0.9;
    transform: scaleX(1);
    transform-origin: left;
  }
}
html:not(.js) .journey-prompt { display: none; }

.journey-rail {
  position: absolute;
  right: 2.2rem;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  z-index: 2;
}
.journey-rail-line {
  width: 1px;
  height: 34vh;
  background: var(--line);
  overflow: hidden;
}
.journey-rail-line i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: top;
  transition: background 0.35s ease;
}
.journey-rail-year {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  color: var(--accent-text);
  writing-mode: vertical-rl;
  transition: color 0.35s ease;
}

/* ------------------------------------------------------- contact + orbs */
.contact-orb {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 45;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 1.1rem 0.15rem color-mix(in srgb, var(--accent) 60%, transparent);
  pointer-events: none;
  transition: opacity 0.32s ease;
}
.contact-trail {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 44;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
  pointer-events: none;
  filter: blur(1px);
}

/* --------------------------------------------------------------- cursor */
/* Ported from the original site: a thin ring on top of everything, pointer-events
   off so it never blocks the orbs or links. JS drives its transform (position +
   scale); CSS only handles the colour and the fade in/out. */
.cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  width: 38px;
  height: 38px;
  border: 2.25px solid var(--accent);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s ease, border-color 0.3s ease;
  will-change: transform;
}
.cursor-ring.is-visible {
  opacity: 0.55;
}
/* On click the ring "implodes" (the canvas effect takes over): hide it instantly,
   then removing the class lets .is-visible fade it back in over the transition. */
.cursor-ring.is-imploding {
  opacity: 0;
  transition: none;
}

/* ------------------------------------------------------------ responsive */
@media (max-width: 900px) {
  :root { --orb-x: 50vw; --orb-y: 52vh; }
}
@media (max-width: 640px) {
  .chrome { padding: 1rem 1.1rem; }
  .nav { gap: 0.8rem; }
  .nav-tabs { gap: 0.8rem; }
  .nav-index { display: none; }
  .status-place, .status-place + .status-sep { display: none; }
  .wordmark-tail { display: none; }
  .field { height: 26vh; }
  .type { width: 3rem; height: 3rem; }
  .pick-hint { top: 24vh; bottom: auto; }
  .journey-rail { right: 1rem; }
  .throne-label {
    left: 50%;
    right: auto;
    top: 6.4rem;
    transform: translateX(-50%);
    align-items: center;
  }
}

/* -------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  .grain, .caret, .journey-prompt-mark, .throne-ring, .throne-ring-b,
  .hero-orbit, .hero-orbit-b,
  .igniter.is-live.orb-intro, .igniter.is-collapsing, .charge-readout.is-critical,
  .social {
    animation: none !important;
  }
  .hero-orbits { opacity: 0.6; }
  .js .journey { height: auto; }
  .js .journey-stage { position: static; height: auto; overflow: visible; padding: 9rem 1.6rem 6rem; max-width: 46rem; margin-inline: auto; }
  .js .scenes { position: static; }
  .js .scene {
    position: relative;
    inset: auto;
    opacity: 1 !important;
    visibility: visible !important;
    display: block;
    text-align: left;
    padding: 0;
    margin-bottom: 5.5rem;
  }
  .js .scene-text { margin: 0; }
  .js .journey-prompt, .js .journey-rail { display: none; }
}
