:root {
  --bg: #141414;
  --card: #151515;
  --card-alt: #191919;
  --border: rgba(255, 255, 255, 0.1);
  --text-dim: rgba(255, 255, 255, 0.45);
  --text-mid: rgba(255, 255, 255, 0.6);
  --text-faint: rgba(255, 255, 255, 0.32);
  --radius: clamp(18px, 2vw, 34px);
  --side: clamp(20px, 4vw, 72px);
  --gap: clamp(14px, 1.6vw, 28px);
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  background: var(--bg);
  color: #fff;
  font-family: "Google Sans Flex", "Inter", -apple-system, sans-serif;
  font-variation-settings: "GRAD" 0, "ROND" 0, "wdth" 100;
  -webkit-font-smoothing: antialiased;
  /* the tilt effect attaches pointer listeners to the hero cards, which is
     enough for mobile Chrome to treat them as "tappable" and flash its
     default gray highlight on touch — Safari/Dia don't do this by default,
     hence the cross-browser inconsistency. Kill it everywhere instead. */
  -webkit-tap-highlight-color: transparent;
}

ul { list-style: none; }

::selection { background: rgba(255, 255, 255, 0.86); color: #141414; }

/* one focus ring for every interactive element, drawn from the palette
   instead of the browser's default blue/orange */
:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 3px;
}
.hero-card:focus-visible { outline-offset: 4px; border-radius: var(--radius); }

html { scrollbar-color: rgba(255, 255, 255, 0.18) transparent; }
img { max-width: none; }

/* ---------- ambient background layers ---------- */

