:root {
  --bg: #12171C;
  --text: #F4F1E7;
  --text-heading: #F2EEE5;
  --rule: rgba(242, 248, 239, 0.5);
  --accent: #d9bd94;
  /* h2 headings and the line beside them. */
  --h2: #A3A3A3;
  --font-heading: "Crimson Text", serif;
  --font-nav: "Geologica", sans-serif;
  --font-body: "Hanken Grotesk", sans-serif;
  --font-mono: "Overpass Mono", monospace;
  --max-width: 1000px;
  /* Vertical space before every block that starts with an h2, whether it's
     a top-level section or a sub-section inside one. */
  --section-gap: 7rem;
}

html {
  font-size: 16px;
  /* Stop iPhone Safari from auto-enlarging running text in landscape
     (which inflates paragraphs but not headings and breaks the balance).
     100%, not none, so user text zoom still works. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

@media (max-width: 768px) {
  html {
    font-size: 10px;
  }
}

/* In-page links (e.g. "see the case study below") scroll smoothly, unless
   the visitor has asked for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1.1rem;
  line-height: 1.6;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

@media (max-width: 768px) {
  body {
    font-size: 1.7rem;
  }
}

a {
  color: inherit;
}

hr {
  border: 0;
  height: 1px;
  background: var(--rule);
  margin: 0;
}

/* ---- Nav ---- */

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2.5rem;
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  padding: 2rem clamp(1.5rem, 5vw, 3rem);
}

.logo {
  font-family: var(--font-nav);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1;
  margin: 0;
  text-decoration: none;
  color: var(--text);
}

.logo a {
  text-decoration: none;
}

.nav-links {
  display: flex;
  gap: 2.5rem;
}

.nav-links a,
.mobile-menu-links a {
  font-family: var(--font-nav);
  font-size: 1rem;
  text-decoration: none;
}

.nav a.active,
.mobile-menu-links a.active {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 0.5em;
}

.menu-toggle,
.menu-close {
  background: none;
  border: none;
  font-family: var(--font-nav);
  font-size: 2.925rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  color: var(--text);
}

/* .menu-toggle lives in the always-rendered header, so it must stay hidden
   until the mobile breakpoint. .menu-close only exists inside .mobile-menu,
   which is already hidden by default via the `hidden` attribute, so it can
   just render normally whenever its parent overlay is shown. */
.menu-toggle {
  display: none;
}

@media (max-width: 768px) {
  .nav-links {
    display: none;
  }
  .menu-toggle {
    display: block;
  }
  .nav .logo {
    font-size: 2.55rem;
  }
  /* The "active" underline on the logo is a desktop-menu thing only. */
  .nav .logo a.active {
    text-decoration: none;
  }
}

/* Mobile overlay menu */

.mobile-menu {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 100;
  padding: 2rem 3rem;
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

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

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

.mobile-menu-top .logo {
  font-size: 2.55rem;
}

.mobile-menu-links {
  display: flex;
  flex-direction: column;
  gap: 1.2rem;
}

.mobile-menu-links a {
  font-size: 2.55rem;
}

/* ---- Page content ---- */

.page-content {
  flex: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  padding: 2rem clamp(1.5rem, 5vw, 3rem) var(--section-gap);
  display: flex;
  flex-direction: column;
  gap: var(--section-gap);
}

h1 {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 4.5rem;
  line-height: 1;
  color: var(--accent);
  margin: 0 0 1.5rem;
}

h2 {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.9rem;
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--h2);
  margin: var(--section-gap) 0 2rem;
}

/* .page-content's own --section-gap (and .hero-wrap's 2.5em gap) already provides
   the "before" spacing for a section's very first heading, so an h2 in that
   position shouldn't also add its own margin-top on top of it — only h2s
   that follow another element within the SAME gapless container (e.g. the
   later sub-sections inside .case-study) need their own margin-top to create
   spacing, since nothing else provides it there. */
.services > h2,
.hero-secondary > h2,
.experience > h2,
.case-study > div:first-child h2 {
  margin-top: 0;
}

