/* ============================================
   FONTS
============================================ */
@font-face {
  
  font-family: 'Sprite Graffiti';
  src: url('../assets/fonts/SpriteGraffiti-Regular.woff2') format('woff2'),
       url('../assets/fonts/SpriteGraffiti-Regular.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}
@font-face {
  
  font-family: 'Poppins';
  src: url('../assets/fonts/Poppins-Light.woff2') format('woff2'),
       url('../assets/fonts/Poppins-Light.ttf') format('truetype');
  font-weight: 300;
  font-style: normal;
  font-display: block;
}
@font-face {
  
  font-family: 'Poppins';
  src: url('../assets/fonts/Poppins-Regular.woff2') format('woff2'),
       url('../assets/fonts/Poppins-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  
  font-family: 'Poppins';
  src: url('../assets/fonts/Poppins-Medium.woff2') format('woff2'),
       url('../assets/fonts/Poppins-Medium.ttf') format('truetype');
  font-weight: 500;
  font-style: normal;
  font-display: block;
}
@font-face {
  
  font-family: 'Poppins';
  src: url('../assets/fonts/Poppins-SemiBold.woff2') format('woff2'),
       url('../assets/fonts/Poppins-SemiBold.ttf') format('truetype');
  font-weight: 600;
  font-style: normal;
  font-display: block;
}
@font-face {
  
  font-family: 'Poppins';
  src: url('../assets/fonts/Poppins-Bold.woff2') format('woff2'),
       url('../assets/fonts/Poppins-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: block;
}
/* ============================================
   ROOT / RESET
============================================ */
:root {
  --pale:      #e9f1ff;
  --periwinkle:#bfd4ff;
  --blue:      #0052ff;
  --indigo:    #0a00cb;
  --navy:      #0c0054;

  --font-display: 'Sprite Graffiti', cursive;
  --font-body: 'Poppins', sans-serif;
  --font-mono: ui-monospace, 'SF Mono', 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace;

  --container: 1180px;
  --section-pad: clamp(64px, 9vw, 120px);
}
* , *::before, *::after { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
}
body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--navy);
  background: var(--pale);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
/* width/height attributes reserve space (no layout shift) but never fix the
   rendered height — any explicit CSS height still wins (zero specificity). */
:where(img[width][height]) { height: auto; }
a { text-decoration: none; color: inherit; }
h1, h2, h3, p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
/* Button pills have no arrow, even when they open in a new tab. */
a[target="_blank"].btn-pill::after { content: none; }
/* External links (anything opening in a new tab) get a small diagonal
   arrow. Masked with currentColor so it matches whatever the link's
   text color already is, in any section. */
a[target="_blank"] {
  display: inline-flex;
  align-items: center;
  gap: 0.32em;
}
/* A link that already draws its own arrow icon gets no second, automatic arrow. */
a[target="_blank"]:has(> svg)::after { content: none; }
a[target="_blank"]::after {
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 0.62em;
  height: 0.62em;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 12 L12 4 M6 4 H12 V10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 12 L12 4 M6 4 H12 V10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}
.section-inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 24px;
}
.section-heading {
  font-family: var(--font-display);
  font-weight: normal;
  letter-spacing: 0.5px;
  line-height: 0.9;
  font-size: clamp(2.6rem, 6vw, 4.4rem);
  color: var(--blue);
}
.section-heading--light { color: #fff; }
.brand-storytelling .section-heading-row,
.events .section-heading-row { justify-content: center; text-align: center; }
.section-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px 24px;
  flex-wrap: wrap;
}
.section-heading-row .btn { flex-shrink: 0; }
/* Extra top clearance for a section that opens a standalone page,
   so content isn't tucked under the fixed header. */
.page-top { padding-top: calc(var(--section-pad) + 90px) !important; }
.page-intro {
  background: var(--pale);
  padding: calc(var(--section-pad) + 90px) 0 40px;
}
.page-intro p {
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--navy);
  max-width: 640px;
  margin-top: 16px;
}
.page-intro p a {
  color: var(--blue);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* ============================================
   SCROLL REVEAL
   Everything here is scoped under html.js-reveal, a class only
   JS adds (and only when prefers-reduced-motion allows it) right
   before first paint. No JS / reduced motion => none of this
   applies and content is simply visible, full stop.
============================================ */
html.js-reveal [data-reveal] { opacity: 0; }
html.js-reveal [data-reveal].is-visible {
  animation-duration: 0.7s;
  animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1);
  animation-fill-mode: both;
  animation-delay: var(--stagger, 0ms);
}
html.js-reveal [data-reveal="up"].is-visible    { animation-name: revealUp; }
html.js-reveal [data-reveal="scale"].is-visible { animation-name: revealScale; }
html.js-reveal [data-reveal="fade"].is-visible  { animation-name: revealFade; }
html.js-reveal [data-reveal="left"].is-visible  { animation-name: revealLeft; }
html.js-reveal [data-reveal="right"].is-visible { animation-name: revealRight; }
/* Side-quest cards already run an infinite float animation on `transform` —
   fading them via opacity-only transition (not animation-name) keeps that
   float loop intact instead of getting clobbered by a competing animation. */
html.js-reveal [data-reveal="sq-fade"] {
  opacity: 0;
  transition: opacity 0.8s cubic-bezier(0.19, 1, 0.22, 1);
  transition-delay: var(--stagger, 0ms);
}
html.js-reveal [data-reveal="sq-fade"].is-visible { opacity: 1; }
@keyframes revealUp {
   from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@keyframes revealScale {
   from { opacity: 0; transform: translateY(16px) scale(0.94); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes revealFade {
   from { opacity: 0; } to { opacity: 1; } }
@keyframes revealLeft {
   from { opacity: 0; transform: translateX(-34px); } to { opacity: 1; transform: translateX(0); } }
@keyframes revealRight {
   from { opacity: 0; transform: translateX(34px); } to { opacity: 1; transform: translateX(0); } }
/* Word-level heading stagger */
html.js-reveal .reveal-heading .reveal-word {
  display: inline-block;
  opacity: 0;
}
html.js-reveal .reveal-heading.is-visible .reveal-word {
  animation: revealWordUp 0.55s cubic-bezier(0.19, 1, 0.22, 1) both;
  animation-delay: var(--stagger, 0ms);
}
@keyframes revealWordUp {
   from { opacity: 0; transform: translateY(0.5em); } to { opacity: 1; transform: translateY(0); } }
/* ============================================
   BUTTONS
============================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  padding: 14px 30px;
  border-radius: 18px;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.25s ease, box-shadow 0.25s ease, background-color 0.25s ease, color 0.25s ease;
  white-space: nowrap;
}
.btn-pill {
  background: transparent;
  border-color: var(--blue);
  color: var(--blue);
}
.btn-pill:hover {
  background: var(--blue);
  color: #fff;
  transform: translateY(-3px);
  box-shadow: 0 10px 20px rgba(0, 82, 255, 0.25);
}
.btn-pill-solid {
  background: var(--blue);
  border-color: var(--blue);
  color: #fff;
}
.btn-pill-solid:hover {
  background: var(--navy);
  border-color: var(--navy);
  transform: translateY(-3px);
  box-shadow: 0 10px 22px rgba(12, 0, 84, 0.3);
}
.btn-pill-light {
  background: var(--periwinkle);
  border-color: var(--periwinkle);
  color: var(--navy);
}
.btn-pill-light:hover {
  background: #fff;
  border-color: #fff;
  transform: translateY(-3px);
  box-shadow: 0 10px 22px rgba(0,0,0,0.2);
}
.btn-outline {
  border-color: var(--navy);
  color: var(--navy);
  padding: 10px 22px;
  font-size: 0.88rem;
}
.btn-outline:hover {
  background: var(--navy);
  color: #fff;
}
/* ============================================
   HEADER / NAV
============================================ */
/* Three separate outlined pills — wordmark on the left, page links in the
   middle, menu button on the right (mobile) — each with a 2px navy ring and
   a white chip fill, floating over the page with no bar behind them. */
.site-header {
  position: fixed;
  top: 12px;
  left: 0;
  right: 0;
  z-index: 100;
  padding: 0 20px;
}
.header-inner {
  max-width: 1240px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  position: relative;
}
.brand,
.main-nav,
.nav-toggle,
.nav-music,
.nav-mail {
  /* Frosted glass, as on the earlier bar: translucent diagonal sweep + blur. */
  background: linear-gradient(150deg, rgba(238, 241, 251, 0.72) 0%, rgba(255, 255, 255, 0.5) 50%, rgba(228, 233, 250, 0.72) 100%);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 0 0 2px var(--navy), 0 10px 24px rgba(10, 0, 84, 0.12), inset 0 1px 1.5px rgba(255, 255, 255, 0.8);
  border-radius: 999px;
}
.nav-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.nav-mail {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--navy);
  padding: 11px 18px;
  white-space: nowrap;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.nav-mail:hover { background: var(--blue); color: #fff; }
/* Music icon beside the email: plays / pauses the Side-Quests playlist. */
.nav-music {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border: 0;
  color: var(--navy);
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.nav-music svg { width: 20px; height: 20px; }
.nav-music:hover, .nav-music.is-on { background: var(--blue); color: #fff; }
.nav-music:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
.nav-music .nav-eq { display: none; align-items: flex-end; gap: 2px; height: 16px; }
.nav-music .nav-eq i { width: 3px; height: 100%; border-radius: 2px; background: currentColor; transform-origin: bottom; animation: nav-eq 2.6s ease-in-out infinite; }
.nav-music .nav-eq i:nth-child(2) { animation-delay: -0.9s; }
.nav-music .nav-eq i:nth-child(3) { animation-delay: -1.7s; }
.nav-music.is-on .nav-eq { display: inline-flex; }
.nav-music.is-on svg { display: none; }
@keyframes nav-eq {
   0%, 100% { transform: scaleY(0.45); } 50% { transform: scaleY(0.9); } }
@media (prefers-reduced-motion: reduce) {
  .nav-music .nav-eq i { animation: none; transform: scaleY(0.7); }
}
.brand {
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--navy);
  letter-spacing: 0.5px;
  flex-shrink: 0;
  padding: 7px 20px;
  line-height: 1.2;
}
.brand span { color: var(--blue); }
.nav-wrap {
  display: flex;
  align-items: center;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
.main-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
}
.nav-link {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--navy);
  padding: 7px 14px;
  border-radius: 999px;
  white-space: nowrap;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.nav-link:hover { background: var(--blue); color: #fff; }
.nav-link.active { background: var(--periwinkle); color: var(--navy); }
.nav-link.active:hover { background: var(--blue); color: #fff; }
.nav-toggle {
  display: none;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: -0.3px;
  color: var(--navy);
  border: none;
  cursor: pointer;
  padding: 10px 16px;
  flex-shrink: 0;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.nav-toggle:hover,
.nav-toggle.open { background: var(--blue); color: #fff; }
/* ============================================
   HERO
============================================ */
/* Rich, dark blue with a subtle grain — a low-opacity fractal-noise
   SVG (self-contained data URI, no image request) laid over a
   radial glow for a bit of depth, standing in for a concrete/paper
   texture without an actual texture asset. */
.hero {
  position: relative;
  background-color: #04051c;   /* the near-black blue the moving gradient has at its edges, so no purple shows before it starts */
  background-image:
    radial-gradient(ellipse 62% 78% at 52% 38%, #0a2fe0 0%, #0624b0 30%, #070c55 62%, rgba(4, 5, 28, 0) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.35'/%3E%3C/svg%3E");
  background-repeat: no-repeat, repeat;
  padding: 110px 24px 0;
  overflow: hidden;
}
/* Animated WebGL gradient layer (see initHeroGradient in script.js) —
   sits above the section's own static background/noise (kept as the
   no-JS/no-WebGL/reduced-motion fallback) and below .hero-inner
   (z-index:1) so the nav-adjacent content stays on top. */
@keyframes heroGradientIn { from { opacity: 0; } to { opacity: 1; } }
.hero-gradient-canvas {
  animation: heroGradientIn 0.9s ease both;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}
/* Desktop only — fills a standard laptop viewport height on first
   load instead of ending abruptly right where the photo happens to
   be cropped. Mobile/tablet already size themselves around their
   own stacked layout and backdrop-photo treatment, so this is left
   alone there. Not vertically centered — the content sits right
   under the nav (padding-top above), and with no padding-bottom the
   photo (the last thing in flow) naturally reaches the section's
   own bottom edge on its own, however tall it renders. */
@media (min-width: 901px) {
  .hero { min-height: 100vh; }
}
@media (max-width: 900px) {
  .hero {
    background-image:
      radial-gradient(ellipse 130% 95% at 50% 40%, #1c35c4 0%, #0b22a2 50%, #08124f 100%),
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.35'/%3E%3C/svg%3E");
  }
}
.hero-inner {
  position: relative;
  z-index: 1;
  max-width: var(--container);
  margin: 0 auto;
  width: 100%;
  text-align: center;
}
.hero-heading {
  position: relative;
  /* Below the photo now (z-index 3) — the photo sits in front of the
     wordmark's lower letters instead of tucking in behind them. */
  z-index: 1;
  margin-bottom: clamp(-56px, -3.6vw, -16px);
}
@media (min-width: 901px) {
  .hero-heading {
      /* Shifts "hi, i'm" + the wordmark down without touching the
         photo: transform doesn't affect document flow, so .hero-row
         (the photo's container) stays exactly where it was — its own
         sizing/position logic in script.js measures .hero-row/.hero
         directly, never .hero-heading, so it's untouched either way.
         The card + one-liner stack DO move with it, since their top is
         measured live off the wordmark's own rendered bottom edge.
         Desktop-only: below this breakpoint the hero is a plain
         stacked flow (no absolutely-positioned card/stack, photo not
         pinned the same way), where a transform this size would just
         shove the heading down into the one-liner/logos below it
         instead of staying part of the same coherent block. */
      transform: translateY(1.25cm);
    }
}
.hero-kicker {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: clamp(1rem, 1.8vw, 1.2rem);
  color: #fff;
}
/* Big enough to nearly fill the hero's own horizontal space (not
   just spill past the nav pill above it) — the extra width is what
   gives the letter-aligned card and stack (see
   alignHeroSideElements in script.js) room to sit further apart,
   and the photo between them room to breathe. */
.hero-name {
  font-family: var(--font-display);
  font-weight: normal;
  color: #fff;
  font-size: clamp(4.2rem, 23.5vw, 18rem);
  line-height: 0.85;
  margin-top: -6px;
}
.hero-row {
  display: flex;
  justify-content: center;
  position: relative;
  z-index: 3;
}
/* Width and horizontal position are set at runtime by
   alignHeroSideElements (script.js), sized to whatever gap is
   actually left between the letter-aligned card and left-stack at
   the current viewport width — a fixed width/offset tuned for one
   viewport doesn't hold up as the wordmark (and so that gap) scales
   at others. This is just the pre-JS fallback. */
.hero-photo {
  width: min(300px, 22vw);
  flex-shrink: 0;
  position: relative;
}
.hero-photo-img,
.hero-photo canvas {
  width: 100%;
  height: auto;
  display: block;
}
/* Desaturated so the real photo sits inside the hero's blue palette
   rather than introducing full-color skin tones/clothing — a touch
   of contrast keeps it from reading flat once the color's gone. */
.hero-photo-img {
  filter: grayscale(1) contrast(1.05);
  /* The image carries 9% extra canvas on the right (the full sleeve). Drawn
     wider than its box and left to overflow, so the photo box, and
     everything positioned from it, stays exactly where it was. */
  width: 109%;
  max-width: none;
}
/* One-liner + logos, stacked on the left side of the hero. right is
   set at runtime by alignHeroSideElements (script.js) to line up
   with the wordmark's "a"; left:0 here is just the fallback before
   that runs. */
.hero-left-stack {
  position: absolute;
  top: clamp(120px, 16vw, 190px);
  left: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  /* One compact frosted box holding the one-liner, the label and the logos. */
  width: max-content;
  max-width: calc(100% - 8px);
  margin-top: -16px; /* sits a touch higher, beside the face rather than down at the shoulder */
  padding: 14px 18px 16px;
  border-radius: 18px;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.06));
  border: 1.5px solid rgba(191, 212, 255, 0.35);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 14px 34px rgba(4, 0, 60, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.hero-left-stack .hero-worked-at {
  align-self: stretch;
  padding-top: 10px;
  border-top: 1px solid rgba(191, 212, 255, 0.28);
}
.hero-left-stack .hero-worked-at-label,
.hero-left-stack .hero-worked-at-logos { max-width: none; }
.hero-left-stack .hero-worked-at-label { margin-bottom: 9px; white-space: nowrap; }
.hero-left-stack .hero-oneliner { text-wrap: balance; }
.hero-oneliner-card {
  display: inline-block;
  max-width: 292px;
  margin-bottom: 0;
}
/* The one-liner: a supporting line under the name, not a second
   headline — bold enough to still carry some weight, but clearly a
   step down in size from "IFEOLUWA" above and a step up from plain
   body copy below. */
.hero-oneliner {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1rem, 1.7vw, 1.2rem);
  line-height: 1.4;
  color: #fff;
  text-align: left;
}
/* A quiet, secondary label introducing the logos — same small-caps
   treatment as .brands-label further down the page, so both read as
   the same kind of "just a label" element rather than a headline.
   Lightened from its original navy-on-pale reading to stay legible
   against the hero's now-dark background. */
.hero-worked-at-label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.72rem;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 1.1px;
  max-width: 300px;
  color: rgba(255, 255, 255, 0.55);
  margin-bottom: 12px;
  text-align: left;
}
/* Evenly spaced across the same width as the label above it, rather
   than left-packed with a fixed gap — space-between stretches the
   row out to that full 300px instead of only as wide as the icons
   themselves need. */
.hero-worked-at-logos {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: 300px;
}
/* The same small colorful rounded-square app icons used for
   .exp-logo in the "I've Worked At" section — reused here at a
   smaller size so they read as a quiet supporting row, not a second
   set of logo cards. */
.hero-worked-at-logo {
  display: block;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  overflow: hidden;
  flex-shrink: 0;
}
.hero-worked-at-logo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* A single fixed-size card that cycles through image+caption pairs —
   an original recreation of the rotating highlight card on
   growxy.framer.website's landing page: split evenly into a photo
   half and a text half, sitting lower than the name, on the right
   (see initHeroHighlightCard in script.js for the cycling itself). */
.hero-cta {
  position: absolute;
  /* top/right are set at runtime by alignHeroSideElements (script.js), in line
     with the wordmark; these are only the pre-JS fallback. */
  top: clamp(120px, 16vw, 190px);
  right: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(230px, 25vw);
  min-width: 190px;
}
/* Each button is its own box in the same template as the one-liner box: same
   glass background, outline, radius and padding. */
.hero-cta-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px 18px;
  border-radius: 18px;
  /* The exact glass of the one-liner box. */
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.06));
  border: 1.5px solid rgba(191, 212, 255, 0.35);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 14px 34px rgba(4, 0, 60, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  color: #fff;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1rem, 1.7vw, 1.2rem);
  line-height: 1.4;
  white-space: nowrap;
  transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.hero-cta-btn:hover {
  transform: translateY(-3px);
  background: linear-gradient(150deg, #0a62ff, #0052ff);
  border-color: rgba(191, 212, 255, 0.7);
}
.hero-cta-btn:focus-visible { outline: 3px solid var(--periwinkle); outline-offset: 3px; }
/* Email, LinkedIn and X: three icon buttons in the same glass, in the spot "Contact me" used to take */
.hero-socials { display: flex; gap: 12px; }
.hero-cta-btn--icon { flex: 1; padding: 13px 0; }
.hero-cta-btn--icon svg { width: 22px; height: 22px; display: block; }
.hero-cta-btn--icon::after { content: none !important; }
/* no little external-link arrow on the icons */
.hero-highlight-slide {
  position: absolute;
  inset: 4px;
  display: flex;
  align-items: stretch;
  gap: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}
.hero-highlight-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}
.hero-highlight-slide img {
  flex: 0 0 auto;
  width: auto;
  height: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 10px;
  object-fit: cover;
  display: block;
}
.hero-highlight-slide p {
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
  align-self: stretch;
  display: flex;
  align-items: center;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(0.72rem, 1.1vw, 0.85rem);
  line-height: 1.28;
  color: var(--navy);
  text-align: left;
}
/* Entrance moment on first load — pure CSS so it's guaranteed to run
   on paint with no JS dependency, and cleanly skipped for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .hero-kicker { animation: heroFadeUp 0.8s cubic-bezier(0.19, 1, 0.22, 1) both 0.15s; }
  .hero-name   { animation: heroNameIn 1s cubic-bezier(0.16, 1, 0.3, 1) both 0.3s; }
  /* On the inner img, not .hero-photo itself — .hero-photo's own
       transform is fully JS-controlled (see alignHeroSideElements),
       and an animation on the same element/property would overwrite
       that once it finishes. */
  .hero-photo-img { animation: heroPhotoIn 1.05s cubic-bezier(0.19, 1, 0.22, 1) both 0.55s; }
  .hero-left-stack { animation: heroFadeUp 0.8s cubic-bezier(0.19, 1, 0.22, 1) both 0.6s; }
  /* On each button, not the wrapper: an animated wrapper would cut the buttons' backdrop blur off from the hero. */
  .hero-cta-btn { animation: heroFadeUp 0.8s cubic-bezier(0.19, 1, 0.22, 1) both 0.7s; }
}
@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  .hero-kicker, .hero-name, .hero-photo-img, .hero-left-stack, .hero-cta-btn {
      animation-duration: 0.4s;
      animation-delay: 0s;
    }
}
@keyframes heroFadeUp {
   from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: translateY(0); } }
@keyframes heroNameIn {
   from { opacity: 0; transform: translateY(46px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes heroPhotoIn {
   from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
/* ============================================
   WHO I AM
============================================ */
.who-i-am {
  background: #fff;
  color: var(--navy);
  padding: 96px 0 8px;
  position: relative;
  overflow-x: clip;   /* the orbiting labels never widen the page (clip, not hidden, so the pinned panel still sticks) */
}
.who-i-am-inner {
  position: relative;
}
.who-i-am-heading { margin-bottom: 0; }
/* Heading + name tag share a row, name tag right-aligned against
   the heading's baseline area. */
.who-i-am-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px 24px;
  flex-wrap: wrap;
  margin-bottom: 56px;
}
/* Text (+ button) on the left, photo on the right. No scroll
   interaction of any kind. The photo column is the taller of the
   two, so align-items:end keeps it grounded at the row's bottom
   edge regardless of how many lines the copy wraps to; the text
   itself starts at the top, right under the heading. */
.who-i-am-row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;            /* the text box and the wheel share one horizontal centre line */
  max-width: 1120px;
  margin: 0 auto;                 /* and the pair sits in the middle of the page */
}
/* Comfortably sized for reading, clearly a step below the "Who I
   Am" display heading so the hierarchy stays obvious. */
.who-i-am-copy {
  max-width: 700px;
  justify-self: center;
  box-sizing: border-box;
  padding: 0;   /* no card: the copy sits straight on the section */
}
.who-i-am-copy p {
  margin: 0 0 22px;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1.15rem, 1.75vw, 1.55rem);
  line-height: 1.55;
  color: var(--navy);
}
.who-i-am-copy p:last-child { margin-bottom: 0; }
.who-i-am-btn { margin-top: 14px; }
/* Locked on desktop: the whole section stays on screen while the words light
   up (see initTextRevealScroll), then scrolls away once the last word is lit.
   Only when scripted reveal is active. */
@media (min-width: 901px) {
  .js-reveal .who-i-am { padding: 0; }
  .js-reveal .who-i-am::after { content: ''; display: block; height: 70vh; }
  .js-reveal .who-i-am-inner {
      position: sticky;
      /* Locks right under the nav, so About follows the hero with no band of
         empty space between them. */
      top: 0;
      min-height: 0;
      box-sizing: border-box;
      padding-top: 96px;
      padding-bottom: 24px;
    }
}
/* Phones/tablets only (the desktop pin needs sticky, which clip doesn't break
   anyway): stop the orbit's moving labels widening the page sideways. */
@media (max-width: 900px) {
  .who-i-am { overflow-x: clip; }
}
/* Stands in for the section's old "Who I Am" heading — same big
   display-heading font-size (matching .section-heading's own clamp
   exactly), but keeps this element's own font (family/weight/
   uppercase/letter-spacing), not the heading's. */
.who-i-am-tag {
  margin: 6px 0 0;
  font-size: clamp(2.3rem, 5.2vw, 3.8rem);
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--navy);
  /* flex-shrink:0 (kept when this was a short, narrow label sitting
     beside a much wider heading) now fights wrapping at this bigger
     font-size on its own — a flex item's default min-width:auto
     blocks it from shrinking/wrapping below its unwrapped text
     width, so at narrow viewports it just overflowed instead of
     wrapping onto a second line. min-width:0 is the standard fix. */
  min-width: 0;
}
.who-i-am-tag strong { color: var(--blue); }
/* ---------- Tools orbit ("Team Section" port) ----------
   Replaces the old .who-i-am-photo-col/.who-i-am-photo rules (photo
   moved to .contact-photo in the Contact section). Vanilla port of a
   Framer "Team Section" component: 3 concentric rings, alternating
   clockwise/counter-clockwise, continuously orbiting — each ring's
   own rotation is a plain CSS animation, and every avatar inside it
   runs a second animation that exactly cancels that rotation (see
   the keyframes below, which read the item's own --angle custom
   property) so the chip stays upright while its position still
   revolves around the center. Hovering any chip pauses the whole
   thing (initToolsOrbit, script.js, toggles .is-paused) — same
   "global pause on hover" behavior the source has. */
.tools-orbit {
  position: relative;
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  container-type: inline-size;
}
.tools-orbit-center {
  position: absolute;
  inset: 30%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 82, 255, 0.14) 0%, rgba(0, 82, 255, 0) 72%);
}
.tools-orbit-ring {
  position: absolute;
  inset: 0;
  margin: auto;
  border-radius: 50%;
  border: 1.5px solid var(--periwinkle);   /* the same periwinkle as the outline of the text box beside it */
  pointer-events: none;
}
/* TWEAK HERE: ring sizes (as % of .tools-orbit) and the 36s duration
   (shared by every ring/avatar keyframe below — keep them in sync if
   you change the speed, or the counter-rotation stops canceling out
   cleanly). */
.tools-orbit-ring--1 { width: 100%; height: 100%; }
.tools-orbit-ring--2 { width: 74%; height: 74%; }
.tools-orbit-ring--3 { width: 46%; height: 46%; }
@keyframes tools-orbit-cw {
   from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes tools-orbit-ccw {
   from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
/* A zero-width pivot line, not a full square — pivoting a whole
   inset:0 box (matching the ring's own size) around its center looks
   equivalent at first, but a rotated SQUARE's axis-aligned bounding
   box grows up to ~1.41x at 45deg, and that invisible overflow was
   real enough to add horizontal page scroll on mobile even though
   nothing was actually painted out there. A zero-width line rotated
   around its own bottom end has no such growth — only the small
   avatar at its tip extends past the ring, by a small, angle-
   independent amount. Spans from ring-center (this element's own
   bottom, height:50% = the ring's radius) out to the ring's edge;
   rotating around that bottom pivot swings the far end — where the
   avatar sits — around the circle. Being a child of the animated
   ring, it also inherits the ring's live rotation on top of its own
   static --angle offset (set inline per item in the HTML), which is
   what makes it orbit. */
.tools-orbit-item {
  position: absolute;
  top: 0;
  left: 50%;
  width: 0;
  height: 50%;
  transform-origin: bottom center;
  pointer-events: none;
  transform: rotate(var(--angle, 0deg));
}
.tools-orbit-avatar {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: auto;
  touch-action: none;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
}
.tools-orbit-avatar.is-dragged { cursor: grabbing; }
/* The translate(-50%,-50%) (centering the chip on the item's tip) has
   to live inside the keyframes themselves, not as a separate static
   `transform` on .tools-orbit-avatar — a CSS animation replaces the
   whole `transform` property outright once it starts, so a static
   declaration alongside it would just get overwritten (the same bug
   the hero photo hit earlier). Starts at -1*angle (canceling the
   item's own static offset) and animates a further ∓360deg over the
   same 36s the ring takes for a full turn — together that keeps the
   chip upright at every instant, not just at the start/end. */
@keyframes tools-orbit-avatar-ccw {
  
  from { transform: translate(-50%, -50%) rotate(calc(-1 * var(--angle))); }
  to   { transform: translate(-50%, -50%) rotate(calc(-1 * var(--angle) - 360deg)); }
}
@keyframes tools-orbit-avatar-cw {
  
  from { transform: translate(-50%, -50%) rotate(calc(-1 * var(--angle))); }
  to   { transform: translate(-50%, -50%) rotate(calc(-1 * var(--angle) + 360deg)); }
}
/* Pulsing halo — its own pseudo-element rather than living on
   .tools-orbit-avatar itself, since that element already owns an
   animated transform (the counter-rotation above); a second animated
   property on the same element would just overwrite it each frame,
   the exact bug the hero photo hit earlier this project. */
.tools-orbit-avatar::before {
  content: '';
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  background: rgba(0, 82, 255, 0.14);
  z-index: -1;
  animation: tools-orbit-pulse 3s ease-in-out infinite;
}
@keyframes tools-orbit-pulse {
  
  0%, 100% { transform: scale(1); opacity: 0.55; }
  50% { transform: scale(1.5); opacity: 0; }
}
/* Hover state lives on a third, non-animated layer (.tools-orbit-
   visual) for the same reason as the halo above. */
.tools-orbit-visual {
  display: block;
  transition: transform 0.3s ease, filter 0.3s ease;
}
.tools-orbit-avatar:hover .tools-orbit-visual {
  transform: scale(1.2);
  filter: brightness(1.1);
}
.tools-orbit-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sized as a share of the orbit (≈10.5%) so chips on neighbouring rings
     (13–14% apart) can never overlap at any orbit size. */
  width: 42px;
  height: 42px;   /* fallback for browsers without container units (older iPhones): without it the chip collapses to nothing */
  border-radius: 50%;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.3px;
  color: #fff;
  text-transform: uppercase;
  box-shadow: 0 6px 16px rgba(10, 0, 84, 0.35);
}
@supports (width: 1cqw) { .tools-orbit-chip { width: 10.5cqw; height: 10.5cqw; } }
@media (max-width: 900px) { .tools-orbit-chip { width: 34px; height: 34px; } @supports (width: 1cqw) { .tools-orbit-chip { width: 10.5cqw; height: 10.5cqw; } } }
/* A real tool logo: white disc with the logo centred inside. */
.tools-orbit-chip.tools-orbit-chip--logo { background: #fff; border: 1px solid rgba(12, 0, 84, 0.08); box-sizing: border-box; }
.tools-orbit-chip--logo img {
  display: block;
  width: 60%;
  height: 60%;
  object-fit: contain;
  border-radius: 6px;
}
.tools-orbit-label {
  position: absolute;
  bottom: -10px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--navy);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, bottom 0.3s ease;
}
.tools-orbit-avatar:hover .tools-orbit-label {
  opacity: 1;
  bottom: -24px;
}
/* Hovering any chip pauses the whole orbit — initToolsOrbit
   (script.js) toggles this class on the root; also applied while the
   section is off-screen (IntersectionObserver) as a cheap perf win,
   same as the source. */
.tools-orbit.is-paused .tools-orbit-ring,
.tools-orbit.is-paused .tools-orbit-avatar,
.tools-orbit.is-paused .tools-orbit-avatar::before {
  animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
  .tools-orbit-ring,
    .tools-orbit-avatar,
    .tools-orbit-avatar::before {
      animation: none;
    }
  /* Static equivalent of the keyframes' own "from" state — without
       it, disabling the animation would also drop the centering/
       upright-canceling transform that used to live inside it (see the
       comment above .tools-orbit-avatar), leaving every chip pinned by
       its corner instead of centered, and tilted at its raw --angle. */
  .tools-orbit-avatar {
      transform: translate(-50%, -50%) rotate(calc(-1 * var(--angle)));
    }
}
@media (max-width: 900px) {
  .tools-orbit { max-width: 320px; margin: 40px auto 0; }
}
/* ============================================
   IMPACT
   The headline-results moment — light background (deliberately
   distinct from the dark About above and the dark Work below) so
   the numbers themselves carry all the contrast. No cards, no
   borders: three stats given generous room to breathe rather than
   boxed in. */
.impact {
  background: var(--pale);
  padding: var(--section-pad) 0;
}
.impact-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(40px, 6vw, 72px);
  margin-top: 56px;
}
.impact-stat {
  text-align: center;
}
/* Deliberately bigger than .metric-value (case study pages, caps at
   2.15rem) and .work-card-stat strong (1.1rem) — this is the
   headline moment those two are quieter echoes of. */
.impact-value {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(3rem, 6vw, 5rem);
  line-height: 1;
  color: var(--blue);
}
.impact-label {
  max-width: 280px;
  margin: 18px auto 0;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
  color: rgba(12, 0, 84, 0.65);
}
/* ============================================
   SIDE QUESTS
============================================ */
.side-quests {
  background: var(--blue);
  padding: var(--section-pad) 0;
  overflow: hidden;
}
.side-quests-kicker {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-size: 0.85rem;
  color: var(--periwinkle);
  background: rgba(255, 255, 255, 0.12);
  padding: 6px 16px;
  border-radius: 999px;
  transform: rotate(-3deg);
  margin-bottom: 14px;
}
.side-quests-heading { transform: rotate(-1.5deg); }
.side-quests-intro {
  font-size: 1.05rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.88);
  max-width: 600px;
  margin-top: 18px;
}
/* ---------- Hover-expand carousel ("Ebbtide" port) ----------
   A vanilla-JS/CSS port of the "Ebbtide" Framer component's core
   mechanics: a seamless auto-scrolling track, drag-with-momentum, a
   hovered tile growing while its siblings dim, and a blurred "haze"
   backdrop that crossfades to match whichever tile is hovered. No
   React/Framer/framer-motion involved — see initSideQuestsCarousel
   in script.js for the animation/drag logic. Selectors that touch
   .img-placeholder are written as compound (.sq-tile .sq-tile-media)
   rather than relying on cascade order, since the shared
   .img-placeholder rules live later in this file and would otherwise
   win on equal specificity. */
.sq-carousel {
  position: relative;
  margin-top: 64px;
  margin-left: -24px;
  margin-right: -24px;
  border-radius: 26px;
  overflow: hidden;
  background: #161616;
}
.sq-carousel-haze {
  position: absolute;
  inset: -10%;
  background-size: cover;
  background-position: center;
  filter: blur(48px);
  transform: scale(1.25);
  opacity: 0;
  transition: opacity 0.8s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
.sq-carousel-haze.is-visible { opacity: 0.9; }
.sq-carousel-viewport {
  position: relative;
  overflow: hidden;
  padding: 28px 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 80px, #000 calc(100% - 80px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 80px, #000 calc(100% - 80px), transparent 100%);
}
.sq-carousel-track {
  display: flex;
  gap: 16px;
  width: max-content;
  padding: 0 24px;
  cursor: grab;
  touch-action: pan-y;
  will-change: transform;
}
.sq-carousel-track.is-dragging { cursor: grabbing; }
.sq-tile {
  position: relative;
  flex: 0 0 auto;
  width: 240px;
  height: 340px;
  border-radius: 14px;
  overflow: hidden;
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.55s cubic-bezier(0.22, 1, 0.36, 1), filter 0.4s ease;
  transform-origin: center;
}
.sq-tile.is-hovered {
  transform: scale(1.12);
  box-shadow: 0 34px 60px -24px rgba(0, 0, 0, 0.55);
  z-index: 2;
}
.sq-carousel-track.has-hover .sq-tile:not(.is-hovered) {
  filter: brightness(0.55);
}
.sq-tile .sq-tile-media {
  position: absolute;
  inset: 0;
  margin: 0;
  border-radius: 0;
  aspect-ratio: auto;
}
.sq-tile .sq-tile-media::after { display: none; }
.sq-tile-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0) 55%);
  pointer-events: none;
}
.sq-tile-emoji {
  position: absolute;
  top: 14px;
  left: 14px;
  font-size: 1.3rem;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.4));
}
.sq-tile-caption {
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 16px;
  transform: translate3d(0, 12px, 0);
  opacity: 0;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease;
}
.sq-tile.is-hovered .sq-tile-caption {
  transform: translate3d(0, 0, 0);
  opacity: 1;
}
.sq-tile-caption h3 {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 1.1rem;
  color: #fff;
  margin-bottom: 4px;
}
.sq-tile-caption p {
  font-size: 0.8rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.78);
}
.sq-tile-link {
  display: inline-block;
  margin-top: 8px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--periwinkle);
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* No hover capability — captions stay visible instead of relying on
   a gesture the device doesn't have. */
