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

img, svg { display: block; }
button { font-family: inherit; cursor: pointer; }

/* ── Base ──────────────────────────────────────────────────── */
html { height: 100%; }

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: #fff;
  color: #000;
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ── Entrance animation ────────────────────────────────────── */
/* Header pops in from noticeably smaller (its visible content — the
   logo — is a small tight circle, so a shallow scale like footer's
   reads as almost nothing; it needs a much bigger delta to register). */
@keyframes intro-in-header {
  from { opacity: 0; transform: scale(0.55); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes intro-in {
  from { opacity: 0; transform: scale(0.92); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes intro-in-card {
  from { opacity: 0; transform: scale(0.98); }
  to   { opacity: 1; transform: scale(1); }
}

/* Sequential stagger: header → footer → card. A short initial pause
   before anything moves, then each is basically done before the next
   starts (delay ≳ duration), so it reads as a clear one-two-three
   instead of a blurred-together fade. Spring-ish ease (same curve
   used for the card morphs) for a smoother settle than plain
   ease-out. Header runs a touch longer than the rest — its scale
   delta is bigger, so it needs more time to land softly. */
.site-header {
  animation: intro-in-header 1.05s cubic-bezier(0.16, 1, 0.3, 1) 0.2s  both;
}
.site-footer {
  animation: intro-in        0.85s cubic-bezier(0.16, 1, 0.3, 1) 1s    both;
}
.site-main {
  animation: intro-in-card   0.65s cubic-bezier(0.16, 1, 0.3, 1) 1.7s  both;
}

/* ── Header ────────────────────────────────────────────────── */
.site-header {
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 60px 20px;
}

.header-inner {
  width: 800px;
  max-width: 100%;
  display: flex;
  justify-content: center;
}

.header-logo {
  background: #000;
  padding: 8px;
  border-radius: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
}

/* ── Main ──────────────────────────────────────────────────── */
.site-main {
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

/* gallery fills site-main; overflow:hidden clips sliding items */
.gallery {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* morph timing — matches s.engineering: fast ease-in exit, spring enter */
  --morph-exit-duration: 140ms;
  --morph-exit-ease: cubic-bezier(0.4, 0, 1, 1);
  --morph-enter-duration: 420ms;
  --morph-enter-ease: cubic-bezier(0.22, 1, 0.36, 1);
  /* Directional nudge: JS sets --dir to +1 (forward) or -1 (back)
     before each transition, so the leaving/entering media carries a
     small directional drift instead of morphing symmetrically. */
  --dir: 1;
  --dir-shift: 16px;
}

/*
  Each gallery-item fills site-main entirely (inset:0).
  display:grid + place-items:center centers the inner project-card.
  No sliding — only one item is ever opacity:1 at a time, and the
  swap between "leaving" and "entering" happens in a single JS tick
  (no transition on opacity), so there's never a frame where both
  are visible. The whole transition lives in the project-card's
  transform keyframes: leaving shrinks alone, then instantly hands off
  to entering, which grows alone from that same shrunk state.
*/
.gallery-item {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  pointer-events: none;
}

.gallery-item.is-active {
  opacity: 1;
  z-index: 2;
  pointer-events: auto;
}

/* Leaving: alone on screen while its media morph-shrinks */
.gallery-item.is-leaving {
  opacity: 1;
  z-index: 2;
}

/* Entering: alone on screen while its media morph-grows */
.gallery-item.is-entering {
  opacity: 1;
  z-index: 2;
}

/* ── Morph keyframes — transform only, no clip-path ─────────────
   This used to animate clip-path: inset(0% → 5% round 24px). That is
   what made the corners glitch in Chrome (they'd render near-square
   for a frame, then snap to rounded): an arbitrary clip-path is not a
   compositor primitive, so Chrome rasterizes it coarsely mid-animation
   and only repaints it correctly at the end. s.engineering animates
   clip-path the same way and has the identical glitch — so matching
   them was never going to fix it.

   The fix is to stop animating clip-path entirely:
   - the rounded window is a STATIC border-radius + overflow:hidden on
     .project-card (rounded-rect clipping IS a compositor primitive, so
     it stays correctly anti-aliased on every frame, for free);
   - the shrink is transform: scale() on that same card, so the whole
     rounded window still contracts — this is what keeps the morph
     feeling alive rather than a static frame with a zooming picture;
   - the media inside counter-scales, so its content stays ~the same
     size while the window closes in on it. That reproduces the "crop"
     character of the old clip-path inset instead of a plain zoom-out.

   Geometry, matched to the old values:
     inset(5%) trimmed 5% off each edge → visible box was 90% wide,
     hence --morph-window-scale: 0.9.
     Content used to scale to 0.985, so the counter-scale is
     0.985 / 0.9 = 1.0944.

   Forward (--dir: 1): leaving drifts up and away, entering rises in
   from below. Backward (--dir: -1): mirrored — leaving drifts down,
   entering drops in from above. Same net direction throughout, so it
   reads as one continuous flow rather than a symmetric zoom. */
@keyframes morph-out {
  from { transform: scale(1) translateY(0); }
  to   { transform: scale(0.9) translateY(calc(var(--dir) * -1 * var(--dir-shift))); }
}
@keyframes morph-in {
  from { transform: scale(0.9) translateY(calc(var(--dir) * var(--dir-shift))); }
  to   { transform: scale(1) translateY(0); }
}
/* Counter-scale on the media, so content size stays put while the
   window shrinks around it (0.985 / 0.9 = 1.0944). */
@keyframes morph-out-content {
  from { transform: scale(1); }
  to   { transform: scale(1.0944); }
}
@keyframes morph-in-content {
  from { transform: scale(1.0944); }
  to   { transform: scale(1); }
}

/* project-card: sized container AND the rounded window itself.
   Width/height set inline by JS (sizeCards), fitted to each case's
   own aspect ratio within the container's available space (padding
   per Figma: 40px desktop / 16px mobile) — cards only shrink when
   they hit those edges, nothing else caps them.
   border-radius + overflow:hidden here are static and never animate;
   only transform does. This is the single rounded clip in the card —
   the media inside no longer carries its own, so there's no second
   arc to show through. */
.project-card {
  position: relative;
  flex-shrink: 0;
  border-radius: 24px;
  overflow: hidden;
  will-change: transform;
}

/* 1px hairline border, on every card (was previously only on video
   cards, where it existed to give the black frame a visible edge on
   white). It sits inside the card, so it rides along with the card's
   shrink transform and needs no animation of its own. */
.project-card::after {
  content: '';
  position: absolute;
  z-index: 4;
  inset: 0;
  border-radius: 24px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
  pointer-events: none;
}

.gallery-item.is-waiting .project-card {
  transform: scale(0.9);
}
.gallery-item.is-leaving .project-card {
  animation: morph-out var(--morph-exit-duration) var(--morph-exit-ease) forwards;
}
.gallery-item.is-entering .project-card {
  animation: morph-in var(--morph-enter-duration) var(--morph-enter-ease) forwards;
}

.project-img {
  will-change: transform;
}

/* Waiting: pre-counter-scale so morph-in starts from that state */
.gallery-item.is-waiting .project-img {
  transform: scale(1.0944);
}

/* Exit: content counter-scales up as the window closes in */
.gallery-item.is-leaving .project-img {
  animation: morph-out-content var(--morph-exit-duration) var(--morph-exit-ease) forwards;
}

/* Enter: content settles back to 1:1 as the window opens (spring) */
.gallery-item.is-entering .project-img {
  animation: morph-in-content var(--morph-enter-duration) var(--morph-enter-ease) forwards;
}

.project-card picture {
  display: block;
  width: 100%;
  height: 100%;
}

.project-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* ── Pagination ────────────────────────────────────────────── */
/* Centered both axes on screen, pill container */
.pagination {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 24px;
  padding: 10px 8px;
  border-radius: 120px;
  background: rgba(235, 235, 235, 0.9);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  /* fade-out slow */
  transition: opacity 0.8s ease;
  pointer-events: none;
}

.pagination.is-visible {
  opacity: 1;
  /* fade-in fast */
  transition: opacity 0.3s ease;
  pointer-events: auto;
}

.pagination__dot {
  width: 4px;
  height: 4px;
  border-radius: 10px;
  background: rgba(102, 102, 102, 0.3);
  transition: background 0.2s ease;
  cursor: pointer;
  flex-shrink: 0;
}

.pagination__dot.is-active {
  background: #666;
}

/* ── Footer ────────────────────────────────────────────────── */
.site-footer {
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 60px 20px;
}

.footer-inner {
  width: 800px;
  max-width: 100%;
  display: flex;
  justify-content: center;
}

/* ── Connect button ────────────────────────────────────────── */
.connect-btn {
  display: inline-flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  padding: 8px 20px;
  border-radius: 120px;
  border: none;
  background: rgba(235, 235, 235, 0.9);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  font-size: 15px;
  line-height: 24px;
  font-weight: 400;
  letter-spacing: -0.035em;
  color: #676767;
  /* width animated by JS between collapsedW and expandedW */
  transition: width 0.35s ease;
  -webkit-tap-highlight-color: transparent;
}

.connect-btn__label {
  flex-shrink: 0;
  white-space: nowrap;
  position: relative;
}

/* Absolutely positioned — out of flow, clipped by button overflow:hidden when collapsed */
.connect-btn__text {
  position: absolute;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 15px;
  line-height: 24px;
  font-weight: 400;
  letter-spacing: -0.03em;
  color: #afafaf;
  white-space: nowrap;
  opacity: 0;
  /* collapse: fade out immediately */
  transition: opacity 0.15s ease;
}

.connect-btn.is-expanded .connect-btn__text {
  opacity: 1;
  /* expand: fade in after width animation */
  transition: opacity 0.2s ease 0.3s;
}

/* Blur swap animations (email → copied) */
@keyframes text-blur-out {
  from { opacity: 1; filter: blur(0px);  }
  to   { opacity: 0; filter: blur(6px);  }
}
@keyframes text-blur-in {
  from { opacity: 0; filter: blur(6px);  }
  to   { opacity: 1; filter: blur(0px);  }
}

.connect-btn__text.is-blur-out {
  animation: text-blur-out 0.15s ease forwards;
}
.connect-btn__text.is-blur-in {
  animation: text-blur-in 0.2s ease forwards;
}

/* ── Video card ───────────────────────────────────────────────
   Frame hugs the video's own native aspect ratio (sized by JS via
   inline width/height) instead of the fixed contain-box used by
   image cards. The rounded clip is .project-card's static
   border-radius/overflow, same as image cards. */
.project-card--video {
  box-sizing: border-box;
}

.project-card--video video.project-img {
  background: #000;
}

/* ── TopVision-style letterboxed video (node 27:239) ──────────
   The video doesn't fill its card — it floats inset within a black
   frame, positioned by percentage (Figma: left/right insets + a
   vertical offset from center), keeping its own 16:9 aspect ratio.
   The black frame is the card, so it gets the card's static rounding
   and its shrink transform; only the counter-scale is wired up here,
   on the video itself. */
.project-card--tv {
  background: #000;
}

.tv-inner {
  position: absolute;
  aspect-ratio: 1920 / 1080;
  left: 6.2%;
  right: 6.2%;
  top: calc(50% + 28.22px);
  transform: translateY(-50%);
}

.tv-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  will-change: transform;
}

/* Same counter-scale as .project-img, so the letterboxed video keeps
   its size while the black frame shrinks around it */
.gallery-item.is-waiting .tv-video {
  transform: scale(1.0944);
}
.gallery-item.is-leaving .tv-video {
  animation: morph-out-content var(--morph-exit-duration) var(--morph-exit-ease) forwards;
}
.gallery-item.is-entering .tv-video {
  animation: morph-in-content var(--morph-enter-duration) var(--morph-enter-ease) forwards;
}

@media (max-width: 767px) {
  .tv-inner {
    left: -2.75%;
    right: -2.67%;
    top: calc(50% + 10.59px);
  }
}

/* ── Video play/pause pill ─────────────────────────────────── */
.video-play-btn {
  display: flex;
  position: absolute;
  z-index: 6;
  bottom: 16px;
  left: 16px;
  align-items: center;
  justify-content: center;
  height: 24px;
  padding: 0 10px;
  border: 0;
  border-radius: 32px;
  background: rgba(235, 235, 235, 0.9);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: #676767;
  font-family: inherit;
  font-size: 11px;
  line-height: 1;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.92);
  transform-origin: center center;
  transition: opacity 180ms ease-out, transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
}

.project-card--video:hover .video-play-btn,
.video-play-btn.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: scale(1);
}

.video-play-btn:active {
  transform: scale(0.92);
}

/* Touch devices: no hover, so JS toggles .is-visible on touch/hold of
   the card, then auto-hides it after a short pause (see main.js) —
   no permanently-visible pill. */

/* ── Mobile ────────────────────────────────────────────────── */
@media (max-width: 767px) {
  .site-header { padding: 16px; }
  .site-footer { padding: 16px; }

  /* Non-video cards: fixed frame shrinks to fit the smaller viewport */
  .project-card:not(.project-card--video) {
    width: calc(100vw - 64px);
    height: calc(100vh - 96px);
  }
}
