/* ==========================================================================
   jozefsimo.com — single stylesheet
   Monochrome, typography-first. Mobile-first breakpoints: 375 / 768 / 1024 / 1440.
   ========================================================================== */

/* ---------- Reset ---------- */

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

/* ---------- Tokens ---------- */

/* ---- The two colour palettes ----------------------------------------------
   Every colour on the site lives here and nowhere else. Edit a value in one of
   these two lists and the whole site follows.

   Names, loudest to quietest:
     --page        the background
     --text        main text and headings
     --text-soft   a hair quieter than main (button hover)
     --text-mid    secondary text
     --text-quiet  tertiary text, captions
     --text-faint  the quietest text, and dashed placeholder borders
     --card        card and panel surfaces
     --line        hairline borders
     --logo-tile   the almost-invisible tile behind each logo

   The light values are not simple opposites of the dark ones. Each grey was
   picked to hit the same contrast against white that its dark twin hits against
   black, so text reads with equal weight in both modes. */
:root {
  /* Dark palette (the original) */
  --dark-page: #000000;
  --dark-text: #ffffff;
  --dark-text-soft: #f5f5f7;
  --dark-text-mid: #a1a1a6;   /* 8.2:1 on black */
  --dark-text-quiet: #86868b; /* 5.6:1 on black */
  --dark-text-faint: #6e6e73; /* 4.1:1 on black — fine at 24px (large-text AA is
                                 3:1), short of the 4.5:1 needed at 14px */
  --dark-card: #1d1d1f;
  --dark-line: #2d2d2f;
  --dark-logo-tile: rgba(255, 255, 255, 0.01);

  /* Light palette */
  --light-page: #ffffff;
  --light-text: #000000;
  --light-text-soft: #1a1a1c;
  --light-text-mid: #4a4a4f;   /* 8.5:1 on white */
  --light-text-quiet: #65656a; /* 5.6:1 on white */
  --light-text-faint: #7d7d82; /* 4.1:1 on white — same caveat as its dark twin */
  --light-card: #f2f2f4;
  --light-line: #dcdce0;
  --light-logo-tile: rgba(0, 0, 0, 0.005); /* black on white reads stronger than
                                              white on black, so this is lower
                                              than its dark twin to look equally
                                              invisible */

  /* Which palette is live. Dark is the default. The three blocks below are the
     only places this switches: the system setting, then the test toggle. */
  --page: var(--dark-page);
  --text: var(--dark-text);
  --text-soft: var(--dark-text-soft);
  --text-mid: var(--dark-text-mid);
  --text-quiet: var(--dark-text-quiet);
  --text-faint: var(--dark-text-faint);
  --card: var(--dark-card);
  --line: var(--dark-line);
  --logo-tile: var(--dark-logo-tile);
  color-scheme: dark;

  --font-body: "SF Pro Text", -apple-system, BlinkMacSystemFont,
               "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* One family everywhere. --font-display is kept as an alias so the existing
     rules keep working; to reintroduce a display face, point it at one here. */
  --font-display: var(--font-body);

  /* Four type roles. Phone values here; tablet and desktop step
     --text-body and --text-display below. 14 / 16 / 24 / 32 sit on
     the 8px scale (14 is the small step). */
  --text-mark: 0.875rem;    /* 14px – BEFORE / AFTER only */
  --text-meta: 1rem;        /* 16px – labels, captions, Approach */
  --text-body: 1rem;        /* 16px phone; 20 tablet; 24 desktop */
  --text-display: 1.5rem;   /* 24px phone; 28 tablet; 32 desktop */

  --leading-tight: 1.2;
  --leading-normal: 1.5;
  --leading-relaxed: 1.35;

  /* Single weight everywhere — hierarchy comes from size, color, and family */
  --font-regular: 400;

  /* Page side padding — steps up at the tablet and desktop breakpoints below */
  --gutter: 1.5rem;        /* 24px, phones */

  --width-prose: 45rem;    /* ~720px */
  --width-wide: 112.5rem;  /* 1800px */
  --space-section: min(6rem + 4vw, 10rem); /* 96px, growing to 160px */
  --space-media: 5rem;                     /* 80px – text to a large image inside a section */
  --radius: 16px;
}

/* Follow the visitor's system setting, unless the toggle has forced dark. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --page: var(--light-page);
    --text: var(--light-text);
    --text-soft: var(--light-text-soft);
    --text-mid: var(--light-text-mid);
    --text-quiet: var(--light-text-quiet);
    --text-faint: var(--light-text-faint);
    --card: var(--light-card);
    --line: var(--light-line);
    --logo-tile: var(--light-logo-tile);
    color-scheme: light;
  }
}

/* The toggle wins over the system setting, in both directions. */
:root[data-theme="light"] {
  --page: var(--light-page);
  --text: var(--light-text);
  --text-soft: var(--light-text-soft);
  --text-mid: var(--light-text-mid);
  --text-quiet: var(--light-text-quiet);
  --text-faint: var(--light-text-faint);
  --card: var(--light-card);
  --line: var(--light-line);
  --logo-tile: var(--light-logo-tile);
  color-scheme: light;
}

:root[data-theme="dark"] {
  --page: var(--dark-page);
  --text: var(--dark-text);
  --text-soft: var(--dark-text-soft);
  --text-mid: var(--dark-text-mid);
  --text-quiet: var(--dark-text-quiet);
  --text-faint: var(--dark-text-faint);
  --card: var(--dark-card);
  --line: var(--dark-line);
  --logo-tile: var(--dark-logo-tile);
  color-scheme: dark;
}

/* Tablet portrait and up: body and display take one step up. */
@media (min-width: 700px) {
  :root {
    --text-body: 1.25rem;    /* 20px */
    --text-display: 1.75rem; /* 28px */
  }
}

/* ---------- Base ---------- */

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

body {
  background: var(--page);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: var(--leading-relaxed);
  font-weight: var(--font-regular);
  -webkit-font-smoothing: antialiased;
}

