/* ReadyAstro — Golden Hour. Tokens map 1:1 to design/portfolio-design-dna.json. Reference raw values only via var(). */
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/inter-standard-normal.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}

:root {
  color-scheme: only light;
  /* color (DNA design_system.color.palette) */
  --c-cream: #FFF8F0; --c-white: #FFFFFF; --c-peach: #FFD6B8; --c-apricot: #FFB482; --c-flame: #FFA383;
  --c-coral: #F05B35; --c-rose: #F4A7A3; --c-lilac: #CDB9E6; --c-ink: #2A1F2D; --c-ink-soft: #564356;
  --c-ember: #AA3518; --c-dusk: #3B2B52; --c-mist: #C9BBC6; --c-orchid: #EDA9D6;
  /* type (measured sizes, Inter tracking calculated) */
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --fs-display: 64px; --lh-display: 1; --tr-display: -0.022em;
  --fs-scene: 40px;   --lh-scene: 1.1;  --tr-scene: -0.022em;
  --fs-sub: 21px;     --lh-sub: 1.19;   --tr-sub: -0.018em;
  --fs-body: 17px;    --lh-body: 1.47;  --tr-body: -0.013em;
  --fs-callout: 14px; --lh-callout: 1.43; --tr-callout: -0.006em;
  --fs-nav: 15px;     --lh-nav: 1.33;
  --fs-footer: 12px;  /* was shared with --fs-nav; split 2026-09-12 so the header can grow alone */
  /* space (24/40/70 measured) */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 40px; --s7: 70px; --s8: 120px;
  --gutter: 22px;
  /* layout (measured) */
  --w-content: 1024px; --w-footer: 980px; --w-copy: 740px; --w-copy-narrow: 580px;
  /* shape (measured) */
  --r-pill: 980px; --r-card: 27px; --r-small: 5px; --r-browser: 14px;
  /* motion (measured: cash 0.6/0.4/0.3, apple 0.32 + cubic) */
  --t-scene: 0.6s; --t-media: 0.4s; --t-button: 0.3s; --t-link: 0.32s;
  --ease-apple: cubic-bezier(0.4, 0, 0.6, 1);
  --nav-h: 72px;  /* client override of the measured 44px; see DECISIONS.md 2026-09-12 */
}

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-padding-top: var(--nav-h); }
body { margin: 0; background: var(--c-cream); color: var(--c-ink); font-family: var(--font); font-size: var(--fs-body); line-height: var(--lh-body); letter-spacing: var(--tr-body); font-weight: 400; font-optical-sizing: auto; -webkit-font-smoothing: antialiased; }
img { max-width: 100%; height: auto; display: block; }
h1, h2, h3, p, ol, ul, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }
button { font: inherit; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip { position: absolute; left: var(--s4); top: -100px; z-index: 100; background: var(--c-ink); color: var(--c-cream); padding: var(--s2) var(--s4); border-radius: var(--r-pill); font-size: var(--fs-callout); text-decoration: none; }
.skip:focus { top: var(--s2); }
/* The ring colour is a token, and every rule that darkens a surface sets it in the SAME declaration block.
   The previous shape — one base colour here plus a separate selector list naming the dark surfaces — is
   what let the stage's dusk scene ship an ink ring on an ink-dark sky at 1.24:1 for the life of the site:
   the scene was a dark surface that nobody remembered to add to the list. A surface cannot now change its
   background without its ring travelling with it, and tools/render-audit.mjs measures every one. */
:focus-visible { outline: 2px solid var(--c-ring, var(--c-ink)); outline-offset: 2px; }
mark.confirm { background: #FFF1A8; color: #2A1F2D; padding: 0 .3em; border-radius: 3px; outline: 1px dashed #2A1F2D; font-weight: 500; }

/* ---------- type ---------- */
.si-display { font-size: var(--fs-display); line-height: var(--lh-display); letter-spacing: var(--tr-display); font-weight: 600; text-wrap: balance; }
.si-title { font-size: 48px; line-height: 1.08; letter-spacing: -0.022em; font-weight: 600; text-wrap: balance; }
.si-subhead { font-size: var(--fs-sub); line-height: var(--lh-sub); letter-spacing: var(--tr-sub); font-weight: 600; text-wrap: pretty; }
.si-subhead--soft { color: var(--c-ink-soft); font-weight: 500; }
.si-subhead--dusk { color: var(--c-lilac); font-weight: 500; }
.si-eyebrow { font-size: var(--fs-callout); line-height: var(--lh-callout); letter-spacing: var(--tr-callout); font-weight: 600; color: var(--c-ember); margin-bottom: var(--s3); }

/* ---------- pills (apple .button measured: 17/20, 11px 21px, 980px) ---------- */
.si-pill { display: inline-flex; align-items: center; gap: var(--s2); min-height: 43px; padding: 11px 21px; border-radius: var(--r-pill); font-size: 17px; line-height: 20px; font-weight: 400; letter-spacing: var(--tr-body); text-decoration: none; border: 1px solid transparent; transition: background-color var(--t-button) ease-in-out, color var(--t-button) ease-in-out, border-color var(--t-button) ease-in-out, transform var(--t-button) ease-in-out; }
.si-pill svg { transition: transform var(--t-button) ease-in-out; }
.si-pill:hover svg { transform: translateX(3px); }
.si-pill--ink { background: var(--c-ink); color: var(--c-cream); }
.si-pill--ink:hover { background: #3E2F42; }
/* coral: FILL ONLY, ink text (4.69:1). Allowed on cream/white/ink/dusk only — never on sky bands. */
.si-pill--coral { background: var(--c-coral); color: var(--c-ink); font-weight: 500; }
.si-pill--coral:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(42, 31, 45, 0.22); } /* fill stays coral on purpose: darkening to #E14B26 put ink text at 3.93:1 and failed 1.4.3. Rest and hover are both 4.69:1. Andy, 2026-09-21 */
.si-pill--outline { background: transparent; color: currentColor; border-color: currentColor; }
.si-pill--outline:hover { background: rgba(42, 31, 45, 0.08); }
.si-pill--nav { min-height: 40px; padding: 9px 20px; font-size: var(--fs-nav); line-height: var(--lh-nav); }
.si-link { display: inline-flex; align-items: center; gap: 6px; color: var(--c-ember); font-size: 17px; text-decoration: none; transition: color var(--t-link) var(--ease-apple); }
.si-link:hover { text-decoration: underline; text-underline-offset: 3px; }
/* Columns, not flex-wrap (2026-09-22): whether the pair sits on one row or two is the breakpoint's decision, never
   the rendered width of the two labels. As flex-wrap it flipped in 16 of 90 measured cells — the Spanish pair is
   ~80px wider than the English one, so the two languages stacked at different widths on the same screen, and so did
   two visitors on different operating systems. Same fix as .ra-strip: one row above the site's mobile breakpoint,
   stacked below it. A third child would land on an implicit row — still decided by count and viewport, not text. */
/* justify-items: a grid item stretches to its column by default, and these are links: stretched, the pill and the
   text link would each take the full column as clickable area and focus ring, which a 320px-wide screenshot could
   never show. Content-sized, centred, exactly as the flex row had them. */
/* auto-flow, NOT grid-template-columns: repeat(2, …). One of the four pairs on the homepage holds a single child,
   and a fixed two-column template keeps the empty second column's 15px gap inside the centred width — that pair
   rendered 7.5px left of where it had always sat. Measured, not guessed: the pixels moved 0.027% of the 1440
   baseline, which is under shot-compare's threshold and would have shipped silently. Auto-flow gives one column
   per child, so centring is exact at any count. */
.si-cta-pair { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, max-content); justify-content: center; justify-items: center; align-items: center; gap: 15px; margin-top: var(--s5); }

/* ---------- nav (apple measured: 44px, 80% + saturate(1.8) blur(20px), 1024/22) ---------- */
/* The bar's glass is painted by ::before, not by .si-nav itself (2026-09-28). An element with its own backdrop-filter is a
   backdrop root, and a backdrop-filter inside one samples only what that root paints: the phone menu hangs inside this
   header, so its blur saw the header and not the page, and on an iPhone the hero showed through the panel's 4%, sharp.
   A pseudo-element is no ancestor of the panel, so the panel now blurs the page, and the bar looks as it did. Keep every
   ancestor of .si-menu free of backdrop-filter, filter, opacity below 1, mask, clip-path, mix-blend-mode and will-change
   (the spec's list, and WebKit's computeCanBeBackdropRoot()'s), or the panel goes see-through again. */
.si-nav { position: fixed; inset: 0 0 auto 0; z-index: 50; height: var(--nav-h); }
.si-nav::before { content: ""; position: absolute; inset: 0; z-index: -1; background: rgba(255, 248, 240, 0.8); -webkit-backdrop-filter: saturate(1.8) blur(20px); backdrop-filter: saturate(1.8) blur(20px); }
.si-nav__inner { max-width: var(--w-content); height: 100%; margin: 0 auto; padding: 0 var(--gutter); display: flex; align-items: center; gap: var(--s5); }
.si-wordmark { display: inline-flex; align-items: center; gap: var(--s2); text-decoration: none; font-weight: 600; font-size: 21px; letter-spacing: -0.01em; color: var(--c-ink); }
.si-nav__primary { margin-left: auto; }
.si-nav__primary ul { display: flex; gap: var(--s5); }   /* 24px, was 40 (Andy, 2026-09-25): the nav CTA fits on one line from 777px (EN) / 767px (ES), not 809 / 799 */
.si-nav__primary a, .si-lang__item { font-size: var(--fs-nav); line-height: var(--lh-nav); text-decoration: none; color: rgba(42, 31, 45, 0.8); transition: color var(--t-link) var(--ease-apple); }
.si-nav__primary a:hover, a.si-lang__item:hover { color: var(--c-ink); }
.si-nav__end { display: flex; align-items: center; gap: var(--s4); }
.si-lang { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-nav); }
.si-lang__item { display: inline-flex; align-items: center; justify-content: center; min-width: 24px; min-height: 24px; }
.si-lang__item.is-current { color: var(--c-ink); font-weight: 600; }
.si-lang__sep { color: var(--c-ink-soft); }
.si-nav__toggle { display: none; background: none; border: 0; padding: 6px; color: var(--c-ink); cursor: pointer; }
.si-menu { background: rgba(255, 248, 240, 0.96); -webkit-backdrop-filter: saturate(1.8) blur(20px); backdrop-filter: saturate(1.8) blur(20px); padding: var(--s5) var(--gutter) var(--s6); }
.si-menu ul { display: grid; gap: var(--s4); margin-bottom: var(--s5); }
/* Every menu link but a pill. The CTA is a .si-pill (0,1,0), which a bare `.si-menu a` (0,1,1) outranked, so it rendered at
   28px/600 and wrapped on phones up to 430px (EN) / 401px (ES). The exclusion sits in :where() so the rule keeps its old
   weight against everything else in the menu; the pill keeps its own 17px/500 (Andy, 2026-09-25). The language link is
   excluded the same way: it took this rule's 28px while the current language, a <span>, stayed at 15px (2026-09-28). */
