/* ==========================================================================
   YSBT Member Portal — shared design tokens & shell layout
   Linked by every page under /site/portal/ (except login.html, which is a
   standalone auth screen with no sidebar). Keep this file the single source
   of truth for the sidebar/portal-main shell, color tokens, and the type
   scale — page-specific styles stay inline in each page's own <style>.
   ========================================================================== */

:root{
  --bg: #060504; --bg-2: #0a0806;
  --ink: #f4ecdc; --ink-dim: #b6ab93; --ink-dimmer: #7a715c;
  --gold-light: #f1d592; --gold: #d4af37; --gold-dark: #9c6f0b;
  --grad: linear-gradient(90deg, var(--gold-light), var(--gold), var(--gold-dark));
  --green: #34d9a4; --amber: #ffb454; --red: #ff5c7a;
  --line: rgba(212,175,55,0.14); --glass: rgba(212,175,55,0.05);

  /* ---- type scale: use these on every page instead of ad-hoc px values ---- */
  --fs-2xs: 13px;     /* timestamps, micro meta — the smallest text allowed */
  --fs-xs:   14px;    /* tags, pills, uppercase labels */
  --fs-sm:   15.5px;  /* secondary text, table cells */
  --fs-base: 17px;    /* body copy, default */
  --fs-md:   19px;    /* card titles, emphasized rows */
  --fs-lg:   22px;    /* section headings (h2) */
  --fs-xl:   27px;    /* page sub-headings, callout numbers */
  --fs-2xl:  34px;    /* page title (e.g. "Welcome back, ...") */
  --fs-3xl:  42px;    /* hero/stat numbers */

  /* ---- spacing scale: use for section/card rhythm ---- */
  --sp-1: 8px;
  --sp-2: 12px;
  --sp-3: 18px;
  --sp-4: 24px;
  --sp-5: 32px;
  --sp-6: 44px;

  /* Breathing room at both ends of the sidebar nav list — below the brand
     and above the logout button. One value so the two stay symmetrical;
     see .sidebar-nav for why that matters. */
  --nav-gutter: 16px;

  /* ── Sidebar rail: sized by viewport HEIGHT so it never scrolls ─────────
     17 nav items + logo + Log Out needed 1276px, which overflows every
     laptop screen — the rail scrolled, and the last few items sat below the
     fold. Rather than a fixed compact size that wastes a tall monitor, every
     vertical dimension is a clamp() on vh: the rail breathes on a 27" display
     and tightens on a 13" laptop, fitting either way.

     Tuned so the full list fits at 700px of viewport height, which covers a
     720p screen and a 13" laptop with browser chrome. `overflow-y:auto` stays
     on .sidebar as a safety valve for anything shorter — an unreachable Log
     Out is worse than a scrollbar. */
  --nav-row-pad:  clamp(3px,  0.72vh, 10px);
  --nav-gap:      clamp(2px,  0.42vh,  7px);
  --nav-icon:     clamp(16px, 2.5vh,  24px);
  --nav-font:     clamp(13px, 1.85vh, 17px);
  --nav-logo:     clamp(38px, 6.5vh,  80px);
  --nav-padblock: clamp(10px, 2vh,    20px);
}

*{margin:0;padding:0;box-sizing:border-box;}
body{
  background:var(--bg); color:var(--ink); font-family:'Inter',sans-serif;
  font-size:var(--fs-base); line-height:1.6;
  min-height:100vh; position:relative; overflow-x:hidden;
}
#starfield{position:fixed; inset:0; z-index:0; pointer-events:none;}
body::before{
  content:''; position:fixed; inset:0; z-index:0;
  background:
    radial-gradient(ellipse 60% 50% at 80% 0%, rgba(156,111,11,.14), transparent 60%),
    radial-gradient(ellipse 50% 60% at 10% 100%, rgba(212,175,55,.10), transparent 60%);
}
.mono{font-family:'JetBrains Mono',monospace;}
a{color:inherit;}

