/* ============================================================================
   CarbonCore — Alternate 2 ("Horizon")
   ============================================================================

   Loads after site.css AND alternate.css. Alternate 1 corrected the delivery;
   this one takes a position.

   The idea comes out of the logo and nothing else. The mark is a C cut by a
   horizon line — surface above, subsurface below. That line is the only thing
   in the identity that is genuinely theirs, and the delivered site uses it
   exactly once, at 48 pixels wide, in the header. Here it becomes the
   structural device of the whole page: every section boundary is a horizon,
   every section is stamped with the depth it sits at, and the page reads top to
   bottom as a descent from the surface to the storage formation.

   That is also the company's actual argument — that a CCS project is one
   continuous path from industrial source to permanent storage. The layout now
   makes that argument instead of describing it.

   Content, wording, structure and navigation are untouched, as in alternate 1.
   ========================================================================== */

/* ── Palette ───────────────────────────────────────────────────────────────
   The copper is pushed warmer to match the redrawn mark; slate is promoted
   from a footnote colour to a carrier of the identity. */

:root {
  --copper-500: #d2622a;
  --copper-700: #ae4f20;
  --alt2-slate: #93a5b0;
  --alt2-horizon: rgba(147, 165, 176, 0.28);
  --alt2-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Roboto Mono", Menlo, monospace;
}

/* ── 0. The typeface the page is actually set in ───────────────────────────
   The design is drawn for Inter, and the delivered stack is
   `Inter, Aptos, "Helvetica Neue", Arial`. Measured in the browser: neither
   Inter nor Aptos is installed here, so the site renders in Helvetica Neue on
   a Mac — and on Windows, where "Helvetica Neue" is absent too, it lands on
   **Arial**. A 1982 grotesque with a synthesised 650 weight is not what makes
   a page look current.

   No font is bundled, on purpose: the supplier avoided the licensing and the
   network dependency, and that decision is worth keeping. So instead of
   downloading anything, the operating system's own interface face is put in
   front of the old fallbacks — SF Pro on Apple, Segoe UI Variable on Windows,
   Roboto on Android. All three are variable faces with a real 650 weight, and
   all three are what current software on those platforms is set in.

   Inter stays first: where it IS installed, the page still renders as drawn.

   Update 06.08.2026: Inter is now bundled and served from this site's own
   bucket, so the page renders as drawn everywhere — and still makes no request
   to any host but its own. Explicitly NOT via Google Fonts: that would fetch
   from a third party on every visit and hand them the visitor's IP address,
   which has been litigated in Germany (LG München I, 3 O 17493/20) and would
   contradict this site's own cookie page, which promises no external services.

   The system stack stays behind it as the fallback, so nothing regresses if the
   font file ever fails to load. */

@font-face {
  font-family: "Inter Web";
  src: url("../fonts/InterVariable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  /* Text is readable from the first paint in the system face and swaps when the
     font arrives — never an empty page waiting on a 345 KB download. */
  font-display: swap;
}

:root {
  --font-sans:
    "Inter Web", Inter,
    -apple-system, BlinkMacSystemFont,
    "Segoe UI Variable Display", "Segoe UI",
    Roboto, "Noto Sans",
    Aptos, "Helvetica Neue", Arial, sans-serif;
}

body {
  /* Real weights instead of smeared ones. Helvetica Neue has no 650, so the
     browser was synthesising it — which is what made the headings look slightly
     mushy at large sizes. */
  font-synthesis: none;
  font-optical-sizing: auto;
}

/* ── 1. The horizon ────────────────────────────────────────────────────────
   A full-bleed hairline at every section boundary, with a copper tick sitting
   on it at the container's left edge — the same relationship as in the mark,
   where the line runs past the C on both sides. */

.section,
.section-sm {
  position: relative;
}

.section + .section::before,
.section + .section-sm::before,
.section-sm + .section::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--alt2-horizon);
  pointer-events: none;
}

