/* Home page. All colours, fonts and sizes come from tokens.css. */

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

/* Paper colour sits on html: body's container-type stops it moving there by itself,
   and a body background would cover the fixed texture below. */
html {
  background: var(--color-paper);
  overflow-x: clip;
}

body {
  margin: 0;
  color: var(--color-ink);
  container-type: inline-size; /* lets --u measure the page width without the scrollbar */
  overflow-x: clip; /* at 1920px the scrollbar covers only empty paper on the right */
}

h1,
h2,
h3,
p,
ul,
ol {
  margin: 0;
  padding: 0;
  font-weight: inherit;
  font-size: inherit;
}

ol,
.projects ul {
  list-style: none;
}

/* Page: the Figma collage, 1920 wide, scaled with --u, in three stacked sections.
   overflow: clip (not hidden) so the pinned timeline can still stick. */
.page {
  width: calc(var(--page-width) * var(--u));
  margin: 0 auto;
  overflow: clip;
}

.section {
  position: relative;
}

.timeline__canvas {
  height: calc(var(--timeline-height) * var(--u));
}

/* Desktop: every section is one screen tall, so each snap stop shows one whole section.
   Content keeps its Figma layout and is centred vertically in the screen:
   --section-offset shifts the Figma y positions down (or up) to the centre. */
@media (min-width: 1024px) {
  /* Hero: a column of text, so it grows with its content and never overlaps */
  .hero {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    min-height: 100vh;
    padding: var(--slide-pad) calc(var(--page-margin) * var(--u));
  }

  .projects {
    --content-top: var(--projects-content-top);
    --content-h: var(--projects-content-height);
    --slide-h: max(100vh, calc(var(--content-h) * var(--u) + var(--slide-pad) * 2));
    --section-offset: calc((var(--slide-h) - var(--content-h) * var(--u)) / 2 - var(--content-top) * var(--u));
    height: var(--slide-h);
  }

  .projects > .at,
  .projects > ul > .at,
  .projects > [aria-hidden] > .at {
    top: calc(var(--y) * var(--u) + var(--section-offset));
  }
}

/* Pinned timeline (js/timeline-steps.js adds .is-pinned).
   The section becomes a tall scroll track. The stage sticks to the screen,
   and the canvas glides so the newest entry sits in the middle. */
.timeline.is-pinned {
  position: relative;
  height: calc(100vh + var(--timeline-steps) * var(--timeline-step-scroll));
}

/* Page snap stops for js/page-snap.js: the hero, Past project ([data-snap-stop])
   and one invisible stop per timeline entry, placed inside that entry's scroll band */
.timeline__snap {
  position: absolute;
  left: 0;
  top: calc(var(--snap-at) * var(--timeline-step-scroll));
  width: 1px;
  height: 1px;
}

.is-pinned .timeline__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: clip;
}

.is-pinned .timeline__canvas {
  transform: translateY(var(--timeline-shift, 0px));
  transition: transform var(--duration-timeline-step) var(--ease-in-out);
}

.is-pinned [data-step] {
  opacity: 0;
  translate: 0 calc(var(--step-rise) * var(--u));
  transition:
    opacity var(--duration-timeline-step) var(--ease-out),
    translate var(--duration-timeline-step) var(--ease-out);
}

.is-pinned [data-step].is-shown {
  opacity: 1;
  translate: 0 0;
}

/* The hand-drawn line draws down to the newest entry */
.is-pinned .timeline__line {
  clip-path: inset(0 0 calc(100% - var(--line-reveal, 0) * var(--u)) 0);
  transition: clip-path var(--duration-timeline-step) var(--ease-in-out);
}

/* Paper texture: stays still while the content scrolls over it.
   On html, not body, because body's container-type would make "fixed" scroll with the page. */
html::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: url("../assets/paper.png") top center / 100% auto repeat-y;
  opacity: var(--paper-texture-opacity);
  pointer-events: none;
}

/* Placement: each item sits at its Figma x / y */
.at {
  position: absolute;
  left: calc(var(--x) * var(--u));
  top: calc(var(--y) * var(--u));
}

/* Hand-drawn lines and arrows */
.drawing {
  display: block;
  width: calc(var(--w) * var(--u));
  height: calc(var(--h) * var(--u));
  pointer-events: none;
}

.drawing--flip-x {
  transform: scaleX(-1);
}

.drawing--turn-90 {
  transform: rotate(90deg);
}

