/*
 * 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;
}

/* Focus Not Obscured (WCAG 2.4.11): when Tab scrolls a link or button into view, keep it
   clear of the sticky top bar (96px) and the screen edges. Only affects focus scrolling,
   not section links like "View my work". */
a,
button,
summary,
video,
[tabindex]:not([tabindex="-1"]) {
  scroll-margin-top: 112px;
  scroll-margin-bottom: 16px;
}

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 to content and Pause animations: Primary buttons (Figma Button) that stay out of
   sight until reached with Tab, then drop in at the top left */
.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 100;
  transform: translateY(-200%);
}

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

/* ---------- Paused animations (footer button, main.js) ----------
   WCAG 2.2.2: anything that moves for over 5 seconds can be paused. The logo row stops
   cycling and the quote animation freezes (main.js); the hero people hold their pose
   (a hover hop still plays) and the green status pulses stop. */
:root[data-motion="paused"] .person:not(:hover, .is-jumping) {
  animation-play-state: paused;
}

:root[data-motion="paused"] .status__badge::after {
  animation: none !important;
}

.quote__gif img.is-frozen {
  opacity: 0;
}

/* ---------- 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);
}

/* Press (mouse, trackpad or touch): the button gives a little and springs back on
   release. The shrink is sized so every button moves a similar amount: pill buttons 95%,
   the big mobile menu buttons 97%, round icon buttons 92%. Uses `scale`, so it never
   fights a button's own `transform`. Nothing moves on hover; the colour change does that
   job. Off with Reduce motion. main.js holds .is-pressed briefly so quick mouse clicks
   still show it. */
@media (prefers-reduced-motion: no-preference) {
  .btn,
  .icon-btn {
    transition: background-color 0.15s ease, box-shadow 0.15s ease,
      scale 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .btn:is(:active, .is-pressed),
  .icon-btn:is(:active, .is-pressed) {
    scale: 0.95;
    transition-duration: 0.15s, 0.15s, 0.08s;
  }

  .mobile-menu .btn:is(:active, .is-pressed) {
    scale: 0.97;
  }

  .icon-btn:is(:active, .is-pressed) {
    scale: 0.92;
  }
}

@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;
  }
}

/* The browser loses :hover during the light/dark fade, so main.js holds the mode
   button's hover colour and tooltip with .is-hover-held until the mouse next moves */
[data-tooltip].is-hover-held::after {
  opacity: 1;
  transform: translate(calc(-50% + var(--tooltip-shift, 0px)), 0);
}

.icon-btn--secondary.is-hover-held {
  background: var(--interactive-action-secondary-default-hover);
  box-shadow: 0 0 0 4px var(--btn-effect);
}

/* Light/dark swap (main.js): colours change in one step, without transitions. Buttons
   keep only their press spring (background, ring, then scale). */
.is-theme-swapping *:not(.btn, .icon-btn),
.is-theme-swapping *::before,
.is-theme-swapping *::after {
  transition: none !important;
}

.is-theme-swapping :is(.btn, .icon-btn) {
  transition-duration: 0s, 0s, 0.35s !important;
}

/* Escape hides any tooltip and the hero speech bubble without moving the pointer
   (WCAG 1.4.13, main.js). They can show again once the pointer or focus moves on. */
.is-popups-dismissed [data-tooltip]::after,
.is-popups-dismissed .speech-bubble {
  opacity: 0 !important;
}

/* ---------- 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);
  }
}

/* Role and accessibility lines take turns in the same spot (main.js); the hidden one is
   still read by screen readers, and tabbing to its link brings it forward */
.status-cycle {
  display: grid;
  justify-items: center;
}

.status__slide {
  grid-area: 1 / 1;
}

.status__slide:not(.is-active) {
  opacity: 0;
  pointer-events: none;
}

/* Accessibility line: 4px between the icon and the text, and no circle behind the icon
   (Figma); the role line keeps 8px and its circle */
.status--a11y {
  gap: 4px;
}

.status--a11y .status__logo {
  background: none;
}

.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: flex;
  justify-content: center;
  padding: 14px 16px;
  background: var(--surface-secondary-default); /* Figma: banner fill */
}

