/* ================================================================
   HOMEPAGE — "one continuous camera move"

   The opener is a camera lens, so the whole page behaves like one:
   sections are not revealed, the camera arrives at them. Five
   primitives, nothing else — scrub, depth, counter-motion, velocity,
   magnetic.

   Everything here is legible with zero JavaScript. js/home.js adds
   `.motion` to <html> only once GSAP is confirmed running and the
   reader has not asked for reduced motion; the hidden start-states
   below are scoped to that class, so a blocked CDN degrades to a
   plain, complete page rather than a stack of empty sections.

   Inner pages don't load this file — they keep css/style.css alone.
   ================================================================ */

body.home {
  --grid: rgba(246, 245, 241, 0.17);
}

/* Lenis drives the scroll; without it this is a normal page. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }

body.home { overflow-x: clip; }

/* ---------------- 1 · The lens ---------------- */

.lens {
  position: relative;
  /* Sits above the name card, which is pulled up underneath it, so the lens
     dissolves away to reveal a crystal that is already pinned and full
     frame. Sliding one section off another left a visible edge: the clip's
     black is #000 and the page is #050505. */
  z-index: 2;
  height: 200vh;
  /* These are scroll tracks, not content sections, so the global
     `section { padding: … 0 }` has to go: with border-box it both shortens
     the distance the stage can pin over and leaves a band of empty black
     between one shot and the next. */
  padding: 0;
  /* Deliberately transparent. The stage inside carries the background and is
     what fades; leaving an opaque one here would keep painting over the name
     card underneath no matter how far the stage faded. */
  background: transparent;
  /* Pull up under the sticky header so the film is full bleed from the
     first frame and the header floats over it. */
  margin-top: calc(-4rem - 1px);
}

.lens-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--paper);
}

.lens-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: opacity;
}

.lens-copy {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(2rem, 6vh, 4rem);
  text-align: center;
  z-index: 2;
  pointer-events: none;
}

.lens-copy h2 {
  font-family: var(--display);
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  font-weight: 500;
  letter-spacing: -0.02em;
}

.lens-hint {
  display: inline-block;
  margin-top: 1.1rem;
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  opacity: 0.5;
}

/* ---------------- 2 · Name card ---------------- */

/* A scroll track, not a screen: the stage pins while the beam sweeps. The
   track only earns its height when there is something to scrub — without
   motion it collapses to a single screen rather than 280vh of dead scroll. */
.namecard {
  position: relative;
  z-index: 1;
  height: auto;
  padding: 0;
  /* Overlaps the lens's last screen so the handoff is a dissolve, not a wipe. */
  margin-top: -100vh;
}

html.motion .namecard { height: 240vh; }

html:not(.motion) .name-stage { position: relative; }

.name-stage {
  position: sticky;
  top: 0;
  isolation: isolate;
  height: 100vh;
  overflow: hidden;
  display: grid;
  align-content: center;
  justify-items: start;
  padding: clamp(4rem, 12vh, 9rem) var(--pad);
}

/* The light reveals the type instead of the type fading in. The mask front
   runs roughly parallel to the beam and descends with it; --rev is driven
   from js/home.js. Its default shows everything, so with no motion the name
   is simply there. */