.drawing--turn-180 {
  transform: rotate(180deg);
}

/* Card outlines (inlined by js/card-outline.js): one line weight on every card,
   however far the outline is stretched */
.card-outline-line {
  vector-effect: non-scaling-stroke;
  stroke-width: max(var(--card-stroke-min), calc(var(--card-stroke) * var(--u)));
}

/* Text styles */
.text-hand {
  font-family: var(--font-hand);
  font-size: calc(var(--text-body) * var(--u));
  line-height: normal;
}

.text-intro {
  font-family: var(--font-hand);
  font-size: calc(var(--text-intro) * var(--u));
  line-height: normal;
}

.text-heading {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: calc(var(--text-heading) * var(--u));
  line-height: normal;
}

/* Label: the exact Figma shape, with the name cut out so the paper shows through */
.label {
  display: block;
  flex: none;
  width: calc(var(--w) * var(--u));
  height: calc(var(--h) * var(--u));
}

/* Labels on project cards never run wider than the card's text column (the long
   "Standard Chartered" label on the narrowest phones); they shrink and keep their shape */
.project .label {
  max-width: 100%;
  height: auto;
  aspect-ratio: var(--w) / var(--h);
}

/* Yellow labels: dark showing through the cut-out letters, because light text
   on yellow is too faint (1.6:1). Black on yellow is 11.6:1. */
.label[data-dark-text] {
  background-color: var(--color-ink);
}

/* Hero */
.hero__title {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: calc(var(--text-hero) * var(--u));
  line-height: calc(var(--leading-display) * var(--u));
  white-space: nowrap;
}

.hero__title-line {
  display: block;
}

/* Role label: real text on a coloured block. js/role-switcher.js changes the title,
   the colour (--role-bg) and the width every second. */
.role {
  position: relative;
  display: inline-block;
  vertical-align: middle;
  height: calc(var(--role-height) * var(--u));
  padding: calc(var(--role-pad-top) * var(--u)) calc(var(--role-pad-x) * var(--u)) 0;
  isolation: isolate;
  color: var(--role-ink, var(--color-paper));
  line-height: calc(var(--role-leading) * var(--u));
  white-space: nowrap;
}

/* Hero label: its own line under "Hello, I’m", close to it so the two read as one headline */
.hero .role {
  display: block;
  margin-top: calc(var(--space-3) * var(--u));
}

/* Coloured block behind the title: it scales (never animates width) to the new title */
.role__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--role-bg, var(--color-label-red));
  transform-origin: left center;
  transition: background-color var(--duration-role-change) var(--ease-in-out);
}

/* Clips the title as it slides up and out, and in from below.
   The clip box reaches a little above and below the line (without moving anything),
   so letter tails like g and y are never cut off. */
.role__window {
  display: block;
  overflow: hidden;
  padding-block: var(--role-clip-room);
  margin-block: calc(var(--role-clip-room) * -1);
}

.role__text {
  display: block;
}

.role__measure {
  position: absolute;
  visibility: hidden;
}

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

/* "paused" note beside a label the visitor stopped (tap or keyboard button) */
.role.is-paused::after {
  content: "paused";
  position: absolute;
  left: calc(100% + var(--space-3) * var(--u));
  top: 50%;
  translate: 0 -50%;
  font-family: var(--font-hand);
  font-weight: normal;
  font-size: max(var(--text-small-min), calc(var(--text-body) * var(--u)));
  line-height: normal;
  color: var(--color-ink);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Hero rhythm: name, then the headline, then the supporting text as a second group */
.hero__title,
.hero__tagline {
  margin-top: calc(var(--space-6) * var(--u));
}

.hero__tagline {
  width: calc(1308 * var(--u));
}

/* Proof points (not in the mockup): three side by side, number above its meaning */
.hero__proof {
  display: flex;
  gap: calc(var(--space-7) * var(--u));
  margin-top: calc(var(--space-5) * var(--u));
  list-style: none;
}

.hero__proof li {
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-1) * var(--u));
  max-width: calc(560 * var(--u));
  text-wrap: pretty; /* no single word left alone on the last line */
}

.hero__proof-num {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: calc(var(--text-heading) * var(--u));
  line-height: 1.2;
}

.hero__proof-label {
  font-family: var(--font-hand);
  font-size: calc(var(--text-body) * var(--u));
  line-height: normal;
}

