/* ============================================================
   Tallyfall — site styles
   Palette: docs/V0_8_VISUAL_IDENTITY_AND_GAME_FEEL_BIBLE.md
   Type: Cormorant (display) + Montserrat (body) — echoes the
   elegant serif used in the game's marketing videos.
   ============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Cormorant:wght@400;500;600;700&family=Montserrat:wght@300;400;500;600;700&display=swap');

:root{
  /* brand */
  --bg:#0D0D12; --bg-2:#12121a; --panel:#181824; --panel-2:#1c1c2b;
  --gold:#D4AF37; --gold-soft:#c6a668; --gold-dim:rgba(212,175,55,.32);
  --risk:#C62828; --risk-hot:#FF3D00;
  --text:#ece7db; --muted:#9a948a; --line:rgba(212,175,55,.14);
  /* spacing scale */
  --s1:8px; --s2:16px; --s3:24px; --s4:40px; --s5:64px; --s6:96px;
  --radius:14px;
  --ease:cubic-bezier(.4,0,.2,1);
}

*{box-sizing:border-box}
/* overflow-x belongs on the root, not body: setting only one axis on body implicitly
   turns its own overflow-y into auto, which makes body (not the document/viewport) the
   real scroll container — and animation-timeline: scroll(root)/view() key off the
   document's scroller, so they silently never move when body is scrolling instead. */
