/* LaunchLens design system -- "Indie Directory": the numbered-leaderboard,
   all-monospace, one-green-accent world of the real product-discovery
   directories LaunchLens's own audience already reads daily
   (canivibecodeit.com, trustmrr.com). Chosen and approved by the user
   directly against those two real sites, via a side-by-side mockup of
   LaunchLens's own real content -- not an invented palette.

   - Ground is warm paper, not stark white/gray -- these directories never
     use a cold neutral.
   - ONE accent (a confident, verified-contrast green) does double duty as
     both the brand color and the "good/yes/hit" semantic -- the same
     overload the reference sites use, not a coincidence to fix.
   - Badges are SOLID fills with dark-mode-safe text, small (3px) radius --
     the deliberate opposite of the previous system's tinted pill Tags.
     Lowercase badge text matches the genre's own voice.
   - Card/panel/input frames take a full 1px INK-colored border (not a soft
     gray hairline) -- the graphic, poster-like contrast this world reads
     on sight. Table body rows use a dashed divider; the header rule is
     solid ink.
   - Every text-on-fill and text-on-tint pairing below is checked against
     the real WCAG contrast formula (see the file's own verification
     script in this session's history), not judged by eye -- same
     discipline the previous Carbon system used, carried forward. Several
     candidate greens/ambers failed at first pass and were darkened
     (light mode) or the badge text flipped to dark-on-bright (dark mode,
     where white-on-bright-green measures only 2.03:1) until verified. */

@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap");

