/* Edgefall Studios — Foundry VTT module studio launchpad */

/* ---------- Webfont ---------- */

@font-face {
  font-family: "Cinzel";
  font-style: normal;
  font-weight: 600 800;
  font-display: swap;
  src: url("/fonts/cinzel-latin-600-700.woff2") format("woff2");
}

/* ---------- Tokens ---------- */

:root {
  --bg: #08090d;
  --bg-elevated: #0c0f15;
  --bg-card: #11141c;
  --bg-deep: #050609;

  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.16);
  --border-warm: rgba(245, 158, 11, 0.22);

  --text: #f5f6f9;
  --text-strong: #ffffff;
  --muted: #8b94a5;
  --muted-strong: #c0c8d4;

  /* Single warm palette — torch + parchment, no cold accents.
     --accent* are kept as semantic tokens (the "interactive accent"
     family) but point at warm hues so every existing rule that
     references them inherits the new look. */
  --accent: #fbbf24;
  --accent-strong: #f59e0b;
  --accent-dim: rgba(245, 158, 11, 0.14);

  --warm: #f59e0b;
  --warm-strong: #fbbf24;
  --warm-deep: #92400e;
  --warm-dim: rgba(245, 158, 11, 0.16);
  --parchment: #fbe8c4;

  --radius: 14px;
  --radius-lg: 18px;
  --shadow: 0 24px 80px rgba(0, 0, 0, 0.55);
  --shadow-glow: 0 24px 60px -16px rgba(245, 158, 11, 0.18);

  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji";
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;
  --font-serif: "Cinzel", "EB Garamond", "Cormorant Garamond", Garamond,
    "Times New Roman", serif;

  --letterbox: clamp(40px, 7vh, 84px);
  --reveal-delay: 0ms;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 17px;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  background-image:
    radial-gradient(ellipse 60rem 40rem at 80% -10%, var(--warm-dim), transparent 60%),
    radial-gradient(ellipse 70rem 50rem at 0% 12%, var(--accent-dim), transparent 65%);
  background-attachment: fixed;
  overflow-x: hidden;
}

a {
  color: var(--accent);
  text-decoration: none;
  transition: color 120ms ease;
}

a:hover {
  color: var(--accent-strong);
}

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg-card);
  color: var(--text);
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  z-index: 1000;
}

.skip:focus {
  left: 1rem;
  top: 1rem;
}

.wrap {
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2rem);
}

.muted { color: var(--muted); }
.small { font-size: 0.875rem; }

/* ---------- Reveal animation ---------- */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 800ms cubic-bezier(0.16, 0.84, 0.32, 1) var(--reveal-delay),
    transform 800ms cubic-bezier(0.16, 0.84, 0.32, 1) var(--reveal-delay);
  will-change: opacity, transform;
}

.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---------- Header ---------- */

.site-header {
  border-bottom: 1px solid var(--border);
  background: rgba(8, 9, 13, 0.65);
  backdrop-filter: saturate(150%) blur(10px);
  -webkit-backdrop-filter: saturate(150%) blur(10px);
  position: sticky;
  top: 0;
  z-index: 50;
}

.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  /* Use padding-block (vertical only) so the parent .wrap's horizontal
     padding survives — the previous shorthand `0.875rem 0` was zeroing
     it out and pinning the brand to the viewport edge. */
  padding-block: 0.875rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  color: var(--text);
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.005em;
}

.brand:hover { color: var(--text); }

/* Adapted from the parent Edgefall LLC mark (brand/icons/edgefall-mark.svg)
   — same E/F geometry, recolored from teal to the warm Studios palette so
   the mark sits beside the full "Edgefall Studios" wordmark. We tried a
   mark-only-as-Edgefall + divider + "Studios" treatment first; it read as
   "E | Studios" to anyone not already familiar with the parent mark.
   Spelling out "Edgefall" alongside the mark keeps the wordmark legible
   and the orange "Studios" still pops as the sub-brand identifier. */
.brand-mark {
  display: inline-flex;
  align-items: center;
  width: 22px;
  height: 26px;
}
.brand-mark svg {
  width: 100%;
  height: 100%;
  display: block;
}
.brand-mark-top { fill: var(--warm-strong); }
.brand-mark-bot { fill: var(--warm); }

/* Inline wrapper so the literal space between the two brand-name spans
   collapses naturally and "Edgefall Studios" reads as one phrase. The
   parent .brand uses a flex `gap` between the mark and this wrapper, so
   the inner word-spacing is handled here by ordinary inline rendering. */
.brand-text {
  display: inline;
  white-space: nowrap;
}

