/* ==========================================================================
   Allenby Accounting Solutions
   Design system: brand palette v1.0, August 2026
   ========================================================================== */

:root {
  /* ---- Allenby brand palette v1.0, August 2026 ----
     Forest Green  #1A3028  nav, footer, dark sections, icons, dividers
     Near-Black    #1E1E1E  all body text
     Slate Blue    #477A9E  CTAs, links, eyebrow text
                            Documented as #4A7FA5. Darkened 3% so link text on
                            white and white text on the button both clear WCAG
                            AA at 4.62:1. The brand original measures 4.31:1
                            and fails in every one of its specified uses.
     Soft Tint     #F0F7F3  alternating section backgrounds
     White         #FFFFFF  page background and cards                       */

  --forest:     #1A3028;
  --forest-d:   #14241E;   /* hover and deeper dividers */
  --ink:        #1E1E1E;   /* body text */
  --ink-90:     #2C2C2C;   /* leads, nav links */
  --graphite:   #5A5A5A;   /* secondary text, 7.0:1 on white */
  --graphite-l: #6E6E6E;   /* tertiary text, 5.1:1 on white */
  --bone:       #FFFFFF;   /* light surface and text on dark */
  --bone-d:     #F0F7F3;   /* soft green tint */
  --paper:      #FFFFFF;   /* cards */
  --hairline:   #E4E0D8;   /* card and section borders */
  --hairline-s: #EDEAE3;
  --oxblood:    #477A9E;   /* slate blue, interactive and labels */
  --oxblood-d:  #3E6C8C;   /* slate hover, darkened 10% */
  --oxblood-t:  #EAF1F6;   /* slate wash */
  --accent-lt:  #9FC2DA;   /* slate for dark grounds, 7.48:1 on --forest */

  /* Flagged and resolved. Graphic marks only, never type: at 3.4:1 and 2.6:1
     on white neither is a text colour. Introduced by the service cards, lifted
     to root when the method hero needed the same pair, so the two graphics
     cannot drift into two different reds. */
  --rust:       #B76A5C;
  --sage:       #7A9D74;
  --rust-t:     rgba(183,106,92,0.11);   /* row tint, flagged */
  --sage-t:     rgba(122,157,116,0.15);  /* row tint, settled */

  /* Hero over photo: forest veil, white type */
  --hero-ink:      #FFFFFF;
  --hero-sub:      rgba(255,255,255,0.88);
  --hero-cream:    #FFFFFF;
  --hero-cream-btn:#FFFFFF;
  --hero-btn-text: #1A3028;

  /* Type */
  --display: "Newsreader", "Iowan Old Style", Georgia, serif;
  --body: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  /* Fluid type scale */
  --t-hero:  clamp(2.75rem, 1.4rem + 5.4vw, 6.25rem);
  --t-h1:    clamp(2.25rem, 1.3rem + 3.8vw, 4.25rem);
  --t-h2:    clamp(1.75rem, 1.1rem + 2.6vw, 3rem);
  --t-h3:    clamp(1.3rem, 1.05rem + 0.9vw, 1.75rem);
  --t-h4:    clamp(1.075rem, 1rem + 0.35vw, 1.25rem);
  --t-lead:  clamp(1.075rem, 0.99rem + 0.5vw, 1.375rem);
  --t-body:  1.0125rem;
  --t-small: 0.9375rem;
  --t-micro: 0.75rem;

  /* Rhythm */
  --gap:      clamp(1.25rem, 0.9rem + 1.4vw, 2rem);
  --section:  clamp(4.5rem, 3rem + 7vw, 9.5rem);
  --shell:    1240px;
  --shell-n:  980px;
  --shell-t:  720px;
  --pad:      clamp(1.25rem, 0.6rem + 3vw, 3rem);

  --rule: 1px solid var(--hairline);
  --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; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--t-body);
  line-height: 1.62;
  font-weight: 400;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, video { max-width: 100%; height: auto; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-optical-sizing: auto;   /* the default; declared so it cannot drift */
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: -0.022em;
  margin: 0;
  text-wrap: balance;
}
p { margin: 0; text-wrap: pretty; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; text-decoration: none; }

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

::selection { background: var(--forest); color: var(--bone); }

/* ---------- Layout primitives ---------- */
.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: var(--pad); }
.shell--narrow { max-width: var(--shell-n); }
.shell--text   { max-width: var(--shell-t); }

.section { padding-block: var(--section); position: relative; }
.section--tight { padding-block: calc(var(--section) * 0.62); }
.section--ink { background: var(--forest); color: var(--bone); }
.section--paper { background: var(--bone-d); }
/* Slate sits at 4.25:1 on the soft tint, just under AA. One step darker on
   those sections only, which reads as the same blue but clears 4.56:1. */
/* The method hero is on the same tint without being a .section--paper, so it
   joins the list rather than declaring a second, slightly different blue. */
.section--paper .eyebrow,
.section--paper .txtlink,
.hero--method .eyebrow { color: #447597; }
.section--rule { border-top: var(--rule); }
/* Pull two adjacent sections together where the default rhythm doubles up. */
.section--pt-tight { padding-top: calc(var(--section) * 0.5); }
.section--pb-tight { padding-bottom: calc(var(--section) * 0.5); }

.section--ink h1, .section--ink h2, .section--ink h3 { color: var(--bone); }
.section--ink .lead, .section--ink p { color: rgba(255, 255, 255, 0.76); }
.section--ink .eyebrow { color: rgba(255, 255, 255, 0.5); }
.section--ink .eyebrow::before { background: rgba(255, 255, 255, 0.4); }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--ink); color: var(--bone);
  padding: 0.75rem 1.25rem; font-size: var(--t-small);
}
.skip:focus { left: 0; }

/* ---------- Typographic components ---------- */
.eyebrow {
  display: flex; align-items: center; gap: 0.75rem;
  font-family: var(--body);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--oxblood);
  margin: 0 0 1.5rem;
}
.eyebrow::before {
  content: ""; width: 2rem; height: 1px; background: var(--oxblood); flex: none;
  opacity: 0.55;
}
.eyebrow--plain::before { display: none; }

.hero-title { font-size: var(--t-hero); letter-spacing: -0.035em; line-height: 0.98; }
.h1 { font-size: var(--t-h1); }
.h2 { font-size: var(--t-h2); }
.h3 { font-size: var(--t-h3); }
.h4 { font-size: var(--t-h4); letter-spacing: -0.012em; }
/* Stage arrows inside a display heading. Set smaller and lifted so they read as
   connectors between the words rather than as characters of equal weight. */
.h2__arw {
  font-style: normal; font-family: var(--body); font-weight: 400;
  color: var(--oxblood); font-size: 0.5em;
  vertical-align: 0.24em; margin: 0 0.16em;
}
.section--paper .h2__arw { color: #447597; }

.lead {
  font-size: var(--t-lead);
  line-height: 1.5;
  color: var(--ink-90);
  letter-spacing: -0.012em;
  max-width: 46ch;
}
.muted { color: var(--graphite); }
.small { font-size: var(--t-small); line-height: 1.55; }
.micro {
  font-size: var(--t-micro); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--graphite); font-weight: 500;
}
.accent { color: var(--oxblood); }
.serif-em { font-family: var(--display); font-style: italic; }

