﻿/* ==========================================================================
   DESIGN TOKENS
   ========================================================================== */
:root {
  /* Color */
  --color-bg: #f6f5f2;
  --color-ink: #121210;
  --color-ink-soft: #55534d;
  --color-line: #dcdad3;
  --color-surface-dark: #0b0b0a;
  --color-paper: #fbfaf8;
  --color-accent: #8c7a63; /* subtle warm stone - used sparingly */

  /* Type */
  --font-display: "Archivo", sans-serif;
  --font-body: "IBM Plex Sans", sans-serif;
  --font-mono: "IBM Plex Mono", monospace;
  /* Pixel type - scoped to the hero's game-screen visual identity only */
  --font-pixel: "Press Start 2P", var(--font-mono);
  --font-pixel-text: "VT323", var(--font-mono);

  /* Layout */
  --gutter: 40px;
  --nav-height: 60px;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

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

html {
  scroll-behavior: auto; /* Lenis handles smoothing; avoid double smoothing */
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

/* Visible keyboard focus everywhere */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
}

/* Screen-reader-only content: visually hidden, still announced */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Off-screen until keyboard-focused, then jumps above the fixed navbar
   so keyboard/screen-reader users can bypass the hero animation */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--gutter);
  z-index: 200;
  padding: 12px 20px;
  background: var(--color-ink);
  color: var(--color-bg);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  transition: top 0.2s var(--ease-out);
}

.skip-link:focus {
  top: 16px;
}

/* ==========================================================================
   NAVBAR
   ========================================================================== */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nav-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--gutter);
  color: #fff;
  border: 1px solid transparent;
  border-radius: 0;
  /* Keep the docked-to-floating change feeling like one smooth motion. */
  transition: background-color 0.6s var(--ease-out), color 0.45s var(--ease-out),
    border-color 0.6s var(--ease-out), border-radius 0.65s var(--ease-out),
    top 0.65s cubic-bezier(0.22, 1, 0.36, 1),
    left 0.65s cubic-bezier(0.22, 1, 0.36, 1),
    right 0.65s cubic-bezier(0.22, 1, 0.36, 1),
    height 0.65s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.6s var(--ease-out),
    translate 0.35s var(--ease-out);
}

.navbar--scrolled {
  top: 12px;
  left: 160px;
  right: 160px;
  height: 44px;
  border-radius: 999px;
  background: rgba(251, 250, 248, 0.92);
  color: var(--color-ink);
  border-color: rgba(255, 255, 255, 0.6);
  box-shadow: 0 12px 30px rgba(18, 18, 16, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.5);
}

@media (prefers-reduced-motion: reduce) {
  .navbar,
  .navbar.navbar--scrolled {
    transition: none;
    animation: none;
  }
}

.navbar__logo {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: 0.08em;
}

/* Pixel mark replaces the wordmark on mobile only (see responsive.css). */
.navbar__logo-mark {
  display: none;
  flex: none;
  image-rendering: pixelated;
}

.navbar__nav {
  display: flex;
  gap: 36px;
}

.navbar__link {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  position: relative;
  padding-bottom: 4px;
}

.navbar__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.35s var(--ease-out);
}

.navbar__link:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

.navbar__toggle {
  display: none;
  color: inherit; /* buttons don't inherit colour, so the bars need this */
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 32px;
  height: 24px;
}

.navbar__toggle span {
  display: block;
  height: 2px;
  width: 22px;
  background: currentColor;
  transition: transform 0.25s var(--ease-out), opacity 0.2s var(--ease-out);
}

/* Hamburger -> X while the menu is open (2px line + 5px gap = 7px travel) */
.navbar__toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.navbar__toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.navbar__toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ==========================================================================
   MOBILE MENU - pixel style, fullscreen overlay (only reachable ≤ 720px)
   Palette + type follow the navbar-mobile spec: yellow accent for the
   active / hovered row, light grey text, mono type, pixel-drawn icons.
   ========================================================================== */
.mobile-menu {
  --menu-bg: var(--color-surface-dark);
  --menu-accent: #ffd866;
  --menu-text: #f5f5f5;
  --menu-muted: #9ca3af;
  --menu-row: #1a1a1a;
  --menu-hairline: rgba(245, 245, 245, 0.1);

  /* Pixel icons are drawn as masks so they can take any colour. */
  --mask-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 9 9' shape-rendering='crispEdges'%3E%3Cpath d='M4 0h1v3h-1zM4 6h1v3h-1zM0 4h3v1h-3zM6 4h3v1h-3zM4 4h1v1h-1z'/%3E%3C/svg%3E");
  --mask-plus-bold: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 9 9' shape-rendering='crispEdges'%3E%3Cpath d='M3 0h3v3h3v3h-3v3h-3v-3h-3v-3h3z'/%3E%3C/svg%3E");
  --mask-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 7' shape-rendering='crispEdges'%3E%3Cpath d='M0 0h1v1h-1zM1 1h1v1h-1zM2 2h1v1h-1zM3 3h1v1h-1zM2 4h1v1h-1zM1 5h1v1h-1zM0 6h1v1h-1z'/%3E%3C/svg%3E");

  position: fixed;
  inset: 0;
  z-index: 90;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(var(--nav-height) + 28px) var(--gutter) 32px;
  background: var(--menu-bg);
  color: var(--menu-text);
  font-family: var(--font-mono);
  opacity: 0;
  visibility: hidden;
  /* Hide from keyboard / screen readers only after the fade has finished. */
  transition: opacity 0.28s var(--ease-out), visibility 0s linear 0.28s;
}

.mobile-menu--open {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

.mobile-menu__nav {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
}

.mobile-menu__link {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 54px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--menu-text);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0.01em;
  outline: none;
  -webkit-tap-highlight-color: transparent;
  /* Entrance state - items step in one after another once the menu opens. */
  opacity: 0;
  transform: translateX(-10px);
  transition:
    opacity 0.28s var(--ease-out) 0s,
    transform 0.28s var(--ease-out) 0s,
    background-color 0.2s ease,
    border-color 0.2s ease,
    color 0.2s ease;
}

.mobile-menu--open .mobile-menu__link {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--i, 0) * 40ms + 90ms), calc(var(--i, 0) * 40ms + 90ms), 0s, 0s, 0s;
}

.mobile-menu__label {
  flex: 1;
  min-width: 0;
}

/* Chevron slot is always reserved so every label lines up; it only shows
   on the active / hovered / pressed row. */
