/* ==========================================================================
   Lumora Projects Co — main stylesheet
   Mobile-first. Single stylesheet by design: at seven pages, splitting CSS per
   section costs more requests than it saves bytes.
   Colours and type come from tokens.css. No hex values below this line.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Reset and base
   -------------------------------------------------------------------------- */

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* No pull-to-refresh and no rubber-band overscroll where the browser
     honours it. Dragging down near the top of the page visibly pulled the
     whole page downward (Chrome Android draws a refresh spinner as well),
     which reads as the page expanding during a scroll gesture. */
  overscroll-behavior-y: none;
  /* Stop the sticky nav covering a section when a jump link lands on it */
  scroll-padding-top: calc(var(--nav-h) + var(--s-4));
}

/* Reduced motion means gentler, not none. Colour and opacity fades carry
   meaning and cause nobody motion sickness, so they stay. What goes is
   movement: transforms, and the long smooth scroll on a jump link. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-property: color, background-color, border-color, opacity, filter, visibility !important;
  }
}

body {
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--ink);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

a { color: var(--forest); }

/* Mobile browsers paint a translucent box over whatever you tap. That was the
   only press feedback the site had; now that every interactive element has a
   real :active state, the default box is just a grey flash on top of it. */
a,
button,
summary,
.btn { -webkit-tap-highlight-color: transparent; }

/* Forest on forest is invisible, so inline links on the dark band switch to
   mist (5.0:1). Buttons set their own colours; the footer sets its own. */
.section--forest a:not(.btn),
.pagehead a,
.entity-card a { color: var(--mist); }

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
  text-wrap: balance;
}

h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }

/* Three tiers, not one. The page ran nine H2s at an identical size and weight
   through nine bands of near-identical height, so a skim produced no ranking
   and the closing section read no louder than an interstitial. Lead is for the
   three sections the page turns on: the offer, the framework, the close. */
.h2--lead { font-size: var(--t-h2-lead); }

/* Playfair is a high-contrast display serif: its thin strokes are hairlines.
   At card-title sizes those hairlines fall under a pixel and the negative
   tracking inherited from the display sizes crowds them further, which is what
   made the small headings hard to read. Normal tracking from h3 down. */
h3 {
  font-size: var(--t-h3);
  line-height: var(--lh-snug);
  letter-spacing: normal;
}

h4 {
  font-size: var(--t-h4);
  line-height: var(--lh-snug);
  letter-spacing: normal;
  font-weight: 600;
}

p { text-wrap: pretty; }

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

/* Visible only to keyboard users, first thing in the tab order */
.skip-link {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -110%);
  z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: var(--forest);
  color: var(--white);
  font-weight: 600;
  text-decoration: none;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  transition: transform 0.15s var(--ease);
}

.skip-link:focus-visible { transform: translate(-50%, 0); }

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

/* --------------------------------------------------------------------------
   2. Layout primitives
   -------------------------------------------------------------------------- */

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

.container--narrow { max-width: var(--container-narrow); }

.section { padding-block: var(--section-y); }
/* Spacing is a decision, not a constant. The framework and the first step are
   what the page is for, so they breathe; the supporting sections sit closer. */
.section--airy { padding-block: calc(var(--section-y) * 1.35); }
.section--close { padding-block: calc(var(--section-y) * 0.78); }
.section--stone { background: var(--stone); }
.section--white { background: var(--white); }

.section--forest {
  background: var(--forest);
  color: var(--stone);
}

.section--forest h2,
.section--forest h3,
.section--forest h4 { color: var(--white); }

/* The hero's rising arc as a shareable device: the same 1.5px ellipse crest,
   for sections that should visibly belong to the opening. Geometry matches
   .hero::after exactly. On the forest band a forest line is invisible, so the
   stroke switches to sage there. The content sits above it (z-index -1) and
   cards with backgrounds pass over it, which is the intent: the line reads in
   the gaps, not through the type. */