.stack > * + * { margin-top: 1.25rem; }
.stack-sm > * + * { margin-top: 0.6rem; }
.stack-lg > * + * { margin-top: 2rem; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.95rem 1.6rem;
  font-size: var(--t-small); font-weight: 500; letter-spacing: -0.005em;
  border: 1px solid var(--oxblood); background: var(--oxblood); color: #fff;
  cursor: pointer; border-radius: 0;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.btn:hover { background: var(--oxblood-d); border-color: var(--oxblood-d); color: #fff; transform: scale(1.02); }
.btn .arw { transition: transform 0.28s var(--ease); }
.btn:hover .arw { transform: translateX(4px); }

.btn--ghost { background: transparent; color: var(--forest); border-color: var(--forest); }
.btn--ghost:hover { background: var(--forest); border-color: var(--forest); color: #fff; }

.btn--onink { background: var(--bone); border-color: var(--bone); color: var(--forest); }
.btn--onink:hover { background: var(--oxblood); border-color: var(--oxblood); color: #fff; }

.btn--ghost-onink { background: transparent; color: var(--bone); border-color: rgba(255,255,255,0.3); }
.btn--ghost-onink:hover { background: transparent; border-color: var(--bone); color: var(--bone); }

.btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }

.txtlink {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: var(--t-small); font-weight: 500;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 3px;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.txtlink:hover { border-color: var(--oxblood); color: var(--oxblood); }
.txtlink .arw { transition: transform 0.25s var(--ease); }
.txtlink:hover .arw { transform: translateX(3px); }
.section--ink .txtlink { border-color: rgba(255,255,255,0.28); }
.section--ink .txtlink:hover { border-color: var(--bone); color: var(--bone); }

/* ---------- Header ---------- */
.hdr {
  position: sticky; top: 0; z-index: 100;
  background: var(--forest);
  border-bottom: 1px solid rgba(255,255,255,0.10);
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease);
}
.hdr.is-stuck { border-bottom-color: rgba(255,255,255,0.18); }
.hdr__in {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; min-height: 74px;
}

.brand { display: flex; align-items: center; gap: 0.7rem; flex: none; }
.brand__mark {
  width: 34px; height: 34px; flex: none;
  background: var(--bone); color: var(--forest);
  display: grid; place-items: center;
  font-family: var(--display); font-size: 1.05rem; line-height: 1;
  padding-bottom: 2px;
}
.brand__wm { display: flex; flex-direction: column; line-height: 1; }
.brand__name { font-family: var(--display); font-size: 1.2rem; letter-spacing: -0.02em; color: var(--bone); }
.brand__sub {
  font-size: 0.5625rem; letter-spacing: 0.2em; text-transform: uppercase;
  color: rgba(255,255,255,0.62); margin-top: 4px; font-weight: 500;
}

.nav { display: flex; align-items: center; gap: 1.75rem; }
.nav a {
  font-size: var(--t-small); color: rgba(255,255,255,0.82);
  /* inline-block because transform does not apply to a non-replaced inline box,
     so the scale below would be silently ignored without it. */
  display: inline-block;
  position: relative; padding-block: 4px;
  transition: color 0.2s ease, transform 0.2s ease;
}
.nav a:hover { transform: scale(1.02); }
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--bone);
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.28s var(--ease);
}
.nav a:hover { color: var(--bone); }
.nav a:hover::after, .nav a[aria-current="page"]::after { transform: scaleX(1); }
.nav a[aria-current="page"] { color: var(--bone); }

.hdr__cta { display: flex; align-items: center; gap: 1.25rem; flex: none; }
.hdr .btn { padding: 0.7rem 1.15rem; }

.burger {
  display: none; width: 42px; height: 42px; flex: none;
  background: transparent; border: 1px solid rgba(255,255,255,0.35); cursor: pointer;
  padding: 0; place-items: center;
}
.burger span {
  display: block; width: 17px; height: 1px; background: var(--bone);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.burger span + span { margin-top: 5px; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.mnav {
  display: none;
  border-top: 1px solid rgba(255,255,255,0.18);
  background: var(--forest);
  padding: 1.5rem var(--pad) 2.25rem;
}
.mnav.is-open { display: block; }
.mnav a {
  display: block; padding: 0.85rem 0;
  font-family: var(--display); font-size: 1.5rem; letter-spacing: -0.02em;
  border-bottom: 1px solid rgba(255,255,255,0.14);
  color: var(--bone);
}
.mnav a[aria-current="page"] { color: #9FC2DA; }
.mnav__foot { margin-top: 1.75rem; display: grid; gap: 0.75rem; }

@media (max-width: 1080px) {
  .nav { display: none; }
  .burger { display: grid; }
}
@media (max-width: 560px) {
  .hdr__cta .btn { display: none; }
}

/* ---------- Hero ---------- */
.hero { padding-block: clamp(3.5rem, 2rem + 8vw, 8rem) clamp(3rem, 2rem + 5vw, 6rem); }
.hero__grid {
  display: grid; grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(2.5rem, 1rem + 6vw, 6rem); align-items: end;
}
.hero__title { margin-bottom: 1.75rem; }
.hero__sub { max-width: 40ch; }

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

/* ---------- Hero: full bleed photo + pine veil ---------- */
.hero--photo {
  position: relative;
  min-height: 600px;
  display: flex;
  align-items: center;
  padding-block: clamp(3.5rem, 2rem + 6vw, 7rem);
  overflow: hidden;
}
.hero__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 30%;
  z-index: 0;
}
.hero__veil {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(90deg,
    rgba(26,48,40,0.95) 0%,
    rgba(26,48,40,0.84) 44%,
    rgba(26,48,40,0.48) 78%,
    rgba(26,48,40,0.62) 100%);
}
.hero--photo .hero__inner { position: relative; z-index: 2; width: 100%; }
.hero__col { max-width: 600px; }
.hero--photo .eyebrow { color: var(--hero-cream); margin-bottom: 1.5rem; }
.hero--photo .eyebrow::before { background: rgba(255,255,255,0.55); }
.hero--photo .hero__title { color: var(--hero-ink); margin-bottom: 1.5rem; }
.hero--photo .hero__sub { color: var(--hero-sub); max-width: 42ch; }

/* Hero buttons over the photo */
.btn--cream { background: var(--hero-cream-btn); border-color: var(--hero-cream-btn); color: var(--hero-btn-text); }
.btn--cream:hover { background: var(--bone-d); border-color: var(--bone-d); color: var(--forest); }
.btn--cream .arw, .btn--cream:hover .arw { color: var(--hero-btn-text); }
.btn--ghost-onphoto { background: transparent; color: var(--hero-ink); border-color: rgba(255,255,255,0.5); }
.btn--ghost-onphoto:hover { background: rgba(255,255,255,0.12); border-color: var(--hero-ink); color: var(--hero-ink); }

@media (max-width: 700px) {
  .hero--photo { min-height: 520px; padding-block: clamp(2.5rem, 2rem + 4vw, 4rem); }
  .hero__veil {
    background: linear-gradient(90deg,
      rgba(26,48,40,0.95) 0%,
      rgba(26,48,40,0.90) 55%,
      rgba(26,48,40,0.76) 100%);
  }
}

/* ---------- Software we work with: interlocking badge band ----------
   Geometry measured off the reference: at 3456px wide, disc 345, halo 460,
   centre pitch 311, wash band 609 tall. That gives:

       halo  / disc   1.33
       pitch / disc   0.90   discs overlap by a tenth, so the row is one chain
       offset/ disc   0.67   top row logo centres sit at y 288, bottom row at
                              520, so 232 over a 345 disc. Cross-checks: halo
                              462 plus offset 232 is 694, against a 677 wash
                              band once the faint edge is allowed for.

   THE BUG THAT MADE EARLIER PASSES LOOK BROKEN. A transform does not change
   layout, so translating the items up and down left them sticking out past a
   row whose height was still just the halo box, and .sw-band's overflow:hidden
   sliced the tops and bottoms of every circle flat. The row now carries
   vertical padding equal to the offset, so the translated items land exactly
   inside it and nothing is cut. overflow:hidden is still there, but now it
   only does the job it is for, which is clipping the ends horizontally.
   ========================================================================== */
.sw-band {
  padding-block: clamp(1rem, 0.7rem + 1.2vw, 2rem);
  overflow: hidden;          /* horizontal bleed only, see the note above */
  /* White at the top, the section tint at the bottom. The band sits between a
     white section and a --bone-d one, so it starts as the colour above it and
     ends as the colour below it: the circles stop being a strip dropped onto
     the page and become the transition between the two sections. This is also
     why the band carries no top rule. */
  /* White through the upper half so the circles read against it, then down to
     the tint of the section below. Starting the fall at the top greyed the
     whole band and flattened everything. */
  background: linear-gradient(to bottom,
    var(--paper) 0%, var(--paper) 45%, var(--bone-d) 100%);
}
/* Large, very soft shapes behind the badges. Pure decoration, so they live on
   a pseudo element rather than in the markup. They fill the gaps the circles
   leave and soften the top edge, so the band dissolves into the page instead
   of ending on a scalloped line of hard arcs. */
.sw {
  /* Ten badges, so the pitch is a tenth of the viewport and the disc is that
     over 0.90. No ceiling: a cap put white gutters back on large monitors. */
  --sw-disc: max(100px, 11.1vw);
  --sw-halo: 1.33;
  --sw-off:  0.335;          /* half of the 0.67 total offset */
  list-style: none; margin: 0; padding: 0;
  display: flex; justify-content: center; align-items: center;
  /* Own stacking context, so .sw__disc's z-index keeps every white disc above
     every halo even though neighbouring badges overlap. */
  position: relative; z-index: 1;
  /* Room for the translated items, or overflow:hidden clips them flat. */
  padding-block: calc(var(--sw-disc) * var(--sw-off));
}
/* The overlap, via negative margin rather than a negative gap.
   gap is specified as a non-negative length, so `gap: -0.43d` is invalid and
   the browser used 0. Every earlier version of this band therefore had the
   halos merely touching, which is why they read as separate pale rings with
   white between them instead of one continuous cloud, and why adjusting the
   ratios changed nothing. Margin accepts negative values, so this actually
   applies: halo 1.33d pulled back 0.43d leaves a 0.90d pitch. */
.sw__i + .sw__i { margin-left: calc(var(--sw-disc) * -0.43); }
.sw__i {
  position: relative; flex: none;
  width: calc(var(--sw-disc) * var(--sw-halo));
  height: calc(var(--sw-disc) * var(--sw-halo));
  display: grid; place-items: center;
}
.sw__i--up   { transform: translateY(calc(var(--sw-disc) * var(--sw-off) * -1)); }
.sw__i--down { transform: translateY(calc(var(--sw-disc) * var(--sw-off))); }

/* Semi-transparent, not flat. The reference's halos deepen where they overlap,
   which is what reads as one cloud rather than ten discs of identical tint;
   opaque circles cannot do that however much they overlap. 13% each, so two
   crossing give 24% and three give 34%.
   Green rather than the slate wash, to sit in the gradient above. The solid
   first line is a fallback for anything without color-mix. */
/* One even tint, blurred. No gradient inside the circle and nothing layered
   behind them: every version that added a shape added an edge, and the edges
   were the problem. Blurring each circle means the only thing with a hard
   outline in the whole band is the white disc itself, and the wash reads as a
   glow the discs are sitting in rather than a ring drawn around each one.

   The blur scales with the badge rather than sitting at a fixed pixel value,
   so the softness holds at every viewport width instead of getting relatively
   crisper as the circles grow. 0.15 of a disc is the 24px that looked right
   at 1440. */
.sw__halo {
  position: absolute; inset: 0; border-radius: 50%;
  background: var(--bone-d);
  background: color-mix(in srgb, var(--sage) 20%, transparent);
  filter: blur(calc(var(--sw-disc) * 0.15));
}
/* The discs overlap, so each sits above its neighbours' halos. */
.sw__disc {
  position: relative; z-index: 1;
  width: var(--sw-disc); height: var(--sw-disc);
  border-radius: 50%; background: var(--paper);
  display: grid; place-items: center;
}
/* Half the circle at most, capped on height too, or a square mark like
   QuickBooks sits far larger than a wide one like Shopify. */
.sw__logo {
  max-width: 50%; max-height: 40%;
  width: auto; height: auto; display: block;
}

/* The interlock is a wide-screen effect. Below this the offsets come off and
   the badges wrap as a plain centred grid. No transforms here, so the row
   needs no extra padding. */
@media (max-width: 1000px) {
  .sw {
    --sw-disc: clamp(76px, 20vw, 100px);
    --sw-halo: 1.16;
    flex-wrap: wrap;
    padding-block: 0;
    gap: clamp(0.3rem, 0.1rem + 0.8vw, 0.7rem);   /* positive, so valid here */
    max-width: 30rem; margin-inline: auto;
  }
  .sw__i--up, .sw__i--down { transform: none; }
  .sw__i + .sw__i { margin-left: 0; }    /* no overlap once they wrap */
  .sw-band { padding-inline: var(--pad); }
}
@media (max-width: 420px) {
  .sw { --sw-disc: clamp(70px, 25vw, 90px); max-width: 22rem; }
}

/* ---------- Home hero: brand ground, drawn underline ----------
   The Toronto photograph and its veil are gone. Same layout, spacing and
   buttons as before; the ground is now the brand green and the headline
   carries the work. */
.hero--brand {
  position: relative;
  min-height: 600px;
  display: flex; align-items: center;
  padding-block: clamp(3.5rem, 2rem + 6vw, 7rem);
  background: linear-gradient(158deg, var(--forest) 0%, var(--forest-d) 100%);
  color: var(--hero-ink);
  overflow: hidden;
}
.hero--brand .hero__inner { position: relative; z-index: 1; width: 100%; }
/* Wider than the shared 600px .hero__col. At the 68px cap "Financial clarity
   starts with" measures about 877px, so a 600px column split it and the
   headline ran to three lines. Scoped to this hero, so every other page's
   column is untouched. The subhead keeps its own narrower measure below, since
   a 900px line of body copy is too long to read comfortably. */
.hero--brand .hero__col { max-width: 920px; }
.hero--brand .hero__title { color: var(--hero-ink); }
.hero--brand .hero__sub { color: var(--hero-sub); max-width: 42ch; }

/* Smaller than --t-hero, which caps at 100px. The underline has to sit under
   one unbroken line, and "books you can trust" at 100px is wider than the
   shell; at the old 44px floor it is wider than a 375px phone. This range
   keeps the phrase on one line from 320px up. */
.hero__title--rule {
  font-size: clamp(2rem, 1.1rem + 4.4vw, 4.25rem);
  text-wrap: balance;
}

/* inline-block so the phrase cannot break across lines. A wrapped inline would
   leave the absolutely positioned stroke measuring only the first fragment. */
.uline { position: relative; display: inline-block; }
.uline__s {
  position: absolute; left: 0; right: 0; top: 100%;
  width: 100%; height: 0.17em; margin-top: -0.05em;
  overflow: visible; pointer-events: none;
}
.uline__s path {
  fill: none; stroke: var(--accent-lt);
  stroke-width: 4; stroke-linecap: round;
  /* Always fully stroked. The reveal is a clip on the svg above, not a dash. */
}

/* The reveal is a clip, not stroke-dasharray, and that is the whole fix.
   Dash based drawing needs the dash length and the path length to agree, but
   vector-effect:non-scaling-stroke computes stroke geometry in screen space
   while pathLength normalises in user space, and preserveAspectRatio="none"
   stretches this box about 2x horizontally and 0.8x vertically. The two
   measurements came apart, so the line stopped short of the end of the phrase
   and left a stub behind on the way out.

   An inset clip has no such coupling. It is a percentage of the rendered box,
   so it is exact at every width, at any font size, with the stroke effect left
   on. Draw 1s, hold 4s, wipe 1s, rest 2s: the right edge opens to reveal, then
   the left edge closes to wipe, so it leaves the same way it arrived. Both
   ends of the loop are fully hidden, so the wrap is invisible. */
.uline__s {
  clip-path: inset(-60% 100% -60% 0);
  animation: uline 8s ease-in-out 0.9s infinite both;
}
/* Vertical insets are negative so the clip only ever cuts left and right. The
   stroke is 3px centred on a path that nearly fills a box about 11px tall, so
   a clip at 0 would shave its top and bottom edges. */
@keyframes uline {
  0%             { clip-path: inset(-60% 100% -60% 0); }
  12.5%, 62.5%   { clip-path: inset(-60% 0 -60% 0); }
  75%, 100%      { clip-path: inset(-60% 0 -60% 100%); }
}

/* The proof line. .ld is the site's own load reveal, and site.js staggers it
   150ms per element, so this lands about 450ms after the headline without any
   timing of its own. */
.hero__stat {
  margin-top: clamp(2rem, 1.5rem + 1.6vw, 3rem);
  padding-top: clamp(1.1rem, 0.9rem + 0.6vw, 1.5rem);
  border-top: 1px solid rgba(255,255,255,0.18);
  max-width: 54ch;
}
.hero__statline {
  margin: 0; color: var(--hero-sub); font-size: var(--t-small); line-height: 1.6;
}
.hero__statline strong { color: var(--hero-ink); font-weight: 600; }

@media (max-width: 700px) {
  .hero--brand { min-height: 0; padding-block: clamp(3rem, 2rem + 5vw, 5rem); }
  .hero--brand .hero__sub { max-width: none; }
}
/* Below about 420px the main range stops being small enough: at 320px it
   floors at 32px, where "books you can trust" measures roughly 293px against
   280px of content width, so the phrase wraps and takes the underline with it.
   This tracks the viewport instead of flooring, and hands back over cleanly at
   421px where the two curves meet. */
@media (max-width: 420px) {
  .hero__title--rule { font-size: clamp(1.55rem, 8.6vw, 2.15rem); }
}

/* Painted finished: the stroke is simply there, with nothing running. */
@media (prefers-reduced-motion: reduce) {
  .uline__s { animation: none; clip-path: none; }
}
/* Same reasoning without the media query. The animation is pure CSS so it runs
   with JS off, but .ld would otherwise hold the text at opacity 0. */
.no-js .uline__s { animation: none; clip-path: none; }

/* ---------- Method page hero (split: headline beside the ledger) ----------
   Was a full bleed stock photograph with white type on a forest veil. The rest
   of the site shows data and diagrams rather than photography, and that hero
   was the last place the rule was broken, so the photo is gone and the space
   it held is now the animated ledger diagnostic. Type is back to ink on the
   soft green tint, which means this hero uses the site's ordinary buttons
   rather than the on-photo pair. No viewport height lock either: the height is
   set by the graphic, and locking a split hero to the viewport only guarantees
   it gets clipped on a short laptop screen. */
.hero--method {
  position: relative;
  background: var(--bone-d);
  border-bottom: var(--rule);
  padding-block: clamp(3rem, 2rem + 5vw, 6rem);
}
.hero--method .hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 540px);
  gap: clamp(2.25rem, 1rem + 4vw, 5rem);
  align-items: center;
}
.hero--method .hero__col { max-width: 640px; }
.hero__mtitle {
  font-family: var(--display); font-weight: 400; color: var(--ink);
  /* Was 120px over a photograph with nothing beside it. Sharing the row with
     the ledger, that size drives the graphic under 300px wide before the
     layout stacks. */
  font-size: clamp(52px, 5.6vw, 86px); line-height: 0.98;
  letter-spacing: -0.035em; margin-bottom: 1.5rem; text-wrap: balance;
}
.hero--method .hero__sub { color: var(--graphite); max-width: 42ch; }
.hero__gfx { min-width: 0; }

@media (max-width: 900px) {
  /* Text above graphic, per the brief. 900px is the site's existing split
     breakpoint, not a new one. */
  .hero--method .hero__inner { grid-template-columns: 1fr; }
  .hero--method .hero__col { max-width: none; }
  .hero__gfx { max-width: 540px; width: 100%; }
}
@media (max-width: 700px) {
  .hero--method { padding-block: clamp(2.5rem, 2rem + 4vw, 4rem); }
  .hero__mtitle { font-size: clamp(46px, 12vw, 72px); }
}

/* Panel: the bordered editorial box */
.panel {
  background: var(--paper);
  border: var(--rule);
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
}
.panel--ink { background: var(--forest); border-color: var(--forest-d); color: var(--bone); }
.panel__title {
  font-family: var(--body); font-size: var(--t-micro); font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--graphite);
  padding-bottom: 1.1rem; margin-bottom: 1.1rem; border-bottom: var(--rule);
}

