/* ============================================================
   yeaboi.ai — design system
   Mirrors the yeaboi TUI: cool near-black, muted green accent,
   quiet grays, mono details. Fraunces display serif for editorial
   headlines, JetBrains Mono for everything terminal.
   Nav is a fixed, vertically-centered, left-aligned rail with no
   background — the same posture as the TUI's main menu.
   ============================================================ */

:root{
  /* surfaces — cool near-black */
  --bg:#0b0c0e;
  --bg-raised:#111317;
  --bg-raised-2:#15181d;
  --bg-sunken:#08090a;
  --bg-translucent:rgba(11,12,14,.82);

  /* type */
  --text:#e4e6e8;
  --text-muted:#9aa0a8;
  --text-dim:#62676f;

  /* accent — the TUI's Planning-theme blue (rgb 110,140,220 / 140,170,255) */
  --accent:#6e8cdc;
  --accent-bright:#8caaff;
  --accent-dim:rgba(110,140,220,.14);
  --accent-glow:rgba(110,140,220,.5);

  /* authentic TUI secondaries */
  --success:#59c07e;      /* velocity / accept / done */
  --warn:#dcab4a;         /* churn / section markers / notices */

  /* per-mode hues (exact TUI theme accents) */
  --m-planning:#6e8cdc;
  --m-analysis:#64b464;
  --m-standup:#c864b4;
  --m-retro:#50bebe;
  --m-poker:#e6c846;
  --m-performance:#dc6e5a;
  --m-reporting:#8c78e6;
  /* the Agents family (AGENT_*_THEME) */
  --m-agent-usage:#46bee6;
  --m-agent-standup:#78d2aa;
  --m-agent-security:#e65a78;

  --border:rgba(228,230,232,.07);
  --border-strong:rgba(228,230,232,.14);

  --display:"Geist",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
  --sans:"Geist",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
  --mono:"JetBrains Mono",ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace;
  /* The TUI recreation does NOT use --mono. JetBrains Mono has no box-drawing or
     block-element coverage (U+2500–259F), so every ▀▄█░ fell back per-glyph to a
     different font with different cell metrics — which is what put seams through
     the block titles and broke the duck into fragments. This stack leads with the
     OS terminal font (SF Mono on macOS, DejaVu on Linux), the same fonts iTerm2
     and friends use, which draw those cells solid and edge-to-edge. */
  --tui-mono:ui-monospace,"SF Mono","SFMono-Regular",Menlo,"DejaVu Sans Mono","Liberation Mono",Consolas,monospace;

  --radius:8px;
  --radius-lg:14px;
  --maxw:1080px;
  --rail:264px;   /* left nav rail width */

  --shadow-card:0 1px 0 rgba(255,255,255,.03) inset, 0 20px 50px -24px rgba(0,0,0,.7);
  --shadow-term:0 1px 0 rgba(255,255,255,.04) inset, 0 40px 90px -30px rgba(0,0,0,.85);
}

*{box-sizing:border-box}
/* Dark bg on <html> too — kills the white flash between page loads.
   Hide the global scrollbar (scrolling stays fully functional). */
html{-webkit-text-size-adjust:100%; background:var(--bg); scrollbar-width:none; -ms-overflow-style:none}
html::-webkit-scrollbar, body::-webkit-scrollbar{width:0; height:0; display:none}

/* Cross-document page transitions fade the CONTENT (root) only — the rail is its
   own transition group so it never fades on navigation (sidebar = motion, not fade). */
.navbar{view-transition-name:rail}
body{
  margin:0; background:var(--bg); color:var(--text);
  font-family:var(--sans); font-size:16px; line-height:1.65;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  padding-left:var(--rail);
}

/* Soft cross-document page transitions — a gentle crossfade instead of a hard
   repaint when navigating between docs pages (Chrome/Edge/Safari TP). */
@view-transition{ navigation:auto }
/* content (root) crossfades... */
::view-transition-old(root){ animation:vt-out .22s cubic-bezier(.4,0,.2,1) both }
::view-transition-new(root){ animation:vt-in .34s cubic-bezier(.2,.6,.2,1) both }
@keyframes vt-out{ to{opacity:0} }
@keyframes vt-in{ from{opacity:0} to{opacity:1} }
/* ...but the rail never fades — it cuts straight to its new state */
::view-transition-old(rail){ animation:none; opacity:0 }
::view-transition-new(rail){ animation:none }
@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){ animation:none }
}

