/*
 * Stefan Adams portfolio — component and layout styles.
 * All colour, type, radius and spacing values come from tokens.css (Figma variables).
 * Breakpoints match the Figma "Breakpoint" collection:
 *   XS <568 · S ≥568 · M ≥768 · L ≥1024 · XL ≥1440
 */

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

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

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  margin: 0;
  background: var(--page-bg-default);
  color: var(--text-primary-default);
  font-family: var(--typography-body-font-family);
  font-weight: var(--typography-body-font-weight-regular);
  font-size: var(--typography-body-font-size-m);
  line-height: var(--typography-body-line-height-m);
  -webkit-font-smoothing: antialiased;
}

/* Safari (iPhone) paints the area behind its floating toolbar with this background when
   it can't show the page there. Grey matches the footer at the end of the page; while
   the mobile menu is open over the page content, main.js switches it to that colour. */
body.is-menu-over-content {
  background: var(--surface-primary-default);
}

h1, h2, h3, p, blockquote, ol, ul {
  margin: 0;
}

ol, ul {
  padding: 0;
  list-style: none;
}

img {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
}

button {
  font: inherit;
  color: inherit;
}

/* Text selection: inverse overlay with inverse text so selected words stay readable */
::selection {
  background: var(--surface-overlay-inverse);
  color: var(--text-primary-inverse);
}

:focus-visible {
  outline: 2px solid var(--border-primary-default);
  outline-offset: 2px;
}

strong {
  font-weight: var(--typography-body-font-weight-bold);
}

/* Theme-specific content (images, icons, labels) */
:root[data-theme="dark"] .theme-light,
:root:not([data-theme="dark"]) .theme-dark {
  display: none !important;
}

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

/* Sections focused by in-page links (main.js) aren't controls, so no focus outline */
[tabindex="-1"]:focus {
  outline: none;
}

.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 100;
  padding: 8px 16px;
  border-radius: var(--border-radius-full);
  background: var(--interactive-action-primary-default-enabled);
  color: var(--text-primary-inverse);
  font-weight: var(--typography-body-font-weight-bold);
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: none;
}

/* ---------- Icons ----------
 * Single-colour SVGs are drawn as masks so they take the current text colour
 * and switch automatically between light and dark themes.
 */