.brand-name { font-weight: 700; }
.brand-name-accent {
  background: linear-gradient(90deg, var(--warm-strong), var(--warm-deep));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.site-nav {
  display: flex;
  gap: 1.5rem;
  font-size: 0.95rem;
}

.site-nav a {
  color: var(--muted-strong);
  font-weight: 500;
}

.site-nav a:hover,
.site-nav a:focus-visible { color: var(--text); }

/* ---------- Mobile nav (hamburger + slide-down sheet) ----------
   Desktop (>640px): the hamburger is hidden, the nav renders inline
   in the header row exactly as before.

   Mobile (<=640px): the hamburger replaces the inline links, and
   .site-nav becomes a fixed slide-down sheet anchored under the
   header. JS sets `data-nav-open` on <body> to open/close; ARIA
   state is mirrored on the toggle. CSS handles transition + scroll
   lock so the sheet is accessible even before main.js boots
   (toggle just sits there as a labeled button — no broken link). */

.nav-toggle {
  display: none;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0.5rem;
  margin: -0.5rem -0.5rem -0.5rem 0;
  cursor: pointer;
  color: var(--muted-strong);
  transition: color 200ms ease, border-color 200ms ease,
    background-color 200ms ease;
}

.nav-toggle:hover,
.nav-toggle:focus-visible {
  color: var(--text);
  border-color: var(--border);
  background-color: rgba(251, 191, 36, 0.04);
  outline: none;
}

.nav-toggle[aria-expanded="true"] {
  color: var(--warm-strong);
}

.nav-toggle-bars {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  width: 22px;
  height: 16px;
  gap: 5px;
}

.nav-toggle-bars span {
  display: block;
  height: 2px;
  width: 100%;
  background: currentColor;
  border-radius: 2px;
  transform-origin: center;
  transition: transform 240ms ease, opacity 180ms ease;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(2) {
  opacity: 0;
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
  .nav-toggle-bars span { transition: none; }
}

@media (max-width: 640px) {
  .nav-toggle { display: inline-flex; align-items: center; }

  /* CRITICAL: drop `backdrop-filter` on the header at mobile widths.
     Per CSS spec, any non-`none` `backdrop-filter` makes the element a
     containing block for `position: fixed` descendants — which means
     the slide-down `.site-nav` sheet below would resolve `top` and
     `bottom` against the 55px header instead of the viewport, and
     collapse to zero height. Replacing the blur with a slightly more
     opaque solid keeps the sticky header reading well over content. */
  .site-header {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(8, 9, 13, 0.92);
  }

  /* Sheet anchored under the sticky header. JS sets --header-h on
     the root from the live header height so this stays accurate if
     the header ever changes size. Falls back to 56px which matches
     the current desktop+mobile header (border included). */
  .site-nav {
    position: fixed;
    top: var(--header-h, 56px);
    left: 0;
    right: 0;
    bottom: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 1rem 1.25rem 2rem;
    background: rgba(8, 9, 13, 0.96);
    backdrop-filter: saturate(150%) blur(14px);
    -webkit-backdrop-filter: saturate(150%) blur(14px);
    border-top: 1px solid var(--border);
    z-index: 49;
    opacity: 0;
    transform: translateY(-6px);
    pointer-events: none;
    visibility: hidden;
    transition: opacity 200ms ease, transform 240ms ease,
      visibility 0s linear 240ms;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  body[data-nav-open="true"] .site-nav {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    visibility: visible;
    transition: opacity 200ms ease, transform 240ms ease,
      visibility 0s linear 0s;
  }

  .site-nav a {
    display: block;
    padding: 1rem 0.25rem;
    font-family: var(--font-serif);
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: 0.005em;
    color: var(--text);
    border-bottom: 1px solid var(--border);
    transition: color 180ms ease, padding-left 180ms ease;
  }

  .site-nav a:hover,
  .site-nav a:focus-visible {
    color: var(--warm-strong);
    padding-left: 0.5rem;
  }

  .site-nav a:last-child { border-bottom: none; }

  /* Lock the page scroll behind the sheet so flicking the menu
     doesn't drag the hero through. */
  body[data-nav-open="true"] {
    overflow: hidden;
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-nav { transition: opacity 0s, transform 0s, visibility 0s; }
  .site-nav a { transition: none; }
}

/* ---------- Buttons (forged plates) ----------

   The buttons are styled to read as physical, crafted objects rather
   than browser chrome — the difference matters in the hero where
   moving embers behind a transparent or glassy button would
   otherwise eat the label. Each `.btn` is built up from:

   1. A fully OPAQUE multi-stop background gradient (subtle top
      highlight, mid plate body, bottom shadow) so embers, video,
      and warm glow behind the button never bleed through and chew
      the type.
   2. A 1px outer border in a warm bronze tone — the "rim" of the
      plate.
   3. A faint inset border 5px inboard via `::after`, creating the
      "double-edged" look common in TTRPG menu plates.
   4. SVG corner glyphs in the top-left and bottom-right via
      `::before` — small angle brackets that read as forge-stamped
      ornament without crossing into pastiche.
   5. Cinzel + uppercase + heavy letter-spacing matching the hero
      title's typographic family so the buttons feel like part of
      the same masthead, not a generic web button bolted on.
   6. A ground-shadow drop (no transform on hover for accessibility
      / reduced-motion calm) plus a hover brightening that warms the
      rim and lifts the inner glow without bouncing the element. */

.btn {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.95rem 1.7rem 0.9rem;
  font-family: var(--font-serif);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  border-radius: 3px;
  border: 1px solid var(--border-warm);
  background:
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.05) 0%,
      rgba(255, 255, 255, 0.01) 8%,
      transparent 16%,
      transparent 84%,
      rgba(0, 0, 0, 0.30) 100%),
    linear-gradient(180deg, var(--bg-card) 0%, var(--bg-deep) 100%);
  color: var(--text-strong);
  box-shadow:
    inset 0 1px 0 rgba(245, 158, 11, 0.18),
    inset 0 -1px 0 rgba(0, 0, 0, 0.55),
    0 8px 22px -10px rgba(0, 0, 0, 0.75),
    0 2px 4px rgba(0, 0, 0, 0.4);
  transition: border-color 200ms ease, color 200ms ease,
    box-shadow 240ms ease, background 200ms ease;
}

/* Inset secondary border — the inner edge of the forged plate. */
.btn::after {
  content: "";
  position: absolute;
  inset: 4px;
  border: 1px solid rgba(245, 158, 11, 0.10);
  border-radius: 1px;
  pointer-events: none;
  z-index: 0;
}

/* Corner glyphs — four right-angle brackets stamped into each
   corner of the plate. Each corner is rendered by its own
   `background-image` layer (an inline SVG data-URI with a single L-
   path), pinned to its corner of the inset region with a fixed pixel
   size so the brackets stay crisp at every button width. The 0.7
   opacity reads as "etched, not painted" — visible without competing
   with the label. */
.btn::before {
  content: "";
  position: absolute;
  inset: 5px;
  pointer-events: none;
  z-index: 0;
  background-image:
    url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M0 9 V0 H9' fill='none' stroke='%23fbbf24' stroke-width='1.2' opacity='0.7'/%3E%3C/svg%3E"),
    url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M16 9 V0 H7' fill='none' stroke='%23fbbf24' stroke-width='1.2' opacity='0.7'/%3E%3C/svg%3E"),
    url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M0 7 V16 H9' fill='none' stroke='%23fbbf24' stroke-width='1.2' opacity='0.7'/%3E%3C/svg%3E"),
    url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M16 7 V16 H7' fill='none' stroke='%23fbbf24' stroke-width='1.2' opacity='0.7'/%3E%3C/svg%3E");
  background-position:
    top 0 left 0,
    top 0 right 0,
    bottom 0 left 0,
    bottom 0 right 0;
  background-size: 16px 16px;
  background-repeat: no-repeat;
}

.btn > * { position: relative; z-index: 1; }

.btn:hover,
.btn:focus-visible {
  border-color: rgba(245, 158, 11, 0.55);
  color: var(--warm-strong);
  box-shadow:
    inset 0 1px 0 rgba(245, 158, 11, 0.32),
    inset 0 -1px 0 rgba(0, 0, 0, 0.55),
    0 12px 30px -10px rgba(245, 158, 11, 0.30),
    0 2px 6px rgba(0, 0, 0, 0.5);
  outline: none;
}

/* Primary plate: warm bronze body with a hotter rim. The Patreon CTA
   wears this when it eventually goes live. */
.btn-primary {
  background:
    linear-gradient(180deg,
      rgba(255, 220, 150, 0.16) 0%,
      rgba(255, 220, 150, 0.04) 10%,
      transparent 18%,
      transparent 82%,
      rgba(0, 0, 0, 0.35) 100%),
    linear-gradient(180deg, #2c1c0e 0%, #1a1106 100%);
  color: var(--warm-strong);
  border-color: rgba(245, 158, 11, 0.42);
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 150, 0.32),
    inset 0 -1px 0 rgba(0, 0, 0, 0.6),
    0 8px 22px -10px rgba(245, 158, 11, 0.30),
    0 2px 4px rgba(0, 0, 0, 0.45);
}

.btn-primary:hover,
.btn-primary:focus-visible {
  border-color: rgba(251, 191, 36, 0.7);
  color: #fff5d6;
  background:
    linear-gradient(180deg,
      rgba(255, 220, 150, 0.22) 0%,
      rgba(255, 220, 150, 0.06) 10%,
      transparent 18%,
      transparent 82%,
      rgba(0, 0, 0, 0.4) 100%),
    linear-gradient(180deg, #3a2511 0%, #211408 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 150, 0.45),
    inset 0 -1px 0 rgba(0, 0, 0, 0.6),
    0 14px 34px -10px rgba(245, 158, 11, 0.50),
    0 2px 6px rgba(0, 0, 0, 0.5);
}

/* Ghost plate: cool stone body, same forged language. Used for the
   "Foundry modules" anchor — solid enough to keep the label readable
   over any background, including animated embers. */
.btn-ghost {
  color: var(--text-strong);
}

/* The ghost variant inherits the base `.btn` plate; this rule exists
   only to allow future divergence from primary without re-stating
   the whole base treatment. The `:hover` warms the rim like the
   base hover. */

/* Disabled / "coming soon" plate: same plate body as primary, but
   the inner glyphs and rim sit muted and the cursor reads
   not-allowed. We keep the warm gradient (so users still understand
   "this is THE primary CTA, just not live yet") but desaturate it
   so it doesn't pretend to be clickable. */
.btn-disabled {
  cursor: not-allowed;
  color: rgba(251, 191, 36, 0.85);
  border-color: rgba(245, 158, 11, 0.28);
  background:
    linear-gradient(180deg,
      rgba(255, 220, 150, 0.08) 0%,
      rgba(255, 220, 150, 0.02) 10%,
      transparent 18%,
      transparent 82%,
      rgba(0, 0, 0, 0.4) 100%),
    linear-gradient(180deg, #1d1308 0%, #110a04 100%);
  box-shadow:
    inset 0 1px 0 rgba(245, 158, 11, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.55),
    0 6px 16px -10px rgba(0, 0, 0, 0.7);
}

.btn-disabled:hover,
.btn-disabled:focus-visible {
  color: rgba(251, 191, 36, 0.85);
  border-color: rgba(245, 158, 11, 0.28);
  background:
    linear-gradient(180deg,
      rgba(255, 220, 150, 0.08) 0%,
      rgba(255, 220, 150, 0.02) 10%,
      transparent 18%,
      transparent 82%,
      rgba(0, 0, 0, 0.4) 100%),
    linear-gradient(180deg, #1d1308 0%, #110a04 100%);
  box-shadow:
    inset 0 1px 0 rgba(245, 158, 11, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.55),
    0 6px 16px -10px rgba(0, 0, 0, 0.7);
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  min-height: 92vh;
  display: flex;
  align-items: center;
  padding: clamp(7rem, 14vh, 10rem) 0 clamp(5rem, 10vh, 7rem);
  overflow: hidden;
  isolation: isolate;
}

/* Mobile + portrait-tablet hero is reframed as a vertical poster: the
   video + flame own the upper portion, and the headline + sub + CTAs
   anchor to the lower portion on their own dark "shelf" so the
   swelling flame ribbon never collides with the type at any point in
   the loop (the source video animates the flame larger and smaller
   over its 8s cycle). The bottom-darkening gradient in
   `.hero-vignette` below seats the text band; `align-items: flex-end`
   pushes the column to the floor of the hero.

   `min-height: auto` (not a viewport unit) lets the hero size itself
   to the content + padding rather than stretching to a fixed
   percentage of the viewport. This eliminates the "empty void"
   between the flame and the text on tall viewports (Safari address
   bar, desktop emulators) — the hero stays compact on every device
   and the composition reads as a single unit. The padding-top still
   reserves enough space above the headline for the flame to breathe
   in the upper portion. */
@media (max-width: 768px) {
  .hero {
    min-height: auto;
    align-items: flex-end;
    padding: clamp(20rem, 50vh, 32rem) 0 clamp(2.5rem, 6vh, 4rem);
  }
}

.hero-stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--bg-deep);
}

.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Source is a tracking shot: hooded figure (left of frame) holding a
     wooden torch (right of frame) with a tall ribbon flame, walking
     through a dark cave. Mirror-flipped at encode time so the bright
     flame sits on the right and the dark silhouette covers the left
     half — that left half is where the headline + sub + CTAs land,
     and the silhouette gives them a naturally dark backdrop without
     hiding the flame.  Object-position is biased upward (40%) so on
     wide viewports the cover-fit crop preserves the full flame
     ribbon at the top instead of clipping it off. */
  object-position: center 40%;
  opacity: 0;
  transition: opacity 1.6s ease;
  filter: saturate(110%) contrast(104%);
}

.hero-video.is-ready { opacity: 1; }

/* On portrait / narrow viewports, object-fit: cover height-scales the
   16:9 source and crops it horizontally — and the default `center`
   x-position hides the flame entirely (it sits in the right ~25% of
   the frame). Bias the crop window toward the right so the torch
   stays in shot. The headline doesn't extend past ~60% of viewport
   width at clamp-min sizes, and the gradient vignette still keeps
   the left-side text legible. Same shift mirrored on the
   reduced-motion poster fallback below. */
/* On portrait / narrow viewports the 16:9 source gets height-fit and
   the horizontal crop window slides to bias the flame into shot.
   `85% 28%` anchors the flame ribbon in the upper-right while
   keeping enough of the torch handle visible to read as "torch in
   the cave" rather than just "fire" — the diagonal from the flame
   down to the lower-left handle gives the composition motion that
   plays well against the bottom-anchored type. */
@media (max-width: 800px), (max-aspect-ratio: 1/1) {
  .hero-video {
    object-position: 85% 28%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-video {
    display: none;
  }
  .hero-stage {
    background-image:
      linear-gradient(90deg, rgba(5,6,9,0.85) 0%, rgba(5,6,9,0.55) 35%, rgba(5,6,9,0.15) 60%, transparent 80%),
      linear-gradient(180deg, rgba(5,6,9,0.5) 0%, transparent 22%, transparent 75%, rgba(5,6,9,0.9) 100%),
      url("/media/hero-poster.jpg");
    background-size: cover, cover, cover;
    background-position: center, center, center 40%;
  }
}

/* Mirror the .hero-video crop shift on the reduced-motion poster
   fallback, so users with motion disabled also see the torch on
   narrow viewports instead of the dark middle of the cave. */
@media (prefers-reduced-motion: reduce) and (max-width: 800px),
       (prefers-reduced-motion: reduce) and (max-aspect-ratio: 1/1) {
  .hero-stage {
    background-position: center, center, 85% 28%;
  }
}

.hero-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* Left-side darkening — pushes the dark silhouette darker so the
       headline + CTAs (which live in a max-width:56rem column anchored
       left) stay legible without a flat black scrim across the whole
       hero. Fades to transparent past ~60% so the torch flame on the
       right side stays bright and uncut. */
    linear-gradient(90deg, rgba(5,6,9,0.78) 0%, rgba(5,6,9,0.55) 28%, rgba(5,6,9,0.18) 55%, transparent 75%),
    /* Cinematic top + bottom edge falloff. Keeps the bottom strong so
       the section break into the next block reads, and adds just
       enough darkening at the very top to seat the floating nav. */
    linear-gradient(180deg, rgba(5,6,9,0.55) 0%, transparent 18%, transparent 70%, rgba(5,6,9,0.92) 100%),
    /* Soft outer vignette focused right-of-center where the flame is,
       so the eye is drawn to the bright spot rather than the corners. */
    radial-gradient(ellipse 90% 75% at 68% 45%, transparent 0%, rgba(5,6,9,0.30) 65%, rgba(5,6,9,0.75) 100%);
}

/* Mobile vignette: replace the desktop comp with a top-of-frame =
   flame, bottom-of-frame = clean dark text shelf. The 180deg gradient
   below builds that shelf — transparent over the upper ~45% (so the
   flame stays bright), then a soft step into deep dark over the lower
   ~55% so the headline column has a dedicated, undisturbed backdrop
   regardless of how big the flame ribbon swells in any given frame
   of the loop. */
/* Mobile vignette stack (rendered top-to-bottom by the browser, but
   listed in `background:` from front-most to back-most):
   1. A faint warm radial centred on the headline area — borrows
      colour from the flame and lays it under the title so the type
      feels like it's catching firelight rather than floating in
      isolated darkness.
   2. The 180deg darkener — top half stays transparent so the flame +
      embers read at full saturation, then a firm step into deep dark
      across the lower half builds the dedicated "text shelf."
   3. The corner radial — pulls the brightest region toward the
      upper-right so the flame anchors there compositionally rather
      than sprawling diagonally across the frame. */
@media (max-width: 768px) {
  .hero-vignette {
    background:
      radial-gradient(
        ellipse 90% 28% at 30% 72%,
        rgba(245, 158, 11, 0.07) 0%,
        rgba(245, 158, 11, 0.03) 45%,
        transparent 80%
      ),
      linear-gradient(
        180deg,
        transparent 0%,
        transparent 38%,
        rgba(5,6,9,0.45) 50%,
        rgba(5,6,9,0.82) 65%,
        rgba(5,6,9,0.95) 80%,
        rgba(5,6,9,0.98) 100%
      ),
      radial-gradient(
        ellipse 65% 45% at 85% 22%,
        transparent 0%,
        rgba(5,6,9,0.10) 55%,
        rgba(5,6,9,0.45) 90%,
        rgba(5,6,9,0.65) 100%
      );
  }
}

.hero-torch {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(
    circle 220px at var(--x, 50%) var(--y, 50%),
    rgba(245, 158, 11, 0.16) 0%,
    rgba(245, 158, 11, 0.06) 30%,
    transparent 70%
  );
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 600ms ease;
}

.hero-torch.is-active { opacity: 1; }

@media (max-width: 800px), (prefers-reduced-motion: reduce) {
  .hero-torch { display: none; }
}

.hero-embers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s ease 600ms;
  mix-blend-mode: screen;
}

.hero-embers.is-active { opacity: 0.85; }

@media (prefers-reduced-motion: reduce) {
  .hero-embers { display: none; }
}

/* Letterbox bars */
.letterbox {
  position: absolute;
  left: 0;
  right: 0;
  height: var(--letterbox);
  background: #000;
  z-index: 2;
  transition: transform 1400ms cubic-bezier(0.5, 0.0, 0.2, 1) 700ms;
  will-change: transform;
}

.letterbox-top { top: 0; transform: translateY(0); }
.letterbox-bottom { bottom: 0; transform: translateY(0); }

.hero.is-opened .letterbox-top { transform: translateY(-100%); }
.hero.is-opened .letterbox-bottom { transform: translateY(100%); }

@media (prefers-reduced-motion: reduce) {
  .letterbox-top { transform: translateY(-100%); }
  .letterbox-bottom { transform: translateY(100%); }
  .letterbox { transition: none; }
}

.hero-inner {
  position: relative;
  z-index: 3;
  max-width: 56rem;
}

.hero-title {
  margin: 0 0 1.5rem;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: clamp(2.6rem, 8.5vw, 6rem);
  line-height: 1.02;
  letter-spacing: 0.005em;
  color: var(--text-strong);
  /* Cinzel renders wide — guard against narrow phones (≤320px) where
     the desktop clamp-min would otherwise clip the headline against
     the right edge of the viewport. The mobile rule below tunes
     down the font-size; this is the catch-all overflow safety. */
  overflow-wrap: break-word;
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.6),
    0 0 80px rgba(245, 158, 11, 0.04);
}