.mobile-menu__chevron,
.mobile-menu__plus {
  flex: none;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

.mobile-menu__chevron {
  width: 8px;
  height: 14px;
  color: var(--menu-accent);
  opacity: 0;
  transform: translateX(-4px);
  -webkit-mask-image: var(--mask-chevron);
  mask-image: var(--mask-chevron);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.mobile-menu__plus {
  width: 18px;
  height: 18px;
  color: var(--menu-muted);
  -webkit-mask-image: var(--mask-plus);
  mask-image: var(--mask-plus);
  transition: color 0.2s ease;
}

/* Active section (set from main.js via aria-current) */
.mobile-menu__link[aria-current] {
  color: var(--menu-accent);
}

.mobile-menu__link[aria-current] .mobile-menu__chevron {
  opacity: 1;
  transform: none;
}

/* Highlight row: mouse hover, keyboard focus and press share one look. */
.mobile-menu__link:focus-visible,
.mobile-menu__link:active {
  background-color: var(--menu-row);
  border-color: rgba(255, 216, 102, 0.4);
  color: var(--menu-text);
}

.mobile-menu__link:focus-visible .mobile-menu__chevron,
.mobile-menu__link:active .mobile-menu__chevron {
  opacity: 1;
  transform: none;
}

.mobile-menu__link:focus-visible .mobile-menu__plus,
.mobile-menu__link:active .mobile-menu__plus {
  color: var(--menu-accent);
}

/* Press: the plus turns into a chunky one, fast (0.2s feel). */
.mobile-menu__link:active .mobile-menu__plus {
  -webkit-mask-image: var(--mask-plus-bold);
  mask-image: var(--mask-plus-bold);
}

/* Hover only where a real pointer exists, so a tap on a phone doesn't
   leave the last row stuck highlighted the next time the menu opens. */
@media (hover: hover) {
  .mobile-menu__link:hover {
    background-color: var(--menu-row);
    border-color: rgba(245, 245, 245, 0.08);
    color: var(--menu-text);
  }

  .mobile-menu__link:hover .mobile-menu__chevron {
    opacity: 1;
    transform: none;
  }

  .mobile-menu__link:hover .mobile-menu__plus {
    color: var(--menu-accent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .mobile-menu,
  .mobile-menu__link,
  .mobile-menu__chevron,
  .mobile-menu__plus {
    transition: none !important;
  }

  .mobile-menu__link {
    opacity: 1;
    transform: none;
  }
}

/* ==========================================================================
   HERO - pixel-art "game opening screen"
   This is the only section using the pixel visual language (fonts,
   crisp-edged icons, retro dialog box). Everything below the hero
   returns to the clean/editorial system.
   ========================================================================== */
.hero {
  position: relative;
  height: 100svh;
  min-height: 640px;
  width: 100%;
  overflow: hidden;
  color: #fff;
  background: #0B0B0A;
}

/* -- Backdrop: gradient sky + a small, quiet starfield. Deliberately
      simple so the character stays the focal point. -- */
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}



.hero__glow {
  position: absolute;
  left: 50%;
  bottom: 27%;
  width: min(420px, 72vw);
  height: 112px;
  transform: translateX(-50%) rotate(-8deg);
  background: transparent;
  border: 1px solid rgba(246, 245, 242, 0.16);
  border-radius: 50%;
  box-shadow: 0 0 0 10px rgba(246, 245, 242, 0.025);
}

.hero__glow::before,
.hero__glow::after {
  content: "";
  position: absolute;
  border: 1px solid rgba(246, 245, 242, 0.08);
  border-radius: 50%;
}

.hero__glow::before {
  inset: 18px -18px;
  transform: rotate(14deg);
}

.hero__glow::after {
  left: 50%;
  bottom: -18px;
  width: 42px;
  height: 5px;
  transform: translateX(-50%);
  border: 0;
  border-radius: 0;
  background: rgba(246, 245, 242, 0.18);
}

/* WebGL galaxy backdrop (js/hero-galaxy.js), layered on top of the
   CSS starfield above so that starfield still shows as a no-JS/
   no-WebGL fallback if the canvas never gets appended. */
.hero__galaxy {
  position: absolute;
  inset: 0;
}

.hero__galaxy canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* -- Frame: top title row, scene fills the remaining space, scroll
      cue anchored at the bottom - the classic title-screen layout. -- */
.hero__frame {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: calc(var(--nav-height) + 26px) var(--gutter) 28px;
}

.hero__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

/* Just a small nameplate now - the actual introduction ("I'm Rafif,
   an Informatics student...") is carried by the dialog box below, so
   this stays a quiet corner tag instead of repeating that copy. */
.hero__title-name-wrap {
  display: block;
  overflow: hidden;
}

.hero__title-name {
  display: block;
  font-family: var(--font-pixel);
  font-size: clamp(16px, 2vw, 20px);
  line-height: 1.3;
  letter-spacing: 0.02em;
  color: #fff;
  text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.35);
}

.hero__title-role {
  display: block;
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.hero__index {
  font-family: var(--font-mono);
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
  padding-top: 4px;
}

/* -- Scene: dialog box, character, platform -- */
.hero__scene {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  min-height: 0;
  padding-bottom: 6px;
}

.hero__character {
  position: relative;
  z-index: 2;
  height: clamp(240px, 34vw, 400px);
  width: clamp(240px, 34vw, 400px);
  will-change: transform;
}


.hero__character-img {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  object-fit: contain;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  opacity: 0;
  pointer-events: none;
  will-change: opacity, transform;
}

.hero__character-img.is-active {
  opacity: 1;
}

.hero__character-shadow {
  position: absolute;
  left: 50%;
  bottom: -6px;
  width: 46%;
  height: 14px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.45), transparent 75%);
}

.hero__platform {
  position: relative;
  z-index: 1;
  width: min(300px, 62vw);
  height: 22px;
  margin-top: -14px;
  background: #242321;
  border: 1px solid rgba(246, 245, 242, 0.18);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

.hero__platform-top {
  position: absolute;
  inset: 0 0 auto 0;
  height: 5px;
  background: #F6F5F2;
  opacity: 0.7;
}

/* -- Dialog box: RPG-style textbox above the character's head -- */
.hero__dialog {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 18px;
  opacity: 0;
}

.hero__dialog-box {
  cursor: pointer;
  user-select: none;
  min-width: 220px;
  max-width: min(360px, 84vw);
  padding: 14px 16px;
  background: rgba(14, 12, 28, 0.82);
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(2px);
}

.hero__dialog-text {
  margin: 0;
  display: inline;
  font-family: var(--font-pixel-text);
  font-size: 21px;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: #fff;
}

.hero__dialog-caret {
  display: inline-block;
  width: 9px;
  height: 18px;
  margin-left: 2px;
  background: #ffd76a;
  vertical-align: -3px;
  animation: hero-caret-blink 0.9s steps(1) infinite;
}

@keyframes hero-caret-blink {
  0%,
  50% {
    opacity: 1;
  }
  51%,
  100% {
    opacity: 0;
  }
}

.hero__dialog-next {
  display: none;
  margin-left: 6px;
  font-size: 13px;
  color: #ffd76a;
  vertical-align: 2px;
  animation: hero-next-bob 0.9s ease-in-out infinite;
}

@keyframes hero-next-bob {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(3px);
  }
}

.hero__dialog-hint {
  position: absolute;
  bottom: 100%;
  margin-bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  opacity: 0;
  pointer-events: none;
}

.hero__dialog-box:focus-visible {
  outline: 2px solid #ffd76a;
  outline-offset: 3px;
}

.hero__dialog-tail {
  width: 0;
  height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 8px solid rgba(255, 255, 255, 0.85);
}

/* Custom cursor inside the hero is now handled by the single
   site-wide pixel cursor system - see the "PIXEL CURSOR" block
   near the end of this file (js/cursor.js). */

/* -- Scroll cue -- */
.hero__scroll {
  align-self: center;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
}

.hero__scroll-arrow {
  display: inline-block;
  animation: hero-bounce 1.8s var(--ease-out) infinite;
}

@keyframes hero-bounce {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__scroll-arrow {
    animation: none;
  }

  .hero__dialog-caret,
  .hero__dialog-next {
    animation: none;
  }
}

/* ==========================================================================
   ABOUT
   ========================================================================== */
.about {
  position: relative;
  padding: 160px var(--gutter) 140px;
  background: var(--color-bg);
  border-top: 1px solid var(--color-line);
}

.about__inner {
  max-width: 1240px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: 100px;
  align-items: start;
}

.about__heading-row {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: 48px;
}

.about__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.about__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(40px, 5.4vw, 76px);
  line-height: 0.95;
  letter-spacing: -0.02em;
}

.about__title-line {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.about__title-line span {
  display: inline-block;
}

.about__title-line + .about__title-line {
  margin-left: 0.32em;
}

.about__body {
  max-width: 540px;
}

.about__paragraph {
  font-size: 17px;
  line-height: 1.75;
  color: var(--color-ink-soft);
  margin: 0 0 22px;
}

.about__paragraph:last-child {
  margin-bottom: 0;
}

.about__meta {
  margin: 56px 0 0;
  padding-top: 32px;
  border-top: 1px solid var(--color-line);
  display: flex;
  flex-wrap: wrap;
  gap: 40px 56px;
}

.about__meta-item dt {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  margin-bottom: 8px;
}

.about__meta-item dd {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
}

.about__media {
  position: relative;
  align-self: start;
}

.about__frame {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: transparent;
}

.about__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.about__photo-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  color: var(--color-ink-soft);
  text-align: center;
  padding: 24px;
}

.about__photo-icon {
  display: flex;
  color: var(--color-accent);
}

.about__photo-text {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  letter-spacing: 0.02em;
}

.about__frame-tick {
  position: absolute;
  width: 22px;
  height: 22px;
  border-color: var(--color-ink);
  border-style: solid;
  border-width: 0;
}

.about__frame-tick--tl {
  top: -1px;
  left: -1px;
  border-top-width: 2px;
  border-left-width: 2px;
}

.about__frame-tick--br {
  bottom: -1px;
  right: -1px;
  border-bottom-width: 2px;
  border-right-width: 2px;
}

/* ==========================================================================
   SELECTED WORK
   ========================================================================== */
.work {
  position: relative;
  background: var(--color-bg);
  border-top: 1px solid var(--color-line);
}

.work__intro {
  padding: 140px var(--gutter) 64px;
  max-width: 1240px;
  margin: 0 auto;
}

.work__intro-row {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: 20px;
}

.work__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.work__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(40px, 5.4vw, 76px);
  line-height: 0.95;
  letter-spacing: -0.02em;
}

.work__lede {
  max-width: 480px;
  font-size: 17px;
  line-height: 1.7;
  color: var(--color-ink-soft);
  margin: 0;
}

.work__hint {
  margin: 28px 0 0;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  transition: opacity 0.4s var(--ease-out);
}

/* Desktop: pinned viewport, vertical scroll drives horizontal track motion */
.work__viewport {
  position: relative;
  height: 100vh;
  padding-top: var(--nav-height);
  box-sizing: border-box;
  overflow: hidden;
}

.work__track {
  display: flex;
  height: 100%;
  will-change: transform;
}

.work__panel {
  flex: 0 0 auto;
  width: min(64vw, 900px);
  height: 100%;
  min-height: 0;
  display: grid;
  grid-template-rows: 1fr 300px;
  padding: 40px 32px 40px 0;
  margin-right: 64px;
  box-sizing: border-box;
}

.work__panel:first-child {
  margin-left: var(--gutter);
}

.work__shot {
  position: relative;
  height: 100%;
  min-height: 0;
  border: 1px solid var(--color-line);
  border-radius: 12px;
  background: var(--color-paper);
  overflow: hidden;
  transition: transform 0.5s var(--ease-out);
}

.work__panel:hover .work__shot {
  transform: scale(1.015);
}

