/* ==========================================================================
   Aniket — Portfolio
   Implemented from Figma "Porfolio" › Main (node 92:1057): a fixed navbar, the
   hero (132:2138) and "section 1" (92:1157) — the 720px statement, then selected work.
   Each section is an art-directed 1440px-wide
   frame laid out on a "stage" that scales down uniformly below 1440px, while the
   background grid bleeds to the full viewport width above it.
   ========================================================================== */

/* ---------- Fonts ---------- */

/* Rightman Signature is not on Google Fonts — self-hosted from the designer's local install. */
@font-face {
  font-family: "Rightman Signature";
  src: url("../assets/fonts/RightmanSignature.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Google Fonts' Lato has no Medium (500) / SemiBold (600); these come from Lato 2.0, as used in Figma. */
@font-face {
  font-family: "Lato";
  src: url("https://cdn.jsdelivr.net/npm/lato-font@3.0.0/fonts/lato-medium/lato-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Lato";
  src: url("https://cdn.jsdelivr.net/npm/lato-font@3.0.0/fonts/lato-semibold/lato-semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* The same Lato with the line metrics Figma lays it out with (hhea: ascent 1974, descent 426, no line gap,
   per 2000 units). Browsers follow Lato's typo metrics instead, which set each line ~1px higher than
   Figma does. Used by the work cards. */
@font-face {
  font-family: "Lato Figma";
  src: url("https://cdn.jsdelivr.net/npm/lato-font@3.0.0/fonts/lato-normal/lato-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  ascent-override: 98.7%;
  descent-override: 21.3%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Lato Figma";
  src: url("https://cdn.jsdelivr.net/npm/lato-font@3.0.0/fonts/lato-medium/lato-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  ascent-override: 98.7%;
  descent-override: 21.3%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Lato Figma";
  src: url("https://cdn.jsdelivr.net/npm/lato-font@3.0.0/fonts/lato-semibold/lato-semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  ascent-override: 98.7%;
  descent-override: 21.3%;
  line-gap-override: 0%;
}

/* ---------- Tokens (Figma variables) ---------- */

:root {
  --grays-white: #fff;
  --grays-black: #000;
  --grays-gray: #8e8e93;
  --grays-gray-6: #f2f2f7;
  --accents-red: #ff383c;
  --accents-pink: #ff2d55;
  --accents-vibrant-indigo: #5c50e6;
  --label-primary: #1a1a1a;
  --accents-orange: #ff5900;

  --ink: #121212;
  --ink-faint: rgba(18, 18, 18, 0.1);
  --nav-link: #9b9b9b;
  --grid-line: #fef6f6;
  --eye-reflection: #1a1b1a;

  --font-display: "Bricolage Grotesque", system-ui, sans-serif;
  --font-ui: "Lato", system-ui, sans-serif;
  --font-ui-figma: "Lato Figma", "Lato", system-ui, sans-serif;
  --font-hand: "Covered By Your Grace", cursive;
  --font-signature: "Rightman Signature", cursive;
  --font-inter: "Inter", system-ui, sans-serif;

  --stage-w: 1440px;
  --hero-h: 782px;
  --section-h: 720px;
  --grid-size: 70px;
}

/* ---------- Base ---------- */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--grays-white);
  color: var(--grays-black);
  font-family: var(--font-ui);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img {
  display: block;
}

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

h1,
h2,
p,
figure {
  margin: 0;
}

:focus-visible {
  outline: 2px solid var(--accents-vibrant-indigo);
  outline-offset: 3px;
  border-radius: 4px;
}

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

/* Holds the SVG filters and the shared cell pattern. Kept renderable — display:none breaks url() filters. */
.fx-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* Cap-height trimmed text boxes, matching Figma's "vertical trim". */
.cap-trim {
  line-height: normal;
  white-space: nowrap;
  text-box: trim-both cap alphabetic;
}

/* ==========================================================================
   Page layout: scaled stages
   ========================================================================== */

.page {
  container-type: inline-size;
  /* One uniform scale for every 1440px stage: 1 at ≥1440px wide, proportional below — and on short
     viewports, small enough that the hero art down to the tagline (~620px of the frame) stays in view. */
  --scale: min(1, tan(atan2(100cqw, 1440px)), tan(atan2(100svh, 620px)));
}

.stage {
  /* How far backgrounds extend past the stage when the viewport is wider than it. */
  --bleed: max(0px, (100cqw / var(--scale) - var(--stage-w)) / 2);

  position: absolute;
  top: 0;
  left: 50%;
  width: var(--stage-w);
  height: var(--section-h);
  transform: translateX(-50%) scale(var(--scale));
  transform-origin: top center;
}

.section {
  position: relative;
  height: calc(var(--section-h) * var(--scale));
  overflow-x: clip;
}

/* A section's visible content box. */
.panel {
  position: relative;
  height: calc(var(--section-h) * var(--scale));
}

/* A Figma frame's contents inside a stage, moved down by --shift when the stage is taller than the frame. */
.frame {
  position: absolute;
  top: var(--shift, 0px);
  left: 0;
  width: var(--stage-w);
  height: var(--section-h);
}

/* ---------- Background (shared): cells, grid lines, monotone noise ---------- */

.bg {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(-1 * var(--bleed));
  right: calc(-1 * var(--bleed));
  overflow: hidden;
  background: var(--grays-white);
}

/* --bg-shift moves the cell pattern and grid with the frame when a panel is taller than its frame. */
.bg__cells {
  position: absolute;
  top: calc(var(--bg-shift, 0px) - 50px);
  left: calc(var(--bleed) - 66px);
}

.bg__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: var(--grid-size) 100%, 100% var(--grid-size);
  background-position: calc(var(--bleed) + 4px) 0, 0 calc(var(--bg-shift, 0px) + 20px);
}

/* Background frame noise (mono, black 8%). */
.bg__grain {
  position: absolute;
  inset: 0;
  background: url("../assets/textures/grain-bg.png");
}

/* ---------- Grain-filled vector shapes ----------
   The Figma vectors are used as masks over a fill + grain texture sampled from
   Figma's render. (Their embedded SVG noise renders as flat grey in browsers.) */

.grain-shape {
  position: absolute;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

/* ---------- Progressive blur ----------
   Figma's progressive layer blur is rebuilt as stacked copies with increasing
   blur, each masked to a horizontal band. `plus-lighter` makes neighbouring
   bands add up to full opacity, so the crossfades don't dip in density. */

.blur-band {
  position: absolute;
  mix-blend-mode: plus-lighter;
  filter: blur(var(--blur, 0px));
  -webkit-mask-image: var(--band);
  mask-image: var(--band);
}

/* ==========================================================================
   Navbar — fixed to the top of the viewport
   ========================================================================== */

/* A zero-height sticky dock, so the bar stays put while the page scrolls without taking up space. */
.nav-dock {
  position: sticky;
  top: 0;
  z-index: 100;
  height: 0;
}

.nav-dock__stage {
  height: 56px;
  pointer-events: none;
}

.navbar {
  position: absolute;
  top: 0;
  left: 530px;
  width: 420px;
  height: 56px;
  pointer-events: auto;
}

.navbar__shape {
  position: absolute;
  inset: 0;
}

.navbar__content {
  position: absolute;
  top: 0;
  left: 12px;
  display: flex;
  width: 396px;
  height: 56px;
  padding: 0 13px;
  justify-content: space-between;
  align-items: center;
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  border-radius: 8px;
}

/* Logo "dot_me": the bot's face — two black eyes on a white tile. */
.brand__logo {
  position: relative;
  display: block;
  width: 32.182px;
  height: 30px;
  border-radius: 8px;
  background: var(--grays-white);
  overflow: hidden;
}

.brand__eyes {
  position: absolute;
  top: 10px;
  left: 8px;
  display: flex;
  gap: 6px;
}

.brand__eyes > span {
  width: 5.091px;
  height: 10.182px;
  border-radius: 7.636px;
  background: var(--grays-black);
}

/* Logo variant "photo": parked below the tile until js/navbar.js adds .is-photo. */
.brand__photo {
  position: absolute;
  top: -13px;
  left: -7px;
  width: 50.89px;
  height: 64px;
  max-width: none;
  object-fit: cover;
  object-position: bottom;
  translate: 0 43px;
}

.brand__logo.is-photo .brand__photo {
  translate: 0 0;
}

.brand__logo.is-photo .brand__eyes > span {
  scale: 1 0.1;
}

/* To the photo: the eyes blink shut and the photo springs up over them.
   Back to the bot: the photo drops away, then the eyes open. (.is-live skips this on first paint.) */
.brand__logo.is-live .brand__photo {
  transition: translate 0.32s cubic-bezier(0.55, 0, 1, 0.45);
}

.brand__logo.is-live.is-photo .brand__photo {
  transition: translate 0.6s var(--spring-gentle) 0.08s;
}

.brand__logo.is-live .brand__eyes > span {
  transition: scale 0.2s var(--ease-out-expo) 0.24s;
}

.brand__logo.is-live.is-photo .brand__eyes > span {
  transition: scale 0.14s ease-in;
}

.navbar__links {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  justify-content: center;
  align-items: center;
  gap: 32px;
}

.navbar__links a {
  display: block;
  overflow: hidden;
  color: var(--nav-link);
  font-size: 14px;
  font-weight: 500;
  line-height: normal;
  transition: color 0.2s ease;
}

.navbar__links a > span {
  display: block;
}

.navbar__links a:hover {
  color: var(--grays-white);
}

.cta {
  display: flex;
  height: 33px;
  padding: 0 14px;
  justify-content: center;
  align-items: center;
  gap: 6px;
  border-radius: 9px;
  background: var(--grays-white);
  color: var(--grays-black);
  font-size: 13px;
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
  overflow: hidden;
}

.cta img {
  transition: transform 0.2s ease;
}

.cta:hover img {
  transform: translate(1.5px, -1.5px);
}

/* ==========================================================================
   Hero (Figma frame 1440 × 782)
   ========================================================================== */

/* Fills the viewport; sits above section 1 so the platform's soft bottom edge can spill over it. */
.hero {
  position: relative;
  z-index: 1;
  height: 100vh;
  height: 100svh;
  overflow-x: clip;
}

.hero__stage {
  /* Stage height (in unscaled px) that fills the viewport once scaled. */
  --fill-h: calc(100svh / var(--scale));
  /* Extra height beyond the 782px frame is split above and below the art, keeping it centred. */
  --shift: max(0px, (var(--fill-h) - var(--hero-h)) / 2);

  height: var(--fill-h);
}

/* The figure (wordmark, g, bot, headline, photo) as laid out in the Figma frame. */
.hero__art {
  position: absolute;
  top: var(--shift);
  left: 0;
  width: var(--stage-w);
  height: var(--hero-h);
}

/* "Scroll ↓ to explore" (Figma 216:1937): pinned to the bottom of the screen at any screen height, 32px
   up from the edge (in design px). It goes away once 10% of the screen has been scrolled and comes back
   at the top of the hero (js/scroll-hint.js toggles .is-hidden).
   It lives outside the scaled stage (fixed positioning doesn't work inside a transform), so its
   contents are scaled to match the stage instead. */
.scroll-hint {
  position: fixed;
  z-index: 40;
  right: 0;
  bottom: calc(32px * var(--scale));
  left: 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

.scroll-hint__inner {
  display: flex;
  height: 22px;
  align-items: center;
  gap: 8px;
  transform: scale(var(--scale));
  transform-origin: 50% 100%;
  transition: opacity 0.3s ease, translate 0.3s var(--ease-out-expo);
  color: var(--grays-white);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.14px;
  white-space: pre; /* keeps the space Figma has after "Scroll" */
}

.scroll-hint.is-hidden .scroll-hint__inner {
  opacity: 0;
  translate: 0 8px;
}

/* The arrow points down: Figma flips the hand-drawn arrow vertically. */
.scroll-hint img {
  width: 16.5px;
  height: 22px;
  scale: 1 -1;
}

/* ---------- Platform (black stage block) ---------- */

/* Starts under the wordmark and ends 92px below the bottom of the hero (Figma: 476 → 874 in a 782px frame). */
.platform {
  position: absolute;
  left: 60px;
  top: calc(476px + var(--shift));
  bottom: -92px;
  width: 1320px;
  filter: url(#fx-grain-platform);
}

/* Figma: progressive blur, radius 0 → 72, from the top edge down 102.5px (25.75% of its 398px).
   Std-devs are calibrated against Figma's render (it peaks at ~31px, not radius / 2), and the
   blurrier bands are widened slightly because Figma's soft edge also drifts outward. */
.platform .blur-band {
  inset: -110px;
}

.platform .blur-band::before {
  content: "";
  position: absolute;
  inset: 110px calc(110px - var(--spread, 0px)) calc(110px - var(--spread, 0px));
  background: var(--grays-black);
}

/* Only the last band shows below the ramp, so the others stay short (blurring them full-height is wasted work). */
.platform .blur-band:not(:last-child) {
  bottom: auto;
  height: 520px;
}

.platform .blur-band:not(:last-child)::before {
  bottom: auto;
  height: 300px;
}

.platform .blur-band:nth-child(1) { --blur: 0px;                     --band: linear-gradient(#000 110px, transparent 127.08px); }
.platform .blur-band:nth-child(2) { --blur: 5.17px;  --spread: 0.5px; --band: linear-gradient(transparent 110px, #000 127.08px, transparent 144.17px); }
.platform .blur-band:nth-child(3) { --blur: 10.33px; --spread: 1px;   --band: linear-gradient(transparent 127.08px, #000 144.17px, transparent 161.25px); }
.platform .blur-band:nth-child(4) { --blur: 15.5px;  --spread: 1.5px; --band: linear-gradient(transparent 144.17px, #000 161.25px, transparent 178.33px); }
.platform .blur-band:nth-child(5) { --blur: 20.67px; --spread: 2.1px; --band: linear-gradient(transparent 161.25px, #000 178.33px, transparent 195.42px); }
.platform .blur-band:nth-child(6) { --blur: 25.83px; --spread: 2.6px; --band: linear-gradient(transparent 178.33px, #000 195.42px, transparent 212.5px); }
.platform .blur-band:nth-child(7) { --blur: 31px;    --spread: 3.1px; --band: linear-gradient(transparent 195.42px, #000 212.5px); }

/* ---------- "g" glyph: dark above the platform, blurred white below it ---------- */

.g-mark {
  position: absolute;
  left: 1115px;
  top: 352px;
  width: 169px;
  height: 226px;
}

.g-mark__top {
  top: 0;
  left: 0;
  width: 168px;
  height: 124px;
  background-color: var(--label-primary);
  background-image: url("../assets/textures/grain-g.png");
  -webkit-mask-image: url("../assets/svg/g-glyph-dark.svg");
  mask-image: url("../assets/svg/g-glyph-dark.svg");
}

/* Figma: progressive blur, radius 0 → 19.06, top to bottom of this frame (std-devs calibrated as on the platform). */
.g-mark__bottom {
  position: absolute;
  top: 123.89px;
  left: 0;
  width: 167.97px;
  height: 102.45px;
  isolation: isolate;
}

.g-mark__bottom .blur-band {
  inset: -30px;
}

.g-mark__bottom .blur-band img {
  position: absolute;
  top: 30px;
  left: 30px;
}

.g-mark__bottom .blur-band:nth-child(1) { --blur: 0px;    --band: linear-gradient(#000 30px, transparent 55.61px); }
.g-mark__bottom .blur-band:nth-child(2) { --blur: 2.05px; --band: linear-gradient(transparent 30px, #000 55.61px, transparent 81.23px); }
.g-mark__bottom .blur-band:nth-child(3) { --blur: 4.1px;  --band: linear-gradient(transparent 55.61px, #000 81.23px, transparent 106.84px); }
.g-mark__bottom .blur-band:nth-child(4) { --blur: 6.16px; --band: linear-gradient(transparent 81.23px, #000 106.84px, transparent 132.45px); }
.g-mark__bottom .blur-band:nth-child(5) { --blur: 8.21px; --band: linear-gradient(transparent 106.84px, #000 132.45px); }

/* ---------- "Aniket" wordmark ---------- */

.name-mark {
  position: absolute;
  left: 156px;
  top: 300px;
  width: 642px;
  height: 176px;
  overflow: hidden;
}

.name-mark__shape {
  top: 0;
  left: 0;
  width: 637px;
  height: 176px;
  background-color: var(--grays-black);
  background-image: url("../assets/textures/grain-name.png");
  -webkit-mask-image: url("../assets/svg/aniket-wordmark.svg");
  mask-image: url("../assets/svg/aniket-wordmark.svg");
}

/* ---------- Headline text ---------- */

.hero__heading {
  font: inherit;
}

.hero__hi {
  position: absolute;
  left: 185px;
  top: 230px;
  color: var(--accents-vibrant-indigo);
  font-family: var(--font-hand);
  font-size: 41.16px;
  font-weight: 400;
}

.hero__role {
  position: absolute;
  left: 530px;
  top: 508px;
  color: var(--grays-white);
  font-family: var(--font-signature);
  font-size: 59.49px;
  font-weight: 400;
}

/* ---------- Polaroid (behind the platform and the lettering) ---------- */

/* Star tucked behind the polaroid's top-right corner. Figma: an 83.4 × 87.2 box rotated −30.27°,
   with the shape inset inside it — placed here by the shape itself, pivoting about the box's centre. */
.hero__star {
  position: absolute;
  left: 1129.031px;
  top: 138.419px;
  width: 76.117px;
  height: 79.296px;
  transform: rotate(-30.27deg);
  transform-origin: 37.957px 39.246px;
}

/* Figma: the frame blends in "luminosity" mode and the photo is fully desaturated, so it reads black & white.
   Figma places it at (833.96, 119) rotated about its top-left corner; it pivots about its centre here
   (same resting position) so the intro can swing it into its tilt. */
.photo-frame {
  position: absolute;
  left: 793.895px;
  top: 145.166px;
  display: flex;
  align-items: center;
  gap: 1.209px;
  padding: 26.347px 24.183px 42.155px 26.347px;
  background: var(--grays-gray-6);
  mix-blend-mode: luminosity;
  transform: rotate(10.038deg);
  transform-origin: 50% 50%;
}

.photo-frame img {
  width: 287.471px;
  height: 361.53px;
  object-fit: cover;
  filter: grayscale(1);
}

/* ==========================================================================
   Bots (the "." after Aniket, and the statement's full stop) + eyes
   ========================================================================== */

.bot {
  position: absolute;
  width: 55.41px;
  height: 49.13px;
}

.bot__shape {
  top: 0;
  left: 0;
  width: 56px;
  height: 50px;
  background-color: var(--grays-black);
  background-image: url("../assets/textures/grain-bot.png");
  -webkit-mask-image: url("../assets/svg/bot-dark.svg");
  mask-image: url("../assets/svg/bot-dark.svg");
}

/* The hero dot has a soft white inner highlight along its top edge (Figma inner shadow). */
.bot__body {
  position: absolute;
  inset: 0;
  filter: url(#fx-dot-shine);
}

/* --look-x / --look-y are set by js/eyes.js so the eyes follow the pointer. */
.eyes {
  position: absolute;
  left: 16.61px;
  top: 16.5px;
  display: inline-flex;
  height: 16px;
  align-items: center;
  gap: var(--eye-gap, 6px);
  transform: translate(var(--look-x, 0px), var(--look-y, 0px));
}

.eye {
  width: var(--eye-w, 8px);
  height: 100%;
  border-radius: 12px;
  background-color: var(--eye-color, var(--grays-white));
  filter: url(#fx-texture);
}

/* White eyes pick up their frame's noise in Figma. */
.bot--down .eye,
.bot--small .eye,
.ghost__solid .eye {
  background-image: url("../assets/textures/grain-eye-light.png");
}

.bot--down {
  left: 968.29px;
  top: 427px;
}

/* Reflection on the platform: 20% opacity, progressive blur radius 0 → 8 top to bottom (calibrated std-devs). */
.bot--up {
  left: 968px;
  top: 476px;
  opacity: 0.2;
  isolation: isolate;
}

.bot--up .eyes {
  --eye-color: var(--eye-reflection);
}

.bot--up .blur-band {
  inset: -12px;
}

.bot--up .blur-band > * {
  translate: 12px 12px;
}

.bot--up .blur-band img {
  position: absolute;
  top: 0;
  left: 0;
}

.bot--up .blur-band:nth-child(1) { --blur: 0px;    --band: linear-gradient(#000 12px, transparent 24.28px); }
.bot--up .blur-band:nth-child(2) { --blur: 0.86px; --band: linear-gradient(transparent 12px, #000 24.28px, transparent 36.57px); }
.bot--up .blur-band:nth-child(3) { --blur: 1.72px; --band: linear-gradient(transparent 24.28px, #000 36.57px, transparent 48.85px); }
.bot--up .blur-band:nth-child(4) { --blur: 2.58px; --band: linear-gradient(transparent 36.57px, #000 48.85px, transparent 61.13px); }
.bot--up .blur-band:nth-child(5) { --blur: 3.44px; --band: linear-gradient(transparent 48.85px, #000 61.13px); }

/* The same bot at 63% (35 × 31), ending the statement. */
.bot--small {
  left: 1014px;
  top: 417px;
  width: 35px;
  height: 31.03px;
}

.bot--small .bot__shape {
  width: 35px;
  height: 32px;
  -webkit-mask-image: url("../assets/svg/bot-small-dark.svg");
  mask-image: url("../assets/svg/bot-small-dark.svg");
}

.bot--small .eyes {
  left: 10.49px;
  top: 10.42px;
  height: 10.11px;
  --eye-w: 5.05px;
  --eye-gap: 3.79px;
}

/* ==========================================================================
   Section 1 — "I Believe in" (Figma frame 1440 × 720)
   ========================================================================== */

/* Section labels: hand-written text between two stars (Figma auto layout, 25px tall, gap 12). */
.eyebrow {
  position: absolute;
  display: flex;
  height: 25px;
  align-items: center;
  gap: 12px;
}

/* Each star's box is 16.26 × 17 with the shape inset inside it. */
.eyebrow__star {
  width: 14.844px;
  height: 15.463px;
  margin: 0.847px 0.69px 0.69px 0.73px;
}

.statement__eyebrow,
.work__title,
.about__title {
  align-self: flex-start;
  color: var(--grays-gray);
  font-family: var(--font-hand);
  font-size: 32px;
  font-weight: 400;
}

.statement__label {
  left: 626.83px;
  top: 176px;
}

.statement__text {
  position: absolute;
  left: 415px;
  top: 261px;
  width: 611px;
  color: var(--ink-faint);
  font-family: var(--font-display);
  font-size: 52px;
  font-weight: 700;
  font-variation-settings: "opsz" 14;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
}

.statement__text strong {
  color: var(--ink);
  font-weight: inherit;
}

/* ==========================================================================
   Section 2 — "Selected work" (Figma: the rest of "section 1", 1440 × 2945, which also holds sections 3 and 4)
   ========================================================================== */

/* When jumping to #work, stop early enough that the heading clears the navbar. */
.work {
  --section-h: 2945px;
  scroll-margin-top: calc(140px * var(--scale));
}

/* Its background is 720px tiles, as in Figma, one after another; the last is cut off 65px down. */
.bg--tile {
  bottom: auto;
  height: 720px;
}

.bg--tile-3 {
  top: 720px;
}

.bg--tile-4 {
  top: 1440px;
}

.bg--tile-5 {
  top: 2160px;
}

.bg--tile-6 {
  top: 2880px;
  height: 65px;
}

/* Figma places the heading 64px above the section's top edge. */
.work__label {
  left: 609.735px;
  top: -64px;
}

/* Star behind the card's top-right corner: the hero's star shape, in an 83.4 × 87.2 box rotated −35.76°.
   Placed by the shape itself, pivoting about the box's centre (as .hero__star). */
.work__star {
  position: absolute;
  left: 1099.356px;
  top: -3.506px;
  width: 76.117px;
  height: 79.296px;
  transform: rotate(-35.765deg);
  transform-origin: 37.957px 39.246px;
}

/* ---------- Case study card (Figma "Documentation", 820 × 392) ----------
   Dark, with a 5px light outside stroke fading from #fafafa to #e5e5e5 down the card. The border is
   that stroke: the content clips to the padding box, which is the Figma frame.
   Not in Figma (Aniket's call): it's lifted off the page by a tight shadow at its edge plus a wide soft one,
   so it stands out as the main piece of work instead of sitting flat in the grid. Hovered, it rises further and
   tilts toward the cursor (after Quizrr Pass's card; js/tilt.js springs --lift 0–1 and --tilt-x/-y −1–1),
   its shadow spreading out softer the higher it goes. */
.work-card {
  --card-bg: #303030;
  --lift: 0;
  --tilt-x: 0;
  --tilt-y: 0;

  position: absolute;
  left: 305px;
  top: 36px;
  width: 830px;
  height: 402px;
  overflow: hidden;
  border: 5px solid transparent;
  font-family: var(--font-ui-figma);
  background:
    linear-gradient(var(--card-bg), var(--card-bg)) padding-box,
    linear-gradient(#fafafa 5px, #e5e5e5 calc(100% - 5px)) border-box;
  box-shadow:
    0 calc(2px + 2px * var(--lift)) calc(4px + 6px * var(--lift)) rgb(0 0 0 / calc(0.06 - 0.03 * var(--lift))),
    0 calc(14px + 12px * var(--lift)) calc(28px + 28px * var(--lift)) -8px rgb(0 0 0 / calc(0.18 - 0.05 * var(--lift))),
    0 calc(36px + 30px * var(--lift)) calc(72px + 60px * var(--lift)) calc(-18px - 6px * var(--lift)) rgb(0 0 0 / calc(0.28 - 0.06 * var(--lift)));
}

/* While hovered (and springing back): the side under the cursor comes up toward you, as the card drifts a little
   toward it, rises 8px and grows 2.5%. Only then, so at rest the card is untransformed and its text stays crisp. */
.work-card.is-tilted {
  transform:
    perspective(1600px)
    translate(calc(6px * var(--tilt-x)), calc(6px * var(--tilt-y) - 8px * var(--lift)))
    rotateX(calc(5deg * var(--tilt-y)))
    rotateY(calc(-7deg * var(--tilt-x)))
    scale(calc(1 + 0.025 * var(--lift)));
}

.work-card__text {
  position: absolute;
  top: 32px;
  left: 32px;
  display: flex;
  width: 320px;
  flex-direction: column;
  gap: 16px;
}

.work-card__copy {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.work-card__meta {
  color: #d9d9d9;
  font-size: 12px;
  font-weight: 500;
  line-height: 18px;
  text-transform: uppercase;
}

.work-card__title {
  margin: 0;
  color: var(--grays-white);
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 600;
  font-variation-settings: "opsz" 14;
  line-height: 34px;
}

.work-card__summary {
  color: #fafafa;
  font-size: 16px;
  font-weight: 400;
  line-height: 22px;
}

/* The phones are Figma exports at 2×, outline included, cut off by the card's bottom edge. Each one
   carries on 2px past that edge (hidden by the card) with a copy of its last rows, drawn as its own
   background: where the edge falls between screen pixels, no sliver of the dark card shows under it. */
.work-card__phone {
  position: absolute;
  box-sizing: content-box;
  padding-bottom: 2px;
  background-position: bottom;
  background-size: 100% auto;
  background-repeat: no-repeat;
}

.work-card__phone--back {
  left: 437.1px;
  top: 83.1px;
  width: 172px;
  height: 309px;
  background-image: url("../assets/images/marks-upsc-book.png?v=2");
}

.work-card__phone--front {
  left: 585.877px;
  top: 43.878px;
  width: 212.5px;
  height: 348.5px;
  background-image: url("../assets/images/marks-upsc-dashboard.png");
}

/* "View Case Study": white with a 4px orange lip along the bottom, which presses flat when clicked. */
.work-card__cta {
  position: absolute;
  top: 316px;
  left: 32px;
  display: flex;
  height: 44px;
  padding: 12px 14px;
  align-items: center;
  gap: 6px;
  overflow: hidden;
  border-bottom: 4px solid var(--accents-orange);
  border-radius: 9px;
  background: var(--grays-white);
  color: var(--grays-black);
  font-size: 13px;
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
  transition: height 0.08s ease-out, border-bottom-width 0.08s ease-out, translate 0.08s ease-out;
}

.work-card__cta img {
  transition: transform 0.2s ease;
}

/* The whole card is a way in: an invisible copy of the button's link covers it, under the button. Pointing anywhere on
   the card nudges the button's arrow, and pressing anywhere presses the button. */
.work-card__link {
  position: absolute;
  inset: 0;
}

.work-card:hover .work-card__cta img,
.work-card__cta:focus-visible img {
  transform: translate(1.5px, -1.5px);
}

.work-card__cta:active,
.work-card__link:active ~ .work-card__cta {
  height: 42px;
  border-bottom-width: 2px;
  translate: 0 2px;
}

/* ---------- "A few user interfaces" (Figma: under the case study card, 950 × 450) ---------- */

.ui-shots__title {
  position: absolute;
  top: 517px;
  left: 551px;
  margin: 0;
  color: #303030;
  font-family: var(--font-signature);
  font-size: 59.49px;
  font-weight: 400;
  text-transform: capitalize;
}

/* The Rapido concept slide (Figma "Slide 44"): a 2× export, its 5px light outline included. */
.ui-shots__slide {
  position: absolute;
  top: 618px;
  left: 205px;
  transition: translate 0.3s var(--ease-out-expo);
}

.ui-shots__slide > img {
  width: 370px;
  height: 460px;
}

.ui-shots__slide:hover {
  translate: 0 -4px;
}

/* Spider-Man's spidey sense (Figma: "Spidey Sense", keyframed): the squiggles round his head flicker and
   throb on a 4s loop, tipping 3° and back, each step easing in and out as in Figma. They're their own image,
   painted out of the slide export; they wait for the section to play in, and stay still for reduced motion. */
.ui-shots__spidey {
  position: absolute;
  top: 307.511px; /* the slide's 5px outline + the layer's place in the slide */
  left: 192.088px;
  width: 104.827px;
  height: 104.827px;
  pointer-events: none;
  animation: spidey-flicker 4s ease-in-out infinite, spidey-throb 4s ease-in-out infinite,
    spidey-tilt 4s ease-in-out infinite;
}

.ui-shots__spidey img {
  display: block;
  width: 100%;
  height: 100%;
}

.scroll-fx .ui-shots:not(.is-revealed) .ui-shots__spidey {
  animation-play-state: paused;
}

@keyframes spidey-flicker {
  0%    { opacity: 0.3; }
  12.5% { opacity: 1; }
  25%   { opacity: 0.2; }
  37.5% { opacity: 0.9; }
  50%   { opacity: 0.15; }
  65%   { opacity: 1; }
  80%   { opacity: 0.2; }
  92.5% { opacity: 0.8; }
  100%  { opacity: 0.3; }
}

@keyframes spidey-throb {
  0%, 100% { scale: 0.85; }
  15%      { scale: 1.1; }
  30%      { scale: 0.9; }
  50%      { scale: 1.15; }
  70%      { scale: 0.88; }
  85%      { scale: 1.08; }
}

@keyframes spidey-tilt {
  0%, 100% { rotate: 0deg; }
  50%      { rotate: -3deg; }
}

@media (prefers-reduced-motion: reduce) {
  .ui-shots__spidey {
    animation: none;
  }
}

/* On hover (or keyboard focus) the slide dims a little and a "View now" button rises into its centre,
   styled like the case study card's "View Case Study". Touch screens can't hover, so it just shows there. */
.ui-shots__slide::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgb(18 18 18 / 0.14);
  opacity: 0;
  transition: opacity 0.25s ease;
}

.ui-shots__cta {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 50%;
  display: flex;
  height: 44px;
  padding: 12px 14px;
  align-items: center;
  gap: 6px;
  border-bottom: 4px solid var(--accents-orange);
  border-radius: 9px;
  background: var(--grays-white);
  box-shadow: 0 10px 28px rgb(18 18 18 / 0.18);
  color: var(--grays-black);
  font-family: var(--font-ui-figma);
  font-size: 13px;
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
  opacity: 0;
  translate: -50% calc(-50% + 12px);
  scale: 0.9;
  transition: opacity 0.2s ease, translate 0.5s var(--spring-pop), scale 0.5s var(--spring-pop);
  pointer-events: none;
}

.ui-shots__slide:is(:hover, :focus-visible)::after {
  opacity: 1;
}

.ui-shots__slide:is(:hover, :focus-visible) .ui-shots__cta {
  opacity: 1;
  translate: -50% -50%;
  scale: 1;
}

@media (hover: none) {
  .ui-shots__cta {
    opacity: 1;
    translate: -50% -50%;
    scale: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ui-shots__cta {
    translate: -50% -50%;
    scale: 1;
    transition: opacity 0.2s ease;
  }
}

/* App screens in a 596px window, 64px right of the slide, sliding left on a loop: the five, then the
   same five again, so the track moves one set's width (5 × (197.386 + 17.8)) and starts over where it
   began. It waits until the section plays in (js/work-reveal.js) and stops while hovered. */
.ui-shots__window {
  position: absolute;
  top: 648.64px;
  left: 634px;
  width: 596px;
  height: 398.72px;
  /* Figma has a blurred white strip over each end (80px layer blur). This is the right one's fade as
     measured from Figma's render, used at both ends so the screens leave as softly as they arrive: gone
     for the last 20px, half faded 56px in, clear by 140px. It reveals the page itself instead of painting
     white, so the background (grid and grain) carries on untouched. */
  --edge-fade: transparent 0, rgb(0 0 0 / 0.01) 20px, rgb(0 0 0 / 0.04) 24px, rgb(0 0 0 / 0.11) 30px,
    rgb(0 0 0 / 0.26) 40px, rgb(0 0 0 / 0.41) 50px, rgb(0 0 0 / 0.54) 60px, rgb(0 0 0 / 0.65) 70px,
    rgb(0 0 0 / 0.74) 80px, rgb(0 0 0 / 0.82) 90px, rgb(0 0 0 / 0.89) 100px, rgb(0 0 0 / 0.94) 110px,
    rgb(0 0 0 / 0.96) 120px, rgb(0 0 0 / 0.98) 130px, #000 140px;
  -webkit-mask-image: linear-gradient(to left, var(--edge-fade)), linear-gradient(to right, var(--edge-fade));
  mask-image: linear-gradient(to left, var(--edge-fade)), linear-gradient(to right, var(--edge-fade));
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Clips the sliding screens 2px inside the window's edges, where the fades are already fully transparent.
   When the page is scaled down, the window's edges fall between screen pixels; clipping at the edge itself
   left a faint 1px line of the screens there, as the clip and the mask round those pixels differently. */
.ui-shots__clip {
  position: absolute;
  inset: 0 2px;
  overflow: hidden;
}

.ui-shots__track {
  display: flex;
  width: max-content;
  margin: 0 0 0 -2px; /* back to the window's left edge, out of the clip's 2px inset */
  padding: 0;
  gap: 17.8px;
  list-style: none;
  animation: ui-shots-loop 32s linear infinite;
}

.scroll-fx .ui-shots:not(.is-revealed) .ui-shots__track,
.ui-shots__window:hover .ui-shots__track {
  animation-play-state: paused;
}

@keyframes ui-shots-loop {
  to { translate: -1075.93px 0; }
}

/* Each screen is a Figma export of the phone, whose corners carry the canvas behind it. They're cut
   back to the phone's outline with a mask: the iPhone mockup (its own image, placed as in Figma) plus
   the screen under it (assets/svg/ui-screen-r16.svg or -r14.svg, by the screen's corner radius). */
.ui-shot {
  flex: none;
  width: 197.386px;
  height: 398.72px;
}

.ui-shot img {
  width: 100%;
  height: 100%;
  -webkit-mask-image: url("../assets/images/ui/iphone-14-plus.png"), url("../assets/svg/ui-screen-r16.svg");
  mask-image: url("../assets/images/ui/iphone-14-plus.png"), url("../assets/svg/ui-screen-r16.svg");
  -webkit-mask-position: 0.39px -1.756px, 0 0;
  mask-position: 0.39px -1.756px, 0 0;
  -webkit-mask-size: 197.386px 401.512px, 100% 100%;
  mask-size: 197.386px 401.512px, 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.ui-shot--r14 img {
  -webkit-mask-image: url("../assets/images/ui/iphone-14-plus.png"), url("../assets/svg/ui-screen-r14.svg");
  mask-image: url("../assets/images/ui/iphone-14-plus.png"), url("../assets/svg/ui-screen-r14.svg");
}

/* Reduced motion: the screens stay put, and the window scrolls sideways instead. */
@media (prefers-reduced-motion: reduce) {
  .ui-shots__track {
    animation: none;
  }

  .ui-shots__clip {
    overflow-x: auto;
    scrollbar-width: none;
  }

  .ui-shot[aria-hidden="true"] {
    display: none;
  }
}

/* ==========================================================================
   Section 3 — "About me" (Figma: in "section 1", under "A few user interfaces")
   ========================================================================== */

/* Its own frame from the label down, so the navbar's "About" lands on it. Inside it, positions are
   Figma's less 1937 (the label's y in "section 1"; the work frame starts 720 down it, so this is 1217 in). */
.about {
  --writing: 0.80s; /* how long the greeting takes to write (set with its pen strokes) */

  top: 1217px;
  height: 397px;
  scroll-margin-top: calc(76px * var(--scale)); /* lands like #work: the label 76px (scaled) from the top */
}

.about__label {
  left: 637px;
  top: 0;
}

/* The greeting is the Figma text box (cap height tall, its bottom on the baseline) holding the text as SVG, so
   that a pen can write it: the text sits under a mask of pen strokes (one path each, in writing order) that
   are drawn one after another once the section plays in. Every stroke covers its ink; when the last one is
   done the mask comes off (js/about-story.js). With reduced motion the text is simply there. */
.about__hey {
  position: absolute;
  top: 116px;
  left: 523px;
  width: 393.297px;
  height: 36.234px;
}

.about__hey-ink {
  position: absolute;
  overflow: visible;
}

.about__hey-ink text {
  fill: #303030;
  font-family: var(--font-signature);
  font-size: 59.49px;
  font-weight: 400;
}

.about__hey-ink path {
  fill: none;
  stroke: #fff;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.scroll-fx .about__hey:not(.is-written) text {
  mask: url(#about-hey-pen);
}

/* Each stroke is a dash the length of its path (pathLength 1), slid in from before its start. */
.scroll-fx .about__hey-ink path {
  stroke-dasharray: 1 2;
  stroke-dashoffset: 1.01;
  animation: pen-write var(--for) cubic-bezier(0.3, 0.1, 0.7, 0.9) var(--at) forwards paused;
}

.scroll-fx .about.is-revealed .about__hey-ink path {
  animation-play-state: running;
}

@keyframes pen-write {
  to { stroke-dashoffset: 0; }
}

/* The line comes in with only its "A" in full ink, the rest at 6% (Figma: 8%), and Goku's story lights it word by
   word (js/about-story.js). With reduced motion it's simply all in ink. */
.about__text {
  position: absolute;
  z-index: 3; /* over the beam, which passes behind "survives on Diet coke & Anime" */
  top: 193px;
  left: 320px; /* Figma's 738px box (x 351), widened evenly so "survives" still fits its row, centred */
  width: 800px;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 52px;
  font-weight: 700;
  font-variation-settings: "opsz" 14;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
}

.about__text strong {
  font-weight: inherit;
}

.about__text .word {
  transition: color 0.3s ease-out;
}

/* The beam's row flashes on as the beam passes each word. */
.about__text .word:nth-of-type(n+4):nth-of-type(-n+9) {
  transition-duration: 0.1s;
}

.scroll-fx .about__text .word:not(.is-lit) {
  color: rgb(18 18 18 / 0.06);
}

/* The can and its star, and the star alone: full-frame layers that the hit shakes about the can's and the star's
   centres. The star stays behind the can, as in Figma. */
.about__target,
.about__burst {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.about__target {
  z-index: 1;
}

.about__target {
  transform-origin: 1234px 272px;
}

.about__burst {
  transform-origin: 1274.791px 236.011px;
}

/* The crushed can, tipped 16.13° clockwise about its top-left corner as in Figma (its image is the fill's crop, 2x). */
.about__can {
  position: absolute;
  top: 203px;
  left: 1213.938px;
  width: 81.069px;
  height: 136.537px;
  rotate: 16.133deg;
  transform-origin: 0 0;
}

/* The star behind the can: the hero's star shape, in an 83.4 × 87.2 box turned 86.88° anticlockwise. Placed by
   the shape itself, pivoting about the box's centre (as .work__star). */
.about__star {
  position: absolute;
  top: 196.765px;
  left: 1236.834px;
  width: 76.117px;
  height: 79.296px;
  transform: rotate(-86.881deg);
  transform-origin: 37.957px 39.246px;
}

/* Goku: one pose at a time from a sprite sheet (Figma "image 288"), drawn at 0.46 and pinned on his front boot
   (238, 347.5), so he stays planted from pose to pose. The Kamehameha leaves his hands at (257.78, 295.98), level
   with the middle of the line's second row. --charge glows him up while he powers up and charges. */
.about__goku {
  --charge: 0;
  --glow: 255 214 64;

  position: absolute;
  z-index: 1;
  background: url("../assets/images/goku-ssj.webp") no-repeat;
  background-size: 799.02px auto;
  filter: drop-shadow(0 0 calc(var(--charge) * 9px) rgb(var(--glow) / calc(var(--charge) * 0.85)));
}

.about__goku[data-pose="idle"]   { left: 208.1px;   top: 249.06px; width: 43.24px;  height: 100.74px; background-position: 0 0; }
.about__goku[data-pose="crouch"] { left: 192.46px;  top: 267.46px; width: 53.82px;  height: 81.42px;  background-position: -45.08px 0; }
.about__goku[data-pose="stance"] { left: 200.74px;  top: 260.1px;  width: 46.92px;  height: 89.24px;  background-position: -100.74px 0; }
.about__goku[data-pose="grow"]   { left: 177.28px;  top: 250.44px; width: 77.28px;  height: 99.36px;  background-position: -149.5px 0; }
.about__goku[data-pose="ssj3"]   { left: 172.68px;  top: 244.46px; width: 80.04px;  height: 105.34px; background-position: -228.62px 0; }
.about__goku[data-pose="aura"]   { left: 148.76px;  top: 215.48px; width: 141.22px; height: 153.64px; background-position: -310.5px 0; }
.about__goku[data-pose="ka"]     { left: 170.84px;  top: 265.16px; width: 74.06px;  height: 83.26px;  background-position: -453.56px 0; }
.about__goku[data-pose="me"]     { left: 169.46px;  top: 265.16px; width: 75.44px;  height: 83.26px;  background-position: -529.46px 0; }
.about__goku[data-pose="ha"]     { left: 171.3px;   top: 259.18px; width: 106.72px; height: 89.7px;   background-position: -606.74px 0; }
.about__goku[data-pose="after"]  { left: 171.3px;   top: 258.72px; width: 83.72px;  height: 90.62px;  background-position: -715.3px 0; }

/* After the Kamehameha he powers down to the design's pose (Figma "image 290") and runs through moves from the same
   sheet (Figma "image 285"), at the design's scale: standing poses keep the stance's front boot and ground,
   airborne ones its centre of mass; the script lifts and moves him about from there. With reduced motion he
   simply stands in the design's pose. */
.about__goku[data-pose^="fz-"] {
  background-image: url("../assets/images/goku-fz.webp");
  background-size: 1104.84px auto;
}

.about__goku[data-pose="fz-stance"]     { left: 172.48px; top: 244.19px; width: 78.23px;  height: 84.68px;  background-position: 0 0; }
.about__goku[data-pose="fz-punch"]      { left: 169.26px; top: 242.58px; width: 106.45px; height: 86.29px;  background-position: -81.45px 0; }
.about__goku[data-pose="fz-turn"]       { left: 180.55px; top: 240.16px; width: 70.97px;  height: 87.9px;   background-position: -191.13px 0; }
.about__goku[data-pose="fz-backfist"]   { left: 175.71px; top: 239.35px; width: 81.45px;  height: 88.71px;  background-position: -265.32px 0; }
.about__goku[data-pose="fz-back"]       { left: 175.71px; top: 239.35px; width: 81.45px;  height: 88.71px;  background-position: -350px 0; }
.about__goku[data-pose="fz-crouch"]     { left: 182.16px; top: 249.03px; width: 67.74px;  height: 79.84px;  background-position: -434.68px 0; }
.about__goku[data-pose="fz-uppercut"]   { left: 193.45px; top: 220px;    width: 36.29px;  height: 108.06px; background-position: -505.65px 0; }
.about__goku[data-pose="fz-flip"]       { left: 188.45px; top: 235.32px; width: 48.39px;  height: 92.74px;  background-position: -545.16px 0; }
.about__goku[data-pose="fz-fly"]        { left: 148.69px; top: 261.94px; width: 103.23px; height: 66.13px;  background-position: -596.77px 0; }
.about__goku[data-pose="fz-dash"]       { left: 130.79px; top: 241.77px; width: 127.42px; height: 87.1px;   background-position: -703.23px 0; }
.about__goku[data-pose="fz-dive"]       { left: 152.24px; top: 257.9px;  width: 95.16px;  height: 70.16px;  background-position: -833.87px 0; }
.about__goku[data-pose="fz-tuck"]       { left: 182.64px; top: 240.16px; width: 55.65px;  height: 87.9px;   background-position: -932.26px 0; }
.about__goku[data-pose="fz-burst"]      { left: 144.18px; top: 232.1px;  width: 113.71px; height: 96.77px;  background-position: -991.13px 0; }

/* The Kamehameha (Figma "image 286"): its start ball over the one in Goku's hands, a body that repeats along it
   with its energy flowing forward, and its head, all flickering through the sheet's three takes. The script
   stretches it out to the can (--len), then it thins away. */
.about__beam {
  --k: 0.46;
  --row: calc(96px * var(--k));

  position: absolute;
  z-index: 2;
  top: calc(295.98px - var(--row) / 2);
  left: calc(257.78px - 41px * var(--k));
  width: var(--len, 0px);
  height: var(--row);
  opacity: 0;
  filter: drop-shadow(0 0 5px rgb(90 205 255 / 0.9)) drop-shadow(0 0 14px rgb(90 205 255 / 0.5));
  pointer-events: none;
  transition: opacity 0.3s ease-in, scale 0.3s ease-in;
}

.about__beam.is-firing {
  opacity: 1;
  transition: none;
}

.about__beam.is-fading {
  opacity: 0;
  scale: 1 0.15;
}

.about__beam > span {
  position: absolute;
  top: 0;
  height: 100%;
}

.about__beam-cap,
.about__beam-head {
  background: url("../assets/images/kamehameha.webp") no-repeat;
  background-size: calc(320px * var(--k)) auto;
  animation: beam-flicker 0.21s steps(3) infinite;
}

.about__beam-cap {
  left: 0;
  width: calc(140px * var(--k));
  background-position-x: 0;
}

.about__beam-head {
  right: 0;
  width: calc(176px * var(--k));
  background-position-x: calc(-144px * var(--k));
}

.about__beam-body {
  left: calc(100px * var(--k));
  right: calc(120px * var(--k));
  overflow: hidden;
}

.about__beam-body::before {
  content: "";
  position: absolute;
  inset: 0 0 0 calc(-82px * var(--k));
  background: url("../assets/images/kamehameha-body.png") repeat-x;
  background-size: calc(82px * var(--k)) auto;
  animation: beam-flicker 0.21s steps(3) infinite, beam-flow 0.2s linear infinite;
}

@keyframes beam-flicker {
  from { background-position-y: 0; }
  to { background-position-y: calc(-3 * var(--row)); }
}

@keyframes beam-flow {
  from { translate: 0 0; }
  to { translate: calc(82px * var(--k)) 0; }
}

/* Flashes: as Goku turns Super Saiyan 3, and where the beam hits the can. */
.about__flash {
  position: absolute;
  z-index: 4;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}

.about__flash--power {
  top: 220px;
  left: 140px;
  width: 170px;
  height: 170px;
  background: radial-gradient(closest-side, rgb(255 255 255 / 0.95), rgb(255 236 140 / 0.7) 45%, rgb(255 200 40 / 0) 100%);
}

.about__flash--hit {
  top: 236px;
  left: 1132px;
  width: 120px;
  height: 120px;
  background: radial-gradient(closest-side, #fff, rgb(200 245 255 / 0.85) 40%, rgb(80 200 255 / 0) 100%);
}

.about.is-transforming .about__flash--power {
  animation: about-flash 0.55s ease-out;
}

.about.is-hit .about__flash--hit {
  animation: about-flash 0.5s ease-out;
}

@keyframes about-flash {
  0% { opacity: 0; scale: 0.3; }
  15% { opacity: 1; }
  100% { opacity: 0; scale: 1.5; }
}

/* The hit: the can jolts back and settles, and its star bursts out and back. */
.about.is-hit .about__target {
  animation: can-knock 0.8s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.about.is-hit .about__burst {
  animation: star-burst 0.7s cubic-bezier(0.2, 0.7, 0.3, 1);
}

@keyframes can-knock {
  0% { transform: none; }
  12% { transform: translate(18px, -7px) rotate(11deg); }
  38% { transform: translate(-4px, 1px) rotate(-3deg); }
  65% { transform: translate(1px, 0) rotate(1deg); }
  100% { transform: none; }
}

@keyframes star-burst {
  0% { transform: none; }
  18% { transform: scale(1.5) rotate(24deg); }
  50% { transform: scale(0.92) rotate(-4deg); }
  100% { transform: none; }
}

/* ---------- Photos (Figma "Photo Frame" × 4, under the line) ----------
   Positions are Figma's less 2392 (the top photo's top edge in "section 1"; 1672 into the work frame). Each is
   a white frame with a light outside stroke (--edge wide) fading down it, as on the case study card, turned
   about the frame's top-left corner as in Figma; the photo sits inside at Figma's crop. The stroke is the box's
   background rather than a border, which browsers would round to whole pixels (moving everything inside).
   Hovered, each lifts and tilts toward the cursor like the case study card (js/tilt.js), casting a soft shadow
   only while it's up (Figma has none). */
.photos {
  top: 1672px;
  height: 400px;
}

.photo {
  --lift: 0;
  --tilt-x: 0;
  --tilt-y: 0;

  position: absolute;
  left: calc(var(--x) - var(--edge));
  top: calc(var(--y) - var(--edge));
  width: calc(var(--w) + 2 * var(--edge));
  height: calc(var(--h) + 2 * var(--edge));
  background: linear-gradient(#f9f9f9 var(--edge), #e5e5e5 calc(100% - var(--edge)));
  box-shadow:
    0 calc(2px + 2px * var(--lift)) calc(4px + 6px * var(--lift)) rgb(0 0 0 / calc(0.07 * var(--lift))),
    0 calc(10px + 14px * var(--lift)) calc(20px + 30px * var(--lift)) -6px rgb(0 0 0 / calc(0.16 * var(--lift))),
    0 calc(24px + 28px * var(--lift)) calc(48px + 56px * var(--lift)) -14px rgb(0 0 0 / calc(0.22 * var(--lift)));
  /* turned about the frame's corner (the stroke's inner corner), written out so the hover can build on it */
  transform: translate(var(--edge), var(--edge)) rotate(var(--turn)) translate(calc(-1 * var(--edge)), calc(-1 * var(--edge)));
  transform-origin: 0 0;
}

/* Hovered: the same tilt as the case study card, about the photo's centre and on top of its turn; being smaller, it
   can tilt and rise further. */
.photo.is-tilted {
  z-index: 1;
  transform:
    translate(var(--edge), var(--edge))
    rotate(var(--turn))
    translate(calc(var(--w) / 2), calc(var(--h) / 2))
    perspective(900px)
    translate(calc(5px * var(--tilt-x)), calc(5px * var(--tilt-y) - 10px * var(--lift)))
    rotateX(calc(10deg * var(--tilt-y)))
    rotateY(calc(-12deg * var(--tilt-x)))
    scale(calc(1 + 0.05 * var(--lift)))
    translate(calc(var(--w) / -2 - var(--edge)), calc(var(--h) / -2 - var(--edge)));
}

.photo::before {
  content: "";
  position: absolute;
  inset: var(--edge);
  background: #fff;
}

.photo img {
  position: absolute;
  left: calc(var(--edge) + var(--img-x));
  top: calc(var(--edge) + var(--img-y));
  width: var(--img-w);
  height: var(--img-h);
  transform: rotate(var(--img-turn, 0deg));
  transform-origin: 0 0;
}

.photo--blossoms {
  --x: 1122.615px; --y: 0px; --w: 191.253px; --h: 238.751px; --edge: 5px; --turn: 10.038deg;
  --img-x: 16.083px; --img-y: 16px; --img-w: 159.17px; --img-h: 206.687px;
}

.photo--mirror {
  --x: 128.015px; --y: 28.126px; --w: 223.674px; --h: 265.617px; --edge: 4.134px; --turn: -4.657deg;
  --img-x: 20px; --img-y: 21.533px; --img-w: 181.79px; --img-h: 224.092px; --img-turn: -0.483deg;
}

.photo--holi {
  --x: 466.797px; --y: 105.893px; --w: 215.953px; --h: 269.585px; --edge: 5.646px; --turn: 4.995deg;
  --img-x: 18.16px; --img-y: 18.066px; --img-w: 179.727px; --img-h: 233.38px;
}

.photo--friends {
  --x: 752.824px; --y: 158.943px; --w: 252px; --h: 197px; --edge: 5px; --turn: -4.394deg;
  --img-x: 16px; --img-y: 16px; --img-w: 220px; --img-h: 165px;
}

/* ==========================================================================
   Section 4 — Get in touch (Figma: the bottom of "section 1")
   ========================================================================== */

/* Positions are Figma's less 2880 (the top of its "Background 5" tile; 2160 into the work frame). The keys and
   their stars, arrows, captions and email are the small-screen page's (Figma "tablet"), at the same size. */
.contact {
  top: 2160px;
  height: 785px;
}

.contact__star--mail      { top: 110.551px; left: 278.574px; }
.contact__star--instagram { top: 110.551px; left: 615.574px; }
.contact__star--linkedin  { top: 111.854px; left: 954.556px; --turn: -25.79deg; }

.contact .key--mail       { top: 143px; left: 310px; }
.contact .key--instagram  { top: 143px; left: 650px; }
.contact .key--linkedin   { top: 143px; left: 990px; }

.contact__arrow {
  position: absolute;
  width: 30px;
  height: 40px;
}

.contact__arrow--mail      { top: 305px; left: 362px; }
.contact__arrow--instagram { top: 308px; left: 698px; transform: scaleY(-1); } /* the tablet's, flipped to point up */
.contact__arrow--linkedin  { top: 309px; left: 1040px; }

/* Each is centred on its Figma text box. */
.contact__caption {
  position: absolute;
  width: 240px;
  margin-left: -120px;
  color: var(--label-primary);
  font-family: var(--font-ui-figma);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.18px;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
}

.contact__caption--mail      { top: 353px; left: 377px; }
.contact__caption--instagram { top: 368px; left: 713.5px; }
.contact__caption--linkedin  { top: 361px; left: 1057px; }

.contact__email {
  top: 456px;
  left: 578px;
  gap: 10px;
  font-size: 18px;
}

/* "That’s All Folks": black at 10%, centred on Figma's text box (x 472, 473 wide) so the longer wording stays put */
.contact__folks {
  position: absolute;
  top: 576px;
  left: 308.5px;
  width: 800px;
  color: rgb(0 0 0 / 0.1);
  text-align: center;
  font-family: var(--font-hand);
  font-size: 96px;
  font-weight: 400;
}

/* Figma's two text boxes, 5px apart (the first one's trailing space doesn't count, as in Figma). */
.contact__copyright {
  position: absolute;
  top: 707px;
  left: 580px;
  display: flex;
  gap: 5px;
  color: #4c4c4c;
  font-family: var(--font-ui-figma);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.18px;
  line-height: 1.3;
  white-space: nowrap;
}

.contact__copyright span:last-child {
  color: #171717;
}

/* No cap trimming (e.g. Firefox): pull the full line box up by its ascent − cap height. */
@supports not (text-box: trim-both cap alphabetic) {
  .hero__hi {
    margin-top: -8.05px;
  }

  .hero__role,
  .ui-shots__title {
    margin-top: -8.77px;
  }

  .statement__eyebrow,
  .work__title,
  .about__title {
    margin-top: -6.26px;
  }

  .small__view--tablet .small-hi {
    margin-top: -8.05px;
  }

  .contact__folks {
    margin-top: -18.77px;
  }

  .small__view--mobile .small-hi {
    margin-top: -4.69px;
  }
}

/* ==========================================================================
   Intro animation — Figma "Hero section_animatied" (node 44:1967), 2s timeline.
   Every animated layer fades in (opacity 0 → its resting opacity) while rising
   into place (translate Y → 0). Each starts at --at and holds its first keyframe until then.
   ========================================================================== */

:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  /* Figma "Gentle" spring (stiffness 100, damping 15 → bounce 0.25), fitted to the keyframe duration. */
  --spring-gentle: var(--ease-out-expo);
}

@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --spring-gentle: linear(0, 0.0173, 0.0626, 0.1273, 0.2044, 0.2881, 0.374, 0.4588, 0.5399, 0.6155, 0.6846, 0.7466, 0.8012, 0.8484, 0.8887, 0.9224, 0.9501, 0.9724, 0.99, 1.0034, 1.0133, 1.0203, 1.0248, 1.0273, 1.0283, 1.0281, 1.0271, 1.0254, 1.0233, 1.021, 1.0185, 1.0161, 1.0138, 1.0116, 1.0095, 1.0077, 1.0061, 1.0047, 1.0035, 1.0025, 1.0016, 1.001, 1.0004, 1, 0.9997, 0.9995, 0.9993, 0.9992, 1);
  }
}

.intro {
  animation:
    intro-fade var(--fade) var(--ease-out-expo) var(--at) both,
    intro-rise var(--rise) var(--rise-ease, var(--ease-out-expo)) var(--at) both;
}

/* Set by the inline script in <head> until fonts and images have loaded. */
.intro-wait .intro {
  animation-play-state: paused;
}

@keyframes intro-fade {
  from { opacity: 0; }
}

@keyframes intro-rise {
  from { translate: 0 var(--from); rotate: var(--tilt, 0deg); scale: var(--grow, 1); }
}

/*                                  start         fade           rise          from                         */
.navbar                           { --at: 0s;     --fade: 0.508s; --rise: 0.62s; --from: -76px; --rise-ease: var(--spring-gentle); }
.platform                         { --at: 0.12s;  --fade: 0.738s; --rise: 0.9s;  --from: 150px; --rise-ease: var(--spring-gentle); }
.photo-frame                      { --at: 0.4s;   --fade: 0.5s;   --rise: 1s;    --from: 120px; --rise-ease: var(--spring-gentle); --tilt: -9deg; }
.hero__star                       { --at: 0.85s;  --fade: 0.2s;   --rise: 0.7s;  --from: 0px;   --rise-ease: var(--spring-pop); --tilt: -100deg; --grow: 0; }
.hero__hi                         { --at: 0.48s;  --fade: 0.394s; --rise: 0.48s; --from: 22px; }
.name-mark                        { --at: 0.6s;   --fade: 0.476s; --rise: 0.58s; --from: 34px; }
.g-mark                           { --at: 0.7s;   --fade: 0.476s; --rise: 0.58s; --from: 34px; }
.bot--down                        { --at: 0.8s;   --fade: 0.41s;  --rise: 0.5s;  --from: 24px; }
.bot--up                          { --at: 0.88s;  --fade: 0.41s;  --rise: 0.5s;  --from: 24px; }
.hero__role                       { --at: 1s;     --fade: 0.426s; --rise: 0.52s; --from: 22px; }
.scroll-hint                      { --at: 1.2s;   --fade: 0.4s;   --rise: 0.5s;  --from: 12px; } /* not in Figma's motion: arrives last */

/* Navbar contents share one motion, staggered 60ms: logo, Home, Work, About, "Let's talk". */
.brand__logo > .intro,
.navbar__links .intro,
.cta > .intro                     { --fade: 0.312s; --rise: 0.38s; --from: 10px; }
.brand__logo > .intro             { --at: 0.12s; }
.navbar__links li:nth-child(1) .intro { --at: 0.18s; }
.navbar__links li:nth-child(2) .intro { --at: 0.24s; }
.navbar__links li:nth-child(3) .intro { --at: 0.3s; }
.cta > .intro                     { --at: 0.36s; }

/* Not in Figma's motion: the polaroid rises out from behind the platform, swings into its tilt,
   and the photo develops — from a pale, soft blank to the sharp black & white portrait.
   The star then spins out from behind its corner (row above). */
.photo-frame img {
  animation: photo-develop 1.4s cubic-bezier(0.45, 0, 0.25, 1) 0.6s both;
}

.intro-wait .photo-frame img {
  animation-play-state: paused;
}

@keyframes photo-develop {
  from { filter: grayscale(1) contrast(0.25) brightness(1.7) blur(4px); }
}

/* ==========================================================================
   Scroll story (js/scroll-story.js) — active when <html> has .scroll-fx
   The page scrolls normally; these effects play along with it:
   1. The dot sinks into the platform, vanishing into its reflection.
   2. The reflection swims through the black platform, turns into the solid bot as it
      leaves it, and glides into the "A" from the left — then "Designer" lights up.
   3. The rest of the statement lights up word by word as it scrolls up the screen.
   4. The bot pops out of "life" as the full stop.
   ========================================================================== */

:root {
  /* Pop spring (bounce 0.5, ~16% overshoot) for the full stop. */
  --spring-pop: var(--ease-out-expo);
}

@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --spring-pop: linear(0, 0.032, 0.1159, 0.2345, 0.3724, 0.5167, 0.657, 0.7857, 0.8976, 0.9897, 1.061, 1.112, 1.1442, 1.1601, 1.1625, 1.1544, 1.1387, 1.1181, 1.0951, 1.0716, 1.0492, 1.029, 1.0117, 0.9977, 0.9872, 0.9798, 0.9754, 0.9736, 0.9738, 0.9755, 0.9784, 0.9819, 0.9857, 0.9895, 0.993, 0.9962, 0.9988, 1.001, 1.0025, 1.0036, 1.0041, 1.0043, 1.0042, 1.0039, 1.0034, 1.0028, 1.0021, 1.0015, 1);
  }
}

/* ---------- The statement's reveal ---------- */

/* "Designer" and every following word go from the design's 10% to full ink as --lit runs 0 → 1. */
.scroll-fx .statement__lead,
.scroll-fx .statement .word {
  color: rgb(18 18 18 / calc(0.1 + 0.9 * var(--lit, 0)));
}

/* The full stop waits inside "life" and pops out once the reveal completes. */
.scroll-fx .statement .bot--small {
  opacity: 0;
  scale: 0;
  translate: -18px 2px;
  transform-origin: 0% 60%;
  transition: opacity 0.2s ease-in, scale 0.25s ease-in, translate 0.25s ease-in;
}

.scroll-fx .statement .bot--small.is-popped {
  opacity: 1;
  scale: 1;
  translate: 0 0;
  transition: opacity 0.12s linear, scale 0.6s var(--spring-pop), translate 0.6s var(--spring-pop);
}

/* The swimming bot: placed on the page (so it scrolls with it) and moved, tilted and scaled about
   its centre by the script. */
.ghost {
  --solid: 0;
  --glow: 0;

  position: absolute;
  top: 0;
  left: 0;
  z-index: 50;
  width: 55.41px;
  height: 49.13px;
  transform-origin: 50% 50%;
  visibility: hidden;
  pointer-events: none;
  will-change: transform, opacity;
}

.ghost .bot {
  top: 0;
  left: 0;
}

/* The faint reflection (a touch brighter once the dot has sunk into it), crossfading to the
   solid bot as it swims out of the black platform. */
.ghost .ghost__reflection {
  opacity: calc((0.2 + 0.12 * var(--glow)) * (1 - var(--solid)));
}

.ghost .ghost__solid {
  opacity: var(--solid);
}

/* The full stop's swim into the case study card: like .ghost, placed on the page and moved, tilted and
   scaled about its centre by the script. The wrapper sits at the page's top-left corner and its clip-path
   (set by the script, in page px) cuts out the card, so the bot disappears under the card's edge as it
   dives into its top-left corner. */
.stop-swim {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 50;
  width: 0;
  height: 0;
  pointer-events: none;
}

.stop-swim .bot {
  top: 0;
  left: 0;
  transform-origin: 50% 50%;
  visibility: hidden;
  will-change: transform;
}

/* Waves spreading across the dark card from where the full stop dives in, on its top edge 40px from the
   corner (played by the script; that's 35px in from the dark area, whose edge is 5px inside the outline).
   Each is a soft band rather than a line: a faint dark trough just inside a light crest, like a ripple
   on water. The card's own clipping shows the lower half of each. */
.work-card__ripple {
  position: absolute;
  top: -5px;
  left: 35px;
  pointer-events: none;
}

.work-card__ripple > span {
  position: absolute;
  top: -170px;
  left: -170px;
  width: 340px;
  height: 340px;
  border-radius: 50%;
  background: radial-gradient(closest-side,
    rgb(255 255 255 / 0) 62%,
    rgb(0 0 0 / 0.16) 76%,
    rgb(255 255 255 / 0.2) 88%,
    rgb(255 255 255 / 0.06) 95%,
    rgb(255 255 255 / 0) 100%);
  opacity: 0;
}

/* ==========================================================================
   Selected work reveal (js/work-reveal.js) — active when <html> has .scroll-fx
   Not in Figma's motion. Each card waits on the intro's first frame and plays once it scrolls
   into view (.is-revealed): the card rises, its lines follow, the phones slide up out of
   it and the star spins in behind its corner. Backwards fill only, so once the animation
   is done the button's hover and press styles apply as normal. The phones ease in without
   the spring's overshoot: they're cut off by the card's bottom edge, so rising past their
   resting place would open a gap under them.
   ========================================================================== */

.scroll-fx .reveal {
  animation:
    intro-fade var(--fade) var(--ease-out-expo) var(--at) backwards paused,
    intro-rise var(--rise) var(--rise-ease, var(--ease-out-expo)) var(--at) backwards paused;
}

/* The case study card comes in flat and its shadow grows as it settles, so it rises off the page. */
.scroll-fx .work-card.reveal {
  animation:
    intro-fade var(--fade) var(--ease-out-expo) var(--at) backwards paused,
    intro-rise var(--rise) var(--rise-ease, var(--ease-out-expo)) var(--at) backwards paused,
    card-lift 1.2s var(--ease-out-expo) calc(var(--at) + 0.1s) backwards paused;
}

@keyframes card-lift {
  from {
    box-shadow:
      0 0 0 rgb(0 0 0 / 0),
      0 0 0 -8px rgb(0 0 0 / 0),
      0 0 0 -18px rgb(0 0 0 / 0);
  }
}

.scroll-fx .is-revealed .reveal {
  animation-play-state: running;
}

/*                                  start          fade           rise          from                        */
.work-card                        { --at: 0s;      --fade: 0.45s; --rise: 0.9s; --from: 64px;  --rise-ease: var(--spring-gentle); --grow: 0.97; }
.work-card__meta                  { --at: 0.16s;   --fade: 0.35s; --rise: 0.5s; --from: 14px; }
.work-card__title                 { --at: 0.22s;   --fade: 0.4s;  --rise: 0.6s; --from: 18px; }
.work-card__summary               { --at: 0.28s;   --fade: 0.4s;  --rise: 0.6s; --from: 18px; }
.work-card__cta                   { --at: 0.34s;   --fade: 0.35s; --rise: 0.5s; --from: 14px; }
.work-card__phone--back           { --at: 0.2s;    --fade: 0.3s;  --rise: 1s;   --from: 170px; }
.work-card__phone--front          { --at: 0.3s;    --fade: 0.3s;  --rise: 1s;   --from: 210px; }

/* The card's spring and scale-up are its own: the parts inside it don't inherit them. */
.work-card .reveal                { --rise-ease: var(--ease-out-expo); --grow: 1; }

.work__star                       { --at: 0.65s;   --fade: 0.2s;  --rise: 0.7s; --from: 0px;   --rise-ease: var(--spring-pop); --tilt: -100deg; --grow: 0; }

/* "A few user interfaces": the title, then the slide (like the card above), then the screens, which start sliding. */
.ui-shots__title                  { --at: 0s;      --fade: 0.4s;  --rise: 0.6s; --from: 18px; }
.ui-shots__slide                  { --at: 0.1s;    --fade: 0.45s; --rise: 0.9s; --from: 64px;  --rise-ease: var(--spring-gentle); --grow: 0.97; }
.ui-shots__window                 { --at: 0.25s;   --fade: 0.5s;  --rise: 0.8s; --from: 40px; }

/* "About me": the greeting writes itself instead (its pen strokes start as the section plays in), while Goku jumps
   in, the can is tossed in, swinging into its tilt, and its star spins in behind it. The line rises in once the
   greeting is written; the rest is js/about-story.js. */
.scroll-fx .about__hey.reveal     { animation: none; }
.about__goku                      { --at: 0.05s;  --fade: 0.3s; --rise: 0.8s; --from: 40px; --rise-ease: var(--spring-pop); --grow: 0.85; }
.about__can                       { --at: 0.15s;  --fade: 0.3s; --rise: 0.9s; --from: 56px; --rise-ease: var(--spring-gentle); --tilt: -6deg; }
.about__star                      { --at: 0.45s;  --fade: 0.2s; --rise: 0.7s; --from: 0px;  --rise-ease: var(--spring-pop); --tilt: -100deg; --grow: 0; }
.about__text                      { --at: calc(var(--writing) + 0.05s); --fade: 0.25s; --rise: 0.45s; --from: 24px; --rise-ease: var(--spring-gentle); }

/* Photos: dropped in one at a time, left to right, each swinging into its tilt about its pinned corner. */
.photo                            { --fade: 0.4s; --rise: 1s; --from: 90px; --rise-ease: var(--spring-gentle); }
.photo--mirror                    { --at: 0s;     --tilt: -8deg; }
.photo--holi                      { --at: 0.1s;   --tilt: 7deg; }
.photo--friends                   { --at: 0.2s;   --tilt: -7deg; }
.photo--blossoms                  { --at: 0.3s;   --tilt: 8deg; }

/* Get in touch: the keys rise in, their stars spin in behind them (to their faint resting state), then the arrows,
   captions and email follow; the sign-off rises in on its own when it's reached. */
.contact .key                     { --fade: 0.35s; --rise: 0.8s; --from: 48px; --rise-ease: var(--spring-gentle); }
.contact .key--mail               { --at: 0s; }
.contact .key--instagram          { --at: 0.08s; }
.contact .key--linkedin           { --at: 0.16s; }
.contact__star                    { --fade: 0.2s; --rise: 0.7s; --from: 0px; --rise-ease: var(--spring-pop); --tilt: -100deg; --grow: 0; }
.contact__star--mail              { --at: 0.3s; }
.contact__star--instagram         { --at: 0.38s; }
.contact__star--linkedin          { --at: 0.46s; }
.contact__arrow,
.contact__caption                 { --fade: 0.35s; --rise: 0.5s; --from: 14px; }
.contact__arrow--mail             { --at: 0.35s; }
.contact__arrow--instagram        { --at: 0.43s; }
.contact__arrow--linkedin         { --at: 0.51s; }
.contact__caption--mail           { --at: 0.42s; }
.contact__caption--instagram      { --at: 0.5s; }
.contact__caption--linkedin       { --at: 0.58s; }
.contact__email                   { --at: 0.6s;   --fade: 0.4s; --rise: 0.6s; --from: 18px; }
.contact__folks                   { --at: 0s;     --fade: 0.5s; --rise: 0.8s; --from: 30px; --rise-ease: var(--spring-gentle); }
.contact__copyright               { --at: 0.15s;  --fade: 0.4s; --rise: 0.6s; --from: 14px; }

/* ==========================================================================
   Small screens (≤ 1024px): instead of the site, "this looks better on a large screen"
   with ways to get in touch. Figma "tablet" (198:349, 1024 × 1366) from 700px up,
   "Mobile" (198:986, 360 × 800) below. Like the desktop stages, each frame keeps its
   Figma layout and is scaled to the screen width.
   ========================================================================== */

.small {
  display: none;
}

/* A phone or tablet asking for the "desktop site" reports a wide page, but its screen (device-width) is still small:
   it gets this page too. */
@media (max-width: 1024px), (pointer: coarse) and (max-device-width: 1024px) {
  .page,
  .ghost,
  .stop-swim {
    display: none;
  }

  .small {
    display: block;
    container-type: inline-size;
  }
}

.small__view {
  --k: tan(atan2(100cqw, var(--frame-w))); /* screen width ÷ frame width */
  position: relative;
  height: max(calc(var(--frame-h) * var(--k) - 1px), 100svh); /* −1px: no stray 1px scroll from rounding (e.g. 768 × 1024) */
  overflow: hidden;
  background: var(--grays-white);
}

.small__view--tablet {
  --frame-w: 1024px;
  --frame-h: 1366px;
}

/* Grows with the phone, but no further than 130% (on wider screens it's centred instead). */
.small__view--mobile {
  --frame-w: 360px;
  --frame-h: 800px;
  --k: min(1.3, tan(atan2(100cqw, var(--frame-w))));
}

.small__view--mobile {
  display: none;
}

@media (max-width: 699.98px), (pointer: coarse) and (max-device-width: 699.98px) {
  .small__view--tablet {
    display: none;
  }

  .small__view--mobile {
    display: block;
  }
}

/* A phone on the "desktop site": the page is laid out wide and shown zoomed out, so the frame fills the width. */
@media (pointer: coarse) and (max-device-width: 699.98px) and (min-width: 700px) {
  .small__view--mobile {
    --k: tan(atan2(100cqw, var(--frame-w)));
  }
}

/* The frame, in Figma px; it grows past the frame height to fill a taller screen. */
.small__stage {
  position: absolute;
  top: 0;
  left: calc((100cqw - var(--frame-w) * var(--k)) / 2);
  width: var(--frame-w);
  height: max(var(--frame-h), calc(100svh / var(--k)));
  transform: scale(var(--k));
  transform-origin: 0 0;
}

/* ---------- Backgrounds ---------- */

/* Tablet: the desktop pattern centred on the 1024px frame (208px to the left), in 720px tiles 4px apart. */
.small-bg {
  --bleed: -208px;
  right: 0;
  left: 0;
}

.small-bg--2 {
  top: 724px;
}

.small-bg--3 {
  top: 1448px;
}

/* Mobile: Figma turns the pattern on its side — a 922 × 461 tile rotated −90°, holding the desktop
   pattern at 102.5% — and its grain is lighter. The tile isn't clipped, so when the frame is centred on
   a wider screen the pattern carries on past its edges (it spans x −235…503 of the frame). */
.small-mbg {
  position: absolute;
  top: 174.5px;
  left: -279.5px;
  width: 922px;
  height: 461px;
  rotate: -90deg;
}

.small-mbg .bg {
  --bleed: 0px;
  top: -186px;
  right: auto;
  bottom: auto;
  left: -318px;
  width: 1440px;
  height: 720px;
  scale: 1.025;
  transform-origin: 0 0;
}

.small-grain {
  position: absolute;
  inset: 0 -240px;
  background: url("../assets/textures/grain-bg.png");
  opacity: 0.73;
}

/* ---------- Navbar ---------- */

.small-nav {
  position: absolute;
  top: 0;
  left: 50%;
  display: flex;
  height: 60px;
  padding: 0 13px;
  justify-content: center;
  align-items: center;
  gap: 32px;
  background: var(--grays-black);
  translate: -50% 0;
}

.small-nav .brand__logo {
  flex-shrink: 0;
}

.small-nav__places {
  display: flex;
  align-items: center;
  gap: 16px;
  color: var(--nav-link);
  font-size: 14px;
  font-weight: 500;
  line-height: normal;
  white-space: pre; /* keeps the space Figma has after "Bangalore" */
}

.small__view--tablet .small-nav {
  width: 286px;
  border-radius: 0 0 12px 12px;
}

.small__view--mobile .small-nav {
  top: 40px;
  padding: 0 17px;
  border-radius: 12px;
}

/* ---------- Greeting and heading ---------- */

.small-hi {
  position: absolute;
  color: var(--accents-orange);
  font-family: var(--font-hand);
  font-weight: 400;
}

.small__view--tablet .small-hi {
  top: 138px;
  left: 334px;
  font-size: 41.161px;
}

.small__view--mobile .small-hi {
  top: 154px;
  left: 76px;
  font-size: 24px;
}

.small-title {
  position: absolute;
  left: 50%;
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 700;
  font-variation-settings: "opsz" 14;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
  translate: -50% 0;
}

.small__view--tablet .small-title {
  top: 217px;
  left: calc(50% + 0.5px);
  font-size: 56px;
}

.small__view--mobile .small-title {
  top: 213px;
  font-size: 24px;
}

/* ---------- Stars (behind each key's corner; Figma boxes rotated, shape inset as on the hero star) ---------- */

/* Faint until their key is hovered, focused or pressed (js/keys.js adds .is-lit), then they pop in.
   The desktop's "Get in touch" stars (.contact__star) are these too, at the tablet's size. */
.small-star,
.contact__star {
  position: absolute;
  opacity: 0.1;
  transform: rotate(var(--turn, -20.95deg));
  rotate: -12deg;
  scale: 0.85;
  transition: opacity 0.25s ease, rotate 0.3s ease, scale 0.3s ease;
}

.small-star.is-lit,
.contact__star.is-lit {
  opacity: 1;
  rotate: 0deg;
  scale: 1;
  transition: opacity 0.1s linear, rotate 0.55s var(--spring-pop), scale 0.55s var(--spring-pop);
}

.small__view--tablet .small-star,
.contact__star {
  width: 76.122px;
  height: 79.294px;
  transform-origin: 37.957px 39.246px;
}

.small__view--tablet .small-star--mail      { top: 474.551px; left: 145.574px; }
.small__view--tablet .small-star--instagram { top: 474.551px; left: 413.574px; }
.small__view--tablet .small-star--linkedin  { top: 475.854px; left: 670.556px; --turn: -25.79deg; }

.small__view--mobile .small-star {
  width: 43.813px;
  height: 45.639px;
  transform-origin: 21.847px 22.589px;
}

.small__view--mobile .small-star--mail      { top: 321.253px; left: 46.41px; }
.small__view--mobile .small-star--linkedin  { top: 321.433px; left: 211.26px; }
.small__view--mobile .small-star--instagram { top: 501.253px; left: 127.93px; }

/* ---------- Keys (Figma "Key / …"): a dark base, two lips and a glossy face ---------- */

/* How far a key is pressed: 0 at rest, about half when hovered, 1 when pressed (js/keys.js adds
   .is-pressed for touch, where :active is unreliable). The face sinks 6px into its lips, the lips get
   shallower, the shadow tightens — and on release it springs back up. */
@property --press {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.key {
  --press: 0;
  position: absolute;
  width: 140px;
  height: 140px;
  border-radius: 34px;
  background: #171717;
  box-shadow: 0 calc(16px - 8px * var(--press)) calc(26px - 12px * var(--press)) rgba(0, 0, 0, 0.28);
  transform-origin: 0 0;
  transition: --press 0.5s var(--spring-pop);
  -webkit-tap-highlight-color: transparent;
}

.key:focus-visible {
  --press: 0.45;
  transition: --press 0.18s var(--ease-out-expo);
}

/* Only where hover is real — on touch screens a tapped key would otherwise stay half-pressed. */
@media (hover: hover) {
  .key:hover {
    --press: 0.45;
    transition: --press 0.18s var(--ease-out-expo);
  }
}

.key:active,
.key.is-pressed {
  --press: 1;
  transition: --press 0.06s ease-out;
}

.key:focus-visible {
  border-radius: 34px;
  outline-offset: 4px;
}

.key__lip,
.key__face {
  position: absolute;
  top: 10px;
  left: 11px;
  width: 118px;
  border-radius: 25px;
}

.key__lip {
  height: 120px;
  background: var(--lip);
  transform-origin: bottom;
  scale: 1 calc(1 - 0.05 * var(--press));
}

.key__lip--top {
  height: 113px;
  background: var(--lip-top);
  scale: 1 calc(1 - 0.0354 * var(--press));
  translate: 0 calc(2px * var(--press));
}

.key__face {
  height: 104px;
  background: var(--face);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.35);
}

.key__letter {
  position: absolute;
  top: 20px;
  left: 26px;
  color: rgba(255, 255, 255, 0.75);
  font-family: var(--font-inter);
  font-size: 13px;
  font-weight: 600;
  line-height: normal;
}

.key__icon {
  position: absolute;
  top: 38px;
  left: 46px;
  width: 48px;
  height: 48px;
}

.key__face,
.key__letter,
.key__icon {
  translate: 0 calc(6px * var(--press));
}

.key--mail {
  --lip: #a8202b;
  --lip-top: #b8232f;
  --face: linear-gradient(#ff6b73, #e3343d 55%, #e3343d);
}

.key--linkedin {
  --lip: #064a8f;
  --lip-top: #0756a6;
  --face: linear-gradient(#4a9af0, #0a66c2 55%, #0a66c2);
}

/* Figma gives Instagram's sides a washed-out gradient over plum, which reads as a muddy brown band.
   Here they're built like the other keys: the colours along the face's bottom edge (orange → pink →
   magenta), a shade darker for the upper lip and darker again for the lower one. */
.key--instagram {
  --lip: linear-gradient(to right, hsl(29 82% 44%) 29.3%, hsl(346 78% 42%) 63.2%, hsl(329 74% 28%));
  --lip-top: linear-gradient(to right, hsl(29 85% 51%) 29.3%, hsl(346 82% 49%) 63.2%, hsl(329 76% 34%));
  --face: linear-gradient(41.39deg, #ffa857 14.644%, #ff4f78 31.615%, #e31480 50%, #a83bb8 66.971%, #6e63cc 85.356%);
}

.small__view--tablet .key--mail      { top: 507px; left: 177px; }
.small__view--tablet .key--instagram { top: 507px; left: 447px; }
.small__view--tablet .key--linkedin  { top: 507px; left: 707px; }

/* Mobile keys are the tablet keys at 57.557%. */
.small__view--mobile .key {
  scale: 0.57557;
}

.small__view--mobile .key--mail      { top: 340px; left: 64.502px; }
.small__view--mobile .key--linkedin  { top: 339.577px; left: 229.768px; }
.small__view--mobile .key--instagram { top: 521.741px; left: 147.471px; }

/* ---------- Hand-drawn arrows and captions ---------- */

.small-arrow {
  position: absolute;
}

.small__view--tablet .small-arrow {
  width: 30px;
  height: 40px;
}

.small__view--tablet .small-arrow--mail      { top: 661px; left: 232px; }
.small__view--tablet .small-arrow--instagram { top: 461px; left: 502px; }
.small__view--tablet .small-arrow--linkedin  { top: 661px; left: 762px; }

/* Mobile reuses the tablet arrows: LinkedIn's is Instagram's flipped upright, Instagram's is Mail's mirrored. */
.small__view--mobile .small-arrow--mail {
  top: 429.066px;
  left: 96.158px;
  width: 17.267px;
  height: 23.023px;
}

.small__view--mobile .small-arrow--linkedin {
  top: 429.15px;
  left: 261.841px;
  width: 17.628px;
  height: 23.504px;
  scale: 1 -1;
}

.small__view--mobile .small-arrow--instagram {
  top: 612.75px;
  left: 178.63px;
  width: 17.267px;
  height: 23.023px;
  scale: -1 1;
}

.small-caption {
  position: absolute;
  color: var(--label-primary);
  font-weight: 500;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
  translate: -50% 0;
}

.small__view--tablet .small-caption {
  font-size: 18px;
  letter-spacing: -0.18px;
}

.small__view--tablet .small-caption--mail      { top: 709px; left: 247px; }
.small__view--tablet .small-caption--instagram { top: 425px; left: 517.5px; }
.small__view--tablet .small-caption--linkedin  { top: 713px; left: 779px; }

.small__view--mobile .small-caption {
  font-size: 11px;
  letter-spacing: -0.11px;
}

.small__view--mobile .small-caption--mail      { top: 456.693px; left: 104.833px; }
.small__view--mobile .small-caption--linkedin  { top: 458px; left: 275px; }
.small__view--mobile .small-caption--instagram { top: 642.301px; left: 182px; }

/* ---------- Email + copy button (js/copy.js; also the desktop's "Get in touch") ---------- */

.small-email,
.contact__email {
  position: absolute;
  display: flex;
  align-items: center;
  color: #111;
  font-family: var(--font-inter);
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
}

.copy-btn {
  position: relative;
  display: flex;
  flex-shrink: 0;
  padding: 0;
  justify-content: center;
  align-items: center;
  border: 0;
  border-radius: var(--radius);
  background: #efefea;
  cursor: pointer;
}

.copy-btn:focus-visible {
  border-radius: var(--radius);
}

.copy-btn img {
  transition: opacity 0.15s ease;
}

.copy-btn {
  transition: scale 0.4s var(--spring-pop);
}

.copy-btn:active {
  scale: 0.9;
  transition-duration: 0.06s;
}

/* Copied: the icon gives way to a tick for a moment. */
.copy-btn::after {
  content: "";
  position: absolute;
  width: 36%;
  height: 18%;
  border: solid #333;
  border-width: 0 0 1.5px 1.5px;
  opacity: 0;
  rotate: -45deg;
  translate: 0 -20%;
  transition: opacity 0.15s ease;
}

.copy-btn.is-copied img {
  opacity: 0;
}

.copy-btn.is-copied::after {
  opacity: 1;
}

.small__view--tablet .small-email {
  top: 776px;
  left: 117px;
  gap: 10px;
  font-size: 18px;
}

.small__view--tablet .copy-btn,
.contact .copy-btn {
  --radius: 8px;
  width: 32px;
  height: 32px;
}

.small__view--tablet .copy-btn img,
.contact .copy-btn img {
  width: 14.4px;
  height: 14.4px;
}

.small__view--mobile .small-email {
  top: 704px;
  left: calc(50% - 0.33px);
  gap: 5.756px;
  font-size: 12px;
  translate: -50% 0;
}

.small__view--mobile .copy-btn {
  --radius: 6.395px;
  width: 25.582px;
  height: 25.582px;
}

.small__view--mobile .copy-btn img {
  width: 11.512px;
  height: 11.512px;
}

/* ==========================================================================
   Sound key — on every screen, fixed to the bottom-right corner (js/sound.js)
   A small graphite version of the contact keys (pressing in the same way, js/keys.js): its bars dance while
   sound is on and flatten to dots when it's off. Hovered or focused, or just after a press, a label says which.
   Not in Figma.
   ========================================================================== */

.sound-dock {
  --at: 1.3s;
  --fade: 0.4s;
  --rise: 0.6s;
  --from: 16px;
  --size: 40px;

  position: fixed;
  z-index: 90;
  right: 20px;
  bottom: 20px;
  width: var(--size);
  height: var(--size);
}

/* The key is drawn at the contact keys' 140px and scaled down from its bottom-right corner. */
.sound-key {
  --lip: #050505;
  --lip-top: #1c1c1c;
  --face: linear-gradient(#4b4b4b, #2b2b2b 55%, #2b2b2b);

  right: 0;
  bottom: 0;
  padding: 0;
  border: 0;
  font: inherit;
  cursor: pointer;
  scale: calc(var(--size) / 140px);
  transform-origin: 100% 100%;
}

.sound-key__bars {
  position: absolute;
  top: 10px;
  left: 11px;
  display: flex;
  width: 118px;
  height: 104px;
  justify-content: center;
  align-items: center;
  gap: 9px;
  translate: 0 calc(6px * var(--press));
}

/* Each bar bobs at its own pace; switched off, the whole row flattens to dots. */
.sound-key__bars span {
  width: 11px;
  height: 46px;
  border-radius: 6px;
  background: var(--grays-white);
  animation: sound-bar 0.9s ease-in-out infinite alternate;
}

.sound-key__bars span:nth-child(2) { animation-duration: 0.7s;  animation-delay: -0.45s; }
.sound-key__bars span:nth-child(3) { animation-duration: 1.05s; animation-delay: -0.15s; }
.sound-key__bars span:nth-child(4) { animation-duration: 0.8s;  animation-delay: -0.6s; }

@keyframes sound-bar {
  from { scale: 1 0.3; }
  to { scale: 1 1; }
}

.sound-key__bars {
  transition: scale 0.35s var(--spring-pop), opacity 0.2s ease;
}

.sound-key.is-muted .sound-key__bars {
  scale: 1 0.2;
  opacity: 0.7;
}

.sound-key.is-muted .sound-key__bars span {
  animation-play-state: paused;
}

/* The label, sliding out to the key's left. */
.sound-key__label {
  position: absolute;
  top: 50%;
  right: calc(100% + 8px);
  padding: 5px 9px;
  border-radius: 8px;
  background: var(--grays-black);
  color: var(--grays-white);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  opacity: 0;
  translate: 6px -50%;
  pointer-events: none;
  transition: opacity 0.2s ease, translate 0.3s var(--ease-out-expo);
}

.sound-key:focus-visible + .sound-key__label,
.sound-dock.is-telling .sound-key__label {
  opacity: 1;
  translate: 0 -50%;
}

@media (hover: hover) {
  .sound-key:hover + .sound-key__label {
    opacity: 1;
    translate: 0 -50%;
  }
}

@media (max-width: 1024px), (pointer: coarse) and (max-device-width: 1024px) {
  .sound-dock {
    --size: 36px;
    right: 14px;
    bottom: 14px;
  }
}

@media (max-width: 699.98px), (pointer: coarse) and (max-device-width: 699.98px) {
  .sound-dock {
    --size: 34px;
    right: 12px;
    bottom: 12px;
  }
}

/* ...and on a phone's zoomed-out "desktop site", sized to the screen instead. */
@media (pointer: coarse) and (max-device-width: 699.98px) and (min-width: 700px) {
  .sound-dock {
    --size: 9.4vw;
    right: 3.3vw;
    bottom: 3.3vw;
  }
}

/* Without motion the bars stand still, at different heights. */
@media (prefers-reduced-motion: reduce) {
  .sound-key__bars span { animation: none; scale: 1 0.55; }
  .sound-key__bars span:nth-child(2) { scale: 1 0.9; }
  .sound-key__bars span:nth-child(3) { scale: 1 0.7; }
  .sound-key__bars span:nth-child(4) { scale: 1 0.45; }
}

/* ---------- Motion preferences ---------- */

/* Nav links jump between sections; glide there unless reduced motion is requested. */
html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
  }

  html {
    scroll-behavior: auto;
  }

  .intro,
  .photo-frame img {
    animation: none;
  }
}
