/* TheLoCas 3.0.

   The visual system is concept/TheLoCas_3.0_Homepage_Mockup.html — its type
   scale, its components, its layout rhythm. The colour is the brand's, not the
   mockup's: the mockup proposes a cool grey ink and a terracotta accent, while
   the logo artwork, the favicon and the client's own banner are dark green
   #15251F with gold #AF8A4F (assets/SPEC.md §2). Token *roles* come from the
   mockup, token *values* from the brand, so the mark never sits on a page that
   disagrees with it.

     mockup            here
     --paper           --paper       #FAF6F2  brand cream
     --paper-deep      --paper-deep  #F0EAE0
     --ink             --ink         #15251F  brand green
     --ink-soft        --ink-soft    #4A5852
     --clay            --accent      #AF8A4F  brand gold
     --clay-soft       --accent-soft #C8A878
     --sage            --sage
     --line            --line

   Gold is lighter than the mockup's clay, so it does not reach 4.5:1 on cream
   at text sizes (3.7:1). --accent carries it where the size makes 3:1 enough —
   display type, rules, the 4px nav dot — and --accent-text is the darkened
   gold used for anything read at body size. Dark mode swaps the two. */

:root {
  /* ---- brand ---- */
  --ink: #15251F;
  --cream: #FAF6F2;
  --gold: #AF8A4F;

  /* ---- semantic, light ---- */
  --bg: var(--cream);
  --fg: var(--ink);
  --paper-deep: #F0EAE0;
  --ink-soft: #4A5852;
  --accent: var(--gold);
  --accent-text: #8A6A34;   /* gold, darkened until it clears 4.5:1 on cream */
  --accent-soft: #C8A878;
  --sage: #5D684A;          /* eyebrow; the mockup's #6E7A57 is 4.3:1 on cream */
  --line: #DDD4C8;

  --muted: var(--ink-soft);
  --hairline: var(--line);
  --chip-bg: color-mix(in srgb, var(--fg) 4%, transparent);
  --veil: .65;   /* strength of the background photograph */

  /* ---- type ----
     Fraunces for display, Work Sans for everything else, per the mockup's
     style guide. Both load from Google Fonts in the page head; the fallbacks
     are chosen to hold the same rough proportions if that request is blocked,
     so the layout does not reflow into something unrecognisable. */
  --font-display: 'Fraunces', 'Iowan Old Style', 'Palatino Linotype', Palatino,
                  Georgia, 'Times New Roman', serif;
  --font-body: 'Work Sans', 'Public Sans', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Helvetica, Arial, sans-serif;

  /* ---- measure ----
     The mockup gives 880px for content and 1040px for the nav/footer bar. Prose
     gets a narrower one: 880px of Work Sans at 17px runs to ~95 characters, and
     About's story is long-form reading rather than a landing block. */
  --frame: 65rem;     /* 1040px — header and footer */
  --wide: 55rem;      /* 880px  — component sections */
  --measure: 42rem;   /* prose */

  /* The header is fixed, so it covers the top of whatever an in-page link
     lands on. This is its height — .9rem padding either side of a 2em lockup,
     plus the hairline — and it is what scroll-margin-top subtracts. */
  --header-h: 4rem;

  /* Halo behind the landing lockup, so it is not competing with the forest
     texture directly behind it. Coloured with --bg, which makes it a light
     "anti-shadow" on cream and a true shadow on ink from one declaration —
     drop-shadow follows the artwork's alpha, so it hugs the letterforms
     rather than boxing them. Two passes: a tight one for edge separation, a
     wide one to lift the field. */
  --hero-halo: drop-shadow(0 0 10px var(--bg)) drop-shadow(0 0 30px var(--bg));

  /* One pair per theme. Both grades carry the photograph's own hues; the dark
     one is lifted rather than inverted, so it reads as a moonlit valley instead
     of a negative. Only the applicable rule's image is ever fetched. */
  --veil-light: url("images/valley.webp");
  --veil-dark:  url("images/valley-dark.webp");
  --veil-image: var(--veil-light);
  /* Alternatives live in assets/backgrounds/ and are not deployed; to use one,
     copy it into images/ and point --veil-light/--veil-dark at it. See README. */

  /* Placeholders, inlined so they cost no request — written by
     assets/tools/make-veil.sh, do not edit by hand. */
  --veil-lqip-light: url("data:image/webp;base64,UklGRo4BAABXRUJQVlA4WAoAAAAQAAAAJwAAFgAAQUxQSIMAAAARHyAkIFPmGKKLiCCH69i21SRYCTf1hA70POQ/Wg1eAZF3sA71vodVENF/hW3bNspe7R/B30R6HpHAMwha+kFrUMrkM5MydIp+lJfhxIlbWlGWKEkMFGNZAApJ41PA6NlB4wISBHEO7HK4Yuh+YEnuDZDHJfW4hKXl8UaysCxd9BleCQBWUDgg5AAAAPAGAJ0BKigAFwA+qUKdSiYjoqGquA34wBUJYwCzgd0YQto1lDdccnTd5umFpoyh1Nw/Hk7sL5X+fcsDNnGYoUAA3gGsl3ab7X/tJFrMIqnpvGTGk/eUDUkUNMYRvVerlL0ItsBSSAUUtp9XYk18th85144DxAROsmP6XiKZkDD77x3A2/qaI8W8THF7Z4Cp49z9WgcjejCy1vH2fr06qSO2ML8WfCLQxjWVmhO3G4yGcfNe0LUkI4Yc3XXTBUzF9B2yuItEoxtQsSmzU8V/OnrKm2VmGTU3en9c3BN6zpz1xceAAA==");
  --veil-lqip-dark:  url("data:image/webp;base64,UklGRrwBAABXRUJQVlA4WAoAAAAQAAAAJwAAFgAAQUxQSIMAAAARHyAkIFPmGKKLiCCH69i21SRYCTf1hA70POQ/Wg1eAZF3sA71vodVENF/hW3bNspe7R/B30R6HpHAMwha+kFrUMrkM5MydIp+lJfhxIlbWlGWKEkMFGNZAApJ41PA6NlB4wISBHEO7HK4Yuh+YEnuDZDHJfW4hKXl8UaysCxd9BleCQBWUDggEgEAAFAHAJ0BKigAFwA+pUSZSSYkIiEwGq1QwBSJYwDGvAoZ6xv/KucmNchd4JISXL3YhZJenLv+6aKvDHE7y7W27RwEklIA/vcsl7p8ozXkFOs+f9kmLI3RqFBMsJC/ZZcmlKW3nf28dAk4gbFcw+ZkhwkcQjGYmFjmex0nXE2996qxzCZaSuKGcX6IClr/k30XtgZEAsTdIdlJyevc/q0lpM8zXDAVCduYwrSQA+leGy1prnH+HLOhE8aNnw1wb+ghQf/Q8Xru7677X+zqlHJ6GqeVvreqeSolJVRWWnrqUurLe1tklUQj6gjVBtF9joFZTUttZnBEoOBIq82PGSrqgymnkeuI4ETHX5mSNG3v2KIAAAA=");
  --veil-lqip: var(--veil-lqip-light);
}