.bg-pattern,
.bg-noise {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

.bg-pattern {
  background-image: url("assets/bg-pattern.png");
  background-size: 230px 220px;
  background-position: top left;
  opacity: 0.14;
}

.bg-noise {
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.85" stitchTiles="stitch" numOctaves="2"/><feColorMatrix type="saturate" values="0"/></filter><rect width="100%25" height="100%25" filter="url(%23n)"/></svg>');
  background-size: 180px 180px;
  mix-blend-mode: overlay;
  opacity: 0.05;
  animation: drift 30s linear infinite;
}

@keyframes drift {
  0%   { background-position: 0% 0%; }
  100% { background-position: 180px 180px; }
}

/* ---------- page shell ---------- */

body { overflow: hidden; }

.page {
  position: relative;
  z-index: 1;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: clamp(16px, 3.2vh, 46px) var(--side);
  gap: clamp(16px, 2.4vh, 38px);
}

/* ---------- reveal-on-load ---------- */

[data-reveal] {
  animation: reveal 0.9s var(--delay, 0s) var(--ease) both;
}

@keyframes reveal {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* data-tilt elements own `transform` via JS at all times — the entrance
   animation must only touch opacity, or its fill-mode:both permanently
   pins `transform` and silently overrides every JS write. */
[data-tilt][data-reveal] {
  animation: revealFade 0.9s var(--delay, 0s) var(--ease) both;
}

@keyframes revealFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { animation: none; }
  .bg-noise { animation: none; }
}

/* ---------- header ---------- */

.site-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.logo {
  font-family: "Pacifico", cursive;
  font-size: clamp(20px, 1.7vw, 28px);
}

.logo-dim { color: #757575; }

.contact-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(27, 27, 27, 0.55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--border);
  border-radius: 50px;
  padding: clamp(10px, 1.4vh, 18px) clamp(16px, 1.6vw, 24px);
  color: #fff;
  text-decoration: none;
  font-size: clamp(13px, 1vw, 16px);
  /* no `transform` here: the magnetic effect in script.js writes it every
     frame, and a CSS transition on top re-starts a 300ms tween per write
     (mushy follow). Press feedback uses the separate `scale` property so
     it composes with the JS transform instead of fighting it. */
  transition: background 0.3s var(--ease), box-shadow 0.3s var(--ease), scale 0.16s var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .contact-pill:hover {
    background: #232323;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}

.contact-pill:active { scale: 0.97; }

.contact-icon { opacity: 0.9; }

/* ---------- hero ---------- */

.hero {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: var(--gap);
  perspective: 1600px;
}

.hero-card {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  /* belt-and-suspenders: blend-mode children can leak past border-radius
     with plain overflow:hidden in Safari/iOS — clip-path is reliable. */
  clip-path: inset(0 round var(--radius));
  border: 1px solid var(--border);
  transform-style: preserve-3d;
  transition: box-shadow 0.4s var(--ease);
  will-change: transform;
}

.card-noise {
  position: absolute;
  z-index: 4;
  inset: 0;
  pointer-events: none;
  background-image: url("assets/card-sheen.webp");
  background-size: cover;
  background-position: center;
  transform: scaleX(-1);
  mix-blend-mode: soft-light;
  opacity: 0.1;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.hero-card .sheen {
  position: absolute;
  z-index: 5;
  inset: -1px;
  /* .hero-portrait no longer clips (overflow:visible, required for the 3D
     flip's preserve-3d chain to work) — so this needs its own rounding +
     clip, or the cursor glow squares off past the card's rounded corners. */
  border-radius: var(--radius);
  overflow: hidden;
  pointer-events: none;
  background: radial-gradient(
    480px circle at var(--mx, 50%) var(--my, 30%),
    rgba(255, 255, 255, 0.07),
    transparent 60%
  );
  opacity: 0;
  transition: opacity 0.4s ease;
}

.hero-card:hover .sheen { opacity: 1; }
.hero-card:hover { box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.6); }

.hero-intro {
  flex: 1 1 66%;
  background: var(--card);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: clamp(20px, 3.2vh, 40px) clamp(22px, 3vw, 49px);
}

/* new dark vignette background (replaces the old color-blob watermark) */
.card-vignette {
  position: absolute;
  top: -0.2%;
  left: 57%;
  transform: translateX(-50%) scaleY(-1);
  width: 116.6%;
  height: 156.1%;
  pointer-events: none;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.card-vignette::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
}

.card-vignette::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("assets/card-bg-vignette.png");
  background-size: cover;
  background-position: bottom;
  opacity: 0.2;
}

.hero-portrait .card-vignette {
  left: 64.8%;
  width: 129.5%;
}

.intro-glow {
  position: absolute;
  left: -17.7%;
  top: 88%;
  transform: translateY(-50%);
  width: 140.8%;
  mix-blend-mode: overlay;
  opacity: 0.18;
  pointer-events: none;
}

.card-row {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.profile {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.4vw, 20px);
}

.profile-avatar {
  width: clamp(34px, 3vw, 48px);
  height: clamp(34px, 3vw, 48px);
  border-radius: 50%;
  object-fit: cover;
  display: block;
}

.profile-name {
  font-size: clamp(14px, 1.15vw, 20px);
  line-height: 1.15;
}

.profile-role {
  font-size: clamp(11px, 0.85vw, 14px);
  color: rgba(255, 255, 255, 0.5); /* --text-faint (0.32) was 2.9:1 on the card */
  margin-top: 2px;
}

.dmnd-mark {
  display: block;
  width: clamp(48px, 5.8vw, 100px);
  filter: drop-shadow(0 0 0 rgba(255, 255, 255, 0));
  transition: transform 0.35s var(--ease), opacity 0.35s ease;
  opacity: 0.92;
}

@media (hover: hover) and (pointer: fine) {
  .dmnd-mark:hover { transform: scale(1.08) rotate(-2deg); opacity: 1; }
}
.dmnd-mark:active { transform: scale(0.96); }
.dmnd-mark img { width: 100%; height: auto; display: block; } /* height:auto: the img carries width/height attrs for CLS, the CSS width is fluid */

.card-copy {
  position: relative;
  max-width: 52em;
}

.card-copy h1 {
  font-weight: 500;
  font-size: clamp(22px, 3.6vw, 52px);
  line-height: 1.16;
  letter-spacing: -0.01em;
}

.card-copy p {
  margin-top: clamp(10px, 1.6vh, 22px);
  font-weight: 300;
  font-size: clamp(13px, 1.35vw, 24px);
  line-height: 1.4;
  color: var(--text-mid);
  max-width: 50em;
  text-wrap: pretty;
}

/* ---------- portrait card ---------- */

.hero-portrait {
  flex: 1 1 34%;
  background: var(--card-alt);
  cursor: pointer;
  perspective: 1500px;
  /* overflow:hidden on .hero-card forces transform-style:flat on this box,
     which breaks preserve-3d on .flip-inner below it (both faces would
     render flattened on top of each other, backface-visibility notwithstanding).
     Clipping instead happens per-face on .flip-face. */
  overflow: visible;
  clip-path: none;
}

/* ---------- card flip (front portrait / back real stats) ---------- */

.flip-inner {
  position: absolute;
  inset: 0;
  background: var(--card-alt);
  border-radius: var(--radius);
  transform-style: preserve-3d;
  transition: transform 0.6s cubic-bezier(0.5, 0, 0.5, 1);
}

.hero-portrait.is-flipped .flip-inner {
  transform: rotateY(180deg);
}

.flip-face {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--radius);
  /* clip-path (not just overflow+radius) would be the usual Safari fix for
     blend-mode children leaking past rounded corners, but recalculating a
     clip-path every frame during the 3D tilt/flip transform produces visibly
     jagged corners — overflow+border-radius alone renders smoother in motion. */
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  will-change: transform; /* own GPU layer: smoother radius during the parent's 3D transform */
}

.flip-face--back {
  transform: rotateY(180deg);
  background: #131313;
  display: flex;
  align-items: center;
}

.flip-btn {
  position: absolute;
  z-index: 6;
  top: 3.4%;
  right: 2.9%;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(20, 20, 20, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transition: transform 0.3s var(--ease), background 0.3s ease;
}

.flip-btn img { width: 17px; height: 17px; }
.flip-btn--back img { transform: rotate(180deg); }
@media (hover: hover) and (pointer: fine) {
  .hero-portrait:hover .flip-btn { background: rgba(0, 0, 0, 0.4); transform: scale(1.1); }
}

.back-floor-grid {
  position: absolute;
  left: 0;
  right: 0;
  height: 55%;
  pointer-events: none;
  background-image: url("assets/back-floor-grid.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  opacity: 0.4;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.back-floor-grid--bottom {
  bottom: -27%;
  background-position: bottom;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 30%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 30%);
}

.back-floor-grid--top {
  top: -27%;
  transform: scaleY(-1);
  background-position: bottom;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 30%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 30%);
}

.back-star-field {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("assets/back-star-field.png");
  background-size: cover;
  background-position: center;
  -webkit-mask-image: radial-gradient(ellipse at center, #000 17.5%, transparent 100%);
  mask-image: radial-gradient(ellipse at center, #000 17.5%, transparent 100%);
  opacity: 0.7;
  animation: starDrift 14s ease-in-out infinite alternate;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

@keyframes starDrift {
  from { transform: scale(1); opacity: 0.55; }
  to   { transform: scale(1.08); opacity: 0.8; }
}

.portrait-img {
  position: absolute;
  z-index: 2;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 15%;
  transform: scale(1.02);
  -webkit-user-drag: none;
  user-select: none;
  -webkit-touch-callout: none;
  pointer-events: auto;
}

.portrait-glow {
  position: absolute;
  z-index: 3;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: overlay;
  background:
    radial-gradient(60% 45% at 50% 0%, rgba(255, 255, 255, 0.32), transparent 70%),
    radial-gradient(70% 35% at 50% 100%, rgba(255, 255, 255, 0.18), transparent 75%);
  opacity: 0.5;
  animation: glowBreathe 9s ease-in-out infinite alternate;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

@keyframes glowBreathe {
  from { opacity: 0.38; transform: translateY(0) scale(1); }
  to   { opacity: 0.55; transform: translateY(-1.5%) scale(1.03); }
}

.portrait-grid {
  position: absolute;
  z-index: 1; /* behind the photo (z:2) and stats (z:4) — decorative only,
                 must never draw over the character or the stat pills */
  inset: 0;
  pointer-events: none;
  opacity: 0.1;
  background-image: repeating-linear-gradient(
    44.5deg,
    rgba(255, 255, 255, 0.7) 0,
    rgba(255, 255, 255, 0.7) 1px,
    transparent 1px,
    transparent 65px
  );
}

.stat-layer {
  position: absolute;
  z-index: 4; /* must beat .portrait-img (2) — this element ITSELF sets the
                 stacking level for the whole subtree, individual .stat
                 z-index values only matter *inside* this context */
  /* equal margin on all three sides (px-based, not %, since % on
     left/right resolves against width while top/bottom resolves against
     height — on a non-square card those would never visually match) */
  left: clamp(12px, 2.4vw, 24px);
  right: clamp(12px, 2.4vw, 24px);
  bottom: clamp(12px, 2.4vw, 24px);
  pointer-events: none;
}

/* single-slot viewport: both the outgoing and incoming .stat occupy the same
   grid cell, so the container's height auto-sizes to the (identical) content
   height without ever needing a hardcoded number. No overflow:hidden here on
   purpose — the entrance/exit transform includes rotate()+scale(), which
   pushes the pill's corners past its own flat (untransformed) box, and a
   viewport clip sized to that flat box would crop the animation. The card
   itself (.flip-face) already clips to the rounded card edge, so the pill
   sliding partway below it mid-animation just reads as emerging from under
   the card — no separate clip needed here. */
.stat-viewport {
  display: grid;
  grid-template-columns: 100%;
}

.stat {
  grid-area: 1 / 1;
  z-index: 3;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: clamp(8px, 1.5vh, 13px) clamp(12px, 1.6vw, 18px) clamp(8px, 1.5vh, 13px) clamp(7px, 0.9vw, 10px);
  border-radius: 18px;
  color: #fff;
  /* glass pill, matching the Figma frame's actual rendered look (its raw
     dev-mode export reports a transparent fill, but the file itself shows
     a frosted glass capsule) */
  background: rgba(255, 255, 255, 0.06);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2);
  /* idle/parked state: sunk below the slot, shrunk, tilted and soft-blurred —
     toggling .is-visible on/off (JS swaps it between the outgoing and
     incoming pill at the same time) drives both halves of the swap from
     these two rules alone. Exit and enter deliberately use different easing
     (defined on each rule, so the transition picks up whichever direction is
     active): the outgoing pill sinks down and melts away on a soft ease-out,
     the incoming one (already parked here) springs up into place with a
     little overshoot, i.e. "goes down" vs. "flies out from below". */
  opacity: 0;
  transform: translateY(30px) scale(0.92) rotate(-2.5deg);
  filter: blur(3px);
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.7, 1), opacity 0.4s ease, filter 0.4s ease;
  will-change: transform, opacity, filter;
}

.stat.is-visible {
  opacity: 1;
  transform: translateY(0) scale(1) rotate(0deg);
  filter: blur(0px);
  transition: transform 0.7s cubic-bezier(0.34, 1.3, 0.64, 1), opacity 0.5s ease, filter 0.3s ease;
  z-index: 4;
}

.stat-left {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1vw, 12px);
  min-width: 0;
}

.stat-icon-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: clamp(27px, 2.9vw, 34px);
  height: clamp(27px, 2.9vw, 34px);
  border-radius: 50%;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.18), rgba(70, 70, 70, 0.1));
  flex-shrink: 0;
}

