/* ============================================================
   Dhvani Patel — Portfolio (Phase 2: faithful static reference)
   Recreated from the live Framer site. Values marked EXISTING are
   pulled directly from the live site's computed styles. Nothing
   here is a new design decision — that's Phase 3.
   ============================================================ */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

:root{
  /* Phase 3 — approved color system (locked: --color-bg-primary, --color-text-primary) */
  --color-bg-primary: #3B0000;
  --color-text-primary: #F0EEE7;
  --color-bg-deep: #4A0101;
  --color-bg-secondary: #8C2323;
  --color-text-secondary: #B8B2A3;
  --color-light-section: #E8E3D8;
  --color-dark-text: #3A0505;
  --color-border: #8A4444;
  --color-accent: #9A2A2A;

  /* EXISTING type */
  --font-serif: Arial, Helvetica, var(--font-sans);
  --font-sans: 'Inter', 'Inter Placeholder', sans-serif;
  /* Forma DJR is a commercial typeface — no font files were supplied, so it
     isn't self-hosted here. This stack will only render as actual Forma DJR
     on a device that has it installed locally; everyone else falls back to
     Inter. See note near .approach below. */
  --font-forma: 'Forma DJR Display', 'Forma DJR Deck', 'Forma DJR Text', 'Forma DJR', var(--font-sans);

  --content-max: 1400px;
  --page-margin: 32px;
  --card-radius: 8px; /* measured 7.94px */
}

*{ box-sizing: border-box; }
/* Deliberately no scroll-behavior:smooth here — the About link jumps
   straight to the flip-card section instead of animating the scroll
   through everything in between. */