/* Headings sit tighter than running text (1.2 against the body's 1.35): the eye
   has a shorter distance to travel back on a short, large line, so body spacing
   reads as loose. This is the --leading-tight value the type scale always
   specified for headings.

   Two deliberate exceptions further down keep the looser body spacing, both
   because they sit directly beside or beneath body text and have to match it:
   .tiers-head h2.eyebrow and .eyebrow (which uses --leading-normal). */
h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: var(--font-regular);
  line-height: var(--leading-tight);
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

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

::selection {
  background: var(--text);
  color: var(--page);
}

:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------- Utilities ---------- */

/* Tablet: 48px sides */
@media (min-width: 48em) {
  :root {
    --gutter: 3rem;
  }
}

/* Desktop: 64px sides, full type size */
@media (min-width: 64em) {
  :root {
    --gutter: 4rem;
    --text-body: 1.5rem;  /* 24px */
    --text-display: 2rem; /* 32px */
  }
}

/* "Skip to content" jumps here. Without this the top of the content would land
   underneath the sticky bar. */
#main {
  scroll-margin-top: 4.5rem;
}

.container--wide {
  width: 100%;
  max-width: var(--width-wide);
  padding-inline: var(--gutter);
  margin-inline: auto;
}

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

/* z-index sits above the sticky bar (20) so the link is visible when focused. */
.skip-link {
  position: absolute;
  left: 1rem;
  top: -100%;
  z-index: 30;
  background: var(--text);
  color: var(--page);
  padding: 0.75rem 1.25rem;
  border-radius: 8px;
  text-decoration: none;
  font-weight: var(--font-regular);
}

.skip-link:focus {
  top: 1rem;
}

/* ---------- Theme toggle — TEST CONTROL, case study and 404 only ----------
   Home follows the visitor's system setting with no override. This button
   lives in the sticky bar on the case study and 404 so light mode can still
   be checked there. To take it out: delete this block, the button in those
   two HTML files, the inline localStorage script in their <head>, and the
   toggle section in js/main.js. System switching does not depend on any of it.

   It shows the mode it will switch you TO: a sun while you are in dark mode,
   a moon while you are in light one. */
.theme-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: var(--page);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 980px;
  cursor: pointer;
  transition: border-color 180ms ease, transform 180ms ease;
}

/* The circle is 34px, which is under the 44px minimum a finger needs. This
   invisible square enlarges the tap area to 44px without changing how the
   button looks. */
.theme-toggle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.theme-toggle:hover {
  border-color: var(--text-faint);
  transform: scale(1.06);
}

.theme-toggle:active {
  transform: scale(0.96);
}

.theme-toggle svg {
  width: 16px;
  height: 16px;
  display: block;
}

/* Which of the two icons shows. Mirrors the palette switch above. */
.theme-toggle .icon-moon {
  display: none;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .theme-toggle .icon-sun {
    display: none;
  }

  :root:not([data-theme="dark"]) .theme-toggle .icon-moon {
    display: block;
  }
}

:root[data-theme="light"] .theme-toggle .icon-sun {
  display: none;
}

:root[data-theme="light"] .theme-toggle .icon-moon {
  display: block;
}

:root[data-theme="dark"] .theme-toggle .icon-sun {
  display: block;
}

:root[data-theme="dark"] .theme-toggle .icon-moon {
  display: none;
}

.eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-meta);
  font-weight: var(--font-regular);
  line-height: var(--leading-normal);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quiet);
}

/* ---------- Sections & rhythm ---------- */

main > section {
  margin-block: var(--space-section);
}

main > section:first-child {
  margin-top: 0;
}

/* The logo bar sits tighter to the intro than the rest of the page rhythm, so the
   two read as one opening block and the logos clear the fold on a laptop.
   ~77px here instead of the usual ~154px.

   Both rules are needed: side-by-side section margins collapse to the larger of
   the two, so leaving the hero's own margin-bottom in place would simply win and
   nothing would change. The hero's is zeroed and the whole gap lives on the
   logo bar. To loosen it again, raise the min() below. */
.hero {
  margin-bottom: 0;
}

main > .proof-section {
  margin-top: min(3rem + 2vw, 5rem);
}

section > .eyebrow,
section h2.eyebrow {
  margin-bottom: 2rem;
}

/* ---------- Links & buttons ---------- */

a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--text-faint);
  text-underline-offset: 0.2em;
  transition: text-decoration-color 180ms ease, color 180ms ease;
}

a:hover {
  text-decoration-color: var(--text);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.625rem 1.625rem;
  border-radius: 980px;
  font-size: var(--text-body);
  font-weight: var(--font-regular);
  text-decoration: none;
  transition: background-color 180ms ease, border-color 180ms ease,
              color 180ms ease, transform 180ms ease;
}

/* Phones: tighter inner padding so the pills aren't oversized next to 16px text.
   min-height stays at 44px, so the tap target is unchanged — only the width
   shrinks (26px of side padding down to 18px). */
@media (max-width: 47.9375em) {
  .btn {
    padding: 0.5rem 1.125rem;
  }
}

/* Grows a little on hover — the only motion on a button, since white-on-white
   leaves almost no room for a colour change to read. */
.btn:hover {
  transform: scale(1.03);
}

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

.btn--primary {
  background: var(--text);
  color: var(--page);
}

.btn--primary:hover {
  background: var(--text-soft);
}

.btn--primary:active {
  background: var(--text-mid);
}

/* Border uses the secondary-text grey, not the hairline grey — at 1.5:1 the
   hairline was too faint to read as a button edge. */
.btn--secondary {
  border: 1px solid var(--text-faint);
  color: var(--text);
}

.btn--secondary:hover {
  border-color: var(--text);
}

.btn--secondary:active {
  background: var(--card);
}

/* ---------- Sticky top bar (case study, 404) ----------
   Back link on the left, colour-mode toggle on the right. It stays with you as
   you scroll. The home page deliberately has no bar.

   position: sticky, not fixed — the bar keeps its place in the page, so nothing
   below it needs padding to compensate. */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  padding-block: 0.625rem;
}