/* Timeline */
.entry {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: calc(var(--gap-s) * var(--u));
  width: calc(var(--column-text) * var(--u));
}

.entry__head {
  display: flex;
  align-items: center;
  gap: calc(var(--gap-xs) * var(--u));
  white-space: nowrap;
}

.entry__desc {
  width: calc(var(--column-text) * var(--u));
  text-align: right;
}

.entry__list {
  width: calc(var(--column-text) * var(--u));
  list-style: disc;
}

.entry__list li {
  margin-inline-start: calc(var(--list-indent) * var(--u));
}

.timeline__end {
  transform: translateX(-100%);
  white-space: nowrap;
}

/* "hire me to be featured here": contact link (underline and writing effect: .link-draw below).
   At least 24px tall so it is easy to click at every width. */
.contact-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
  color: inherit;
  outline: var(--focus-ring-width) solid transparent;
  outline-offset: var(--focus-ring-offset);
}

.contact-link:focus-visible {
  outline-color: var(--color-focus);
}

.contact-link:active {
  transform: translateY(1px);
}

/* Text links: the underline draws in from the left on hover and keyboard focus.
   On hover it waits until the letters have finished writing (--write-total, js/link-write.js).
   Touch screens cannot hover, so there the underline is always shown. */
.link-draw {
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) left bottom / 0% var(--link-underline-width) no-repeat;
  transition: background-size var(--duration-underline) var(--ease-out);
}

.link-draw:focus-visible {
  background-size: 100% var(--link-underline-width);
  transition-delay: 0ms;
}

@media (hover: hover) {
  .link-draw:hover {
    background-size: 100% var(--link-underline-width);
    transition-delay: var(--write-total, 0ms);
  }
}

@media (hover: none) {
  .link-draw {
    background-size: 100% var(--link-underline-width);
  }
}

/* Writing effect: on mouse hover each letter is revealed left to right, one after another,
   like a pen writing it (js/link-write.js adds .is-writing). */
.write__word {
  display: inline-block;
  white-space: nowrap;
}

.write__char {
  display: inline-block;
}

.is-writing .write__char {
  animation: write-in var(--write-char) var(--ease-out) backwards;
  animation-delay: calc(var(--i) * var(--write-stagger));
}

@keyframes write-in {
  from { clip-path: inset(-25% 100% -25% -25%); }
  to { clip-path: inset(-25% -25% -25% -25%); }
}

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

  .link-draw:hover {
    transition-delay: 0ms;
  }

  .is-writing .write__char {
    animation: none;
  }
}

/* Past projects */
.projects__title {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: calc(var(--text-section) * var(--u));
  line-height: calc(var(--leading-display) * var(--u));
  white-space: nowrap;
}

/* Project card link. States are not in the mockup:
   hover and focus = card raises and fills white with a soft shadow,
   focus also shows an ink ring, pressed = card drops part way back. */
.project-card {
  width: calc(var(--card-width) * var(--u));
  height: calc(var(--card-min-height) * var(--u));
}

.project-card__link {
  position: absolute;
  inset: 0;
  color: inherit;
  text-decoration: none;
  outline: var(--focus-ring-width) solid transparent;
  outline-offset: var(--focus-ring-offset);
  transition:
    transform var(--duration-hover) var(--ease-out),
    opacity var(--duration-dim) var(--ease-out);
}

/* White fill in the exact shape of the hand-drawn outline, with a soft shadow */
.project-card__highlight {
  width: calc(100% + 2 * var(--u));
  height: calc(100% + 2 * var(--u));
  opacity: 0;
  filter: drop-shadow(0 calc(var(--card-shadow-y) * var(--u)) calc(var(--card-shadow-blur) * var(--u)) var(--color-card-shadow));
  transition: opacity var(--duration-hover) var(--ease-out);
}

.project-card__fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--color-card-highlight);
  mask: url("../assets/card-fill.svg?v=2") center / 100% 100% no-repeat; /* ?v=2: stretching shape, skips old saved copies */
}

.project-card__link:focus-visible {
  outline-color: var(--color-focus);
  transform: translateY(calc(var(--card-raise) * -1 * var(--u)));
}

.project-card__link:focus-visible .project-card__highlight {
  opacity: 1;
}

@media (hover: hover) {
  .project-card__link:hover {
    transform: translateY(calc(var(--card-raise) * -1 * var(--u)));
  }

  .project-card__link:hover .project-card__highlight {
    opacity: 1;
  }
}