h2::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--h2);
}

h3 {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 1.8rem;
  /* Tight enough that headings wrapping onto two or three lines (e.g. the
     case study title on mobile) still read as one heading. */
  line-height: 1.25;
  color: var(--text-heading);
  margin: 0 0 0.75rem;
}
/* Sub-subheadings (e.g. "In short" / "Key learnings" in the case study):
   same serif as h3 but smaller, with a little space above to separate
   them from the content before. */
h4 {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 1.4rem;
  line-height: 1.5;
  color: var(--text-heading);
  margin: 3.25rem 0 0.4rem;
}

@media (max-width: 768px) {
  h2 {
    font-size: 1.5rem;
  }
  h3 {
    font-size: 2.6rem;
  }
  h4 {
    font-size: 2.3rem;
  }
}

p {
  margin: 0 0 1em;
}

p:last-child {
  margin-bottom: 0;
}

ul {
  margin: 0;
  padding-left: 1.5em;
}

li {
  margin: 0.6em 0;
  line-height: 1.35;
}

li::marker {
  color: var(--accent);
}
/* Step numbers in front of numbered h3s (Teams & Culture process). */
.step-number {
  color: var(--accent);
}

/* Bold phrases in body text pick up the accent colour. */
strong {
  color: var(--accent);
}

/* So do links in body text. */
.page-content p a,
.page-content li a {
  color: var(--accent);
}
/* Hero two-column layout, reused on every page */

/* Wraps the Index hero + its follow-up paragraphs so they sit close together
   (one blank-line's worth of space) instead of using .page-content's normal
   6.25rem gap between sections. */
.hero-wrap {
  display: flex;
  flex-direction: column;
  gap: 2.5em;
}

/* Hero text that continues below the image (Coaching, About): just a normal
   paragraph break, so it reads as one block of text. */
.hero-wrap-continued {
  gap: 1em;
}

.hero {
  display: flex;
  gap: 4rem;
  align-items: flex-end;
}

.hero-text {
  flex: 1 1 60%;
}

.hero-image {
  flex: 1 1 40%;
  align-self: flex-start;
}

.hero-image img {
  width: 100%;
  display: block;
}

/* 62/38 hero split for the hero illustration (About). */
@media (min-width: 769px) {
  .hero-split-38 .hero-text {
    flex-basis: 62%;
  }
  .hero-split-38 .hero-image {
    flex-basis: 38%;
  }
}

/* Mobile: less side inset than the default. */
@media (max-width: 768px) {
  .hero.hero-split-38 .hero-image {
    padding: 6% 2%;
  }
  /* Slightly smaller, right-aligned. */
  .hero-split-38 .hero-image img {
    width: 72%;
    margin-left: auto;
    margin-right: 5%;
  }
}

/* Text-only 65/35 split (e.g. intro copy + a bullet list side by side) */
.text-split {
  display: flex;
}

.text-split-main {
  flex: 1 1 65%;
  padding-right: 2rem;
}

.text-split-aside {
  flex: 1 1 35%;
  padding-left: 2rem;
  border-left: 1px solid var(--rule);
}
/* Image on the left, text on the right (40/60, mirroring the hero), used
   inline within a section's text. The bottom margin matches the spacing
   between paragraphs. */
.image-text-split {
  display: flex;
  gap: 4rem;
  align-items: center;
  margin-bottom: 1em;
}

.image-text-split-image {
  flex: 1 1 40%;
}

.image-text-split-image img {
  width: 100%;
  display: block;
}

.image-text-split-text {
  flex: 1 1 60%;
}

/* As the last thing in a section, no paragraph-gap below it, so the space
   before the next section heading stays the standard --section-gap. */
.image-text-split:last-child {
  margin-bottom: 0;
}

/* Optional caption under the image/clip (About page). The media then sits
   in a .split-media wrapper, which on desktop fills whatever height the
   caption leaves (see below). */
.split-caption {
  margin: 0.5rem 0 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.8rem;
  line-height: 1.3;
  color: var(--h2);
}

