/* ---------- Font ---------- */
/* Variable font: weight 300–900, slant 0–12 */
@font-face {
  font-family: "CDA Independence";
  src: url("CDAIndependenceVAR.ttf") format("truetype");
  font-weight: 300 900;
  font-display: swap;
}

/* ---------- Reference variables + body styles ---------- */
:root {
  --black: #000;
  --white: #fff;
  --grey: rgba(233, 233, 233, .7);
  --yellow: #fade2b;
  --font: "CDA Independence", sans-serif;
  --header-nav-height: 65px;
  --header-message-height: 25px;
  --header-height: calc(var(--header-nav-height) + var(--header-message-height));
  --padding: 60px;
  --padding-small: 20px;
  --border-radius: clamp(.4rem, calc(.25rem + .35vw), .5rem);
  --gap: 1rem;
  --h1: clamp(3.815rem, calc(3.563rem + 1.259vw), 5.074rem);
  --h2: clamp(3.052rem, calc(2.851rem + 1.007vw), 4.059rem);
  --h3: clamp(2.441rem, calc(2.28rem + .806vw), 3.247rem);
  --h4: clamp(1.953rem, calc(1.824rem + .644vw), 2.597rem);
  --h5: clamp(1.563rem, calc(1.46rem + .516vw), 2.079rem);
  --h6: clamp(1.25rem, calc(1.168rem + .413vw), 1.663rem);
  --large: clamp(4.768rem, calc(4.453rem + 1.573vw), 6.341rem);
  --body: clamp(1rem, calc(.934rem + .33vw), 1.33rem);
  --small: clamp(.8rem, calc(.747rem + .264vw), 1.064rem);
  --shadow: drop-shadow(0px 4px 24px rgba(0, 0, 0, .08));
  --bounce: cubic-bezier(.175, .885, .32, 1.275);
  /* Font weights (wght axis 300–900) */
  --weight-heading: 900;
  --weight-body: 400;
  --weight-support: 600;
  scroll-padding-top: var(--header-height);

  /* Theme colours (light); dark values below. Yellow, red and the yellow
     sections stay the same in both themes. */
  color-scheme: light;
  --bg: var(--white);
  --fg: var(--black);
  --surface: #e6e6e6;                /* empty thumbnails */
  --muted: rgba(0, 0, 0, .5);        /* placeholder text */
  --line: rgba(0, 0, 0, .3);         /* rules between entries */
  --line-soft: rgba(0, 0, 0, .08);   /* rules between list items */
  --pill-bg: var(--grey);            /* frosted pills */
  --pill-border: var(--white);
  --ink-bg: var(--black);            /* black word blocks and tagline */
  --ink-fg: var(--white);
  --tagline-bg: var(--black);
  --tagline-border: transparent;
  --box-outline: transparent;        /* edge for black boxes on a black page */
}

/* Dark theme: set on <html> by the inline script in index.html */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0d0d0d;
  --fg: #f2f2f2;
  --surface: #222;
  --muted: rgba(255, 255, 255, .55);
  --line: rgba(255, 255, 255, .3);
  --line-soft: rgba(255, 255, 255, .1);
  --pill-bg: rgba(30, 30, 30, .65);
  --pill-border: rgba(255, 255, 255, .35);
  --ink-bg: #f2f2f2;
  --ink-fg: var(--black);
  --tagline-bg: #1c1c1c;
  --tagline-border: rgba(255, 255, 255, .15);
  --box-outline: rgba(255, 255, 255, .15);
}

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

/* Smooth scrolling. script.js starts Lenis (the reference's library) and adds .lenis to
   <html>; then CSS smooth scrolling must be off, or every Lenis step would be animated a
   second time. Without Lenis (blocked CDN), in-page links still glide with CSS alone. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
html.lenis { scroll-behavior: auto; }
/* Lenis's own stylesheet (lenis@1.3.13/dist/lenis.css), inlined */
html.lenis, html.lenis body { height: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent], .lenis [data-lenis-prevent-wheel], .lenis [data-lenis-prevent-touch] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }

body {
  margin: 0;
  -webkit-font-smoothing: antialiased;
  font-size: var(--body);
  font-family: var(--font);
  font-optical-sizing: auto;
  font-weight: var(--weight-body);
  font-style: normal;
  /* Upright, serif level 2 (SRFF axis 2–10) */
  font-variation-settings: "slnt" 0, "SRFF" 2;
  line-height: 1.4;
  background: var(--bg);
  color: var(--fg);
  height: auto;
  cursor: auto;
  transition: background-color .3s ease, color .3s ease;
}

/* Italic: the font's own slant axis rather than a synthesised slope */
em {
  font-style: normal;
  font-variation-settings: "slnt" 12, "SRFF" 2;
}

/* Form controls don't inherit fonts by default */
button { font: inherit; color: inherit; }

h1, h2, h3, p { margin: 0; }
a { color: inherit; text-decoration: none; }