/* Mobile / portrait-tablet: scale the headline down so it wraps as a
   tidy two-line poster — "Premium modules" / "for Foundry." — on
   every modern phone viewport down to ~320px (Galaxy S9+ and
   similar narrow devices) without ever splitting "Premium" off
   onto a lonely top line. The desktop clamp re-engages above 768px.

   The min is intentionally lower than 9vw at 320px (28.8px) so the
   9vw scaler controls size at common phone widths (360-414px =
   32-37px, identical to the previous min: 2rem ramp), and the lower
   floor only engages on viewports narrower than 320px where the
   title would otherwise clip. */
@media (max-width: 768px) {
  .hero-title {
    font-size: clamp(1.5rem, 9vw, 2.6rem);
  }
}

.hero-title-accent {
  background: linear-gradient(180deg, var(--warm-strong) 0%, var(--warm) 60%, var(--warm-deep) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-style: normal;
}

.hero-sub {
  margin: 0 0 1.4rem;
  font-size: clamp(1.0625rem, 1.6vw, 1.2rem);
  line-height: 1.55;
  color: var(--muted-strong);
  max-width: 38rem;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
  align-items: center;
  margin: 0;
}

/* Scroll cue */
.scroll-cue {
  position: absolute;
  bottom: calc(var(--letterbox) + 1.25rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 4;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  color: var(--muted-strong);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.85;
  transition: opacity 200ms ease, color 200ms ease;
}

.scroll-cue:hover {
  opacity: 1;
  color: var(--accent);
}

.scroll-cue-line {
  display: block;
  width: 1px;
  height: 36px;
  background: linear-gradient(180deg, transparent, currentColor);
  position: relative;
  overflow: hidden;
}

.scroll-cue-line::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent, var(--warm));
  transform: translateY(-100%);
  animation: scroll-cue 2.2s ease-in-out infinite;
}

