/* =============================================================
   Kestrel Advisory — demo build
   -------------------------------------------------------------
   A port of the Coachen Framer template (Salim / Webestica) onto a
   hand-coded token system. Measured off the live template at
   coachen-wbs.framer.website rather than eyeballed, in the same way
   the Ellerby build was measured off Calibre.

   WHAT MAKES THIS TEMPLATE LOOK LIKE ITSELF, in order of how easy
   each is to lose:

   1. THE SERIF ACCENT WORD. Every headline is Satoshi 500 except for
      one word, which swaps to Instrument Serif 400 in the accent
      colour — and is set ONE STEP LARGER than the words around it
      (84px against 82, 74 against 72). The size bump is the part
      people drop, and without it the serif word reads as a mistake
      rather than emphasis. Its tracking is nearly neutral (-1.1px)
      against the sans's very tight -3px, because a serif at -3px
      collides with itself.

   2. THE INK IS NOT BLACK. #2d2920 is a warm near-black with the
      cream's own hue in it. Against #f4eedf, true #000 reads as a
      hole punched in the page. Same reason the greys are warm.

   3. THE 200px SECTION RHYTHM. This is an editorial layout and the
      whitespace is doing the "expensive advisory" work. Tightening
      it to a normal 96px makes the page look like a cheaper site
      immediately, whatever else is right.

   4. THE CREAM IS A FAMILY, NOT A COLOUR. Three steps — page, card,
      well — carry almost all of the page's depth, because there are
      no borders anywhere. Cards sit on the page by being a shade
      deeper than it, not by being outlined.

   The animation vocabulary is deliberately ONE primitive; see
   script.js. Don't add a second easing curve.
   ============================================================= */

:root {
  /* ---- Ink. Warm near-black; see note 2 above. ---- */
  --ink:        #2d2920;
  --ink-deep:   #201c15;   /* dark bands sit a step below the ink */
  --muted:      #555350;   /* body copy on cream */
  --muted-soft: #7d7871;   /* labels, captions, table sub-rows */

  /* ---- Cream family. Page, card, well. See note 4. ---- */
  --cream:      #f4eedf;
  --cream-2:    #ede3cd;
  --cream-3:    #e3d7bc;

  /* ---- Accents. Rust on cream, gold on dark. ----
     They are the SAME role in two environments, not two accents.
     Rust at #8e3f22 has the contrast to be read as small text on
     cream; gold at #e8a842 has it on ink. Swapping them over fails
     both ways round, so the serif word picks by band. */
  --rust:       #8e3f22;
  --rust-deep:  #71301a;
  --gold:       #e8a842;

  --white:      #ffffff;
  --line:       rgba(45, 41, 32, .12);   /* hairline, ink at low alpha */
  --line-dark:  rgba(255, 255, 255, .14);

  /* ---- Geometry. Template radii, in the two sizes it actually
     leans on plus the pill. 10px is cards, 20px is panels. ---- */
  --r-sm:  10px;
  --r-md:  20px;
  --r-pill: 100px;

  --wrap:   1200px;   /* measured */
  --read:    760px;   /* the reading column inside it */
  --gutter:   24px;

  /* ---- The one motion curve. See script.js. ---- */
  --ease: cubic-bezier(.44, 0, .56, 1);
  --dur:  .4s;
}

/* =============================================================
   RESET / BASE
   ============================================================= */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: "Satoshi", "Satoshi Placeholder", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 17px;
  font-weight: 500;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

h1, h2, h3, h4 { margin: 0; font-weight: 500; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

::selection { background: var(--rust); color: var(--cream); }

:focus-visible {
  outline: 2px solid var(--rust);
  outline-offset: 3px;
  border-radius: 4px;
}
.dark :focus-visible, .band--dark :focus-visible { outline-color: var(--gold); }

.sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* =============================================================
   TYPE SCALE
   -------------------------------------------------------------
   Measured off the template at 1280px wide. The clamp() lower
   bounds are ours — Framer swaps whole breakpoint layouts, we
   scale fluidly, and a 82px h1 at -3px tracking is unreadable on
   a phone if it only shrinks proportionally. Tracking has to
   RELAX as the size drops, which is why each level sets its own
   em-based tracking rather than inheriting a px value.
   ============================================================= */

.h-display,
.h1 { font-size: clamp(44px, 6.4vw, 82px); line-height: 1.0;  letter-spacing: -.037em; }
.h2 { font-size: clamp(36px, 5.6vw, 72px); line-height: 1.1;  letter-spacing: -.042em; }
.h3 { font-size: clamp(26px, 3.2vw, 40px); line-height: 1.15; letter-spacing: -.035em; }
.h4 { font-size: clamp(20px, 2.1vw, 24px); line-height: 1.25; letter-spacing: -.02em;  }

/* ---- The serif accent word. See note 1 in the header. ----
   One step LARGER than its neighbours and near-neutral tracking.
   `em` keeps the ratio intact through every clamp above without
   restating the scale. */
.ser {
  font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
  font-weight: 400;
  font-size: 1.025em;
  letter-spacing: -.013em;
  color: var(--rust);
  font-style: italic;
}
/* On a dark band the rust disappears; gold is the same role there. */
.band--dark .ser, .dark .ser { color: var(--gold); }

/* ---- Eyebrow. Small, uppercase-ish label above every section
   heading. The template sets it sentence-case with a leading dot
   rather than uppercase — keep that; uppercasing it makes the
   page look like a SaaS site. ---- */
.eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 14px; font-weight: 500; letter-spacing: .01em;
  color: var(--muted-soft);
  margin: 0 0 22px;
}
.eyebrow::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--rust); flex: none;
}
.band--dark .eyebrow { color: rgba(255,255,255,.62); }
.band--dark .eyebrow::before { background: var(--gold); }