.name-reveal {
  position: relative;
  z-index: 1;
  --rev: 200%;
  /* A soft front, angled to sit roughly along the beam. Narrow, because the
     beam itself is narrow — a wide falloff reads as a fade rather than as
     something being lit. */
  -webkit-mask-image: linear-gradient(193deg, #000 calc(var(--rev) - 13%), transparent var(--rev));
          mask-image: linear-gradient(193deg, #000 calc(var(--rev) - 13%), transparent var(--rev));
}

/* Lit glass: bright along the top of each letter, falling away below, with a
   little bloom so it reads as illuminated rather than simply filled.
   `color: transparent` is gated behind support for clipping the fill to the
   glyphs — ungated it would render the name invisible wherever that fails. */
.name-reveal .name-line {
  filter: drop-shadow(0 0 26px rgba(255, 246, 232, 0.28));
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .name-reveal .name-line {
    background: linear-gradient(176deg, #ffffff 0%, rgba(246, 245, 241, 0.92) 55%, rgba(246, 245, 241, 0.8) 100%);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
  }
}

/* The unlit copy underneath — edges only, the way glass reads in the dark. */
.name-block { position: relative; }

.name-ghost {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.name-ghost .name-line {
  color: transparent;
  -webkit-text-stroke: 1px rgba(246, 245, 241, 0.07);
}

.name-ghost .name-sub { color: rgba(246, 245, 241, 0.05); }

/* Optional film bed behind the constructed grid. js/home.js removes the
   element if the file is missing, so the page is identical without it. */
.name-bed {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.2s ease;
}

.namecard.has-bed .name-bed { opacity: 1; }

/* The crystal is itself a lit faceted solid, so the drawn icosahedron would
   just be the same idea twice. It steps aside when there is film; remove the
   file and it comes straight back. */
.namecard.has-bed .floor { opacity: 0.3; }
.namecard.has-bed .solid { opacity: 0; }

/* One-point perspective — horizon, vanishing point, orthogonals. Drawn as
   SVG rather than a rotated CSS plane: a constructed drawing is exact, and a
   3D plane at this angle collapses its own grid into streaks. */
.perspective {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.floor {
  position: absolute;
  left: -12%;
  bottom: -8%;
  width: 124%;
  height: 88%;
  fill: none;
  stroke: var(--grid);
  stroke-width: 1;
  will-change: transform;
}

.floor line { vector-effect: non-scaling-stroke; }

.name {
  position: relative;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(3.5rem, 15vw, 13rem);
  line-height: 0.86;
  letter-spacing: -0.045em;
  text-transform: uppercase;
}

.name-line { display: block; will-change: letter-spacing, transform; }

.name-sub {
  position: relative;
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  max-width: 40ch;
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  opacity: 0.7;
}


/* The wireframe solid is the icosahedron from studio (Fuller's Fly's Eye
   Dome), drawn and rotated in js/home.js rather than exported as an image. */
.solid {
  position: absolute;
  right: -6%;
  top: 50%;
  width: min(46vw, 560px);
  height: min(46vw, 560px);
  transform: translateY(-52%);
  overflow: visible;
  opacity: 0.62;
}

.solid line {
  stroke: rgba(246, 245, 241, 0.8);
  /* non-scaling-stroke reads stroke-width in screen pixels, not user units,
     so this is a hairline at any size — a viewBox-relative value here would
     render as nothing. */
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.solid circle { fill: var(--accent); }

/* Cursor spotlight — the grid and the solid brighten where you point. */
.name-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(
    circle 380px at var(--mx, 50%) var(--my, 40%),
    rgba(255, 77, 0, 0.10),
    rgba(246, 245, 241, 0.05) 38%,
    transparent 70%
  );
  opacity: 0;
  transition: opacity 0.5s ease;
}

html.motion .name-stage:hover::after { opacity: 1; }

/* ---------------- 4 · The spine (architecture) ---------------- */

/* Scroll track, same as the lens and the name card: the global
   `section { padding: … 0 }` would offset the pinned stage inside it and leave
   a band of black between this shot and the one before. */
.spine { position: relative; height: 240vh; padding: 0; }

.spine-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}

.spine-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.22);
  will-change: transform;
}

.spine-veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top, rgba(5,5,5,0.92) 0%, rgba(5,5,5,0.45) 42%, rgba(5,5,5,0.1) 70%),
    linear-gradient(to right, rgba(5,5,5,0.7), transparent 55%);
}

.spine-copy {
  position: absolute;
  left: var(--pad);
  bottom: clamp(5rem, 14vh, 9rem);
  max-width: min(46ch, 70vw);
  will-change: transform;
}

.spine-title {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(2rem, 5.5vw, 4.5rem);
  line-height: 1.02;
  letter-spacing: -0.035em;
}

.spine-body {
  margin-top: 1.25rem;
  max-width: 46ch;
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  opacity: 0.85;
}

.spine-caption {
  position: absolute;
  left: var(--pad);
  bottom: clamp(1.5rem, 4vh, 2.5rem);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.5;
}

/* A link that is a line of type and an arrow, not a rectangle. */
.link-arrow {
  display: inline-block;
  margin-top: 1.75rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--line-strong);
  transition: color 0.25s ease, border-color 0.25s ease;
  will-change: transform;
}

.link-arrow::after { content: " \2192"; }

.link-arrow:hover { color: var(--accent); border-color: var(--accent); }

/* ---------------- 4 · The work lane ----------------
   Infinite moving cards. The lane never stops; hovering a card pulls it
   forward in 3D, dims its neighbours and holds the lane still so you can
   actually read it. Scroll velocity leads the drift, same as the tools. */

.work { padding: clamp(5rem, 14vh, 9rem) 0; }

/* Base layout is a wrapping grid — the same rule as everywhere else on this
   page: a clipped lane that nothing can translate would hide three of the
   five projects the moment the CDN fails. The drift is layered on top. */
.work-lane {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: clamp(2rem, 4vw, 3rem) clamp(1.5rem, 3vw, 2.75rem);
  padding: 0 var(--pad);
}

.wcard {
  display: block;
  transform-style: preserve-3d;
}

html.motion .work { overflow: hidden; }

html.motion .work-lane {
  display: flex;
  align-items: center;
  width: max-content;
  padding: 0 1rem;
  perspective: 1200px;
  will-change: transform;
}