/* Checklist */
.checks { display: grid; gap: 1.1rem; }
.checks li { display: grid; grid-template-columns: auto 1fr; gap: 0.85rem; align-items: start; }
.checks .tick {
  width: 18px; height: 18px; flex: none; margin-top: 3px;
  border: 1px solid var(--forest); color: var(--forest);
  display: grid; place-items: center; font-size: 10px; line-height: 1;
}
.checks strong { font-weight: 500; letter-spacing: -0.01em; display: block; }
.checks .small { color: var(--graphite); margin-top: 2px; }

/* ---------- Canadian flag mark ---------- */

/* ---------- Hero process line ---------- */
.hero__how {
  margin-top: 1.15rem;
  font-size: var(--t-small);
  color: var(--graphite);
  max-width: 48ch;
  line-height: 1.6;
}
.hero__how strong {
  color: var(--ink); font-weight: 500;
  border-bottom: 1px solid var(--oxblood); padding-bottom: 1px;
}

/* ---------- Stat band ---------- */
/* The marquee closes on a hairline. Without this the badge grid border sits
   directly on it and reads as one doubled line. */
.credband { margin-top: clamp(2.75rem, 1.6rem + 3.2vw, 4.5rem); }
.statband {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border: var(--rule); background: var(--paper);
}
.statband > li {
  padding: clamp(1.4rem, 1rem + 1.5vw, 2.15rem);
  border-right: var(--rule);
  position: relative;
}
.statband > li::before {
  content: ""; position: absolute; top: -1px; left: -1px; width: 34px; height: 2px;
  background: var(--oxblood);
}
.statband > li:last-child { border-right: 0; }
.statband__n {
  display: block; font-family: var(--display);
  font-size: clamp(1.7rem, 1.1rem + 2vw, 2.75rem);
  line-height: 1; letter-spacing: -0.03em; margin-bottom: 0.7rem;
  font-variant-numeric: tabular-nums;
}
.statband__l {
  display: block; font-size: var(--t-small);
  color: var(--graphite); line-height: 1.45; max-width: 22ch;
}
@media (max-width: 860px) {
  .statband { grid-template-columns: repeat(2, 1fr); }
  .statband > li:nth-child(2n) { border-right: 0; }
  .statband > li:nth-child(-n+2) { border-bottom: var(--rule); }
}
@media (max-width: 480px) {
  .statband { grid-template-columns: 1fr; }
  .statband > li { border-right: 0; border-bottom: var(--rule); }
  .statband > li:last-child { border-bottom: 0; }
}

/* ---------- Marquee / credential strip ---------- */
.credstrip {
  border-block: var(--rule);
  padding-block: 1.1rem;
  display: flex; flex-wrap: wrap; gap: 0.75rem 2.5rem; align-items: center;
}
.credstrip li {
  display: flex; align-items: center; gap: 0.55rem;
  font-size: var(--t-small); color: var(--graphite);
}
.credstrip .dot { width: 4px; height: 4px; background: var(--forest); flex: none; }

/* ---------- Stat row ---------- */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); border-top: var(--rule); }
.stats--2 { grid-template-columns: repeat(2, 1fr); }
.stats > li { padding: 2rem 1.5rem 0 0; border-right: var(--rule); }
.stats > li:last-child { border-right: 0; padding-right: 0; }
.stats > li:not(:first-child) { padding-left: 1.5rem; }
.stat__n {
  font-family: var(--display); font-size: clamp(2.25rem, 1.4rem + 3vw, 3.5rem);
  line-height: 1; letter-spacing: -0.03em; display: block; margin-bottom: 0.6rem;
}
.stat__l { font-size: var(--t-small); color: var(--graphite); line-height: 1.45; }

@media (max-width: 780px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
  .stats > li { border-bottom: var(--rule); padding-bottom: 1.75rem; }
  .stats > li:nth-child(2n) { border-right: 0; }
  .stats > li:nth-child(n+3) { border-bottom: 0; }
  .stats > li:nth-child(odd) { padding-left: 0; }
  .stats > li:nth-child(even) { padding-left: 1.25rem; padding-right: 0; }
}

/* ---------- Section head ---------- */
.shead { margin-bottom: clamp(2.5rem, 1.5rem + 3vw, 4.5rem); }
.shead__grid { display: grid; grid-template-columns: 1fr auto; gap: 2rem; align-items: end; }
@media (max-width: 780px) { .shead__grid { grid-template-columns: 1fr; align-items: start; } }

/* ---------- Numbered steps ---------- */
.steps { border-top: var(--rule); }
.step {
  display: grid; grid-template-columns: 5.5rem 1fr 1.05fr;
  gap: clamp(1.25rem, 0.5rem + 2.5vw, 3.5rem);
  padding-block: clamp(2rem, 1.2rem + 2.5vw, 3.25rem);
  border-bottom: var(--rule);
  align-items: start;
}
.step__n {
  font-family: var(--display); font-size: 1.5rem; letter-spacing: -0.02em;
  color: var(--oxblood); font-variant-numeric: tabular-nums;
}
.step__t { margin-bottom: 0.75rem; }
.step__list { display: grid; gap: 0.65rem; }
.step__list li {
  display: grid; grid-template-columns: auto 1fr; gap: 0.75rem;
  font-size: var(--t-small); color: var(--graphite); line-height: 1.5;
}
.step__list li::before { content: ""; width: 14px; height: 1px; background: var(--hairline); margin-top: 0.7em; }

@media (max-width: 860px) {
  .step { grid-template-columns: 3rem 1fr; }
  .step__list { grid-column: 2; }
}

/* ---------- Card grids ---------- */
.grid { display: grid; gap: 0; }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }

.gcard {
  padding: clamp(1.5rem, 1rem + 1.6vw, 2.25rem);
  border-right: var(--rule); border-bottom: var(--rule);
  transition: background 0.3s var(--ease);
}
.gcard:hover { background: var(--paper); }
.grid--3 > .gcard:nth-child(3n) { border-right: 0; }
.grid--2 > .gcard:nth-child(2n) { border-right: 0; }
.gcard__k {
  font-family: var(--display); font-size: 0.9375rem; color: var(--oxblood);
  display: block; margin-bottom: 1rem; font-variant-numeric: tabular-nums;
}
.gcard h3 { margin-bottom: 0.7rem; }
.gcard p { color: var(--graphite); font-size: var(--t-small); }

/* ---------- Advisory tiles: icon at rest, description on reveal ----------
   Ported from the artifact as literally as it goes. The values, the easings,
   the delay on opacity and the max-height approach are all exactly as written
   there; nothing was substituted for display or visibility, which would kill
   the transition.

   Three deliberate departures, all of them things the brief said to change:

   The container is the site's own .gcard, so padding, borders and every
   responsive step come from the grid that was already here. .cell carries no
   box of its own and exists purely as the hook the ported selectors need.

   --ink, --grey and --num are not redefined. They map onto the site's
   existing tokens: --ink is already --ink, --grey becomes --graphite, and the
   number colour becomes --oxblood, which the tiles were already using.

   The artifact's `outline:none` on .cell is not carried over. It would strip
   the focus ring from an element the same file makes focusable, which is the
   one thing that stops these being keyboard operable. The site's own
   :focus-visible ring applies instead. */
.icon-stage{
  position:relative; height:34px; margin-bottom:18px;
}
.icon-stage svg{
  position:absolute; top:0; left:0;
  stroke:var(--ink); fill:none; stroke-width:1.4;
  opacity:1; transform:translateY(0) scale(1);
  transition: opacity .28s ease, transform .28s ease;
}
.cell:hover .icon-stage svg, .cell:focus-within .icon-stage svg{
  opacity:0; transform:translateY(-6px) scale(.94);
}
.desc{
  display:block; color:var(--graphite); font-size:14px; line-height:1.5;
  max-height:0; opacity:0; margin-top:0;
  transform:translateY(-4px);
  transition: max-height .32s ease, opacity .28s ease .04s, margin-top .32s ease, transform .32s ease;
  overflow:hidden;
}
.cell:hover .desc, .cell:focus-within .desc{
  max-height:70px; opacity:1; margin-top:10px; transform:translateY(0);
}
.cell:hover .icon-stage, .cell:focus-within .icon-stage{ height:0; margin-bottom:0; }
.icon-stage{ transition: height .28s ease, margin-bottom .28s ease; }

/* Not in the artifact, kept from the previous build. Without it the reveal
   still fires for these users, it just fires at once instead of over 320ms. */
@media (prefers-reduced-motion: reduce) {
  .icon-stage, .icon-stage svg, .desc { transition: none; }
}

/* Problem grid sat too tight under the section lead. Adds breathing room
   above the cards, and spaces the closing line beneath them. */
.problem-grid { margin-top: clamp(2.25rem, 1.5rem + 2vw, 3.5rem); }
.problem-note { margin-top: clamp(1.5rem, 1.2rem + 1vw, 2.25rem); }

/* Service pillar: the services line sits beneath a rule, pinned to the base
   so the three cards align regardless of description length. */
.pillar { display: flex; flex-direction: column; }
.pillar__svcs {
  margin-top: auto; padding-top: 1.1rem;
  border-top: 1px solid var(--hairline-s);
  font-size: var(--t-micro); line-height: 1.75; letter-spacing: 0.01em;
  color: var(--graphite-l); text-transform: none;
}

.grid--bordered { border-top: var(--rule); border-left: var(--rule); }
.grid--bordered > .gcard { border-right: var(--rule); }
.grid--bordered > .gcard:nth-child(3n) { border-right: var(--rule); }
.grid--bordered.grid--2 > .gcard:nth-child(2n) { border-right: var(--rule); }

@media (max-width: 900px) { .grid--3 { grid-template-columns: repeat(2, 1fr); }
  .grid--3 > .gcard:nth-child(3n) { border-right: var(--rule); }
  .grid--3 > .gcard:nth-child(2n) { border-right: 0; }
}
@media (max-width: 620px) {
  .grid--3, .grid--2 { grid-template-columns: 1fr; }
  .grid--3 > .gcard, .grid--2 > .gcard { border-right: 0 !important; }
  .grid--bordered { border-left: 0; }
}

/* ---------- Service rows ---------- */
/* ---------- Before / After ---------- */
.ba { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border: var(--rule); }
.ba__col { padding: clamp(1.5rem, 1rem + 2vw, 2.5rem); }
.ba__col:first-child { border-right: var(--rule); background: var(--bone-d); }
.ba__col:last-child { background: var(--paper); }
.ba__h {
  font-size: var(--t-micro); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--graphite); margin-bottom: 1.5rem; padding-bottom: 0.9rem;
  border-bottom: var(--rule); font-weight: 500;
}
.ba__col:last-child .ba__h { color: var(--oxblood); }
.ba ul { display: grid; gap: 1rem; }
.ba li { font-size: var(--t-small); line-height: 1.5; display: grid; grid-template-columns: auto 1fr; gap: 0.75rem; }
.ba li::before { content: ""; width: 12px; height: 1px; background: var(--graphite-l); margin-top: 0.7em; }
.ba__col:last-child li::before { background: var(--oxblood); }
@media (max-width: 720px) {
  .ba { grid-template-columns: 1fr; }
  .ba__col:first-child { border-right: 0; border-bottom: var(--rule); }
}