/* Dark is a re-grade, not an inversion: gold and sage both step *up* so they
   stay legible on the green, and the accent pair swaps roles — the light gold
   that was reserved for display type is now the readable one. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: var(--ink);
    --fg: var(--cream);
    --paper-deep: #1B2E27;
    --ink-soft: #B4C0BA;
    --accent: var(--accent-soft);
    --accent-text: var(--accent-soft);
    --sage: #9BA882;
    --line: color-mix(in srgb, var(--cream) 18%, transparent);
    --veil: .5;
    --veil-image: var(--veil-dark);
    --veil-lqip: var(--veil-lqip-dark);
  }
}
/* Explicit choice wins in both directions. */
:root[data-theme="dark"] {
  --bg: var(--ink); --fg: var(--cream);
  --paper-deep: #1B2E27; --ink-soft: #B4C0BA;
  --accent: var(--accent-soft); --accent-text: var(--accent-soft);
  --sage: #9BA882; --line: color-mix(in srgb, var(--cream) 18%, transparent);
  --veil: .5; --veil-image: var(--veil-dark); --veil-lqip: var(--veil-lqip-dark);
}
:root[data-theme="light"] {
  --bg: var(--cream); --fg: var(--ink);
  --paper-deep: #F0EAE0; --ink-soft: #4A5852;
  --accent: var(--gold); --accent-text: #8A6A34;
  --sage: #5D684A; --line: #DDD4C8;
  --veil: .65; --veil-image: var(--veil-light); --veil-lqip: var(--veil-lqip-light);
}

/* Resolution switching, the CSS counterpart of srcset. app.js preloads with the
   same 1x/2x candidates, so its request is a cache hit, not a second download. */
@supports (background-image: image-set(url("i.webp") 1x)) {
  :root {
    --veil-light: image-set(url("images/valley.webp") 1x,
                            url("images/valley@2x.webp") 2x);
    --veil-dark:  image-set(url("images/valley-dark.webp") 1x,
                            url("images/valley-dark@2x.webp") 2x);
  }
}

/* ---- page transitions ------------------------------------------------
   Cross-document view transitions, so moving between pages is a crossfade
   rather than a cut. Browsers without support navigate exactly as before. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: page-out .3s cubic-bezier(.4, 0, .6, 1) both; }
  ::view-transition-new(root) { animation: page-in .42s cubic-bezier(.2, 0, .2, 1) both; }

  /* Arrival fade where view transitions are unavailable. The class is set by
     the pre-paint bootstrap, so this never double-fades.

     Deliberately no fill-mode: with 'both' the page holds the keyframe's
     opacity 0 whenever the animation does not actually run, which fails closed
     on an invisible page. Without it the element simply sits at its normal
     style and the animation is pure decoration. */
  html:not(.vt) body { animation: page-in .42s cubic-bezier(.2, 0, .2, 1); }

  /* Departure fade, same case: applied just before navigating. */
  html.leaving body { opacity: 0; transition: opacity .28s ease; }
}

@keyframes page-out { to   { opacity: 0; transform: translateY(-1.2vh); } }
@keyframes page-in  { from { opacity: 0; transform: translateY(1.6vh); } }

/* Motion is the point here, so honour a request for less of it completely. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
  html { scroll-behavior: auto; }
}

* { box-sizing: border-box; }

html {
  /* The cover's cue is an in-page anchor now, so the jump wants easing. */
  scroll-behavior: smooth;
  min-height: 100%;
}