@keyframes scroll-cue {
  0% { transform: translateY(-100%); }
  60%, 100% { transform: translateY(100%); }
}

/* Hidden in production: the candle in the hero already pulls the eye
   downward and an additional vertical line indicator was visually
   cutting through the flame. Keeping the markup so the
   reduced-motion fallback selectors don't break and so we can
   re-enable later if the hero composition changes. */
.scroll-cue { display: none; }

@media (prefers-reduced-motion: reduce) {
  .scroll-cue-line::after { animation: none; opacity: 0; }
}

/* ---------- Rune divider ---------- */

.rune-divider {
  position: relative;
  width: 100%;
  padding: clamp(2rem, 4vw, 3rem) 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

.rune {
  width: min(720px, 90%);
  height: auto;
  display: block;
  overflow: visible;
  color: var(--warm);
}

.rune-line {
  fill: none;
  stroke: rgba(245, 158, 11, 0.4);
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-dasharray: 360;
  stroke-dashoffset: 360;
  transition: stroke-dashoffset 1500ms cubic-bezier(0.5, 0.05, 0.2, 1);
}

.rune-glyph {
  fill: rgba(245, 158, 11, 0.55);
  stroke: rgba(245, 158, 11, 0.85);
  stroke-width: 1.25;
  transform-origin: 400px 12px;
  transform-box: view-box;
  transform: scale(0);
  transition: transform 800ms cubic-bezier(0.34, 1.56, 0.64, 1) 400ms,
    fill 1200ms ease;
  filter: drop-shadow(0 0 6px rgba(245, 158, 11, 0.5));
}

.rune-divider.is-visible .rune-line { stroke-dashoffset: 0; }
.rune-divider.is-visible .rune-glyph { transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  .rune-line { stroke-dashoffset: 0; transition: none; }
  .rune-glyph { transform: scale(1); transition: none; }
}

/* ---------- Section ---------- */

.section {
  padding: clamp(4.5rem, 11vw, 7rem) 0;
  position: relative;
}

.section-elevated {
  background:
    linear-gradient(180deg, rgba(245, 158, 11, 0.04) 0%, transparent 30%),
    var(--bg-elevated);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.section-kicker {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--accent-dim);
  background: rgba(245, 158, 11, 0.05);
  border-radius: 999px;
  margin: 0 0 1.5rem;
}

.section-title {
  margin: 0 0 1.5rem;
  font-family: var(--font-serif);
  font-size: clamp(1.875rem, 4.4vw, 3rem);
  line-height: 1.12;
  letter-spacing: 0.005em;
  font-weight: 700;
  max-width: 50rem;
  color: var(--text-strong);
}

.section-lede {
  margin: 0 0 2.5rem;
  font-size: clamp(1rem, 1.4vw, 1.125rem);
  line-height: 1.6;
  color: var(--muted-strong);
  max-width: 50rem;
}

/* ---------- Modules (empty state) ---------- */

.modules-inner {
  max-width: 56rem;
}

/* The modules section is intentionally an empty/placeholder state
   on launch — per brand SOP we don't tease specific upcoming
   modules or name module categories in evergreen copy. The dashed
   warm border + amber status dot reads as "coming soon" without
   needing decoration that would imply unannounced product. */
.module-empty {
  margin-top: 1.5rem;
  padding: clamp(1.75rem, 4vw, 2.5rem);
  background:
    linear-gradient(180deg, rgba(245, 158, 11, 0.05) 0%, transparent 60%),
    var(--bg-card);
  border: 1px dashed rgba(245, 158, 11, 0.32);
  border-radius: var(--radius-lg);
  text-align: center;
}

.module-empty-status {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 0.85rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--warm-strong);
}

