/* ==========================================================================
   Balanced Bodywork
   Hand-written. No framework. Read DESIGN.md before changing anything here —
   it carries the anti-slop constraints these rules exist to satisfy.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */

@font-face {
  font-family: "Archivo";
  src: url("/assets/fonts/archivo-latin-var.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("/assets/fonts/sourcesans3-latin-var.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

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

:root {
  --ink:        #0E1A24;
  --ink-soft:   #16242F;
  --brand:      #205F8F;
  --brand-deep: #184A70;
  --steel:      #518EB4;
  --mist:       #AED0E0;
  --paper:      #F9F5EE;
  --paper-warm: #F2ECE1;
  --rule:       #DCD3C4;
  --rule-dark:  #2A3B48;
  --muted:      #5A6873;

  --sans: "Source Sans 3", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
          Roboto, Helvetica, Arial, sans-serif;
  --display: "Archivo", var(--sans);

  --fs-hero: clamp(2.75rem, 1.4rem + 5.6vw, 4.5rem);
  --fs-h2:   clamp(1.9rem, 1.15rem + 2.6vw, 2.9rem);
  --fs-h3:   clamp(1.15rem, 1.05rem + 0.4vw, 1.35rem);
  --fs-lead: clamp(1.1rem, 1.02rem + 0.32vw, 1.3rem);
  --fs-body: clamp(1rem, 0.98rem + 0.1vw, 1.06rem);

  --shell: 1180px;
  --gut: clamp(1.25rem, 0.7rem + 2.3vw, 2.5rem);
  --band: clamp(4rem, 2.4rem + 6.4vw, 7.5rem);

  /* Two radii, on purpose. Controls stay tight and mechanical; photographs
     are softened further so they read as images rather than as cropped tiles.
     The client asked for softer pictures — the gap between the two is the
     design, not an inconsistency. Neither is allowed to go blobby. */
  --r:     5px;   /* buttons, the burger, the skip link */
  --r-img: 12px;  /* photographs only */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
  /* The stock iOS/Android tap flash is a saturated blue rectangle that fights
     the palette. Replace it with a faint ink wash. */
  -webkit-tap-highlight-color: rgba(14, 26, 36, 0.09);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; height: auto; display: block; }
figure { margin: 0; }
ul { list-style: none; margin: 0; padding: 0; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0 0 1.1em; max-width: 68ch; }
p:last-child { margin-bottom: 0; }

a { color: var(--brand); text-decoration-thickness: 1px; text-underline-offset: 3px; }

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

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

.skip {
  position: absolute; left: var(--gut); top: -100px; z-index: 100;
  background: var(--ink); color: var(--paper);
  padding: 0.7rem 1.1rem; border-radius: var(--r);
  font-weight: 600; text-decoration: none;
  transition: top 0.18s var(--ease);
}
.skip:focus { top: 0.75rem; }

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gut);
}

/* --- Buttons -------------------------------------------------------------- */
/* Flat fills and real borders. No gradient, no glow, no lift-on-hover. */

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  --btn-bd: var(--rule);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  min-height: 46px;
  padding: 0.7rem 1.25rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r);
  font-family: var(--display);
  font-size: 0.97rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease),
              color 0.16s var(--ease), transform 0.1s var(--ease);
}

.btn--lg { min-height: 54px; padding: 0.9rem 1.7rem; font-size: 1.05rem; }

.btn--solid  { --btn-bg: var(--brand); --btn-fg: var(--paper); --btn-bd: var(--brand); }
.btn--solid:hover  { --btn-bg: var(--brand-deep); --btn-bd: var(--brand-deep); }

.btn--outline { --btn-bd: var(--ink); --btn-fg: var(--ink); }
.btn--outline:hover { --btn-bg: var(--ink); --btn-fg: var(--paper); }

.btn--ghost { --btn-bd: var(--rule); --btn-fg: var(--ink); }
.btn--ghost:hover { --btn-bd: var(--ink); }

.btn--onblue { --btn-bg: var(--paper); --btn-fg: var(--brand-deep); --btn-bd: var(--paper); }
.btn--onblue:hover { --btn-bg: var(--ink); --btn-fg: var(--paper); --btn-bd: var(--ink); }

