/* =================================================================
   SASHA ELLA — v10 stylesheet
   Tightened Inter-primary type system.
   Inter is loaded as a variable font with the optical-size axis
   (14–32) so the same family yields both the text cut (Inter at
   opsz 14–18) for body copy and the display cut (Inter Display at
   opsz 32) for hero and page-title scale. Cormorant Italic is
   reserved for pull-quotes and conversational interaction lines
   only. Spectral is removed entirely (no @import).
   ================================================================= */

@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@1,300&family=Inter:opsz,wght@14..32,200..500&display=swap');

:root {
  /* ---------------- Palette (unchanged from v9 design system) ---- */
  --bone:        #f4f0e3;
  --page-bg:     #ecebe5;
  --hairline:    #b3b58e;
  --eyebrow:     #8a8d62;
  --olive-deep:  #4d502c;
  --ink:         #2a2722;

  /* ---------------- Type families ----------------
     Body, display, UI, anchor lockup — all Inter.
     Cormorant Italic is the *only* surviving display serif.   */
  --font-system:   "Inter", "Söhne", "Neue Haas Grotesk Display",
                   system-ui, sans-serif;
  --font-quote:    "Cormorant Garamond", "Adobe Garamond Premier Pro",
                   Georgia, serif;

  /* ---------------- Type scale ----------------
     Hero + page-title clamp: ceiling at 40px. Rule of the system:
     hero and page-title text MUST wrap to no more than three lines
     on any viewport (1440 / 768 / 375 all verified). Four-line
     hero headlines are a typography violation — shorten the copy
     or lower the ceiling, never accept the wrap. */
  --t-hero:        clamp(36px, 6.4vw, 64px);   /* hero tagline only — ~1.6x display */
  --t-display:     clamp(24px, 3.5vw, 40px);   /* (legacy display measure) */
  --t-h3:          22px;
  --t-body:        17px;
  --t-body-ui:     14px;
  --t-eyebrow:     11px;
  --t-footer:      11px;

  /* Tracking */
  --track-display: -0.005em;
  --track-anchor:  0.28em;
  --track-eyebrow: 0.28em;
  --track-micro:   0.06em;

  /* Line-heights */
  --lh-display:    1.1;
  --lh-prose:      1.7;
  --lh-ui:         1.55;

  /* ---------------- Spacing scale ---------------- */
  --s-1:           8px;
  --s-2:           16px;
  --s-3:           24px;
  --s-4:           32px;
  --s-5:           48px;
  --s-6:           64px;
  --s-7:           96px;
  --s-8:           128px;

  /* Layout */
  --prose-max:     640px;    /* reading column for body prose */
  --display-max:   920px;    /* wider measure for hero + page titles */
  --content-max:   1200px;
  --edge-min:      32px;

  /* Interactive dot */
  --cta-dot-small: 14px;
  --cta-dot-large: 30px;
  --cta-dot-color: var(--olive-deep);
}

/* ================================================================
   BASE
   ================================================================ */
html, body {
  margin: 0;
  padding: 0;
  background: var(--page-bg);
  color: var(--ink);
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body-ui);
  line-height: var(--lh-ui);
  /* Let the browser pull the right optical cut of Inter at every
     size — text cut for body, display cut for hero/title. */
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
::selection { background: var(--olive-deep); color: var(--bone); }

main { display: block; }

/* First-page-load fade-in — single, quiet, 600ms. */
@keyframes pageFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.app {
  animation: pageFadeIn 600ms ease forwards;
}

/* The dot — typographic period mark, sized in em to its host. */
.dot {
  display: inline-block;
  width: 0.13em;
  height: 0.13em;
  border-radius: 50%;
  background: currentColor;
  vertical-align: baseline;
  margin-left: 0.04em;
  margin-bottom: 0.05em;
}

/* Skip link */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--olive-deep);
  color: var(--bone);
  padding: 8px 12px;
  font-family: var(--font-system);
  font-size: 12px;
  letter-spacing: 0.06em;
}
.skip:focus { left: 16px; top: 16px; z-index: 10; }

/* ================================================================
   SITE HEADER — wordmark is now an Inter Medium 500 small-caps
   anchor lockup. No dot. Matches business card back.
   ================================================================ */
.site-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: var(--s-5) var(--edge-min) 0;
  gap: var(--s-4);
}
.wordmark-link {
  text-decoration: none;
  color: inherit;
  display: inline-block;
}
.wordmark {
  font-family: var(--font-system);
  font-weight: 500;
  font-size: 12px;
  line-height: 1;
  letter-spacing: var(--track-anchor);
  text-transform: uppercase;
  color: var(--olive-deep);
  margin: 0;
}