/* Terminal-style header */
.work__shot-terminal-header {
  display: flex !important;
  align-items: center;
  justify-content: flex-start;
  height: 32px;
  padding: 0 14px;
  background: #f5f5f5 !important;
  border-bottom: 1px solid #ddd;
  position: relative;
  z-index: 1;
}

.work__shot-terminal-chrome {
  display: flex;
  gap: 8px;
  align-items: center;
}

.work__shot-terminal-chrome span {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: block;
}

.work__shot-terminal-chrome span:nth-child(1) {
  background: #ff5f56 !important;
}

.work__shot-terminal-chrome span:nth-child(2) {
  background: #ffbd2e !important;
}

.work__shot-terminal-chrome span:nth-child(3) {
  background: #27c93f !important;
}

.work__shot-body {
  height: calc(100% - 32px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  color: var(--color-ink-soft);
  text-align: center;
  padding: 24px;
}

.work__shot-body--image {
  padding: 0;
}

.work__shot-image {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: top;
}

.work__shot-mark {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 56px;
  color: var(--color-line);
  line-height: 1;
}

.work__shot-label {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  margin: 0;
}

.work__panel-info {
  padding: 28px 0 0;
  transition: transform 0.5s var(--ease-out);
}

.work__panel:hover .work__panel-info {
  transform: translateX(6px);
}

.work__panel-number {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-accent);
}

.work__panel-category {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  margin: 6px 0 10px;
  transition: opacity 0.4s var(--ease-out);
}

.work__panel:hover .work__panel-category {
  opacity: 0.7;
}

.work__panel-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(24px, 2.4vw, 34px);
  margin: 0 0 12px;
  letter-spacing: -0.01em;
}

.work__panel-desc {
  font-size: 15px;
  line-height: 1.65;
  color: var(--color-ink-soft);
  max-width: 480px;
  margin: 0 0 14px;
}

.work__panel-tech {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-ink-soft);
  margin: 0;
}

.work__panel-result {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-accent);
  margin: 10px 0 0;
}

.work__panel-link {
  display: inline-block;
  margin-top: 16px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--color-ink);
  padding-bottom: 2px;
}

/* ==========================================================================
   CERTIFICATES
   ========================================================================== */
.certificates {
  position: relative;
  padding: 140px var(--gutter) 150px;
  background: var(--color-bg);
  border-top: 1px solid var(--color-line);
  overflow: hidden;
}

.certificates__intro {
  position: relative;
  z-index: 1;
  max-width: 1240px;
  margin: 0 auto 64px;
}

.certificates__intro-row {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: 20px;
}

.certificates__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.certificates__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(36px, 4.6vw, 64px);
  letter-spacing: -0.02em;
}

.certificates__lede {
  max-width: 480px;
  font-size: 17px;
  line-height: 1.7;
  color: var(--color-ink-soft);
  margin: 20px 0 0;
}

/* Decorative ambient canvas - behind the cards, never intercepts input */
.certificates__canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.certificates__grid {
  position: relative;
  z-index: 1;
  max-width: 1240px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
}

.certificates__card {
  display: flex;
  flex-direction: column;
  padding: 28px;
  border: 1px solid var(--color-line);
  border-radius: 12px;
  background: var(--color-paper);
  transition: transform 0.4s var(--ease-out), border-color 0.4s var(--ease-out),
    box-shadow 0.4s var(--ease-out);
}

.certificates__card:hover,
.certificates__card:focus-within {
  transform: translateY(-4px);
  border-color: var(--color-ink);
  box-shadow: 0 18px 36px -24px rgba(18, 18, 16, 0.35);
}

.certificates__card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 18px;
}

.certificates__card-number {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-accent);
}

.certificates__card-date {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-ink-soft);
}

.certificates__card-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 20px;
  line-height: 1.3;
  margin: 0 0 8px;
  letter-spacing: -0.005em;
  overflow-wrap: break-word;
}

.certificates__card-issuer {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  margin: 0 0 20px;
}

.certificates__card-skills {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 24px;
  padding: 0;
}

.certificates__card-skills li {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: var(--color-ink-soft);
  border: 1px solid var(--color-line);
  border-radius: 999px;
  padding: 5px 12px;
}

.certificates__card-link {
  margin-top: auto;
  display: inline-block;
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--color-ink);
  padding-bottom: 2px;
  transition: opacity 0.3s var(--ease-out);
}

.certificates__card-link:hover {
  opacity: 0.65;
}

/* Entrance motion (js/certificates-scene.js) animates cards straight
   from GSAP with no CSS pre-hidden state, so without JS - or before
   the script runs - cards simply sit in this fully visible state. */

@media (prefers-reduced-motion: reduce) {
  .certificates__card,
  .certificates__card:hover,
  .certificates__card:focus-within {
    transition: none;
    transform: none;
  }
}

/* ==========================================================================
   PROJECT PROCESS
   ========================================================================== */
.process {
  position: relative;
  height: 440vh;
  background: var(--color-surface-dark);
}

.process__pin {
  position: relative;
  height: 100vh;
  padding: calc(var(--nav-height) + 40px) var(--gutter) 60px;
  display: grid;
  grid-template-columns: 1fr 240px;
  grid-template-rows: auto 1fr;
  gap: 0 48px;
  color: #fff;
  box-sizing: border-box;
}

.process__head {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: 20px;
}

.process__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.process__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(32px, 3.6vw, 52px);
  letter-spacing: -0.01em;
}

.process__stages {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 0;
}

.process__stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.5s var(--ease-out), transform 0.5s var(--ease-out);
  pointer-events: none;
}

.process__stage.is-active {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.process__stage-number {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(48px, 7vw, 96px);
  line-height: 1;
  color: var(--color-accent);
  opacity: 0.55;
  margin-bottom: 8px;
}

.process__stage-name {
  margin: 0 0 18px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(32px, 4.4vw, 60px);
  letter-spacing: -0.01em;
}

.process__stage-desc {
  max-width: 460px;
  font-size: 16px;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.68);
  margin: 0;
}

.process__nav-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.process__path {
  position: absolute;
  left: -1px;
  top: 0;
  width: 24px;
  height: 100%;
  overflow: visible;
}

.process__path-track {
  stroke: rgba(255, 255, 255, 0.14);
  stroke-width: 1.5;
}

.process__path-progress {
  stroke: var(--color-accent);
  stroke-width: 1.5;
  stroke-linecap: round;
}

.process__path-dot {
  position: absolute;
  left: 0;
  top: 0;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 10px 1px rgba(140, 122, 99, 0.55);
  pointer-events: none;
}

.process__nav {
  list-style: none;
  margin: 0;
  padding: 0 0 0 28px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 26px;
}

.process__nav-item {
  display: flex;
  gap: 12px;
  align-items: baseline;
  opacity: 0.35;
  transition: opacity 0.4s var(--ease-out);
}

.process__nav-item.is-active {
  opacity: 1;
}

.process__nav-number {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-accent);
}

.process__nav-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* ==========================================================================
   SKILLS & TECHNOLOGIES
   ========================================================================== */
.skills {
  position: relative;
  padding: 140px var(--gutter) 150px;
  background: var(--color-bg);
  border-top: 1px solid var(--color-line);
}

.skills__inner {
  max-width: 1240px;
  margin: 0 auto;
}

.skills__head {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: 72px;
}

.skills__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.skills__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(36px, 4.6vw, 64px);
  letter-spacing: -0.02em;
}

.skills__groups {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 48px;
}

.skills__group-label {
  margin: 0 0 24px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  padding-bottom: 16px;
  border-bottom: 1px solid var(--color-line);
}

.skills__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.skills__item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 0;
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
}

.skills__icon {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--color-ink-soft);
  transition: color 0.35s var(--ease-out), transform 0.45s var(--ease-out);
}

.skills__icon svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.skills__icon img,
.footer__link-icon img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* GitHub/LinkedIn in the footer come from brand-colored PNGs (see
   ASSET_ICONS in icons.js); force them to plain white so they match
   the stroke-based email icon instead of showing brand colors. */
.footer__link-icon img {
  filter: brightness(0) invert(1);
  opacity: 0.6;
  transition: opacity 0.3s var(--ease-out);
}

.footer__link:hover .footer__link-icon img {
  opacity: 1;
}

/* dot accents inside icons (e.g. React's core, Node's center) stay filled */
.skills__icon svg .icon-dot {
  fill: currentColor;
  stroke: none;
}

.skills__name {
  position: relative;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 16px;
  color: var(--color-ink-soft);
  transition: color 0.35s var(--ease-out);
}

.skills__name::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: var(--color-ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease-out);
}

.skills__item:hover {
  transform: translateX(6px);
}

.skills__item:hover .skills__icon {
  color: var(--color-ink);
  transform: scale(1.12) rotate(-6deg);
}

.skills__item:hover .skills__name {
  color: var(--color-ink);
}

.skills__item:hover .skills__name::after {
  transform: scaleX(1);
}

/* while one item in a group is hovered, the rest quiet down -
   keeps focus on the item the user is actually looking at */
.skills__group.is-hovering .skills__item:not(.is-active) {
  opacity: 0.4;
}

@media (prefers-reduced-motion: reduce) {
  .skills__item,
  .skills__item:hover,
  .skills__icon,
  .skills__item:hover .skills__icon {
    transform: none;
  }
}