@media (hover: none) {
  .sq-tile-caption { opacity: 1; transform: translate3d(0, 0, 0); }
}
@media (max-width: 640px) {
  .sq-tile { width: 180px; height: 260px; }
  .sq-carousel-viewport { padding: 20px 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sq-tile, .sq-tile-caption, .sq-carousel-haze { transition: none; }
}
.sq-outro {
  margin-top: 88px;
  text-align: center;
  max-width: 680px;
  margin-left: auto;
  margin-right: auto;
}
.sq-outro-heading {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.5rem, 3.2vw, 2.2rem);
  color: #fff;
  line-height: 1.3;
  margin-bottom: 16px;
}
.sq-outro-copy {
  font-size: 1.05rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.85);
  margin-bottom: 28px;
}
.sq-social-icons {
  display: flex;
  justify-content: center;
  gap: 14px;
  margin-top: 28px;
}
/* ============================================
   WHAT I DO — draggable pill strip
============================================ */
.pill-strip-section {
  padding: 40px 0;
}
.pill-strip-label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: rgba(12, 0, 84, 0.5);
  margin-bottom: 18px;
}
/* Full-bleed to the viewport's right edge (so the last pill sits
   visibly cut off) while the first pill still lines up with the
   label above — the left padding mirrors .section-inner's own
   centering math so the two stay aligned at any width. Scrollbar is
   hidden but the element is still a normal scroll container, so
   trackpad/wheel/touch scrolling all keep working even without the
   drag JS. */