/* Anything an in-page link can land on keeps clear of the fixed header, plus a
   little air so the target does not start flush against it. Without this the
   cover's Enter cue scrolls a header's height too far and the block it aims at
   begins underneath the nav. :where() keeps the specificity at zero, so any
   rule below can still override it. */
:where([id]) { scroll-margin-top: calc(var(--header-h) + 1.5rem); }

body {
  margin: 0;
  /* room for iOS home indicators */
  padding: 0 0 max(2rem, env(safe-area-inset-bottom));
  min-height: 100%;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  transition: background-color .25s ease, color .25s ease;
}

/* ---- background ------------------------------------------------------
   The valley, in its own colours, with a tone curve baked into the file's
   ALPHA. Bright ground — sky, mist, sunlit granite — is fully transparent and
   reads as the page background exactly; dark ground keeps its colour and its
   weight. Carrying the alpha in the image rather than as a separate CSS mask
   is what allows colour here at all: a mask can only ever be filled with one
   flat paint, which is what made the earlier version monochrome.

   Two layers so the load is progressive: the inlined placeholder paints on the
   first frame, and the photograph crossfades over it once decoded. See README. */
body::before,
body::after {
  content: "";
  position: fixed;          /* keeps them out of the document flow */
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-size: cover;
  /* Centred slightly high: a bottom anchor crops the ridgelines away on a
     wide viewport, and the sky costs nothing to keep, being empty. */
  background-position: 50% 42%;
  background-repeat: no-repeat;
  transition: opacity .5s ease;
}

body::before { background-image: var(--veil-lqip);  opacity: 0; }
body::after  { background-image: var(--veil-image); opacity: var(--veil); }

/* While the photograph is in flight, show the placeholder instead. The class is
   set by the pre-paint bootstrap and removed by app.js on load, so with no
   JavaScript at all the photograph is simply shown directly. */
:root.veil-loading body::before { opacity: var(--veil); }
:root.veil-loading body::after  { opacity: 0; }

/* Behind body text the photograph has to step back — it is at .65/.5 for the
   landing lockup, which is far too much to read over. On a page with no cover
   that is simply the whole page; on the homepage it now has to be both, so it
   fades on the same 'scrolled' class that swaps the two lockups. */
body:not(.has-hero) { --veil: .1; }
:root.scrolled body.has-hero { --veil: .1; }

/* It is decoration, and it lowers contrast — drop it when more is asked for. */
@media (prefers-contrast: more) {
  body::before, body::after { display: none; }
}

/* ---- type ------------------------------------------------------------
   Fraunces for H1/H2/H3 only; body copy never goes below H3 into the serif. */

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -.01em;
  line-height: 1.15;
  margin: 0;
}

/* The accent phrase — the brand's single recurring flourish, written as
   *asterisks* in content/ and used one to three words at a time. Only in
   display type: an <em> inside body copy stays ordinary italic. */
/* A block headline introduces the copy under it, so it needs the gap an <h1>
   at the top of a page does not. */
.block .block-headline { margin-bottom: .5em; }

.headline em,
.page-title em,
.block-headline em,
.statement em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  color: var(--accent);
}

.eyebrow {
  font-family: var(--font-body);
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--sage);
  margin: 0 0 1.1rem;
}

a { color: inherit; }

/* ---- buttons ---------------------------------------------------------
   Sharp corners, not pills: the mockup keeps the whole system at a 2px radius
   so it reads editorial rather than corporate. */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .8rem 1.5rem;
  border-radius: 2px;
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-size: .94rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .2s ease, border-color .2s ease, color .2s ease;
}

.btn.primary { background: var(--fg); color: var(--bg); }
.btn.primary:hover,
.btn.primary:focus-visible { background: var(--accent-text); border-color: var(--accent-text); color: var(--bg); }

.btn.secondary { border-color: var(--fg); color: var(--fg); background: transparent; }
.btn.secondary:hover,
.btn.secondary:focus-visible { border-color: var(--accent-text); color: var(--accent-text); }

/* Every other link on the site is relative, so an absolute one is by definition
   leaving. The arrow says so before the click rather than after it. */
.btn[href^="http"]::after,
.link-cta[href^="http"]::after { content: " \2197"; }

/* A CTA whose destination is not built yet. Shown, because the copy calls for
   it, but inert and labelled — a link to a 404 and a silently missing button
   are both worse than a button that says it is not ready. */
.btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
}
.btn.primary[aria-disabled="true"]:hover { background: var(--fg); border-color: transparent; }
.btn.secondary[aria-disabled="true"]:hover { border-color: var(--fg); color: var(--fg); }

.link-cta {
  display: inline-block;
  color: var(--accent-text);
  font-weight: 600;
  font-size: .94rem;
  text-decoration: none;
  transition: color .15s ease;
}
.link-cta::after { content: " \2192"; }
.link-cta:hover { color: var(--fg); }

:where(a, button):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip-link {
  position: fixed;
  top: .5rem; left: .5rem;
  z-index: 20;
  padding: .5rem .9rem;
  background: var(--fg);
  color: var(--bg);
  border-radius: 2px;
  font-size: .8rem;
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform .15s ease;
}
.skip-link:focus { transform: none; }

/* ---- controls -------------------------------------------------------- */

.control {
  display: inline-flex;
  align-items: center;
  gap: .1rem;
  padding: .2rem;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  background: var(--chip-bg);
}