/* ============ shared component primitives (opt-in) ============ */
.eyebrow-label{font-size:var(--fs-xs); font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-dimmer);}
.section-title{font-size:var(--fs-lg); font-weight:700; text-transform:uppercase; letter-spacing:.04em; margin-bottom:var(--sp-3);}
.card{background:var(--glass); border:1px solid var(--line); border-radius:16px; padding:var(--sp-4) var(--sp-4);}
.card-title{font-size:var(--fs-md); font-weight:600; margin-bottom:var(--sp-1);}
.card-body{font-size:var(--fs-sm); color:var(--ink-dim); line-height:1.65;}
.stat-num{font-family:'JetBrains Mono',monospace; font-size:var(--fs-xl); font-weight:800; color:var(--gold-light);}

/* ============ SIDEBAR SHELL (shared across all portal pages) ============ */
/* The nav is withheld until GET /portal/me confirms an active membership.
   portal-nav.js paints the member sidebar on DOM-ready so the rail is never
   empty, which meant a signed-out or unpaid visitor got a full set of portal
   controls sitting next to "You're not logged in". Anyone not entitled is
   redirected (login or /join), so the rail only ever needs to appear for
   members — and hiding it costs nothing for them, since data-portal-ready
   lands in the same tick as their first render. */
body:not([data-portal-ready]) .sidebar,
body:not([data-portal-ready]) .mobile-topbar-toggle,
body:not([data-portal-ready]) .sidebar-overlay{display:none !important;}

.portal-shell{position:relative; z-index:1; display:flex; min-height:100vh;}
.brand-logo{height:66px; width:auto; display:block;}

.sidebar{
  width:284px; flex:0 0 284px; position:sticky; top:0; height:100vh; overflow-y:auto;
  background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.01));
  border-right:1px solid var(--line); display:flex; flex-direction:column;
  padding:var(--nav-padblock) 22px;
}
/* The lockup spans the sidebar's full content width rather than sitting at a
   fixed height — the 8px side padding is dropped so it reaches edge to edge,
   which is what lets it scale up with the sidebar instead of floating small
   in the corner. */
.sidebar-brand{display:flex; align-items:center; padding:0 0 clamp(6px,1.2vh,16px);}
/* height-driven so the lockup shrinks with the rail instead of
   dominating it — width:100% is what made it 91px tall and pushed the
   list past the fold. */
.sidebar-brand .brand-logo{height:var(--nav-logo); width:auto; max-width:100%;}
.sidebar-brand .name{font-weight:700; font-size:var(--fs-sm); letter-spacing:.015em; line-height:1.3;}
/* flex:1 makes the nav absorb the sidebar's leftover height; centering the
   items inside it splits that slack evenly above and below the list, so the
   gap under the last item matches the gap between the logo and the first.
   The brand's padding-bottom and the logout's margin-top are kept equal
   (--nav-gutter) so both ends stay symmetrical as items are added/removed.
   `safe` degrades to flex-start rather than overflowing the top items out of
   reach on short viewports. */
.sidebar-nav{
  display:flex; flex-direction:column; gap:var(--nav-gap); flex:1;
  justify-content:center;
  justify-content:safe center;
}
/* labels wrap rather than force the sidebar wide — "Darwinacci Scanner" is the
   only one long enough to need a second line, and nowrap would clip it. */