:root {
  /* --- Color: light (warm paper) --- */
  --ink: #14150f;
  --ink-soft: #55544a;
  --ink-faint: #6b6a5c;      /* verified 4.92:1 on --paper, 5.37:1 on --paper-raised */
  --paper: #f4f3ee;
  --paper-sunken: #eae8dd;   /* one step down -- toolbar / hover wash */
  --paper-raised: #fdfdfb;   /* cards, inputs, panels */
  --line: #d6d4c6;           /* dashed row dividers, subtle rules */
  --line-strong: #14150f;    /* the graphic ink-black frame border */

  /* --- Color: single accent (verified green), doubles as "good" --- */
  --accent: #146b37;              /* verified: white text 6.59:1, text-on-paper 5.93:1 */
  --accent-ink: #0e5228;          /* hover/active, darker still */
  --accent-active: #0a3f1f;
  --accent-tint: #e2ede7;         /* 12% mix -- accent text on this tint verified 5.49:1 */

  /* --- Color: semantic (solid-fill badges; all white-on-fill verified) --- */
  --good: var(--accent);
  --good-tint: var(--accent-tint);
  --mid: #9c6a0a;                 /* verified: white text 4.69:1 */
  --mid-tint: color-mix(in srgb, #9c6a0a 12%, white);
  --bad: #b7362c;                 /* verified: white text 5.87:1 */
  --bad-tint: color-mix(in srgb, #b7362c 10%, white);
  --info: #2f4fc4;                /* verified: white text 6.92:1 */
  --info-tint: color-mix(in srgb, #2f4fc4 10%, white);
  --warn: var(--mid);
  --warn-tint: var(--mid-tint);
  --success: var(--good);
  --success-tint: var(--good-tint);

  /* --- Color: stage badges --- */
  --stage-forming: var(--ink-soft);
  --stage-forming-tint: color-mix(in srgb, var(--ink-soft) 12%, white);
  --stage-heating: var(--mid);
  --stage-heating-tint: var(--mid-tint);
  --stage-crowded: var(--bad);
  --stage-crowded-tint: var(--bad-tint);
  --stage-cooling: var(--info);
  --stage-cooling-tint: var(--info-tint);

  --danger: var(--bad);
  --danger-tint: var(--bad-tint);

  /* --- Color: badge TEXT -- solid fills, white in light mode (all
     verified above); dark mode overrides this to dark-on-bright below,
     since white fails badly on the brighter dark-mode fills (2.03:1). --- */
  --badge-text: #ffffff;

  /* --- Color: sponsor card fills -- a separate small palette from the
     site's one main accent (sponsor cards are a deliberately different,
     commercial section). Each individually darkened from its natural hue
     until it hit >=4.5:1 against white --badge-text (computed, not
     eyeballed -- e.g. raw #eab308 measured only 1.92:1). --- */
  --sponsor-0: #2f6fed;
  --sponsor-1: #9e42f6;
  --sponsor-2: #c35305;
  --sponsor-3: #e0177a;
  --sponsor-4: #0f8578;
  --sponsor-5: #916f05;

  /* --- Type: one committed voice -- monospace everywhere, the genre's
     own convention, not a "technical" costume grafted onto a sans app. --- */
  --font-ui: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* --- Spacing: unchanged scale -- a redesign of the visual world, not
     the layout rhythm that already works. --- */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;

  /* --- Shape: sharp but not square -- a small 3-4px radius, distinct from
     both the previous system's 0 and a soft rounded-card default. --- */
  --radius-sm: 3px;
  --radius-md: 4px;
  --radius-pill: 999px;

  --shadow-sm: none;
  --shadow-md: 0 2px 10px rgba(20, 21, 15, 0.35);
  --focus-ring: none;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #f2f1e6;
    --ink-soft: #c9c7b5;      /* verified 10.68:1 */
    --ink-faint: #9b9884;     /* verified 6.26:1 */
    --paper: #15160f;
    --paper-sunken: #1b1c14;
    --paper-raised: #1f2017;
    --line: #3a3928;
    --line-strong: #f2f1e6;   /* the frame border flips to a light stroke */

    --accent: #3ecf6e;              /* verified 8.98:1 vs --paper */
    --accent-ink: #56dc82;
    --accent-active: #6fe895;
    --accent-tint: color-mix(in srgb, #3ecf6e 16%, #15160f);

    --good: var(--accent);
    --good-tint: var(--accent-tint);
    --mid: #e0a838;                 /* verified dark-ink-on-fill 8.52:1 */
    --mid-tint: color-mix(in srgb, #e0a838 16%, #15160f);
    --bad: #ff6f64;                 /* verified dark-ink-on-fill 6.68:1 */
    --bad-tint: color-mix(in srgb, #ff6f64 16%, #15160f);
    --info: #7c93ff;                /* verified dark-ink-on-fill 6.48:1 */
    --info-tint: color-mix(in srgb, #7c93ff 16%, #15160f);
    --warn: var(--mid);
    --warn-tint: var(--mid-tint);
    --success: var(--good);
    --success-tint: var(--good-tint);

    --stage-forming: var(--ink-soft);
    --stage-forming-tint: color-mix(in srgb, var(--ink-soft) 16%, #15160f);
    --stage-heating: var(--mid);
    --stage-heating-tint: var(--mid-tint);
    --stage-crowded: var(--bad);
    --stage-crowded-tint: var(--bad-tint);
    --stage-cooling: var(--info);
    --stage-cooling-tint: var(--info-tint);

    --danger: var(--bad);
    --danger-tint: var(--bad-tint);

    /* Dark mode's fills are bright-on-dark, the inverse of light mode's
       deep-on-light -- white text on them measures only 2.03:1 (verified),
       so badge text flips to the near-black ink instead. */
    --badge-text: var(--paper);

    /* Dark mode keeps these near their natural saturation (bright fill +
       near-black text is the correct pairing here, the inverse of light
       mode) -- only the blue needed brightening to clear 4.5:1 against
       --paper (#15160f); the other five already passed at 4.6-9.5:1. */
    --sponsor-0: #427cef;
    --sponsor-1: #a855f7;
    --sponsor-2: #f97316;
    --sponsor-3: #ec4899;
    --sponsor-4: #14b8a6;
    --sponsor-5: #eab308;

    --shadow-sm: none;
    --shadow-md: 0 2px 12px rgba(0, 0, 0, 0.6);
    --focus-ring: none;
  }
}

/* =========================== Reset & base =========================== */

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

html {
  color-scheme: light dark;
}

body {
  margin: 0;
  padding: 0;
  font: 400 0.875rem/1.5 var(--font-ui);
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--accent-tint);
  color: var(--ink);
}

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

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

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* =========================== Type scale =========================== */
/* One committed monospace voice throughout. Headings carry weight (not
   just size) since mono figures read lighter than a proportional sans at
   the same weight -- a real, deliberate departure from the previous
   system's light/regular heading convention. */

h1,
h2,
h3,
h4 {
  font-family: var(--font-ui);
  color: var(--ink);
  margin: 0 0 var(--sp-2);
}

h1 {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin-bottom: var(--sp-3);
}

h2 {
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.35;
  margin-top: var(--sp-6);
  margin-bottom: var(--sp-2);
}

h3 {
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
}

h4 {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-faint);
  text-transform: lowercase;
  letter-spacing: 0.02em;
}

p {
  margin: 0 0 var(--sp-3);
}

.num,
td.num,
.opp-cell,
th.num {
  font-variant-numeric: tabular-nums;
}

/* =========================== Page shell =========================== */

header,
main,
footer,
#filter-bar {
  max-width: 1300px;
  margin: 0 auto;
  padding: 0 var(--sp-5);
}

header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
  padding-top: var(--sp-4);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line-strong);
}

header > a {
  font-weight: 800;
  font-size: 1.05rem;
  color: var(--accent);
  text-decoration: none;
  letter-spacing: -0.01em;
  text-transform: lowercase;
}

.site-tagline {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* A stacked logo-then-nav header, not side by side: with 17 tabs, sharing
   a row with the logo made wrapped rows start at an offset and end at
   uneven, arbitrary widths -- reads as scattered rather than organized.
   Full-width wrapping from a fixed left edge, plus a bordered "chip" per
   tab (matching the app's own badge language) instead of bare inline
   text, is what turns a wrapped nav into rows that read as deliberate. */
header nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  row-gap: var(--sp-2);
  column-gap: var(--sp-2);
  width: 100%;
}

header nav a {
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 500;
  font-size: 0.8125rem;
  text-transform: lowercase;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper-raised);
}

header nav a:hover {
  color: var(--ink);
  border-color: var(--accent);
}

header nav a[aria-current="page"] {
  color: var(--badge-text);
  background: var(--accent);
  border-color: var(--accent);
}

main {
  padding-top: var(--sp-6);
  padding-bottom: var(--sp-7);
  min-height: 50vh;
}

footer {
  padding-top: var(--sp-4);
  padding-bottom: var(--sp-6);
  border-top: 1px solid var(--line-strong);
}

footer p {
  color: var(--ink-faint);
  font-size: 0.8rem;
  margin: 0;
  max-width: 70ch;
}

/* ======================= Sponsor rail (ads app) ======================= */
/* Verified directly against trustmrr.com's own rendered DOM: its sponsor
   rails are `position: fixed; top: 0; height: 100vh` at the true viewport
   edges -- NOT part of the scrolling page (a stretched grid column, which
   scrolls away with the page, was wrong). The rail itself never moves;
   only rotating which page of cards is inside it changes. body's own
   horizontal padding reserves the width so header/filter-bar/main/footer
   (still centered via their own max-width/margin:auto rule) never render
   underneath the fixed rails. Hidden below 1200px entirely rather than
   reflowed: a squeezed rail competing with narrower content read worse
   than just not showing it. */

@media (min-width: 1201px) {
  body {
    padding: 0 216px; /* 200px rail + 16px clearance */
  }
}

.sponsor-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  width: 200px;
  padding: var(--sp-6) var(--sp-4);
  box-sizing: border-box;
  overflow: hidden;
  /* Without this, position:fixed alone doesn't guarantee paint order --
     #main's own stacking context (any descendant that sets position, e.g.
     a tooltip or chart layer) can end up rendered on top during scroll on
     mobile, confirmed as a real bug: the fixed top/bottom sponsor strips
     were disappearing beneath page content while scrolling. z-index:40
     puts both rails and the mobile marquee strips above the entire #main
     subtree unconditionally. */
  z-index: 40;
}

.sponsor-rail-left {
  left: 0;
}

.sponsor-rail-right {
  right: 0;
}

/* .sponsor-page's own height:100% (below) needs an ancestor with a real
   height to resolve against -- .sponsor-rail (position:fixed, top:0,
   bottom:0) has one, but .sponsor-pages sits between them in the DOM
   (added when the mobile marquee track was split out into its own
   sibling) and has no explicit height of its own, so without this line
   .sponsor-page's 100% collapsed to its own content height instead of
   the rail's -- which left space-between nothing to distribute, and
   cards lost the spacing between them entirely.

   position:relative + perspective turns this into the flip transition's
   3D stage: every .sponsor-page below stacks absolutely inside it (so
   swapping pages never reflows this box), and perspective gives their
   rotateX() transforms real depth instead of a flat, fake-looking spin. */
.sponsor-pages {
  height: 100%;
  position: relative;
  perspective: 800px;
}

.sponsor-page {
  display: flex;
  flex-direction: column;
  /* space-between spreads VISIBLE_PER_RAIL (5) natural-sized cards evenly
     across the full rail height -- matching trustmrr's own rail, and how
     this looked before VISIBLE_PER_RAIL was briefly (and wrongly) dropped
     to 2, which is what actually broke this layout: 2 cards pinned to the
     very top/bottom with a huge empty gap. At 5, that failure mode doesn't
     apply -- don't reintroduce it by second-guessing this again. */
  justify-content: space-between;
  height: 100%;
  /* Absolutely stacked (not display:none'd) so the outgoing and incoming
     page can both be mid-transform at once -- a real page-flip, not an
     instant swap. Resting state (no .is-active) is rotated 90 degrees on
     its top edge and invisible; .is-active rotates it flat and visible.
     Toggling which page has that class (see rail.js) is the entire
     animation -- the transition below does the rest. */
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  transform-origin: top center;
  transform: rotateX(-90deg);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.5s ease-in-out, opacity 0.3s ease-in-out;
}

.sponsor-page.is-active {
  transform: rotateX(0deg);
  opacity: 1;
  pointer-events: auto;
}

.sponsor-card {
  position: relative; /* anchors .sponsor-clicks in the corner */
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-1);
  padding: var(--sp-3);
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--ink);
}