.section + .section::after,
.section + .section-sm::after {
  content: "";
  position: absolute;
  top: -3px;
  left: max(var(--gutter), calc((100% - var(--container)) / 2));
  width: 34px;
  height: 5px;
  background: var(--copper-500);
  pointer-events: none;
}

/* ── 2. Depth stamps ───────────────────────────────────────────────────────
   Each eyebrow gains a monospaced depth marker, so the page is measured rather
   than merely long. Set on the section, read by the eyebrow — no markup change.

   Deliberately generic: any section without a stamp simply shows none. */

.eyebrow {
  font-family: var(--alt2-mono);
  letter-spacing: 0.12em;
  font-size: 0.7rem;
  gap: 0.7rem;
}

/* The attribute sits on the eyebrow, NOT on the section: attr() can only read
   from the element its pseudo-element belongs to. Reading it from an ancestor
   silently yields an empty string — which is exactly how the first version of
   this shipped, looking correct in the markup and rendering nothing. */
.eyebrow[data-depth]::after {
  content: attr(data-depth);
  margin-left: auto;
  padding-left: 1.5rem;
  color: var(--alt2-slate);
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

/* On narrow screens the eyebrow wraps and `margin-left: auto` drops the stamp
   at the end of the last line, where it reads as part of the label
   ("… ENGINEERING BOUTIQUE  0 M") instead of as marginalia. The depth scale is
   a reading aid for the wide layout; on a phone it is noise. */
@media (max-width: 900px) {
  .eyebrow[data-depth]::after {
    display: none;
  }
}

/* Copper that survives a light ground.
   ---------------------------------------------------------------------------
   Measured at 1440px: the copper labels sit at 3.55-3.65:1 on the light
   sections. WCAG AA wants 4.5:1 for text this size, and these are 11-12px —
   the smallest type on the page, so it is the least forgiving place to be
   short. Part of that is inherited (site.css line 1942 overrides the darker
   --copper-700 with --copper-500 for every eyebrow), part of it I caused by
   warming the copper to match the redrawn mark.

   So: the warm copper stays where it works — on the navy grounds, where it
   measured 4.66:1 — and the light sections get a darker one. Same hue, same
   family, enough weight to be read.

   Two coppers, chosen per ground: no single value clears 4.5:1 against both
   #F1F1F2 and #2E3947 — the two requirements exclude each other — so one
   accent colour for both grounds is not a thing that exists. */

:root {
  --alt2-copper-on-light: #9a4517;
  --alt2-copper-on-dark: #e0763a;
  /* Both neutrals sat just under the line on their own grounds (4.21-4.42 on
     paper, 3.93 on the dark cards). Nudged rather than replaced. */
  --slate: #5a6874;
  --steel: #93aebb;
}

.section:not(.section-dark):not(.section-ink) .eyebrow,
.section:not(.section-dark):not(.section-ink) .micro,
.section:not(.section-dark):not(.section-ink) .card-index,
.section-light .number,
.section-light .arrow,
.lifecycle-item .number,
.lifecycle-item .arrow {
  color: var(--alt2-copper-on-light);
}

/* Dark surfaces need the copper to go the other way. `.team-lead` is the case
   that proves the section class alone is not enough: it is a DARK card sitting
   inside a `.section-light`, so the rule above darkened copper onto near-black
   and pushed it to 2.8:1 — worse than what was delivered. Measured, then
   scoped. */
.section-dark .num,
.section-ink .num,
.section-dark .number,
.section-ink .number,
/* Specificity, not taste: the light-ground rule above carries four classes
   (:not() contributes its argument), so `.team-lead .micro` at two would lose
   and the label would stay dark on near-black. Matched deliberately. */
.section:not(.section-dark):not(.section-ink) .team-lead .micro,
.section:not(.section-dark):not(.section-ink) .team-lead .team-contact,
.section:not(.section-dark):not(.section-ink) .team-lead .card-index {
  color: var(--alt2-copper-on-dark);
}

/* The card surfaces in the dark sections composite to #2E3947 — appreciably
   lighter than the section itself, because the card lays white at low alpha
   over it. Against that, even the light copper only reaches 3.8:1. This one
   tint is opened up further; it is the only place on the site that needs it. */
.section-dark .card .card-index,
.section-ink .card .card-index,
.section-dark .card-index,
.section-ink .card-index {
  color: #ef9257;
}

/* The 404 page paints its dark ground on <main>, not on a section, so every
   dark-context rule in the delivered CSS misses it — same gap as .page-hero. */
.not-found .lead,
.not-found .copy,
.not-found p {
  color: #c7d2da;
}

/* Technical labels read as instrument readouts, not as small headlines. */
.micro,
.status,
.nav-cta,
.btn,
.button {
  font-family: var(--alt2-mono);
  letter-spacing: 0.1em;
}

.status {
  border-radius: 0;
  font-family: var(--alt2-mono);
}

/* Numbers align in columns wherever they appear. */
table,
.stat,
.metric,
.figure {
  font-variant-numeric: tabular-nums;
}

/* ── 3. Drawing, not decoration ────────────────────────────────────────────
   Rounded corners and soft shadows are the visual language of consumer
   software. This is a firm that sells subsurface judgement; the reference for
   its surfaces is a drawing sheet — hairline rules, square corners, one
   accent that means something. */

:root {
  --radius-xs: 0;
  --radius-sm: 0;
  --radius-md: 0;
  --radius-lg: 0;
  --shadow-sm: none;
  --shadow-lg: none;
}

.card,
.tile,
.service-card,
.insight-card,
.operating-strip article {
  position: relative;
  border-radius: 0;
  transition:
    border-color 0.3s var(--ease),
    background-color 0.3s var(--ease);
}

/* Hover raises a copper edge on the left — the same gesture as the section
   tick, one level down. No lift, no shadow. */
.card::before,
.tile::before,
.insight-card::before,
.service-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--copper-500);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.32s var(--ease);
}