.has-sweep {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.has-sweep::after {
  content: '';
  position: absolute;
  z-index: -1;
  left: -30%;
  right: -30%;
  /* Crest near the section's top, so the arch rises over the headline and
     falls away only at the viewport edges, outside the centred text column.
     The hero keeps its own lower crest; this device sits above the content
     rather than behind it. */
  top: 4%;
  aspect-ratio: 2.2 / 1;
  border-radius: 50%;
  border-top: 1.5px solid var(--forest);
  opacity: 0.35;
  pointer-events: none;
}

.section--forest.has-sweep::after {
  border-top-color: var(--sage);
  opacity: 0.45;
}

/* The closing band's arc passes under its button, bookending the hero: the
   same horizon beneath the first ask and the last. The default crest (4%)
   arches over a headline; this variant grounds a CTA instead. */
.has-sweep--under::after { top: 88%; }   /* the band is short; 82% landed the
                                            crest inside the button on mobile */

/* --- Who we help: audience prose beside problem rows ----------------------
   Split, not cards: the problems are a list you scan down, and rows read as
   "we handle these" where cards read as "symptoms you have". Stacks on
   mobile, who first. */
.whohelp {
  display: grid;
  gap: var(--s-8);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 64rem) {
  .whohelp { grid-template-columns: 5fr 7fr; gap: var(--s-9); }
}

.whohelp__who p {
  margin-top: var(--s-4);
  max-width: 40ch;
  font-size: var(--t-lead);
  color: var(--ink-muted);
}

/* The blurb ends on a colon that the problem rows complete, so the CTA sits
   after the list: mobile reads blurb, problems, button; desktop places the
   button back under the blurb in the left column via grid placement. */
.whohelp__cta { margin-top: var(--s-2); }

/* The section's close: full width beneath both columns, centred. Anchoring
   it inside the left column broke every time the heading or list changed
   height; a centred close is stable against both. */
.whohelp__cta { justify-content: center; }

@media (min-width: 64rem) {
  .whohelp__cta { grid-column: 1 / -1; margin-top: 0; }
}

.problems {
  display: grid;
  gap: var(--s-6);
  list-style: none;
  margin: 0;
  padding: 0;
}

.problem {
  display: flex;
  gap: var(--s-5);
  align-items: flex-start;
}

.problem__icon {
  flex: none;
  width: 40px;
  height: 40px;
  color: var(--forest);
}

.problem h3 {
  font-size: var(--t-h4);
  margin-bottom: var(--s-1);
}

.problem p { color: var(--ink-muted); max-width: 52ch; }

/* --- Services: five stone cards, 2 + 3 -----------------------------------
   Two wide cards over three narrow ones on a six-column grid, both rows
   sharing the same left edge; five items, five cells, no void. Cards earn
   their place here because the services are discrete purchasable things,
   and this is the page's only card grid. The treatment is the brand's:
   square corners, stone fill on the white section doing the work a border
   or shadow would, no hover — these are not links. Grid default stretch
   keeps each row's cards equal height. */
.svc-grid {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: 1fr;
  max-width: 62rem;
  margin-inline: auto;
}

@media (min-width: 48rem) {
  .svc-grid { grid-template-columns: repeat(6, 1fr); }
  .svc { grid-column: span 3; }
  .svc:nth-child(n + 3) { grid-column: span 2; }
}

.svc {
  padding: var(--s-6);
  /* Forest-tint: the faintest wash in the palette, between mist and white.
     Stone matched the band above; full mist tiled five cards with too much
     pigment. This keeps the cool green cast at half the weight. */
  background: var(--forest-tint);
}

.svc h3 { margin-bottom: var(--s-2); }

.svc p {
  color: var(--ink-muted);
  max-width: 52ch;
}

/* --------------------------------------------------------------------------
   3. Section headings
   -------------------------------------------------------------------------- */

/* No eyebrow labels, no decorative rules under headings. A small uppercase
   badge above every section heading is the single most recognisable
   generated-page tell, and the headings here carry themselves. */

.section-head {
  max-width: 46rem;
  margin-bottom: var(--s-8);
}


/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;              /* tap target floor */
  padding: var(--s-3) var(--s-6);
  /* Inter, not Playfair. The guide assigns Playfair to headings and Inter to
     body; a display serif set small, uppercase and tracked reads badly. */
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  border: 2px solid transparent;
  border-radius: var(--radius-btn);
  cursor: pointer;
  transition: background-color 0.18s var(--ease),
              border-color 0.18s var(--ease),
              color 0.18s var(--ease),
              filter 0.18s var(--ease),
              transform 0.16s var(--ease-out);
}

/* Hover lift AND press scale live only on real pointers. The press scale ran
   unguarded on touch, and on a phone the thumb starts most scrolls on or near
   the hero CTA: the button scaled to 0.97 on touch-down, then expanded back
   over 160ms as the scroll began. That is a scaling effect above the fold,
   fired by the act of scrolling, on mobile only — reported three times and
   twice misdiagnosed (the dvh band growth and the font-swap reflow were both
   real, but this was the one being seen). Touch devices now get NO transform
   on buttons, by explicit request. Desktop keeps both. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-1px); }
  .btn:active { transform: scale(0.97); }
}

.btn--primary {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--white);
}

.btn--primary:hover {
  background: var(--forest-dark);
  border-color: var(--forest-dark);
}

.btn--outline {
  background: transparent;
  border-color: var(--forest);
  color: var(--forest);
}

.btn--outline:hover {
  background: var(--forest);
  color: var(--white);
}

/* On the forest band a forest outline disappears. Mist is 5.0:1 on forest;
   sand gold is only 3.8:1 and cannot carry button text. */
.on-dark .btn--outline,
.entity-card .btn--outline,
.section--forest .btn--outline {
  border-color: var(--mist);
  color: var(--mist);
}

.on-dark .btn--outline:hover,
.entity-card .btn--outline:hover,
.section--forest .btn--outline:hover {
  background: var(--mist);
  color: var(--forest);
}

/* A forest-filled primary button on a forest surface is the same colour as
   the surface — it reads as loose text, not a button. Invert it. The photo
   band gets the same treatment: its placeholder ground is forest, and once a
   real photograph lands the white fill still reads against the scrim. */


.section--forest .btn--primary,
.entity-card .btn--primary {
  background: var(--white);
  border-color: var(--white);
  color: var(--forest);
}

.section--forest .btn--primary:hover,
.entity-card .btn--primary:hover {
  background: var(--mist);
  border-color: var(--mist);
  color: var(--forest-dark);
}

.btn--sm {
  min-height: 40px;
  padding: var(--s-2) var(--s-4);
  font-size: 0.8125rem;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-6);
}