.stat-icon {
  width: 54%;
  height: 54%;
}

.stat-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-size: clamp(15px, 1.65vw, 20px);
  white-space: nowrap;
}

.stat-label {
  font-family: "Inter", "Google Sans Flex", -apple-system, sans-serif;
  font-weight: 400;
  font-size: clamp(11px, 1.05vw, 14px);
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- real stats (card back) ---------- */

.real-stats {
  position: relative;
  z-index: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 1vh, 10px);
  padding: clamp(16px, 3vh, 24px);
}

.real-stat,
.real-stat-row .real-stat {
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: clamp(10px, 1.6vh, 20px) clamp(14px, 1.8vw, 24px);
}

.real-stat-row {
  display: flex;
  gap: clamp(6px, 1vw, 10px);
}

.real-stat-row .real-stat { flex: 1 1 0; min-width: 0; }

.real-stat-label {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1vw, 14px);
  font-weight: 500;
  font-size: clamp(12px, 1.3vw, 20px);
  color: rgba(255, 255, 255, 0.56); /* was 0.4: 3.8:1, under AA */
  white-space: nowrap;
  min-width: 0;
}

.real-stat-label img { width: clamp(16px, 1.4vw, 22px); height: auto; }

.real-stat-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums; /* count-up no longer jitters width */
  letter-spacing: -0.01em;
  font-size: clamp(20px, 2.4vw, 38px);
  color: #fff;
}