.project-card__link:active {
  transform: translateY(calc(var(--card-raise-pressed) * -1 * var(--u)));
  transition-duration: var(--duration-press);
}

@media (prefers-reduced-motion: reduce) {
  .project-card__link,
  .project-card__link:hover,
  .project-card__link:focus-visible,
  .project-card__link:active {
    transform: none;
  }

  .project-card__highlight {
    transition-duration: var(--duration-press);
  }
}

.project {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--gap-s) * var(--u));
  width: calc(var(--column-card) * var(--u));
}

.project__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--gap-xs) * var(--u));
  white-space: nowrap;
}

/* Left-aligned under the title (changed from the mockup, which aligns it right) */
.project__desc {
  align-self: stretch;
  text-align: left;
}

/* Type · role · where (not in the mockup): small Quicksand, so it differs from the
   handwritten description by typeface rather than by fading */
.project__tag {
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  font-size: max(var(--text-small-min), calc(var(--text-small) * var(--u)));
  line-height: 1.3;
  text-wrap: balance; /* when it wraps, two even lines rather than one word left over */
}

/* Title and tag read as one group; the description starts a new one */
.project__tag + .project__desc {
  margin-top: calc(var(--space-2) * var(--u));
}

/* "read case study" note under the card, starting near the outline's bottom-left corner.
   Shows on hover and focus, always shown on touch screens (they cannot hover) */
.project-card__cta {
  opacity: 0;
  white-space: nowrap;
  transition: opacity var(--duration-hover) var(--ease-out);
}

.project-card__link:focus-visible .project-card__cta {
  opacity: 1;
}

/* On hover the note shows at once and writes itself in (js/link-write.js, no underline);
   it fades out when the mouse leaves */
@media (hover: hover) {
  .project-card__link:hover .project-card__cta {
    opacity: 1;
    transition-duration: 0ms;
  }
}

@media (hover: none) {
  .project-card__cta {
    opacity: 1;
  }
}

/* Dim the other cards while one is hovered or focused */
.projects ul:has(.project-card__link:focus-visible) .project-card__link:not(:focus-visible) {
  opacity: var(--dim-opacity);
}

@media (hover: hover) {
  .projects ul:has(.project-card__link:hover) .project-card__link:not(:hover) {
    opacity: var(--dim-opacity);
  }
}

/* Desktop: cards grow with their text (never clip it). The text sits in the flow with the
   same inset on every card, and the hand-drawn outline and white fill stretch to the card. */
@media (min-width: 1024px) {
  .project-card {
    height: auto;
  }

  /* flow-root: the text's top margin stays inside the card instead of pushing the card down */
  .project-card__link {
    position: relative;
    inset: auto;
    display: flow-root;
    min-height: calc(var(--card-min-height) * var(--u));
  }

  /* The outline's sides lean in by up to ~10% of its width and its top edge dips ~12%
     of its height, so each inset is a fixed minimum or a share of the card's width,
     whichever is larger. (At smaller sizes the minimum text sizes make cards taller
     for their width, so fixed insets alone run into the outline.) */
  .project-card .project {
    position: relative;
    left: auto;
    top: auto;
    width: auto;
    margin:
      max(calc(var(--card-pad-y) * var(--u)), var(--card-inset-top))
      max(calc(var(--card-pad-x) * var(--u) * 0.9), var(--card-inset-x))
      0
      max(calc(var(--card-pad-x) * var(--u)), var(--card-inset-x));
    padding-bottom: calc(var(--card-pad-y) * var(--u));
  }

  /* Outline A (not mirrored) dips lowest at its top-left corner, where the label sits */
  .project-card:has(.project-card__fill:not(.drawing--flip-x)) .project {
    margin-top: max(calc(var(--card-pad-y-a) * var(--u)), var(--card-inset-top-a));
  }

  .project-card__link > .drawing {
    width: calc(100% + 2 * var(--u));
    height: calc(100% + 2 * var(--u));
  }

  /* "read case study" sits just under the card, whatever its height */
  .project-card .project-card__cta {
    top: calc(100% + var(--space-2) * var(--u));
  }

  /* Right-aligned note, for a card whose left corner is crowded by its arrow */
  .project-card .project-card__cta--end {
    left: auto;
    right: calc(var(--card-pad-x) * var(--u));
  }
}

/* Small desktops (1024 to 1439px): the 16px minimum text size makes cards taller for
   their width, so the last line needs a bottom inset that grows with the card */