@media (max-width: 768px) {
  .split-caption {
    font-size: 1.3rem;
  }
  /* Stacked on mobile, the portrait clip in the narrow (30/70) split
     (Fish) is cropped to a square so it doesn't take over the screen. */
  .image-text-split-30 .image-text-split-image video {
    aspect-ratio: 1;
    object-fit: cover;
  }
}

/* A silent looping clip in place of the image: no controls and no pointer
   events (see .video-row). Uncropped when stacked on mobile; on desktop
   it's cropped to the text's height like the photos (below). */
.image-text-split-image video {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
}

/* Text on the left, image on the right (Coaching page). The image stays
   first in the markup, so on mobile it still stacks above the text. */
.image-text-split-reverse {
  flex-direction: row-reverse;
}

/* Desktop: the text sets the row's height and the image fills it, cropping
   the sides when the column is narrower than the photo at that height. The
   image is taken out of flow so it never makes the row taller itself. */
@media (min-width: 769px) {
  .image-text-split {
    align-items: stretch;
  }
  .image-text-split-image {
    position: relative;
  }
  .image-text-split-image img {
    position: absolute;
    inset: 0;
    height: 100%;
    object-fit: cover;
    /* Set --focus on the img to pick which part survives the crop. */
    object-position: var(--focus, center);
  }
  /* With a caption: the column stacks media + caption, and the media
     wrapper takes the remaining height, its img/video filling it. */
  .image-text-split-image {
    display: flex;
    flex-direction: column;
  }
  .split-media {
    position: relative;
    flex: 1 1 auto;
  }
  /* A clip in the image column is cropped to the text's height, like the
     photos (About page). */
  .image-text-split-image video {
    position: absolute;
    inset: 0;
    height: 100%;
    object-fit: cover;
    /* Set --focus on the video to pick which part survives the crop. */
    object-position: var(--focus, center);
  }
  /* 30/70 variant (About page portrait Fish clip): a narrow column so the
     portrait clip isn't cropped into a wide strip. As the window narrows the
     text wraps and the row gets taller, which would crop the clip's sides
     more — so the column widens instead: 30% at 1100px+, growing steadily
     to at most 45% near the mobile breakpoint. */
  .image-text-split-30 {
    --split-img: clamp(30%, calc(30% + (1100px - 100vw) * 0.25), 45%);
  }
  .image-text-split-30 .image-text-split-image {
    flex-basis: var(--split-img);
  }
  .image-text-split-30 .image-text-split-text {
    flex-basis: calc(100% - var(--split-img));
  }
  /* 45/55 variant (Coaching photo, About video) instead of the default
     40/60. */
  .image-text-split-45 .image-text-split-image {
    flex-basis: 45%;
  }
  .image-text-split-45 .image-text-split-text {
    flex-basis: 55%;
  }
}

@media (max-width: 768px) {
  .image-text-split {
    flex-direction: column;
    gap: 2.5rem;
  }  .image-text-split-image,
  .image-text-split-text {
    flex: 1 1 auto;
    width: 100%;
  }
  /* Media after the first paragraph instead of above the text (About
     clips). The text wrapper is dissolved so its paragraphs can be ordered
     around the media; the split's gap spaces them all. */
  .image-text-split-media-second .image-text-split-text {
    display: contents;
  }
  .image-text-split-media-second .image-text-split-text > p {
    order: 2;
    margin: 0;
  }
  .image-text-split-media-second .image-text-split-text > p:first-child {
    order: 0;
  }
  .image-text-split-media-second .image-text-split-image {
    order: 1;
  }
  /* With only one paragraph the media ends up last, so give it the same
     space below as above before the text that follows. */
  .image-text-split-media-second:has(.image-text-split-text > p:only-child) {
    margin-bottom: 2.5rem;
  }
}

/* Row of equally tall poster images. Every cell has the same 2:3 shape;
   object-fit: cover trims the rare poster that's slightly wider. Wraps to
   4 per row on mobile. */
.poster-row {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 0.5rem;
  margin-bottom: 1em;
}