.si-menu a:where(:not(.si-pill, .si-lang__item)) { font-size: 28px; font-weight: 600; text-decoration: none; letter-spacing: -0.02em; }
/* EN|ES in the menu: 17px/600 for both, the CTA pill's size directly above them (Andy, 2026-09-25). (0,2,0), over the
   header's 15px at (0,1,0). Each item keeps the 24x24 minimum of .si-lang__item, which 2.5.8 asks for. */
.si-menu .si-lang__item { font-size: 17px; font-weight: 600; }
.si-menu li { opacity: 0; transform: translateY(-6px); transition: opacity 0.24s, transform 0.24s; }
.si-menu.is-open li { opacity: 1; transform: none; }
.si-menu.is-open li:nth-child(2) { transition-delay: 0.08s; }
.si-menu.is-open li:nth-child(3) { transition-delay: 0.16s; }
.si-menu__cta { display: flex; width: fit-content; margin-bottom: var(--s5); } /* block-level so it takes its own line above the language switch */
/* The open panel fills the screen below the header, not just its links (Andy, 2026-09-29): it ended at its content, and
   the page below it, the hero's own CTA among it, stayed sharp. The links keep their padding, order and sizes; only the
   height changed. dvh, so it ends at the visible bottom with the iOS toolbar shown or hidden. Where the links do not fit
   (a phone on its side), the panel scrolls inside itself. tools/render-audit.mjs holds its bottom to the viewport's. */