@media (min-width: 1024px) and (max-width: 1439.98px) {
  .project-card .project {
    padding-bottom: max(calc(var(--card-pad-y) * var(--u)), var(--card-inset-bottom));
  }
}

/* Mind-map lines (inline SVG from js/project-map.js) */
.is-inline .map-line {
  stroke-dasharray: 1;
}

/* Arrow redraws, a little bolder, when its card is hovered or focused */
@keyframes map-redraw {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

.is-active .map-line {
  stroke-width: var(--map-active-stroke);
  animation: map-redraw var(--duration-redraw) var(--ease-out);
}

/* Draw-in, every time the section comes into view (resets once it is off screen):
   oval and title, then each arrow in turn, each card fading in as its arrow lands */
.will-draw .map-line {
  stroke-dashoffset: 1;
}

.will-draw .map-head,
.will-draw .projects__title,
.will-draw .project-card {
  opacity: 0;
}

.will-draw .project-card {
  translate: 0 calc(var(--step-rise) * var(--u));
}

.will-draw [data-arrow-for] {
  --arrow-delay: calc(var(--map-arrow-start) + var(--i) * var(--map-stagger));
}

.will-draw .project-card {
  --card-delay: calc(var(--map-arrow-start) + var(--i) * var(--map-stagger) + var(--map-card-after-arrow));
}

.is-drawn .map-line {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset var(--duration-draw-arrow) var(--ease-in-out) var(--arrow-delay, 0ms);
}

.is-drawn .map-oval .map-line {
  transition-duration: var(--duration-draw-oval);
}

.is-drawn .map-head {
  opacity: 1;
  transition: opacity var(--duration-press) var(--ease-out)
    calc(var(--arrow-delay) + var(--duration-draw-arrow) - var(--duration-press));
}

.is-drawn .projects__title {
  opacity: 1;
  transition: opacity var(--duration-draw-oval) var(--ease-out);
}

.is-drawn .project-card {
  opacity: 1;
  translate: 0 0;
  transition:
    opacity var(--duration-card-in) var(--ease-out) var(--card-delay),
    translate var(--duration-card-in) var(--ease-out) var(--card-delay);
}

@media (prefers-reduced-motion: reduce) {
  .is-active .map-line {
    animation: none;
  }
}

/* Keyboard pause button for the role label: hidden until it gets keyboard focus */
.role-toggle {
  position: absolute;
  left: calc(var(--page-margin) * var(--u));
  bottom: calc(var(--space-6) * var(--u));
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-ink);
  cursor: pointer;
  outline: var(--focus-ring-width) solid transparent;
  outline-offset: var(--focus-ring-offset);
}

.role-toggle:not(:focus-visible) {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.role-toggle:focus-visible {
  outline-color: var(--color-focus);
}

.role-toggle:active {
  transform: translateY(1px);
}

.role-toggle[hidden] {
  display: none;
}

/* Card grows into its case study page (cross-page view transition) */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

::view-transition-group(*) {
  animation-duration: var(--duration-page-transition);
  animation-timing-function: var(--ease-in-out);
}

/* "Let's Talk" footer, left aligned on the page margin like the rest of the page:
   handwritten note, giant heading with the switching label, then pills and email */
.footer {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  /* One screen tall (at least its Figma-px height) */
  --slide-h: max(100vh, calc(var(--footer-height) * var(--u)));
  min-height: var(--slide-h);
  /* Same frame as .page: 1920 wide at most and centred */
  width: calc(var(--page-width) * var(--u));
  margin-inline: auto;
  padding: 0 calc(var(--page-margin) * var(--u));
  font-family: var(--font-hand);
  color: var(--color-ink);
}

/* Giant heading: "Let's" and the label fill the width between the page margins */
.footer__title {
  /* Plus the label's lift, so the lifted label keeps its full gap below the note */
  margin-top: calc(var(--footer-note-gap) * var(--u) - var(--footer-tag-lift));
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: calc(var(--text-footer-giant) * var(--u));
  line-height: calc(var(--leading-display) / var(--text-hero) * 1em);
  white-space: nowrap; /* the switching word never drops to a new line */
}

/* "Talk" switches like the hero label (js/role-switcher.js, words: Talk, Meet, Chat, Discuss).
   Same proportions as the hero label, in em so they grow with the giant heading. */
.footer__tag {
  height: calc(var(--role-height) / var(--text-hero) * 1em);
  padding: calc(var(--role-pad-top) / var(--text-hero) * 1em)
    calc(var(--role-pad-x) / var(--text-hero) * 1em) 0;
  line-height: calc(var(--role-leading) / var(--text-hero) * 1em);
  /* Centring the block on the line leaves its word lower than "Let’s";
     this lifts it so both words share one baseline */
  top: var(--footer-tag-lift);
}

/* Email and LinkedIn pills, in a row under the heading */
.footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--footer-pill-gap) * var(--u));
  margin: calc(var(--footer-bottom-gap) * var(--u)) 0 0;
  padding: 0;
  list-style: none;
}