.card:hover::before,
.tile:hover::before,
.insight-card:hover::before,
.service-card:hover::before,
.card:focus-within::before,
.insight-card:focus-within::before {
  transform: scaleY(1);
}

/* Alternate 1 lifted cards; here that would fight the drawing metaphor. */
.card:hover,
.tile:hover,
.service-card:hover,
.insight-card:hover {
  transform: none;
  box-shadow: none;
}

/* ── 3b. Four cards, one baseline grid ─────────────────────────────────────
   The pillar cards each hold icon / label / heading / body. Icon, label and
   heading line up across the row because they are fixed height; the body does
   not, because the headings run one, two or three lines. Measured: the four
   paragraphs start at three different heights, so the row reads as drifting
   rather than as a set.

   Subgrid makes the four cards share the PARENT's rows, so every element
   aligns with its counterpart regardless of how long the heading above it is —
   without inventing a min-height that guesses at the longest text and breaks
   the moment the copy changes.

   Behind @supports: where subgrid is unavailable the cards keep the delivered
   behaviour exactly. Nothing is lost, only the alignment. */

@supports (grid-template-rows: subgrid) {
  .pillar-grid {
    grid-template-rows: auto auto auto auto;
  }

  .pillar {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 4;
  }
}

/* ── 3c. A box the delivery never meant to draw ────────────────────────────
   site.css declares `.principle` twice. Line 1386 gives it the treatment it
   was designed for — a 2px copper rule on top, `padding-top: 1.3rem`, no frame.
   Line 2625 then sweeps it into a list with `.card`, `.team-card` and four
   others and hands the whole group `border: 1px` plus a white background.

   The second rule wins, so `.principle` becomes a bordered card — but its
   padding is still top-only, because a text block never needed sides. Measured:
   `padding: 20.8px 0 0`, heading ending 1px from its own border, bottom gap
   drifting between 17px and 43px with the length of the copy. The other six
   classes in that rule bring their own padding and are unaffected; this is the
   only one it breaks, on three pages.

   Restoring the first rule is both the fix and the better design here: a ruled
   entry is exactly this edition's vocabulary, and a soft white card is exactly
   what it is trying not to be. */

