/* ──────────────────────────────────────────────────────────────────────────
   Spherical gallery — UI / overlay styling.
   The 3D gallery itself lives entirely in the #gl canvas; everything here is
   the HUD, the vignette, and the project page that animates in over the top.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  --bg: #06080c;
  --ink: #f3f3f0;
  --mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  --hud-fade: rgba(243, 243, 240, 0.55);
}

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

/* ── Loading screen ─────────────────────────────────────────────────────────
   Black screen, bold white "K" that stretches horizontally to both edges as
   the page loads (driven inline in index.html). Fades out once the gallery is
   ready. Highest layer so it covers the canvas, HUD and intro. */
#loader {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  overflow: hidden;
  transition: opacity 0.6s ease;
}
#loader.loader-out { opacity: 0; pointer-events: none; }
#loader-k {
  display: block;
  height: 34vh;
  width: 34vh;          /* starting (≈ natural) width; JS animates this out to 100vw */
  fill: #fff;
  will-change: width;
}

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  overflow: hidden;
  cursor: grab;
  -webkit-font-smoothing: antialiased;
}
/* During the intro the page scrolls (the scroll drives the video). The scroll
   container must be the root <html> element so window.scrollY / the window
   'scroll' event engage — hence the class lands on <html>, and <body> is
   allowed to grow past the viewport so the spacer creates real scroll height. */
html.intro-active { overflow-y: auto; overflow-x: hidden; overscroll-behavior-y: none; cursor: default; }
html.intro-active body { overflow: visible; height: auto; min-height: 100%; }
html.intro-active #hud { opacity: 0; }
/* On a touchscreen the swipe falls through the pointer-events:none #intro layer
   onto the #gl canvas, whose touch-action:none (for the ball drag) blocks native
   scrolling — leaving the intro un-swipeable. While the intro owns the page,
   re-allow vertical panning so swipes scrub the video. The class is dropped at
   handoff, restoring touch-action:none for the gallery. */
html.intro-active #gl { touch-action: pan-y; }
body.dragging { cursor: grabbing; }
body.page-open { cursor: default; }

#gl {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  touch-action: none;        /* let us own the drag gesture */
  will-change: filter;
}

/* Radial vignette — darkens the edges so the sphere reads as a volume. */
#vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background:
    radial-gradient(120% 120% at 50% 45%,
      rgba(6, 8, 12, 0) 38%,
      rgba(6, 8, 12, 0.55) 78%,
      rgba(6, 8, 12, 0.92) 100%);
}

/* ── Intro video layer (scroll-scrubbed, fades into the gallery) ─────────── */
#intro {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: #000;
  will-change: opacity;
  pointer-events: none; /* purely visual; scroll + card clicks pass through */
}
#intro-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}
/* The invisible tall element that gives the page something to scroll through. */
#intro-spacer {
  width: 1px;
  height: var(--intro-scroll-height, 348vh);
  pointer-events: none;
}

#intro-hint {
  position: fixed;
  left: 50%;
  bottom: 38px;
  transform: translateX(-50%);
  z-index: 11;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: rgba(243, 243, 240, 0.6);
  pointer-events: none;
  animation: hint-bob 2.4s ease-in-out infinite;
}
@keyframes hint-bob { 0%, 100% { transform: translate(-50%, 0); } 50% { transform: translate(-50%, 6px); } }

/* ── Scroll-driven captions (KEONI + discipline subheads) ─────────────────── */
#intro-captions {
  position: fixed;
  inset: 0;
  z-index: 12;            /* above the intro video (10/11), below the HUD (20) */
  pointer-events: none;
  overflow: hidden;
}
/* The caption is now just a centred, single-line container; each glyph is its
   own .cap-letter child so it can fly into place independently (see intro.js). */
.caption {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 100%;
  margin: 0;
  opacity: 0;             /* intro.js drives this from scroll progress */
  transform: translate(-50%, -50%);
  text-align: center;
  text-transform: uppercase;
  white-space: nowrap;
  font-family: "Anton", var(--mono);
  font-weight: 400;
  line-height: 0.86;
  will-change: opacity, transform;
}
.caption-title {
  font-size: clamp(80px, 23vw, 340px);
  letter-spacing: 0.16em;   /* letters spread wide */
}
.caption-sub {
  font-size: clamp(44px, 12vw, 168px);
  letter-spacing: 0.26em;
}
/* Small readable subtitle under a title (e.g. KEONI's "Just building stuff").
   Mono so it stays legible, with a soft neon bloom rather than the glitch type.
   Explicit font-size since it would otherwise inherit the title's huge size. */
.cap-tagline {
  margin-top: clamp(10px, 1.6vw, 22px);
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(12px, 1.5vw, 21px);
  letter-spacing: 0.14em;
  line-height: 1.2;
  text-transform: none;
  color: #fff;
  text-shadow: 0 0 4px rgba(255, 255, 255, 0.85), 0 0 11px rgba(255, 255, 255, 0.55);
}

/* One glyph. intro.js sets its transform each frame so it tumbles in from its
   own direction and settles as you scroll to it. */