/* Nav — about · contact. Inter Medium small caps, olive-mid.
   Olive-deep underline on active, hover underline on inactive. */
.site-nav {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  font-family: var(--font-system);
  font-weight: 500;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-anchor);
  text-transform: uppercase;
  color: var(--eyebrow);
}
.nav-link {
  color: var(--eyebrow);
  text-decoration: none;
  padding-bottom: 0.25em;
  border-bottom: 0.5px solid transparent;
  transition: border-bottom-color 120ms ease;
}
.nav-link:hover { border-bottom-color: var(--eyebrow); }
.nav-link.is-active { border-bottom-color: var(--olive-deep); }

/* ================================================================
   PAGE-LEVEL TYPE
   ================================================================ */
.page-title {
  font-family: var(--font-system);
  /* Demoted to a quiet wayfinding label \u2014 same small-caps, tracked
     treatment as the "HOW I WORK" eyebrow. A marker, not a headline. */
  font-weight: 500;
  font-size: var(--t-eyebrow);
  line-height: 1.2;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  /* Shared section-label colour token. Every label uses this one
     value — any apparent shade difference between labels comes only
     from the lens focus-mask opacity, never from a different colour. */
  color: var(--eyebrow);
  margin: 0;
  max-width: var(--display-max);
}

.eyebrow {
  font-family: var(--font-system);
  font-weight: 500;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--eyebrow);
  margin: 0 0 var(--s-3) 0;
}

.prose {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body);
  line-height: var(--lh-prose);
  letter-spacing: -0.003em;
  color: var(--ink);
  max-width: var(--prose-max);
  margin: 0 0 var(--s-4) 0;
  text-wrap: pretty;
}

.pull-quote {
  font-family: var(--font-quote);
  font-style: italic;
  font-weight: 300;
  font-size: 28px;
  line-height: 1.35;
  letter-spacing: -0.005em;
  color: var(--olive-deep);
  max-width: var(--prose-max);
  /* Sits close to the prose it punctuates: a modest gap above, and no
     bottom margin so the gap below is governed solely by the section
     rule (it is the last element in About) — no stacked whitespace. */
  margin: 40px 0 0 0;
  text-wrap: pretty;
}

a.inline-link {
  color: var(--olive-deep);
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-underline-offset: 0.18em;
  transition: text-decoration-thickness 120ms ease;
}
a.inline-link:hover { text-decoration-thickness: 1px; }

/* ================================================================
   HOME
   ================================================================ */
.home-main { min-height: 100vh; display: flex; flex-direction: column; }
.home-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--s-6) var(--edge-min);
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
}
.hero-line {
  font-family: var(--font-system);
  /* Quiet editorial display: lighter than body (300), larger, tighter.
     Inter Light-ish (200) at the opsz=32 display cut. */
  font-weight: 200;
  font-variation-settings: "opsz" 32;
  font-size: var(--t-hero);
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--olive-deep);
  /* Display measure \u2014 wider than the prose below. */
  max-width: min(var(--display-max), 42ch);
  text-wrap: balance;
  /* Owns the top of the page \u2014 stands apart from the intro below. */
  margin: 0 0 var(--s-7) 0;
}
.home-intro { max-width: var(--prose-max); }
.home-intro .prose:last-of-type { margin-bottom: 0; }

/* Hero tagline — the positioning line beneath the name. A step down
   from the display name, a step up from body prose: Inter Light at
   reading-display scale, olive-deep, on the wider display measure.
   Sits close under the name, with air before the intro prose. */
.hero-tagline {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: clamp(20px, 2.4vw, 26px);
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--olive-deep);
  max-width: min(var(--display-max), 30ch);
  text-wrap: balance;
  margin: 0 0 var(--s-5) 0;
}
.hero-line + .hero-tagline { margin-top: 0; }
/* The name owns less bottom air when a tagline follows it. */
.section-hero .hero-line { margin-bottom: var(--s-3); }

/* Hero "Start a conversation" — plain pulsing text (no pill, no dot).
   It opens the same engagement overlay as the marginalia dot. The
   slow opacity breath draws the eye; hover stills it and underlines. */