/* The see-through fade behind the bar. Page colour at the top, dissolving to
   nothing below the controls, so text scrolling underneath is never cut off by
   a hard edge. It reads the live --page value, so it is black in dark mode and
   white in light with no second rule and no hardcoded colour.

   Two knobs, if you want to tune how see-through it is:
     opacity  — how solid the whole thing is at its strongest. 1 would hide
                whatever is behind the top of the bar completely.
     the 30%  — how far down the fade starts. Higher holds the colour longer
                before it begins dissolving.

   Two details that matter: it runs taller than the bar (150%) so the fade
   finishes below the buttons rather than at their edge, and pointer-events:none
   keeps it from swallowing taps meant for the page underneath. */
.site-nav::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 150%;
  background: linear-gradient(to bottom, var(--page) 30%, transparent);
  opacity: 0.95;
  pointer-events: none;
  z-index: -1;
}

.site-nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* padding-inline gives the link a finger-sized target; the matching negative
   margin pulls the text back so it still lines up with the page edge. */
.site-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 0.75rem;
  margin-inline: -0.75rem;
  color: var(--text-mid);
  text-decoration: none;
  font-size: var(--text-meta);
  transition: color 180ms ease;
}

.site-nav a:hover {
  color: var(--text);
}

.site-nav .theme-toggle {
  flex: none;
}

/* Short screens — a phone held sideways — get a tighter bar so it eats less of
   what little height there is. */
@media (max-height: 32em) {
  .site-nav {
    padding-block: 0.25rem;
  }
}

/* ---------- Hero (home) ----------
   Split layout: name + location quiet on the left,
   bio + CTA carrying the weight on the right. */

.hero {
  padding-top: min(4rem + 10vw, 11rem);
  padding-bottom: clamp(1.5rem, 0.75rem + 2vw, 3rem);
}

/* Two-column split shared by the hero and the footer:
   quiet identity on the left, content on the right. */
.split-grid {
  display: grid;
  gap: 2.5rem;
  align-items: start;
}

@media (min-width: 700px) {
  .split-grid {
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
  }
}

/* font-size is load-bearing: without it the browser's own h1 size (48px) wins. */
.hero h1 {
  font-size: var(--text-body);
}

.hero-location {
  color: var(--text-faint);
}

.hero-intro {
  margin-bottom: 2rem;
}

/* "Let's talk" + LinkedIn side by side, wrapping onto two lines if the space runs out */
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
}

/* ---------- Proof bar ---------- */

/* Two columns on phones, one row of four from tablet-portrait up.
   700px (not 768 / 48em) so iPad Mini portrait (744) and anything just
   under a full iPad still get four across. */
.proof-bar {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
  list-style: none;
}

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

/* Ratio rather than a fixed height, so tiles shrink with their column instead of
   turning into tall narrow boxes on a phone. */
.logo-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 2 / 1;
  padding-inline: 1rem;
  background: var(--logo-tile);
  border-radius: var(--radius);
  color: var(--text);
}

/* Each logo links to that company's site. The link box still spans the whole
   tile — that is what lets the percentage widths on the image below measure
   against the tile's inner box — but only the artwork responds to a click.

   pointer-events is how: the link ignores the mouse entirely and the image takes
   it back, so the area around the logo is inert. Clicks on the image still
   travel up to the link and follow it, hovering the image still triggers
   .logo-link:hover, and keyboard focus is unaffected, because none of those
   depend on the link itself being a pointer target. */
.logo-link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  text-decoration: none;
  pointer-events: none;
  transition: opacity 180ms ease;
}

/* 180ms ease is the timing every other hover on the site uses. */
.logo-link:hover,
.logo-link:focus-visible {
  opacity: 0.92;
}

/* Logos scale with their tile: 60% of the tile's inner width, height follows the
   artwork. Full white — the filter forces any coloured wordmark to pure white,
   so an SVG can be dropped in as supplied. */
/* The image is the clickable part (see .logo-link above).

   padding-block is there for fingers, not for looks. These wordmarks are wide
   and thin — No Boring Tech is 244 × 30 — so on a phone the artwork is only
   about 16px tall, far under the 44px a fingertip needs. The padding grows the
   clickable box vertically without changing the size of the logo itself, since
   the width is set separately and the height follows the artwork. */
.logo-slot img {
  width: 60%;
  height: auto;
  padding-block: 0.75rem;
  object-fit: contain;
  pointer-events: auto;
  filter: grayscale(1) brightness(10);
}

/* Light mode: the same filter, then flipped, so the white artwork comes out
   black on the white page. Any new logo dropped in gets this for free. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .logo-slot img {
    filter: grayscale(1) brightness(10) invert(1);
  }
}

:root[data-theme="light"] .logo-slot img {
  filter: grayscale(1) brightness(10) invert(1);
}

:root[data-theme="dark"] .logo-slot img {
  filter: grayscale(1) brightness(10);
}

/* Per-logo tuning, because these wordmarks have very different proportions.
   Be My Eyes is nearly square, so 60% of the tile makes it much taller than the
   wide wordmarks beside it. No Boring Tech is the longest and thinnest, so it
   needs more width to read at the same weight. */
.logo-slot img[src*="be-my-eyes"] {
  width: 45%;
}

.logo-slot img[src*="no-boring-tech"] {
  width: 70%;
}

/* Anything below desktop has narrow tiles: phones because there are only two
   columns of a small screen, tablets because four columns divide 672px. Both end
   up ~130-220px wide, where the desktop percentages leave the wide wordmarks
   8-10px tall. Thinner padding and near-full widths buy back about 60%. */
@media (max-width: 63.9375em) {
  .logo-slot {
    padding-inline: 0.5rem;
  }

  .logo-slot img {
    width: 85%;
  }

  .logo-slot img[src*="no-boring-tech"] {
    width: 90%;
  }

  .logo-slot img[src*="be-my-eyes"] {
    width: 60%;
  }
}

/* ---------- Case study cards (home) ---------- */

/* One column on phones, two from tablet-portrait up. Same 700px floor as
   the logo bar, so the two sections switch together. */
.case-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (min-width: 700px) {
  .case-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 2rem;
  }
}