/* ---------- Quote ---------- */
.quote { border-left: 2px solid var(--forest); padding: 0.25rem 0 0.25rem 1.75rem; }
.quote p {
  font-family: var(--display); font-size: var(--t-h3); line-height: 1.3;
  letter-spacing: -0.018em; color: var(--ink);
}
.quote footer { margin-top: 1.1rem; font-size: var(--t-small); color: var(--graphite); }

.scenario { border: var(--rule); background: var(--paper); padding: clamp(1.5rem, 1rem + 2vw, 2.25rem); }
.scenario__meta {
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem;
  font-size: var(--t-micro); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--graphite); padding-bottom: 1rem; margin-bottom: 1.35rem; border-bottom: var(--rule);
}
.scenario__nums { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; margin-top: 1.5rem; padding-top: 1.5rem; border-top: var(--rule); }
.scenario__nums .stat__n { font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.25rem); }
@media (max-width: 620px) { .scenario__nums { grid-template-columns: 1fr 1fr; } }

.disclosure {
  font-size: var(--t-micro); letter-spacing: 0.02em; text-transform: none;
  color: var(--graphite-l); line-height: 1.6; margin-top: 1.5rem;
  border-top: 1px solid var(--hairline-s); padding-top: 1rem;
}

/* ---------- Blog ---------- */
.posts { border-top: var(--rule); }
.post-row {
  display: grid; grid-template-columns: 9rem 1fr auto;
  gap: clamp(1.25rem, 0.5rem + 2.5vw, 3rem);
  padding-block: clamp(1.75rem, 1.2rem + 2vw, 2.75rem);
  border-bottom: var(--rule); align-items: start;
  transition: background 0.3s var(--ease);
}
.post-row:hover { background: var(--paper); }
.post-row__date { font-size: var(--t-small); color: var(--graphite); font-variant-numeric: tabular-nums; }
.post-row h3 { margin-bottom: 0.6rem; max-width: 24ch; }
.post-row p { color: var(--graphite); font-size: var(--t-small); max-width: 60ch; }
@media (max-width: 780px) {
  .post-row { grid-template-columns: 1fr; gap: 0.75rem; }
  .post-row__go { display: none; }
}

.prose { max-width: 66ch; }
.prose > * + * { margin-top: 1.5rem; }
.prose h2 { font-size: var(--t-h3); margin-top: 3rem; }
.prose h3 { font-size: var(--t-h4); font-family: var(--body); font-weight: 600; margin-top: 2.25rem; }
.prose p { color: var(--ink-90); }
.prose ul { display: grid; gap: 0.85rem; }
.prose ul li { display: grid; grid-template-columns: auto 1fr; gap: 0.85rem; color: var(--ink-90); }
.prose ul li::before { content: ""; width: 14px; height: 1px; background: var(--oxblood); margin-top: 0.8em; }
.prose ol { display: grid; gap: 0.85rem; counter-reset: n; }
.prose ol li { display: grid; grid-template-columns: 1.75rem 1fr; gap: 0.5rem; color: var(--ink-90); counter-increment: n; }
.prose ol li::before { content: counter(n, decimal-leading-zero); font-family: var(--display); color: var(--oxblood); }
.prose strong { font-weight: 600; }
.prose blockquote {
  border-left: 2px solid var(--forest); padding-left: 1.5rem; margin-left: 0;
  font-family: var(--display); font-size: var(--t-h4); line-height: 1.45;
}
.prose .callout {
  background: var(--paper); border: var(--rule); padding: 1.5rem;
  font-size: var(--t-small);
}
.prose .callout p { margin: 0; }
.prose .callout > * + * { margin-top: 0.75rem; }

.toc { border-top: var(--rule); border-bottom: var(--rule); padding-block: 1.25rem; }
.toc ol { display: grid; gap: 0.5rem; counter-reset: t; }
.toc li { counter-increment: t; font-size: var(--t-small); }
.toc a { color: var(--graphite); border-bottom: 1px solid transparent; }
.toc a:hover { color: var(--oxblood); border-bottom-color: var(--oxblood); }

/* ---------- FAQ ---------- */
.faq { border-top: var(--rule); }
.faq details { border-bottom: var(--rule); }
.faq summary {
  cursor: pointer; list-style: none; padding-block: 1.4rem;
  display: grid; grid-template-columns: 1fr auto; gap: 1.5rem; align-items: center;
  font-family: var(--display); font-size: var(--t-h4); letter-spacing: -0.015em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; font-family: var(--body); font-size: 1.25rem; color: var(--oxblood);
  transition: transform 0.28s var(--ease);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details > div { padding-bottom: 1.5rem; max-width: 62ch; }
.faq details p { color: var(--graphite); font-size: var(--t-small); }

/* ---------- Form ---------- */
.form { display: grid; gap: 1.15rem; }
.field { display: grid; gap: 0.45rem; }
.field > label { font-size: var(--t-micro); letter-spacing: 0.12em; text-transform: uppercase; color: var(--graphite); font-weight: 500; }
.field input, .field select, .field textarea {
  width: 100%; padding: 0.8rem 0.9rem;
  background: var(--paper); border: var(--rule); border-radius: 0;
  font-size: var(--t-small); color: var(--ink);
  transition: border-color 0.22s var(--ease);
}
.field textarea { resize: vertical; min-height: 120px; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--oxblood); outline: none; }
.field input:user-invalid, .field textarea:user-invalid { border-color: var(--oxblood); }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.15rem; }
@media (max-width: 620px) { .form__row { grid-template-columns: 1fr; } }
.form__note { font-size: var(--t-micro); color: var(--graphite-l); line-height: 1.6; letter-spacing: 0.01em; text-transform: none; }
.form__status { font-size: var(--t-small); padding: 0.9rem 1rem; border: 1px solid var(--oxblood); background: var(--oxblood-t); color: var(--oxblood-d); }
.form__status[hidden] { display: none; }
/* A failed send has to look different from a successful one. Same treatment,
   different colour, so neither is mistaken for the other at a glance. */
.form__status--ok   { border-color: #5F8459; background: #EDF4EA; color: #3F6139; }
.form__status--fail { border-color: #B76A5C; background: #F8EFED; color: #8F4638; }
/* Honeypot: off screen for people, still in the DOM for bots. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.contact-list { display: grid; gap: 1.5rem; }
.contact-list dt { font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--graphite); margin-bottom: 0.35rem; font-weight: 500; }
.contact-list dd { margin: 0; font-family: var(--display); font-size: var(--t-h4); letter-spacing: -0.015em; }
.contact-list dd a { border-bottom: 1px solid var(--hairline); }
.contact-list dd a:hover { border-bottom-color: var(--oxblood); color: var(--oxblood); }

/* ---------- Split ---------- */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 1rem + 4vw, 5rem); align-items: start; }
.split--wide-left { grid-template-columns: 1.2fr 0.8fr; }
.split--wide-right { grid-template-columns: 0.8fr 1.2fr; }
/* Both columns centred on each other rather than hanging from a shared top
   edge. Child margins are zeroed because a stray margin-top on one column
   shifts the row's centre line and the two stop looking level. */
.split--mid { align-items: center; }
.split--mid > * { margin-block: 0; }
.split--mid .signature { text-align: center; }

@media (max-width: 880px) { .split, .split--wide-left, .split--wide-right { grid-template-columns: 1fr; } }
/* Stacked, the columns are no longer beside anything, and a centred pull quote
   sitting above left aligned body copy reads as a mistake. */
@media (max-width: 880px) {
  .split--mid .signature { text-align: left; }
  .split--mid > * + * { margin-top: 1.5rem; }
}

/* ---------- Pill list ---------- */
.pills { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pills li {
  border: var(--rule); padding: 0.45rem 0.85rem;
  font-size: var(--t-small); color: var(--graphite); background: var(--paper);
}
.section--ink .pills li { border-color: rgba(255,255,255,0.2); background: transparent; color: rgba(255,255,255,0.8); }

/* ---------- CTA band ---------- */
.cta-band { text-align: left; }
.cta-band h2 { max-width: 20ch; margin-bottom: 1.5rem; }
.cta-band .lead { max-width: 52ch; margin-bottom: 2.25rem; }

/* ---------- Breadcrumb ---------- */
.crumbs { display: flex; flex-wrap: wrap; gap: 0.5rem; font-size: var(--t-micro); letter-spacing: 0.1em; text-transform: uppercase; color: var(--graphite-l); padding-top: 2rem; }
.crumbs a:hover { color: var(--oxblood); }
.crumbs span[aria-hidden] { opacity: 0.5; }

/* ---------- Footer ---------- */
.ftr { background: var(--forest); color: rgba(255,255,255,0.7); padding-block: clamp(3rem, 2rem + 4vw, 5rem) 2rem; }
.ftr__top { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 2.5rem; padding-bottom: 3rem; border-bottom: 1px solid rgba(255,255,255,0.14); }
.ftr .brand__mark { background: var(--bone); color: var(--forest); }
.ftr .brand__name { color: var(--bone); }
.ftr .brand__sub { color: rgba(255,255,255,0.5); }
.ftr__blurb { font-size: var(--t-small); max-width: 34ch; margin-top: 1.5rem; color: rgba(255,255,255,0.62); }
.ftr__h { font-size: var(--t-micro); letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255,255,255,0.45); margin-bottom: 1.1rem; font-weight: 500; }
.ftr__list { display: grid; gap: 0.65rem; font-size: var(--t-small); }
.ftr__list a { color: rgba(255,255,255,0.72); transition: color 0.22s var(--ease); }
.ftr__list a:hover { color: var(--bone); }
.ftr__bot { display: flex; flex-wrap: wrap; gap: 0.75rem 2rem; justify-content: space-between; padding-top: 2rem; font-size: var(--t-micro); color: rgba(255,255,255,0.45); letter-spacing: 0.04em; }
@media (max-width: 880px) { .ftr__top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .ftr__top { grid-template-columns: 1fr; gap: 2rem; } }

/* ==========================================================================
   Diagrams
   ========================================================================== */

/* ---------- Stage flow ---------- */
.flow {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
  gap: 0; align-items: stretch;
}
.flow__join {
  display: grid; place-items: center; gap: 0.5rem;
  padding-inline: clamp(0.5rem, 0.2rem + 1vw, 1.25rem);
  align-content: center;
}
.flow__arrow { color: var(--oxblood); font-size: 1.1rem; line-height: 1; }
.flow__label {
  font-size: 0.5625rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--graphite-l); white-space: nowrap; font-weight: 500;
}
.stage {
  display: flex; flex-direction: column;
  border: var(--rule); background: var(--paper);
  padding: clamp(1.1rem, 0.8rem + 1vw, 1.6rem);
}
.stage { position: relative; }
/* Progression bar: same accent throughout, deepening across the four stages.
   Gives the sequence a visual direction without inventing new brand colours. */
.stage::before {
  content: ""; position: absolute; inset: -1px -1px auto -1px; height: 3px;
  background: var(--forest); opacity: var(--tone, 0.4);
}
.stage--start { background: transparent; border-style: dashed; border-color: var(--hairline); }
.stage--focus { background: var(--forest); border-color: var(--forest); color: var(--bone); }
.stage--end { background: var(--oxblood-t); border-color: #CFE0EC; }

/* Simplified stage card: title, one italic line, one summary line */
.stage__sub {
  font-family: var(--display); font-style: italic;
  font-size: 0.9375rem; line-height: 1.45; color: var(--graphite);
  margin-bottom: 1.1rem;
}
.stage--focus .stage__sub { color: rgba(255,255,255,0.72); }
.stage__line {
  font-size: 0.8125rem; line-height: 1.55; color: var(--ink-90);
  margin-top: auto; padding-top: 0.95rem; border-top: 1px solid var(--hairline-s);
}
.stage--focus .stage__line { color: rgba(255,255,255,0.82); border-top-color: rgba(255,255,255,0.16); }
.stage--start .stage__line { border-top-color: var(--hairline); }
.stage--end .stage__line { border-top-color: #CFE0EC; }

/* Summary rail under a flow */
.flow-rail {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 0.5rem 1rem; margin-top: clamp(2rem, 1.5rem + 1.5vw, 3rem);
}
.flow-rail b {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(1.05rem, 0.9rem + 0.7vw, 1.45rem); letter-spacing: -0.02em;
}
.flow-rail i { color: var(--oxblood); font-style: normal; }
.flow-rail__note {
  flex-basis: 100%; text-align: center; font-size: var(--t-small);
  color: var(--graphite); margin-top: 0.75rem;
}

/* Founder portrait, monogram until a photograph exists */
.portrait {
  aspect-ratio: 4 / 5; border: var(--rule); background: var(--bone-d);
  overflow: hidden;
}
/* The crop is already 4:5, so cover has nothing to trim. It is kept as a
   guard: if the frame ratio is ever changed, the photo adapts instead of
   distorting. */
.portrait img, .portrait__img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.portrait__cap {
  font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--graphite); margin-top: 0.9rem; font-weight: 500;
}
.signature {
  font-family: var(--display); font-style: italic;
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.5rem); letter-spacing: -0.02em;
  color: var(--ink); margin-top: 1.75rem;
}
/* Founder lede: photo beside the bio, stacked before the column gets tight.
   1080px rather than the split's own 880px, because at half a 1240px shell a
   280px photo leaves the text about 290px, which is too narrow for the
   paragraph beside it. */