.hero-cta { margin-top: var(--s-5); }
.start-convo {
  display: inline-block;
  font-family: var(--font-system);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: 0.04em;
  color: var(--olive-deep);
  text-decoration: none;
  cursor: pointer;
  animation: convo-pulse 3.2s ease-in-out infinite;
}
.start-convo:hover {
  animation: none;
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-underline-offset: 0.2em;
}
.start-convo:focus-visible { outline: 0.5px solid var(--olive-deep); outline-offset: 4px; }
@keyframes convo-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.5; }
}
@media (prefers-reduced-motion: reduce) {
  .start-convo { animation: none !important; }
}

/* ================================================================
   WHAT I DO — four calm stacked blocks. Each block carries a
   sentence-case Inter Medium heading and two short body paragraphs,
   separated from the block above by a single hairline rule.
   ================================================================ */
.do-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  max-width: var(--prose-max);
}
.do-item {
  border-top: 0.5px solid var(--hairline);
  padding-top: var(--s-4);
}
.do-item h3 {
  font-family: var(--font-system);
  font-weight: 500;
  font-size: 21px;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--olive-deep);
  margin: 0 0 var(--s-2) 0;
}
.do-item p {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body);
  line-height: var(--lh-prose);
  letter-spacing: -0.003em;
  color: var(--ink);
  margin: 0 0 var(--s-2) 0;
  max-width: var(--prose-max);
  text-wrap: pretty;
}
.do-item p:last-child { margin-bottom: 0; }

/* ================================================================
   WHO I WORK WITH — a restrained two-column directory of sectors.
   No bullets, no rules between items: names read top-to-bottom down
   the first column, then the second. A single hairline opens the
   list, matching the Services treatment.
   ================================================================ */
.who-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  display: grid;
  grid-template-columns: max-content max-content;
  grid-template-rows: repeat(4, auto);
  grid-auto-flow: column;
  justify-content: start;
  column-gap: var(--s-6);
  row-gap: 14px;
}
/* CI-style brace that collects the sector list. It hangs in the left
   gutter (where the marginalia dot lives) with its cusp pointing left,
   so the focus dot — anchored to the list's vertical centre — settles
   just to the left of the cusp, level with the middle of the list. */
.who-collect {
  position: relative;
  margin: var(--s-4) 0;
}
.who-brace {
  position: absolute;
  left: -17px;
  top: -2px;
  bottom: -2px;
  width: 12px;
  color: var(--olive-deep);
  pointer-events: none;
  opacity: 0;
  transform: translateX(4px);
  transition: opacity 320ms ease, transform 320ms ease;
}
/* Revealed only when the marginalia dot settles on the sector list. */
.who-list.brace-on + .who-brace,
.who-collect:has(.who-list.brace-on) .who-brace { opacity: 1; transform: none; }
.who-brace svg { display: block; width: 100%; height: 100%; }
.who-list li {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body);
  line-height: 1.5;
  letter-spacing: -0.003em;
  color: var(--olive-deep);
  white-space: nowrap;
}
@media (max-width: 720px) {
  .who-brace { display: none; }
  .who-list {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-auto-flow: row;
  }
  .who-list li { white-space: normal; }
}

/* ================================================================
   ABOUT
   ================================================================ */
.about-main { max-width: calc(var(--prose-max) + 64px); margin: 0 auto; }
.about-top  { padding-top: var(--s-7); }
/* No horizontal padding — the .section rail owns the left edge. */
.about-block + .about-block { margin-top: var(--s-7); }
.about-block.tight + .about-block { margin-top: var(--s-3); }

.how-list { display: flex; flex-direction: column; gap: 40px; margin-top: 0; }
/* Match the label-to-content gap used by .about-block sections so the
   "Approach" marker isn't flush against "Directly". */
.about-block.tight + .how-list { margin-top: var(--s-3); }
.how-item h3 {
  font-family: var(--font-system);
  /* Item-heading tier (matches the Services item headings): sentence-case
     Inter Medium, olive-deep — a step down from the all-caps section
     marker above, restoring the two-tier hierarchy. */
  font-weight: 500;
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--olive-deep);
  margin: 0 0 10px 0;
}
.how-item p {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body);
  line-height: var(--lh-prose);
  color: var(--ink);
  margin: 0;
  max-width: var(--prose-max);
}

.clients-list {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: clamp(12px, 1.5vw, 18px);
  line-height: 1.5;
  letter-spacing: -0.005em;
  color: var(--olive-deep);
  margin: 0;
  /* One line. No wrap. The list reads as a directory, not prose. */
  white-space: nowrap;
}