.control button {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--muted);
  padding: .35rem .45rem;
  border-radius: 2px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* comfortable touch target without inflating the visible chip */
  min-width: 2rem;
  min-height: 1.9rem;
  transition: color .15s ease, background-color .15s ease;
}

.icon {
  width: 1.05rem;
  height: 1.05rem;
  fill: currentColor;
  display: block;
}

/* The sprite is a definitions carrier, never rendered in place. Hidden by
   collapsing rather than display:none, which can break <use> in some engines. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.control button:hover { color: var(--fg); }

.control button[aria-pressed="true"] {
  color: var(--bg);
  background: var(--fg);
}

/* ---- header ---------------------------------------------------------- */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  max-width: none;
  padding: .9rem clamp(1rem, 4vw, 2rem);
  /* no bar: the page shows through, and a hairline appears once scrolled */
  border-bottom: 1px solid transparent;
  transition: border-color .3s ease, background-color .3s ease;
}

/* set by app.js past the first screenful */
:root.scrolled .site-header {
  border-bottom-color: var(--hairline);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
}

/* Blur only where the full-screen menu is not used: backdrop-filter creates a
   containing block for fixed descendants, which would trap the overlay inside
   the header box instead of letting it fill the viewport. */
@media (min-width: 62.01rem) {
  :root.scrolled .site-header {
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: blur(8px);
  }
}

.brand {
  display: flex;
  align-items: center;
  color: var(--fg);
  flex: none;
}

/* The horizontal lockup is 10.57:1 — at 2em it is 338px wide, which does not
   fit a phone beside the controls. Below the breakpoint the mark alone stands
   in; it is 1.25:1, so 40px at the same height. */
.brand-full { height: 2em; width: auto; display: block; }
.brand-mark { height: 2em; width: auto; display: none; }

.site-nav ul {
  display: flex;
  gap: clamp(1rem, 2.2vw, 1.75rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Sentence case at 14px, per the mockup — the previous uppercase tracking
   competed with the eyebrow style, which is the one place small caps earn
   their keep. */
.site-nav a {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: .875rem;
  white-space: nowrap;
  padding: .35rem 0;
  transition: color .15s ease;
}

.site-nav a:hover { color: var(--accent-text); }

/* Current section marked with the gold node already in the brand — the rule
   dot in SPEC.md §5 — rather than a new underline treatment. */
.site-nav a[aria-current="page"] { color: var(--fg); }
.site-nav a[aria-current="page"]::before {
  content: "";
  display: inline-block;
  width: .4em; height: .4em;
  margin-right: .5em;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: .1em;
}

.site-controls {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: .6rem;
}

.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: .45em;
  color: var(--muted);
  text-decoration: none;
  font-size: .78rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  padding: .4rem .6rem;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease;
}
.lang-switch:hover { color: var(--fg); border-color: var(--fg); }

/* Flags are 3:2, so they need the extra width to avoid being squashed. They
   are drawn in currentColor and re-ink with the theme like the rest. */
.icon-flag { width: 1.25rem; height: .84rem; flex: none; }

/* Inert without JavaScript, so not shown; the language switch is a real
   link and stays either way. */
.no-js .site-controls .control { display: none; }

.menu-toggle {
  display: none;
  appearance: none;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  color: var(--muted);
  font: inherit;
  font-size: .78rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  padding: .4rem .8rem;
  cursor: pointer;
}

/* ---- cover (home only) ------------------------------------------------ */

.hero {
  position: relative;
  min-height: 88vh;          /* not 100 — a sliver of the next block shows */
  min-height: 88svh;
  display: grid;
  place-items: center;
  padding: 5rem clamp(1.25rem, 6vw, 4rem) 3rem;
}

.hero-lockup-wrap { margin: 0; width: min(100%, 62rem); }

.hero-lockup {
  filter: var(--hero-halo);
  width: 100%;
  height: auto;
  max-height: 62vh;
  color: var(--fg);
  display: block;
}

/* Crossfade: the big lockup gives way to the one in the header, so only one
   is ever showing. One class on <html>, set by app.js past the hero. */
.hero-lockup-wrap,
.site-header .brand { transition: opacity .4s ease; }

:root.scrolled .hero-lockup-wrap { opacity: 0; }

/* On a page with a hero the header logo starts hidden; everywhere else it is
   simply always there. */
html:not(.scrolled) body.has-hero .site-header .brand {
  opacity: 0;
  pointer-events: none;
}

.hero-enter {
  position: absolute;
  bottom: clamp(1.5rem, 5vh, 3rem);
  left: 50%;
  transform: translateX(-50%);
  /* Full ink, and a ground of its own. At --muted over the photograph its
     contrast depended on whichever part of the valley happened to sit behind
     it, which on the light theme came to almost none. */
  color: var(--fg);
  text-decoration: none;
  font-size: .74rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 600;
  padding: .6rem 1.4rem;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(3px);
  transition: background-color .2s ease, border-color .2s ease, color .2s ease,
              opacity .3s ease;
}

.hero-enter:hover,
.hero-enter:focus-visible {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}
/* An arrow that points down: this scrolls. It navigated while the homepage was
   a wordless cover with nothing under the fold; there are sections there now. */
.hero-enter::after { content: " \2193"; }

/* Once you have taken the hint it has nothing left to say. */
:root.scrolled .hero-enter { opacity: 0; pointer-events: none; }

/* ---- sections --------------------------------------------------------- */

main { display: block; }

.page-title,
.block {
  width: min(100%, var(--measure));
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 6vw, 2rem);
}