.si-menu { height: calc(100dvh - var(--nav-h)); overflow-y: auto; overscroll-behavior: contain; }
html.is-menu-open, html.is-menu-open body { overflow: hidden; touch-action: none; } /* scroll lock: the panel lives in the fixed header, the page must not scroll behind it */

/* ---------- hero (apple tile: full-bleed, content 70px below top) ---------- */
.si-hero { position: relative; overflow: clip; min-height: clamp(560px, 88svh, 860px); padding: calc(var(--nav-h) + var(--s7)) var(--gutter) 0; text-align: center; display: flex; flex-direction: column; align-items: center; }
.si-hero__sky { position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, var(--c-cream) 0%, #FFF0E2 38%, var(--c-peach) 72%, var(--c-apricot) 100%); }
.si-hero__sun { position: absolute; left: 50%; bottom: -34vmin; width: 92vmin; height: 92vmin; margin-left: -46vmin; border-radius: 50%; background: radial-gradient(circle at 50% 50%, rgba(255, 180, 130, 0.95) 0%, rgba(255, 163, 131, 0.75) 32%, rgba(244, 167, 163, 0.35) 56%, rgba(255, 214, 184, 0) 72%); transform: translate3d(0, calc(var(--sink, 0) * 18vh), 0); will-change: transform; }
.si-hero::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 140px; z-index: 3; pointer-events: none; background: linear-gradient(180deg, rgba(255, 248, 240, 0) 0%, var(--c-cream) 92%); }
.si-hero__haze { position: absolute; inset: auto 0 0 0; height: 30%; background: linear-gradient(180deg, rgba(255, 248, 240, 0) 0%, rgba(205, 185, 230, 0.35) 100%); }
.si-hero__content { max-width: var(--w-copy); position: relative; z-index: 2; }
.si-hero__content .si-subhead { margin: var(--s4) auto 0; max-width: var(--w-copy-narrow); color: var(--c-ink-soft); font-weight: 500; }
.si-hero__fan { position: relative; z-index: 1; margin-top: auto; width: min(1100px, 100%); height: clamp(220px, 30vw, 340px); translate: 0 calc(var(--rise, 0) * -40px); }
.si-hero__fan-item { position: absolute; bottom: -8%; width: 44%; }
.si-hero__fan-item--0 { left: 2%; transform: rotate(-5deg); transform-origin: bottom right; }
.si-hero__fan-item--1 { left: 28%; width: 44%; bottom: -2%; z-index: 2; }
.si-hero__fan-item--2 { right: 2%; transform: rotate(5deg); transform-origin: bottom left; }