.btn-row--center { justify-content: center; }

/* Full-width buttons on the narrowest phones, side by side above that */
@media (max-width: 27.4375rem) {
  .btn-row .btn { flex: 1 1 100%; }
}

/* --------------------------------------------------------------------------
   5. Navigation
   -------------------------------------------------------------------------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--white);
  border-bottom: 1px solid var(--rule);
}

/* Deepen the shadow once the page has scrolled. main.js sets .is-stuck, and
   like .is-open this class never appears in the built HTML, so a class-usage
   scan will always report it dead. It is not. */
.nav.is-stuck { box-shadow: var(--shadow); }

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: var(--nav-h);
}

/* --- Wordmark --- */
.wordmark {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  text-decoration: none;
  line-height: 1;
}

.wordmark__name {
  display: flex;
  align-items: center;
  font-family: var(--font-heading);
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: var(--ls-logo);
  color: var(--ink);
}

/* The O in LUMORA carries the beacon mark from the logo */
.wordmark__mark {
  width: 0.92em;
  height: 0.92em;
  margin-inline: 0.02em;
  flex: none;
}

.wordmark__sub {
  font-family: var(--font-heading);
  font-size: 0.5625rem;
  font-weight: 600;
  letter-spacing: 0.42em;
  text-indent: 0.42em;   /* optical centring against the tracked-out name */
  color: var(--forest);
}

.on-dark .wordmark__name { color: var(--white); }
.on-dark .wordmark__sub { color: var(--mist); }

/* --- Links --- */
.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  margin-right: calc(var(--s-3) * -1);
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
}

.nav__toggle svg { width: 26px; height: 26px; }
.nav__toggle .icon-close { display: none; }
.nav__toggle[aria-expanded='true'] .icon-close { display: block; }
.nav__toggle[aria-expanded='true'] .icon-open { display: none; }

/* Closed by default below the desktop breakpoint. main.js adds .is-open, and
   the rule below is the only thing that reveals the drawer — without it the
   toggle sets aria-expanded="true" over a menu that never appears, which is
   both a dead mobile nav and a WCAG 4.1.2 failure. Do not remove it because a
   class-usage scan of the HTML reports it unused: it is added at runtime and
   so never appears in the built markup. */
/* Closed state. The drawer used to snap between display:none and flex, and it
   is the most-used interaction on the site.

   visibility, not display. display cannot be transitioned; the modern fix is
   @starting-style plus transition-behavior: allow-discrete, which works but
   needs Chrome 117, Safari 17.5 or Firefox 129. This site is handed over with
   nobody maintaining it, so it takes the option with no support cliff.

   visibility:hidden still removes the links from the tab order and from the
   accessibility tree, and it transitions reliably: delayed to the end on the
   way out so the fade is visible, immediate on the way in. The element is
   absolutely positioned, so it costs no layout while hidden. */
.nav__menu {
  visibility: hidden;
  opacity: 0;
  transform: translateY(-8px);
  display: flex;
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-4) var(--gutter) var(--s-6);
  list-style: none;
  background: var(--white);
  border-bottom: 1px solid var(--rule);
  box-shadow: var(--shadow);
  transition: opacity 0.2s var(--ease-out),
              transform 0.2s var(--ease-out),
              visibility 0s linear 0.2s;
}

.nav__menu.is-open {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.2s var(--ease-out),
              transform 0.2s var(--ease-out),
              visibility 0s linear 0s;
}

.nav__menu a {
  display: block;
  padding: var(--s-3) 0;
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--rule);
}

.nav__menu .btn {
  margin-top: var(--s-4);
  border-bottom: 0;
  color: var(--white);
}

.nav__cta-desktop { display: none; }

@media (min-width: 60rem) {
  .nav__toggle { display: none; }

  .nav__menu {
    display: flex;
    visibility: visible;
    opacity: 1;
    transform: none;
    transition: none;
    position: static;
    flex-direction: row;
    align-items: center;
    gap: var(--s-6);
    padding: 0;
    background: none;
    border: 0;
    box-shadow: none;
  }

  .nav__menu a {
    padding: var(--s-2) 0;
    border-bottom: 2px solid transparent;
    transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
  }

  @media (hover: hover) and (pointer: fine) {
    .nav__menu a:hover { color: var(--forest); }
  }

  .nav__menu .btn { display: none; }
  .nav__cta-desktop { display: inline-flex; }
}

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */

/* Light hero, following the brand guide (13 Aug 2026), which is 78% light and
   has no dark neutral at all. Warm stone ground with the guide's soft green
   wave rising from the lower left. Template structure kept: two-size
   headline, check row, dual CTA, cut-out photo bleeding into the strip. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--stone);
  color: var(--ink);
  padding-block: var(--section-y);
  text-align: center;
  /* Between a strip and a full screen. Content-height (455px on a 900px
     viewport, 51%) read as a strip; the full first screen (92%) left the four
     words swimming. ~72% of the viewport splits the difference: a real
     opening that still shows the next section starting under the fold, which
     is itself a scroll cue. svh, NOT dvh: dvh is the dynamic unit and grows
     as the mobile address bar collapses, which made the hero visibly expand
     while scrolling. svh is the small, stable measurement, so the band is
     sized once and never moves. The rem cap stops a tall desktop window
     stranding the content in a void. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Phones get a FIXED height: no viewport units at all, so no browser's
     handling of them can move this band by any mechanism. 29rem matches what
     the svh formula resolved to on a typical phone. Desktop keeps the
     viewport-proportional size, where browser chrome never animates. */
  min-height: 29rem;
}