.founder__lede {
  display: grid; grid-template-columns: 280px minmax(0, 1fr);
  gap: clamp(1.25rem, 1rem + 1.5vw, 2rem);
  align-items: start; margin-top: 1.5rem;
}
@media (max-width: 1080px) {
  .founder__lede { grid-template-columns: 1fr; }
  .founder__lede .portrait { max-width: 280px; }
}
/* The family note. Same italic serif as .signature, one step quieter, so it
   reads as an aside rather than another line of credentials. */
.signature--soft {
  color: var(--graphite);
  font-size: clamp(1rem, 0.94rem + 0.3vw, 1.15rem);
  margin-top: 1.25rem;
}

.signature span {
  display: block; font-family: var(--body); font-style: normal;
  font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--graphite); margin-top: 0.6rem; font-weight: 500;
}
.stage__n {
  font-size: 0.5625rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--oxblood); font-weight: 500; margin-bottom: 0.5rem;
}
.stage--focus .stage__n { color: #9FC2DA; }
.stage__t {
  font-family: var(--display); font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.3rem);
  letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1rem;
}
.stage__list { display: grid; gap: 0.4rem; margin-top: auto; }
.stage__list li {
  font-size: 0.8125rem; line-height: 1.4; color: var(--graphite);
  background: var(--bone); border: 1px solid var(--hairline-s);
  padding: 0.55rem 0.7rem;
}
.stage--focus .stage__list li { background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.14); color: rgba(255,255,255,0.8); }
.stage--end .stage__list li { background: transparent; border: 0; padding: 0.15rem 0; display: grid; grid-template-columns: auto 1fr; gap: 0.55rem; color: var(--ink-90); }
.stage--end .stage__list li::before {
  content: "\2713"; color: var(--oxblood); font-size: 0.7rem; margin-top: 0.15em;
}
.stage__note { font-size: 0.8125rem; line-height: 1.45; color: rgba(255,255,255,0.7); margin-top: auto; }
.stage__hub {
  border: 1px dashed rgba(255,255,255,0.3); padding: 1.1rem 0.75rem; text-align: center;
  font-size: 0.8125rem; color: rgba(255,255,255,0.9); margin-bottom: 0.9rem;
  font-family: var(--display); letter-spacing: -0.01em;
}

@media (max-width: 1000px) {
  .flow { grid-template-columns: 1fr auto 1fr; }
  .flow__join--wrap { display: none; }
}
@media (max-width: 720px) {
  .flow { grid-template-columns: 1fr; }
  .flow__join, .flow__join--wrap { display: grid; grid-auto-flow: column; padding: 0.85rem 0; }
  .flow__arrow { transform: rotate(90deg); }
}

/* ---------- Drift timeline ---------- */
.drift { border-top: var(--rule); }
.drift__row {
  display: grid; grid-template-columns: 7rem 1fr 1fr;
  gap: clamp(1rem, 0.5rem + 2vw, 2.5rem);
  padding-block: clamp(1.5rem, 1rem + 1.5vw, 2.25rem);
  border-bottom: var(--rule); align-items: start; position: relative;
}
.drift__when {
  font-family: var(--display); font-size: clamp(1.1rem, 0.9rem + 0.8vw, 1.5rem);
  letter-spacing: -0.02em; line-height: 1.1;
}
.drift__when span { display: block; font-family: var(--body); font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--graphite-l); margin-top: 0.4rem; font-weight: 500; }
.drift__bar { height: 6px; background: var(--hairline-s); margin-bottom: 1rem; position: relative; overflow: hidden; }
.drift__bar i { position: absolute; inset: 0 auto 0 0; background: var(--forest); display: block; }
.drift__what h4 { font-family: var(--body); font-weight: 600; font-size: var(--t-small); margin-bottom: 0.45rem; letter-spacing: 0; }
.drift__what p { font-size: var(--t-small); color: var(--graphite); }
.drift__cost { font-size: var(--t-small); color: var(--graphite); }
.drift__cost strong { display: block; font-family: var(--display); font-size: var(--t-h4); color: var(--ink); font-weight: 400; margin-bottom: 0.4rem; letter-spacing: -0.015em; }
@media (max-width: 820px) {
  .drift__row { grid-template-columns: 1fr; gap: 1rem; }
}

/* ---------- Ledger visual ---------- */
.ledger { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border: var(--rule); }
.ledger__side { padding: clamp(1.25rem, 1rem + 1.5vw, 2rem); }
.ledger__side:first-child { border-right: var(--rule); background: var(--bone-d); }
.ledger__side:last-child { background: var(--paper); }
.ledger__h {
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  font-size: var(--t-micro); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--graphite); padding-bottom: 0.9rem; margin-bottom: 1.1rem; border-bottom: var(--rule); font-weight: 500;
}
.ledger__side:last-child .ledger__h { color: var(--oxblood); }
.lrow {
  display: grid; grid-template-columns: 1fr auto auto;
  gap: 0.75rem; align-items: center;
  padding: 0.6rem 0; border-bottom: 1px dashed var(--hairline-s);
  font-size: 0.8125rem;
}
.lrow:last-child { border-bottom: 0; }
.lrow__name { color: var(--ink-90); }
.lrow__val { font-variant-numeric: tabular-nums; color: var(--graphite); letter-spacing: -0.01em; }
.lrow__flag {
  width: 16px; height: 16px; display: grid; place-items: center;
  font-size: 9px; line-height: 1; border: 1px solid currentColor; flex: none;
}
.lrow--bad .lrow__flag { color: var(--oxblood); }
.lrow--bad .lrow__val { color: var(--oxblood); text-decoration: line-through; text-decoration-thickness: 1px; }
.lrow--ok .lrow__flag { color: var(--forest); }
.lrow--ok .lrow__val { color: var(--ink); }
.ledger__foot {
  margin-top: 1.25rem; padding-top: 0.9rem; border-top: var(--rule);
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: var(--t-micro); letter-spacing: 0.1em; text-transform: uppercase; color: var(--graphite);
}
.ledger__foot strong { font-family: var(--display); font-size: var(--t-body); letter-spacing: -0.01em; text-transform: none; font-weight: 400; }
.ledger__side:last-child .ledger__foot strong { color: var(--oxblood); }
@media (max-width: 760px) {
  .ledger { grid-template-columns: 1fr; }
  .ledger__side:first-child { border-right: 0; border-bottom: var(--rule); }
}

.dgm-cap {
  font-size: var(--t-micro); color: var(--graphite-l); letter-spacing: 0.02em;
  text-transform: none; margin-top: 1.25rem; line-height: 1.6;
}

/* ==========================================================================
   Client stories carousel
   ========================================================================== */
.stories { position: relative; }

/* Ring carousel. Cards are absolutely positioned and placed by their circular
   distance from the active index, so the sequence loops with no start or end. */
.stories__stage {
  position: relative; overflow: hidden;
  min-height: 420px;
  margin-inline: calc(var(--pad) * -1); padding-inline: var(--pad);
}
.stories__ring { position: relative; height: 100%; display: block; }

.story {
  position: absolute; top: 0; left: 50%;
  width: clamp(270px, 74vw, 380px); height: 100%;
  display: flex; flex-direction: column;
  border: var(--rule); background: var(--paper);
  padding: clamp(1.5rem, 1rem + 1.6vw, 2.25rem);
  opacity: 0.42;
  transform: translateX(-50%);
  transition: transform 0.55s var(--ease), opacity 0.55s var(--ease),
              background 0.55s var(--ease), border-color 0.55s var(--ease);
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .story { transition: opacity 0.2s linear, background 0.2s linear; }
}

/* Without JS the ring cannot position itself, so fall back to a plain
   readable stack rather than a pile of overlapping cards. */
.no-js .stories__stage { min-height: 0; overflow: visible; }
.no-js .stories__ring { display: grid; gap: 1.5rem; }
.no-js .story {
  position: static; width: auto; height: auto;
  opacity: 1; transform: none;
}
.story__mark {
  font-family: var(--display); font-size: 2.75rem; line-height: 0.7;
  color: var(--forest); margin-bottom: 1.25rem; display: block;
}
.story__quote {
  font-family: var(--display); font-size: clamp(1.02rem, 0.95rem + 0.4vw, 1.2rem);
  line-height: 1.4; letter-spacing: -0.016em; color: var(--ink);
  margin-bottom: 2rem;
}
.story__who {
  margin-top: auto; padding-top: 1.25rem; border-top: var(--rule);
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
}
.story__name {
  font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: 500; color: var(--ink); display: block;
}
.story__co { font-size: var(--t-small); color: var(--graphite); display: block; margin-top: 0.35rem; }
.story__tag {
  font-size: var(--t-micro); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--oxblood); white-space: nowrap;
}

/* Exactly one card is active at a time: whichever sits in the centre. */
.story.is-active { background: var(--ink); border-color: var(--ink); opacity: 1; }
.story.is-active .story__quote { color: var(--bone); }
.story.is-active .story__name { color: var(--bone); }
.story.is-active .story__co { color: rgba(255,255,255,0.65); }
.story.is-active .story__who { border-top-color: rgba(255,255,255,0.18); }
.story.is-active .story__mark { color: #9FC2DA; }
.story.is-active .story__tag { color: #9FC2DA; }

.stories__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; margin-top: 1.75rem; flex-wrap: wrap;
}
.stories__dots { display: flex; gap: 0.5rem; align-items: center; }
.stories__dot {
  width: 26px; height: 3px; padding: 0; border: 0; cursor: pointer;
  background: var(--hairline); transition: background 0.3s var(--ease);
}
.stories__dot:hover { background: var(--graphite-l); }
.stories__dot[aria-selected="true"] { background: var(--oxblood); }
.stories__count {
  font-size: var(--t-micro); letter-spacing: 0.14em; color: var(--graphite);
  font-variant-numeric: tabular-nums; margin-left: 0.75rem;
}
.no-js .stories__foot { display: none; }

.stories__nav { display: flex; gap: 0.6rem; }
.stories__btn {
  width: 46px; height: 46px; border: 1px solid var(--hairline);
  background: transparent; color: var(--ink); cursor: pointer;
  display: grid; place-items: center; font-size: 1rem; line-height: 1;
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease), color 0.25s var(--ease);
}
.stories__btn:hover:not(:disabled) { border-color: var(--ink); background: var(--ink); color: var(--bone); }
.stories__btn:disabled { opacity: 0.3; cursor: default; }
.no-js .stories__nav { display: none; }

/* ==========================================================================
   Why choose Allenby
   ========================================================================== */
.why { display: grid; grid-template-columns: repeat(2, 1fr); border-top: var(--rule); border-left: var(--rule); }
.why > li {
  --why-pad: clamp(1.3rem, 1rem + 1.15vw, 1.9rem);
  padding: var(--why-pad);
  border-right: var(--rule); border-bottom: var(--rule);
}
/* The illustration is pulled back out through the card's own padding so it
   meets the border on three sides. Holding the padding in a variable keeps the
   two in step: change the padding and the bleed follows it. The value itself is
   unchanged, so the text below sits exactly where it did before. */
.why__art {
  display: block;
  width: calc(100% + 2 * var(--why-pad));
  height: auto;
  margin: calc(-1 * var(--why-pad)) calc(-1 * var(--why-pad)) var(--why-pad);
}
.why__n {
  font-family: var(--display); font-size: var(--t-small); color: var(--oxblood);
  display: block; margin-bottom: 0.55rem; font-variant-numeric: tabular-nums;
}
.why__t {
  font-family: var(--display); font-size: clamp(1.15rem, 1.02rem + 0.55vw, 1.4rem);
  letter-spacing: -0.022em; line-height: 1.18; margin-bottom: 0.85rem;
}
/* These boxes carry a heading only. Nothing below it, so nothing to space to. */
.why__t:last-child { margin-bottom: 0; }
.why__p { font-size: var(--t-small); color: var(--graphite); line-height: 1.6; max-width: 46ch; }
.section--ink .why { border-color: rgba(255,255,255,0.16); }
.section--ink .why > li { border-color: rgba(255,255,255,0.16); }
.section--ink .why__t { color: var(--bone); }
.section--ink .why__p { color: rgba(255,255,255,0.68); }
.section--ink .why__n { color: #9FC2DA; }
@media (max-width: 760px) {
  .why { grid-template-columns: 1fr; border-left: 0; }
  .why > li { border-right: 0; }
}

/* Closing line on a section, statement plus inline link. */
.sectcta {
  margin-top: clamp(2rem, 1.5rem + 1.5vw, 3rem);
  font-size: var(--t-lead);
  line-height: 1.5;
  letter-spacing: -0.012em;
  color: var(--ink-90);
}
.sectcta .txtlink { white-space: nowrap; }

/* ==========================================================================
   Logo marquee
   Pure CSS. The track holds two identical groups; each group carries its own
   trailing gap as padding-right, so the track width is exactly 2 x (group +
   gap) and translateX(-50%) lands on a perfect seam. Putting the gap on the
   track instead would leave a half gap jump on every loop.
   ========================================================================== */
.marquee {
  background: var(--bone);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  padding: 48px 0;
  overflow: hidden;
}
.marquee__label {
  text-align: center;
  font-family: var(--body);
  font-size: 12px;
  line-height: 1;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--oxblood);
  margin: 0 0 32px;
}
/* Clipped to the same content width as the panel below, so the logos enter and
   leave in line with that box rather than running to the viewport edge. The
   cream band and its hairlines still span full width. */