/* ----------------------------------------------------------------
   Services — a name-left list within the 640px reading width. Each
   service is a two-column row: a fixed ~188px name column and a
   running line of items that wraps in the rest. Rows align on the
   baseline, so the name sits level with the first line of its items.
   One hairline above the list (no per-row rules); the Sectors line
   below follows the same two-column alignment. Each name is a focus-
   dot anchor, so the dot snaps service to service on scroll.
   Below 720px the two columns stack (name above items).
   ---------------------------------------------------------------- */
.services {
  max-width: var(--prose-max);     /* 640px — the body reading measure */
  width: 100%;
}
.svc-list {
  border-top: 0.5px solid var(--hairline);
  padding-top: 28px;
}
.svc-row {
  display: grid;
  grid-template-columns: 188px 1fr;
  column-gap: 28px;
  align-items: baseline;
}
/* Fixed pitch: every service row is at least 64px tall (enough for the
   two-line "Brand & marketing" items), so the three headings sit at an
   even ~64px rhythm regardless of how many lines the items wrap. The
   even spacing also gives each name an equal focus-band window, so the
   dot snaps to all three in turn instead of gliding past the short
   middle row. One-line rows carry open space beneath — that buys the
   even spacing. */
/* Fixed pitch: every service row is at least 92px tall — wider than
   strictly needed for the content, but the extra room gives the focus
   dot a comfortably large "window" per name so it lands and HOLDS on
   each one as you scroll. At the old tight 64px the names were packed
   closer than the dot's snap could resolve, so whichever name you
   scrolled past quickly never settled. */
.svc-list .svc-row { min-height: 116px; }
/* The last row needs no trailing window (the dot's AI stop runs to the
   next section), so collapse it to content height. That makes the gap
   from the last items to the bottom hairline equal the 28px gap from the
   top hairline to the first name — balanced top and bottom. */
.svc-list .svc-row:last-child { min-height: 0; }
.svc-name {
  font-family: var(--font-system);
  font-weight: 500;
  font-size: 16px;
  line-height: 1.6;
  letter-spacing: 0;
  color: var(--olive-deep);
  white-space: nowrap;             /* fits 188px on one line */
}
.svc-items {
  margin: 0;
  font-family: var(--font-system);
  font-weight: 300;
  font-size: 15px;
  line-height: 1.6;
  letter-spacing: 0.01em;
  color: var(--eyebrow);
  text-wrap: pretty;
}
/* Sectors — same two-column alignment, under its own hairline. */
.svc-sectors {
  border-top: 0.5px solid var(--hairline);
  margin-top: 28px;
  padding-top: 28px;
}
.svc-sectors-label {
  font-family: var(--font-system);
  font-weight: 500;
  font-size: 11px;
  line-height: 1.6;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--eyebrow);
  white-space: nowrap;
}
.svc-sectors-values {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: 15px;
  line-height: 1.6;
  letter-spacing: 0.01em;
  color: var(--olive-deep);
}
@media (max-width: 720px) {
  /* Stack name above items; the names stay dot anchors so the dot
     still snaps per service. */
  .svc-row { grid-template-columns: 1fr; column-gap: 0; row-gap: 4px; }
  .svc-name { white-space: normal; }
}

/* ================================================================
   CONTACT
   ================================================================ */
.contact-main {
  max-width: calc(var(--prose-max) + 64px);
  margin: 0 auto;
  padding: var(--s-7) var(--edge-min);
}
.contact-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-4);
}
.contact-line {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body-ui);
  letter-spacing: 0.04em;
  color: var(--eyebrow);
  margin: 0;
}
.contact-line a {
  color: var(--eyebrow);
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-underline-offset: 0.18em;
  transition: text-decoration-thickness 120ms ease, color 120ms ease;
}
.contact-line a:hover {
  color: var(--olive-deep);
  text-decoration-thickness: 1px;
}
.contact-location {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body-ui);
  letter-spacing: 0.06em;
  color: var(--eyebrow);
  margin: 0;
}

/* ================================================================
   FOOTER
   ================================================================ */
.site-footer {
  padding: var(--s-5) var(--edge-min);
  max-width: calc(var(--prose-max) + 64px);
  margin: 0 auto;
}
.site-footer p {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-footer);
  letter-spacing: 0.04em;
  color: var(--eyebrow);
  margin: 0;
}

/* End-of-page footer — an in-flow credential that scrolls in after
   Contact (NOT fixed). Reads as a discreet line, not a titled
   section: a small-caps olive label, the client list in small quiet
   type, then the copyright just beneath. Aligns to the shared rail
   via the .section class on the element. */