/* atmosphere: barely-there cool wash + film grain */
body::before{
  content:""; position:fixed; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(1100px 540px at 75% -10%, rgba(110,140,220,.06), transparent 60%),
    radial-gradient(900px 620px at -8% 8%, rgba(140,170,255,.035), transparent 55%);
}
body::after{
  content:""; position:fixed; inset:0; z-index:60; pointer-events:none; opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Grain-textured aura that slowly reveals as you scroll. A soft colour glow
   rises from the bottom, masked by fine noise so it reads as granular rather
   than a flat wash. Opacity + drift are driven by scroll position. */
.scroll-aura{
  position:fixed; inset:0; z-index:0; pointer-events:none; opacity:0;
  background:
    radial-gradient(135% 95% at 50% 116%, rgba(110,140,220,.44), transparent 60%),
    radial-gradient(95% 75% at 80% 106%, rgba(140,170,255,.30), transparent 62%),
    radial-gradient(80% 60% at 18% 110%, rgba(90,150,220,.22), transparent 60%),
    linear-gradient(to top, rgba(110,140,220,.12), transparent 46%);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
          mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  -webkit-mask-size:140px 140px; mask-size:140px 140px;
  /* opacity + transform are driven by site.js from scroll progress */
  will-change:opacity;
}
@media (prefers-reduced-motion:reduce){
  .scroll-aura{opacity:.32; transform:none}
}

a{color:var(--accent); text-decoration:none}
a:hover{color:var(--accent-bright)}
::selection{background:rgba(110,140,220,.3)}
code,pre,kbd{font-family:var(--mono)}
.wrap{max-width:var(--maxw); margin:0 auto; padding:0 40px; position:relative; z-index:1}
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0}

/* ---- left nav rail (TUI posture: vertically centered, left aligned, no background) ---- */
.navbar{
  position:fixed; top:0; left:0; bottom:0; width:var(--rail); z-index:50;
  display:flex; align-items:center;           /* vertical centering */
  pointer-events:none;                         /* only the controls catch clicks */
}
.navbar::before{
  /* thin accent edge anchoring the rail as its own zone — still no filled
     background panel, just enough to read as a distinct nav strip. */
  content:""; position:absolute; top:0; bottom:0; left:0; width:2px;
  background:linear-gradient(180deg, transparent, var(--border-strong) 15%, var(--border-strong) 85%, transparent);
}
.rail{
  display:flex; flex-direction:column; align-items:flex-start; gap:38px;
  padding:0 22px 0 44px; margin:0; max-width:none; width:100%; pointer-events:auto;
}
.rail-nav{display:flex; flex-direction:column; align-items:flex-start; gap:0; width:100%}


/* Brand + section toggle share ONE style: big, thick, tracked mono. */
.rail .brand,
.rail-toggle{
  background:none; border:none; padding:0; margin:0; cursor:pointer; text-align:left;
  display:inline-flex; align-items:center;
  font-family:var(--mono); font-weight:800; font-size:2.35rem;
  letter-spacing:.1em; text-transform:uppercase; color:var(--text);
  transition:color .25s ease;
}
.rail .brand:hover,
.rail-toggle:hover{color:var(--accent-bright)}
/* no ugly click box; keep a clean keyboard-only focus ring — a soft glow,
   not a hard rectangle */
.rail .brand:focus,
.rail-toggle:focus{outline:none}
.rail .brand:focus-visible,
.rail-toggle:focus-visible{
  outline:none; border-radius:7px;
  box-shadow:0 0 0 3px rgba(110,140,220,.32);
}
.rail-toggle.active{color:var(--text)}
.rail.expanded .rail-toggle{color:var(--accent-bright)}

/* click feedback: a slow colour + opacity pulse (JS toggles .rail-pulse) */
@keyframes rail-pulse{
  0%{opacity:1}
  45%{opacity:.4; color:var(--accent-bright)}
  100%{opacity:1}
}
.rail-pulse{animation:rail-pulse 1s ease-in-out}
@media (prefers-reduced-motion:reduce){ .rail-pulse{animation:none} }

/* slide-up + fade: menu height animates (grid-rows), navbar re-centres so the
   toggle rises; menu fades in beneath it. */
/* Sidebar = MOTION ONLY: the menu opens by animating its height (clipped by the
   grid), never by fading. */
/* Closed state is enforced THREE independent ways at once (grid-rows AND
   max-height AND visibility) — not just the fr-unit grid trick alone. That
   trick depends on the browser freshly computing an intrinsic-size target,
   which (as with the item-wraps earlier) can silently fail to apply in some
   render paths; stacking hard, unambiguous fallbacks means the menu physically
   cannot show content unless .expanded is genuinely present, regardless of
   whether the grid computation itself behaves. */