.case-card {
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color 180ms ease, transform 180ms ease;
}

/* Grows on hover like .btn does, but more subtly (1.012 vs the button's 1.03)
   — a card this large reads a 3% grow as jumpy in a way a small pill doesn't.
   Same 180ms ease as the button. Press-in on click is scaled down to match
   (0.995 vs the button's 0.99). */
.case-card:hover {
  border-color: var(--text-quiet);
  transform: scale(1.012);
}

.case-card:active {
  transform: scale(0.995);
}

/* Shaped to the screenshot itself (3024 x 1794) rather than a round 16:9, which
   is a whisker wider and would have cropped into the top of the sidebar. The
   grey fill stays underneath as the colour shown while the picture loads.

   The hairline along the bottom is doing real work: the screenshot is
   white-backed and so is the card, so without it the picture bled straight into
   the title beneath and the card had no internal edge. */
.case-card-media {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 504 / 299;
  object-fit: cover;
  object-position: center center;
  background: var(--card);
  border-bottom: 1px solid var(--line);
}

/* Empty slot: dashed outline, no image block, text centred in the card. It is a
   plain div, not a link, so there is nothing to click. */
.case-card--empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 14rem;
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  border-style: dashed;
  border-color: var(--line);
  text-align: center;
}

.case-card--empty:hover {
  border-color: var(--line);
}

.case-card-empty-title {
  color: var(--text-quiet);
}

.case-card-empty-note {
  color: var(--text-faint);
}

.case-card-body {
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
}

.case-card-body h3 {
  font-size: var(--text-body);
  font-weight: var(--font-regular);
  margin-bottom: 0.75rem;
}

.case-card-body p {
  color: var(--text-faint);
  max-width: 55ch;
  margin-bottom: 1.5rem;
}

.case-card-link {
  color: var(--text-faint);
  font-weight: var(--font-regular);
  transition: color 180ms ease;
}

.case-card:hover .case-card-link {
  color: var(--text);
}

/* ---------- Ways of working ---------- */

/* Section header sits on the same two-column grid as the .tier rows below */
.tiers-head {
  display: grid;
  gap: 1.25rem;
  margin-bottom: 3rem;
}

/* Flattened to match body text — the grey step is the only thing marking it
   as a label. The .eyebrow style itself is untouched for the case study page.
   The relaxed line spacing below is deliberate and must stay: this heading sits
   in the left column with the .tiers-intro paragraph beside it in the right, and
   the two have to sit on the same baseline. It is the one heading on the site
   that does not use the tighter 1.2. */
.tiers-head h2.eyebrow,
.details-head h2.eyebrow {
  margin-bottom: 0;
  font-size: var(--text-body);
  line-height: var(--leading-relaxed);
  letter-spacing: normal;
  text-transform: none;
  color: var(--text);
}

@media (min-width: 700px) {
  .tiers-head {
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
    align-items: start;
  }
}

/* Same as body text now — kept as a named hook in case it needs to grow again */
.tiers-intro {
  font-size: var(--text-body);
}

.tier {
  padding-block: 6rem;
  display: grid;
  gap: 1.25rem;
  align-content: start;
}

@media (min-width: 700px) {
  .tier {
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
  }
}

/* Title and duration sit tight together — no gap beyond the line height */
.tier-head h3 {
  font-size: var(--text-body);
  font-weight: var(--font-regular);
  margin-bottom: 0;
}

/* Timebox label — same type style as the tier title, in grey. It is a paragraph,
   not a heading, so it needs the tight line spacing set explicitly: it sits
   directly under the h3 with no gap between them, and if the two used different
   line spacing the pair would come apart. */
.tier-timebox {
  font-family: var(--font-display);
  font-size: var(--text-body);
  font-weight: var(--font-regular);
  line-height: var(--leading-tight);
  color: var(--text-faint);
}

.tier-body {
  display: grid;
  gap: 1.25rem;
  justify-items: start;
}

/* The button needs more air than the paragraph rhythm gives it. The grid gap
   above puts every child 20px apart, which left the button sitting closer to the
   text than one line of that text is tall — so it read as another paragraph
   rather than something to click.

   This adds to the grid gap instead of replacing it, so the paragraph rhythm is
   untouched: 20px + 20px = 40px from tablet up, double the paragraph spacing.
   On phones both the text and the button are smaller, so it takes less to
   separate them: 20px + 8px = 28px. Raise or lower these two numbers to tune. */
.tier-body .btn {
  margin-top: 1.25rem;
}

@media (max-width: 699px) {
  .tier-body .btn {
    margin-top: 0.5rem;
  }
}

/* Service descriptions are full white, same as any body paragraph.
   .tier-punch is therefore the same colour now — it stays as the hook if the
   punch lines ever need to stand out again. */
.tier-punch {
  color: var(--text);
}

/* ---------- Footer ---------- */

.site-footer {
  margin-top: var(--space-section);
  padding-block: 2.5rem;
}

/* Same two-column split as tablet/desktop: year left, links right.
   The hero still stacks on phones; only the footer stays a pair. */
.site-footer .split-grid {
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}

.footer-links {
  display: grid;
  justify-items: start;
  list-style: none;
}

.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  color: var(--text);
  text-decoration: none;
  transition: color 180ms ease;
}

.footer-links a:hover {
  color: var(--text-mid);
}

.footer-copy {
  color: var(--text);
}

/* ==========================================================================
   Case study page
   ========================================================================== */

.cs-hero {
  padding-top: clamp(3rem, 2rem + 6vw, 6rem);
}

/* Client logo in place of the old "Emirates NBD" text label. Kept small — about
   the height of the label it replaced — so the headline stays the loudest thing
   on the page. The alt text carries the client name for screen readers and for
   anyone browsing with images off. */
.cs-client {
  margin-bottom: 1.5rem;
}

/* 36px tall renders the wordmark 132px wide. Sized a little above the 32px
   headline below it so the client reads first, but kept narrow enough that the
   headline still carries the hero. One value covers every screen — the headline
   is 32px at all sizes, and 132px fits a 390px phone. */