/* Absolutely positioned -- out of normal flow, so it can't disturb the
   already-fought-over vertical space budget (icon/name always visible,
   only the tagline drops on a short viewport, see the max-height:700px
   rule below). Hidden entirely at 0 clicks rather than showing "0" --
   this renders on the live, public rail, and a visible zero reads as a
   bad signal for an ad next to one that has real clicks. */
.sponsor-clicks {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 0.625rem;
  font-weight: 600;
  opacity: 0.85;
}

.sponsor-card:hover {
  border-color: var(--accent);
}

/* A real paid sponsor's whole card is filled with its assigned color (not
   just the icon) -- deliberately bold/commercial-looking, distinct from
   an unsold house-ad card. color: var(--badge-text) here is the same
   solid-fill-needs-real-contrast-checked-text pattern every other badge
   in this app already uses; .sponsor-fill-N below supplies which color. */
.sponsor-card.sponsor-card-colored {
  border-color: transparent;
  color: var(--badge-text);
}

.sponsor-card.sponsor-card-colored:hover {
  border-color: var(--badge-text);
}

.sponsor-card.sponsor-card-colored .sponsor-tagline {
  color: inherit;
}

.sponsor-fill-0 { background-color: var(--sponsor-0); }
.sponsor-fill-1 { background-color: var(--sponsor-1); }
.sponsor-fill-2 { background-color: var(--sponsor-2); }
.sponsor-fill-3 { background-color: var(--sponsor-3); }
.sponsor-fill-4 { background-color: var(--sponsor-4); }
.sponsor-fill-5 { background-color: var(--sponsor-5); }