.cap-letter {
  display: inline-block;
  position: relative;
  color: #fff;
  will-change: transform;
  /* Soft white bloom — monochrome, dialled back so the static type stays legible. */
  text-shadow:
    0 0 2px rgba(255, 255, 255, 0.9),
    0 0 7px rgba(255, 255, 255, 0.6),
    0 0 15px rgba(255, 255, 255, 0.4);
  /* Glyph fill is fine scanlines that crawl vertically (live-CRT feel); the
     glow above is a text-shadow, so it survives the transparent text-fill. */
  background:
    repeating-linear-gradient(
      0deg,
      #ffffff 0, #ffffff 1px,
      rgba(255, 255, 255, 0.45) 1px, rgba(255, 255, 255, 0.45) 2px,
      #ffffff 2px, #ffffff 3px,
      rgba(180, 180, 180, 0.25) 3px, rgba(180, 180, 180, 0.25) 4px);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: cap-scan 0.42s steps(4, end) infinite, cap-flicker 2.7s steps(9, end) infinite;
}
/* Scanlines crawl up the letterforms; brightness flicker fakes signal noise. */
@keyframes cap-scan   { to { background-position: 0 -8px; } }
@keyframes cap-flicker {
  0%, 100% { filter: brightness(1); }
  10%  { filter: brightness(1.35); }
  22%  { filter: brightness(0.82); }
  35%  { filter: brightness(1.18); }
  55%  { filter: brightness(0.92); }
  70%  { filter: brightness(1.4); }
  85%  { filter: brightness(0.88); }
}

/* Typewriter caret — a neon block that blinks at the typing head. Inherits the
   active word's --neon via the caption's inline style. */
.cap-caret {
  display: none;            /* intro.js shows it only while a word is typing */
  width: 0.09em;
  height: 0.74em;
  margin-left: 0.1em;
  vertical-align: baseline;
  background: #fff;
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.8), 0 0 14px rgba(255, 255, 255, 0.5);
  animation: caret-blink 0.8s steps(1, end) infinite;
}
@keyframes caret-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* RGB-split glitch: two clipped copies tear across the text in fine slices.
   steps() with many keyframes makes each copy jump band-to-band, so it reads
   as torn static rather than a smooth drift. */
.glitch::before,
.glitch::after {
  content: attr(data-text);
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  text-align: center;
  pointer-events: none;
}
.glitch::before {
  color: rgba(255, 255, 255, 0.7);
  text-shadow: -2px 0 rgba(255, 255, 255, 0.5);
  animation: glitch-slice-a 0.62s steps(1, end) infinite;
}
.glitch::after {
  color: rgba(255, 255, 255, 0.7);
  text-shadow: 2px 0 rgba(255, 255, 255, 0.5);
  animation: glitch-slice-b 0.49s steps(1, end) infinite;
}
@keyframes glitch-slice-a {
  0%   { clip-path: inset(8%  0 86% 0); transform: translate(-4px, 0) skewX(-6deg); }
  12%  { clip-path: inset(34% 0 58% 0); transform: translate(3px, 0); }
  24%  { clip-path: inset(72% 0 22% 0); transform: translate(-2px, 0) skewX(4deg); }
  36%  { clip-path: inset(18% 0 74% 0); transform: translate(4px, 0); }
  48%  { clip-path: inset(54% 0 38% 0); transform: translate(-5px, 0) skewX(-3deg); }
  60%  { clip-path: inset(88% 0 4%  0); transform: translate(2px, 0); }
  72%  { clip-path: inset(26% 0 66% 0); transform: translate(-3px, 0) skewX(5deg); }
  84%  { clip-path: inset(62% 0 30% 0); transform: translate(5px, 0); }
  96%  { clip-path: inset(44% 0 48% 0); transform: translate(-2px, 0); }
  100% { clip-path: inset(12% 0 82% 0); transform: translate(3px, 0); }
}
@keyframes glitch-slice-b {
  0%   { clip-path: inset(78% 0 10% 0); transform: translate(4px, 0) skewX(5deg); }
  11%  { clip-path: inset(22% 0 70% 0); transform: translate(-3px, 0); }
  23%  { clip-path: inset(50% 0 42% 0); transform: translate(5px, 0) skewX(-4deg); }
  34%  { clip-path: inset(6%  0 88% 0); transform: translate(-4px, 0); }
  46%  { clip-path: inset(66% 0 26% 0); transform: translate(2px, 0) skewX(3deg); }
  58%  { clip-path: inset(38% 0 56% 0); transform: translate(-5px, 0); }
  70%  { clip-path: inset(84% 0 8%  0); transform: translate(3px, 0) skewX(-5deg); }
  82%  { clip-path: inset(16% 0 76% 0); transform: translate(-2px, 0); }
  94%  { clip-path: inset(58% 0 34% 0); transform: translate(4px, 0); }
  100% { clip-path: inset(30% 0 62% 0); transform: translate(-3px, 0); }
}