/* Small uppercase labels (originally a monospace font) */
.label,
.nav-links a,
.nav-work-toggle,
.nav-clock,
.pill,
.text-link {
  font-size: var(--small);
  text-transform: uppercase;
  letter-spacing: .06em;
}

/* Headings and titles */
.hero-words,
.intro h1,
.card h3,
.view-more,
.band h2,
.process,
.about-title,
.credentials-title,
.credential-school-name {
  font-weight: var(--weight-heading);
}

/* Supporting text */
.label,
.nav-links a,
.nav-work-toggle,
.nav-clock,
.pill,
.text-link,
.hero-tagline,
.card p,
.placeholder,
.credential-school-parent,
.credential-meta,
.site-footer {
  font-weight: var(--weight-support);
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100; /* above the thumbnail pills (z-index 10) and anything else in the page */
  height: var(--header-nav-height);
  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: 0 var(--padding-small);
  /* Transparent: white content inverted against whatever is behind it */
  color: var(--white);
  mix-blend-mode: difference;
}

/* Logo: the header's difference blend would turn the red (#AD0000) cyan, so it is
   pre-inverted (#52FFFF); blended over white it shows exactly the original red */
.logo {
  --logo-size: 48px;
  position: relative;
  display: block;
  width: var(--logo-size);
  height: var(--logo-size);
}
.logo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  filter: invert(1);
  transition: opacity .25s ease;
}
/* Hidden while the screensaver copy is away from the header */
.logo.is-away { opacity: 0; }
/* Swap to the slanted version on hover; both images share one box, so nothing moves */
.logo-img--hover { opacity: 0; }
.logo:hover .logo-img--hover,
.logo:focus-visible .logo-img--hover { opacity: 1; }
.logo:hover .logo-img:not(.logo-img--hover),
.logo:focus-visible .logo-img:not(.logo-img--hover) { opacity: 0; }
/* Over the dark page the difference blend leaves the original red as it is,
   so the logo needs no pre-inversion there */
[data-theme="dark"] .logo-img,
[data-theme="dark"] .logo-screensaver img { filter: none; }

/* Visitor's city and country (or a fallback) and local time, next to the logo */
.nav-clock {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: .6em;
  line-height: 1.2;
  white-space: nowrap;
}
.nav-clock-place { overflow: hidden; text-overflow: ellipsis; }
.nav-clock-time { font-variant-numeric: tabular-nums; }

/* Nav: Work / About / Contact, the social icons and the theme switch read as one row,
   with one gap between every item (--nav-gap). Text items share line-height 1.2, so their
   capitals sit on one line. In this typeface (ascent 1000, descent 200, cap height 700 per
   1000) capitals at line-height 1.2 sit 0.05em below the middle of the line, so the icons
   and the switch, which are centred, move down by the same 0.05em to line up with them. */
.site-header { --nav-gap: 1.25rem; --cap-shift: calc(var(--small) * .05); }
.main-nav { display: flex; align-items: center; gap: var(--nav-gap); }
.nav-links { display: flex; align-items: center; gap: var(--nav-gap); }
.nav-work { display: flex; } /* a block here put the button on a 1.4 text line, so it sat lower */
.nav-links > a,
.nav-work-toggle {
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
}
.nav-links a:focus-visible,
.nav-work-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }
.site-header .social-links,
.site-header .theme-toggle { translate: 0 var(--cap-shift); }

/* Instagram, LinkedIn and email icons (nav and footer): 16px, with a stroke that renders
   about as thick as the 600-weight labels (2.4 of 24 units = 1.6px) */
