/* ==========================================================================
   landing.css
   Astrology Eclipse — landing page (index.html) only.

   Deliberately separate from css/style.css, which styles the reading report
   inside onboarding.html. The two pages no longer share a visual language,
   and keeping one stylesheet per language means a change here can never
   reach into a page that was already signed off.

   Direction: warm paper ground, big rounded panels that sit ON that ground,
   a high-contrast display serif for everything editorial, and one dusty
   mauve and one deep cocoa as the only saturated colours.
   ========================================================================== */

:root {
  /* Surfaces, lightest to darkest. Every one is warm — a neutral grey
     anywhere in this palette immediately reads as dirt. */
  --paper: #e9e0d5;   /* the outermost page ground, visible between panels */
  --panel: #f3ece3;   /* the large rounded panels */
  --panel-lift: #faf6f0; /* cards sitting on a panel */
  --mauve: #d6c9cf;   /* dusty plum band */
  --cocoa: #6b4438;   /* closing CTA + footer */
  --cocoa-hover: #7d5142;
  --cream: #f7f1e9;   /* text and fills ON cocoa. Deliberately not
                         --panel-lift (#faf6f0) — keep the two distinct. */

  /* Ink */
  /* Measured on --panel, the lightest ground these ever sit on. */
  --ink: #402f28;     /* display headings — 10.8:1 */
  --body: #6a5850;    /* body copy — 5.7:1 */
  --muted: #78665d;   /* tertiary — 4.65:1. The obvious lighter choice here
                         (#93827a) looks correct on screen but measures
                         3.1:1, and it carries the form hints and the strip
                         labels, so it has to clear AA. */
  --line: #ddd0c3;

  /* The chip colours in the method band. Fills only — each carries white
     text, so all four are dark enough to hold it. */
  --plum: #94465c;
  --olive: #6a5b49;
  --violet: #6f6394;
  --teal: #1e5850;

  --error: #a3341c;

  /* Type. Playfair carries every heading; body copy stays on the house
     sans so long paragraphs remain plain and readable. */
  --serif: "Playfair Display", Georgia, "Times New Roman", serif;
  --sans: "Helvetica Neue", Helvetica, Arial, sans-serif;

  --wrap: 1140px;
  --round: 44px;      /* the panel corner radius — the page's signature */

  --ease: cubic-bezier(0.22, 0.68, 0.36, 1);
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The nav pill is fixed and ~83px tall including its offset. Without this
     every in-page anchor parks its heading underneath it — #start worst of
     all, since the hero has no top padding of its own. */
  scroll-padding-top: 104px;
}

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

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

/* Filter definitions only — carries no visible artwork. Taken out of flow as
   well as zeroed, so it can never contribute a stray line box at the very top
   of the document. */
.ec-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.ec-wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 clamp(20px, 5vw, 48px);
}

/* Skip link. Off-screen until focused, then lands above the fixed nav pill.
   Positioned rather than display:none so it stays in the tab order. */
.ec-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
}

.ec-skip:focus {
  left: 12px;
  top: 12px;
  padding: 12px 20px;
  font-size: 16px;
  font-weight: 700;
  color: var(--panel-lift);
  background: var(--cocoa);
  border-radius: 999px;
  text-decoration: none;
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Panels

   The page is a stack of rounded blocks on the paper ground. Each one rounds
   its own corners rather than relying on a parent, so sections can be
   reordered without the radii going wrong.
   -------------------------------------------------------------------------- */

.ec-panel--top {
  /* The clearance the fixed nav needs. It used to be padding on THIS element,
     which meant the hero — and therefore the sky inside it — started below the
     nav and left a bare cream strip across the top of the page. The value now
     lives on .ec-hero instead, so the hero owns the full height of the panel
     and its clouds run edge to edge with the nav floating on top of them.
     Declared here rather than on .ec-hero because the narrow-screen override
     already targets this selector, and .ec-sky__stage reads it too. */
  /* Capped against the viewport HEIGHT as well as its width. The clamp alone
     is width-driven, so a wide-but-short window (a 1280x800 laptop, where the
     usable height is ~705px) took the full 190px of nav clearance and pushed
     the hero's CTA below the fold — the one thing the hero is sized to
     prevent. 21vh gives that case ~148px instead and buys back ~40px, while
     on a tall monitor 21vh exceeds 190 so the clamp still wins and nothing
     changes. .ec-hero's min-height reads this variable, so it follows. */
  --hero-top: min(clamp(120px, 16vw, 190px), 21vh);
  background: var(--panel);
  /* The nav floats over this, so the top corners never show. */
  border-radius: 0 0 var(--round) var(--round);
}

/* Every band overlaps the one above it by exactly its own corner radius, so
   its rounded top corners cut into the previous section and reveal it at the
   notches. That single uniform rule is what produces the stacked-cards look.
   `position: relative` puts each band in the positioned layer so it paints
   over its predecessor in document order.

   Uniform on purpose: an earlier version special-cased mauve→light pairs,
   which broke the moment two light bands ended up adjacent (samples → FAQ)
   and cut a visible notch between two sections of identical colour. When the
   neighbours share a colour this rule is simply invisible, which is the
   behaviour you want. */
.ec-band {
  position: relative;
  margin-top: calc(var(--round) * -1);
  padding: calc(clamp(64px, 9vw, 118px) + var(--round)) 0 clamp(64px, 9vw, 118px);
  border-radius: var(--round);
}

.ec-band--mauve { background: var(--mauve); }
.ec-band--light { background: var(--panel); }

/* --------------------------------------------------------------------------
   Nav — floating pill
   -------------------------------------------------------------------------- */

.ec-nav {
  position: fixed;
  z-index: 60;
  top: 18px;
  left: 0;
  right: 0;
  padding: 0 clamp(14px, 4vw, 40px);
}

.ec-nav__pill {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 9px 9px 9px 26px;
  display: flex;
  align-items: center;
  gap: 24px;
  background: rgba(250, 246, 240, 0.86);
  backdrop-filter: saturate(1.5) blur(18px);
  -webkit-backdrop-filter: saturate(1.5) blur(18px);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 999px;
  transition: box-shadow 0.3s var(--ease), background 0.3s var(--ease);
}

.ec-nav.is-stuck .ec-nav__pill {
  background: rgba(250, 246, 240, 0.95);
  box-shadow: 0 14px 40px -18px rgba(64, 47, 40, 0.45);
}

.ec-nav__brand {
  display: flex;
  align-items: center;
  min-height: 44px;
  /* Sets the wordmark's colour — the symbol's paths are currentColor. */
  color: var(--ink);
  text-decoration: none;
}

/* Height drives it and `width: auto` follows the aspect-ratio, so there is
   only ever one number to change. The ratio is declared rather than left to
   the viewBox because a <use> reference does not always give the outer <svg>
   an intrinsic size to work from. */
.ec-nav__logo {
  display: block;
  height: 24px;
  width: auto;
  aspect-ratio: 2005 / 343;
}

.ec-nav__brand em {
  font-style: italic;
  font-weight: 400;
}

.ec-nav__links {
  display: flex;
  gap: 4px;
  margin-left: auto;
}

/* Hamburger. Hidden until the script un-hides it — see the note in the
   markup — and only ever shown on narrow viewports. */
.ec-nav__toggle {
  display: none;
  flex: 0 0 auto;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0 11px;
  background: transparent;
  border: none;
  border-radius: 999px;
  cursor: pointer;
}

.ec-nav__toggle span {
  display: block;
  height: 2px;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.25s var(--ease), opacity 0.2s var(--ease);
}

.ec-nav__toggle[aria-expanded="true"] span:first-child {
  transform: translateY(3.5px) rotate(45deg);
}

.ec-nav__toggle[aria-expanded="true"] span:last-child {
  transform: translateY(-3.5px) rotate(-45deg);
}

.ec-nav__toggle:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: 2px;
}

.ec-nav__links a {
  padding: 12px 14px; /* 12px vertical clears a 44px tap target */
  font-size: 15px;
  font-weight: 700;
  /* 0.2px, not the 0.13em (≈2px) this used to carry. Set in px on purpose so
     it stays hairline-thin rather than scaling with the font size. */
  letter-spacing: 0.2px;
  text-transform: uppercase;
  color: var(--body);
  text-decoration: none;
  border-radius: 999px;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}

.ec-nav__links a:hover,
.ec-nav__links a:focus-visible {
  color: var(--ink);
  background: rgba(107, 68, 56, 0.09);
}

.ec-nav__links a:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: -2px;
}

.ec-nav__cta {
  flex: 0 0 auto;
  padding: 12px 26px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--panel-lift);
  background: var(--cocoa);
  border-radius: 999px;
  text-decoration: none;
  white-space: nowrap;
  transition: transform 0.2s var(--ease), background 0.2s var(--ease);
}