.pill-strip-track {
  overflow-x: auto;
  overflow-y: hidden;
  cursor: grab;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-left: max(24px, calc((100vw - 1180px) / 2 + 24px));
  padding-right: 24px;
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 90%, transparent 100%);
  mask-image: linear-gradient(to right, #000 0%, #000 90%, transparent 100%);
}
.pill-strip-track::-webkit-scrollbar { display: none; }
.pill-strip-track.is-dragging {
  cursor: grabbing;
  scroll-behavior: auto;
}
.pill-strip-inner {
  display: flex;
  gap: 12px;
  width: max-content;
}
.pill-strip-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.92rem;
  padding: 13px 24px;
  border-radius: 18px;
  background: var(--periwinkle);
  color: var(--navy);
  user-select: none;
  transition: background-color 0.25s ease, color 0.25s ease;
}
@media (hover: hover) {
  .pill-strip-pill:hover { background: var(--blue); color: #fff; }
}
/* ============================================
   SKILL DETAIL MODAL
============================================ */
body.modal-open { overflow: hidden; }
.skill-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.skill-modal.is-open {
  opacity: 1;
  pointer-events: auto;
}
.skill-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(12, 0, 84, 0.55);
}
.skill-modal-panel {
  position: relative;
  background: var(--pale);
  border-radius: 24px;
  max-width: 520px;
  width: 100%;
  /* .skill-modal wraps this in 24px of padding on every side, so
     this is exactly the space left over — taller content scrolls
     inside the panel itself instead of getting clipped by the
     viewport (the fixed modal wrapper has no scroll of its own). */
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 44px 36px 40px;
  box-shadow: 0 30px 60px rgba(4, 0, 60, 0.4);
  transform: scale(0.94) translateY(10px);
  transition: transform 0.25s ease;
}
.skill-modal.is-open .skill-modal-panel {
  transform: scale(1) translateY(0);
}
.skill-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(12, 0, 84, 0.08);
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--navy);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.2s ease;
}
.skill-modal-close:hover { background: rgba(12, 0, 84, 0.16); }
.skill-modal-title {
  font-family: var(--font-display);
  font-weight: normal;
  font-size: clamp(1.8rem, 4vw, 2.4rem);
  line-height: 1;
  color: var(--blue);
  margin-bottom: 18px;
  padding-right: 24px;
}
.exp-modal-head {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 24px;
}
.exp-modal-logo { flex-shrink: 0; }
.exp-modal-title { margin-bottom: 6px; padding-right: 0; }
.exp-modal-role {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--navy);
}
.exp-modal-date {
  font-size: 0.85rem;
  font-style: italic;
  color: var(--blue);
  margin-top: 2px;
}
.exp-modal-body p {
  font-size: 1.02rem;
  line-height: 1.7;
  color: var(--navy);
  margin-bottom: 16px;
}
.exp-modal-body p:last-child { margin-bottom: 0; }
/* ============================================
   VIDEO MODAL
   Same open/close mechanics as the skill/exp modals (via
   initDetailModal), sized for a portrait video instead of text.
============================================ */
.video-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.video-modal.is-open {
  opacity: 1;
  pointer-events: auto;
}
.video-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(12, 0, 84, 0.7);
}
.video-modal-panel {
  position: relative;
  width: 100%;
  max-width: 380px;
  max-height: 85vh;
  aspect-ratio: var(--video-aspect, 4 / 5);
  border-radius: 24px;
  overflow: hidden;
  background: #000;
  box-shadow: 0 30px 60px rgba(4, 0, 60, 0.45);
  transform: scale(0.94) translateY(10px);
  transition: transform 0.25s ease;
}
.video-modal.is-open .video-modal-panel {
  transform: scale(1) translateY(0);
}
.video-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  transition: background-color 0.2s ease;
}
.video-modal-close:hover { background: rgba(255, 255, 255, 0.3); }
.video-modal-player {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  background: #000;
}
/* Same visual language as the thumbnail's play icon, just bigger —
   shown while paused, hidden once playback starts. */