.social-links {
  display: flex;
  align-items: center;
  gap: var(--nav-gap, 1.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.social-links li { display: flex; }
.social-link {
  position: relative;
  display: flex;
  transition: transform .3s var(--bounce), opacity .3s ease;
}
.social-link::after { content: ""; position: absolute; inset: -10px -6px; } /* bigger click area */
.social-link svg { display: block; }
.social-link:hover,
.social-link:focus-visible { transform: translateY(-2px) scale(1.12); }
.social-link:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; border-radius: 4px; }

/* Work dropdown (desktop). script.js moves the list out of the header into
   .work-dropdown, a fixed layer just below it: the header's difference blend applies to
   everything inside the header, so a list left there would be colour-inverted. Out
   here only the panel itself is frosted; the header keeps its own look while it opens.
   It fades and scales up from 0.8, like the reference's submenus. */
:root {
  --frost-bg: rgba(255, 255, 255, .56);
  --frost-border: rgba(255, 255, 255, .7);
  --frost-shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 18px 40px rgba(0, 0, 0, .12);
  --frost-hover: rgba(0, 0, 0, .06);
}
:root[data-theme="dark"] {
  --frost-bg: rgba(24, 24, 24, .52);
  --frost-border: rgba(255, 255, 255, .14);
  --frost-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 18px 40px rgba(0, 0, 0, .45);
  --frost-hover: rgba(255, 255, 255, .1);
}
.nav-work-toggle {
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.work-menu { margin: 0; padding: 0; list-style: none; }
.work-menu a { display: block; }
/* Without JavaScript the list stays in the header; on desktop it's simply not shown there */
@media (min-width: 801px) {
  .nav-work .work-menu { display: none; }
}
.work-dropdown {
  position: fixed;
  z-index: 101; /* above the header (100) and the page */
  top: 0;
  left: 0;
  pointer-events: none;
}
.work-dropdown .work-menu {
  display: flex;
  flex-direction: column;
  padding: .5rem;
  white-space: nowrap;
  color: var(--fg);
  background: var(--frost-bg);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  backdrop-filter: blur(24px) saturate(1.5);
  border: 1px solid var(--frost-border);
  border-radius: var(--border-radius);
  box-shadow: var(--frost-shadow);
  opacity: 0;
  visibility: hidden;
  transform: scale(.8);
  transform-origin: top left;
  transition: opacity .2s ease, transform .2s ease, visibility .2s ease;
}
.work-dropdown.is-open .work-menu {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}
.work-dropdown .work-menu a {
  font-size: var(--small);
  font-weight: var(--weight-support);
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: .45rem .75rem;
  line-height: 1.4; /* room for stacked accents (Ầ, Ế) inside the rolling text */
  border-radius: calc(var(--border-radius) * .75);
  transition: background-color .2s ease;
}
.work-dropdown .work-menu a:hover,
.work-dropdown .work-menu a:focus-visible { background: var(--frost-hover); }
.work-dropdown .work-menu a:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }

/* Light/dark switch at the far right. At rest it is just a circle (the knob), with no
   track. On hover or keyboard focus the track grows outward from that circle into a
   pill: the track's edges start on the knob's own outline and move out to the full
   size, while it fades in. The knob stays put and the track grows towards the side it
   can still travel to: right of the knob in light mode, left of it in dark mode. The
   button always reserves the full width, so nothing around it moves. Touch screens
   show the full track all the time, since they have no hover. */
.theme-toggle {
  --toggle-w: 34px;
  --toggle-h: 22px;
  --knob: 14px;
  --inset: calc((var(--toggle-h) - var(--knob)) / 2);
  --travel: calc(var(--toggle-w) - var(--knob) - 2 * var(--inset));
  /* the track's edges while it is just the knob's outline (light mode: knob at the left) */
  --track-top: var(--inset);
  --track-bottom: var(--inset);
  --track-left: var(--inset);
  --track-right: calc(var(--toggle-w) - var(--inset) - var(--knob));
  --knob-shift: 0px;
  flex: 0 0 auto;
  position: relative;
  width: var(--toggle-w);
  height: var(--toggle-h);
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.theme-toggle[aria-pressed="true"] {
  /* dark mode: knob at the right end */
  --track-left: calc(var(--toggle-w) - var(--inset) - var(--knob));
  --track-right: var(--inset);
  --knob-shift: var(--travel);
}
/* Bigger tap area without changing the look */
.theme-toggle::before { content: ""; position: absolute; inset: -11px -4px; }
/* The track */
.theme-toggle::after {
  content: "";
  position: absolute;
  top: var(--track-top);
  right: var(--track-right);
  bottom: var(--track-bottom);
  left: var(--track-left);
  border: 1.5px solid currentColor;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transition:
    top .45s cubic-bezier(.22, 1, .36, 1),
    right .45s cubic-bezier(.22, 1, .36, 1),
    bottom .45s cubic-bezier(.22, 1, .36, 1),
    left .45s cubic-bezier(.22, 1, .36, 1),
    opacity .2s ease;
}
.theme-toggle:hover::after,
.theme-toggle:focus-visible::after {
  --track-top: 0px;
  --track-right: 0px;
  --track-bottom: 0px;
  --track-left: 0px;
  opacity: 1;
}
.theme-toggle-knob {
  position: absolute;
  top: var(--inset);
  left: var(--inset);
  width: var(--knob);
  height: var(--knob);
  border: 1.5px solid currentColor;
  border-radius: 50%;
  transform: translateX(var(--knob-shift));
  transition: transform .4s var(--bounce), background-color .3s ease;
}
.theme-toggle[aria-pressed="true"] .theme-toggle-knob { background: currentColor; }
.theme-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; border-radius: 999px; }
@media (hover: none) {
  .theme-toggle::after {
    --track-top: 0px;
    --track-right: 0px;
    --track-bottom: 0px;
    --track-left: 0px;
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .theme-toggle::after,
  .theme-toggle-knob { transition: none; }
}

/* Menu hover: letters roll down, replaced by a copy from above */
.wave {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
}
.wave-copy { position: absolute; left: 0; bottom: 100%; }
.wave-letter { display: inline-block; white-space: pre; }

/* Idle screensaver: floating copy of the logo */
.logo-screensaver {
  position: fixed;
  z-index: 1002;
  width: 48px;
  height: 48px;
  pointer-events: none;
  mix-blend-mode: difference;
  transform-origin: center;
}
.logo-screensaver img {
  width: 100%;
  height: 100%;
  filter: invert(1); /* same as the header logo, so the hand-off is invisible */
}

/* Hamburger button (shown on small screens only) */
.nav-toggle {
  display: none;
  position: relative;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.nav-toggle-line {
  position: absolute;
  left: 10px;
  right: 10px;
  height: 2px;
  background: currentColor;
  transition: transform .3s ease, top .3s ease;
}
.nav-toggle-line:first-child { top: 17px; }
.nav-toggle-line:last-child { top: 25px; }
.site-header.nav-open .nav-toggle-line:first-child { top: 21px; transform: rotate(45deg); }
.site-header.nav-open .nav-toggle-line:last-child { top: 21px; transform: rotate(-45deg); }

/* ---------- Page-load reveal ---------- */
.page-reveal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
  background: var(--bg);
  -webkit-backdrop-filter: blur(50px);
  backdrop-filter: blur(50px);
  animation: reveal .5s ease .25s forwards;
}
@keyframes reveal {
  to {
    background: transparent;
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
    visibility: hidden;
  }
}

/* ---------- Image fade-in ---------- */
img { transition: opacity .5s ease-in; }
.js img:not(.is-loaded) { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .page-reveal { display: none; }
  img { transition: none; }
}

/* ---------- Tilted word blocks ---------- */
.tag {
  display: inline-block;
  padding: .05em .45em;
  border-radius: var(--border-radius);
  text-transform: uppercase;
  line-height: 1.15;
}
.tag--black { background: var(--ink-bg); color: var(--ink-fg); }
.tag--yellow { background: var(--yellow); color: var(--black); }
.tag--red { background: #ad0000; color: var(--white); } /* the logo's red */
.tilt-a { transform: rotate(-.5deg); }
.tilt-b { transform: rotate(1deg); }
.tilt-c { transform: rotate(-1.5deg); }

/* ---------- Hero ---------- */
/* The hero slides under the transparent header and spans the full width.
   Everything is laid out from the top; with physics on, the boxes then fall
   through the empty space below to the hero's floor (the bottom of the screen). */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: calc(-1 * var(--header-nav-height));
  padding: calc(var(--header-nav-height) + 1rem) var(--padding) 3rem;
  text-align: center;
  overflow: clip; /* nothing can widen the page */
  /* Size of the hero image boxes; also used by click-dropped images */
  --image-size: min(calc((100vw - 2 * var(--padding) - 5 * var(--gap) - 16px) / 6), 24vh, 280px); /* 16px: room for a scrollbar */
}
@media (prefers-reduced-motion: no-preference) {
  .hero {
    min-height: max(600px, 100vh);
    min-height: max(600px, 100svh);
  }
}

/* Six project image boxes spread across the full width */
.hero-images {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
  width: 100%;
  margin-bottom: 1.5rem;
}
.hero-image {
  flex: 0 0 auto;
  width: var(--image-size);
  height: var(--image-size);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .5rem;
  overflow: hidden;
  border-radius: var(--border-radius);
  filter: var(--shadow);
  box-shadow: inset 0 0 0 1px var(--box-outline);
  font-size: var(--small);
  font-weight: var(--weight-support);
}

/* Name on one line, as large as the width (and height) allows */
.hero-words { font-size: min(calc((100vw - 2 * var(--padding)) / 13), 15vh); }
.hero-words .tag { padding-top: .2em; } /* room for the accents on Â and Ễ */
.hero-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .12em;
}

/* Tagline on two lines: the second in yellow */
.hero-tagline {
  margin-top: 1.2rem;
  padding: .9rem 1.6rem;
  font-size: var(--h6);
  line-height: 1.3;
  background: var(--tagline-bg);
  color: var(--white);
  border: 1px solid var(--tagline-border);
  border-radius: var(--border-radius);
  transform: rotate(-1.5deg);
}
.tagline-line { display: block; }
.tagline-accent { color: var(--yellow); }
@media (min-width: 769px) {
  .tagline-line { white-space: nowrap; }
}

/* Phones: name over two lines, and no square project-image boxes (desktop and tablet
   only; script.js skips hidden boxes and doesn't drop new ones on tap at this width) */
@media (max-width: 767px) {
  .hero-images { display: none; }
  .hero-words { font-size: min(11.5vw, 9vh); }
  .hero-tagline { font-size: var(--body); }
}

.pill-links { display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem; margin-top: .8rem; }
/* Frosted-glass pills */
.pill {
  padding: .4rem .9rem;
  background: var(--pill-bg);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: .5px solid var(--pill-border);
  border-radius: var(--border-radius);
  transition: background-color .3s ease, color .3s ease, border-color .3s ease;
}
.pill:hover,
.pill:focus-visible { background: var(--bg); color: var(--fg); border-color: var(--fg); }

/* Physics sections: hero, "Let's Work Together" and thumbnails (added by script.js; off for reduced motion) */
.physics-on { user-select: none; -webkit-user-select: none; -webkit-user-drag: none; }
.physics-on [data-physics] {
  position: relative;
  cursor: grab;
  touch-action: pan-y; /* touch: sideways flicks move boxes, vertical swipes still scroll */
  will-change: transform;
  -webkit-user-drag: none;
}
/* Don't animate the transform, or boxes would lag behind the physics */
.physics-on [data-physics] { transition-property: background-color, color, border-color; }
.physics-on [data-physics].is-dragging { z-index: 2; cursor: grabbing; }

/* Project images spawned by clicking empty hero space */
.hero.physics-on { position: relative; }
.spawned-image {
  position: absolute;
  width: var(--image-size);
  height: var(--image-size);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--border-radius);
  filter: var(--shadow);
  box-shadow: inset 0 0 0 1px var(--box-outline);
  font-size: var(--small);
  font-weight: var(--weight-support);
  text-align: center;
  cursor: grab;
  will-change: transform;
}
.spawned-image.is-dragging { z-index: 2; cursor: grabbing; }
.spawned-image img { width: 100%; height: 100%; object-fit: cover; }