.cs-client img {
  height: 36px;
  width: auto;
}

/* The logo file is white artwork, so it needs flipping on a white page. Same
   approach as the logo bar on the home page. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .cs-client img {
    filter: invert(1);
  }
}

:root[data-theme="light"] .cs-client img {
  filter: invert(1);
}

:root[data-theme="dark"] .cs-client img {
  filter: none;
}

/* Line spacing comes from the shared heading rule (1.2) — no override needed. */
.cs-hero h1 {
  max-width: 16em;
  font-size: var(--text-display);
  margin-bottom: 1.75rem;
}

.cs-subtitle {
  color: var(--text-mid);
}

/* Lead image below the hero text. Unlike the placeholders inside .prose, this one
   is not held to the 720px reading width — it spans the full container. The 16:9
   shape and card background come from .media; only the spacing is set here, at
   3rem to match the figures further down the page. */
/* Lead image: desktop shot full width, phone laid over its bottom-right corner.
   The phone is positioned in percentages of the picture, so the whole
   composition scales together and never drifts as the page gets wider. */
.cs-hero-media {
  position: relative;
  margin-top: 3rem;
  /* Room for the part of the phone that hangs below the desktop shot, so it
     isn't clipped and the Details section below doesn't ride up into it. A
     percentage margin is measured against the width, which is what the overhang
     is measured against too, so the two stay in step at every screen size. */
  margin-bottom: 7%;
}

/* Held to the frame's original 3024:1800 shape and cropped to fill it
   (object-fit: cover, centred) rather than switching to the new photo's own
   proportions — so the hero keeps the same footprint no matter what image is
   dropped in here next. */
.cs-hero-desktop {
  display: block;
  width: 100%;
  aspect-ratio: 3024 / 1800;
  object-fit: cover;
  object-position: center center;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Sized to what a phone actually is next to a laptop, not to what fits neatly:
   an iPhone is about 71mm across and a 14-inch MacBook screen about 302mm, so
   the phone is 23% of the desktop shot's width. It hangs below the bottom edge
   by roughly a tenth of the picture's height, which is what makes it read as
   standing in front of the laptop rather than pasted onto it. */
.cs-hero-phone {
  position: absolute;
  right: 5%;
  bottom: -10%;
  width: 23%;
  /* The bezel: a dark rounded surround with the screenshot inset in it. Kept to
     a plain outline — no notch, no side buttons — because nothing else on this
     site is decorated, and a drawn-on iPhone would be the one ornament here. */
  padding: 0.4%;
  background: #1d1d1f;
  /* Corners are about a tenth of the phone's width, which is roughly where a
     real handset's are. Scaled off the viewport so they stay in proportion as
     the picture grows instead of looking square on a big screen. */
  border-radius: clamp(0.75rem, 2.6vw, 3.25rem);
  box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.25);
}

.cs-hero-phone img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: clamp(0.6rem, 2.3vw, 2.9rem);
}

/* ---------- Details block ---------- */

/* Half and half across the full page width, the same split "Ways of working"
   uses on the home page: the section title on the left, the four facts on the
   right. Below tablet it stacks, where two columns would starve both of them.
   The heading's type comes from the shared rule up in the Ways of working block
   — one definition, so the two sections can't drift apart. */
.details-head {
  display: grid;
  gap: 1.25rem;
}

@media (min-width: 700px) {
  .details-head {
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
    align-items: start;
  }
}

.details {
  border-top: 1px solid var(--line);
}

/* Label above its value, not beside it. In a half-width column there isn't room
   for both side by side without the longer values wrapping into towers. */
.details > div {
  display: grid;
  gap: 0.25rem;
  padding-block: 0.625rem;
  border-bottom: 1px solid var(--line);
}

.details dt {
  font-size: var(--text-meta);
  font-weight: var(--font-regular);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quiet);
}

/* The values are set at body size. Only the labels beside them stay small and
   uppercase, so the contrast between the two is what gives each row its shape. */
.details dd {
  color: var(--text);
}


/* ---------- At a glance ---------- */

/* Full strength like the prose below it — same running-sentence role, just
   sitting outside the .prose container. Width comes from the shared .prose
   class on the element itself (see index.html) rather than its own copy of
   the same max-width rule. */
.stats-intro {
  color: var(--text);
  margin-bottom: 3rem;
}

.stats {
  display: grid;
  gap: 1rem;
  list-style: none;
  margin-bottom: 2.5rem;
}

/* Tablet portrait: two columns so the 40+ / 600+ pair inside the users
   card stays readable. Three-across from landscape / 1024px. */
@media (min-width: 700px) {
  .stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 64em) {
  .stats {
    grid-template-columns: repeat(3, 1fr);
  }
}

.stat {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 1.25rem + 1vw, 2rem);
  display: grid;
  gap: 0.625rem;
  align-content: start;
}

/* Two figures side by side inside a single stat card. Stays two columns at every
   screen size, including phones, where the card is about 280px wide — the numbers
   are short enough to take it. Each half keeps the card's own 0.625rem rhythm
   between its number and its label. */
.stat-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.stat-pair > div {
  display: grid;
  gap: 0.625rem;
  align-content: start;
}