.lede {
  font-size: clamp(17px, 1.5vw, 19px);
  line-height: 1.6;
  color: var(--muted);
  max-width: 56ch;
}
.band--dark .lede { color: rgba(255,255,255,.72); }

.small { font-size: 14px; line-height: 1.5; color: var(--muted-soft); }

/* =============================================================
   LAYOUT
   -------------------------------------------------------------
   200px section rhythm — see note 3. It compresses on small
   screens but never below 88px, or the page stops reading as
   editorial and starts reading as a landing page.
   ============================================================= */

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
.read { max-width: var(--read); }

.sec { padding-block: clamp(88px, 12vw, 200px); }
.sec--tight { padding-block: clamp(64px, 8vw, 120px); }

/* Full-bleed dark band. The template uses these to break the cream
   up roughly every third section; they are what stop 200px of
   whitespace becoming monotonous. */
.band--dark { background: var(--ink-deep); color: var(--white); }
.band--well { background: var(--cream-2); }

.sec__head { margin-bottom: clamp(48px, 6vw, 84px); }
.sec__head--split {
  display: grid; gap: 32px;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  align-items: end;
}
@media (max-width: 860px) { .sec__head--split { grid-template-columns: minmax(0, 1fr); align-items: start; } }

/* =============================================================
   MOTION PRIMITIVE
   -------------------------------------------------------------
   The entire template is ONE move: fade in from 30px below, over
   400ms, on cubic-bezier(.44,0,.56,1), staggered 50ms. Hero items
   run it on load; everything else runs it when scrolled into view;
   headings run it once per WORD.

   Do not add a second easing curve or a blur. The uniformity is
   why a page this long doesn't feel busy — every element on it
   arrives the same way, and the only thing that varies is when.

   The `.is-in` class is added by script.js. Without JS every
   element stays at its animated end state, so the page is fully
   readable — the initial offset is applied by a `.js` class that
   script.js puts on <html> before first paint.
   ============================================================= */

.js .rise {
  opacity: 0;
  transform: translateY(30px);
}
.js .rise.is-in {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: var(--d, 0s);
}

/* Word-split headings. script.js wraps each word in
   `.w > span`; the span is what moves. The outer `.w` keeps the
   word an inline-block so the line still wraps naturally — this is
   NOT a flex row of words, which is how Framer does it and which
   breaks text wrapping at narrow widths. */
.w { display: inline-block; }
.js .w > span {
  display: inline-block;
  opacity: 0;
  transform: translateY(30px);
  will-change: opacity, transform;
}
.js .is-in .w > span,
.js .w.is-in > span {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: var(--d, 0s);
}

/* Reduced motion: everything is simply present. No fade, no
   offset, no stagger — a 12-word heading fading in word by word is
   exactly the effect the setting exists to stop. */
@media (prefers-reduced-motion: reduce) {
  .js .rise, .js .rise.is-in,
  .js .w > span, .js .is-in .w > span, .js .w.is-in > span {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    will-change: auto;
  }
}

/* =============================================================
   BUTTONS
   -------------------------------------------------------------
   100px pills — the single most-used radius on the template (41
   occurrences against 32 for the next one). Two variants only:
   solid ink, and a hairline ghost. The template's hover is a
   colour swap plus the label rolling one line, the same text-roll
   used on the main Built by Boot nav.
   ============================================================= */

.btn {
  --btn-bg: var(--ink);
  --btn-fg: var(--cream);
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 14px 26px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: 15px; font-weight: 500; letter-spacing: .01em;
  cursor: pointer;
  transition: background .28s var(--ease), color .28s var(--ease), border-color .28s var(--ease);
}
.btn:hover { --btn-bg: var(--rust); }

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  border-color: var(--line);
}
.btn--ghost:hover { --btn-bg: var(--ink); --btn-fg: var(--cream); border-color: var(--ink); }

.band--dark .btn { --btn-bg: var(--cream); --btn-fg: var(--ink); }
.band--dark .btn:hover { --btn-bg: var(--gold); }
.band--dark .btn--ghost { --btn-bg: transparent; --btn-fg: var(--white); border-color: var(--line-dark); }
.band--dark .btn--ghost:hover { --btn-bg: var(--white); --btn-fg: var(--ink); border-color: var(--white); }

.btn--sm { padding: 11px 20px; font-size: 14px; }
.btn--lg { padding: 17px 32px; font-size: 16px; }
.btn--block { display: flex; width: 100%; }

/* Text-roll hover. Two copies of the label in a clipped box; the
   pair slides one line. `.roll` needs a definite line-height or
   the copies drift apart at different font sizes and the outgoing
   word never fully clears — same fix as the main site's nav. */
.roll { display: block; overflow: hidden; height: 1.45em; line-height: 1.45; }
.roll > span { display: block; transition: transform .42s var(--ease); }
.roll > span + span { color: inherit; }
a:hover > .roll > span, button:hover > .roll > span,
.btn:hover .roll > span { transform: translateY(-1.45em); }
@media (prefers-reduced-motion: reduce) {
  .roll > span { transition: none; }
  a:hover > .roll > span, button:hover > .roll > span, .btn:hover .roll > span { transform: none; }
}

/* Arrow that nudges on hover — used on the inline "read more" links. */
.arw { display: inline-flex; align-items: center; gap: 8px; color: var(--rust); font-size: 15px; }
.arw svg { transition: transform .3s var(--ease); }
.arw:hover svg { transform: translateX(4px); }
.band--dark .arw { color: var(--gold); }
@media (prefers-reduced-motion: reduce) { .arw:hover svg { transform: none; } }