.icon {
  display: inline-block;
  flex: none;
  width: var(--icon-size-20);
  height: var(--icon-size-20);
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

.icon--16 { width: var(--icon-size-16); height: var(--icon-size-16); }
.icon--20 { width: var(--icon-size-20); height: var(--icon-size-20); }
.icon--28 { width: 28px; height: 28px; }
.icon--32 { width: var(--icon-size-32); height: var(--icon-size-32); }

/* ---------- Typography helpers ---------- */
.heading-l {
  font-family: var(--typography-heading-font-family);
  font-weight: var(--typography-heading-font-weight-semi-bold);
  font-size: var(--typography-heading-font-size-l);
  line-height: var(--typography-heading-line-height-l);
}

.heading-m {
  font-family: var(--typography-heading-font-family);
  font-weight: var(--typography-heading-font-weight-semi-bold);
  font-size: var(--typography-heading-font-size-m);
  line-height: var(--typography-heading-line-height-m);
}

.display {
  font-family: var(--typography-display-font-family);
  font-weight: var(--typography-display-font-weight-regular);
  letter-spacing: var(--typography-display-letter-spacing);
  color: var(--text-secondary-default);
}

/* ---------- Links ---------- */
.body-link {
  text-decoration: underline;
  text-underline-position: from-font;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.body-link--s {
  font-size: var(--typography-body-font-size-s);
  line-height: var(--typography-body-line-height-s);
  color: var(--interactive-navigation-secondary-default-enabled);
}

@media (hover: hover) { .body-link--s:hover { color: var(--interactive-navigation-secondary-default-hover); background: var(--interactive-navigation-secondary-default-effect); } }
.body-link--s:active { color: var(--interactive-navigation-secondary-default-pressed); background: var(--interactive-navigation-secondary-default-effect); }

.body-link--bold {
  font-weight: var(--typography-body-font-weight-bold);
  font-size: var(--typography-body-font-size-m);
  line-height: var(--typography-body-line-height-m);
  color: var(--interactive-navigation-primary-default-enabled);
}

@media (hover: hover) { .body-link--bold:hover { color: var(--interactive-navigation-primary-default-hover); background: var(--interactive-navigation-primary-default-effect); } }
.body-link--bold:active { color: var(--interactive-navigation-primary-default-pressed); background: var(--interactive-navigation-primary-default-effect); }

.display-link {
  font-family: var(--typography-display-font-family);
  font-size: var(--typography-display-font-size-l);
  line-height: var(--typography-display-line-height-l);
  letter-spacing: var(--typography-display-letter-spacing);
  color: var(--interactive-navigation-secondary-default-enabled);
  text-decoration: underline;
  text-underline-position: from-font;
  transition: color 0.15s ease, background-color 0.15s ease;
}

@media (hover: hover) { .display-link:hover { color: var(--interactive-navigation-secondary-default-hover); background: var(--interactive-navigation-secondary-default-effect); } }
.display-link:active { color: var(--interactive-navigation-secondary-default-pressed); background: var(--interactive-navigation-secondary-default-effect); }

/* ---------- Buttons ----------
 * Figma Button / Icon button states: hover and pressed change the fill and add a
 * 4px ring outside the button (Effect layer stroke, OUTSIDE) using the matching
 * interactive/action/…/effect token. Each variant sets --btn-effect for its ring.
 */
.btn,
.icon-btn {
  --btn-effect: transparent;
  border: var(--border-width-1) solid transparent;
  border-radius: var(--border-radius-full);
  cursor: pointer;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.btn:active,
.icon-btn:active {
  box-shadow: 0 0 0 4px var(--btn-effect);
}

@media (hover: hover) {
  .btn:hover,
  .icon-btn:hover {
    box-shadow: 0 0 0 4px var(--btn-effect);
  }
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 48px;
  padding: 0 20px;
  font-family: var(--typography-body-font-family);
  font-weight: var(--typography-body-font-weight-bold);
  font-size: var(--typography-body-font-size-m);
  line-height: var(--typography-body-line-height-m);
  white-space: nowrap;
  text-decoration: none;
}

.btn--primary {
  --btn-effect: var(--interactive-action-primary-default-effect);
  background: var(--interactive-action-primary-default-enabled);
  border-color: var(--border-transparent-inverse);
  color: var(--text-primary-inverse);
}
@media (hover: hover) { .btn--primary:hover { background: var(--interactive-action-primary-default-hover); } }
.btn--primary:active { background: var(--interactive-action-primary-default-pressed); }

.btn--secondary {
  --btn-effect: var(--interactive-action-secondary-default-effect);
  background: var(--interactive-action-secondary-default-enabled);
  border-color: var(--border-transparent-default);
  color: var(--text-primary-default);
}
@media (hover: hover) { .btn--secondary:hover { background: var(--interactive-action-secondary-default-hover); } }
.btn--secondary:active { background: var(--interactive-action-secondary-default-pressed); }

.btn--primary-inverse {
  --btn-effect: var(--interactive-action-primary-inverse-effect);
  background: var(--interactive-action-primary-inverse-enabled);
  border-color: var(--border-transparent-default);
  color: var(--text-primary-default);
}
@media (hover: hover) { .btn--primary-inverse:hover { background: var(--interactive-action-primary-inverse-hover); } }
.btn--primary-inverse:active { background: var(--interactive-action-primary-inverse-pressed); }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 48px;
  height: 48px;
  padding: 0;
}

.icon-btn--s {
  width: 36px;
  height: 36px;
}

.icon-btn--secondary {
  --btn-effect: var(--interactive-action-secondary-default-effect);
  background: var(--interactive-action-secondary-default-enabled);
  border-color: var(--border-transparent-default);
  color: var(--icon-primary-default);
}
@media (hover: hover) { .icon-btn--secondary:hover { background: var(--interactive-action-secondary-default-hover); } }
.icon-btn--secondary:active { background: var(--interactive-action-secondary-default-pressed); }
.icon-btn--secondary:disabled {
  cursor: default;
  background: var(--interactive-action-secondary-default-enabled);
  box-shadow: none;
  color: var(--icon-secondary-default);
  opacity: 0.5;
}

.icon-btn--primary {
  --btn-effect: var(--interactive-action-primary-default-effect);
  background: var(--interactive-action-primary-default-enabled);
  border-color: var(--border-transparent-inverse);
  color: var(--icon-primary-inverse);
}
@media (hover: hover) { .icon-btn--primary:hover { background: var(--interactive-action-primary-default-hover); } }
.icon-btn--primary:active { background: var(--interactive-action-primary-default-pressed); }

.icon-btn--secondary-inverse {
  --btn-effect: var(--interactive-action-secondary-inverse-effect);
  background: var(--interactive-action-secondary-inverse-enabled);
  border-color: var(--border-transparent-inverse);
  color: var(--icon-primary-inverse);
}
@media (hover: hover) { .icon-btn--secondary-inverse:hover { background: var(--interactive-action-secondary-inverse-hover); } }
.icon-btn--secondary-inverse:active { background: var(--interactive-action-secondary-inverse-pressed); }

.icon-btn--primary-inverse {
  --btn-effect: var(--interactive-action-primary-inverse-effect);
  background: var(--interactive-action-primary-inverse-enabled);
  border-color: var(--border-transparent-default);
  color: var(--icon-primary-default);
}

/* ---------- Menu icon morph ----------
 * Hamburger ↔ close as one shape: three lines drawn to match the menu icon
 * (x 3.3–16.7, y 5/10/15, 1.67 stroke, round caps). Opening rotates the top and
 * bottom lines 45° into the X — stretched 6% to match the close icon’s
 * (5,5)→(15,15) diagonals — while the middle line shrinks away.
 * A subtle blur pulses during the move, matching the home button.
 */
.morph {
  display: inline-block;
  width: var(--icon-size-20);
  height: var(--icon-size-20);
}

.morph svg {
  display: block;
  overflow: visible;
}

.morph__line {
  stroke: currentColor;
  stroke-width: 1.6667;
  stroke-linecap: round;
  transform-box: view-box;
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.morph__line--top { transform-origin: 10px 5px; }
.morph__line--middle { transform-origin: 10px 10px; }
.morph__line--bottom { transform-origin: 10px 15px; }

.morph[data-morph="open"] .morph__line--top {
  transform: translateY(5px) rotate(45deg) scaleX(1.0607);
}

.morph[data-morph="open"] .morph__line--middle {
  opacity: 0;
  transform: scaleX(0);
}

.morph[data-morph="open"] .morph__line--bottom {
  transform: translateY(-5px) rotate(-45deg) scaleX(1.0607);
}

/* Blur pulse while morphing (only after the first interaction, not on page load).
   Two identical keyframes so the animation restarts on every state change. */
@keyframes morph-blur-a { 50% { filter: blur(1.5px); } }
@keyframes morph-blur-b { 50% { filter: blur(1.5px); } }

.morph[data-animated][data-morph="open"] svg { animation: morph-blur-a 0.3s ease; }
.morph[data-animated][data-morph="closed"] svg { animation: morph-blur-b 0.3s ease; }

.morph--instant .morph__line {
  transition: none;
}

.morph--instant svg {
  animation: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .morph__line {
    transition: none;
  }

  .morph svg {
    animation: none !important;
  }
}

/* ---------- Tooltip ----------
 * Figma Tooltip (shown on Icon button hover): centred 12px below the button,
 * 4px 8px padding, fully rounded (border-radius/full), body/s text. Text comes
 * from data-tooltip; the theme toggle swaps to data-tooltip-dark in dark mode.
 * Only for devices with a real hover (mouse/trackpad) plus keyboard focus.
 * --tooltip-shift (set in main.js) nudges it inward when centring would put it
 * within 16px of the screen edge.
 */
[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 12px);
  left: 50%;
  z-index: 30;
  padding: 4px 8px;
  border: var(--border-width-1) solid var(--border-transparent-inverse);
  border-radius: var(--border-radius-full);
  background: var(--interactive-action-primary-default-hover);
  background-clip: padding-box;
  box-shadow: 0 0 20px -8px var(--black-transparent-12);
  color: var(--text-primary-inverse);
  font-family: var(--typography-body-font-family);
  font-weight: var(--typography-body-font-weight-regular);
  font-size: var(--typography-body-font-size-s);
  line-height: var(--typography-body-line-height-s);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(calc(-50% + var(--tooltip-shift, 0px)), -4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}

:root[data-theme="dark"] [data-tooltip-dark]::after {
  content: attr(data-tooltip-dark);
}

[data-tooltip]:focus-visible::after {
  opacity: 1;
  transform: translate(calc(-50% + var(--tooltip-shift, 0px)), 0);
}

@media (hover: hover) {
  [data-tooltip]:hover::after {
    opacity: 1;
    transform: translate(calc(-50% + var(--tooltip-shift, 0px)), 0);
  }
}

/* Reduced motion: tooltips fade in place instead of dropping in */
@media (prefers-reduced-motion: reduce) {
  [data-tooltip]::after {
    transform: translate(calc(-50% + var(--tooltip-shift, 0px)), 0);
    transition: opacity 0.15s ease;
  }
}

/* ---------- Tag ---------- */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  border-radius: var(--border-radius-8);
  background: var(--surface-transparent-default);
  color: var(--text-primary-default);
  font-size: var(--typography-body-font-size-s);
  line-height: var(--typography-body-line-height-s);
  white-space: nowrap;
}

.tag--success {
  background: var(--surface-status-success);
  color: var(--text-status-success);
}

/* ---------- Employment status ---------- */
.status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.status__logo {
  position: relative;
  display: inline-flex;
  border-radius: var(--border-radius-full);
  background: var(--surface-primary-default);
  color: var(--icon-secondary-default);
}

/* Figma: 8px dot with a 2px OUTSIDE stroke (12px overall). The dot sits at
   top -4 / left 16 (or 24), so the outer edge is offset by the stroke width. */
.status__badge {
  position: absolute;
  top: -6px;
  left: 14px;
  box-sizing: content-box;
  width: 8px;
  height: 8px;
  border: var(--border-width-2) solid var(--border-primary-inverse);
  border-radius: var(--border-radius-full);
  background: var(--icon-status-success);
}

.status__badge--28 {
  left: 22px;
}

/* Gentle "live" pulse: a soft ring in the success colour grows out of the dot and
   fades, every 2s. The dot itself stays still. */
.status__badge::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: var(--icon-status-success);
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .status__badge::after {
    animation: status-pulse 2s ease-out infinite;
  }
}

@keyframes status-pulse {
  0% {
    opacity: 0.55;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(2.4);
  }
}

.status__text {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--text-secondary-default);
  font-size: var(--typography-body-font-size-s);
  line-height: var(--typography-body-line-height-s);
}

/* ---------- Page shell ---------- */
.page {
  padding: 0 var(--page-padding);
}

.status-bar {
  display: none;
  justify-content: center;
  padding: 14px 16px;
}

.page-content {
  position: relative;
  border-radius: var(--page-content-border-radius);
  background: var(--surface-primary-default);
  overflow: clip;
}