.nav-item{
  position:relative; overflow:hidden; /* clips the click ripple */
  display:flex; align-items:center; gap:11px; padding:var(--nav-row-pad) 14px; border-radius:12px;
  font-size:var(--nav-font); font-weight:600; text-decoration:none; line-height:1.25;
  transition:transform .18s ease, box-shadow .18s ease, background .25s ease, border-color .25s ease, color .18s ease;
  background:linear-gradient(135deg, rgba(212,175,55,.17), rgba(212,175,55,.04));
  color:var(--gold-light); border:1px solid rgba(212,175,55,.26);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
}
.nav-item svg{width:var(--nav-icon); height:var(--nav-icon); flex:0 0 auto;}
.nav-item:hover{
  transform:translateY(-2px);
  background:linear-gradient(135deg, rgba(212,175,55,.28), rgba(212,175,55,.08));
  border-color:rgba(212,175,55,.45); color:var(--ink);
  box-shadow:0 10px 26px -12px rgba(212,175,55,.55), inset 0 1px 0 rgba(255,255,255,.08);
}
.nav-item.active{
  background:linear-gradient(135deg, rgba(212,175,55,.38), rgba(212,175,55,.1));
  color:var(--gold-light); border:1px solid rgba(212,175,55,.6);
  box-shadow:0 10px 26px -10px rgba(212,175,55,.55), inset 0 1px 0 rgba(255,255,255,.1);
}
.nav-item.soon{color:var(--ink-dimmer); font-weight:500; background:transparent; border:1px solid transparent; box-shadow:none;}
.nav-item.soon:hover{background:var(--glass); color:var(--ink); transform:none; box-shadow:none;}
.nav-item.soon.active{
  background:linear-gradient(135deg, rgba(212,175,55,.18), rgba(212,175,55,.05));
  color:var(--gold-light); border:1px solid rgba(212,175,55,.3);
}
.lock-icon{margin-left:auto; width:calc(var(--nav-icon) * .8); height:calc(var(--nav-icon) * .8); flex:0 0 auto; color:var(--ink-dimmer);}
.nav-item.soon:hover .lock-icon{color:var(--ink);}
.nav-item.soon.active .lock-icon{color:var(--gold-light);}
.nav-divider{height:1px; background:linear-gradient(90deg, transparent, rgba(212,175,55,.4), transparent); margin:clamp(3px,.6vh,8px) 4px;}
.sidebar-logout{
  display:flex; align-items:center; gap:11px; padding:var(--nav-row-pad) 14px; border-radius:12px; margin-top:clamp(6px,1.2vh,16px);
  background:var(--glass); border:1px solid var(--line); color:var(--ink-dim); font-size:var(--nav-font); font-weight:600; cursor:pointer;
  width:100%; text-align:left; transition:transform .18s ease, box-shadow .18s ease, color .18s ease, border-color .18s ease;
}
.sidebar-logout:hover{color:var(--ink); border-color:rgba(255,255,255,.3); transform:translateY(-2px); box-shadow:0 10px 22px -12px rgba(0,0,0,.5);}
.sidebar-logout svg{width:var(--nav-icon); height:var(--nav-icon);}
@media (prefers-reduced-motion: reduce){
  .nav-item, .sidebar-logout{transition:none;}
  .nav-item:hover, .sidebar-logout:hover{transform:none;}
}

/* ---- main content column ----
   The old 40px side padding + 336px sidebar + 1280px content cap left a wide
   dead band on anything above ~1600px. Sidebar is now 272px, side padding is
   24px, and the content cap is 1760px so wide monitors actually fill. */
.portal-main{flex:1; min-width:0; padding:36px 24px 80px;}

/* Two content widths, deliberately:
   .page-inner          — data-dense pages (dashboard, signals, history,
                          analytics, payouts, achievements, live session, chat).
                          Wide so tables/grids/charts get the room they need.
   .page-inner.narrow   — form and long-form reading pages (risk calculator,
                          test & practice, first 72 hours, affiliate, coming
                          soon). Capped near a comfortable measure so inputs
                          and paragraphs don't stretch across a large monitor. */
.page-inner{max-width:1760px; margin:0 auto;}
.page-inner.narrow{max-width:1080px;}

/* solid black with a gold outline and gold bars — the SVG strokes use
   currentColor, so `color` is what tints the three lines */
.mobile-topbar-toggle{
  display:none; position:fixed; top:16px; left:16px; z-index:60; width:42px; height:42px; border-radius:10px;
  /* padding:0 is load-bearing: trade-ideas.html declares a generic button{}
     with padding:12px 22px, and anything this rule leaves unset leaks in from
     it — which is what made the burger render misshapen on that page alone. */
  padding:0; line-height:0;
  background:#0a0806;
  border:1px solid var(--gold); color:var(--gold-light);
  box-shadow:0 6px 18px -6px rgba(0,0,0,.65);
  align-items:center; justify-content:center; cursor:pointer;
  -webkit-appearance:none; appearance:none;
}
.mobile-topbar-toggle:active{filter:brightness(.92);}
.mobile-topbar-toggle svg{width:20px; height:20px;}
.sidebar-overlay{display:none; position:fixed; inset:0; background:rgba(5,7,13,.7); z-index:45;}