/* The homepage blocks are components rather than prose, and take the mockup's
   wider measure.

   Scoped to .page-home on purpose. These class names come from the block ids,
   and the same ids mean ordinary prose elsewhere — resources, community and
   connect all open with `intro`, framework has a `pillars`. Unscoped, this
   widened those sections' prose while the rules below squeezed it to 34ch. */
.page-home :is(.block-intro, .block-pillars, .block-statement, .block-next),
.page-home .route {
  width: min(100%, var(--wide));
}

.page-title {
  font-size: clamp(2rem, 5vw, 3.2rem);
  line-height: 1.08;
  margin: clamp(7rem, 18vh, 11rem) auto clamp(2rem, 6vh, 3.5rem);
  text-wrap: balance;
}

.block { margin-block: clamp(3rem, 8vh, 4.5rem); }


.block p {
  font-size: 1.06rem;
  line-height: 1.7;
  margin: 0 0 1.1em;
  color: color-mix(in srgb, var(--fg) 92%, transparent);
}

/* Fraunces italic, so a sub-heading inside prose reads as a turn in the voice
   rather than as a second title competing with the page's own. */
.block h3 {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.25rem, 2.2vw, 1.45rem);
  line-height: 1.3;
  margin: 2.2em 0 .7em;
}

/* A heading that opens its block should not also push it down. */
.block h3:first-child { margin-top: 0; }

/* A sign-off closing a passage. Same display face as the sub-headings, but a
   paragraph — it closes rather than introduces, and an <h3> here would announce
   a section with nothing under it. Written as `~ text` in content/. */
/* A couplet — two closing lines in a row — is one thought, not two sections. */
.block .closing + .closing { margin-top: .35em; }

.block .closing {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.25rem, 2.2vw, 1.45rem);
  line-height: 1.4;
  margin: 2em 0 0;
  color: var(--fg);
}

.block ul { margin: 0 0 1.4em; padding-left: 1.1em; }
.block li { line-height: 1.7; margin-bottom: .45em; }

.block a { color: var(--accent-text); text-underline-offset: .2em; }
.block a:hover { color: var(--fg); }
.block .btn, .block .link-cta, .block h3 a { text-decoration: none; }

/* ---- inner pages ------------------------------------------------------
   The mockup runs every inner section down one 880px column and caps the
   prose inside it, rather than centring each section on its own width. That
   is what puts the hero, the stages and the grids on a single left edge —
   centred columns of different widths would each start somewhere else. */

body:not(.page-home) main {
  width: min(100%, var(--wide));
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 6vw, 2rem);
}

body:not(.page-home) .block {
  width: auto;
  margin-inline: 0;
  padding-inline: 0;
}

/* Prose keeps a reading measure inside that column; the grids use its width. */
body:not(.page-home) .block > :is(p, ul:not([class]), ol:not([class])) {
  max-width: 40rem;
}

/* The story carries a floated portrait, so the float's containing block has to
   be near the text rather than out at the full column, or it detaches. */
body:not(.page-home) .block-story { max-width: 44rem; }

/* Inside the single left-aligned column the route lines up with the text
   rather than centring itself on whatever block happens to contain it. */
body:not(.page-home) .route { margin-inline: 0; padding-inline: 0; }

.inner-hero { margin: clamp(6rem, 15vh, 9rem) 0 clamp(2rem, 6vh, 3rem); }

.page-headline {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -.01em;
  font-size: clamp(1.9rem, 4vw, 2.65rem);
  line-height: 1.14;
  max-width: 44rem;      /* the mockup's 700px */
  margin: 0;
  text-wrap: balance;
}

.page-sub {
  font-size: 1.06rem;
  color: var(--ink-soft);
  max-width: 32rem;      /* the mockup's 520px */
  margin: 1rem 0 0;
}

/* A quieter aside — "coming soon", "not a checklist". Sage, so it reads as a
   note from the author rather than as more of the copy. */
.note {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--sage);
  font-size: .95rem;
  max-width: 38rem;
  margin: 1.75rem 0 0;
}

/* ---- framework stages -------------------------------------------------- */

.stage-list {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  counter-reset: none;
}

.stage {
  display: flex;
  gap: 1.5rem;
  padding: 1.6rem 0;
  border-top: 1px solid var(--line);
  margin: 0;
}
.stage:last-child { border-bottom: 1px solid var(--line); }

.stage-num {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.6rem;
  line-height: 1.2;
  color: var(--accent-text);
  flex: none;
  width: 2.75rem;
}

.stage-copy h3 { font-style: normal; font-weight: 500; font-size: 1.2rem; margin: 0 0 .35rem; }
.stage-copy p { margin: 0; font-size: .97rem; color: var(--ink-soft); max-width: 35rem; }

/* ---- community --------------------------------------------------------- */

/* The questions are the page's argument, so they are set in display italic on
   ruled lines rather than as bullets. */