body{
  margin: 0;
  background: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
img{ max-width: 100%; display: block; }
a{ color: inherit; text-decoration: none; }
ul{ list-style: none; margin: 0; padding: 0; }
button{ font: inherit; color: inherit; background: none; border: none; cursor: pointer; }

.wrap{
  max-width: var(--content-max);
  margin: 0 auto;
  padding-left: var(--page-margin);
  padding-right: var(--page-margin);
}

/* ---------- Nav ---------- */
.nav{
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px var(--page-margin);
}
.nav .logo{
  font-family: var(--font-serif);
  font-size: 32px;
  letter-spacing: -1.9px;
}
/* Logo is an <img>, sized in em so it tracks whichever font-size context
   it's placed in (32px in the nav, 24px in the footer) without a separate
   rule per location. */
.logo img{
  height: 1em;
  width: auto;
  display: block;
}
/* Just the link group reads as its own clickable surface — a translucent,
   blurred pill — separate from the logo, which stays plain. */
.nav-links{
  display: flex;
  gap: 40px;
  font-size: 14px;
  padding: 14px 28px;
  border-radius: 100px;
  background: rgba(59, 0, 0, 0.55);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid rgba(240, 238, 231, 0.1);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.nav-links a{ opacity: .9; }
.nav-links a:hover{ opacity: 1; }
.nav-toggle{ display: none; font-size: 24px; }

/* ---------- Hero ---------- */
.hero{
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 80px 24px; /* symmetric top/bottom so the composition is truly vertically centered */
  overflow: hidden;
  background: var(--color-bg-primary);
}
.hero-bg-video{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.hero-content{
  position: absolute;
  left: 50%;
  bottom: 40px;
  transform: translateX(-50%);
  z-index: 2;
  will-change: opacity;
}

.hero-scroll-cue{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: #ffffff; /* locked, excluded from the Phase 3 color pass */
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.45); /* keeps it legible over the full-bleed video */
  opacity: 0;
  animation: hero-sub-in 0.8s ease 0.4s forwards;
}
.hero-scroll-label{
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.hero-scroll-arrow{
  font-size: 18px;
  line-height: 1;
  animation: hero-scroll-bounce 1.6s ease-in-out infinite;
}
@keyframes hero-sub-in{
  from{ opacity: 0; transform: translateY(8px); }
  to{ opacity: 1; transform: translateY(0); }
}
@keyframes hero-scroll-bounce{
  0%, 100%{ transform: translateY(0); }
  50%{ transform: translateY(6px); }
}

/* ---------- Film strip ---------- */
.film-strip{
  position: relative;
  background: var(--color-light-section);
  padding: 14px 0;
  overflow: hidden;
}
.film-edge{
  height: 14px;
  background-color: transparent;
  background-image: repeating-linear-gradient(
    to right,
    var(--color-bg-primary) 0, var(--color-bg-primary) 8px,
    transparent 8px, transparent 22px
  );
  background-position: 6px center;
  background-repeat: repeat-x;
  opacity: 1;
}
.film-track{
  display: flex;
  align-items: center;
  gap: 16px;
  width: max-content;
  padding: 16px 0;
  animation: film-scroll 38s linear infinite;
}
.film-frame{
  flex: 0 0 auto;
  width: 170px;
  height: 230px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--color-bg-primary);
}
.film-frame img{
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@keyframes film-scroll{
  from{ transform: translateX(0); }
  to{ transform: translateX(-50%); }
}

/* ---------- Project stage: scroll-scrubbed scattered → grid composition ----------
   Motion reference: user-supplied recording (scattered cards resolving into a
   grid as the page scrolls). Visual design, colors, typography and all cover
   images below are this site's own — only the motion idea is borrowed.
   Pinning uses native `position: sticky`, not manual scroll-jacking: the
   section is tall (see height below), the inner stage sticks to the viewport
   while that extra height scrolls past, and normal scrolling resumes the
   instant the section's height is exhausted. */
.project-stage{
  position: relative;
  height: 460vh; /* longer runway so the rearrangement has room to breathe */
}
.stage-sticky{
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-primary);
}
.stage-word{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(90px, 20vw, 280px);
  letter-spacing: -4px;
  color: var(--color-text-primary);
  opacity: 0.06;
  white-space: nowrap;
  pointer-events: none;
  z-index: 0;
}
.stage-header{
  position: absolute;
  top: 48px;
  left: 0;
  right: 0;
  text-align: center;
  z-index: 3;
  pointer-events: none;
}
.stage-label{
  font-size: 14px;
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.stage-cue{
  margin-top: 10px;
  font-size: 12px;
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  transition: opacity 0.3s ease;
}
.stage-cards{
  position: relative;
  width: 100%;
  height: 100%;
  z-index: 2;
}
.stage-card{
  position: absolute;
  top: 40%;
  left: 50%;
  width: 300px;
  height: 300px;
  border-radius: var(--card-radius);
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(0,0,0,0.35);
  will-change: transform;
}
.stage-card img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s ease, filter 0.4s ease;
}
/* Centered, not a bottom caption — legible at a glance instead of needing
   a close look. The card's own dimming/blur (below) does the contrast
   work, so this only needs a light scrim on top for extra insurance. */
.stage-card-title{
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--color-text-primary);
  background: rgba(0, 0, 0, 0.28);
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
.stage-card:hover img{ transform: scale(1.08); filter: blur(5px) brightness(0.6); }
.stage-card:hover .stage-card-title{ opacity: 1; }

/* Lives inside the pinned stage itself, right below the grid, so it
   appears together with the settled composition instead of after a
   separate stretch of scrolling. Opacity/pointer-events are driven
   continuously from script.js as the cards resolve (see the `g` /
   resolve-progress value in initProjectStage) — the 0 here is just the
   pre-JS default so it doesn't flash visible before the first frame. */
.stage-all-projects{
  position: absolute;
  left: 50%;
  bottom: 6%;
  transform: translateX(-50%);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 24px;
  border-radius: 100px;
  background: rgba(240, 238, 231, 0.1);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.32px;
  color: var(--color-text-primary);
  opacity: 0;
  pointer-events: none;
}
.stage-all-projects-arrow{
  display: inline-block;
  transition: transform 0.25s ease;
}
.stage-all-projects:hover .stage-all-projects-arrow{ transform: translateX(5px); }
.stage-all-projects:hover{ opacity: 0.75 !important; }

/* prefers-reduced-motion (and any browser without JS support for the scrub)
   falls back to a plain static grid via this class, added by script.js */
.project-stage.no-motion{ height: auto; }
.project-stage.no-motion .stage-sticky{
  position: relative;
  height: auto;
  flex-direction: column;
  padding: 140px 24px;
  background: var(--color-bg-primary);
}
.project-stage.no-motion .stage-word{ display: none; }
.project-stage.no-motion .stage-header{ position: static; margin-bottom: 40px; }
.project-stage.no-motion .stage-cue{ display: none; }
.project-stage.no-motion .stage-cards{
  position: static;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  max-width: var(--content-max);
  margin: 0 auto;
}
.project-stage.no-motion .stage-card{
  position: static;
  width: auto;
  height: auto;
  aspect-ratio: 1/1;
  transform: none !important;
}
.project-stage.no-motion .stage-all-projects{
  position: static;
  transform: none;
  margin: 40px auto 0;
  opacity: 1;
  pointer-events: auto;
}

.project-card{
  position: relative;
  flex: 0 0 auto;
  width: min(70vw, 560px);
  aspect-ratio: 1/1;
  border-radius: var(--card-radius);
  overflow: hidden;
  scroll-snap-align: start;
}
.project-grid{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 26px 24px;
}
.project-grid .project-card{ width: 100%; }
.project-card img{
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.6s ease;
}
.project-card:hover img{ transform: scale(1.03); }
.project-card::before{
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
  background:
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 0 0/16px 2px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 0 0/2px 16px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 100% 0/16px 2px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 100% 0/2px 16px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 0 100%/16px 2px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 0 100%/2px 16px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 100% 100%/16px 2px no-repeat,
    linear-gradient(var(--color-text-primary),var(--color-text-primary)) 100% 100%/2px 16px no-repeat;
}
.project-card:hover::before{ opacity: 0.9; }

/* ---------- Approach ---------- */
/* This section now also serves as the "About" anchor — the flip-card
   About section was removed and replaced with this section. Reverted back
   to the original Instrument Serif treatment per feedback. */
.approach{
  padding: 110px 0 48px; /* tightened top gap so the film reel flows into this section without a dead gap; bottom gap tightened separately for the move into Projects */
  text-align: center;
}
.approach h2{
  /* Scoped to this section only — 'Helvetica Neue' is Haas's own later
     revision of Neue Haas Grotesk and ships on every Mac/iOS device, so
     it's the closest real-world match without self-hosted font files.
     Everywhere else on the site keeps var(--font-serif) untouched. */
  font-family: 'Helvetica Neue', Helvetica, Arial, var(--font-sans);
  font-weight: 100;
  font-size: 38px;
  letter-spacing: 0.5px;
  margin: 0 0 24px;
}
.approach blockquote{
  font-family: 'Helvetica Neue', Helvetica, Arial, var(--font-sans);
  font-weight: 100;
  font-size: 38px;
  letter-spacing: 0.5px;
  line-height: 1.2;
  max-width: 900px;
  margin: 0 auto;
}

/* ---------- About / flip ----------
   Sized as a normal content block (not a full-viewport section) so it
   sits at the same visual weight as the rest of the page — a moderate
   card, not a hero moment. Click-to-flip (whole card, not a small link),
   toggling on every click; the same 3D flip mechanic used earlier in the
   site, just relocated and resized. */
.about-flip{
  padding: 120px 0;
}
.flip-scene{
  max-width: 760px;
  margin: 0 auto;
  perspective: 1800px;
}
/* Separate layer from .flip-card on purpose: this one only ever carries the
   mouse-tracked tilt (rotateX/rotateY set directly via inline style in
   script.js), while .flip-card only ever carries the click-triggered 180°
   flip. Nesting two preserve-3d boxes under one shared perspective composes
   correctly, so the card can tilt and flip independently without the two
   interactions fighting over the same transform. */
.flip-tilt{
  transform-style: preserve-3d;
  transition: transform 0.5s cubic-bezier(.22,1,.36,1);
  will-change: transform;
}
.flip-card{
  position: relative;
  width: 100%;
  aspect-ratio: 1556 / 980;
  cursor: pointer;
  transform-style: preserve-3d;
  transition: transform 0.9s cubic-bezier(.4,.1,.2,1);
  /* Without this, some browsers only promote this element to its own
     GPU layer once the rotateY transition actually starts, instead of
     ahead of time — that late promotion can show up as a garbled/
     corrupted frame (jumbled boxes, broken text) partway through the
     flip. will-change asks for the layer up front so the whole 0.9s
     rotation composites from a stable layer instead. */
  will-change: transform;
}
.flip-card.is-flipped{ transform: rotateY(180deg); }
.flip-face{
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 16px;
  padding: 56px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  box-shadow: 0 20px 45px rgba(0,0,0,0.35);
  transition: box-shadow 0.4s ease;
}
.flip-tilt.is-hovering .flip-face{
  box-shadow: 0 32px 64px rgba(0,0,0,0.5);
}
/* Front face is the uploaded reference image exactly as provided — its own
   baked-in dotted border, background and heading are the design here, not
   a template we re-typeset. Card is sized to the image's own aspect ratio
   so it fills edge-to-edge with no cropping and no added framing. The
   "Take a flip" line was baked in as flat text with no button affordance,
   so it's been painted out of the image and rebuilt below as a real
   .flip-cta button in the same spot, sized/positioned as % of this box
   (which maps 1:1 to the image's pixel grid since the aspect ratio matches
   exactly) so it stays aligned as the card scales. */
.flip-front{
  padding: 0;
}
.flip-cta{
  position: absolute;
  left: 19%;
  top: 58.5%;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: #3B0000;
  color: #FEF9F9;
  border: none;
  padding: 13px 24px;
  border-radius: 999px;
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.2px;
  cursor: pointer;
  box-shadow: 0 10px 24px rgba(59,0,0,0.28);
  opacity: 0.85;
  transition: transform 0.25s ease, box-shadow 0.25s ease, background 0.25s ease, opacity 0.25s ease;
}
.flip-cta:hover{
  background: #520000;
  transform: translateY(-2px);
  box-shadow: 0 14px 30px rgba(59,0,0,0.38);
  opacity: 1;
}
.flip-cta:active{ transform: translateY(0); }
.flip-cta-arrow{ transition: transform 0.25s ease; }
.flip-cta:hover .flip-cta-arrow{ transform: translateX(4px); }
.flip-front-image{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Idle-animation sprite. The box is deliberately bigger than her hair's
   tightest bounds (819,116)-(1484,786) of the 1556x980 source, expressed as
   % of the front face) and filled with the backdrop's own flat background
   colour (#FEF9F9, sampled from the source) — because frames 2+ are a
   different pose/head-tilt than the one baked into the static backdrop
   underneath, a box sized exactly to the silhouette would let slivers of
   the ORIGINAL baked-in face peek out around the new pose. The opaque
   patch fully masks that instead, so any pose swaps cleanly with no
   ghosting, and the matching fill colour keeps its edges invisible against
   the surrounding art. Frame 1 is a byte-for-byte copy of
   about-illustration.png (about-character-01.png) cropped to this same
   box, so it still sits flush over the backdrop with zero visible seam.
   Frames 2-5 are separate transparent-background close-up exports; each
   gets its own hand-fit width/left/top (see the per-[data-frame] rules)
   from its source pixel dimensions and a manual read of where her face
   sits in that file, so her eyes land at the same spot and apparent size
   in the box no matter which frame is active. Script.js activates a frame
   automatically the moment its image loads — dropping in further
   about-character-NN.png files (with a matching HTML <img> and a
   same-pattern position override here) extends the cycle with no other
   changes needed. */
.flip-character{
  position: absolute;
  left: 52.63%;
  top: 11.84%;
  width: 42.74%;
  height: 68.37%;
  overflow: hidden;
  background: #FEF9F9;
  pointer-events: none;
  /* This box lives inside .flip-card's preserve-3d/backface-hidden stack
     (see .flip-card, .flip-face). WebKit has a well-known bug where any
     opacity/z-index mutation on a descendant of a backface-hidden, 3D-
     transformed ancestor can force that layer to be momentarily
     repainted from scratch — visible as a flash of the layer's blank
     background between frames. Promoting this box to its own stable
     compositing layer keeps it painted independently of the card's 3D
     stack, so the frame swap below can never trigger that repaint. */
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  isolation: isolate;
}
/* Frame swap is an instant, un-transitioned opacity cut (0 <-> 1, no
   animated middle state) — deliberately, not an oversight. A crossfade
   holds two differently-posed frames at partial opacity at once, and
   wherever their silhouettes don't overlap the flat patch colour shows
   through underneath; on a slower device or a dropped frame that can read
   as a visible blank flash between characters. A hard cut has no such
   window: exactly one frame is ever visible, so there's nothing to show
   through. Matches the pixel art's crisp style better anyway. */
.flip-character-frame{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  opacity: 0;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  will-change: opacity;
}
.flip-character-frame.is-active{ opacity: 1; }
.flip-character-frame[data-frame="1"]{
  left: -123.16%;
  top: -17.31%;
  right: auto;
  bottom: auto;
  width: 233.98%;
  height: auto;
  object-fit: initial;
}
/* Frames 2-5 are isolated, transparent-background character exports at
   their own arbitrary crops/scales — unlike frame 1, they can't share one
   set of offsets. Each gets its own scale (width%) and position (left%/
   top%), calibrated by measuring her hair span at eye-level in each source
   file (an automated, unambiguous metric — unlike guessing where "cheek" or
   "face" edges are by eye) and scaling so that span renders at the same
   width, at the same anchor point, as it does in frame 1. That keeps her
   at a consistent size and position across every frame instead of jumping
   between them. */
.flip-character-frame[data-frame="2"]{
  left: 7.31%;
  top: 16.81%;
  right: auto;
  bottom: auto;
  width: 101.48%;
  height: auto;
  object-fit: initial;
}
.flip-character-frame[data-frame="3"]{
  left: 5.22%;
  top: 20.25%;
  right: auto;
  bottom: auto;
  width: 104.22%;
  height: auto;
  object-fit: initial;
}
.flip-character-frame[data-frame="4"]{
  left: -3.9%;
  top: 19.46%;
  right: auto;
  bottom: auto;
  width: 91.57%;
  height: auto;
  object-fit: initial;
}
.flip-character-frame[data-frame="5"]{
  left: 18.2%;
  top: 19.67%;
  right: auto;
  bottom: auto;
  width: 65.61%;
  height: auto;
  object-fit: initial;
}
/* Extremely subtle idle sway — a living detail, not a swing. Only runs
   while script.js has determined the card is on-screen and the user
   hasn't asked for reduced motion (both gates live in initCharacterAnimation). */
@keyframes character-idle-sway{
  0%, 100%{ transform: translateX(0) rotate(0deg); }
  25%{ transform: translateX(-1.5px) rotate(-0.4deg); }
  75%{ transform: translateX(1.5px) rotate(0.4deg); }
}
.flip-character.is-idle{
  animation: character-idle-sway 6s ease-in-out infinite;
}
.flip-back{
  background: var(--color-bg-deep);
  color: var(--color-text-primary);
  transform: rotateY(180deg);
  display: grid;
  grid-template-columns: 220px 1fr;
  column-gap: 40px;
  row-gap: 28px;
  align-items: center;
  /* Copy column runs taller than the photo now, so centering (rather than
     top-aligning) the row keeps the photo visually balanced against it
     instead of stranded near the top. */
  /* Copy on this face runs longer than the front, so it gets tighter
     padding than .flip-face's default 56px — reclaims room for the extra
     lines without touching the card's own fixed size (see .flip-card). */
  padding: 44px 44px 70px;
}
.flip-back .about-photo{
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: 10px;
  overflow: hidden;
  background: rgba(240,238,231,0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--color-text-primary);
  text-align: center;
  padding: 8px;
}
.flip-back .about-photo img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.about-copy{
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.about-desc{
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--color-text-primary);
  opacity: 0.9;
  max-width: 48ch;
}
.about-experience{ margin-top: 4px; }
.about-experience-label{
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-secondary);
  margin-bottom: 3px;
}
.about-experience-value{ font-size: 14px; }
.about-cta{
  position: absolute;
  right: 44px;
  bottom: 24px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--color-text-primary);
  color: var(--color-dark-text);
  padding: 12px 22px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.2px;
}
.about-cta-arrow{ transition: transform 0.25s ease; }
.about-cta:hover .about-cta-arrow{ transform: translateX(4px); }

/* ---------- Footer ---------- */
.footer{
  padding: 100px 0 40px;
  border-top: 1px solid var(--color-border);
}
.footer-cta-label{
  color: var(--color-text-secondary);
  margin-bottom: 16px;
}
.footer-cta{
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: 44px;
  letter-spacing: -1.8px;
  line-height: 1.15;
  max-width: 700px;
  margin: 0 0 80px;
  display: block;
}
.footer-cta:hover{ opacity: .8; }
.footer-bottom{
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 20px;
  padding-top: 32px;
  border-top: 1px solid var(--color-border);
}
.footer-links{ display: flex; gap: 28px; font-size: 14px; }
.footer-copy{ color: var(--color-text-secondary); font-size: 14px; }

/* ---------- Project detail page ---------- */
.project-hero{
  padding: 160px 0 40px;
}
.project-intro{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: start;
}
.project-label{ font-size: 16px; color: var(--color-text-secondary); }
.project-desc{ font-size: 20px; line-height: 1.6; }
.project-hero-visual{
  margin: 60px auto 0;
  max-width: var(--content-max);
  padding: 0 var(--page-margin);
}
.project-hero-visual img{
  width: 100%;
  border-radius: var(--card-radius);
}
.project-nav-arrows{
  display: flex;
  gap: 16px;
  margin-top: 32px;
  font-size: 22px;
  color: var(--color-text-secondary);
}

/* ---------- Contact ---------- */
.contact{
  padding-top: 160px;
  padding-bottom: 100px;
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: 60px;
}
.contact h1{
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: 44px;
  letter-spacing: -1.8px;
  line-height: 1.15;
  margin: 0 0 48px;
}
.contact-field{ margin-bottom: 28px; }
.contact-field-label{
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: 6px;
}
.contact-field-value{ font-size: 17px; }
.contact-form label{
  display: block;
  font-size: 15px;
  color: var(--color-text-secondary);
  margin-bottom: 8px;
}
.contact-form .field{ margin-bottom: 28px; }
.contact-form input,
.contact-form select,
.contact-form textarea{
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-primary);
  font: inherit;
  padding: 8px 0;
}
.contact-form input:focus,
.contact-form select:focus,
.contact-form textarea:focus{ outline: none; border-color: var(--color-text-primary); }
.contact-submit{
  margin-top: 16px;
  font-size: 16px;
  font-weight: 600;
  float: right;
}
.contact-submit:hover{ opacity: .75; }
.contact-submit:disabled{ opacity: .5; cursor: default; }
.contact-submit:disabled:hover{ opacity: .5; }
/* Sits below the floated submit button; clear:both keeps it from wrapping
   beside it, and it's empty (no height) until JS fills it in on submit, so
   it never shifts layout while the form is just sitting there. */