/* 1px page-content border (Figma: border/transparent/default, inside stroke).
   Below M it's the bottom edge only (above the footer), drawn on top as an overlay so,
   like Figma's inside stroke, it takes no space. From M it's a real border all round. */
.page-content::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5; /* above section content, below the nav (10) and mobile menu (20) */
  border-bottom: var(--border-width-1) solid var(--border-transparent-default);
  border-radius: inherit;
  pointer-events: none;
}

@media (min-width: 768px) {
  .status-bar {
    display: flex;
  }

  .page-content {
    border: var(--border-width-1) solid var(--border-transparent-default);
  }

  .page-content::after {
    display: none;
  }
}

.section {
  display: flex;
  justify-content: center;
  padding: 0 16px;
  background: var(--surface-primary-default);
  overflow: clip;
}

.section--bordered {
  border-top: var(--border-width-1) solid var(--border-transparent-default);
}

.container {
  width: 100%;
  max-width: 1200px;
  padding: 96px 0;
}

/* ---------- Frosted background (nav bar + mobile menu) ----------
 * Figma: progressive background blur, 16px at the top → 0 at the bottom, plus a
 * fill fading from surface/primary-default (top) to surface/overlay/none (bottom).
 * CSS has no progressive blur, so it is built from four stacked blur layers
 * (14 → 7 → 3.5 → 1.75px, softened slightly from Figma's 16px), each masked to its own band so the strength eases down.
 */
.frosted {
  isolation: isolate;
}

.frosted__blur,
.frosted::after {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.frosted__blur span {
  position: absolute;
  inset: 0;
}

/* Let the nav's blur and fade run 32px past its bottom edge so it tails off
   softly instead of stopping at the edge (doesn't affect layout or clicks). */
.nav .frosted__blur,
.nav.frosted::after {
  bottom: -32px;
}

.frosted__blur span:nth-child(1) {
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 25%, transparent 50%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 25%, transparent 50%);
}

.frosted__blur span:nth-child(2) {
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 25%, #000 50%, transparent 75%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 25%, #000 50%, transparent 75%);
}

.frosted__blur span:nth-child(3) {
  -webkit-backdrop-filter: blur(3.5px);
  backdrop-filter: blur(3.5px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 25%, #000 50%, #000 75%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 25%, #000 50%, #000 75%, transparent 100%);
}

.frosted__blur span:nth-child(4) {
  -webkit-backdrop-filter: blur(1.75px);
  backdrop-filter: blur(1.75px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 50%, #000 72%, rgb(0 0 0 / 0.5) 86%, rgb(0 0 0 / 0.15) 95%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 50%, #000 72%, rgb(0 0 0 / 0.5) 86%, rgb(0 0 0 / 0.15) 95%, transparent 100%);
}

.frosted::after {
  content: "";
  /* Eased fade (surface/primary-default → surface/overlay/none) so it tapers off softly */
  background: linear-gradient(
    to bottom,
    var(--surface-primary-default) 0%,
    color-mix(in srgb, var(--surface-primary-default) 73.8%, transparent) 19%,
    color-mix(in srgb, var(--surface-primary-default) 54.1%, transparent) 34%,
    color-mix(in srgb, var(--surface-primary-default) 38.2%, transparent) 47%,
    color-mix(in srgb, var(--surface-primary-default) 27.8%, transparent) 56.5%,
    color-mix(in srgb, var(--surface-primary-default) 19.4%, transparent) 65%,
    color-mix(in srgb, var(--surface-primary-default) 12.6%, transparent) 73%,
    color-mix(in srgb, var(--surface-primary-default) 7.5%, transparent) 80.2%,
    color-mix(in srgb, var(--surface-primary-default) 4.2%, transparent) 86.1%,
    color-mix(in srgb, var(--surface-primary-default) 2.1%, transparent) 91%,
    color-mix(in srgb, var(--surface-primary-default) 0.8%, transparent) 95.2%,
    color-mix(in srgb, var(--surface-primary-default) 0.2%, transparent) 98.2%,
    var(--surface-overlay-none) 100%
  );
}

/* ---------- Navigation ---------- */
.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  height: 92px;
  margin-bottom: -92px;
  padding: 24px 24px 32px;
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: 1056px;
  margin: 0 auto;
}

.nav__home {
  display: block;
  flex: none;
  width: 96px;
  height: 32px;
}

/* Home button: "STEFAN" swaps to "ADAMS" on hover/press (Figma: Home button states).
   Colours follow the primary action tokens, so it works in both themes. */
.nav__home {
  color: var(--interactive-action-primary-default-enabled);
}

/* Both logos are stacked. On hover/press they swap like the brand logo cycler:
   STEFAN lifts 8px, fades and blurs out while ADAMS rises 8px from below and
   sharpens in (same movement, blur and easing as the logos, at a snappier 0.35s).
   Moving away plays it in reverse. */
.nav__home {
  position: relative;
  transition: color 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
  -webkit-tap-highlight-color: transparent;
}

.home-logo {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  transition: opacity 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), filter 0.35s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.home-logo__bg {
  fill: var(--surface-primary-default);
}

/* Waiting below, out of sight */
.home-logo--adams {
  opacity: 0;
  filter: blur(4px);
  transform: translateY(8px);
}

/* Lifted away on hover/press with a mouse or trackpad, at any window size. On touch
   screens a tap keeps STEFAN (the quick swap-and-back flickered) and only changes its
   colour, below. */
@media (hover: hover) {
  .nav__home:active .home-logo--stefan {
    opacity: 0;
    filter: blur(4px);
    transform: translateY(-8px);
  }

  .nav__home:active .home-logo--adams {
    opacity: 1;
    filter: blur(0);
    transform: none;
  }
}

@media (hover: hover) {
  .nav__home:hover .home-logo--stefan {
    opacity: 0;
    filter: blur(4px);
    transform: translateY(-8px);
  }

  .nav__home:hover .home-logo--adams {
    opacity: 1;
    filter: blur(0);
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .nav__home,
  .home-logo {
    transition: none;
  }
}

@media (hover: hover) {
  .nav__home:hover {
    color: var(--interactive-action-primary-default-hover);
  }
}

.nav__home:active {
  color: var(--interactive-action-primary-default-pressed);
}

/* Touch screens: a tap just tints STEFAN in the pressed colour (no ADAMS swap). The
   colour snaps on as you press, so it shows on a quick tap, then eases back. */
@media (hover: none) {
  .nav__home:active {
    transition-duration: 0s;
  }
}

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

.nav__buttons--desktop {
  display: none;
}

@media (min-width: 768px) {
  .nav {
    height: 96px;
    margin-bottom: -96px;
    padding: 16px 16px 32px;
  }

  .nav__inner {
    padding: 0 8px;
  }

  .nav__home:not(.nav__home--s) {
    width: 108px;
    height: 36px;
  }

  .nav__buttons--desktop {
    display: flex;
  }

  .nav__buttons--mobile {
    display: none;
  }
}

@media (min-width: 1024px) {
  .nav__inner {
    padding: 0 56px;
  }
}

/* ---------- Mobile menu ---------- */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 24px;
}

.mobile-menu[hidden] {
  display: none;
}

.mobile-menu__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.mobile-menu__buttons {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}

/* Micro-interaction: when the menu opens, the buttons drop into place one after
   another, fading and un-blurring like the menu icon morph. The animation
   restarts on every open because the menu goes from hidden to shown. */
@keyframes menu-item-in {
  from {
    opacity: 0;
    filter: blur(4px);
    transform: translateY(-12px);
  }
}

.mobile-menu__buttons > * {
  animation: menu-item-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.mobile-menu__buttons > :nth-child(2) { animation-delay: 0.06s; }
.mobile-menu__buttons > :nth-child(3) { animation-delay: 0.12s; }
.mobile-menu__buttons > :nth-child(4) { animation-delay: 0.18s; }

@media (prefers-reduced-motion: reduce) {
  .mobile-menu__buttons > * {
    animation: none;
  }
}

/* Figma "Section / Job title" (mobile menu): hugs its content, centred, 16px from the bottom */
.mobile-menu__status {
  position: absolute;
  bottom: 16px;
  left: 50%;
  width: max-content;
  max-width: calc(100% - 32px);
  padding: 11px 15px; /* 12px 16px in Figma, minus the 1px border Figma draws inside */
  transform: translateX(-50%);
  border: var(--border-width-1) solid var(--border-transparent-default);
  border-radius: var(--border-radius-full);
  background: var(--surface-secondary-default);
  /* Rises into place after the buttons have dropped in */
  animation: menu-pill-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) 0.24s both;
}

/* Same as the buttons' entrance but from below; keeps the -50% centring offset */
@keyframes menu-pill-in {
  from {
    opacity: 0;
    filter: blur(4px);
    transform: translate(-50%, 12px);
  }
}

/* Closing plays the opening in reverse (main.js adds .is-closing, then hides the menu):
   the pill drops away and the buttons lift back up and blur out, bottom one first. */
@keyframes menu-item-out {
  to {
    opacity: 0;
    filter: blur(4px);
    transform: translateY(-12px);
  }
}

@keyframes menu-pill-out {
  to {
    opacity: 0;
    filter: blur(4px);
    transform: translate(-50%, 12px);
  }
}

/* The page's Message button would show through the frosted menu, so it fades right out
   while the menu is open (main.js toggles body.is-menu-open) and back in as it closes */
.nav__buttons--mobile > [data-link="message"] {
  transition: opacity 0.2s ease;
}

body.is-menu-open .nav__buttons--mobile > [data-link="message"] {
  opacity: 0;
}

.mobile-menu.is-closing {
  pointer-events: none; /* nothing is tappable while it leaves */
}

.mobile-menu.is-closing .mobile-menu__status {
  animation: menu-pill-out 0.2s cubic-bezier(0.4, 0, 0.6, 1) both;
}

.mobile-menu.is-closing .mobile-menu__buttons > * {
  animation: menu-item-out 0.2s cubic-bezier(0.4, 0, 0.6, 1) both;
}

.mobile-menu.is-closing .mobile-menu__buttons > :nth-last-child(1) { animation-delay: 0.03s; }
.mobile-menu.is-closing .mobile-menu__buttons > :nth-last-child(2) { animation-delay: 0.06s; }
.mobile-menu.is-closing .mobile-menu__buttons > :nth-last-child(3) { animation-delay: 0.09s; }
.mobile-menu.is-closing .mobile-menu__buttons > :nth-last-child(4) { animation-delay: 0.12s; }

/* The frosted background moves with everything else: it fades in as the buttons drop
   in, and on close fades out alongside them, finishing as the last button leaves
   (0.1s + 0.22s = the buttons' 0.32s), so nothing snaps away at the end. */
@keyframes menu-bg-in {
  from {
    opacity: 0;
  }
}

@keyframes menu-bg-out {
  to {
    opacity: 0;
  }
}

.mobile-menu > .frosted__blur,
.mobile-menu::after {
  animation: menu-bg-in 0.35s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.mobile-menu.is-closing > .frosted__blur,
.mobile-menu.is-closing::after {
  animation: menu-bg-out 0.22s cubic-bezier(0.4, 0, 0.6, 1) 0.1s both;
}

@media (prefers-reduced-motion: reduce) {
  .mobile-menu__status,
  .mobile-menu > .frosted__blur,
  .mobile-menu::after {
    animation: none;
  }
}

/* Switching theme with a button (main.js), at any size: the new mode cross-fades in
   evenly over the old screen (from the mobile menu, in place of its close animation),
   so the whole screen changes at once. The old screen stays solid underneath. */
::view-transition-old(root) {
  animation: none;
}

::view-transition-new(root) {
  animation: theme-fade-in 0.4s cubic-bezier(0.4, 0, 0.2, 1) both;
}

@keyframes theme-fade-in {
  from {
    opacity: 0;
  }
}
@media (min-width: 768px) {
  .mobile-menu {
    display: none !important;
  }
}

/* ---------- Hero ---------- */
.hero__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 56px;
  padding: 144px 0 0;
}

/* XS + S: the hero fills the screen height, and the people grow to fill the space
   under the button (keeping their proportions), bleeding off both sides as needed.
   svh = the height with the browser's toolbars showing, so it fits without jumping as
   they collapse. Taller content (small phones) just makes it taller. */
@media (max-width: 767px) {
  .hero__container {
    min-height: 100vh; /* fallback for older browsers */
    min-height: 100svh;
  }

  .hero__people-slot {
    position: relative;
    flex: 1 0 auto;
    width: 100%;
    min-height: calc(600px * 248 / 1000); /* the 600px-wide row, when there's no spare room */
  }

  /* The people (and their speech bubble) take the slot's full height, their width follows
     in proportion, centred so both sides bleed off the screen evenly. (.hero__people-slot >
     lifts this above the general .hero__people-wrap rule further down.) */
  .hero__people-slot > .hero__people-wrap {
    position: absolute;
    bottom: 0;
    left: 50%;
    translate: -50% 0;
    height: 100%;
    width: auto;
    aspect-ratio: 1000 / 248;
  }
}

.hero__heading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  max-width: 496px;
  text-align: center;
}