/* Flickering TV-snow over the whole caption layer — fractal-noise SVG whose
   sampling window jumps each frame so the grain reshuffles like static. `screen`
   lightens it against the near-black video so the snow reads strongly, and the
   contrast filter crisps the smooth noise into sharp black/white speckle. */
#intro-captions::after {
  content: "";
  position: absolute;
  inset: -50%;            /* oversize so the jumping background-position never bares an edge */
  pointer-events: none;
  opacity: 0.65;
  mix-blend-mode: screen;
  filter: contrast(1.9) brightness(1.05);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 150px 150px;
  animation: grain-shift 0.38s steps(6, end) infinite;
}
@keyframes grain-shift {
  0%   { background-position: 0 0; }
  20%  { background-position: -42px 28px; }
  40%  { background-position: 33px -19px; }
  60%  { background-position: -25px -37px; }
  80%  { background-position: 18px 41px; }
  100% { background-position: -36px 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .cap-letter { animation: none; }
  .cap-caret { animation: none; }
  .glitch::before, .glitch::after { animation: none; }
  #intro-captions::after { animation: none; opacity: 0.28; }
}

/* ── HUD ────────────────────────────────────────────────────────────────── */
#hud {
  position: fixed;
  inset: 0;
  z-index: 20;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 22px clamp(18px, 3vw, 40px);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.hud-row { display: flex; align-items: flex-start; gap: 24px; }
.hud-top { justify-content: space-between; }
.hud-logo { flex: 0 0 auto; opacity: 0.95; }
.hud-mark {
  display: grid;
  place-items: center;
  width: 22px;
  height: 24px;
  color: #fff;
  font-weight: 700;
  font-size: 19px;
  line-height: 1;
}
.hud-tagline {
  flex: 1 1 auto;
  max-width: 460px;
  line-height: 1.55;
  color: var(--hud-fade);
}
.hud-clock { flex: 0 0 auto; text-align: right; white-space: nowrap; }
.hud-city { color: var(--hud-fade); }

.hud-bottom { justify-content: space-between; align-items: flex-end; }
.hud-meta { color: var(--hud-fade); }
.hud-hint {
  color: var(--hud-fade);
  animation: hint-pulse 3.2s ease-in-out infinite;
}
@keyframes hint-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.85; } }

/* ── Card clone used for the expand transition ──────────────────────────── */
.card-clone {
  position: fixed;
  z-index: 28;
  background-size: cover;
  background-position: center;
  border: 1px solid rgba(255, 255, 255, 0.12);
  overflow: hidden;
  will-change: left, top, width, height;
}

/* ── Project page ───────────────────────────────────────────────────────── */
#page-root { position: fixed; inset: 0; z-index: 30; pointer-events: none; }

.project-page {
  position: fixed;
  inset: 0;
  pointer-events: auto;
  opacity: 0;
  visibility: hidden;
  background: var(--bg);
  overflow-y: auto;
}
.project-hero {
  position: relative;
  height: 64vh;
  min-height: 360px;
  background-size: cover;
  background-position: center;
}
.project-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(6, 8, 12, 0.25) 0%,
    rgba(6, 8, 12, 0.05) 40%,
    rgba(6, 8, 12, 0.85) 88%,
    var(--bg) 100%);
}
.project-body {
  position: relative;
  max-width: 980px;
  margin: -120px auto 0;
  padding: 0 clamp(20px, 5vw, 64px) 120px;
  z-index: 1;
}
.project-eyebrow {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hud-fade);
  margin-bottom: 18px;
}
.project-title {
  font-size: clamp(34px, 6vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  font-weight: 700;
  margin-bottom: 28px;
}
.project-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 44px;
}
.project-tag {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hud-fade);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  padding: 7px 14px;
}
.project-copy {
  max-width: 620px;
  font-size: 15px;
  line-height: 1.85;
  color: rgba(243, 243, 240, 0.74);
}
.project-copy p + p { margin-top: 20px; }

.project-links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 32px;
}
.project-link {
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(243, 243, 240, 0.92);
  text-decoration: none;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  padding: 11px 20px;
  transition: border-color 0.25s ease, background 0.25s ease;
}
.project-link:hover {
  border-color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.08);
}

/* ── Screenshot gallery (thumbnails on the project page) ──────────────────── */
.project-gallery {
  margin-top: 40px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
}
.project-shot {
  display: block;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.02);
  cursor: zoom-in;
  transition: border-color 0.25s ease, transform 0.25s ease;
}
.project-shot:hover { border-color: rgba(255, 255, 255, 0.4); transform: translateY(-2px); }
.project-shot img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16 / 10; }

/* ── Lightbox (clicked screenshot, full size) ────────────────────────────── */
.project-lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4vh 4vw;
  background: rgba(4, 5, 8, 0.92);
  cursor: zoom-out;
}
.project-lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 10px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
}

.project-back {
  position: fixed;
  top: 22px;
  left: clamp(18px, 3vw, 40px);
  z-index: 2;
  pointer-events: auto;
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 10px 16px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.project-back:hover { background: rgba(255, 255, 255, 0.14); transform: translateX(-2px); }