.sponsor-card .sponsor-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: #ffffff;
  /* Fixed white, not per-sponsor -- the card itself carries the color now;
     favicons (transparent or white-backed, typical for real sites) need a
     plain neutral backing to stay legible regardless of the card color.
     A ring + soft shadow (not a flat, borderless circle) is what makes
     this read as a deliberate badge component sitting ON the colored
     card, rather than a stray white sticker dropped on top of it. */
  border: 2px solid rgba(255, 255, 255, 0.6);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

.sponsor-card .sponsor-icon img {
  width: 22px;
  height: 22px;
  object-fit: contain;
  border-radius: 50%;
}

.sponsor-card .sponsor-name {
  font-weight: 700;
  font-size: 0.8125rem;
}

.sponsor-card .sponsor-tagline {
  color: var(--ink-soft);
  font-size: 0.75rem;
  line-height: 1.3;
  /* Capped at 2 lines regardless of length -- keeps every card's height
     predictable so space-between's even spacing (above) stays even. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sponsor-card-house {
  border-style: dashed;
  color: var(--ink-soft);
}

.sponsor-card-house:hover {
  color: var(--ink);
}

/* Below 1200px width (covers virtually all mobile screens), two 200px
   side rails can't fit alongside real content at all -- restructures into
   a horizontal scrolling strip instead (one pinned top, one bottom,
   reusing the existing left/right sponsor split rather than merging the
   two lists), the standard mobile pattern for this. Confirmed with the
   user this is wanted here specifically -- unlike the max-height rule
   below, this one is a real width constraint, not a judgment call about
   when fixed-size cards stop fitting vertically. */
/* Default (desktop) state for the mobile-only marquee track -- see
   _sponsor_rail.html for why it's rendered (twice, for a seamless loop)
   even outside the max-width:1200px block below. */
.sponsor-marquee-track {
  display: none;
}

@media (max-width: 1200px) {
  .sponsor-rail {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    width: auto;
    height: auto;
    padding: var(--sp-2) 0;
    overflow: hidden;
  }

  .sponsor-rail-left {
    top: 0;
    bottom: auto;
  }

  .sponsor-rail-right {
    top: auto;
    bottom: 0;
  }

  /* The desktop pages (discrete rotation, see rail.js) are replaced
     entirely by the continuously-moving marquee track below -- not shown
     at the same time as it. */
  .sponsor-pages {
    display: none;
  }

  /* A real horizontally-scrollable container now (see ads/rail.js's
     bindMarquee), not a pure CSS transform animation -- that had nothing
     for a touch/drag to actually grab, so a finger on the strip couldn't
     move it. JS auto-advances scrollLeft when idle and gets out of the
     way the instant the user touches it, so they can drag through the
     cards at their own pace like any other native scroll strip. */
  .sponsor-marquee-track {
    display: flex;
    flex-direction: row;
    gap: var(--sp-3);
    width: max-content;
    overflow-x: auto;
    /* Deliberately NOT -webkit-overflow-scrolling:touch -- that legacy,
       now-deprecated property puts iOS Safari's scroller on a separate
       compositor layer that (a real, well-known WebKit quirk) doesn't
       reliably repaint on a JS-driven scrollLeft write until the user
       touches the element again -- exactly "manual scroll works, JS
       auto-advance visibly does nothing" on iOS. Modern WebKit already
       gives plain overflow-x:auto native momentum scrolling for free. */
    scrollbar-width: none; /* Firefox */
  }

  .sponsor-marquee-track::-webkit-scrollbar {
    display: none; /* Chrome/Safari */
  }

  .sponsor-card {
    flex-direction: row;
    flex: 0 0 auto;
    white-space: nowrap;
    padding: var(--sp-2) var(--sp-3);
  }

  /* No room for a second line in a compact horizontal card -- name only. */
  .sponsor-card .sponsor-tagline {
    display: none;
  }

  .sponsor-card .sponsor-icon {
    width: 1.75rem;
    height: 1.75rem;
  }

  .sponsor-card .sponsor-icon img {
    width: 16px;
    height: 16px;
  }

  /* min-width:1201px's left/right reservation (for the vertical rails,
     gone below 1200px) doesn't apply here -- add top/bottom instead, for
     the two horizontal strips. */
  body {
    padding: 3.5rem 0;
  }
}

/* Icon and name are never resized or hidden -- only the tagline (the one
   truly optional line) drops when a short window can't fit all 5 cards
   with it. Dropping it shrinks each card's own height by ~35px (removing
   two lines of text + the gap around them), which is what actually needs
   to happen here -- space-between (already on .sponsor-page) keeps
   handling the gap BETWEEN cards on its own, unchanged. 700px is the
   viewport height where 5 full cards (with tagline) would need zero gap
   to fit at all; below it, every following card would otherwise start
   clipping one by one as height keeps decreasing, so all 5 drop their
   tagline together at the same point rather than each disappearing at a
   different, mysterious threshold. */
@media (max-height: 700px) {
  .sponsor-card .sponsor-tagline {
    display: none;
  }
}

/* Plain POST forms (sponsor signup/waitlist) -- the filter bar's own
   input styling is scoped to .filter-form, so these need their own. */
.sponsor-form label {
  display: block;
  margin-bottom: var(--sp-4);
  font-weight: 500;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.sponsor-form input[type="text"],
.sponsor-form input[type="email"],
.sponsor-form input[type="url"] {
  display: block;
  width: 100%;
  max-width: 420px;
  margin-top: var(--sp-1);
  height: 2.5rem;
  padding: 0 var(--sp-3);
  font: inherit;
  color: var(--ink);
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
}

/* =========================== Filter bar =========================== */

.filter-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-5);
  align-items: flex-end;
}