/* Close affordance inside the drawer. Desktop has no drawer, so it only ever
   appears on mobile while the rail is open. position:fixed resolves against
   .sidebar rather than the viewport, because the transform on .sidebar makes
   it the containing block — which is what keeps this pinned to the rail's
   top-right instead of scrolling away with the nav list. */
/* Scoped to `.sidebar .sidebar-close` (0,2,0) so it comfortably outranks the
   generic `button{}` rules some pages declare in their own <style> — that's
   why this button rendered gold on one page and invisible on another. All the
   visuals live here rather than on the .open state selector, so the button can
   never appear unstyled. The glyph is drawn from two pseudo-element bars
   instead of the &times; character, which keeps it identical everywhere
   regardless of font loading. */
.sidebar .sidebar-close{
  display:none;
  position:fixed; top:14px; right:14px; z-index:55;
  width:34px; height:34px; flex:0 0 auto; padding:0;   /* 20% down from 42px */
  align-items:center; justify-content:center;
  border-radius:9px; cursor:pointer;
  background:#ffffff;
  border:1px solid rgba(0,0,0,.38);
  box-shadow:0 6px 18px -6px rgba(0,0,0,.65);
  font-size:0;                      /* suppresses the &times; in the markup */
  -webkit-appearance:none; appearance:none;
}
.sidebar .sidebar-close::before,
.sidebar .sidebar-close::after{
  content:''; position:absolute; width:15px; height:2.2px; border-radius:2px;
  background:#0a0806;
}
.sidebar .sidebar-close::before{transform:rotate(45deg);}
.sidebar .sidebar-close::after{transform:rotate(-45deg);}
.sidebar .sidebar-close:active{filter:brightness(.92);}

@media (max-width: 880px){
  /* z-index:auto is load-bearing. .portal-shell's z-index:1 creates a stacking
     context, which traps the drawer inside it — so .sidebar's z-index:50 can
     never rise above .sidebar-overlay (z-index:45), a sibling of the shell.
     The overlay then paints over the open menu, dimming it and swallowing
     every tap. Releasing the context lets sidebar(50) > overlay(45) apply. */
  .portal-shell{display:block; z-index:auto;}
  .sidebar{
    position:fixed; left:0; top:0; z-index:50; transform:translateX(-100%); transition:transform .3s ease;
    box-shadow:20px 0 60px rgba(0,0,0,.5); width:316px; flex:0 0 316px;
    background:var(--bg-2);
    /* 100vh measures the viewport WITHOUT the browser's address bar, so on a
       phone the rail runs taller than what's actually on screen and Log Out
       sits permanently below the fold — scrollable in theory, unreachable in
       practice. dvh tracks the visible area instead. */
    height:100dvh;
    /* clears the fixed burger (16px top + 42px tall) so it stops sitting on
       top of the lockup */
    /* Burger clearance only — the drawer has its own close button, so this
       just stops the lockup colliding with it. Trimmed from a flat 68px:
       with 17 items the drawer overflowed by 41px on a 812px phone, and this
       plus the tighter tail below is where that came back from. */
    padding-top:clamp(44px, 5.5vh, 60px);
    /* The drawer lockup goes FULL WIDTH and centered (owner's ask,
       2026-07-29: "logo the size of the sidebar so it feels strong").
       Width-driven here, unlike the desktop rail — the drawer scrolls, so
       the taller lockup costs a swipe, not the fold. */
    --nav-logo:clamp(34px, 5.2vh, 52px);
    /* generous tail so Log Out clears the browser's bottom chrome and isn't
       something you have to fight the last few pixels of scroll to reach */
    padding-bottom:calc(clamp(12px, 2vh, 28px) + env(safe-area-inset-bottom));
    /* without contain, a swipe that runs past either end of the rail chains
       into the page underneath and scrolls the dashboard behind the drawer */
    overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch;
  }
  .sidebar.open{transform:translateX(0);}
  /* the scrim is not a scrollable surface — dragging on it must not move the
     page behind it. Taps still register, so tap-to-close is unaffected. */
  .sidebar-overlay{touch-action:none; overscroll-behavior:contain;}
  .sidebar-overlay.visible{display:block;}
  /* visuals already set above — this only reveals it */
  .sidebar.open .sidebar-close{display:flex;}
  .mobile-topbar-toggle{display:flex;}
  /* iOS Safari ignores overflow:hidden on body for touch scrolling, so the
     page kept moving behind the drawer. Pinning it with position:fixed is the
     only lock that holds; the JS stores the scroll offset in `top` and puts it
     back on close so the page doesn't jump to the top. */
  body.drawer-open{position:fixed; left:0; right:0; width:100%; overflow:hidden;}
  .portal-main{padding:76px 16px 60px;}
  /* The drawer inherits the same vh scale as the desktop rail — hard
     sizes here were the reason the mobile list still ran past the
     bottom of the screen. Only the burger clearance is mobile-specific. */
  .nav-item{gap:11px; padding:var(--nav-row-pad) 14px;}
  .sidebar-logout{gap:11px; padding:var(--nav-row-pad) 14px;}

  /* Full-width centered lockup in the drawer. height:auto overrides the
     desktop height-driven sizing; the aspect ratio does the rest. */
  .sidebar-brand{justify-content:center;}
  .sidebar-brand .brand-logo{height:auto; width:100%; max-width:238px;}

  /* While the drawer is open the burger disappears — it was sitting on top
     of the lockup (the drawer has its own close button, so the burger has no
     job while open). Two separate rules on purpose: a browser without :has()
     support drops a whole grouped selector, and body.drawer-open depends on
     each page's drawer JS having adopted that class. */
  body.drawer-open .mobile-topbar-toggle{display:none;}
  body:has(.sidebar.open) .mobile-topbar-toggle{display:none;}
}