.page-footer .eyebrow { margin-bottom: var(--s-3); }
.footer-clients {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body-ui);   /* 14px — small and quiet */
  line-height: var(--lh-ui);
  letter-spacing: 0.01em;
  color: var(--eyebrow);
  margin: 0 0 var(--s-5) 0;
  text-wrap: pretty;
}
.footer-copy {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-footer);    /* 11px */
  letter-spacing: 0.04em;
  color: var(--eyebrow);
  margin: 0;
}

/* ================================================================
   LONG-SCROLL ARCHITECTURE — single-page document.

   All content lives inside a fixed-viewport .lens scroll container
   with a top/bottom mask fade. Header, footer and the marginalia
   dot sit OUTSIDE the lens so they stay at full opacity through
   the entire scroll.

   This section only kicks in on pages that have a .lens element
   (the merged single-page Sasha Ella.html). The legacy three-page
   files are unaffected.
   ================================================================ */

/* Reading-lens scroll container. The top/bottom fade is painted by
   .lens-fade overlay layers (see below) rather than a mask-image
   on this element \u2014 a mask on a scrolling fixed container forces
   the browser to recomposite every frame, which jitters under load.
   Overlay layers are composited once and let the scroller stay
   natively smooth. */
.lens {
  position: fixed;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scroll-behavior: smooth;
  /* Hide the scrollbar visually but keep scrolling functional. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Promote to its own compositor layer so scroll repaints stay
     cheap and the fade overlays composite cleanly on top. */
  will-change: scroll-position;
  -webkit-overflow-scrolling: touch;
}
.lens::-webkit-scrollbar { width: 0; height: 0; }

/* Fade overlays \u2014 fixed strips at the top and bottom of the
   viewport, sitting above the .lens but below the header/footer.
   Pointer-events: none so they never intercept clicks or wheel
   events. Background gradient blends to --page-bg so the effect
   reads identically to the previous mask. */
.lens-fade {
  position: fixed;
  left: 0;
  right: 0;
  pointer-events: none;
  z-index: 30;
}
.lens-fade--top {
  top: 0;
  height: 22vh;
  background: linear-gradient(
    to bottom,
    var(--page-bg) 0%,
    var(--page-bg) 30%,
    rgba(236, 235, 229, 0) 100%
  );
}
.lens-fade--bottom {
  bottom: 0;
  /* Bottom fade brought up to sit closer to the dot. Spans 54%→100% of
     the viewport: content is clear above 54%, fades 54%→78%, solid
     page-bg below 78%. (Top fade is left untouched.)
     Live-tune: raise BOTH the height and the solid-break stop together
     to tighten further (e.g. 42vh + 52%), lower them for more runway.
     Keep the slight downward bias — don't make it symmetric with top. */
  height: 38vh;
  background: linear-gradient(
    to top,
    var(--page-bg) 0%,
    var(--page-bg) 32%,
    rgba(236, 235, 229, 0) 100%
  );
}

/* Header and footer sit on top of the lens with full opacity.
   The mask fades content to transparent behind them, so the page
   background is what shows through \u2014 not faded content. */
body:has(.lens) .site-header {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 50;
  margin: 0;
  background: transparent;
  max-width: none;
}
body:has(.lens) .site-header { top: 0; padding-top: var(--s-5); }

/* Inner content flow \u2014 the only thing that actually scrolls.
   Top and bottom padding give the first content (hero) room to sit
   in the focus band on initial load, and the last content (contact)
   room to reach it on full scroll. */
.page-flow {
  /* Top padding trimmed ~27% (30vh → 22vh) to lift the hero higher
     on the opening screen while keeping generous breathing room.
     Bottom padding unchanged. */
  padding-top: 22vh;
  padding-bottom: 30vh;
  max-width: var(--content-max);
  margin: 0 auto;
}

/* Sections \u2014 major content groups. Each centres on its own
   measure: hero uses display-max (920), about/contact use prose-max
   + padding. */
.section {
  /* One shared rail for every section. box-sizing: border-box keeps
     the padding INSIDE the max-width, so the content-left edge is
     identical on all sections no matter which element pays for the
     padding. This is the single source of horizontal alignment —
     individual sections must not re-declare width or side padding. */
  box-sizing: border-box;
  margin-left: auto;
  margin-right: auto;
  max-width: calc(var(--display-max) + 2 * var(--edge-min));
  padding-left: var(--edge-min);
  padding-right: var(--edge-min);
}
/* Major section break — the single named gap between sections. Tightened
   to 72px: still clearly larger than within-section gaps (≤48px) so the
   structure reads, but with noticeably less dead air in the focus band. */