/* As the last thing in a section, no extra gap before the next heading. */
.poster-row:last-child {
  margin-bottom: 0;
}

.poster-row img {
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  display: block;
}

@media (max-width: 768px) {
  .poster-row {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Justified rows of silent, looping showreel clips (About, creative
   career), uncropped. Each video sets --ar (its width / height) and grows
   in proportion to it, so every clip in a row ends up the same height and
   the row exactly fills the width — the row's height follows from that.
   No controls; pointer events are off so the browser's right-click video
   menu can't pause or expose them. */
.video-row {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

/* First row: a paragraph's worth of space below the text above it. */
p + .video-row {
  margin-top: 1em;
}

.video-row video {
  display: block;
  flex: var(--ar) 1 0;
  min-width: 0;
  aspect-ratio: var(--ar);
  height: auto;
  pointer-events: none;
}

/* Keep the aside heading aligned with the top of .text-split-main's text
   instead of getting its own extra top margin. */
.text-split-aside h2 {
  margin-top: 0;
}

/* When a .text-split follows another sub-section inside .case-study, put the
   usual h2 top spacing on the split itself, so both columns' headings (and
   the divider line) start at the same height. */
.case-study > .text-split:not(:first-child) {
  margin-top: var(--section-gap);
}

.text-split-main > h2:first-child {
  margin-top: 0;
}

@media (max-width: 768px) {
  .text-split {
    flex-direction: column;
  }
  .text-split-main {
    padding-right: 0;
  }
  .text-split-aside {
    padding-left: 0;
    border-left: none;
    /* Stacked on mobile, the aside is a new h2 block like any other. */
    padding-top: var(--section-gap);
  }
  .text-split-main,
  .text-split-aside {
    flex: 1 1 auto;
    width: 100%;
  }
}

/* Small illustration on its own line below a paragraph, right-aligned but
   inset from the edge (Teams & Culture, "My leadership style"). */
.text-image-below {
  display: block;
  width: 12.5rem;
  max-width: 100%;
  height: auto;
  /* Negative bottom margin pulls the next section up a little, as the note
     otherwise leaves the gap below the section looking oversized. */
  margin: 1rem 6% -2rem auto;
}

/* Small illustration on its own line, centred (About, below the "My story"
   timeline). */
.text-image-below-center {
  display: block;
  width: 12rem;
  max-width: 100%;
  height: auto;
  /* Centred in the space between the timeline (whose 1.5rem bottom padding
     adds to the top margin) and the next section heading (7rem
     --section-gap, reduced by the negative bottom margin): 3.75rem each. */
  margin: 2.25rem auto -3.25rem;
}

/* The same between two sections with nothing padding the one above (Teams
   & Culture, Tools): the whole 3.75rem goes in the top margin. Wider, as
   it's a long, flat image. */
.text-image-between-sections {
  width: 10.75rem;
  margin-top: 3.75rem;
}

/* A one-line note (Coaching): wider, so its handwriting comes out the same
   size as the two-line one on Teams & Culture. */
.text-image-below-long {
  width: 18.5rem;
}

/* Sizes: fixed on desktop (what the old percentages gave at the full
   1000px page width), so they don't shrink as the window narrows. On
   mobile a fixed part plus a smaller percentage, so they scale with the
   screen but less than a plain percentage would. */
@media (max-width: 768px) {
  .text-image-below {
    width: calc(6.5rem + 24%);
  }
  .text-image-below-long {
    width: calc(11.9rem + 24%);
  }
  .text-image-below-center {
    width: calc(8.1rem + 20%);
  }
  .text-image-between-sections {
    width: calc(7.75rem + 20%);
  }
}

/* Photo in the top-right corner of a section, with the text wrapping
   around it (Coaching, "What I bring as a coach"). Uncropped. */

.float-right-image {
  float: right;
  width: 42%;
  height: auto;
  margin: 0.35rem 0 1.5rem 2.5rem;
}

/* Mobile: no wrapping; the photo comes first, full width. */
@media (max-width: 768px) {
  .float-right-image {
    float: none;
    display: block;
    width: 100%;
    margin: 0 0 2.5rem;
  }
}

/* Outline around a larger block of content to make it stand out (Teams &
   Culture case study). Roomier padding than the small boxes. */
.outlined-box {
  border: 1px solid var(--rule);
  padding: 2.5rem 3rem;
}

@media (max-width: 768px) {
  .outlined-box {
    padding: 2.5rem;
  }
}

/* A short bulleted list laid out in a grid, 3 across (2 on mobile)
   (Teams & Culture services). The column gap leaves room for each column's
   bullets, which hang outside the text like in a normal list. */
.grid-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: 4rem;
  row-gap: 1.5em;
  margin: 0;
  padding-left: 1.5em;
}

.grid-list li {
  margin: 0;
  /* Serif (as in the h3s), sized up since Crimson Text reads smaller than
     the body font. */
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1.3;
}

/* Desktop: in a narrow side column (Teams & Culture, next to "What I
   bring to the table") the grid becomes a single column. */
@media (min-width: 769px) {
  .text-split-aside .grid-list {
    grid-template-columns: 1fr;
  }
  /* Mirrored split: the narrow aside on the left, main on the right, with
     the divider on the aside's right edge (between the two). */
  .text-split.text-split-reverse {
    flex-direction: row-reverse;
  }
  .text-split-reverse .text-split-main {
    padding-right: 0;
    padding-left: 2rem;
  }
  .text-split-reverse .text-split-aside {
    padding-left: 0;
    padding-right: 2rem;
    border-left: none;
    border-right: 1px solid var(--rule);
  }
}

/* Mobile: the mirrored split stacks with the aside first, so the section
   gap moves from above the aside to above the main column. */
@media (max-width: 768px) {
  .text-split.text-split-reverse {
    flex-direction: column-reverse;
  }
  .text-split-reverse .text-split-aside {
    padding-top: 0;
  }
  .text-split-reverse .text-split-main {
    padding-top: var(--section-gap);
  }
}

@media (max-width: 768px) {
  .grid-list {
    grid-template-columns: repeat(2, 1fr);
  }
  .grid-list li {
    font-size: 2.3rem;
  }
}

/* Numbered step headings in monospace (Teams & Culture process). */
.steps h3 {
  font-family: var(--font-mono);
  font-weight: 500;
  font-variant-ligatures: none;
  font-kerning: none;
  font-size: 1.05rem;
  line-height: 1.5;
  margin-top: 2.25rem;
  margin-bottom: 0.2rem;
}

.steps .timeline > h3:first-child {
  margin-top: 0;
}

/* Timeline down the left of the steps. ::before is the vertical line: it
   starts at the first step heading's tick (half that heading's line box
   down, --tl-start) and stops where ::after, a downward arrowhead, sits at
   the bottom. Each step heading draws a tick (--tl-tick long) centred on
   the line, so it sticks out both sides; the line sits half a tick in from
   the content edge so the ticks' left ends line up with the page margin.
   --tl-gap is the space from the line to the text. */
.timeline {
  --tl-start: calc(1.05rem * 1.5 / 2);
  --tl-arrow: 10px;
  --tl-tick: 1.125rem;
  --tl-gap: 3.25rem;
  --tl-x: calc(var(--tl-tick) / 2);
  position: relative;
  padding-left: calc(var(--tl-x) + var(--tl-gap));
  padding-bottom: 1.5rem;
}

.timeline::before {
  content: "";
  position: absolute;
  left: calc(var(--tl-x) - 1px);
  top: var(--tl-start);
  bottom: var(--tl-arrow);
  width: 2px;
  margin-top: -1px;
  background: var(--accent);
}

.timeline::after {
  content: "";
  position: absolute;
  left: var(--tl-x);
  bottom: 0;
  transform: translateX(-50%);
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: var(--tl-arrow) solid var(--accent);
}

.timeline > h3 {
  position: relative;
}

.timeline > h3::before {
  content: "";
  position: absolute;
  top: 50%;
  /* Back out across the padding to the content edge. */
  left: calc(-1 * (var(--tl-x) + var(--tl-gap)));
  width: var(--tl-tick);
  height: 2px;
  margin-top: -1px;
  background: var(--accent);
}

@media (max-width: 768px) {
  .timeline {
    --tl-start: calc(1.55rem * 1.5 / 2);
    --tl-tick: 0.9375rem;
    --tl-gap: 2.6rem;
  }
}

@media (max-width: 768px) {
  .steps h3 {
    font-size: 1.55rem;
  }
}

@media (max-width: 768px) {
  .hero {
    flex-direction: column-reverse;
    gap: 2.5rem;
  }
  .hero-text,
  .hero-image {
    flex: 1 1 auto;
    width: 100%;
  }
  /* An empty placeholder image column would only add a stray gap when
     stacked. */
  .hero-image:empty {
    display: none;
  }  /* Headline, then image, then the intro text (Teams & Culture, Coaching,
     About). The
     .hero-text wrapper is dissolved so its h1 and .hero-intro can be
     ordered around the image; the hero's gap spaces all three. */
  .hero.hero-image-middle {
    flex-direction: column;
    align-items: stretch;
  }
  .hero-image-middle .hero-text {
    display: contents;
  }
  .hero-image-middle h1 {
    order: 1;
    margin-bottom: 0;
  }
  .hero-image-middle .hero-image {
    order: 2;
    /* Inset the illustration so it doesn't run edge to edge on a phone. */
    padding: 6% 12%;
  }
  .hero-image-middle .hero-intro {
    order: 3;
  }}

/* Index: full-width top section (nav + hero) with a photo behind it.
   Swap the photo by changing --top-image. The photo fills a ::before layer;
   a left-side scrim keeps the headline readable, a top scrim keeps the nav
   readable, and the bottom edge fades into --bg. */

.top-section {
  --top-image: url("../assets/IndexHero.jpg");
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 42rem;
}

/* The layer is as tall as the section and exactly as wide as the photo at
   that height (aspect-ratio matches IndexHero.jpg, 3839x931), centered, so
   nothing is cropped — its bottom edge fades into --bg.
   Update aspect-ratio if --top-image changes to a different shape. */
.top-section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  aspect-ratio: 3839 / 931;
  /* Shifted up 30px so the top 30px is cropped off (clipped by overflow). */
  transform: translate(-50%, -30px);
  z-index: -1;
  background:
    linear-gradient(to bottom, rgba(15, 20, 23, 0.35), rgba(15, 20, 23, 0) 9rem),
    linear-gradient(to right, rgba(15, 20, 23, 0.7), rgba(15, 20, 23, 0) 55%),
    var(--top-image) center / cover no-repeat;
  /* Bottom fade intersected with a long left/right fade, so on screens wider
     than the photo its side edges melt into --bg instead of ending hard.
     The fades follow a smootherstep curve (many stops) rather than a plain
     two-stop linear ramp, so there's no visible line where they start. */
  --fade-y: linear-gradient(to bottom,
    #000 75%,
    rgba(0, 0, 0, .991) 77.5%, rgba(0, 0, 0, .942) 80%,
    rgba(0, 0, 0, .837) 82.5%, rgba(0, 0, 0, .683) 85%,
    rgba(0, 0, 0, .5) 87.5%, rgba(0, 0, 0, .317) 90%,
    rgba(0, 0, 0, .163) 92.5%, rgba(0, 0, 0, .058) 95%,
    rgba(0, 0, 0, .009) 97.5%, transparent);
  --fade-x: linear-gradient(to right,
    transparent,
    rgba(0, 0, 0, .009) 2.2%, rgba(0, 0, 0, .058) 4.4%,
    rgba(0, 0, 0, .163) 6.6%, rgba(0, 0, 0, .317) 8.8%,
    rgba(0, 0, 0, .5) 11%, rgba(0, 0, 0, .683) 13.2%,
    rgba(0, 0, 0, .837) 15.4%, rgba(0, 0, 0, .942) 17.6%,
    rgba(0, 0, 0, .991) 19.8%, #000 22%,
    #000 78%,
    rgba(0, 0, 0, .991) 80.2%, rgba(0, 0, 0, .942) 82.4%,
    rgba(0, 0, 0, .837) 84.6%, rgba(0, 0, 0, .683) 86.8%,
    rgba(0, 0, 0, .5) 89%, rgba(0, 0, 0, .317) 91.2%,
    rgba(0, 0, 0, .163) 93.4%, rgba(0, 0, 0, .058) 95.6%,
    rgba(0, 0, 0, .009) 97.8%, transparent);
  -webkit-mask-image: var(--fade-y), var(--fade-x);
  -webkit-mask-composite: source-in;
  mask-image: var(--fade-y), var(--fade-x);
  mask-composite: intersect;
}

.top-section .hero {
  flex: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  padding: 2rem clamp(1.5rem, 5vw, 3rem) 3rem;
}

.hero-spacer {
  flex: 1 1 40%;
}

@media (max-width: 768px) {
  .top-section {
    min-height: 0;
  }
  .top-section::before {
    left: 0;
    right: 0;
    height: 50.5rem;
    aspect-ratio: auto;
    transform: none;
    /* Photo is 57.5rem tall in a 50.5rem layer, anchored at the bottom, so
       7rem is cropped off the top and nothing off the bottom. 58% puts the
       face slightly right of centre (60% would centre it). */
    background-size: auto, auto, auto 57.5rem;
    background-position: center, center, 58% bottom;
    /* Photo spans the full width here, so only the (eased) bottom fade. */
    --fade-y: linear-gradient(to bottom,
      #000 55%,
      rgba(0, 0, 0, .991) 59.5%, rgba(0, 0, 0, .942) 64%,
      rgba(0, 0, 0, .837) 68.5%, rgba(0, 0, 0, .683) 73%,
      rgba(0, 0, 0, .5) 77.5%, rgba(0, 0, 0, .317) 82%,
      rgba(0, 0, 0, .163) 86.5%, rgba(0, 0, 0, .058) 91%,
      rgba(0, 0, 0, .009) 95.5%, transparent);
    -webkit-mask-image: var(--fade-y);
    mask-image: var(--fade-y);
  }
  .hero-spacer {
    flex: 0 0 auto;
    height: 26rem;
  }
}

/* Services grid on the homepage */

/* Each card is a bordered box; the grid's gap separates them. */
.services-grid {
  display: flex;
  gap: 2rem;
}

.service-card {
  flex: 1;
  padding: 1.5rem 2rem;
  border: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.service-card h3,
.service-card p {
  margin: 0;
}

/* Back-to-back paragraphs get a normal paragraph break (1em total, on top
   of the card's 0.5rem gap), like body text elsewhere. */
.service-card p + p {
  margin-top: calc(1em - 0.5rem);
}

.service-card .read-more {
  margin-top: auto;
  padding-top: 1.5rem;
}

.service-card .read-more a {
  color: var(--accent);
}

@media (max-width: 768px) {
  .services-grid {
    flex-direction: column;
    gap: 2.5rem;
  }
  .service-card {
    padding: 2rem 2.5rem;
  }
  /* Stacked, the auto margin no longer pushes "Read more" down, so add an
     extra line's worth of space above it. */
  .service-card .read-more {
    padding-top: calc(1.5rem + 1em);
  }
}

/* Case-study / experience sections */

.case-study {
  display: flex;
  flex-direction: column;
}

.experience {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}

/* Footer */

.site-footer {
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  padding: 0 clamp(1.5rem, 5vw, 3rem) 3rem;
}

/* .page-content's bottom padding already provides the --section-gap above
   the footer. */
.site-footer > h2 {
  margin-top: 0;
}

/* Mobile: the call button sits at the top, so the "I love to talk!" note
   above it reaches ~40px above the name row; extra room below the heading
   keeps it clear of the heading's line. (On desktop the button is centred
   lower down, so the note fits within the content.) */
@media (max-width: 768px) {
  .site-footer > h2 {
    margin-bottom: 6.5rem;
  }
}

.site-footer p {
  margin: 0 0 0.3em;
}

/* Two columns on desktop and mobile alike: name, city and email (left) and
   the call button (right-aligned). Desktop centres the button vertically
   against the left column; mobile pins it to the top, level with the
   name. */
.footer-columns {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

@media (max-width: 768px) {
  .footer-columns {
    align-items: flex-start;
  }
  /* Centre the button on the name's line box (2.6rem font x 1.25). */
  .footer-contact {
    min-height: calc(2.6rem * 1.25);
    display: flex;
    align-items: center;
  }
}

.footer-name {
  margin: 0 0 0.25rem;
}

.footer-contact {
  flex: 0 0 auto;
  text-align: right;
}

.footer-details {
  text-align: left;
}

/* "Schedule a call" as a rounded accent-coloured button. */
.footer-contact .read-more {
  margin: 0;
  position: relative;
}

/* "I love to talk!" note anchored to the call button, at a fixed 70% of
   its 158x76 file size (111x53; never scaled with the layout). Its arrow
   tip is at about (74, 52) at that size and points down-right, so the image
   sits above-left of the button with the tip pointing at its left edge. */
.button-note {
  position: absolute;
  right: 100%;
  bottom: 100%;
  width: 111px;
  height: 53px;
  max-width: none;
  /* Right edge sits on the button's left edge, so the tip (37px in from the
     right) is shifted 17px right to land 20px off it; the tip is 1px above
     the image's bottom, so it's dropped 9px to point 8px down the button. */
  margin: 0 -17px -9px 0;
  pointer-events: none;
}

.footer-contact .read-more a {
  display: inline-block;
  padding: 0.45em 1.1em;
  border-radius: 0.5em;
  font-size: 0.9em;
  background: var(--accent);
  color: var(--bg);
  font-weight: 700;
  text-decoration: none;
  line-height: 1.2;
}

.footer-contact .read-more a:hover {
  filter: brightness(1.08);
}

/* Dev-only desktop/mobile preview toggle — injected by main.js, never on a real host */

.dev-toggle {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 999;
  display: flex;
  gap: 0.5rem;
  font-family: var(--font-nav);
  font-size: 0.8rem;
}

.dev-toggle button {
  background: #000;
  color: #fff;
  border: none;
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  cursor: pointer;
  opacity: 0.85;
}

.dev-toggle button:hover {
  opacity: 1;
}

/* Dev-only colour variable editor (opened from the dev toggle's "Colors"
   button). Fixed px sizes so it looks the same in the mobile preview. */

.dev-colors {
  position: fixed;
  bottom: 64px;
  right: 24px;
  z-index: 999;
  width: 340px;
  max-width: calc(100vw - 48px);
  padding: 14px 16px;
  border-radius: 12px;
  background: #000;
  color: #fff;
  font: 13px/1.3 var(--font-nav);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}

.dev-colors[hidden] {
  display: none;
}

.dev-colors-row {
  display: grid;
  grid-template-columns: 96px 36px 1fr;
  grid-template-areas:
    "name picker alpha"
    ".    value  value";
  align-items: center;
  column-gap: 10px;
  margin-bottom: 10px;
}

.dev-colors-name {
  grid-area: name;
  font-family: monospace;
}

.dev-colors-row input[type="color"] {
  grid-area: picker;
  width: 36px;
  height: 26px;
  padding: 0;
  border: 1px solid #555;
  border-radius: 4px;
  background: none;
  cursor: pointer;
}

.dev-colors-row input[type="range"] {
  grid-area: alpha;
  width: 100%;
}

.dev-colors-value {
  grid-area: value;
  font-family: monospace;
  font-size: 11px;
  opacity: 0.7;
}

.dev-colors-actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}

.dev-colors-actions button {
  flex: 1;
  padding: 6px 10px;
  border: 1px solid #555;
  border-radius: 999px;
  background: #222;
  color: #fff;
  font: inherit;
  cursor: pointer;
}

.dev-colors-actions button:hover {
  background: #333;
}