html{scroll-behavior:smooth; -webkit-text-size-adjust:100%; overflow-x:hidden}
body{
  margin:0; background:var(--bg); color:var(--text);
  font-family:'Montserrat',-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
  font-size:16px; line-height:1.65; font-weight:400;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,.display{font-family:'Cormorant',Georgia,serif; font-weight:600; line-height:1.15}

a{color:var(--gold); text-decoration:none; transition:color .2s var(--ease)}
a:hover{color:#e8c85a}
a:focus-visible,button:focus-visible{outline:2px solid var(--gold); outline-offset:3px; border-radius:4px}
img{max-width:100%; display:block}

.wrap{max-width:1080px; margin:0 auto; padding:0 var(--s3)}
.narrow{max-width:720px}

/* ---------- scroll progress bar ---------- */
/* Static by default (no @supports match, no motion): a plain full-width line. Animated
   fill is added below, under scroll-driven animations. */
.scrollbar{
  position:fixed; top:0; left:0; z-index:60; height:3px; width:100%;
  background:var(--gold); transform-origin:0 50%;
  box-shadow:0 0 12px rgba(212,175,55,.45); pointer-events:none;
}

/* ---------- header ---------- */
header.site{
  position:sticky; top:0; z-index:50;
  background:rgba(13,13,18,.82); backdrop-filter:blur(12px);
  border-bottom:1px solid var(--line);
}
header.site .wrap{display:flex; align-items:center; gap:var(--s2); height:68px}
.brand{display:flex; align-items:center; gap:10px; letter-spacing:.22em; font-size:15px;
  font-weight:600; color:var(--gold); font-family:'Montserrat',sans-serif}
.brand img{width:34px; height:34px; border-radius:8px}
nav.main{margin-left:auto; display:flex; gap:var(--s2); font-size:14px; letter-spacing:.06em}
/* 44px minimum touch target (WCAG / iOS HIG): the label is only ~23px tall, so pad it out
   rather than relying on the text box itself. */
nav.main a{color:var(--muted); display:inline-flex; align-items:center;
  min-height:44px; padding:0 10px; border-radius:8px}
nav.main a:hover,nav.main a[aria-current]{color:var(--gold)}
nav.main a:hover{background:rgba(212,175,55,.06)}

/* ---------- hero ---------- */
.hero{
  position:relative; text-align:center; padding:var(--s6) 0 var(--s5);
  border-bottom:1px solid var(--line); overflow:hidden;
}
/* layered depth without WebGL: a slow radial glow behind the mark */
.hero:before{
  content:""; position:absolute; inset:-40% 0 auto; height:640px;
  background:radial-gradient(ellipse at 50% 40%, rgba(212,175,55,.13), transparent 62%);
  pointer-events:none;
}
.hero > *{position:relative}
.hero .mark{
  width:132px; height:132px; border-radius:30px; margin:0 auto;
  box-shadow:0 18px 60px rgba(212,175,55,.22), 0 4px 14px rgba(0,0,0,.6);
}
.hero h1{
  font-size:clamp(52px,10vw,104px); margin:var(--s3) 0 var(--s1);
  letter-spacing:.14em; color:var(--gold); font-weight:600;
}
.hero .tag{font-size:clamp(19px,2.6vw,26px); font-family:'Cormorant',serif; color:var(--text); margin:0 0 12px}
.hero .sub{color:var(--muted); max-width:560px; margin:0 auto; font-size:16px}
.cta{
  display:inline-flex; align-items:center; gap:10px; margin-top:var(--s4);
  padding:14px 30px; border:1px solid var(--gold-soft); border-radius:999px;
  color:var(--gold); font-size:14px; letter-spacing:.12em; text-transform:uppercase;
  background:linear-gradient(180deg,rgba(212,175,55,.10),rgba(212,175,55,.02));
  transition:transform .2s var(--ease), box-shadow .2s var(--ease), background .2s var(--ease);
}
.cta:hover{transform:translateY(-2px); box-shadow:0 10px 28px rgba(212,175,55,.18); color:var(--gold)}

/* A slow ambient glow, not tied to scroll at all — a time-based @keyframes loop, so unlike
   everything above it has no @supports gate to fail: every browser that runs CSS
   animations at all runs this one. Reduced-motion still turns it off, same as everything
   else on the page. Uses filter, not box-shadow: :hover above sets box-shadow, and a
   running animation on a property always wins over a plain rule for that same property,
   which would have swallowed the hover feedback while the glow was mid-pulse. */
@media not (prefers-reduced-motion:reduce){
  .cta{animation:cta-glow 3.2s ease-in-out infinite}
  @keyframes cta-glow{
    0%,100%{filter:drop-shadow(0 0 0 rgba(212,175,55,0))}
    50%{filter:drop-shadow(0 0 10px rgba(212,175,55,.35))}
  }
}

/* ---------- sections ---------- */
section{padding:var(--s6) 0; border-bottom:1px solid var(--line)}
section:last-of-type{border-bottom:0}
.section-head{text-align:center; margin-bottom:var(--s5)}
h2{font-size:clamp(30px,4.4vw,42px); color:var(--gold); letter-spacing:.06em; margin:0 0 10px}
.section-head p{color:var(--muted); margin:0; max-width:560px; margin-inline:auto}
h3{font-size:19px; margin:0 0 6px; color:var(--text)}
p{margin:0 0 14px}

/* ---------- device mockups ---------- */
.devices{display:flex; gap:var(--s4); overflow-x:auto; padding:var(--s2) var(--s3) var(--s4);
  scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch; scrollbar-width:thin}
.device{
  flex:0 0 auto; scroll-snap-align:center; width:266px;
  background:linear-gradient(160deg,#2a2a33,#131318);
  border-radius:38px; padding:9px;
  box-shadow:0 26px 60px rgba(0,0,0,.65), 0 0 0 1px rgba(212,175,55,.16),
             inset 0 1px 1px rgba(255,255,255,.09);
}
.device .screen{position:relative; border-radius:30px; overflow:hidden; background:#000}
.device .screen img{width:100%; height:auto; display:block}
/* dynamic island */
.device .screen:before{
  content:""; position:absolute; top:9px; left:50%; transform:translateX(-50%);
  width:74px; height:20px; background:#000; border-radius:12px; z-index:2;
}
.device figcaption{margin-top:var(--s2); text-align:center; color:var(--muted);
  font-size:13px; letter-spacing:.05em}

/* ---------- feature cards ---------- */
.grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(238px,1fr)); gap:var(--s3)}
.card{
  background:linear-gradient(180deg,var(--panel),var(--panel-2));
  border:1px solid var(--line); border-radius:var(--radius); padding:var(--s3);
  transition:transform .25s var(--ease), border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.card:hover{transform:translateY(-3px); border-color:var(--gold-dim); box-shadow:0 14px 34px rgba(0,0,0,.45)}
.card .icon{width:28px; height:28px; color:var(--gold); margin-bottom:12px}
.card h3{font-family:'Montserrat',sans-serif; font-size:15px; font-weight:600;
  letter-spacing:.08em; text-transform:uppercase; color:var(--gold-soft)}
.card p{color:var(--muted); font-size:15px; margin:0}

/* ---------- honest block ---------- */
.honest{
  background:linear-gradient(180deg,rgba(212,175,55,.05),transparent);
  border:1px solid var(--gold-dim); border-radius:18px; padding:var(--s4);
}
.honest ul{list-style:none; padding:0; margin:0 0 var(--s3);
  display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:10px}
.honest li{padding-left:28px; position:relative}
.honest li svg{position:absolute; left:0; top:5px; width:16px; height:16px; color:var(--gold)}
.honest .kicker{font-family:'Cormorant',serif; font-size:26px; color:var(--gold); margin:0}

/* ---------- doc pages ---------- */
.doc h1{font-size:clamp(34px,5vw,46px); color:var(--gold); letter-spacing:.05em; margin:0 0 6px}
.doc .updated{color:var(--muted); font-size:14px; margin-bottom:var(--s4)}
.doc h2{font-size:22px; text-align:left; margin:var(--s4) 0 10px}
.doc ul{padding-left:22px} .doc li{margin:7px 0}

/* ---------- footer ---------- */
footer{padding:var(--s4) 0 var(--s5); color:var(--muted); font-size:14px}
footer .wrap{display:flex; flex-wrap:wrap; gap:var(--s2); align-items:center}
footer nav{margin-left:auto; display:flex; gap:var(--s1); flex-wrap:wrap}
footer a{color:var(--muted); display:inline-flex; align-items:center;
  min-height:44px; padding:0 10px; border-radius:8px}
footer a:hover{color:var(--gold); background:rgba(212,175,55,.06)}

/* ---------- scroll reveal ----------
   Progressive enhancement: content is visible by default. Only once JS confirms it can
   drive the animation does it add .js-reveal to <html>, which hides elements to animate
   them in. Without JS (or if the observer never fires) everything simply stays visible —
   a crawler or reviewer must never get a blank page. */
.js-reveal .reveal{opacity:0; transform:translateY(18px);
  transition:opacity .6s var(--ease), transform .6s var(--ease)}
.js-reveal .reveal.in{opacity:1; transform:none}

/* ---------- scroll-driven animations (native CSS, no JS) ----------
   animation-timeline ties a @keyframes animation to scroll position instead of time. Zero
   script-src or style-src footprint (unlike GSAP, whose ScrollTrigger writes styles via
   cssText and gets blocked outright by this site's style-src 'self' with no
   'unsafe-inline' — confirmed by hand before choosing this route).

   A pinned, horizontally-scrubbing device carousel and checkmarks/list items popping in on
   scroll were built, then pulled after a live test showed every one stuck at its start
   keyframe regardless of scroll position, then reinstated the next session: the "failure"
   traced back to the test tab itself, whose document.hidden read true throughout —
   confirmed by a plain time-based @keyframes loop (the CTA glow below) reading equally
   frozen in the same test, which a real scroll-timeline bug could not explain. A
   backgrounded tab pausing animation ticking is standard, spec-correct browser behavior,
   not a defect in animation-timeline or named view-timelines. Kept regardless: every
   opacity-based entrance still only ever starts hidden inside this @supports block, so a
   browser that doesn't support the feature never runs it and never hides anything — the
   worst case if a real, non-backgrounded browser genuinely failed to advance a timeline is
   still contained to this block, not the rest of the page. */
@supports (animation-timeline:view()){
  @media not (prefers-reduced-motion:reduce){
    /* Fixed progress bar: fills left-to-right as the whole page scrolls. */
    .scrollbar{animation:scrollbar-fill linear; animation-timeline:scroll(root)}
    @keyframes scrollbar-fill{from{transform:scaleX(0)} to{transform:scaleX(1)}}

    /* Hero: the glow and the app icon drift at different rates as the hero scrolls by —
       parallax depth without a canvas or a scroll listener. */
    .hero:before{animation:hero-glow-parallax linear; animation-timeline:view(); animation-range:cover}
    .hero .mark{animation:hero-mark-parallax linear; animation-timeline:view(); animation-range:cover}
    @keyframes hero-glow-parallax{from{transform:translateY(-40px)} to{transform:translateY(140px)}}
    @keyframes hero-mark-parallax{from{transform:translateY(0) scale(1)} to{transform:translateY(-36px) scale(.9)}}

    /* Device strip drifts a little further left than the user's own scroll as the section
       passes — same view()-on-the-element-itself pattern as the hero above, not the named
       view-timeline that failed. Small enough (40px over the whole section) to read as
       depth rather than fight the row's own horizontal swipe/scroll-snap. */
    .devices{animation:devices-drift linear; animation-timeline:view(); animation-range:cover}
    @keyframes devices-drift{from{transform:translateX(16px)} to{transform:translateX(-24px)}}

    /* Honest-by-design list items pop in as they scroll into view. Each gets its own named
       timeline (view-timeline-name is per-element, but six identical siblings sharing one
       name is the kind of case still worth isolating explicitly) rather than relying on an
       anonymous view() on the <li> itself. */
    .honest li:nth-child(1){view-timeline-name:--check-1}
    .honest li:nth-child(2){view-timeline-name:--check-2}
    .honest li:nth-child(3){view-timeline-name:--check-3}
    .honest li:nth-child(4){view-timeline-name:--check-4}
    .honest li:nth-child(5){view-timeline-name:--check-5}
    .honest li:nth-child(6){view-timeline-name:--check-6}
    .honest li{opacity:0; transform:translateX(-14px); view-timeline-axis:block;
      animation:honest-li-in linear; animation-range:cover 0% cover 45%}
    .honest li:nth-child(1){animation-timeline:--check-1}
    .honest li:nth-child(2){animation-timeline:--check-2}
    .honest li:nth-child(3){animation-timeline:--check-3}
    .honest li:nth-child(4){animation-timeline:--check-4}
    .honest li:nth-child(5){animation-timeline:--check-5}
    .honest li:nth-child(6){animation-timeline:--check-6}
    @keyframes honest-li-in{to{opacity:1; transform:none}}
  }
}

/* Pinned, scrubbing device carousel — desktop only. Below 900px the swipeable
   scroll-snap strip stays exactly as it is: a pinned section fighting a touch scroll is a
   worse experience than a strip you can flick through with a thumb. */
@media (min-width:900px){
  @supports (animation-timeline:view()){
    @media not (prefers-reduced-motion:reduce){
      .scrub-stage{height:230vh; position:relative; view-timeline-name:--scrub; view-timeline-axis:block}
      .scrub-stage .devices{
        position:sticky; top:0; height:100vh; margin:0; overflow:visible;
        scroll-snap-type:none; padding:0 var(--s6);
        /* Overrides the drift animation above with the scrub, rather than stacking both —
           a sticky, pinned track doesn't need the small ambient drift on top of the much
           larger deliberate translation it's about to get. */
        animation:devices-scrub linear; animation-timeline:--scrub; animation-range:cover;
      }
      @keyframes devices-scrub{
        from{transform:translateX(0)}
        to{transform:translateX(calc(-1490px + 100vw - 2 * var(--s6)))}
      }
    }
  }
}

/* ---------- responsive ---------- */
@media(max-width:768px){
  :root{--s5:48px; --s6:64px}
  header.site .wrap{height:60px}
  nav.main{gap:var(--s2); font-size:13px}
  .device{width:222px}
  .devices{gap:var(--s3)}
}
@media(max-width:380px){
  .device{width:196px}
  nav.main{gap:12px; font-size:12px}
}

/* Below ~500px the brand mark and all four nav links no longer fit on one line — they
   were overlapping (confirmed on a real 375px screenshot: "Changelog" printed directly
   over "ROGUE21"). Wrap the nav onto its own centered row instead of continuing to
   shrink text that was already close to unreadable. */
@media(max-width:500px){
  header.site .wrap{flex-wrap:wrap; height:auto; padding-top:10px; padding-bottom:10px; row-gap:6px}
  nav.main{margin-left:0; width:100%; justify-content:center}
}

/* ---------- accessibility ---------- */
@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*:before,*:after{animation-duration:.01ms !important; transition-duration:.01ms !important}
  .reveal{opacity:1; transform:none}
}

/* Utility classes — replace inline style attributes so the CSP can forbid
   'unsafe-inline' for styles entirely. */
.hero-xl{font-size:clamp(64px,14vw,132px)}
.ta-left{text-align:left}
.ta-center{text-align:center}
.mb-3{margin-bottom:var(--s3)}
.lede-center{color:var(--muted); max-width:480px; margin:0 auto var(--s3)}