.rail-menu{
  display:grid; grid-template-rows:0fr; width:100%; margin-top:0;
  overflow:hidden; max-height:0; visibility:hidden; pointer-events:none;
  transition:grid-template-rows .55s cubic-bezier(.2,.6,.2,1),
             margin-top .55s cubic-bezier(.2,.6,.2,1),
             max-height .55s cubic-bezier(.2,.6,.2,1),
             visibility 0s .55s;
}
.rail.expanded .rail-menu{
  grid-template-rows:1fr; margin-top:22px; max-height:80vh;
  visibility:visible; pointer-events:auto; transition-delay:0s;
}
/* Search header is a FIXED block, structurally outside the scrolling list —
   not position:sticky (a mask-image on an ancestor would still paint over a
   sticky descendant, which was fading/clipping the search box).
   Deliberately plain BLOCK layout, not flex: .rail-menu-inner's own height
   already comes from an ancestor 0fr/1fr grid trick (see .rail-menu below);
   stacking a flex column with a flex:1 auto child on TOP of that created a
   circular indefinite-size situation where the child (and everything inside
   it, including each item's own 0fr/1fr reveal) collapsed to zero. Giving
   .nav-scroll its own EXPLICIT max-height sidesteps that entirely. */
.rail-menu-inner{ max-height:66vh }
.nav-scroll{
  max-height:calc(66vh - 66px); min-height:0; overflow:hidden; overflow-y:auto;
  scrollbar-width:thin; scrollbar-color:var(--border-strong) transparent;
  padding-right:6px; margin-right:-6px; /* scrollbar sits inside the panel, not on its edge */
  /* fade both scroll edges only where there's more content — --ft/--fb set by JS */
  --ft:0px; --fb:28px;
  -webkit-mask-image:linear-gradient(to bottom, transparent 0, #000 var(--ft), #000 calc(100% - var(--fb)), transparent 100%);
          mask-image:linear-gradient(to bottom, transparent 0, #000 var(--ft), #000 calc(100% - var(--fb)), transparent 100%);
}
.nav-scroll::-webkit-scrollbar{width:4px}
.nav-scroll::-webkit-scrollbar-thumb{background:var(--border-strong); border-radius:2px}

/* Each top-level nav item reveals independently via its own height, staggered
   by JS (transition-delay set per item). Motion only, no opacity.
   Uses max-height (a generous fixed cap, not the 0fr/1fr grid-intrinsic-size
   trick): that trick requires the browser to freshly measure each item's
   natural content size at the exact moment the class toggles, and nesting it
   two levels deep (outer .rail-menu + inner .item-wrap, with a scrolling
   max-height region in between) turned out to reliably resolve to 0 whenever
   triggered in the same tick as the unrelated #page-content swap elsewhere on
   the page (a large outerHTML replacement interrupts that measurement) — even
   though the exact same code worked fine on a plain direct page load. A fixed
   max-height cap doesn't depend on that measurement at all, so it's immune. */
.item-wrap{
  max-height:0; overflow:hidden;
  transition:max-height .34s cubic-bezier(.2,.6,.2,1);
}
.rail-menu-inner.items-open .item-wrap{max-height:340px} /* comfortably covers even "Overview" (~272px with all 6 children) */
.item-inner{overflow:hidden; min-height:0}

/* ---- shared docs-nav component (rail dropdown + docs-shell sidebar) ---- */
/* Anchored: a plain fixed header above the scrolling list (see .nav-scroll
   above) — not position:sticky, so nothing can ever paint through/behind it. */
.nav-search-wrap{
  flex:0 0 auto; padding-bottom:14px;
}
.nav-search{
  width:100%; background:var(--bg-raised); border:1px solid var(--border);
  border-radius:var(--radius); color:var(--text); font-family:var(--sans);
  font-size:.9rem; padding:9px 12px; transition:border-color .2s;
}
.nav-search::placeholder{color:var(--text-dim)}
.nav-search:focus{outline:none; border-color:var(--accent)}
.nav-group{margin:0 0 24px}
.nav-group:last-child{margin-bottom:4px}
.nav-group-label{
  font-family:var(--mono); font-size:.74rem; letter-spacing:.16em; text-transform:uppercase;
  color:var(--text-dim); margin:0 0 10px; padding:0 4px; font-weight:500;
}
ul.nav-tree{list-style:none; margin:0; padding:0; font-size:1.02rem}
ul.nav-tree ul.nav-tree{margin:3px 0 6px 12px; border-left:1px solid var(--border); padding-left:8px; font-size:.98rem}
.nav-tree li{margin:2px 0}
.nav-tree a{
  display:block; padding:7px 11px; border-radius:6px; color:var(--text); font-weight:500;
  transition:color .15s, background .15s, backdrop-filter .15s; line-height:1.4;
}
.nav-tree ul.nav-tree a{color:var(--text-muted); font-weight:450}  /* nested: a touch softer */
/* frosted glass: translucent white + blur, not a flat fill. isolation:isolate
   scopes the blur to what's actually behind these links — without it, the
   fixed full-page .scroll-aura glow (see site.css's .scroll-aura) bleeds
   straight through and shows as a smeared colored haze once scrolled. */
.nav-tree a:hover{
  color:var(--text); text-decoration:none;
  background:rgba(255,255,255,.07); isolation:isolate;
  -webkit-backdrop-filter:blur(10px) saturate(1.4);
          backdrop-filter:blur(10px) saturate(1.4);
}
.nav-tree a.current{
  color:var(--accent-bright); font-weight:600;
  background:rgba(255,255,255,.1); isolation:isolate;
  -webkit-backdrop-filter:blur(10px) saturate(1.4);
          backdrop-filter:blur(10px) saturate(1.4);
  box-shadow:0 0 0 1px rgba(255,255,255,.08) inset;
}

/* ---- buttons ---- */
.btn{
  display:inline-flex; align-items:center; gap:9px; border-radius:var(--radius);
  border:1px solid var(--border-strong); padding:11px 20px; font-size:.94rem; font-weight:600;
  font-family:var(--sans); cursor:pointer; background:transparent; color:var(--text);
  transition:border-color .2s, color .2s, background .2s, transform .2s;
}
.btn:hover{border-color:var(--accent); color:var(--accent-bright); transform:translateY(-1px)}
.btn-primary{background:var(--text); color:var(--bg); border-color:var(--text)}
.btn-primary:hover{background:var(--accent-bright); border-color:var(--accent-bright); color:var(--bg)}

/* ---- cards ---- */
.card{
  background:rgba(255,255,255,.018); isolation:isolate;
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid var(--border); border-radius:var(--radius-lg);
  padding:24px 26px;
}
.badge{
  display:inline-flex; align-items:center; gap:10px;
  font-family:var(--mono); font-size:.72rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--text-muted); font-weight:500;
  border:1px solid var(--border-strong);
  border-radius:999px; padding:5px 14px;
}
.badge::before{content:""; width:6px; height:6px; border-radius:50%; background:var(--success);
  box-shadow:0 0 8px var(--success)}

/* Beta pill — a caution marker for a feature that ships but whose output isn't
   verified yet. Deliberately NOT a .badge: that one injects a green "live" dot,
   which says the opposite. The amber matches BETA_RGB in src/yeaboi/beta.py, so
   the terminal chip and this pill are the same colour. */
.beta-pill{
  display:inline-block; vertical-align:middle; margin-left:10px;
  font-family:var(--mono); font-size:.62rem; letter-spacing:.14em;
  text-transform:uppercase; font-weight:600; line-height:1;
  color:rgb(224,138,72); border:1px solid rgba(224,138,72,.45);
  border-radius:999px; padding:4px 8px;
}

/* ---- terminal window chrome ---- */
.term{
  background:#0a0b0c; border:1px solid var(--border-strong); border-radius:var(--radius-lg);
  overflow:hidden; box-shadow:var(--shadow-term);
}
.term-bar{
  display:flex; align-items:center; gap:7px; padding:12px 16px;
  background:linear-gradient(180deg,#15171b,#101215); border-bottom:1px solid var(--border);
}
.dot{width:11px;height:11px;border-radius:50%; background:var(--border-strong)}
.app-bar .dot:nth-child(1){background:#ec6a5e}
.app-bar .dot:nth-child(2){background:#f4bf4f}
.app-bar .dot:nth-child(3){background:#61c554}
.term-title{
  margin:0 auto; transform:translateX(-20px);
  font-family:var(--mono); font-size:.76rem; color:var(--text-dim);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.term-body{padding:20px clamp(16px,3vw,26px) 24px; font-family:var(--mono); font-size:.83rem; line-height:1.75}

/* ---- code blocks + copy button ---- */
.codeblock{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  background:var(--bg-sunken); border:1px solid var(--border-strong); border-radius:var(--radius);
  padding:13px 13px 13px 18px; font-family:var(--mono); font-size:.94rem;
  box-shadow:0 1px 0 rgba(255,255,255,.03) inset;
}
.codeblock code{color:var(--text)}
.codeblock code::before{content:"$ "; color:var(--accent)}
.copy{
  border:1px solid var(--border-strong); background:var(--bg-raised); color:var(--text-muted);
  font-family:var(--mono); font-size:.74rem; border-radius:6px; padding:7px 12px;
  cursor:pointer; transition:.18s; white-space:nowrap;
}
.copy:hover{color:var(--text); border-color:var(--accent)}
.copy.done{color:var(--bg); background:var(--accent); border-color:var(--accent)}

/* ---- scroll-reveal: content side = pure FADE (no motion) ---- */
[data-reveal]{opacity:0; transition:opacity .8s cubic-bezier(.2,.6,.2,1)}
[data-reveal].is-visible{opacity:1}

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

/* ---- mobile: NOT the desktop sidebar in a row — a compact sticky top bar.
   Small brand left, a bordered "docs" pill right, and the docs menu opening
   as a full-width sheet UNDER the bar (absolute, overlaying content) instead
   of the rail's giant type pushing the page around. ---- */
@media (max-width:900px){
  body{padding-left:0}
  /* invisible at the top of the page — just the wordmark on the page bg;
     the frosted band + hairline only appear once you scroll (site.js
     toggles .scrolled), so the bar never reads as a stamped-on grey strip */
  .navbar{
    position:sticky; top:0; bottom:auto; width:auto; z-index:50;
    display:block; pointer-events:auto;
    background:transparent; border-bottom:1px solid transparent;
    transition:background .3s ease, border-color .3s ease;
  }
  .navbar.scrolled{
    background:var(--bg-translucent);
    -webkit-backdrop-filter:blur(16px) saturate(1.3);
            backdrop-filter:blur(16px) saturate(1.3);
    border-bottom-color:var(--border);
  }
  /* sheet open: bar and sheet must read as ONE surface — same solid fill as
     the sheet (no translucent frost against its opaque bg), no divider */
  .navbar:has(.rail.expanded),
  .navbar.scrolled:has(.rail.expanded){
    background:var(--bg);
    -webkit-backdrop-filter:none; backdrop-filter:none;
    border-bottom-color:transparent;
  }
  /* ...and the page recedes behind it: blurred, dimmed, and inert (tapping
     the blurred area closes the sheet — handled in site.js). Safe to filter
     here: on mobile nothing inside #page-content is sticky/fixed. */
  #page-content{transition:filter .3s ease}
  body:has(.rail.expanded) #page-content{
    filter:blur(10px) brightness(.55);
    pointer-events:none;
  }
  .navbar::before{display:none}   /* the vertical accent edge is desktop-only */
  /* .navbar .rail, not .rail — the element is `.wrap.rail`, and the generic
     .wrap{padding:0 24px} later in this block would zero the vertical padding
     at equal specificity */
  .navbar .rail{
    flex-direction:row; align-items:center; justify-content:space-between;
    gap:16px; padding:20px 24px;
  }
  /* brand + toggle share one typographic voice (same as desktop) — the
     toggle is just the smaller, quieter sibling, not a pill component */
  .rail .brand{font-size:1.1rem; letter-spacing:.14em}
  .rail-toggle{
    font-size:.82rem; letter-spacing:.22em; color:var(--text-muted);
    padding:6px 0; transition:color .2s;
  }
  .rail-toggle:hover{color:var(--text)}
  .rail.expanded .rail-toggle{color:var(--accent-bright)}
  .rail-nav{width:auto; position:static}
  /* the menu becomes a full-bleed sheet anchored to the bar's bottom edge —
     same 0fr/1fr open animation, but overlaying the page, not reflowing it.
     SOLID background, no backdrop-filter: a blur here nests inside the bar's
     own backdrop-filter, and Chrome's nested-backdrop compositing lets the
     blurred page bleed through even a 97%-alpha fill (ghost text over the
     menu). Solid is also simply correct for a full-bleed sheet. */
  .rail-menu{
    position:absolute; top:100%; left:0; right:0; width:auto;
    background:var(--bg);
    border-bottom:1px solid var(--border-strong);
  }
  .rail.expanded .rail-menu{margin-top:0}
  .rail-menu-inner{max-height:none; padding:16px 20px 20px}
  .nav-scroll{max-height:60vh}
  .wrap{padding:0 24px}
}