.stat-number {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: var(--font-regular);
  line-height: var(--leading-tight);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.stat-label {
  color: var(--text-soft);
  font-weight: var(--font-regular);
}

.stat-desc {
  color: var(--text-mid);
  font-size: var(--text-meta);
  line-height: var(--leading-normal);
}

/* ---------- Prose sections ---------- */

.prose {
  max-width: var(--width-prose);
}

.prose > * + * {
  margin-top: 1.5rem;
}

/* Running body copy sits at full strength, matching the home page. The greys on
   this page are reserved for genuinely secondary things — captions, labels, the
   "before" column of the results table — so grey means "supporting", not just
   "smaller". */
.prose p {
  color: var(--text);
}

/* Now that body copy is full strength, <strong> can no longer make a phrase
   brighter — the two would be identical. It keeps its own rule anyway: without
   it, the browser's default bold would fire and break the site's single-weight
   rule. So this reads as "leave it looking like everything else". */
.prose strong {
  color: var(--text);
  font-weight: var(--font-regular);
}

/* ---------- Approach ---------- */

/* The Approach section used to sit in its own tinted card (.approach-block, now
   deleted). It is plain prose in the reading column, like The brief, so the two
   narrative sections match and the only tinted card left on the page is the one
   that earns it — the "before" half of the mobile comparison.

   Beneath that paragraph sits a second, alternative rendering of the same six
   steps as a full-width rail, so the two can be compared live. Deliberately not
   cards: the page is down to exactly one tinted card on purpose (see above), and
   six more would reverse that. What replaces the arrows is the hairline — one
   per column rather than one continuous line, so the gaps break it into six
   segments and the sequence reads as steps. It also needs no rework at any
   column count: three across becomes two rows of three segments, one across
   becomes a ruled list. */

.approach-rail {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  list-style: none;
  margin-top: var(--space-media);
}

/* Two, then three, then all six. The 90em step is the only new breakpoint on the
   page: below it each of six columns falls under ~200px and the sentences tower. */
@media (min-width: 48em) {
  .approach-rail {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 64em) {
  .approach-rail {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 90em) {
  .approach-rail {
    grid-template-columns: repeat(6, 1fr);
  }
}

.approach-step {
  border-top: 1px solid var(--line);
  padding-top: 1rem;
  display: grid;
  gap: 0.75rem;
  align-content: start;
}

/* Same label treatment as .details dt and the results table's column headers —
   the numbers mark position in the sequence, they aren't content. */
.approach-num {
  font-size: var(--text-meta);
  font-weight: var(--font-regular);
  letter-spacing: 0.12em;
  color: var(--text-quiet);
  font-variant-numeric: tabular-nums;
}

/* Meta, not body: at six across a column is about 250px, where
   body size would run eleven characters to the line. */
.approach-step p {
  font-size: var(--text-meta);
  line-height: var(--leading-normal);
  color: var(--text);
}

/* ---------- Before / after compare slider (reusable) ----------
   Drop the markup on any page inside a .container--wide and it works — see the
   commented example in the case study's "brief" section.

   How the reveal works: both pictures are full size and stacked on top of each
   other. Nothing is ever resized. The top one is clipped from the right at
   --pos, a single number the script writes as you drag. That is the whole
   mechanism. Resizing the top layer instead — the obvious approach — squashes
   the picture as the handle moves, which is the bug this avoids. */

/* 900px, stepping out past the 720px text column but never past the page
   gutters. It sits in its own full-width container rather than inside .prose,
   which is what lets `100%` mean "all the room the page has" and makes
   overflow impossible at any screen size. */
.compare-figure {
  width: min(56.25rem, 100%);
  margin-inline: auto;
}

.compare {
  --pos: 50%;  /* the script overwrites this; 50% is also the no-JavaScript view */
  position: relative;
  /* Matches the "after" screenshot exactly (3024 x 1794). The "before" one is a
     whisker squarer, so it loses about 15 pixels off its top and bottom — under
     1%, and nothing sits that close to its edges. If you swap in pictures of a
     different shape, change this pair of numbers to their width / height. */
  aspect-ratio: 504 / 299;
  border-radius: var(--radius);
  overflow: hidden;
  /* Same reason as the hairline on .shot — the screenshot is white-backed and
     the page behind it is near-white, so without an outline it has no edge. The
     halves sit inside the border, not under it, so it stays visible. */
  box-sizing: border-box;
  border: 1px solid var(--line);
}

.compare-pane {
  position: absolute;
  inset: 0;
}

.compare-pane--before {
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

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

/* Each label lives inside its own half, so it is clipped away with it. Drag the
   handle fully left and "Before" leaves with the picture it names. */
.compare-label {
  position: absolute;
  /* Bottom, not top: screenshots put their logo and header bar along the top
     edge, and a pill there sits right on the branding. */
  bottom: 1rem;
  font-size: var(--text-mark);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* Fixed to the dark palette in both modes, rather than following the theme.
     These pills sit on a screenshot, not on the page, and the screenshot is
     white-backed whichever mode the visitor is in — so a light pill vanished
     into it. Dark on light always reads here; light on light did not. */
  color: var(--dark-text);
  background: var(--dark-page);
  border-radius: 999px;
  padding: 0.1875rem 0.75rem;
}

.compare-label--before {
  left: 1rem;
}

.compare-label--after {
  right: 1rem;
}

/* The real control, stretched over the whole picture and invisible. Keeping a
   native <input type="range"> is what buys dragging, tap-to-jump, touch, arrow
   keys and screen-reader support for free. */
.compare-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  opacity: 0;
  cursor: ew-resize;
  touch-action: pan-y;  /* horizontal drags work the slider, vertical ones still
                           scroll the page — important on a phone */
}

/* Sits above the invisible control and ignores the mouse, so clicks pass
   straight through to it. */
.compare-divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 1px;
  margin-left: -0.5px;
  background: var(--text);
  pointer-events: none;
}

.compare-handle {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;   /* the site's minimum tap target */
  height: 44px;
  border: 1px solid var(--text);
  border-radius: 50%;
  background: var(--page);
  color: var(--text);
}

/* The viewBox is cropped to the two chevrons, so they fill this box rather than
   sitting as a small mark in the middle of a mostly empty square. */
.compare-handle svg {
  width: 22px;
  height: 14px;
}

/* The control itself is invisible, so its focus ring would be too. Show it on
   the handle instead, which is why the input sits before the divider in the
   HTML — the ~ selector can only look forwards. */
.compare-range:focus-visible ~ .compare-divider .compare-handle {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

/* ---------- Figures / image placeholders ---------- */

figure {
  margin-block: var(--space-media);
}

/* ---------- Live prototype teaser (Results) ---------- */

/* Same cover photo as the home page card and the hero above, blurred and
   tinted dark so it reads as "there's more here" rather than a legible
   screenshot. The tint is fixed dark in both light and dark mode - like the
   BEFORE/AFTER pills on the slider, it sits on a photo rather than the page,
   so it needs to hold its own contrast regardless of site theme.

   margin-top uses --space-media, same as the slider, the phone cards, and
   the Approach rail. */
.proto-teaser {
  position: relative;
  display: block;
  overflow: hidden;
  margin-top: var(--space-media);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
}

.proto-teaser-media {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center center;
  /* Lighter than a first pass (was 20px/0.45) - the photo should still read
     as a real screenshot underneath, not vanish behind the glass. Scaled up
     slightly so the blur doesn't reveal soft edges. */
  filter: blur(8px);
  transform: scale(1.04);
}

.proto-teaser-glass {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.25);
}

.proto-teaser-content {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  padding: 1.5rem;
  text-align: center;
}

.proto-teaser-title {
  color: var(--dark-text);
  font-size: var(--text-display);
  /* The tint is lighter now, so contrast against a busy image isn't
     guaranteed on its own - a soft shadow keeps the title readable without
     darkening the glass back down. */
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);
}

.proto-teaser-btn {
  gap: 0.5rem;
}

.proto-teaser-icon {
  width: 1.1em;
  height: 1.1em;
}

/* Two more product shots under the teaser, full page width, side by side.
   Each cell is a figure (image + caption). Zero the figure's default margin
   so it doesn't throw the grid off. */
.results-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  margin-top: 1.5rem;
}

