/*
 * Pre-launch site — LuminaMovement.
 *
 * Deliberately hand-written CSS with no build step and no framework: this site
 * is served as static files by Caddy on the production box and has to stay
 * editable by anyone who can open a text editor. The palette and type scale are
 * the same tokens the real app uses (packages/ui/src/tokens.ts) so the two do
 * not drift apart before the app takes over this domain.
 */

/* --- fonts, self-hosted --------------------------------------------------
 * Both families are variable fonts, so one file per family covers every weight
 * we use. Served from our own origin: no request to a third party on page load,
 * nothing for a visitor's browser to leak, and no CDN in the critical path.
 */
@font-face {
  font-family: 'Jost';
  src: url('fonts/jost.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/inter.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --lm-bone: #f2ede5;
  --lm-linen: #faf7f2;
  --lm-sand: #e7e0d5;
  --lm-border: #dcd3c6;
  --lm-ink: #33372c;
  --lm-ink-soft: #5c6153;
  --lm-ink-muted: #87897c;
  --lm-gold: #c39a5d;
  --lm-gold-deep: #7a5a28;
  --lm-sage: #9cac8d;
  --lm-sage-deep: #55654a;

  --display: 'Jost', 'Century Gothic', 'Futura', system-ui, sans-serif;
  --body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --gutter: clamp(20px, 5vw, 40px);
  --section-y: clamp(56px, 9vw, 108px);
  --radius: 14px;
}

* { box-sizing: border-box; }

/* The `hidden` attribute is only a UA-stylesheet `display: none`, so any author
   rule that sets `display` on the same element beats it — .socials{display:flex}
   silently unhides the footer's placeholder social links. Restore the intent. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  /* The header is sticky, so an anchor jump would otherwise land with the
     heading hidden underneath it. */
  scroll-padding-top: 88px;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--lm-bone);
  color: var(--lm-ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

.container {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}
.narrow { max-width: 760px; }

/* --- type ---------------------------------------------------------------- */

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 1.2;
  margin: 0;
  color: var(--lm-ink);
}
h2 { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem); }
h3 { font-size: 1.18rem; letter-spacing: 0.02em; }

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--lm-gold-deep); text-underline-offset: 3px; }
a:hover { color: var(--lm-ink); }

.eyebrow {
  font-family: var(--display);
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--lm-sage-deep);
  margin: 0;
}
.lede {
  font-size: clamp(1.02rem, 0.98rem + 0.25vw, 1.15rem);
  color: var(--lm-ink-soft);
}
.muted { color: var(--lm-ink-muted); }
.small { font-size: 0.92rem; }
.tiny { font-size: 0.82rem; }

/* Small utilities. These exist so no element needs a `style` attribute, which
   is what lets the site ship a Content-Security-Policy with no 'unsafe-inline'
   in style-src — inline style attributes are blocked by CSP just as inline
   <style> blocks are. */
.m-0 { margin: 0; }
.mt-xs { margin-top: 6px; }
.center { text-align: center; }
.page-title {
  margin-top: 18px;
  font-size: clamp(1.7rem, 1.3rem + 1.7vw, 2.6rem);
}
.step-name { font-weight: 500; margin: 4px 0; }

.rule {
  width: 56px;
  height: 2px;
  background: var(--lm-gold);
  border: 0;
  margin: 20px 0;
}
.rule.center { margin-left: auto; margin-right: auto; }

/* --- layout -------------------------------------------------------------- */

section { padding: var(--section-y) 0; }
.band-linen {
  background: var(--lm-linen);
  border-top: 1px solid var(--lm-border);
  border-bottom: 1px solid var(--lm-border);
}
.section-head { max-width: 760px; margin-bottom: clamp(32px, 5vw, 52px); }
.section-head.center { margin-left: auto; margin-right: auto; text-align: center; }
.section-head h2 { margin-bottom: 14px; }

.grid {
  display: grid;
  gap: clamp(18px, 2.6vw, 28px);
}
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
/* Four-up rows (the method pillars, the visit journey). A narrower track than
   grid-3 so all four sit on one line at container width instead of wrapping 3+1. */
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(205px, 1fr)); }

.card {
  background: var(--lm-linen);
  border: 1px solid var(--lm-border);
  border-radius: var(--radius);
  padding: clamp(22px, 3vw, 30px);
}
.band-linen .card { background: var(--lm-bone); }
.card h3 { margin-bottom: 8px; }

/* Photo cards (the three program disciplines): pillar label and title, then the
   photograph as its own band, then the description. The photo was briefly a
   background behind the copy and it read as muddy — type over a busy picture at
   any opacity that still shows the picture. It is a real <img> now, so it is
   never behind text and it carries alt text. */
.card-photo { overflow: hidden; }
/* <picture> is inline by default, which would leave a descender gap under the
   photo and make the negative margins below behave unpredictably. */