/* ---------- mockups ---------- */
.mock { position: relative; }
.mock__browser { background: var(--c-white); border-radius: var(--r-browser); overflow: hidden; outline: 1px solid rgba(42, 31, 45, 0.08); }
.mock__bar { display: flex; align-items: center; gap: var(--s3); height: 28px; padding: 0 var(--s3); background: #F4EDE6; }
.mock__dots { display: inline-flex; gap: 5px; }
.mock__dots i { width: 8px; height: 8px; border-radius: 50%; background: #DDD0C6; }
.mock__url { flex: 1; font-size: 10px; line-height: 16px; color: var(--c-ink-soft); background: var(--c-white); border-radius: var(--r-pill); padding: 0 var(--s3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 70%; margin: 0 auto; text-align: center; letter-spacing: 0; }
.mock__screen { position: relative; aspect-ratio: 1440 / 900; overflow: hidden; background: #EEE; }
.mock__screen img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
/* The phone's overhang must never exceed the scene grid's gap, or it sits on top of the right-hand
   copy column and hides the first characters of every line. `-6%` alone does exactly that: it is a
   percentage of the media column, which grows with the viewport, while the gap is a fixed --s6.
   They cross at ~1920px and the damage grows from there — 14.5px of text swallowed at 2560px.
   So clamp the overhang to the gap minus 16px of air. Both halves must stay tied: if --s6 changes,
   this follows it. Never re-state the clamp as a bare pixel value. */
.mock__phone { position: absolute; right: max(-6%, calc(-1 * (var(--s6) - 16px))); bottom: -10%; width: 24%; padding: 5px; border-radius: 26px; background: var(--c-ink); }
.mock__phone .mock__screen { aspect-ratio: 390 / 844; border-radius: 21px; }
/* placeholder wireframes, each in its example's own palette (until screenshots exist) */
.mock__ph { position: absolute; inset: 0; display: grid; grid-template-rows: 14% 38% 1fr; gap: 6%; padding: 6%; }
.mock__ph i { display: block; border-radius: 3px; }
.mock__ph i:nth-child(4) { display: none; }
.mock__ph--pinewhistle-hvac { background: #0E1B2C; } .mock__ph--pinewhistle-hvac i:nth-child(1) { background: #1F3350; } .mock__ph--pinewhistle-hvac i:nth-child(2) { background: #FF6A13; width: 70%; } .mock__ph--pinewhistle-hvac i:nth-child(3) { background: #EAF2FB; }
.mock__ph--mossgrove-landscaping { background: #F6F1E7; } .mock__ph--mossgrove-landscaping i:nth-child(1) { background: #DCE3D1; } .mock__ph--mossgrove-landscaping i:nth-child(2) { background: #3F5B3A; border-radius: 40% 60% 55% 45%; } .mock__ph--mossgrove-landscaping i:nth-child(3) { background: #B7653F; width: 60%; }
.mock__ph--covecrest-insurance { background: #FFFFFF; } .mock__ph--covecrest-insurance i:nth-child(1) { background: #F4F6F7; } .mock__ph--covecrest-insurance i:nth-child(2) { background: #0F766E; width: 45%; } .mock__ph--covecrest-insurance i:nth-child(3) { background: #CCEDEA; }
.mock__ph--glisten-exterior-wash { background: #E8FBFF; } .mock__ph--glisten-exterior-wash i:nth-child(1) { background: #00B8D9; border-radius: 99px; } .mock__ph--glisten-exterior-wash i:nth-child(2) { background: #FFFFFF; border-radius: 12px; } .mock__ph--glisten-exterior-wash i:nth-child(3) { background: #FFD23F; border-radius: 12px; width: 55%; }
.mock__ph--cinderpeak-roofing { background: #1C1C1C; } .mock__ph--cinderpeak-roofing i:nth-child(1) { background: repeating-linear-gradient(-45deg, #FFC82C 0 8px, #1C1C1C 8px 16px); } .mock__ph--cinderpeak-roofing i:nth-child(2) { background: #D9D6D0; } .mock__ph--cinderpeak-roofing i:nth-child(3) { background: #FFC82C; width: 40%; }
.mock__ph--brassbolt-electric { background: #0F0F12; } .mock__ph--brassbolt-electric i:nth-child(1) { background: #1A1A20; border-bottom: 1px solid #C9A45C; } .mock__ph--brassbolt-electric i:nth-child(2) { background: #2A2A31; } .mock__ph--brassbolt-electric i:nth-child(3) { background: #C9A45C; width: 35%; }

/* ---------- sunset stage (cash transitions on native scroll) ---------- */
.si-stage { position: relative; }
.si-stage__intro { max-width: var(--w-copy); margin: 0 auto; padding: var(--s8) var(--gutter) var(--s7); text-align: center; }
.si-stage__intro .si-subhead { margin-top: var(--s4); }
/* Three ways round the stage (2026-09-24). A skip link past it: seven scenes of scroll, and a keyboard walk through every
   scene link, otherwise. Parked off-screen at full size until it takes focus, exactly as .skip is, then an ink pill at
   the top of the section; a plain fragment link, no script. Not clipped to 1px: a 1px box is a 1px target to every tool
   that measures one, and the audit found it that way. */
.si-stage__skip { position: absolute; left: 50%; top: -9999px; z-index: 5; transform: translateX(-50%); padding: var(--s2) var(--s4); border-radius: var(--r-pill); background: var(--c-ink); color: var(--c-cream); font-size: var(--fs-callout); line-height: var(--lh-callout); white-space: nowrap; text-decoration: none; }
.si-stage__skip:focus { top: var(--s5); }
/* The examples as jump links above the cards, wherever the stage is stacked: phones and tablets, windows too short to pin
   it, reduced motion, no script. Where it pins, the counter below says where you are instead. Columns are the viewport's (4, and 2 at 734px and
   below), never the names' width: `.si-stage__index ol` is on the row contract in tools/render-audit.mjs. Each chip
   carries its scene's sky as a dot, which is decoration; the name is ink on white. */
.si-stage__index { max-width: var(--w-copy); margin: 0 auto; padding: 0 var(--gutter) var(--s6); }
.si-stage__index ol { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s2); }
.si-stage__index a { display: flex; align-items: center; gap: var(--s2); min-height: 44px; padding: 0 var(--s4); border: 1px solid rgba(42, 31, 45, 0.15); border-radius: var(--r-pill); background: var(--c-white); color: var(--c-ink); font-size: var(--fs-callout); line-height: var(--lh-callout); font-weight: 600; text-decoration: none; transition: border-color var(--t-button) ease-in-out; }
.si-stage__index a:hover { border-color: var(--c-ink); }
.si-stage__index a::before { content: ""; flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--dot); box-shadow: inset 0 0 0 1px rgba(42, 31, 45, 0.15); }
.si-stage__index a[data-sky="peach"] { --dot: var(--c-peach); } .si-stage__index a[data-sky="apricot"] { --dot: var(--c-apricot); } .si-stage__index a[data-sky="flame"] { --dot: var(--c-flame); } .si-stage__index a[data-sky="rose"] { --dot: var(--c-rose); } .si-stage__index a[data-sky="orchid"] { --dot: var(--c-orchid); } .si-stage__index a[data-sky="lilac"] { --dot: var(--c-lilac); } .si-stage__index a[data-sky="dusk"] { --dot: var(--c-dusk); }
.si-stage__counter { display: none; }
/* default (no JS / reduced motion): stacked scenes, no pinning */
.si-stage__track { position: relative; }
.si-stage__frame { position: relative; }
.si-stage__markers, .si-stage__dots, .si-stage__glow { display: none; }
.si-scene { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr) minmax(0, 1fr); align-items: center; gap: var(--s6); padding: var(--s8) max(4vw, var(--gutter)); color: var(--c-ink); }
.si-scene[data-sky="peach"] { background: var(--c-peach); } .si-scene[data-sky="apricot"] { background: var(--c-apricot); } .si-scene[data-sky="flame"] { background: var(--c-flame); } .si-scene[data-sky="rose"] { background: var(--c-rose); } .si-scene[data-sky="orchid"] { background: var(--c-orchid); } .si-scene[data-sky="lilac"] { background: var(--c-lilac); } .si-scene[data-sky="dusk"] { background: var(--c-dusk); color: var(--c-cream); --c-ring: var(--c-cream); }
.si-scene__meta { font-size: var(--fs-callout); line-height: var(--lh-callout); font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; }
.si-scene__title { font-size: var(--fs-scene); line-height: var(--lh-scene); letter-spacing: var(--tr-scene); font-weight: 600; margin-top: var(--s3); text-wrap: balance; }
.si-scene__style { font-size: var(--fs-sub); line-height: var(--lh-sub); letter-spacing: var(--tr-sub); font-weight: 500; margin-top: var(--s3); color: var(--c-ink-soft); }
.si-scene__style > span { display: inline-block; }   /* trade and style each break as a unit: see styleLine() in home.mjs */
.si-scene[data-text="cream"] .si-scene__style { color: var(--c-lilac); }
.si-scene__desc { font-size: var(--fs-body); line-height: var(--lh-body); margin-bottom: var(--s5); max-width: 34ch; }
.si-scene__media { position: relative; }
.si-scene__media .mock { width: 100%; }

/* pinned mode (JS on, motion allowed) */
.js-motion .si-stage__track { height: calc((var(--scenes, 6) + 0.6) * 100svh); }
.js-motion .si-stage__sticky { position: sticky; top: 0; height: 100svh; overflow: clip; }
.js-motion .si-stage__frame { position: absolute; inset: 0; background-color: var(--sky, var(--c-peach)); transition: background-color var(--t-scene) ease-in-out;
  clip-path: inset(calc(var(--reveal, 0) * (var(--nav-h) + 3vh)) calc(var(--reveal, 0) * 4vw) calc(var(--reveal, 0) * 4vh) round calc(var(--reveal, 0) * var(--r-card))); }
.js-motion .si-stage__frame[data-sky="peach"] { --sky: var(--c-peach); } .js-motion .si-stage__frame[data-sky="apricot"] { --sky: var(--c-apricot); } .js-motion .si-stage__frame[data-sky="flame"] { --sky: var(--c-flame); } .js-motion .si-stage__frame[data-sky="rose"] { --sky: var(--c-rose); } .js-motion .si-stage__frame[data-sky="orchid"] { --sky: var(--c-orchid); } .js-motion .si-stage__frame[data-sky="lilac"] { --sky: var(--c-lilac); } .js-motion .si-stage__frame[data-sky="dusk"] { --sky: var(--c-dusk); }
.js-motion .si-stage__glow { display: block; position: absolute; left: 50%; top: 50%; width: 70vmin; height: 70vmin; margin: -35vmin 0 0 -35vmin; border-radius: 50%; background: radial-gradient(circle, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 65%); pointer-events: none; }
.js-motion .si-stage__frame[data-sky="dusk"] .si-stage__glow { background: radial-gradient(circle, rgba(205, 185, 230, 0.28) 0%, rgba(205, 185, 230, 0) 65%); }
.js-motion .si-scene { position: absolute; inset: 0; background: none !important; padding: calc(var(--nav-h) + 5vh) max(7vw, var(--gutter)) 6vh; pointer-events: none; transition: color var(--t-scene) ease; }
.js-motion .si-scene.is-active { pointer-events: auto; }
.js-motion .si-scene__copy { opacity: 0; transform: translate3d(0, calc(var(--dir, 1) * 24px), 0); transition: opacity var(--t-scene) ease, transform var(--t-scene) ease; }
.js-motion .si-scene__media { opacity: 0; transform: translate3d(0, calc(var(--dir, 1) * 24px), 0) scale(0.98); transition: opacity var(--t-scene) ease, transform var(--t-media) ease-in-out; }
.js-motion .si-scene.is-active .si-scene__copy, .js-motion .si-scene.is-active .si-scene__media { opacity: 1; transform: none; }
.js-motion .si-scene.is-leaving .si-scene__copy, .js-motion .si-scene.is-leaving .si-scene__media { transform: translate3d(0, calc(var(--dir, 1) * -24px), 0); }
.js-motion .si-scene__copy--right { transition-delay: 0.06s; }
.js-motion .si-stage__markers { display: grid; grid-auto-rows: 100svh; position: absolute; inset: 60svh 0 auto 0; pointer-events: none; }
.js-motion .si-stage__dots { display: flex; position: absolute; left: 50%; bottom: calc(5vh + 12px); transform: translateX(-50%); gap: 8px; }
.js-motion .si-stage__dots li { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.25; transition: opacity var(--t-link) var(--ease-apple), width var(--t-link) var(--ease-apple); }
.js-motion .si-stage__dots li.is-active { opacity: 0.9; width: 22px; border-radius: 3px; }
.js-motion .si-stage__frame[data-sky="dusk"] .si-stage__dots { color: var(--c-cream); }
/* The counter: "01 / 07 · Pinewhistle", centred above the dots for as long as the stage is pinned, following the scene
   (readyastro.js copies each scene's data-counter). Its colour follows the scene's at the scene's own 0.6s. */
.js-motion .si-stage__counter { display: block; position: absolute; left: 50%; bottom: calc(5vh + 30px); transform: translateX(-50%); margin: 0; font-size: var(--fs-callout); line-height: var(--lh-callout); font-weight: 600; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; white-space: nowrap; transition: color var(--t-scene) ease; }
.js-motion .si-stage__frame[data-sky="dusk"] .si-stage__counter { color: var(--c-cream); }
.js-motion .si-stage__index { display: none; }
/* Scoped to the dusk scene itself, not every scene inside a dusk frame. Where the stage pins, exactly one
   scene shows, so the loose selector was invisible; where it stacks (below 901px, and short windows) the scenes
   all show and setActive still writes frame.dataset.sky, so reaching the last scene turned the six pastel scenes
   above it cream-on-pastel at 1.23:1 — a whole section of the page unreadable at phone width. */
.js-motion .si-stage__frame[data-sky="dusk"] .si-scene[data-sky="dusk"] { color: var(--c-cream); --c-ring: var(--c-cream); }

/* ---------- tiles (apple: full-bleed, 70px top) ---------- */
.si-tile { padding: var(--s8) var(--gutter); text-align: center; }
.si-tile__content { max-width: var(--w-content); margin: 0 auto; }
.si-tile__content > .si-subhead { max-width: var(--w-copy-narrow); margin: var(--s4) auto 0; }
.si-tile--bilingual { background: var(--c-white); }
.si-tile--process { background: var(--c-cream); }
.si-tile--dusk { background: var(--c-dusk); color: var(--c-cream); --c-ring: var(--c-cream); }
.si-bi { position: relative; height: 150px; margin: var(--s6) auto 0; max-width: 720px; }
.si-bi__pair { position: absolute; inset: 0; display: grid; place-content: center; gap: var(--s2); opacity: 0; transform: translateY(12px); transition: opacity var(--t-scene) ease, transform var(--t-scene) ease; }
.si-bi__pair.is-active { opacity: 1; transform: none; }
.si-bi__en { font-size: 22px; font-weight: 500; color: var(--c-ink-soft); letter-spacing: -0.018em; }
.si-bi__es { font-size: 44px; font-weight: 600; letter-spacing: -0.022em; line-height: 1.1; }
.si-steps { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--s5); margin-top: var(--s7); text-align: left; counter-reset: step; }
.si-step { padding-top: var(--s4); border-top: 2px solid var(--c-ink); }
.si-step__n { font-size: var(--fs-callout); font-weight: 600; color: var(--c-ember); }
.si-step__name { font-size: var(--fs-sub); line-height: var(--lh-sub); letter-spacing: var(--tr-sub); font-weight: 600; margin: var(--s2) 0; }
.si-step__text { font-size: var(--fs-callout); line-height: var(--lh-callout); letter-spacing: var(--tr-callout); color: var(--c-ink-soft); }
.js-motion [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity var(--t-scene) ease, transform var(--t-scene) ease; }
.js-motion [data-reveal].is-in { opacity: 1; transform: none; }
.js-motion .si-steps [data-reveal]:nth-child(2) { transition-delay: 0.08s; } .js-motion .si-steps [data-reveal]:nth-child(3) { transition-delay: 0.16s; } .js-motion .si-steps [data-reveal]:nth-child(4) { transition-delay: 0.24s; } .js-motion .si-steps [data-reveal]:nth-child(5) { transition-delay: 0.32s; }
.si-contact { display: grid; gap: var(--s3); margin: var(--s6) auto 0; max-width: 520px; text-align: left; }
.si-contact__row { display: grid; grid-template-columns: 140px 1fr; gap: var(--s4); align-items: center; padding: var(--s3) 0; border-bottom: 1px solid rgba(205, 185, 230, 0.3); }
.si-contact dt { display: inline-flex; align-items: center; gap: var(--s2); color: var(--c-lilac); font-size: var(--fs-callout); }
.si-contact dd a { color: var(--c-cream); }
.si-tile--dusk .si-link { color: var(--c-cream); }
/* Who answers and how fast (2026-09-24): facts from site.json, worded in i18n, set between the rows and the CTA like a
   sign-off. Lilac on dusk, the subhead's pairing; the name in cream at the dt/dd weight step. */
.si-contact__reply { margin-top: var(--s5); color: var(--c-lilac); }
.si-contact__who { margin-top: var(--s1); color: var(--c-cream); font-weight: 600; }

/* ---------- footer (apple: 12px, 980/22) ---------- */
.si-footer { background: var(--c-ink); color: var(--c-mist); --c-ring: var(--c-cream); font-size: var(--fs-footer); line-height: var(--lh-nav); }
.si-footer__inner { max-width: var(--w-footer); margin: 0 auto; padding: var(--s6) var(--gutter); }
/* Named areas rather than a flex row that wraps at ≤734px (2026-09-22). Which of the three — wordmark, links, language
   — shared a row was decided by how wide the links rendered: 65 of 150 measured cells changed, and English and Spanish
   parted company at 600px on the same screen. Now the breakpoint decides: one row above 734px, stacked below. The
   links keep their own flex row; they are one item here. */
.si-footer__top { display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr) minmax(0, max-content); grid-template-areas: "mark links lang"; align-items: center; justify-items: start; gap: var(--s5); padding-bottom: var(--s4); border-bottom: 1px solid rgba(201, 187, 198, 0.25); }
.si-footer__top .si-wordmark { grid-area: mark; }
.si-footer__top .si-lang { grid-area: lang; }
.si-footer .si-wordmark { color: var(--c-cream); }
.si-footer__links { grid-area: links; display: flex; gap: var(--s5); justify-self: end; }   /* justify-self, not margin-left: auto — the row is a grid now */
/* 24px tall at every width, the 2.5.8 minimum, like the language items beside them (2026-09-24). In one row the links
   also pass on spacing; stacked one per line below 480px they would not, so the box itself carries the minimum. Taller
   than the 16px line, but never the row's tallest item, so no row it sits in changes height above 734px. */
.si-footer__links a { display: inline-flex; align-items: center; min-height: 24px; }
.si-footer a { color: var(--c-mist); text-decoration: none; transition: color var(--t-link) var(--ease-apple); }
.si-footer a:hover { color: var(--c-cream); text-decoration: underline; }
.si-footer .si-lang__item { color: var(--c-mist); } .si-footer .si-lang__item.is-current { color: var(--c-cream); } .si-footer .si-lang__sep { color: var(--c-mist); }
.si-footer__note { margin-top: var(--s4); }
.si-footer__legal { margin-top: var(--s2); }

/* ---------- privacy / 404 ---------- */
.si-prose { max-width: var(--w-copy); margin: 0 auto; padding: calc(var(--nav-h) + var(--s7)) var(--gutter) var(--s8); }
.si-prose h1 { font-size: 48px; line-height: 1.08; letter-spacing: -0.022em; font-weight: 600; margin-bottom: var(--s5); }
.si-prose h2 { font-size: var(--fs-sub); line-height: var(--lh-sub); font-weight: 600; margin: var(--s6) 0 var(--s3); }
.si-prose p, .si-prose li { color: var(--c-ink-soft); margin-bottom: var(--s3); }
.si-prose ul { list-style: disc; padding-left: 1.2em; }
.si-prose :where(a:not(.si-pill)) { color: var(--c-ember); } /* :where keeps pills' own colors winning (audit A2) */
/* 404: English and Spanish at equal weight (2026-09-24). Spanish is the h2 (one h1; the page's lang is en-US) but set
   exactly like the h1, and both blocks end in the same ink pill. (0,2,0), so it outranks .si-prose h1/h2 at (0,1,1). */
.si-404 .si-404__title { font-size: 48px; line-height: 1.08; letter-spacing: -0.022em; font-weight: 600; margin: 0 0 var(--s5); }
.si-404__block + .si-404__block { margin-top: var(--s7); }

/* ---------- responsive (apple breakpoints 1068 / 734) ---------- */
@media (max-width: 1068px) {
  :root { --fs-display: 48px; --fs-scene: 34px; }
  .si-title { font-size: 40px; }
  .si-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* The stage stacks wherever it cannot pin (Andy, 2026-09-29): below 901px wide, as before, and in any window too short for
   the pinned scene to hold the counter below every block it would cross, with 16px to spare. Pinned, a scene's content is
   centred between the header and its bottom padding, the browser mockup grows with the width, and the counter's top sits
   at 95vh − 50px, so the counter clears the mockup by 16px exactly when the height is at least 231.44px + 25.314vw
   (measured to the pixel, Chromium 141 and WebKit 26, 901 to 1920 wide, EN and ES: docs/DECISIONS.md, 2026-09-29, CC 22).
   A phone in landscape stacks; a laptop window does not. This condition is written here once: readyastro.js reads this
   block's media text and pins only where it does not match, so the script never pins a stacked stage, or the reverse.
   Only the pin's own rules are here. Above 900px, stacked is what reduced motion already shows there (three columns, the
   base .si-scene padding); the single column below is the max-width: 900px block's, unchanged. */
@media (max-width: 900px), (max-height: calc(231.44px + 25.314vw)) {
  .js-motion .si-stage__track { height: auto; }
  .js-motion .si-stage__sticky { position: relative; height: auto; overflow: visible; }
  .js-motion .si-stage__frame { position: relative; clip-path: none; background: none; }
  .js-motion .si-stage__markers, .js-motion .si-stage__dots, .js-motion .si-stage__glow { display: none; }
  .js-motion .si-stage__counter { display: none; }
  .js-motion .si-stage__index { display: block; }
  .js-motion .si-scene { position: relative; inset: auto; padding: var(--s8) max(4vw, var(--gutter)); pointer-events: auto; }   /* the base .si-scene padding */
  .js-motion .si-scene[data-sky="peach"] { background: var(--c-peach) !important; } .js-motion .si-scene[data-sky="apricot"] { background: var(--c-apricot) !important; } .js-motion .si-scene[data-sky="flame"] { background: var(--c-flame) !important; } .js-motion .si-scene[data-sky="rose"] { background: var(--c-rose) !important; } .js-motion .si-scene[data-sky="orchid"] { background: var(--c-orchid) !important; } .js-motion .si-scene[data-sky="lilac"] { background: var(--c-lilac) !important; } .js-motion .si-scene[data-sky="dusk"] { background: var(--c-dusk) !important; color: var(--c-cream); --c-ring: var(--c-cream); }
  .js-motion .si-scene__copy, .js-motion .si-scene__media { opacity: 1; transform: none; }
}
@media (max-width: 900px) {
  /* minmax(0, 1fr), never a bare 1fr: bare `1fr` means minmax(auto, 1fr), and `auto` as a track
     minimum is the item's min-content contribution. The mock browser's URL pill is the widest
     thing in a scene, and the Spanish URLs are ~4 characters longer than the English ones, so at
     360px the track grew to 344px inside a 316px content box and pushed the whole page 6px
     sideways. Clamping the minimum to 0 lets the URL do what it was already styled to do — the
     `text-overflow: ellipsis` on .mock__url — instead of widening the document. */
  .js-motion .si-scene, .si-scene { position: relative; inset: auto; grid-template-columns: minmax(0, 1fr); padding: var(--s7) var(--gutter); pointer-events: auto; text-align: left; }
  .si-scene__media { order: 3; margin: var(--s4) 6% var(--s5) 0; }
  .si-scene__copy--right { order: 2; }
}
@media (max-width: 734px) {
  :root { --fs-display: 40px; --fs-scene: 30px; --fs-sub: 19px; --nav-h: 56px; }
  .si-title { font-size: 32px; }
  .si-nav__primary, .si-nav__end > .si-lang, .si-pill--nav { display: none; }
  .si-nav__toggle { display: inline-flex; }
  .si-nav__end { margin-left: auto; }
  .si-hero__fan { height: 190px; }
  .si-hero__fan-item { width: 62%; } .si-hero__fan-item--1 { left: 19%; width: 62%; } .si-hero__fan-item--0 { left: -14%; } .si-hero__fan-item--2 { right: -14%; }
  .si-steps { grid-template-columns: 1fr; }
  .si-bi__es { font-size: 30px; }
  .si-contact__row { grid-template-columns: 1fr; gap: var(--s1); }
  .si-cta-pair { grid-auto-flow: row; }
  .si-stage__index ol { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .si-footer__top { grid-template-columns: minmax(0, 1fr); grid-template-areas: "mark" "links" "lang"; }
  .si-footer__links { justify-self: start; }
}
/* Phones: the footer links go one per line (Andy, 2026-09-24). As a row they squeezed rather than wrapped: at 360px the
   Spanish "Términos de uso" broke onto two lines inside its own box (DECISIONS 2026-09-22). The breakpoint decides, never
   the text: tools/render-audit.mjs holds .si-footer__links to one row at 480px and up and to one link per line below. */
@media (max-width: 479px) {
  .si-footer__links { flex-direction: column; align-items: flex-start; gap: var(--s1); }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01s !important; animation-duration: 0.01s !important; scroll-behavior: auto !important; }
  .si-pill--coral:hover { transform: none; }
}

/* ---------- contact page + the real form (2026-09-21) ---------- */
/* Deliberately NOT .si-contact — that is the dl of contact rows in the homepage's dusk tile, and reusing it
   here would have inherited its cream-on-dusk colours onto a cream page. */
.si-contactpage { max-width: var(--w-prose, 1100px); margin: 0 auto; padding: var(--s7) var(--gutter) var(--s8); }
.si-contactpage__left { display: grid; gap: var(--s6); align-content: start; }
/* The heading takes the column; the lede keeps the measure the whole intro had (Andy, 2026-09-25). The intro was capped at
   22ch (235px), which set "Let's talk about your project" one word per line from 1069px and pushed the Spanish "proyecto"
   past its own box. The cap now applies to the lede alone, at the same 22ch OF THE INTRO'S FONT: a registered <length> is
   computed where it is declared, so the 20px lede inherits 235px, not 22 of its own wider ch, and does not re-wrap. */
@property --si-intro-measure { syntax: '<length>'; inherits: true; initial-value: 0px; }
.si-contactpage__intro { --si-intro-measure: 22ch; }
.si-contactpage__intro .si-lede { max-width: var(--si-intro-measure); }
.si-lede { font-size: var(--fs-lede, 20px); line-height: 1.5; color: var(--c-ink-soft); margin-top: var(--s3); }
.si-contactpage__grid { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr); gap: clamp(24px, 5vw, 72px); align-items: start; }
.si-contactpage__direct h2, .si-contactpage__form h2 { font-size: var(--fs-h3, 24px); margin-bottom: var(--s3); }
.si-contactpage__big { font-size: var(--fs-h3, 24px); line-height: 1.3; margin: 0 0 var(--s2); }
.si-contactpage__big a { color: var(--c-ember); text-decoration: underline; text-underline-offset: 3px; }
.si-contactpage__note { color: var(--c-ink-soft); margin-top: var(--s4); max-width: 34ch; }
.si-contactpage__reply { margin-top: var(--s4); font-weight: 600; }   /* the reply time: the fact, then the note under it as one block */
.si-contactpage__reply + .si-contactpage__note { margin-top: var(--s1); }

.si-form { display: grid; gap: var(--s4); margin-top: var(--s2); }
.si-field { display: grid; gap: 6px; font-size: var(--fs-callout, 15px); font-weight: 500; }
.si-field input, .si-field textarea { font: inherit; font-weight: 400; font-size: 17px; min-height: 48px; padding: 10px 12px;
  border: 1.5px solid var(--c-mist); border-radius: 6px; background: var(--c-white); color: var(--c-ink); }
.si-field textarea { min-height: 120px; resize: vertical; }
.si-field input:focus, .si-field textarea:focus { border-color: var(--c-ink); }
.si-form__hint { color: var(--c-ink-soft); font-size: var(--fs-callout, 15px); margin: 0; }
/* The unwired-endpoint notice. It is a [CONFIRM] mark, so it is already loud; this only gives it room. */
.si-form__unwired { margin: 0; }
/* The honeypot is for bots, so it must be gone for everyone else — including screen readers, which is why
   the label is aria-hidden as well as visually removed. Not display:none: some bots skip hidden fields. */
.si-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.si-hp input { width: 1px; height: 1px; padding: 0; border: 0; }
.si-consent { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s2); align-items: start; font-size: var(--fs-callout, 15px); line-height: 1.45; }
.si-consent input { width: 24px; height: 24px; margin-top: 0; accent-color: var(--c-ink); }   /* 24px is the 2.5.8 minimum, not a style choice */
.si-consent a { color: var(--c-ember); text-decoration: underline; text-underline-offset: 3px; }
.si-form button { justify-self: start; }
.si-thanks .si-contactpage__big { margin-top: var(--s4); }

@media (max-width: 900px) {
  .si-contactpage__grid { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
}
.si-err { display: block; margin-top: 4px; padding-left: 10px; border-left: 3px solid var(--c-ember); color: var(--c-ember); font-size: var(--fs-callout, 15px); font-weight: 600; line-height: 1.4; }
.si-err[hidden] { display: none; }