.hero__subtitle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding-bottom: 4px;
  color: var(--text-primary-default);
  font-size: var(--typography-body-font-size-l);
  line-height: var(--typography-body-line-height-l);
}

.avatar {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--border-radius-8);
  overflow: hidden;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__title {
  font-family: var(--typography-heading-font-family);
  font-weight: var(--typography-heading-font-weight-semi-bold);
  font-size: var(--typography-heading-font-size-xxl);
  line-height: var(--typography-heading-line-height-xxl);
  color: var(--text-primary-default);
}

/* Hero headline micro-story, played once on load:
   1. "users" (alone and centred on its line) gets crossed out — a pen line drawn
      left to right, slightly angled with rounded ends.
   2. "users" glides left as "People." makes room for itself.
   3. "People." is handwritten: js/main.js swaps in an SVG copy of the word and a pen
      traces each letter's outline in turn, then the letter fills with ink.
   Reduced-motion visitors (and no-JS) just see the finished headline. */
.hero__title s {
  position: relative;
  text-decoration: none;
}

.hero__title s::after {
  content: "";
  position: absolute;
  top: 0.79em; /* middle of PT Serif's lowercase letters */
  left: -4%;
  width: 108%;
  height: 0.07em;
  border-radius: 999px;
  background: currentColor;
  transform: translateY(-50%) rotate(-3deg);
  transform-origin: left center;
}

[data-handwrite] {
  position: relative;
  display: inline-block;
  white-space: nowrap; /* never wrap a multi-word phrase like "so far..." mid-animation */
  padding-left: 0.25em; /* the word space before the handwritten word */
  box-sizing: content-box; /* width = the word itself; the space is extra */
}

/* While the handwriting plays, the real text is invisible (the SVG copy draws it)
   and the word's box grows from nothing, nudging "users" across. */
/* Before the script takes over, keep the word collapsed so the rest of the heading starts centred */
.motion [data-handwrite]:not(.is-writing):not(.is-done) {
  width: 0;
  padding-left: 0;
  color: transparent;
}

[data-handwrite].is-writing {
  width: var(--word-width);
  color: transparent;
  animation: make-way 0.5s cubic-bezier(0.65, 0, 0.35, 1) 1.2s both;
}