/* =============================================================
   NAV
   -------------------------------------------------------------
   106px tall, transparent, sitting directly on the cream — NOT a
   floating capsule. That is a real difference from the Ellerby
   build and it is deliberate: this template's authority comes from
   the page being calm, and a glass pill hovering over it is the
   sort of flourish the template's own description says it avoids.

   It gains a cream backdrop only once you scroll (`.is-stuck`), so
   headings never run underneath bare type.
   ============================================================= */

.nav {
  position: sticky; top: 0; z-index: 60;
  transition: background .3s var(--ease), box-shadow .3s var(--ease);
}
.nav.is-stuck {
  background: rgba(244, 238, 223, .88);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  box-shadow: 0 1px 0 var(--line);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav.is-stuck { background: var(--cream); }
}

.nav__in {
  height: 106px;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 24px;
}
.brand {
  font-size: 21px; letter-spacing: -.03em; font-weight: 500;
  display: inline-flex; align-items: baseline; gap: 2px;
}
.brand sup { font-size: .42em; top: -.7em; position: relative; color: var(--rust); }

.nav__links { display: flex; align-items: center; gap: 34px; justify-self: center; }
.nav__links a { font-size: 16px; color: var(--muted); transition: color .25s var(--ease); }
.nav__links a:hover, .nav__links a[aria-current="page"] { color: var(--ink); }

.nav__end { display: flex; align-items: center; gap: 12px; justify-self: end; }

.nav__burger {
  display: none; width: 42px; height: 42px; padding: 0;
  border: 1px solid var(--line); border-radius: 50%; background: transparent;
  cursor: pointer; position: relative;
}
.nav__burger span {
  position: absolute; left: 12px; width: 18px; height: 1.5px; background: var(--ink);
  transition: transform .3s var(--ease), opacity .2s var(--ease);
}
.nav__burger span:nth-child(1) { top: 17px; }
.nav__burger span:nth-child(2) { top: 23px; }
.nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(3px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:nth-child(2) { transform: translateY(-3px) rotate(-45deg); }

.nav__drawer {
  display: none; flex-direction: column; gap: 4px;
  padding: 8px 0 28px;
}
.nav__drawer a { padding: 13px 2px; font-size: 19px; letter-spacing: -.02em; border-bottom: 1px solid var(--line); }
.nav__drawer .btn { margin-top: 18px; border-bottom: 0; }
.nav.is-open .nav__drawer { display: flex; }
.nav.is-open { background: var(--cream); box-shadow: 0 1px 0 var(--line); }

@media (max-width: 940px) {
  .nav__in { grid-template-columns: 1fr auto; height: 84px; }
  .nav__links { display: none; }
  .nav__burger { display: block; }
  .nav__end .btn { display: none; }
}

/* =============================================================
   HERO
   -------------------------------------------------------------
   Centred, with an availability pill above the h1 and a stats row
   below it. The template's hero is type-only — no photograph — and
   that is worth keeping even once the licensed photographs land:
   the picture arrives lower down, on the founder block, where it
   is doing the "who am I trusting" job. A face in the hero would
   make the page about the person, and this build's whole argument
   is that it is about the process.
   ============================================================= */

.hero { padding-block: clamp(40px, 5vw, 72px) clamp(72px, 9vw, 128px); text-align: center; }
.hero__in { display: flex; flex-direction: column; align-items: center; }

/* Availability pill — "Accepting Q3 engagements" with a live dot.
   It is scarcity stated as a fact rather than a countdown, which
   is the only kind this template's tone can carry. */
.pill {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 8px 16px 8px 12px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: rgba(255,255,255,.4);
  font-size: 14px; color: var(--muted);
}
.pill__dot { width: 7px; height: 7px; border-radius: 50%; background: #3fa34d; position: relative; flex: none; }
.pill__dot::after {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 1px solid #3fa34d; opacity: .5;
  animation: ping 2.4s var(--ease) infinite;
}
@keyframes ping { 0% { transform: scale(.6); opacity: .7; } 100% { transform: scale(1.5); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .pill__dot::after { animation: none; opacity: .4; } }
.pill__sep { width: 1px; height: 13px; background: var(--line); }

.hero__h1 { margin: 26px 0 0; max-width: 15ch; }
.hero__p  { margin: 26px auto 0; max-width: 54ch; text-align: center; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 36px; }
.hero__note { margin-top: 18px; font-size: 13px; color: var(--muted-soft); font-style: italic; }

/* ---- Stats row ----
   Counters. The numbers animate up from zero once, when the row
   first enters view; see script.js. They are demo figures for a
   fictional practice, which the badge at the top of the page says
   plainly. */
.stats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px; margin-top: clamp(56px, 7vw, 88px);
  border-top: 1px solid var(--line); padding-top: clamp(32px, 4vw, 48px);
  width: 100%;
}
.stat__n {
  font-size: clamp(34px, 4.4vw, 56px); line-height: 1; letter-spacing: -.045em;
  font-variant-numeric: tabular-nums;
}
.stat__l { margin-top: 10px; font-size: 14px; color: var(--muted-soft); }
@media (max-width: 700px) {
  .stats { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .stat { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
  .stat__l { margin-top: 0; }
}

/* =============================================================
   MARQUEE — "featured in"
   -------------------------------------------------------------
   Two identical sets translating -50%, so the loop is seamless
   without measuring anything. Pauses on hover and stops entirely
   under reduced motion, where it becomes a plain wrapped row.
   ============================================================= */

.marq { overflow: hidden; padding-block: clamp(28px, 4vw, 44px); border-block: 1px solid var(--line); }
.marq__lab { text-align: center; font-size: 13px; color: var(--muted-soft); margin-bottom: 26px; }
.marq__track { display: flex; width: max-content; animation: slide 34s linear infinite; }
.marq:hover .marq__track { animation-play-state: paused; }
.marq__set { display: flex; align-items: center; gap: clamp(40px, 6vw, 76px); padding-right: clamp(40px, 6vw, 76px); }
.marq__item { font-size: clamp(17px, 1.8vw, 21px); letter-spacing: -.02em; color: var(--muted-soft); white-space: nowrap; }
@keyframes slide { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .marq__track { animation: none; width: 100%; flex-wrap: wrap; justify-content: center; }
  .marq__set:last-child { display: none; }
}

/* =============================================================
   DRAWN ART — the stopgap set
   -------------------------------------------------------------
   TEMPORARY. Every slot here is a photograph in the finished
   build; these panels exist so the pages are judgeable before the
   licensed originals arrive, exactly as Hollins and Ellerby each
   shipped once. When the photographs land, `scripts/make_kestrel_images.py`
   bakes the crops and these rules come out.

   They are drawn from the page's own tokens rather than being
   grey boxes, so the layout can be assessed for real — but a
   drafting grid is not a face, and the founder block in
   particular is not finished until there is a person in it.
   ============================================================= */

.art {
  position: relative; overflow: hidden;
  background: var(--cream-3); border-radius: var(--r-md);
  isolation: isolate;
}
.art::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 34px 34px;
  opacity: .55;
}
.art::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: radial-gradient(70% 60% at 30% 25%, rgba(255,255,255,.55), transparent 70%);
}
.art > svg, .art > .art__mark { position: relative; z-index: 2; }
.art__tag {
  position: absolute; z-index: 3; left: 14px; bottom: 14px;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted-soft); background: rgba(244,238,223,.82);
  padding: 5px 10px; border-radius: var(--r-pill);
}
.art--dark { background: var(--ink-deep); }
.art--dark::before { background-image:
    linear-gradient(var(--line-dark) 1px, transparent 1px),
    linear-gradient(90deg, var(--line-dark) 1px, transparent 1px); opacity: .5; }
.art--dark::after { background: radial-gradient(70% 60% at 30% 25%, rgba(232,168,66,.16), transparent 70%); }
.art--dark .art__tag { color: rgba(255,255,255,.6); background: rgba(32,28,21,.75); }

.art--16x9 { aspect-ratio: 16 / 9; }
.art--4x3  { aspect-ratio: 4 / 3; }
.art--1x1  { aspect-ratio: 1 / 1; }
.art--4x5  { aspect-ratio: 4 / 5; }

/* Initials disc — stands in for a headshot. Deliberately NOT a
   silhouette icon: a generic avatar reads as a missing image,
   whereas initials read as a placeholder someone chose. */
.art__mark {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: clamp(28px, 5vw, 46px); letter-spacing: -.04em; color: var(--muted-soft);
}
.art--dark .art__mark { color: rgba(232,168,66,.55); }

/* =============================================================
   THE FIVE-PHASE FRAMEWORK — sticky steps
   -------------------------------------------------------------
   This is the section the whole build exists for. The brief was
   "sell the process, not the person", and this is the process,
   made unskippable: the heading pins to the left while five cards
   pass it on the right.

   HOW IT WORKS, because sticky is easy to break: the grid's LEFT
   column is `position: sticky` inside a tall right column. The
   sticky element does not need a height calculation — it pins for
   exactly as long as its sibling is taller than the viewport,
   which is what makes this survive a copy change. Do not put
   `overflow: hidden` on `.steps` or any ancestor; it silently
   kills sticky and the section just scrolls.

   Below 940px the left column stops being sticky and becomes an
   ordinary heading, because pinning half the screen on a phone
   leaves no room for the thing being pinned against.
   ============================================================= */

.steps { display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); gap: clamp(32px, 5vw, 88px); align-items: stretch; }
/* The grid ITEM stretches to the full row height and an inner
   element does the pinning. Sticking the item itself looks like it
   works and then quietly releases two-thirds of the way down: a
   grid item sized to its own content can only travel the
   difference between its height and the row's, which here was
   453px against a 972px list. Don't put `align-items: start` back
   on this grid. */