.marquee__viewport {
  overflow: hidden;
  width: calc(min(100%, var(--shell)) - 2 * var(--pad));
  margin-inline: auto;
}

.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee-scroll 24s linear infinite;
}
.marquee__group {
  display: flex;
  align-items: center;
  gap: 120px;
  padding-right: 120px;  /* the trailing gap that makes the seam exact */
  margin: 0;
  flex: none;
}
.marquee__group li { display: flex; align-items: center; flex: none; }

.marquee__logo {
  height: 64px;
  width: auto;
  max-width: 220px;
  object-fit: contain;
  filter: grayscale(100%);
  opacity: 0.65;
  /* No blend mode. Every logo now has a real alpha channel, and multiply would
     wipe out the light elements: Beswick's cream outlines, the Dutch Dreams
     crest highlights and the white inside The Meat Dept. */
  transition: filter 0.3s ease, opacity 0.3s ease;
}
.marquee__logo:hover { filter: grayscale(0%); opacity: 1; }

/* Square marks render only 64px wide against 213px for the wide logos, so
   matching on height alone leaves them looking half the size. Extra height
   brings their visual area closer to the rest of the row. */
.marquee__logo--tall { height: 86px; max-width: 150px; }

/* Pale, sparse marks wash out once greyscaled. Fergo measures 156/255 mean
   luminance at 9% ink coverage, against 8/255 for Canadian Choice. This pulls
   it back to roughly the weight of the other logos. */
.marquee__logo--boost {
  filter: grayscale(100%) contrast(1.5) brightness(0.72);
  opacity: 0.82;
}
.marquee__logo--boost:hover { filter: grayscale(0%) contrast(1.08); opacity: 1; }
@media (hover: none) {
  .marquee__logo--boost { filter: grayscale(0%) contrast(1.08); opacity: 1; }
}

/* Touch devices never fire hover, so the logos would be permanently grey. */
@media (hover: none) {
  .marquee__logo { filter: grayscale(0%); opacity: 1; }
}

@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@media (max-width: 767px) {
  .marquee { padding: 36px 0; }
  .marquee__track { animation-duration: 16s; }
  .marquee__group { gap: 72px; padding-right: 72px; }
  .marquee__logo { height: 52px; max-width: 170px; }
}

/* Continuous motion with no pause is a vestibular trigger, so it stops here
   and the row becomes a normal horizontal scroll. */
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee__viewport { overflow-x: auto; }
  .marquee__group:last-child { display: none; }
}

/* ---------- Reveal ---------- */
/* Reveal. .rv waits for the element to scroll into view, .ld runs once on load
   and is used for the hero only. Both share the same motion so the page reads
   as one behaviour. --rvd carries the stagger, assigned per section in site.js
   rather than hand written into the markup. */
.rv, .ld { opacity: 0; transform: translateY(24px); }
.rv.is-in, .ld.is-in {
  opacity: 1; transform: none;
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
  transition-delay: var(--rvd, 0ms);
}
/* The cleanup section is the differentiator, so it arrives fractionally slower. */
.rv--slow.is-in { transition-duration: 0.8s; }
/* Shorter travel on a phone: 24px of movement in a viewport this narrow reads
   as a jump rather than a settle. */
@media (max-width: 640px) { .rv, .ld { transform: translateY(12px); } }
@media (prefers-reduced-motion: reduce) {
  .rv, .rv.is-in, .ld, .ld.is-in { opacity: 1; transform: none; transition: none; }
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}
.no-js .rv, .no-js .ld { opacity: 1; transform: none; }

/* ---------- Problem to service routing, /what-we-fix/ ---------- */
/* A follow-up line, not a second headline: same serif, well under half the
   size of the h1 above it so the hierarchy is unmistakable. */
.h1__after {
  max-width: 52ch;
  font-family: var(--display); font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.35rem);
  letter-spacing: -0.018em; line-height: 1.35; color: var(--graphite);
  margin-top: 1.15rem;
}
/* Full width headline: at 68px across the shell it lands on three lines, and
   "complex, broken, or badly behind" holds together across the last two.

   This carried a max-width of 44ch, which was a bug. The ch unit resolves
   against the element's own font size, and .fix-head is a plain div inheriting
   the 16px body size, so 44ch computed to about 350px rather than the 1400 it
   implied. The headline was squeezed into eleven lines. No max-width now, so
   the h1's own text-wrap:balance does the work. */
.fix-head { max-width: none; }
/* The h1 already balances its lines. Left aligned to the shell edge so it lines
   up with the eyebrow above it and the diagram below. */
.fix-head .h1 { text-wrap: balance; }
.routes { margin-top: clamp(2.5rem, 1.5rem + 3vw, 4.5rem); }
.routes__svg { display: block; width: 100%; height: auto; }

/* Motion. Paused at the 0% keyframe by default, so the diagram renders in its
   "before" state with no JS: lines undrawn, pills muted. site.js adds .play on
   first intersect and never removes it. --d is the per-element delay, computed
   in routing.py from the routing itself. */
.rt-line {
  stroke-dasharray: 100; stroke-dashoffset: 100;
  animation: rt-draw 700ms ease-out var(--d) 1 both paused;
}
.rt-pill rect { animation: rt-box 400ms ease-out var(--d) 1 both paused; }
.rt-pill text { animation: rt-txt 400ms ease-out var(--d) 1 both paused; }