/* "Solo" words (e.g. the Message me! link) have nothing beside them to push across:
   no leading space, no collapse or make-way — they are simply written in place.
   Once the last letter is inked, the underline is drawn left to right like a pen
   stroke. It replaces the text underline, matching the font's own underline
   (0.18em below the baseline, 0.09em thick); --underline-baseline is set by the script.
   Without motion (or JS) the normal text underline is used. */
[data-handwrite="solo"] {
  padding-left: 0;
  --handwriting-offset: 0;
}

.motion [data-handwrite="solo"] {
  text-decoration: none;
}

.motion [data-handwrite="solo"]:not(.is-writing):not(.is-done) {
  width: auto;
}

[data-handwrite="solo"].is-writing {
  animation: none;
}

.motion [data-handwrite="solo"]::after {
  content: "";
  position: absolute;
  top: calc(var(--underline-baseline, 0.8em) + 0.18em);
  left: 0;
  width: 100%;
  height: 0.09em;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
}

.motion [data-handwrite="solo"].is-done::after {
  transform: scaleX(1);
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}

.handwriting {
  position: absolute;
  top: 0;
  left: var(--handwriting-offset, 0.25em);
  overflow: visible;
  color: var(--text-secondary-default);
  pointer-events: none;
}

/* The visible word is solid ink; it only shows where the invisible pen (a mask) has
   been. The pen is a thick stroke tracing each letter's outline in turn, wide enough
   to cover the letter's whole line, so the ink appears along the path, no outline. */
.handwriting__ink {
  fill: currentColor;
}

.handwriting__pen tspan {
  fill: #fff;
  fill-opacity: 0;
  stroke: #fff;
  stroke-width: 0.16em;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--pen-length) var(--pen-length);
  stroke-dashoffset: var(--pen-length);
  animation:
    pen-stroke 0.42s cubic-bezier(0.45, 0.05, 0.55, 0.95) var(--pen-delay) forwards,
    pen-ink 0.2s ease-out calc(var(--pen-delay) + 0.35s) forwards;
}

@media (prefers-reduced-motion: no-preference) {
  .hero__title s::after {
    animation: strike-through 0.45s cubic-bezier(0.65, 0, 0.35, 1) 0.75s both;
  }
}

@keyframes strike-through {
  from {
    transform: translateY(-50%) rotate(-3deg) scaleX(0);
  }
}

@keyframes make-way {
  from {
    width: 0;
    padding-left: 0;
  }
}

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

@keyframes pen-ink {
  to {
    fill-opacity: 1; /* make sure every bit of the letter is revealed */
  }
}

.hero__title .display {
  font-size: var(--typography-display-font-size-xxl);
  line-height: var(--typography-display-line-height-xxl);
}

.hero__text {
  padding: 16px 0 24px;
  /* 2px extra each side: browsers render Inter a fraction wider than Figma, and the
     first line ("…experiences across") is 496.9px — just over the 496px box. */
  width: calc(100% + 4px);
  margin-inline: -2px;
  color: var(--text-secondary-default);
  font-size: var(--typography-body-font-size-l);
  line-height: var(--typography-body-line-height-l);
}

.hero__people {
  --people: url(../assets/people-transparent.png); /* people.png with its off-white paper removed */
  position: relative;
  width: 100%;
  aspect-ratio: 1000 / 248;
  /* Same framing as before, but with room above and to the sides so hopping and swaying
     heads aren't cut off. (clip-path also limits what the hover hit area can reach, so the
     drawing's empty margins never sit over the button above.) */
  clip-path: inset(-15% -16px 0);
}

:root[data-theme="dark"] .hero__people {
  --people: url(../assets/people-dark.png);
}

/* The whole 2400×889 drawing, placed as the old <img> was (cover, 50% 54%): full width,
   moved up by 54% of the part that doesn't fit, so the visible crop is unchanged. */
.hero__people-stage {
  position: absolute;
  inset: 0 0 auto;
  aspect-ratio: 2400 / 889;
  margin-top: -6.6105%; /* (889/2400 − 248/1000) × 54%, as a share of the width */
}

/* Each person: a vertical strip of the drawing, cut in the empty gaps between figures.
   --origin is the middle of their feet, so they sway and hop from the ground. */
.person {
  position: absolute;
  top: 0;
  left: var(--left);
  width: var(--width);
  height: 100%;
  background: var(--people) var(--bg-x) 0 / var(--bg-size) 100% no-repeat;
  transform-origin: var(--origin);
  -webkit-tap-highlight-color: transparent;
}

/* Idle life: each person has their own gentle loop, with different lengths and
   delays so they never fall into step. Hovering one makes them hop. */
@media (prefers-reduced-motion: no-preference) {
  .person {
    animation: var(--idle) ease-in-out var(--delay, 0s) infinite;
  }

  .person:nth-child(1) { --idle: person-sway 4.2s; }
  .person:nth-child(2) { --idle: person-breathe 3.1s; --delay: -1.2s; }
  .person:nth-child(3) { --idle: person-hop 5.3s; --delay: 1.4s; }
  .person:nth-child(4) { --idle: person-sway 5.7s; --delay: -2.1s; }
  .person:nth-child(5) { --idle: person-bob 2.9s; --delay: -0.6s; }
  .person:nth-child(6) { --idle: person-lean 6.1s; --delay: 0.8s; }
  .person:nth-child(7) { --idle: person-hop 6.7s; --delay: 3.6s; }

  /* Tapped on a touch screen (main.js adds .is-jumping for one hop) */
  .person.is-jumping {
    animation: person-jump 0.6s ease-out;
  }

  @media (hover: hover) {
    .person:hover {
      animation: person-jump 0.6s ease-out;
    }
  }
}

@keyframes person-sway {
  0%, 100% { transform: rotate(-1.5deg); }
  50% { transform: rotate(1.5deg); }
}

@keyframes person-breathe {
  0%, 100% { transform: scale(1, 1); }
  50% { transform: scale(1.015, 1.03); }
}

@keyframes person-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-1.2%); }
}

@keyframes person-lean {
  0%, 60%, 100% { transform: rotate(0); }
  20%, 40% { transform: rotate(-2.5deg); }
}

/* Mostly still, then a small hop with a squash on landing */
@keyframes person-hop {
  0%, 78%, 100% { transform: translateY(0) scale(1, 1); }
  82% { transform: translateY(0) scale(1.04, 0.96); }
  88% { transform: translateY(-4%) scale(0.98, 1.03); }
  94% { transform: translateY(0) scale(1.03, 0.97); }
}

@keyframes person-jump {
  0%, 100% { transform: translateY(0) scale(1, 1); }
  15% { transform: translateY(0) scale(1.05, 0.95); }
  45% { transform: translateY(-7%) scale(0.97, 1.04); }
  80% { transform: translateY(0) scale(1.04, 0.96); }
}

/* ---------- Speech bubble (Figma "Speech bubble") ----------
   Pops out of the 6th person (mouth open, arms out) while they're hovered, or for a
   moment when they're tapped on touch screens (.is-talking, set in main.js). It sits
   outside .hero__people so the frame's clip can't cut it off; sizes and positions are
   shares of the drawing's width, so it tracks the person at every breakpoint.
   Tail tip (82.7% across, bottom of the bubble) lands just above their head. */
.hero__people-slot {
  width: 100%;
}

.hero__people-wrap {
  position: relative;
  width: 100%;
  /* Figma: the people never shrink below 600px wide. On narrower screens the row stays
     centred and runs off both edges, cropped by the page (overflow: clip). */
  min-width: 600px;
}

.speech-bubble {
  --tip-x: 75%;    /* the 6th person's head, across the drawing */
  --tip-y: 10.4%;  /* down from the top of the frame */
  position: absolute;
  left: calc(var(--tip-x) - 12.3% * 0.827);
  bottom: calc(100% - var(--tip-y));
  width: 12.3%; /* 123px in Figma, against the 1000px-wide drawing */
  overflow: visible;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.3) rotate(-8deg);
  transform-origin: 82.7% 100%; /* grows out of the tail tip */
  transition: opacity 0.15s ease, transform 0.2s ease-in;
}