.module-empty-line {
  margin: 0 auto;
  max-width: 38rem;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--muted-strong);
}

/* ---------- Studio (manifesto) ---------- */

.studio-inner {
  max-width: 52rem;
}

.manifesto {
  margin: 0 0 1.5rem;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(1.375rem, 2.6vw, 1.85rem);
  line-height: 1.35;
  color: var(--text-strong);
  letter-spacing: 0.005em;
}

.manifesto + .manifesto {
  margin-top: 1.5rem;
  font-weight: 600;
  color: var(--muted-strong);
  font-size: clamp(1.125rem, 2.1vw, 1.5rem);
}

.drop-cap {
  font-size: 1.55em;
  line-height: 1;
  font-weight: 700;
  background: linear-gradient(180deg, var(--warm-strong), var(--warm-deep));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  padding-right: 0.05em;
}

/* ---------- Stay close (channels) ---------- */

.stay-close-inner {
  max-width: 56rem;
}

.channel-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.75rem;
}

.channel {
  display: grid;
  /* Column 2 is `auto` (not `1fr`) so it sizes to the widest piece of
     status content across all rows — currently the email link
     `hello@edgefallstudios.com`. With `1fr` the email overflowed its
     track and butted up against the description in column 3 with no
     visible gap; `auto` reserves the natural width and the grid `gap`
     stays intact between status and description. */
  grid-template-columns: minmax(7rem, 9rem) auto minmax(0, 1fr);
  align-items: center;
  gap: 1.25rem 1.75rem;
  padding: 1.1rem 1.4rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 240ms ease, transform 240ms ease;
}

