/* famoe.ly — the shared cosmic identity.
 *
 * Every page in the family loads this file and then its own style.css for whatever is genuinely
 * its own. Before this existed, each page carried its own copy of the chrome below, and the
 * copies had already drifted: the Scorecard's colophon aurora was independently reimplemented at
 * .10 alpha with amber dropped, the Songbook's ran at .4/.45; one page called the sparkles
 * .spark, the other .spark-i; the same two background colors were --bg0/--bg1 here and
 * --void/--night there. Nobody decided any of that. It just happened, which is the argument for
 * this file.
 *
 * Rules for editing:
 *   - Anything that makes a page feel like famoe.ly belongs here.
 *   - Anything that encodes what a page MEANS (chart series, tier colors, table density) belongs
 *     in that page's own style.css. Tokens are shared; the mapping of token -> meaning is not.
 *   - Never style bare table/td/th here. The Scorecard's tables run at 9px inside 250px scrolling
 *     columns and the Tape Measure's run at 13px; a shared table rule would silently break one of
 *     them and make cascade order load-bearing.
 */

/* ---- fonts: real files, served from /assets/fonts/ ------------------------------------------
 * These used to be ~94KB of base64 inlined into every single page. As files they are fetched
 * once and cached across the whole site. Both are variable fonts (one file spans the weight
 * axis), latin subset, SIL OFL 1.1 — the license text ships beside them, which the OFL requires.
 * font-display:swap so text paints immediately in the fallback and reflows when the font lands. */
@font-face{
  font-family:'Space Grotesk'; font-style:normal; font-weight:400 700; font-display:swap;
  src:url(/assets/fonts/space-grotesk-latin-variable.woff2) format('woff2');
}
@font-face{
  font-family:'Inter'; font-style:normal; font-weight:400 600; font-display:swap;
  src:url(/assets/fonts/inter-latin-variable.woff2) format('woff2');
}

/* ---- tokens ---------------------------------------------------------------------------------
 * The landing page is the identity source, so its names win: --void/--night, not --bg0/--bg1.
 * --blue and --pink existed only in the Songbook but the Tape Measure had independently
 * hardcoded the same two hexes, so they are promoted here rather than reinvented a third time. */
:root{
  --void:#08050f; --night:#0f0a1c;
  --ink:#ece7f7; --muted:#9a90b6; --faint:#6c6385;

  --teal:#2de2c6; --violet:#a78bfa; --magenta:#ff5ed2;
  --amber:#ffd166; --flare:#ff8a3d; --ember:#ff5a3c;
  --blue:#6ea8ff; --pink:#ff8fb0;

  /* Semantic aliases. The Scorecard had invented #5be3a7/#ff7a90 and the Tape Measure had its own
   * ok/warn/bad off a local python dict — two private micro-palettes for the same three ideas. */
  --ok:var(--teal); --warn:var(--flare); --bad:var(--ember);

  /* Surfaces. --glass is the blurred, interactive finish; --panel-solid is the flat one for
   * data-dense pages that cannot afford compositing cost (see .panel below). */
  --glass:rgba(255,255,255,.045); --glass-brd:rgba(255,255,255,.10);
  --panel-solid:#1a1030;

  /* Blur is a 3-tier scale, not three accidents: settled surfaces get more than floating ones. */
  --blur-chrome:10px;   /* the fanbar */
  --blur-panel:12px;    /* cards, tiles, hero glass */
  --blur-float:8px;     /* tooltips, transient overlays */

  --display:'Space Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --body:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
}

/* ---- reset + base ---------------------------------------------------------------------------- */
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0; min-height:100vh;
  background:var(--void); color:var(--ink); font-family:var(--body);
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}

/* ---- the cosmic background ------------------------------------------------------------------
 * Three radial gradients over a vertical wash: a violet dome up top, a teal cast bleeding in from
 * the left, a magenta glow rising from the bottom-right. Static — no drifting blobs.
 * It lives on a fixed <div class="bg">, not on body, so every page in the family has the same
 * structure and the shell template never has to branch. */