.form-status{
  clear: both;
  margin: 16px 0 0;
  font-size: 14px;
  color: var(--color-text-secondary);
}
.form-status.is-error{ color: var(--color-accent); }

/* ---------- Responsive ---------- */
/* Tablet: 810–1199px, measured on the live site */
@media (max-width: 1199px) and (min-width: 810px){
  .project-stage{ height: 400vh; }
  .stage-card{ width: 200px; height: 200px; }
  /* cover crops hard on a taller/narrower viewport than the video's own
     landscape frame — the monitor+keyboard composition loses its edges.
     contain keeps the whole frame visible, same as the desktop crop,
     just letterboxed; the video's own background matches the hero's, so
     the letterbox bars are invisible. */
  .hero-bg-video{ object-fit: contain; }
}

/* Phone: <810px, measured on the live site */
@media (max-width: 809px){
  /* Same reasoning as the tablet block above: keep the full video frame
     visible instead of cover cropping most of it away. */
  .hero-bg-video{ object-fit: contain; }
  .nav-links{
    display: none;
    position: absolute;
    top: 100%;
    left: var(--page-margin);
    right: var(--page-margin);
    margin-top: 12px;
    flex-direction: column;
    align-items: flex-start;
    gap: 18px;
    padding: 20px 24px;
    border-radius: 20px;
  }
  .nav-toggle{ display: block; position: relative; z-index: 101; }
  .hero-scroll-label{ font-size: 11px; }
  .film-frame{ width: 110px; height: 150px; }
  .film-track{ gap: 10px; }
  .project-stage{ height: 320vh; }
  .stage-card{ width: 180px; height: 180px; }
  .stage-word{ font-size: clamp(70px, 24vw, 140px); }
  .project-stage.no-motion .stage-cards{ grid-template-columns: 1fr 1fr; }
  .project-grid{ grid-template-columns: 1fr; }
  .project-intro{ grid-template-columns: 1fr; }
  .contact{ grid-template-columns: 1fr; padding-top: 120px; }
  .footer-cta{ font-size: 32px; }
  .approach h2{ font-size: 28px; }
  .approach blockquote{ font-size: 20px; }
  .about-flip{ padding: 80px 0; }
  /* Keep the exact same card: the real 3D rotateY flip and the photo +
     copy side-by-side grid, unchanged from desktop — just shrunk to fit.
     (An earlier version reflowed this to a stacked layout on mobile to
     avoid clipping, but that changed the whole pattern; scaling every
     dimension down instead keeps it visually identical to the laptop
     view, just smaller and denser, per explicit request — legibility of
     the tiny text is a deliberate trade-off here, not an oversight.) */
  .flip-face{ padding: 32px; }
  .flip-front{ padding: 0; }
  /* The baked-in heading shrinks with the card since it's part of the image, but
     this button is real text/padding and won't — scale it down to match so it
     stays a secondary accent under the heading instead of overpowering it. */
  .flip-cta{ padding: 10px 18px; font-size: 14px; gap: 8px; }
  /* Every value below is the desktop value (see .flip-back and its
     children, base rules above) scaled by the same ratio the card itself
     shrinks by — not shrunk further/unevenly. An earlier pass shrank the
     photo column and spacing more aggressively than the text, which is
     what actually read as "bad design": a disproportionately tiny photo
     and cramped spacing next to text that was relatively fine. Keeping
     every piece at the same scale reproduces the laptop composition
     faithfully, just smaller — not a layout redesign.
     The scale itself is expressed as calc(--mw * ratio) rather than one
     fixed px value: --mw tracks the card's own actual content width
     (100vw minus the two page-margins) at whatever width this loads at,
     so the proportions hold from 320px up through the 809px edge of this
     breakpoint instead of only being correct at the one width they were
     last tuned against. */
  .flip-back{
    --mw: calc(100vw - (2 * var(--page-margin)));
    grid-template-columns: calc(var(--mw) * 0.28947) 1fr;
    column-gap: calc(var(--mw) * 0.05263);
    row-gap: calc(var(--mw) * 0.03684);
    padding: calc(var(--mw) * 0.05789) calc(var(--mw) * 0.05789) calc(var(--mw) * 0.09211);
  }
  .flip-back .about-photo{ border-radius: calc(var(--mw) * 0.01316); }
  .about-copy{ gap: calc(var(--mw) * 0.01316); }
  /* Text ratios are trimmed ~5% below their exact desktop proportion —
     word-wrap breaks at a different point at every width, so a
     paragraph can pick up a whole extra line right around 760–809px
     (this covers, e.g., an iPad Mini in portrait) even though the same
     ratio fits everywhere else. This margin absorbs that without being
     visibly different from the true proportion. */
  .about-desc{ max-width: none; font-size: calc(var(--mw) * 0.01687); line-height: 1.44; }
  .about-experience{ margin-top: calc(var(--mw) * 0.00526); }
  .about-experience-label{ font-size: calc(var(--mw) * 0.01374); margin-bottom: calc(var(--mw) * 0.00395); }
  .about-experience-value{ font-size: calc(var(--mw) * 0.0175); }
  .about-cta{
    right: calc(var(--mw) * 0.05789);
    bottom: calc(var(--mw) * 0.03158);
    padding: calc(var(--mw) * 0.01579) calc(var(--mw) * 0.02895);
    font-size: calc(var(--mw) * 0.01842);
    gap: calc(var(--mw) * 0.01053);
  }
}

@media (prefers-reduced-motion: reduce){
  *{ animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
}