/* Hearts that float up and fade on click */
.heart-particle {
  position: fixed;
  z-index: 1001;
  pointer-events: none;
  font-size: 25px;
  line-height: 1;
  color: var(--white);
  mix-blend-mode: difference;
}

/* Custom cursor (mouse only; script.js adds .custom-cursor-on): a solid circle replaces
   the pointer. !important so it also wins over the grab cursors on the physics boxes.
   Two layers follow the pointer together: the circle (difference blend, so it inverts
   against what's behind it) and a yellow pill. Over a project with data-cursor-label,
   both morph to the pill's size at the same time while the circle fades out and the
   pill fades in, so it reads as one shape stretching into the pill.
   Both are centred on the pointer by the translate(-50%, -50%) that script.js adds. */
.custom-cursor-on,
.custom-cursor-on * { cursor: none !important; }
.cursor-dot,
.cursor-pill {
  --size: 14px;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1003;
  pointer-events: none;
  width: var(--size);
  height: var(--size);
  border-radius: 999px;
  opacity: 0;
  transition:
    width .45s cubic-bezier(.22, 1, .36, 1),
    height .45s cubic-bezier(.22, 1, .36, 1),
    opacity .3s ease;
}
.cursor-dot {
  background: var(--white);
  mix-blend-mode: difference;
}
.cursor-dot.is-visible { opacity: 1; }
/* Slightly bigger over things that can be clicked or grabbed, smaller while pressed */
.cursor-dot.is-hover,
.cursor-pill.is-hover { --size: 24px; }
.cursor-dot.is-pressed,
.cursor-pill.is-pressed { --size: 10px; }
.cursor-pill {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--yellow);
  color: var(--black);
}
.cursor-label {
  font-size: var(--small);
  font-weight: var(--weight-support);
  text-transform: uppercase;
  letter-spacing: .06em;
  line-height: 1;
  white-space: nowrap;
  opacity: 0;
  transition: opacity .2s ease;
}
/* Pill state: --pill-width is measured from the label by script.js */
.cursor-dot.is-pill,
.cursor-pill.is-pill { width: var(--pill-width, 140px); height: 40px; }
/* Only one shape at a time: the circle fades out quickly when the pill or the book takes over */
.cursor-dot.is-pill,
.cursor-dot.is-book {
  opacity: 0;
  transition: width .45s cubic-bezier(.22, 1, .36, 1), height .45s cubic-bezier(.22, 1, .36, 1), opacity .12s ease;
}
.cursor-pill.is-pill.is-visible { opacity: 1; }
.cursor-pill.is-pill .cursor-label { opacity: 1; transition-delay: .12s; }