.card-photo picture { display: block; }
/* A 3:2 band spanning the card's text column — the same left and right edge as
   the title and the description, so it sits inside the card's padding rather
   than bleeding to the border. The source photographs are square and are cropped
   to 3:2 at build time (see DEPLOYMENT.md) rather than being squeezed by
   object-fit here, so the files carry no bytes the box never shows. Keep the
   ratio in the CSS and the crop in the files in step: if one changes without
   the other the picture is cropped twice and loses its subject. */
.card-photo .card-media {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  margin: clamp(18px, 2.4vw, 22px) 0;
  object-fit: cover;
  object-position: 50% 50%;
  border-radius: calc(var(--radius) - 4px);
  background: var(--lm-sand);
}
.card-photo picture + p { margin-top: 0; }

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

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(250, 247, 242, 0.92);
  backdrop-filter: saturate(180%) blur(8px);
  border-bottom: 1px solid var(--lm-border);
}
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 12px;
  padding-bottom: 12px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}
.brand img { height: 30px; width: auto; }
.brand span {
  font-family: var(--display);
  font-size: 1.12rem;
  letter-spacing: 0.2em;
  padding-right: 0.2em;
  color: var(--lm-ink);
}
.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.2vw, 26px);
}
/* Scoped to .nav-link, not to `.site-nav a`. The broader selector has higher
   specificity (0,1,1) than .btn-primary (0,1,0), so it would win the colour
   cascade against the CTA button in this same nav and paint dark ink on dark
   green — an invisible button. */
.site-nav .nav-link {
  font-size: 0.93rem;
  color: var(--lm-ink-soft);
  text-decoration: none;
}
.site-nav .nav-link:hover { color: var(--lm-ink); }
/* The in-page anchors are noise on a narrow screen; the CTA is what matters. */
@media (max-width: 720px) {
  .site-nav .nav-link { display: none; }
  .brand span { display: none; }
  .brand img { height: 34px; }
}

/* --- buttons ------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--display);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  padding: 12px 24px;
  border-radius: 999px;
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn-primary {
  background: var(--lm-sage-deep);
  color: var(--lm-linen);
}
.btn-primary:hover { background: var(--lm-ink); color: var(--lm-linen); }
.btn-secondary {
  background: transparent;
  border-color: var(--lm-border);
  color: var(--lm-ink);
}
.btn-secondary:hover { border-color: var(--lm-ink-soft); color: var(--lm-ink); }
.btn-sm { padding: 9px 18px; font-size: 0.88rem; }

/* Visible keyboard focus everywhere, not just where a browser guesses. */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--lm-gold-deep);
  outline-offset: 2px;
}

/* --- hero ---------------------------------------------------------------- */

/* The hero carries a photograph behind it, dimmed to 50% against the page's
 * cream so the logo and the copy stay the thing you read first.
 *
 * Two layers, both decorative pseudo-elements so no <img> lands in the accessibility
 * tree and no `style` attribute is needed (the CSP forbids inline styles):
 *
 *   ::before  the photograph itself, at opacity .5 over --lm-bone.
 *   ::after   cream fades at the top and bottom edges, so the band dissolves
 *             into the page instead of ending on a hard line, plus a gentle
 *             veil over the lower half where the essence line, the lede and the
 *             buttons sit — the photo's floor is bright and low-contrast there.
 *
 * `isolation: isolate` makes .hero a stacking context, which keeps the two
 * z-index: -1 layers behind the hero's own content without escaping behind the
 * page background.
 */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  text-align: center;
  padding-top: clamp(40px, 6vw, 72px);
  padding-bottom: clamp(64px, 9vw, 116px);
}
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.hero::before {
  /* The plain url() is the fallback for anything without image-set(); browsers
     that understand image-set() take the WebP, which is 124 KB against 213 KB. */
  background-image: url('hero-bg.jpg');
  background-image: image-set(url('hero-bg.webp') type('image/webp'),
                              url('hero-bg.jpg') type('image/jpeg'));
  background-size: cover;
  background-position: 50% 42%;
  background-repeat: no-repeat;
  opacity: 0.5;
}
/* Every transparent stop below is written as rgba(242, 237, 229, 0) — the same
   colour as --lm-bone at zero alpha — rather than the keyword `transparent`,
   which is rgba(0, 0, 0, 0) and can fade through grey on the way out. */
.hero::after {
  background: linear-gradient(
    to bottom,
    var(--lm-bone) 0,
    rgba(242, 237, 229, 0) 12%,
    rgba(242, 237, 229, 0) 38%,
    rgba(242, 237, 229, 0.55) 58%,
    rgba(242, 237, 229, 0.80) 78%,
    var(--lm-bone) 100%
  );
}
/* Narrow screens crop a 2:1 photo hard, so serve the half-size file (50 KB) and
   hold the frame at its centre where the class actually is. */