.steps__side { align-self: stretch; }
.steps__pin { position: sticky; top: 132px; }
.steps__list { display: flex; flex-direction: column; gap: 16px; }

.step {
  display: grid; grid-template-columns: auto 1fr auto; gap: 20px; align-items: start;
  padding: clamp(22px, 2.6vw, 32px);
  background: var(--cream-2); border-radius: var(--r-sm);
  transition: background .32s var(--ease), transform .32s var(--ease);
}
.step:hover { background: var(--cream-3); }
.step__n {
  font-size: 13px; letter-spacing: .04em; color: var(--rust);
  font-variant-numeric: tabular-nums; padding-top: 4px;
}
.step__t { font-size: clamp(19px, 1.9vw, 23px); letter-spacing: -.025em; }
.step__p { margin-top: 10px; font-size: 15px; line-height: 1.6; color: var(--muted); max-width: 46ch; }
.step__wk {
  font-size: 13px; color: var(--muted-soft); white-space: nowrap;
  padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--r-pill);
}
@media (max-width: 940px) {
  .steps { grid-template-columns: minmax(0, 1fr); }
  .steps__pin { position: static; }
}
@media (max-width: 620px) {
  .step { grid-template-columns: auto 1fr; }
  .step__wk { grid-column: 2; justify-self: start; margin-top: 4px; }
}

/* Closing line under the steps — the template puts a one-line
   claim plus a CTA here rather than ending the section cold. */