.ec-nav__cta:hover {
  background: var(--cocoa-hover);
  transform: translateY(-1px);
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */

.ec-hero {
  position: relative;
  /* HOW DARK IT IS. 0 is broad daylight and the page exactly as designed; 1 is
     maximum eclipse. motion.js writes this one number every frame of the
     transit and nothing else — the dusk layer, the stars and the corona all
     read it from here, so the entire look of "night" is tunable in this file
     and the script never touches a colour.

     Declared, not assumed. Two rules below multiply it inside calc(), and an
     undefined custom property there is invalid at COMPUTED-VALUE time — which
     does not fall back to the previous declaration, it throws the property out
     entirely. Leave this line in place. */
  --ec-night: 0;
  /* One screen, one action. The hero used to be sized by whatever it
     contained, which — with the 600px form card inside it — came out around
     1100px tall and pushed the only button below the fold on any laptop.
     Now the height is stated, not inherited.

     The hero ends where the first screen ends. The 760px cap stops a tall
     monitor from opening on a mostly empty sky, and leaves the next band just
     peeking to invite the scroll.

     The nav's clearance is padding HERE rather than on .ec-panel--top, which
     is what lets the sky reach the top of the page instead of starting below
     a bare cream strip. The reset sets border-box, so min-height counts that
     padding — hence "760px of content PLUS the clearance", which reproduces
     the previous geometry exactly instead of making the hero taller. */
  padding-top: var(--hero-top);
  min-height: min(100vh, calc(760px + var(--hero-top)));
  display: flex;
  align-items: center;
  /* Lit air, not flat paper. The first layer is the sun's own light thrown
     into the sky from where the disc actually sits — that single gradient is
     what stops the sun reading as a sticker on a blank wall. */
  background:
    radial-gradient(62% 58% at 76% 44%, rgba(255, 186, 92, 0.3) 0%, rgba(255, 186, 92, 0) 62%),
    radial-gradient(70% 55% at 18% 12%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 68%),
    linear-gradient(178deg, #f7f2ea 0%, var(--panel) 62%, var(--panel) 100%);
}

/* Vignette, above the sky and below the copy. Frames the composition so the
   bodies sit inside a scene instead of floating on an edgeless field. */
.ec-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background:
    radial-gradient(120% 78% at 50% 30%, rgba(94, 70, 58, 0) 58%, rgba(94, 70, 58, 0.1) 100%);
}

/* --------------------------------------------------------------------------
   Hero sky — clouds, sun, moon

   Sized in vw/% rather than px so the whole composition scales as one scene
   instead of drifting apart between breakpoints. Every layer is inert:
   aria-hidden in the markup and pointer-events: none here, so none of it can
   swallow a click meant for the form.
   -------------------------------------------------------------------------- */

/* No z-index here, deliberately. A z-index on a positioned element creates a
   stacking context, and mix-blend-mode only blends within its own context —
   so with `z-index: 0` the clouds multiplied against this box's
   transparent backdrop instead of the page and rendered as plain white
   rectangles. Leaving z-index auto lets them blend with the panel behind.
   Content still sits on top because .ec-hero__inner is z-index 1. */
.ec-sky {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Both levels. The bodies moved inside .ec-sky__stage, and a `.ec-sky > *`
   child selector stopped matching them — they lost position:absolute, their
   `right: 0` did nothing, and the sun laid out as a static block on the left. */
.ec-sky > *,
.ec-sky__stage > * {
  position: absolute;
  pointer-events: none;
}

/* Explicit paint order, back to front. Not optional: mix-blend-mode makes an
   element its own stacking context, which stops it obeying plain DOM order.
   A flare layer with multiply painted over the moon that followed it, and the
   moon vanished entirely while still reporting correct geometry and full
   opacity — z-index did not rescue it, only deleting that layer did.
   Numbering what remains makes the order a stated fact rather than a side
   effect of source order. */
.ec-sky__night  { z-index: 0; }
.ec-sky__clouds { z-index: 1; }
.ec-sky__stars  { z-index: 1; }  /* after the clouds in the DOM, so above them */
.ec-sky__sun    { z-index: 2; }
.ec-sky__moon   { z-index: 3; }

/* The clouds arrive on a white ground, so multiply drops the white and keeps
   only the grey — which also stops them bleaching the warm cream the rest of
   the page is built on. This is the ONLY blended layer left and it is the
   bottom one. Keep it that way: a blended layer above the sun or the moon
   occludes them outright. */
.ec-sky__clouds {
  inset: 0;
  /* WIDER than the frame, by 14% on each side. The drift below moves this
     layer horizontally, and `cover no-repeat` means there is nothing beyond
     its own edges — without the overhang a few percent of travel would pull a
     hard vertical edge into view. The overhang is deliberately far larger than
     the ±3.5% the animation actually uses. */
  left: -14%;
  right: -14%;
  /* One layer, and it must be `cover`. A second pass at `150% auto` was
     added for depth, but an `auto` height resolves to a fixed pixel height
     from the image's aspect ratio — on a narrow viewport that fell short of
     the hero and cut a hard horizontal seam across the sky. It only looked
     fine on desktop because there the computed height happened to exceed the
     hero. Depth comes from the gradients instead. */
  background: url("../images/clouds.png") center top / cover no-repeat;
  mix-blend-mode: multiply;
  opacity: 0.7;
  /* Declared base, and it is load-bearing twice over. The global
     reduced-motion block cuts the animation to 0.01ms; with no fill-mode the
     layer then falls back to its un-animated transform, so without this line
     that fallback is "none" and the sky would sit off-centre for anyone who
     prefers reduced motion. It also pins the resting position for the
     no-GSAP path. */
  transform: translate3d(0, 0, 0);
  animation: ec-clouds-drift 120s ease-in-out infinite alternate;
}

/* Very slow, and `alternate` rather than a loop — clouds that scroll steadily
   in one direction read as a video background, and a non-seamless PNG cannot
   loop without a visible seam anyway. Drifting out and back never repeats a
   frame and never shows a join. The slight scale change is parallax: the
   layer breathes toward the viewer as it travels. */
@keyframes ec-clouds-drift {
  from { transform: translate3d(-3.5%, 0, 0) scale(1); }
  to   { transform: translate3d(3.5%, 0, 0) scale(1.05); }
}

/* --------------------------------------------------------------------------
   Shooting stars

   AMBER, not white. The obvious way to draw a meteor is a bright white streak,
   and that is exactly wrong here: this sky is cream (#f3ece3), not night, so
   white on it has almost no contrast and the streak simply does not appear.
   The same problem the light flares had. A saturated amber core reading as
   sunlit debris is what cream can actually show, with only the leading tip
   going near-white.
   -------------------------------------------------------------------------- */

.ec-shoot {
  /* ABOVE the copy, on purpose. These share a stacking context with
     .ec-hero__inner (z-index 1), so anything higher paints over the headline —
     which is what is wanted here: a streak that vanished the moment it reached
     the text looked like a bug, not a design. 6 clears the copy, the sun (2),
     the moon (3) and the hero's vignette (4).

     It costs nothing in legibility: the streak is 2px, it is over any given
     word for a fraction of a second, and the whole flight lasts about 1.5s
     out of a 13-21s cycle. */
  z-index: 6;
  width: clamp(90px, 11vw, 170px);
  height: 2px;
  border-radius: 2px;
  /* Tail at the left, head at the right, so the streak points the way it
     travels. */
  background: linear-gradient(
    90deg,
    rgba(255, 176, 74, 0) 0%,
    rgba(240, 150, 60, 0.5) 45%,
    rgba(255, 190, 110, 0.95) 88%,
    rgba(255, 250, 240, 1) 100%
  );
  filter: drop-shadow(0 0 5px rgba(255, 170, 80, 0.75));
  /* Base state is invisible, and that is deliberate. The global
     reduced-motion block cuts the animation to 0.01ms, so the element falls
     back to this — meaning anyone who prefers reduced motion gets no meteors
     at all rather than three streaks frozen mid-flight. Decorative motion
     should disappear, not stall. */
  opacity: 0;
}

/* Long cycles that are mostly idle: the streak is only visible for about a
   tenth of each one, so the sky stays calm and a star crosses it now and
   then. Different durations AND delays, or all three would sync up. */
/* Spread over the full height and width of the hero, not just the empty strip
   above the headline. Each travels 560px down a 26-degree axis — +503px across
   and +245px down — so these starts are chosen for where the whole FLIGHT
   lands, not where it begins; a start that looks clear can still finish
   somewhere silly. Staggered so no two are in the same band at the same time.

   Durations are all coprime-ish and the delays are spread, which is what stops
   the five drifting into a synchronised volley after a few minutes. */
.ec-shoot--a {
  top: 8%;
  left: 1%;
  animation: ec-shoot-go 13s linear infinite;
  animation-delay: 1.5s;
}

.ec-shoot--b {
  top: 26%;
  left: 18%;
  animation: ec-shoot-go 17s linear infinite;
  animation-delay: 7s;
}

.ec-shoot--c {
  top: 52%;
  left: 4%;
  animation: ec-shoot-go 19s linear infinite;
  animation-delay: 3.5s;
}

.ec-shoot--d {
  top: 14%;
  left: 44%;
  animation: ec-shoot-go 21s linear infinite;
  animation-delay: 12.5s;
}

.ec-shoot--e {
  top: 66%;
  left: 34%;
  animation: ec-shoot-go 23s linear infinite;
  animation-delay: 17s;
}

/* The rotation has to be repeated in every keyframe — `transform` is one
   property, so a rotate declared on the rule would be dropped the moment the
   animation started and the streaks would fly flat sideways. Translating on X
   AFTER the rotate is what sends each one along the axis it points down. */
@keyframes ec-shoot-go {
  0%   { opacity: 0; transform: rotate(26deg) translate3d(-40px, 0, 0) scaleX(0.25); }
  2%   { opacity: 0.9; }
  9%   { opacity: 0.9; }
  13%  { opacity: 0; transform: rotate(26deg) translate3d(520px, 0, 0) scaleX(1); }
  100% { opacity: 0; transform: rotate(26deg) translate3d(520px, 0, 0) scaleX(1); }
}

/* Capped and centred, so the bodies keep their relationship to the copy at
   every width instead of sliding to the screen edge. */
.ec-sky__stage {
  /* Reference scale: the sun carries roughly 60% of the hero's height, the way
     the Atlas Obscura eclipse page does. This is the only number that needs
     touching to resize the whole composition — the moon derives from it and
     every offset is expressed in diameters.

     THREE caps, and the third is the one that is easy to forget. 480px holds
     the ceiling on a large monitor; 34vw keeps it in proportion as the window
     narrows; and 62vh exists because the moon sits 0.34 diameters ABOVE the
     sun's centre, so on a short-but-wide window (a laptop at 1400x700, say) a
     width-only cap would push the moon's crown straight off the top of the
     hero. Height has to be part of the limit, not just width. */
  --sun-w: min(480px, 34vw, 62vh);
  /* Still derived from --sun-w, so resizing the composition is still one
     number — but the gap is now a flat 10px rather than the old 0.82 ratio,
     which the client asked for directly: the two bodies read as the same size
     and the moon covers the disc almost exactly at maximum.

     Know what changed with it. The old 0.82 was chosen when the moon was a
     DARK body that "took over the composition" at equal size; it is a pale
     silver disc now, so that reason expired. What is left is that a 10px gap
     is a different FRACTION at each breakpoint — 2% of a 480px desktop sun and
     6% of a 156px phone one — so the bodies look fractionally further apart in
     size on a phone. For a constant proportion instead, use
     `calc(var(--sun-w) * 0.98)`; everything else here holds either way.

     Nothing downstream hardcodes the ratio any more. The transit offset used
     to live in motion.js as 0.34/0.82 = 41.46% of the moon's own width, which
     this change would have silently thrown off by up to 2.4px per breakpoint —
     it is computed from --sun-w in CSS now (see .ec-sky__moon's `translate`)
     precisely so a change here cannot drift the eclipse out of alignment. */
  --moon-w: calc(var(--sun-w) - 10px);
  position: absolute;
  inset: 0;
  /* After inset, deliberately. The CLOUDS take the full hero now that it runs
     up behind the nav, but the bodies must not: they are positioned off this
     box's centre line, so letting it grow upward by the nav clearance would
     drag the whole eclipse down out of relation with the copy it is composed
     against. The stage stays on the content area; only the sky behind it got
     taller. */
  top: var(--hero-top);
  max-width: calc(var(--wrap) + 220px);
  margin: 0 auto;
}

/* Centred with margin, NOT transform: translateY(-50%).

   motion.js tweens `scale` on the sun and `xPercent/yPercent` on the moon.
   GSAP owns the whole `transform` property once it touches an element, so any
   centring done there is state GSAP has to be trusted to preserve. Both images
   are square (600x600) and their width is --sun-w, so half their height is
   exactly `--sun-w * 0.5` — the offset can be pure layout instead, and the
   transform channel is left entirely to the animation. */
.ec-sky__sun {
  /* 54%, not 50%. The moon sits up and to the left of this anchor, and at 50%
     its top edge crossed the hero's own top boundary — .ec-panel--top clips,
     so it came out sliced flat across the crown and read as a rendering bug
     rather than a body. Nudging the whole pair down clears it. */
  top: 54%;
  right: 0;
  width: var(--sun-w);
  /* An explicit height, because this is a wrapper <div> now and `auto` would
     collapse it to zero — its children are all absolutely positioned. */
  height: var(--sun-w);
  margin-top: calc(var(--sun-w) * -0.5);
  /* Not fully opaque. At 1 the disc was the only element on the page not
     sitting in the haze — clouds are 0.7, the moon is behind a mask — and it
     read as a sticker no matter how much corona was added. Letting a few
     percent of the sky gradient through puts it in the same air as the rest.
     motion.js reads this resting value rather than assuming 1. */
  opacity: 0.96;
  /* No filter here on purpose: a drop-shadow on the wrapper would be computed
     over the flares as well as the disc, which is both expensive and muddy.
     The corona lives on .ec-sky__disc instead. */
}

/* The wrapper is sized to the DISC, and everything inside is measured against
   it. That is deliberate: every eclipse measurement on this page — the moon's
   offsets, --moon-w, the 0.34-diameter separation — is expressed in disc
   diameters, so keeping this box exactly one diameter wide meant swapping a
   flat PNG for a four-layer group changed none of that math. */
.ec-sky__sun > * {
  position: absolute;
}

/* The photosphere, from sun_component.svg.

   Sized so its DISC measures --sun-w, NOT so its own box does. In that file
   the disc is a 573.959 square inside a 723x706 viewBox — 79.4% of the width,
   centred at (49.87%, 48.67%) rather than dead centre. The numbers below are
   that geometry solved for "put the disc in the middle of a box one diameter
   across": 1 / 0.794 = 125.9% wide, then pulled back by the gap between the
   disc's centre in the artwork and the centre of this box. Get this wrong and
   the moon bites empty air next to the sun instead of the sun itself. */
.ec-sky__disc {
  width: 125.9%;
  /* The global `img { max-width: 100% }` silently clamps every width above
     100% back down to the wrapper — the disc rendered at 79% of its intended
     size and the flares, at 205-300%, collapsed to the disc's own width and
     vanished entirely. Both this rule and .ec-sky__flare must opt out. */
  max-width: none;
  height: auto;
  left: -12.79%;
  top: -9.83%;
  /* Warmed and lifted toward gold, matching the reference. The artwork itself
     is already close — compared side by side at full size the raw asset and
     this differ only slightly — so this is a nudge, not a correction: a little
     brightness and saturation take it from a deepish orange to the golden
     yellow the client asked for.

     The bleed is deliberately low. It went 0.45 -> 0.16 -> 0.42 -> this: at
     0.42 the halo spilled a broad flat yellow wash across the sky next to the
     disc, which is a different complaint from the hard rim that 0.45 caused
     but comes from the same place. 0.2 over a wide 80px radius keeps light
     leaving the sun without pooling into a visible field around it. Raise
     this and the wash comes back before the rim does. */
  filter:
    brightness(1.08) saturate(1.06)
    drop-shadow(0 0 80px rgba(255, 190, 90, 0.2));
  animation: ec-sun-breathe 9s ease-in-out infinite;
}

.ec-sky__flare {
  height: auto;
  /* See .ec-sky__disc — without this the global img cap flattens every flare
     to the disc's width and the rays disappear. */
  max-width: none;
  left: 50%;
  top: 50%;
  /* OFF by choice — the rays were still reading as too much light around the
     sun even after the boost was dialled back, so the flares are left at their
     native strength.

     Understand what that means before switching it back on or off again: these
     PNGs are near-empty. 97% of Light_1's pixels carry alpha 25 or less (mean
     3.4 of 255) and Light_4 never exceeds 110 anywhere — they were drawn to add
     light against a DARK sky. Unfiltered on cream they contribute almost
     nothing, so the sun's visible glow is now essentially the disc's own
     drop-shadow plus the corona baked into sun_component.svg.

     Do not try to bring them back with opacity, saturate() or brightness():
     all three were tried and all three failed, because they scale COLOUR and
     the problem is alpha. #ec-flare-boost (still defined in index.html) is the
     only lever that works — re-enable this line and tune its feFuncA slope,
     which is around 3.4 for clearly visible rays and ~1.8 for a hint. */
  /* filter: url(#ec-flare-boost); */
  /* The centring translate is declared BOTH here and inside every keyframe,
     and it has to be in both places.
       - In the keyframes, because `transform` is a single property: a rotation
         keyframe without the translate would drop the centring the instant the
         animation started, and each flare would jump down-right by half its
         own size.
       - Here, because the global reduced-motion block forces
         `animation-duration: 0.01ms` — the animation then finishes immediately
         and, with no fill-mode, the element falls back to its un-animated
         value. Without this line that fallback is "no transform", so the exact
         same jump happens permanently for anyone who prefers reduced motion.
     A running animation outranks this declaration, so the two never fight. */
  transform: translate(-50%, -50%);
}

/* Different sizes, speeds and directions. Matched values would read as one
   rigid pinwheel; crossing them at these ratios never visibly repeats.

   The opacities run high because this artwork was drawn for a DARK sky, where
   pale rays add light against black. Here they land on cream, so the same
   pixels have almost no contrast to spend — at the 0.4-0.5 that looked
   sensible on paper the rays were invisible and only a vague warm haze came
   through. Their own alpha already falls off steeply, so pushing the layer
   opacity up brings the rays back without hardening the falloff. */
.ec-sky__flare--a {
  width: 260%;
  opacity: 0.62;
  animation: ec-flare-cw 150s linear infinite;
}

.ec-sky__flare--b {
  width: 205%;
  opacity: 0.5;
  animation: ec-flare-ccw 105s linear infinite;
}

/* The slow one also breathes. Its pulse animates OPACITY only — a second
   transform-based animation would fight the rotation for the same property
   and the last one declared would simply win. */
.ec-sky__flare--c {
  width: 300%;
  /* Base value for the same reason the centring translate has one: reduced
     motion cuts the animation to 0.01ms, and with no fill-mode the element
     falls back to its un-animated opacity. Without this that fallback is 1
     and the softest flare becomes the loudest thing in the hero. */
  opacity: 0.48;
  animation:
    ec-flare-cw 235s linear infinite,
    ec-flare-glow 11s ease-in-out infinite;
}

@keyframes ec-flare-cw {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

@keyframes ec-flare-ccw {
  from { transform: translate(-50%, -50%) rotate(360deg); }
  to   { transform: translate(-50%, -50%) rotate(0deg); }
}

@keyframes ec-flare-glow {
  0%, 100% { opacity: 0.36; }
  50%      { opacity: 0.62; }
}

/* Barely there, and that is the point — a sun that is perfectly still reads
   as a decal. 2% over nine seconds is under the threshold of "something is
   moving" and above the threshold of "this is alive". */
@keyframes ec-sun-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.022); }
}