/* Pill: a link inside a hand-drawn outline (the "Past projects" oval, stretched to fit) */
.footer__pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
  padding: calc(var(--footer-pill-pad-y) * var(--u)) calc(var(--footer-pill-pad-x) * var(--u));
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: calc(var(--text-heading) * var(--u));
  color: inherit;
  text-decoration: none;
  outline: var(--focus-ring-width) solid transparent;
  outline-offset: var(--focus-ring-offset);
  /* The word writes itself more slowly than on text links (js/link-write.js) */
  --write-char: var(--write-char-pill);
  --write-stagger-max: var(--write-stagger-pill);
  --write-total-max: var(--write-total-pill);
  transition:
    transform var(--duration-hover) var(--ease-out),
    opacity var(--duration-dim) var(--ease-out);
}

/* The word sits above the white fill */
.footer__pill > [data-write] {
  position: relative;
}

/* White fill in the shape of the hand-drawn oval, with a soft shadow, like the project cards.
   The shadow is on the outer span because a mask would cut it off. */
.footer__pill-highlight {
  position: absolute;
  left: calc(var(--footer-pill-outline-out) * -1 * var(--u));
  top: calc(var(--footer-pill-outline-out) * -1 * var(--u));
  width: calc(100% + var(--footer-pill-outline-out) * 2 * var(--u));
  height: calc(100% + var(--footer-pill-outline-out) * 2 * var(--u));
  opacity: 0;
  filter: drop-shadow(0 calc(var(--card-shadow-y) * var(--u)) calc(var(--card-shadow-blur) * var(--u)) var(--color-card-shadow));
  pointer-events: none;
  transition: opacity var(--duration-hover) var(--ease-out);
}

.footer__pill-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--color-card-highlight);
  mask: url("../assets/oval-fill.svg") center / 100% 100% no-repeat;
}

.footer__pill-outline {
  position: absolute;
  left: calc(var(--footer-pill-outline-out) * -1 * var(--u));
  top: calc(var(--footer-pill-outline-out) * -1 * var(--u));
  width: calc(100% + var(--footer-pill-outline-out) * 2 * var(--u));
  height: calc(100% + var(--footer-pill-outline-out) * 2 * var(--u));
  pointer-events: none;
}

/* The line keeps its weight when the outline is stretched to fit the pill */
.footer__pill-outline path {
  vector-effect: non-scaling-stroke;
}

/* Location: plain Quicksand, so the facts read clearly */
.footer__meta {
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  font-size: max(var(--text-small-min), calc(var(--text-small) * var(--u)));
}

.footer__links + .footer__meta {
  margin-top: calc(var(--footer-meta-gap) * var(--u));
}

/* How the site was made: a handwritten aside under the location */
.footer__source {
  margin-top: calc(var(--space-3) * var(--u));
  font-size: calc(var(--text-body) * var(--u));
}

/* Draw-in (js/contact-oval.js): after the heading, one pill after the other */
.footer.is-drawn .footer__pill-outline .map-line {
  transition-delay: calc(var(--footer-draw-delay) + var(--i, 0) * var(--map-stagger));
}

/* Hover and keyboard focus: the outline redraws at its normal weight,
   and the word is written again (js/link-write.js) */
.footer__pill:focus-visible .map-line {
  animation: map-redraw var(--duration-redraw) var(--ease-out);
}

@media (hover: hover) {
  .footer__pill:hover .map-line {
    animation: map-redraw var(--duration-redraw) var(--ease-out);
  }
}

/* Hover and keyboard focus, as on the project cards: the pill lifts and fills white,
   and the other pills dim. Pressed = drops part way back. */
.footer__pill:focus-visible {
  outline-color: var(--color-focus);
  transform: translateY(calc(var(--card-raise) * -1 * var(--u)));
}