/* ---------- Intro ---------- */
.intro {
  max-width: 1000px;
  margin: 0 auto;
  padding: clamp(4rem, 9vw, 8rem) var(--padding-small) 4rem;
  text-align: center;
}
/* Two intentional lines: "Curious by nature," / "thoughtful by design."
   Sized to the screen so neither line breaks again (the longer one is ~11em wide) */
.intro h1 {
  font-size: min(var(--h1), calc((100vw - 2 * var(--padding-small)) / 11.5));
  line-height: 1.1;
  margin-bottom: 2rem;
}
.intro-line { display: block; text-wrap: balance; }
.intro-text { max-width: 46rem; margin: 0 auto; font-size: var(--h6); line-height: 1.35; }
.intro-text p + p { margin-top: 1.2em; }
/* "Get to know Zoe →" under the location line; outside the scroll reveal, so it's
   always fully visible */
.intro .intro-link { display: inline-block; margin-top: 2.5rem; }

/* ---------- Work grid ---------- */
.work { padding: 4rem var(--padding) 2rem; }

/* Two equal columns; The Rollian spans both. Every regular thumbnail has the same
   4:3 shape, and the feature a wide 21:9, so nothing depends on the image's own size */
.work-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4rem var(--padding);
  margin-bottom: 2.5rem;
}
.card--feature { grid-column: 1 / -1; }
.card-media { aspect-ratio: 4 / 3; }
.card--feature .card-media { aspect-ratio: 21 / 9; }

