/* ==========================================================================
   Reset & base
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; margin: 0; padding: 0; }

/* Form controls don't inherit font-family by default in any browser —
   Manrope is the site-wide default, so pull it in explicitly here. */
button, input, textarea, select {
  font-family: inherit;
}

:root {
  --color-bg: #ffffff;
  --color-text: #0a0a0a;
  --font-body: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  --hero-gutter: clamp(24px, 4vw, 140px);
}

html {
  font-size: 16px;
  font-family: var(--font-body);
  -webkit-text-size-adjust: 100%;
  /* Every page on this site is dark-themed (--color-bg is #121212
     everywhere it's overridden). Setting it here too — not just on
     `body` — means iOS Safari's rubber-band overscroll at the very top/
     bottom (and the gap during its toolbar show/hide transition) reveals
     this dark color instead of the browser's default white, keeping the
     status bar and bottom address bar area reading as part of the page
     instead of a bright seam. */
  background: #121212;
}

body {
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  height: 100dvh;
  overflow: hidden;
}

/* Only truly extreme (very short) viewports fall back to a scrolling page —
   the mobile layout below is sized to fit 100dvh on its own. */
@media (max-height: 400px) {
  body {
    height: auto;
    min-height: 100dvh;
    overflow: visible;
  }
}

/* ==========================================================================
   Page-load entrance — a staggered "stack" reveal, once per load, on every page
   ========================================================================== */
@keyframes stack-in {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hero-media,
.navbar,
.about-card > *,
.portfolio-cards > * {
  animation: stack-in 600ms var(--ease-out) both;
}

.hero-media   { animation-delay: 0ms; }

.navbar                     { animation-delay: 0ms; }
.about-card > *:nth-child(1) { animation-delay: 80ms; }
.about-card > *:nth-child(2) { animation-delay: 160ms; }
.about-card > *:nth-child(3) { animation-delay: 240ms; }
.about-card > *:nth-child(4) { animation-delay: 320ms; }
.about-card > *:nth-child(5) { animation-delay: 400ms; }

.portfolio-cards > *:nth-child(1) { animation-delay: 80ms; }
.portfolio-cards > *:nth-child(2) { animation-delay: 160ms; }

@media (prefers-reduced-motion: reduce) {
  .hero-media, .hero-title,
  .navbar, .about-card > *, .portfolio-cards > * {
    animation: stack-in-fade 200ms ease both;
  }

  .hero-title__line--1,
  .hero-title__text,
  .hero-title__accent {
    animation: none;
    opacity: 1;
    clip-path: none;
  }


  @keyframes stack-in-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}

/* ==========================================================================
   Transitions.dev — Text reveal
   Reusable staggered entrance for a block of text lines (e.g. hero
   eyebrow/bio/actions). Add `.t-stagger` to the shared parent, `.t-stagger-
   line` + `.t-stagger-line--N` to each child line, then toggle `.is-shown`
   on the parent (see assets/js/text-reveal.js) once the initial hidden
   state has painted.
   ========================================================================== */
:root {
  --stagger-dur: 500ms;
  --stagger-distance: 12px;
  --stagger-stagger: 40ms;
  --stagger-blur: 3px;
  --stagger-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

.t-stagger-line {
  display: block;
  opacity: 0;
  transform: translateY(var(--stagger-distance));
  filter: blur(var(--stagger-blur));
  transition:
    opacity   var(--stagger-dur) var(--stagger-ease),
    transform var(--stagger-dur) var(--stagger-ease),
    filter    var(--stagger-dur) var(--stagger-ease);
  will-change: transform, opacity, filter;
}
.t-stagger-line--2 { transition-delay: var(--stagger-stagger); }
.t-stagger-line--3 { transition-delay: calc(var(--stagger-stagger) * 2); }
.t-stagger-line--4 { transition-delay: calc(var(--stagger-stagger) * 3); }
.t-stagger-line--5 { transition-delay: calc(var(--stagger-stagger) * 4); }

.t-stagger.is-shown .t-stagger-line {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}

.t-stagger.is-hiding .t-stagger-line {
  opacity: 0;
  transform: translateY(0);
  filter: blur(0);
  transition:
    opacity 200ms ease,
    transform 0s linear,
    filter 0s linear;
  transition-delay: 0s;
}

@media (prefers-reduced-motion: reduce) {
  .t-stagger-line { transition: none !important; }
}

/* ==========================================================================
   Hero layout
   ========================================================================== */
.hero {
  position: relative;
  display: flex;
  align-items: stretch;
  width: 100%;
  height: 100dvh;
  overflow: hidden;
}

@media (max-height: 400px) {
  .hero {
    height: auto;
    min-height: 100dvh;
    overflow: visible;
  }
}

/* ---- Left: portrait media ---- */
.hero-media {
  position: relative;
  z-index: 2;
  flex: 0 0 44.5%;
  height: 100%;
  overflow: hidden;
  background: var(--color-bg);
  cursor: default;
  padding-left: clamp(12px, 2vw, 28px);
}

/* Soft black blur sitting behind both portrait layers (toon + real), so
   the transparent-background cutouts read with some depth instead of
   looking pasted flat on the panel. */
.hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(ellipse 60% 48% at 50% 42%, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0) 72%);
  filter: blur(50px);
  pointer-events: none;
}

.hero-media__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  /* Scales continuously with viewport width instead of a flat multiplier,
     so the portrait actually grows with the screen rather than jumping
     between two fixed sizes. transform-origin stays centered so scaling
     is symmetric and the image never drifts off-center. Bounded below 1
     so it can only ever shrink toward (never below) its natural
     contain-fit size — going past 1 on narrow/short viewports would
     overflow the height-capped mobile box and get clipped by
     `.hero-media`'s overflow: hidden. (Note: the divisor needs an
     explicit px unit — dividing a length by a bare number stays a
     length, which then can't be added to the unitless 0.8 and silently
     invalidates the whole custom property.) */
  transform: scale(clamp(1, calc(0.8 + (100vw - 900px) / 2000px), 1.35));
  transform-origin: 50% 100%;
  pointer-events: none;
  user-select: none;
}

/* Full photo (original blue-violet backdrop kept, no cutout) so the panel's
   colour comes from the picture's own lighting. To roll back to the cutout,
   point index.html at assets/hero/portrait.webp and drop the --photo class. */
.hero-media .hero-media__img--photo {
  object-fit: cover;
  object-position: 50% 20%;
  transform: none;
}

@media (max-width: 720px) {
  .hero-media .hero-media__img--photo {
    object-position: 50% 30%;
  }
}

/* ---- Layered hero photo: backdrop plate, a light glow behind, the cut-out
   subject, and a soft-light highlight clipped to the subject. All layers
   share one box + object-fit so they register pixel-for-pixel; the JS in
   assets/js/hero-photo-fx.js only moves them a few px and slides the light
   (via --lx/--ly), on hover-capable pointers. Without JS it reads as the
   flat photo. ---- */
@property --lx { syntax: '<percentage>'; inherits: true; initial-value: 28%; }
@property --ly { syntax: '<percentage>'; inherits: true; initial-value: 30%; }

.hero-photo {
  --hero-photo-pos: 50% 20%;
  --glow-rgb: 140, 150, 255;
  --light-rgb: 255, 236, 214;
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* The light drifts slowly on its own (like a light source breathing), so
     it never reads as cursor-chasing. Eased and long, with alternate
     direction so there's no visible loop point. */
  animation: hero-light-drift 18s ease-in-out infinite alternate;
}

@keyframes hero-light-drift {
  0%   { --lx: 20%; --ly: 24%; }
  50%  { --lx: 42%; --ly: 40%; }
  100% { --lx: 30%; --ly: 18%; }
}

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

.hero-photo__layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-photo-pos);
  pointer-events: none;
  user-select: none;
  will-change: transform;
  /* Slight contrast lift, applied identically to backdrop and subject so
     they stay matched. */
  filter: contrast(1.1);
}

/* A little overscale on every layer leaves room to shift without exposing edges. */
.hero-photo__bg {
  transform: translate3d(var(--bx, 0px), var(--by, 0px), 0) scale(1.06);
}

.hero-photo__subject {
  transform: translate3d(var(--sx, 0px), var(--sy, 0px), 0) scale(1.06);
}

.hero-photo__glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at var(--lx) var(--ly), rgba(var(--glow-rgb), 0.5), rgba(var(--glow-rgb), 0) 55%);
  mix-blend-mode: screen;
}

.hero-photo__light {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at var(--lx) var(--ly), rgba(var(--light-rgb), 0.75), rgba(var(--light-rgb), 0) 48%);
  mix-blend-mode: soft-light;
  -webkit-mask-image: url('../hero/portrait.webp?v=2');
  mask-image: url('../hero/portrait.webp?v=2');
  -webkit-mask-size: cover;
  mask-size: cover;
  -webkit-mask-position: var(--hero-photo-pos);
  mask-position: var(--hero-photo-pos);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  transform: translate3d(var(--sx, 0px), var(--sy, 0px), 0) scale(1.06);
}

@media (max-width: 720px) {
  .hero-photo { --hero-photo-pos: 50% 30%; --focus-x: 54%; --focus-y: 42%; }
}

/* ---- Cinematic lens blur: two soft-masked backdrop-blur layers, clear over
   the face (--focus-x/--focus-y) and ramping up toward the edges, so the eye
   lands on the face. The second layer also carries a faint cool vignette. ---- */
.hero-photo {
  --focus-x: 52%;
  --focus-y: 36%;
}