/* Below and left of the sun, drifting toward it — an eclipse starting to
   happen, which is what the page is about.

   It used to sit bottom-left and that never worked: the form card owns that
   corner, the gap between them came out at exactly 1px, and because the moon
   was clipped by the viewport edge while the form tracked the centred wrap,
   the visible slice swung between 12% and 27% depending on window width.
   Over here it is framed by empty sky and reads the same at every size. */
/* Big and cropped by the hero's bottom edge, not a small disc floating in
   clear space. Scale is what separates a composed scene from pasted clip art:
   a token-sized ball reads as a sticker no matter how much it is filtered,
   while a body too large to fit the frame reads as deliberate.

   An earlier pass tried to hide the sticker problem with heavy
   brightness/contrast filtering and only destroyed the craters, leaving a
   featureless grey blob. The surface detail is the point — keep it, and let
   warmth and a soft rim do the integrating instead. */
/* THE ECLIPSE. The moon crosses the sun's upper-left limb and takes a bite
   out of it.

   This is the whole point of the page and it was previously not happening at
   all: the moon sat at `bottom: -26%` while the sun sat at `top: 50%`, roughly
   600px apart, so the two bodies never met. At 0.42 opacity behind a mask that
   started fading at 58% it read as a grey smudge cropped by the page edge —
   nobody connects that to an eclipse.

   Both bodies now share one anchor (`top: 50%; right: 0` plus --sun-w) and are
   the same size, so the geometry is stated rather than eyeballed and holds at
   every width. The offset is 0.39 of a diameter on each axis, putting the
   centres ~0.55 diameters apart — the classic partial-eclipse crescent. Less
   overlap reads as two separate circles that happen to touch; more, and the
   sun is swallowed and the warmth the page is built on goes with it. */
/* The transit itself is NOT here, and one attempt to put it here is worth
   recording so it is not retried. Driving the moon with the independent
   `translate` property off a --ec-transit variable looks ideal — CSS owns the
   geometry, it follows --sun-w through every cap for free, and `translate`
   composes with `transform` instead of fighting it. It does not work: GSAP
   writes `translate: none; rotate: none; scale: none` inline on any element it
   transforms, so it can own `transform` outright, and inline beats the
   stylesheet. Measured after motion.js ran, this element carried
   `style="transform: translate(0px, 0px); translate: none; …"` and the moon
   never moved a pixel while --ec-transit went 0 to 1 correctly.

   It is also why the bug is invisible in a static screenshot: with
   --force-prefers-reduced-motion GSAP never runs, nothing clears `translate`,
   and the frame renders exactly as intended.

   motion.js measures the gap between the two centres off offsetLeft/offsetTop
   instead — see §1b. That reads the composed geometry rather than restating
   it, so the numbers below stay the only place the eclipse is described. */