.video-modal-play-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 92px;
  height: 92px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(12, 0, 84, 0.55);
  cursor: pointer;
  transition: opacity 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
  z-index: 1;
}
.video-modal-play-icon:hover {
  background: rgba(12, 0, 84, 0.75);
  transform: translate(-50%, -50%) scale(1.08);
}
.video-modal-play-icon::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-40%, -50%);
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 17px 0 17px 27px;
  border-color: transparent transparent transparent #fff;
}
.video-modal-panel.is-playing .video-modal-play-icon {
  opacity: 0;
  pointer-events: none;
}
/* ============================================
   EXPERIENCE (I'VE WORKED AT)
============================================ */
.experience {
  background: #fff;
  padding: 56px 0;
}
.experience-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(24px, 5vw, 60px);
  row-gap: 20px;
  margin-top: 48px;
}
.exp-card {
  position: relative;
  display: block;
  width: 100%;
  background: #fff;
  border: none;
  border-radius: 18px;
  padding: 22px 56px 22px 22px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: transform 0.25s ease, box-shadow 0.25s ease, background-color 0.25s ease;
}
.exp-card:hover {
  background: var(--periwinkle);
  transform: translateY(-3px);
  box-shadow: 0 14px 26px rgba(12, 0, 84, 0.16);
}
.exp-card:hover .exp-plus {
  background: var(--navy);
  transform: translateY(-50%) rotate(90deg);
}
.exp-head {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 16px;
}
.exp-card h3 {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--navy);
  text-transform: uppercase;
}
.exp-role {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--navy);
  margin-top: 4px;
}
.exp-date {
  font-size: 0.85rem;
  font-style: italic;
  color: var(--blue);
  margin-top: 2px;
}
.exp-plus {
  position: absolute;
  right: 18px;
  top: 50%;
  transform: translateY(-50%);
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--blue);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  font-weight: 700;
  line-height: 1;
  transition: transform 0.25s ease, background-color 0.25s ease;
}
.exp-logo {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--pale);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.exp-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.exp-logo--lg {
  width: 64px;
  height: 64px;
  border-radius: 16px;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.exp-logo--lg:hover {
  transform: translateY(-4px);
  box-shadow: 0 10px 20px rgba(12, 0, 84, 0.18);
}
/* ============================================
   FULL EXPERIENCE (SHOWCASE)
============================================ */
.exp-showcase {
  background: var(--pale);
  padding: var(--section-pad) 0;
}
.exp-showcase--periwinkle { background: var(--periwinkle); }
.exp-media-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  margin-top: 48px;
}
.exp-media-card {
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.exp-media-card .img-placeholder { margin-bottom: 16px; }
.exp-media-card h3 {
  font-size: 1.08rem;
  font-weight: 700;
  color: var(--navy);
  text-transform: uppercase;
}
.exp-media-card:hover {
  transform: translateY(-6px);
}
/* ============================================
   WORK
============================================ */
.work {
  position: relative;
  isolation: isolate;
  background: #fff;                 /* the white page carries on down both sides of the blue panel */
  padding: var(--section-pad) 0;
}
.work::before {                     /* the blue panel, with softly curved corners */
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: var(--work-gutter); right: var(--work-gutter);
  /* white margin down each side: 18px on phones, 32px at 1280, 40px at 1600, then growing to a 56px cap by 1920 */
  --work-gutter: max(clamp(18px, 2.5vw, 40px), min(56px, calc(40px + (100vw - 1600px) * 0.05)));
  background: var(--pale);
  border-radius: 36px;
  z-index: -1;
}
.work .section-heading--light { color: var(--blue); }
.work .btn-pill-light { background: var(--blue); border-color: var(--blue); color: #fff; }
.work .btn-pill-light:hover { background: var(--navy); border-color: var(--navy); color: #fff; box-shadow: 0 10px 22px rgba(12, 0, 84, 0.3); }
.marquee {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  cursor: grab;
  touch-action: pan-y;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
}
.marquee::-webkit-scrollbar { display: none; }
.marquee.is-dragging { cursor: grabbing; }
.marquee-track {
  display: flex;
  width: max-content;
  gap: 28px;
  will-change: transform;
  perspective: 1200px;
}
.marquee-track a { -webkit-user-drag: none; user-select: none; }
/* A static 3-column grid, two rows — no auto-scroll or drag, just the
   six projects laid out plainly. */
.work-page {
  background: #fff;
  padding: 128px 0 var(--section-pad);
}
.work-page-heading { margin: 0 0 clamp(28px, 4vw, 48px); text-align: center; }
.page-intro--centered { text-align: center; }
.page-intro--centered p { margin-left: auto; margin-right: auto; }
.work-grid {
  margin-top: 48px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.work-grid-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 28px;
}
/* Two of the six cards break the portrait grid — full-width,
   image beside copy instead of image above it, exactly the
   landscape/portrait mix from the reference layout. */
.work-grid-row--landscape { grid-template-columns: 1fr; }
.work-card.work-card--landscape {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: stretch;
  gap: 28px;
}
.work-grid .work-card--landscape .img-placeholder {
  margin-bottom: 0;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}
.work-card--landscape .work-card-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}
.work-card--landscape .work-card-body p { -webkit-line-clamp: 3; }
/* the category pill is as wide as its text on the wide cards too (the body is a column, which would stretch it) */
.work-card--landscape .work-card-body .work-card-pill { align-self: flex-start; width: fit-content; }
/* Coverflow tilt (rotateY/scale/translateY) is written to each card's
   inline transform every frame by JS, so it owns that property —
   only box-shadow gets a CSS transition here. Still used by the
   Brand Storytelling marquee below, which shares this class. */
.work-card {
  display: block;
  flex: 0 0 auto;
  width: 300px;
  background: var(--pale);
  border-radius: 20px;
  padding: 16px;
  transition: background-color 0.3s ease;
  will-change: transform;
}
.work .work-card { background: #fff; }
.work .work-card:hover { background: var(--periwinkle); }
.work-card:hover { background: var(--periwinkle); }
.work-card-pill { transition: background-color 0.3s ease, color 0.3s ease; }
.work-card:hover .work-card-pill { background: var(--blue); color: #fff; }
.work-card .img-placeholder { margin-bottom: 16px; }
/* The Work section's own grid isn't a marquee — cards fill their
   column instead of holding the marquee's fixed card width. */
.work-grid-row .work-card {
  flex: initial;
  width: 100%;
  will-change: auto;
}
/* The two video projects get a translucent play badge over their
   marquee thumbnail, same visual language as the storytelling page's
   video thumbnails, so it reads as playable content at a glance. */
.work-card-media { position: relative; display: block; }
.work-card-media--video::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 48px;
  height: 48px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(12, 0, 84, 0.5);
  pointer-events: none;
}
.work-card-media--video::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-42%, -50%);
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 9px 0 9px 14px;
  border-color: transparent transparent transparent #fff;
  z-index: 1;
  pointer-events: none;
}
/* Category pill — same visual recipe as .pill-strip-pill (the "What
   I Do" strip): periwinkle fill, no border/shadow, fully rounded.
   Single line by design so it never adds a variable amount of
   height across cards. */
.work-card-pill {
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 5px 12px;
  border-radius: 18px;
  background: var(--periwinkle);
  color: var(--navy);
  margin-bottom: 10px;
}
.work-card h3 {
  font-size: 1.08rem;
  font-weight: 700;
  color: var(--navy);
  text-transform: uppercase;
  margin-bottom: 8px;
}
.work-card p {
  font-size: 0.9rem;
  line-height: 1.55;
  color: rgba(12, 0, 84, 0.75);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
/* Results — two "bold number, small label" stats per card, the same
   spirit as .metric-callout on the case study pages but scaled down
   for a compact homepage card, with no background box of its own so
   it stays light. The label is clamped to 2 lines with a matching
   min-height reserved, so a short label and a long one still leave
   every card the same height. */
.work-card-results {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(12, 0, 84, 0.1);
}
.work-card-stat {
  min-width: 0;
}
.work-card-stat strong {
  display: block;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1.1rem;
  line-height: 1.2;
  color: var(--blue);
  overflow-wrap: break-word;
}
.work-card-stat span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  margin-top: 3px;
  min-height: 2.6em;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.3;
  color: rgba(12, 0, 84, 0.6);
}
/* ============================================
   WORK DETAIL (CASE STUDY SLIDES)
============================================ */
.work-slide { padding: var(--section-pad) 0; }
.work-slide--pale { background: var(--pale); }
.work-slide--periwinkle { background: var(--periwinkle); }
.work-slide-title {
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  margin-bottom: 40px;
}
.work-slide-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(28px, 5vw, 56px);
  align-items: start;
}
/* Grid items default to min-width:auto, which respects their content's
   min-content size over the fr ratio above — an unbreakable token like
   a metric placeholder ([METRIC_1_VALUE], no spaces to wrap on) would
   otherwise force that column wide regardless of what fr says. */