.section + .section { margin-top: 72px; }
.section-contact { text-align: left; }

/* Floating dividers are banned system-wide. A section change is
   marked by its heading/label, never by a horizontal rule. Any
   stray .section-break left in markup renders to nothing. */
.section-break { display: none; }

/* In the long-scroll context, the contact stack reverts to a
   left-aligned column inside the prose measure (it was centred on
   the standalone contact page). */
body:has(.lens) .contact-stack {
  align-items: flex-start;
  text-align: left;
  max-width: var(--prose-max);
  margin: 0;
  /* Uniform label-to-content / item spacing down the contact block.
     This is the gap from "Contact." to the tagline as well. */
  gap: var(--s-3);
}
body:has(.lens) .contact-stack .page-title { text-align: left; }
body:has(.lens) .contact-invite {
  font-family: var(--font-system);
  font-weight: 300;
  font-size: var(--t-body);
  line-height: var(--lh-prose);
  color: var(--ink);
  max-width: var(--prose-max);
  margin: 0;
  text-align: left;
}

/* ================================================================
   MARGINALIA DOT \u2014 paragraph-anchored, content-column relative.

   The dot is position: fixed in CSS but its top/left are written
   by JS each frame so it follows the active paragraph's vertical
   centre AND the prose column's left edge (anchor.rect.left - 40px,
   clamped to a 16px minimum so it remains on-screen on narrow
   viewports). Anchor handoff is an eased 300ms transition.
   ================================================================ */
.dot-marginalia {
  position: fixed;
  top: 50vh;           /* placeholder; JS overrides on first tick */
  left: 16px;          /* placeholder; JS overrides on first tick */
  /* JS computes the visual centre of the active anchor and writes
     it to `top`. Translate the dot up by half its own height so the
     dot's centre — not its top edge — lands on that line. */
  transform: translateY(-50%);
  z-index: 70;
  line-height: 0;
  opacity: 0;          /* JS reveals after first positioning pass */
  transition: opacity 240ms ease;
  will-change: top, left;
}
.dot-marginalia.is-ready { opacity: 1; }
/* The dot follows its name 1:1 (no top/left transition) while scrolling.
   The ease is switched on ONLY during a handoff to the next name — set by
   JS via .is-handoff — so the glide reads as a deliberate snap between
   names without lagging behind the scroll the rest of the time. */
.dot-marginalia.is-handoff {
  transition: top 230ms cubic-bezier(0.4, 0, 0.2, 1),
              left 230ms cubic-bezier(0.4, 0, 0.2, 1),
              opacity 240ms ease;
}

.dot-marginalia__btn {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  padding: 12px;
  margin: -12px;
  cursor: pointer;
  display: inline-block;
  line-height: 0;
}
.dot-marginalia__btn:focus-visible {
  outline: 0.5px solid var(--olive-deep);
  outline-offset: 4px;
}
.dot-marginalia__mark {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--olive-deep);
  /* No animation, no pulse, no glow — by design. */
}

/* Mobile: shrink the dot slightly so it doesn't crowd the narrow column. */
@media (max-width: 720px) {
  .dot-marginalia__mark {
    width: 12px;
    height: 12px;
  }
}

/* ================================================================
   OVERLAY — the focused interaction surface.
   No card, no rounded corners, no X-close. Blurred page beneath,
   floating typography on top. The blur is the focus mechanism.
   ================================================================ */
.dot-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  background: rgba(236, 235, 229, 0.4);  /* ~40% bone dim */
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 200ms ease;
}
.dot-overlay.is-open {
  display: block;
  opacity: 1;
}
.dot-overlay__content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(440px, 86vw);
  text-align: center;
}
.dot-overlay__line {
  font-family: var(--font-quote);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(28px, 3vw, 36px);
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--olive-deep);
  margin: 0 0 var(--s-4);
}
@media (max-width: 720px) {
  /* Scale the invitation type down so it breathes inside a phone column. */
  .dot-overlay__line { font-size: clamp(20px, 5vw, 28px); }
  .dot-overlay__content { width: min(360px, 86vw); }
}
.dot-overlay__email {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 0.5px solid var(--eyebrow);
  border-radius: 0;
  padding: 10px 0;
  font-family: var(--font-system);
  font-weight: 300;
  font-size: 16px;
  letter-spacing: 0.04em;
  color: var(--olive-deep);
  text-align: left;
  outline: none;
  transition: border-bottom-color 160ms ease;
}
/* Stacked enquiry fields — name · surname · email · phone · message.
   A small gap separates each hairline-underlined field. */