@media (min-width: 60rem) {
  .hero { min-height: min(42rem, calc(72svh - var(--nav-h))); }
}

/* The watercolour wave from the guide's hero panel: soft sage banked into the
   lower left, with a barely-there gold warmth top right. */
.hero::before {
  content: '';
  position: absolute;
  z-index: -2;
  inset: -10% -20%;
  background:
    radial-gradient(ellipse 52% 34% at 12% 96%, rgba(167, 195, 176, 0.85), transparent 70%),
    radial-gradient(ellipse 38% 26% at 30% 104%, rgba(46, 107, 87, 0.22), transparent 72%),
    radial-gradient(ellipse 42% 30% at 86% 4%, rgba(216, 200, 161, 0.30), transparent 74%);
}

/* The brand's rising arc, drawn as the sweep from the logo. Pinned by its
   TOP edge, not its bottom: the visible line is the ellipse's crest, and the
   ellipse's height grows with the viewport width (160% wide at 2.2:1). Pinned
   by bottom, the crest cleared the top of the band and was clipped at any
   desktop width — the line only ever showed on phones. A top pin cannot be
   clipped at any size. 76%, not 68%: the earlier crest ran through the
   vertical middle of the CTA, and a stroke through the primary button reads
   as a defect at the exact point of decision. The arc now passes beneath it,
   the horizon the logo draws it as. */
.hero::after {
  content: '';
  position: absolute;
  z-index: -1;
  left: -30%;
  right: -30%;
  top: 76%;
  aspect-ratio: 2.2 / 1;
  border-radius: 50%;
  border-top: 1.5px solid var(--forest);
  opacity: 0.35;
}

.hero h1 { color: var(--ink); }

/* One-part headline; the old two-part template treatment and its wrapper
   span are gone. Sized between the cautious nine-word curve (76px max) and
   the full-screen one (96px): 36px at 375 and 84px at 1440, one line from
   ~750px up, two balanced lines on a phone. */
.hero__title {
  max-width: 60rem;
  margin-inline: auto;
  font-size: clamp(2.25rem, 1.19rem + 4.51vw, 5.25rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.025em;
}

/* Never styled until now: no max-width meant a 1200px measure at 17px, and no
   margin meant it sat flush against the headline. */
.hero__lead {
  max-width: 46ch;
  margin: var(--s-6) auto 0;
  font-size: var(--t-lead);
  line-height: 1.6;
  color: var(--ink-muted);
}

/* The hero is centred, and .btn-row defaults to flex-start. Without this the
   CTAs sit hard left under a centred headline. */
.hero .btn-row { justify-content: center; }

/* --------------------------------------------------------------------------
   7. Media and placeholders
   -------------------------------------------------------------------------- */

.media {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--mist);
}

.media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The portrait is 2:3 against a 3:4 box (and 4:3 on mobile), so cover crops
   vertically. Default centring keeps the middle band, which cuts the face on
   the mobile crop; anchoring near the top keeps it at every ratio. */
.img--face { object-position: 50% 18%; }

.media--4x3   { aspect-ratio: 4 / 3; }
.media--3x4   { aspect-ratio: 3 / 4; }

/* A 3:4 portrait at full phone width is ~500px tall and swallows the screen.
   Crop it rather than let it push the copy off the fold. */
@media (max-width: 55.9375rem) {
  .media--3x4 { aspect-ratio: 4 / 3; }}

/* --------------------------------------------------------------------------
   7b. Template components
   Two-tone headings, photo tiles, the repeated CTA pair, dashed process
   cards, the reasons grid, quotes, photo bands and the FAQ.
   -------------------------------------------------------------------------- */

/* Two-tone heading. The template opens each heading with its accent colour and
   finishes in the dark — it does the job an eyebrow label would, without the
   badge. */
.h-accent { color: var(--forest); }
.section--forest .h-accent { color: var(--mist); }

.section-head--center {
  margin-inline: auto;
  text-align: center;
}

/* --- Numbered process cards, dashed border alternating forest/sage --- */
/* The framework is the one idea here a competitor cannot copy, and it was
   drawn as four identical boxes: sequence asserted, never shown. Now it is a
   line with four markers on it. The markers are rings, which is the wordmark's
   own replaced O, and they fill progressively across the four stages so the
   direction of travel is visible rather than implied by the numerals alone.
   The numerals stay: this is a real sequence, so they carry information. */
.stages {
  display: grid;
  gap: var(--s-7);
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
  margin: 0;
  position: relative;
}

.stage {
  position: relative;
  padding-left: calc(var(--s-6) + 18px);
}

/* The line. Vertical on narrow screens, running through the markers. */
.stages::before {
  content: '';
  position: absolute;
  left: 17px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: var(--rule);
}

.stage__num {
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--forest);
  background: var(--white);
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1;
  color: var(--forest);
}

/* Fill increases with the stage. Stage 4 is solid, so the eye reads a
   direction across the row without needing to read the numbers. */