.channel:hover {
  border-color: var(--border-strong);
  transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
  .channel:hover { transform: none; }
}

.channel-name {
  margin: 0;
  font-family: var(--font-serif);
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-strong);
}

.channel-status {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--muted-strong);
  text-transform: uppercase;
}

.channel-status-link {
  text-transform: none;
  letter-spacing: 0;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--accent);
}

.channel-status-link:hover { color: var(--accent-strong); }

.channel-line {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--muted-strong);
}

@media (max-width: 720px) {
  .channel {
    grid-template-columns: 1fr;
    gap: 0.4rem;
    padding: 1rem 1.1rem;
  }
}

/* ---------- Footer ---------- */

.site-footer {
  border-top: 1px solid var(--border);
  background: rgba(5, 6, 9, 0.85);
  padding: 3rem 0 1.5rem;
  margin-top: 2rem;
}

.footer-inner {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 2rem;
}

@media (max-width: 720px) {
  .footer-inner { grid-template-columns: 1fr; }
}

.footer-brand {
  margin: 0 0 0.5rem;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.25rem;
  background: linear-gradient(90deg, var(--warm-strong), var(--warm-deep));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  display: inline-block;
  letter-spacing: 0.01em;
}

.footer-line {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.55;
  max-width: 28rem;
}