.filter-form [hidden] {
  display: none;
}

.filter-form label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: lowercase;
}

.filter-form fieldset {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

#news-filter fieldset,
#funding-filter fieldset {
  display: block;
  width: 100%;
  columns: 12rem;
  column-gap: var(--sp-5);
}

#news-filter fieldset legend,
#funding-filter fieldset legend {
  margin-bottom: var(--sp-1);
}

#news-filter fieldset > label,
#funding-filter fieldset > label {
  break-inside: avoid;
  margin-bottom: var(--sp-1);
}

.filter-form fieldset legend {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: lowercase;
  padding: 0;
  margin-bottom: var(--sp-1);
}

.filter-form fieldset > label {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 400;
  color: var(--ink);
  text-transform: none;
  break-inside: avoid;
  margin-bottom: var(--sp-1);
}

.filter-form select,
.filter-form input[type="number"],
.filter-form input[type="text"],
.filter-form input[type="search"],
.filter-form input[type="date"] {
  font: inherit;
  font-size: 0.875rem;
  color: var(--ink);
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  height: 2.5rem;
  padding: 0 var(--sp-3);
  min-width: 9rem;
}

.filter-form input[type="number"] {
  min-width: 5rem;
}

.filter-form input[type="checkbox"] {
  accent-color: var(--accent);
  width: 1rem;
  height: 1rem;
}

.btn,
.filter-form button[type="submit"] {
  font: inherit;
  font-weight: 700;
  font-size: 0.875rem;
  text-transform: lowercase;
  color: var(--badge-text);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  height: 2.5rem;
  padding: 0 var(--sp-4);
  cursor: pointer;
}

.btn:hover,
.filter-form button[type="submit"]:hover {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
}

.btn:active,
.filter-form button[type="submit"]:active {
  background: var(--accent-active);
  border-color: var(--accent-active);
}

#filter-bar {
  padding-top: var(--sp-4);
  padding-bottom: var(--sp-4);
  background: var(--paper-sunken);
  border-bottom: 1px solid var(--line-strong);
}

#search-box,
#funding-filter,
#news-filter {
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  margin-bottom: var(--sp-5);
}

/* =========================== Notices (unified) =========================== */

.screen-intro,
.caveat,
.weights-explainer,
.metric-explainer {
  max-width: 68ch;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--info-tint);
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
  margin: 0 0 var(--sp-4);
}

.caveat {
  background: var(--warn-tint);
  color: var(--ink);
}

.weights-explainer,
.metric-explainer {
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  color: var(--ink-soft);
  max-width: none;
}

.metric-explainer ul {
  margin: var(--sp-2) 0 0;
  padding-left: 1.1rem;
}

.metric-explainer li {
  margin-bottom: var(--sp-1);
}