.work-slide-grid > * { min-width: 0; }
.work-slide-media {
  aspect-ratio: 1 / 1;
  width: 100%;
}
img.work-slide-media {
  border-radius: 16px;
  object-fit: cover;
  display: block;
}
/* Brand Storytelling: presentation-style widescreen framing (matching
   the aspect ratio decks/slides are actually exported at), except the
   video-recap thumbnails, which use the video's own native frame. */
body[data-page="storytelling"] .work-slide-media {
  aspect-ratio: 16 / 9;
}
body[data-page="storytelling"] .work-slide-media--video {
  aspect-ratio: var(--video-aspect, 4 / 5);
}
/* Each project's image links to the same place its CTA does — except
   video thumbnails, which open the video modal instead (button, not a). */
a.work-slide-media-link,
button.work-slide-media-link {
  display: block;
  width: 100%;
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font: inherit;
  text-align: inherit;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
a.work-slide-media-link::after { content: none; }
a.work-slide-media-link:hover,
button.work-slide-media-link:hover {
  transform: translateY(-4px);
  box-shadow: 0 18px 34px rgba(4, 0, 60, 0.22);
}
/* Video thumbnails get a play-button affordance overlaid on the frame. */
button.work-slide-media-link { position: relative; }
button.work-slide-media-link::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 64px;
  height: 64px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(12, 0, 84, 0.55);
  transition: background-color 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
button.work-slide-media-link:hover::after {
  background: rgba(12, 0, 84, 0.75);
  transform: translate(-50%, -50%) scale(1.08);
}
button.work-slide-media-link::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-42%, -50%);
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 12px 0 12px 19px;
  border-color: transparent transparent transparent #fff;
  z-index: 1;
  pointer-events: none;
}
.work-slide-brief h3 {
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--navy);
  margin-bottom: 8px;
}
.work-slide-brief p {
  font-size: 1rem;
  line-height: 1.65;
  color: rgba(12, 0, 84, 0.85);
  margin-bottom: 28px;
}
.story-slide-copy p {
  font-size: 1.05rem;
  line-height: 1.7;
  color: rgba(12, 0, 84, 0.85);
  margin-bottom: 28px;
}
.story-slide-copy p a {
  color: var(--blue);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.story-slide-copy p a:hover { color: var(--indigo); }
.work-slide-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
.work-slide-cards > * { min-width: 0; }
/* same fr-vs-min-content issue as .work-slide-grid */
.slide-card {
  background: rgba(255, 255, 255, 0.55);
  border-radius: 18px;
  padding: 22px;
}
.slide-card--results {
  background: rgba(10, 0, 203, 0.1);
}
.slide-card h4 {
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--navy);
  margin-bottom: 12px;
}
.slide-card ul { display: flex; flex-direction: column; gap: 9px; }
.slide-card li {
  font-size: 0.88rem;
  line-height: 1.5;
  color: rgba(12, 0, 84, 0.85);
  padding-left: 16px;
  position: relative;
}
.slide-card li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 8px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--blue);
}
.slide-card li a {
  color: var(--blue);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.slide-card li a:hover { color: var(--indigo); }
/* Bullets read as short, confident fragments rather than sentences —
   only Work/Events use .slide-card, so this is safe to make bolder
   than a normal list item without affecting Brand Storytelling. */
.slide-card li { font-weight: 600; }
.slide-card-link {
  display: inline-block;
  margin-top: 14px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.slide-card-link:hover { color: var(--indigo); }
/* ============================================
   METRIC CALLOUTS
   Big-number stat cards replacing prose "Results" bullets. Values
   are placeholder tokens ([METRIC_N_VALUE]) until real figures are
   finalised — see slide-card--results usage in work.html/events.html.
============================================ */
/* Grid, not flexbox — minmax() caps each callout's growth at its
   share of the container's actual width. A flex-grow item has no
   such ceiling: it grows toward its content's max-content size,
   which for a single wide callout dragged the whole card (and its
   ancestors) wider than the layout, causing horizontal overflow. */
.metric-callouts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 16px;
}
.metric-callout {
  min-width: 0;
  background: rgba(255, 255, 255, 0.55);
  border-radius: 14px;
  padding: 16px 18px;
}
.metric-value {
  display: block;
  /* Body font, not the display/marker face — Sprite Graffiti has no
     glyphs for [ ] _, so it silently drops them, and a placeholder
     token needs those brackets to actually read as a placeholder. */
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.5rem, 3.2vw, 2.15rem);
  line-height: 1.15;
  color: var(--blue);
  /* [METRIC_1_VALUE] has no spaces, so it can't wrap at a word break
     like normal text — force a break instead of letting it push the
     card (and its whole grid column) wider than intended. */
  overflow-wrap: break-word;
}
.metric-label {
  display: block;
  margin-top: 8px;
  font-size: 0.76rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: rgba(12, 0, 84, 0.65);
}
/* ============================================
   CASE STUDY LAYOUT MOMENTS
   Three deliberately reused patterns, cycled across Work/Events
   projects (hero -> split -> overlay -> repeat) rather than every
   card getting a one-off bespoke layout. Applied via a modifier on
   .work-slide; all three build on the same .work-slide-grid markup
   so Brand Storytelling (which doesn't use these modifiers) is
   untouched.
============================================ */
/* Hero: image dominates full-width up top, condensed copy below. */
.work-slide--hero .work-slide-grid {
  grid-template-columns: 1fr;
}
.work-slide--hero .work-slide-media {
  margin-bottom: 8px;
}
.work-slide--hero .work-slide-copy {
  max-width: 760px;
  margin: 0 auto;
}
/* Split: image takes the larger share of the two columns (inverted
   from the old text-led default) rather than playing a supporting role.
   What I Did / Results stack instead of sitting side by side, since
   the narrower copy column doesn't have room for two more columns
   inside it without cramping the metric callouts. */
.work-slide--split .work-slide-grid {
  grid-template-columns: 1.15fr 0.85fr;
}
.work-slide--split .work-slide-cards {
  grid-template-columns: 1fr;
}
/* Overlay: image fills the whole card as a backdrop; copy sits over
   a gradient scrim on top of it. The most stripped-down of the three
   moments on purpose — brief + metrics only, no bullet list — so the
   image reads as the whole point of the card. Constrained to a
   centered "poster" width (rather than the full section width hero
   uses) to read as a distinct third layout. The grid itself is the
   constrained box — .work-slide-copy's inset:0 is relative to it —
   so media and the copy overlay always share the same box at any
   viewport width. */