.speech-bubble__fill { fill: var(--surface-primary-default); }
.speech-bubble__line { fill: var(--border-primary-default); }
.speech-bubble__text { fill: var(--text-secondary-default); }

.hero__people-wrap.is-talking .speech-bubble {
  opacity: 1;
  transform: none;
  transition: opacity 0.12s ease, transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (hover: hover) {
  .hero__people-wrap:has(.person:nth-child(6):hover) .speech-bubble {
    opacity: 1;
    transform: none;
    transition: opacity 0.12s ease, transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .speech-bubble,
  .hero__people-wrap.is-talking .speech-bubble,
  .hero__people-wrap:has(.person:nth-child(6):hover) .speech-bubble {
    transform: none;
    transition: opacity 0.15s ease;
  }
}

@media (min-width: 1024px) {
  .hero__container {
    gap: 72px;
    padding-top: 144px;
  }
}

/* ---------- Motion: hero entrance + scroll-linked blur reveal ----------
 * The hero sharpens in from a soft blur on load; everything below fades and
 * rises slightly as it scrolls into view (no blur, so nothing looks out of focus). The scroll effect is
 * tied to scroll position (CSS scroll-driven animations), so it scrubs both ways.
 * Browsers without support, and reduced-motion users, simply see the page.
 */
@keyframes blur-in {
  from {
    opacity: 0;
    filter: blur(6px);
    transform: translateY(16px);
  }
}

/* Scroll reveal: fade and rise only, no blur */
@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* Hero: staggered entrance on page load */
  .hero__subtitle,
  .hero__title,
  .hero__text,
  .hero__heading > .btn,
  .hero__people {
    animation: blur-in 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  }

  .hero__title { animation-delay: 0.08s; }
  .hero__text { animation-delay: 0.16s; }
  .hero__heading > .btn { animation-delay: 0.24s; }
  .hero__people { animation-delay: 0.32s; }

  /* Sections below: reveal as they enter the viewport */
  @supports (animation-timeline: view()) {
    .skill-card,
    .work__title,
    .work-card,
    .quote__text,
    .quote__gif,
    .career__title,
    .timeline,
    .freelance,
    .logos,
    .logo-cycler,
    .testimonials__title,
    .carousel,
    .carousel-controls,
    .contact__text,
    .contact__video {
      animation: rise-in linear both;
      animation-timeline: view();
      /* Fully revealed once the element is 160px onto the screen — a fixed distance,
         so tall cards are sharp almost as soon as they appear, like short headings. */
      animation-range: cover 0px cover 160px;
    }

    /* Cards side by side reveal in a slight cascade */
    .skill-card:nth-child(2),
    .work-card:nth-child(3),
    .quote__gif,
    .contact__video {
      animation-range: cover 20px cover 180px;
    }

    .skill-card:nth-child(3) { animation-range: cover 40px cover 200px; }
    .skill-card:nth-child(4) { animation-range: cover 60px cover 220px; }
  }
}

/* ---------- Skills ---------- */
.skills__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

.skill-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 24px 24px 32px;
  border-radius: var(--border-radius-20);
  background: var(--surface-secondary-default);
  color: var(--icon-primary-default);
}

.skill-card .icon {
  margin-bottom: 4px;
}

.skill-card__title {
  font-size: var(--typography-body-font-size-m);
  line-height: var(--typography-body-line-height-m);
  font-weight: var(--typography-body-font-weight-bold);
  color: var(--text-primary-default);
}

.skill-card__text {
  color: var(--text-secondary-default);
}

@media (min-width: 568px) {
  .skills__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

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

/* ---------- Selected work ---------- */
.work__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 64px;
}

.work__title {
  text-align: center;
}

.work__cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  width: 100%;
}

.work-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 20px;
  min-height: 560px;
  padding: var(--component-card-padding);
  border-radius: var(--border-radius-20);
  background: var(--surface-secondary-default);
  color: var(--text-primary-default);
  text-decoration: none;
  transition: box-shadow 0.15s ease;
}

/* Figma Card: hover and pressed add a 4px ring outside the card (primary effect token).
   The arrow button keeps its own hover for when the pointer is directly on it. */
.work-card:active {
  box-shadow: 0 0 0 4px var(--interactive-action-primary-default-effect);
}

@media (hover: hover) {
  .work-card:hover {
    box-shadow: 0 0 0 4px var(--interactive-action-primary-default-effect);
  }
}

.work-card__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.work-card__content .tag {
  margin-bottom: 8px;
  color: var(--text-primary-default);
}

.work-card__title {
  font-size: var(--typography-body-font-size-l);
  line-height: var(--typography-body-line-height-l);
  font-weight: var(--typography-body-font-weight-bold);
}

.work-card__button {
  align-self: flex-end;
}

@media (hover: hover) { .icon-btn--primary-inverse:hover { background: var(--interactive-action-primary-inverse-hover); } }
.icon-btn--primary-inverse:active { background: var(--interactive-action-primary-inverse-pressed); }

@media (min-width: 768px) {
  .work__cards {
    grid-template-columns: repeat(2, 1fr);
  }

  .work-card--wide {
    grid-column: 1 / -1;
  }

  .work-card--wide .work-card__content {
    max-width: calc(50% - 32px);
  }
}

/* ---------- Quote ---------- */
.quote__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 56px;
}

.quote__text {
  color: var(--text-primary-default);
  text-align: center;
  max-width: 488px;
}

/* Handwritten quote marks (Figma: display/l, secondary text colour) */
.quote__mark {
  font-size: var(--typography-display-font-size-l);
  line-height: 0; /* the two fonts sit differently on the line; let the heading set the line height, as in Figma */
}

.quote__gif {
  position: relative;
  width: 100%;
  max-width: 488px;
  aspect-ratio: 789 / 444;
  overflow: hidden;
}

.quote__gif img {
  position: absolute;
  top: -59.33%;
  left: -59.41%;
  width: 218.82%;
  height: 218.76%;
  max-width: none;
  object-fit: cover;
}

/* Side by side from L; XS–M stay stacked */
@media (min-width: 1024px) {
  .quote__container {
    flex-direction: row;
    gap: 64px;
  }

  .quote__text {
    flex: 1 1 0;
    text-align: left;
    max-width: 492px;
  }

  .quote__gif {
    flex: 1 1 0;
    max-width: none;
  }
}

/* ---------- Career ---------- */
.career__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 64px;
}

.career__title {
  max-width: 488px;
  font-family: var(--typography-heading-font-family);
  font-weight: var(--typography-heading-font-weight-semi-bold);
  font-size: var(--typography-heading-font-size-l);
  line-height: var(--typography-heading-line-height-l); /* heading/l/semi-bold */
  text-align: center;
}

/* "so far..." uses display/l/regular: 36 → 44 → 48px across breakpoints */
.career__title .display {
  font-size: var(--typography-display-font-size-l);
  line-height: var(--typography-display-line-height-l);
}

/* Vertical timeline (XS + S): oldest first, Kadfire → BT, like the desktop row */
.timeline {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 358px;
  padding: 40px 32px;
  border-radius: var(--border-radius-20);
  background: var(--surface-secondary-default);
}

.timeline__item {
  display: flex;
  gap: 12px;
}

.timeline__marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: none;
  width: 28px;
  color: var(--icon-primary-default);
}

.timeline__line {
  flex: 1 1 auto;
  width: 2px;
  background: var(--border-transparent-default);
}