@media (max-width: 720px) {
  .hero::before {
    background-image: url('hero-bg-960.jpg');
    background-image: image-set(url('hero-bg-960.webp') type('image/webp'),
                                url('hero-bg-960.jpg') type('image/jpeg'));
    background-position: 50% 50%;
  }
}

.hero .badge {
  display: inline-block;
  background: var(--lm-sand);
  border: 1px solid var(--lm-border);
  border-radius: 999px;
  padding: 8px 20px;
  margin-bottom: clamp(28px, 4vw, 40px);
}
.hero .logo {
  width: min(420px, 78vw);
  height: auto;
  margin: 0 auto;
}

/* The animated logo.
 *
 * Sized so the artwork inside the video lands at the same visual width as the
 * static logo elsewhere on the site: the video frame carries more surrounding
 * background than the tightly-cropped PNG (the artwork is ~84% of the video's
 * width against ~96% of the PNG's), so the element is correspondingly wider.
 *
 * That surrounding background is invisible rather than a pale rectangle: the
 * video has had its vignette flattened and its outer edge feathered to exactly
 * --lm-bone, so it dissolves into the page. Keep this background in sync with
 * the page's if the palette ever changes.
 */
.hero-logo { position: relative; margin: 0 auto; width: min(482px, 90vw); }

/* A cream halo behind the logo, and the reason the photograph above does not
 * ruin it.
 *
 * The video is not transparent art — its own background is flattened to
 * --lm-bone and feathered at the edge, which dissolves invisibly into a flat
 * cream page but reads as a cream rectangle sitting on a photograph. So the
 * page paints cream back underneath it, fading out past the artwork.
 *
 * The insets are percentages, which resolve against .hero-logo's own width and
 * height, so the halo scales with the logo at every viewport instead of needing
 * a second set of numbers for phones. `closest-side` puts the gradient's 100%
 * stop exactly on the box edge.
 *
 * The falloff has to be long. A halo that goes opaque-to-clear over a short
 * band just moves the problem: the eye stops reading a cream rectangle and
 * starts reading a cream panel with soft corners. Opaque only as far as the
 * logo's own cream actually reaches (about 45% of the way out), then the
 * remaining 55% is one continuous fade, which reads as light rather than as a
 * shape.
 *
 * `z-index: -1`, and it matters. .hero-logo is `position: relative` with
 * `z-index: auto`, which does NOT open a stacking context, so this pseudo-
 * element resolves against .hero — the same layer as the photograph. At any
 * z-index of 0 or more it would instead paint as a positioned element, which is
 * above the hero's non-positioned copy, and the halo reaches 70% of the logo's
 * height below the logo: it silently washed out the essence line and the top of
 * the lede. Behind the copy is the only correct place for it.
 */
.hero-logo::before {
  content: "";
  position: absolute;
  inset: -70% -55%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    closest-side ellipse at 50% 50%,
    var(--lm-bone) 0,
    var(--lm-bone) 45%,
    rgba(242, 237, 229, 0) 100%
  );
}
/* Above the halo, which is what makes it a backdrop rather than a cover. */
.hero-logo-video,
.hero-logo-still { position: relative; z-index: 1; }

.hero-logo-video,
.hero-logo-still img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 890 / 630;
  background: var(--lm-bone);
}

/* Dissolve the video's outer edge to transparent so the page shows through.
 *
 * The video's own background is already flattened and feathered to --lm-bone,
 * but 4:2:0 chroma subsampling means a flat cream encodes and decodes back
 * about two levels off (242,237,229 becomes 242,235,228). On a large flat
 * field that is enough to read as a faint rectangle around the hero. Masking
 * the edge away makes the seam exact by construction instead of relying on the
 * codec round-tripping a colour perfectly.
 *
 * The masked band is pure background in every frame — the artwork never
 * reaches it — so nothing is lost. Where mask-image is unsupported the video
 * simply renders unmasked, which is the two-level edge and no worse. */
.hero-logo-video {
  --edge: 6%;
  -webkit-mask-image:
    linear-gradient(to right, transparent 0, #000 var(--edge), #000 calc(100% - var(--edge)), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 var(--edge), #000 calc(100% - var(--edge)), transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0, #000 var(--edge), #000 calc(100% - var(--edge)), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 var(--edge), #000 calc(100% - var(--edge)), transparent 100%);
  mask-composite: intersect;
}
/* Only one of the two is ever shown. */
.hero-logo-still { display: none; }

@media (prefers-reduced-motion: reduce) {
  .hero-logo-video { display: none; }
  .hero-logo-still { display: block; }
}
/* One step darker than the --lm-ink-muted this line used on a flat cream page.
   Widely-tracked uppercase at 0.82rem is the most fragile text on the site, and
   it now sits over a photograph. */
.hero .essence {
  font-family: var(--display);
  font-size: 0.82rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--lm-ink-soft);
  margin-top: 22px;
}
.hero .lede { margin: 24px auto 0; max-width: 620px; }
/* The secondary button is a transparent pill everywhere else on the site, which
   works on a flat background and does not over a photograph — the label loses
   its ground and the border disappears into whatever is behind it. In the hero
   only, give it one back. */