#loadingState, #errorState{text-align:center; padding:80px 20px; color:var(--ink-dim);}
#errorState{display:none;}
#errorState a{color:var(--gold); text-decoration:underline;}

/* ---- Owner / member role split -------------------------------------------
   Rendered by portal-nav.js. See docs/sops/roles.md.

   These rules are PRESENTATION ONLY. Hiding an element is not access control:
   every privileged endpoint is gated by requireOwner on the server, and a
   member who unhides a button with devtools still gets a 403. The point is
   that a member is never shown a control that would fail for them. */

/* Makes it unmistakable which portal is loaded. The owner and member views
   share a layout, so the mistake worth preventing is the owner believing they
   are looking at the customer experience when they are not. */
.nav-role-badge{
  display:inline-block; align-self:flex-start; margin:0 0 14px 12px;
  font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:#060504; background:linear-gradient(135deg, var(--gold-light,#f1d592), var(--gold,#d4af37));
  border-radius:20px; padding:4px 12px;
}

/* Any markup-level owner control. Default to HIDDEN so a control that forgets
   to declare a role is invisible to everyone rather than visible to everyone —
   the safe direction to fail in. */
/* :not(body) is load-bearing. An owner-only PAGE marks itself with
   <body data-owner-only> so portal-nav.js can redirect members away; without
   this exclusion that same attribute would match here and blank the entire
   page for the owner too. */
[data-owner-only]:not(body){ display:none; }
body[data-role="owner"] [data-owner-only]{ display:revert; }


/* ---- tab click feedback (portal-nav.js renders it on every tab) ---- */
.nav-item.nav-clicked{
  transform:scale(.965);
  background:linear-gradient(135deg, rgba(212,175,55,.34), rgba(212,175,55,.12));
  border-color:rgba(212,175,55,.7);
}
.nav-ripple{
  position:absolute; width:14px; height:14px; border-radius:50%;
  transform:translate(-50%,-50%) scale(0); pointer-events:none;
  background:radial-gradient(circle, rgba(241,213,146,.9), rgba(212,175,55,.35) 55%, transparent 75%);
  animation:navRipple .5s ease-out forwards;
}
@keyframes navRipple{ to{transform:translate(-50%,-50%) scale(16); opacity:0;} }
@media (prefers-reduced-motion: reduce){
  .nav-ripple{display:none;}
  .nav-item.nav-clicked{transform:none;}
}