.steps__foot {
  margin-top: clamp(40px, 5vw, 64px); padding-top: clamp(28px, 3vw, 40px);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 20px;
}
.steps__foot p { font-size: clamp(18px, 2vw, 24px); letter-spacing: -.03em; max-width: 30ch; }

/* =============================================================
   INCLUSIONS — "inside a private engagement"
   -------------------------------------------------------------
   A bento: one tall feature tile plus four ordinary ones. The
   feature tile carries the week-strip widget, which is the
   template's one piece of "product" UI and the thing that makes
   the section look like software rather than a brochure.
   ============================================================= */

.bento { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.bento__c {
  background: var(--cream-2); border-radius: var(--r-sm);
  padding: clamp(24px, 2.6vw, 32px);
  display: flex; flex-direction: column; gap: 14px;
  transition: background .32s var(--ease);
}
.bento__c:hover { background: var(--cream-3); }
.bento__c--tall { grid-row: span 2; justify-content: space-between; }
.bento__c--wide { grid-column: span 2; }
.bento__t { font-size: clamp(18px, 1.8vw, 21px); letter-spacing: -.025em; }
.bento__p { font-size: 15px; line-height: 1.6; color: var(--muted); }
@media (max-width: 860px) {
  .bento { grid-template-columns: 1fr 1fr; }
  .bento__c--tall { grid-row: auto; }
  .bento__c--wide { grid-column: span 2; }
}
@media (max-width: 560px) {
  .bento { grid-template-columns: minmax(0, 1fr); }
  .bento__c--wide { grid-column: auto; }
}

/* Week strip — M T W T F S S with the worked days filled. */
.week { display: flex; gap: 6px; }
.week span {
  flex: 1; aspect-ratio: 1; display: grid; place-items: center;
  border-radius: 6px; font-size: 12px; color: var(--muted-soft);
  background: rgba(45,41,32,.06);
}
.week span.on { background: var(--ink); color: var(--cream); }
.week__hd { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 14px; }
.week__hd b { font-size: clamp(24px, 2.6vw, 32px); font-weight: 500; letter-spacing: -.04em; }

/* =============================================================
   PRICING — three tiers
   -------------------------------------------------------------
   £2,500 / £12,000 / £30,000. Demo figures for a fictional UK
   advisory practice; scaled to an SME coach rather than the
   template's US high-ticket ladder ($8K/$48K/$120K), because the
   business that would actually buy a £1,200 website is not paying
   £120,000 for advice.

   The middle card is the one being sold: it inverts to ink and
   lifts. That is the template's own hierarchy and it is doing the
   work — three equal cards make the reader compare on price, one
   raised card makes them compare against it.
   ============================================================= */

.tiers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: start; }
.tier {
  background: var(--cream-2); border-radius: var(--r-md);
  padding: clamp(26px, 3vw, 38px);
  display: flex; flex-direction: column; gap: 22px;
  position: relative;
}
.tier--feature {
  background: var(--ink-deep); color: var(--white);
  transform: translateY(-18px); padding-block: clamp(36px, 4vw, 52px);
}
.tier--feature .tier__p, .tier--feature .tier__meta { color: rgba(255,255,255,.68); }
.tier--feature .tier__list li { color: rgba(255,255,255,.82); }
.tier--feature .tier__list li::before { background: var(--gold); }
.tier--feature .tier__rule { background: var(--line-dark); }
.tier--feature .btn { --btn-bg: var(--cream); --btn-fg: var(--ink); }
.tier--feature .btn:hover { --btn-bg: var(--gold); }
@media (max-width: 900px) { .tier--feature { transform: none; } }