.question-list {
  list-style: none;
  margin: 1.75rem 0 2rem;
  padding: 0;
  max-width: 40rem;
}
.question-list li {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.15rem;
  color: var(--fg);
  padding: .9rem 0;
  margin: 0;
  border-bottom: 1px solid var(--line);
}
.question-list li:first-child { border-top: 1px solid var(--line); }

.find-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: .9rem 2rem;
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
}
.find-grid li {
  font-size: .97rem;
  color: var(--ink-soft);
  padding-left: 1.15rem;
  position: relative;
  margin: 0;
}
.find-grid li::before { content: "—"; position: absolute; left: 0; color: var(--accent-text); }

/* ---- resources --------------------------------------------------------- */

.resource-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.75rem;
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
}
.resource-card { margin: 0; }
.resource-card h3 { font-style: normal; font-weight: 500; font-size: 1.1rem; margin: 0 0 .4rem; }
.resource-card p { font-size: .92rem; color: var(--ink-soft); margin: 0; }
.resource-card .tag {
  display: block;
  font-size: .7rem;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: 600;
  color: var(--sage);
  margin-bottom: .4rem;
}

/* ---- connect ----------------------------------------------------------- */

.pathway-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.75rem 2.5rem;
  list-style: none;
  margin: 2rem 0 3rem;
  padding: 0;
}
.pathway { margin: 0; }
.pathway h3 { font-style: normal; font-weight: 500; font-size: 1.15rem; margin: 0 0 .4rem; }
.pathway p { font-size: .95rem; color: var(--ink-soft); margin: 0 0 .75rem; max-width: 26rem; }

.contact { max-width: 30rem; margin-top: 1.5rem; }
.contact label {
  display: block;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: 600;
  color: var(--sage);
  margin: 1.1rem 0 .35rem;
}
.contact input,
.contact textarea {
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--line);
  background: transparent;
  font-family: var(--font-body);
  font-size: .95rem;
  padding: .5rem 0;
  color: var(--fg);
}
.contact input:focus,
.contact textarea:focus { outline: none; border-bottom-color: var(--accent); }
.contact button { margin-top: 1.75rem; }
.contact :disabled { opacity: .5; cursor: not-allowed; }

/* Said out loud rather than hidden: while form.action is empty the fields are
   inert, and a form that looks live and loses every message is worse than none
   on a page whose whole purpose is "write to me". */
.form-notice,
.pending-notice {
  margin: 1rem 0 0;
  font-size: .85rem;
  color: var(--accent-text);
  max-width: 30rem;
}

.block-signoff .closing { color: var(--ink-soft); font-size: 1.1rem; max-width: 35rem; }

/* ---- disclaimer -------------------------------------------------------- */
/* Every page. It disclaims a therapist-client relationship and carries a
   crisis line, so it is set to be read, not tucked away at footnote size. */

.disclaimer {
  background: var(--paper-deep);
  border-top: 1px solid var(--line);
  margin-top: clamp(4rem, 12vh, 7rem);
  padding: 2.25rem clamp(1.25rem, 6vw, 2rem);
}
.disclaimer p {
  width: min(100%, var(--frame));
  margin-inline: auto;
  max-width: 45rem;
  font-size: .88rem;
  line-height: 1.7;
  color: var(--ink-soft);
}
.disclaimer strong { color: var(--fg); font-weight: 600; }

/* The footer follows the disclaimer directly, so it drops its own top gap. */
.disclaimer + .site-footer { margin-top: 0; border-top: 0; }


/* ---- intro ------------------------------------------------------------ */

.page-home .block-intro { margin-top: clamp(2rem, 5vh, 3rem); }

.headline {
  font-size: clamp(2.2rem, 5.4vw, 3.4rem);
  line-height: 1.08;
  max-width: 20ch;
  text-wrap: balance;
}

/* The cover's lockup already sets "Psychology · Education · Belonging" in type,
   and the intro's eyebrow is the same three words — with both on screen at once
   the page says it twice. The eyebrow holds at opacity 0 until the lockup has
   gone, on the same 'scrolled' class that fades it, so the two hand over rather
   than overlap. Opacity rather than display: the space stays reserved, so the
   headline below never shifts.

   Gated on :not(.no-js) because 'scrolled' is set by app.js. Without it the
   class never arrives and an unguarded rule would hide the eyebrow for good —
   the same fail-closed trap as an animation with fill-mode: both. */
.page-home .block-intro .eyebrow { transition: opacity .4s ease; }

html:not(.no-js):not(.scrolled) body.has-hero .block-intro .eyebrow {
  opacity: 0;
}

/* .subhead is only ever emitted by the homepage's intro template, but keep the
   scope explicit so a future template reusing the class cannot inherit the
   34ch cap, which is a hero measure and not a reading one. */
.page-home .block-intro .subhead {
  font-size: 1.15rem;
  color: var(--ink-soft);
  max-width: 34ch;
  margin-top: 1.3rem;
}

.cta-row { display: flex; gap: 1.1rem; margin-top: 2.2rem; flex-wrap: wrap; }

/* ---- the route --------------------------------------------------------
   Lima → Madrid → Amsterdam → California, Lorena's actual path. It appears
   exactly twice in the site — small under the homepage intro, larger at the
   top of About — because it only means anything by being true and by being
   rare. It is not available as generic decoration. */

.route {
  margin: clamp(2.5rem, 7vh, 4rem) auto;
  padding-inline: clamp(1.25rem, 6vw, 2rem);
}