.footer__pill:focus-visible .footer__pill-highlight {
  opacity: 1;
}

.footer__links:has(.footer__pill:focus-visible) .footer__pill:not(:focus-visible) {
  opacity: var(--dim-opacity);
}

@media (hover: hover) {
  .footer__pill:hover {
    transform: translateY(calc(var(--card-raise) * -1 * var(--u)));
  }

  .footer__pill:hover .footer__pill-highlight {
    opacity: 1;
  }

  .footer__links:has(.footer__pill:hover) .footer__pill:not(:hover) {
    opacity: var(--dim-opacity);
  }
}

.footer__pill:active {
  transform: translateY(calc(var(--card-raise-pressed) * -1 * var(--u)));
  transition-duration: var(--duration-press);
}

@media (prefers-reduced-motion: reduce) {
  .footer__pill .map-line {
    animation: none !important;
  }

  .footer__pill,
  .footer__pill:hover,
  .footer__pill:focus-visible,
  .footer__pill:active {
    transform: none;
  }

  .footer__pill-highlight {
    transition-duration: var(--duration-press);
  }
}
/* ---------------------------------------------------------------
   Phones and tablets (under 1024px). Not designed in Figma (assumption):
   items leave their Figma x / y and stack in one column.
   The timeline is not pinned and the mind-map does not draw in (see the scripts).
   --------------------------------------------------------------- */