.real-stat--big .real-stat-value { font-size: clamp(24px, 3vw, 44px); }

/* ---------- marquee ---------- */

.marquee-stack {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 2.6vh, 34px);
}

.marquee-row {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.marquee-track {
  display: flex;
  width: max-content;
  gap: 10px;
  will-change: transform;
}

.marquee-track li {
  padding: clamp(8px, 1.1vh, 14px) clamp(16px, 1.7vw, 34px);
  border-radius: 100px;
  border: 1px solid transparent;
  font-size: clamp(11px, 1.15vw, 24px);
  color: var(--text-dim);
  white-space: nowrap;
  cursor: default;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.marquee-track li:nth-child(odd) { border-color: rgba(255, 255, 255, 0.28); }

@media (hover: hover) and (pointer: fine) {
  .marquee-track li:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.7);
  }
}

/* ---------- bottom nav ---------- */

.pill-nav {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  gap: clamp(8px, 1vw, 14px);
}

a.nav-pill {
  text-decoration: none;
  cursor: pointer;
}

.nav-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(21, 21, 21, 0.55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: clamp(8px, 1.2vh, 12px) clamp(14px, 1.4vw, 20px) clamp(8px, 1.2vh, 12px) clamp(10px, 1.1vw, 16px);
  font-size: clamp(11px, 0.85vw, 15px);
  font-weight: 600;
  color: var(--text-dim);
  position: relative;
  /* transform is owned by the magnetic effect in script.js (see .contact-pill) */
  transition: background 0.3s var(--ease), box-shadow 0.3s var(--ease), color 0.25s ease, scale 0.16s var(--ease);
}