.work-slide--overlay .work-slide-grid {
  display: block;
  position: relative;
  max-width: 600px;
  margin: 0 auto;
}
.work-slide--overlay .work-slide-media {
  border-radius: 20px;
}
.work-slide--overlay .work-slide-copy {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(24px, 4vw, 40px);
  border-radius: 20px;
  /* Darkens gradually from the top so the brief text (sitting in the
     middle of this zone) has real contrast too, not just Results at
     the very bottom — legibility shouldn't depend on the source
     image's own tone once real photos replace these placeholders. */
  background: linear-gradient(180deg, rgba(12, 0, 84, 0) 8%, rgba(12, 0, 84, 0.6) 48%, rgba(12, 0, 84, 0.92) 100%);
}
.work-slide--overlay .work-slide-brief h3 { color: rgba(255, 255, 255, 0.7); }
.work-slide--overlay .work-slide-brief p { color: #fff; margin-bottom: 20px; }
.work-slide--overlay .slide-card { background: rgba(255, 255, 255, 0.14); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.work-slide--overlay .slide-card--results { background: rgba(255, 255, 255, 0.2); }
.work-slide--overlay .slide-card h4 { color: rgba(255, 255, 255, 0.75); }
.work-slide--overlay .metric-callout { background: rgba(255, 255, 255, 0.16); }
.work-slide--overlay .metric-value { color: #fff; }
.work-slide--overlay .metric-label { color: rgba(255, 255, 255, 0.75); }
.work-slide--overlay .slide-card-link { color: #fff; }
.work-slide--overlay .slide-card-link:hover { color: rgba(255, 255, 255, 0.8); }
.work-detail-outro {
  background: #fff;
  padding: var(--section-pad) 0;
  text-align: center;
}
.work-outro-heading {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.5rem, 3.2vw, 2.3rem);
  color: var(--blue);
  max-width: 720px;
  margin: 0 auto 16px;
  line-height: 1.3;
}
.work-outro-copy {
  font-size: 1.1rem;
  color: var(--navy);
  margin-bottom: 28px;
}
/* ============================================
   EVENTS
============================================ */
.events {
  background: #fff;
  padding: 0;
}
/* Sticky, scroll-driven full-bleed gallery — an original recreation of
   Framer's "ImageScroller": the outer track is tall (one scroll "run"
   per event), the inner panel pins via position:sticky while it does,
   and a scroll listener (initEventsScroller in script.js) works out
   which event is active and cross-fades to it. The thumbnail dock
   lets you jump straight to one instead of scrolling through. */
.events-scroller {
  position: relative;
  width: 100%;
  margin-top: 0;
  height: 260vh; /* 5 events × 60vh of scroll travel each */
}
.events-scroller-sticky {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
  background: transparent;
}
.events-scroller-head {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 3;
  padding-top: 92px;
}
.events-scroller-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease;
  pointer-events: none;
}
.events-scroller-slide.is-active {
  opacity: 1;
  pointer-events: auto;
  z-index: 1;
}
.events-scroller-slide-link { display: block; width: 100%; height: 100%; }
/* Real event images: the poster/photo sits whole in the middle of the section, so
   nothing is cropped whatever its shape. */
.events-scroller-media--img {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 196px 24px 136px;
  overflow: hidden;
  background: transparent;
}
.events-scroller-photo {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 22px;
}
.events-scroller-media {
  width: 100%;
  height: 100%;
  border-radius: 0;
  aspect-ratio: auto;
  margin-bottom: 0;
}
/* This gallery has its own big centered caption — suppress the
   generic img-placeholder label strip so it doesn't also render. */
.events-scroller-media::after,
.events-scroller-thumb-media::after {
  display: none;
}
.events-scroller-caption {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 32px clamp(24px, 5vw, 72px);
  pointer-events: none;
}
.events-scroller-caption h3 {
  color: var(--navy);
  font-family: var(--font-body);
  font-weight: 700;
  text-transform: uppercase;
  text-align: left;
  font-size: clamp(1.3rem, 2.8vw, 2.4rem);
  line-height: 1.15;
  max-width: 11ch;
}
.events-scroller-dock {
  position: absolute;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  gap: 10px;
  padding: 10px;
  background: rgba(12, 0, 84, 0.1);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-radius: 20px;
}
.events-scroller-thumb {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
}
@media (max-width: 480px) {
  .events-scroller-dock { max-width: calc(100vw - 16px); gap: 6px; padding: 8px; }
  .events-scroller-thumb { width: 52px; height: 52px; flex: 0 1 52px; }
}
.events-scroller-thumb-outline {
  position: absolute;
  inset: -4px;
  border-radius: 16px;
  border: 2px solid transparent;
  transition: border-color 0.2s ease;
  pointer-events: none;
}
.events-scroller-thumb.is-active .events-scroller-thumb-outline { border-color: var(--navy); }
.events-scroller-thumb-media {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 12px;
  aspect-ratio: auto;
  margin-bottom: 0;
  opacity: 0.55;
  transition: opacity 0.2s ease;
}
.events-scroller-thumb.is-active .events-scroller-thumb-media { opacity: 1; }
.events-scroller-thumb:hover .events-scroller-thumb-media { opacity: 0.85; }
@media (max-width: 900px) {
  /* Narrow screens: the title goes across the top, the picture sits below it. */
  .events-scroller-caption { align-items: flex-start; justify-content: center; padding: 140px 24px 20px; }
  .events-scroller-caption h3 { text-align: center; max-width: 22ch; font-size: clamp(1.1rem, 4.6vw, 1.6rem); }
  .events-scroller-media--img { padding: 226px 20px 124px; }
}
img.events-scroller-thumb-media { object-fit: cover; }
@media (prefers-reduced-motion: reduce) {
  .events-scroller-slide { transition: none; }
}
/* ============================================
   BRAND STORYTELLING
============================================ */
.brand-storytelling {
  background: #fff;
  padding: var(--section-pad) 0;
}
/* Square framing to match the Work page's case-study images, rather
   than the default 4:3 card crop. */
.work-grid .img-placeholder { aspect-ratio: 1 / 1; }
/* ============================================
   BRAND STORYTELLING GALLERY
   One large card holding every story image side by side — a static
   bento wall (no slider) modelled on Framer's "Gallery" component.
   Each tile keeps its own image's true aspect ratio so nothing gets
   cropped; the caption is hidden until hover/focus, when the image
   blurs and dims behind it, same as the reference component.
============================================ */
.storytelling-gallery {
  margin-top: 48px;
  background: #fff;
  border-radius: 28px;
  padding: 16px;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 12px;
}
.storytelling-tile--wide { grid-column: span 4; }
.storytelling-tile--narrow { grid-column: span 2; }
.storytelling-tile--half { grid-column: span 3; }
.storytelling-tile {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 14px;
  cursor: pointer;
}
.storytelling-tile > img,
.storytelling-tile .work-card-media {
  display: block;
  width: 100%;
}
.storytelling-tile .work-card-media img {
  width: 100%;
  height: 100%;
}
.storytelling-tile img {
  object-fit: cover;
  transition: filter 0.35s ease;
}
.storytelling-tile:hover img,
.storytelling-tile:focus-visible img,
.storytelling-tile:active img {
  filter: blur(6px);
}
.storytelling-tile-overlay {
  position: absolute;
  inset: 0;
  background: rgba(8, 4, 30, 0.5);
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.storytelling-tile:hover .storytelling-tile-overlay,
.storytelling-tile:focus-visible .storytelling-tile-overlay,
.storytelling-tile:active .storytelling-tile-overlay {
  opacity: 1;
}
.storytelling-tile-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  margin: 0;
  padding: 20px;
  color: #fff;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1.05rem;
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}
.storytelling-tile:hover .storytelling-tile-caption,
.storytelling-tile:focus-visible .storytelling-tile-caption,
.storytelling-tile:active .storytelling-tile-caption {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .storytelling-tile img,
    .storytelling-tile-overlay,
    .storytelling-tile-caption { transition: none; }
}
/* ============================================
   IMAGE PLACEHOLDERS
============================================ */
.img-placeholder {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 16px;
  overflow: hidden;
  background-image: repeating-linear-gradient(135deg, var(--periwinkle) 0 16px, var(--pale) 16px 32px);
  display: flex;
  align-items: flex-end;
}
.img-placeholder--accent {
  background-image: repeating-linear-gradient(135deg, rgba(255,255,255,0.35) 0 16px, rgba(255,255,255,0.1) 16px 32px);
}
img.img-placeholder {
  object-fit: cover;
  display: block;
}
.img-placeholder::after {
  content: attr(data-label);
  display: block;
  width: 100%;
  padding: 8px 12px;
  background: rgba(12, 0, 84, 0.55);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.3;
}
/* Frame color variants — used to vary placeholder frames instead of
   repeating the same default periwinkle/pale stripe everywhere. */
.img-placeholder--v1 { background-image: repeating-linear-gradient(135deg, var(--indigo) 0 16px, var(--blue) 16px 32px); }
.img-placeholder--v2 { background-image: repeating-linear-gradient(135deg, var(--navy) 0 16px, var(--periwinkle) 16px 32px); }
.img-placeholder--v3 { background-image: none; background-color: #fff; }
.img-placeholder--v3::after { background: rgba(12, 0, 84, 0.12); color: var(--navy); }
.img-placeholder--v4 { background-image: repeating-linear-gradient(135deg, var(--periwinkle) 0 16px, var(--blue) 16px 32px); }
.img-placeholder--v5 { background-image: repeating-linear-gradient(135deg, var(--indigo) 0 16px, var(--navy) 16px 32px); }
.img-placeholder--v1::after,
.img-placeholder--v5::after { background: rgba(255, 255, 255, 0.2); }
/* ============================================
   CONTACT / FOOTER
============================================ */
.contact {
  background: #fff;
  padding-top: var(--section-pad);
}
.contact-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: clamp(32px, 6vw, 80px);
  align-items: center;
  padding-bottom: 56px;
}
.contact-heading { margin-bottom: 22px; }
.contact-copy p {
  font-size: 1.15rem;
  line-height: 1.7;
  color: var(--navy);
  margin-bottom: 16px;
  max-width: 520px;
}
.contact-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 24px;
}
.contact-copy .social-icons { margin-top: 28px; }
/* Photo with its "Work With Me!" tag, left-aligned in the right column. */
.contact-side {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  text-align: left;
}
.contact-photo {
  width: 100%;
  max-width: 340px;
}
.contact-photo-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 20px;
  filter: grayscale(1) contrast(1.05);
}
.contact-tag {
  color: var(--blue);
  font-size: clamp(2rem, 4vw, 3rem);
}
.social-icons { display: flex; gap: 14px; }
.social-icon {
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--pale);
  color: var(--navy);
  transition: transform 0.25s ease, box-shadow 0.25s ease, background-color 0.25s ease, color 0.25s ease;
}
.social-icon svg { width: 20px; height: 20px; }
.social-icon:hover {
  background: var(--blue);
  color: #fff;
  transform: translateY(-3px);
  box-shadow: 0 10px 20px rgba(0, 82, 255, 0.25);
}
.brands-strip {
  border-top: 1px solid rgba(12, 0, 84, 0.12);
  padding: 36px 0 48px;
}
.brands-label {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: rgba(12, 0, 84, 0.55);
  margin-bottom: 18px;
}
.brands-logos {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
.contact-bar {
  background: var(--blue);
  color: #fff;
  padding: 22px 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 0.85rem;
}
.contact-bar a { font-weight: 600; }
.contact-bar a:hover { text-decoration: underline; }
/* ============================================
   INTERACTIVE FOOTER (homepage only)
   Vanilla rebuild of a Framer "Interactive Footer" component,
   matching its exact arrangement top to bottom: atmosphere
   background → spotlight glow → big Contact CTA → divider → a
   directory block (Explore/Follow link columns, z-index:2, laid
   over an oversized floating character on its own blurred shadow,
   z-index:auto so the columns win any overlap — same rule the
   source uses) → divider → a legal row (copyright + Privacy/Terms).
   Only the character's design departs from the source: illustrated
   artwork there, a plain-CSS Minion here (see the "MINION" rules
   below) — same eye-tracking/blink/tilt rig either way, driven by
   the --mascot-* custom properties initFooterMascot (script.js)
   writes every frame; the values here are just their pre-JS
   fallbacks. */
.site-footer {
  position: relative;
  overflow: hidden;
  width: calc(100% - 32px);
  max-width: var(--container);
  margin: 0 auto 16px;
  border-radius: 28px;
  padding: 48px 48px 18px;
  background: var(--pale);
  color: var(--navy);
}
.footer-atmosphere {
  position: absolute;
  inset: 0;
  background: linear-gradient(145deg, #fbfbfd 0%, #e9edf7 46%, #c7d3ee 100%);
  pointer-events: none;
}
.footer-spotlight {
  position: absolute;
  top: 130px;
  left: 50%;
  transform: translateX(-50%);
  width: min(660px, 55vw);
  height: min(600px, 50vw);
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(255, 255, 255, 0.96) 0%, rgba(191, 212, 255, 0.7) 30%, rgba(191, 212, 255, 0.22) 52%, rgba(191, 212, 255, 0) 74%);
  filter: blur(6px);
  pointer-events: none;
}
/* Big rolling headline + circular arrow — the source's "Contact"
   sub-component is a CTA link, not an actual multi-field form. */
.footer-cta {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  padding: 12px 0 28px;
  color: var(--navy);
}
.footer-cta-text {
  font-family: var(--font-display);
  font-weight: normal;
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  line-height: 1;
}
.footer-cta-arrow {
  flex-shrink: 0;
  width: clamp(52px, 6vw, 76px);
  height: clamp(52px, 6vw, 76px);
  border-radius: 50%;
  border: 2px solid rgba(12, 0, 84, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.25s ease, border-color 0.25s ease, transform 0.25s ease;
}
.footer-cta-arrow svg { width: 42%; height: 42%; transition: color 0.25s ease; }
.footer-cta:hover .footer-cta-arrow {
  background: var(--blue);
  border-color: var(--blue);
  transform: rotate(45deg);
}
.footer-cta:hover .footer-cta-arrow svg { color: #fff; }
.footer-divider {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 1px;
  background: rgba(12, 0, 84, 0.14);
}
.footer-directory {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  min-height: 420px;
  padding-top: 32px;
}
.footer-columns {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 40px;
  width: 100%;
}
.footer-col { display: flex; flex-direction: column; }
.footer-col--follow { align-items: flex-start; }
.footer-col-label {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: rgba(12, 0, 84, 0.5);
  margin-bottom: 14px;
}
.footer-link {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--navy);
  padding: 6px 0;
  transition: color 0.2s ease;
}
.footer-link:hover { color: var(--blue); }
.footer-mascot-shadow {
  position: absolute;
  bottom: 18px;
  left: 50%;
  transform: translateX(-50%);
  width: 200px;
  height: 32px;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(12, 0, 84, 0.35) 0%, rgba(12, 0, 84, 0) 72%);
  filter: blur(8px);
}
.footer-mascot-stage {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* ---------- MINION ----------
   Plain CSS shapes standing in for the source's illustrated
   character — same overall footprint/behavior contract (oversized,
   centered, floats behind the link columns), swapped design only. */
.footer-mascot {
  position: relative;
  width: min(180px, 22vw);
  height: min(232px, 28vw);
  pointer-events: auto;
  transform:
    translateY(var(--mascot-breathe, 0px))
    perspective(1000px)
    rotateX(var(--mascot-depth-x, 0deg))
    rotateY(var(--mascot-depth-y, 0deg))
    rotateZ(var(--mascot-tilt, 0deg));
}
.minion-hair { position: absolute; top: -3%; left: 50%; width: 50%; height: 16%; transform: translateX(-50%); }
.minion-hair::before, .minion-hair::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 9%;
  height: 70%;
  background: #17140f;
  border-radius: 60% 40% 0 0 / 100% 100% 0 0;
}
.minion-hair::before { left: 32%; transform: rotate(-22deg); }
.minion-hair::after { left: 56%; transform: rotate(16deg); }
.footer-face {
  position: absolute;
  top: 30%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 6%;
  width: 74%;
}
.minion-goggle-strap {
  position: absolute;
  top: 50%;
  left: -14%;
  right: -14%;
  height: 12%;
  transform: translateY(-50%);
  background: #2b2b2b;
  z-index: 0;
}
.footer-eye { position: relative; z-index: 1; width: 42%; aspect-ratio: 1; }
.minion-goggle {
  position: absolute;
  inset: -18%;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #f8f8f8 0%, #d7d7d7 55%, #8e8e8e 100%);
  box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.6), 0 4px 8px rgba(0, 0, 0, 0.28);
}
.minion-goggle .footer-sclera { inset: 15%; }
.footer-sclera {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(ellipse at 32% 18%, #ffffff 0%, rgba(255, 255, 255, 0.85) 45%, #dfe6f5 100%);
  box-shadow: inset -3px -5px 9px rgba(10, 0, 84, 0.28), inset 1px 2px 3px rgba(255, 255, 255, 0.7);
  clip-path: inset(calc(var(--mascot-blink, 0) * 49.5%) 0 round 48%);
}
.footer-pupil {
  position: absolute;
  width: 42%;
  height: 42%;
  top: 50%;
  left: 50%;
  border-radius: 50%;
  background: radial-gradient(ellipse at 32% 18%, #3a2c14 0%, #1c1409 55%, #000 85%);
  transform: translate(-50%, -50%);
}
.footer-pupil::after {
  content: '';
  position: absolute;
  top: 16%;
  left: 20%;
  width: 34%;
  height: 34%;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
}
.minion-mouth {
  position: absolute;
  top: 56%;
  left: 50%;
  width: 26%;
  height: 12%;
  transform: translateX(-50%);
  border-radius: 0 0 50% 50% / 0 0 100% 100%;
  background: #3a2410;
}
.minion-overalls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 40%;
  background: linear-gradient(180deg, #4169c9 0%, #2c4a94 100%);
  border-radius: 0 0 44% 44% / 0 0 58% 58%;
  box-shadow: inset 0 6px 10px rgba(255, 255, 255, 0.25);
}
.minion-pocket {
  position: absolute;
  top: 18%;
  left: 50%;
  width: 26%;
  height: 32%;
  transform: translateX(-50%);
  background: #23386e;
  border-radius: 3px;
}
.minion-strap {
  position: absolute;
  top: -20%;
  width: 15%;
  height: 28%;
  border-radius: 5px;
  background: linear-gradient(180deg, #4169c9, #2c4a94);
}
.minion-strap--left { left: 16%; transform: rotate(16deg); }
.minion-strap--right { right: 16%; transform: rotate(-16deg); }
/* Minion's own yellow body, drawn as the mascot's ::before so the
   overalls/hair/face above can layer on top without extra markup. */
.footer-mascot::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 48% 48% 44% 44% / 40% 40% 44% 44%;
  background: linear-gradient(160deg, #ffeb8a 0%, #ffd400 55%, #fbb800 100%);
  box-shadow:
    inset -10px -14px 26px rgba(120, 80, 0, 0.28),
    inset 10px 14px 20px rgba(255, 255, 255, 0.55),
    0 30px 50px -18px rgba(10, 0, 84, 0.35);
  z-index: -1;
}
.footer-legal {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  width: 100%;
  padding: 14px 0 0;
  font-size: 0.8rem;
  color: rgba(12, 0, 84, 0.6);
}
.footer-copyright { color: inherit; }
.footer-legal-links { display: flex; gap: 14px; }
.footer-legal-link { color: inherit; transition: color 0.2s ease; }
.footer-legal-link:hover, .footer-copyright:hover { color: var(--blue); }
@media (max-width: 900px) {
  .footer-directory { min-height: 380px; }
  .footer-spotlight { top: 110px; width: min(540px, 60vw); height: min(530px, 55vw); }
}
@media (max-width: 640px) {
  .site-footer { padding: 26px 24px 16px; border-radius: 24px; }
  .footer-cta { flex-direction: row; padding: 8px 0 20px; }
  .footer-columns { flex-wrap: wrap; }
  .footer-directory { min-height: 320px; padding-bottom: 12px; gap: 12px; }
  .footer-mascot { width: min(140px, 38vw); height: min(180px, 48vw); }
  .footer-spotlight { top: 60px; width: 80vw; height: 70vw; }
}
/* ============================================
   CUSTOM CURSOR
   A native CSS cursor image — the Deathly Hallows sign (triangle,
   circle and angled line, tilted the same way as the reference
   drawing) rather than a tracking overlay, so there's no JS
   positioning/lag and it's simply ignored on touch. Each shape is
   drawn twice: a wider white pass underneath for a halo that
   separates it from photos/cards of any colour, then a thinner
   colour pass on top. That colour pass switches between two
   variants for contrast — dark navy over the site's pale/periwinkle
   sections, brand blue over its indigo/navy/blue ones — chosen per
   section below rather than sampled live, since every background on
   the site is a flat, known colour.
============================================ */
:root {
  --cursor-hallows-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='36' viewBox='-3 -4 41 49'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 0 L34 23 L1 35 Z'/%3E%3Cpath d='M6 0 L21.5 41'/%3E%3Ccircle cx='13.58' cy='19.51' r='10.26'/%3E%3C/g%3E%3Cg fill='none' stroke='%230a00cb' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 0 L34 23 L1 35 Z'/%3E%3Cpath d='M6 0 L21.5 41'/%3E%3Ccircle cx='13.58' cy='19.51' r='10.26'/%3E%3C/g%3E%3C/svg%3E") 7 3, auto;
  --cursor-hallows-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='36' viewBox='-3 -4 41 49'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 0 L34 23 L1 35 Z'/%3E%3Cpath d='M6 0 L21.5 41'/%3E%3Ccircle cx='13.58' cy='19.51' r='10.26'/%3E%3C/g%3E%3Cg fill='none' stroke='%230052ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 0 L34 23 L1 35 Z'/%3E%3Cpath d='M6 0 L21.5 41'/%3E%3Ccircle cx='13.58' cy='19.51' r='10.26'/%3E%3C/g%3E%3C/svg%3E") 7 3, auto;
}
@media (hover: hover) and (pointer: fine) {
  body,
    a,
    button,
    .exp-card { cursor: var(--cursor-hallows-dark); }
  /* Dark-background sections/components get the light (blue)
       variant instead, so the cursor still reads against them. */
  .work,
    .side-quests,
    .contact-bar,
    .transition-card--accent,
    .transition-card--cover { cursor: var(--cursor-hallows-light); }
  /* Color-only hover — no magnetic pull (removed; nav links now only
       switch color on hover, no transform). */
  .nav-link { transition: color 0.2s cubic-bezier(0.19, 1, 0.22, 1); }
}
/* ============================================
   PAGE TRANSITION — stacked cards
   Two full-viewport cards cover both exit (leaving the current page)
   and entry (loading the new one): an accent card and, on top of it,
   a solid cover card carrying the destination's title. Both default
   to translateY(0%) — fully covering, no JS/reduced-motion needed
   for that base state — and slide up together to translateY(-100%)
   to reveal the page underneath. The two cards are staggered in
   opposite orders depending on which way they're moving: covering,
   the accent leads and the cover trails half a beat behind, landing
   on top; revealing, the cover (on top, blocking the view) lifts
   first, briefly exposing the accent before it also lifts away —
   the "stacked cards" read of osmo.supply's page-transition resource,
   adapted to a static multi-page site as a matched exit/entry pair
   rather than one continuous in-page animation.
============================================ */
.page-transition {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
  display: none; /* transitions switched off for now: remove this line to restore */
}
.transition-card {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: translateY(0%);
}
.transition-card--accent {
  z-index: 1;
  background: var(--pale);          /* back card */
}
.transition-card--cover {
  z-index: 2;
  background: var(--blue);          /* front card */
  display: flex;
  align-items: center;
  justify-content: center;
}
/* No page-title card in this version of the transition. */
.loader-title { display: none; }
/* The page itself (main) sits behind the cards. While the cards cover it
   it is held slightly shrunk and dropped; when the cards lift away it
   scales up to full size, as in the stacked-cards reference. It is
   `none` at rest so it never affects layout or fixed-position children. */
.page-transition ~ main {
  /* (inactive while the overlay is hidden) */
}
@media (prefers-reduced-motion: no-preference) {
  .transition-card {
      transition: transform 620ms cubic-bezier(0.76, 0, 0.24, 1), border-radius 620ms cubic-bezier(0.76, 0, 0.24, 1);
    }
  .transition-card--accent { transition-delay: 0ms; }
  .transition-card--cover  { transition-delay: 110ms; }
  /* Lifted away: shrunk a touch with soft lower corners, parked above the screen. */
  .page-transition.is-revealed .transition-card {
      transform: translateY(-101%) scale(0.94);
      border-radius: 0 0 36px 36px;
      transition-duration: 700ms;
    }
  .page-transition.is-revealed .transition-card--accent { transition-delay: 140ms; }
  .page-transition.is-revealed .transition-card--cover  { transition-delay: 0ms; }
}
@keyframes loaderTitleShow {
  
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .page-transition { display: none; }
  .page-transition ~ main { transform: none; }
}
/* ============================================
   RESPONSIVE
============================================ */
@media (max-width: 960px) {
  .nav-wrap { display: none; }
  .nav-toggle { display: flex; }
  .nav-mail { display: none; }
  .nav-wrap.open {
      display: block;
      position: absolute;
      top: calc(100% + 10px);
      left: auto;
      right: 0;
      transform: none;
    }
  .nav-wrap.open .main-nav {
      flex-direction: column;
      align-items: stretch;
      gap: 2px;
      padding: 8px;
      border-radius: 24px;
      min-width: 200px;
    }
  .nav-wrap.open .nav-link { font-size: 1rem; padding: 10px 16px; }
}
/* Desktop only: Work's "View All Work" link drops out of the
   heading row, leaving the centered "Work" heading as the row's
   only content — justify-content does the centering, since a lone
   flex item sizes to its own content rather than the row's full
   width. "I've Worked At" is a plain heading (no row), so it's
   centered directly. Mobile/tablet keep the left-aligned heading +
   button layout untouched. */
@media (min-width: 901px) {
  .work .section-heading-row { justify-content: center; }
  .work .section-heading-row [data-view-all-work] { display: none; }
  .experience .section-heading { text-align: center; }
}
/* A narrow band just above the stacked breakpoint where the desktop
   two-column layout still fits, but only just — the photo and the
   card/text column start touching around 1024–1180px unless both
   get a bit more room. Trims the photo and the one-liner's wrap
   width so there's always clear space between them. */
@media (min-width: 901px) and (max-width: 1200px) {
  .hero-photo { width: min(480px, 34vw); }
  .hero-oneliner-card { max-width: 300px; }
  /* The headshot is shorter (relative to its width) than the old
       illustration was, and no longer reliably taller than the
       absolutely-positioned left column beside it — guarantee the
       section is tall enough to contain both regardless. */
  .hero-inner { min-height: 560px; }
}
@media (max-width: 900px) {
  /* Text stacks above the tools grid instead of sitting beside it. */
  .who-i-am-row { grid-template-columns: 1fr; }
  /* Stacked, with generous spacing so each stat still reads as its
       own moment rather than a cramped list. */
  .impact-grid { grid-template-columns: 1fr; gap: 48px; margin-top: 40px; }
  /* The hero's two-column arrangement (text/logos/CTA on the left,
       the card floating on the right, both overlapping the photo)
       stops having room to breathe well before phone widths — the card
       and the text column start overlapping the photo around tablet
       widths. Rather than keep stretching the desktop layout thinner,
       it switches to the same single-column, left-aligned stack used
       on phones, just starting earlier. */
  .hero-inner { text-align: left; }
  .hero-photo { width: min(360px, 60vw); margin: 0 auto 28px; }
  /* Centred on the page in the stacked layout: the "hi, i'm" line, and the figure
       itself. The photo file carries 9% extra canvas on the right (the sleeve), and
       the head sits at 53.15% of the box, so the image is pulled left by 3.15% to put
       the head exactly on the centre line. */
  .hero-kicker { text-align: center; }
  .hero-photo-img { margin-left: -3.15%; }
  .hero-heading { margin-bottom: 0; }
  /* position:relative (not static) so these keep their z-index and
       stay above the photo once it becomes an absolutely-positioned
       backdrop on phones — a statically-positioned box ignores
       z-index entirely and would paint underneath it regardless of
       the value. top/right are reset since relative (unlike static)
       still honours them, and they're carrying desktop's floating
       offsets otherwise. */
  .hero-left-stack,
    .hero-cta {
      position: relative;
      top: auto;
      right: auto;
      margin: 0 0 14px;
    }
  .hero-left-stack { align-items: flex-start; }
  /* Compact, regular-sized boxes side by side (same 18px corners). */
  .hero-cta { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 10px; width: auto; min-width: 0; }
  .hero-cta-btn { padding: 10px 18px; font-size: 0.95rem; }
  .hero-socials { gap: 10px; }
  .hero-cta-btn--icon { flex: none; width: 52px; padding: 10px 0; }
  .experience-grid { grid-template-columns: 1fr; }
  .exp-media-grid { grid-template-columns: repeat(2, 1fr); }
  /* Splitting into two 3-card grids leaves an odd card dangling next
       to an empty cell at 2 columns — flatten both rows into one 6-card
       grid instead, so every pair fills its line with no gap. */
  .work-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .work-grid-row { display: contents; }
  .work-card--landscape { grid-column: 1 / -1; }
  /* Same idea for the storytelling bento — plain 2-column grid, with
       the odd 9th card (there's no partner for it) spanning full width
       instead of leaving a gap next to it. */
  .storytelling-gallery { grid-template-columns: repeat(2, 1fr); }
  .storytelling-gallery > * { grid-column: span 1; }
  .storytelling-gallery > *:last-child { grid-column: 1 / -1; }
  .contact-grid { grid-template-columns: 1fr; }
  .contact-side { align-items: flex-start; text-align: left; }
  .contact-tag { transform: none; }
  .work-slide-grid { grid-template-columns: 1fr; }
  .work-slide-media { max-width: 420px; margin: 0 auto; }
  /* --split would otherwise keep its 2-column ratio here (higher
       specificity than the plain rule above) — force it back to 1
       column too. --overlay's own grid is already the constrained,
       centered box (see the --overlay rules above) — media just needs
       to fill 100% of it, not get independently narrowed by the plain
       rule above, or it'd drift out of alignment with the copy overlay
       that's positioned against the grid, not the media element itself. */
  .work-slide--split .work-slide-grid { grid-template-columns: 1fr; }
  .work-slide--overlay .work-slide-media { max-width: none; margin: 0; }
}
@media (max-width: 640px) {
  /* On phones the hero is one plain column: name, one-liner and logos,
       then the photo, then the highlight card below it so the card never
       sits on the face. Everything stays inside the viewport width. */
  .hero-inner { display: flex; flex-direction: column; align-items: stretch; }
  .hero-heading { order: 1; z-index: 3; }
  .hero-left-stack { order: 2; }
  .hero-row {
      order: 3;
      width: 100%;
      max-width: 100%;
      margin: 12px 0 18px;
    }
  .hero-photo { width: min(340px, 86vw); margin: 0 auto; }
  /* Card sits below the photo in normal flow so it never covers the face. */
  .hero-cta { order: 4; align-self: center; margin: 0 0 24px; }
  .hero { padding-bottom: 0; }
  .work-card { width: 250px; }
  /* Back to two plain stacked rows below the tablet breakpoint — the
       flattened 2-column grid above is undone here. */
  .work-grid { display: flex; flex-direction: column; gap: 32px; }   /* was 24px: a little more air between cards on phones */
  .work-grid-row { display: grid; grid-template-columns: 1fr; gap: 36px; }   /* was 28px */
  /* Landscape cards drop back to a plain stacked layout (image over
       copy) at phone widths — side-by-side has no room left here. */
  .work-card.work-card--landscape { grid-template-columns: 1fr; }
  .work-grid .work-card--landscape .img-placeholder { aspect-ratio: 16 / 10; height: auto; }
  /* Only the top 3 projects show by default on mobile — the rest stay
       collapsed until "View All Work" is tapped (see initWorkExpand in
       script.js, which intercepts that tap on mobile only). */
  .work-grid-row--extra { display: grid; }
  .work-grid.is-expanded .work-grid-row--extra { display: grid; }
  .storytelling-gallery { grid-template-columns: 1fr; }
  .storytelling-gallery > * { grid-column: 1 / -1; }
  .exp-media-grid { grid-template-columns: 1fr; }
  .work-slide-cards { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
  .btn { padding: 12px 22px; font-size: 0.85rem; }
  .hero { padding-top: 130px; }
}
/* ---- Solid 3D blocks: "What I Did" and "I've Done This At" ----
   A run of solid shadows stepping down and to the right builds the thick side of each block (lit from
   the top left), a bright rim and a faint inner shade finish the form, and a deep shadow on the ground
   lifts it off the page. Hover changes colour (the sides deepen with it); pressing sinks the block in. */
.work .work-card,
.exp-card,
.exp-card:hover {
  --edge: #b6caf5;
  --edge-deep: #7fa2ee;
  --ground: rgba(12, 0, 84, 0.3);
  position: relative;
  border-radius: 22px;
  box-shadow:
    inset 2px 2px 0 rgba(255, 255, 255, 0.95),
    inset -2px -3px 0 rgba(12, 0, 84, 0.06),
    1px 1px 0 var(--edge),
    2px 2px 0 var(--edge),
    3px 3px 0 var(--edge),
    4px 4px 0 var(--edge),
    5px 5px 0 var(--edge-deep),
    13px 19px 24px -6px var(--ground);
  transition: background-color 0.3s ease, box-shadow 0.2s ease, translate 0.15s ease;
}
.work .work-card:hover,
.exp-card:hover { --edge: #94b2f1; --edge-deep: #5d8aeb; }
.work .work-card:active,
.exp-card:active {
  translate: 4px 4px;
  box-shadow:
    inset 2px 2px 0 rgba(255, 255, 255, 0.95),
    inset -2px -3px 0 rgba(12, 0, 84, 0.06),
    1px 1px 0 var(--edge-deep),
    2px 2px 0 var(--edge-deep),
    3px 3px 0 var(--edge-deep),
    7px 9px 10px -4px var(--ground);
}
/* the pictures and logos sit slightly raised inside the block */
.work .work-card .img-placeholder { box-shadow: 2px 5px 0 rgba(12, 0, 84, 0.12), 6px 12px 16px -4px rgba(12, 0, 84, 0.34), 0 0 0 1px rgba(12, 0, 84, 0.06); }
.exp-card .exp-logo { box-shadow: 2px 3px 0 rgba(12, 0, 84, 0.12), 4px 8px 12px -3px rgba(12, 0, 84, 0.3); }
.exp-card:hover { transform: none; }
.work-grid-row { padding: 0 8px 9px 0; }
.experience-grid { padding-right: 8px; padding-bottom: 9px; row-gap: 22px; }
@media (prefers-reduced-motion: reduce) {
  .work .work-card, .exp-card { transition: background-color 0.3s ease; }
  .work .work-card:active, .exp-card:active { translate: 0 0; }
}
/* Phones/tablets: copy first, photo last. The photo sits on the hero's base
   edge (the skills strip crosses it) and the card + buttons move up above it,
   mirroring the desktop arrangement. */
@media (max-width: 900px) {
  .hero { padding-bottom: 0; }
  .hero-inner { display: flex; flex-direction: column; }
  .hero-heading { order: 1; }
  .hero-left-stack { order: 2; }
  .hero-cta { order: 3; margin-top: 18px; margin-bottom: 8px; }
  .hero-row { order: 4; margin-bottom: 0; }
  .hero-photo { margin-bottom: 0; }
}
/* Smaller screens: every section topic is centred, never left-aligned. */
@media (max-width: 900px) {
  .section-heading, .st-heading,
    .sq2-story-heading, .sq2-rotation-heading, .ev-heading, .game-title {
      text-align: center !important;
      margin-left: auto;
      margin-right: auto;
    }
}
/* "What I Did" (landing) and the Work page: the heading sits on white; the pale panel starts where the cards
   start and wraps them (the same treatment as the Brand Storytelling gallery). */
.work::before { content: none; }
.work .work-grid {
  margin-top: 0;
  background: var(--pale);
  border-radius: 32px;
  padding: clamp(16px, 3vw, 36px);
}
.work:not(.work-page) .section-heading-row { margin-bottom: clamp(22px, 3vw, 36px); }
@media (max-width: 640px) {
  .work .work-grid { border-radius: 26px; padding: 14px; }
  .work:not(.work-page) { padding-top: 44px; padding-bottom: 44px; }
}
/* Phones/tablets: the one-liner card is centred, and the logo row runs exactly as wide as the
   "Some of the places I've done this" line above it (first logo under its first letter, last under its last). */
@media (max-width: 900px) {
  .hero-left-stack { align-items: center; text-align: center; width: 100%; max-width: 342px; margin-left: auto; margin-right: auto; box-sizing: border-box; }
  .hero-left-stack .hero-oneliner-card { width: 100%; }
  .hero-left-stack .hero-oneliner { text-align: center; }
  .hero-left-stack .hero-worked-at { width: fit-content; max-width: 100%; margin: 0 auto; }
  .hero-left-stack .hero-worked-at-label { text-align: center; }
  .hero-left-stack .hero-worked-at-logos { width: 100%; max-width: none; }
}
/* Phones: less air between "I've Done This At" and the first company card */
@media (max-width: 640px) {
  .experience-grid { margin-top: 22px; }
}
/* Landing "What I Did": the panel is a little wider than the Brand Storytelling one (1240px / screen - 40px). */
.work:not(.work-page) { overflow-x: clip; }
.work:not(.work-page) .work-grid {
  width: min(calc(100vw - 28px), 1300px);
  margin-left: calc(50% - min(calc(100vw - 28px), 1300px) / 2);
  box-sizing: border-box;
}
/* ...but the cards keep their original size, centred inside the wider panel (1060px of cards). */
.work:not(.work-page) .work-grid {
  padding-left: max(clamp(14px, 3vw, 36px), calc((min(100vw - 28px, 1300px) - 1060px) / 2));
  padding-right: max(clamp(14px, 3vw, 36px), calc((min(100vw - 28px, 1300px) - 1060px) / 2));
}
/* a little more room above and below the cards inside the panel */
.work:not(.work-page) .work-grid {
  padding-top: clamp(22px, 4vw, 52px);
  padding-bottom: clamp(22px, 4vw, 52px);
}
/* subtext under "What I Did" */
.work-sub {
  margin: 10px auto clamp(22px, 3vw, 34px);
  max-width: 560px;
  text-align: center;
  text-wrap: balance;
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  line-height: 1.5;
  color: rgba(12, 0, 84, 0.72);
}
.work:not(.work-page) .section-heading-row { margin-bottom: 0; }
@media (min-width: 1100px) {
  .work-sub { font-size: 1rem; }
}
/* Work and Events pages: the subtext sits right under the heading */
.work-page-heading:has(+ .work-sub), .ev-heading:has(+ .work-sub) { margin-bottom: 0; }
.work-page .work-sub, .ev-section .work-sub { margin-bottom: clamp(26px, 4vw, 44px); }
/* the nav music icon while music plays: a still pause symbol (two bars), not moving bars */
.nav-music.is-on .nav-eq { display: inline-flex; gap: 4px; height: 14px; }
.nav-music.is-on .nav-eq i { width: 4px; height: 100%; animation: none; transform: none; border-radius: 2px; }
.nav-music.is-on .nav-eq i:nth-child(3) { display: none; }
/* while music is playing, the music icon in the top bar steps aside (the bottom icon / bar takes over) */
.nav-music.is-on, .nav-music.is-carried { display: none; }
/* Very large screens: the hero content scales up with the screen (the factor is set by script.js; it is 1 up to 1536px wide). */
@media (min-width: 1537px) {
  .hero-inner { zoom: var(--hero-zoom, 1); }
  .hero { padding-top: calc(110px * var(--hero-zoom, 1)); }
}
/* Phones: the story and event titles on the Storytelling and Events pages are centred, like the other topics. */
@media (max-width: 900px) {
  .st-story-title,
    .ev-title,
    .ev-detail-title { text-align: center; }
}