.window-note {
  color: var(--ink-faint);
  font-size: 0.8125rem;
  margin-bottom: var(--sp-4);
}

.chart-caption {
  color: var(--ink-faint);
  font-size: 0.75rem;
  margin-top: var(--sp-2);
}

.gap-marker {
  color: var(--danger);
  font-weight: 700;
}

.error-text {
  color: var(--danger);
}

.gap-list {
  margin: var(--sp-4) 0;
}

.gap-table td, .gap-table th {
  font-variant-numeric: tabular-nums;
}

.gap-row {
  cursor: pointer;
}

.gap-row:hover {
  background: var(--paper-sunken);
}

.gap-status {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  text-transform: lowercase;
  color: var(--badge-text);
}

.gap-status-signal {
  background: var(--success);
}

.gap-status-thin {
  background: var(--warn);
}

.gap-status-untested {
  background: var(--info);
}

.pain-point-list {
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-3);
}

.pain-point-list h3 {
  margin: 0 0 var(--sp-2);
  font-size: 0.85rem;
}

.pain-point-list ul {
  margin: 0;
  padding-left: 1.1rem;
}

.pain-point-list li {
  margin-bottom: var(--sp-1);
  font-size: 0.875rem;
}

.pp-points {
  color: var(--ink-faint);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}

/* =========================== Badges =========================== */
/* Solid fill, small radius, lowercase -- the deliberate opposite of the
   previous system's tinted-pill Tag. Text color is --badge-text (white in
   light mode, dark ink in dark mode -- see the :root block's comment on
   why dark mode flips this). */

.vibe-badge {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: lowercase;
  letter-spacing: 0.01em;
  padding: 2px 9px;
  border-radius: var(--radius-sm);
  margin-right: var(--sp-2);
  text-decoration: none;
  color: var(--badge-text);
}

.vibe-yes {
  background: var(--success);
}

.vibe-partial {
  background: var(--warn);
}

.vibe-no {
  background: var(--danger);
}

.difficulty-easy {
  background: var(--success);
}

.difficulty-medium {
  background: var(--warn);
}

.difficulty-hard {
  background: var(--danger);
}

.difficulty-easy,
.difficulty-medium,
.difficulty-hard {
  color: var(--badge-text);
}

.research-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.research-card {
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
}

.research-card header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}

/* =========================== Tooltips =========================== */

.hint {
  position: relative;
  cursor: help;
  text-decoration: none;
  color: inherit;
}

.hint::after {
  content: attr(data-tip);
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  z-index: 30;
  width: max-content;
  max-width: 260px;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.75rem;
  font-family: var(--font-ui);
  font-style: normal;
  font-weight: 400;
  line-height: 1.35;
  white-space: normal;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(3px);
  pointer-events: none;
  transition: opacity 0.1s ease, transform 0.1s ease;
}

.hint:hover::after,
.hint:focus::after,
.hint:focus-visible::after {
  opacity: 1;
  transform: translateY(0);
}

/* =========================== Charts =========================== */

[data-chart] {
  width: 100%;
  min-height: 320px;
}

.chart-col {
  width: 100%;
}

.chart-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}

.chart-row .chart-col {
  flex: 1 1 320px;
}

.chart-row .chart-col-narrow {
  flex: 0 1 420px;
}

.detail-panel {
  margin-top: var(--sp-5);
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  min-height: 120px;
  scroll-margin-top: var(--sp-4);
}

.detail-panel .table-wrap,
.detail-panel table {
  width: 100%;
}

.detail-panel:empty::before,
.detail-panel .panel-placeholder {
  content: "";
}

.panel-placeholder {
  color: var(--ink-faint);
  font-size: 0.875rem;
  text-align: center;
  padding: var(--sp-5) var(--sp-3);
}

#matrix-chart,
#map-chart,
#trend-chart,
#dashboard-heatmap-chart {
  min-height: 560px;
}

@media (max-width: 700px) {
  #matrix-chart,
  #map-chart,
  #trend-chart,
  #dashboard-heatmap-chart {
    min-height: 420px;
  }
}

/* =========================== Stats =========================== */

.kpi-row {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}

.kpi-card {
  flex: 1 1 150px;
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
}

.kpi-label {
  font-size: 0.75rem;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.kpi-value {
  font-size: 1.5rem;
  font-weight: 700;
  margin: 0.2em 0 0.1em;
  overflow-wrap: anywhere;
}

.kpi-delta {
  font-size: 0.8125rem;
}

.kpi-delta.is-up {
  color: var(--good);
}

.kpi-delta.is-down {
  color: var(--bad);
}

.kpi-delta.is-flat {
  color: var(--ink-faint);
}

.tab-card {
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  flex: 1 1 320px;
}

.tab-nav {
  display: flex;
  gap: var(--sp-4);
  margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--line);
}