/* invisible hit-area extension: the pill stays visually compact but the
   tappable box reaches ~44px on phones */
.nav-pill::after,
.contact-pill::after {
  content: "";
  position: absolute;
  inset: -4px;
}
.contact-pill { position: relative; }

.nav-pill img {
  opacity: 0.45;
  transition: opacity 0.25s ease;
}

@media (hover: hover) and (pointer: fine) {
  .nav-pill:hover {
    background: #1f1f1f;
    color: #fff;
    box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.5);
  }

  .nav-pill:hover img { opacity: 1; }
}

.nav-pill:active { scale: 0.96; }

/* ================= motion + interaction layer ================= */

:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}

/* headline: each line rises out of a soft blur, one after the other.
   Runs once per visit, so it can afford a longer beat. */
.card-copy h1 .line { display: block; }
.card-copy h1 .line-in {
  display: inline-block;
  animation: lineIn 1s calc(0.3s + var(--i, 0) * 0.09s) var(--ease-out) both;
}

@keyframes lineIn {
  from { opacity: 0; transform: translateY(0.45em); filter: blur(10px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

.card-copy p { animation: fadeUp 0.9s 0.5s var(--ease-out) both; }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* nav pills stagger in. Uses the standalone `translate` property, not
   `transform`: transform belongs to the magnetic effect in script.js and a
   fill-mode:both keyframe on it would pin it forever (same trap as
   [data-tilt][data-reveal]). */
.pill-nav .nav-pill {
  animation: pillIn 0.6s calc(0.42s + var(--i, 0) * 0.05s) var(--ease-out) both;
}

@keyframes pillIn {
  from { opacity: 0; translate: 0 10px; }
  to   { opacity: 1; translate: 0 0; }
}

/* icons answer the pointer with a small nudge */
.nav-pill img,
.contact-icon {
  transition: opacity 0.25s ease, transform 0.35s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .nav-pill:hover img { transform: translateY(-1px) rotate(-8deg) scale(1.1); }
  .contact-pill:hover .contact-icon { transform: rotate(-14deg) scale(1.06); }
}

/* desktop nav: the only route to Blog/Drawing/Gallery/Plugins, so it gets
   real size and contrast instead of reading as a footnote */
@media (min-width: 861px) and (min-height: 561px) {
  .pill-nav { gap: clamp(8px, 0.8vw, 12px); }
  .nav-pill {
    font-size: clamp(13px, 0.95vw, 16px);
    padding: clamp(10px, 1.3vh, 13px) clamp(16px, 1.5vw, 22px) clamp(10px, 1.3vh, 13px) clamp(12px, 1.2vw, 16px);
    border-radius: 12px;
    color: rgba(255, 255, 255, 0.62);
  }
  .nav-pill img { opacity: 0.6; }
}

/* ---- flip card ---- */

.flip-inner { transition: transform 0.7s var(--ease-in-out); }

.flip-btn img { transition: transform 0.4s var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .hero-portrait:hover .flip-btn img { transform: rotate(90deg); }
  .hero-portrait:hover .flip-btn--back img { transform: rotate(270deg); }
}

/* one-time hint that the card turns over: a short peek on desktop and a
   ring off the flip button everywhere (mobile ignores the peek because
   .flip-inner's transform is !important-ed to none there) */
.hero-portrait.is-hinting:not(.is-flipped) .flip-inner {
  animation: peek 1.1s var(--ease-in-out);
}

@keyframes peek {
  0%, 100% { transform: rotateY(0); }
  42%      { transform: rotateY(-14deg); }
}

.flip-btn::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.55);
  opacity: 0;
  pointer-events: none;
}

.hero-portrait.is-hinting .flip-face--front .flip-btn::after {
  animation: ring 1.1s var(--ease-out) 2;
}

@keyframes ring {
  from { opacity: 0.8; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.9); }
}