/* The title link covers the whole card, so the card is one click target */
.card { position: relative; }
.card-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--border-radius);
}
.card-link:focus-visible { outline: none; }
.card-link:focus-visible::after { outline: 2px solid currentColor; outline-offset: 4px; }

.card h3 {
  margin-top: 1rem;
  font-size: var(--h6);
  text-transform: uppercase;
}
.card p { margin-top: .3rem; }

/* Image area: placeholder, optional <img>, dark overlay and sliding caption */
.card-media {
  position: relative;
  overflow: hidden;
  border-radius: var(--border-radius);
}
.card-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.placeholder {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--small);
  transition: transform .4s var(--bounce);
}
.card:hover .placeholder,
.card:focus-within .placeholder { transform: scale(1.02); }

.card-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .3);
  opacity: 0;
  transition: opacity .3s ease;
}
/* Category/industry pills inside each thumbnail: physics boxes confined to this
   area, landing near the bottom edge (they sit in the corner without physics) */
.card-tags {
  position: absolute;
  inset: var(--padding-small);
  z-index: 10;
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: calc(var(--gap) / 2);
  pointer-events: none; /* empty space still clicks through to the card link */
}
.card-tag {
  pointer-events: auto;
  padding: .3em 1em;
  font-size: var(--small);
  font-weight: var(--weight-support);
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
  color: var(--fg);
  background: var(--pill-bg);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: .5px solid var(--pill-border);
  border-radius: var(--border-radius);
}

/* On touch screens the pills ignore taps, so taps always reach the card link */
@media (hover: none) {
  .card-tag { pointer-events: none; }
}

.card:hover .card-overlay,
.card:focus-within .card-overlay { opacity: 1; }

/* Let's Work Together: a roomy physics box, so the words have room to fall, bounce and be
   thrown around (like the reference). The words start at the top of it, close to the
   projects above, and drop into the open space below. Smaller, lighter words than the
   headings; the arrow is an SVG, so it sits in the exact centre of its circle and turns
   with it. */
.view-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: .25rem;
  min-height: clamp(300px, 38vh, 360px);
  margin: 0;
  padding: 0 0 2rem;
  font-size: var(--h1);
  font-weight: var(--weight-support);
}
.view-more-row { display: flex; align-items: center; gap: .25rem; }
.arrow-circle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.15em;
  height: 1.15em;
  border-radius: 50%;
  background: var(--pill-bg);
  transition: transform .4s var(--bounce);
}
.arrow-circle svg { width: 55%; height: 55%; display: block; }
@media (max-width: 800px) {
  .view-more { min-height: clamp(260px, 36vh, 320px); font-size: var(--h3); }
}
.view-more:hover .arrow-circle { transform: translateX(.2em); }

/* ---------- Coloured bands ---------- */
.band {
  padding: 4rem var(--padding-small);
  text-align: center;
}
.band h2 {
  max-width: 40rem;
  margin: 0 auto 1rem;
  font-size: var(--h5);
  line-height: 1.2;
  text-wrap: balance;
}
.band .text-link { display: inline-block; margin-top: 2.5rem; }

/* Underline bar that fills the link on hover */
.text-link,
.inline-link {
  --link-bar: var(--yellow);
  --link-hover-text: var(--black);
  padding: .1em .2em;
  background: linear-gradient(var(--link-bar), var(--link-bar)) no-repeat left bottom / 100% 2px;
  transition: background-size .3s ease, color .3s ease;
}
.text-link:hover,
.text-link:focus-visible,
.inline-link:hover,
.inline-link:focus-visible { background-size: 100% 100%; color: var(--link-hover-text); }
.band--yellow .text-link { --link-bar: var(--black); --link-hover-text: var(--yellow); }
/* Rolling letters (as in the menu) instead of the fill; the underline stays put */
.text-link--wave:hover,
.text-link--wave:focus-visible { background-size: 100% 2px; color: inherit; }
.text-link--wave:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }

/* Yellow sections keep black text in both themes */
.band--yellow { background: var(--yellow); color: var(--black); }

/* ---------- Process ---------- */
.process {
  padding: 4rem 0;
  overflow: hidden;
  font-size: var(--large);
  text-transform: uppercase;
  line-height: 1.5;
  white-space: nowrap;
}
.indent-1 { padding-left: 4%; }
.indent-2 { padding-left: 23%; }
.indent-3 { margin-left: -.4em; }
.indent-4 { padding-left: 14%; }

/* ---------- About page ---------- */
/* Editorial layout after baothiento.com/about: a title, then the biography in one
   comfortable column with the portrait beside it, framed like a slightly turned photo
   print and staying in view while the text is read; a row of photo prints; then the
   credentials. The reference's body text is small and light; this keeps the site's own
   typeface at an easy 16-18px, with a reading width of about 60 characters. */