.dot-overlay__email-wrap { display: flex; flex-direction: column; }
.dot-overlay__field + .dot-overlay__field { margin-top: 6px; }
/* With five fields the block is taller. Only on genuinely short screens
   (landscape phones, small windows) cap it to the viewport and let it
   scroll internally so the send dot never clips — taller screens show
   the whole form with no scrollbar at all. overflow-x stays hidden so a
   vertical bar can't push the 100%-wide inputs into a horizontal one. */
@media (max-height: 640px) {
  .dot-overlay__content {
    max-height: calc(100dvh - 32px);
    overflow-y: auto;
    overflow-x: hidden;
  }
}
.dot-overlay__email::placeholder {
  color: var(--hairline);
  font-style: italic;
}
.dot-overlay__email:focus { border-bottom-color: var(--olive-deep); }
.dot-overlay__email[aria-invalid="true"] { border-bottom-color: var(--olive-deep); }

/* Sent state — email field hidden, the line itself swaps to the ack. */
.dot-overlay.is-sent .dot-overlay__email-wrap { display: none; }

/* Caption — appears under the email field when the value validates.
   Eyebrow-style: Inter Medium small caps, olive-mid, tracked.
   Directs the visitor to click the (marginalia) dot to send. */
.dot-overlay__hint {
  font-family: var(--font-system);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--eyebrow);
  margin: var(--s-3) 0 0;
  opacity: 0;
  transition: opacity 200ms ease;
  pointer-events: none;
}
.dot-overlay.is-valid .dot-overlay__hint { opacity: 1; }

/* ================================================================
   PRIMING + REVEAL STATES — first-time-visitor entry.

   Body starts in .priming. Everything except the dot is at opacity 0
   (still in the DOM and SEO-indexable). The dot is centred, larger
   (26px), and breathing with a slow pulse. A click reveals the
   page: chrome and content fade up over 600ms while the dot
   transitions from centre to its marginalia anchor.

   Return visitors (localStorage flag) and direct-link visitors
   (URL hash) skip this state entirely \u2014 the inline script in
   the document body removes .priming before paint.
   ================================================================ */

/* Priming \u2014 hide chrome and content. The dot is the only thing
   visible. Pointer-events disabled on the hidden chrome so the dot
   gets every click. */
body.priming .site-header,
body.priming .lens,
body.priming .site-footer {
  opacity: 0;
  pointer-events: none;
}

/* Revealed \u2014 chrome and content fade up over 600ms ease. */
body.revealed .site-header,
body.revealed .lens,
body.revealed .site-footer {
  opacity: 1;
  transition: opacity 600ms ease;
}

/* Priming dot \u2014 centred, larger, pulsing.
   Both priming and marginalia use the same translateY(-50%) +
   left-edge convention (set in the base .dot-marginalia rule), so
   the reveal animates as a clean two-axis lerp on top/left with no
   transform jump. The half-width offset (calc(50vw - 13px)) puts
   the dot's visual centre at 50vw. */
body.priming .dot-marginalia {
  top: 58vh;
  left: calc(50vw - 13px);
  opacity: 1;
}
body.priming .dot-marginalia__mark {
  width: 26px;
  height: 26px;
  animation: dot-pulse 3.2s ease-in-out infinite;
  /* Origin centre so scale breathes around the dot's middle. */
  transform-origin: center;
}
body.priming .dot-marginalia__btn:hover .dot-marginalia__mark {
  animation: none;
  transform: scale(1.12);
  transition: transform 240ms ease;
}

@keyframes dot-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.07); }
}

/* Revealing \u2014 the in-flight 600ms transition window. JS writes
   the marginalia target to dot.style.top/left; this transition
   animates the dot from its priming centre to that target. */
body.revealing .dot-marginalia {
  transition: top 600ms cubic-bezier(0.4, 0, 0.2, 1),
              left 600ms cubic-bezier(0.4, 0, 0.2, 1);
}
body.revealing .dot-marginalia__mark {
  transition: width 600ms ease, height 600ms ease;
  width: 14px;
  height: 14px;
  animation: none;
}
@media (max-width: 720px) {
  body.revealing .dot-marginalia__mark { width: 12px; height: 12px; }
}