.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 top and bottom edges only (under the top banner and above the footer),
   drawn as an overlay so, like Figma's inside stroke, it takes no space. It sits above
   the top bar's frosted backdrop, which would otherwise hide the top edge. From M it's a
   real border all round. */
.page-content::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 11; /* above the nav's frosted backdrop (10), below the mobile menu (20) */
  border-top: var(--border-width-1) solid var(--border-transparent-default);
  border-bottom: var(--border-width-1) solid var(--border-transparent-default);
  border-radius: inherit;
  pointer-events: none;
}

@media (min-width: 768px) {
  .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;
  }
}

/* Closing plays the opening in reverse (main.js adds .is-closing, then hides the menu):
   the buttons lift back up and blur out, bottom one first. */
@keyframes menu-item-out {
  to {
    opacity: 0;
    filter: blur(4px);
    transform: translateY(-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__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. The blur itself builds up
   (each layer from no blur to its own strength) rather than the layer fading in:
   browsers can't draw a backdrop blur inside anything partly transparent, so a
   fading layer only showed its blur at the very end, as a pop. The tint fades
   alongside. Opening matches the buttons dropping in (0.5s); closing unwinds with
   them, finishing as the last button leaves (0.1s + 0.22s = the buttons' 0.32s). */
@keyframes menu-blur-in {
  from {
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
  }
}

@keyframes menu-blur-out {
  to {
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
  }
}

@keyframes menu-bg-in {
  from {
    opacity: 0;
  }
}

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

.mobile-menu > .frosted__blur span {
  animation: menu-blur-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

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

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

.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 > .frosted__blur span,
  .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;
}

/* The fade doesn't block the page: clicks and hover reach it as normal, so the button
   keeps its hover colour and tooltip, and every click presses and switches */
::view-transition {
  pointer-events: none;
}

/* The mode button's own layer (main.js) fades in step with the screen; its new layer
   is live, so its press springs back during the fade */
::view-transition-old(theme-toggle) {
  animation: none;
}

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

::view-transition-group(theme-toggle) {
  animation-duration: 0s;
}

@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 top banner and the hero together fill the screen height (main.js measures
   the banner into --status-bar-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: calc(100vh - var(--status-bar-height, 48px)); /* fallback for older browsers */
    min-height: calc(100svh - var(--status-bar-height, 48px));
  }

  .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);
}

/* ---------- 404 page (404.html, Figma "404 / XL->XS") ----------
   Same heading, text and button as the hero, with "404" in the display font above the
   title and the golf illustration underneath. */
.not-found__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 56px;
  padding: 144px 0 96px;
}

.not-found__code {
  display: block;
  margin-bottom: 8px;
  font-size: var(--typography-display-font-size-xxl);
  line-height: var(--typography-display-line-height-xxl);
}

.not-found__art {
  width: 100%;
  max-width: 444px; /* XS: 320px, S + M: 444px, L + XL: 592px */
}

.not-found__art img {
  display: block;
  width: 100%;
  height: auto;
}

@media (min-width: 1024px) {
  .not-found__container {
    gap: 72px;
    padding-bottom: 128px;
  }

  .not-found__art {
    max-width: 592px;
  }
}

@media (max-width: 567px) {
  .not-found__art {
    max-width: 320px;
  }
}

/* XS + S: like the homepage hero, the top banner and this section fill the screen
   (main.js measures the banner into --status-bar-height); any spare height goes above
   the illustration, so it sits at the bottom */
@media (max-width: 767px) {
  .not-found__container {
    min-height: calc(100vh - var(--status-bar-height, 48px)); /* fallback for older browsers */
    min-height: calc(100svh - var(--status-bar-height, 48px));
  }

  .not-found__art {
    margin-top: auto;
  }
}

.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);
}

/* XS (Figma Section / Skills, Breakpoint=XS): icon and heading side by side, 12px apart,
   with the description underneath. From S the icon sits above the heading. */
@media (max-width: 567px) {
  .skill-card {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 12px;
  }

  .skill-card .icon {
    margin-bottom: 0;
  }

  .skill-card__text {
    grid-column: 1 / -1;
  }
}