/* back face: rows arrive in sequence as the card lands, and vanish
   instantly when it turns away (exit faster than enter) */
.real-stats > * {
  opacity: 0;
  translate: 0 10px;
  transition: opacity 0.15s ease, translate 0s 0.15s;
}

.hero-portrait.is-flipped .real-stats > * {
  opacity: 1;
  translate: 0 0;
  transition:
    opacity 0.45s calc(0.22s + var(--i, 0) * 0.06s) ease,
    translate 0.6s calc(0.22s + var(--i, 0) * 0.06s) var(--ease-out);
}

/* stat pill: hairline that fills while the pill is on screen, so the swap
   reads as scheduled rather than random. Added by script.js only while
   the rotator actually runs. */
.stat::after {
  content: "";
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 0;
  height: 1px;
  border-radius: 1px;
  background: rgba(255, 255, 255, 0.4);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0;
}

.stat.is-timing::after {
  opacity: 1;
  animation: statTimer var(--stat-ms, 4200ms) linear forwards;
}

@keyframes statTimer {
  to { transform: scaleX(1); }
}

/* ---- marquee: grab and fling ---- */

.marquee-row {
  touch-action: pan-y; /* vertical page scroll stays native on phones */
  -webkit-user-select: none;
  user-select: none;
}

@media (hover: hover) and (pointer: fine) {
  .marquee-row { cursor: grab; }
  .marquee-row.is-dragging { cursor: grabbing; }
  .marquee-row.is-dragging li { pointer-events: none; }
}

/* ---- page-to-page crossfade (Chrome, Safari 18.2+; others just navigate) ---- */

@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.32s;
  animation-timing-function: var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  .card-copy h1 .line-in,
  .card-copy p,
  .pill-nav .nav-pill { animation: none; }
  .real-stats > *,
  .hero-portrait.is-flipped .real-stats > * { translate: 0 0; }
  .hero-portrait.is-hinting .flip-inner,
  .hero-portrait.is-hinting .flip-btn::after { animation: none; }
}

/* ---------- mobile ---------- */