.tab-nav button {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: var(--sp-2) 0;
  font: inherit;
  font-size: 0.875rem;
  color: var(--ink-faint);
  cursor: pointer;
}

.tab-nav button.is-active {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

.tab-panel {
  display: none;
}

.tab-panel.is-active {
  display: block;
}

.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  margin: 0 0 var(--sp-3);
}

.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--good);
  display: inline-block;
  animation: live-pulse 2s ease-in-out infinite;
}

@keyframes live-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* =========================== Blog body =========================== */
/* Only blockquote/code need scoped rules -- h1-h3/p/table already have
   site-wide styles from elsewhere in this file that apply to markdown-
   rendered content without any extra work. */

.blog-body blockquote {
  margin: var(--sp-4) 0;
  padding: var(--sp-3) var(--sp-4);
  border-left: 3px solid var(--accent);
  background: var(--paper-raised);
  color: var(--ink-soft);
}

.blog-body code {
  font-family: inherit;
  background: var(--paper-sunken);
  padding: 0.1em 0.3em;
  border-radius: var(--radius-sm);
}

.blog-body pre {
  background: var(--paper-sunken);
  padding: var(--sp-4);
  overflow-x: auto;
  border-radius: var(--radius-sm);
}

.blog-body pre code {
  background: none;
  padding: 0;
}

/* =========================== Tables =========================== */
/* Header: lowercase, semibold, a solid ink rule beneath -- the genre's own
   leaderboard-table convention. Body rows: a dashed divider, not a solid
   hairline -- the one place this world borrows the reference sites'
   literal texture rather than just their color logic. */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.table-wrap {
  overflow-x: auto;
}

th,
td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px dashed var(--line);
  text-align: left;
  vertical-align: middle;
}

th {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 0.8125rem;
  color: var(--ink);
  text-transform: lowercase;
  background: var(--paper-sunken);
  border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
}

th a {
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
}

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

tbody tr:hover {
  background: var(--paper-sunken);
}

tbody tr.row-flash {
  background: var(--accent-tint);
  transition: background 1.2s ease-out;
}

td .num,
td {
  font-variant-numeric: tabular-nums;
}

/* A numbered rank column -- real sequence information (rank by whatever
   the table is sorted on), the genre's own leaderboard convention. Opt-in
   via .rank so tables that aren't ranked lists don't grow a column. */
td.rank {
  color: var(--ink-faint);
  font-weight: 600;
  width: 2.5rem;
}

/* =========================== Badges (stage / outcome) =========================== */

.stage-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 2px 9px;
  border-radius: var(--radius-sm);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: lowercase;
  letter-spacing: 0.01em;
  color: var(--badge-text);
}

.stage-badge::before {
  content: "";
  width: 0.4em;
  height: 0.4em;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
  opacity: 0.7;
}

.stage-forming {
  background: var(--stage-forming);
}

.stage-heating {
  background: var(--stage-heating);
}

.stage-crowded {
  background: var(--stage-crowded);
}

.stage-cooling {
  background: var(--stage-cooling);
}

.outcome-badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--radius-sm);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: lowercase;
  color: var(--badge-text);
}

.outcome-hit {
  background: var(--success);
}

.outcome-miss {
  background: var(--stage-crowded);
}

/* =========================== Track record (Predictions) =========================== */

.track-record {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  margin: var(--sp-4) 0 var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
}

.tr-stat {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.tr-value {
  font-size: 1.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.tr-label {
  font-size: 0.75rem;
  color: var(--ink-faint);
  text-transform: lowercase;
}

/* =========================== Opportunity bar =========================== */

.opp-cell {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.opp-bar {
  display: inline-block;
  width: 60px;
  height: 6px;
  background: var(--line);
  border-radius: var(--radius-pill);
  margin-right: var(--sp-2);
  vertical-align: middle;
  overflow: hidden;
}

.opp-bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-pill);
}

/* =========================== Theme detail metrics =========================== */

.theme-metrics dl,
.theme-source-split dl {
  margin: 0 0 var(--sp-4);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
}

.theme-metrics dt,
.theme-source-split dt {
  font-size: 0.75rem;
  color: var(--ink-faint);
  text-transform: lowercase;
}

.theme-metrics dd,
.theme-source-split dd {
  margin: 0 0 var(--sp-2);
  font-weight: 700;
}

.theme-meta {
  color: var(--ink-faint);
  font-size: 0.8125rem;
}

/* =========================== Launch detail page =========================== */

.launch-tagline {
  color: var(--ink-soft);
  font-size: 1.05rem;
  margin-top: calc(-1 * var(--sp-2));
}

.launch-fields dl,
.launch-enrichment dl {
  margin: 0 0 var(--sp-4);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-4);
  max-width: 60ch;
}

.launch-fields dt,
.launch-enrichment dt {
  color: var(--ink-faint);
  font-size: 0.8125rem;
  text-transform: lowercase;
}

