/* ============================================================
   Michelle Shen — Portfolio
   Traditional Chinese peach-pink aesthetic
   ============================================================ */

/* ---------- self-hosted fonts ---------- */
@font-face{
  font-family: "Comfortaa";
  src: url("fonts/Comfortaa-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face{
  font-family: "Comfortaa";
  src: url("fonts/Comfortaa-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face{
  font-family: "DR Exclusive Editorial";
  src: url("fonts/DRExclusiveEditorial.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root{
  --peach-pink: #e96aa3;
  --light-green: #AFDD22;
  --cyan: #61B0AD;
  --bg: #ffffff;
  --ink: #000000;

  --peach-pink-soft: #f7c3da;
  --peach-pink-dim: rgba(233,106,163,0.14);
  --cyan-dim: rgba(97,176,173,0.14);
  --green-dim: rgba(175,221,34,0.18);

  --font-display: "DR Exclusive Editorial", "Comfortaa", Georgia, serif;
  --font-body: "Comfortaa", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  --nav-h: 64px;
  --container: 1180px;
  --radius: 22px;

  --ease: cubic-bezier(.22,.61,.36,1);
}

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

html{ scroll-behavior: smooth; overflow-x: clip; }

@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior: auto; }
  *, *::before, *::after{
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- scroll reveal ----------
   JS tags content blocks with .reveal and adds .is-visible as they
   scroll into view. Gated behind html.reveal-ready so that if the
   script never runs, nothing stays hidden. */
.reveal-ready .reveal{
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 640ms var(--ease), transform 640ms var(--ease);
}
.reveal-ready .reveal.is-visible{
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce){
  .reveal-ready .reveal{ opacity: 1; transform: none; transition: none; }
}

body{
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  transition: background 900ms var(--ease);
}

h1, h2, h3, h4{
  font-family: var(--font-display);
  font-weight: 400;
  margin: 0;
  line-height: 1.12;
  letter-spacing: 0;
}

p{ margin: 0; max-width: 62ch; }

/* bolded key points inside body copy — Comfortaa Bold, full-strength ink
   so they read clearly against the dimmed paragraph text */
p strong, p b{
  font-weight: 700;
  color: var(--ink);
}

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

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

ul, ol{ margin: 0; padding: 0; list-style: none; }

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

.container{
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 32px;
}

.visually-hidden{
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

:focus-visible{
  outline: 2px solid var(--peach-pink);
  outline-offset: 3px;
}

/* ============================================================
   NAV
   ============================================================ */

.site-nav{
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  background: rgba(255,255,255,0.62);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.site-nav .container{
  display: flex;
  align-items: center;
  gap: 40px;
  width: 100%;
}

.nav-logo{
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.nav-logo img{ width: 38px; height: 38px; object-fit: contain; display: block; }

.nav-links{
  display: flex;
  align-items: center;
  gap: 28px;
}

.nav-links > li{ position: relative; }

.nav-link{
  font-size: 14px;
  text-transform: lowercase;
  color: var(--ink);
  padding: 8px 2px;
  position: relative;
}

.nav-item-works .nav-link{
  display: inline-flex;
  align-items: center;
}

.nav-link::after{
  content: "";
  position: absolute;
  left: 0; right: 100%;
  bottom: 4px;
  height: 1px;
  background: var(--peach-pink);
  transition: right 260ms var(--ease);
}
.nav-link:hover::after,
.nav-link:focus-visible::after,
.nav-link.is-current::after{ right: 0; }

.nav-item-works{ display: flex; align-items: center; }

.nav-caret{
  width: 9px; height: 9px;
  margin-left: 6px;
  flex-shrink: 0;
  transition: transform 220ms var(--ease);
}
.nav-item-works:hover .nav-caret,
.nav-item-works.is-open .nav-caret{ transform: rotate(180deg); }

/* Invisible hover bridge: sits flush against the trigger (no gap) so the
   mouse never leaves the hoverable area on the way down to the menu. The
   visible box is the inner panel, offset with its own margin instead. */
.works-dropdown-wrap{
  position: absolute;
  top: 100%;
  left: 0;
  padding-top: 14px;
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 200ms var(--ease), transform 200ms var(--ease);
}

.nav-item-works:hover .works-dropdown-wrap,
.nav-item-works:focus-within .works-dropdown-wrap,
.nav-item-works.is-open .works-dropdown-wrap{
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.works-dropdown{
  min-width: 190px;
  background: rgba(255,255,255,0.94);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(0,0,0,0.08);
  border-radius: var(--radius);
  padding: 6px;
  box-shadow: 0 14px 30px rgba(0,0,0,0.09);
}

.works-dropdown a{
  display: block;
  padding: 10px 14px;
  font-size: 13px;
  text-transform: lowercase;
  border-radius: 14px;
}
.works-dropdown a:hover,
.works-dropdown a:focus-visible{
  background: var(--peach-pink-dim);
  color: var(--peach-pink);
}

/* ============================================================
   FOOTER
   ============================================================ */

.site-footer{
  background: var(--ink);
  color: #ffffff;
  padding: 46px 0 28px;
  margin-top: 120px;
}

.footer-row{
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
}

.footer-left{
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
}

.footer-logo img{ width: 34px; height: 34px; object-fit: contain; display: block; }

.footer-social{
  display: flex;
  align-items: center;
  gap: 18px;
}

.footer-social a{
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: #ffffff;
  opacity: 0.82;
  transition: opacity 180ms var(--ease), color 180ms var(--ease);
}
.footer-social a:hover, .footer-social a:focus-visible{
  color: var(--peach-pink);
  opacity: 1;
}
.footer-social svg{ width: 17px; height: 17px; }
.footer-social svg path{ fill: currentColor; }

.footer-email{
  font-size: 13px;
  color: #ffffff;
  opacity: 0.82;
  border-bottom: 1px solid transparent;
  transition: opacity 180ms var(--ease), color 180ms var(--ease), border-color 180ms var(--ease);
}
.footer-email:hover, .footer-email:focus-visible{
  color: var(--peach-pink);
  opacity: 1;
  border-color: var(--peach-pink);
}

.footer-credit{
  font-size: 12.5px;
  opacity: 0.6;
  letter-spacing: 0.01em;
}

/* ============================================================
   PAGE HEADER (shared across works/about pages)
   ============================================================ */

/* Vertical spacing only — the left/right gutter comes from .container.
   The doubled `.page-head.container` selector outweighs the .container
   padding rule inside the responsive media queries, so the title always
   clears the fixed nav (previously it slid underneath on phones). */
.page-head,
.page-head.container{
  padding-top: calc(var(--nav-h) + 64px);
  padding-bottom: 40px;
}

.page-head .page-kicker{
  font-size: 13px;
  color: var(--peach-pink);
  margin-bottom: 10px;
}

.page-head h1{
  font-size: clamp(2.2rem, 5vw, 3.4rem);
}

.page-head .page-dek{
  margin-top: 16px;
  font-size: 16px;
  opacity: 0.7;
}

.page-divider{
  height: 1px;
  background: linear-gradient(90deg, var(--peach-pink), var(--cyan) 50%, var(--light-green));
  opacity: 0.35;
  margin: 8px 0 56px;
}

section.block{ margin-bottom: 84px; }
section.block:last-of-type{ margin-bottom: 96px; }

.block-head{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 26px;
  flex-wrap: wrap;
}

.block-head h2{ font-size: clamp(1.5rem, 3vw, 2rem); }
.block-head .block-note{ font-size: 13.5px; opacity: 0.55; max-width: 40ch; }

.block-copy{
  font-size: 14.5px;
  line-height: 1.65;
  color: rgba(0,0,0,0.72);
  max-width: 62ch;
  margin: -8px 0 26px;
}

/* media frame — sized to the image/video's own intrinsic dimensions.
   Never stretches, crops, or distorts; the box is exactly as tall as
   the media wants to be at its rendered width. */
.frame{
  position: relative;
  background: #fafafa;
  border: 1px solid rgba(0,0,0,0.1);
  border-radius: var(--radius);
  overflow: hidden;
  line-height: 0;
}
.frame img,
.frame video{
  display: block;
  width: 100%;
  height: auto;
}
.frame-label{
  font-size: 11.5px;
  letter-spacing: 0.02em;
  text-transform: lowercase;
  padding: 5px 10px;
  border: 1px solid rgba(0,0,0,0.14);
  border-radius: 20px;
  display: inline-block;
}
/* fallback placeholder look — only applies to a .frame with no media inside */
.frame:empty,
.frame:not(:has(img, video)){
  min-height: 160px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(0,0,0,0.32);
}
.frame:not(:has(img, video))::before{
  content: "";
  position: absolute; inset: 0;
  background-image:
    linear-gradient(45deg, rgba(0,0,0,0.035) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.035) 50%, rgba(0,0,0,0.035) 75%, transparent 75%, transparent);
  background-size: 16px 16px;
}
.frame > *{ position: relative; z-index: 1; }

.frame.accent-pink{ border-color: var(--peach-pink); }
.frame.accent-cyan{ border-color: var(--cyan); }
.frame.accent-green{ border-color: var(--light-green); }

/* UN speech placeholder — an empty .frame with a play badge overlay,
   standing in for an embedded screen recording */
.civic-play-frame{
  min-height: 380px;
  cursor: pointer;
}
.play-badge{
  width: 62px; height: 62px;
  border-radius: 50%;
  background: rgba(0,0,0,0.7);
  display: flex; align-items: center; justify-content: center;
  transition: transform 200ms var(--ease), background 200ms var(--ease);
}
.civic-play-frame:hover .play-badge{
  transform: scale(1.08);
  background: var(--peach-pink);
}
.play-badge svg{ width: 20px; height: 20px; margin-left: 3px; }
.play-badge path{ fill: #fff; }

/* static hero-toned gradient wash, used behind a section's media grid
   so gaps and uneven masonry heights show soft color instead of flat
   white — same palette as the homepage hero's watercolor wash, just
   fixed in place rather than cursor-tracking */
.section-gradient{
  position: relative;
  z-index: 0;
}
.section-gradient::before{
  content: "";
  position: absolute;
  inset: -24px;
  z-index: -1;
  border-radius: 28px;
  background:
    radial-gradient(circle at 28% 32%,
      rgba(233,106,163,0.32) 0%,
      rgba(97,176,173,0.26) 38%,
      rgba(175,221,34,0.2) 62%,
      rgba(255,255,255,0) 80%);
  filter: blur(30px) saturate(115%);
}

/* soft hero-toned gradient tucked into the bottom-right white space at
   the end of a page's <main> — same palette as the homepage hero wash,
   drifting gently. Sits behind everything (z-index:-1); the parent
   clips it so it never adds horizontal scroll. */
.has-corner-blob{
  position: relative;
  overflow: hidden;
}
.corner-blob{
  position: absolute;
  left: 50%;
  margin-left: -240px;
  bottom: 10px;
  width: 480px;
  height: 480px;
  z-index: -1;
  pointer-events: none;
  border-radius: 47% 53% 55% 45% / 52% 46% 54% 48%;
  background:
    radial-gradient(circle at 42% 40%,
      rgba(233,106,163,0.4) 0%,
      rgba(97,176,173,0.32) 42%,
      rgba(175,221,34,0.26) 66%,
      rgba(255,255,255,0) 82%);
  filter: blur(36px) saturate(115%);
  animation: cornerBlobWiggle 14s ease-in-out infinite;
}

/* looser, more organic drift — travels a little further and morphs its
   outline as it goes, so it reads as a wobbling blob rather than a
   rigid circle nudging up and down */
@keyframes cornerBlobWiggle{
  0%, 100%{
    transform: translate(0, 0) rotate(0deg) scale(1);
    border-radius: 47% 53% 55% 45% / 52% 46% 54% 48%;
  }
  25%{
    transform: translate(-26px, -16px) rotate(-4deg) scale(1.06);
    border-radius: 58% 42% 46% 54% / 44% 58% 42% 56%;
  }
  50%{
    transform: translate(-14px, -34px) rotate(3deg) scale(0.96);
    border-radius: 42% 58% 60% 40% / 56% 44% 56% 44%;
  }
  75%{
    transform: translate(-30px, -12px) rotate(5deg) scale(1.04);
    border-radius: 52% 48% 40% 60% / 48% 60% 40% 52%;
  }
}

/* ============================================================
   HOME
   ============================================================ */

.hero-stage{
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(26px, 5vh, 48px);
  padding-top: var(--nav-h);
  /* leave room at the bottom for the scroll cue so the centered
     mascot / logo / CTA stack never collides with it */
  padding-bottom: clamp(96px, 15vh, 150px);
  overflow: hidden;
  background: var(--bg);
  --mx: 50%;
  --my: 50%;
}

/* Ambient base wash: a faint, centered version of the hero gradient that
   is always present, so the stage never falls back to flat white. The
   cursor-tracking wash blooms in on top of this and fades back down to
   it on mouse-leave. */
.hero-stage::before{
  content: "";
  position: absolute;
  inset: -10%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 50% 50%,
      rgba(233,106,163,0.24) 0%,
      rgba(97,176,173,0.17) 32%,
      rgba(175,221,34,0.14) 54%,
      rgba(255,255,255,0) 78%);
  filter: blur(38px) saturate(115%);
}

/* Watercolor wash: on hover the centre around the cursor stays a soft,
   low-strength pink — so the mascot / logo don't get washed over — that
   blends up through the same palette into the fuller colours toward the
   hero's borders. Tracks the cursor; fades in on mouse-over, back out on
   mouse-leave. */
.watercolor-wash{
  position: absolute;
  inset: -10%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 700ms var(--ease);
  background:
    radial-gradient(circle at var(--mx) var(--my),
      rgba(233,106,163,0.15) 0%,
      rgba(233,106,163,0.22) 12%,
      rgba(175,221,34,0.32) 34%,
      rgba(97,176,173,0.42) 58%,
      rgba(233,106,163,0.55) 92%);
  filter: blur(38px) saturate(115%);
  will-change: opacity, background;
}

.hero-stage.is-active .watercolor-wash{ opacity: 1; }

.hero-particles{
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.hero-particles circle{
  opacity: 0;
  transition: opacity 900ms var(--ease);
}
.hero-stage.is-active .hero-particles circle{ opacity: 0.55; }

.hero-particles circle:nth-child(odd){
  animation: drift 9s ease-in-out infinite;
}
.hero-particles circle:nth-child(even){
  animation: drift 11s ease-in-out infinite reverse;
}

@keyframes drift{
  0%, 100%{ transform: translateY(0px); }
  50%{ transform: translateY(-22px); }
}

.hero-inner{
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5in;
  flex-wrap: wrap;
}

.hero-mascot,
.hero-logo-img{
  height: min(30vw, 260px);
  width: auto;
  max-width: 46vw;
  object-fit: contain;
  flex-shrink: 0;
}

/* hero mascot + logo: both drift in a slow, soft vertical float, slightly
   out of sync so they feel alive rather than mechanically linked */
.hero-mascot{
  animation: heroFloat 6s ease-in-out infinite;
  transform-origin: 50% 50%;
  will-change: transform;
}
.hero-logo-img{
  animation: heroFloat 7.5s ease-in-out infinite;
  animation-delay: -2s;
  transform-origin: 50% 50%;
  will-change: transform;
}

@keyframes heroFloat{
  0%, 100%{ transform: translateY(0) rotate(0deg); }
  50%{ transform: translateY(-14px) rotate(-1.5deg); }
}

/* hero call-to-action — a white pill under the mascot + logo with the
   label always drawn as a palette gradient (the text lives in its own
   <span> so the white box fill and the gradient text-clip don't compete
   for the single background). On hover it lifts with a soft shadow. */
.hero-cta{
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  padding: 14px 32px;
  border: 1px solid rgba(0,0,0,0.06);
  border-radius: 999px;
  background: #ffffff;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.03em;
  text-transform: lowercase;
  color: transparent;
  transition: box-shadow 240ms var(--ease), transform 240ms var(--ease);
}
.hero-cta span{
  display: inline-block;
  background-image: linear-gradient(90deg,
    var(--peach-pink), var(--cyan) 50%, var(--light-green));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.hero-cta:hover,
.hero-cta:focus-visible{
  box-shadow: 0 16px 38px rgba(0,0,0,0.16);
  transform: translateY(-2px);
}

.hero-scroll-cue{
  position: absolute;
  bottom: 34px; left: 50%;
  transform: translateX(-50%);
  z-index: 1;
  font-size: 12px;
  opacity: 0.45;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.hero-scroll-cue .cue-line{
  width: 1px; height: 26px;
  background: var(--ink);
  opacity: 0.5;
}

.intro-strip{
  position: relative;
  padding: 90px 0 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 48px;
  /* only the top is clipped (so the glow can't bleed up into the hero);
     the gradient runs freely out to the browser-window edge. The
     horizontal scrollbar it would otherwise cause is caught by
     `overflow-x: clip` on <html>. */
  clip-path: inset(0 -100vw -100vw -100vw);
}

/* soft hero-toned gradient that carries on behind the selfie and past it,
   fading out only as it reaches the browser-window edge — so there's no
   hard cut anywhere, least of all at the edge of the photo. */
.intro-strip::before{
  content: "";
  position: absolute;
  top: 0;
  right: calc(50% - 50vw);   /* right edge sits at the window's right edge */
  bottom: -40px;
  width: min(760px, 82vw);
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 52% 45%,
      rgba(233,106,163,0.34) 0%,
      rgba(97,176,173,0.26) 36%,
      rgba(175,221,34,0.2) 58%,
      rgba(255,255,255,0) 88%);
  filter: blur(34px) saturate(115%);
}

.intro-strip > *{ position: relative; z-index: 1; }

.intro-strip-text{ flex: 1 1 auto; }

.intro-strip p{
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 2.6vw, 1.9rem);
  max-width: 20ch;
  font-weight: 400;
}
.text-pink{ color: var(--peach-pink); }

/* selfie placeholder — pinned to the right edge of the band, with the
   resume button stacked directly beneath it */
.intro-strip-selfie{
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 16px;
  margin-left: auto;
}
.intro-selfie-frame{
  width: 260px;
  height: 320px;
}
.intro-selfie-frame img{
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* CV button — a pill with a download glyph. Solid peach by default
   (about page); white with peach text on the home intro strip. */
.resume-btn{
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 12px 20px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-transform: lowercase;
  color: #fff;
  background: var(--peach-pink);
  border: 1px solid var(--peach-pink);
  border-radius: var(--radius);
  transition: background 180ms var(--ease), transform 180ms var(--ease), box-shadow 180ms var(--ease);
}
.resume-btn:hover, .resume-btn:focus-visible{
  transform: translateY(-2px);
  box-shadow: 0 10px 22px rgba(233,106,163,0.28);
}
.resume-btn svg{ width: 15px; height: 15px; }
.resume-btn svg path{ stroke: currentColor; }

/* home intro strip: white fill, peach label + icon */
.intro-strip-selfie .resume-btn{
  background: #ffffff;
  border-color: rgba(0,0,0,0.10);
  color: var(--peach-pink);
}
.intro-strip-selfie .resume-btn:hover,
.intro-strip-selfie .resume-btn:focus-visible{
  box-shadow: 0 10px 24px rgba(0,0,0,0.13);
}

.home-links{
  display: flex;
  gap: 14px;
  margin-top: 34px;
  flex-wrap: wrap;
}
.home-link-card{
  border: 1px solid var(--peach-pink);
  border-radius: var(--radius);
  padding: 16px 22px;
  font-size: 13.5px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--peach-pink);
  color: #fff;
  transition: background 200ms var(--ease), box-shadow 200ms var(--ease), transform 200ms var(--ease);
}
.home-link-card:hover, .home-link-card:focus-visible{
  transform: translateY(-2px);
  box-shadow: 0 12px 26px rgba(233,106,163,0.30);
}
.home-link-card .dot{
  width: 7px; height: 7px; border-radius: 50%;
}

/* ============================================================
   WORKS PAGES — shared media grid (civic / design / dev)
   Every box sizes to its own image's or video's natural aspect
   ratio — a masonry column layout so mixed heights sit cleanly
   side by side with no cropping, stretching, or forced ratios.
   ============================================================ */

.media-grid{
  columns: 3 260px;
  column-gap: 18px;
}
.media-grid figure{
  break-inside: avoid;
  margin: 0 0 18px;
}
.media-grid figcaption{
  font-size: 12.5px;
  opacity: 0.6;
  margin-top: 8px;
  text-align: center;
}

/* two- and four-up variants for sections where a fixed column count
   reads better than a flowing masonry (e.g. a 4-piece card set) */
.media-grid.cols-2{ columns: 2 320px; }
.media-grid.cols-4{ columns: 4 200px; }

/* media-row: a strict single line, evenly split N-ways. Use for any
   section whose item count won't tile cleanly in the masonry above
   (an odd count that would otherwise leave a lopsided last row) —
   every item keeps its own aspect ratio, so nothing stretches or
   crops, but every column claims equal width and the row never
   wraps into an uneven leftover gap on desktop. */
.media-row{
  display: flex;
  align-items: flex-start;
  gap: 18px;
}
.media-row > figure{
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
}
.media-row figcaption{
  font-size: 12.5px;
  opacity: 0.6;
  margin-top: 8px;
  text-align: center;
}

/* compact row: same idea, but items shrink to a comfortable size
   instead of stretching to fill — the row stays centered instead
   of spanning full width */
.media-row.is-compact-row{
  justify-content: center;
}
.media-row.is-compact-row > figure{
  flex: 0 1 280px;
}

/* downward flow connector between stacked process rows */
.flow-arrow{
  display: flex;
  justify-content: center;
  align-items: center;
  color: rgba(0,0,0,0.55);
  margin: 4px 0 22px;
}
.flow-arrow svg{ width: 26px; height: 26px; }

/* crop an image to a 3:2 landscape frame (matches the keychain sketch's
   native ratio) so a square source sits at the same rendered size as
   its landscape neighbour in the row instead of towering over it */
.frame.is-crop-3x2{ aspect-ratio: 3 / 2; }
.frame.is-crop-3x2 img{
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* shrink a single item within a row/grid — e.g. a portrait-oriented
   photo frame sitting alongside wider landscape pieces */
.frame.is-compact{
  max-width: 220px;
  margin: 0 auto;
}

/* single centered piece, for a section with just one image */
.media-single{
  max-width: 480px;
  margin: 0 auto;
}

/* ---------- carousel ---------- */
.carousel{
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
}
.carousel-track{
  display: flex;
  align-items: flex-start;
  transition: transform 480ms var(--ease);
}
.carousel-track > .frame{
  flex: 0 0 100%;
  width: 100%;
}
.carousel-controls{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  margin-top: 14px;
}
.carousel-btn{
  width: 34px; height: 34px;
  border: 1px solid rgba(0,0,0,0.15);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: border-color 180ms var(--ease), background 180ms var(--ease);
  flex-shrink: 0;
}
.carousel-btn:hover, .carousel-btn:focus-visible{
  border-color: var(--peach-pink);
  background: var(--peach-pink-dim);
}
.carousel-btn svg{ width: 14px; height: 14px; }
.carousel-dots{ display: flex; gap: 7px; }
.carousel-dots button{
  width: 6px; height: 6px; border-radius: 50%;
  background: rgba(0,0,0,0.2);
  transition: background 180ms var(--ease), transform 180ms var(--ease);
}
.carousel-dots button.is-active{
  background: var(--peach-pink);
  transform: scale(1.3);
}

.media-card-caption{
  font-size: 12.5px;
  opacity: 0.55;
  margin-top: 10px;
  text-align: center;
}

/* ============================================================
   ABOUT
   ============================================================ */

.about-main{
  position: relative;
  overflow: hidden;
}

/* floating gradient blob, tucked into the bottom-right corner — same
   palette as the homepage hero wash, drifting gently instead of tracking
   the cursor. Sized in vw so it stays a corner accent on narrow screens
   instead of washing across the page, and the radial is centred toward
   the corner so the colour fades out well before it reaches the content. */
.about-blob{
  position: absolute;
  right: -50px;
  bottom: -50px;
  width: min(300px, 44vw);
  height: min(300px, 44vw);
  z-index: 0;
  pointer-events: none;
  border-radius: 50%;
  background:
    radial-gradient(circle at 64% 66%,
      rgba(233,106,163,0.5) 0%,
      rgba(97,176,173,0.4) 30%,
      rgba(175,221,34,0.3) 48%,
      rgba(255,255,255,0) 66%);
  filter: blur(30px) saturate(115%);
  animation: blobFloat 9s ease-in-out infinite;
}

@keyframes blobFloat{
  0%, 100%{ transform: translate(0, 0); }
  50%{ transform: translate(-12px, -18px); }
}

/* on phones even a corner accent dominates — drop it */
@media (max-width: 620px){
  .about-blob{ display: none; }
}

.about-grid{
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 360px) 1fr;
  gap: 28px;
  align-items: start;
}

.about-visuals{
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* tall, narrow portrait frame — aspect ratio matches the photo's own
   3:4 so object-fit: cover barely crops (shows the full, zoomed-out shot) */
.about-hallo-frame{
  aspect-ratio: 3 / 4;
  max-width: 360px;
  margin-right: auto;
  position: relative;
}
.about-hallo-frame img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
}

.about-copy-block{ margin-bottom: 46px; }
.about-copy-block:last-child{ margin-bottom: 0; }
.about-copy-label{
  font-size: 13px;
  color: var(--peach-pink);
  margin-bottom: 12px;
}
.about-copy-block h2{
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  margin-bottom: 18px;
}
.about-copy-block p{
  font-size: 15px;
  color: rgba(0,0,0,0.72);
  margin-bottom: 12px;
  max-width: 52ch;
}
.about-copy-block p:last-child{ margin-bottom: 0; }

/* ---------- about: "I'd love to connect!" ---------- */
.about-connect{
  position: relative;
  z-index: 1;
  margin-top: 60px;
  padding-top: 40px;
  border-top: 1px solid rgba(0,0,0,0.08);
  display: flex;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}
.about-connect-mark{
  width: clamp(130px, 18vw, 190px);
  height: auto;
  flex-shrink: 0;
  object-fit: contain;
}
.about-connect-body{ flex: 1 1 320px; }
.about-connect-title{
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.4vw, 2.1rem);
  margin-bottom: 22px;
  color: var(--peach-pink);
}
.about-connect-row{
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px 26px;
}
.connect-item{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--ink);
  opacity: 0.78;
  transition: color 180ms var(--ease), opacity 180ms var(--ease);
}
.connect-item:hover,
.connect-item:focus-visible{
  color: var(--peach-pink);
  opacity: 1;
}
.connect-item svg{
  width: 20px;
  height: 20px;
  fill: currentColor;
}

/* ============================================================
   WORKS PAGES — scroll-past-the-edge page jump
   A floating arrow + label that grows in as you keep scrolling
   past the top or bottom of a works page; pushing far enough
   navigates to the previous / next works page. JS sets --p (0–1).
   ============================================================ */

.page-jump{
  position: fixed;
  left: 50%;
  z-index: 95;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 12px 24px;
  border-radius: 999px;
  background: var(--peach-pink-soft);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  border: 1px solid rgba(0,0,0,0.10);
  box-shadow: 0 14px 34px rgba(233,106,163,0.28);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  color: var(--ink);
  opacity: 0;
  pointer-events: none;
  transform: translateX(-50%) translateY(var(--rest, 14px));
  transition: opacity 220ms var(--ease), transform 220ms var(--ease);
}
.page-jump-next{ bottom: 20px; --rest: 16px; }
.page-jump-prev{ top: calc(var(--nav-h) + 12px); --rest: -16px; }

.page-jump svg{
  width: 22px; height: 22px;
  transition: transform 220ms var(--ease);
}
.page-jump-prev svg{ transform: rotate(180deg); }

.page-jump.is-visible{
  pointer-events: auto;
  opacity: calc(0.62 + 0.38 * var(--p, 0));
  transform: translateX(-50%) translateY(calc(var(--rest, 14px) * (1 - var(--p, 0))));
}
.page-jump-next.is-visible svg{ transform: scale(calc(1 + 0.2 * var(--p, 0))); }
.page-jump-prev.is-visible svg{ transform: rotate(180deg) scale(calc(1 + 0.2 * var(--p, 0))); }

.page-jump.is-firing{
  opacity: 1;
  transform: translateX(-50%) scale(1.06);
}

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 860px){
  .nav-links{ gap: 18px; }
  .hero-mascot, .hero-logo-img{ height: auto; width: 62vw; max-width: 62vw; }
  .media-grid{ columns: 2 200px; }
  .media-grid.cols-4{ columns: 2 200px; }
  .media-row{ flex-wrap: wrap; }
  .media-row > figure{ flex: 1 1 45%; }
  .about-grid{ grid-template-columns: 1fr; }
  .footer-row{ flex-direction: column; align-items: flex-start; }
  .intro-strip{ flex-direction: column; align-items: stretch; gap: 32px; }
  .intro-strip::before{ width: 100%; max-width: none; right: 0; top: auto; height: 60%; bottom: 0; }
  .intro-strip-selfie{ align-items: center; margin-left: 0; width: 100%; }
  .intro-selfie-frame{ width: 100%; max-width: 320px; height: 280px; }
}

@media (max-width: 540px){
  .container{ padding: 0 20px; }
  .media-grid, .media-grid.cols-2, .media-grid.cols-4{ columns: 1; }
  .media-row{ flex-direction: column; }
  .media-row > figure{ flex: 1 1 auto; }
}