html.motion .wcard {
  flex: 0 0 auto;
  width: clamp(250px, 26vw, 400px);
  will-change: transform, opacity;
}

.wcard-frame {
  display: block;
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
}

.wcard-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.05);
  filter: saturate(0.9) brightness(0.92);
  transition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1), filter 0.5s ease;
}

.wcard:hover .wcard-frame img,
.wcard:focus-visible .wcard-frame img {
  transform: scale(1.12);
  filter: saturate(1.05) brightness(1.05);
}

.wcard-meta { display: block; margin-top: 1.15rem; }

.wcard-name {
  display: block;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.25rem, 2vw, 1.75rem);
  letter-spacing: -0.02em;
  transition: color 0.25s ease;
}

.wcard:hover .wcard-name { color: var(--accent); }

.wcard-line {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.95rem;
  line-height: 1.45;
  opacity: 0.68;
  max-width: 36ch;
}

/* ---------------- 6 · Flagship ---------------- */

.flagship {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(5rem, 14vh, 10rem) var(--pad);
  border-top: 1px solid var(--line);
}

.flagship-mark { width: clamp(120px, 14vw, 190px); height: auto; opacity: 0.95; }

.flagship-claim {
  margin-top: clamp(1.75rem, 4vh, 3rem);
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.8rem, 4.6vw, 3.6rem);
  line-height: 1.06;
  letter-spacing: -0.035em;
  max-width: 20ch;
}

.flagship-body {
  margin-top: 1.5rem;
  max-width: 52ch;
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  opacity: 0.8;
}

/* ---------------- 7 · Tools ---------------- */

.tools { padding: clamp(4rem, 12vh, 8rem) 0; border-top: 1px solid var(--line); }

.tools-intro {
  max-width: var(--max);
  margin: 0 auto clamp(2.5rem, 6vh, 4rem);
  padding: 0 var(--pad);
}

.tools-intro h2 {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.9rem, 4.5vw, 3.4rem);
  letter-spacing: -0.035em;
}

.tool-rows { display: grid; gap: clamp(1rem, 2.5vw, 2rem); }

.tool-row { overflow: hidden; }

.tool-lane {
  display: flex;
  align-items: flex-start;
  gap: clamp(2rem, 4vw, 3.75rem);
  width: max-content;
  padding: 0 1rem;
  will-change: transform;
}

.tool {
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  width: clamp(80px, 8vw, 110px);
  opacity: 0.85;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.tool:hover { opacity: 1; transform: translateY(-4px); }

.tool img {
  width: clamp(44px, 4.2vw, 60px);
  height: clamp(44px, 4.2vw, 60px);
  object-fit: contain;
}

/* The app icons are opaque tiles; rounding them lets Rhino and SketchUp
   sit with Adobe's rounded squares instead of reading as bare rectangles. */
.tool img[src$=".png"] { border-radius: 22%; }

.tool em {
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.6;
  text-align: center;
  line-height: 1.3;
}

/* ---------------- Footer ---------------- */

body.home .site-foot { padding-top: clamp(5rem, 12vh, 8rem); }

body.home .foot-head {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(2.2rem, 6vw, 5rem);
  letter-spacing: -0.04em;
  line-height: 1;
  margin-top: 0;
}

/* ================================================================
   Motion start-states — scoped to html.motion, which js/home.js sets
   only when GSAP is live and reduced motion is off.
   ================================================================ */

html.motion .tools-intro h2,
html.motion .flagship > *,
html.motion .drawing-note {
  opacity: 0;
  transform: translateY(28px);
}

html.motion .spine-copy,
html.motion .spine-caption { opacity: 0; }

html.motion .wcard { opacity: 0; transform: translateY(40px); }

/* ---------------- Responsive ---------------- */

@media (max-width: 900px) {
  .spine { height: 200vh; }
  .spine-copy { max-width: none; right: var(--pad); }

  /* The lane still drifts on a phone, just with smaller cards. */
  html.motion .wcard { width: clamp(220px, 62vw, 280px); }
  .wcard-line { max-width: none; font-size: 0.9rem; }
  .tool-lane { gap: 2rem; }
}

@media (max-width: 560px) {
  html.motion .namecard { height: 240vh; }
  .name { font-size: clamp(3rem, 17vw, 5rem); }
  .spine-body { font-size: 1rem; }
}

/* ---------------- Reduced motion ---------------- */

@media (prefers-reduced-motion: reduce) {
  .lens { height: 100vh; }
  /* Nothing dissolves, so nothing should overlap. */
  .namecard { margin-top: 0; }
  .spine { height: auto; }
  .spine-stage { position: relative; height: 78vh; }
  .spine-img { transform: none; }
  .wcard-frame img { transition: none; }
  .tool { transition: none; }
}