.stage:nth-child(2) .stage__num { background: var(--mist); }
.stage:nth-child(3) .stage__num { background: var(--sage); }
.stage:nth-child(4) .stage__num {
  background: var(--forest);
  color: var(--white);
}

.stage h3 {
  margin-bottom: var(--s-3);
}

.stage p {
  font-size: var(--t-body);
  color: var(--ink-muted);
}

/* The framework on its forest band. Marker fills are remapped so every pair
   passes the measured contrast rules in tokens.css: white on forest 6.3, ink
   on mist and on sage 9+, forest on white 6.3. The progression now ends on a
   solid white ring, so the direction of travel reads as arriving at light.
   Body copy switches to mist (4.98:1, passes AA for normal text). */
.section--forest .stages::before { background: var(--rule-dark); }

.section--forest .stage__num {
  border-color: var(--mist);
  background: var(--forest);
  color: var(--white);
}

.section--forest .stage:nth-child(2) .stage__num {
  border-color: var(--mist);
  background: var(--mist);
  color: var(--ink);
}

.section--forest .stage:nth-child(3) .stage__num {
  border-color: var(--sage);
  background: var(--sage);
  color: var(--ink);
}

.section--forest .stage:nth-child(4) .stage__num {
  border-color: var(--white);
  background: var(--white);
  color: var(--forest);
}

.section--forest .stage p { color: var(--mist); }

/* The framework now carries its own CTA (the old interstitial band is gone).
   A step above the stage gap, so the button reads as the section's close
   rather than a fifth stage. */
/* The framework's closing beat: outcome, not process. Sits between the
   stages and the CTA so the ask lands after the payoff is stated. */
.framework__outcome {
  max-width: 46rem;
  margin: var(--s-8) auto 0;
  text-align: center;
  font-size: var(--t-lead);
  color: var(--mist);
}

.btn-row--after-stages { margin-top: var(--s-6); }

@media (min-width: 60rem) {
  .stages {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s-6);
  }

  .stage {
    padding-left: 0;
    padding-top: calc(var(--s-6) + 12px);
  }

  /* The line turns horizontal and stops at the outer markers rather than
     running off the edges of the row. */
  .stages::before {
    left: calc(12.5% + 18px);
    right: calc(12.5% + 18px);
    top: 18px;
    bottom: auto;
    width: auto;
    height: 1px;
  }

  .stage__num { top: 0; left: 0; }
}

/* --- Why owners choose Lumora ---
   These are claims, not steps, and they were the third of four consecutive
   card grids. Same-size cards of icon + heading + text as page structure is
   the lazy container, and by the third repetition a skimmer has stopped
   distinguishing sections. Now a two-column text block: hairline rule above
   each item, icon inline with its heading, no box, no shadow, no fill. It
   reads as prose beside the framework's line and the symptom cards. */
.reasons {
  display: grid;
  gap: var(--s-7) var(--s-9);
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: 62rem;
  margin-inline: auto;
}

@media (min-width: 48rem) { .reasons { grid-template-columns: repeat(2, 1fr); } }

/* Hanging icon: the glyph sits in its own left column and the heading and
   paragraph share one indented text spine, matching the problem rows so the
   page's two list sections rhyme. 40px matches the problem icons too. */
.reason {
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
  display: flex;
  gap: var(--s-5);
  align-items: flex-start;
}

.reason__icon {
  flex: none;
  width: 40px;
  height: 40px;
  color: var(--forest);
}

.reason h3 {
  margin: 0 0 var(--s-2);
}

.reason p {
  font-size: var(--t-body);
  color: var(--ink-muted);
  max-width: 46ch;
}

/* --- FAQ accordion --- */
.faq {
  max-width: 52rem;
  margin-inline: auto;
  border-radius: var(--radius);
  overflow: hidden;
}

.faq details {
  background: var(--forest);
  border-bottom: 1px solid var(--rule-dark);
}

.faq details:last-child { border-bottom: 0; }

.faq summary {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  cursor: pointer;
  list-style: none;
  color: var(--white);
  font-weight: 500;
  /* The caret rotates over 200ms; without this the background behind it
     jumped instantly — two halves of one state change at different speeds. */
  transition: background-color 0.15s var(--ease);
}

.faq summary::-webkit-details-marker { display: none; }

/* Caret rotates on open — the only state change, no height animation */
.faq summary::before {
  content: '';
  flex: none;
  width: 0;
  height: 0;
  border-left: 6px solid var(--mist);
  border-block: 5px solid transparent;
  transition: transform 0.2s var(--ease);
}

.faq details[open] summary::before { transform: rotate(90deg); }

@media (hover: hover) and (pointer: fine) {
  .faq summary:hover { background: var(--forest-dark); }
}

.faq summary:active { background: var(--forest-dark); }

.faq .faq__answer {
  padding: 0 var(--s-5) var(--s-5) calc(var(--s-5) + var(--s-3) + 6px);
  color: var(--mist);
  font-size: var(--t-body);
}

.faq .faq__answer a { color: var(--mist); }


/* --- Symptom cards: the reader diagnoses herself --------------------------- */
@media (min-width: 44rem) { }
@media (min-width: 66rem) { }

/* --------------------------------------------------------------------------
   9. Split blocks (image beside copy)
   -------------------------------------------------------------------------- */