@media (max-width: 860px), (max-height: 560px) {
  /* base rule pins html/body to a fixed 100% (~one viewport). Mobile Chrome's
     100% here can resolve taller than the actual dvh the content uses (its
     toolbar-show/hide viewport math differs from Safari's), so body was
     staying pinned to that larger fixed height — a full extra screen of
     empty space below the real content. Let it size to content instead. */
  html, body { height: auto; }
  body { overflow-y: auto; }

  .page {
    height: auto;
    min-height: 100dvh;
    overflow: visible;
    gap: 20px;
  }

  .hero {
    flex-direction: column;
    gap: 16px;
  }

  /* Mobile WebKit (iOS Safari / iOS Chrome, which is Safari underneath)
     renders rotateY + border-radius + backface-visibility together very
     badly — instead of a smooth foreshortened rotation you get a hard,
     unrounded, sliced-looking mid-transition frame. Skip the 3D rotation
     entirely here and crossfade the two faces instead — same interaction,
     guaranteed-clean rendering everywhere. */
  .flip-inner {
    transition: none;
    transform: none !important;
  }

  .flip-face {
    backface-visibility: visible;
    transition: opacity 0.45s ease;
    /* mobile no longer rotates (crossfade above), so it's now safe to add
       clip-path back: overflow:hidden alone doesn't reliably clip a
       *transformed* child's corners in mobile Safari (.portrait-img has
       scale(1.02)) — its square corners were poking out past the rounding. */
    clip-path: inset(0 round var(--radius));
    /* base rule sets will-change:transform for the desktop 3D flip, which
       no longer applies here (mobile animates opacity, not transform).
       Leaving it set anyway promotes this element to its own GPU layer,
       and that layer + clip-path together is a mobile Safari bug: the
       repeating-gradient .portrait-grid background gets rasterized past
       the clipped edge, leaking a triangle of diagonal texture at the
       rounded corners. Match will-change to what's actually animated. */
    will-change: opacity;
  }

  .flip-face--front { opacity: 1; transform: none; }
  .flip-face--back { opacity: 0; transform: none; pointer-events: none; }

  .hero-portrait.is-flipped .flip-face--front { opacity: 0; pointer-events: none; }
  .hero-portrait.is-flipped .flip-face--back { opacity: 1; pointer-events: auto; }

  .hero-intro,
  .hero-portrait {
    flex: none;
    width: 100%;
  }

  /* ---- header: keep the full email on one line ---- */

  .logo { font-size: 18px; }

  .contact-pill {
    gap: 6px;
    padding: 8px 12px;
    font-size: 11px;
  }

  .contact-icon { width: 15px; height: 15px; }

  /* ---- intro card: even, deliberate spacing ---- */

  .hero-intro {
    padding: 18px 20px 22px;
    gap: 22px;
    /* permanent version of the lighter/grayer look the card briefly showed
       on tap in Chrome before -webkit-tap-highlight-color was disabled —
       a flat white wash over the dark card background */
    background: linear-gradient(rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.07)), var(--card);
  }

  /* the desktop card-vignette's top/left/width/height % were tuned to that
     card's wide aspect ratio — on the full-width, much shorter mobile card
     the same numbers push most of the blur out of the clipped card bounds,
     so it reads as "no background". Drop the crop math and just cover the
     card; ::after already has background-size:cover to frame it cleanly. */
  .hero-intro .card-vignette {
    top: 0;
    left: 50%;
    width: 100%;
    height: 100%;
    transform: translateX(-50%);
  }

  .card-copy { display: flex; flex-direction: column; gap: 12px; }
  .card-copy h1 { font-size: clamp(22px, 6.5vw, 34px); }
  .card-copy p { font-size: 15px; margin-top: 0; }

  /* ---- portrait card: single stat pill (layout is already fluid/shared
     with desktop via .stat-layer/.stat-viewport/.stat) ---- */

  .hero-portrait { height: 400px; }

  .real-stats { padding: 16px; gap: 8px; }
  .real-stat-value { font-size: 22px; }
  .real-stat--big .real-stat-value { font-size: 26px; }

  .stat {
    /* no backdrop-filter here: it's inside the 3D flip and Mobile Safari
       renders backdrop-filter + 3D transform very poorly (flicker/garbling
       during rotation) — solid-ish background instead */
    background: rgba(18, 18, 18, 0.82);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.35);
  }

  /* ---- marquee: a touch tighter ---- */

  .marquee-track li { font-size: 13px; padding: 8px 16px; }

  /* ---- bottom nav: one row, always ---- */

  .pill-nav {
    flex-wrap: nowrap;
    gap: 6px;
  }

  .nav-pill {
    gap: 6px;
    padding: 8px 10px 8px 8px;
    font-size: 11px;
  }

  .nav-pill img { width: 16px; height: 16px; }
}

@media (max-width: 380px) {
  .nav-pill span { display: none; }
  .nav-pill { padding: 10px; }
}
