/* ===========================================================================
   layout.css — containers, section rhythm, grids, and the scroll-reveal
   primitive used across every page.
   =========================================================================== */

/* ── Containers ──────────────────────────────────────────────────────────── */
.container {
  inline-size: 100%;
  max-inline-size: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--wide   { max-inline-size: var(--container-wide); }
.container--narrow { max-inline-size: var(--container-narrow); }

/* ── Sections ────────────────────────────────────────────────────────────── */
.section { padding-block: var(--section-y); position: relative; }
.section--tight { padding-block: var(--section-y-tight); }
.section--flush-top { padding-block-start: 0; }
.section--sunken { background: var(--surface-sunken); }
.section--brand {
  background: var(--brand-800);
  color: var(--c-text-invert);
}
.section--brand h2, .section--brand h3 { color: var(--c-text-invert); }

/* A hairline that reads as structure rather than decoration */
.section--ruled { border-block-start: 1px solid var(--c-border); }

/* ── Section headers ─────────────────────────────────────────────────────── */
.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  max-inline-size: 42rem;
  margin-block-end: clamp(2rem, 4vw, 3.5rem);
}
.section-head--center { margin-inline: auto; text-align: center; align-items: center; }
/* Heading on one side, a "view all" action on the other. Below 48rem there is
   no room for two columns, so it stacks — side by side it was squeezing the
   heading into a narrow ragged column on every phone. */
.section-head--split {
  max-inline-size: none;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-5);
}
.section-head--split .section-head__text { max-inline-size: 40rem; }

@media (min-width: 48rem) {
  .section-head--split {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-6);
  }
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--brand-600);
}
.eyebrow::before {
  content: "";
  inline-size: 1.5rem;
  block-size: 2px;
  border-radius: var(--r-full);
  background: currentColor;
  opacity: 0.55;
}
.section-head--center .eyebrow::before { display: none; }
.section--brand .eyebrow { color: var(--brand-200); }

.lede {
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
  color: var(--c-text-muted);
  /* Measured at 118 characters per line on a 1440px screen before this — the
     lede simply inherited the full 1216px container width. Past roughly 75
     characters the reader starts losing the line-return: the eye can't find
     the next line's left edge reliably. A ch unit tracks the font rather
     than guessing a pixel width. */
  max-inline-size: 62ch;
}
.section--brand .lede { color: var(--brand-100); }

/* ── Grids ───────────────────────────────────────────────────────────────── */
.grid { display: grid; gap: var(--sp-5); }

/* auto-fit grids: mobile-first single column, growing without breakpoints */
.grid--cards   { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.grid--wide    { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.grid--halves  { grid-template-columns: 1fr; gap: clamp(2rem, 5vw, 4.5rem); }

@media (min-width: 60rem) {
  .grid--halves { grid-template-columns: 1fr 1fr; align-items: center; }
  .grid--halves.grid--halves-wide-start { grid-template-columns: 1.15fr 1fr; }
  .grid--halves.grid--halves-wide-end   { grid-template-columns: 1fr 1.15fr; }
}

/* Explicit order control that survives RTL (order is direction-agnostic) */
.order-first { order: -1; }
@media (min-width: 60rem) { .lg\:order-first { order: -1; } .lg\:order-last { order: 1; } }

/* ── Stack / flow utilities ──────────────────────────────────────────────── */
.stack > * + * { margin-block-start: var(--flow, var(--sp-4)); }
.stack--sm { --flow: var(--sp-3); }
/* --md was used in 25 places across 19 templates before it was ever defined.
   An undefined modifier doesn't fall back to the rule's own default: --flow
   is a custom property, so `var(--flow, var(--sp-4))` finds the value
   *inherited from the nearest ancestor that set one* and only reaches the
   16px default if no ancestor ever did. A .stack--md inside a .stack--xl was
   therefore silently spacing at 32px. Same value as the bare .stack default,
   stated explicitly so the class means what it says. */
.stack--md { --flow: var(--sp-4); }
.stack--lg { --flow: var(--sp-5); }
.stack--xl { --flow: var(--sp-6); }

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
}
.cluster--lg { gap: var(--sp-5); }

/* ── Scroll reveal ───────────────────────────────────────────────────────────
   Elements marked [data-reveal] start slightly offset and fade in once, driven
   by an IntersectionObserver in main.js. If JS never runs, the .js class is
   absent and everything is simply visible — progressive enhancement, no FOUC.
   -------------------------------------------------------------------------- */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity var(--dur-slower) var(--ease-out),
    transform var(--dur-slower) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
.js [data-reveal].is-revealed { opacity: 1; transform: none; }

/* Three reveals, not one. Everything on the page entering the same way — a
   14px rise — is what makes scroll animation read as a plugin someone
   switched on rather than as design. What moves, and how far, should say
   something about what the thing *is*:
     rise  — headings arrive from further down and settle. They announce.
     scale — cards and tiles grow very slightly into place. They assemble.
   Both land on the same easing and duration as the default, so the page
   still moves as one system. */
.js [data-reveal="rise"] { transform: translateY(28px); }
.js [data-reveal="scale"] { transform: translateY(10px) scale(0.965); }
.js [data-reveal="rise"].is-revealed,
.js [data-reveal="scale"].is-revealed { transform: none; }

/* Stagger children without writing a delay for each one */
.js [data-reveal-group] > * { --reveal-delay: 0ms; }
.js [data-reveal-group] > *:nth-child(2) { --reveal-delay: 70ms; }
.js [data-reveal-group] > *:nth-child(3) { --reveal-delay: 140ms; }
.js [data-reveal-group] > *:nth-child(4) { --reveal-delay: 210ms; }
.js [data-reveal-group] > *:nth-child(5) { --reveal-delay: 280ms; }
.js [data-reveal-group] > *:nth-child(6) { --reveal-delay: 350ms; }

/* ── Decorative flourishes ───────────────────────────────────────────────── */
.blob-bg {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  filter: blur(60px);
  opacity: 0.5;
  border-radius: var(--r-full);
}
.section > .container { position: relative; z-index: 1; }