.results-grid figure {
  margin: 0;
  min-width: 0;
}

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

.results-grid-img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center center;
  border-radius: var(--radius);
  border: 1px solid var(--line);
}

/* ---------- Real screenshots (as opposed to grey .media placeholders) -------- */

/* No aspect-ratio here on purpose: the picture keeps its own proportions, so
   shots of different heights are never squashed or cropped.

   The hairline outline earns its keep: these screenshots are white-backed and
   the page behind them is near-white, so without it they bleed into the page
   and lose their edges. `border-box` keeps the line from adding to the width,
   which would otherwise push a six-across row a few pixels over. */
.shot {
  display: block;
  width: 100%;
  height: auto;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Six phone shots in a row. It sits at the same 900px as the compare slider
   above it rather than inside the 720px reading column — six phones in 720px
   would be barely 100px each. */
/* Before and after side by side, across the full content width rather than the
   900px reading measure the slider above uses. The two states being level with
   each other is the point: one glance compares them. At 1800px that still
   leaves each phone around 250px, wide enough to read. */
.shot-set {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  /* Left at the default stretch on purpose: the two cards end level even when
     one card's points run to a different number of lines, which is what makes
     them read as a matched pair rather than two unrelated blocks. */
  /* Extra room from the slider above - its figcaption already gives it some
     breathing room, but sitting right under a full-width image the cards
     still read as cramped without an explicit push. */
  margin-top: var(--space-media);
}

/* Phones only: the two cards stack so each row of three shots can use the
   full width. Tablet portrait keeps the landscape pair. */
@media (max-width: 699px) {
  .shot-set {
    grid-template-columns: 1fr;
  }
}

.shot-card {
  margin: 0;
  padding: 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* The tint is the only styling difference between the two cards. It marks the
   "before" as the state being left behind without needing a colour that means
   "error" — this is the old product, not a warning. */
.shot-card--before {
  background: var(--card);
}

/* Pulled further from the phone screenshots above it than the generic
   figcaption top margin (0.875rem) gives elsewhere on the page — sitting this
   close under the images read as cramped once the tag moved down here. */
.shot-card figcaption {
  margin-top: 2rem;
}

.shot-tag {
  font-size: var(--text-mark);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quiet);
  margin-bottom: 1rem;
}

/* Before/After tag on the mobile shot cards, restyled to match the pill used
   on the slider above (.compare-label): same fixed-dark colours (this reads
   fine against the page background, same as it does against a screenshot),
   same rounded shape and padding. Not absolutely positioned like the
   slider's version, since it sits above the screenshots rather than on top
   of one. */
.shot-pill {
  display: inline-block;
  font-size: var(--text-mark);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dark-text);
  background: var(--dark-page);
  border-radius: 999px;
  padding: 0.1875rem 0.75rem;
  margin-bottom: 1rem;
}

.shot-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.75rem;
}

/* On a phone the three shots sit two-across (two on the first row, one on
   the second) instead of a single full-width stack. */
@media (max-width: 699px) {
  .shot-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* What changed, in three points, then the quieter naming line under it.

   Each point is marked with a cross or a tick instead of a dot. They are drawn
   as masked shapes rather than typed characters (× and ✓) for two reasons: a
   mask takes its colour from the text, so the marks follow light and dark mode
   by themselves, and a screen reader reads straight past them instead of
   announcing "multiplication sign" before every line.

   They are deliberately not red and green. The site has no colour in it at all,
   and the two shapes already say which is which — which also means the meaning
   survives for anyone who can't tell red from green. */
.shot-points {
  margin: 1.25rem 0 0;
  padding-left: 0;
  list-style: none;
  color: var(--text);
  font-size: var(--text-meta);
  line-height: var(--leading-normal);
}

/* Padding + a parked mark, not a grid: a grid treats a link inside the
   sentence as its own cell and drops it onto the next line. */
.shot-points li {
  position: relative;
  padding-left: 1.625rem;
}

.shot-points li + li {
  margin-top: 0.5rem;
}

.shot-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 1rem;
  height: 1rem;
  /* Nudged down so the mark sits on the middle of the first line of text
     rather than riding above it. */
  margin-top: 0.1875em;
  background: currentColor;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* A cross in a circle, and a tick in a circle. Drawn at the same weight as the
   chevrons on the compare handle so they belong to the same set. */
.shot-points--bad li::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M9 9l6 6M15 9l-6 6'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M9 9l6 6M15 9l-6 6'/%3E%3C/svg%3E");
}

.shot-points--good li::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M8 12.4l2.6 2.6L16 9.6'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M8 12.4l2.6 2.6L16 9.6'/%3E%3C/svg%3E");
}