/* ==========================================================================
   EXPERIMENTS - sticky grid scroll
   (see js/experiments-grid.js - desktop only, plain grid elsewhere)
   ========================================================================== */
.experiments {
  position: relative;
  height: 320vh;
  background: var(--color-bg);
  border-top: 1px solid var(--color-line);
  /* Space reserved at the top of the sticky viewport for
     .experiments__head, which is absolutely positioned and so isn't
     accounted for by the wrapper's own flex centering below. Without
     this, the grid/content center themselves across the full 100vh
     and the title can end up sitting right on top of the first row.
     The bottom clearance is the band the description + CTA drop into
     once the grid has opened up: the copy used to sit on top of the
     middle of the grid, which meant the centre tile had to be hidden
     and the two tiles above and below it were unclickable. */
  --experiments-top-clearance: clamp(180px, 22vh, 230px);
  --experiments-bottom-clearance: clamp(132px, 17vh, 180px);
}

.experiments__wrapper {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 0 var(--gutter);
  padding-top: var(--experiments-top-clearance);
  padding-bottom: var(--experiments-bottom-clearance);
}

.experiments__head {
  position: absolute;
  top: calc(var(--nav-height) + 40px);
  left: var(--gutter);
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 20px;
}

.experiments__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.experiments__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(36px, 4.6vw, 64px);
  letter-spacing: -0.02em;
}

.experiments__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 140px));
  gap: 14px;
}

.experiments__item {
  position: relative;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--color-line);
  background: var(--color-paper);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  padding: 10px;
  cursor: default;
  overflow: hidden;
  transition: border-color 0.35s ease, background-color 0.35s ease,
    transform 0.35s var(--ease-out), box-shadow 0.35s var(--ease-out);
}

/* Every tile in the grid is a button wired to a live demo (see
   js/experiment-modal.js + the js/experiment-demos-*.js files).
   Visually they sit quiet at rest, with a warm border and a small
   pulsing dot to flag that they open something. */
.experiments__item--live {
  font: inherit;
  color: inherit;
  position: relative;
  cursor: pointer;
  border-color: rgba(140, 122, 99, 0.5);
}

.experiments__item-live-badge {
  position: absolute;
  top: 10px;
  right: 11px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.experiments__item-live-badge::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-accent);
  animation: experiments-live-pulse 2.2s ease-out infinite;
}

@keyframes experiments-live-pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(140, 122, 99, 0.45);
  }
  70% {
    box-shadow: 0 0 0 6px rgba(140, 122, 99, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(140, 122, 99, 0);
  }
}

.experiments__item-tag {
  position: absolute;
  bottom: 10px;
  left: 0;
  right: 0;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.04em;
  color: var(--color-accent);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

@media (hover: hover) and (pointer: fine) {
  .experiments__item--live:hover .experiments__item-tag {
    opacity: 1;
    transform: translateY(0);
  }

  .experiments__item--live:hover .experiments__item-name {
    transform: translateY(-6px);
  }
}

.experiments__item-index {
  position: absolute;
  top: 10px;
  left: 12px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--color-line);
  transition: color 0.35s ease;
}

.experiments__item-icon {
  width: 26px;
  height: 26px;
  color: var(--color-ink-soft);
  opacity: 0.85;
  transition: color 0.35s ease, opacity 0.35s ease,
    transform 0.45s var(--ease-out);
}

.experiments__item-icon svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.experiments__item-icon svg .icon-dot {
  fill: currentColor;
  stroke: none;
}

.experiments__item-name {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--color-ink-soft);
  transition: color 0.35s ease, transform 0.3s ease;
}

/* Subtle hover: item lifts a touch, gains a soft shadow, icon/index pick
   up the accent colour - no rotation/blur, keeps to the "80% clean, 20%
   crazy" rule. Desktop-only interaction (grid is static on touch/mobile). */
@media (hover: hover) and (pointer: fine) {
  .experiments__item:hover {
    border-color: var(--color-ink);
    background: var(--color-bg);
    transform: translateY(-4px);
    box-shadow: 0 16px 30px rgba(18, 18, 16, 0.08);
  }

  .experiments__item:hover .experiments__item-index {
    color: var(--color-accent);
  }

  .experiments__item:hover .experiments__item-icon {
    color: var(--color-accent);
    opacity: 1;
    transform: scale(1.08) translateY(-2px);
  }

  .experiments__item:hover .experiments__item-name {
    color: var(--color-ink);
  }
}

/* Description + CTA sit on top of the grid once it has zoomed and
   pushed outward to make room - hidden by JS until then (desktop only) */
.experiments__content {
  position: absolute;
  top: auto;
  right: 0;
  bottom: 0;
  left: 0;
  height: var(--experiments-bottom-clearance);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  max-width: 640px;
  margin: 0 auto;
  text-align: center;
  padding: 0 var(--gutter);
  /* The block is wider than the copy inside it, so it must never
     swallow clicks meant for the tiles behind it. Only the link
     itself takes pointer events (re-enabled by experiments-grid.js
     once the copy has faded in). */
  pointer-events: none;
}

.experiments__desc {
  font-size: 15px;
  line-height: 1.6;
  color: var(--color-ink-soft);
  margin: 0 0 14px;
}

.experiments__cta {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--color-ink);
  padding-bottom: 2px;
  transition: color 0.3s ease, border-color 0.3s ease, letter-spacing 0.3s ease;
}

@media (hover: hover) and (pointer: fine) {
  .experiments__cta:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
    letter-spacing: 0.06em;
  }
}

/* ==========================================================================
   CURRENTLY EXPLORING - scroll-driven motion path
   (see exploring-motion-path.js - GSAP MotionPathPlugin)
   ========================================================================== */
.exploring {
  position: relative;
  padding: 140px 0 0;
  background: var(--color-surface-dark);
  color: #fff;
  overflow: hidden;
}

/* Starfield - two tiled layers of tiny radial-gradient dots, each
   twinkling on its own cycle so the flicker feels irregular instead
   of a single pulsing block. Pure CSS, no extra markup or JS, sits
   behind everything else in the section. */
.exploring::before,
.exploring::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-repeat: repeat;
}

.exploring::before {
  background-image:
    radial-gradient(1.6px 1.6px at 10% 8%, rgba(255, 255, 255, 0.9), transparent 100%),
    radial-gradient(1.2px 1.2px at 32% 22%, rgba(255, 255, 255, 0.7), transparent 100%),
    radial-gradient(1.6px 1.6px at 58% 6%, rgba(255, 255, 255, 0.85), transparent 100%),
    radial-gradient(1.2px 1.2px at 74% 30%, rgba(255, 255, 255, 0.6), transparent 100%),
    radial-gradient(1.6px 1.6px at 90% 14%, rgba(255, 255, 255, 0.9), transparent 100%),
    radial-gradient(1.2px 1.2px at 15% 48%, rgba(255, 255, 255, 0.6), transparent 100%),
    radial-gradient(1.6px 1.6px at 45% 60%, rgba(255, 255, 255, 0.8), transparent 100%),
    radial-gradient(1.2px 1.2px at 68% 74%, rgba(255, 255, 255, 0.65), transparent 100%),
    radial-gradient(1.6px 1.6px at 85% 55%, rgba(255, 255, 255, 0.85), transparent 100%),
    radial-gradient(1.2px 1.2px at 25% 88%, rgba(255, 255, 255, 0.6), transparent 100%),
    radial-gradient(1.6px 1.6px at 52% 94%, rgba(255, 255, 255, 0.8), transparent 100%),
    radial-gradient(1.2px 1.2px at 95% 86%, rgba(255, 255, 255, 0.65), transparent 100%);
  animation: exploring-twinkle-a 3.4s ease-in-out infinite;
}

.exploring::after {
  background-image:
    radial-gradient(1.2px 1.2px at 6% 30%, rgba(255, 255, 255, 0.75), transparent 100%),
    radial-gradient(1px 1px at 22% 65%, rgba(255, 255, 255, 0.6), transparent 100%),
    radial-gradient(1.4px 1.4px at 40% 12%, rgba(255, 255, 255, 0.8), transparent 100%),
    radial-gradient(1px 1px at 62% 42%, rgba(255, 255, 255, 0.55), transparent 100%),
    radial-gradient(1.4px 1.4px at 80% 68%, rgba(255, 255, 255, 0.8), transparent 100%),
    radial-gradient(1px 1px at 12% 78%, rgba(255, 255, 255, 0.6), transparent 100%),
    radial-gradient(1.4px 1.4px at 35% 92%, rgba(255, 255, 255, 0.75), transparent 100%),
    radial-gradient(1px 1px at 55% 80%, rgba(255, 255, 255, 0.55), transparent 100%),
    radial-gradient(1.4px 1.4px at 97% 40%, rgba(255, 255, 255, 0.8), transparent 100%);
  animation: exploring-twinkle-b 2.6s ease-in-out infinite;
  animation-delay: 0.5s;
}

@keyframes exploring-twinkle-a {
  0%,
  100% {
    opacity: 0.4;
  }
  50% {
    opacity: 1;
  }
}