.hero .btn-secondary {
  background: rgba(250, 247, 242, 0.86);
  border-color: var(--lm-ink-muted);
}
.hero .btn-secondary:hover { background: var(--lm-linen); }
.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  margin-top: 34px;
}

/* --- program ------------------------------------------------------------- */

.pillar-label {
  font-family: var(--display);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--lm-gold-deep);
  margin-bottom: 10px;
}
/* A supporting line under a section's h2. Deliberately not a second h2 — two
   equal-weight headlines read as two sections to a screen reader and flatten
   the visual hierarchy on the page. */
.section-subhead {
  font-family: var(--display);
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--lm-gold-deep);
  margin-top: -6px;
  margin-bottom: 14px;
}

/* --- schedule ------------------------------------------------------------ */

.week { display: grid; gap: 12px; }
.day {
  display: grid;
  grid-template-columns: 132px 1fr;
  gap: 18px;
  align-items: start;
  padding: 18px 0;
  border-top: 1px solid var(--lm-border);
}
.day:last-child { border-bottom: 1px solid var(--lm-border); }
.day-name {
  font-family: var(--display);
  font-size: 0.98rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--lm-ink);
  padding-top: 2px;
}
.classes { display: grid; gap: 10px; }
.class-row {
  display: grid;
  grid-template-columns: 108px 1fr auto;
  gap: 14px;
  align-items: baseline;
}
.class-time {
  font-variant-numeric: tabular-nums;
  color: var(--lm-ink);
  font-size: 0.95rem;
}
.class-name { color: var(--lm-ink); }
.class-with { color: var(--lm-ink-muted); font-size: 0.9rem; }
@media (max-width: 640px) {
  .day { grid-template-columns: 1fr; gap: 10px; }
  .class-row { grid-template-columns: 92px 1fr; }
  .class-with { grid-column: 2; }
}

/* --- instructors --------------------------------------------------------- */

.instructor-photo {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: calc(var(--radius) - 4px);
  margin-bottom: 18px;
  background: var(--lm-sand);
}
.instructor-role {
  font-family: var(--display);
  font-size: 0.74rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--lm-gold-deep);
  margin: 6px 0 12px;
}
blockquote.instructor-quote {
  margin: 14px 0 0;
  padding-left: 14px;
  border-left: 2px solid var(--lm-gold);
  color: var(--lm-ink-soft);
  font-style: italic;
}

/* --- waitlist form ------------------------------------------------------- */

.signup { margin-top: clamp(28px, 4vw, 38px); max-width: 560px; }
.field-row { display: flex; flex-wrap: wrap; gap: 10px; }
.field-row input {
  flex: 1 1 220px;
  min-width: 0;
  font-family: var(--body);
  font-size: 1rem;
  color: var(--lm-ink);
  background: var(--lm-linen);
  border: 1px solid var(--lm-border);
  border-radius: 999px;
  padding: 12px 20px;
}
.band-linen .field-row input { background: var(--lm-bone); }
.field-row input::placeholder { color: var(--lm-ink-muted); }
.field-row .btn { flex: 0 0 auto; }
.form-note { margin-top: 12px; }
.form-status { margin-top: 14px; font-size: 0.95rem; }
.form-status.ok { color: var(--lm-sage-deep); }
.form-status.err { color: #8c3a2b; }
/* Bot bait: a real person never sees or fills this. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

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

.detail-label {
  font-family: var(--display);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--lm-ink-muted);
  margin-bottom: 6px;
}
.detail a { color: var(--lm-ink-soft); text-decoration: none; border-bottom: 1px solid var(--lm-border); }
.detail a:hover { border-color: var(--lm-ink-soft); color: var(--lm-ink); }

.site-footer {
  border-top: 1px solid var(--lm-border);
  background: var(--lm-linen);
  padding: 40px 0;
}
.site-footer .container {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
}
.socials { display: flex; gap: 14px; align-items: center; }
.socials a {
  display: inline-flex;
  color: var(--lm-ink-soft);
  text-decoration: none;
}
.socials a:hover { color: var(--lm-ink); }
.socials svg { width: 20px; height: 20px; fill: currentColor; }
.footer-links { display: flex; gap: 16px; flex-wrap: wrap; }
.footer-links a { color: var(--lm-ink-muted); text-decoration: none; font-size: 0.88rem; }
.footer-links a:hover { color: var(--lm-ink); }