.footer-heading {
  margin: 0 0 0.625rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.footer-link {
  display: block;
  color: var(--muted-strong);
  font-size: 0.95rem;
  padding: 0.2rem 0;
}

.footer-link:hover { color: var(--accent); }

.footer-fineprint {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

/* ---------- Legal pages ---------- */

.legal-page {
  padding: clamp(3rem, 8vw, 5rem) 0;
}

.legal-page .wrap { max-width: 48rem; }

.legal-page h1 {
  margin: 0 0 0.5rem;
  font-family: var(--font-serif);
  font-size: clamp(2rem, 4.4vw, 2.75rem);
  line-height: 1.15;
  letter-spacing: 0.005em;
  font-weight: 700;
  color: var(--text-strong);
}

.legal-page .lastmod {
  margin: 0 0 2.5rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--muted);
}

.legal-page h2 {
  margin: 2.5rem 0 0.75rem;
  font-family: var(--font-serif);
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: 0.005em;
  color: var(--text-strong);
}

.legal-page p,
.legal-page li {
  color: var(--muted-strong);
  line-height: 1.65;
}

.legal-page p { margin: 0 0 1rem; }

.legal-page ul {
  margin: 0 0 1.5rem;
  padding-left: 1.25rem;
}

.legal-page li { margin-bottom: 0.5rem; }

.legal-page a {
  text-decoration: underline;
  text-decoration-color: var(--accent-dim);
  text-underline-offset: 3px;
}

.legal-page a:hover { text-decoration-color: var(--accent); }

/* ---------- 404 ---------- */

.error-page {
  position: relative;
  min-height: calc(100vh - 6rem);
  display: grid;
  place-items: center;
  padding: 4rem 0;
  isolation: isolate;
}

.error-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(ellipse 30rem 20rem at 50% 50%, rgba(245, 158, 11, 0.08), transparent 70%);
}

.error-page .wrap {
  text-align: center;
  max-width: 32rem;
}

.error-code {
  font-family: var(--font-serif);
  font-size: clamp(5rem, 14vw, 9rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.01em;
  background: linear-gradient(180deg, var(--warm-strong) 0%, var(--warm-deep) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin: 0 0 0.5rem;
  text-shadow: 0 0 80px rgba(245, 158, 11, 0.15);
}

.error-title {
  margin: 0 0 1rem;
  font-family: var(--font-serif);
  font-size: 1.65rem;
  font-weight: 700;
  color: var(--text-strong);
}

.error-body {
  color: var(--muted-strong);
  margin: 0 0 2rem;
  font-size: 1.05rem;
}