@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;
}

/* 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);
  }
}

/* Hover (mouse/trackpad, motion allowed): the card lifts 6px, settling with a slight
   overshoot. No tilt, shadow or glow; the Figma hover ring stays, and the arrow button
   takes its hover colour (it doesn't move). Touch and reduced motion keep just the Figma hover ring. */
.work-card {
  position: relative;
  transition: box-shadow 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1), scale 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* The About me video frame lifts the same way, only while it isn't playing
   (its transition sits with its other styles under "Contact") */

.work-card__button {
  transition: background-color 0.15s ease, scale 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .work-card:hover {
    z-index: 1;
    transform: translateY(-6px);
  }

  .contact__video:not([data-state="playing"], :fullscreen):hover {
    transform: translateY(-6px);
  }

  .work-card:not(.work-card--image):hover .work-card__button {
    background: var(--interactive-action-primary-inverse-hover);
  }

  .work-card--image:hover .work-card__button {
    background: var(--interactive-action-primary-fixed-dark-hover);
  }
}

/* Press (mouse, trackpad or touch): like the buttons, the tile or video gives a little
   (98%, a touch less than the buttons as they're much bigger) and springs back on release. */
@media (prefers-reduced-motion: no-preference) {
  .work-card:is(:active, .is-pressed),
  .contact__video:not(:fullscreen):is(:active, .is-pressed) {
    scale: 0.98;
    transition-duration: 0.3s, 0.45s, 0.08s;
  }
}

/* Lifting elements scroll in with `translate` (not `transform`), leaving `transform` free for the hover */
@keyframes rise-in-move {
  from {
    opacity: 0;
    translate: 0 16px;
  }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .work-card,
    .contact__video {
      animation-name: rise-in-move;
    }
  }
}

.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;
}

/* Work card with artwork (Figma Card with an image fill, e.g. Plusnet): the artwork
   covers the card, and the text, tag and (from M) arrow button use the fixed colour
   tokens, so they stay the same over the artwork in light and dark mode. */
.work-card--image {
  background: var(--card-image) center / cover no-repeat, var(--surface-secondary-default);
}

.work-card--image .work-card__title,
.work-card--image .work-card__text {
  color: var(--text-fixed-light);
}

.work-card--image .tag--fixed-light {
  background: var(--surface-transparent-fixed-light);
  color: var(--text-fixed-light);
}

/* Fixed dark Icon button (Figma: Style=Fixed dark), at every size */
.work-card--image .work-card__button {
  --btn-effect: var(--interactive-action-primary-fixed-dark-effect);
  background: var(--interactive-action-primary-fixed-dark-enabled);
  border-color: var(--border-transparent-fixed-light);
  color: var(--icon-fixed-light);
}

@media (hover: hover) {
  .work-card--image .work-card__button:hover {
    background: var(--interactive-action-primary-fixed-dark-hover);
  }
}

.work-card--image .work-card__button:active {
  background: var(--interactive-action-primary-fixed-dark-pressed);
}

@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,
.quote__gif canvas {
  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.3s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
    scale 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* The video and its written description, stacked */
.contact__media {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  width: 100%;
}

/* Video description (WCAG 1.2.1/1.2.3): a small link-style toggle under the video that
   opens the words of the animation as text */
.video-description {
  width: 100%;
  color: var(--text-secondary-default);
  font-size: var(--typography-body-font-size-s);
  line-height: var(--typography-body-line-height-s);
}

.video-description__toggle,
.a11y-statement__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0; /* the chevron sits right against the text (Figma) */
  width: fit-content;
  margin: 0 auto;
  border-radius: 2px;
  cursor: pointer;
  list-style: none;
}

.video-description__toggle::-webkit-details-marker {
  display: none;
}

.video-description__chevron,
.a11y-statement__chevron {
  transition: rotate 0.2s ease;
}

.video-description[open] .video-description__chevron,
.a11y-statement__toggle[aria-expanded="true"] .a11y-statement__chevron {
  rotate: 90deg;
}

.video-description__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 32px;
}