.launch-fields dd,
.launch-enrichment dd {
  margin: 0;
  font-weight: 600;
}

.launch-links,
.launch-siblings ul,
.launch-competitor-list {
  list-style: none;
  margin: 0 0 var(--sp-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* =========================== Launch panel (Map sidebar) =========================== */

.launch-panel h3 {
  margin-bottom: var(--sp-1);
}

.launch-panel > p:first-of-type {
  color: var(--ink-soft);
  font-size: 0.875rem;
  margin-bottom: var(--sp-3);
}

.launch-panel dl {
  margin: 0 0 var(--sp-3);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-3);
  font-size: 0.8125rem;
}

.launch-panel dt {
  color: var(--ink-faint);
  text-transform: lowercase;
}

.launch-panel dd {
  margin: 0;
  font-weight: 600;
  text-align: right;
}

.launch-panel ul {
  list-style: none;
  margin: 0 0 var(--sp-3);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

/* =========================== Row-expand / detail panels in tables =========================== */

/* A grid of bordered cards, not a single full-width stack -- each section
   reads as its own unit instead of stretching thin content across the
   whole table's width. The chart and summary span every column: a time
   series and a paragraph both read better wide than squeezed into one
   card-width column. */
.row-expand {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-4);
  padding: var(--sp-5);
  background: var(--paper-sunken);
}

.row-expand section {
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
}

.row-expand section.row-expand-full {
  grid-column: 1 / -1;
}

.row-expand h4 {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--ink-soft);
  margin: 0 0 var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px dashed var(--line);
}

/* The real chart replacing the old 60px-tall, axis-less sparkline --
   enough room for a labeled axis to actually be readable. */
#row-expand-traj-chart {
  min-height: 260px;
}

.row-expand-summary p {
  margin: 0;
  color: var(--ink-soft);
}

.row-expand dl {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
}

.row-expand dt {
  font-size: 0.75rem;
  color: var(--ink-faint);
  text-transform: lowercase;
}

dt.hint,
label.hint {
  display: inline-block;
}

.row-expand dd {
  margin: 0 0 var(--sp-2);
  font-weight: 700;
}

.row-expand ul {
  margin: 0;
  padding-left: 1.1rem;
}

.pagination {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-3);
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* =========================== Buttons in tables =========================== */

table button[type="button"] {
  font: inherit;
  font-weight: 700;
  color: var(--ink-soft);
  background: var(--paper-sunken);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  width: 1.75rem;
  height: 1.75rem;
  cursor: pointer;
  line-height: 1;
}

table button[type="button"]:hover {
  background: var(--accent-tint);
  color: var(--accent-ink);
  border-color: var(--accent);
}

/* =========================== Range sliders =========================== */

input[type="range"] {
  accent-color: var(--accent);
  width: 100%;
}

#weight-inputs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-5);
  border: none;
  padding: 0;
  margin: var(--sp-3) 0 0;
}

#weight-inputs > legend {
  width: 100%;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--ink);
  text-transform: lowercase;
  padding: 0;
  margin-bottom: var(--sp-1);
}

#weight-inputs > .weights-explainer {
  flex-basis: 100%;
  margin: 0;
}

#weight-inputs label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  width: 220px;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: lowercase;
}

/* =========================== Pagination =========================== */

nav.pagination {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-4);
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

nav.pagination a {
  font-weight: 700;
  text-decoration: none;
}

/* =========================== Metric toggle (Matrix) =========================== */

.metric-toggle {
  display: flex;
  gap: 1px;
  margin-bottom: var(--sp-4);
  border: 1px solid var(--line-strong);
  width: fit-content;
}

.metric-toggle a {
  text-decoration: none;
  font-weight: 600;
  font-size: 0.8125rem;
  text-transform: lowercase;
  color: var(--ink-soft);
  background: var(--paper-raised);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-4);
}

.metric-toggle a[aria-current="page"] {
  color: var(--badge-text);
  background: var(--accent);
}

.metric-toggle a:hover:not([aria-current="page"]) {
  background: var(--paper-sunken);
  color: var(--ink);
}

/* =========================== Company match review =========================== */

.match-card {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto;
  gap: var(--sp-5);
  align-items: center;
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-3);
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
}

.match-card-confirmed {
  grid-template-columns: 1fr auto 1fr;
  background: var(--success-tint);
  border-color: var(--success);
}

.match-side h4 {
  margin: 0 0 var(--sp-1);
  font-size: 0.75rem;
  color: var(--ink-faint);
  text-transform: lowercase;
  letter-spacing: 0.02em;
}

.match-side p {
  margin: 0.2em 0;
}

.match-side ul {
  margin: var(--sp-2) 0 0;
  padding-left: 1.1rem;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.match-score {
  font-family: var(--font-mono);
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--accent);
  text-align: center;
}

.match-actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.match-actions form {
  margin: 0;
}

.match-actions button {
  width: 100%;
}