@keyframes exploring-twinkle-b {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

.exploring__viewport {
  position: relative;
  z-index: 1;
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.exploring__head {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin: 0 0 24px;
}

.exploring__index {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
}

.exploring__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(36px, 4.6vw, 64px);
  letter-spacing: -0.02em;
}

.exploring__intro {
  max-width: 420px;
  margin: 0;
  font-size: 16px;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.56);
}

.exploring__hint {
  display: inline-block;
  margin-top: 20px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  animation: exploring-hint-bob 2.2s ease-in-out infinite;
}

@keyframes exploring-hint-bob {
  0%,
  100% {
    transform: translateY(0);
    opacity: 0.4;
  }
  50% {
    transform: translateY(4px);
    opacity: 0.7;
  }
}

/* ---- The scroll track itself - mirrors the original codepen's
   ".main" + ".container" + ".marker"/".box" structure, just themed
   and with a text label added next to each waypoint. ---- */
.exploring__main {
  position: relative;
  height: 260vh;
  margin-top: 64px;
}

.exploring__waypoint {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.exploring__waypoint--initial {
  left: 60%;
  top: 5%;
}

.exploring__waypoint--01 {
  left: 8%;
  top: 24%;
}

.exploring__waypoint--02 {
  right: 8%;
  top: 44%;
}

.exploring__waypoint--03 {
  left: 14%;
  top: 64%;
}

.exploring__waypoint--04 {
  left: 54%;
  top: 80%;
}

.exploring__waypoint--05 {
  left: 10%;
  top: 95%;
}

/* Each waypoint marker is now one of the real planet icons
   (assets/icons/planet1–5.png) instead of a CSS-drawn sphere. The
   marker span stays as the sized, positioned box the motion-path JS
   measures; the icon fills it, and a blurred color-matched halo
   (::after) sits behind it for a bit of atmosphere glow against the
   starfield. --planet-glow (set per waypoint below) drives that
   halo's color plus the active-state glow filter on the icon. */
.exploring__marker {
  position: relative;
  width: 40px;
  height: 40px;
  transition: transform 0.4s var(--ease-out);
}

.exploring__marker-icon {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
  transition: filter 0.4s var(--ease-out);
}

.exploring__marker::after {
  content: "";
  position: absolute;
  inset: -12px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--planet-glow, rgba(255, 255, 255, 0.5)), transparent 72%);
  filter: blur(10px);
  opacity: 0.4;
  z-index: 0;
  animation: exploring-planet-glow 4.2s ease-in-out infinite;
}

@keyframes exploring-planet-glow {
  0%,
  100% {
    opacity: 0.28;
  }
  50% {
    opacity: 0.55;
  }
}

.exploring__waypoint--01 {
  --planet-glow: #3f9be0;
}

.exploring__waypoint--02 {
  --planet-glow: #ff9a3f;
}

.exploring__waypoint--03 {
  --planet-glow: #b06fe0;
}

.exploring__waypoint--04 {
  --planet-glow: #ffcf4a;
}

.exploring__waypoint--05 {
  --planet-glow: #ff5a4a;
}

.exploring__waypoint.is-active .exploring__marker {
  transform: scale(1.18);
}

.exploring__waypoint.is-active .exploring__marker-icon {
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35)) drop-shadow(0 0 12px var(--planet-glow, var(--color-accent)));
}

.exploring__waypoint.is-active .exploring__marker::after {
  opacity: 0.8;
}

/* The travelling rocket - assets/icons/rocket.png. Nose points up by
   default in the source art; the icon itself is rotated 90° so
   "0deg" points right, matching autoRotate's reference direction,
   and GSAP handles the rest as it eases along the path. Sized up a
   notch from the original so it reads clearly against the planets.
   The box itself gets a 180° default rotation so that at rest -
   before scroll drives GSAP's own rotation via autoRotate - the
   rocket faces left instead of right. GSAP overwrites this with an
   inline transform the moment the scrubbed animation starts, so it
   only affects the pre-scroll resting pose. */
.exploring__box {
  width: 72px;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
  transform: rotate(180deg);
  filter: drop-shadow(0 0 16px rgba(140, 122, 99, 0.6));
}

.exploring__box-icon {
  width: auto;
  height: 60px;
  display: block;
  transform: rotate(90deg);
}

.exploring__label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  opacity: 0;
  transform: translateY(10px) scale(0.94);
  transition: opacity 0.25s var(--ease-out), transform 0.25s var(--ease-out);
}

/* Label stays hidden until the rocket actually reaches this waypoint,
   then eases in - this is the "text keluar" the marker reveal is for. */
.exploring__waypoint.is-active .exploring__label {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.exploring__label-index {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--color-accent);
}

.exploring__label-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
}

/* Mirrors the codepen's ".spacer.final" - gives the scroll trigger
   somewhere to end at, right after the last waypoint. */
.exploring__final {
  height: 20vh;
}

/* ---- Space FX: nebula clouds, drifting stardust, passing meteors ----
   Sits behind .exploring__viewport (z-index 0, same layer as the
   twinkling starfield). Everything here animates transform/opacity
   only, so it stays cheap even though several pieces run at once. */
.exploring__space-fx {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Soft, slow-drifting color washes - reuses the same hues as the
   planet markers so the whole section feels like one palette. Heavy
   blur keeps them a background wash rather than a solid shape. */
.exploring__nebula {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.2;
  animation: exploring-nebula-drift 28s ease-in-out infinite;
}

.exploring__nebula--01 {
  top: 4%;
  left: 10%;
  width: 380px;
  height: 380px;
  background: radial-gradient(circle, #6f56a8, transparent 70%);
}

.exploring__nebula--02 {
  top: 46%;
  right: 6%;
  width: 340px;
  height: 340px;
  background: radial-gradient(circle, #3f7ea6, transparent 70%);
  animation-duration: 34s;
  animation-delay: -8s;
}

.exploring__nebula--03 {
  bottom: 2%;
  left: 36%;
  width: 300px;
  height: 300px;
  background: radial-gradient(circle, #a4557f, transparent 70%);
  animation-duration: 24s;
  animation-delay: -15s;
}

@keyframes exploring-nebula-drift {
  0%,
  100% {
    transform: translate(0, 0) scale(1);
  }
  50% {
    transform: translate(22px, -16px) scale(1.05);
  }
}

/* Loose stardust motes drifting slowly through the section - distinct
   from the fixed twinkling starfield, which stays in place. */
.exploring__stardust {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 6px 1px rgba(255, 255, 255, 0.75);
  opacity: 0;
  animation: exploring-stardust-drift linear infinite;
}

.exploring__stardust--01 {
  top: 14%;
  left: 22%;
  animation-duration: 22s;
}

.exploring__stardust--02 {
  top: 38%;
  left: 68%;
  animation-duration: 30s;
  animation-delay: -6s;
}

.exploring__stardust--03 {
  top: 66%;
  left: 30%;
  animation-duration: 26s;
  animation-delay: -13s;
}

.exploring__stardust--04 {
  top: 84%;
  left: 74%;
  animation-duration: 34s;
  animation-delay: -20s;
}

@keyframes exploring-stardust-drift {
  0% {
    transform: translate(0, 0);
    opacity: 0;
  }
  10%,
  90% {
    opacity: 0.55;
  }
  100% {
    transform: translate(60px, -90px);
    opacity: 0;
  }
}

/* Meteors - a bright head with a fading tail. Each one sits invisible
   for most of its (long) animation loop and only flashes/streaks
   across in the final stretch, so they read as occasional passers-by
   rather than a constant looping effect. Two meteors, offset timing,
   so they never fire in sync. */
.exploring__meteor {
  position: absolute;
  width: 110px;
  height: 2px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0));
  border-radius: 999px;
  opacity: 0;
  animation: exploring-meteor linear infinite;
}

.exploring__meteor--01 {
  top: 8%;
  right: 14%;
  animation-duration: 9s;
  animation-delay: 1s;
}

.exploring__meteor--02 {
  top: 52%;
  right: 34%;
  animation-duration: 13s;
  animation-delay: 6s;
}

@keyframes exploring-meteor {
  0%,
  90% {
    opacity: 0;
    transform: translate(0, 0) rotate(-32deg);
  }
  92% {
    opacity: 1;
  }
  97% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate(-260px, 170px) rotate(-32deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .exploring__hint {
    animation: none;
  }

  .exploring::before,
  .exploring::after {
    animation: none;
    opacity: 0.6;
  }

  .exploring__marker::after {
    animation: none;
    opacity: 0.4;
  }

  .exploring__nebula,
  .exploring__stardust,
  .exploring__meteor {
    animation: none;
  }

  .exploring__nebula {
    opacity: 0.15;
  }

  .exploring__stardust,
  .exploring__meteor {
    display: none;
  }
}



/* ==========================================================================
   CONTACT
   ========================================================================== */
.contact {
  position: relative;
  padding: 160px var(--gutter) 140px;
  background: var(--color-surface-dark);
  color: #fff;
  overflow: hidden;
}

.contact__inner {
  max-width: 1000px;
  margin: 0 auto;
}

.contact__index {
  display: block;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-accent);
  letter-spacing: 0.06em;
  margin-bottom: 28px;
}

.contact__heading {
  margin: 0 0 32px;
  line-height: 0.92;
}

.contact__heading-line {
  display: block;
  overflow: hidden;
}

.contact__heading-line span {
  display: block;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(48px, 8vw, 108px);
  letter-spacing: -0.02em;
}

.contact__sub {
  max-width: 420px;
  font-size: 17px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.68);
  margin: 0 0 56px;
}

.contact__links {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 40px;
}

.contact__link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
  transition: color 0.3s var(--ease-out);
}