.hero-photo::before,
.hero-photo::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.hero-photo::before {
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  -webkit-mask-image: radial-gradient(ellipse 68% 100% at var(--focus-x) var(--focus-y), transparent 42%, #000 100%);
  mask-image: radial-gradient(ellipse 68% 100% at var(--focus-x) var(--focus-y), transparent 42%, #000 100%);
}

.hero-photo::after {
  -webkit-backdrop-filter: blur(11px);
  backdrop-filter: blur(11px);
  background: radial-gradient(ellipse 80% 105% at var(--focus-x) var(--focus-y), rgba(8, 10, 44, 0) 55%, rgba(8, 10, 44, 0.32) 100%);
  -webkit-mask-image: radial-gradient(ellipse 58% 98% at var(--focus-x) var(--focus-y), transparent 58%, #000 100%);
  mask-image: radial-gradient(ellipse 58% 98% at var(--focus-x) var(--focus-y), transparent 58%, #000 100%);
}


/* ---- Right: content ---- */
.hero-content {
  position: relative;
  z-index: 2;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  min-width: 0;
  padding: clamp(24px, 6vh, 40px) var(--hero-gutter);
}

.hero-content__inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  width: 100%;
  max-width: 780px;
}

.hero-eyebrow {
  margin: 0;
  width: 100%;
  font-weight: 700;
  font-size: clamp(14px, 1.1vw, 17px);
  letter-spacing: 0.01em;
  color: var(--color-text);
  text-align: center;
}

.hero-title {
  margin: 48px 0 0;
  width: 100%;
  font-weight: 800;
  font-size: clamp(34px, 6.5vw, 100px);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--color-text);
  text-align: center;
}

.hero-title__line {
  display: block;
}

/* ---- Sequenced reveal: title line 1, then the "make sense" accent, flow
   straight out of the same `.t-stagger` cascade as the eyebrow/bio/actions
   below (see the Transitions.dev text-reveal component) — one continuous
   40ms-stepped sequence with no extra pause between the headline and the
   copy underneath it. ---- */
.hero-title__text {
  display: inline-block;
}

/* "make sense": same bold sans as the line above, filled with a gradient
   sampled from the portrait's backdrop (blue -> periwinkle -> lavender,
   lifted a little so it stays vivid on the dark panel). Reveals with the
   same `.t-stagger` cascade as the rest of the copy. */
.hero-title__accent {
  display: inline-block;
  /* Scaled so "make sense" spans the same width as "I make designs" above
     it. Manrope's proportions are fixed, so one em factor holds at every
     screen size (letter-spacing is set in em here so it scales with it). */
  font-size: 1.245em;
  letter-spacing: -0.02em;
  line-height: inherit;
  padding-bottom: 0.08em; /* keeps descenders from being clipped by background-clip */
  background: linear-gradient(95deg, #4f80f5 0%, #7482ee 55%, #a3a7fa 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  opacity: 0;
  transform: translateY(var(--stagger-distance));
  filter: blur(var(--stagger-blur));
  transition:
    opacity   var(--stagger-dur) var(--stagger-ease),
    transform var(--stagger-dur) var(--stagger-ease),
    filter    var(--stagger-dur) var(--stagger-ease);
  transition-delay: calc(var(--stagger-stagger) * 2);
}

.t-stagger.is-shown .hero-title__accent {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}

@media (prefers-reduced-motion: reduce) {
  .hero-title__accent { transition: none !important; }
}

/* ---- Subtle glitch on "make sense": two ghost copies (cyan / magenta) that
   only appear for a split second every few seconds, as sliced, slightly
   offset strips — a quick RGB-split flicker, then still. The gradient text
   itself never moves. Skipped for reduced-motion. ---- */
.hero-title__accent {
  position: relative;
}

.hero-title__accent::before,
.hero-title__accent::after {
  content: attr(data-text);
  content: attr(data-text) / "";
  position: absolute;
  inset: 0;
  white-space: nowrap;
  pointer-events: none;
  background: none;
  -webkit-text-fill-color: currentColor;
  opacity: 0;
  mix-blend-mode: screen;
  animation: hero-glitch 7s steps(1, end) infinite;
  animation-delay: 4s;
}

.hero-title__accent::before { color: #34e0ff; }
.hero-title__accent::after  { color: #ff3fa8; animation-name: hero-glitch-b; }

@keyframes hero-glitch {
  0%, 92%, 100% { opacity: 0; clip-path: inset(0 0 0 0); transform: none; }
  93%   { opacity: 0.6; clip-path: inset(12% 0 60% 0); transform: translateX(-0.045em); }
  94.5% { opacity: 0.6; clip-path: inset(58% 0 18% 0); transform: translateX(0.03em); }
  96%   { opacity: 0.5;  clip-path: inset(32% 0 42% 0); transform: translateX(-0.025em); }
  97.5% { opacity: 0.4;  clip-path: inset(72% 0 4% 0);  transform: translateX(0.04em); }
  99%   { opacity: 0; }
}

@keyframes hero-glitch-b {
  0%, 92%, 100% { opacity: 0; clip-path: inset(0 0 0 0); transform: none; }
  93.5% { opacity: 0.6; clip-path: inset(40% 0 38% 0); transform: translateX(0.045em); }
  95%   { opacity: 0.55;  clip-path: inset(8% 0 70% 0);  transform: translateX(-0.03em); }
  96.5% { opacity: 0.5;  clip-path: inset(62% 0 14% 0); transform: translateX(0.025em); }
  98%   { opacity: 0.4; clip-path: inset(24% 0 52% 0); transform: translateX(-0.04em); }
  99.2% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-title__accent::before,
  .hero-title__accent::after { display: none; }
}

.hero-bio {
  margin: 14px auto 0;
  width: 100%;
  font-size: clamp(16px, 1.25vw, 19px);
  line-height: 1.7;
  color: var(--color-text);
  max-width: 56ch;
  text-align: center;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(12px, 1.6vw, 24px);
  width: 100%;
  margin-top: 48px;
}

.hero-actions .btn { flex: 1 1 200px; }

/* The Uiverse "Realism" reference button is a compact, content-hugging
   pill — don't stretch it to fill the row like the other CTA style did.
   Kept close to its natural (~140-155px) content width rather than a
   fixed 236px minimum: that wider floor forced the row to wrap well
   before the hero column actually ran out of room, so even fairly wide
   desktop viewports never got to show all three side by side. At this
   size `flex-wrap` alone lets them drop to a new row one at a time as
   the column narrows, all the way down to the mobile breakpoint below. */
.hero-actions .btn--primary { flex: 0 0 auto; min-width: 150px; }

/* ==========================================================================
   Buttons — four types, referred to by these names from here on:
   primary (hero CTAs), secondary (bordered nav button, e.g. "Portfolio"),
   tertiary (plain nav button with icon, e.g. "Back to main"),
   link (plain underlined text, e.g. "Download CV")
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(14px, 1vw, 16px);
  line-height: 1.55;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.btn:active { transform: scale(0.97); }

.btn:focus-visible {
  outline: 2px solid #000;
  outline-offset: 3px;
}

/* ==========================================================================
   Primary — ported exactly from dexter-st's button on Uiverse
   (uiverse.io/dexter-st/young-newt-20): a glass-glow "AI generate" pill
   with a twinkling sparkle icon, per-letter shimmer, and a layered
   inset/outset shadow that reads as light hitting brushed glass. The
   source swaps "Generate" → "Generating" on focus; kept the same
   crossfade mechanic here with duplicate labels (no actual text change)
   since these are nav links, not a loading state.
   ========================================================================== */
.btn--primary {
  --border-radius: 24px;
  --padding: 4px;
  --transition: 0.4s;
  --button-color: #101010;
  --highlight-color-hue: 210deg;

  position: relative;
  user-select: none;
  gap: 12px;
  font-size: 16px;
  font-weight: 600;
  padding: 0.9em 1.3em;
  background-color: var(--button-color);
  border: solid 1px #fff2;
  border-radius: var(--border-radius);
  box-shadow:
    inset 0px 1px 1px rgba(255, 255, 255, 0.2),
    inset 0px 2px 2px rgba(255, 255, 255, 0.15),
    inset 0px 4px 4px rgba(255, 255, 255, 0.1),
    inset 0px 8px 8px rgba(255, 255, 255, 0.05),
    inset 0px 16px 16px rgba(255, 255, 255, 0.05),
    0px -1px 1px rgba(0, 0, 0, 0.02),
    0px -2px 2px rgba(0, 0, 0, 0.03),
    0px -4px 4px rgba(0, 0, 0, 0.05),
    0px -8px 8px rgba(0, 0, 0, 0.06),
    0px -16px 16px rgba(0, 0, 0, 0.08);
  transition: box-shadow var(--transition), border var(--transition), background-color var(--transition);
}

.btn--primary::before {
  content: '';
  position: absolute;
  top: calc(0px - var(--padding));
  left: calc(0px - var(--padding));
  width: calc(100% + var(--padding) * 2);
  height: calc(100% + var(--padding) * 2);
  border-radius: calc(var(--border-radius) + var(--padding));
  pointer-events: none;
  background-image: linear-gradient(0deg, #0004, #000a);
  z-index: -1;
  box-shadow:
    0 -8px 8px -6px #0000 inset,
    0 -16px 16px -8px #00000000 inset,
    1px 1px 1px #fff2,
    2px 2px 2px #fff1,
    -1px -1px 1px #0002,
    -2px -2px 2px #0001;
  transition: box-shadow var(--transition), filter var(--transition);
}

.btn--primary::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background-image: linear-gradient(0deg, #fff, hsl(var(--highlight-color-hue), 100%, 70%), hsla(var(--highlight-color-hue), 100%, 70%, 50%), 8%, transparent);
  opacity: 0;
  transition: opacity var(--transition), filter var(--transition);
}

.btn--primary__icon {
  flex: none;
  width: 24px;
  height: 24px;
  fill: #e8e8e8;
  filter: drop-shadow(0 0 2px #fff9);
  transition: fill var(--transition), filter var(--transition), opacity var(--transition);
}

.btn--primary__icon--line {
  fill: none;
  stroke: #e8e8e8;
  transition: stroke var(--transition), filter var(--transition), opacity var(--transition);
}

.btn--primary__txt-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn--primary__txt-1 {
  position: static;
  animation: btn-primary-txt-appear 1s ease-in-out forwards;
}

.btn--primary__txt-2 {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
}

@keyframes btn-primary-txt-appear {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

.btn--primary:focus .btn--primary__txt-1 {
  animation: btn-primary-txt-fade 0.3s ease-in-out forwards;
  animation-delay: 1s;
}

.btn--primary:focus .btn--primary__txt-2 {
  animation: btn-primary-txt-fade 0.3s ease-in-out reverse forwards;
  animation-delay: 1s;
}

@keyframes btn-primary-txt-fade {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

.btn--primary__letter {
  position: relative;
  display: inline-block;
  color: rgba(255, 255, 255, 0.94);
  transition: color var(--transition), text-shadow var(--transition), opacity var(--transition);
}


.btn--primary:active {
  border: solid 1px hsla(var(--highlight-color-hue), 100%, 80%, 70%);
  background-color: hsla(var(--highlight-color-hue), 50%, 20%, 0.5);
}

.btn--primary:active::before {
  box-shadow:
    0 -8px 12px -6px #fffa inset,
    0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 80%) inset,
    1px 1px 1px #fff4,
    2px 2px 2px #fff2,
    -1px -1px 1px #0002,
    -2px -2px 2px #0001;
}

.btn--primary:active::after {
  opacity: 1;
  mask-image: linear-gradient(0deg, #fff, transparent);
  filter: brightness(200%);
}

.btn--primary:active .btn--primary__letter {
  text-shadow: 0 0 1px hsla(var(--highlight-color-hue), 100%, 90%, 90%);
  animation: none;
}

.btn--primary:hover {
  border: solid 1px hsla(var(--highlight-color-hue), 100%, 80%, 40%);
}

.btn--primary:hover::before {
  box-shadow:
    0 -8px 8px -6px #fffa inset,
    0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 30%) inset,
    1px 1px 1px #fff2,
    2px 2px 2px #fff1,
    -1px -1px 1px #0002,
    -2px -2px 2px #0001;
}

.btn--primary:hover::after {
  opacity: 1;
  mask-image: linear-gradient(0deg, #fff, transparent);
}

.btn--primary:hover .btn--primary__icon {
  fill: #fff;
  filter: drop-shadow(0 0 3px hsl(var(--highlight-color-hue), 100%, 70%)) drop-shadow(0 -4px 6px #0009);
  animation: none;
}

.btn--primary:hover .btn--primary__icon--line {
  fill: none;
  stroke: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .btn--primary,
  .btn--primary__icon,
  .btn--primary__letter,
  .btn--primary__txt-1 {
    animation: none;
  }
}

/* ==========================================================================
   Secondary / Tertiary — "AnimatedButton": constant neutral fill, subtle
   scale on hover/tap, plus a shine sweep (soft diagonal glint + bright 1px
   border sweep) that only plays while hovered/focused — never idle.
   Secondary keeps a hairline border; tertiary drops it. Light-neutral by
   default; pages that opt into the dark theme (page-home/about/portfolio)
   flip it to dark-neutral further down.
   ========================================================================== */
.btn--secondary,
.btn--tertiary {
  --shine: rgba(0, 0, 0, 0.35);
  position: relative;
  overflow: hidden;
  gap: 10px;
  padding: 11px 20px;
  border-radius: 8px;
  background: #fafafa;
  color: #171717;
  transition: transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1), background-color 200ms ease, color 200ms ease, border-color 200ms ease, padding 220ms var(--ease-out), font-size 220ms var(--ease-out);
}

.btn--secondary { border-color: #e5e5e5; }
.btn--tertiary { border-color: transparent; }

.btn--secondary:hover,
.btn--tertiary:hover {
  transform: scale(1.01);
}

.btn--secondary:active,
.btn--tertiary:active {
  transform: scale(0.97);
}

.btn--secondary::after,
.btn--tertiary::after {
  content: '';
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background-image: linear-gradient(-75deg, transparent 30%, var(--shine) 50%, transparent 70%);
  background-repeat: no-repeat;
  background-size: 250% 100%;
  background-position: 200% 0;
  will-change: background-position;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

.btn--secondary:hover::after,
.btn--secondary:focus-visible::after,
.btn--tertiary:hover::after,
.btn--tertiary:focus-visible::after {
  opacity: 1;
  animation: btn-shine-sweep 2.8s linear infinite;
}

/* Range tuned so the shine is inside the button's visible window for the
   whole sweep (no dead run-up) — it starts showing the instant you hover,
   instead of animating for ~1.3s off-screen first. */
@keyframes btn-shine-sweep {
  from { background-position: 115% 0; }
  to   { background-position: -15% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .btn--primary,
  .btn--secondary,
  .btn--tertiary {
    animation: none;
  }

  .btn--secondary:hover,
  .btn--tertiary:hover,
  .btn--primary:hover {
    transform: none;
  }

  .btn--secondary::after,
  .btn--tertiary::after {
    display: none;
  }
}

/* ==========================================================================
   Mobile view
   ========================================================================== */
@media (max-width: 720px) {
  .hero {
    flex-direction: column;
  }

  /* ---- Image: its own bounded band at the top of the screen (not the
     flex-grow-everything box this used to be) — that approach let the
     image eat all the leftover height on tall phones, shoving the
     content block down and leaving a huge dead gap above it. A capped
     height keeps the portrait a predictable size and leaves the rest of
     the screen to `.hero-content` below. */
  .hero-media {
    flex: none;
    width: 100%;
    height: clamp(220px, 40dvh, 420px);
  }

  /* object-fit: contain keeps the whole portrait visible, never
     cropped/masked, at any size. */
  .hero-media__img {
    transform: none;
    object-position: 50% 100%;
  }

  /* ---- Content: `.hero-media` above now has a bounded height instead
     of eating all the leftover space, so this container (flex: 1 1
     auto) gets a real, consistent share of the screen — its content is
     vertically centered within that share (align-items: center; this
     box is row-direction, so align-items is the cross/vertical axis)
     rather than pinned to the very bottom, so extra height on a tall
     phone spreads evenly around the group instead of piling into one
     gap above it. */
  .hero-content {
    flex: 1 1 auto;
    align-items: center;
    text-align: center;
    padding: 16px 24px calc(clamp(16px, 3dvh, 32px) + env(safe-area-inset-bottom, 0px));
  }

  /* ---- Content group: eyebrow/title/bio/actions sit close together as
     one tight block instead of spreading out, so they read as a single
     grouped unit sitting on the buttons below them. */
  .hero-content__inner {
    align-items: center;
    gap: 0;
  }

  .hero-title {
    margin-top: 16px;
    font-size: clamp(24px, 7vw, 40px);
  }


  .hero-bio {
    margin-top: 16px;
    margin-inline: auto;
    font-size: clamp(13px, 3.6vw, 16px);
    line-height: 1.5;
  }

  /* ---- Buttons: stacked, centered, close under the bio above. Width
     scales with the screen (min(100%, 220px)) instead of a flat px
     value, and all three share it regardless of label length so they
     read as one uniform stack instead of ragged-width bars. Corner
     radius stays the full pill — same as desktop — via the unmodified
     --border-radius on .btn--primary. ---- */
  .hero-actions {
    flex-direction: column;
    align-items: center;
    gap: clamp(8px, 1.4dvh, 16px);
    margin-top: 16px;
  }

  .hero-actions .btn {
    flex: none;
    width: min(100%, 220px);
    padding: 0.7em 1.3em;
  }
}

/* ==========================================================================
   About page
   ========================================================================== */
body.scroll-page {
  height: auto;
  overflow: visible;
}

.navbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  background: var(--color-bg);
  padding: clamp(16px, 3vw, 32px) clamp(20px, 6vw, 56px);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0);
  transition: padding 220ms var(--ease-out), box-shadow 220ms var(--ease-out), background-color 220ms var(--ease-out);
}

.navbar.is-scrolled {
  padding-top: 12px;
  padding-bottom: 12px;
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}

.navbar.is-scrolled .btn--tertiary,
.navbar.is-scrolled .btn--secondary {
  font-size: 14px;
}

.navbar.is-scrolled .btn--tertiary {
  padding: 9px 16px;
}

.navbar.is-scrolled .btn--tertiary svg {
  width: 18px;
  height: 18px;
}

.navbar.is-scrolled .btn--secondary {
  padding: 9px 18px;
}

.navbar.is-scrolled .btn--icon-only {
  padding: 9px;
}

@media (prefers-reduced-motion: reduce) {
  .navbar { transition: box-shadow 150ms ease; }
}

.nav-right {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

/* ---- Case-study switcher: dropdown in the navbar to jump between projects ---- */
.case-switcher {
  position: relative;
}

.case-switcher__trigger {
  max-width: min(60vw, 320px);
  justify-content: space-between;
}

.case-switcher__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

.case-switcher__chevron {
  flex: none;
  transition: transform 200ms var(--ease-out);
}

.case-switcher.is-open .case-switcher__chevron {
  transform: rotate(180deg);
}

.case-switcher__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 20;
  width: max-content;
  min-width: 240px;
  max-width: min(90vw, 320px);
  margin: 0;
  padding: 8px;
  list-style: none;
  background: #fff;
  border: 1px solid #000;
  border-radius: 12px;
  box-shadow: 0.25rem 0.25rem #000;
  transform-origin: top right;
  transform: scale(0.95);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: transform 180ms var(--ease-out), opacity 180ms var(--ease-out), visibility 180ms;
}

.case-switcher.is-open .case-switcher__menu {
  transform: scale(1);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.case-switcher__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 8px;
  font-weight: 700;
  font-size: 14px;
  color: #000;
  transition: background-color 150ms ease;
}

.case-switcher__item span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.case-switcher__item:hover,
.case-switcher__item:focus-visible {
  background: #f3f4f6;
  outline: none;
}

.case-switcher__item.is-current {
  color: #005c87;
}

.case-switcher__item.is-current svg {
  flex: none;
  color: #005c87;
}

@media (prefers-reduced-motion: reduce) {
  .case-switcher__menu { transition: opacity 150ms ease, visibility 150ms; }
  .case-switcher__chevron { transition: none; }
}

.btn--icon-only {
  padding: 11px;
  gap: 0;
}

/* Text link on larger screens, icon-only tertiary button on mobile */
.nav-cv-icon { display: none; }

@media (max-width: 520px) {
  .nav-cv-full { display: none; }
  .nav-cv-icon { display: inline-flex; }
}

/* ---- Link: plain text with an underline that grows in on hover/focus ---- */
/* ---- Link: "double" interaction — two underlines animate in with
   different timings/origins, per the user's reference component ---- */
.btn--link {
  position: relative;
  padding: 0;
  border: none;
  border-radius: 0;
  font-size: clamp(14px, 1vw, 16px);
  color: #000;
}

.btn--link::before,
.btn--link::after {
  content: '';
  position: absolute;
  left: 0;
  top: 100%;
  width: 100%;
  height: 1px;
  background: currentColor;
  pointer-events: none;
}

.btn--link::before {
  transform: scale3d(0, 1, 1);
  transform-origin: 100% 50%;
  transition: transform 0.3s cubic-bezier(0.7, 0, 0.2, 1);
}

.btn--link:hover::before,
.btn--link:focus-visible::before {
  transform: scale3d(1, 1, 1);
  transform-origin: 0% 50%;
  transition-timing-function: cubic-bezier(0.4, 1, 0.8, 1);
}

.btn--link::after {
  top: calc(100% + 4px);
  transform: scale3d(0, 1, 1);
  transform-origin: 0% 50%;
  transition: transform 0.3s cubic-bezier(0.7, 0, 0.2, 1);
}

.btn--link:hover::after,
.btn--link:focus-visible::after {
  transform: scale3d(1, 1, 1);
  transform-origin: 100% 50%;
  transition-timing-function: cubic-bezier(0.4, 1, 0.8, 1);
}

@media (prefers-reduced-motion: reduce) {
  .btn--link::before,
  .btn--link::after {
    transition-duration: 0.01ms;
  }
}

/* Tighten the navbar buttons so "Back to main" (left) and the icon+Portfolio
   group (right) both fit on one line instead of wrapping. Placed after the
   base type rules above so it actually wins the cascade at this width. */
@media (max-width: 520px) {
  .navbar { padding-left: 16px; padding-right: 16px; gap: 8px; }
  .nav-right { gap: 8px; }

  .btn--tertiary {
    gap: 6px;
    padding: 10px 12px;
    font-size: 14px;
  }

  .btn--tertiary svg { width: 20px; height: 20px; }

  .btn--secondary {
    padding: 10px 18px;
    font-size: 14px;
  }

  .btn--icon-only { padding: 10px; }

  .navbar.is-scrolled .btn--tertiary {
    padding: 8px 10px;
    font-size: 13px;
  }

  .navbar.is-scrolled .btn--tertiary svg { width: 18px; height: 18px; }

  .navbar.is-scrolled .btn--secondary {
    padding: 8px 16px;
    font-size: 13px;
  }

  .navbar.is-scrolled .btn--icon-only { padding: 8px; }
}

.about-main {
  position: relative;
  padding: 0 clamp(20px, 6vw, 56px) clamp(40px, 8vw, 56px);
}

/* ---- Landing heading: "gooey" blur reveal, adapted from a GSAP
   GooeyTextReveal component the user supplied. The SVG feColorMatrix
   filter is the part that makes the blur look "gooey" (it sharpens the
   alpha channel so blurred edges snap into blobs); the variable blur
   amount is animated on the inner span via GSAP + ScrollTrigger. ---- */
.gooey-heading {
  margin: clamp(24px, 5vw, 48px) 0 0;
  font-weight: 800;
  font-size: clamp(32px, 5vw, 64px);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: #000;
}

.gooey-heading__line {
  display: block;
}

.gooey-heading__line.is-animating {
  filter: url(#gooey-heading-filter) blur(0.4px);
}

.gooey-heading__inner {
  display: inline-block;
  will-change: filter;
  filter: blur(0.35em);
}

@keyframes gooey-glitch {
  0%, 100% { transform: translate(0, 0); text-shadow: none; }
  20% { transform: translate(-2px, 1px); text-shadow: 2px 0 #ff004c, -2px 0 #00e5ff; }
  40% { transform: translate(2px, -1px); text-shadow: -2px 0 #ff004c, 2px 0 #00e5ff; }
  60% { transform: translate(-1px, 0); text-shadow: 1px 0 #ff004c, -1px 0 #00e5ff; }
  80% { transform: translate(1px, 0); text-shadow: none; }
}

.gooey-heading__inner.is-glitching {
  animation: gooey-glitch 350ms steps(2, end);
}

@media (prefers-reduced-motion: reduce) {
  .gooey-heading__line.is-animating {
    filter: none;
  }

  .gooey-heading__inner {
    filter: none;
  }

  .gooey-heading__inner.is-glitching {
    animation: none;
  }
}

.about-card {
  display: flex;
  flex-direction: column;
  gap: 80px;
  width: 100%;
  padding: clamp(24px, 5vw, 56px) 0;
}

.section {
  display: flex;
  flex-direction: column;
  gap: 32px;
  width: 100%;
}

.section-title {
  margin: 0;
  font-weight: 800;
  font-size: clamp(24px, 3vw, 32px);
  line-height: 1.375;
  color: #000;
}

.divider {
  height: 2px;
  width: 100%;
  background: #000;
  margin-top: -16px;
}

/* ---- Career ---- */
.career-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 48px;
  width: 100%;
}

@media (min-width: 1440px) {
  .career-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.career-item {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  margin: -16px;
  border: 1px solid transparent;
  border-radius: 12px;
  cursor: pointer;
  transition: background-color 200ms ease;
}

.career-item:hover,
.career-item:focus-visible {
  background: #f2f2f2;
  outline: none;
}

@media (prefers-reduced-motion: reduce) {
  .career-item { transition: background-color 160ms ease; }
}

.date-range {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #000;
}

.pill {
  padding: 2px 8px 3px;
  border-radius: 24px;
  background: #c7eaa5;
  font-weight: 800;
  font-size: 12px;
  text-transform: uppercase;
  color: #000;
}

.chevron-icon { flex: none; color: #000; }

.role-block {
  display: flex;
  flex-direction: column;
}

.role-title {
  font-size: 16px;
  line-height: 1.5;
  color: #000;
}

.role-company {
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

.meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.meta-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 500;
  font-size: 12px;
  color: #000;
}

.meta-icon { flex: none; color: #000; }

.meta-item a {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  text-decoration: underline;
  font-weight: 600;
}

/* ---- Education ---- */
.education-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  width: 100%;
}

.edu-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.edu-year {
  font-weight: 800;
  font-size: 14px;
  text-transform: uppercase;
  color: #171f37;
}

.edu-item p {
  margin: 0;
  font-size: 16px;
  line-height: 1.5;
  color: #171f37;
}

/* ---- Few things about me ---- */
.info-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  width: 100%;
}

.info-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.info-label {
  font-weight: 800;
  font-size: 14px;
  text-transform: uppercase;
  color: #000;
}

.info-item p {
  margin: 0;
  font-size: 16px;
  line-height: 1.5;
  color: #000;
}

.info-item p.muted { opacity: 0.5; }

/* ---- Copy-to-clipboard button on the email row: appears on hover/focus of
   the row (always visible on touch, which has no hover), swaps to a check
   after copying. ---- */
.info-copyable {
  display: flex;
  align-items: center;
  gap: 4px;
}

.copy-btn {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #8a8a8a;
  cursor: pointer;
  opacity: 0;
  transform: translateX(-4px);
  transition:
    opacity 150ms var(--ease-out),
    transform 150ms var(--ease-out),
    color 150ms ease,
    background-color 150ms ease;
}

.info-item:hover .copy-btn,
.info-item:focus-within .copy-btn,
.copy-btn:focus-visible,
.copy-btn[data-state="copied"] {
  opacity: 1;
  transform: none;
}

.copy-btn:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
}

.copy-btn:active { transform: scale(0.94); }

.copy-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.copy-btn__icon {
  grid-area: 1 / 1;
  transition: opacity 150ms ease, transform 150ms var(--ease-out);
}

.copy-btn__icon--done {
  opacity: 0;
  transform: scale(0.8);
}

.copy-btn[data-state="copied"] { color: #8fd49a; }
.copy-btn[data-state="copied"] .copy-btn__icon--copy { opacity: 0; transform: scale(0.8); }
.copy-btn[data-state="copied"] .copy-btn__icon--done { opacity: 1; transform: none; }

.copy-btn__tip {
  font-size: 12px;
  line-height: 1;
  color: #8fd49a;
  opacity: 0;
  transition: opacity 150ms ease;
}

.copy-btn__tip:not(:empty) { opacity: 1; }

@media (hover: none) {
  .copy-btn { opacity: 1; transform: none; }
}


/* ---- Footer: dark band with the faded works collage as backdrop,
   implemented from Figma node 4546:5242 ---- */
.aboutme-footer {
  position: relative;
  width: 100%;
  min-height: 420px;
  overflow: hidden;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 24px;
}

.aboutme-footer__cta {
  position: relative;
  z-index: 1;
  flex: none;
  margin-right: clamp(20px, 6vw, 100px);
  color: #fff;
}

@media (max-width: 700px) {
  .aboutme-footer__cta {
    margin-right: auto;
    margin-left: clamp(20px, 6vw, 100px);
    margin-bottom: clamp(20px, 4vh, 40px);
  }
}

.aboutme-footer__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.aboutme-footer__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
  max-width: 620px;
  padding: clamp(40px, 8vh, 72px) clamp(20px, 6vw, 100px);
  color: #fff;
}

.aboutme-footer__text {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.aboutme-footer__text h2 {
  margin: 0;
  font-weight: 800;
  font-size: clamp(18px, 4.4vw, 32px);
  line-height: 1.375;
  color: #fff;
  white-space: nowrap;
}

.aboutme-footer__text p {
  margin: 0;
  font-size: 16px;
  line-height: 1.75;
  color: #fff;
}

.aboutme-footer__text a {
  font-weight: 700;
  color: inherit;
  text-decoration: underline;
}

.aboutme-footer__links {
  display: flex;
  align-items: center;
  gap: 24px;
}

.aboutme-footer__social {
  font-weight: 800;
  font-size: 14px;
  text-transform: uppercase;
  text-decoration: underline;
}

.aboutme-footer__social--linkedin { color: #4e9fff; }
.aboutme-footer__social--dribbble { color: #ff75af; }

/* ---- Scroll-to-top FAB: an icon-only primary button ----
   Same hover mechanic as primary/secondary/tertiary (white fill, black
   text/icon, lift + hard shadow), plus its own appear/disappear animation
   as it toggles with scroll position. */
.scroll-fab {
  --shine: rgba(0, 0, 0, 0.35);
  position: fixed;
  right: clamp(16px, 4vw, 40px);
  /* Clears the iOS home-indicator / floating Safari toolbar so the FAB
     never sits under either. */
  bottom: calc(clamp(16px, 4vw, 40px) + env(safe-area-inset-bottom, 0px));
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: #000;
  color: #fff;
  border: 1px solid #000;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  opacity: 0;
  transform: scale(0.9);
  pointer-events: none;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out),
              background-color 200ms ease, color 200ms ease, box-shadow 200ms ease;
}

.scroll-fab::before,
.scroll-fab::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background-size: 250% 100%;
  background-position: 200% 0;
}

.scroll-fab::before {
  background-image: linear-gradient(-75deg, transparent 40%, var(--shine) 50%, transparent 60%);
  mix-blend-mode: overlay;
}

.scroll-fab::after {
  padding: 1px;
  background-image: linear-gradient(-75deg, transparent 30%, var(--shine) 50%, transparent 70%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

.scroll-fab:hover::before,
.scroll-fab:focus-visible::before,
.scroll-fab:hover::after,
.scroll-fab:focus-visible::after {
  opacity: 1;
  animation: btn-shine-sweep 900ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .scroll-fab::before,
  .scroll-fab::after {
    display: none;
  }
}

.scroll-fab.is-visible {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}

.scroll-fab:hover,
.scroll-fab:focus-visible {
  background: #fff;
  color: #000;
}

.scroll-fab.is-visible:hover,
.scroll-fab.is-visible:focus-visible {
  transform: scale(1) translate(-0.25rem, -0.25rem);
  box-shadow: 0.25rem 0.25rem #000;
}

.scroll-fab.is-visible:active {
  transform: scale(1) translate(0, 0);
  box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
  .scroll-fab {
    transition: opacity 200ms ease, background-color 200ms ease, color 200ms ease;
    transform: none;
  }

  .scroll-fab.is-visible:hover,
  .scroll-fab.is-visible:focus-visible,
  .scroll-fab.is-visible:active {
    transform: none;
    box-shadow: none;
  }
}

@media (max-width: 860px) {
  .career-grid,
  .education-grid,
  .info-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 520px) {
  .career-grid,
  .education-grid,
  .info-grid {
    grid-template-columns: 1fr;
  }
}

/* ---- Career detail bottom sheet ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 400ms var(--ease-out);
}

.modal-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.modal {
  display: flex;
  flex-direction: column;
  width: calc(100% - 64px);
  max-width: 1200px;
  max-height: calc(100vh - 64px);
  margin-bottom: calc(32px + env(safe-area-inset-bottom, 0px));
  background: #fff;
  border: 1px solid #ededed;
  border-radius: 12px;
  box-shadow: 0 8px 8px -4px rgba(0, 0, 0, 0.12), 0 12px 24px -4px rgba(0, 0, 0, 0.2);
  overflow: hidden;
  transform: translateY(100%);
  opacity: 0;
  transition: transform 480ms var(--ease-drawer), opacity 480ms var(--ease-drawer);
}

.modal-overlay.is-open .modal {
  transform: translateY(0);
  opacity: 1;
}

/* ---- Mobile: true bottom sheet ----
   Matches the site's mobile breakpoint (720px) rather than the modal's
   own narrower one, so it reads as "mobile" at the same point everything
   else on the page does. Sized off its own content (`.modal` has no
   fixed height) and capped so that once that content is tall enough to
   hit the cap, the sheet's top edge always lands exactly 24px from the
   top of the screen — beyond that, `.modal-body`'s `overflow-y: auto`
   (set above) scrolls the content while `.modal-header` (flex: none)
   stays put, instead of letting the sheet keep growing off-screen. */
@media (max-width: 720px) {
  .modal {
    width: 100%;
    max-height: calc(100dvh - 24px - 8px - env(safe-area-inset-bottom, 0px));
    margin-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    border-radius: 20px 20px 0 0;
  }
}

.modal-header {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: clamp(24px, 5vw, 56px) clamp(24px, 5vw, 56px) 0;
}

.modal-header__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
}

.modal-title {
  margin: 0;
  font-weight: 800;
  font-size: clamp(22px, 3vw, 32px);
  line-height: 1.375;
  color: #000;
}

.modal-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin: 8px 0 0;
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

.modal-meta__sep {
  flex: none;
  width: 1px;
  height: 14px;
  background: #d9d9d9;
}

.modal-meta a {
  font-weight: 700;
  color: inherit;
  text-decoration: underline;
}

.modal-close {
  flex: none;
}

.modal-divider {
  height: 2px;
  width: 100%;
  background: #000;
  margin-top: 16px;
}

.modal-body {
  flex: 1;
  overflow-y: auto;
  padding: clamp(24px, 5vw, 56px);
  display: flex;
  flex-direction: column;
  gap: 20px;
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

.modal-body p { margin: 0; }

.modal-body a {
  font-weight: 700;
  color: inherit;
  text-decoration: underline;
}

body.modal-open {
  overflow: hidden;
}

/* ---- Hire-me modal: reuses .modal-overlay/.modal/.modal-header exactly
   as the career detail sheet does, just narrower (it's a short form, not
   a long bio) and with form fields instead of read-only .modal-body text. ---- */
.modal--form {
  max-width: 720px;
}

#hireMeModal .modal-header {
  gap: 24px;
  padding: 16px 16px 0;
}

/* Icon-only close button — shared by any modal that uses it (hire-me
   form, career detail sheet): neutral, borderless circle with a light-
   grey hover fill, and none of `.btn--tertiary`'s shine-sweep pseudo-
   elements (built for dark nav buttons, not a plain modal close). */
.modal-overlay .modal-close.btn--icon-only {
  border-color: transparent;
  background: transparent;
  color: #000;
  box-shadow: none;
  border-radius: 999px;
}

.modal-overlay .modal-close.btn--icon-only:hover,
.modal-overlay .modal-close.btn--icon-only:focus-visible {
  background: #f0f0f0;
}

.modal-overlay .modal-close.btn--icon-only::before,
.modal-overlay .modal-close.btn--icon-only::after {
  display: none;
}

#hireMeModal .modal-meta {
  margin-top: 12px;
}

.hire-form {
  display: flex;
  flex-direction: column;
  gap: 32px;
  padding: 24px 16px 16px;
  overflow-y: auto;
}

.form-row {
  display: flex;
  gap: 16px;
}

.form-row .form-field {
  flex: 1 1 0;
  min-width: 0;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.form-field label,
.form-label {
  font-weight: 700;
  font-size: 14px;
  color: #000;
}

.form-field input[type="text"],
.form-field input[type="email"],
.form-field textarea {
  width: 100%;
  padding: 12px 16px;
  border: 1px solid #e5e5e5;
  border-radius: 8px;
  background: #fafafa;
  color: #000;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  transition: border-color 150ms ease, background-color 150ms ease;
}

.form-field input:focus,
.form-field textarea:focus {
  outline: none;
  border-color: #000;
  background: #fff;
}

.form-field textarea {
  resize: vertical;
  min-height: 100px;
  font-family: var(--font-body);
}

.form-segmented {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.form-segmented__option {
  position: relative;
  cursor: pointer;
}

.form-segmented__option input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.form-segmented__option span {
  display: inline-flex;
  align-items: center;
  padding: 10px 16px;
  border: 1px solid #e5e5e5;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  color: #555;
  background: #fafafa;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}

.form-segmented__option input:checked + span {
  background: #000;
  border-color: #000;
  color: #fff;
}

.form-segmented__option input:focus-visible + span {
  outline: 2px solid #000;
  outline-offset: 2px;
}

.hire-form__submit,
.hire-form__submit:hover,
.hire-form__submit:active,
.hire-form__submit:focus {
  align-self: flex-start;
  color: #fff;
  border: none;
}

.hire-form__status {
  margin: 0;
  font-size: 14px;
  color: #555;
}

@media (prefers-reduced-motion: reduce) {
  .modal-overlay,
  .modal {
    transition: opacity 160ms ease;
    transform: none;
  }
}

/* ==========================================================================
   Portfolio page
   ========================================================================== */
.portfolio-main {
  position: relative;
  padding: clamp(20px, 6vw, 56px);
}

/* Generous side margins from tablet up (the grid just fills what's left, so
   the cards scale down proportionally); phones keep the original padding. */
@media (min-width: 601px) {
  .portfolio-main {
    padding-inline: clamp(40px, 9vw, 150px);
  }
}

.portfolio-cards {
  display: flex;
  flex-direction: column;
  gap: 56px;
  width: 100%;
}

/* ---- Split staggered reveal cards ----
   Adapted from a CodePen by Simey ("Split Card Hover Effect",
   https://codepen.io/simeydotme/pen/abqYGWa): on hover/focus, two dark
   panels wipe in from the left, the image zooms slightly, and the title
   (per-letter) and description (per-word) rise into view with a stagger
   driven by --char-index/--word-index — set by Splitting.js. ---- */
.portfolio-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  width: 100%;
}

.split-card {
  position: relative;
  display: flex;
  flex-direction: column;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: 12px;
  box-shadow: 0 8px 8px -4px rgba(0, 0, 0, 0.12), 0 12px 24px -4px rgba(0, 0, 0, 0.2);
  transition: box-shadow 400ms var(--ease-in-out);
  cursor: pointer;
}

.split-card > * { z-index: 2; }

/* Plain pass-through wrapper — the scroll parallax transform lives on
   `.split-card` itself instead, so this just needs to fill the card. */
.split-card__img-wrap {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.split-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 600ms var(--ease-out);
}

/* The overlay layers. On phones `::after` is the permanent bottom gradient
   (overridden in the <=520px block below); `::before` is the desktop
   hover overlay defined in the next section. */
.split-card::before,
.split-card::after {
  content: '';
  position: absolute;
  z-index: 1;
  pointer-events: none;
}

.split-card::after {
  left: 0;
  width: 100%;
}

/* ---- Desktop hover: a plain dark overlay fades in over the artwork (the
   artwork also eases in a subtle zoom, below). Phones skip this and use the
   permanent bottom gradient instead. ---- */
@media (min-width: 521px) {
  .split-card::after { display: none; }

  .split-card::before {
    inset: 0;
    background: linear-gradient(180deg, var(--overlay-start), var(--overlay-end));
    opacity: 0;
    transition: opacity 300ms var(--ease-out);
  }

  .split-card:hover::before,
  .split-card:focus-visible::before {
    opacity: 1;
  }
}

.split-card:hover,
.split-card:focus-visible {
  box-shadow: 0 16px 24px -8px var(--shadow-color), 0 24px 40px -8px rgba(0, 0, 0, 0.3);
  outline: none;
}

.split-card:hover .split-card__img,
.split-card:focus-visible .split-card__img {
  transform: scale(1.08);
}

.split-card__text {
  position: absolute;
  inset: 20px;
  top: auto;
}

.split-card__title {
  margin: 0 0 4px;
  font-weight: 800;
  font-size: clamp(18px, 2vw, 22px);
  line-height: 1.3;
}

.split-card__desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
}

.split-card__title .char,
.split-card__desc .word {
  display: inline-block;
  position: relative;
  color: var(--highlight, #fff);
  opacity: 0;
  transform: translateY(16px);
  transition-property: transform, opacity, color;
  transition-timing-function: var(--ease-out);
  transition-duration: 500ms, 500ms, 900ms;
}

.split-card:hover .split-card__title .char,
.split-card:hover .split-card__desc .word,
.split-card:focus-visible .split-card__title .char,
.split-card:focus-visible .split-card__desc .word {
  opacity: 1;
  transform: translateY(0);
}

.split-card:hover .split-card__title .char,
.split-card:focus-visible .split-card__title .char {
  transition-delay: calc(150ms + var(--char-index) * 45ms);
}

.split-card:hover .split-card__desc .word,
.split-card:focus-visible .split-card__desc .word {
  transition-delay: calc(150ms + var(--word-index) * 18ms);
}

.split-card {
  --highlight: #fff;
}

/* Single flat black wipe overlay, same across every card (was previously
   a different colored gradient sampled per card). */
.split-card {
  --overlay-start: rgba(0, 0, 0, 0.62);
  --overlay-end: rgba(0, 0, 0, 0.95);
  --shadow-color: rgba(0, 0, 0, 0.45);
}

@media (prefers-reduced-motion: reduce) {
  .split-card__img,
  .split-card::before,
  .split-card::after,
  .split-card__title .char,
  .split-card__desc .word {
    transition-duration: 150ms;
  }

  .split-card:hover .split-card__img,
  .split-card:focus-visible .split-card__img {
    transform: none;
  }
}

@media (max-width: 860px) {
  .portfolio-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 520px) {
  .portfolio-grid { grid-template-columns: 1fr; }

  .split-card { aspect-ratio: 3 / 3.6; }

  /* Hover doesn't exist on touch — show the bottom gradient and title
     permanently instead of waiting for an interaction that won't happen. */
  .split-card::before {
    display: none;
  }

  .split-card::after {
    top: auto;
    bottom: 0;
    height: 130px;
    background-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.75) 55%, rgba(0, 0, 0, 0.9) 100%);
    background-size: 100% 100%;
    background-position: center;
    transform: scaleX(1);
    transition: none;
  }

  .split-card__desc {
    display: none;
  }

  .split-card__title .char {
    opacity: 1;
    transform: translateY(0);
    transition: none;
  }
}

/* ==========================================================================
   Case study detail page
   ========================================================================== */
.case-main {
  padding: clamp(20px, 6vw, 56px);
}

.case-cards {
  display: flex;
  flex-direction: column;
  gap: 56px;
  width: 100%;
}

/* ---- Hero: full-bleed coloured banner (breaks out of .case-main's
   padding via negative margins, then restores it as its own inset) ---- */
.case-hero {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 48px;
  padding: calc(var(--navbar-h, 96px) + env(safe-area-inset-top, 0px) + clamp(24px, 5vw, 48px)) 0 clamp(48px, 8vw, 96px) 56px;
  background: #0C2C32;
  overflow: hidden;
}

.case-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: #000;
  opacity: 0;
  pointer-events: none;
}

/* Reserves the (short) scroll distance the fixed hero stays visible for —
   decoupled from the hero's own full-viewport height, so a single scroll
   is enough to reveal the content instead of a full viewport-height's
   worth of scrolling. */
.case-hero-spacer {
  height: 100vh;
  height: 100dvh;
}

/* Everything after the hero rides on top of it as the page scrolls, so the
   fixed hero appears to sit still while the rest of the case study covers
   it — and reverses cleanly when scrolling back up. */
.case-scroll-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 56px;
  /* Fade in from transparent over the top ~200px instead of a hard cut, so
     the sticky hero (blurred + darkened by then) melts into this panel
     rather than being sliced off. Slightly taller than the top padding so
     the first row of cards still emerges from the tail of the fade.
     Solid for the rest of the panel. */
  background: linear-gradient(to bottom, transparent 0, var(--color-bg, #121212) 200px, var(--color-bg, #121212) 100%);
  /* Bleed to the same full-width edges as the hero (which breaks out of
     .case-main's padding via negative margins) so the covering panel has
     no gap at the sides for the hero to show through, then restore the
     normal inset via matching padding so the content inside still lines
     up with the rest of the page. */
  width: calc(100% + clamp(40px, 12vw, 112px));
  margin-left: calc(-1 * clamp(20px, 6vw, 56px));
  margin-right: calc(-1 * clamp(20px, 6vw, 56px));
  /* Also bleed past .case-main's own bottom padding — otherwise that strip
     sits below this panel with nothing covering it, letting the fixed
     hero show through at the very end of the page. */
  margin-bottom: calc(-1 * clamp(20px, 6vw, 56px));
  padding-left: clamp(20px, 6vw, 56px);
  padding-right: clamp(20px, 6vw, 56px);
  padding-top: clamp(40px, 6vw, 72px);
  padding-bottom: clamp(20px, 6vw, 56px);
}


.case-hero__text {
  display: flex;
  flex: 0 1 560px;
  min-width: 0;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

.case-hero__scroll-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(16px, 3vh, 32px);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: rgba(255, 255, 255, 0.4);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: opacity 220ms var(--ease-out), visibility 220ms;
}

.case-hero__scroll-cue.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.case-hero__scroll-cue svg {
  animation: case-scroll-bounce 1.6s var(--ease-in-out) infinite;
}

@keyframes case-scroll-bounce {
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  50% { transform: translateY(6px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .case-hero__scroll-cue svg { animation: none; }
}

/* ---- Section quick-jump rail: fixed left nav that appears once the
   hero has scrolled past, letting the reader jump between sections ---- */
/* Hover zone along the left edge — hovering anywhere in this strip (once
   the nav has scrolled into view) expands the collapsed icon into the
   full section list, same as hovering the nav itself. */
.section-nav-trigger {
  position: fixed;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 160px;
  height: min(70vh, 560px);
  z-index: 14;
  pointer-events: none;
}

.section-nav-trigger.is-active {
  pointer-events: auto;
}

.section-nav {
  position: fixed;
  left: 8px;
  top: 50%;
  transform: translate(-8px, -50%);
  z-index: 15;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 250ms var(--ease-out), transform 250ms var(--ease-out), visibility 250ms;
}

.section-nav.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(0, -50%);
}

/* The list drives the container's real size (normal flow); the icon and
   backdrop are both absolutely centered on top of it, so the collapsed
   icon always sits at the exact vertical center regardless of list
   length, and the backdrop always matches the list's rendered bounds —
   no separate width/height to keep in sync, no extra top padding. */
.section-nav__list {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 4px 12px;
  width: max-content;
  opacity: 0;
  transform: translateX(-6px);
  pointer-events: none;
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

/* Blurred scrim behind the expanded panel, so the flyout reads clearly
   above the page content instead of sitting flush on top of it. */
.section-nav__backdrop {
  position: absolute;
  inset: -12px -16px;
  border-radius: 0 20px 20px 0;
  background: rgba(40, 44, 48, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 20px 48px -16px rgba(0, 0, 0, 0.6);
  opacity: 0;
  transform: scale(0.96);
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out);
  pointer-events: none;
}

.section-nav__icon {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #121212;
  color: #fff;
  opacity: 1;
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}

.section-nav-trigger:hover + .section-nav,
.section-nav:hover,
.section-nav:focus-within {
  transform: translate(0, -50%);
}

.section-nav-trigger:hover + .section-nav .section-nav__backdrop,
.section-nav:hover .section-nav__backdrop,
.section-nav:focus-within .section-nav__backdrop {
  opacity: 1;
  transform: scale(1);
}

.section-nav-trigger:hover + .section-nav .section-nav__icon,
.section-nav:hover .section-nav__icon,
.section-nav:focus-within .section-nav__icon {
  opacity: 0;
  transform: translateY(-50%) scale(0.9);
}

.section-nav-trigger:hover + .section-nav .section-nav__list,
.section-nav:hover .section-nav__list,
.section-nav:focus-within .section-nav__list {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}

.section-nav__item {
  display: flex;
  align-items: center;
  gap: 10px;
  color: #94a3ad;
}

.section-nav__tick {
  flex: none;
  width: 16px;
  height: 2px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.2);
  transition: width 220ms var(--ease-out), background-color 220ms var(--ease-out);
}

.section-nav__label {
  font-weight: 700;
  font-size: 13px;
  white-space: nowrap;
  opacity: 1;
  transition: color 200ms var(--ease-out);
}

.section-nav__item:hover .section-nav__tick,
.section-nav__item:focus-visible .section-nav__tick {
  background: #55e1fd;
}

.section-nav__item:hover .section-nav__label,
.section-nav__item:focus-visible .section-nav__label {
  color: #005c87;
}

.section-nav__item.is-active .section-nav__tick {
  width: 32px;
  background: #005c87;
}

.section-nav__item.is-active .section-nav__label {
  color: #005c87;
}

@media (max-width: 1180px) {
  .section-nav-trigger { display: none; }
}

@media (min-width: 861px) and (max-width: 1180px) {
  .section-nav { display: none; }
}

/* ---- Mobile: the rail becomes a floating action button bottom-right.
   Tap to open the list (instead of hover), tap again/outside to close. ---- */
@media (max-width: 860px) {
  .section-nav {
    left: auto;
    right: 8px;
    top: auto;
    bottom: 8px;
    transform: translateY(12px);
  }

  .section-nav.is-visible {
    transform: translateY(0);
  }

  .section-nav__backdrop {
    display: none;
  }

  .section-nav__icon {
    position: static;
    width: 48px;
    height: 48px;
    box-shadow: 0 12px 28px -8px rgba(0, 0, 0, 0.55);
  }

  .section-nav__list {
    position: absolute;
    right: 0;
    bottom: calc(100% + 14px);
    padding: 12px 16px;
    border-radius: 20px;
    background: rgba(40, 44, 48, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.14);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    box-shadow: 0 20px 48px -16px rgba(0, 0, 0, 0.6);
    align-items: flex-end;
    transform: translateY(8px);
  }

  .section-nav__label {
    text-align: right;
  }

  .section-nav.is-open .section-nav__list {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .section-nav,
  .section-nav__tick,
  .section-nav__label {
    transition: opacity 150ms ease;
  }
}

.case-hero__meta {
  display: flex;
  align-items: center;
  gap: 8px;
}

.case-hero__tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 8px;
  border-radius: 24px;
  background: rgba(0, 0, 0, 0.38);
  color: #fff;
  font-weight: 500;
  font-size: 12px;
  line-height: 14px;
  text-transform: uppercase;
}

.case-hero__tag-icon {
  width: 12px;
  height: 12px;
  flex: none;
}

.case-hero__logo {
  height: 18px;
  width: auto;
}

.case-hero--payroll .case-hero__logo {
  filter: brightness(0) invert(1);
}

.case-hero__title {
  margin: 8px 0 0;
  font-weight: 800;
  font-size: 80px;
  line-height: 1.15;
  color: #000;
}

.case-hero__subtitle {
  margin: 0;
  font-size: clamp(16px, 1.6vw, 22px);
  font-weight: 500;
  line-height: 1.36;
  color: rgba(255, 255, 255, 0.4);
}

.case-hero__image {
  flex: none;
  width: min(45%, 480px);
}

.case-hero__image img {
  width: 100%;
  height: auto;
  display: block;
}

/* Two-layer tilted screenshot treatment (payroll case study banner) */
.case-hero__image--layered {
  position: relative;
  aspect-ratio: 681 / 723;
}

.case-hero__image--layered .case-hero__image-ghost {
  position: absolute;
  top: 4%;
  left: 8%;
  width: 78%;
  height: auto;
  transform: rotate(21deg);
  opacity: 0.35;
  border-radius: 12px;
  z-index: 0;
}

.case-hero__image--layered .case-hero__image-main {
  position: absolute;
  top: 30%;
  left: 0;
  width: 92%;
  height: auto;
  transform: rotate(10deg);
  border-radius: 8px;
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 24px 24px 48px -20px rgba(0, 0, 0, 0.55);
  z-index: 1;
}

/* ---- Section card ---- */
.case-section {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
  padding: clamp(24px, 5vw, 48px);
  background: #f7f8fa;
  border-radius: 24px;
  overflow: hidden;
  position: relative;
}

.case-section > * {
  position: relative;
  z-index: 1;
}

.case-section--row {
  flex-direction: row;
  align-items: flex-start;
}

.case-section__title {
  margin: 0;
  font-weight: 800;
  font-size: clamp(24px, 3vw, 32px);
  line-height: 1.375;
  color: #000;
}

.case-section p {
  margin: 0;
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

.case-section b,
.case-section strong {
  font-weight: 700;
}

.case-two-col {
  display: flex;
  gap: 50px;
  width: 100%;
}

.case-two-col .case-section {
  flex: 1 1 0;
  min-width: 0;
}

/* ---- Pills ---- */
.case-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  width: 100%;
}

.case-pill {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  padding: 8px 16px;
  border-radius: 16px;
  font-size: 16px;
  line-height: 1.75;
  color: #000;
  background: #fff;
}

.case-pill--blue { background: #e6edfb; }
.case-pill--green { background: #dbf0d4; }
.case-pill--pink { background: #f8e0e1; }

/* ---- Quote ---- */
.case-quote {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  padding: 24px 0;
  width: 100%;
}

.case-quote__mark {
  flex: none;
  width: 34px;
  height: 31px;
  transform: scaleY(-1);
}

.case-quote__mark img {
  width: 100%;
  height: 100%;
  display: block;
}

.case-quote p {
  flex: 1 1 0;
  margin: 0;
  font-weight: 500;
  font-size: clamp(20px, 2.6vw, 32px);
  line-height: 1.4;
  color: #7a7e85;
}

/* ---- Icon feature cards (Key improvements / Impact) ---- */
.case-feature-grid {
  display: flex;
  gap: 12px;
  width: 100%;
}

/* When Key improvements / Impact sit side by side in a .case-two-col, stack
   each section's three cards vertically instead of cramming them into a
   half-width row. */
.case-two-col .case-feature-grid {
  flex-direction: column;
  gap: 20px;
}

.case-feature-card {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  align-items: center;
  gap: 24px;
  padding: 24px;
  background: #fff;
  border-radius: 24px;
}

.case-feature-card__icon {
  flex: none;
  width: 32px;
  height: 32px;
}

.case-feature-card__icon img {
  width: 100%;
  height: 100%;
  display: block;
}

.case-feature-card p {
  margin: 0;
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

/* Lets a .case-feature-grid wrap onto multiple rows (e.g. 4-up lists)
   instead of squeezing every card onto one line. */
.case-feature-grid--wrap {
  flex-wrap: wrap;
}

.case-feature-grid--wrap .case-feature-card {
  min-width: 220px;
}

/* Blue-tinted feature card variant (e.g. "key enhancements" callouts) */
.case-feature-card--tint {
  background: #e6edfb;
}

body.case-page .case-feature-card--tint { background: #17233b; }
body.case-page .case-feature-card--tint p { color: #b9cdf5; }
body.case-page .case-feature-card--tint .case-feature-card__icon img { filter: brightness(0) invert(1); }

/* Numbered circle badge (e.g. onboarding-flow steps) */
.case-step-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border: 1px solid #354756;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 700;
  color: #354756;
}

body.case-page .case-step-badge {
  border-color: #8fa3b3;
  color: #cdd9e2;
}

/* Row of .case-info-card siblings (e.g. the three claim types, cohort
   cards) — stacks to one per row once a card would get too narrow to
   read comfortably. */
.case-info-row {
  display: flex;
  gap: 16px;
  width: 100%;
}

@media (max-width: 700px) {
  .case-info-row {
    flex-direction: column;
  }
}

/* Vertical icon + title + paragraph card (e.g. testing phases) */
.case-info-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 1 1 0;
  min-width: 0;
  padding: 16px;
  border-radius: 16px;
  background: #fff;
}

.case-info-card__icon {
  width: 32px;
  height: 32px;
}

.case-info-card__icon img {
  width: 100%;
  height: 100%;
  display: block;
}

.case-info-card h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: #000;
}

.case-info-card p {
  margin: 0;
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

body.case-page .case-info-card { background: #232323; }
body.case-page .case-info-card h3,
body.case-page .case-info-card p { color: #fff; }
body.case-page .case-info-card__icon img { filter: brightness(0) invert(1); }

body.case-page.case-byjus .case-info-card { background: rgba(255, 255, 255, 0.08); }
body.case-page.case-shinc .case-info-card { background: rgba(255, 255, 255, 0.05); }

/* ---- Highlight tri-cards (User-Centric / Design System / Readability) ---- */
.case-highlight-grid {
  display: flex;
  gap: 12px;
  width: 100%;
}

.case-highlight-card {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  background: #e6edfb;
  border-radius: 16px;
}

.case-highlight-card__title {
  margin: 0;
  font-weight: 700;
  font-size: 18px;
  line-height: 1.75;
  color: #000;
}

.case-highlight-card ul {
  margin: 0;
  padding-left: 24px;
  list-style: disc;
}

.case-highlight-card li {
  font-size: 16px;
  line-height: 1.75;
  color: #000;
}

/* ---- Media ---- */
.case-media {
  width: 100%;
  border-radius: 16px;
  overflow: hidden;
}

.case-media img {
  width: 100%;
  height: auto;
  display: block;
}

.case-media-row {
  display: flex;
  gap: 24px;
  width: 100%;
}

.case-media-row .case-media {
  flex: 1 1 0;
  min-width: 0;
}

/* ---- Media placeholder: drop-zone for a screen/image that hasn't been
   added yet — swap it for a real `.case-media` + <img> once the asset is
   ready. A faint diagonal texture + icon chip keep it feeling like a
   designed slot rather than a bare wireframe box. Aspect ratio defaults to
   a wide banner; `--tall`/`--square` cover the other shapes a case study
   needs (portrait app screens, diagrams/matrices). ---- */
.case-media--placeholder {
  aspect-ratio: 16 / 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 24px;
  text-align: center;
  border: 1.5px dashed rgba(0, 0, 0, 0.14);
  background:
    repeating-linear-gradient(135deg, rgba(24, 82, 254, 0.05) 0px, rgba(24, 82, 254, 0.05) 1px, transparent 1px, transparent 13px),
    rgba(0, 0, 0, 0.02);
  color: rgba(0, 0, 0, 0.45);
}

.case-media--tall { aspect-ratio: 4 / 5; }
.case-media--square { aspect-ratio: 1 / 1; }

.case-media--placeholder__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: rgba(24, 82, 254, 0.12);
  color: #1852fe;
}

.case-media--placeholder__icon svg {
  width: 20px;
  height: 20px;
}

.case-media--placeholder span {
  max-width: 320px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.5;
}

body.case-page .case-media--placeholder {
  border-color: rgba(255, 255, 255, 0.14);
  background:
    repeating-linear-gradient(135deg, rgba(24, 82, 254, 0.07) 0px, rgba(24, 82, 254, 0.07) 1px, transparent 1px, transparent 13px),
    rgba(255, 255, 255, 0.025);
  color: rgba(255, 255, 255, 0.45);
}

body.case-page .case-media--placeholder__icon {
  background: rgba(24, 82, 254, 0.18);
  color: #82a4ff;
}

/* Hero image slot uses the same treatment, sized to match the real
   hero image column instead of the wide `.case-media` aspect ratio. */
.case-hero__image-placeholder {
  aspect-ratio: 4 / 4.6;
  border-radius: 16px;
}

/* ---- Icon chip: the same small circular marine-tinted icon container as
   the media placeholders, reused on cards so a case study's icon language
   stays consistent instead of introducing a second style. ---- */
.case-icon-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: rgba(24, 82, 254, 0.12);
  color: #1852fe;
}

.case-icon-chip svg {
  width: 20px;
  height: 20px;
}

body.case-page .case-icon-chip {
  background: rgba(24, 82, 254, 0.18);
  color: #82a4ff;
}

/* ---- Lessons grid: an even 2x2 (icon + title + body, stacked) instead
   of flex-wrap's uneven 3-then-1 row, so every card reads as one group
   rather than the last one looking orphaned on its own line. ---- */
.case-lessons-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
  width: 100%;
}

.case-lessons-grid .case-feature-card {
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.case-lessons-grid .case-icon-chip {
  margin-bottom: 12px;
}

.case-lessons-grid .case-feature-card h3 {
  margin: 0;
  font-size: 17px;
  font-weight: 800;
}

.case-lessons-grid .case-feature-card p {
  margin: 0;
}

/* Match the page's regular body copy instead of the tint variant's
   blue-tinted paragraph color. */
body.case-page .case-lessons-grid .case-feature-card p {
  color: #ccc;
}

@media (max-width: 640px) {
  .case-lessons-grid {
    grid-template-columns: 1fr;
  }
}

/* ---- Text-beside-media row: a shorter passage paired with a compact
   (tall/square) image instead of the usual full-bleed banner — used
   sparingly to break up a long run of stacked full-width media. Reverse
   the order on alternating instances for a zigzag rhythm down the page. ---- */
.case-media-pair {
  display: flex;
  align-items: center;
  gap: clamp(24px, 4vw, 48px);
  width: 100%;
}

.case-media-pair__text {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 1 1 0;
  min-width: 0;
}

.case-media-pair__text p { margin: 0; }

.case-media-pair .case-media {
  flex: none;
  width: min(38%, 320px);
}

.case-media-pair--reverse {
  flex-direction: row-reverse;
}

@media (max-width: 700px) {
  .case-media-pair {
    flex-direction: column;
  }

  .case-media-pair .case-media {
    width: 100%;
  }
}

/* ---- Lead line: a short emphasised statement introducing a subsection
   (e.g. "One vessel, several claims.") ---- */
.case-lead {
  margin: 0;
  font-weight: 700;
  font-size: clamp(20px, 2.2vw, 26px);
  line-height: 1.4;
  color: #1852fe;
}

/* ---- Structural flow diagram: shows how a parent claim relates to its
   children (charterers, increments) without needing a real screenshot. ---- */
.case-flow {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: clamp(24px, 4vw, 40px) 24px;
  border-radius: 16px;
  background: #f4f6fb;
}

.case-flow__node {
  padding: 12px 22px;
  border-radius: 10px;
  background: #fff;
  border: 1px solid #dde3ee;
  font-weight: 700;
  font-size: 15px;
  color: #000;
  text-align: center;
  white-space: nowrap;
}

.case-flow__node--parent {
  background: #1852fe;
  border-color: #1852fe;
  color: #fff;
}

.case-flow__node small {
  display: block;
  margin-top: 2px;
  font-weight: 500;
  font-size: 12px;
  opacity: 0.75;
}

.case-flow__connector {
  width: 1px;
  height: 20px;
  background: #dde3ee;
}

.case-flow__branches {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
}

.case-flow--row {
  flex-direction: row;
  flex-wrap: wrap;
}

.case-flow__arrow {
  display: flex;
  align-items: center;
  color: #99a3b3;
  font-size: 18px;
}

body.case-page .case-flow { background: rgba(255, 255, 255, 0.04); }
body.case-page .case-flow__node { background: #101d3a; border-color: #2a3f6b; color: #e7ecf7; }
body.case-page .case-flow__connector { background: #2a3f6b; }
body.case-page .case-flow__arrow { color: #5c6c8a; }

@media (max-width: 520px) {
  .case-flow--row {
    flex-direction: column;
  }
}

/* ---- Shell-component demo (card header with title/tag/button group) ---- */
.case-shell-demo {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
}

.case-shell-demo__rows {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
}

.case-shell-demo__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  width: 100%;
  padding: 10px 16px;
  background: #fff;
  border: 1px solid #d9dfe7;
  border-radius: 8px;
}

.case-shell-demo__title-group {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.case-shell-demo__title {
  font-weight: 700;
  font-size: 14px;
  color: #5d6f7e;
}

.case-shell-demo__sep {
  width: 1px;
  height: 14px;
  background: #d9dfe7;
}

.case-shell-demo__subtext {
  font-size: 12px;
  color: #5d6f7e;
}

.case-shell-demo__tag {
  font-size: 11px;
  font-weight: 700;
  color: #fff;
  background: #2a9f58;
  border-radius: 12px;
  padding: 2px 10px;
}

.case-shell-demo__actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.case-shell-demo__btn {
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  padding: 6px 12px;
  border-radius: 4px;
  border: 1px solid transparent;
  cursor: default;
}

.case-shell-demo__btn--ghost { color: #1e8fa3; background: transparent; }
.case-shell-demo__btn--outline { color: #1e8fa3; background: #fff; border-color: #1e8fa3; }
.case-shell-demo__btn--solid { color: #fff; background: #1e8fa3; }

.case-shell-demo__divider {
  width: 1px;
  height: 20px;
  background: #d9dfe7;
}

.case-shell-demo__chevron {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  border: none;
  background: transparent;
  color: #5d6f7e;
  font-size: 16px;
  line-height: 1;
  cursor: default;
}

/* ---- Legacy vs. modernised design comparison ---- */
.case-compare {
  display: flex;
  flex-direction: row;
  gap: 32px;
  align-items: flex-start;
  width: 100%;
}

.case-compare__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.case-compare__title {
  margin: 0;
  font-weight: 800;
  font-size: clamp(22px, 3vw, 32px);
  line-height: 1.35;
  text-align: center;
}

.case-compare__title--legacy { color: #969dac; }
.case-compare__title--modern { color: #3792a4; }

.case-compare__panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
  border-radius: 24px;
  padding: 32px 24px;
  width: 100%;
}

.case-compare__panel--legacy { background: #f7f8fa; }
.case-compare__panel--modern { background: #eff9fb; }

.case-compare__panel img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 6px;
  border: 1px solid #bac1c8;
}

@media (max-width: 900px) {
  .case-compare { flex-direction: column; }
}

/* ---- Closing statement ---- */
.case-closing {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  width: 100%;
  padding: 24px 0;
}

.case-closing__mark {
  flex: none;
  width: 48px;
  height: 45px;
  transform: scaleY(-1);
  opacity: 0.5;
}

.case-closing__mark img {
  width: 100%;
  height: 100%;
  display: block;
}

.case-closing p {
  margin: 0 0 16px;
  font-weight: 800;
  font-style: italic;
  font-size: clamp(18px, 2.2vw, 24px);
  line-height: 1.4;
  color: #384257;
}

.case-closing p:last-child { margin-bottom: 0; }

@media (max-width: 860px) {
  .case-hero {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    padding-left: clamp(20px, 6vw, 56px);
  }

  .case-hero__text {
    flex: none;
  }

  .case-hero__title {
    font-size: clamp(32px, 9vw, 56px);
  }

  .case-hero__image {
    width: 100%;
    order: -1;
  }

  .case-two-col {
    flex-direction: column;
  }

  /* `flex: 1 1 0` on `.case-section` makes sense for the desktop row
     (flex-basis:0 splits width evenly) but once the container above
     switches to a column, that same flex-basis applies to height instead
     — collapsing each card down to just its title, with the rest of the
     content clipped by `overflow: hidden`. Let each card size to its own
     content on mobile instead. */
  .case-two-col .case-section {
    flex: none;
  }

  .case-section--row {
    flex-direction: column;
  }

  .case-pills {
    flex-direction: column;
    align-items: stretch;
  }

  .case-pill {
    justify-content: center;
    text-align: center;
  }

  .case-feature-grid,
  .case-highlight-grid {
    flex-direction: column;
  }

  .case-media-row {
    flex-direction: column;
  }

  .case-media-row .case-media {
    flex: none;
    width: 100%;
  }

  .case-quote p,
  .case-closing p {
    font-size: clamp(18px, 5vw, 24px);
  }
}

/* ==========================================================================
   Landing page — dark theme (scoped to index.html only)
   ========================================================================== */
body.page-home {
  --color-bg: #121212;
}

body.page-home .hero-bio {
  color: #999999;
}

body.page-home .hero-eyebrow {
  color: #cccccc;
}

body.page-home .hero-title {
  color: #ffffff;
}

body.page-home .btn:focus-visible {
  outline-color: #fff;
}

/* ==========================================================================
   About page — dark theme (scoped to about.html only)
   ========================================================================== */
body.page-about {
  --color-bg: #121212;
}

body.page-about .navbar.is-scrolled {
  background: rgba(18, 18, 18, 0.72);
}

body.page-about .btn--secondary,
body.page-about .btn--tertiary {
  --shine: rgba(255, 255, 255, 0.5);
  background: transparent;
  color: #fff;
}

body.page-about .btn--secondary { border-color: #3a3a3a; }
body.page-about .btn--tertiary { border-color: transparent; }

body.page-about .btn:focus-visible {
  outline-color: #fff;
}

.about-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: clamp(56px, 12vh, 112px) 0 clamp(32px, 6vh, 56px);
}

.about-hero .gooey-heading {
  position: relative;
  z-index: 1;
  margin: 0;
  text-align: center;
}

.about-hero__attribution {
  margin: 16px 0 0;
  font-family: 'Imperial Script', cursive;
  font-size: clamp(22px, 2.4vw, 30px);
  font-weight: 400;
  color: #999999;
}

body.page-about .gooey-heading { color: #999999; }

body.page-about .section-title { color: #fff; }

body.page-about .divider { background: rgba(255, 255, 255, 0.15); }

body.page-about .career-item:hover,
body.page-about .career-item:focus-visible {
  background: rgba(255, 255, 255, 0.08);
}

body.page-about .role-title,
body.page-about .edu-year,
body.page-about .info-label {
  color: #fff;
}

body.page-about .date-range,
body.page-about .chevron-icon,
body.page-about .meta-item,
body.page-about .meta-icon,
body.page-about .role-company,
body.page-about .edu-item p,
body.page-about .info-item p {
  color: #999999;
}

/* Quiet experience note beside the Career title (total + relevant), in a
   light grey regular weight so it informs without competing with the title. */
.section-title__meta {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-left: 16px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0;
  color: #8a8a8a;
  vertical-align: middle;
}

.tenure-item { white-space: nowrap; }
.tenure-sep { opacity: 0.6; }

@media (max-width: 560px) {
  .section-title__meta {
    display: flex;
    margin: 8px 0 0;
  }

  .tenure-sep { display: none; }
}

body.page-about .aboutme-footer { background: #121212; }

body.page-about .scroll-fab {
  background: #fff;
  color: #121212;
  border-color: #fff;
}

/* ==========================================================================
   Portfolio page — dark theme + floating glass navbar (scoped to portfolio.html)
   ========================================================================== */
body.page-portfolio {
  --color-bg: #121212;
}

/* ---- Floating glass-pill navbar, styled after Skiper UI's header:
   centered, translucent + blurred, rounded, bordered. Shared across any
   page that opts in via the `navbar--glass` class. ---- */
.navbar--glass {
  /* Clears the notch/Dynamic Island status-bar area instead of floating
     underneath it. */
  top: calc(16px + env(safe-area-inset-top, 0px));
  width: calc(100% - 32px);
  max-width: 800px;
  margin: 0 auto;
  padding: 8px;
  gap: 16px;
  background: rgba(18, 18, 18, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 20px;
  box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}

/* Scrolled state changes background/shadow only — padding and button
   sizes stay put so the header never visibly resizes while scrolling. */
.navbar--glass.is-scrolled {
  padding: 8px;
  background: rgba(18, 18, 18, 0.78);
  box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.navbar--glass.is-scrolled .btn--tertiary,
.navbar--glass.is-scrolled .btn--secondary {
  font-size: clamp(14px, 1vw, 16px);
}

.navbar--glass.is-scrolled .btn--tertiary,
.navbar--glass.is-scrolled .btn--secondary {
  padding: 11px 20px;
}

.navbar--glass.is-scrolled .btn--tertiary svg {
  width: 20px;
  height: 20px;
}

.navbar--glass.is-scrolled .btn--icon-only {
  padding: 11px;
}

@media (max-width: 520px) {
  .navbar--glass {
    width: calc(100% - 24px);
    padding: 8px;
    gap: 12px;
  }
}

@media (max-width: 860px) {
  .navbar--glass {
    transition: padding 220ms var(--ease-out), box-shadow 220ms var(--ease-out), background-color 220ms var(--ease-out), transform 200ms var(--ease-out), opacity 200ms var(--ease-out);
  }

  .navbar--glass.is-hidden-scroll {
    transform: translateY(-140%);
    opacity: 0;
  }
}

@media (max-width: 640px) {
  .navbar--glass:has(.case-switcher) {
    flex-wrap: nowrap;
  }

  /* Icon-only on mobile for any plain secondary nav link (back button,
     "About me"/"My Works", etc.) — the dropdown trigger keeps its label
     since that text is the whole point of the control. */
  .navbar--glass .btn--secondary:not(.case-switcher__trigger) {
    padding: 11px;
    gap: 0;
  }

  .navbar--glass .btn--secondary:not(.case-switcher__trigger) span {
    display: none;
  }

  .navbar--glass .case-switcher {
    flex: 1 1 auto;
    min-width: 0;
  }

  .navbar--glass .case-switcher__trigger {
    width: 100%;
    max-width: none;
  }
}

body.page-portfolio .btn--secondary {
  --shine: rgba(255, 255, 255, 0.5);
  background: transparent;
  color: #fff;
  border-color: #3a3a3a;
}

body.page-portfolio .btn:focus-visible {
  outline-color: #fff;
}

body.page-portfolio .scroll-fab {
  background: #fff;
  color: #121212;
  border-color: #fff;
}

/* ---- Ambient gradient wash that fades in behind the glass navbar once
   the page scrolls — echoes the glow used on the About header ---- */
.scroll-glow {
  position: fixed;
  top: -220px;
  left: 50%;
  transform: translateX(-50%);
  width: min(760px, 110vw);
  height: 320px;
  background: radial-gradient(ellipse 55% 55% at 50% 30%, rgba(66, 133, 255, 0.16), rgba(66, 133, 255, 0) 72%);
  filter: blur(40px);
  pointer-events: none;
  z-index: 5;
  opacity: 0;
  transition: opacity 500ms ease;
}

body.page-portfolio:has(.navbar.is-scrolled) .scroll-glow {
  opacity: 1;
}

/* ==========================================================================
   Progressive blur — edge blur/fade for content scrolling under the sticky
   header, adapted from Skiper UI's Progressive Blur (skiper41)
   https://skiper-ui.com/v1/skiper41
   ========================================================================== */
.progressive-blur-top {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 4;
  width: 100%;
  height: 120px;
  pointer-events: none;
  user-select: none;
  background: linear-gradient(to top, transparent, var(--color-bg));
  -webkit-mask-image: linear-gradient(to bottom, var(--color-bg) 50%, transparent);
  mask-image: linear-gradient(to bottom, var(--color-bg) 50%, transparent);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

/* ==========================================================================
   Case study pages — dark theme (scoped to body.case-page)
   ========================================================================== */
body.case-page {
  --color-bg: #121212;
}

/* The case-hero sits right under the navbar and is always-on at rest, so
   the generic blur strip would otherwise show a blur over it before any
   scrolling happens. Fade it in via JS (page-chrome.js) once scrolling
   actually starts instead of hiding it outright. */
body.case-page .progressive-blur-top {
  opacity: 0;
  transition: opacity 200ms ease;
}

body.case-page .progressive-blur-top.is-visible {
  opacity: 1;
}

body.case-page .btn--secondary,
body.case-page .btn--tertiary {
  --shine: rgba(255, 255, 255, 0.5);
  background: transparent;
  color: #fff;
}

body.case-page .btn--secondary { border-color: #3a3a3a; }
body.case-page .btn--tertiary { border-color: transparent; }

body.case-page .btn:focus-visible {
  outline-color: #fff;
}

/* Match the header's secondary-button style (same shine-sweep hover, same
   pill shape) instead of the standalone invert-on-hover FAB look, once
   .btn--secondary is combined onto .scroll-fab — with a solid black fill
   at rest and on hover, on every page. */
.scroll-fab.btn--secondary {
  width: auto;
  height: auto;
  border-radius: 999px;
}

/* #scrollFab (id specificity) so this reliably beats page-scoped
   .btn--secondary background overrides (e.g. body.page-portfolio's
   transparent fill) regardless of stylesheet order. */
#scrollFab.btn--secondary {
  background: #252525;
  color: #fff;
  border-color: #252525;
}

#scrollFab.btn--secondary:hover,
#scrollFab.btn--secondary:focus-visible {
  background: #252525;
  color: #fff;
}

.scroll-fab.btn--secondary.is-visible:hover,
.scroll-fab.btn--secondary.is-visible:focus-visible {
  transform: scale(1.01);
  box-shadow: none;
}

.scroll-fab.btn--secondary.is-visible:active {
  transform: scale(0.97);
}

body.case-page .case-hero {
  background: #0C2C32;
}

body.case-page .case-hero--payroll {
  background: #1b3866;
}

body.case-page .case-hero--bloopme {
  background: #2b1151;
}

.case-hero__logo--bloopme {
  height: 26px;
}

body.case-page.case-bloopme .case-section {
  background: #142d45;
}

body.case-page .case-hero--byjus {
  background: #3d2e7b;
}

body.case-page.case-byjus .case-section {
  background: #241a3d;
}

body.case-page .case-hero--shinc {
  background: #001E4C;
}

/* SHINC's title is a full sentence rather than a short product name, so it
   needs a smaller size than the other case studies to land the intended
   "Making laytime claims" / "easier" two-line break instead of wrapping
   word-by-word down the column. */
.case-hero--shinc .case-hero__title {
  font-size: clamp(32px, 3.6vw, 48px);
}

/* Figma's SHINC logo lockup (icon + wordmark) is proportioned wider/
   shorter than the other case studies' logos, so it needs its own height
   instead of the shared 18px rule. */
.case-hero--shinc .case-hero__logo {
  height: 30px;
}

/* Figma dims the subtitle further than the shared 40%-opacity default. */
.case-hero--shinc .case-hero__subtitle {
  color: rgba(255, 255, 255, 0.24);
}

body.case-page.case-shinc .case-section {
  background: #0d2140;
}

body.case-page.case-shinc .case-pill--blue {
  background: rgba(24, 82, 254, 0.18);
  color: #b7c9ff;
}

body.case-page.case-shinc .case-lead {
  color: #6f9bff;
}

.case-hero__wordmark {
  margin: 0;
  font-weight: 800;
  font-size: 20px;
  letter-spacing: 0.02em;
  color: #fff;
}

body.case-page .case-hero__title {
  color: #fff;
}

body.case-page .case-section { background: #1a1a1a; }
body.case-page .case-section__title { color: #fff; }
body.case-page .case-section h3 { color: #fff; }
body.case-page .case-section p { color: #ccc; }

body.case-page .case-quote p { color: #a0a5ac; }

body.case-page .case-feature-card { background: none; padding: 0; }
body.case-page .case-feature-card p { color: #fff; }
body.case-page .case-feature-card__icon img { filter: brightness(0) invert(1); }

body.case-page .case-highlight-card { background: #232323; }
body.case-page .case-highlight-card__title,
body.case-page .case-highlight-card li { color: #fff; }

body.case-page .case-pill {
  background: #232323;
  color: #f0f0f0;
}

body.case-page .case-pill--blue { background: #17233b; color: #b9cdf5; }
body.case-page .case-pill--green { background: #17281c; color: #b6e7ae; }
body.case-page .case-pill--pink { background: #2c1a20; color: #f0bcc4; }

body.case-page .case-closing p { color: #d5d9e0; }

body.case-page .case-shell-demo p { color: #ccc; }
body.case-page .case-shell-demo__row { background: #1f1f1f; border-color: #3a3a3a; }
body.case-page .case-shell-demo__title,
body.case-page .case-shell-demo__subtext,
body.case-page .case-shell-demo__chevron { color: #c7cdd6; }
body.case-page .case-shell-demo__sep,
body.case-page .case-shell-demo__divider { background: #3a3a3a; }
body.case-page .case-shell-demo__btn--outline { background: #1f1f1f; }

body.case-page .case-compare__panel--legacy { background: #1a1a1a; }
body.case-page .case-compare__panel--modern { background: #12242a; }
body.case-page .case-compare__panel img { border-color: #3a3a3a; }

body.case-page .section-nav__tick { background: rgba(255, 255, 255, 0.25); }
body.case-page .section-nav__item.is-active .section-nav__tick,
body.case-page .section-nav__item.is-active .section-nav__label {
  color: #7fd4ff;
  background: #7fd4ff;
}
body.case-page .section-nav__item.is-active .section-nav__label { background: none; }
body.case-page .section-nav__item:hover .section-nav__label,
body.case-page .section-nav__item:focus-visible .section-nav__label {
  color: #7fd4ff;
}

body.case-page .case-switcher__menu {
  background: rgba(18, 18, 18, 0.88);
  border-color: rgba(255, 255, 255, 0.09);
  box-shadow: 0 16px 40px -8px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(40px);
  -webkit-backdrop-filter: blur(40px);
}

body.case-page .case-switcher__item { color: #fff; }

body.case-page .case-switcher__item:hover,
body.case-page .case-switcher__item:focus-visible {
  background: rgba(255, 255, 255, 0.08);
}

body.case-page .case-switcher__item.is-current,
body.case-page .case-switcher__item.is-current svg {
  color: #7fd4ff;
}

/* ==========================================================================
   Illustrations page — standalone (not the case-study template), a scroll-
   linked parallax gallery inspired by Skiper UI's "Oliver Parallax"
   (skiper-ui.com/v1/skiper30): columns of images pre-built by JS (column
   count responds to viewport width) each drift up/down at a different rate
   as the page scrolls the gallery through view, clipped by the section's
   own overflow:hidden so it reads as a self-contained scrolling grid rather
   than plain page scroll.
   ========================================================================== */
body.page-illustrations,
html:has(body.page-illustrations) {
  overscroll-behavior: none;
}

.illustration-gallery {
  position: relative;
  display: flex;
  gap: clamp(12px, 2vw, 24px);
  height: 100dvh;
  margin: 0 auto;
  padding: clamp(100px, 18vh, 160px) clamp(20px, 6vw, 56px) clamp(40px, 6vw, 64px);
  overflow: hidden;
  touch-action: none;
}

.illustration-gallery__col {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 2vw, 24px);
  will-change: transform;
}

.illustration-gallery__col:nth-child(2n) {
  margin-top: clamp(20px, 4vw, 48px);
}

.illustration-gallery__col img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 16px;
  display: block;
  background: #1a1a1a;
}

@media (max-width: 640px) {
  .illustration-gallery {
    gap: 10px;
    padding-inline: 16px;
  }

  .illustration-gallery__col {
    gap: 10px;
  }
}

/* ==========================================================================
   Branding page — standalone (not the case-study template): just the
   shared navbar + case-switcher header, then each brand's mockup collage
   (a single flattened image per Figma section) full-bleed in its own
   `.case-media` card, stacked with generous breathing room between them.
   ========================================================================== */
.branding-main {
  display: flex;
  flex-direction: column;
  gap: clamp(56px, 8vw, 96px);
  max-width: 1400px;
  margin: 0 auto;
  padding: clamp(120px, 20vh, 200px) clamp(20px, 6vw, 56px) clamp(56px, 8vw, 96px);
}

/* ==========================================================================
   Hire-me CTA — sits right beside the glass navbar pill (24px gap), and the
   pill+button pair is centered together as one unit via .navbar-row, on
   every page that has one. Reuses .btn--primary as-is so the style stays
   consistent with the homepage hero CTAs, while sitting outside the pill so
   the placement reads as a deliberate, distinct action rather than another
   nav item. The pill caps out at 800px wide, and this button's own
   footprint needs roughly 180px — that only fits past ~1160px, so the
   switchover uses the same 1180px breakpoint as the section-nav rail.
   Below it, the pair splits to the row's two edges (pill shrinks to make
   room) and the button drops its label, becoming a compact icon-only CTA
   that still rides along the top with the header instead of floating
   elsewhere on the page. */
.navbar-row {
  position: sticky;
  top: 16px;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  width: 100%;
}

.navbar-row .navbar--glass {
  position: static;
  margin: 0;
}

.hire-me-btn {
  flex: none;
}

/* .btn--primary's own text color lives on .btn--primary__letter (part of
   the letter-crossfade markup this simplified button skips), so without
   this the label just inherits the page's default dark text color — near-
   invisible against the button's own near-black fill. */
.hire-me-btn__label {
  color: #fff;
}

@media (max-width: 1180px) {
  .navbar-row {
    justify-content: space-between;
    gap: 12px;
    padding: 0 clamp(12px, 3vw, 16px);
  }

  .navbar-row .navbar--glass {
    width: auto;
    max-width: none;
    flex: 1 1 auto;
    min-width: 0;
  }

  .hire-me-btn {
    --border-radius: 999px;
    padding: 14px;
  }

  .hire-me-btn__label {
    display: none;
  }

  .hire-me-btn .btn--primary__icon {
    margin: 0;
  }
}