.principle {
  border: 0;
  border-top: 2px solid var(--copper-500);
  background: transparent;
  padding: 1.2rem 0 0;
}

.principle h3,
.principle .h3 {
  font-size: 1.28rem;
  line-height: 1.25;
  letter-spacing: -0.014em;
  margin-bottom: 0.5rem;
}

.principle p {
  max-width: 36ch;
  margin-bottom: 0;
}

/* ── 3d. The accordion on a dark ground ────────────────────────────────────
   Same family of defect as the .page-hero one: the accordion's colours were
   written for a light ground, but on services.html it sits inside a
   `.section-dark` (#0C1929). Measured there: the button label — a bare
   <strong> — at 1.03:1, and the panel body at 1.84:1. 1.03 is not "hard to
   read", it is the same colour twice.

   It survived because the earlier version of my contrast check walked a
   hand-picked list of ten tags and <strong> was not one of them. The check now
   walks every element that carries text. */

.section-dark .accordion-button,
.section-ink .accordion-button,
.section-dark .accordion-button strong,
.section-ink .accordion-button strong {
  color: #e6e8eb;
}

.section-dark .accordion-content,
.section-ink .accordion-content,
.section-dark .accordion-panel,
.section-ink .accordion-panel {
  color: #c7d2da;
}

/* ── 4. The hero ───────────────────────────────────────────────────────────
   The delivered hero centres its two columns against each other. Hanging the
   copy from a horizon rule instead puts the headline and the figure on one
   datum — and repeats the mark at page scale. */

.hero {
  position: relative;
}

.hero .eyebrow {
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--alt2-horizon);
}

.hero .display,
.hero .h1 {
  margin-top: 1.6rem;
}

/* The rule under the headline in the delivered version is a stray dash; give it
   the horizon's proportions. */
.hero hr,
.hero .rule {
  border-top-width: 3px;
  border-color: var(--copper-500);
  max-width: 88px;
}

.hero-visual img {
  border-radius: 0;
  border: 1px solid var(--alt2-horizon);
}

/* ── 5. Navigation ─────────────────────────────────────────────────────────
   Alternate 1 grew a line from the left on hover. Here the line is the site's
   grammar, so it is always present and merely lights up — the nav becomes a
   row of markers on one horizon. */

/* One continuous line under the whole nav — segments under each word would read
   as tabs, not as a horizon. */
.desktop-nav .nav-list {
  position: relative;
}

.desktop-nav .nav-list::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -8px;
  height: 1px;
  background: var(--alt2-horizon);
  pointer-events: none;
}

/* The per-link mark sits ON that line and lights up — the marker, not the line. */
.nav-link::after {
  transform: scaleX(0);
  background: var(--copper-500);
  bottom: -8px;
  height: 2px;
  z-index: 1;
}

.nav-link:hover::after,
.nav-link:focus-visible::after,
.nav-link[aria-current="page"]::after {
  transform: scaleX(1);
}

/* The mobile nav has no such row; keep the delivered behaviour there. */
.mobile-nav .nav-list::after {
  display: none;
}

.nav-cta {
  border-radius: 0;
}

/* ── 5b. The lockup, at a size where it can be read ────────────────────────
   The tagline was set at 8.6 units in a 520-unit canvas that the header shows
   220-288px wide — 3.6 to 4.8 rendered pixels. No screen renders that; it was
   grey texture, not words. The SVG now sets it at 14, which gets it to ~7.8px
   at the delivered display width. That is better and still not enough, so the
   lockup itself is shown larger here.

   The header is 78px tall; at 340px wide the lockup is 60px, which still sits
   inside it. Below 1200px the nav needs the room back, so the size steps down
   and the tagline goes with it — at that point it is decoration and hiding it
   is more honest than pretending it can be read. */

.brand img {
  width: 340px;
}