/* Revealed (post-transition) \u2014 pulse is permanently off. */
body.revealed:not(.revealing) .dot-marginalia__mark {
  animation: none;
}
/* The focus dot is also the engagement trigger, so it breathes like
   every interactive dot. The pulse goes on .dot-marginalia (the element
   carrying translateY(-50%)); its keyframes re-state that translateY so
   the dot never drops out of vertical centre mid-breath. Same timing
   and curve as the priming dot. */
body.revealed:not(.revealing) .dot-marginalia {
  animation: dot-marginalia-breath 3.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
/* Hover — attention has arrived: stop calling for it. Pause the breath
   and settle to a slightly larger static scale, keeping translateY. */
body.revealed:not(.revealing) .dot-marginalia:hover {
  animation: none;
  transform: translateY(-50%) scale(1.15);
  transition: transform 240ms ease;
}

/* The breath — scale around centre while preserving the -50% Y offset
   that keeps the dot on its reading band. */
@keyframes dot-marginalia-breath {
  0%, 100% { transform: translateY(-50%) scale(1); }
  50%      { transform: translateY(-50%) scale(1.07); }
}

/* ================================================================
   prefers-reduced-motion — collapse all motion to instant.
   ================================================================ */
@media (prefers-reduced-motion: reduce) {
  .app { animation: none; }
  .dot-marginalia,
  .dot-marginalia.is-ready,
  .dot-marginalia__mark,
  .dot-overlay,
  .dot-overlay__email,
  .nav-link,
  a.inline-link,
  .contact-line a {
    transition: none !important;
  }
  /* Priming pulse off; reveal is instant (no fade, no dot motion). */
  body.priming .dot-marginalia__mark { animation: none !important; }
  /* Focus-dot breath off too — size + cursor:pointer still mark it as
     interactive. Stays static at translateY(-50%). */
  body.revealed .dot-marginalia { animation: none !important; }
  body.revealed .site-header,
  body.revealed .lens,
  body.revealed .site-footer { transition: none !important; }
  body.revealing .dot-marginalia,
  body.revealing .dot-marginalia__mark { transition: none !important; }
  .lens { scroll-behavior: auto; }
}

/* ================================================================
   Mobile
   ================================================================ */
@media (max-width: 720px) {
  .site-header { padding: var(--s-4) var(--s-3) 0; }
  /* Header was overflowing on narrow phones: the wordmark plus four nav
     links on a single row is wider than the column. Stack them — wordmark
     on top, nav flush-left beneath it (the brand's flush-left lockup) —
     and let the nav wrap as a final safety. No hamburger (out of system). */
  .site-header {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
  }
  .wordmark { letter-spacing: 0.16em; }
  .site-nav {
    flex-wrap: wrap;
    gap: 0.3em 0.5em;
    font-size: 10px;
    letter-spacing: 0.08em;
  }

  /* Hero tagline carries a fixed desktop width (600px) set inline; on a
     phone that overflows the rail and clips. Let it wrap to the column. */
  .hero-tagline { max-width: 100%; width: auto !important; }

  /* Contact line: the non-breaking spaces around the middle dots glued
     the whole run together, so the only break point was inside the phone
     number. Stack the three items instead — each on its own line, dots
     hidden — which reads cleanly in a narrow column. */
  .contact-line { line-height: 1.5; }
  .contact-line a { display: block; }
  .contact-line .dotsep { display: none; }

  .home-body { padding: var(--s-5) var(--s-3); }
  .contact-main { padding: var(--s-6) var(--s-3); }
  .site-footer { padding: var(--s-4) var(--s-3); }
  .about-block + .about-block { margin-top: var(--s-6); }

  /* Long-scroll: tighter rail padding and section break on phones.
     Padding stays on .section so every rail still aligns. Left padding is
     wider than right so the marginalia dot (clamped to 16px from the edge)
     keeps a clean gutter instead of sinking into the first words. */
  .section { padding-left: 40px; padding-right: var(--s-3); }
  .section + .section { margin-top: var(--s-6); }
  body:has(.lens) .site-header { padding: var(--s-4) var(--s-3) 0; }
  body:has(.lens) .site-footer { padding: var(--s-3) var(--s-3); }
  /* The priming dot is smaller on phones too; recentre to match. */
  body.priming .dot-marginalia { left: calc(50vw - 12px); }
  body.priming .dot-marginalia__mark { width: 24px; height: 24px; }
}