.ec-sky__moon {
  top: 54%;
  /* Positioned by CENTRE, because the two bodies are different sizes now and
     a shared `right` offset would no longer put them a known distance apart.
     Sun centre sits --sun-w/2 in from the stage edge; the moon's centre wants
     to be a further 0.34 diameters out, so its own right offset is that minus
     half its own width. Centres end up ~0.48 diameters apart — enough that the
     moon takes a clear bite out of the limb without swallowing the disc. */
  right: calc(var(--sun-w) * 0.84 - var(--moon-w) * 0.5);
  width: var(--moon-w);
  height: auto;
  margin-top: calc(var(--moon-w) * -0.5 - var(--sun-w) * 0.34);
  /* LIGHTENED, not darkened. brightness(0.72) was the obvious reading —
     an eclipsing moon is backlit, so make it dark — and it was wrong here:
     the source PNG is already a dark grey, so 0.72 crushed it to near-black
     and dropped a heavy black hole into a page built entirely on warm cream.
     It also out-weighed the sun, which is meant to be the focal point.
     Lifting it to a warm stone grey keeps the craters readable, keeps the
     occlusion legible, and lets the sun stay the brightest thing here. */
  /* Near-opaque. A pale moon on cream has far less contrast to spare than a
     dark one did, so it can no longer afford to be see-through as well. */
  opacity: 0.96;
  /* The gamma lift lives in the inline SVG filter; see #ec-moon-light for why
     brightness() could not do this job. The drop-shadow stays as a CSS
     function chained after it — that is light spilling around the occluder,
     and it is now doing double duty, because a pale silver disc needs the
     warm rim to separate it from the cream sky behind. */
  filter:
    url(#ec-moon-light)
    drop-shadow(0 0 26px rgba(255, 170, 80, 0.5))
    /* Second shadow, and it is not decorative. Over the sun a near-white moon
       separates on brightness alone, but its upper-left half sits against
       cream — and white on cream has almost no contrast left to spend. This
       soft warm-grey drop, offset downward so it reads as a body casting
       rather than a ring drawn around one, is what keeps the disc legible
       there. Take it out and the moon dissolves into the sky on that side. */
    drop-shadow(0 5px 20px rgba(122, 98, 84, 0.34));
  /* `closest-side` is load-bearing. A radial-gradient circle defaults to
     farthest-corner, so 100% resolved to the box's corner distance (~0.707 of
     the side) and the mask's opaque region had a radius of ~0.66 side — wider
     than the 0.5-side disc. It was masking nothing, and the square of lifted
     transparent pixels outside the moon showed through. closest-side puts
     100% exactly on the disc's own edge. */
  -webkit-mask-image: radial-gradient(circle closest-side at 50% 50%, #000 97%, rgba(0, 0, 0, 0) 100%);
  mask-image: radial-gradient(circle closest-side at 50% 50%, #000 97%, rgba(0, 0, 0, 0) 100%);
}

/* --------------------------------------------------------------------------
   NIGHT — the sky the eclipse drags across the hero

   Driven entirely by --ec-night (0 day, 1 maximum), which motion.js tweens
   while the moon is over the disc. At 0 every layer here is at opacity 0 and
   the hero is byte-identical to the design that was signed off, so a blocked
   CDN, a thrown error or a reduced-motion preference all leave the page
   untouched rather than stuck in a half-dark state.

   WHERE THE DARK IS ALLOWED TO GO IS A CONTRAST DECISION, NOT A TASTE ONE.
   The obvious version of this — drop a deep indigo wash over the whole hero —
   was measured and cannot ship: --muted (#78665d) carries .ec-hero__note at
   4.65:1 on cream, which is 0.15 above the AA floor. ANY darkening behind that
   line fails it. A wash at only 0.05 alpha takes it to 4.50; at 0.14 it is
   3.5:1. --body (5.7:1) has barely more room. So the copy column keeps a
   near-zero clearing and the darkness is pushed to where nothing is read:
   overhead, out to the left and right edges, and along the bottom lip.

   Two things that were tried first and do not work, so they are not worth
   re-deriving:
     - A CENTRED VIGNETTE with the copy in the lit middle. The sun's centre
       sits at x~80% and the lede's right edge at x~67%, roughly 190px apart at
       1440 — closer than the sun's own radius. There is no ellipse that is
       dark at the sun and clear at the copy; anything tight enough to separate
       them resolves over ~6% of the viewport and draws a visible hard edge.
     - CROSSFADING THE COPY to cream as the sky darkens, which is what a real
       night mode does. Text and ground swap luminance, so they must cross:
       measured at the midpoint the headline came out at 1.29:1, unreadable for
       about a second and a half in each direction. A hero that goes briefly
       illegible twice a minute is worse than a hero that stays lit.

   The drama therefore comes from the things that do not sit behind text: the
   stars, the corona, the ring the moon leaves around the disc, and how deep
   the top of the sky goes. If the client asks for more, --ec-night-gain below
   is the single knob; it scales every layer here at once.
   -------------------------------------------------------------------------- */

.ec-sky__night {
  inset: 0;
  /* The gain exists so "make it darker" is one number rather than a re-tune of
     six gradients. Anything above ~1.3 starts eating the copy's clearing. */
  opacity: calc(var(--ec-night, 0) * var(--ec-night-gain, 1));
  background:
    /* The 360-degree sunset that rings the horizon at totality, held BELOW
       y=91%. It is not decoration that can drift up: .ec-hero__note ends at
       ~85% on a 900px-tall window and ~88% on a 700px one, and warm orange
       over cream LOWERS luminance, so this glow fails --muted just as fast as
       the dark does if it reaches the copy. */
    radial-gradient(160% 12% at 50% 102%,
      rgba(255, 150, 62, 0.5) 0%,
      rgba(255, 128, 54, 0.14) 46%,
      rgba(255, 120, 50, 0) 72%),
    /* The frame, deepest in the corners and out past the sun. Centred on the
       copy rather than on the hero, so the type sits in the shallowest part of
       a sky that is dark everywhere. */
    radial-gradient(74% 80% at 30% 52%,
      rgba(9, 12, 34, 0) 0%,
      rgba(9, 12, 34, 0.18) 62%,
      rgba(8, 11, 30, 0.46) 100%),
    /* THE SKY. Full strength straight through the copy — the client asked for
       real dark and the type goes white to meet it, so there is no clearing to
       protect any more. This layer used to top out at 0.26 over the copy and
       hold 0.70 only out at the frame.

       0.70 through the middle is what white type needs: measured over cream it
       puts the ground at L 0.093, so --cream lands at 6.5:1, and the frame at
       0.87 takes it past 11:1. Below about 0.60 white type stops clearing AA,
       which is the floor on this number rather than taste.

       It lets go over the last tenth on purpose. The hero's bottom edge meets
       the cream .ec-strip inside the same rounded panel, and a sky that stays
       at 0.70 to the boundary draws a hard black line across the page. The
       horizon glow above sits in that same band and carries the hand-off. The
       run-out starts at 88% because .ec-hero__note sits at ~85% on a 900px
       window and ~88% on a 700px one, and white type needs the dark to still
       be there underneath it. */
    linear-gradient(180deg,
      rgba(8, 11, 30, 0.78) 0%,
      rgba(9, 12, 32, 0.7) 26%,
      rgba(9, 12, 32, 0.7) 88%,
      rgba(10, 13, 34, 0.34) 97%,
      rgba(10, 13, 34, 0.2) 100%);
}

/* Stars, in the one part of the desktop sky that goes properly dark: the band
   above the headline. They are masked rather than merely positioned up there,
   so a star can never end up hanging in the lit middle of the page if the copy
   block moves — the mask is tied to the same run-out as the gradient above.

   One element, ~20 single-dot radial gradients. The background paints once and
   only `opacity` changes after that, so the whole field is composited rather
   than repainted every frame. */
.ec-sky__stars {
  inset: 0;
  opacity: calc(var(--ec-night, 0) * var(--ec-night-gain, 1));
  background-image:
    radial-gradient(circle 1.5px at 6% 9%,   rgba(255,252,246,0.95), rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.2px at 13% 20%, rgba(255,250,240,0.8),  rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.7px at 19% 6%,  rgba(255,252,246,0.9),  rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.2px at 26% 29%, rgba(255,248,238,0.75), rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.5px at 33% 13%, rgba(255,252,246,0.9),  rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.1px at 39% 24%, rgba(255,250,240,0.7),  rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.8px at 46% 7%,  rgba(255,253,248,0.95), rgba(255,253,248,0) 100%),
    radial-gradient(circle 1.3px at 52% 18%, rgba(255,250,240,0.82), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.1px at 58% 31%, rgba(255,248,238,0.7),  rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.6px at 64% 11%, rgba(255,252,246,0.9),  rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.2px at 70% 22%, rgba(255,250,240,0.78), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.7px at 76% 5%,  rgba(255,253,248,0.92), rgba(255,253,248,0) 100%),
    radial-gradient(circle 1.2px at 82% 27%, rgba(255,248,238,0.75), rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.5px at 88% 15%, rgba(255,252,246,0.88), rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.3px at 94% 8%,  rgba(255,250,240,0.85), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.1px at 10% 33%, rgba(255,248,238,0.68), rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.4px at 30% 4%,  rgba(255,252,246,0.9),  rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.1px at 43% 34%, rgba(255,248,238,0.66), rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.2px at 67% 33%, rgba(255,248,238,0.7),  rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.4px at 97% 21%, rgba(255,250,240,0.85), rgba(255,250,240,0) 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 26%, rgba(0, 0, 0, 0) 46%);
  mask-image: linear-gradient(180deg, #000 0%, #000 26%, rgba(0, 0, 0, 0) 46%);
}

/* A second, dimmer field that breathes. It has to be a child rather than a
   second animation on .ec-sky__stars, because that element's opacity is
   already spoken for by --ec-night and transform/opacity is one property per
   element — nesting multiplies the two instead of letting them fight.

   0.7 is declared as the base for the usual reason on this page: the global
   reduced-motion block cuts animation-duration to 0.01ms and, with no
   fill-mode, the element falls back to its un-animated value. */
.ec-sky__stars::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.7;
  background-image:
    radial-gradient(circle 1.3px at 9% 15%,  rgba(255,250,240,0.85), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.1px at 22% 11%, rgba(255,248,238,0.7),  rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.2px at 36% 30%, rgba(255,250,240,0.75), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.4px at 49% 26%, rgba(255,252,246,0.8),  rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.1px at 55% 9%,  rgba(255,248,238,0.72), rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.5px at 61% 20%, rgba(255,252,246,0.85), rgba(255,252,246,0) 100%),
    radial-gradient(circle 1.2px at 73% 16%, rgba(255,250,240,0.78), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.1px at 79% 33%, rgba(255,248,238,0.66), rgba(255,248,238,0) 100%),
    radial-gradient(circle 1.6px at 85% 6%,  rgba(255,253,248,0.9),  rgba(255,253,248,0) 100%),
    radial-gradient(circle 1.2px at 91% 29%, rgba(255,250,240,0.72), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.3px at 16% 27%, rgba(255,250,240,0.75), rgba(255,250,240,0) 100%),
    radial-gradient(circle 1.1px at 44% 17%, rgba(255,248,238,0.7),  rgba(255,248,238,0) 100%);
  animation: ec-star-twinkle 6.5s ease-in-out infinite alternate;
}

@keyframes ec-star-twinkle {
  from { opacity: 0.28; }
  to   { opacity: 0.95; }
}

/* THE CORONA. The one moment these flares were drawn for.

   Their own comment explains why they are invisible the rest of the time: 97%
   of Light_1's pixels sit at alpha 25 or below, because the artwork was made
   to add light against a DARK sky and this one is cream. The eclipse finally
   gives it that dark sky, and #ec-corona-night is the alpha lever that lets
   the rays spend it — motion.js ramps its feFuncA slope from 1 (identity, so
   attaching the filter is invisible) up with --ec-night.

   The filter is ATTACHED by a class rather than left on permanently: three
   PNGs at 205-300% of the sun, each running an SVG filter through every frame
   of a continuous rotation, is real GPU work to be doing for the ~65% of the
   cycle when the slope is 1 and the filter changes nothing.

   Safe where the deleted flare layer was not — that one needed
   mix-blend-mode to erase a white ground, and the blend made it a stacking
   context that painted over the moon. A filter on a DESCENDANT of .ec-sky__sun
   cannot reach the moon: the moon is a sibling of the sun group at a higher
   z-index, so it paints above the whole subtree either way. */
.ec-sky__sun.is-night .ec-sky__flare {
  filter: url(#ec-corona-night);
}

/* 999.98, matching the width where the copy stops splitting left. At 860
   these two disagreed and every width from 861 to 999 got the desktop sky
   with centred copy — the sun sat directly behind the headline. */
@media (max-width: 999.98px) {
  /* On a narrow screen the copy re-centres and spans the full width, so there
     is no clear lane for a solid sun — every position put it through the
     headline. The sky becomes atmosphere here instead of an object: pushed
     into the top-right corner and dropped to a wash the text reads over.

     Moved and faded AS A UNIT, on the stage, rather than by re-positioning
     each body. That is the whole reason the pair is anchored to a shared
     --sun-w: the eclipse geometry is set once and survives every breakpoint
     instead of being re-eyeballed here and drifting apart — which is exactly
     how the two bodies ended up 600px apart with the page still called
     "eclipse".

     Note for motion.js: this opacity is on the PARENT, so the sun and moon
     still report their own 0.96 / 0.94 to getComputedStyle and the reveal
     tweens stay correct. Do not move the wash onto the bodies themselves. */
  .ec-sky__stage {
    --sun-w: 68vw;
    /* Up and out, far enough that the pair clears the first headline line
       rather than sitting directly behind it. The copy is centred and
       full-width at this size, so the only clear space is the top corner. */
    /* -26%, not -31%. The bodies are larger now, and the moon sits above the
       sun's centre, so the old lift put its crown 12px from the top of the
       page — one notch from looking sliced. */
    transform: translate(22%, -26%);
    opacity: 0.32;
  }

  /* The drop-shadow corona roughly doubles the sun's visual footprint, so it
     is dialled back once the disc is this close to the copy. */
  .ec-sky__disc {
    filter: drop-shadow(0 0 40px rgba(255, 176, 74, 0.35));
  }

  /* Two of the three flares are dropped here rather than merely faded. The
     stage already sits at 0.32 on this breakpoint, so their contribution is
     invisible — but an offscreen-ish `display: none` layer costs nothing while
     a transparent one still decodes a ~330KB PNG and composites a rotation
     every frame on the least capable devices. The remaining flare keeps the
     sun from going static. */
  .ec-sky__flare--b,
  .ec-sky__flare--c {
    display: none;
  }
}

/* Both hosts clip the rings, so an oversized wheel can bleed to the edges
   without adding page width. */
.ec-panel--top {
  overflow: hidden;
}

.ec-hero__inner {
  position: relative;
  z-index: 1;
  /* Flex item of .ec-hero now, so it has to claim the full width or the
     `margin: 0 auto` it inherits from .ec-wrap has nothing to centre. */
  width: 100%;
  text-align: center;
  /* The copy is vertically centred by the parent, so the old bottom padding
     would only push it off-centre. A little is kept so the block sits a
     fraction above true middle, which reads as centred to the eye. */
  padding-bottom: clamp(16px, 2.4vw, 34px);
}

.ec-hero__title {
  max-width: 15ch;
  margin: 0 auto 20px;
  font-family: var(--serif);
  /* The preferred size is whichever is SMALLER, width- or height-derived, and
     the clamp still holds the 40-78px range. This headline runs to three lines
     and is by far the tallest thing in the hero, so on a short window a purely
     width-driven size is what pushes the CTA under the fold: at 1366x768 the
     margin was down to 5px and at 1024x650 it had already gone negative.
     9.5vh costs nothing on a tall monitor — there it exceeds 78 and the clamp
     wins — and buys back roughly 45px of hero on a short laptop. */
  font-size: clamp(40px, min(6.4vw, 9.5vh), 78px);
  font-weight: 500;
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ec-hero__title em {
  font-style: italic;
  font-weight: 400;
}

.ec-hero__lede {
  max-width: 46ch;
  /* Height-aware for the same reason as the headline: this gap is pure
     vertical space, so on a short window it should give some back rather than
     hold a width-derived 52px while the CTA falls off the screen. */
  margin: 0 auto clamp(20px, min(5vw, 5vh), 52px);
  font-size: 20px;
  line-height: 1.7;
  color: var(--body);
}

/* The hero's single action, in place of the form that used to be here. */
.ec-hero__cta {
  margin: 0;
}

.ec-hero__note {
  margin: 16px 0 0;
  font-size: 16px; /* 14px floor — 13px is below the minimum for this project */
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   THE COPY GOES WHITE AT NIGHT. See the NIGHT block for the other half.

   The client asked for a genuinely dark hero and white type on it (2026-07-21),
   which replaces the earlier compromise here — that one DEEPENED the copy
   toward --ink so a light hero could take a little dusk. Once the sky is
   allowed to reach 0.70+ alpha, deepening is exactly backwards.

   --ec-swap, NOT --ec-night, and this is the whole craft of it. Text and
   ground swap luminance, so any linear crossfade puts them at the same
   lightness somewhere in the middle: measured at 1.29:1 on the headline, which
   is illegible. There is no way to avoid crossing — a mid-grey sky holds
   neither dark nor white type (dark needs the ground above L 0.32, white needs
   it below L 0.16, and nothing satisfies both). What CAN be controlled is how
   long it lasts and where it happens.

   So the swap is held off until the sky is already half dark, then run fast:
   flat 0 until --ec-night 0.55, full white by 0.82. Either side of that window
   both states measure well — dark type is 5.0:1 where the swap starts, white
   type is 4.9:1 where it ends, and at maximum white sits at 6.5:1 over the
   copy and 11:1 out at the frame. Inside it, roughly one second in each
   direction dips low, out of a 50s cycle that spends 30 of those seconds at
   maximum. The text-shadow below is what carries that second: a fixed-geometry
   halo whose ALPHA is the only animated part, so it costs no re-raster.

   Widen that 0.27 window and the murky moment gets longer; narrow it and the
   swap starts to read as a hard flick. It was tuned against both.

   color-mix has been in every evergreen browser since mid-2023; where it is
   missing these declarations fail to PARSE and the plain `color` above each
   survives, which is the intended fallback. Do not merge them into the rules
   above — that would take the fallback with them. */
.ec-hero__title,
.ec-hero__lede,
.ec-hero__note {
  --ec-swap: clamp(0, calc((var(--ec-night, 0) - 0.55) / 0.27), 1);
  text-shadow: 0 2px 26px rgba(6, 9, 26, calc(0.62 * var(--ec-night, 0)));
}

.ec-hero__title {
  color: color-mix(in srgb, var(--ink), var(--cream) calc(var(--ec-swap) * 100%));
}

.ec-hero__lede {
  color: color-mix(in srgb, var(--body), var(--cream) calc(var(--ec-swap) * 100%));
}

.ec-hero__note {
  color: color-mix(in srgb, var(--muted), var(--cream) calc(var(--ec-swap) * 100%));
}

/* --------------------------------------------------------------------------
   Form card
   -------------------------------------------------------------------------- */

.ec-formcard {
  max-width: 470px;
  margin: 0 auto;
  padding: clamp(26px, 3.4vw, 36px);
  text-align: left;
  background: var(--panel-lift);
  border: 1px solid var(--line);
  border-radius: 26px;
  box-shadow: 0 30px 70px -34px rgba(64, 47, 40, 0.4);
}


/* The card on the cocoa closing band.

   It keeps its own cream ground, which is why every .ec-field / .ec-btn style
   above works here untouched — the ~50 lines that once re-skinned form fields
   for a dark background are still not needed. The shadow deepens because a
   soft warm shadow is invisible against cocoa. */
.ec-formcard--close {
  text-align: left;
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.5);
}

/* From the width where the sun is big enough to matter, the copy moves to the
   left half and leaves the right half to the sky — the reference's split.
   Centred copy put the hero text straight through the sun.

   The form card is deliberately NOT in this block any more. It used to carry
   `margin-left: 0` to left-align it in the hero; now that the card lives in
   the centred closing band, that rule would drag it to the left edge of an
   otherwise symmetrical section. */
@media (min-width: 1000px) {
  .ec-hero__inner {
    text-align: left;
    padding-bottom: clamp(16px, 2.4vw, 34px);
  }

  .ec-hero__title {
    max-width: 12ch;
    margin-left: 0;
  }

  .ec-hero__lede {
    max-width: 34ch;
    margin-left: 0;
  }
}

.ec-field {
  margin-bottom: 18px;
}

.ec-field label {
  display: block;
  margin-bottom: 7px;
  font-size: 15px; /* 14px floor — 13px is below the minimum for this project */
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink);
}

.ec-field input[type="text"],
.ec-field input[type="email"],
.ec-field input[type="date"],
.ec-field input[type="time"] {
  display: block;
  width: 100%;
  padding: 13px 15px;
  font-family: var(--sans);
  font-size: 17px; /* 16px minimum — stops iOS zoom-on-focus */
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ec-field input::placeholder {
  color: #8a7970; /* 4.6:1 — #a2928a measured 2.55:1 */
}

/* The transparent outline is deliberate — do not swap it back to
   `outline: none`. A box-shadow-only ring is not painted in Windows High
   Contrast / forced-colors mode, which left keyboard users there with no
   focus indicator at all. Transparent is invisible normally and gets
   repainted as a real ring by forced-colors. */
.ec-field input[type="text"]:focus-visible,
.ec-field input[type="email"]:focus-visible,
.ec-field input[type="date"]:focus-visible,
.ec-field input[type="time"]:focus-visible {
  outline: 3px solid transparent;
  outline-offset: 2px;
  border-color: var(--cocoa);
  box-shadow: 0 0 0 3px rgba(107, 68, 56, 0.18);
}

.ec-field input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.ec-field input[aria-invalid="true"] {
  border-color: var(--error);
}

/* Checkbox row */
.ec-check {
  display: flex !important;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin: 4px 0 0 !important;
  font-size: 16px !important;
  font-weight: 400 !important;
  color: var(--body) !important;
  cursor: pointer;
}

.ec-check input {
  width: 18px;
  height: 18px;
  accent-color: var(--cocoa);
  cursor: pointer;
}

/* The checkbox needs its own visible ring — it has no border for the text
   input rule to recolour. */
.ec-check input:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: 3px;
}

.ec-hint {
  margin: 7px 0 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
}

.ec-error {
  display: none;
  margin: 7px 0 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--error);
}

.ec-error.is-visible {
  display: block;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.ec-btn {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 16px 28px;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--panel-lift);
  background: var(--cocoa);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s var(--ease), transform 0.15s var(--ease),
    box-shadow 0.2s var(--ease);
}

.ec-btn:hover {
  background: var(--cocoa-hover);
  transform: translateY(-2px);
  box-shadow: 0 16px 30px -14px rgba(107, 68, 56, 0.6);
}

.ec-btn:active {
  transform: translateY(0);
}

.ec-btn:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

/* On the cocoa band the fill has to invert or the button disappears. */
.ec-btn--light {
  color: var(--cocoa);
  background: var(--panel-lift);
}

.ec-btn--light:hover {
  background: #ffffff;
  box-shadow: 0 16px 30px -14px rgba(0, 0, 0, 0.45);
}

.ec-btn--light:focus-visible {
  outline-color: var(--panel-lift);
}

/* The hero's CTA inverts at night, for exactly the reason .ec-btn--light
   exists above: cocoa on a dark ground disappears. Measured against the sky at
   maximum it is 1.13:1 — the pill stops being a shape and only its cream
   lettering survives, which is a poor look for the page's one action.

   So it crosses to --btn--light's own two colours rather than to anything
   invented: --panel-lift fill, --cocoa text, ending exactly on the pair the
   stylesheet already uses on the cocoa band. It rides --ec-swap, so the button
   and the copy change together instead of at two different moments.

   Placed after .ec-btn:hover deliberately — equal specificity, later wins —
   and the hover rule is restated so the lift shadow survives the override. */
.ec-hero__cta .ec-btn {
  color: color-mix(in srgb, var(--panel-lift), var(--cocoa) calc(var(--ec-swap, 0) * 100%));
  background: color-mix(in srgb, var(--cocoa), var(--panel-lift) calc(var(--ec-swap, 0) * 100%));
}

.ec-hero__cta .ec-btn:hover {
  background: color-mix(in srgb, var(--cocoa-hover), #ffffff calc(var(--ec-swap, 0) * 100%));
  box-shadow: 0 16px 30px -14px rgba(107, 68, 56, 0.6);
}

/* The swap variable is declared on the copy elements, which the button is not
   a child of — it needs its own. Same expression, one source of truth would be
   better, but a custom property cannot be shared sideways without hoisting it
   to a common ancestor, and .ec-hero already carries --ec-night for both. */
.ec-hero__cta {
  --ec-swap: clamp(0, calc((var(--ec-night, 0) - 0.55) / 0.27), 1);
}

/* The base button is a full-width submit control inside a card. The hero's
   CTA is a link standing on open sky, so it sizes to its own text — a
   470px-wide pill floating in the middle of a hero reads as a broken form
   field, not as an invitation. text-decoration is reset because this is the
   one place .ec-btn lands on an <a>. */
.ec-btn--inline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  margin-top: 0;
  padding: 17px 38px;
  font-size: 17px;
  text-decoration: none;
}

.ec-formcard__note {
  margin: 14px 0 0;
  text-align: center;
  font-size: 15px; /* 14px floor — 13px is below the minimum for this project */
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Eclipse strip
   -------------------------------------------------------------------------- */

.ec-strip {
  /* Lifted so the hero rings, which sit at z-index 0 in the root stacking
     context, can never paint over this text if the hero gets shorter. */
  position: relative;
  z-index: 1;
  padding: clamp(30px, 4vw, 48px) 0;
  /* Rule removed on purpose. It used to mark where the hero ended, but the
     hero now runs a full viewport and its sky fades out into the panel, so a
     hairline across that fade cut the gradient in half and read as a seam.
     The margin below does the separating instead. */
  /* border-top: 1px solid var(--line); */
  margin-top: clamp(40px, 6vw, 70px);
}

.ec-strip__now {
  margin: 0 0 clamp(20px, 2.6vw, 30px);
  text-align: center;
  font-family: var(--serif);
  font-size: clamp(20px, 2.2vw, 24px);
  font-style: italic;
  color: var(--ink);
}

/* A segmented card, not five columns floating in space.

   The row used to be a bare flex list, which read as a table dump: five
   identical stacks, no container, and the "these two are real dates, those
   three are facts" distinction carried entirely by a 22px underline that
   looked like a rendering artifact. It now sits in the same cream card
   language as .ec-formcard, so it belongs to the page.

   Dividers are borders on the cells, NOT the grid-gap trick (a 1px gap over a
   line-coloured container). That trick draws perfect dividers but fills any
   empty cell in the last row with solid line colour, and 5 items in a 3- or
   2-column grid always leaves one. Borders with per-breakpoint nth-child
   resets survive the wrap. */
.ec-strip__row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--panel-lift);
  border: 1px solid var(--line);
  border-radius: 22px;
  /* Clips the cells' square corners to the container's radius, and keeps the
     active cell's inset accent bar inside the curve. */
  overflow: hidden;
  box-shadow: 0 24px 50px -34px rgba(64, 47, 40, 0.35);
}

.ec-strip__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: clamp(20px, 2.4vw, 30px) 10px;
  text-align: center;
  border-left: 1px solid var(--line);
  transition: background 0.2s var(--ease);
}

.ec-strip__item:hover {
  background: #fffdfa;
}

/* Five across: only the first cell has no divider to its left. */
.ec-strip__item:nth-child(5n + 1) {
  border-left: 0;
}

.ec-strip__kind {
  font-size: 15px; /* 14px floor — this was 12px */
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.ec-strip__mon {
  font-size: 15px; /* 14px floor — this was 12px */
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.ec-strip__day {
  font-family: var(--serif);
  font-size: clamp(32px, 3.4vw, 46px);
  line-height: 1;
  color: var(--ink);
}

/* The next eclipse gets a tab-style accent bar across the top of its cell,
   replacing the old 22px underline under the numeral — that sat close enough
   to the digits to read as a stray rule rather than an indicator. An inset
   shadow rather than a border so it adds no height and cannot shift the grid. */
.ec-strip__item.is-active {
  background: #fffdf8;
  box-shadow: inset 0 3px 0 var(--cocoa);
}

.ec-strip__item.is-active .ec-strip__kind {
  color: var(--cocoa);
}

/* The three trailing stats stay quieter, so the row still reads "dates first,
   facts after" — that hierarchy was the one good idea in the old version. */
.ec-strip__item.is-ghost .ec-strip__day {
  color: var(--muted);
  font-size: clamp(28px, 2.8vw, 38px);
}

/* Straight from five columns to two — there is no useful three-column step,
   because five items across three columns strands two in a half-empty row and
   the card stops reading as a rectangle. 720px is where five cells still clear
   the longest label ("SAROS 126" is ~86px at this size, in a ~120px cell). */
@media (max-width: 719.98px) {
  .ec-strip__row {
    grid-template-columns: repeat(2, 1fr);
  }

  .ec-strip__item {
    border-left: 1px solid var(--line);
  }

  .ec-strip__item:nth-child(2n + 1) {
    border-left: 0;
  }

  /* Everything from the second row down needs a horizontal divider too. */
  .ec-strip__item:nth-child(n + 3) {
    border-top: 1px solid var(--line);
  }

  /* Five into two leaves the last item alone in a half row. Spanning it keeps
     the card a clean rectangle instead of a card with a bite out of it. */
  .ec-strip__item:last-child {
    grid-column: 1 / -1;
  }
}

/* --------------------------------------------------------------------------
   Pull quote
   -------------------------------------------------------------------------- */

.ec-quote {
  position: relative;
  z-index: 1;
  padding: clamp(56px, 9vw, 120px) 0 clamp(64px, 10vw, 130px);
  text-align: center;
}

.ec-quote__line {
  max-width: 20ch;
  margin: 0 auto 26px;
  font-family: var(--serif);
  font-size: clamp(32px, 5vw, 58px);
  font-weight: 500;
  line-height: 1.16;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ec-quote__line em {
  font-style: italic;
  font-weight: 400;
}

.ec-quote__sub {
  max-width: 56ch;
  margin: 0 auto;
  font-size: 17px;
  line-height: 1.8;
  color: var(--body);
}

/* --------------------------------------------------------------------------
   Section headings
   -------------------------------------------------------------------------- */

.ec-sechead {
  margin: 0 0 clamp(36px, 5vw, 58px);
  font-family: var(--serif);
  font-size: clamp(30px, 4vw, 46px);
  font-weight: 500;
  line-height: 1.14;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* The second line drops back — the reference's two-tone heading.
   0.6, not the 0.34 this started at. That measured 1.9:1 on the cream panel
   and 1.8:1 on the mauve band, which is below the 3:1 large-text minimum by
   a wide margin — and this is not decoration, it is the second half of the
   section heading. 0.6 clears 3:1 on BOTH grounds (3.5 and 3.1) while still
   reading as clearly recessive against the solid first line. */
.ec-sechead__fade {
  color: rgba(64, 47, 40, 0.6);
}

.ec-sechead--right {
  text-align: right;
}

/* --------------------------------------------------------------------------
   Features band
   -------------------------------------------------------------------------- */

.ec-features {
  display: flex;
  align-items: flex-start;
  gap: clamp(24px, 4vw, 60px);
}

.ec-features__main {
  flex: 1 1 auto;
  min-width: 0;
}

.ec-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Rules sit BETWEEN rows only — no line above the first or below the last,
   which is what keeps the block reading as a list rather than a boxed table. */
.ec-list__item + .ec-list__item {
  border-top: 1px solid rgba(64, 47, 40, 0.16);
}

.ec-list__item summary {
  padding: clamp(20px, 2.5vw, 28px) 0;
  font-family: var(--serif);
  font-size: clamp(19px, 1.9vw, 24px);
  font-weight: 500;
  letter-spacing: -0.01em;
  /* Dimmed while closed. Contrast is intentionally low — these are the
     options you have not chosen, and the whole point of the pattern is that
     one title carries the section and the rest recede. */
  color: rgba(74, 55, 47, 0.42);
  cursor: pointer;
  list-style: none;
  transition: color 0.25s var(--ease);
}

/* Kill the native disclosure triangle in both engines, same as the FAQ. */
.ec-list__item summary::-webkit-details-marker { display: none; }
.ec-list__item summary::marker { content: ""; }

.ec-list__item summary:hover {
  color: rgba(74, 55, 47, 0.72);
}

.ec-list__item details[open] summary {
  font-weight: 700;
  color: var(--cocoa);
  padding-bottom: 10px;
}

.ec-list__item summary:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: 3px;
  border-radius: 4px;
}

.ec-list__item p {
  margin: 0;
  padding-bottom: clamp(20px, 2.5vw, 28px);
  max-width: 62ch;
  font-size: 17px; /* house body size — this was 15px */
  line-height: 1.65;
  color: #5f4e46; /* darkened for the mauve ground — 5.2:1 there */
  /* Native <details> pops open with no transition, which is abrupt next to
     how quietly the rest of this page moves. Fading only the copy is enough
     to soften it, and it is safe under the global reduced-motion block: the
     animation is cut to 0.01ms, finishes, and the element falls back to its
     un-animated opacity of 1. Nothing is ever left hidden. */
  animation: ec-feat-in 0.45s var(--ease);
}

@keyframes ec-feat-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* The big rotated word. Decorative, so it is aria-hidden in the markup and
   simply disappears when there is no room for it. */
.ec-vertical {
  flex: 0 0 auto;
  margin: 0;
  writing-mode: vertical-rl;
  font-family: var(--serif);
  /* Much larger than it was (was 8vw / 116px cap). At this size the word runs
     most of the band's height and becomes the thing balancing the composition
     — which matters more now that the list is an accordion and its content
     collapsed to one open description. The cap is 160, not 200: past that the
     word starts driving the band's height on its own and opens a gap under
     the list. */
  font-size: clamp(80px, 13vw, 160px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
  color: rgba(255, 255, 255, 0.5);
  -webkit-user-select: none;
  user-select: none;
}

@media (max-width: 860px) {
  .ec-vertical { display: none; }
}

/* --------------------------------------------------------------------------
   Learn cards
   -------------------------------------------------------------------------- */

/* One feature, two secondary — an editorial block rather than a row.

   The previous version was three cards of different widths bottom-aligned with
   `align-items: end`. That produced ragged tops that read as broken alignment
   rather than rhythm, and because the two small cards hung off the bottom of
   the tallest one, roughly the top 40% of the band was dead space. The grid
   below removes both problems at once: the feature spans both rows on the
   left, the two secondary cards stack beside it, and every edge lines up.

   The --sm / --md / --lg class names are kept so index.html did not have to
   change, but they now mean "secondary, secondary, feature" rather than three
   sizes on one row. */
.ec-cards {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 1fr;
  gap: clamp(16px, 1.8vw, 24px);
  align-items: stretch;
}

.ec-card--lg {
  grid-column: 1 / span 7;
  grid-row: 1 / span 2;
}

.ec-card--sm {
  grid-column: 8 / span 5;
  grid-row: 1;
}

.ec-card--md {
  grid-column: 8 / span 5;
  grid-row: 2;
}

/* The cards are surfaces now, not bare images on the panel. --panel-lift on
   --panel is the house "card sitting on a panel" pairing already used by the
   form and the eclipse strip. */
.ec-card {
  display: flex;
  flex-direction: column;
  padding: clamp(14px, 1.4vw, 18px);
  background: var(--panel-lift);
  border: 1px solid var(--line);
  border-radius: 22px;
  box-shadow: 0 20px 44px -32px rgba(64, 47, 40, 0.4);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease),
    box-shadow 0.3s var(--ease);
}

/* The two secondary cards run horizontally: thumbnail beside the copy, so a
   pair of them stacks to the same height as the feature without either one
   becoming a tall thin sliver. Done with grid on the <article> rather than by
   adding a wrapper, so the markup stays as it is. */
.ec-card--sm,
.ec-card--md {
  display: grid;
  grid-template-columns: minmax(110px, 34%) 1fr;
  grid-template-rows: auto auto;
  column-gap: clamp(14px, 1.4vw, 20px);
  align-content: center;
}

.ec-card--sm .ec-card__media,
.ec-card--md .ec-card__media {
  grid-column: 1;
  grid-row: 1 / 3;
  align-self: center;
}

.ec-card--sm h3,
.ec-card--md h3 {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  margin-top: 0;
}

.ec-card--sm p,
.ec-card--md p {
  grid-column: 2;
  grid-row: 2;
  align-self: start;
}

/* The frame. Radius and clipping live here rather than on the <img> so the
   artwork can scale inside a fixed box — an image that owns its own radius
   cannot clip its own transform. The hairline and shadow are what make the
   three read as objects sitting on the panel instead of three loose pictures;
   it matters most for the chart-wheel card, whose artwork is nearly the same
   cream as the band behind it and had no edge at all before. */
/* Radius and clipping only. The card around it already carries the border and
   the shadow — giving the frame its own as well stacked two shadows a few
   pixels apart and read as a printing misregistration. */
.ec-card__media {
  position: relative;
  overflow: hidden;
  border-radius: 14px;
  background: var(--mauve);
}

.ec-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform 0.55s var(--ease);
}

/* Square thumbnails on the secondary cards: a 4/3 crop in a 34%-wide column
   left the artwork too small to read as anything. */
.ec-card--sm img,
.ec-card--md img {
  aspect-ratio: 1 / 1;
}

/* The feature's media takes whatever height the two stacked cards beside it
   add up to, rather than a fixed ratio — that is what keeps the left and
   right columns the same height without hard-coding either. */
.ec-card--lg .ec-card__media {
  flex: 1 1 auto;
  min-height: 240px;
}

.ec-card--lg img {
  height: 100%;
  aspect-ratio: auto;
}

/* NOTHING here transforms the <article>. motion.js tweens y and scale on the
   cards themselves, so a transform on the card root would be overwritten by
   GSAP — or fight it mid-reveal. The surface properties are free, and scaling
   the artwork inside its clipped frame supplies the movement. */
@media (hover: hover) {
  .ec-card:hover {
    background: #fffdfa;
    border-color: rgba(64, 47, 40, 0.2);
    box-shadow: 0 30px 60px -30px rgba(64, 47, 40, 0.5);
  }

  .ec-card:hover img {
    transform: scale(1.05);
  }

  .ec-card:hover h3 {
    color: var(--cocoa);
  }
}

.ec-card h3 {
  margin: 18px 0 8px;
  font-family: var(--serif);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  font-size: 20px;
  line-height: 1.24;
  transition: color 0.25s var(--ease);
}

.ec-card--lg h3 {
  font-size: clamp(22px, 2.4vw, 30px);
}

.ec-card p {
  margin: 0;
  font-size: 17px; /* house body size — the small cards were 14px, the lg 15px */
  line-height: 1.62;
  color: var(--body);
}

.ec-card--lg p {
  max-width: 54ch;
}

/* One column below the feature-and-stack width. Every explicit row and column
   placement has to be unwound here, not just the columns — a `grid-row: 1 /
   span 2` left over from the desktop rules would strand the feature in a
   two-row track while everything else piled into row 1. */
@media (max-width: 999.98px) {
  .ec-cards {
    grid-template-columns: 1fr;
    grid-auto-rows: auto;
  }

  .ec-card--lg,
  .ec-card--sm,
  .ec-card--md {
    grid-column: 1;
    grid-row: auto;
  }

  /* The feature leads. In source order it is last — it is the third <article>
     because on desktop the grid places it, not the document — so stacking
     these one per row buried the biggest, most important card at the bottom
     of the section on exactly the screens with the least patience for
     scrolling. `order` fixes the presentation without touching the markup,
     and it is safe for reading order here because all three are peers: the
     cards are independent article teasers, so no one of them has to be read
     before another for the section to make sense. */
  .ec-card--lg {
    order: -1;
  }

  /* The feature loses its stretched media and goes back to a fixed ratio,
     since there is no longer a second column for it to match the height of. */
  .ec-card--lg .ec-card__media {
    flex: 0 0 auto;
    min-height: 0;
  }

  .ec-card--lg img {
    height: auto;
    aspect-ratio: 16 / 9;
  }
}

/* Below this the horizontal secondary cards stop working: a 110px thumbnail
   plus a text column cannot both hold their line length. They become vertical
   like the feature. */
@media (max-width: 560px) {
  .ec-card--sm,
  .ec-card--md {
    display: flex;
    flex-direction: column;
  }

  /* These four resets are not tidying — without them the card is visibly
     broken. The horizontal layout above sets `align-self: end` on the h3 and
     `align-self: start` on the p to align them vertically within their grid
     row. Switching the container to a COLUMN flex swaps which axis `align-self`
     controls, so those values silently became horizontal: the title rendered
     hard right while its paragraph stayed left. `margin-top` comes back for
     the same reason — it was zeroed only because the title sat beside the
     thumbnail, and stacked it needs the gap. */
  .ec-card--sm h3,
  .ec-card--md h3,
  .ec-card--sm p,
  .ec-card--md p {
    align-self: stretch;
  }

  .ec-card--sm h3,
  .ec-card--md h3 {
    margin-top: 18px;
  }

  .ec-card--sm img,
  .ec-card--md img {
    aspect-ratio: 16 / 9;
  }
}

/* --------------------------------------------------------------------------
   Method band — statement + scattered chips
   -------------------------------------------------------------------------- */

.ec-method__inner {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(420px, 46vw, 560px);
}

.ec-method__title {
  position: relative;
  z-index: 2;
  margin: 0;
  text-align: center;
  font-family: var(--serif);
  font-size: clamp(52px, 9.5vw, 124px);
  font-weight: 700;
  line-height: 0.94;
  letter-spacing: -0.035em;
  color: var(--cocoa);
}

/* Chips are absolutely placed around the statement on wide screens and fall
   back to a plain grid below — the scatter is decoration, not information,
   so it is the first thing to go. */
.ec-chip {
  padding: 18px 20px;
  border-radius: 18px;
  color: rgba(255, 255, 255, 0.88);
  box-shadow: 0 22px 46px -22px rgba(64, 47, 40, 0.75);
}

.ec-chip__dot {
  display: block;
  width: 22px;
  height: 22px;
  margin-bottom: 12px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.24);
}

.ec-chip__title {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 700;
  color: #ffffff;
}

.ec-chip p:last-child {
  margin: 0;
  font-size: 16px;
  line-height: 1.55;
}

.ec-chip--plum   { background: var(--plum); }
.ec-chip--olive  { background: var(--olive); }
.ec-chip--violet { background: var(--violet); }
.ec-chip--teal   { background: var(--teal); }

/* 1140, not 1000. The scatter needs room for two 236px chips PLUS the
   statement between them; at 1000px the left pair sat on top of "Exact."
   and "Personal." and the band read as a collision rather than a layout.
   Below this the chips fall back to the plain grid, which is fine at any
   width. Kept in sync with the max-width query below. */
@media (min-width: 1140px) {
  .ec-chips {
    position: absolute;
    inset: 0;
    z-index: 1;
  }

  .ec-chip {
    position: absolute;
    width: 21%;
  }

  /* None of these may sit flush at 0. A rotation grows an element's bounding
     box — a 253px chip at 4 degrees gains about 6px — and .ec-chips is
     positioned against the wrap's PADDING box, not its content box, so a chip
     at `right: 0` already starts at the wrap's outer edge with nothing to
     absorb that growth. Between roughly 1140 and 1190px, where the wrap is as
     wide as the viewport, the teal chip pushed past it and the whole document
     scrolled sideways. It went unnoticed because at any wider viewport the
     centred wrap has margins for the overhang to hide in. */
  .ec-chip--plum   { top: 6%;  left: 1%;  transform: rotate(-7deg); }
  .ec-chip--olive  { top: 46%; left: 4%;  transform: rotate(4deg); }
  .ec-chip--violet { top: 2%;  right: 3%; transform: rotate(6deg); width: 18%; }
  .ec-chip--teal   { top: 40%; right: 1%; transform: rotate(-4deg); }
}

@media (max-width: 1139.98px) {
  .ec-method__inner { min-height: 0; }

  .ec-chips {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 16px;
    margin-top: clamp(36px, 5vw, 56px);
  }
}

/* Source strip under the statement */
.ec-sources {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px clamp(24px, 5vw, 60px);
  margin: clamp(44px, 6vw, 74px) 0 0;
  padding: clamp(24px, 3vw, 32px) 0 0;
  list-style: none;
  border-top: 1px solid rgba(64, 47, 40, 0.16);
  text-align: center;
}

.ec-sources strong {
  display: block;
  font-family: var(--serif);
  font-size: 24px;
  font-weight: 500;
  color: var(--ink);
}

.ec-sources span {
  font-size: 16px;
  color: #5f4e46;
}

/* --------------------------------------------------------------------------
   Samples
   -------------------------------------------------------------------------- */

.ec-samples {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(18px, 2.4vw, 28px);
}

.ec-sample {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: clamp(24px, 3vw, 32px);
  background: var(--panel-lift);
  border: 1px solid var(--line);
  border-radius: 22px;
}

.ec-sample blockquote {
  margin: 0 0 20px;
  font-family: var(--serif);
  font-size: clamp(17px, 1.6vw, 20px);
  font-style: italic;
  line-height: 1.5;
  color: var(--ink);
}

.ec-sample figcaption {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.ec-sample__who {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.ec-sample__tag {
  font-size: 15px;
  font-weight: 700;
  color: var(--cocoa);
}

/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */

.ec-faq__inner {
  display: flex;
  align-items: flex-start;
  gap: clamp(28px, 6vw, 90px);
}

.ec-faq__head {
  flex: 0 0 auto;
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(30px, 3.6vw, 44px);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ec-faq__list {
  flex: 1 1 auto;
  min-width: 0;
  border-top: 1px solid var(--line);
}

.ec-faq__list details {
  border-bottom: 1px solid var(--line);
}

.ec-faq__list summary {
  position: relative;
  padding: 20px 40px 20px 0;
  font-size: 17px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}

/* Hide the native disclosure triangle in both engines. */
.ec-faq__list summary::-webkit-details-marker { display: none; }
.ec-faq__list summary::marker { content: ""; }

.ec-faq__list summary::after {
  content: "";
  position: absolute;
  right: 6px;
  top: 50%;
  width: 8px;
  height: 8px;
  margin-top: -5px;
  border-right: 1.5px solid var(--body);
  border-bottom: 1.5px solid var(--body);
  transform: rotate(-45deg);
  transition: transform 0.25s var(--ease);
}

.ec-faq__list details[open] summary::after {
  transform: rotate(45deg);
  margin-top: -2px;
}

.ec-faq__list summary:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: 2px;
  border-radius: 4px;
}

.ec-faq__list details p {
  margin: 0;
  padding: 0 40px 22px 0;
  max-width: 68ch;
  font-size: 17px;
  line-height: 1.75;
  color: var(--body);
}

@media (max-width: 780px) {
  .ec-faq__inner {
    flex-direction: column;
    align-items: stretch;
  }
}

/* --------------------------------------------------------------------------
   Closing CTA — cocoa
   -------------------------------------------------------------------------- */

/* Same overlap as .ec-band, but the footer continues straight on underneath
   in the same colour, so only the top corners round. */
.ec-close {
  position: relative;
  margin-top: calc(var(--round) * -1);
  padding: calc(clamp(80px, 11vw, 150px) + var(--round)) 0 clamp(64px, 8vw, 100px);
  background: var(--cocoa);
  border-radius: var(--round) var(--round) 0 0;
  text-align: center;
}

.ec-close__title {
  margin: 0 0 14px;
  font-family: var(--serif);
  font-size: clamp(46px, 8.6vw, 112px);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: -0.03em;
  color: var(--cream);
}

.ec-close__title em {
  font-style: italic;
  font-weight: 400;
}

.ec-close__sub {
  margin: 0 auto clamp(38px, 5vw, 56px);
  max-width: 44ch;
  font-size: 17px;
  color: rgba(247, 241, 233, 0.76);
}

/* .ec-close__cta and .ec-close__note were removed with the link-back-to-the-
   hero pattern they served. The band now ends in the actual form
   (.ec-formcard--close), and that card carries its own submit button and
   reassurance note, so both rules were dead. */

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

.ec-foot {
  padding: clamp(48px, 6vw, 76px) 0 40px;
  background: var(--cocoa);
  color: rgba(247, 241, 233, 0.72);
}

.ec-foot__inner {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(28px, 4vw, 52px);
  padding-bottom: 40px;
  border-bottom: 1px solid rgba(247, 241, 233, 0.18);
}

.ec-foot__brandcol {
  flex: 1 1 290px;
}

.ec-foot__brand {
  margin: 0 0 14px;
  /* Cream, not the artwork's own #6B4438 — that is the cocoa this footer is
     printed on and the wordmark would have disappeared into it. Possible only
     because the symbol's paths are currentColor. */
  color: var(--cream);
}

.ec-foot__logo {
  display: block;
  height: 30px;
  width: auto;
  aspect-ratio: 2005 / 343;
}

.ec-foot__brand em {
  font-style: italic;
  font-weight: 400;
}

.ec-foot__blurb {
  margin: 0 0 16px;
  font-size: 16px;
  line-height: 1.7;
}

.ec-foot__legal {
  margin: 0;
  max-width: 32ch;
  font-size: 15px;
  color: rgba(247, 241, 233, 0.72);
}

.ec-foot__col {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 140px;
  max-width: 200px;
}

.ec-foot__head {
  margin: 0 0 4px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(247, 241, 233, 0.72);
}

/* padding + negative margin: the links measured 23px tall, well under the
   44px touch minimum. The margin keeps the visual rhythm of the column
   unchanged while the hit area grows. */
.ec-foot__col a {
  padding: 11px 0;
  margin: -11px 0;
  font-size: 16px;
  color: rgba(247, 241, 233, 0.82);
  text-decoration: none;
  transition: color 0.2s var(--ease);
}

.ec-foot__col a:focus-visible {
  outline: 3px solid var(--gold, var(--cream));
  outline-offset: 2px;
  border-radius: 4px;
}

.ec-foot__col a:hover {
  color: #ffffff;
}

.ec-foot__fine {
  margin: 20px 0 0;
  font-size: 14px;
  line-height: 1.6;
  color: rgba(247, 241, 233, 0.72);
}

/* --------------------------------------------------------------------------
   Tablet and below
   -------------------------------------------------------------------------- */

@media (max-width: 860px) {
  :root { --round: 32px; }

  .ec-features {
    flex-direction: column;
    /* stretch, not the inherited flex-start: in column direction the cross
       axis is horizontal, so flex-start shrink-wraps the whole column. */
    align-items: stretch;
  }

  .ec-nav__pill {
    position: relative;
    padding-left: 20px;
    gap: 10px;
    flex-wrap: wrap;
  }

  .ec-nav__cta { margin-left: auto; }

  /* No-JS fallback: the links wrap onto a second row inside the pill. Not
     elegant, but the nav still works. They used to be `display: none` here
     with nothing in their place, which silently removed the whole primary
     nav on every phone. */
  .ec-nav__links {
    order: 3;
    width: 100%;
    margin: 0;
    padding-top: 4px;
    justify-content: flex-start;
    flex-wrap: wrap;
  }

  /* With JS, the same list becomes a proper dropdown. */
  .ec-nav.has-menu .ec-nav__toggle { display: flex; }
  .ec-nav.has-menu .ec-nav__pill { flex-wrap: nowrap; }

  .ec-nav.has-menu .ec-nav__links {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    right: 0;
    width: auto;
    flex-direction: column;
    gap: 2px;
    padding: 10px;
    /* Fully opaque, not translucent like the pill. This panel sits over the
       hero headline, and even a few percent of transparency let 78px serif
       type read straight through the menu items. */
    background: var(--panel-lift);
    border: 1px solid var(--line);
    border-radius: 24px;
    box-shadow: 0 22px 48px -22px rgba(64, 47, 40, 0.55);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity 0.22s var(--ease), transform 0.22s var(--ease),
      visibility 0.22s var(--ease);
  }

  .ec-nav.has-menu .ec-nav__links.is-open {
    opacity: 1;
    visibility: visible;
    transform: none;
  }

  .ec-nav.has-menu .ec-nav__links a {
    padding: 14px 16px;
    font-size: 15px;
    border-radius: 14px;
  }

  .ec-sechead--right { text-align: left; }
}

/* --------------------------------------------------------------------------
   Mobile — nothing below 14px anywhere, body stays at 16px
   -------------------------------------------------------------------------- */

@media (max-width: 560px) {
  /* The variable, not padding — .ec-hero owns the padding now so that the
     clouds run to the top of the page behind the nav. */
  .ec-panel--top { --hero-top: 106px; }

  .ec-formcard { padding: 22px; }

  /* No gap override any more. The row is a segmented card whose cells are
     divided by borders and must sit flush — a gap here would open seams
     through the card and expose the panel behind it. */

  /* This block only ever RAISES. Anything whose desktop size is already above
     the value below must stay out of it, or the rule quietly does the opposite
     of its job — .ec-card p, .ec-chip p, .ec-foot__blurb, .ec-foot__col a and
     .ec-hero__note are all 16px now and are deliberately excluded for exactly
     that reason. Check the desktop value before adding anything here. */
  .ec-sources span,
  .ec-foot__legal,
  .ec-foot__fine,
  .ec-formcard__note,
  .ec-sample__tag,
  .ec-hint,
  .ec-error,
  .ec-field label,
  .ec-nav__cta {
    font-size: 15px;
  }

  .ec-strip__kind,
  .ec-strip__mon,
  .ec-sample__who,
  .ec-foot__head,
  .ec-nav__links a,
  .ec-nav.has-menu .ec-nav__links a {
    /* Being uppercase micro-labels is not an exemption from the floor —
       letterspaced caps are, if anything, harder to read small. */
    font-size: 15px;
    letter-spacing: 0.1em;
  }
}

/* --------------------------------------------------------------------------
   PHONE — hero recomposed, nav made to fit

   These come last on purpose: they override the earlier mobile blocks, and
   grouping them keeps the phone layout readable as one thing rather than as
   corrections scattered through the file.
   -------------------------------------------------------------------------- */

/* The eclipse gets its OWN BAND above the copy instead of sitting behind it.

   Measured at 390px before this: the headline ran y280-407 while the moon ran
   y114-332, so the type sat straight on top of the moon. The old answer was to
   drop the whole stage to opacity 0.32 and hope it read as atmosphere — but a
   washed-out eclipse behind text is muddy at both jobs, and the client is
   right that it looked like two things fighting.

   Now the artwork is a real element at FULL opacity in a reserved band, and
   the copy starts underneath it. Nothing overlaps, and the eclipse finally
   reads on a phone. */
/* Runs to 999.98px, matching the width where the desktop two-column split
   takes over. An earlier cut at 700px left the 701-999 tablet range on the old
   wash-behind-text layout, where the headline still sat on the moon. */
@media (max-width: 999.98px) {
  .ec-hero {
    /* Named, because four rules now need the same number and one of them is
       the night gradient — which has to know exactly where the band ends, in
       pixels, or the dusk lands on the headline. The band is 43% of the hero
       at 390px wide and 60% at 999px, so no percentage stop can track it.
       The value is unchanged; it just has a name now. */
    --band-h: clamp(200px, 58vw, 330px);
    /* Nav clearance + the eclipse band + a gap before the headline. */
    padding-top: calc(var(--hero-top) + var(--band-h) + 18px);
    min-height: min(100vh, calc(680px + var(--hero-top) + var(--band-h)));
  }

  /* The phone sky is dark THE WHOLE WAY DOWN, and that is not a stylistic
     choice — it is forced by the white type.

     This block used to release the dark at the band's foot and leave the copy
     below it on plain cream, which was right while the copy was dark ink. The
     moment the type crosses to --cream that same gradient renders white text
     on a cream ground: invisible. Anything that darkens the copy on desktop
     has to darken it here too, and the two backgrounds are separate rules, so
     this pairing is easy to break. They change together or not at all.

     The band still goes deeper than the copy — 0.9 against 0.76 — so the
     eclipse keeps its own pocket of night and the hand-off between them is the
     horizon glow rather than a step. Stops stay lengths off --band-h, not
     percentages, because the band is 43% of the hero at 390px and 60% at
     999px. */
  .ec-sky__night {
    background:
      /* Sunset along the band's foot. Held to 0.34: warm orange over dark
         RAISES luminance toward the middle of the range, and at the 0.45 this
         carried before, white type crossing it measured 4.14:1. */
      radial-gradient(120% 18% at 50% calc(var(--hero-top) + var(--band-h)),
        rgba(255, 150, 62, 0.34) 0%,
        rgba(255, 120, 50, 0) 76%),
      linear-gradient(180deg,
        rgba(8, 11, 30, 0.9) 0px,
        rgba(9, 12, 32, 0.86) calc(var(--hero-top) + var(--band-h) * 0.55),
        rgba(10, 13, 34, 0.76) calc(var(--hero-top) + var(--band-h) + 30px),
        rgba(10, 13, 34, 0.74) 95%,
        rgba(10, 13, 34, 0.4) 100%);
  }

  /* Clipped to the band for the same reason, so the star positions — written
     as percentages of their own box — land in the dark instead of being spread
     down a hero that is mostly lit copy on a phone. */
  .ec-sky__stars {
    bottom: auto;
    height: calc(var(--hero-top) + var(--band-h));
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 64%, rgba(0, 0, 0, 0) 100%);
    mask-image: linear-gradient(180deg, #000 0%, #000 64%, rgba(0, 0, 0, 0) 100%);
  }

  /* No corona boost here. The one surviving flare is 260% of a sun that is
     itself 40vw, so a lit ray reaches ~300px past the band and lands on the
     headline — and unlike the desktop case there is no dark sky under it to
     justify the spill. The band gets its drama from the sky going nearly black
     behind the disc instead. */
  .ec-sky__sun.is-night .ec-sky__flare {
    filter: none;
  }

  .ec-sky__stage {
    /* Both values are capped, and the RATIO between them is the thing that
       matters. The moon hangs 0.75 sun-widths above the band's 54% line, so
       the band must stay at least 1.39x the sun's width or the moon's crown is
       cut off. 330 / 225 = 1.47 leaves a little margin at the widest end.

       Uncapped 58vw reached 406px at 700px wide, which pushed the hero's CTA
       below the fold — the band has to stop growing before the copy does. */
    --sun-w: min(40vw, 225px);
    top: var(--hero-top);
    bottom: auto;
    height: var(--band-h);
    /* The pair is anchored to the stage's right edge, so it needs pulling back
       to sit centred over the copy. */
    transform: translateX(-25%);
    /* Full strength. It is no longer behind anything. */
    opacity: 1;
  }
}

/* The nav pill overflowed its own box by 42px at 390px — the hamburger sat at
   x401 while the pill ended at x359, so the button was simply off the screen.
   Brand + "Get my reading" + toggle will not fit at their desktop sizes on a
   375px-wide phone, so all three give a little. The toggle keeps its 44px tap
   target; only type and padding shrink. */
@media (max-width: 480px) {
  .ec-nav { padding: 0 12px; }

  .ec-nav__pill {
    padding: 8px 8px 8px 16px;
    gap: 8px;
  }

  /* The brand is a wordmark now, so height is the lever — a font-size here
     would do nothing at all. */
  .ec-nav__logo { height: 19px; }

  .ec-nav__cta {
    padding: 11px 14px;
    font-size: 14px;
    letter-spacing: 0;
  }
}

/* ==========================================================================
   FOOTER MODALS — About / Privacy / Terms

   Native <dialog> panels opened from the footer's "More" column. Ported from
   the reading report (onboarding.html, styled by style.css) and remapped onto
   the landing palette so index.html carries the same three panels without
   pulling in style.css. Class names use the ec- prefix to stay in the landing
   page's language; the .ecl-modal originals are left untouched on the report.

   showModal() brings its own backdrop, Esc-to-close and focus trapping, so the
   only JS needed is opening the right dialog (see the foot of index.html).
   ========================================================================== */
.ec-modal {
  width: min(560px, 92vw);
  max-width: 92vw;
  margin: auto;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--body);
  overflow: visible;
}

.ec-modal::backdrop {
  background: rgba(26, 15, 10, 0.62);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.ec-modal__card {
  position: relative;
  max-height: 85vh;
  overflow: auto;
  padding: clamp(28px, 4vw, 46px);
  background: var(--panel-lift);
  border: 1px solid var(--line);
  border-radius: clamp(20px, 2.4vw, 30px);
  box-shadow: 0 40px 110px -34px rgba(48, 28, 20, 0.5);
}

.ec-modal__close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  font-size: 26px;
  line-height: 1;
  color: var(--muted);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}

.ec-modal__close:hover {
  background: var(--mauve);
  color: var(--ink);
}

.ec-modal__eyebrow {
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--plum);
}

.ec-modal__title {
  margin: 0 0 16px;
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(26px, 3vw, 34px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ec-modal__body p {
  margin: 0 0 14px;
  font-size: 16px;
  line-height: 1.65;
  color: var(--body);
}

.ec-modal__body p:last-child { margin-bottom: 0; }

.ec-modal__body a {
  color: var(--plum);
  text-decoration: underline;
  text-underline-offset: 2px;
}

@media (prefers-reduced-motion: no-preference) {
  .ec-modal[open] { animation: ec-modal-in 0.28s var(--ease); }
}

@keyframes ec-modal-in {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   ACCOUNT — nav state + sign-in modal

   Added on top of the signed-off landing page, so it invents no colours, no
   type and no shapes: the nav items are .ec-nav__links anchors, the modal is
   the same .ec-modal / .ec-modal__card chrome the About / Privacy / Terms
   dialogs already use, and the field and button inside it are .ec-field and
   .ec-btn untouched. Only the pieces those three don't already cover — the
   note, the error, the link-sent state — are new.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Nav — logged-out / signed-in swap

   Driven by a CLASS on <body>, never by an inline `display` written from JS.
   At ≤999.98px .ec-nav__links becomes a dropdown panel, and an inline
   display:block from a script beats that media query — the account links then
   leak out of the burger and sit on top of the hero. A class lets the
   stylesheet stay in charge of layout at every width.
   -------------------------------------------------------------------------- */

/* Used by the nav AND the footer's More column, so the pair is named for the
   state rather than for the nav. Neither selector sets anything but display —
   both hosts are flex containers, so `block` is blockified to match whatever
   the column already does. */
.ec-auth--in { display: none; }
body.is-in .ec-auth--out { display: none; }
body.is-in .ec-auth--in { display: block; }

/* Account actions read as account actions, not as another section anchor.
   Cocoa on the pill's cream measures 7.4:1. */
.ec-nav__links .ec-nav__auth { color: var(--cocoa); }

/* --------------------------------------------------------------------------
   Sign-in modal
   -------------------------------------------------------------------------- */

.ec-modal--auth { width: min(460px, 92vw); }

/* Mauve, the palette's one soft band colour, rather than a new tint. */
.ec-auth__note {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  margin: 0 0 24px;
  padding: 13px 15px;
  background: rgba(214, 201, 207, 0.5);
  border-radius: 14px;
  /* Set on the container, not on a child: the note is itself a <p> wrapping a
     <span>, so a `.ec-auth__note p` rule would never match anything. */
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
}

.ec-auth__note svg { flex: 0 0 auto; margin-top: 2px; }

.ec-auth__err {
  display: none;
  align-items: flex-start;
  gap: 10px;
  margin: -4px 0 18px;
  padding: 12px 14px;
  background: #fbeae5;
  border-left: 3px solid var(--error);
  border-radius: 0 12px 12px 0;
}

.ec-auth__err.is-visible { display: flex; }

.ec-auth__err svg { flex: 0 0 auto; margin-top: 3px; }

.ec-auth__err p {
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--error); /* 5.6:1 on #fbeae5 */
}

.ec-auth__err b { word-break: break-word; }

/* The "start your free trial" affordance inside the not-a-subscriber message.
   A <button> so Enter works on it — see the note where it is written. */
.ec-auth__err a,
.ec-auth__err button {
  font-family: var(--sans);
  font-size: 15px;
  color: var(--error);
  font-weight: 700;
  background: none;
  border: none;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.ec-auth__err button:focus-visible {
  outline: 2px solid var(--error);
  outline-offset: 2px;
  border-radius: 3px;
}

.ec-auth__alt {
  margin: 16px 0 0;
  text-align: center;
  font-size: 15px;
  color: var(--muted);
}

/* A real <button>, so it is reachable by keyboard and announced as an action.
   Styled back down to a link — the modal already has one .ec-btn and a second
   filled pill would read as two equal choices. */
.ec-auth__alt button {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 700;
  color: var(--plum);
  background: none;
  border: none;
  padding: 4px 2px;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.ec-auth__alt button:hover { color: var(--cocoa); }

.ec-auth__alt button:focus-visible {
  outline: 3px solid var(--cocoa);
  outline-offset: 2px;
  border-radius: 4px;
}

/* --- Link-sent state ------------------------------------------------------ */

.ec-auth__ok { display: none; text-align: center; }
.ec-auth__ok.is-visible { display: block; }

.ec-auth__ico {
  width: 62px;
  height: 62px;
  margin: 0 auto 18px;
  display: grid;
  place-items: center;
  background: var(--mauve);
  border-radius: 999px;
}

.ec-auth__ok h3 {
  margin: 0 0 8px;
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(24px, 2.6vw, 30px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ec-auth__ok > p {
  margin: 0 0 12px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--body);
}

.ec-auth__mail {
  display: inline-block;
  margin: 0 0 22px;
  padding: 8px 18px;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  word-break: break-word;
}

/* The band between the burger and a comfortable desktop row.

   The hamburger takes over at ≤860px, and above ~920px the pill has room to
   spare — but the account link makes the row five items instead of four, and
   measured at 884px the row wanted 857px inside an 798px pill. Flex items
   shrink rather than overflow, so nothing spilled: every label just wrapped
   and the pill grew from 69px to 94px tall.

   Reclaimed from the gaps and the links' side padding only, which is the
   65-odd pixels needed. The vertical padding is untouched, so the 44px tap
   target survives, and nothing here applies at any width where the original
   four-item row already fitted. */
@media (min-width: 861px) and (max-width: 940px) {
  .ec-nav__links { gap: 0; }
  .ec-nav__links a { padding-left: 9px; padding-right: 9px; }
}