/* Line and dots are CSS, names are ordinary text. Drawing the whole thing as
   one SVG scales the type down with the viewport, which is the one part that
   must not scale. Each stop is a zero-width anchor at its percentage along the
   track; the padding is half a dot, so the end dots sit fully inside. */
.route-track {
  position: relative;
  height: 2.7rem;
  padding-inline: .35rem;
}

.route-track::before {
  content: "";
  position: absolute;
  left: .35rem; right: .35rem;
  top: .3rem;
  border-top: 1px solid var(--line);
}

.route-stop { position: absolute; left: var(--at); top: 0; width: 0; }

.route-dot {
  position: absolute;
  top: 0; left: 0;
  transform: translateX(-50%);
  width: .55rem; height: .55rem;
  border-radius: 50%;
  background: var(--bg);
  border: 1.5px solid var(--accent);
}
/* The last stop is filled: it is where the route arrives. */
.route-stop:last-child .route-dot { background: var(--accent); }

.route-name {
  position: absolute;
  top: 1.25rem; left: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: .75rem;
  color: var(--ink-soft);
}
/* The end labels turn inward so neither runs past the track. */
.route-stop:first-child .route-name { transform: none; }
.route-stop:last-child .route-name { transform: translateX(-100%); }

/* On About it takes the prose measure, so its ends line up with the page title
   above it rather than hanging past both edges. */
.route-large {
  width: min(100%, var(--measure));
  margin-block: clamp(1rem, 3vh, 2rem) clamp(3rem, 8vh, 4rem);
}
.route-large .route-track { height: 3.1rem; }
.route-large .route-dot { width: .7rem; height: .7rem; }
.route-large .route-name { top: 1.5rem; font-size: .82rem; }

/* ---- pillars ---------------------------------------------------------- */

.page-home .block-pillars {
  border-top: 1px solid var(--line);
  padding-top: clamp(2.5rem, 7vh, 4rem);
}

/* The lead-in keeps a reading measure even though the grid below it does not. */
.page-home .block-pillars > :is(p, h3) { max-width: 38rem; }

.pillar-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2.25rem;
  margin: 2.25rem 0 0;
  padding: 0;
  list-style: none;
}

.pillar { margin: 0; }
/* Roman, not the prose italic above — the mockup sets the pillar names and
   the onward-card titles upright, and they are labels rather than voice. */
.pillar h3 { font-style: normal; font-weight: 500;
             font-size: 1.3rem; margin: 0 0 .4rem; }
.pillar p { font-size: .97rem; margin: 0; color: var(--ink-soft); }

/* Numbering is generated, not written — 01/02/03 mark a genuine sequence, and
   per the style guide belong only here and to the Framework's stages. */
.pillar .mark {
  display: block;
  margin-bottom: .6rem;
  font-family: var(--font-display);
  font-style: italic;
  font-size: .95rem;
  color: var(--accent-text);
}

/* ---- statement -------------------------------------------------------- */

/* Any `>` quote, anywhere: a quiet rule and the display face. */
.block blockquote {
  font-family: var(--font-display);
  font-weight: 400;
  font-style: italic;
  font-size: 1.2rem;
  line-height: 1.5;
  margin: 0 0 1.4em;
  padding-left: 1.1rem;
  border-left: 2px solid var(--accent);
  color: var(--fg);
}

/* The homepage's is the mockup's full-size statement, and carries its quotes. */
.page-home .block-statement blockquote {
  font-size: clamp(1.5rem, 3vw, 2rem);
  line-height: 1.4;
  letter-spacing: -.01em;
  margin: 0;
  padding-left: 0;
  border-left: 0;
  max-width: 44rem;   /* the mockup's 700px */
}
.page-home .block-statement blockquote::before { content: "\201C"; }
.page-home .block-statement blockquote::after  { content: "\201D"; }
.page-home .block-statement p { margin-top: 1.4em; color: var(--ink-soft); }

/* ---- pull-quotes -------------------------------------------------------
   A line lifted out of the copy around it and set large, as a way into a long
   passage. Deliberately smaller than the homepage's statement, which is a
   section in its own right rather than a repeat: they should not read as the
   same component. The gold rule marks it as pulled rather than spoken. */

.pullquote {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.3rem, 2.6vw, 1.65rem);
  line-height: 1.4;
  letter-spacing: -.01em;
  color: var(--fg);
  max-width: 28ch;
  margin: clamp(2.25rem, 6vh, 3rem) 0;
  padding-top: 1.1rem;
  border-top: 2px solid var(--accent);
}

/* ---- the guide -------------------------------------------------------- */
/* A full-bleed band, so the page has one horizontal break in it. */

.block-guide {
  width: 100%;
  max-width: none;
  padding-inline: 0;
  background: var(--paper-deep);
  border-block: 1px solid var(--line);
  margin-block: clamp(3rem, 9vh, 5rem);
}

.guide-row {
  width: min(100%, var(--wide));
  margin-inline: auto;
  padding: clamp(2.75rem, 7vh, 4rem) clamp(1.25rem, 6vw, 2rem);
  display: flex;
  gap: clamp(1.75rem, 4vw, 3rem);
  align-items: flex-start;
  flex-wrap: wrap;
}