/* Pressed state, declared after every :hover so the press actually wins when
   both apply. A 1px nudge and one step deeper in value is the whole gesture:
   the button reads as a physical key going down, not as something springing
   off the page. Each pressed fill is darker than its own hover fill, so the
   paper-coloured label only gains contrast. */
.btn:active { transform: translateY(1px); }
.btn--solid:active   { --btn-bg: #133C5B; --btn-bd: #133C5B; }
.btn--outline:active { --btn-bg: #060F17; --btn-fg: var(--paper); --btn-bd: #060F17; }
.btn--ghost:active   { --btn-bg: var(--rule); --btn-bd: var(--ink); }
.btn--onblue:active  { --btn-bg: #060F17; --btn-fg: var(--paper); --btn-bd: #060F17; }

/* --- Masthead ------------------------------------------------------------- */

.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--paper);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
}
.masthead.is-stuck {
  border-bottom-color: var(--rule);
  background: color-mix(in srgb, var(--paper) 94%, transparent);
}
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .masthead.is-stuck { background: var(--paper); }
}

.masthead__in {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  min-height: 72px;
}

.masthead__brand { display: block; flex: 0 0 auto; line-height: 0; }
.masthead__brand img { height: 38px; width: auto; }

.nav { margin-left: auto; }

.nav__list { display: flex; gap: clamp(1rem, 2.2vw, 1.9rem); }

.nav__list a {
  position: relative;
  display: block;
  padding: 0.35rem 0;
  color: var(--ink);
  font-weight: 600;
  font-size: 0.97rem;
  text-decoration: none;
}
/* Underline drawn from a border, not a pseudo-gradient bar. */
.nav__list a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s var(--ease);
}
.nav__list a:hover::after,
.nav__list a[aria-current="true"]::after { transform: scaleX(1); }
.nav__list a[aria-current="true"] { color: var(--brand); }

.masthead__call {
  flex: 0 0 auto;
  --btn-bg: var(--ink); --btn-fg: var(--paper); --btn-bd: var(--ink);
  min-height: 42px;
  padding: 0.5rem 1rem;
}
.masthead__call:hover { --btn-bg: var(--brand); --btn-bd: var(--brand); }
.masthead__call:active { --btn-bg: var(--brand-deep); --btn-bd: var(--brand-deep); }
.masthead__call { gap: 0.3em; }
.masthead__call .btn__num { font-variant-numeric: tabular-nums; }

.burger {
  display: none;
  flex: 0 0 auto;
  width: 46px; height: 46px;
  padding: 0;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  cursor: pointer;
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease);
}
.burger:active { background: var(--paper-warm); border-color: var(--ink); }
.burger__bar {
  display: block;
  width: 20px; height: 2px;
  margin: 4px auto;
  background: var(--ink);
  transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}