.tier__flag {
  position: absolute; top: 0; left: 50%; transform: translate(-50%, -50%);
  background: var(--gold); color: var(--ink-deep);
  font-size: 12px; letter-spacing: .04em; padding: 6px 14px; border-radius: var(--r-pill);
  white-space: nowrap;
}
.tier__t { font-size: clamp(21px, 2.1vw, 26px); letter-spacing: -.03em; }
.tier__p { font-size: 15px; line-height: 1.6; color: var(--muted); }
.tier__rule { height: 1px; background: var(--line); }
.tier__price { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.tier__price b { font-size: clamp(32px, 3.6vw, 44px); font-weight: 500; letter-spacing: -.045em; }
.tier__meta { font-size: 14px; color: var(--muted-soft); }
.tier__list { display: flex; flex-direction: column; gap: 11px; }
.tier__list li { position: relative; padding-left: 24px; font-size: 15px; line-height: 1.5; color: var(--muted); }
.tier__list li::before {
  content: ""; position: absolute; left: 0; top: .52em;
  width: 11px; height: 2px; background: var(--rust); border-radius: 2px;
}
.tier .btn { margin-top: auto; }
@media (max-width: 900px) { .tiers { grid-template-columns: minmax(0, 1fr); } }

/* =============================================================
   COMPARISON TABLE — "how we are different"
   -------------------------------------------------------------
   Two columns of claims, ours and theirs, over five rows. This is
   the second half of "sell the process": the framework says what
   we do, this says what it is instead of.

   IT IS A REAL <table>, not a grid of divs. Five rows of paired
   claims IS tabular data, and a screen reader announcing "Client
   load — us — capped at twelve a quarter" only works if the
   header association exists. On narrow screens the rows restack
   with the column labels repeated via data attributes, which is
   the one case where duplicating the header text is correct.
   ============================================================= */

.cmp { width: 100%; border-collapse: collapse; }
.cmp caption { text-align: left; font-size: 14px; color: var(--muted-soft); padding-bottom: 18px; }
.cmp th, .cmp td { text-align: left; padding: 22px 20px; vertical-align: top; font-weight: 500; }
.cmp thead th { font-size: 14px; color: var(--muted-soft); padding-block: 0 16px; }
.cmp thead th:nth-child(2) { color: var(--ink); }
.cmp tbody tr { border-top: 1px solid var(--line); }
.cmp tbody th { font-size: 15px; color: var(--muted-soft); width: 24%; }
.cmp td { font-size: 16px; line-height: 1.5; letter-spacing: -.01em; }
/* Ours reads as the answer; theirs is set back. */
.cmp td.is-us { color: var(--ink); background: rgba(255,255,255,.42); }
.cmp td.is-them { color: var(--muted-soft); }
.cmp tbody tr:first-child td.is-us { border-radius: var(--r-sm) var(--r-sm) 0 0; }
.cmp tbody tr:last-child td.is-us { border-radius: 0 0 var(--r-sm) var(--r-sm); }

@media (max-width: 760px) {
  .cmp, .cmp tbody, .cmp tr, .cmp th, .cmp td { display: block; }
  .cmp thead { display: none; }
  .cmp tbody tr { padding-block: 20px; }
  .cmp tbody th { width: auto; padding: 0 0 12px; font-size: 14px; }
  .cmp td { padding: 10px 14px; border-radius: var(--r-sm); }
  .cmp td::before { content: attr(data-lab) " · "; color: var(--muted-soft); font-size: 13px; }
}

/* =============================================================
   PEOPLE
   -------------------------------------------------------------
   Founder panel plus a four-up specialist network. Every image
   here is currently a drawn placeholder; see the ART block.
   ============================================================= */

.founder { display: grid; grid-template-columns: minmax(0, .78fr) minmax(0, 1.22fr); gap: clamp(28px, 4vw, 64px); align-items: center; }
@media (max-width: 860px) { .founder { grid-template-columns: minmax(0, 1fr); } }
.founder__quote { font-size: clamp(21px, 2.4vw, 30px); line-height: 1.35; letter-spacing: -.03em; max-width: 24ch; }
.founder__meta { display: flex; flex-wrap: wrap; gap: 10px 26px; margin-top: 26px; font-size: 14px; color: var(--muted-soft); }
.founder__stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; margin-top: 34px; padding-top: 28px; border-top: 1px solid var(--line); }
/* `1fr` is `minmax(auto, 1fr)`, so a track can refuse to shrink
   below its content's min-content width and push the whole page
   sideways — three stat labels in a 375px viewport did exactly
   that, giving 510px of page. Every multi-column grid on this
   sheet uses minmax(0, 1fr) for that reason, and the stats stack
   outright on a phone rather than being squeezed to fit. */