.split {
  display: grid;
  gap: var(--s-7);
  align-items: center;
  grid-template-columns: 1fr;
}

@media (min-width: 56rem) {
  .split { grid-template-columns: repeat(2, 1fr); gap: var(--s-9); }


  /* Photo on the right, copy on the left */
  .split--reverse .split__media { order: 2; }
}

.split__body h2 { margin-bottom: var(--s-4); }

.split__body p + p { margin-top: var(--s-4); }

.split__body p { color: var(--ink-muted); }

.section--forest .split__body p { color: var(--mist); }

/* --------------------------------------------------------------------------
   12. CTA bands
   -------------------------------------------------------------------------- */

.cta-band { text-align: center; }

.cta-band h2 { max-width: 24ch; margin-inline: auto; }



/* The CTA band is used on both the forest band and a light stone section, so
   the paragraph colour cannot be pinned to either. Mist on stone is 1.1:1. */
.cta-band p {
  max-width: 52ch;
  margin: var(--s-4) auto 0;
  color: var(--ink-muted);
}

.section--forest .cta-band p,
.section--forest.cta-band p { color: var(--mist); }

/* --------------------------------------------------------------------------
   13. Contact
   -------------------------------------------------------------------------- */

.contact {
  display: grid;
  gap: var(--s-7);
  grid-template-columns: 1fr;
}

@media (min-width: 56rem) {
  .contact { grid-template-columns: 1.1fr 1fr; gap: var(--s-9); }
}

.contact-list {
  margin: var(--s-6) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-5);
}

/* Child combinator, not a descendant selector. The Social row nests its own
   <ul> of marks, and a descendant selector put a divider under each of those
   too — everything except the last one, so two stray rules under the icons. */
.contact-list > li {
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--rule);
}

.contact-list > li:last-child { padding-bottom: 0; border-bottom: 0; }

.contact-list__label {
  font-family: var(--font-body);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: var(--s-1);
}

.contact-list__value {
  font-size: var(--t-h4);
  font-weight: 500;
  line-height: 1.45;
  color: var(--ink);
}

/* No resting underline: the EMAIL label above already says what this is, and a
   rule under a long address is noisy. The affordance comes back on hover and
   focus, so it is still discoverable rather than colour-only. */
.contact-list__value a {
  color: var(--ink);
  text-decoration: none;
  overflow-wrap: anywhere;   /* long email must not overflow on narrow phones */
}

.contact-list__value a:hover,
.contact-list__value a:focus-visible {
  color: var(--forest);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}


/* Signature block under the founder's letter on the home page */
.letter-sign {
  margin-top: var(--s-5);
  color: var(--ink-muted);
  line-height: var(--lh-snug);
}

.letter-sign strong {
  font-family: var(--font-heading);
  font-size: var(--t-h4);
  color: var(--ink);
}

/* Card holding the entity details an acquirer and a client both want to see */
.entity-card {
  padding: var(--s-6);
  background: var(--forest);
  color: var(--mist);
  border-radius: var(--radius);
}

.entity-card h3 {
  color: var(--white);
  margin-bottom: var(--s-4);
  font-size: var(--t-h4);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.entity-card dl {
  display: grid;
  gap: var(--s-3);
  margin: 0;
  font-size: var(--t-small);
}

.entity-card dt {
  font-weight: 600;
  color: var(--mist);
}

.entity-card dd {
  margin: var(--s-1) 0 0;
}


/* --------------------------------------------------------------------------
   Offer card: the single deliverables panel, paired with a photograph
   -------------------------------------------------------------------------- */

.offercard {
  padding: var(--s-8) var(--s-7);
  background: var(--white);
  box-shadow: var(--shadow-sm);
  /* Sizes its own heading: see the rule below */
  container-type: inline-size;
}

.offercard h2 { margin-bottom: var(--s-4); }

/* "The Lumora Advisory" must hold one line, and the lead tier (58px at
   desktop) needs 581px for it while the split column offers ~452. The size
   is fitted to the card itself with container units: 100cqw is the card's
   content-box width (cqw already excludes the padding), divided by the
   measured width-per-px-of-font-size for this string in Playfair 700
   (10.0, set at 10.4 for slack). Scales with the column, so it holds at
   laptop widths a static cap would miss. On a phone this lands around
   28px, a step under the other lead headings; the price of one line. */
.offercard .h2--lead {
  font-size: min(var(--t-h2-lead), calc(100cqw / 10.4));
}

.offercard__intro {
  font-size: var(--t-body);
  color: var(--ink-muted);
}

/* Ticks appear exactly once on the page. A page where every list has green
   checkmarks reads as generated; one list of concrete deliverables does not. */
.ticks {
  display: grid;
  gap: var(--s-4);
  margin: var(--s-6) 0 0;
  padding: 0;
  list-style: none;
}

.ticks li {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: var(--s-3);
  align-items: start;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--ink-muted);
}

.ticks strong {
  display: block;
  font-weight: 600;
  color: var(--ink);
}

.ticks svg {
  width: 22px;
  height: 22px;
  margin-top: 1px;
  color: var(--forest);
}

@media (max-width: 55.9375rem) {
  .offercard { padding: var(--s-7) var(--s-6); }
}