/* Below 1200px the navigation needs the room back, so the lockup shrinks — and
   at that size the claim would land near 6.7px, which is not small type, it is
   texture. So the compact lockup takes over: same construction, no claim.
   Dropping a line nobody can read is more honest than shipping grey noise. */
@media (max-width: 1200px) {
  .brand img {
    width: 250px;
    height: auto;
    content: url("../logos/carboncore-compact-dark.svg");
  }
}

/* ── 6. Type ───────────────────────────────────────────────────────────────
   Tighter than alternate 1 at the top end and heavier in the eye: an
   engineering firm's headline should look machined, not airy. */

.display {
  font-size: clamp(3rem, 5.4vw, 5.8rem);
  letter-spacing: -0.05em;
  font-weight: 700;
}

.h1 {
  letter-spacing: -0.05em;
  font-weight: 700;
}

.h2 {
  letter-spacing: -0.04em;
  font-weight: 700;
}

/* Headlines sized by their COLUMN, not by the window.
   ---------------------------------------------------------------------------
   The delivered .h2 scales with the viewport (4.4vw), which is right for a
   full-width headline and wrong for the split layouts: at 1440px it renders
   54px inside columns of 382-534px. "specifically" alone then eats an entire
   line, which is why headings broke as "Five evidence / gates for / German /
   onshore CCUS." — one word stranded per line.

   text-wrap: balance cannot repair that; it distributes lines, it does not
   choose a type size. The mismatch is between the size and the measure, so the
   size is what has to give. Container query units make the headline read the
   column it actually sits in: 8.6cqi is ~33px in the 382px column and ~53px in
   the 620px one, capped so a wide column never overshoots the delivered scale. */

.home-intro > *,
.lifecycle-shell > *,
.anchor-split > *,
.cta-panel > * {
  container-type: inline-size;
}

.home-intro .h2,
.lifecycle-shell .h2,
.anchor-split .h2,
.cta-panel .h2 {
  font-size: clamp(1.9rem, 8.6cqi, 3.4rem);
}

/* The lead is the sentence people actually read. Give it room and weight. */
.lead {
  font-size: clamp(1.2rem, 1.7vw, 1.5rem);
  color: var(--graphite);
}

.section-dark .lead,
.section-ink .lead,
.page-hero .lead,
.hero .lead {
  color: #c7d2da;
}

/* The dark context that the delivered CSS forgets.
   ---------------------------------------------------------------------------
   Every inner page opens with <section class="page-hero">, which paints
   #07111F. But the rules that lighten body text are written for .section-dark
   and .section-ink only, so .page-hero never gets them and the lead paragraph
   keeps its default --graphite (#3A4652). Dark grey on near-black, measured at
   1.96:1 where 3:1 is the floor for text this size — on twelve pages, in the
   first paragraph a visitor reads.

   It looks deliberate rather than broken, which is why it survived: the text is
   visible enough to be mistaken for a dim style choice. */

.page-hero .copy,
.page-hero p:not([class]),
.page-hero li,
.page-hero .muted {
  color: #b3c0ca;
}

.page-hero .rule,
.page-hero hr {
  border-color: rgba(147, 165, 176, 0.28);
}

/* Pull quotes get the horizon rather than a bar in the margin. */
.prose blockquote {
  border-left: 0;
  padding: 1.6rem 0;
  border-top: 3px solid var(--copper-500);
  border-bottom: 1px solid var(--alt2-horizon);
  font-family: inherit;
}

/* ── 7. Contrast between the layers ────────────────────────────────────────
   Surface sections lighter, subsurface sections deeper, so scrolling reads as
   descending. The delivered palette already implies this; it just never
   commits to it. */

.section-light {
  background: #fbfaf8;
}

.section-dark {
  background: #0c1929;
}

.section-ink {
  background: #060d17;
}

