{"project":{"id":"zKV5StZ","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"SKADI — Deep Ice Observatory","thumbnail":"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","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>SKADI · Deep Ice Neutrino Observatory</title>\n  <meta name=\"description\" content=\"A scroll descent from the Antarctic surface to bedrock at 2,450 m, through a cubic kilometre of instrumented ice. A fictional observatory, rendered live.\">\n  <meta property=\"og:title\" content=\"SKADI · Deep Ice Neutrino Observatory\">\n  <meta property=\"og:description\" content=\"Descend 2,450 m through Antarctic ice to a buried telescope. A fictional observatory, rendered live in WebGL.\">\n  <meta property=\"og:type\" content=\"website\">\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n  <link href=\"https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,100..900&family=Fragment+Mono&display=swap\" rel=\"stylesheet\">\n  <link rel=\"stylesheet\" href=\"css/styles.css\">\n  <script src=\"https://unpkg.com/lucide@latest/dist/umd/lucide.js\" defer></script>\n  <script type=\"importmap\">\n  {\n    \"imports\": {\n      \"three\": \"https://cdn.jsdelivr.net/npm/three@0.169.0/build/three.module.js\",\n      \"three/addons/\": \"https://cdn.jsdelivr.net/npm/three@0.169.0/examples/jsm/\"\n    }\n  }\n  </script>\n</head>\n<body>\n  <a class=\"skip-link\" href=\"#ice\">Skip to content</a>\n\n  <canvas id=\"scene\" aria-hidden=\"true\"></canvas>\n\n  <header class=\"site-head\">\n    <a class=\"site-head__mark\" href=\"#top\">SKADI</a>\n    <nav class=\"site-head__nav\" aria-label=\"Sections\">\n      <a href=\"#array\">the instrument</a>\n      <a href=\"#events\">events</a>\n      <a href=\"#why\">why neutrinos</a>\n    </nav>\n  </header>\n\n  <div class=\"depth-gauge\" aria-hidden=\"true\">\n    <div class=\"depth-gauge__rail\">\n      <div class=\"depth-gauge__marker\" id=\"depthMarker\"></div>\n    </div>\n    <div class=\"depth-gauge__readout\" id=\"depthReadout\">0000 m</div>\n  </div>\n\n  <main id=\"top\">\n    <section class=\"hero\">\n      <div class=\"hero__block\">\n        <h1 class=\"hero__title\">SKADI</h1>\n        <p class=\"hero__sub\">Deep Ice Neutrino Observatory</p>\n        <p class=\"hero__meta\"><span>74°35′S 11°13′E ·</span> <span>Dronning Maud Land, Antarctica ·</span> <span>est. 2031</span></p>\n        <p class=\"hero__lead\">A cubic kilometre of glacial ice, drilled, instrumented, and listening for the faintest light in the universe.</p>\n      </div>\n      <a class=\"hero__cue\" href=\"#ice\">\n        <i data-lucide=\"chevrons-down\" aria-hidden=\"true\"></i>\n        <span>Begin descent</span>\n      </a>\n    </section>\n\n    <section id=\"ice\" class=\"section\" tabindex=\"-1\">\n      <div class=\"section__inner reveal\">\n        <p class=\"depth-mark\">depth 0120 m</p>\n        <h2>The clearest solid on Earth</h2>\n        <p>At the surface, snow. A hundred and twenty metres down, the weight of centuries has pressed it into glass. Below 1,400 metres the trapped air is gone, squeezed into the crystal itself, and the ice becomes the clearest solid on Earth: a photon can travel a hundred metres before anything touches it.</p>\n        <p>We did not pour a detector. We found one, ninety thousand years old, and moved in.</p>\n      </div>\n    </section>\n\n    <section id=\"array\" class=\"section section--right\">\n      <div class=\"section__inner reveal\">\n        <p class=\"depth-mark\">depth 1450 m</p>\n        <h2>312 strings. 18,720 eyes.</h2>\n        <p>Each string descends through a hot-water borehole and freezes into place carrying 60 optical modules: glass spheres rated to the weight of two and a half kilometres of ice, each able to register a single photon.</p>\n        <p>When a neutrino interacts nearby, the charged particle it produces outruns light in the ice and drags a cone of blue Cherenkov radiation behind it. The array reads the cone's arrival nanosecond by nanosecond and reconstructs where it came from, to within half a degree.</p>\n        <dl class=\"spec\">\n          <div class=\"spec__row\"><dt>Instrumented volume</dt><dd>1.02 km³</dd></div>\n          <div class=\"spec__row\"><dt>Strings</dt><dd>312</dd></div>\n          <div class=\"spec__row\"><dt>Optical modules</dt><dd>18,720</dd></div>\n          <div class=\"spec__row\"><dt>Depth band</dt><dd>1,450 – 2,450 m</dd></div>\n          <div class=\"spec__row\"><dt>Timing resolution</dt><dd>1.7 ns</dd></div>\n        </dl>\n      </div>\n    </section>\n\n    <section id=\"events\" class=\"section section--wide\">\n      <div class=\"section__inner section__inner--wide reveal\">\n        <p class=\"depth-mark\">depth 1950 m</p>\n        <h2>Last night, read from below</h2>\n        <p class=\"section__copy\">Skadi records around 275 neutrino candidates a day. Most are born in our own atmosphere. A few each month are not, and those are the ones worth the drilling: messengers from blazars, tidal disruptions, and places that do not have names yet.</p>\n        <div class=\"event-panel\">\n          <figure class=\"event-display\">\n            <canvas id=\"eventCanvas\" width=\"640\" height=\"640\" role=\"img\" aria-label=\"Event display loading\"></canvas>\n            <figcaption class=\"event-display__caption\" id=\"eventCaption\">Reconstructing…</figcaption>\n          </figure>\n          <div class=\"event-side\">\n            <table class=\"event-log\">\n              <caption class=\"event-log__caption\">Recent candidates, UTC</caption>\n              <thead>\n                <tr><th scope=\"col\">time</th><th scope=\"col\">energy</th><th scope=\"col\">zenith</th><th scope=\"col\">class</th></tr>\n              </thead>\n              <tbody id=\"eventLogBody\"></tbody>\n            </table>\n            <button type=\"button\" class=\"btn\" id=\"nextEvent\">Show next event</button>\n          </div>\n        </div>\n      </div>\n    </section>\n\n    <section id=\"why\" class=\"section\">\n      <div class=\"section__inner reveal\">\n        <p class=\"depth-mark\">depth 2200 m</p>\n        <h2>The only honest messenger</h2>\n        <p>Light scatters. Charged particles spiral in magnetic fields and forget where they started. A neutrino crosses the universe in a straight line, through gas, dust, and entire galaxies, and arrives carrying the address of whatever made it.</p>\n        <p>It also, almost always, leaves without a word. A hundred billion neutrinos pass through your thumbnail every second; the ice catches a handful a day. Skadi exists to be watching when one of them flinches.</p>\n      </div>\n    </section>\n\n    <section id=\"bedrock\" class=\"section section--bedrock\">\n      <div class=\"section__inner reveal\">\n        <p class=\"depth-mark\">depth 2450 m</p>\n        <h2>Bedrock</h2>\n        <p>The drill stopped here on 14 January 2031. Above this point: ninety thousand years of compressed sky, and the largest telescope ever buried by hand.</p>\n      </div>\n      <footer class=\"site-foot reveal\">\n        <p class=\"site-foot__line\">Skadi Deep Ice Neutrino Observatory · 74°35′S 11°13′E · 2,450 m to bedrock</p>\n        <p class=\"site-foot__line\">Operated by the Skadi Collaboration: 14 institutes, 9 countries, 41 winter-overs.</p>\n        <p class=\"site-foot__line site-foot__line--muted\">A fiction. Built as a love letter to IceCube and Super-Kamiokande, and to the people who winter over.</p>\n        <p class=\"site-foot__line site-foot__line--muted\">Made in blnq.studio</p>\n      </footer>\n    </section>\n  </main>\n\n  <script type=\"module\" src=\"js/main.js\"></script>\n</body>\n</html>\n"},{"name":"PRODUCT.md","content":"# Product\n\n## Register\n\nbrand\n\n## Users\n\nPeople the author shares the link with: designers, developers, and friends evaluating what a browser-built page can do. They arrive cold, on desktop mostly, and decide within seconds whether to keep scrolling.\n\n## Product Purpose\n\nA cinematic, shareable landing page for SKADI, a fictional neutrino observatory buried in Antarctic ice. The page is a scroll-driven descent from the polar surface to bedrock at 2,450 m. Success: the visitor scrolls to the bottom and asks \"how was this made?\"\n\n## Brand Personality\n\nCold, precise, monumental. The voice of a scientific instrument's logbook: factual sentences, real units, no hype. Wonder comes from the subject, not from adjectives.\n\n## Anti-references\n\n- Generic SaaS landing grammar: hero metrics, feature card grids, eyebrow labels above every section.\n- Crypto/web3 neon gradients and grids-to-horizon.\n- Sci-fi HUD costume: fake terminals, scanlines, crosshairs.\n- Space-nebula AI defaults: purple cosmos gradients.\n\n## Design Principles\n\n1. The effects are the subject. Every canvas effect depicts something real about the observatory (aurora, ice, sensors, Cherenkov light), never decoration.\n2. Scroll is descent. Page position maps to physical depth; the depth gauge never lies.\n3. Instrument voice. Numbers, units, coordinates. Copy reads like a logbook, not a brochure.\n4. Darkness is the material. Light is spent sparingly so each photon matters.\n5. Max spectacle, honest fallback. Push the GPU on desktop; reduced-motion users get a still, beautiful page.\n\n## Accessibility & Inclusion\n\n- Canvas layers are decorative and aria-hidden; all information also exists as text.\n- prefers-reduced-motion: no smooth-scroll hijack, no auto-firing events, no idle camera drift.\n- Body text contrast ≥ 7:1 on the near-black surface; muted text ≥ 4.5:1.\n- Semantic HTML, keyboard-reachable controls, visible focus.\n"},{"name":"README.md","content":"# SKADI · Deep Ice Neutrino Observatory\n\nA fictional neutrino observatory buried in Antarctic ice, told as a scroll-driven\ndescent from the polar surface to bedrock at 2,450 m. Built live in blnq.studio.\n\n## How it works\n\n- `/index.html` — the page: hero, five descent sections, event display, footer.\n- `/js/scene.js` — the WebGL descent (Three.js via CDN): aurora shader plane,\n  clamped soft-particle snow and trapped-air bubbles, a 312-string / 18,720-sensor\n  instanced detector array, simulated Cherenkov cascade events with an\n  amber-to-blue timing gradient, atmosphere shell, and UnrealBloom post.\n- `/js/shaders.js` — GLSL for the aurora curtains (fbm noise).\n- `/js/events.js` — 2D canvas event display: top view of the array, hits sized by\n  amplitude and colored by arrival time, plus the candidate log.\n- `/js/main.js` — one rAF loop: native scroll position drives the camera,\n  the depth gauge, and section reveals. No scroll hijacking.\n\nScroll progress maps to physical depth via camera keyframes; the depth gauge on\nthe right always tells the truth. `window.__skadiProgress` (0–1) can be set in\nthe console to inspect any depth without scrolling.\n\nReduced motion: no auto-firing events, no idle camera drift, no entrance\nanimations; the page stays fully readable.\n\nA fiction, built as a love letter to IceCube and Super-Kamiokande.\n"}],"folders":[{"folder":"css","files":[{"name":"styles.css","content":"/* SKADI · Deep Ice Neutrino Observatory\n   Palette: glacial near-black, Cherenkov blue, photomultiplier amber. OKLCH only. */\n\n:root {\n  color-scheme: dark;\n\n  --bg: oklch(0.09 0.011 235);\n  --surface: oklch(0.13 0.014 235);\n  --line: oklch(0.26 0.02 235);\n  --ink: oklch(0.96 0.008 230);\n  --body: oklch(0.88 0.012 232);\n  --muted: oklch(0.72 0.02 232);\n  --cherenkov: oklch(0.75 0.13 230);\n  --cherenkov-dim: oklch(0.45 0.08 232);\n  --amber: oklch(0.78 0.13 75);\n\n  --font-sans: \"Archivo\", system-ui, sans-serif;\n  --font-mono: \"Fragment Mono\", ui-monospace, monospace;\n\n  --z-canvas: 0;\n  --z-content: 1;\n  --z-chrome: 10;\n  --z-skip: 100;\n}\n\n* { box-sizing: border-box; }\n\nhtml {\n  scroll-behavior: smooth;\n}\n\nbody {\n  margin: 0;\n  background: var(--bg);\n  color: var(--body);\n  font-family: var(--font-sans);\n  font-size: 1.0625rem;\n  line-height: 1.7;\n  -webkit-font-smoothing: antialiased;\n}\n\n::selection {\n  background: var(--amber);\n  color: oklch(0.15 0.02 75);\n}\n\n:focus-visible {\n  outline: 2px solid var(--amber);\n  outline-offset: 3px;\n  border-radius: 2px;\n}\n\n/* skip-link target: focus lands programmatically, never show a ring on the section */\nsection:focus { outline: none; }\n\n/* ---------- fixed scene ---------- */\n\n#scene {\n  position: fixed;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n  z-index: var(--z-canvas);\n}\n\nmain {\n  position: relative;\n  z-index: var(--z-content);\n}\n\n/* ---------- chrome ---------- */\n\n.skip-link {\n  position: fixed;\n  z-index: var(--z-skip);\n  top: 0.75rem;\n  left: 0.75rem;\n  padding: 0.5rem 0.9rem;\n  background: var(--surface);\n  color: var(--ink);\n  font-family: var(--font-mono);\n  font-size: 0.8rem;\n  text-decoration: none;\n  border: 1px solid var(--line);\n  transform: translateY(-300%);\n}\n.skip-link:focus-visible { transform: none; }\n\n.site-head {\n  position: fixed;\n  z-index: var(--z-chrome);\n  inset: 0 0 auto 0;\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n  padding: 1.1rem clamp(1.25rem, 4vw, 2.5rem);\n  background: linear-gradient(to bottom, oklch(0.07 0.011 235 / 0.85), oklch(0.07 0.011 235 / 0));\n  pointer-events: none;\n}\n.site-head a { pointer-events: auto; }\n\n.site-head__mark {\n  font-family: var(--font-mono);\n  font-size: 0.85rem;\n  letter-spacing: 0.22em;\n  color: var(--ink);\n  text-decoration: none;\n}\n\n.site-head__nav {\n  display: flex;\n  gap: 1.75rem;\n}\n.site-head__nav a {\n  font-family: var(--font-mono);\n  font-size: 0.78rem;\n  color: var(--muted);\n  text-decoration: none;\n  transition: color 160ms ease-out;\n  padding: 0.75rem 0.25rem;\n  margin: -0.75rem -0.25rem;\n}\n.site-head__nav a:hover { color: var(--ink); }\n\n/* ---------- depth gauge ---------- */\n\n.depth-gauge {\n  position: fixed;\n  z-index: var(--z-chrome);\n  right: clamp(1rem, 2.5vw, 2rem);\n  top: 50%;\n  transform: translateY(-50%);\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 0.75rem;\n}\n\n.depth-gauge__rail {\n  position: relative;\n  width: 9px;\n  height: 44vh;\n  background:\n    repeating-linear-gradient(to bottom,\n      var(--line) 0, var(--line) 1px,\n      transparent 1px, transparent calc(44vh / 10));\n  border-right: 1px solid var(--line);\n}\n\n.depth-gauge__marker {\n  position: absolute;\n  right: -1px;\n  top: 0;\n  width: 14px;\n  height: 2px;\n  background: var(--amber);\n  box-shadow: 0 0 8px oklch(0.78 0.13 75 / 0.7);\n}\n\n.depth-gauge__readout {\n  font-family: var(--font-mono);\n  font-size: 0.72rem;\n  color: var(--muted);\n  font-variant-numeric: tabular-nums;\n}\n\n/* ---------- hero ---------- */\n\n.hero {\n  position: relative;\n  min-height: 100svh;\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-end;\n  padding: clamp(1.5rem, 5vw, 4rem);\n  padding-bottom: 18svh;\n}\n\n.hero__title {\n  margin: 0;\n  font-size: clamp(3.25rem, 10vw, 6rem);\n  font-weight: 800;\n  font-stretch: 125%;\n  letter-spacing: 0.05em;\n  line-height: 0.95;\n  color: var(--ink);\n  text-shadow: 0 2px 40px oklch(0.05 0.01 235 / 0.9);\n}\n\n.hero__sub {\n  margin: 0.9rem 0 0;\n  font-size: clamp(1.1rem, 2.2vw, 1.4rem);\n  font-weight: 500;\n  font-stretch: 110%;\n  color: var(--ink);\n}\n\n.hero__meta {\n  margin: 0.75rem 0 0;\n  font-family: var(--font-mono);\n  font-size: 0.8rem;\n  color: var(--amber);\n}\n.hero__meta span { white-space: nowrap; }\n\n.hero__lead {\n  margin: 1.5rem 0 0;\n  max-width: 42ch;\n  font-size: 1.125rem;\n  color: var(--body);\n  text-shadow: 0 1px 18px oklch(0.05 0.01 235 / 0.8);\n}\n\n.hero__cue {\n  position: absolute;\n  left: 50%;\n  bottom: 2.25rem;\n  transform: translateX(-50%);\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 0.35rem;\n  font-family: var(--font-mono);\n  font-size: 0.75rem;\n  color: var(--muted);\n  text-decoration: none;\n  animation: cue-float 2.6s ease-in-out infinite;\n  transition: color 160ms ease-out;\n}\n.hero__cue:hover { color: var(--ink); }\n.hero__cue svg { width: 18px; height: 18px; }\n\n@keyframes cue-float {\n  0%, 100% { transform: translateX(-50%) translateY(0); }\n  50% { transform: translateX(-50%) translateY(7px); }\n}\n\n/* ---------- sections ---------- */\n\n.section {\n  display: grid;\n  align-items: center;\n  min-height: 115vh;\n  padding: clamp(4rem, 14vh, 10rem) clamp(1.5rem, 7vw, 8rem);\n}\n\n.section__inner {\n  max-width: 36rem;\n  padding: clamp(1rem, 3vw, 2.5rem);\n  background: radial-gradient(130% 130% at 50% 50%,\n    oklch(0.08 0.012 235 / 0.65) 0%,\n    oklch(0.08 0.012 235 / 0) 74%);\n}\n\n.section--right { justify-items: end; }\n\n.section--wide .section__inner--wide { max-width: 62rem; justify-self: center; }\n\n.section h2 {\n  margin: 0.6rem 0 1.25rem;\n  font-size: clamp(2.1rem, 4.5vw, 3.4rem);\n  font-weight: 750;\n  font-stretch: 115%;\n  line-height: 1.05;\n  letter-spacing: -0.015em;\n  color: var(--ink);\n  text-wrap: balance;\n}\n\n.section p {\n  margin: 0 0 1.1rem;\n  max-width: 62ch;\n  text-wrap: pretty;\n}\n.section p:last-child { margin-bottom: 0; }\n\n.depth-mark {\n  margin: 0;\n  font-family: var(--font-mono);\n  font-size: 0.78rem;\n  color: var(--amber);\n  font-variant-numeric: tabular-nums;\n}\n\n/* ---------- spec sheet ---------- */\n\n.spec {\n  margin: 2.25rem 0 0;\n  max-width: 26rem;\n  font-family: var(--font-mono);\n  font-size: 0.8rem;\n}\n\n.spec__row {\n  display: flex;\n  justify-content: space-between;\n  gap: 2rem;\n  padding: 0.55rem 0;\n  border-top: 1px solid var(--line);\n}\n.spec__row:last-child { border-bottom: 1px solid var(--line); }\n\n.spec dt { color: var(--muted); }\n.spec dd { margin: 0; color: var(--ink); font-variant-numeric: tabular-nums; }\n\n/* ---------- events ---------- */\n\n.section__copy { max-width: 58ch; }\n\n.event-panel {\n  display: grid;\n  grid-template-columns: minmax(280px, 460px) minmax(260px, 1fr);\n  gap: clamp(1.5rem, 4vw, 3rem);\n  align-items: start;\n  margin-top: 2.5rem;\n}\n\n.event-display {\n  margin: 0;\n}\n\n#eventCanvas {\n  display: block;\n  width: 100%;\n  height: auto;\n  background: radial-gradient(100% 100% at 50% 42%,\n    oklch(0.12 0.018 235) 0%, oklch(0.075 0.012 235) 100%);\n  border: 1px solid var(--line);\n}\n\n.event-display__caption {\n  margin-top: 0.7rem;\n  font-family: var(--font-mono);\n  font-size: 0.75rem;\n  line-height: 1.6;\n  color: var(--muted);\n}\n\n.event-log {\n  width: 100%;\n  border-collapse: collapse;\n  font-family: var(--font-mono);\n  font-size: 0.78rem;\n  font-variant-numeric: tabular-nums;\n}\n\n.event-log__caption {\n  text-align: left;\n  padding-bottom: 0.6rem;\n  color: var(--ink);\n  font-size: 0.8rem;\n}\n\n.event-log th {\n  text-align: left;\n  font-weight: 400;\n  color: var(--muted);\n  padding: 0.45rem 0.75rem 0.45rem 0;\n  border-bottom: 1px solid var(--line);\n}\n\n.event-log td {\n  padding: 0.5rem 0.75rem 0.5rem 0;\n  border-bottom: 1px solid oklch(0.2 0.016 235);\n  color: var(--body);\n}\n\n.event-log tr.is-new td { color: var(--amber); }\n\n.btn {\n  margin-top: 1.5rem;\n  padding: 0.7rem 1.3rem;\n  font-family: var(--font-mono);\n  font-size: 0.8rem;\n  color: var(--ink);\n  background: transparent;\n  border: 1px solid var(--line);\n  cursor: pointer;\n  transition: border-color 160ms ease-out, background 160ms ease-out;\n}\n.btn:hover {\n  border-color: var(--cherenkov);\n  background: oklch(0.75 0.13 230 / 0.08);\n}\n.btn:active {\n  border-color: var(--cherenkov);\n  background: oklch(0.75 0.13 230 / 0.16);\n}\n\n/* ---------- bedrock / footer ---------- */\n\n.section--bedrock {\n  min-height: 130vh;\n  align-content: end;\n  padding-bottom: 4rem;\n}\n\n.site-foot {\n  margin-top: clamp(4rem, 12vh, 8rem);\n  padding-top: 1.5rem;\n  border-top: 1px solid var(--line);\n}\n\n.site-foot__line {\n  margin: 0.35rem 0;\n  font-family: var(--font-mono);\n  font-size: 0.78rem;\n  color: var(--body);\n}\n\n.site-foot__line--muted { color: var(--muted); }\n\n/* ---------- reveal (default state is fully visible; the class only plays an entrance) ---------- */\n\n@keyframes reveal-rise {\n  from { opacity: 0; transform: translateY(26px); }\n  to { opacity: 1; transform: none; }\n}\n\n.reveal--in {\n  animation: reveal-rise 800ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n/* ---------- responsive ---------- */\n\n@media (max-width: 860px) {\n  .event-panel { grid-template-columns: 1fr; }\n}\n\n@media (max-width: 720px) {\n  .depth-gauge { display: none; }\n  .site-head__nav { display: none; }\n  .section { min-height: 100vh; }\n  .section--right { justify-items: start; }\n}\n\n/* ---------- reduced motion ---------- */\n\n@media (prefers-reduced-motion: reduce) {\n  html { scroll-behavior: auto; }\n  .hero__cue { animation: none; }\n  .reveal--in { animation: none; }\n  * { transition-duration: 1ms !important; }\n}\n"}],"folders":[]},{"folder":"js","files":[{"name":"events.js","content":"// 2D canvas event display: top view of the array, hits colored by arrival time\n// (photomultiplier amber = early, Cherenkov blue = late), plus the candidate log.\n\nconst SPACING = 1;\nconst STRING_COUNT = 312;\n\nfunction hexGrid() {\n  const pts = [];\n  const R = 10;\n  for (let q = -R; q <= R; q++) {\n    for (let r = -R; r <= R; r++) {\n      const s = -q - r;\n      if (Math.abs(s) > R) continue;\n      const x = SPACING * (q + r / 2);\n      const z = SPACING * (r * Math.sqrt(3) / 2);\n      pts.push([x, z, Math.hypot(x, z)]);\n    }\n  }\n  pts.sort((a, b) => a[2] - b[2]);\n  return pts.slice(0, STRING_COUNT).map(([x, z]) => [x, z]);\n}\n\nconst STRINGS = hexGrid();\nconst MAX_R = Math.max(...STRINGS.map(([x, z]) => Math.hypot(x, z)));\n\nconst CLASSES = [\n  { name: 'atm. muon', weight: 0.55 },\n  { name: 'atm. neutrino', weight: 0.33 },\n  { name: 'astro candidate', weight: 0.12 },\n];\n\nlet counter = 0;\n\nfunction pad(n, w = 2) { return String(n).padStart(w, '0'); }\n\nfunction pick() {\n  const r = Math.random();\n  let acc = 0;\n  for (const c of CLASSES) { acc += c.weight; if (r < acc) return c.name; }\n  return CLASSES[0].name;\n}\n\nexport function generateEvent(minutesAgo) {\n  counter += 1;\n  const now = new Date(Date.now() - minutesAgo * 60000);\n  const cls = pick();\n  const energy = cls === 'astro candidate'\n    ? 40 + Math.pow(10, Math.random() * 2.2)\n    : 0.4 + Math.pow(10, Math.random() * 1.6);\n  const zenith = Math.random() * 180;\n  const azimuth = Math.random() * Math.PI * 2;\n  const impact = (Math.random() - 0.5) * MAX_R * 0.9;\n\n  // hits in top view: strings near the track line\n  const dx = Math.cos(azimuth), dz = Math.sin(azimuth);\n  const ox = -dz * impact, oz = dx * impact;\n  const hits = [];\n  let tMin = Infinity, tMax = -Infinity;\n  const reach = MAX_R * (0.16 + Math.min(energy / 600, 1) * 0.22);\n  for (let i = 0; i < STRINGS.length; i++) {\n    const [x, z] = STRINGS[i];\n    const wx = x - ox, wz = z - oz;\n    const tp = wx * dx + wz * dz;\n    const px = wx - dx * tp, pz = wz - dz * tp;\n    const perp = Math.hypot(px, pz);\n    if (perp > reach) continue;\n    const t = tp + perp * 0.4;\n    hits.push({ i, t, amp: Math.exp(-perp / (reach * 0.55)) });\n    tMin = Math.min(tMin, t);\n    tMax = Math.max(tMax, t);\n  }\n  for (const h of hits) h.mix = (h.t - tMin) / Math.max(tMax - tMin, 0.001);\n\n  return {\n    id: `SK-26-${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}${String.fromCharCode(65 + (counter % 26))}`,\n    time: `${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}:${pad(now.getUTCSeconds())}`,\n    energy, zenith, azimuth, impact, hits, cls,\n    chi2: (0.8 + Math.random() * 0.7).toFixed(2),\n  };\n}\n\nfunction fmtEnergy(e) {\n  return e >= 1 ? `${e.toFixed(e < 10 ? 1 : 0)} TeV` : `${(e * 1000).toFixed(0)} GeV`;\n}\n\nexport function drawEvent(ctx, ev, size, alpha) {\n  const c = size / 2;\n  const scale = (size * 0.44) / MAX_R;\n\n  ctx.clearRect(0, 0, size, size);\n  ctx.globalAlpha = alpha;\n\n  // array footprint\n  ctx.fillStyle = 'oklch(0.32 0.025 235)';\n  for (const [x, z] of STRINGS) {\n    ctx.beginPath();\n    ctx.arc(c + x * scale, c + z * scale, 1.4, 0, Math.PI * 2);\n    ctx.fill();\n  }\n\n  // track line\n  const dx = Math.cos(ev.azimuth), dz = Math.sin(ev.azimuth);\n  const ox = c + (-dz * ev.impact) * scale, oz = c + (dx * ev.impact) * scale;\n  const L = size * 0.62;\n  const grad = ctx.createLinearGradient(\n    ox - dx * L, oz - dz * L, ox + dx * L, oz + dz * L\n  );\n  grad.addColorStop(0, 'oklch(0.78 0.13 75 / 0.85)');\n  grad.addColorStop(1, 'oklch(0.75 0.13 230 / 0.85)');\n  ctx.strokeStyle = grad;\n  ctx.lineWidth = 1.5;\n  ctx.setLineDash([7, 5]);\n  ctx.beginPath();\n  ctx.moveTo(ox - dx * L, oz - dz * L);\n  ctx.lineTo(ox + dx * L, oz + dz * L);\n  ctx.stroke();\n  ctx.setLineDash([]);\n\n  // arrowhead\n  ctx.fillStyle = 'oklch(0.75 0.13 230)';\n  ctx.beginPath();\n  const hx = ox + dx * L, hz = oz + dz * L;\n  ctx.moveTo(hx, hz);\n  ctx.lineTo(hx - dx * 12 - dz * 5, hz - dz * 12 + dx * 5);\n  ctx.lineTo(hx - dx * 12 + dz * 5, hz - dz * 12 - dx * 5);\n  ctx.fill();\n\n  // hits, early amber to late blue, radius by amplitude\n  for (const h of [...ev.hits].sort((a, b) => b.amp - a.amp)) {\n    const [x, z] = STRINGS[h.i];\n    const px = c + x * scale, pz = c + z * scale;\n    const r = 3 + h.amp * 21;\n    const hue = 75 + (230 - 75) * h.mix;\n    const chroma = 0.13;\n    ctx.shadowColor = `oklch(0.75 ${chroma} ${hue} / 0.9)`;\n    ctx.shadowBlur = 16;\n    ctx.strokeStyle = `oklch(0.78 ${chroma} ${hue} / 0.95)`;\n    ctx.lineWidth = 1.6;\n    ctx.beginPath();\n    ctx.arc(px, pz, r, 0, Math.PI * 2);\n    ctx.stroke();\n    ctx.shadowBlur = 0;\n    ctx.fillStyle = `oklch(0.85 ${chroma * 0.8} ${hue} / ${0.25 + h.amp * 0.5})`;\n    ctx.beginPath();\n    ctx.arc(px, pz, Math.max(r * 0.32, 1.6), 0, Math.PI * 2);\n    ctx.fill();\n  }\n\n  ctx.globalAlpha = 1;\n}\n\nexport function initEvents({ canvas, caption, logBody, button, reducedMotion }) {\n  const dpr = Math.min(window.devicePixelRatio || 1, 2);\n  const cssSize = 640;\n  canvas.width = cssSize * dpr;\n  canvas.height = cssSize * dpr;\n  const ctx = canvas.getContext('2d');\n  ctx.scale(dpr, dpr);\n\n  let current = null;\n  let fadeRaf = 0;\n\n  function describe(ev) {\n    return `${ev.id} · ${fmtEnergy(ev.energy)} · zenith ${ev.zenith.toFixed(1)}° · `\n      + `${ev.hits.length} strings hit · reconstruction χ²/n ${ev.chi2} · ${ev.cls}`;\n  }\n\n  function addLogRow(ev) {\n    const tr = document.createElement('tr');\n    tr.innerHTML = `<td>${ev.time}</td><td>${fmtEnergy(ev.energy)}</td>`\n      + `<td>${ev.zenith.toFixed(0)}°</td><td>${ev.cls}</td>`;\n    logBody.querySelectorAll('tr').forEach(r => r.classList.remove('is-new'));\n    tr.classList.add('is-new');\n    logBody.prepend(tr);\n    while (logBody.children.length > 6) logBody.lastElementChild.remove();\n  }\n\n  function show(ev, instant) {\n    current = ev;\n    caption.textContent = describe(ev);\n    canvas.setAttribute('aria-label',\n      `Event display: ${fmtEnergy(ev.energy)} ${ev.cls}, zenith ${ev.zenith.toFixed(0)} degrees, ${ev.hits.length} strings hit.`);\n    addLogRow(ev);\n\n    cancelAnimationFrame(fadeRaf);\n    if (instant || reducedMotion) {\n      drawEvent(ctx, ev, cssSize, 1);\n      return;\n    }\n    const t0 = performance.now();\n    const tick = (t) => {\n      const a = Math.min((t - t0) / 600, 1);\n      drawEvent(ctx, ev, cssSize, 0.15 + 0.85 * a);\n      if (a < 1) fadeRaf = requestAnimationFrame(tick);\n    };\n    fadeRaf = requestAnimationFrame(tick);\n  }\n\n  // seed the log with a quiet night; prepend oldest first so newest ends up on top\n  const seed = [34, 21, 13, 8, 3].map(m => generateEvent(m));\n  seed.slice(0, 4).forEach(ev => addLogRow(ev));\n  show(seed[4], true);\n\n  button.addEventListener('click', () => show(generateEvent(0), false));\n\n  if (!reducedMotion) {\n    setInterval(() => {\n      if (!document.hidden) show(generateEvent(0), false);\n    }, 11000);\n  }\n}\n"},{"name":"main.js","content":"// SKADI orchestrator: one rAF drives smooth scroll, the WebGL descent,\n// the depth gauge, reveals, and the event display.\n\nimport { createScene } from './scene.js';\nimport { initEvents } from './events.js';\n\nconst reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\nlet sceneApi;\ntry {\n  sceneApi = createScene(document.getElementById('scene'), { reducedMotion });\n} catch (err) {\n  // No WebGL: the page stays a fully readable static document\n  console.warn('SKADI scene unavailable:', err);\n  sceneApi = { frame() {}, getDepth: () => 0 };\n}\n\n// ---------- main loop (native scrolling; the scene only reads position) ----------\n\nconst readout = document.getElementById('depthReadout');\nconst marker = document.getElementById('depthMarker');\n\nfunction progress() {\n  const max = document.documentElement.scrollHeight - window.innerHeight;\n  return max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0;\n}\n\nlet lastDepthShown = -1;\n\nfunction raf(time) {\n  // __skadiProgress: debug override used to inspect the descent at a fixed depth\n  const p = window.__skadiProgress ?? progress();\n  sceneApi.frame(time, p);\n\n  const depth = Math.round(sceneApi.getDepth());\n  if (depth !== lastDepthShown) {\n    lastDepthShown = depth;\n    readout.textContent = `${String(depth).padStart(4, '0')} m`;\n  }\n  marker.style.top = `calc(${(p * 100).toFixed(2)}% - 1px)`;\n\n  requestAnimationFrame(raf);\n}\nrequestAnimationFrame(raf);\n\n// ---------- section reveals ----------\n\nconst revealEls = document.querySelectorAll('.reveal');\nif (!reducedMotion && 'IntersectionObserver' in window) {\n  const io = new IntersectionObserver((entries) => {\n    for (const en of entries) {\n      if (en.isIntersecting) {\n        en.target.classList.add('reveal--in');\n        io.unobserve(en.target);\n      }\n    }\n  }, { threshold: 0.05, rootMargin: '0px 0px -6% 0px' });\n  revealEls.forEach((el) => io.observe(el));\n}\n\n// ---------- event display ----------\n\ninitEvents({\n  canvas: document.getElementById('eventCanvas'),\n  caption: document.getElementById('eventCaption'),\n  logBody: document.getElementById('eventLogBody'),\n  button: document.getElementById('nextEvent'),\n  reducedMotion,\n});\n\n// ---------- icons ----------\n\nfunction icons() { window.lucide?.createIcons(); }\nif (document.readyState === 'complete') icons();\nelse window.addEventListener('load', icons);\n"},{"name":"scene.js","content":"// SKADI descent scene: aurora surface, ice interior, detector array, bedrock.\n// One fixed WebGL canvas; scroll progress drives a keyframed camera down 2,450 m.\n\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/addons/postprocessing/RenderPass.js';\nimport { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';\nimport { OutputPass } from 'three/addons/postprocessing/OutputPass.js';\nimport { auroraVertex, auroraFragment } from './shaders.js';\n\nconst ARRAY_TOP = -1450;\nconst ARRAY_BOTTOM = -2450;\nconst BEDROCK = -2470;\nconst DOMS_PER_STRING = 60;\nconst STRING_COUNT = 312;\nconst STRING_SPACING = 72;\n\n// Camera path: {p: scroll progress, pos, tgt}\nconst KEYS = [\n  { p: 0.00, pos: [0, 16, 140], tgt: [0, 170, -900] },\n  { p: 0.13, pos: [0, -50, 60], tgt: [0, -180, -180] },\n  { p: 0.30, pos: [150, -750, 230], tgt: [0, -1550, 0] },\n  { p: 0.46, pos: [430, -1520, 430], tgt: [0, -1900, 0] },\n  { p: 0.62, pos: [220, -1880, -170], tgt: [-40, -1970, 40] },\n  { p: 0.80, pos: [-270, -2060, 250], tgt: [0, -2160, 0] },\n  { p: 1.00, pos: [0, -2350, 330], tgt: [0, -2470, 0] },\n];\n\n// Clear-color / fog stops by depth (positive metres)\nconst DEPTH_STOPS = [\n  { d: 0, c: [0.018, 0.026, 0.045], fog: 0.00035 },\n  { d: 130, c: [0.045, 0.10, 0.18], fog: 0.0009 },\n  { d: 600, c: [0.012, 0.030, 0.062], fog: 0.0011 },\n  { d: 1500, c: [0.005, 0.010, 0.022], fog: 0.0008 },\n  { d: 2450, c: [0.003, 0.004, 0.008], fog: 0.0007 },\n];\n\nfunction hexGrid() {\n  const pts = [];\n  const R = 10;\n  for (let q = -R; q <= R; q++) {\n    for (let r = -R; r <= R; r++) {\n      const s = -q - r;\n      if (Math.abs(s) > R) continue;\n      const x = STRING_SPACING * (q + r / 2);\n      const z = STRING_SPACING * (r * Math.sqrt(3) / 2);\n      pts.push([x, z, Math.hypot(x, z)]);\n    }\n  }\n  pts.sort((a, b) => a[2] - b[2]);\n  return pts.slice(0, STRING_COUNT).map(([x, z]) => [x, z]);\n}\n\n// Soft round particles with a clamped screen size: points passing right next to\n// the camera must never inflate into giant quads (that reads as full-screen flicker).\nfunction softPoints(positions, { color, size, maxPx, opacity, fadeNear, fadeFar }) {\n  const g = new THREE.BufferGeometry();\n  g.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n  const m = new THREE.ShaderMaterial({\n    transparent: true,\n    depthWrite: false,\n    uniforms: {\n      uColor: { value: new THREE.Color(color) },\n      uSize: { value: size },\n      uMax: { value: maxPx },\n      uOpacity: { value: opacity },\n      uPR: { value: Math.min(window.devicePixelRatio, 2) },\n      uFade: { value: new THREE.Vector2(fadeNear, fadeFar) },\n    },\n    vertexShader: /* glsl */ `\n      uniform float uSize;\n      uniform float uMax;\n      uniform float uPR;\n      varying float vDist;\n      void main() {\n        vec4 mv = modelViewMatrix * vec4(position, 1.0);\n        vDist = -mv.z;\n        gl_PointSize = clamp(uSize * 300.0 / max(vDist, 1.0), 1.0, uMax) * uPR;\n        gl_Position = projectionMatrix * mv;\n      }\n    `,\n    fragmentShader: /* glsl */ `\n      uniform vec3 uColor;\n      uniform float uOpacity;\n      uniform vec2 uFade;\n      varying float vDist;\n      void main() {\n        float d = length(gl_PointCoord - 0.5);\n        float a = smoothstep(0.5, 0.12, d) * uOpacity;\n        a *= smoothstep(uFade.y, uFade.x, vDist);\n        if (a < 0.01) discard;\n        gl_FragColor = vec4(uColor, a);\n      }\n    `,\n  });\n  return new THREE.Points(g, m);\n}\n\nexport function createScene(canvas, { reducedMotion }) {\n  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });\n  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n  renderer.setSize(window.innerWidth, window.innerHeight);\n  renderer.toneMapping = THREE.ACESFilmicToneMapping;\n  renderer.toneMappingExposure = 1.0;\n\n  const scene = new THREE.Scene();\n  scene.fog = new THREE.FogExp2(0x05080d, 0.0005);\n\n  const camera = new THREE.PerspectiveCamera(\n    62, window.innerWidth / window.innerHeight, 1, 12000\n  );\n  camera.position.set(...KEYS[0].pos);\n\n  // ---------- atmosphere shell ----------\n  // The raw clear color renders lighter than fog-processed geometry, so the\n  // atmosphere lives on a BackSide sphere that goes through the same pipeline.\n  const skyMat = new THREE.MeshBasicMaterial({ side: THREE.BackSide });\n  skyMat.fog = false;\n  const sky = new THREE.Mesh(new THREE.SphereGeometry(10500, 24, 16), skyMat);\n  scene.add(sky);\n\n  // ---------- surface: ground, stars, aurora, snow ----------\n\n  const ground = new THREE.Mesh(\n    new THREE.CircleGeometry(6000, 48).rotateX(-Math.PI / 2),\n    new THREE.MeshBasicMaterial({ color: 0x090c11, side: THREE.DoubleSide })\n  );\n  ground.position.y = 0;\n  scene.add(ground);\n\n  {\n    const n = 1600;\n    const pos = new Float32Array(n * 3);\n    for (let i = 0; i < n; i++) {\n      const theta = Math.random() * Math.PI * 2;\n      const phi = Math.acos(Math.random() * 0.95); // upper hemisphere\n      const r = 4200;\n      pos[i * 3] = r * Math.sin(phi) * Math.cos(theta);\n      pos[i * 3 + 1] = r * Math.cos(phi) + 50;\n      pos[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);\n    }\n    // soft round stars, dim enough to stay under the bloom threshold\n    scene.add(softPoints(pos, {\n      color: 0xb6c6dd, size: 36, maxPx: 2.6, opacity: 0.8,\n      fadeNear: 9000, fadeFar: 20000,\n    }));\n  }\n\n  const aurora = new THREE.Mesh(\n    new THREE.PlaneGeometry(10000, 2200),\n    new THREE.ShaderMaterial({\n      vertexShader: auroraVertex,\n      fragmentShader: auroraFragment,\n      uniforms: { uTime: { value: 0 } },\n      transparent: true,\n      blending: THREE.AdditiveBlending,\n      depthWrite: false,\n    })\n  );\n  aurora.position.set(0, 1000, -3300);\n  scene.add(aurora);\n\n  let snow, snowPos;\n  {\n    const n = 1100;\n    snowPos = new Float32Array(n * 3);\n    for (let i = 0; i < n; i++) {\n      snowPos[i * 3] = (Math.random() - 0.5) * 900;\n      snowPos[i * 3 + 1] = Math.random() * 280;\n      snowPos[i * 3 + 2] = (Math.random() - 0.5) * 900;\n    }\n    snow = softPoints(snowPos, {\n      color: 0xcfdcec, size: 1.6, maxPx: 5, opacity: 0.45,\n      fadeNear: 60, fadeFar: 700,\n    });\n    scene.add(snow);\n  }\n\n  // ---------- ice interior: trapped air ----------\n\n  let bubbles;\n  {\n    const n = 3500;\n    const pos = new Float32Array(n * 3);\n    for (let i = 0; i < n; i++) {\n      const a = Math.random() * Math.PI * 2;\n      const r = Math.sqrt(Math.random()) * 650;\n      pos[i * 3] = Math.cos(a) * r;\n      pos[i * 3 + 1] = -60 - Math.random() * 1100;\n      pos[i * 3 + 2] = Math.sin(a) * r;\n    }\n    bubbles = softPoints(pos, {\n      color: 0xa7cce6, size: 2.2, maxPx: 6, opacity: 0.4,\n      fadeNear: 50, fadeFar: 1000,\n    });\n    scene.add(bubbles);\n  }\n\n  // ---------- detector array ----------\n\n  const strings = hexGrid();\n\n  {\n    const verts = new Float32Array(strings.length * 6);\n    strings.forEach(([x, z], i) => {\n      verts.set([x, -40, z, x, ARRAY_BOTTOM, z], i * 6);\n    });\n    const g = new THREE.BufferGeometry();\n    g.setAttribute('position', new THREE.BufferAttribute(verts, 3));\n    scene.add(new THREE.LineSegments(g, new THREE.LineBasicMaterial({\n      color: 0x223240, transparent: true, opacity: 0.26,\n    })));\n  }\n\n  const domCount = strings.length * DOMS_PER_STRING;\n  const domPositions = new Float32Array(domCount * 3);\n  const baseColors = new Float32Array(domCount * 3);\n  const doms = new THREE.InstancedMesh(\n    new THREE.SphereGeometry(1.6, 10, 10),\n    new THREE.MeshBasicMaterial({ color: 0xffffff }),\n    domCount\n  );\n  doms.frustumCulled = false;\n  {\n    const dy = (ARRAY_TOP - ARRAY_BOTTOM) / (DOMS_PER_STRING - 1);\n    const mat = new THREE.Matrix4();\n    const col = new THREE.Color();\n    let i = 0;\n    for (const [x, z] of strings) {\n      for (let j = 0; j < DOMS_PER_STRING; j++, i++) {\n        const yy = ARRAY_TOP - j * Math.abs(dy);\n        mat.setPosition(x, yy, z);\n        doms.setMatrixAt(i, mat);\n        domPositions[i * 3] = x;\n        domPositions[i * 3 + 1] = yy;\n        domPositions[i * 3 + 2] = z;\n        const v = 0.7 + Math.random() * 0.6;\n        col.setRGB(0.016 * v, 0.028 * v, 0.05 * v);\n        doms.setColorAt(i, col);\n        baseColors[i * 3] = col.r;\n        baseColors[i * 3 + 1] = col.g;\n        baseColors[i * 3 + 2] = col.b;\n      }\n    }\n  }\n  scene.add(doms);\n\n  // ---------- bedrock ----------\n\n  const bedrock = new THREE.Mesh(\n    new THREE.CircleGeometry(2400, 48).rotateX(-Math.PI / 2),\n    new THREE.MeshBasicMaterial({ color: 0x161009 })\n  );\n  bedrock.position.y = BEDROCK;\n  scene.add(bedrock);\n\n  let sediment;\n  {\n    const n = 700;\n    const pos = new Float32Array(n * 3);\n    for (let i = 0; i < n; i++) {\n      const a = Math.random() * Math.PI * 2;\n      const r = Math.sqrt(Math.random()) * 1200;\n      pos[i * 3] = Math.cos(a) * r;\n      pos[i * 3 + 1] = BEDROCK + 1 + Math.random() * 6;\n      pos[i * 3 + 2] = Math.sin(a) * r;\n    }\n    const g = new THREE.BufferGeometry();\n    g.setAttribute('position', new THREE.BufferAttribute(pos, 3));\n    sediment = new THREE.Points(g, new THREE.PointsMaterial({\n      color: 0x8a6a36, size: 2.2, sizeAttenuation: true,\n      transparent: true, opacity: 0.4, depthWrite: false,\n    }));\n    scene.add(sediment);\n  }\n\n  // ---------- muon track ----------\n\n  const trackMat = new THREE.MeshBasicMaterial({\n    color: new THREE.Color(0.55, 0.8, 1.2), transparent: true, opacity: 0,\n    blending: THREE.AdditiveBlending, depthWrite: false,\n  });\n  const track = new THREE.Mesh(\n    new THREE.CylinderGeometry(0.9, 0.9, 1, 8, 1, true), trackMat\n  );\n  track.visible = false;\n  scene.add(track);\n\n  const headMat = new THREE.MeshBasicMaterial({ color: new THREE.Color(5, 7, 11) });\n  headMat.toneMapped = false;\n  const head = new THREE.Mesh(new THREE.SphereGeometry(3.2, 12, 12), headMat);\n  head.visible = false;\n  scene.add(head);\n\n  // ---------- post ----------\n\n  const composer = new EffectComposer(renderer);\n  composer.addPass(new RenderPass(scene, camera));\n  const bloom = new UnrealBloomPass(\n    new THREE.Vector2(window.innerWidth, window.innerHeight), 0.9, 0.5, 0.4\n  );\n  composer.addPass(bloom);\n  composer.addPass(new OutputPass());\n\n  // ---------- event simulation ----------\n\n  const TRACK_LEN = 1700;\n  const TRACK_SPEED = 620; // m/s of animation time\n  const AMBER = new THREE.Color(1.9, 1.15, 0.3);\n  const BLUE = new THREE.Color(0.45, 1.1, 2.3);\n\n  let evt = null; // { start, hits: [{i, t, amp, mix}], origin, dir, touched }\n  let nextEventAt = Infinity;\n  let hasArrived = false;\n\n  function spawnEvent(now) {\n    const origin = new THREE.Vector3(\n      (Math.random() - 0.5) * 500,\n      -1650 - Math.random() * 600,\n      (Math.random() - 0.5) * 500\n    );\n    const az = Math.random() * Math.PI * 2;\n    const zen = (15 + Math.random() * 50) * (Math.PI / 180);\n    const dir = new THREE.Vector3(\n      Math.sin(zen) * Math.cos(az), -Math.cos(zen), Math.sin(zen) * Math.sin(az)\n    );\n\n    const hits = [];\n    const w = new THREE.Vector3();\n    let tMin = Infinity, tMax = -Infinity;\n    for (let i = 0; i < domCount; i++) {\n      w.set(\n        domPositions[i * 3] - origin.x,\n        domPositions[i * 3 + 1] - origin.y,\n        domPositions[i * 3 + 2] - origin.z\n      );\n      const tProj = w.dot(dir);\n      if (tProj < -TRACK_LEN / 2 || tProj > TRACK_LEN / 2) continue;\n      const perp2 = w.lengthSq() - tProj * tProj;\n      if (perp2 > 170 * 170) continue;\n      const perp = Math.sqrt(Math.max(perp2, 0));\n      const t = (tProj + TRACK_LEN / 2) / TRACK_SPEED + perp / (TRACK_SPEED * 2.2);\n      hits.push({ i, t, amp: Math.exp(-perp / 85) });\n      tMin = Math.min(tMin, t);\n      tMax = Math.max(tMax, t);\n    }\n    if (hits.length < 12) return; // missed the array, try again soon\n    for (const h of hits) h.mix = (h.t - tMin) / Math.max(tMax - tMin, 0.001);\n\n    evt = { start: now, hits, origin, dir, touched: hits.map(h => h.i) };\n    track.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);\n    track.visible = true;\n    head.visible = true;\n  }\n\n  const tmpColor = new THREE.Color();\n\n  function updateEvent(now) {\n    if (!evt) return;\n    const el = (now - evt.start) / 1000;\n    const travel = Math.min(el * TRACK_SPEED, TRACK_LEN);\n\n    // track geometry: grows along dir from origin - L/2\n    const startPt = evt.dir.clone().multiplyScalar(-TRACK_LEN / 2).add(evt.origin);\n    const mid = evt.dir.clone().multiplyScalar(travel / 2).add(startPt);\n    track.position.copy(mid);\n    track.scale.set(1, Math.max(travel, 1), 1);\n    const lifeFade = Math.max(0, 1 - Math.max(el - TRACK_LEN / TRACK_SPEED, 0) / 1.6);\n    trackMat.opacity = 0.55 * lifeFade;\n    head.position.copy(evt.dir.clone().multiplyScalar(travel).add(startPt));\n    head.visible = travel < TRACK_LEN;\n\n    let alive = lifeFade > 0;\n    for (const h of evt.hits) {\n      const local = el - h.t;\n      tmpColor.setRGB(\n        baseColors[h.i * 3], baseColors[h.i * 3 + 1], baseColors[h.i * 3 + 2]\n      );\n      if (local > 0) {\n        const env = Math.exp(-local * 1.5) * Math.min(local * 16, 1);\n        if (env > 0.01) alive = true;\n        const b = h.amp * env * 2.6;\n        tmpColor.r += (AMBER.r * (1 - h.mix) + BLUE.r * h.mix) * b;\n        tmpColor.g += (AMBER.g * (1 - h.mix) + BLUE.g * h.mix) * b;\n        tmpColor.b += (AMBER.b * (1 - h.mix) + BLUE.b * h.mix) * b;\n      }\n      doms.setColorAt(h.i, tmpColor);\n    }\n    doms.instanceColor.needsUpdate = true;\n\n    if (!alive && el > 2) {\n      // restore and end\n      for (const i of evt.touched) {\n        tmpColor.setRGB(baseColors[i * 3], baseColors[i * 3 + 1], baseColors[i * 3 + 2]);\n        doms.setColorAt(i, tmpColor);\n      }\n      doms.instanceColor.needsUpdate = true;\n      track.visible = false;\n      head.visible = false;\n      evt = null;\n      nextEventAt = now + 6000 + Math.random() * 5000;\n    }\n  }\n\n  // ---------- camera path ----------\n\n  const curPos = new THREE.Vector3(...KEYS[0].pos);\n  const curTgt = new THREE.Vector3(...KEYS[0].tgt);\n  const keyPos = new THREE.Vector3();\n  const keyTgt = new THREE.Vector3();\n\n  function sampleKeys(p) {\n    let a = KEYS[0], b = KEYS[KEYS.length - 1];\n    for (let i = 0; i < KEYS.length - 1; i++) {\n      if (p >= KEYS[i].p && p <= KEYS[i + 1].p) { a = KEYS[i]; b = KEYS[i + 1]; break; }\n    }\n    const span = Math.max(b.p - a.p, 0.0001);\n    let t = (p - a.p) / span;\n    t = t * t * (3 - 2 * t); // smoothstep\n    keyPos.fromArray(a.pos).lerp(new THREE.Vector3(...b.pos), t);\n    keyTgt.fromArray(a.tgt).lerp(new THREE.Vector3(...b.tgt), t);\n  }\n\n  const clearColor = new THREE.Color();\n\n  function applyAtmosphere(depth) {\n    let a = DEPTH_STOPS[0], b = DEPTH_STOPS[DEPTH_STOPS.length - 1];\n    for (let i = 0; i < DEPTH_STOPS.length - 1; i++) {\n      if (depth >= DEPTH_STOPS[i].d && depth <= DEPTH_STOPS[i + 1].d) {\n        a = DEPTH_STOPS[i]; b = DEPTH_STOPS[i + 1]; break;\n      }\n    }\n    const t = Math.min(Math.max((depth - a.d) / Math.max(b.d - a.d, 1), 0), 1);\n    clearColor.setRGB(\n      a.c[0] + (b.c[0] - a.c[0]) * t,\n      a.c[1] + (b.c[1] - a.c[1]) * t,\n      a.c[2] + (b.c[2] - a.c[2]) * t\n    );\n    renderer.setClearColor(clearColor);\n    skyMat.color.copy(clearColor);\n    scene.fog.color.copy(clearColor);\n    scene.fog.density = a.fog + (b.fog - a.fog) * t;\n  }\n\n  // ---------- frame loop (driven externally) ----------\n\n  let lastT = 0;\n\n  function frame(timeMs, progress) {\n    const t = timeMs / 1000;\n    const dt = Math.min(t - lastT, 0.05);\n    lastT = t;\n\n    sampleKeys(progress);\n\n    if (!reducedMotion && window.__skadiProgress == null) {\n      keyPos.x += Math.sin(t * 0.13) * 7;\n      keyPos.y += Math.sin(t * 0.09) * 2;\n      keyPos.z += Math.cos(t * 0.11) * 7;\n      const k = 1 - Math.exp(-dt * 3.2);\n      curPos.lerp(keyPos, k);\n      curTgt.lerp(keyTgt, k);\n    } else {\n      curPos.copy(keyPos);\n      curTgt.copy(keyTgt);\n    }\n    camera.position.copy(curPos);\n    camera.lookAt(curTgt);\n\n    const depth = Math.max(0, -curPos.y);\n    applyAtmosphere(depth);\n\n    // surface effects only near surface\n    const nearSurface = depth < 320;\n    snow.visible = nearSurface;\n    aurora.visible = depth < 900;\n    bubbles.visible = depth > 10 && depth < 1500;\n    // never both: at horizon grazing angles these planes z-fight (2,470 m apart\n    // is below depth-buffer precision at 4-6 km), which strobes during sway\n    ground.visible = depth < 700;\n    bedrock.visible = depth > 700;\n    sediment.visible = depth > 700;\n\n    if (aurora.visible && !reducedMotion) aurora.material.uniforms.uTime.value = t;\n\n    if (nearSurface && !reducedMotion) {\n      for (let i = 0; i < snowPos.length; i += 3) {\n        snowPos[i] += dt * 26;\n        snowPos[i + 1] -= dt * 14;\n        if (snowPos[i + 1] < 0) snowPos[i + 1] += 280;\n        if (snowPos[i] > 450) snowPos[i] -= 900;\n      }\n      snow.geometry.attributes.position.needsUpdate = true;\n    }\n\n    // events: arm once the visitor reaches the array\n    const now = timeMs;\n    if (!reducedMotion) {\n      if (!hasArrived && depth > 1250) {\n        hasArrived = true;\n        nextEventAt = now + 600;\n      }\n      if (hasArrived && !evt && now >= nextEventAt && depth > 1100) {\n        spawnEvent(now);\n        if (!evt) nextEventAt = now + 1500;\n      }\n      updateEvent(now);\n    }\n\n    composer.render();\n  }\n\n  function onResize() {\n    camera.aspect = window.innerWidth / window.innerHeight;\n    camera.updateProjectionMatrix();\n    renderer.setSize(window.innerWidth, window.innerHeight);\n    composer.setSize(window.innerWidth, window.innerHeight);\n  }\n  window.addEventListener('resize', onResize);\n\n  return {\n    frame,\n    getDepth: () => Math.max(0, Math.min(2450, -curPos.y)),\n  };\n}\n"},{"name":"shaders.js","content":"// GLSL for the aurora curtain plane (surface scene).\n\nexport const auroraVertex = /* glsl */ `\nvarying vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nexport const auroraFragment = /* glsl */ `\nprecision highp float;\nuniform float uTime;\nvarying vec2 vUv;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\nfloat noise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  return mix(\n    mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),\n    mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),\n    u.y\n  );\n}\n\nfloat fbm(vec2 p) {\n  float v = 0.0;\n  float a = 0.5;\n  for (int i = 0; i < 5; i++) {\n    v += a * noise(p);\n    p *= 2.03;\n    a *= 0.5;\n  }\n  return v;\n}\n\nvoid main() {\n  float t = uTime * 0.045;\n\n  float curtain = fbm(vec2(vUv.x * 5.0 + t, t * 0.6));\n  curtain = pow(smoothstep(0.40, 0.96, curtain), 1.7);\n\n  float fine = fbm(vec2(vUv.x * 15.0 - t * 1.8, t * 1.2));\n  curtain *= 0.6 + 0.4 * fine;\n\n  float rays = fbm(vec2(vUv.x * 34.0 + t * 2.2, vUv.y * 2.2));\n  float vfall = smoothstep(0.02, 0.16, vUv.y) * pow(1.0 - vUv.y, 1.6);\n\n  float i = curtain * vfall * (0.7 + 0.55 * rays);\n\n  vec3 base = vec3(0.16, 0.82, 0.52);\n  vec3 tip  = vec3(0.30, 0.55, 0.95);\n  vec3 col = mix(base, tip, clamp(vUv.y * 1.7, 0.0, 1.0));\n\n  gl_FragColor = vec4(col * i * 1.15, i * 0.9);\n}\n`;\n"}],"folders":[]},{"folder":"other","files":[{"name":"main.sketch","content":""}],"folders":[]}]},"variants":null,"createdAt":"2026-06-10T12:10:20.565Z","updatedAt":"2026-06-10T12:10:44.764Z"}}