@media (max-width: 560px) {
  .founder__stats { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  .founder__stats .stat { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
  .founder__stats .stat__l { margin-top: 0; }
}

.people { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.person__t { margin-top: 16px; font-size: 17px; letter-spacing: -.02em; }
.person__r { margin-top: 4px; font-size: 14px; color: var(--rust); }
.person__n { margin-top: 8px; font-size: 13px; color: var(--muted-soft); line-height: 1.5; }
@media (max-width: 860px) { .people { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .people { grid-template-columns: minmax(0, 1fr); } }

/* =============================================================
   CASE STUDIES
   ============================================================= */

.cases { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.case {
  background: var(--cream-2); border-radius: var(--r-md); overflow: hidden;
  display: flex; flex-direction: column;
  transition: background .32s var(--ease);
}
.case:hover { background: var(--cream-3); }
.case__art { border-radius: 0; }
.case__body { padding: clamp(22px, 2.6vw, 32px); display: flex; flex-direction: column; gap: 12px; flex: 1; }
.case__sec { font-size: 13px; color: var(--rust); }
.case__t { font-size: clamp(20px, 2.1vw, 26px); letter-spacing: -.03em; }
.case__p { font-size: 15px; line-height: 1.6; color: var(--muted); }
.case__res { display: flex; gap: 26px; margin-top: auto; padding-top: 18px; border-top: 1px solid var(--line); }
.case__res b { display: block; font-size: clamp(22px, 2.4vw, 28px); font-weight: 500; letter-spacing: -.04em; }
.case__res span { font-size: 13px; color: var(--muted-soft); }
@media (max-width: 760px) { .cases { grid-template-columns: minmax(0, 1fr); } }

/* Testimonial — one quote, set large, with a rating strip. */
.quote { text-align: center; max-width: 22ch; margin-inline: auto; }
.quote__t { font-size: clamp(24px, 3.4vw, 44px); line-height: 1.25; letter-spacing: -.04em; }
.quote__by { margin-top: 30px; font-size: 15px; color: var(--muted-soft); }
.rating { display: inline-flex; align-items: center; gap: 12px; margin-top: 34px; padding: 12px 20px; border: 1px solid var(--line); border-radius: var(--r-pill); font-size: 14px; color: var(--muted-soft); }
.rating b { font-size: 19px; font-weight: 500; color: var(--ink); letter-spacing: -.03em; }
.band--dark .rating { border-color: var(--line-dark); color: rgba(255,255,255,.66); }
.band--dark .rating b { color: var(--white); }

/* =============================================================
   FAQ — accordion
   -------------------------------------------------------------
   <details>/<summary> with the height animated by script.js.
   Built on the native element on purpose: it is keyboard- and
   screen-reader-correct with no ARIA of ours, and it still opens
   and closes with JS off. The script only adds the height
   transition; it never owns the open state.
   ============================================================= */

.faq { display: flex; flex-direction: column; }
.faq__i { border-top: 1px solid var(--line); }
.faq__i:last-child { border-bottom: 1px solid var(--line); }
.faq__q {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 26px 0; cursor: pointer; list-style: none;
  font-size: clamp(17px, 1.8vw, 21px); letter-spacing: -.025em;
  transition: color .25s var(--ease);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--rust); }
.faq__ic { flex: none; width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--line); display: grid; place-items: center; position: relative; }
.faq__ic::before, .faq__ic::after {
  content: ""; position: absolute; background: var(--ink);
  transition: transform .32s var(--ease), opacity .2s var(--ease);
}
.faq__ic::before { width: 11px; height: 1.5px; }
.faq__ic::after  { width: 1.5px; height: 11px; }
.faq__i[open] .faq__ic::after { transform: rotate(90deg); opacity: 0; }
.faq__a { overflow: hidden; }
.faq__a p { padding: 0 60px 28px 0; font-size: 16px; line-height: 1.65; color: var(--muted); max-width: 62ch; }
.faq__foot { margin-top: 36px; display: flex; flex-wrap: wrap; align-items: center; gap: 16px; font-size: 15px; color: var(--muted); }

/* =============================================================
   FORM — the application
   -------------------------------------------------------------
   Enquiry-only, like Bramblewood, Hollins and Ellerby. /work
   states the £1,200 tier excludes online payments, so this takes
   an application and nothing else — no consultation fee, no card.
   ============================================================= */

.apply { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(32px, 5vw, 80px); align-items: start; }
@media (max-width: 860px) { .apply { grid-template-columns: minmax(0, 1fr); } }
.apply__list { display: flex; flex-direction: column; gap: 14px; margin-top: 34px; }
.apply__list li { display: flex; gap: 12px; font-size: 15px; line-height: 1.55; color: rgba(255,255,255,.78); }
.apply__list svg { flex: none; margin-top: 3px; color: var(--gold); }

.form { background: var(--cream); color: var(--ink); border-radius: var(--r-md); padding: clamp(24px, 3vw, 38px); }
.form__grid { display: flex; flex-direction: column; gap: 16px; }
.field label { display: block; font-size: 14px; color: var(--muted-soft); margin-bottom: 7px; }
.field input, .field select, .field textarea {
  width: 100%; padding: 13px 16px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--white); color: var(--ink);
  font: inherit; font-size: 15px;
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.field textarea { min-height: 110px; resize: vertical; }
.field select { appearance: none; -webkit-appearance: none; padding-right: 40px; background-repeat: no-repeat; background-position: right 16px center; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--rust); box-shadow: 0 0 0 3px rgba(142,63,34,.12);
}
.form__note { margin-top: 14px; font-size: 13px; color: var(--muted-soft); text-align: center; }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 520px) { .form__row { grid-template-columns: 1fr; } }

/* The under-threshold warning. Fires when the revenue band picked
   is below the level this practice can help — the same qualifying
   move Ellerby makes, kept here because the template's own form
   has the band select and removing it would be porting less than
   was asked for. It warns; it never blocks the submit. */
.form__warn {
  display: none; gap: 10px; margin-top: -4px;
  padding: 12px 14px; border-radius: var(--r-sm);
  background: rgba(142, 63, 34, .07); border: 1px solid rgba(142, 63, 34, .2);
  font-size: 14px; line-height: 1.5; color: var(--rust-deep);
}
.form__warn.is-on { display: flex; }

/* Success state — swaps in place of the form, same as the main
   site's intake page. */
.form__done { display: none; text-align: center; padding: clamp(30px, 4vw, 48px) 0; }
.form.is-done .form__grid, .form.is-done .form__note { display: none; }
.form.is-done .form__done { display: block; }
.form__done h3 { font-size: clamp(22px, 2.4vw, 30px); letter-spacing: -.035em; }
.form__done p { margin-top: 12px; color: var(--muted); font-size: 15px; }

/* The select's caret. Kept as its own rule so the data URI sits in
   one place and the layout rule above stays readable. */
.field select {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23555350' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* =============================================================
   INNER-PAGE HERO
   -------------------------------------------------------------
   Every page except the home page opens with the same compact
   header: eyebrow, h1, one line of lede. Shorter than the home
   hero on purpose — the reader has already decided to be here,
   so the job is orientation, not persuasion.
   ============================================================= */

.phero { padding-block: clamp(48px, 6vw, 88px) clamp(48px, 6vw, 80px); }
.phero__h1 { max-width: 18ch; }
.phero__p { margin-top: 24px; }
.phero--center { text-align: center; }
.phero--center .phero__h1, .phero--center .phero__p { margin-inline: auto; }

/* =============================================================
   JOURNAL (the template's "blog")
   -------------------------------------------------------------
   Called the Journal rather than the Blog. A practice charging
   £12,000 for a six-month engagement does not have a blog, and
   the word does more positioning work than it looks like.
   ============================================================= */

.posts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.post {
  display: flex; flex-direction: column;
  background: var(--cream-2); border-radius: var(--r-md); overflow: hidden;
  transition: background .32s var(--ease), transform .32s var(--ease);
}
.post:hover { background: var(--cream-3); transform: translateY(-3px); }
@media (prefers-reduced-motion: reduce) { .post:hover { transform: none; } }
.post__body { padding: clamp(20px, 2.4vw, 28px); display: flex; flex-direction: column; gap: 11px; flex: 1; }
.post__meta { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--muted-soft); }
.post__meta .tag { color: var(--rust); }
.post__t { font-size: clamp(18px, 1.9vw, 22px); letter-spacing: -.028em; line-height: 1.25; }
.post__p { font-size: 15px; line-height: 1.6; color: var(--muted); }
.post__more { margin-top: auto; padding-top: 14px; }
@media (max-width: 900px) { .posts { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .posts { grid-template-columns: 1fr; } }

/* A featured lead post — first item, full width, art beside copy. */
.post--lead { grid-column: 1 / -1; flex-direction: row; align-items: stretch; }
.post--lead .post__art { width: 46%; flex: none; }
.post--lead .post__body { justify-content: center; padding: clamp(28px, 3.4vw, 48px); gap: 14px; }
.post--lead .post__t { font-size: clamp(24px, 2.8vw, 36px); }
@media (max-width: 760px) {
  .post--lead { flex-direction: column; }
  .post--lead .post__art { width: 100%; }
}

/* =============================================================
   PROSE — the article body on a journal entry
   ============================================================= */

.prose { max-width: 68ch; }
.prose > * + * { margin-top: 24px; }
.prose p { font-size: 17px; line-height: 1.72; color: var(--muted); }
.prose h2 { font-size: clamp(24px, 2.6vw, 32px); letter-spacing: -.035em; color: var(--ink); margin-top: 52px; }
.prose h3 { font-size: clamp(19px, 2vw, 23px); letter-spacing: -.028em; color: var(--ink); margin-top: 40px; }
.prose ul { display: flex; flex-direction: column; gap: 10px; }
.prose li { position: relative; padding-left: 24px; font-size: 17px; line-height: 1.65; color: var(--muted); }
.prose li::before { content: ""; position: absolute; left: 0; top: .62em; width: 11px; height: 2px; background: var(--rust); border-radius: 2px; }
.prose blockquote {
  margin: 40px 0; padding: 26px 30px;
  background: var(--cream-2); border-radius: var(--r-sm);
  font-size: clamp(19px, 2.1vw, 24px); line-height: 1.4; letter-spacing: -.03em; color: var(--ink);
}
.prose strong { font-weight: 700; color: var(--ink); }

/* =============================================================
   CTA BAND — closes every inner page
   ============================================================= */

.cta { text-align: center; }
.cta__h { max-width: 18ch; margin-inline: auto; }
.cta__p { margin: 24px auto 0; max-width: 46ch; }
.cta__b { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 34px; }

/* =============================================================
   FOOTER
   ============================================================= */

.foot { background: var(--ink-deep); color: var(--white); padding-block: clamp(56px, 7vw, 90px) 32px; }
.foot__top { display: grid; grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr)); gap: 40px 24px; }
.foot__brand { font-size: 24px; letter-spacing: -.03em; }
.foot__brand sup { font-size: .42em; position: relative; top: -.7em; color: var(--gold); }
.foot__blurb { margin-top: 16px; font-size: 15px; line-height: 1.6; color: rgba(255,255,255,.62); max-width: 34ch; }
.foot h4 { font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: rgba(255,255,255,.42); margin-bottom: 16px; }
.foot ul { display: flex; flex-direction: column; gap: 11px; }
.foot ul a, .foot ul li { font-size: 15px; color: rgba(255,255,255,.72); transition: color .25s var(--ease); }
.foot ul a:hover { color: var(--gold); }
.foot__bot {
  margin-top: clamp(44px, 5vw, 68px); padding-top: 26px; border-top: 1px solid var(--line-dark);
  display: flex; flex-wrap: wrap; gap: 12px 26px; align-items: center; justify-content: space-between;
  font-size: 13px; color: rgba(255,255,255,.5);
}
@media (max-width: 860px) { .foot__top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .foot__top { grid-template-columns: 1fr; gap: 32px; } }