.contact__link:hover {
  color: #fff;
}

.contact__link-arrow {
  transition: transform 0.35s var(--ease-out);
}

.contact__link:hover .contact__link-arrow {
  transform: translateX(5px);
}

@media (prefers-reduced-motion: reduce) {
  .contact__link,
  .contact__link-arrow {
    transform: none !important;
  }
}

/* ==========================================================================
   FOOTER REVEAL - bouncy curtain, morphs from a wave into a flat line
   right as it scrolls into view. See js/footer-bounce.js.
   ========================================================================== */
.footer-reveal {
  position: relative;
  width: 100%;
  line-height: 0;
  background: var(--color-surface-dark);
  overflow: hidden;
}

.footer-reveal__svg {
  display: block;
  width: 100%;
  height: max(96px, calc(100vw * 683 / 2278));
}

/* Same grain treatment as the rest of the site's dark sections, kept
   subtle so the gradient doesn't read as a flat digital block. */
.footer-reveal::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("https://assets.codepen.io/16327/noise.png");
  background-blend-mode: color-dodge;
  opacity: 0.5;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .footer-reveal::after {
    display: none;
  }
}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.footer {
  background: var(--color-surface-dark);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.footer__inner {
  max-width: 1240px;
  margin: 0 auto;
  padding: 32px var(--gutter);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 32px;
}

.footer__logo {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: #fff;
}

.footer__copy {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
}

.footer__links {
  display: flex;
  gap: 10px;
  margin-left: auto;
}

.footer__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.6);
  transition: color 0.3s var(--ease-out), background-color 0.3s var(--ease-out),
    transform 0.35s var(--ease-out);
}

.footer__link:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  transform: translateY(-2px);
}

.footer__link-icon {
  width: 17px;
  height: 17px;
}

.footer__link-icon svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.footer__link-icon svg .icon-dot {
  fill: currentColor;
  stroke: none;
}

@media (prefers-reduced-motion: reduce) {
  .footer__link:hover {
    transform: none;
  }
}

.footer__note {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.35);
}

@media (max-width: 720px) {
  .footer__links {
    margin-left: 0;
  }
}

/* ==========================================================================
   WORK PANEL - action row (View Detail + Source Code side by side)
   ========================================================================== */
.work__panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin-top: 4px;
}

.work__panel-details {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

/* ==========================================================================
   PROJECT DETAIL MODAL
   ========================================================================== */
.no-scroll {
  overflow: hidden;
}

.project-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
}

.project-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(11, 11, 10, 0.88);
}

.project-modal__panel {
  position: relative;
  z-index: 1;
  width: min(760px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  background: var(--color-paper);
  color: var(--color-ink);
  padding: 56px 48px;
  border: 1px solid var(--color-line);
}

.project-modal__close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 36px;
  height: 36px;
  border: 1px solid var(--color-line);
  background: transparent;
  color: var(--color-ink);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.2s var(--ease-out), color 0.2s var(--ease-out);
}

.project-modal__close:hover {
  background: var(--color-ink);
  color: var(--color-paper);
}

.project-modal__category {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  margin: 0 0 8px;
}

.project-modal__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  margin: 0 0 12px;
}

.project-modal__status {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-accent);
  border: 1px solid var(--color-line);
  padding: 4px 10px;
  margin-bottom: 32px;
}

.project-modal__section {
  margin-bottom: 28px;
}

.project-modal__section h3 {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  margin: 0 0 8px;
}

.project-modal__section p {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.65;
  margin: 0;
}

.project-modal__list {
  margin: 0;
  padding-left: 18px;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.7;
}

.project-modal__list li {
  margin-bottom: 4px;
}

.project-modal__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  border-top: 1px solid var(--color-line);
  padding-top: 24px;
  margin-bottom: 24px;
}

.project-modal__grid .project-modal__section {
  margin-bottom: 0;
}

.project-modal__links {
  display: flex;
  gap: 24px;
  padding-top: 8px;
  border-top: 1px solid var(--color-line);
}

.project-modal__links a {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-ink);
  text-decoration: none;
  border-bottom: 1px solid var(--color-ink);
  padding-bottom: 2px;
}

@media (max-width: 680px) {
  .project-modal__panel {
    padding: 40px 24px;
  }
  .project-modal__grid {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   EXPERIMENT DEMO MODAL
   Shared host for the nine live demos in the Experiments section. The
   panel chrome is dark so each demo's own stage is what carries the
   colour, rather than the light .project-modal chrome.
   Demo styles below are grouped by experiment, in grid order.
   ========================================================================== */

.experiment-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
}

.experiment-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(11, 11, 10, 0.92);
}

.experiment-modal__panel {
  position: relative;
  z-index: 1;
  width: min(920px, 100%);
  max-height: 92vh;
  overflow: hidden;
  background: var(--color-surface-dark);
  color: var(--color-bg);
  border: 1px solid rgba(246, 245, 242, 0.14);
  padding: 28px 28px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.experiment-modal__close {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 2;
  width: 36px;
  height: 36px;
  border: 1px solid rgba(246, 245, 242, 0.3);
  background: transparent;
  color: var(--color-bg);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.2s var(--ease-out), color 0.2s var(--ease-out);
}

.experiment-modal__close:hover {
  background: var(--color-bg);
  color: var(--color-surface-dark);
}

.experiment-modal__head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding-right: 40px;
}

.experiment-modal__index {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-accent);
}

.experiment-modal__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
}

.experiment-modal__stage {
  position: relative;
  width: 100%;
  height: clamp(300px, 56vh, 520px);
  overflow: hidden;
  background: #0e0e0d;
  border: 1px solid rgba(246, 245, 242, 0.1);
  /* Keeps the Cursor Follower's difference-blend ring blending against
     the demo behind it, not against the modal chrome and backdrop. */
  isolation: isolate;
}

.experiment-modal__hint {
  min-height: 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: rgba(246, 245, 242, 0.55);
  text-align: center;
}

/* --------------------------------------------------------------------------
   SHARED DEMO CHROME
   -------------------------------------------------------------------------- */

.exp-root {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #0e0e0d;
}

.exp-controls {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  gap: 4px;
  padding: 4px;
  background: rgba(11, 11, 10, 0.82);
  border: 1px solid rgba(246, 245, 242, 0.14);
}

.exp-controls__btn {
  padding: 6px 11px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: rgba(246, 245, 242, 0.6);
  background: transparent;
  border: 1px solid transparent;
  transition: color 0.25s ease, background-color 0.25s ease;
}

.exp-controls__btn:hover {
  color: var(--color-bg);
}

.exp-controls__btn.is-active {
  color: #0e0e0d;
  background: var(--color-bg);
}

.exp-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 24px;
  text-align: center;
  font-family: var(--font-body);
  font-size: 14px;
  color: rgba(246, 245, 242, 0.7);
}

/* --------------------------------------------------------------------------
   01 - TEXT REVEAL
   -------------------------------------------------------------------------- */

.exp-tr {
  display: flex;
  align-items: center;
  cursor: pointer;
}

.exp-tr__type {
  width: 100%;
  padding: 0 clamp(20px, 6%, 56px);
  perspective: 900px;
}

.exp-tr__kicker {
  margin: 0 0 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--color-accent);
}

