/* ============================================================
   Melanie Lee — portfolio
   Colours sampled from the Framer build (ready-designers-315061)
   ============================================================ */
:root {
  /* typewriter word colours, in the order the Framer colour list had them */
  --rose: #e0b4b4;
  --peri: #90b1d1;
  --sage: #a3b18a;
  --gray8: #8a8a8a;

  --navy: #182b50;
  --ink: #545454;
  --header-bg: #fafafa;
  --cream: #faf7f2;
  --taupe: #6b5e52;
  --taupe-soft: #9b8e82;
  --line: rgba(107, 94, 82, 0.2);
  --dot: #dfe3ea;

  --gutter: clamp(20px, 6vw, 110px);
  --header-h: 58px;
  --wand-h: 52px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --spring: cubic-bezier(0.34, 1.26, 0.64, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: var(--header-h); }
body {
  margin: 0;
  background: #fff;
  color: var(--ink);
  font-family: "Manrope", system-ui, sans-serif;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { display: block; max-width: 100%; }
/* buttons/inputs don't inherit typography by default */
button, input, textarea, select { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }

.section-title {
  color: var(--navy);
  font-weight: 400;
  font-size: clamp(1.7rem, 2.5vw, 2.5rem);
  letter-spacing: -0.035em;
  line-height: 1.1;
}
.section-title.center { text-align: center; }
/* letters rise in sequence once the heading scrolls in */
.section-title .ltr { display: inline-block; }
/* when the animated line is itself a .reveal, cancel its block-level move and
   let the spans do the work; when it is not, these still apply on their own */
.js .reveal.letters { opacity: 1; transform: none; }
.js .letters .ltr { opacity: 0; transform: translateY(0.4em); }
.js .letters.in .ltr {
  opacity: 1; transform: none;
  transition: opacity 0.45s var(--ease), transform 0.55s var(--spring);
  transition-delay: calc(var(--i) * 45ms);
}
.eyebrow {
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--taupe);
}
.eyebrow.muted { color: var(--taupe-soft); margin: 0 0 20px; }

/* ---------- Top gradient wash (home page) ---------- */
/* Runs from the very top, behind the header bar. The wrapper is exactly
   viewport-width and clips; the glow inside overhangs so its drift never
   exposes an edge. Clipping here rather than on html/body is what
   keeps the overhang out of the document's scrollable area — body can't contain it
   (once body's overflow propagates to the viewport, body stops clipping) and
   html's overflow-x:clip computes to hidden, which still allows scripted scrolling. */
.top-glow-wrap {
  position: absolute; top: 0; left: 0; right: 0; z-index: -1;
  height: clamp(460px, 64vh, 780px);
  overflow: hidden; pointer-events: none;
  -webkit-mask-image: linear-gradient(#000 0%, #000 48%, transparent 100%);
          mask-image: linear-gradient(#000 0%, #000 48%, transparent 100%);
}
.top-glow {
  position: absolute; top: 0; bottom: 0; left: -8%; right: -8%;
  background:
    radial-gradient(62% 60% at 6% 0%,    #f0e8fa 0%, rgba(240, 232, 250, 0) 66%),
    radial-gradient(58% 58% at 32% -6%,  #fdeae0 0%, rgba(253, 234, 224, 0) 66%),
    radial-gradient(62% 62% at 60% -2%,  #fbe4ef 0%, rgba(251, 228, 239, 0) 66%),
    radial-gradient(68% 66% at 93% 8%,   #dfeafb 0%, rgba(223, 234, 251, 0) 68%),
    radial-gradient(52% 46% at 80% 34%,  #e9f5fb 0%, rgba(233, 245, 251, 0) 72%);
  animation: glow-drift 42s ease-in-out infinite;
  /* the wand nudges the wash upward; separate property so it layers on top of
     the drift animation's transform instead of fighting it */
  translate: 0 var(--lift, 0px);
  transition: translate 0.7s var(--ease);
}
/* slow enough to feel like weather rather than an animation */
@keyframes glow-drift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  33%      { transform: translate3d(-1.8%, 1.2%, 0) scale(1.05); }
  66%      { transform: translate3d(1.6%, -0.8%, 0) scale(1.03); }
}

/* ---------- Header ---------- */
/* a floating rounded bar rather than a full-bleed strip */
.site-header {
  position: sticky; top: 10px; z-index: 100;
  height: var(--header-h);
  margin: 10px;
  border-radius: 999px;
  /* the logo alone sits closer to the edge; everything else keeps --gutter */
  padding: 0 var(--gutter) 0 clamp(16px, 2vw, 34px);
  display: flex; align-items: center; justify-content: space-between;
  background: rgba(253, 253, 254, 0.34);
  backdrop-filter: blur(14px);
  transition: box-shadow 0.35s ease, opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.site-header.scrolled { box-shadow: 0 6px 24px rgba(20, 25, 45, 0.07); }
/* slides away once Featured Work reaches the top */
.site-header.gone { opacity: 0; transform: translateY(-130%); pointer-events: none; }
.logo img {
  height: 30px; width: auto;
  transition: transform 0.45s var(--spring);
}
.logo:hover img { transform: scale(1.14); }
.menu-btn {
  width: 40px; height: 40px; padding: 0; border: 0; background: none;
  position: relative; cursor: pointer;
}
.menu-btn span {
  /* right-aligned, same grey as the dot cursor, translucent but still legible */
  position: absolute; right: 0; width: 28px; height: 3px;
  border-radius: 999px;
  background: rgba(138, 138, 138, 0.45);
  transition: transform 0.4s var(--ease), top 0.4s var(--ease);
}
.menu-btn span:nth-child(1) { top: 16px; }
.menu-btn span:nth-child(2) { top: 24px; }
.menu-open .menu-btn span:nth-child(1) { top: 20px; transform: rotate(45deg); }
.menu-open .menu-btn span:nth-child(2) { top: 20px; transform: rotate(-45deg); }

/* ---------- Menu overlay ---------- */
.menu {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center;
  background: rgba(250, 250, 250, 0.97);
  backdrop-filter: blur(6px);
  opacity: 0; visibility: hidden;
  transition: opacity 0.4s var(--ease), visibility 0.4s;
}
.menu-open .menu { opacity: 1; visibility: visible; }
.menu-open { overflow: hidden; }

/* left-aligned panel: logo, tagline, nav, socials, resume */
.menu { display: block; }
.menu-panel {
  width: min(520px, 100%); height: 100%;
  padding: clamp(28px, 5vh, 56px) var(--gutter);
  display: flex; flex-direction: column; align-items: flex-start;
  overflow-y: auto;
}
.menu-tag {
  margin-top: clamp(26px, 5vh, 52px);
  font-size: 1.05rem; line-height: 1.45; color: var(--ink);
}
.menu-tag .menu-tag-top { display: block; color: #b6b6b6; }
/* each word picks up its own sparkle colour on hover */
.hl { transition: color 0.3s var(--ease); }
.hl-blue:hover  { color: var(--peri); }
.hl-green:hover { color: var(--sage); }
.hl-pink:hover  { color: var(--rose); }

.menu-nav { margin-top: clamp(34px, 6vh, 70px); width: 100%; }
.menu-nav li {
  opacity: 0; transform: translateY(14px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.menu-open .menu-nav li { opacity: 1; transform: none; }
.menu-open .menu-nav li:nth-child(2) { transition-delay: 0.05s; }
.menu-open .menu-nav li:nth-child(3) { transition-delay: 0.1s; }
.menu-open .menu-nav li:nth-child(4) { transition-delay: 0.15s; }
.menu-nav a {
  display: inline-block;
  padding: clamp(9px, 1.4vh, 15px) 0;
  color: var(--gray8); font-size: clamp(1.3rem, 2vw, 1.65rem);
  font-weight: 400; letter-spacing: -0.02em;
  transition: color 0.3s, text-shadow 0.3s;
}
/* the links keep their colour — only the buttons below react */

.menu-icons { display: flex; gap: 12px; margin-top: clamp(30px, 5vh, 60px); }
.menu-icons a {
  width: 46px; height: 46px; display: grid; place-items: center;
  border: 1px solid #d7d7d7; border-radius: 50%; color: var(--gray8);
  transition: border-color 0.25s, color 0.25s, transform 0.3s var(--spring);
}
.menu-icons a:hover, .menu-icons a:focus-visible, .menu-icons a:active,
.menu .resume-btn:hover, .menu .resume-btn:focus-visible, .menu .resume-btn:active {
  background: #b4b8c2; border-color: #b4b8c2; color: #fff;
}
.menu-icons svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }

.menu .resume-btn {
  width: 100%; margin-top: 22px; padding: 14px 0;
  border: 1px solid #d0d0d0; border-radius: 10px;
  text-align: center; font-size: 1.05rem; color: var(--ink);
  transition: background 0.3s, color 0.3s, border-color 0.3s;
}
.menu .resume-btn:hover, .menu .resume-btn:focus-visible, .menu .resume-btn:active { font-weight: 600; }

/* ---------- Hero ---------- */
.hero { padding: clamp(60px, 10vw, 150px) var(--gutter) clamp(80px, 12vw, 170px); }
.hero-title {
  max-width: 20em;
  font-size: clamp(1.75rem, 3.6vw, 3.4rem);
  font-weight: 400; letter-spacing: -0.035em; line-height: 1.42;
  color: var(--ink);
}
.hero-line { display: block; }
/* handwritten nudge; fades in whenever the pointer is over the hero */
.hero-hint {
  margin-top: clamp(18px, 2.4vw, 30px);
  text-align: right;
  padding-right: clamp(20px, 5vw, 90px);
  font-family: "Space Mono", ui-monospace, monospace;
  font-style: italic;
  font-size: clamp(0.76rem, 0.95vw, 0.9rem); font-weight: 400;
  color: var(--gray8);
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.hero:hover .hero-hint { opacity: 0.75; transform: none; }
/* The wordmark's own baseline sits ~7% above the bottom of the PNG, so it needs a
   small negative vertical-align to drop that baseline onto the text baseline.
   Only margin-TOP may be negative: `vertical-align` aligns an inline image's bottom
   MARGIN edge, so a negative margin-bottom would shove the art below the baseline
   by that exact amount. Top margin only trims the line box. */
/* The wrapper carries the inline positioning so the decorations can be absolutely
   placed around the wordmark without disturbing the baseline. */
.wm {
  position: relative; display: inline-block;
  margin: -0.45em 0 0 0.2em; vertical-align: -0.109em;
}
.wordmark {
  display: block; height: 1.55em; width: auto;
  transform-origin: 50% 80%;
  transition: transform 0.5s var(--spring);
}
/* scale only — no rotation, so it grows straight out of the line */
.wm:hover .wordmark { transform: scale(1.07); }

/* doodles that pop out around the name on hover */
.wm-deco { position: absolute; inset: 0; pointer-events: none; }
.wm-deco img {
  position: absolute; width: auto;
  opacity: 0;
  transform: scale(0.3) rotate(var(--r, 0deg));
  transition: opacity 0.4s var(--ease), transform 0.55s var(--spring);
}
.wm:hover .wm-deco img { opacity: 1; transform: scale(1) rotate(var(--r, 0deg)); }
.wm-deco .d1 { height: 0.72em; left: -7%;   top: -38%; --r: -14deg; transition-delay: 0.02s; }
.wm-deco .d2 { height: 0.52em; right: 11%;  top: -50%; --r: 8deg;   transition-delay: 0.09s; }
.wm-deco .d3 { height: 0.6em;  right: -5%;  top: -40%; --r: 12deg;  transition-delay: 0.05s; }
.wm-deco .d4 { height: 0.52em; right: -11%; top: 16%;  --r: -9deg;  transition-delay: 0.12s; }
.wm-deco .d5 { height: 0.3em;  right: 22%;  top: -34%; --r: -6deg;  transition-delay: 0.15s; }
.comma { margin-left: -0.12em; }
/* Nudge the typed word off the "a" — the plain word-space reads too tight against
   the bold weight. The caret stays tucked to the word itself. */
.typed { font-weight: 700; color: var(--peri); transition: color 0.4s ease; margin-left: 0.14em; }
.typed[data-i="0"] { color: var(--peri); }
.typed[data-i="1"] { color: var(--rose); }
.typed[data-i="2"] { color: var(--sage); }
.typed[data-i="3"] { color: var(--gray8); }
.caret {
  display: inline-block; width: 1.5px; height: 0.9em;
  margin-left: 0.08em; background: var(--ink); vertical-align: -0.1em;
  animation: blink 1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* ---------- Featured Work — project card grid ---------- */
.work { padding: 0 var(--gutter) clamp(40px, 5vw, 64px); }
.pgrid {
  margin-top: clamp(28px, 4vw, 48px);
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: clamp(34px, 4vw, 56px) clamp(24px, 3vw, 40px);
}
.pgrid-sm { grid-template-columns: repeat(4, 1fr); }
.archive-label { margin-top: clamp(56px, 7vw, 90px); }

.pcard { display: block; }
.pcard-media {
  position: relative; overflow: hidden;
  aspect-ratio: 16 / 10.5;
  border-radius: 14px;
  display: grid; place-items: center;
  transition: transform 0.55s var(--ease), box-shadow 0.55s var(--ease);
}
.pcard.small .pcard-media { aspect-ratio: 16 / 10; border-radius: 12px; }
/* fills the card; the gradient is only a fallback for a missing image */
.pcard-media img { width: 100%; height: 100%; object-fit: cover; }
.pcard:hover .pcard-media { transform: translateY(-5px); box-shadow: 0 16px 40px rgba(40, 50, 80, 0.12); }

/* soft gradient backdrops, used as-is until a project image is dropped in */
.g1 { background: linear-gradient(135deg, #dbe4f5, #f0d9e8 45%, #cfd8ef); }
.g2 { background: linear-gradient(135deg, #f7dccb, #f3c9d6 40%, #d2e2d5); }
.g3 { background: linear-gradient(135deg, #dfe7f7, #e4ddf2 50%, #f1e2ea); }
.g4 { background: linear-gradient(135deg, #fbe0cd, #e9d9f0 45%, #cfe0f5); }

.pcard-meta {
  margin-top: 14px;
  display: flex; align-items: baseline; justify-content: space-between; gap: 18px;
}
.pcard-title { color: var(--ink); font-size: 0.95rem; letter-spacing: -0.01em; }
.pcard.small .pcard-title { font-size: 0.88rem; }
.pcard-client { color: var(--gray8); font-size: 0.88rem; flex-shrink: 0; }
.pcard.small .pcard-client { font-size: 0.8rem; }

/* ---------- Dotted background ---------- */
.dotted { position: relative; isolation: isolate; }
.dotted::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background-image: radial-gradient(var(--dot) 1.4px, transparent 1.6px);
  background-size: 24px 24px;
  -webkit-mask-image: radial-gradient(ellipse 72% 65% at 50% 50%, #000 30%, transparent 100%);
          mask-image: radial-gradient(ellipse 72% 65% at 50% 50%, #000 30%, transparent 100%);
}

/* ---------- About: a pinned collage ---------- */
.about { padding: clamp(40px, 5vw, 60px) var(--gutter) clamp(70px, 9vw, 120px); }
/* the home teaser is small, so it does not need the collage's breathing room */
.home .about { padding-top: clamp(16px, 2.2vw, 26px); padding-bottom: clamp(22px, 3vw, 36px); }
/* on /about the closing block brings its own rule and padding */
.about-body .about { padding-bottom: clamp(40px, 5vw, 70px); }

/* Positions mirror the layout mockup, as percentages of the stage, so the whole
   arrangement scales as one piece. */
.collage {
  position: relative;
  width: min(760px, 100%);
  margin: clamp(22px, 3vw, 36px) auto 0;
  aspect-ratio: 1054 / 894;
}
.c-item {
  position: absolute; margin: 0; padding: 0; border: 0; background: none;
  line-height: 0;
  /* everything breathes a little, each on its own offset. float uses `translate`,
     which is its own property, so it layers over the transform below. */
  animation: c-float var(--dur, 5s) ease-in-out var(--delay, 0s) infinite;
  /* --fx/--fy/--fs are driven by scroll; the hover scale multiplies into --fs */
  transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(var(--fs, 1));
  transition: transform 0.45s var(--spring);
}
.c-item > img { width: 100%; height: auto; display: block; }
@keyframes c-float {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -6px; }
}
.pop { cursor: pointer; }
.pop:hover { z-index: 8; transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(calc(var(--fs, 1) * 1.06)); }
/* clicking any sticker brings it in front of the others */
.c-item.front { z-index: 8; }
.c-item.front.lift { animation: bk-lift 0.45s var(--spring); }

/* photographs get a white edge, doodles do not */
/* the base photo only: the pop-out photo must not inherit the white edge, which
   with border-box would eat into its box and skew the aspect ratio. Golf's base
   now sits inside a .swap, so it is named rather than matched as a child. */
.c-hero > img { border: 5px solid #fff; box-shadow: 0 10px 28px rgba(40, 40, 60, 0.16); }
/* the baby photo is a little polaroid: even white edge, deeper at the foot.
   The action shot that replaces it stays a plain rounded photo. */
.c-golf .a {
  border: 5px solid #fff;
  border-bottom-width: 17px;
  box-shadow: 0 10px 28px rgba(40, 40, 60, 0.16);
}

.c-hero  { left: 22.4%; top: 6.5%;  width: 58%;   --dur: 6.5s; --delay: 0s;   z-index: 2; }
.c-golf  { left: 13.8%; top: 3.5%;  width: 22.8%; --dur: 5.4s; --delay: .6s;  z-index: 3; rotate: -1.5deg; }
.c-dog   { left: 81.5%; top: 5.8%;  width: 14.5%; --dur: 4.8s; --delay: .2s;  z-index: 4; }
.c-globe { left: 89%;   top: 22.5%; width: 10%;   --dur: 5.8s; --delay: 1.1s; z-index: 4; }
.c-booth { left: 7%;    top: 64%;   width: 15%;   --dur: 6.1s; --delay: .4s;  z-index: 3; rotate: -7deg; }
.c-food  { left: 17%;   top: 79%;   width: 17.5%; --dur: 5.2s; --delay: .9s;  z-index: 5; }
.c-books { left: 70%;   top: 73%;   width: 29%;   --dur: 5.6s; --delay: .7s;  z-index: 5; aspect-ratio: 3 / 2; }

/* a doodle exchanges for the real photo, in place */
.swap { position: relative; display: block; }
.swap img { transition: opacity 0.35s var(--ease); }
.swap .b {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; border-radius: 8px; opacity: 0;
}
.pop:hover .swap .b { opacity: 1; }
.pop:hover .swap .a { opacity: 0; }

/* the action shot is squarer than the baby photo it covers, so like Mallorca it
   takes its own aspect and centres, which is what lets the rounding show */
.c-golf .swap .b {
  inset: auto; left: 50%; top: 50%;
  width: 100%; max-width: none; height: auto;
  translate: -50% -50%;
  border-radius: 10px;
  box-shadow: 0 8px 22px rgba(40, 40, 60, 0.2);
}

/* the Mallorca photo comes out larger than the globe doodle. width + height:auto
   keeps its own aspect, so border-radius clips the real edges; with object-fit
   the image would letterbox inside the box and the rounding would never show. */
.c-globe .swap .b {
  inset: auto; left: 50%; top: 50%;
  /* the base `img { max-width: 100% }` would clamp it back to the doodle's width */
  width: 150%; max-width: none; height: auto;
  translate: -50% -50%;
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(40, 40, 60, 0.18);
}

/* the globe shakes first and the photo only arrives once it has settled, so
   what you see shaking is the globe rather than the picture. The delay is on
   the hover state alone, so leaving still clears the photo straight away. */
.c-globe.pop:hover .swap img { transition-delay: 0.62s; }

/* a shake on hover, like you just picked the globe up. c-float animates
   `translate` and this animates `rotate`, so the two layer rather than fight. */
.c-globe:hover {
  animation:
    c-float var(--dur, 5s) ease-in-out var(--delay, 0s) infinite,
    globe-shake 0.7s ease-in-out;
}
@keyframes globe-shake {
  0%, 100% { rotate: 0deg; }
  12% { rotate: -7deg; }
  28% { rotate: 6deg; }
  44% { rotate: -4.5deg; }
  60% { rotate: 3deg; }
  78% { rotate: -1.5deg; }
}

/* the books fan out from a stack */
.c-books .bk {
  position: absolute; left: 50%; top: 0; width: 44%; height: auto;
  cursor: pointer;
  border-radius: 3px; filter: drop-shadow(0 5px 12px rgba(40, 40, 60, 0.22));
  transform-origin: 50% 88%;
  transition: transform 0.55s var(--spring);
}
.c-books .bk1 { transform: translateX(-50%) rotate(-4deg); }
.c-books .bk2 { transform: translateX(-50%) rotate(2deg); }
.c-books .bk3 { transform: translateX(-50%) rotate(0deg); }
.c-books .bk.front { z-index: 3; }
.c-books .bk.lift { animation: bk-lift 0.45s var(--spring); }
@keyframes bk-lift {
  0%   { scale: 1; }
  45%  { scale: 1.09; }
  100% { scale: 1; }
}
.c-books:hover .bk1 { transform: translateX(-104%) rotate(-15deg); }
.c-books:hover .bk2 { transform: translateX(-50%) rotate(0deg) translateY(-8px); }
.c-books:hover .bk3 { transform: translateX(4%) rotate(15deg); }

/* what pops out beside the object: the photo at its own crop, plus the note */
.pop-out {
  position: absolute; top: 50%; z-index: 9;
  /* absolutely positioned inside a narrow object, it would shrink-to-fit that
     object's width and wrap to one word per line; max-content sizes it to the
     text instead, and max-width caps how wide it may get */
  width: max-content; max-width: 190px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 7px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.28s var(--ease), transform 0.42s var(--spring);
}
.pop-out img {
  /* natural size, so the photo keeps the crop it came with; flex:0 0 auto stops
     the column from resizing it off its own aspect */
  flex: 0 0 auto;
  width: auto; height: auto;
  max-width: clamp(120px, 17vw, 190px); max-height: 180px;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(40, 40, 60, 0.2);
}
.pop-out .txt {
  align-self: center;
  /* wraps rather than running off the page, but balanced so lines hold several
     words instead of stacking one per row */
  max-width: 100%; text-align: center;
  text-wrap: balance;
  font-family: "Space Mono", ui-monospace, monospace;
  font-style: italic;
  font-size: clamp(0.72rem, 0.9vw, 0.84rem); line-height: 1.3;
  letter-spacing: -0.01em; color: var(--gray8);
}
.pop-right { left: calc(100% + 12px); transform: translateY(-50%) translateX(-8px) scale(0.92); }
.pop-left  { right: calc(100% + 12px); align-items: flex-end; transform: translateY(-50%) translateX(8px) scale(0.92); }
/* for objects sitting on top of the photo, the clear space is above or below */
.pop-bottom {
  top: calc(100% + 8px); left: 50%; align-items: center;
  transform: translateX(-50%) translateY(-6px) scale(0.92);
}
.pop-top {
  top: auto; bottom: calc(100% + 8px); left: 50%; align-items: center;
  transform: translateX(-50%) translateY(6px) scale(0.92);
}
.pop:hover .pop-out { opacity: 1; transform: translateY(-50%) translateX(0) scale(1); }
.pop:hover .pop-bottom, .pop:hover .pop-top { transform: translateX(-50%) translateY(0) scale(1); }
/* only ~16% of the stage is clear beside the portrait here, so this one wraps */
.c-dog .pop-out { max-width: 125px; }
/* centred over the strip, this one's right edge reached the portrait at full
   width; capping it wraps to two lines and keeps the text off the photo.
   The strips lean 6.9deg inside the PNG (measured off its alpha channel) on top
   of the object's own -7deg, so the caption leans with them rather than being
   straightened back up. The negative margin is the strips' centre of mass,
   8.4% left of the PNG's middle, so it sits between the two rather than over
   the hand. */
/* the tilt inflates this one's box, so it needs real clearance to sit off
   the strips rather than the 8px the shared rule gives */
.c-booth .pop-out { max-width: 140px; margin-left: -8.4%; margin-bottom: 42px; }
/* centred over the stack, this one's left end sat right against the portrait's
   edge, so it shifts well clear of it and still has 20px inside the stage.
   The books overflow their box when they fan, so it also lifts clear of them. */
.c-books .pop-out { margin-left: 48px; margin-bottom: 16px; }
/* the tilted items rotate their children too, so straighten the pop-out back up */
/* the swapped-in action shot is taller than the baby photo and rises ~20px
   above the object, so the caption lifts clear of it */
.c-golf .pop-out  { rotate: 1.5deg; margin-bottom: 20px; }
.c-booth .pop-out { rotate: -6.9deg; }   /* leans with the strips, not upright */

/* ---------- About teaser (home page) ---------- */
.ab-teaser {
  margin: clamp(26px, 3.4vw, 44px) auto 0;
  max-width: 860px;
  display: flex; align-items: center;
  gap: clamp(24px, 3.6vw, 52px);
}
.ab-teaser-photo {
  flex: 0 0 auto;
  /* portrait crop, so this is narrower than the landscape shot it replaced */
  width: clamp(190px, 25vw, 290px); height: auto;
  border-radius: 10px;
  box-shadow: 0 12px 30px rgba(40, 40, 60, 0.14);
  animation: teaser-float 6.2s ease-in-out infinite;
}
@keyframes teaser-float {
  0%, 100% { translate: 0 0;     rotate: -0.6deg; }
  50%      { translate: 0 -10px; rotate: 0.6deg; }
}
/* on the home page the heading rides with the paragraph, so it drops from
   section-title size to something that sits with body copy */
.home .about .section-title {
  font-size: clamp(1.25rem, 1.8vw, 1.65rem);
  margin-bottom: 13px;
}
.ab-teaser-copy p {
  font-size: clamp(1rem, 1.25vw, 1.15rem);
  line-height: 1.7;
  color: var(--ink);
  text-wrap: balance;
}
.ab-more {
  display: inline-block;
  margin-top: 18px;
  color: var(--navy);
  font-family: "Space Mono", ui-monospace, monospace;
  font-style: italic;
  font-size: 0.86rem;
  padding-bottom: 2px;
  border-bottom: 1px solid var(--line);
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.ab-more span { display: inline-block; transition: translate 0.3s var(--spring); }
.ab-more:hover { color: var(--taupe); border-bottom-color: var(--taupe); }
.ab-more:hover span { translate: 5px 0; }
@media (max-width: 760px) {
  .ab-teaser { flex-direction: column; text-align: center; }
  .ab-teaser-photo { width: min(320px, 78%); }
}

@media (max-width: 720px) {
  /* the pinned layout stops working on a phone, so it becomes a simple flow */
  .collage { aspect-ratio: auto; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 16px; }
  .c-item { position: static; width: auto; max-width: 42%; rotate: none; animation: none; }
  .c-hero { max-width: 100%; order: -1; }
  .c-books { aspect-ratio: auto; height: 130px; width: 60%; position: relative; }
  .pop-out { display: none; }
}
@media (prefers-reduced-motion: reduce) { .c-item, .ab-teaser-photo { animation: none; } }

/* ---------- Art carousel ---------- */
.art { position: relative; padding: clamp(34px, 4vw, 52px) 0 clamp(20px, 3vw, 40px); }
/* the carousel is decorative, so the way through to Play only appears when
   someone is already looking at it */
.art-peek {
  position: absolute; right: clamp(16px, 4vw, 40px); bottom: clamp(8px, 2vw, 22px);
  z-index: 3;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 16px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 22px rgba(20, 25, 45, 0.14);
  color: var(--ink); font-size: 0.82rem; white-space: nowrap;
  opacity: 0; translate: 0 8px; pointer-events: none;
  transition: opacity 0.35s var(--ease), translate 0.35s var(--ease);
}
.art:hover .art-peek,
.art:focus-within .art-peek { opacity: 1; translate: 0 0; pointer-events: auto; }
.art-peek span { transition: translate 0.3s var(--spring); }
.art-peek:hover span { translate: 3px 0; }
@media (hover: none) { .art-peek { opacity: 1; translate: 0 0; pointer-events: auto; } }
.art-viewport { overflow: hidden; padding: 22px 0; }
.art-track {
  display: flex; align-items: center; gap: 22px; width: max-content;
  animation: art-scroll 52s linear infinite;
}
.art-item {
  flex-shrink: 0; margin: 0;
  height: clamp(190px, 24vw, 330px);
  border-radius: 8px; overflow: hidden;
  transition: transform 0.4s var(--ease);
}
.art-item img { display: block; height: 100%; width: auto; max-width: none; object-fit: cover; }
/* tiles pop on hover; the track keeps scrolling underneath them */
.art-item:hover { transform: scale(1.07); position: relative; z-index: 2; }
/* half the track is a duplicate; the extra half-gap keeps the seam invisible */
@keyframes art-scroll { to { transform: translateX(calc(-50% - 11px)); } }

/* ---------- Footer ---------- */
.site-footer {
  position: relative; isolation: isolate;
  padding: clamp(60px, 9vw, 130px) var(--gutter) 34px;
  /* transparent, so the top edge is exactly the page colour where the
     carousel ends and there is no seam */
  background: transparent;
}
/* wash rising from the bottom, gone by the time it reaches the name */
.site-footer::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(to top,
    rgba(223, 234, 251, 0.9) 0%,
    rgba(251, 228, 239, 0.6) 26%,
    rgba(253, 234, 224, 0.34) 48%,
    rgba(255, 255, 255, 0) 78%);
}
.site-footer > * { position: relative; z-index: 1; }
.footer-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: clamp(30px, 6vw, 90px);
  padding-bottom: clamp(26px, 4vw, 48px);
}
.footer-name {
  flex: 1; min-width: 0;
  font-size: clamp(2.6rem, 9vw, 6.5rem);
  font-weight: 200; line-height: 1.04; letter-spacing: -0.045em;
  color: var(--ink);
  min-height: 2.08em;              /* holds its own height while the text types in */
}
.footer-name.typing::after {
  content: ""; display: inline-block;
  width: 0.045em; height: 0.78em; margin-left: 0.04em;
  background: currentColor; vertical-align: -0.06em;
  animation: blink 1s steps(1) infinite;
}
.footer-links { display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; }
.footer-links a {
  font-size: 0.92rem; color: var(--gray8);
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 1px no-repeat;
  transition: background-size 0.4s var(--ease), color 0.25s;
}
.footer-links a:hover { background-size: 100% 1px; color: var(--navy); }
.fine { margin-top: 26px; font-size: 0.75rem; color: #b4b4b4; }

/* ---------- Magic wand cursor ---------- */
.has-wand, .has-wand * { cursor: none !important; }
.wand-cursor {
  position: fixed; top: 0; left: 0; z-index: 2147483647;
  pointer-events: none; will-change: transform;
}
/* Size lives here, not in JS, and BOTH dimensions are explicit: the percentages in
   transform-origin resolve against the border box, so with width:auto they were
   collapsing to 0 and the wand pivoted from its left edge instead of the star tip.
   wand.webp is 225x560, so width = height * 0.402.
   Positive angle leans the shaft to the left. */
.wand-cursor img {
  height: var(--wand-h);
  width: calc(var(--wand-h) * 0.402);
  transform-origin: 46% 3%;
  transform: translate(-46%, -3%) rotate(-32deg);
  transition: opacity 0.25s ease, scale 0.3s var(--ease);
}

/* Over Featured Work the wand becomes a small translucent grey dot; clicking
   brings it to full opacity. */
.wand-cursor::before {
  content: ""; position: absolute;
  width: var(--dot-size, 15px); height: var(--dot-size, 15px);
  margin: calc(var(--dot-size, 15px) / -2) 0 0 calc(var(--dot-size, 15px) / -2);
  border-radius: 50%; background: #8a8a8a;
  opacity: 0; scale: 0.4;
  transition: opacity 0.25s ease, scale 0.3s var(--spring), background 0.2s ease;
}
/* Case-study pages use the same dot as the home page — no size override. */
.wand-cursor.dot img { opacity: 0; scale: 0.7; }
.wand-cursor.dot::before { opacity: 0.4; scale: 1; }
.wand-cursor.dot.press::before { opacity: 1; scale: 0.88; }
.sparkle {
  position: fixed; z-index: 2147483646;
  pointer-events: none; user-select: none; line-height: 1;
  animation: sparkle-drift ease-out forwards;
}
@keyframes sparkle-drift {
  0%   { opacity: 1; transform: translate(-50%, -50%) translate(0, 0) scale(1); }
  80%  { opacity: 0.6; }
  100% { opacity: 0; transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) scale(0.5); }
}

/* ---------- Scroll reveal ---------- */
.js .reveal { opacity: 0; transform: translateY(26px); transition: opacity 0.9s var(--ease), transform 0.9s var(--ease); }
.js .reveal.in { opacity: 1; transform: none; }

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  .pgrid { grid-template-columns: 1fr; }
  .pgrid-sm { grid-template-columns: repeat(2, 1fr); }

}
@media (max-width: 600px) {
  .worlds { padding: 26px 18px; border-radius: 20px; }
  /* name over links once they can't sit side by side */
  .footer-row { flex-direction: column; align-items: flex-start; gap: 26px; }
  .footer-name { width: 100%; }   /* column flex would otherwise shrink it to 0 */
  .footer-links { flex-direction: row; flex-wrap: wrap; gap: 8px 20px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition-duration: 0.01ms !important; }
  .js .reveal { opacity: 1; transform: none; }
}

/* ---------- Play page ---------- */
.play-page { padding: clamp(50px, 8vw, 110px) var(--gutter) clamp(60px, 8vw, 110px); }
.play-lede { margin-top: 14px; max-width: 34em; color: var(--taupe-soft); }
.art-wall {
  margin-top: clamp(36px, 5vw, 64px);
  columns: 3 260px; column-gap: 22px;
}
.art-wall .art-tile {
  break-inside: avoid; margin: 0 0 22px;
  border-radius: 16px; overflow: hidden;
  box-shadow: 0 8px 26px rgba(20, 25, 45, 0.13);
  transition: transform 0.45s var(--ease), box-shadow 0.45s var(--ease);
}
.art-tile:not(:has(img)) { height: clamp(220px, 26vw, 340px); }
.art-tile img { display: block; width: 100%; height: auto; }
/* the title belongs to the piece, not beside it: a soft scrim on hover */
.art-wall .art-tile { position: relative; }
.play-page .gallery-label { margin-top: clamp(30px, 4vw, 48px); }
.play-page .gallery-label + .art-wall { margin-top: 16px; }
.art-tile figcaption {
  position: absolute; right: 0; bottom: 0; left: 0;
  display: flex; flex-direction: column; gap: 3px;
  padding: 42px 16px 15px;
  color: #fff;
  background: linear-gradient(to top, rgba(16, 20, 34, 0.82) 38%, rgba(16, 20, 34, 0));
  opacity: 0; translate: 0 6px;
  transition: opacity 0.36s var(--ease), translate 0.36s var(--ease);
}
.art-title { font-size: 0.88rem; font-weight: 600; line-height: 1.3; letter-spacing: -0.01em; }
.art-medium { font-size: 0.74rem; color: rgba(255, 255, 255, 0.72); }
.art-note { margin-top: 4px; font-size: 0.76rem; line-height: 1.45; color: rgba(255, 255, 255, 0.85); }
.art-tile:hover figcaption,
.art-tile:focus-within figcaption { opacity: 1; translate: 0 0; }
/* no cursor to hover with, so leave them on */
@media (hover: none) {
  .art-tile figcaption { opacity: 1; translate: 0 0; }
}
.art-tile:hover { transform: translateY(-5px); box-shadow: 0 18px 44px rgba(20, 25, 45, 0.2); }
.site-header .proj-back {
  padding: 8px 15px; border-radius: 999px; background: #fff;
  font-size: 0.83rem; color: var(--ink);
  box-shadow: 0 1px 3px rgba(20, 25, 45, 0.08);
  transition: color 0.25s;
}
.site-header .proj-back:hover { color: var(--peri); }

/* ---------- Layered cover: the sky drifts behind a still peak ---------- */
/* Both layers cover by height at the same scale, so they stay registered:
   the sky plate is 12% wider and inset by -6%, giving the drift its headroom. */
.pcard-media.is-parallax { display: block; }
.pcard-media.is-parallax img { position: absolute; top: 0; height: 100%; max-width: none; object-fit: cover; }
.pcard-media.is-parallax .layer-back {
  left: -6%; width: 112%;
  transition: translate 1.1s var(--ease), scale 1.1s var(--ease);
}
.pcard-media.is-parallax .layer-front {
  left: 0; width: 100%;
  transition: translate 1.1s var(--ease), scale 1.1s var(--ease);
}
.pcard:hover .is-parallax .layer-back {
  translate: -2.6% 0; scale: 1.04;
  transition-duration: 5.5s;
}
.pcard:hover .is-parallax .layer-front {
  translate: 0.5% 0; scale: 1.015;
  transition-duration: 5.5s;
}

/* Every other cover gets the same slow push, from the one layer it has.
   The parallax rules above are more specific, so Summit keeps its real depth.
   Nothing zooms: a fixed 5% oversize at rest gives the drift somewhere to go,
   since several covers now fill the card exactly and would open a gap. */
.pcard-media img { transition: translate 1.1s var(--ease), scale 1.1s var(--ease); }
.pcard-media:not(.is-parallax):not(.is-panels) img { scale: 1.05; }
.pcard:hover .pcard-media img { translate: -1.8% 0; transition-duration: 5.5s; }

@media (prefers-reduced-motion: reduce) {
  .pcard:hover .pcard-media img,
  .pcard:hover .is-parallax .layer-back,
  .pcard:hover .is-parallax .layer-front { translate: 0 0; }
}

/* ---------- Three-panel cover: each world shifts on its own ---------- */
/* One image three times, each clipped to its panel. The clip lives on the
   wrapper, which never moves, so the seams between the panels hold still
   while the LA skyline, the rocket and the denim drift behind them. */
.pcard-media.is-panels { display: block; }
.pcard-media.is-panels .pane { position: absolute; inset: 0; overflow: hidden; }
.pcard-media.is-panels .pane img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none; object-fit: cover;
  pointer-events: none;
}
.pcard-media.is-panels .pane .plate {
  transition: translate 1.1s var(--ease), scale 1.1s var(--ease);
}
/* the panels are exact thirds now, with a hair of overlap so no seam shows */
.pane-laci     { clip-path: inset(0 66.4% 0 0); }
.pane-troylabs { clip-path: inset(0 33.1% 0 33.2%); }
.pane-tao      { clip-path: inset(0 0 0 66.5%); }

/* One gesture across the three: the panels lift and spread outward from the
   middle, all on the same 6s cycle, so it reads as the collage breathing
   rather than three separate tricks. */
.pcard-media.is-panels .pane-troylabs .plate,
.pcard-media.is-panels .rocket { scale: 1.04; }
.pcard:hover .is-panels .pane .plate { transition-duration: 6s; }
.pcard:hover .is-panels .pane-laci .plate     { translate: -1.4% 0; }
.pcard:hover .is-panels .pane-troylabs .plate { translate: 0 -1.2%; }
.pcard:hover .is-panels .pane-tao .plate      { translate: 1.4% 0; }

/* The palms sway from the ground up, so the skyline holds and the fronds
   move; the denim breathes on the same beat. translate/scale compose here. */
.pcard-media.is-panels .pane-laci .plate,
.pcard-media.is-panels .pane-tao .plate { transform-origin: 50% 100%; }
.pcard:hover .is-panels .pane-laci .plate { animation: palm-sway 6s ease-in-out infinite; }
.pcard:hover .is-panels .pane-tao .plate  { animation: denim-shift 6s ease-in-out infinite; }

@keyframes palm-sway {
  0%   { transform: skewX(0deg); }
  25%  { transform: skewX(0.34deg); }
  50%  { transform: skewX(0deg); }
  75%  { transform: skewX(-0.24deg); }
  100% { transform: skewX(0deg); }
}
@keyframes denim-shift {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(0.4%, -0.55%); }
  50%  { transform: translate(0, 0); }
  75%  { transform: translate(-0.3%, 0.4%); }
  100% { transform: translate(0, 0); }
}

/* the chalk rocket, lifted off the plate so it can leave. It launches on the
   first quarter of that cycle and then drifts with everything else. */
.pcard-media.is-panels .rocket {
  transition: translate 1s var(--ease), rotate 1s var(--ease), scale 1s var(--ease);
}
.pcard:hover .is-panels .rocket {
  translate: 2.6% -24%; rotate: 6deg; scale: 1.05;
  transition-duration: 1.5s;
  transition-timing-function: cubic-bezier(0.38, 0.02, 0.2, 1);
  animation: rocket-drift 6s 1.5s ease-in-out infinite;
}
@keyframes rocket-drift {
  0%   { transform: translate(0, 0) rotate(0deg); }
  25%  { transform: translate(0.5%, -1.1%) rotate(1.2deg); }
  50%  { transform: translate(0, 0) rotate(0deg); }
  75%  { transform: translate(-0.4%, 0.7%) rotate(-0.9deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .pcard:hover .is-panels .pane .plate { translate: 0 0; animation: none; }
  .pcard:hover .is-panels .rocket { translate: 0 0; rotate: 0deg; scale: 1; animation: none; }
}



/* ---------- Covers with a moving part ---------- */
/* The plate and the part drift together on a wrapper whose origin is the
   centre, so they stay registered; the part itself only turns, about its own
   pivot. Cover fits these images by height, so the pivot in element-box
   terms is y = py/ih and x = 0.5 + (px - iw/2) / (ih * cardRatio). */
.pcard-media.is-parts { display: block; }
.pcard-media.is-parts .plate,
.pcard-media.is-parts .part-layer { position: absolute; inset: 0; }
.pcard-media.is-parts .part-layer { overflow: hidden; pointer-events: none; }
.pcard-media.is-parts img {
  width: 100%; height: 100%; max-width: none; object-fit: cover;
  pointer-events: none;
}
.pcard-media.is-parts .part-layer img { position: absolute; inset: 0; }
.pcard-media.is-parts .plate,
.pcard-media.is-parts .part-layer {
  transition: translate 1.1s var(--ease), scale 1.1s var(--ease);
}
.pcard:hover .is-parts .plate,
.pcard:hover .is-parts .part-layer {
  translate: -1.8% 0; transition-duration: 5.5s;
}

/* A wordmark should not move at all: lifting it onto a layer that stays put
   lets the picture behind it take the full push without the name growing. */
.pcard-media.is-parts .part-layer.is-still,
.pcard:hover .is-parts .part-layer.is-still { translate: 0 0; scale: 1; }

/* the watch keeps time while you look at it */
.pcard-media.is-parts .hands {
  transform-origin: 48.12% 45.91%;
  transition: rotate 1.1s var(--ease);
}
.pcard:hover .is-parts .hands { rotate: 16deg; transition-duration: 5.5s; }

@media (prefers-reduced-motion: reduce) {
  .pcard:hover .is-parts .plate,
  .pcard:hover .is-parts .part-layer { translate: 0 0; }
  .pcard:hover .is-parts .hands { rotate: 0deg; }
}

/* Cartier holds still and only the hands turn: on a photograph of a watch,
   drifting the whole picture competes with the one thing that should move. */
.pcard:hover .is-parts.is-hold .plate,
.pcard:hover .is-parts.is-hold .part-layer { translate: 0 0; }

/* Pandora comes forward instead of sideways: the composition is centred on
   her, so a drift across it reads as the frame slipping rather than movement. */
.pcard:hover .pcard-media.is-forward img { translate: 0 0; scale: 1.09; }
/* a lifted wordmark still holds its place and its size */
.pcard-media.is-forward .part-layer.is-still img,
.pcard:hover .is-forward .part-layer.is-still img { translate: 0 0; scale: 1.05; }