@media (max-width: 1023.98px) {
  html {
    overflow-x: clip;
  }

  .page {
    width: 100%;
  }

  .at {
    position: static;
    left: auto;
    top: auto;
  }

  .hero,
  .timeline__canvas,
  .projects {
    height: auto;
    padding-inline: var(--space-page);
  }

  /* Hero: same rhythm as desktop (margins from the spacing scale), phone sizes */
  .hero {
    display: flex;
    flex-direction: column;
    padding-block: calc(var(--space-section) * 1.25) var(--space-section);
  }

  .hero__title {
    font-size: var(--text-hero-compact);
    line-height: 1.05;
    white-space: normal;
  }

  .hero .role {
    height: auto;
    padding: 0.14em 0.38em 0.2em;
    font-size: var(--text-role-compact);
    line-height: 1.25;
  }

  /* Smaller than the role label, so the headline stays the loudest thing on a phone */
  .hero__tagline {
    width: auto;
    font-size: var(--text-tagline-compact);
  }

  .hero__proof {
    flex-direction: column;
    gap: var(--space-proof-compact);
  }

  .hero__proof-num {
    font-size: var(--text-proof-compact);
  }

  /* Labels nearly fill a phone's width, so the "paused" note sits under them */
  .role.is-paused::after {
    left: 0;
    top: 100%;
    translate: 0 var(--space-1);
  }

  /* Timeline: entries in one column, hand-drawn line on the left, no markers */
  .timeline__canvas {
    padding-block: var(--space-section);
  }

  .timeline ol {
    display: flex;
    flex-direction: column;
    gap: var(--space-stack);
    padding-left: var(--timeline-indent);
  }

  .timeline ol > li {
    display: flex;
    flex-direction: column;
    gap: var(--gap-s);
  }

  .timeline .drawing {
    display: none;
  }

  .timeline .timeline__line {
    display: block;
    position: absolute;
    left: var(--space-page);
    top: var(--space-section);
    width: calc(var(--timeline-indent) / 2);
    height: calc(100% - var(--space-section) * 2);
  }

  /* Each entry's hand-drawn marker sits on the line, level with its year label.
     (The desktop markers are placed by Figma x / y, so they are hidden here.) */
  .timeline ol > li,
  .timeline__end {
    position: relative;
  }

  .timeline ol > li::before,
  .timeline__end::before {
    content: "";
    position: absolute;
    width: var(--marker-size-compact);
    height: var(--marker-size-compact);
    background: center / contain no-repeat;
    pointer-events: none;
  }

  /* Line centre is a quarter of the indent in from the page margin */
  .timeline ol > li::before {
    left: calc(var(--timeline-indent) / 4 - var(--timeline-indent) - var(--marker-size-compact) / 2);
    top: calc(76 * var(--u) / 2 - var(--marker-size-compact) / 2);
  }

  .timeline ol > li:nth-child(1)::before { background-image: url("../assets/timeline-start.svg"); }
  .timeline ol > li:nth-child(2)::before { background-image: url("../assets/marker-2022.svg"); }
  .timeline ol > li:nth-child(3)::before { background-image: url("../assets/marker-2024.svg"); }
  .timeline ol > li:nth-child(4)::before { background-image: url("../assets/marker-current.svg"); }

  .timeline__end::before {
    left: calc(var(--timeline-indent) / 4 - var(--marker-size-compact) / 2);
    top: calc(0.7em - var(--marker-size-compact) / 2);
    background-image: url("../assets/marker-current.svg");
  }

  .entry {
    width: auto;
    align-items: flex-start;
  }

  .entry__desc,
  .entry__list {
    width: auto;
    text-align: left;
  }

  /* Readable lines on tablets: about 60 characters at most, a little more line spacing */
  .entry__desc,
  .entry__list,
  .project__desc,
  .hero__tagline,
  .hero__proof,
  .timeline__end {
    max-width: var(--measure-compact);
  }

  .text-hand {
    line-height: var(--leading-hand-compact);
  }

  .entry__list li {
    margin-inline-start: 1.2em;
  }

  .timeline__end {
    transform: none;
    margin-top: var(--space-stack);
    padding-left: var(--timeline-indent);
    white-space: normal;
  }

  /* Past projects: cards stacked, mind-map oval and arrows hidden */
  .projects {
    padding-block: var(--space-section) calc(var(--space-section) * 1.25);
  }

  .projects__title {
    margin-bottom: var(--space-stack);
    line-height: 1.1;
    white-space: normal;
  }

  .projects > [aria-hidden="true"] {
    display: none;
  }

  .projects ul {
    display: flex;
    flex-direction: column;
    gap: var(--space-stack);
  }

  .project-card {
    position: relative;
    width: 100%;
    max-width: var(--card-max-compact);
    height: auto;
  }

  /* Side padding grows with the card so text stays inside the slanted outline */
  .project-card__link {
    position: relative;
    inset: auto;
    display: block;
    padding: calc(var(--space-card-y) * 1.5) max(var(--space-card-x), var(--card-pad-ratio)) calc(var(--space-card-y) * 1.5);
  }

  /* Outline A dips lowest at its top-left corner; on phones the cards are taller than
     they are wide, so that dip is deeper and the label needs more room above it */
  .project-card:has(.project-card__fill:not(.drawing--flip-x)) .project-card__link {
    padding-top: var(--space-card-top-a-compact);
  }

  /* Outline and white highlight stretch to the card */
  .project-card__highlight,
  .project-card__link > .drawing {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
  }

  .project {
    position: relative;
    width: auto;
  }

  .project__head {
    white-space: normal;
  }

  .project-card__cta {
    position: relative;
    display: inline-block;
    margin-top: calc(var(--space-stack) / 4);
    opacity: 1;
  }

  /* Keyboard pause button sits under the hero title */
  .role-toggle {
    position: static;
    align-self: flex-start;
  }

  /* Footer: same left-aligned order, phone sizes. The label sits on its own line
     (like the hero), so the heading never jumps between one and two lines. */
  .footer {
    width: auto;
    min-height: 0;
    padding: var(--space-section) var(--space-page) calc(var(--space-section) * 1.25);
  }

  .footer__note {
    max-width: var(--measure-compact);
  }

  .footer__title {
    margin-top: calc(var(--footer-note-gap) * var(--u)); /* no lift on phones */
    font-size: var(--text-hero-compact);
    line-height: 1.05;
    white-space: normal;
  }

  .footer__tag {
    display: block;
    height: auto;
    margin-top: calc(var(--space-stack) / 4);
    padding: 0.1em 0.34em 0.16em;
    line-height: 1.2;
    top: 0; /* on its own line here, so no lift */
  }

  .footer__links {
    margin-top: var(--space-stack);
  }

  /* Slimmer sides, so both pills fit on one row on most phones */
  .footer__pill {
    min-height: var(--touch-target);
    padding-inline: var(--space-card-x);
  }
}

/* The narrowest phones (under 360px): cards are much taller than wide, so the outline's
   top edge dips deeper still and the wide labels need more room above them */
@media (max-width: 359.98px) {
  .project-card__link {
    padding-top: var(--space-card-top-narrow);
  }

  .project-card:has(.project-card__fill:not(.drawing--flip-x)) .project-card__link {
    padding-top: var(--space-card-top-a-narrow);
  }
}