.video-description__title {
  display: block;
  color: var(--text-primary-default);
}

/* An acrostic, like the video: the bold letters down the left spell STEFAN. It stays a
   list underneath (without bullets), so screen readers say "list, 6 items". */
.video-description__letters {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.video-description__letters strong {
  color: var(--text-primary-default);
}

/* 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, scale 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.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__media {
    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: 8px;
  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;
  padding-top: 12px; /* XS–M: a little apart from the © lines (Figma); none on L+XL */
  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);
}

/* Figma: the Accessibility block (Pause animations, accessibility statement) on top, a
   full-width divider, then the © lines and location */
.footer__main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
}

.footer__rule {
  width: 100%;
  height: 1px;
  background: var(--border-transparent-default);
}

.footer__a11y {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 24px 8px;
  width: 100%;
}

.footer__a11y .footer__divider--a11y {
  display: block;
}

/* Pause animations (WCAG 2.2.2): a real button, styled like the small body links, with a
   pause icon (play once paused) */
.footer__motion {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: none;
  font: inherit;
  cursor: pointer;
}

:root[data-motion="paused"] .footer__motion-icon--pause,
:root:not([data-motion="paused"]) .footer__motion-icon--play {
  display: none;
}

/* Accessibility statement: a button beside Pause animations that shows the statement on
   its own line below (main.js). It shares the video description's look (link text and a
   turning chevron), led by the a11y icon. */
.a11y-statement__toggle {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
}

/* Open (Figma): surface/secondary, the same grey as the footer, so it reads as text on
   the footer rather than a card; 16px at the sides */
.a11y-statement {
  flex-basis: 100%;
  width: 100%;
  padding: 0 16px;
  background: var(--surface-secondary-default);
}

.a11y-statement__icon {
  margin-right: 4px; /* 4px from the text; the chevron has none */
  color: var(--icon-secondary-default);
  transition: color 0.15s ease;
}

/* Hover and pressed: the icon takes the link's colour, like the text and chevron */
@media (hover: hover) {
  .a11y-statement__toggle:hover .a11y-statement__icon {
    color: inherit;
  }
}

.a11y-statement__toggle:active .a11y-statement__icon {
  color: inherit;
}

.a11y-statement__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 640px;
  margin: 0 auto;
  padding-top: 8px; /* + the line's 24px gap = 32px below the toggle (Figma) */
  text-align: left;
}

.a11y-statement__body section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.a11y-statement__body h2 {
  margin: 0;
  color: var(--text-primary-default);
  font-size: inherit;
  line-height: inherit;
  font-weight: var(--typography-body-font-weight-bold);
}

.a11y-statement__body ul {
  margin: 0;
  padding-left: 18px; /* bullets indented, wrapped lines line up with the text (Figma) */
  list-style: disc;
}

/* One line at every size (Figma): "Pause animations | Built to WCAG 2.2 AA", in the same
   order as the keyboard; the opened statement sits on its own line below */

/* Phones narrower than 390px (e.g. iPhone SE, 375px): the line no longer fits, so the two
   stack, Pause animations above the statement toggle, without the divider */
@media (max-width: 389px) {
  .footer__a11y {
    flex-direction: column;
    gap: 16px;
  }

  .footer__a11y .footer__divider--a11y {
    display: none;
  }

  .a11y-statement {
    flex-basis: auto; /* in a column, 100% would mean its height */
    align-self: stretch;
    width: auto;
  }
}

/* L + XL: the Accessibility line sits on the left, and so does the opened statement */
@media (min-width: 1024px) {
  .footer__a11y {
    justify-content: flex-start;
  }

  .a11y-statement__body {
    margin: 0;
  }

  .a11y-statement {
    padding: 0;
  }
}

@media (min-width: 768px) {
  .footer__container {
    gap: 24px; /* M and up: 24px around the divider (XS + S: 32px) */
  }

  .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%;
  }
}

/* L + XL: © lines on the left and location on the right, on one line */
@media (min-width: 1024px) {
  .footer__main {
    flex-direction: row;
    gap: 8px;
  }

  .footer__location {
    padding-top: 0;
  }

  .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;
  }
}