:root { --print-bg: #fff; --print-shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 10px 26px rgba(0, 0, 0, .1); }
:root[data-theme="dark"] { --print-bg: #ececec; --print-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 30px rgba(0, 0, 0, .5); }
.about-page {
  --about-width: min(100% - 2 * var(--padding), 1080px);
  --about-body: 15px; /* phones; a first test size, Zoe 2026-10-23 */
}
@media (min-width: 641px) {
  .about-page { --about-body: 16px; }
}
.about-intro,
.about-gallery,
.about-page .credentials { width: var(--about-width); margin-inline: auto; }
.about-page .credentials { padding-inline: 0; font-size: var(--about-body); line-height: 1.5; }
.about-intro { padding-top: clamp(2.5rem, 7vw, 6rem); }
.about-title {
  font-size: clamp(2.25rem, 1.6rem + 2.6vw, 3.75rem);
  line-height: 1.05;
  margin-bottom: clamp(1.75rem, 3.5vw, 3rem);
  text-wrap: balance;
}
.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 31rem) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 6rem);
  align-items: start;
}
.about-text { max-width: 31rem; font-size: var(--about-body); line-height: 1.6; }
.about-text p + p { margin-top: 1.25em; } /* the same gap between every paragraph */
/* The portrait and the music player under it stay in view together while the text is read */
.about-aside {
  position: sticky;
  top: calc(var(--header-nav-height) + 2rem);
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
}
/* Music player: a small label, then Spotify's own embed in the same kind of frame as the prints */
.now-playing { display: flex; flex-direction: column; align-items: flex-start; gap: .6rem; }
.now-playing-label {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  padding: .3em 1em;
  font-size: var(--small);
  font-weight: var(--weight-support);
  letter-spacing: .02em;
  color: var(--fg);
  background: var(--pill-bg);
  border: .5px solid var(--pill-border);
  border-radius: var(--border-radius);
}
.now-playing-label::before { content: ""; width: .5em; height: .5em; border-radius: 50%; background: var(--yellow); }
.now-playing-frame {
  align-self: stretch;
  padding: 7px;
  background: var(--print-bg);
  border-radius: 2px;
  box-shadow: var(--print-shadow);
}
.now-playing-frame iframe { display: block; width: 100%; height: 152px; border: 0; border-radius: var(--border-radius); }
/* The résumé link, in the paragraph: the same link as "Instagram @zoe.ngx →" below the
   gallery (.text-link .text-link--wave: small capitals, yellow underline, rolling hover) */
.about-text .text-link { white-space: nowrap; }
/* In the sentence it reads in lowercase at the body size ("résumé →"), and sits in a box
   like the gallery link's, so the yellow underline falls the same distance below the letters */
.about-text .resume-link { display: inline-block; font-size: 1em; text-transform: none; letter-spacing: 0; }

/* Photo prints: white border, soft shadow, a slight turn (--tilt) */
.print {
  margin: 0;
  padding: 7px;
  background: var(--print-bg);
  border-radius: 2px;
  box-shadow: var(--print-shadow);
  transform: rotate(var(--tilt, 0deg));
}
.print img { display: block; width: 100%; height: auto; }
.print--portrait { --tilt: -1.5deg; }
/* Five prints in a row, bottoms aligned, each with its own shape (--ratio) */
.about-gallery { padding-top: clamp(3.5rem, 8vw, 6.5rem); }
.gallery {
  display: flex;
  align-items: flex-end;
  gap: clamp(.75rem, 2vw, 1.5rem);
}
.print--photo { flex: 1 1 0; min-width: 0; }
.print-placeholder {
  aspect-ratio: var(--ratio, 1 / 1);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .5rem;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--small);
  font-weight: var(--weight-support);
  text-align: center;
}
.print--photo img { aspect-ratio: var(--ratio, 1 / 1); object-fit: cover; }
.gallery-link { display: inline-block; margin-top: 2.5rem; }

@media (max-width: 860px) {
  /* One column: the portrait first, then the text, as on the reference */
  .about-grid { grid-template-columns: minmax(0, 1fr); }
  .about-aside { position: static; order: -1; max-width: 30rem; }
  .about-aside .print--portrait { --tilt: 1.2deg; }
}
@media (max-width: 700px) {
  /* Prints become a row you swipe through, one centred at a time */
  .gallery {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    margin-inline: calc(-1 * var(--padding));
    padding: 1rem var(--padding) 1.5rem;
    scrollbar-width: none;
  }
  .gallery::-webkit-scrollbar { display: none; }
  .print--photo { flex: 0 0 min(68vw, 260px); scroll-snap-align: center; }
}

/* Scroll reveal: script.js wraps each character; opacity is set from the paragraph's own
   position in the window. html.reveal-off (reduced motion) shows everything. */