.burger[aria-expanded="true"] .burger__bar:first-child { transform: translateY(3px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bar:last-child  { transform: translateY(-3px) rotate(-45deg); }

/* --- Bands ---------------------------------------------------------------- */

.band { padding-block: var(--band); }
.band--warm { background: var(--paper-warm); }

.band--ink {
  background: var(--ink);
  color: var(--mist);
}
.band--ink h2 { color: var(--paper); }
.band--ink .lead { color: #C6DCE8; }

.band h2 {
  font-size: var(--fs-h2);
  margin-bottom: 1.4rem;
}

.lead { font-size: var(--fs-lead); line-height: 1.55; }

/* --- Hero ----------------------------------------------------------------- */
/* Asymmetric on purpose: copy column is wider than the image column, and the
   whole thing is left-aligned. No centred stack, no badge above the H1. */

.hero { padding-block: clamp(3rem, 1.6rem + 5.6vw, 6rem) var(--band); }

.hero__grid {
  display: grid;
  gap: clamp(2.5rem, 1rem + 5vw, 4.5rem);
  align-items: center;
}

.hero__title {
  font-size: var(--fs-hero);
  letter-spacing: -0.035em;
  margin-bottom: 1.6rem;
}
.hero__title span { display: block; }
.hero__title span:nth-child(2) { color: var(--brand); }

.hero__copy .lead { margin-bottom: 1rem; }
.hero__copy p { color: #364550; }
.hero__copy .lead { color: var(--ink); }

.hero__act {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

.hero__meta {
  margin: 1.25rem 0 0;
  font-size: 0.95rem;
  color: var(--muted);
}

.hero__fig img {
  width: 100%;
  border-radius: var(--r-img);
}

/* --- Split sections ------------------------------------------------------- */

.split {
  display: grid;
  gap: clamp(2rem, 1rem + 4vw, 4rem);
  align-items: center;
}

.split__fig img { width: 100%; border-radius: var(--r-img); }

.split__copy .lead { margin-bottom: 1rem; }
.split__copy strong { font-weight: 600; color: inherit; }
.split__act { margin-top: 1.8rem; }

.band--warm .split__copy p:not(.lead) { color: var(--muted); }

/* --- Statement ------------------------------------------------------------ */
/* The client's own pivot line, set as an editorial two-column feature opener.
   No image: the sentence is the strongest thing in this section and a picture
   beside it only competes with it. */

.stmt { display: grid; gap: clamp(1.5rem, 0.6rem + 3vw, 3.5rem); }

.stmt__head {
  font-size: clamp(2.1rem, 1.1rem + 3.4vw, 3.4rem);
  letter-spacing: -0.03em;
  max-width: 16ch;
  margin: 0;
}

.stmt__body .lead { margin-bottom: 1.1rem; }

@media (min-width: 900px) {
  .stmt { grid-template-columns: 1fr 1fr; align-items: start; }
}

/* --- Focus triptych ------------------------------------------------------- */
/* Photographs, not icon cards. Staggered so it doesn't read as a 3-up grid. */

.focus {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(0.75rem, 0.4rem + 1.4vw, 1.5rem);
  margin-top: clamp(2.5rem, 1.2rem + 4vw, 4.5rem);
}
.focus__item img {
  width: 100%;
  border-radius: var(--r-img);
}
/* The drop is held in a custom property so the scroll reveal can add its own
   translate on top of it rather than cancelling it. */
.focus__item { --drop: 0px; }
.focus__item--drop { --drop: clamp(1rem, 3vw, 2.75rem); transform: translateY(var(--drop)); }

/* --- Price list ----------------------------------------------------------- */
/* A ruled trade price list. Deliberately not three floating cards. */

.price { border-top: 1px solid var(--rule); }

.price__row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1rem clamp(1.5rem, 4vw, 4rem);
  align-items: start;
  padding-block: clamp(1.6rem, 1rem + 1.6vw, 2.4rem);
  border-bottom: 1px solid var(--rule);
}

/* The hover tint is painted by a pseudo-element that bleeds a little past the
   row on each side, so the row gets breathing room around the wash without
   any padding that would drag the hairlines out of line with the heading. */
.price__row::before {
  content: "";
  position: absolute;
  z-index: 0;
  top: 0; bottom: 0;
  left: calc(-1 * clamp(0.6rem, 1.4vw, 1rem));
  right: calc(-1 * clamp(0.6rem, 1.4vw, 1rem));
  background-color: transparent;
  transition: background-color 0.18s var(--ease);
}
.price__row > * { position: relative; z-index: 1; }

/* Pointer only: on touch this would latch on after a tap and look broken. */
@media (hover: hover) {
  .price__row:hover::before { background-color: var(--paper-warm); }
  .price__row:hover .price__fig { color: var(--brand-deep); }
}

.price__name {
  font-size: var(--fs-h3);
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.price__body p { color: var(--muted); margin: 0; max-width: 58ch; }

.price__fig {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(2rem, 1.3rem + 2.4vw, 3rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: color 0.18s var(--ease);
}
.price__fig--text {
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
  font-weight: 700;
  line-height: 1.4;
  color: var(--muted);
  text-align: right;
  white-space: normal;
  max-width: 9rem;
  padding-top: 0.35rem;
}

.price__act { margin-top: clamp(2rem, 1.2rem + 2vw, 3rem); }

/* --- About ---------------------------------------------------------------- */

.about { max-width: 46rem; }
.about .lead { color: var(--ink); }
.about .lead + .lead { color: var(--muted); }

/* --- Practitioner --------------------------------------------------------- */
/* A signature, not a "meet the team" card: a rule and a name carry more
   credibility here than a portrait tile would, and the client supplied no
   photograph of themselves. */

.who {
  margin-top: clamp(2rem, 1.2rem + 2vw, 3rem);
  padding-top: 1.4rem;
  border-top: 2px solid var(--ink);
  max-width: 34rem;
}

.who__name {
  margin: 0 0 0.2rem;
  font-family: var(--display);
  font-size: var(--fs-h3);
  font-weight: 800;
  letter-spacing: -0.015em;
}

.who__cred {
  margin: 0;
  font-size: 0.95rem;
  color: var(--muted);
}

/* --- Closing CTA ---------------------------------------------------------- */

.cta {
  background: var(--brand);
  color: var(--paper);
  padding-block: var(--band);
}
.cta__in { text-align: center; }

.cta__title {
  font-size: var(--fs-h2);
  color: var(--paper);
  margin-bottom: 0.9rem;
}

.cta__line {
  margin: 0 auto 2rem;
  font-size: var(--fs-lead);
  color: #DCEAF4;
}
.cta__line a { color: var(--paper); font-weight: 600; }

.cta__hours {
  margin: 1.5rem auto 0;
  font-size: 0.95rem;
  color: #CFE2EF;
}

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

.foot {
  background: var(--ink);
  color: var(--mist);
  padding-top: clamp(3rem, 2rem + 3vw, 4.5rem);
}

.foot__in {
  display: grid;
  gap: 2rem;
  padding-bottom: clamp(2rem, 1.4rem + 2vw, 3rem);
  border-bottom: 1px solid var(--rule-dark);
}

.foot__brand img { height: 44px; width: auto; }

.foot__tag {
  margin: 1rem 0 0;
  font-family: var(--display);
  font-weight: 600;
  font-size: 1rem;
  color: var(--steel);
}

.foot__meta p { margin: 0 0 0.35rem; }

.foot__phone {
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}
.foot__phone a { color: var(--paper); text-decoration: none; }
.foot__phone a:hover { text-decoration: underline; }

.foot__area {
  padding-block: 1.4rem;
  border-bottom: 1px solid var(--rule-dark);
  font-size: 0.95rem;
  color: var(--mist);
}
.foot__area p { margin: 0; max-width: none; }
.foot__area strong { color: var(--paper); font-weight: 600; }

.foot__base {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  justify-content: space-between;
  padding-block: 1.5rem;
  font-size: 0.9rem;
  color: #8AA2B2;
}
.foot__base p { margin: 0; max-width: none; }

/* --- 404 ------------------------------------------------------------------ */
/* Without a real 404 page Cloudflare Pages answers every unknown path with the
   homepage and a 200, which is a soft 404: Google can index unlimited
   duplicate URLs, and a mistyped link silently looks like it worked. */

.lost { padding-block: clamp(4rem, 2rem + 8vw, 9rem); }
.lost__in { max-width: 44rem; }

.lost__title {
  font-size: var(--fs-h2);
  margin-bottom: 1.3rem;
}

.lost .lead { margin-bottom: 1rem; }
.lost p:not(.lead):not(.lost__meta) { color: var(--muted); }

.lost__act {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2.2rem;
}

.lost__meta {
  margin-top: 2rem;
  font-size: 0.95rem;
  color: var(--muted);
}

@media (max-width: 767px) {
  .lost__act .btn { flex: 1 1 100%; }
}

/* --- Build credit --------------------------------------------------------- */
/* A quiet line, set below the client's own footer content and a step smaller,
   so the credit reads as a signature rather than competing with the practice's
   details. The link is deliberately plain — rel carries only `noopener`, which
   is a security attribute and leaves the link followable. */

.foot__credit {
  padding-bottom: 1.6rem;
  font-size: 0.85rem;
  color: #7B92A2;
}
.foot__credit p { margin: 0; max-width: none; }

.foot__credit a {
  color: var(--mist);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 0.16s var(--ease);
}
.foot__credit a:hover { color: var(--paper); }

/* --- Sticky mobile call bar ----------------------------------------------- */
/* Phone is the only conversion path on this site, so on the device where
   calling is one tap it stays permanently reachable. */

.callbar {
  display: none;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  min-height: 58px;
  padding: 0.5rem 1rem calc(0.5rem + env(safe-area-inset-bottom));
  background: var(--brand);
  color: var(--paper);
  border-top: 1px solid var(--brand-deep);
  font-family: var(--display);
  text-decoration: none;
  transition: transform 0.4s var(--ease), background-color 0.14s var(--ease);
}
.callbar:active { background: var(--brand-deep); }
/* Added by site.js only, and only when the page opens at the top. */
.callbar.is-away { transform: translateY(100%); }
.callbar__label { font-weight: 600; font-size: 0.95rem; opacity: 0.92; }
.callbar__num {
  font-weight: 800;
  font-size: 1.12rem;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* --- Breakpoints ---------------------------------------------------------- */

@media (min-width: 900px) {
  .hero__grid { grid-template-columns: 1.15fr 0.85fr; }
  .split { grid-template-columns: 1fr 1fr; }
  .split--flip .split__fig { order: -1; }
}

@media (min-width: 720px) {
  .foot__in { grid-template-columns: 1fr auto; align-items: end; }
  .foot__meta { text-align: right; }
}

@media (max-width: 899px) {
  .hero__title { max-width: 12ch; }
  /* Stacked, the 4:5 hero portrait eats most of a screen before any copy is
     reached. Crop it to a landscape band so the CTA stays close to the fold. */
  .hero__fig img { aspect-ratio: 4 / 3; object-fit: cover; object-position: center 38%; }
}

/* Mobile: nav collapses into a disclosure panel, call bar appears. */
@media (max-width: 767px) {
  html { scroll-padding-top: 76px; }

  /* .nav is out of flow at this width, so the burger needs to claim the
     free space itself to sit at the right edge. */
  .has-js .burger { display: block; margin-left: auto; }
  .masthead__call { display: none; }
  .masthead__in { min-height: 64px; gap: 0.75rem; }
  .masthead__brand img { height: 32px; }

  .nav {
    position: absolute;
    left: 0; right: 0; top: 100%;
    margin: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
    display: none;
  }
  .nav.is-open { display: block; }
  .nav__list {
    flex-direction: column;
    gap: 0;
    padding: 0.5rem var(--gut) 1rem;
  }
  .nav__list li { border-bottom: 1px solid var(--rule); }
  .nav__list li:last-child { border-bottom: 0; }
  .nav__list a { padding: 0.9rem 0; font-size: 1.05rem; }
  .nav__list a::after { display: none; }

  .callbar { display: flex; }
  body { padding-bottom: 58px; }

  .focus__item--drop { --drop: 0px; }

  .price__row { grid-template-columns: 1fr; gap: 0.75rem; }
  .price__fig { font-size: 2.1rem; }
  .price__fig--text { text-align: left; max-width: none; padding-top: 0; }

  .hero__act .btn { flex: 1 1 100%; }
}

@media (max-width: 520px) {
  .focus { grid-template-columns: 1fr 1fr; }
  .focus__item:last-child { grid-column: span 2; }
  .focus__item:last-child img { aspect-ratio: 16 / 9; object-fit: cover; }
}

/* --- Photograph hover ------------------------------------------------------
   The ban on image hover *zoom, scale and rotate* stands and is unrelated to
   this: moving a photograph under the cursor is the stock template gesture,
   and it also shifts the layout's optical alignment for no reason. Changing
   only the image's colour is a different thing — nothing moves, nothing is
   cropped, and the response reads as the picture waking up.

   Photographs therefore rest a touch cooler and flatter than their source and
   resolve to full richness under the pointer. Scoped to `hover: hover` in its
   entirety, so a touch device — which has no hover and would be stuck with the
   rest state — always gets the full-strength image. */

@media (hover: hover) {
  .hero__fig img,
  .split__fig img {
    filter: saturate(0.9) contrast(0.97) brightness(0.98);
    transition: filter 0.3s var(--ease);
  }
  .hero__fig:hover img,
  .split__fig:hover img { filter: saturate(1.06) contrast(1.03) brightness(1.02); }

  /* The triptych is already duotone, so there is no saturation to give back.
     A contrast and brightness lift is the equivalent gesture on a monochrome
     image, and it holds the same weight as the two rules above. */
  .focus__item img {
    filter: brightness(0.93) contrast(0.96);
    transition: filter 0.3s var(--ease);
  }
  .focus__item:hover img { filter: brightness(1.05) contrast(1.08); }
}

/* --- Reveal on scroll ------------------------------------------------------
   Read this together with the "Motion budget" section of DESIGN.md.

   The hidden state below is unreachable unless site.js puts `.reveal` on an
   element at runtime — and it only ever does that after it has an observer
   watching that element and a failsafe timer armed to undo it. Nothing in this
   stylesheet hides content on its own, so with JavaScript blocked, broken or
   simply slow, the page renders exactly as it did before any of this existed.
   That is the whole reason a reveal is acceptable here at all. */

/* Keyframes rather than a transition, deliberately. A `transition` shorthand
   on .reveal would outrank the transitions already declared on .btn and
   .price__row and quietly kill their hover and press feedback. An animation
   with `backwards` fill covers the stagger delay and then lets go of the
   element completely, so nothing is left overridden once it has landed. */

@keyframes reveal-in {
  from { opacity: 0; transform: translateY(calc(var(--drop, 0px) + 10px)); }
  to   { opacity: 1; transform: translateY(var(--drop, 0px)); }
}

.reveal:not(.is-in) {
  opacity: 0;
  /* Composes with the triptych's static drop instead of flattening it. */
  transform: translateY(calc(var(--drop, 0px) + 10px));
}
.reveal.is-in { animation: reveal-in 0.5s var(--ease) backwards; }

/* Photographs settle in as they decode rather than snapping into place. Same
   rule: applied by script, and only to an image that has not loaded yet. */
@keyframes pic-in { from { opacity: 0; } to { opacity: 1; } }

.pic-fade:not(.is-in) { opacity: 0; }
.pic-fade.is-in { animation: pic-in 0.45s var(--ease) backwards; }

/* --- Reduced motion ------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .focus__item--drop { --drop: 0px; }

  /* site.js skips arming any of this under reduced motion, so these rules only
     matter when the preference is switched on after the page has loaded. They
     drop the element back into place rather than shortening its journey. */
  .reveal, .reveal:not(.is-in), .reveal.is-in {
    opacity: 1 !important;
    transform: translateY(var(--drop, 0px)) !important;
  }
  .pic-fade, .pic-fade:not(.is-in) { opacity: 1 !important; }
  .callbar.is-away { transform: none !important; }

  /* The photograph hover is a colour change rather than motion, so it stays —
     but the crossfade to it does not. The global rule above already zeroes
     every transition-duration; this makes that explicit for the filter. */
  .hero__fig img, .split__fig img, .focus__item img { transition: none !important; }
}

/* --- Print ---------------------------------------------------------------- */

@media print {
  .masthead, .callbar, .hero__act, .price__act, .split__act, .skip { display: none !important; }
  /* A reveal that had not fired yet must not print as a blank block. */
  .reveal, .reveal:not(.is-in), .pic-fade, .pic-fade:not(.is-in) {
    opacity: 1 !important;
    animation: none !important;
    transform: translateY(var(--drop, 0px)) !important;
  }
  body { background: #fff; color: #000; padding-bottom: 0; }
  .band--ink, .cta, .foot { background: #fff !important; color: #000 !important; }
  .band--ink h2, .cta__title, .foot__phone a { color: #000 !important; }
  a[href^="tel:"]::after { content: " (" attr(href) ")"; }
}