@keyframes rt-draw { to { stroke-dashoffset: 0; } }
@keyframes rt-box  { from { stroke: #E4E0D8; } to { stroke: #477A9E; } }
@keyframes rt-txt  { from { fill: #9A9A9A; }  to { fill: #1E1E1E; } }

.routes.play .rt-line,
.routes.play .rt-pill rect,
.routes.play .rt-pill text { animation-play-state: running; }

/* Reduced motion: the finished state, painted immediately. Not "animation
   disabled", which would leave every line undrawn and every pill greyed out. */
@media (prefers-reduced-motion: reduce) {
  .rt-line { animation: none; stroke-dashoffset: 0; }
  .rt-pill rect { animation: none; stroke: #477A9E; }
  .rt-pill text { animation: none; fill: #1E1E1E; }
}
/* One diagram now, and it is the desktop one. Below 768px it is hidden and the
   accordions below take over. The upright SVG that used to fill this gap has
   been removed: it was the no-JS and reduced-motion path for a phone, and the
   accordions are both of those already, with real headings and lists instead of
   an aria-label on a picture. */
@media (max-width: 767px) {
  .routes__svg { display: none; }
  /* Nothing above the accordions any more, so the wrapper's own top margin is
     the only thing separating them from the heading. */
  .routes { margin-top: 0; }
}

/* ==========================================================================
   Animated service cards
   White card on the Soft Green Tint section, same family as every other card
   on the site. The graphic IS the card: no inner panel, no drop shadow, no
   browser chrome, no colour blooms. The animated ledger, calendar and chart
   sit directly on the card surface.

   Every animation is paused by default and renders its 0% keyframe, which is
   the "before" state. site.js adds .play on first intersect, the animation
   runs once and fill-mode forwards holds the resolved state. Nothing replays.
   ========================================================================== */
.svc {
  --svc-accent: #477A9E;   /* slate blue, 4.62:1 on white */
  --svc-ok:     var(--sage);
  --svc-flag:   var(--rust);
  --svc-gold:   #C9A24D;
  --svc-fill:   #DCE6ED;   /* graphic primary, cool light slate */
  --svc-fill-2: #EDF2F6;   /* graphic secondary */
  --svc-tint:   #F0F7F3;   /* brand soft tint, empty calendar cells */
}

.svc-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
/* Spec: single column below 1024. Capped so a stacked card does not stretch
   to the full 1240 shell, which would leave the graphic marooned. */
@media (max-width: 1023px) { .svc-grid { max-width: 520px; margin-inline: auto; } }
@media (min-width: 1024px) { .svc-grid { grid-template-columns: repeat(3, 1fr); gap: 1.75rem; } }

.svc-card {
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: 20px;
  overflow: hidden;
}

/* ---------- graphic zone ---------- */
/* Fixed height so all three graphics share a baseline regardless of content. */
.svc-card__gfx {
  display: grid; align-content: center; justify-items: stretch;
  height: 172px;
  padding: 0 clamp(1.5rem, 1.2rem + 1vw, 2rem);
}
/* The graphic zone is a fixed height so the three cards line up. Each graphic
   therefore has to fit inside it, and both of these are taller than they are
   wide relative to the zone. Capping the width caps the height with it.
   The chart is 200x132, so height is 0.66 of width: at the full 300px inner
   width it wanted 198px inside a 172px zone and was being cut top and bottom
   by the card's overflow:hidden. */
.svc-card__gfx--cal,
.svc-card__gfx--chart { justify-items: center; }
.svc-card__gfx--cal > *   { width: 100%; max-width: 196px; }
.svc-card__gfx--chart > * { width: 100%; max-width: 252px; }

/* ---------- card copy ---------- */
.svc-card__body { padding: 0 clamp(1.5rem, 1.2rem + 1vw, 2rem) 1.85rem; }
.svc-card__k {
  display: block;
  font-family: var(--body); font-size: 11px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--svc-accent); margin-bottom: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.svc-card__t {
  font-family: var(--body); font-size: 19px; font-weight: 600;
  line-height: 1.3; letter-spacing: -0.012em;
  color: var(--ink); margin-bottom: 0.6rem;
}
.svc-card__p { font-size: 14px; line-height: 1.6; color: var(--graphite); }

/* ==========================================================================
   Animation timing
   Each element loops on the SAME duration as every other element in its card,
   with its stagger carried by animation-delay. Delay applies once, so after
   the first pass each element repeats on its own continuous cycle and the
   whole composition holds phase indefinitely with no drift.

   That is why the secondary offsets (the 620ms before a flag resolves, the
   130ms before a check pops) live inside the keyframes as percentages rather
   than in animation-delay. Two different delays on one loop would put those
   elements permanently out of step with the rows they belong to.

   Every cycle is build, hold, reset. The reset is ordered deliberately:
   the resolved state clears first, the error state returns, and only then does
   the layout scatter, so the graphic never shows a contradictory frame.
   ========================================================================== */

/* ==========================================================================
   01 cleanup: six ledger rows settle from messy to aligned, flags resolve
   Loop 6000ms. Rows stagger 0 to 450ms.
   ========================================================================== */
.lg { display: block; }
.lg__row {
  display: flex; align-items: center; gap: 8px;
  height: 14px; margin-top: 8px;
  transform: translateY(var(--dy)) rotate(var(--dr));
  animation: lg-settle 6000ms cubic-bezier(0.65,0,0.35,1) var(--del) infinite both paused;
}
.lg__row:first-child { margin-top: 0; }
.lg__a, .lg__b { height: 7px; border-radius: 3.5px; background: var(--svc-fill); }
.lg__b { background: var(--svc-fill-2); }
.lg__flag { position: relative; flex: none; width: 13px; height: 13px; margin-left: auto; }
.lg__flag svg { position: absolute; inset: 0; width: 13px; height: 13px; }
.lg__flag path { fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.lg__x  { stroke: var(--svc-flag); }
.lg__ck { stroke: var(--svc-ok); }
.lg__x  { animation: lg-x-cycle  6000ms ease-out var(--del) infinite both paused; }
.lg__ck { animation: lg-ck-cycle 6000ms cubic-bezier(0.34,1.4,0.64,1) var(--del) infinite both paused; }

@keyframes lg-settle {
  0%,  0.01% { transform: translateY(var(--dy)) rotate(var(--dr)); }
  15%,  82%  { transform: translateY(0) rotate(0deg); }        /* settle, then hold */
  100%       { transform: translateY(var(--dy)) rotate(var(--dr)); }  /* scatter back */
}
@keyframes lg-x-cycle {          /* visible, clears at 620ms, returns before the scatter */
  0%,  10.3% { opacity: 1; transform: scale(1); }
  15.7%, 78% { opacity: 0; transform: scale(0.55); }
  84%, 100%  { opacity: 1; transform: scale(1); }
}
@keyframes lg-ck-cycle {         /* absent, lands at 700ms, clears first on reset */
  0%,  11.7% { opacity: 0; transform: scale(0.45); }
  18%,   76% { opacity: 1; transform: scale(1); }
  81%,  100% { opacity: 0; transform: scale(0.45); }
}

/* ==========================================================================
   02 bookkeeping: three calendar cells fill and check, staggered, with overshoot
   Loop 5000ms. Cells stagger 0, 350, 700ms.
   ========================================================================== */
.cal { display: block; }
/* Five columns, Monday to Friday. Gap eases off with the extra column so the
   cells do not shrink as much: 5 cells and 4 gaps at 7px left them noticeably
   smaller than the ledger rows on the card beside this one. */
.cal__head, .cal__grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
.cal__head { margin-bottom: 8px; }
.cal__d {
  font-family: var(--body); font-size: 9px; font-weight: 600;
  letter-spacing: 0.1em; text-align: center;
  color: var(--graphite-l); font-style: normal;
}
.cal__c {
  position: relative; aspect-ratio: 1 / 1;
  border-radius: 5px; background: var(--svc-tint);
}
.cal__c--hit { animation: cal-fill 5000ms ease-out var(--del) infinite both paused; }
.cal__ck {
  position: absolute; inset: 0; margin: auto; width: 58%; height: 58%;
  animation: cal-pop 5000ms cubic-bezier(0.34,1.56,0.64,1) var(--del) infinite both paused;
}
.cal__ck path {
  fill: none; stroke: var(--svc-ok); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
@keyframes cal-fill {
  0%           { background: var(--svc-tint); }
  /* Through a variable so the phone can lift it along with the other fills. */
  8.4%,  78%   { background: var(--svc-lit, rgba(122,157,116,0.26)); }
  86%,  100%   { background: var(--svc-tint); }
}
@keyframes cal-pop {
  0%,   2.6%   { opacity: 0; transform: scale(0.3); }
  8.1%         { opacity: 1; transform: scale(1.18); }   /* overshoot */
  11.8%, 76%   { opacity: 1; transform: scale(1); }      /* settle, then hold */
  83%,  100%   { opacity: 0; transform: scale(0.3); }
}

/* ==========================================================================
   03 advisory: line draws, dots land as it passes, bars grow after
   The line never reverses. It is drawn by a single dash sliding along the
   path, so the reset is the tail following the head off the right-hand edge
   rather than the line retracting back the way it came.
   Loop 6000ms, up from the 5500ms in the brief. The bars do not finish
   arriving until 2425ms, and at 5500 the last of them was still clearing as
   the line began redrawing. 6000 gives the reset room to finish.
   ========================================================================== */
.ch { display: block; width: 100%; height: auto; }
.ch__grid line { stroke: var(--svc-fill-2); stroke-width: 1; }
.ch__base { stroke: var(--svc-fill); stroke-width: 1; }
.ch__line {
  fill: none; stroke: var(--svc-accent); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 100 400; stroke-dashoffset: 120;
  animation: ch-draw 6000ms cubic-bezier(0.4,0,0.35,1) 0ms infinite both paused;
}
.ch__dot {
  fill: var(--svc-accent);
  transform-box: fill-box; transform-origin: center;
  animation: ch-pop 6000ms cubic-bezier(0.34,1.5,0.64,1) var(--del) infinite both paused;
}
/* Gold appears exactly once on the site, on the final peak. It lands last, at
   1860ms, so on the shared cycle its exit would fall into the next pass. Its
   own cycle clears it at 5250ms, the moment the tail sweeps past it. */
.ch__dot--peak { fill: var(--svc-gold); animation-name: ch-pop-peak; }
.ch__bar {
  fill: var(--svc-fill);
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: ch-grow 6000ms cubic-bezier(0.22,0.61,0.36,1) var(--del) infinite both paused;
}
@keyframes ch-draw {
  0%          { stroke-dashoffset: 120; }   /* parked clear of the path */
  1%          { stroke-dashoffset: 100; }   /* at the start line */
  31%,   78%  { stroke-dashoffset: 0; }     /* drawn by 1860ms, then hold */
  87.5%       { stroke-dashoffset: -100; }  /* tail follows the head out right */
  88.5%, 100% { stroke-dashoffset: -120; }  /* parked again, ready to re-enter */
}
@keyframes ch-pop {
  0%          { opacity: 0; transform: scale(0.2); }
  4.6%        { opacity: 1; transform: scale(1.3); }
  7%,   70%   { opacity: 1; transform: scale(1); }
  76%, 100%   { opacity: 0; transform: scale(0.2); }
}
@keyframes ch-pop-peak {
  0%           { opacity: 0; transform: scale(0.2); }
  4.6%         { opacity: 1; transform: scale(1.3); }
  7%,   56.5%  { opacity: 1; transform: scale(1); }
  63%,  100%   { opacity: 0; transform: scale(0.2); }
}
@keyframes ch-grow {
  0%          { transform: scaleY(0); }
  8.7%,  40%  { transform: scaleY(1); }
  47%,  100%  { transform: scaleY(0); }
}

/* ---------- the trigger ---------- */
.svc-card.play .lg__row,
.svc-card.play .lg__x,
.svc-card.play .lg__ck,
.svc-card.play .cal__c--hit,
.svc-card.play .cal__ck,
.svc-card.play .ch__line,
.svc-card.play .ch__dot,
.svc-card.play .ch__bar { animation-play-state: running; }

/* ---------- reduced motion: hold the resolved state, no loop ---------- */
@media (prefers-reduced-motion: reduce) {
  .lg__row { animation: none; transform: none; }
  .lg__x   { animation: none; opacity: 0; }
  .lg__ck  { animation: none; opacity: 1; transform: scale(1); }
  .cal__c--hit { animation: none; background: rgba(122,157,116,0.26); }
  .cal__ck { animation: none; opacity: 1; transform: scale(1); }
  .ch__line { animation: none; stroke-dashoffset: 0; }
  .ch__dot { animation: none; opacity: 1; transform: none; }
  .ch__bar { animation: none; transform: none; }
}

/* ---------- narrow screens: scale the graphic, never crop it ---------- */
@media (max-width: 420px) {
  .svc-card__gfx { height: 152px; }
  .svc-card__body { padding-bottom: 1.6rem; }
  .lg__row { height: 12px; margin-top: 7px; }
  .lg__a, .lg__b { height: 6px; }
  /* Zone drops to 152px here, so both caps come down with it. */
  .svc-card__gfx--cal > *   { max-width: 172px; }
  .svc-card__gfx--chart > * { max-width: 222px; }
}

/* On a phone these graphics are read at arm's length, often outdoors, on a
   screen the viewer is not looking at straight on. The desktop fills sit at
   1.09:1 to 1.27:1 against the card, which survives none of that and reads as
   a half-loaded image. Structure is lifted to roughly 2:1 here and the
   checkmarks to 3.7:1. Only these four values change, and only below 640px. */
@media (max-width: 640px) {
  .svc {
    --svc-fill:   #A9C0D1;
    --svc-fill-2: #D3DFE9;
    --svc-tint:   #DFEDE5;
    --svc-ok:     #6B8F65;
  }
  .cal__d { color: var(--graphite); }
  .ch__line { stroke-width: 3; }
  .lg__flag path, .cal__ck path { stroke-width: 2.1; }

  /* The loop runs here exactly as it does on desktop: same keyframes, same
     timing, same scroll trigger. Only the palette above differs, lifted because
     the desktop fills sit at 1.1:1 to 1.3:1 against the card and do not survive
     a phone screen held at arm's length. */
  .svc { --svc-lit: rgba(107,143,101,0.34); }
}

/* ==========================================================================
   What we fix, narrow: the accordions
   Markup and copy in build/routing.py.

   This replaces a pinned, scroll scrubbed component that used to live here: a
   sticky stage, a card that grew and shrank per symptom, a connector drawn from
   live geometry, a progress rule, and the page heading physically relocated
   into the pin and put back on the way out. Roughly 150 lines of CSS and 240 of
   JS, all gone. The accordions say the same thing in one screen, can be read in
   any order, and hold still while being read.

   Hidden at 768px and up, where the wide fan already covers this ground.
   ========================================================================== */
.fx { display: none; }

@media (max-width: 767px) {
  .fx { display: block; }

  .fx__item { border: var(--rule); background: var(--paper); border-radius: 14px; }
  .fx__item + .fx__item { margin-top: 10px; }
  /* Class rather than :has(), because site.js is already touching this element
     and a JS toggle needs no support caveat. */
  .fx__item.is-open { border-color: var(--oxblood); }

  /* The whole row is the target and its own padding clears the 44px minimum,
     so nothing here needs a hit area bolted on separately. */
  .fx__head {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 15px 18px; background: none; border: 0; cursor: pointer;
    font-family: var(--body); text-align: left;
  }
  .fx__name {
    flex: 1; min-width: 0;
    font-size: 17px; line-height: 1.3; color: var(--ink); font-weight: 500;
  }
  .fx__chev {
    flex: none; width: 16px; height: 16px;
    fill: none; stroke: var(--oxblood); stroke-width: 1.8;
    stroke-linecap: round; stroke-linejoin: round;
    transition: transform 0.28s var(--ease);
  }
  .fx__head[aria-expanded="true"] .fx__chev { transform: rotate(180deg); }

  /* The animating box carries no padding of its own. Padding on the element
     whose height is transitioning gets crushed at height 0, and the last line
     appears to slide out from under the heading. */
  .fx__panel { height: 0; overflow: hidden; transition: height 0.3s var(--ease); }
  .fx__panel[hidden] { display: none; }
  .fx__list {
    margin: 0; padding: 0 18px 16px 18px; list-style: none;
    display: grid; gap: 9px;
  }
  .fx__list li {
    display: grid; grid-template-columns: auto 1fr; gap: 11px;
    font-size: 15px; line-height: 1.45; color: var(--graphite);
  }
  /* The same hairline dash the service lists elsewhere on the site use, rather
     than a bullet glyph that would be the only one on the page. */
  .fx__list li::before {
    content: ""; width: 14px; height: 1px;
    background: var(--oxblood); margin-top: 0.72em;
  }
}

@media (max-width: 400px) {
  .fx__name { font-size: 16px; }
  .fx__list li { font-size: 14px; }
}

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

/* Without JS nothing can open, so every panel is simply open and the three
   headings become labels over their lists. */
.no-js .fx__panel { height: auto; }
.no-js .fx__panel[hidden] { display: block; }
.no-js .fx__chev { display: none; }
.no-js .fx__head { cursor: default; }

/* ==========================================================================
   Ledger diagnostic, /method/ hero
   Markup in build/methodhero.py, which also owns the schedule. Every delay
   arrives here in --d (when the element appears) or --r (when its row is
   corrected), so one keyframe rule per effect drives all five rows and all
   three findings, and changing the cadence means editing Python, not this.

   Same motion contract as the service cards and the routing diagram: declared
   paused, rendering the 0% keyframe, which is the "as found" state. site.js
   adds .play on first intersect, it runs once, fill-mode holds the resolved
   state. Nothing loops and nothing replays.
   ========================================================================== */
.lx { --lx-pad: clamp(0.7rem, 0.45rem + 1vw, 1rem); }

.lx__card {
  position: relative; overflow: hidden;
  background: var(--paper); border: var(--rule);
  padding: 0.4rem 0;
}
.lx__head, .lx__row {
  display: grid;
  /* Date and amount are as wide as their content, the description takes the
     rest, and the mark column is reserved at a fixed width whether or not the
     row has one. Reserved rather than added on correction: a column that
     appears mid sequence would shove every amount left as the first row
     resolves. */
  grid-template-columns: auto minmax(0, 1fr) auto 18px;
  align-items: center;
  gap: 0.5rem clamp(0.6rem, 0.3rem + 1vw, 1.1rem);
  padding: 0.62rem var(--lx-pad);
}
.lx__head {
  position: relative;
  font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--graphite-l); font-weight: 500;
  border-bottom: var(--rule); padding-bottom: 0.7rem;
}
.lx__hamt { text-align: right; }

.lx__row { font-size: clamp(0.78rem, 0.72rem + 0.28vw, 0.9375rem); }
.lx__date { color: var(--graphite-l); font-variant-numeric: tabular-nums; }
.lx__desc { color: var(--ink); }
.lx__num {
  display: grid; justify-items: end; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
/* Both figures occupy the same cell, so the column is as wide as the wider of
   the two and the swap moves nothing. */
.lx__num > i { grid-area: 1 / 1; font-style: normal; }

.lx__mark { position: relative; width: 18px; height: 14px; }
.lx__dot {
  position: absolute; inset: 50% auto auto 50%;
  width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  border-radius: 50%; background: var(--rust);
}
.lx__ck {
  position: absolute; inset: 0; width: 100%; height: 100%;
  fill: none; stroke: var(--sage); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Reconciled badge, pinned into the header rather than laid out in it, so it
   cannot widen the header row before it appears. */
.lx__badge {
  position: absolute; top: 50%; right: var(--lx-pad);
  transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.22rem 0.55rem 0.22rem 0.4rem;
  background: var(--sage-t); border-radius: 999px;
  font-size: 0.625rem; letter-spacing: 0.08em; color: var(--ink);
}
.lx__badge .lx__ck { position: static; width: 12px; height: 12px; flex: none; }

/* The scan. One pass, left to right, then gone. */
.lx__scan {
  position: absolute; inset: 0 auto 0 0; width: 42%;
  pointer-events: none;
  background: linear-gradient(to right,
    rgba(234,241,246,0) 0%, var(--oxblood-t) 45%, rgba(234,241,246,0) 100%);
}

/* ---- findings ---- */
.lx__find {
  margin-top: 0.75rem;
  background: var(--paper); border: var(--rule);
  padding: var(--lx-pad);
  /* No reserved height, and none needed. The three items are in the markup
     from the start and only their opacity animates, so they hold their space
     while invisible and the panel is the same height at 0ms as at the end.
     A min-height would have to guess the worst case wrap and would leave dead
     space on every width that did not hit it. */
}
.lx__flabel {
  margin: 0 0 0.7rem;
  font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--graphite-l); font-weight: 500;
}
.lx__flist { margin: 0; padding: 0; list-style: none; }
.lx__f {
  display: grid; grid-template-columns: 18px minmax(0, 1fr);
  gap: 0.55rem; align-items: start;
  font-size: clamp(0.78rem, 0.72rem + 0.28vw, 0.875rem);
  line-height: 1.5; color: var(--ink);
}
.lx__f + .lx__f { margin-top: 0.55rem; }
.lx__f .lx__mark { height: 1.35em; }
.lx__ftext { display: grid; }
.lx__ftext > i { grid-area: 1 / 1; font-style: normal; }
.lx__ftext b { font-weight: 600; }

/* ---- motion ----------------------------------------------------------- */
@keyframes lx-in   { from { opacity: 0; transform: translateY(8px); } }
@keyframes lx-tint { from { background-color: var(--rust-t); }
                       to { background-color: var(--sage-t); } }
@keyframes lx-out  { to   { opacity: 0; } }
@keyframes lx-fade { from { opacity: 0; } }
@keyframes lx-mark { from { opacity: 0; transform: scale(0.7); } }
@keyframes lx-scan {
  0%        { transform: translateX(-110%); opacity: 0; }
  18%, 82%  { opacity: 1; }
  100%      { transform: translateX(275%); opacity: 0; }
}

.lx__row  { animation: lx-in 460ms var(--ease) var(--d) 1 both paused; }
.lx__f    { animation: lx-in 460ms var(--ease) var(--d) 1 both paused; }
.lx__scan { animation: lx-scan 1150ms ease-in-out 900ms 1 both paused; }

/* A second animation on the same element. The row's entry handles opacity and
   position, this handles only the background, and because both are `both` the
   rust tint is painted from frame one and holds until --r. */
.lx__row--flag {
  animation:
    lx-in 460ms var(--ease) var(--d) 1 both paused,
    lx-tint 560ms ease-out var(--r) 1 both paused;
}
.lx__row--flag .lx__was,
.lx__f .lx__was   { animation: lx-out 560ms ease-out var(--r) 1 both paused; }
.lx__row--flag .lx__is,
.lx__f .lx__is    { animation: lx-fade 560ms ease-out var(--r) 1 both paused; }
.lx__row--flag .lx__dot,
.lx__f .lx__dot   { animation: lx-out 300ms ease-out var(--r) 1 both paused; }
.lx__row--flag .lx__ck,
.lx__f .lx__ck    { animation: lx-mark 420ms var(--ease) calc(var(--r) + 180ms) 1 both paused; }
/* The badge takes the Amount label's place rather than sitting on top of it,
   so the label goes as the badge lands. Both read the header's own --d. */
.lx__hamt         { animation: lx-out 300ms ease-out var(--d) 1 both paused; }
.lx__badge        { animation: lx-mark 460ms var(--ease) var(--d) 1 both paused; }

.lx.play .lx__row, .lx.play .lx__f, .lx.play .lx__scan,
.lx.play .lx__was, .lx.play .lx__is, .lx.play .lx__dot,
.lx.play .lx__ck,  .lx.play .lx__badge,
.lx.play .lx__hamt { animation-play-state: running; }

/* ---- the loop seam ----
   The sequence itself is untouched: same keyframes, same delays, same
   durations. Looping is done by resetting it, not by making each animation
   infinite, because with per element delays spread across eight seconds an
   infinite iteration count would wrap each element at a different moment and
   the card would come apart one row at a time.

   So site.js fades the card out, strips the animations for one frame to send
   every element back to its 0% state, and fades back in. is-reset outranks the
   element rules on specificity alone, and is applied only while the card is at
   opacity 0, so the snap is never on screen. */
.lx { transition: opacity var(--lx-fade, 450ms) ease; }
.lx.is-rewind { opacity: 0; }
.lx.is-reset * { animation: none; }

/* Painted finished, with nothing running. The global reduced-motion rule
   crushes durations to 0.001ms, but these are paused and site.js never adds
   .play, so without this they would sit on their 0% frame for good. */
@media (prefers-reduced-motion: reduce) {
  .lx__row, .lx__f, .lx__scan, .lx__was, .lx__is,
  .lx__dot, .lx__ck, .lx__badge, .lx__hamt { animation: none; }
  .lx__scan, .lx__was, .lx__dot, .lx__hamt { opacity: 0; }
  .lx__row--flag { background-color: var(--sage-t); }
  .lx__is, .lx__ck, .lx__badge { opacity: 1; transform: none; }
}
/* Same reasoning without the media query: no JS means no .play, so the graphic
   would never leave its as-found state. */
.no-js .lx__scan, .no-js .lx__was,
.no-js .lx__dot, .no-js .lx__hamt { opacity: 0; }
.no-js .lx__row--flag { background-color: var(--sage-t); }
.no-js .lx__row, .no-js .lx__f, .no-js .lx__is, .no-js .lx__ck,
.no-js .lx__badge, .no-js .lx__hamt { animation: none; }
.no-js .lx__row, .no-js .lx__f, .no-js .lx__is,
.no-js .lx__ck, .no-js .lx__badge { opacity: 1; transform: none; }

@media (max-width: 420px) {
  /* Tighter, never truncated: every figure and description stays whole. */
  .lx { --lx-pad: 0.55rem; }
  .lx__head, .lx__row { gap: 0.4rem 0.5rem; padding-block: 0.5rem; }
}

/* ==========================================================================
   Service selector and self check, /services/
   Markup in build/servicesel.py, which owns the copy and the mapping.

   Square, hairline ruled, no shadows: the reference mockup rounded its corners
   at 5 to 8px, but every card, button and panel on this site has square
   corners, and one rounded component would read as pasted in from elsewhere.
   Colours are the existing tokens rather than the mockup's approximations of
   them, so slate is --oxblood and the tinted ground is --bone-d.
   ========================================================================== */
.sv {
  display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  border: var(--rule); background: var(--paper);
}
.sv__list { border-right: var(--rule); background: var(--bone-d); }

.sv__item {
  position: relative;
  display: flex; align-items: flex-start; gap: 0.75rem; width: 100%;
  text-align: left; background: none; border: 0; border-bottom: var(--rule);
  padding: 1rem 1.1rem; cursor: pointer; font-family: var(--body);
  transition: background 0.2s ease;
}
.sv__item:last-child { border-bottom: 0; }
.sv__item:hover { background: rgba(255,255,255,0.6); }
.sv__item.is-on { background: var(--paper); }
/* The accent bar reads as the selection, so it is drawn rather than implied by
   background alone: the two backgrounds are four percent apart in luminance
   and that is not a difference anyone should have to hunt for. */
.sv__item.is-on::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px; background: var(--oxblood);
}
.sv__ico {
  flex: none; width: 34px; height: 34px; margin-top: 1px;
  display: grid; place-items: center;
  background: var(--paper); border: var(--rule); color: var(--oxblood);
}
.sv__item.is-on .sv__ico { border-color: var(--oxblood); }
.sv__glyph { width: 17px; height: 17px; }
.sv__txt { flex: 1; min-width: 0; }
.sv__name-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.sv__name { font-size: 0.9375rem; font-weight: 500; color: var(--ink); }
.sv__best {
  display: block; margin-top: 0.2rem;
  font-size: 0.8125rem; line-height: 1.45; color: var(--graphite);
}
/* Accent, not alert. White on --oxblood is 4.62:1. */
.sv__badge {
  font-size: 0.625rem; letter-spacing: 0.08em; text-transform: uppercase;
  font-weight: 500; background: var(--oxblood); color: #fff;
  padding: 0.15rem 0.45rem; white-space: nowrap;
}

.sv__detail { padding: clamp(1.5rem, 1rem + 2.4vw, 2.5rem); }
.sv__panel[hidden] { display: none; }
.sv__pico {
  width: 46px; height: 46px; margin-bottom: 1.25rem;
  display: grid; place-items: center;
  background: var(--bone-d); color: var(--ink);
}
.sv__pglyph { width: 22px; height: 22px; }
.sv__tag {
  margin: 0 0 0.6rem;
  font-size: var(--t-micro); letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: 500; color: var(--oxblood);
}
.sv__h {
  font-family: var(--display); font-weight: 400; color: var(--ink);
  font-size: var(--t-h3); line-height: 1.15; letter-spacing: -0.02em;
  margin: 0 0 0.6rem;
}
.sv__body { color: var(--graphite); max-width: 56ch; margin: 0 0 1.4rem; }
.sv__points { list-style: none; margin: 0 0 1.6rem; padding: 0; display: grid; gap: 0.7rem; }
.sv__points li {
  display: grid; grid-template-columns: auto 1fr; gap: 0.65rem;
  font-size: var(--t-small); line-height: 1.5; color: var(--ink-90);
}
.sv__tick {
  width: 15px; height: 15px; margin-top: 0.32em;
  fill: none; stroke: var(--sage); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Content changing under a static heading is easy to miss, so the panel moves
   a little as it swaps. Opacity and transform only: both are compositor work,
   and this fires on every click. */
@keyframes sv-in { from { opacity: 0; transform: translateY(6px); } }
.sv__panel.is-on { animation: sv-in 280ms var(--ease) both; }

@media (max-width: 880px) {
  /* The site's existing tablet breakpoint. List on top, panel full width. */
  .sv { grid-template-columns: 1fr; }
  .sv__list { border-right: 0; border-bottom: var(--rule); }
}

/* ---------- self check ---------- */
/* A fixed 4 / 2 / 1 ladder rather than auto-fit. auto-fit chose three columns
   across a band of widths and left the fourth option sitting alone, which is
   the exact fault this widget was cut from five options to fix. Every step
   here divides four evenly. build/servicesel.py refuses to build if the
   option count stops matching. */
.sv__opts {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.6rem; margin-bottom: 1.4rem;
}
@media (max-width: 880px) { .sv__opts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .sv__opts { grid-template-columns: 1fr; } }
.sv__opt {
  font-family: var(--body); font-size: var(--t-small); text-align: left;
  color: var(--ink); background: var(--paper); border: var(--rule);
  padding: 0.9rem 1rem; cursor: pointer; line-height: 1.45;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.sv__opt:hover { border-color: var(--oxblood); }
/* Inset ring rather than a second border: a 2px border would reflow the grid
   by a pixel on every pick. */
.sv__opt.is-on {
  border-color: var(--oxblood); box-shadow: inset 0 0 0 1px var(--oxblood);
}
.sv__result {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  background: var(--paper); border: var(--rule); padding: 1.25rem 1.4rem;
}
.sv__result[hidden] { display: none; }
.sv__result:not([hidden]) { animation: sv-in 250ms var(--ease) both; }
.sv__rlabel {
  margin: 0; font-size: var(--t-micro); letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--graphite-l); font-weight: 500;
}
.sv__rname {
  margin: 0.2rem 0 0; font-family: var(--display); font-size: 1.1875rem;
  letter-spacing: -0.02em; color: var(--ink); flex-basis: 100%;
}
.sv__rlink {
  margin-left: auto; font-size: var(--t-small); font-weight: 500;
  color: var(--oxblood); border-bottom: 1px solid var(--oxblood);
  padding-bottom: 2px; white-space: nowrap;
}
.sv__rlink .arw { transition: transform 0.28s var(--ease); display: inline-block; }
.sv__rlink:hover .arw { transform: translateY(-3px); }
@media (max-width: 560px) {
  .sv__result { display: block; }
  .sv__rlink { display: inline-block; margin: 0.9rem 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .sv__panel.is-on, .sv__result:not([hidden]) { animation: none; }
}
/* Without JS the tabs cannot switch, so every panel shows in order and the
   list becomes a plain index of what is below rather than a row of dead
   buttons. The page still carries all five services in full. */
.no-js .sv { grid-template-columns: 1fr; }
.no-js .sv__list { border-right: 0; border-bottom: var(--rule); }
.no-js .sv__item { cursor: default; }
.no-js .sv__panel[hidden] { display: block; }
.no-js .sv__panel + .sv__panel { border-top: var(--rule); padding-top: 2rem; margin-top: 2rem; }
/* The self check is interactive and nothing else. Rather than leave a heading
   above five buttons that cannot answer, the whole section goes. Every service
   it would have recommended is spelled out in full directly above. */
.no-js .js-only { display: none; }