/* =============================================================
   DEMO BADGE
   -------------------------------------------------------------
   Same contract as every other demo in this repo: it says on
   every page that this is a concept, and it links home. The
   screenshot script crops it off the bottom, so its position and
   height matter to scripts/make_demo_shots.py — don't move it.
   ============================================================= */

.demobadge {
  position: fixed; z-index: 90; left: 50%; bottom: 16px; transform: translateX(-50%);
  padding: 9px 18px; border-radius: var(--r-pill);
  background: rgba(32, 28, 21, .9); color: rgba(255,255,255,.9);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  font-size: 12.5px; letter-spacing: .01em; white-space: nowrap;
  box-shadow: 0 6px 24px rgba(0,0,0,.18);
  transition: background .25s var(--ease);
}
.demobadge:hover { background: var(--rust); }

/* =============================================================
   PRINT — the fee ladder is the page people print.
   ============================================================= */

@media print {
  .nav, .demobadge, .marq, .cta, .form { display: none !important; }
  body { background: #fff; color: #000; }
  .band--dark { background: #fff !important; color: #000 !important; }
  .sec { padding-block: 24px; }
  .js .rise, .js .w > span { opacity: 1 !important; transform: none !important; }
}

/* =============================================================
   PHOTOGRAPHY
   -------------------------------------------------------------
   Real photographs, as opposed to the drawn `.art` placeholders
   above. Crops are baked at build time by
   scripts/make_kestrel_images.py, so every file already arrives
   at the exact aspect its slot wants — `object-fit: cover` here
   is a guard against sub-pixel rounding, not the cropping
   mechanism. Re-frame in that script's MANIFEST, never by
   hand-editing a WebP.

   `aspect-ratio` on the wrapper plus width/height on the <img>
   means the space is reserved before the file arrives, so a slow
   connection never reflows the page around a loading portrait.
   ============================================================= */

.ph {
  position: relative; overflow: hidden;
  border-radius: var(--r-md);
  background: var(--cream-3);
}
.ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph--4x5  { aspect-ratio: 4 / 5; }
.ph--1x1  { aspect-ratio: 1 / 1; }
.ph--16x9 { aspect-ratio: 16 / 9; }

/* Inside a card the image is flush to the card's own corners, so
   it drops its radius exactly as the drawn version does. */
.case__art.ph, .post__art.ph { border-radius: 0; }