.reveal-word { white-space: nowrap; }
.reveal-char { opacity: .2; }
html.reveal-off .reveal-char { opacity: 1 !important; }
/* Text for screen readers only */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
  user-select: none; /* so copying the visible text doesn't copy it twice */
  -webkit-user-select: none;
}

/* ---------- Recognitions & Education ---------- */
.credentials { padding: 4rem var(--padding); }
.credentials + .credentials { padding-top: 2rem; }
.credentials-title {
  font-size: var(--h4);
  line-height: 1.1;
  margin-bottom: 2rem;
}
/* Two columns, one on phones. Recognitions reads Tyler | SUNY, then AIGA | AIGA Philadelphia */
.credentials-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 3rem clamp(2rem, 6vw, 6rem);
}
@media (max-width: 640px) {
  .credentials-grid { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
}
.credential {
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
}
/* School first (heavy), parent university underneath (lighter) */
.credential-school { font-size: 1.2em; line-height: 1.2; text-wrap: balance; }
.credential-school span { display: block; }
.credential-school-parent { margin-top: .15em; }
/* The line under the school: the date (Recognitions) or the location (Education) */
.credential-meta {
  margin: .5rem 0 1rem;
  font-size: var(--small);
  text-transform: uppercase;
  letter-spacing: .06em;
}
.credential-list { list-style: none; margin: 0; padding: 0; }
.credential-list li { padding: .35rem 0; }
.credential-list li + li { border-top: 1px solid var(--line-soft); }
.credential-note { margin-top: .6rem; }

/* Contact page (placeholder until the owner writes it): centred, filling the screen
   between the header and the footer */
.contact-page {
  min-height: calc(100vh - var(--header-nav-height) - 6rem);
  min-height: calc(100svh - var(--header-nav-height) - 6rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 4rem var(--padding-small);
  text-align: center;
}
.contact-page h1 { font-size: var(--h1); font-weight: var(--weight-heading); line-height: 1.1; text-wrap: balance; }
.contact-page-lead { font-size: var(--h6); font-weight: var(--weight-support); }

/* ---------- Footer ---------- */
/* Copyright on the left, the social icons on the right, on one line */
.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 2rem;
  padding: 2rem var(--padding);
  font-size: var(--small);
  line-height: 1.2;
  border-top: 1px solid var(--line-soft);
}

/* ---------- Mobile ---------- */
@media (max-width: 800px) {
  :root { --padding: 20px; }

  /* Mobile menu: hidden panel under the header, opened by the hamburger */
  .nav-toggle { display: block; }
  .main-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    padding: .5rem var(--padding-small) 1.5rem;
    background: var(--bg);
    box-shadow: 0 12px 24px rgba(0, 0, 0, .08);
    opacity: 0;
    visibility: hidden;
    transform: scale(.98);
    transform-origin: top;
    transition: opacity .2s ease, transform .2s ease, visibility .2s ease;
  }
  .site-header.nav-open .main-nav { opacity: 1; visibility: visible; transform: none; }
  .nav-links { flex-direction: column; align-items: stretch; gap: 0; width: 100%; }
  .nav-links > a { padding: .8rem 0; font-size: var(--body); }
  /* Social icons as a row at the bottom of the panel, a little bigger to tap */
  .main-nav .social-links {
    gap: 1.5rem;
    translate: none;
    width: 100%;
    margin-top: .8rem;
    padding-top: 1.2rem;
    border-top: 1px solid var(--line-soft);
  }
  .main-nav .social-link svg { width: 22px; height: 22px; }
  /* All projects listed straight in the panel, no Work button; the panel scrolls
     if the list is taller than the screen */
  .main-nav { overscroll-behavior: contain; max-height: calc(100vh - var(--header-nav-height)); max-height: calc(100svh - var(--header-nav-height)); overflow-y: auto; }
  .nav-work-toggle { display: none; }
  .work-menu { padding: .3rem 0; }
  .work-menu a { padding: .45rem 0; font-size: var(--body); }
  /* While the menu is open, show it normally so the white panel stays readable */
  .site-header.nav-open { mix-blend-mode: normal; color: var(--fg); background: var(--bg); }
  .site-header.nav-open .logo-img { filter: none; }
  /* Row on small screens: logo, place/time, theme switch, menu button */
  .site-header { --nav-gap: 1rem; }
  .theme-toggle { order: 1; }
  .nav-toggle { order: 2; margin-right: -10px; } /* line up the icon with the page edge */
  .process { font-size: var(--h5); }
}

/* Phones: one project per row, The Rollian a little taller so it still reads as the lead */
@media (max-width: 600px) {
  .work-grid { grid-template-columns: minmax(0, 1fr); gap: 3rem; }
  .card--feature .card-media { aspect-ratio: 16 / 9; }
}

/* Small phones: place over time, so the row never clips */
@media (max-width: 480px) {
  .nav-clock { flex-direction: column; align-items: flex-start; gap: 0; font-size: .72rem; line-height: 1.25; }
}