/* Hairline-separated Engineering handover block, under the three points:
   small quiet label, full-size text below it. */
.shot-handover {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

.shot-handover-text {
  margin: 0.375rem 0 0;
}

.shot-card--before .shot-handover-text {
  color: var(--text-mid);
}

.shot-card--after .shot-handover-text {
  color: var(--text);
}

.shot-note {
  margin-top: 0.875rem;
  color: var(--text-quiet);
  font-size: var(--text-meta);
}


figcaption {
  color: var(--text-quiet);
  font-size: var(--text-meta);
  margin-top: 0.875rem;
}

/* ---------- Named stakeholder (in "The brief") ---------- */

/* A round photo followed by the name, linked, sitting inline in a paragraph.
   Every size here is in em, so the whole group scales with the text on its own —
   it follows --text-body — and needs no separate mobile rule.
   Held on one line so the photo can never be left stranded at the end of a line
   with the name wrapping to the next. */
.stakeholder {
  white-space: nowrap;
}

/* Exactly 1em tall, so the circle matches the height of the text beside it.

   Two separate controls decide where it sits, and the difference matters:

   `vertical-align` is part of layout — push the circle too high with it and the
   browser grows the line to fit, spreading the paragraph's lines apart. It is
   parked at -0.15em, low enough that the circle always fits inside the existing
   1.35 line height with room to spare.

   `top` (with position: relative) is purely visual — it slides the circle up on
   screen while layout still measures it at the parked position, so the line
   spacing physically cannot change no matter what value is used. All of the
   raising is done here. Adjust `top` freely; leave `vertical-align` alone. */
.stakeholder-avatar {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: 0.25em;
  vertical-align: -0.15em;
  position: relative;
  top: -0.09em;
  border-radius: 50%;
  object-fit: cover;  /* square source, so nothing is actually cropped */
  background: var(--card);  /* fills the circle for the moment before the photo loads */
}

/* Her name is the link to her LinkedIn profile — no separate icon.

   At rest it deliberately does NOT stand out: `inherit` takes whatever colour the
   paragraph is using, so the name never reads as a brighter word dropped into the
   sentence. Only the quiet underline marks it as a link.

   Since body copy went to full strength, `inherit` and the hover colour resolve to
   the same value, so hovering now changes only the underline, not the text. The
   colour rule is kept rather than deleted: it is what guarantees the name tracks
   the paragraph if that colour is ever dialled back down again.

   The photo sits outside the link on purpose, so the underline stops at the name
   instead of running under the circle. */
.stakeholder a {
  color: inherit;
}

.stakeholder a:hover {
  color: var(--text);
  text-decoration-color: var(--text);
}

/* ---------- Closing CTA (case study) ----------
   One invitation line + one primary button after the story ends. Stays in the
   720px reading column (via .prose on the container). No card, no eyebrow, no
   second headline — the section spacing is the only frame. The gap between the
   line and the button comes from the shared .prose > * + * rule. The .cs-cta
   class is a hook for later if the invitation ever needs its own tweak. */

/* ---------- Placeholders (content pending) ---------- */

.ph-inline {
  border: 1px dashed var(--text-faint);
  border-radius: 6px;
  padding: 0.05em 0.4em;
  color: var(--text-mid);
}

.ph-quote {
  border: 1px dashed var(--text-faint);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 1.25rem + 1.5vw, 2.25rem);
  margin-top: 2.5rem;
}

.ph-quote p {
  font-family: var(--font-display);
  font-size: var(--text-body);
  color: var(--text-mid);
  margin-bottom: 1rem;
}

.ph-quote footer {
  color: var(--text-quiet);
  font-size: var(--text-meta);
}

/* ---------- 404 ---------- */

.error-page {
  min-height: 60vh;
  display: grid;
  align-content: center;
  gap: 1.5rem;
  justify-items: start;
}

.error-page h1 {
  font-size: var(--text-display);
}

.error-page p {
  color: var(--text-mid);
}

/* ---------- Scroll reveal (class added by js/main.js only) ---------- */

.reveal {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 500ms ease-out, transform 500ms ease-out;
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  a,
  .btn,
  .case-card,
  .case-card-link,
  .logo-link,
  .site-nav a,
  .footer-links a,
  .theme-toggle {
    transition: none;
  }

  .btn:hover,
  .btn:active,
  .case-card:hover,
  .case-card:active,
  .theme-toggle:hover,
  .theme-toggle:active {
    transform: none;
  }
}

/* ---------- Print ----------
   This site is not built to be printed, but a visitor reading in dark mode who
   hits Cmd-P (or "Save as PDF") would otherwise get a solid black page. Rather
   than restating every colour by hand, this switches to the light palette from
   the top of the file, so there is only ever one set of colours to maintain.

   The [data-theme] selectors are repeated here on purpose: the toggle's rules
   are more specific than a plain :root, so without them a visitor who had
   forced dark mode would still print black. */

@media print {
  :root,
  :root[data-theme="dark"],
  :root[data-theme="light"] {
    --page: var(--light-page);
    --text: var(--light-text);
    --text-soft: var(--light-text-soft);
    --text-mid: var(--light-text-mid);
    --text-quiet: var(--light-text-quiet);
    --text-faint: var(--light-text-faint);
    --card: var(--light-card);
    --line: var(--light-line);
    --logo-tile: var(--light-logo-tile);
    color-scheme: light;
  }

  body {
    font-size: 11pt;
  }

  /* Every logo on the site is white artwork, so on white paper it prints blank. */
  .logo-slot img,
  :root[data-theme="dark"] .logo-slot img {
    filter: grayscale(1) brightness(10) invert(1);
  }

  .cs-client img,
  :root[data-theme="dark"] .cs-client img {
    filter: invert(1);
  }

  /* Screen furniture that means nothing on paper. */
  .theme-toggle,
  .site-nav,
  .skip-link,
  .site-footer {
    display: none;
  }

  main > section {
    margin-block: 2.5rem;
  }

  /* Sections fade in as you scroll; on paper they must be fully visible. */
  .reveal {
    opacity: 1;
    transform: none;
  }
}