/* --------------------------------------------------------------------------
   Contact form
   Lives inside .entity-card, so it sits on forest. Inputs are white with ink
   text; labels are mist (5.0:1 on forest).
   -------------------------------------------------------------------------- */

.formcard__intro {
  margin-bottom: var(--s-5);
  font-size: var(--t-small);
  color: var(--mist);
}

.cform { display: grid; gap: var(--s-4); }

/* Name and email share a row on desktop. Two full-width stacks become one,
   the biggest saving available without dropping a field, and it is how a short
   enquiry form is normally laid out anyway. Below the breakpoint the grid
   collapses to one column and every field is full width again. */
@media (min-width: 56rem) {
  .cform { grid-template-columns: 1fr 1fr; column-gap: var(--s-4); }
  .cform > * { grid-column: 1 / -1; }
  .cform__field--half { grid-column: span 1; }
}

.cform__field { display: grid; gap: var(--s-2); }

.cform__field label {
  font-family: var(--font-body);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--mist);
}

.cform__opt {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  opacity: 0.75;
}

.cform input,
.cform textarea {
  width: 100%;
  min-height: 48px;          /* tap target floor, unchanged */
  padding: var(--s-2) var(--s-4);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.5;
  color: var(--ink);
  background: var(--white);
  border: 2px solid transparent;
  border-radius: var(--radius-btn);
}

.cform textarea { min-height: 5.5rem; resize: vertical; }

.cform input:focus-visible,
.cform textarea:focus-visible {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(216, 200, 161, 0.45);
}

/* Set by main.js when a field fails native validation; cleared on input.
   Runtime-only attribute — never in the built HTML, so a class-usage scan
   will call it dead. It is not. */
.cform input[aria-invalid='true'],
.cform textarea[aria-invalid='true'] { border-color: var(--error); }

/* The spam trap. Off-screen rather than display:none, because some bots skip
   hidden fields but not positioned ones. */
.cform__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.cform__submit { width: 100%; margin-top: var(--s-2); }

/* The one thing on this card that has to be pressed. A white button on a
   forest card reads as another panel rather than an action, so the submit
   takes the brand's sand gold — the only colour on the card that is neither
   the card nor the input fields.

   Deliberately NOT folded into `.entity-card .btn--primary`: that rule is
   shared with `.section--forest .btn--primary`, which styles the CTA band on
   the home page. This overrides only the submit. Specificity is (0,3,0) on the
   hover, matching the rule above it, so source order decides — keep it here,
   below that rule.

   Measured: ink on gold 10.4:1, well past AA. Gold on forest 3.8:1, which
   clears the 3:1 floor for a UI surface. Gold still never carries text
   elsewhere; here it is the surface and ink is the text. */
.entity-card .cform__submit,
.entity-card .cform__submit:hover {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--ink);
}

.entity-card .cform__submit:hover { filter: brightness(0.94); }

.cform__legal {
  font-size: var(--t-small);
  color: var(--mist);
}

/* --- Submission result banner --------------------------------------------- */
.formnote {
  margin-bottom: var(--s-7);
  padding: var(--s-5) var(--s-6);
  background: var(--forest);
  color: var(--white);
  font-size: var(--t-lead);
}

.formnote__title {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-1);
  font-family: var(--font-heading);
  font-size: var(--t-h3);
  line-height: var(--lh-snug);
}

/* White, not gold: the tick is the confirmation itself, not decoration. */
.formnote__title svg {
  width: 1.5rem;
  height: 1.5rem;
  flex-shrink: 0;
  fill: none;
  stroke: var(--white);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.formnote[data-state='error'] {
  background: #F7E4DF;
  color: var(--ink);
  border-left: 4px solid var(--error);
}

.formnote[data-state='error'] .formnote__title svg {
  display: none;
}

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */

.footer {
  /* One step deeper than the forest content bands, so the footer reads as the
     page's floor rather than another section. Same hue, so it stays inside
     the five-colour guide: forest-dark is the declared primary shaded, not a
     sixth colour. Matters most where a forest band or the contact card sits
     just above the footer, which used to merge into it. */
  background: var(--forest-dark);
  color: var(--mist);
  padding-block: var(--section-y-s) 0;
}

.footer__grid {
  display: grid;
  gap: var(--s-7);
  grid-template-columns: 1fr;
}

@media (min-width: 40rem) {
  .footer__grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 64rem) {
  .footer__grid { grid-template-columns: 1.6fr 1fr 1fr; gap: var(--s-8); }
}

.footer__about p {
  margin-top: var(--s-5);
  font-size: var(--t-small);
  max-width: 34ch;
}

.footer .footer__contact {
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule-dark);
}

/* Contact details in the footer, as the template does */
.footer__contact {
  margin-top: var(--s-5);
  display: grid;
  gap: var(--s-2);
  font-size: var(--t-small);
}

.footer__contact a { border-bottom: 1px solid transparent; }
.footer__contact a:hover { border-bottom-color: var(--mist); text-decoration: none; }

.footer h3 {
  color: var(--white);
  font-family: var(--font-body);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  margin-bottom: var(--s-4);
}

.footer ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-3);
  font-size: var(--t-small);
}

/* Scoped to the link lists, not to every anchor in the footer. A bare
   `.footer a` also caught the wordmark, and because text-decoration inherits
   into descendants and cannot be cancelled by a child rule, hovering the logo
   struck a line through LUM(mark)RA and "Projects Co" with no way to undo it
   from .wordmark__name. Same species as the .contact-list li bug. */