/* Vertical: only the line below each logo shows (the one above is for the desktop row) */
.timeline__marker .timeline__line:first-child {
  display: none;
}

.timeline__line--none {
  visibility: hidden;
}

.timeline__logo {
  position: relative;
  display: flex;
}

.timeline__content {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding-bottom: 32px;
}

.timeline__item:last-child .timeline__content {
  padding-bottom: 0;
}

.timeline__role {
  color: var(--text-secondary-default);
}

.timeline__content .tag {
  margin-top: 4px;
}

/* Horizontal timeline (M and up): oldest → newest, left to right */
@media (min-width: 768px) {
  .timeline {
    flex-direction: row;
    max-width: 824px;
    padding: 48px 32px;
  }

  .timeline__item {
    flex: 1 1 0;
    flex-direction: column;
    align-items: center;
    gap: 12px;
  }

  .timeline__marker {
    flex-direction: row;
    width: 100%;
  }

  .timeline__line {
    width: auto;
    height: 2px;
  }

  .timeline__marker .timeline__line:first-child {
    display: block;
  }

  .timeline__content {
    align-items: center;
    padding: 0 16px;
    text-align: center;
  }

  .timeline__item:last-child .timeline__content {
    padding: 0 16px;
  }

  .timeline__role span {
    display: block;
  }

  .timeline__role .timeline__sep {
    display: none;
  }
}

/* ---------- Timeline: line draws in on scroll (preview) ----------
   Tied to the timeline's own scroll position (scroll-driven animation), so it scrubs
   both ways, and plays once per visit: after it finishes, main.js adds .is-drawn and it
   stays drawn. A border/secondary line is drawn over the faint track in date order,
   Kadfire → Lounge → BT (top to bottom on phones, left to right from M), and each role
   fades up as the line reaches it. Reduced motion / unsupported browsers: as before. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .timeline {
      view-timeline: --career block;
    }

    .timeline__line {
      position: relative;
    }

    .timeline__line::after {
      content: "";
      position: absolute;
      inset: 0;
      background: var(--border-secondary-default);
      transform-origin: top;
      animation: line-draw-y linear both;
      animation-timeline: --career;
      animation-range: var(--draw, cover 0% cover 0%);
    }

    .timeline__content {
      animation: career-reach linear both;
      animation-timeline: --career;
      animation-range: var(--reach, cover 4% cover 10%);
    }

    /* Logos fade from a solid pale grey rather than see-through, so the line never shows
       through them (or through Lounge's cut-out "L");
       the waiting colour is icon/secondary-inverse */
    .timeline__marker > .icon,
    .timeline__logo > .icon {
      animation: career-reach-logo linear both;
      animation-timeline: --career;
      animation-range: var(--reach, cover 4% cover 10%);
    }

    /* Played once: leave everything in its finished state */
    .timeline.is-drawn .timeline__line::after,
    .timeline.is-drawn .timeline__content,
    .timeline.is-drawn .timeline__marker > .icon,
    .timeline.is-drawn .timeline__logo > .icon {
      animation: none;
    }

    /* BT's green "available" badge stays off while BT loads in, then switches on once the
       line has fully arrived (.is-drawn): a quick pop, and its live pulse starts from then */
    .timeline__logo .status__badge {
      opacity: 0;
      transform: scale(0);
    }

    .timeline.is-drawn .timeline__logo .status__badge {
      animation: badge-on 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.15s both;
    }

    .timeline.is-drawn .timeline__logo .status__badge::after {
      animation: status-pulse 2s ease-out 0.5s infinite;
    }

    /* Phones: Kadfire → Lounge, then Lounge → BT, as it scrolls into view */
    .timeline__item:nth-child(2) { --reach: cover 25% cover 31%; }
    .timeline__item:nth-child(3) { --reach: cover 43% cover 49%; }
    .timeline__item:nth-child(1) .timeline__line:last-child { --draw: cover 10% cover 27.5%; }
    .timeline__item:nth-child(2) .timeline__line:last-child { --draw: cover 27.5% cover 45%; }

    @media (min-width: 768px) {
      .timeline__line::after {
        transform-origin: left;
        animation-name: line-draw-x;
      }

      .timeline__item:nth-child(2) { --reach: cover 23% cover 29%; }
      .timeline__item:nth-child(3) { --reach: cover 41% cover 47%; }
      .timeline__item:nth-child(1) .timeline__line:last-child { --draw: cover 10% cover 18.75%; }
      .timeline__item:nth-child(2) .timeline__line:first-child { --draw: cover 18.75% cover 27.5%; }
      .timeline__item:nth-child(2) .timeline__line:last-child { --draw: cover 27.5% cover 36.25%; }
      .timeline__item:nth-child(3) .timeline__line:first-child { --draw: cover 36.25% cover 45%; }
    }
  }
}

@keyframes line-draw-y {
  from { transform: scaleY(0); }
}

@keyframes line-draw-x {
  from { transform: scaleX(0); }
}

@keyframes career-reach {
  from {
    opacity: 0.15;
    transform: translateY(6px);
  }
}

@keyframes badge-on {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes career-reach-logo {
  from {
    color: var(--icon-secondary-inverse);
  }
}

.freelance {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 488px;
  color: var(--text-secondary-default);
  text-align: center;
}

.logos {
  display: flex;
  gap: 40px;
  width: 100%;
  max-width: 824px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 32px, #000 calc(100% - 32px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 32px, #000 calc(100% - 32px), transparent);
}

.logos::-webkit-scrollbar {
  display: none;
}

.logos li {
  flex: none;
  width: 88px;
  height: 53px;
}

.logos img {
  width: 100%;
  height: 100%;
}

@media (min-width: 768px) {
  .logos {
    gap: 72px;
  }

  .logos li {
    width: 104px;
    height: 62px;
  }
}

/* Logo cycler (built by js/main.js, replaces the strip above when JS runs).
 * Inspired by mobbin.com: a fixed row of slots; every few seconds each slot swaps
 * to the next brand — the old logo lifts away and blurs out while the new one
 * rises in and sharpens, rippling left to right. 3 slots on phones, 5 from M up. */
.logo-cycler {
  --logo-slots: 3;
  display: grid;
  grid-template-columns: repeat(var(--logo-slots), 1fr);
  justify-items: center;
  width: 100%;
  max-width: 824px;
}

.logo-cycler__slot {
  position: relative;
  width: 88px;
  height: 53px;
}

.logo-cycler__slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

@media (min-width: 768px) {
  .logo-cycler {
    --logo-slots: 5;
  }

  .logo-cycler__slot {
    width: 104px;
    height: 62px;
  }
}

/* ---------- Testimonials ---------- */
.testimonials__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
}

.testimonials__title {
  max-width: 656px;
  padding-bottom: 40px;
  text-align: center;
}

/* The carousel bleeds past the 1200px container to the section edges */
.carousel {
  flex: none;
  width: calc(100vw - 2 * var(--page-padding));
}