.guide-photo {
  width: 168px;
  height: 168px;
  border-radius: 2px;
  flex: none;
  /* A 4:5 portrait in a square: crop high, or the centre crop takes the chin
     and leaves the forehead out of frame. */
  object-fit: cover;
  object-position: 50% 22%;
}

/* Until a photograph exists, the arch mark rather than a grey "photo here"
   box — the placeholder should read as a considered choice, not as a hole. */
.guide-photo-placeholder {
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  border: 1px solid var(--line);
}
.guide-mark { width: 42%; height: auto; color: var(--accent); opacity: .85; }

.guide-copy { flex: 1; min-width: 17rem; }
.guide-copy .block-headline { font-size: clamp(1.5rem, 3vw, 1.75rem); margin-bottom: .7rem; }
.guide-copy p { max-width: 44ch; color: var(--ink-soft); }

/* ---- section plates ---------------------------------------------------
   A photograph set between passages, written as ![alt](name) in content/.
   Centred and well inside the measure: at the full 42rem a 4:5 frame stands
   809px tall and takes over the page, which is not what a plate is for. */
.block figure.photo {
  margin: clamp(2.5rem, 7vh, 3.5rem) auto;
  width: min(100%, 24rem);
}

/* The block's own margin already sets the gap to whatever follows, so a plate
   at the end of one must not add its margin on top. Normally the two collapse
   and this would change nothing — but a block that establishes a BFC does not
   collapse through its edges, and About's story is one of those, because it
   contains a floated portrait. That gave its photograph 128px of space beneath
   it against 72px everywhere else. */
.block > figure.photo:last-child { margin-bottom: 0; }

/* A vista needs more width than a portrait plate to still read as one. */
.block figure.photo-wide { width: min(100%, 34rem); }

.photo-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 2px;
}

/* ---- About's portrait -------------------------------------------------
   Set into the story rather than banded above it: this is a personal page and
   the photograph reads as part of the telling, not as a header image. Floated,
   so the opening paragraphs close around it instead of leaving a column of
   dead space beside a short one.

   :has() rather than a clear on the next block — the figure is injected into
   whichever block comes first, so the rule should follow it rather than name a
   block id. flow-root contains the float without adding a clearing element. */
.block:has(> .about-portrait) { display: flow-root; }

.about-portrait {
  float: right;
  width: clamp(11rem, 34%, 16rem);
  margin: .35rem 0 1.25rem 2rem;
}

.about-photo {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 2px;
}

/* ---- where to go from here -------------------------------------------- */

.next-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin: 1.75rem 0 0;
  padding: 0;
  list-style: none;
}

.next-card {
  border-top: 2px solid var(--fg);
  padding-top: 1rem;
  margin: 0;
}
.next-card h3 { font-style: normal; font-weight: 500;
                font-size: 1.2rem; margin: 0 0 .4rem; }
/* Only when a card has no verb of its own — then the title carries the link. */
.next-card h3 a { color: var(--fg); }
.next-card h3 a::after { content: " \2192"; color: var(--accent-text); }
.next-card h3 a:hover { color: var(--accent-text); }
.next-card p { font-size: .94rem; margin: 0 0 .9rem; color: var(--ink-soft); }

/* ---- footer ----------------------------------------------------------- */

/* One line, left in the 1040 frame under a hairline, per the mockup. */
.site-footer {
  width: min(100%, var(--frame));
  margin: clamp(3rem, 10vh, 6rem) auto 0;
  padding: 2rem clamp(1.25rem, 6vw, 2rem);
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .82rem;
}
.site-footer p { margin: 0; }

/* ---- narrow ----------------------------------------------------------- */

@media (max-width: 62rem) {
  .brand-full { display: none; }
  .brand-mark { display: block; }
  .menu-toggle { display: inline-block; }

  .site-nav {
    position: fixed;
    inset: 0;
    background: var(--bg);
    display: grid;
    place-items: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity .25s ease, visibility .25s;
  }
  .site-nav[data-open="true"] { opacity: 1; visibility: visible; }

  /* The overlay is positioned, so it paints above static siblings whatever the
     DOM order. Lift the controls back over it: the toggle has to be reachable
     to close the menu, and language and theme stay available while it is open. */
  .menu-toggle,
  .site-controls { position: relative; z-index: 1; }

  .site-nav ul { flex-direction: column; gap: 1.5rem; text-align: center; }
  .site-nav a { font-size: 1.15rem; }
}

/* One column under 720px, per the mockup. */
@media (max-width: 45rem) {
  .pillar-grid, .next-grid,
  .find-grid, .resource-grid, .pathway-grid { grid-template-columns: 1fr; gap: 1.75rem; }
  .stage { gap: 1rem; }
  .stage-num { width: 2rem; font-size: 1.35rem; }
  .headline { max-width: none; }
  .guide-photo { width: 120px; height: 120px; }
  .route-large .route-name { font-size: .75rem; }

  /* Too little room left beside a float at this width — the portrait goes
     above the story instead, and the text runs full measure under it. */
  .about-portrait { float: none; width: min(100%, 13rem); margin: 0 0 1.75rem; }
}

@media (max-width: 30rem) {
  :root { --header-h: 3.4rem; }   /* the header's padding tightens below */
  .site-header { gap: .6rem; padding: .7rem .8rem; }
  .icon { width: .95rem; height: .95rem; }
  .cta-row .btn { flex: 1 1 100%; justify-content: center; }
}