.footer li a {
  color: var(--mist);
  text-decoration: none;
  transition: color 0.15s var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .footer li a:hover { color: var(--white); text-decoration: underline; }
}

.footer li a:active { opacity: 0.7; }

/* The wordmark keeps the footer's light-on-dark colour but never an underline */
.footer .wordmark { color: var(--mist); text-decoration: none; }

.footer__bar {
  margin-top: var(--section-y-s);
  padding-block: var(--s-5);
  border-top: 1px solid var(--rule-dark);
  font-size: var(--t-small);
}

.footer__bar-inner {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  align-items: center;
  text-align: center;
}

@media (min-width: 48rem) {
  .footer__bar-inner { flex-direction: row; justify-content: space-between; text-align: left; }
}

/* --------------------------------------------------------------------------
   14b. Interior page header (About, Contact)
   -------------------------------------------------------------------------- */

/* Deliberately not a hero. Interior pages open with a compact forest band so
   they read as part of the site without competing with the home page. */
.pagehead {
  background: var(--forest);
  color: var(--mist);
  padding-block: var(--section-y-s);
}

.pagehead h1 { color: var(--white); }

/* For a page whose head is a single word. The standard pagehead reserves room
   for a lead paragraph; contact no longer has one. */
.pagehead--slim { padding-block: var(--s-6); }

.pagehead p {
  max-width: 48ch;
  margin-top: var(--s-4);
  font-size: var(--t-lead);
}



/* --- Footer social marks --------------------------------------------------
   Not links yet: there are no accounts. Rendered as plain marks so nothing
   looks clickable when it is not.
   -------------------------------------------------------------------------- */
.contact-list .social {
  display: flex;
  gap: var(--s-4);
  margin-top: var(--s-2);
  padding: 0;
  list-style: none;
}

/* On a light surface the marks take the brand green, not the footer's mist. */
.social--light .social__mark { color: var(--forest); opacity: 1; }

.footer .social {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--s-4);
  margin-top: var(--s-6);
  padding: 0;
  list-style: none;
}

.social__mark {
  display: grid;
  place-content: center;
  width: 24px;
  height: 24px;
  color: var(--mist);
  opacity: 0.85;
}

.social__mark svg { width: 22px; height: 22px; }

/* When these become links, they need a hover and a 44px hit area */
.social a.social__mark {
  width: 44px;
  height: 44px;
  margin: -10px;
  opacity: 1;
  transition: color 0.15s var(--ease);
}

.social a.social__mark:hover { color: var(--white); }

/* --------------------------------------------------------------------------
   15. Legal pages
   -------------------------------------------------------------------------- */

.legal { padding-block: var(--section-y); }

.legal__head { margin-bottom: var(--s-8); }

.legal__updated {
  margin-top: var(--s-3);
  font-size: var(--t-small);
  color: var(--ink-muted);
}

.prose h2 {
  font-size: var(--t-h3);
  margin-top: var(--s-9);
  margin-bottom: var(--s-5);
}

.prose h2:first-child { margin-top: 0; }

.prose h3 {
  font-size: var(--t-h4);
  margin-top: var(--s-6);
  margin-bottom: var(--s-3);
}

.prose p,
.prose ul,
.prose ol { margin-top: var(--s-4); }

.prose ul,
.prose ol { padding-left: var(--s-5); }

.prose li + li { margin-top: var(--s-2); }

.prose a { color: var(--forest); }

/* Numbered legal clauses: the number sits in its own column so the text keeps
   a straight left edge down the page. */
.clause {
  display: grid;
  grid-template-columns: 3.5rem 1fr;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

.clause__n {
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

.subclauses {
  margin: var(--s-3) 0 0 3.5rem;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-3);
}

.subclauses li {
  display: grid;
  grid-template-columns: 3.5rem 1fr;
  gap: var(--s-2);
}

@media (max-width: 34rem) {
  .clause, .subclauses li { grid-template-columns: 2.75rem 1fr; }
  .subclauses { margin-left: 2.75rem; }
}

/* These three declarations used to be inline style attributes. The CSP sets
   style-src 'self' with no 'unsafe-inline', which blocks style *attributes*
   outright, so the browser dropped them and the paragraphs rendered with no
   top margin in full-strength ink. Keep styling in this file; inline style
   attributes do not work on this site. */
.sprite { position: absolute; }

.note-muted {
  margin-top: var(--s-5);
  color: var(--ink-muted);
}


/* --------------------------------------------------------------------------
   16. Error page
   -------------------------------------------------------------------------- */

.error-page {
  display: grid;
  place-content: center;
  min-height: 60vh;
  padding-block: var(--section-y);
  text-align: center;
}

.error-page__code {
  font-family: var(--font-heading);
  font-size: clamp(4rem, 3rem + 5vw, 7rem);
  font-weight: 700;
  line-height: 1;
  color: var(--forest);
}

/* --------------------------------------------------------------------------
   17. Print
   -------------------------------------------------------------------------- */

@media print {
  .nav,
.cta-band,
.btn-row,
.skip-link { display: none !important; }
  body { color: #000; background: #fff; }
  .section { padding-block: 1rem; }
}