/* ── 8. Motion, honestly ───────────────────────────────────────────────────
   One effect only, and it is informational: the horizon ticks draw themselves
   as the section arrives. Everything else stays still.

   Scroll-driven animation is progressive — where `animation-timeline` is not
   supported the tick is simply always at full width, which is the same design
   without the reveal. Nothing depends on it, and the reduced-motion rule from
   alternate.css still switches it off. */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .section + .section::after,
    .section + .section-sm::after {
      animation: alt2-tick linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 22%;
    }

    @keyframes alt2-tick {
      from {
        transform: scaleX(0);
      }
      to {
        transform: scaleX(1);
      }
    }
  }
}

.section + .section::after,
.section + .section-sm::after {
  transform-origin: left;
}

/* ── 8b. Touch targets ─────────────────────────────────────────────────────
   Measured on a 375px viewport: the footer links are 17px tall, the contact
   links 26px. WCAG 2.5.8 sets the floor at 24x24 CSS pixels, so the footer
   navigation fails it outright — and on a phone that is not a formality, it is
   the difference between opening the page you meant and the one below it.

   Raised to 44px, which is the comfortable target rather than the legal
   minimum. Only on touch-sized viewports; the desktop rows keep their spacing. */

@media (max-width: 900px) {
  .footer-links a,
  .mobile-nav .nav-link,
  .contact-line a,
  .contact-list a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* ── 8c. Craft ─────────────────────────────────────────────────────────────
   The details that decide whether a page reads as considered or merely
   assembled. None of these are visible on their own; together they are most of
   the difference. */

/* The header is fixed at 88px and no target reserves room for it, so every
   in-page link — including "Explore our approach", the first button on the
   site — scrolls its destination underneath the header. */
:target,
[id] {
  scroll-margin-top: calc(var(--header-h) + 2rem);
}

/* The stamp inherits the eyebrow's uppercase and renders "0 M". The unit is a
   metre; it is written lowercase. */
.eyebrow[data-depth]::after {
  text-transform: none;
  letter-spacing: 0.06em;
}

/* One hairline value for every rule on the page. The delivered CSS mixes
   rgba(58,70,82,.22) and rgba(221,226,230,.16) with per-component borders, so
   dividers sit at slightly different weights depending on where they are. */
.rule,
hr:not(.horizon),
.prose th,
.prose td {
  border-color: var(--alt2-horizon);
}

/* Underlines that clear the descenders instead of striking through them. */
.prose a,
.copy a,
p a {
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  transition: text-decoration-color 0.25s var(--ease);
}

.prose a:hover,
.copy a:hover,
p a:hover {
  text-decoration-color: currentColor;
}

/* Uniform focus treatment. The delivered stylesheet leaves buttons and cards to
   the browser default, which on a dark ground is a thin ring that disappears. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 2px solid var(--copper-500);
  outline-offset: 3px;
}

.section-dark a:focus-visible,
.section-ink a:focus-visible,
.hero a:focus-visible,
.page-hero a:focus-visible,
.site-header a:focus-visible {
  outline-color: var(--aqua-400);
}

/* Form fields: the caret and the focused border should agree with the brand
   rather than with the browser. */
input,
textarea,
select {
  caret-color: var(--copper-500);
  border-radius: 0;
}

input:focus,
textarea:focus,
select:focus {
  border-color: var(--copper-500);
}

/* Tabular figures wherever numbers are meant to line up in a column. */
.card-index,
.num,
.number,
.lifecycle-item .number,
time {
  font-variant-numeric: tabular-nums;
  font-family: var(--alt2-mono);
}

/* ── 8d. Motion ────────────────────────────────────────────────────────────
   The page was correct and completely still. Motion here is not decoration; it
   is the instrument metaphor carried into time: things settle into position
   rather than appear, the way a needle comes to rest.

   Three rules kept it from becoming the usual scroll-animation noise:

   1. NOTHING is hidden in a base rule. Every "from" state lives inside
      @keyframes, so if animations never run — old browser, reduced motion, a
      stylesheet that fails — the page is simply the finished state. The common
      way to break a site with scroll animations is `opacity: 0` in the base
      rule and a reveal that never fires.
   2. Only transform and opacity are animated. No layout, no paint thrash.
   3. Distances are small — 10px, not 40. At 40px the eye reads "website doing
      a trick"; at 10px it reads "solid".

   Reduced motion is already handled globally in alternate.css, which forces
   every duration to 0.01ms — with `both` fill, that lands on the end state
   immediately. Nothing here needs to opt out separately. */

@media (prefers-reduced-motion: no-preference) {
  /* ---- The hero, once, on load ------------------------------------------
     Above the fold there is nothing to scroll into view, so the hero gets a
     short entrance in reading order. It is over in under a second. */

  @keyframes alt2-rise {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
  }

  .hero .eyebrow,
  .hero .display,
  .hero .h1,
  .hero .rule,
  .hero .lead,
  .hero .copy,
  .hero .button-row,
  .hero-visual {
    animation: alt2-rise 0.7s var(--ease) both;
  }

  .hero .display,
  .hero .h1 { animation-delay: 0.06s; }
  .hero .rule { animation-delay: 0.14s; }
  .hero .lead { animation-delay: 0.18s; }
  .hero .copy { animation-delay: 0.24s; }
  .hero .button-row { animation-delay: 0.3s; }
  /* The figure comes last and from further out — it is the object being
     placed on the sheet. */
  .hero-visual { animation-delay: 0.12s; animation-duration: 0.9s; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* ---- Everything below the fold settles as it arrives ----------------
       Scroll-driven, so the movement is tied to the reader's own scrolling
       rather than to a timer that fires whether they are looking or not. */

    @keyframes alt2-settle {
      from {
        opacity: 0;
        transform: translateY(12px);
      }
    }

    .section .eyebrow,
    .section .h2,
    .section .lead,
    .section .copy,
    .section .rule {
      animation: alt2-settle linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 30%;
    }

    /* Grids come in as a row, one card behind the next. The stagger is by
       position, so it reads as a sequence rather than a flock. */
    .pillar,
    .lifecycle-item,
    .card,
    .tile,
    .insight-card,
    .principle {
      animation: alt2-settle linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 26%;
    }

    .pillar:nth-child(2),
    .card:nth-child(2),
    .insight-card:nth-child(2),
    .principle:nth-child(2) { animation-range: entry 3% entry 29%; }

    .pillar:nth-child(3),
    .card:nth-child(3),
    .insight-card:nth-child(3),
    .principle:nth-child(3) { animation-range: entry 6% entry 32%; }

    .pillar:nth-child(4),
    .card:nth-child(4) { animation-range: entry 9% entry 35%; }

    /* The horizon draws itself, left to right, like a rule being ruled.
       Its copper tick already did this; now the line does too. */
    @keyframes alt2-draw {
      from { transform: scaleX(0); }
    }

    .section + .section::before,
    .section + .section-sm::before,
    .section-sm + .section::before {
      transform-origin: left;
      animation: alt2-draw linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 18%;
    }

    /* The figures drift a few pixels against the scroll. Deliberately tiny:
       enough to give the page depth, not enough to notice as an effect. */
    .hero-visual img,
    .anchor-visual img {
      animation: alt2-parallax linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }

    @keyframes alt2-parallax {
      from { transform: translateY(-8px); }
      to   { transform: translateY(8px); }
    }
  }
}

/* ── 9. Print ──────────────────────────────────────────────────────────────
   alternate.css already turns the dark sections light. The horizon survives —
   on paper it is the one thing that still carries the identity. */

@media print {
  /* Insurance: a print context has no scrolling and may have no running
     timeline, so any element still waiting on an entrance animation would
     print blank. Nothing animates on paper anyway. */
  *,
  *::before,
  *::after {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  .section + .section::before,
  .section + .section-sm::before {
    background: #000 !important;
  }

  .section + .section::after {
    background: #000 !important;
    animation: none !important;
    transform: none !important;
  }
}