.exp-tr__line {
  position: relative;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(28px, 6vw, 62px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: var(--color-bg);
  transform-style: preserve-3d;
}

.exp-tr__line + .exp-tr__line {
  color: var(--color-accent);
}

.exp-tr__char {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.exp-tr__char-in {
  display: inline-block;
}

.exp-tr__sweep {
  position: absolute;
  left: 0;
  top: 14%;
  width: 100%;
  height: 72%;
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   02 - MAGNETIC CURSOR
   -------------------------------------------------------------------------- */

.exp-mg__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.exp-mg__magnet {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3;
  padding: 17px 30px;
  border: 1px solid var(--color-accent);
  background: rgba(11, 11, 10, 0.72);
  color: var(--color-bg);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  transform: translate(-50%, -50%);
}

.exp-mg__magnet-label {
  display: inline-block;
}

/* --------------------------------------------------------------------------
   03 - IMAGE PARALLAX (depth field)
   -------------------------------------------------------------------------- */

.exp-depth {
  position: relative;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: grab;
}

.exp-depth:active {
  cursor: grabbing;
}

.exp-depth__canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.exp-depth__label {
  position: absolute;
  left: 16px;
  bottom: 16px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-mono);
  color: var(--color-bg);
  pointer-events: none;
  transition: opacity 0.18s ease;
}

.exp-depth__label.is-changing {
  opacity: 0;
}

.exp-depth__label-index {
  font-size: 11px;
  color: var(--color-accent);
}

.exp-depth__label-name {
  font-size: 13px;
}

/* --------------------------------------------------------------------------
   04 - CURSOR FOLLOWER
   -------------------------------------------------------------------------- */

.exp-cf {
  cursor: none;
}

.exp-cf .exp-controls {
  cursor: auto;
}

.exp-cf__type {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.exp-cf__word {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(32px, 8vw, 80px);
  line-height: 0.94;
  letter-spacing: -0.035em;
  color: #2b2a27;
}

.exp-cf__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.exp-cf__ring {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 4;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--color-bg);
  mix-blend-mode: difference;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   05 - HORIZONTAL SCROLL
   -------------------------------------------------------------------------- */

.exp-hs {
  cursor: grab;
  touch-action: none;
}

.exp-hs.is-dragging {
  cursor: grabbing;
}

.exp-hs__track {
  position: absolute;
  top: 0;
  bottom: 42px;
  left: 24px;
  display: flex;
  align-items: center;
  gap: 16px;
}

.exp-hs__card {
  position: relative;
  flex: 0 0 auto;
  width: 228px;
  height: 262px;
  padding: 18px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 7px;
  background: rgba(246, 245, 242, 0.04);
  border: 1px solid rgba(246, 245, 242, 0.14);
  transition: border-color 0.4s ease, background-color 0.4s ease;
}

.exp-hs__card.is-active {
  border-color: var(--color-accent);
  background: rgba(140, 122, 99, 0.16);
}

.exp-hs__card-index {
  position: absolute;
  top: 16px;
  left: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-accent);
}

.exp-hs__card-name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 19px;
  line-height: 1.16;
}

.exp-hs__card-stack {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.7;
  color: rgba(246, 245, 242, 0.55);
}

.exp-hs__card-year {
  font-family: var(--font-mono);
  font-size: 10px;
  color: rgba(246, 245, 242, 0.32);
}

.exp-hs__progress {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 22px;
  height: 1px;
  background: rgba(246, 245, 242, 0.16);
}

.exp-hs__progress-fill {
  display: block;
  height: 100%;
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left center;
}

.exp-hs__counter {
  position: absolute;
  right: 24px;
  bottom: 34px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: rgba(246, 245, 242, 0.5);
}

/* --------------------------------------------------------------------------
   06 - PARTICLE MORPH
   -------------------------------------------------------------------------- */

.exp-pm {
  cursor: crosshair;
  touch-action: none;
}

.exp-pm__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* --------------------------------------------------------------------------
   07 - MASK REVEAL
   -------------------------------------------------------------------------- */

.exp-mr {
  cursor: crosshair;
  touch-action: none;
}

.exp-mr__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* --------------------------------------------------------------------------
   08 - TYPOGRAPHY MOVEMENT
   -------------------------------------------------------------------------- */

.exp-tm__field {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.exp-tm__row {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(20px, 4.2vw, 42px);
  letter-spacing: -0.015em;
  color: var(--color-bg);
  white-space: nowrap;
}

.exp-tm__row--big {
  font-size: clamp(28px, 5.8vw, 58px);
}

.exp-tm__letter {
  display: inline-block;
}

.exp-tm__ring {
  position: relative;
  width: 100%;
  height: 100%;
}

.exp-tm__orbit-letter {
  position: absolute;
  left: 50%;
  top: 50%;
  font-family: var(--font-mono);
  font-size: clamp(12px, 1.6vw, 17px);
  color: var(--color-bg);
}

.exp-tm__ring-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(24px, 4vw, 42px);
  letter-spacing: -0.02em;
  color: var(--color-accent);
}

/* --------------------------------------------------------------------------
   09 - LIQUID METAL
   -------------------------------------------------------------------------- */

.exp-lm {
  cursor: crosshair;
  touch-action: none;
}

.exp-lm__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

@media (max-width: 680px) {
  .experiment-modal {
    padding: 20px 12px;
  }
  .experiment-modal__panel {
    padding: 22px 16px 16px;
  }
  .experiment-modal__title {
    font-size: 18px;
  }
  .experiment-modal__stage {
    height: clamp(260px, 44vh, 420px);
  }
  .exp-hs__card {
    width: 176px;
    height: 216px;
  }
  .exp-cf__word {
    font-size: clamp(26px, 12vw, 50px);
  }
}

/* ==========================================================================
   PIXEL CURSOR
   One site-wide custom cursor with five pixel-art states, injected
   and fully driven by js/cursor.js (fine-pointer desktops only, off
   entirely under prefers-reduced-motion):

     - normal        a tiny 16×16 pixel dot, shown across most of the
                      site. Colour flips between the two theme tokens
                      below depending on which [data-theme] section
                      the pointer is over, so it stays visible on both
                      light and dark backgrounds.
     - hero          a 20×20 accent-yellow crosshair with a small gap
                      at the centre, shown only inside the hero.
     - hover         an 18×18 hollow pixel box with a small pixel-font
                      label chip ("View Project" / "Open Link" / etc,
                      set by js/cursor.js depending on what's under
                      the pointer) - used for project previews, demo
                      cards, and every other interactive element.
     - click (.is-down) a 14×14 set of four corner ticks, shown for as
                      long as the mouse button is held, layered over
                      whichever of the states above was already active.

   Paused (native cursor shown instead) inside any open modal (where
   each demo/panel sets its own cursor - grab, crosshair, none, or
   the Cursor Follower demo's own blend ring - and a second cursor on
   top would just double up).
   ========================================================================== */

.pixel-cursor {
  --cursor-light: #f5f5f5;
  --cursor-dark: #17160f;
  --cursor-accent: #ffd866;
  --cursor-color: var(--cursor-light);

  position: fixed;
  top: 0;
  left: 0;
  z-index: 500;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.pixel-cursor.is-visible {
  opacity: 1;
}

/* Theme-aware colour for the states that need to read against
   whatever section the pointer happens to be over. */
.pixel-cursor.is-on-light {
  --cursor-color: var(--cursor-dark);
}

.pixel-cursor__dot,
.pixel-cursor__cross,
.pixel-cursor__box,
.pixel-cursor__ticks {
  position: absolute;
  top: 0;
  left: 0;
  display: none;
}

/* -- 01. Normal -- 16×16, small solid pixel square -- */
.pixel-cursor__dot {
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  background: var(--cursor-color);
  transition: background-color 0.2s ease;
}

/* -- 02. Hero -- 20×20 crosshair, accent yellow, gap at centre -- */
.pixel-cursor__cross {
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
}

.pixel-cursor__cross i {
  position: absolute;
  display: block;
  background: var(--cursor-accent);
}

.pixel-cursor__cross i:nth-child(1),
.pixel-cursor__cross i:nth-child(2) {
  left: 50%;
  width: 2px;
  height: 7px;
  transform: translateX(-50%);
}

.pixel-cursor__cross i:nth-child(1) {
  top: 0;
}

.pixel-cursor__cross i:nth-child(2) {
  bottom: 0;
}

.pixel-cursor__cross i:nth-child(3),
.pixel-cursor__cross i:nth-child(4) {
  top: 50%;
  width: 7px;
  height: 2px;
  transform: translateY(-50%);
}

.pixel-cursor__cross i:nth-child(3) {
  left: 0;
}

.pixel-cursor__cross i:nth-child(4) {
  right: 0;
}

/* -- 03/04. Hover project / Hover link -- 18×18 hollow pixel box + label -- */
.pixel-cursor__box {
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border: 2px solid var(--cursor-color);
  transition: border-color 0.2s ease;
}

.pixel-cursor__label {
  position: absolute;
  top: 0;
  left: 0;
  display: none;
  transform: translate(10px, 10px);
  padding: 5px 8px;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cursor-color);
  background: rgba(11, 11, 10, 0.85);
  border: 1px solid var(--cursor-color);
}

.pixel-cursor.is-on-light .pixel-cursor__label {
  background: rgba(245, 245, 245, 0.9);
}

/* -- 05. Click -- 14×14, four accent-yellow corner ticks -- */
.pixel-cursor__ticks {
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
}

.pixel-cursor__ticks i {
  position: absolute;
  display: block;
  width: 5px;
  height: 5px;
}

.pixel-cursor__ticks i:nth-child(1) {
  top: 0;
  left: 0;
  border-top: 2px solid var(--cursor-accent);
  border-left: 2px solid var(--cursor-accent);
}

.pixel-cursor__ticks i:nth-child(2) {
  top: 0;
  right: 0;
  border-top: 2px solid var(--cursor-accent);
  border-right: 2px solid var(--cursor-accent);
}

.pixel-cursor__ticks i:nth-child(3) {
  bottom: 0;
  left: 0;
  border-bottom: 2px solid var(--cursor-accent);
  border-left: 2px solid var(--cursor-accent);
}

.pixel-cursor__ticks i:nth-child(4) {
  bottom: 0;
  right: 0;
  border-bottom: 2px solid var(--cursor-accent);
  border-right: 2px solid var(--cursor-accent);
}

/* -- State switching -- */
.pixel-cursor[data-state="normal"] .pixel-cursor__dot {
  display: block;
}

.pixel-cursor[data-state="hero"] .pixel-cursor__cross {
  display: block;
}

.pixel-cursor[data-state="hover"] .pixel-cursor__box {
  display: block;
}

.pixel-cursor[data-state="hover"] .pixel-cursor__label {
  display: block;
}

/* Click feedback wins over whatever state was showing. */
.pixel-cursor.is-down .pixel-cursor__dot,
.pixel-cursor.is-down .pixel-cursor__cross,
.pixel-cursor.is-down .pixel-cursor__box,
.pixel-cursor.is-down .pixel-cursor__label {
  display: none;
}

.pixel-cursor.is-down .pixel-cursor__ticks {
  display: block;
}

/* Toggled on <body> by js/cursor.js the moment the site initialises
   on desktop, and removed only while a modal/demo needs its own
   native cursor language (grab, crosshair, etc.) instead of ours.

   The `*` selector is required: plenty of elements (button,
   .experiments__item--live, .work__panel-details, .exp-tr, ...) set
   their own explicit `cursor: pointer`/`grab` directly. An explicit
   cursor on an element always wins over an inherited value no matter
   how specific the ancestor rule is, so without this, hovering any
   of those would show the real OS pointer stacked on top of our
   pixel box - exactly the "default cursor" this rule kills. The
   !important is what lets `cursor: none` win against those explicit,
   per-element declarations everywhere except inside a modal, where
   removing .has-pixel-cursor from <body> lets them show through
   again on purpose. */
@media (hover: hover) and (pointer: fine) {
  body.has-pixel-cursor,
  body.has-pixel-cursor * {
    cursor: none !important;
  }
}


/* ==========================================================================
   PRELOADER
   Two stages in one fixed black overlay:
     Stage 1 (.preloader__stage--line) - resn.co.nz-style minimal beat:
       one small mark + a thin center line growing outward.
     Stage 2 (.preloader__stage--shutter) - glitch counter, scramble
       text, camera-shutter strip reveal.
   Both stages sit in the same full-black `.preloader`, so crossfading
   between them never shows a seam - only the content within changes.
   ========================================================================== */
html.is-loading,
body.is-loading {
  overflow: hidden;
  height: 100%;
}

.preloader {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
  background: #000;
}

.preloader__stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* ---------- Stage 1: minimal line ---------- */

.preloader__stage--line {
  gap: 28px;
}

/* Small diamond mark - a rotated pixel/dot, echoing the hero's pixel
   motif rather than a literal logo. Sits still; nothing bounces. */
.preloader__mark {
  width: 8px;
  height: 8px;
  background: var(--color-paper);
  transform: rotate(45deg);
}

/* The line's soft, tapered ends come from the gradient itself - scaling
   the element (not its width) keeps that taper proportional as it grows. */
.preloader__track {
  display: block;
  width: clamp(220px, 24vw, 320px);
  height: 1px;
}

.preloader__fill {
  display: block;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(251, 250, 248, 0.7) 50%,
    transparent 100%
  );
  transform: scaleX(0);
  transform-origin: center;
}