.bg{
  position:fixed; inset:0; z-index:-2;
  background:
    radial-gradient(1100px 620px at 50% -6%, #241041 0%, transparent 60%),
    radial-gradient(900px 520px at 8% 4%,  #0c2f3a 0%, transparent 52%),
    radial-gradient(1000px 760px at 92% 120%, #2a0f33 0%, transparent 55%),
    linear-gradient(180deg,var(--void),var(--night) 62%,var(--void));
}
.stars{position:fixed; inset:0; z-index:-2; pointer-events:none; overflow:hidden}
.star{position:absolute; border-radius:50%; background:#fff; box-shadow:0 0 4px rgba(255,255,255,.55)}
.grain{
  position:fixed; inset:0; z-index:-1; pointer-events:none; opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- the fan-page bar -----------------------------------------------------------------------
 * Conspicuous and always visible. This is a fan site and it says so before it says anything else;
 * the disclaimer is not fine print at the bottom. */
.fanbar{
  position:sticky; top:0; z-index:50;
  background:linear-gradient(180deg,rgba(20,13,34,.92),rgba(15,10,28,.86));
  backdrop-filter:blur(var(--blur-chrome));
  border-bottom:1px solid rgba(255,209,102,.22);
  box-shadow:0 6px 22px -14px rgba(0,0,0,.9);
}
.fanbar-in{
  max-width:1180px; margin:0 auto; padding:9px 22px;
  display:flex; gap:10px 18px; align-items:center; flex-wrap:wrap; font-size:13px;
}
.fanbar-home{
  color:var(--teal); font-family:var(--display); font-weight:600;
  text-decoration:none; white-space:nowrap; margin-right:auto;
}
.fanbar-home:hover,.fanbar-home:focus-visible{color:#8ff6e5}
.fanbar-msg{color:var(--muted)}
.fanbar-msg b{color:var(--ink); font-weight:600}
.fanbar-star{color:var(--amber); margin-right:5px}
.fanbar-link{
  color:var(--amber); font-weight:600; text-decoration:none; white-space:nowrap;
  border-bottom:1px solid rgba(255,209,102,.4); padding-bottom:1px; margin-left:auto; transition:.15s;
}
.fanbar-link:hover,.fanbar-link:focus-visible{color:#ffe19a; border-bottom-color:#ffe19a}
.fanbar-link .ext{font-size:.85em; opacity:.85}

/* ---- layout ---------------------------------------------------------------------------------
 * --wrap-max is set per page by the shell template. The four pages genuinely want different
 * measures (a 900px landing page vs an 1180px dashboard), and forcing one number on all of them
 * would be uniformity for its own sake. */
.wrap{max-width:var(--wrap-max,1180px); margin:0 auto; padding:0 22px 90px}

/* ---- hero -----------------------------------------------------------------------------------
 * Three tiers, chosen per page by the shell: the full sun + kicker + wordmark, a kicker-only
 * variant for dense pages, or nothing at all. Dense data pages skip the sun on purpose — a 350px
 * glowing graphic sitting on top of two charts fights the tone and costs first paint. */
.hero{position:relative; text-align:center; padding:66px 0 20px}
.hero--tall{padding:96px 0 40px}
.sun{
  position:absolute; left:50%; top:2px; transform:translateX(-50%);
  width:clamp(220px,34vw,326px); aspect-ratio:1; z-index:-1; opacity:.5;
  filter:drop-shadow(0 14px 66px rgba(255,138,61,.2));
}
.hero--tall .sun{top:-14px; width:clamp(228px,40vw,356px); opacity:.68; filter:drop-shadow(0 14px 66px rgba(255,138,61,.22))}
.sun svg{display:block; width:100%; height:100%; transform-origin:center}
.kicker{
  font-family:var(--display); text-transform:uppercase; letter-spacing:.28em;
  font-size:11.5px; color:var(--teal); margin:0 0 18px; opacity:.92;
}
/* The wordmark, landing page only. Every other page uses its own h1 sizing — content density
 * differs too much to share one scale. */
.wm{
  font-family:var(--display); font-weight:700; margin:0; line-height:.9;
  font-size:clamp(58px,13vw,132px); letter-spacing:-.02em; color:#f6f2ff;
  text-shadow:0 1px 2px rgba(8,5,15,.6), 0 3px 30px rgba(8,5,15,.55), 0 6px 48px rgba(120,80,200,.3);
}
.wm .moe{color:#ffdd84}
.wm .dot{color:var(--flare); text-shadow:0 0 22px rgba(255,138,61,.9)}
.blurb{color:var(--muted); max-width:560px; margin:22px auto 0; font-size:16.5px; line-height:1.62}
.blurb b{color:var(--ink); font-weight:600}
.pulse{
  display:inline-block; width:7px; height:7px; border-radius:50%;
  background:var(--teal); box-shadow:0 0 10px var(--teal); margin-right:7px;
  animation:pl 1.6s ease-in-out infinite;
}
@keyframes pl{50%{opacity:.3; transform:scale(.7)}}

/* ---- surfaces -------------------------------------------------------------------------------
 * Two finishes, one shape.
 *
 * .glass is the family default: translucent, blurred, for surfaces you interact with or that
 * carry a little air — tool cards, stat tiles, hero furniture.
 *
 * .panel is the flat variant, and it exists for one honest reason: backdrop-filter is a real
 * compositing cost, and the Tape Measure carries two large inline SVGs, a dense numeric table and
 * a stack of <details>. Blurring ten surfaces on that page is where scroll jank would come from.
 * Same padding, radius and typography as .glass; it just doesn't blur. Use it on data-dense pages. */
.glass{
  position:relative;
  background:var(--glass); border:1px solid var(--glass-brd);
  backdrop-filter:blur(var(--blur-panel));
  border-radius:20px;
}
.glass--tile{border-radius:18px}
/* the hairline sheen along the top edge */
.glass::before{
  content:""; position:absolute; left:0; right:0; top:0; height:1px;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.14),transparent);
}

/* Smoked glass: the same surface, tinted DARK instead of light.
 *
 * This is a real distinction, not drift. Plain .glass is translucent white — lovely for a card
 * floating over the cosmos, and hopeless behind a dense control panel, where the starfield shows
 * through and fights the text. Anything holding inputs, long prose or a sticky toolbar wants the
 * background muted rather than sampled. Six surfaces on the Songbook had each invented their own
 * version of one of these two ideas, at six different opacities. There are two ideas. */
.glass--smoked{
  background:rgba(16,10,28,.62);
  backdrop-filter:blur(14px);
}
/* Sticky toolbars sit over scrolling content, so they get the strongest treatment in the family. */
.glass--smoked.is-sticky{
  background:rgba(16,10,28,.74);
  backdrop-filter:blur(18px) saturate(1.2);
}
.panel{
  position:relative;
  background:var(--panel-solid);
  border-radius:14px;
}

/* ---- stat tiles -------------------------------------------------------------------------------
 * The row of figures under a page's hero: label, big number, optional sub-line. It says how big
 * the thing you are looking at is, before you look at it.
 *
 * This lives here rather than in each page's own sheet because it was drifting — the Tape Measure
 * drew amber numbers, the Songbook drew a white-to-lilac gradient, and they were the same
 * component. One definition, one color.
 *
 * Grid, not flex: with flex-wrap six tiles broke 5+1 and the orphan stretched itself across the
 * full width, reading as a banner rather than a sixth tile. auto-fit keeps every tile the same
 * width and lets the row count fall out of the viewport.
 *
 * The FINISH is the page's call, not this component's: pair .tile with .panel on data-dense pages
 * (flat, cheap to composite) or with .glass .glass--tile where the row can carry some air and
 * wants the hairline sheen. Both are family; the numbers read the same either way. */
.tiles{display:grid; grid-template-columns:repeat(auto-fit,minmax(148px,1fr)); gap:10px;
  margin:26px 0 8px}
.tile{padding:14px 16px; text-align:center; overflow:hidden}
.tl{font-size:11px; text-transform:uppercase; letter-spacing:.09em; color:var(--muted)}
.tv{font-family:var(--display); font-weight:700; font-size:28px; color:var(--amber); line-height:1.25}
.ts{font-size:11.5px; color:var(--muted)}

/* ---- explainer --------------------------------------------------------------------------------
 * The "What is this?" fold. A hero blurb should say why a page exists in one breath; everything
 * that wants a paragraph to explain a metric goes in here, where a reader opts into it. */
.explainer{margin:30px 0 4px; overflow:hidden}
.explainer summary{list-style:none; cursor:pointer; padding:17px 22px; font-family:var(--display);
  font-weight:600; font-size:16px; display:flex; align-items:center; gap:11px; color:var(--ink);
  transition:.15s}
.explainer summary::-webkit-details-marker{display:none}
.explainer summary:hover{color:var(--teal)}
.explainer .ex-q{color:var(--teal); font-size:15px}
.explainer .chev{margin-left:auto; opacity:.6; transition:transform .22s}
.explainer[open] .chev{transform:rotate(180deg)}
.explainer[open] summary{border-bottom:1px solid rgba(255,255,255,.07)}
.explainer .ex-body{padding:18px 24px 22px; color:var(--muted); font-size:15px; line-height:1.66;
  max-width:none}
.explainer .ex-body p{margin:0 0 13px}
.explainer .ex-body p:last-child{margin-bottom:0}
.explainer .ex-body b{color:var(--ink); font-weight:600}
.explainer .ex-body .try{color:var(--teal); font-weight:600}
.explainer .ex-body em{color:var(--amber); font-style:normal; font-weight:500}

/* ---- links ----------------------------------------------------------------------------------- */
a{color:var(--teal)}
a:hover{text-decoration:underline}

/* ---- fine print -----------------------------------------------------------------------------
 * Sources, methodology, attributions — the stuff that has to be on the page but should never
 * compete with it. Sits above the colophon. */
.foot-note{
  max-width:1000px; margin:0 auto 26px; padding:0 22px;
  color:var(--faint); font-size:11px; line-height:1.6;
}
.foot-note a{color:var(--muted); text-decoration:none; border-bottom:1px solid rgba(154,144,182,.4)}
.foot-note a:hover,.foot-note a:focus-visible{color:var(--ink)}
.foot-note b{color:var(--muted)}

/* ---- footer + the quarantined "spacy" moment ------------------------------------------------
 * The one place the site is allowed to be indulgent: a conic-gradient aurora that only wakes up on
 * hover/focus. Quarantined to the colophon precisely so the rest of the site can stay calm.
 * This block is the landing page's, verbatim. The Scorecard had reimplemented it from scratch at
 * a tenth of the alpha with amber missing; that version is gone. */
.foot{position:relative; overflow:hidden; margin-top:90px; padding:56px 22px 70px; text-align:center}
.colophon{
  position:relative; display:inline-block; max-width:560px;
  padding:24px 30px; border-radius:20px; border:1px solid transparent;
  transition:border-color .35s, background .35s;
}
.colophon::before{
  content:""; position:absolute; inset:-55% -30%; z-index:-1; border-radius:50%;
  background:conic-gradient(from 0deg,
    rgba(45,226,198,.4), rgba(167,139,250,.45), rgba(255,94,210,.4),
    rgba(255,209,102,.4), rgba(45,226,198,.4));
  filter:blur(38px); opacity:0; transition:opacity .55s ease;
}
.colophon:hover,.colophon:focus-within{border-color:var(--glass-brd); background:rgba(255,255,255,.025)}
.colophon:hover::before,.colophon:focus-within::before{opacity:.6; animation:swirl 9s linear infinite}
.colo-run{font-family:var(--display); font-weight:500; font-size:15px; color:var(--ink); margin:0}
.colo-blog{color:var(--muted); font-size:14.5px; margin:9px 0 0; line-height:1.55}
.colo-disc{color:var(--faint); font-size:12.5px; margin:16px 0 0; line-height:1.5}
.colo-disc a{color:var(--muted); text-decoration:none; border-bottom:1px solid rgba(154,144,182,.4)}
.colo-disc a:hover,.colo-disc a:focus-visible{color:var(--ink)}

/* the personal-blog link and its hover sparkles.
 * Named .spark-i, not .spark: the Scorecard already owns .spark for its churn sparklines, and
 * "spark" is exactly the word the next stats page will reach for too. */
.blogwrap{position:relative; white-space:nowrap}
.blog{color:var(--teal); text-decoration:none; border-bottom:1px dashed rgba(45,226,198,.55); transition:.18s}
.blog:hover,.blog:focus-visible{color:#8ff6e5; border-bottom-color:transparent; text-shadow:0 0 12px rgba(45,226,198,.6); outline:none}
.spark-i{position:absolute; opacity:0; color:var(--amber); font-size:11px; pointer-events:none; transition:opacity .2s}
.spark-i.s1{top:-11px; left:-8px; --sd:0s}
.spark-i.s2{top:-7px; right:-9px; --sd:.28s}
.spark-i.s3{bottom:-9px; left:24%; --sd:.5s}
.spark-i.s4{bottom:-11px; right:16%; --sd:.16s}
.blogwrap:hover .spark-i,.blog:focus-visible ~ .spark-i{opacity:1; animation:sparkle 1.15s var(--sd) ease-in-out infinite}

/* ---- motion: restrained, and respectful ------------------------------------------------------ */
@keyframes twinkle{0%,100%{opacity:.28}50%{opacity:1}}
@keyframes breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.035)}}
@keyframes swirl{to{transform:rotate(360deg)}}
@keyframes sparkle{
  0%,100%{transform:scale(.55) rotate(0deg); opacity:.2}
  50%{transform:scale(1.15) rotate(90deg); opacity:1}
}
@media (prefers-reduced-motion:no-preference){
  .star{animation:twinkle 4s var(--d,0s) ease-in-out infinite}
  .sun svg{animation:breathe 9s ease-in-out infinite}
}
@media (prefers-reduced-motion:reduce){
  .pulse{animation:none!important}
  .colophon:hover::before,.colophon:focus-within::before{animation:none!important}
  .blogwrap:hover .spark-i,.blog:focus-visible ~ .spark-i{animation:none!important}
}

/* ---- responsive ------------------------------------------------------------------------------
 * 640px, which is what two of the three pages already used before this file existed. */
@media (max-width:640px){
  .fanbar-home,.fanbar-link{margin:0}
  .fanbar-in{gap:4px 14px; font-size:11.5px; justify-content:center}
  .hero{padding:52px 0 16px}
  .hero--tall{padding:74px 0 30px}
  .kicker{letter-spacing:.2em; font-size:10.5px}
  .blurb{font-size:15.5px}
  .wm{font-size:clamp(52px,15vw,88px)}
}