.carousel__track {
  position: relative;
  --card-width: min(424px, calc(100vw - 2 * var(--page-padding) - 48px));
  display: flex;
  gap: 16px;
  padding-inline: calc(50% - var(--card-width) / 2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  /* Room below the cards for the hanging character (scroll areas clip their overflow);
     the negative margin cancels it out so the layout is unchanged. */
  padding-bottom: 200px;
  margin-bottom: -200px;
  isolation: isolate; /* keeps the character behind the card but above the section */
}

.carousel__track::-webkit-scrollbar {
  display: none;
}

.testimonial {
  position: relative;
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 16px;
  width: var(--card-width);
  padding: 24px 24px 32px;
  border-radius: var(--border-radius-20);
  background: var(--surface-secondary-default);
  scroll-snap-align: center;
}

.testimonial__header {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.testimonial__avatar {
  flex: none;
  width: 40px;
  height: 40px;
  margin-top: 4px;
  border-radius: var(--border-radius-8);
  object-fit: cover;
}

.testimonial__name {
  color: var(--text-primary-default);
}

.testimonial__name strong {
  display: block;
}

.testimonial__quote {
  color: var(--text-secondary-default);
}

.testimonial--image {
  align-items: center;
  justify-content: center;
}

.testimonial--image img {
  width: 100%;
  max-width: 262px;
  aspect-ratio: 262 / 270;
  object-fit: contain;
}

.carousel-controls {
  display: flex;
  gap: 16px;
  position: relative;
  z-index: 1; /* stay clickable above the carousel's extra space */
}

/* Figma "Image / Man head": hangs from the bottom of Dayle's card, centred, with its
   top tucked behind the card (XS+S 86px wide, 32px tucked; M 103/43; L+XL 96/40). */
.testimonial__hanger {
  position: absolute;
  top: calc(100% - 32px);
  left: 50%;
  z-index: -1;
  width: 86px;
  height: auto;
  transform: translateX(-50%);
  pointer-events: none;
}

@media (min-width: 768px) {
  .testimonial__hanger {
    top: calc(100% - 43px);
    width: 103px;
  }
}

@media (min-width: 1024px) {
  .testimonial__hanger {
    top: calc(100% - 40px);
    width: 96px;
  }
}

/* ---------- Footer banner / contact ----------
 * Spacing from the Figma "Section / Footer banner" variants:
 *   XS+S+M: stacked, text block → video 48, heading group → link 28,
 *           heading → subtext 12, text block max-width 486, centred
 *   L+XL:   side by side 32, heading group → link 40, heading → subtext 20,
 *           vertically centred, left-aligned
 */
.contact__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
}

.contact__text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
  width: 100%;
  max-width: 486px;
  text-align: center;
}

.contact__heading {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}

.contact__heading p {
  color: var(--text-secondary-default);
}

.contact__video {
  position: relative;
  width: 100%;
  aspect-ratio: 994 / 560;
  border-radius: var(--border-radius-20);
  overflow: hidden;
  isolation: isolate;
  transition: box-shadow 0.15s ease;
}

/* Same hover/pressed ring as the project cards (Figma Card): 4px outside the frame. */
.contact__video:active {
  box-shadow: 0 0 0 4px var(--interactive-action-primary-default-effect);
}

@media (hover: hover) {
  .contact__video:hover {
    box-shadow: 0 0 0 4px var(--interactive-action-primary-default-effect);
  }
}

.contact__video video {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit; /* browsers draw video on its own layer and can ignore the parent's rounded clip */
  object-fit: cover;
}

/* Custom video controls: small secondary Icon buttons along the bottom.
   Play/pause + sound on the left, full screen on the right. While playing they
   fade out after a moment without movement; when paused they always show. */
.contact__video {
  cursor: pointer;
}

.video-controls {
  position: absolute;
  right: 16px;
  bottom: 16px;
  left: 16px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none; /* only the buttons themselves take clicks */
  transition: opacity 0.3s ease;
}

.video-controls button {
  pointer-events: auto;
}

.video-controls__group {
  display: flex;
  gap: 8px;
}

.contact__video[data-state="playing"][data-idle] .video-controls {
  opacity: 0;
}

/* Big centred Play (medium primary Icon button) before the video starts.
   The small control bar stays hidden until then. */
.video-start {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  transform: translate(-50%, -50%);
  transition: opacity 0.3s ease, filter 0.3s ease, transform 0.3s ease,
    background-color 0.15s ease, box-shadow 0.15s ease;
}

.contact__video:not([data-started]) .video-controls {
  opacity: 0;
  visibility: hidden;
}

.contact__video[data-started] .video-start {
  opacity: 0;
  filter: blur(4px);
  transform: translate(-50%, -50%) scale(0.8);
  visibility: hidden;
  transition: opacity 0.3s ease, filter 0.3s ease, transform 0.3s ease, visibility 0s 0.3s;
}

/* Reduced motion: the big Play button just fades out (no shrink or blur) */
@media (prefers-reduced-motion: reduce) {
  .contact__video[data-started] .video-start {
    filter: none;
    transform: translate(-50%, -50%);
  }
}

.contact__video[data-state="playing"][data-idle] {
  cursor: none;
}

/* Swap each button's icon to match the current state */
.contact__video[data-state="playing"] .video-controls__when-paused,
.contact__video:not([data-state="playing"]) .video-controls__when-playing,
.contact__video[data-muted] .video-controls__when-sound,
.contact__video:not([data-muted]) .video-controls__when-muted,
.contact__video[data-fullscreen] .video-controls__when-windowed,
.contact__video:not([data-fullscreen]) .video-controls__when-fullscreen {
  display: none;
}

/* Full screen: the whole box (video + custom controls) fills the screen */
.contact__video:fullscreen {
  border-radius: 0;
  background: var(--black-full);
}

.contact__video:fullscreen video {
  border-radius: 0;
  object-fit: contain;
}

.contact__video:fullscreen::after {
  display: none;
}

/* The border is drawn on top of the video so its rounded corners can never be covered.
   border/transparent/default: 8% black in light, 16% white in dark (visible in both). */
.contact__video::after {
  content: "";
  position: absolute;
  inset: 0;
  border: var(--border-width-1) solid var(--border-transparent-default);
  border-radius: inherit;
  pointer-events: none;
}

@media (min-width: 1024px) {
  .contact__container {
    flex-direction: row;
    gap: 32px;
  }

  .contact__text {
    flex: 1 1 0;
    align-items: flex-start;
    gap: 40px;
    max-width: none;
    text-align: left;
  }

  .contact__heading {
    gap: 20px;
  }

  .contact__video {
    flex: 1 1 0;
  }
}

/* ---------- Footer ---------- */
.footer {
  display: flex;
  justify-content: center;
  padding: 0 16px;
  background: var(--page-bg-default); /* its own grey, since phones use a surface-coloured page */
  color: var(--text-secondary-default);
  font-size: var(--typography-body-font-size-s);
  line-height: var(--typography-body-line-height-s);
}

.footer__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  width: 100%;
  max-width: 1200px;
  padding: 24px 0;
  container: footer / inline-size; /* lets the location line react to its own width */
}

.footer__meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}

.footer__divider {
  display: none;
  flex: none;
  width: 1px;
  height: 16px;
  background: var(--border-transparent-default);
}

.footer__location {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

/* Location | live UK time, split by a divider with 8px either side
   (Figma: 4px padding on each side on top of the 4px gaps) */
.footer__location .footer__divider {
  display: block;
  margin-left: 4px;
}

.footer__time {
  padding-left: 4px;
  font-variant-numeric: tabular-nums; /* digits keep the same width as they tick over */
}

/* Too narrow for one line (it needs 295px): the time drops to its own centred line,
   without the divider. */
@container footer (max-width: 299px) {
  .footer__location .footer__divider {
    display: none;
  }

  .footer__time {
    flex-basis: 100%;
    padding-left: 0;
    text-align: center;
  }
}

.footer__location .icon {
  color: var(--icon-secondary-default);
}

@media (min-width: 768px) {
  .footer__meta {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    column-gap: 8px;
  }

  .footer__divider {
    display: block;
  }

  .footer__divider--l {
    display: none;
  }

  .footer__illustrations {
    flex-basis: 100%;
  }
}

@media (min-width: 1024px) {
  .footer__container {
    flex-direction: row;
    gap: 8px;
  }

  .footer__meta {
    flex: 1;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 8px;
  }

  .footer__divider--l {
    display: block;
  }

  .footer__illustrations {
    flex: 1;
    flex-basis: auto;
    text-align: left;
  }
}