/* ---------- Stage 2: glitch counter + shutter reveal ---------- */

.preloader__stage--shutter {
  opacity: 0;
}

.preloader__shutter {
  position: absolute;
  inset: 0;
  display: flex;
}

.preloader__strip {
  flex: 1 0 0;
  height: 100%;
  background: var(--color-surface-dark);
  border-right: 1px solid rgba(251, 250, 248, 0.06);
}

.preloader__strip:last-child {
  border-right: none;
}

/* Alternating transform-origin so neighboring strips collapse from
   opposite edges - reads as a shutter/blind, not a uniform wipe. */
.preloader__strip:nth-child(odd) {
  transform-origin: top center;
}

.preloader__strip:nth-child(even) {
  transform-origin: bottom center;
}

.preloader__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

.preloader__eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-paper);
  opacity: 0.65;
}

.preloader__count {
  position: relative;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(72px, 16vw, 220px);
  line-height: 1;
  color: var(--color-paper);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

/* Glitch flicker applied briefly + periodically via JS while counting -
   a duplicated, color-split ghost of the digits, RGB-split style. */
.preloader__count.is-glitching::before,
.preloader__count.is-glitching::after {
  content: attr(data-value);
  position: absolute;
  inset: 0;
  color: var(--color-paper);
}

.preloader__count.is-glitching::before {
  transform: translate(2px, 0);
  opacity: 0.6;
  mix-blend-mode: screen;
  clip-path: inset(0 0 55% 0);
}

.preloader__count.is-glitching::after {
  transform: translate(-2px, 0);
  opacity: 0.6;
  mix-blend-mode: screen;
  clip-path: inset(55% 0 0 0);
}

.preloader__bar {
  width: clamp(160px, 22vw, 280px);
  height: 1px;
  background: rgba(251, 250, 248, 0.2);
  overflow: hidden;
}

.preloader__bar-fill {
  display: block;
  width: 0%;
  height: 100%;
  background: var(--color-paper);
  transform-origin: left center;
}

.preloader__wordmark {
  position: absolute;
  bottom: var(--gutter);
  left: var(--gutter);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--color-paper);
  opacity: 0.7;
  z-index: 1;
}

.preloader__index {
  position: absolute;
  bottom: var(--gutter);
  right: var(--gutter);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--color-paper);
  opacity: 0.7;
  z-index: 1;
}

@media (max-width: 720px) {
  .preloader__track {
    width: clamp(160px, 55vw, 240px);
  }

  .preloader__wordmark,
  .preloader__index {
    bottom: 24px;
  }

  .preloader__wordmark {
    left: 24px;
  }

  .preloader__index {
    right: 24px;
  }

  .preloader__strip {
    border-right: none;
  }
}

/* --------------------------------------------------------------------------
   Cinematic preloader override
   The legacy selector names above are no longer used by the current markup.
   -------------------------------------------------------------------------- */
.preloader {
  overflow: hidden;
  background: transparent;
}

.preloader__loading-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #100f14;
  clip-path: inset(0 50% 0 50%);
  will-change: clip-path;
}

.preloader__slice {
  position: absolute;
  z-index: 4;
  top: 0;
  left: 50%;
  width: 2px;
  height: 100%;
  background: #050507;
  box-shadow: 0 0 14px rgba(5, 5, 7, 0.3);
  transform: translateX(-50%) scaleY(0);
  transform-origin: top center;
  will-change: transform, opacity;
}

.preloader__curtain {
  position: absolute;
  top: 0;
  z-index: 1;
  width: calc(50% + 1px);
  height: 100%;
  background: #242424;
  will-change: transform;
}

.preloader__curtain--left {
  left: 0;
  transform-origin: right center;
}

.preloader__curtain--right {
  right: 0;
  transform-origin: left center;
}

.preloader__composition {
  --preloader-pixel: 3px;
  position: absolute;
  z-index: 5;
  top: 50%;
  left: 50%;
  width: clamp(96px, 18vw, 192px);
  height: 32px;
  transform: translate(-50%, -50%);
}

.preloader__object {
  --preloader-icon-primary: #050507;
  --preloader-icon-highlight: #18191f;
  --preloader-icon-shadow: #2b2d35;
  position: absolute;
  top: 0;
  left: calc(50% - (var(--preloader-pixel) * 2));
  width: var(--preloader-pixel);
  height: var(--preloader-pixel);
  visibility: hidden;
  background: var(--preloader-icon-primary);
  box-shadow:
    calc(var(--preloader-pixel) * 1) 0 var(--preloader-icon-primary),
    calc(var(--preloader-pixel) * 2) 0 var(--preloader-icon-highlight),
    0 calc(var(--preloader-pixel) * 1) var(--preloader-icon-primary),
    calc(var(--preloader-pixel) * 1) calc(var(--preloader-pixel) * 1) var(--preloader-icon-shadow),
    calc(var(--preloader-pixel) * 2) calc(var(--preloader-pixel) * 1) var(--preloader-icon-primary),
    calc(var(--preloader-pixel) * 3) calc(var(--preloader-pixel) * 1) var(--preloader-icon-highlight),
    calc(var(--preloader-pixel) * 1) calc(var(--preloader-pixel) * 2) var(--preloader-icon-primary),
    calc(var(--preloader-pixel) * 2) calc(var(--preloader-pixel) * 2) var(--preloader-icon-highlight),
    calc(var(--preloader-pixel) * 3) calc(var(--preloader-pixel) * 2) var(--preloader-icon-primary),
    calc(var(--preloader-pixel) * 2) calc(var(--preloader-pixel) * 3) var(--preloader-icon-primary);
  image-rendering: pixelated;
  transform-origin: center;
  will-change: transform, background, box-shadow;
  z-index: 1;
}

.preloader__progress {
  position: absolute;
  top: 23px;
  left: 0;
  width: 100%;
  height: 2px;
  visibility: hidden;
  opacity: 0;
  background: rgba(5, 5, 7, 0.22);
  box-shadow: 0 5px 12px rgba(5, 6, 20, 0.45);
  transform-origin: center;
  overflow: hidden;
  will-change: transform, opacity;
  z-index: 1;
}

.preloader__progress-fill {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
  background: #050507;
  transform: scaleX(0);
  will-change: transform;
}

.preloader__progress-fill--left {
  left: 0;
  transform-origin: right center;
}

.preloader__progress-fill--right {
  right: 0;
  transform-origin: left center;
}

@media (max-width: 720px) {
  .preloader__slice {
    width: 1px;
  }

  .preloader__composition {
    --preloader-pixel: 2px;
    width: clamp(80px, 28vw, 128px);
    height: 28px;
  }

  .preloader__progress {
    top: 18px;
  }
}
