/* ============================================================================
   GitHub Repo Compare — visual system
   ----------------------------------------------------------------------------
   Rewritten 2026-10-14. The old sheet was a flat light card layout in
   system-ui: correct, readable, and indistinguishable from an unstyled
   template. Everything below is about depth and material.

   THE RULES THIS FOLLOWS, because "glassmorphism" done badly is just blur:

   1. Glass needs something worth blurring. A translucent panel over flat grey
      is fog. The page therefore has a real backdrop — a deep base with large,
      soft colour fields — so every panel picks up the hue of what is behind it
      and the edges have something to refract.
   2. The specular edge is the effect. What reads as "glass" is not the blur,
      it is the thin bright rim where light catches the lip, brighter at the
      top where the light is. That is one inset box-shadow and it does more
      work than the blur does.
   3. Saturation, not just blur. `backdrop-filter: blur()` alone turns colour
      to mud; pairing it with `saturate(180%)` is what keeps a panel looking
      like glass rather than frosted plastic.
   4. Blur is expensive and is spent deliberately. The homepage carries 50
      comparison tiles; 50 compositing layers with backdrop-filter drops
      scrolling to single figures on an integrated GPU. Only the big surfaces
      (bar, search field, data table, result cards) get a real backdrop-filter.
      The tiles get a layered gradient that reads the same at tile size and
      costs nothing. See `.ccard`.
   5. Numbers are the content. This is a comparison table; every figure is set
      in a mono face with tabular figures so columns line up and a 4.5k never
      shifts against a 515.

   Dark is the base and light is a genuine alternative, not an afterthought —
   `prefers-color-scheme: light` re-points every token, and the glass recipe is
   written in terms of those tokens so both modes get the same material.
   ========================================================================== */

:root {
  color-scheme: dark light;

  /* --- base surfaces ----------------------------------------------------- */
  --bg:        #070b14;
  --bg-2:      #0a1120;
  --ink:       #e9eefb;
  --muted:     #94a7c4;   /* 7.1:1 on --bg */
  --dim:       #6f84a3;

  /* --- accents ----------------------------------------------------------- */
  --a1:        #22d3ee;
  --a2:        #8b5cf6;
  --a1-rgb:    34, 211, 238;
  --a2-rgb:    139, 92, 246;
  --brand:     #5ee0f5;

  /* --- semantic ---------------------------------------------------------- */
  --good:      #34d399;
  --warn:      #fbbf24;
  --bad:       #fb7185;
  --good-rgb:  52, 211, 153;
  --warn-rgb:  251, 191, 36;
  --bad-rgb:   251, 113, 133;

  /* --- the glass recipe, in one place ------------------------------------ */
  --glass:       linear-gradient(158deg, rgba(255,255,255,.11), rgba(255,255,255,.035));
  --glass-hover: linear-gradient(158deg, rgba(255,255,255,.16), rgba(255,255,255,.06));
  --line:        rgba(255,255,255,.12);
  --line-strong: rgba(255,255,255,.22);
  /* top-lip highlight + bottom shade + the cast shadow, as one token */
  --lift:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -1px 0 rgba(0,0,0,.25),
    0 18px 40px -22px rgba(0,0,0,.9);
  --lift-hover:
    inset 0 1px 0 rgba(255,255,255,.17),
    inset 0 -1px 0 rgba(0,0,0,.22),
    0 26px 56px -26px rgba(0,0,0,.9);
  --blur: saturate(180%) blur(20px);

  /* The sticky bar's height. The table header sticks BELOW it, not under it —
     with both pinned to top:0 the header slid beneath the bar and the status
     pills were sheared in half on every scroll. */
  --navh: 51px;

  --r:    14px;
  --r-lg: 20px;
  --maxw: 1180px;

  --ff: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ff-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme=dark]) {
    --bg:        #eef2f9;
    --bg-2:      #e4ebf6;
    --ink:       #0b1322;
    --muted:     #4a5b74;
    --dim:       #687993;
    --a1:        #0891b2;
    --a2:        #7c3aed;
    --brand:     #0b7285;
    --good:      #0f9d6b;
    --warn:      #b45309;
    --bad:       #be123c;
    /* On light, glass is a white lift rather than a white veil — the same
       recipe inverted, so panels still read as a sheet above the page. */
    --glass:       linear-gradient(158deg, rgba(255,255,255,.88), rgba(255,255,255,.62));
    --glass-hover: linear-gradient(158deg, rgba(255,255,255,.97), rgba(255,255,255,.80));
    --line:        rgba(10,20,40,.10);
    --line-strong: rgba(10,20,40,.20);
    --lift:
      inset 0 1px 0 rgba(255,255,255,.9),
      0 14px 34px -22px rgba(16,32,64,.45);
    --lift-hover:
      inset 0 1px 0 rgba(255,255,255,1),
      0 22px 46px -24px rgba(16,32,64,.5);
  }
}

/* ---------------------------------------------------------------------------
   Reset + page
   ------------------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html { background: var(--bg); }

body {
  margin: 0;
  font-family: var(--ff);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: transparent;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  position: relative;
  min-height: 100vh;
  overflow-x: clip;        /* decoration can never widen the document */
}

/*
  THE BACKDROP. Two fixed layers, no extra markup, no filter: the colour comes
  from wide radial stops, which the compositor handles far more cheaply than a
  blurred element. This is the thing the glass is glass *against* — delete it
  and every panel below turns into a grey box.
*/
body::before {
  content: "";
  position: fixed;
  inset: -20%;
  z-index: -2;
  background:
    radial-gradient(34% 30% at 12% 2%,   rgba(var(--a1-rgb), .40), transparent 68%),
    radial-gradient(30% 34% at 88% -2%,  rgba(var(--a2-rgb), .42), transparent 68%),
    radial-gradient(26% 26% at 66% 32%,  rgba(var(--a2-rgb), .20), transparent 70%),
    radial-gradient(44% 34% at 30% 76%,  rgba(var(--a1-rgb), .18), transparent 72%),
    linear-gradient(180deg, var(--bg), var(--bg-2));
  animation: drift 34s ease-in-out infinite alternate;
}

/* A faint rule grid, so the glass has an edge to refract and the page reads as
   a surface rather than a void. Masked to fade out before the footer. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255,255,255,.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.028) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(70% 55% at 50% 22%, #000 25%, transparent 100%);
          mask-image: radial-gradient(70% 55% at 50% 22%, #000 25%, transparent 100%);
}

/*
  Grain. An inline SVG turbulence at very low opacity — a perfectly smooth
  gradient is what makes a dark page read as flat vector art rather than as a
  lit surface, and it is also what makes banding visible on 8-bit panels.
  ~400 bytes, no request, composited once.
*/
body > :first-child::after { content: none; }
html::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .035;
  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='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme=dark]) body::after {
    background-image:
      linear-gradient(rgba(10,20,40,.045) 1px, transparent 1px),
      linear-gradient(90deg, rgba(10,20,40,.045) 1px, transparent 1px);
  }
}

@keyframes drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(0, -2.5%, 0) scale(1.06); }
}

/* ---------------------------------------------------------------------------
   Typography
   ------------------------------------------------------------------------ */
a { color: var(--brand); text-decoration-color: rgba(var(--a1-rgb), .4); text-underline-offset: 3px; }
a:hover { text-decoration-color: currentColor; }

h1 {
  font-size: clamp(32px, 5.4vw, 52px);
  line-height: 1.06;
  letter-spacing: -.035em;
  font-weight: 800;
  margin: 0 0 12px;
  /* The gradient is clipped to the glyphs, which is the one place on the page
     where the accent is allowed to be loud. */
  background: linear-gradient(104deg, var(--ink) 18%, var(--a1) 58%, var(--a2) 92%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

h2 {
  font-size: clamp(20px, 2.4vw, 25px);
  letter-spacing: -.02em;
  font-weight: 700;
  margin: 40px 0 14px;
}

h3 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .16em;
  font-weight: 700;
  color: var(--dim);
  margin: 34px 0 12px;
  display: flex;
  align-items: center;
  gap: 12px;
}
/* A hairline that runs out from each section label to the edge of the column. */
h3::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent);
}

/*
  THE GRADIENT HEADLINE IS FOR THE HOMEPAGE ONLY.

  A left-to-right gradient puts the brightest colour on the LAST word, and on a
  comparison page the last word is one of the repositories being compared —
  "GRBL vs FluidNC vs grblHAL" lit grblHAL and left GRBL in plain ink, which
  reads as a verdict the data has not given. The page's whole claim is that it
  is even-handed, so the comparison pages get one solid ink colour and keep the
  accent for things that are genuinely ours to emphasise.
*/
main.page h1 {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: var(--ink);
}

.sub, .lede {
  color: var(--muted);
  font-size: clamp(15px, 1.5vw, 17.5px);
  max-width: 68ch;
  margin: 0 0 22px;
}

.crumbs { font-size: 13px; color: var(--dim); margin: 4px 0 14px; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--brand); }

.note { font-size: 13px; color: var(--dim); margin: 16px 0; max-width: 72ch; }

/* ---------------------------------------------------------------------------
   Shell
   ------------------------------------------------------------------------ */
nav.top {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  gap: 18px;
  align-items: center;
  font-size: 14px;
  padding: 13px max(16px, calc(50vw - var(--maxw) / 2));
  background: linear-gradient(180deg, rgba(10,16,30,.72), rgba(10,16,30,.42));
  -webkit-backdrop-filter: var(--blur);
          backdrop-filter: var(--blur);
  border-bottom: 1px solid var(--line);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme=dark]) nav.top {
    background: linear-gradient(180deg, rgba(255,255,255,.82), rgba(255,255,255,.55));
  }
}

nav.top .brand {
  font-weight: 800;
  letter-spacing: -.02em;
  font-size: 15px;
  color: var(--ink);
  text-decoration: none;
  margin-right: auto;
  display: inline-flex;
  align-items: center;
  gap: 9px;
}
/* The mark: a small gradient lozenge. Cheaper than an SVG request and it ties
   the bar to the accent used on the headline. */
nav.top .brand::before {
  content: "";
  width: 20px;
  height: 20px;
  border-radius: 7px;
  background: linear-gradient(135deg, var(--a1), var(--a2));
  box-shadow: 0 0 18px rgba(var(--a1-rgb), .55), inset 0 1px 0 rgba(255,255,255,.4);
}
nav.top a:not(.brand) { color: var(--muted); text-decoration: none; font-weight: 500; }
nav.top a:not(.brand):hover { color: var(--ink); }

main, footer { max-width: var(--maxw); margin: 0 auto; padding: 0 16px; }
main.page { max-width: 980px; }
.hero { padding-top: clamp(26px, 4.5vw, 48px); }

footer {
  margin-top: 72px;
  padding: 30px 16px 56px;
  color: var(--dim);
  font-size: 13.5px;
  border-top: 1px solid var(--line);
  max-width: var(--maxw);
}

/* ---------------------------------------------------------------------------
   Controls
   ------------------------------------------------------------------------ */
form {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

input {
  flex: 1 1 340px;
  min-width: 0;
  padding: 15px 18px;
  font-family: var(--ff);
  font-size: 15.5px;
  color: var(--ink);
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--lift);
  -webkit-backdrop-filter: var(--blur);
          backdrop-filter: var(--blur);
  transition: border-color .18s, box-shadow .18s;
}
input::placeholder { color: var(--dim); }
input:focus {
  outline: none;
  border-color: rgba(var(--a1-rgb), .55);
  box-shadow: var(--lift), 0 0 0 4px rgba(var(--a1-rgb), .16);
}

button, .btn {
  padding: 15px 24px;
  font-family: var(--ff);
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -.01em;
  color: #04121a;
  background: linear-gradient(135deg, var(--a1), var(--a2) 140%);
  border: 0;
  border-radius: var(--r);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.45),
    0 10px 26px -12px rgba(var(--a1-rgb), .8);
  transition: transform .16s, box-shadow .16s, filter .16s;
}
button:hover, .btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.07);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.55),
    0 16px 34px -12px rgba(var(--a1-rgb), .95);
}
button:active, .btn:active { transform: translateY(0); }

button.ghost {
  color: var(--muted);
  background: var(--glass);
  border: 1px solid var(--line);
  box-shadow: var(--lift);
  font-weight: 600;
  padding: 9px 15px;
  font-size: 13.5px;
  -webkit-backdrop-filter: var(--blur);
          backdrop-filter: var(--blur);
}
button.ghost:hover {
  color: var(--ink);
  background: var(--glass-hover);
  border-color: var(--line-strong);
  filter: none;
  box-shadow: var(--lift-hover);
}

:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--a1);
  outline-offset: 3px;
  border-radius: 6px;
}

#msg, .live-msg { font-size: 14px; color: var(--muted); min-height: 22px; }
.err { color: var(--bad) !important; }

/* ---------------------------------------------------------------------------
   Live result cards
   ------------------------------------------------------------------------ */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(286px, 1fr));
  gap: 16px;
  margin-top: 14px;
}

.card {
  position: relative;
  padding: 20px;
  border-radius: var(--r-lg);
  background: var(--glass);
  border: 1px solid var(--line);
  box-shadow: var(--lift);
  -webkit-backdrop-filter: var(--blur);
          backdrop-filter: var(--blur);
  transition: transform .2s, box-shadow .2s, border-color .2s;
}
.card:hover {
  transform: translateY(-3px);
  border-color: var(--line-strong);
  box-shadow: var(--lift-hover);
}
/* The specular lip: a bright hairline across the top that fades at both ends.
   This is the single detail that reads as "glass" rather than "translucent". */
.card::before, .ccard::before, .tw::before, .empty::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.5), transparent);
  opacity: .7;
  pointer-events: none;
}

.card h2 { font-size: 16.5px; margin: 0 34px 6px 0; word-break: break-word; letter-spacing: -.01em; }
.card h2 a { color: var(--ink); text-decoration: none; }
.card h2 a:hover { color: var(--brand); }
.desc { color: var(--muted); font-size: 13.5px; margin: 0 0 14px; min-height: 38px; }

.x {
  position: absolute;
  top: 12px; right: 12px;
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  background: rgba(255,255,255,.05);
  border: 1px solid var(--line);
  border-radius: 9px;
  color: var(--dim);
  font-size: 16px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  box-shadow: none;
  transition: color .15s, background .15s, border-color .15s;
}
.x:hover {
  color: var(--bad);
  background: rgba(var(--bad-rgb), .14);
  border-color: rgba(var(--bad-rgb), .4);
  transform: none;
  filter: none;
  box-shadow: none;
}

.score { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }

/* Status chip — a glass capsule with a lit dot, not a solid block of colour. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 650;
  font-size: 11.5px;
  letter-spacing: .02em;
  padding: 5px 11px 5px 9px;
  border-radius: 999px;
  margin-top: 4px;
  border: 1px solid currentColor;
  background: color-mix(in srgb, currentColor 14%, transparent);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
}
.pill::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 9px currentColor;
}
.s-good { color: var(--good); }
.s-warn { color: var(--warn); }
.s-bad  { color: var(--bad); }

dl {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 12px;
  margin: 0;
  font-size: 14px;
  border-top: 1px solid var(--line);
  padding-top: 13px;
}
dt { color: var(--muted); }
dd {
  margin: 0;
  font-family: var(--ff-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 13.5px;
  text-align: right;
}

.empty {
  position: relative;
  color: var(--dim);
  text-align: center;
  padding: 30px 20px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: linear-gradient(158deg, rgba(255,255,255,.035), rgba(255,255,255,.012));
  grid-column: 1 / -1;
}
.empty::before { opacity: .35; }

/* ---------------------------------------------------------------------------
   Findings list
   ------------------------------------------------------------------------ */
.facts { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; }
.facts li {
  position: relative;
  padding: 13px 16px 13px 44px;
  border-radius: var(--r);
  background: linear-gradient(158deg, rgba(255,255,255,.045), rgba(255,255,255,.015));
  border: 1px solid var(--line);
  font-size: 15px;
}
/* A small gradient diamond instead of a bullet. */
.facts li::before {
  content: "";
  position: absolute;
  left: 17px; top: 50%;
  width: 9px; height: 9px;
  margin-top: -4.5px;
  border-radius: 3px;
  transform: rotate(45deg);
  background: linear-gradient(135deg, var(--a1), var(--a2));
  box-shadow: 0 0 12px rgba(var(--a1-rgb), .6);
}
.facts b, .facts strong { color: var(--ink); font-weight: 680; }

.snap {
  font-size: 14px;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0 0 16px;
}

/* ---------------------------------------------------------------------------
   The comparison table — the reason the page exists
   ------------------------------------------------------------------------ */
.tw {
  position: relative;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--glass);
  box-shadow: var(--lift);
  -webkit-backdrop-filter: var(--blur);
          backdrop-filter: var(--blur);
}

table.cmp {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  min-width: 580px;
  font-size: 14.5px;
  /*
    FIXED LAYOUT, and the bars are the entire reason.

    With `auto`, each column is sized by its own widest cell, so the repository
    columns came out at different widths — measured live at 248 / 194 / 275 px
    on one page. A bar is a percentage of ITS OWN cell, so CrewAI at 100% drew
    194px while LangGraph at 66% drew 187px: visually identical lengths for
    values that differ by a third. The bars were arithmetically right and
    visually a lie.

    Fixed layout gives every repository column the same width, so equal
    percentages are equal pixel lengths and a reader can compare bar against bar
    across the row — which is the whole point of drawing them.
  */
  table-layout: fixed;
}

/* The metric names need a known column; everything left over is split equally
   between the repositories by `table-layout: fixed`. */
.cmp th[scope=row], .cmp thead th:first-child { width: 24%; min-width: 140px; }

/* Fixed layout will not widen a column for a long release tag, so the text
   rows wrap instead of overflowing. The numeric rows are short and unaffected. */
.cmp tbody td { overflow-wrap: anywhere; }

.cmp th, .cmp td {
  padding: 13px 18px;
  border-bottom: 1px solid var(--line);
  text-align: right;
  vertical-align: top;
}

/*
  NOT STICKY — and that is the second attempt at this.

  A pinned header is the obvious thing to want on a comparison table and it is
  wrong here twice over. First it collided with the sticky bar (both at top:0)
  and sheared the status pills in half on every scroll. Pushing it to
  `top: var(--navh)` fixed that and introduced a worse one: with
  `border-collapse: separate`, a sticky `th` whose content is two lines tall
  (name + status pill) stopped reserving its own height, and the first body row
  was drawn 50px INSIDE the header — measured, not guessed.

  The feature was never worth it: these tables are thirteen rows and fit on one
  screen, so the header never leaves the viewport in the first place. Removing
  it deletes the whole class of bug.
*/
.cmp thead th {
  text-align: right;
  font-size: 15px;
  padding-top: 16px;
  padding-bottom: 14px;
  background: linear-gradient(180deg, rgba(12,19,34,.92), rgba(12,19,34,.72));
  -webkit-backdrop-filter: var(--blur);
          backdrop-filter: var(--blur);
  border-bottom: 1px solid var(--line-strong);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme=dark]) .cmp thead th {
    background: linear-gradient(180deg, rgba(255,255,255,.95), rgba(255,255,255,.8));
  }
}
.cmp thead th a { color: var(--ink); font-weight: 750; text-decoration: none; letter-spacing: -.01em; }
.cmp thead th a:hover { color: var(--brand); }

.cmp th[scope=row], .cmp thead th:first-child {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  font-size: 14px;
}

/* Every figure is mono + tabular so the columns are genuinely comparable. */
.cmp tbody td {
  font-family: var(--ff-mono);
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  color: var(--ink);
}

.cmp tbody tr { transition: background .15s; }
.cmp tbody tr:hover th[scope=row] { background: rgba(255,255,255,.04); }

/* ---------------------------------------------------------------------------
   IN-CELL BARS — one colour per repository
   --------------------------------------------------------------------------
   The table answered "what are the numbers" and left "who is bigger" to the
   reader's arithmetic. Each numeric cell now carries a bar whose length is that
   repository's share of the largest value in ITS OWN ROW (`--p`, written by
   build.py), so a glance down a row ranks the repos without reading a digit.

   FOUR DECISIONS WORTH KEEPING:

   1. Scaled per row, never globally. 61k stars and 534 contributors would
      otherwise be drawn at wildly different lengths and the eye would compare
      metrics instead of repositories, which is meaningless.
   2. Anchored RIGHT, growing left. The figures are right-aligned, so a
      left-anchored bar would leave a short value floating far from the end of
      its own bar. Anchored right, every number sits exactly at the tip.
   3. Colour identifies the COLUMN, not the rank. Each repository keeps one hue
      down the whole table and the same hue appears as a dot beside its name in
      the header — a bar chart with no legend is decoration.
   4. The palette deliberately avoids green/amber/red. Those three already mean
      Active / Slowing / Dormant on this page, and a repo whose column happened
      to be amber would read as "Slowing" in every row.
   ------------------------------------------------------------------------ */
.cmp [data-c="0"] { --c: 34, 211, 238; }   /* cyan   */
.cmp [data-c="1"] { --c: 167, 139, 250; }  /* violet */
.cmp [data-c="2"] { --c: 244, 114, 182; }  /* pink   */
.cmp [data-c="3"] { --c: 56, 189, 248; }   /* sky    */
.cmp [data-c="4"] { --c: 129, 140, 248; }  /* indigo */

/* The legend: the same hue as the column's bars, beside the repository name. */
.cmp thead th[data-c] a::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 8px;
  border-radius: 50%;
  vertical-align: middle;
  background: rgb(var(--c));
  box-shadow: 0 0 10px rgba(var(--c), .9);
}

.cmp tbody td { position: relative; isolation: isolate; }

.cmp tbody td[style*="--p"]::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 4px;
  bottom: 4px;
  right: 0;
  /* Capped just short of the cell so a 100% bar does not butt against the
     previous column's bar — without the gutter two adjacent full-length bars
     merge into one band and the cell boundary disappears. */
  width: min(var(--p), calc(100% - 6px));
  /* Clear of the 3px gauge along the bottom. */
  bottom: 10px;
  border-radius: 5px 0 0 5px;
  background: linear-gradient(270deg, rgba(var(--c), .42), rgba(var(--c), .10));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}

/*
  THE TRACK — the empty channel every bar is drawn inside.

  Without it the row's largest value fills its cell edge to edge and there is
  nothing to read it against: every top bar looks "full", and whether the
  runner-up is at 97% or at 60% has to be judged from the bar's absolute
  length rather than from how much of the channel it has taken. The track makes
  the denominator visible, so each bar is read as a fraction of a known whole —
  which is what "scaled to the row" actually means to a reader.

  It is drawn for every cell that has a bar, including the 100% one, so the
  channel is the same length in all three columns.
*/
/* The track is painted as the cell's own background layer rather than a third
   pseudo-element — a `td` only has two, and the spread gauge needs ::after. */
.cmp tbody td[style*="--p"] {
  background:
    linear-gradient(rgba(255,255,255,.05), rgba(255,255,255,.05))
    no-repeat right 0 top 4px / calc(100% - 6px) calc(100% - 18px);
}

/*
  THE SPREAD GAUGE — the second scale, and the reason it is a separate mark.

  `--q` rescales the row from its own smallest value to its largest, so a row of
  61k / 59k / 43k reads 100 / 89 / 0 instead of 100 / 97 / 69. That makes a
  close race legible, and it is exactly the truncated axis every honest chart
  warns about: the row's lowest value is drawn as nothing however large it is.

  So it never touches the main bar. It is a 3px rule along the bottom of the
  cell, visually subordinate, and the note under the table says in words that it
  is a spread and not a quantity. Two scales, each drawn as itself.
*/
.cmp tbody td[style*="--q"]::after {
  content: "";
  position: absolute;
  z-index: -1;
  right: 0;
  bottom: 3px;
  height: 3px;
  width: min(var(--q), calc(100% - 6px));
  border-radius: 2px;
  background: rgb(var(--c));
  opacity: .85;
  box-shadow: 0 0 8px -1px rgba(var(--c), .9);
}
/* A zero-spread cell (the row's lowest) still gets a dot, so the gauge reads as
   "at the bottom of the range" rather than as missing. */
.cmp tbody td[style*="--q"]::after { min-width: 3px; }

/* The winner keeps a mark, but the BAR now carries the magnitude — so this is
   a brighter version of the same bar plus a lit leading edge, rather than the
   separate block of accent colour it used to be (which fought the bars). */
.cmp td.best { font-weight: 700; color: var(--ink); }
.cmp td.best[style*="--p"]::before {
  background: linear-gradient(270deg, rgba(var(--c), .72), rgba(var(--c), .20));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.18),
    0 0 18px -2px rgba(var(--c), .55);
}
/* The leading edge of the winning bar, as a border on the bar itself — it has
   to track the bar's own capped width, and a border always does. */
.cmp td.best[style*="--p"]::before {
  border-left: 2px solid rgb(var(--c));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.18),
    0 0 18px -2px rgba(var(--c), .55),
    -1px 0 10px -1px rgba(var(--c), .9);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme=dark]) .cmp tbody td[style*="--p"] {
    background:
      linear-gradient(rgba(10,20,40,.06), rgba(10,20,40,.06))
      no-repeat right 0 top 4px / calc(100% - 6px) calc(100% - 18px);
  }
  :root:not([data-theme=dark]) .cmp tbody td[style*="--p"]::before {
    background: linear-gradient(270deg, rgba(var(--c), .50), rgba(var(--c), .13));
  }
  :root:not([data-theme=dark]) .cmp td.best[style*="--p"]::before {
    background: linear-gradient(270deg, rgba(var(--c), .80), rgba(var(--c), .24));
  }
}

/* A row hover must not wash the bars out, so it tints the label only. */
.cmp tbody tr:hover th[scope=row] { color: var(--ink); }

.cmp tr:last-child th, .cmp tr:last-child td { border-bottom: 0; }

/* ---------------------------------------------------------------------------
   Comparison index tiles
   --------------------------------------------------------------------------
   NO backdrop-filter here, on purpose — see rule 4 at the top of the file.
   There are 50 of these on the homepage and each compositing layer costs real
   frames. The layered gradient reads identically at this size.
   ------------------------------------------------------------------------ */
.cgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 12px;
}

.ccard {
  position: relative;
  display: block;
  overflow: hidden;
  padding: 16px 18px;
  border-radius: var(--r);
  text-decoration: none;
  color: var(--ink);
  background: linear-gradient(158deg, rgba(255,255,255,.058), rgba(255,255,255,.02));
  border: 1px solid var(--line);
  box-shadow: 0 10px 26px -22px rgba(0,0,0,.9);
  transition: transform .18s, border-color .18s, box-shadow .18s, background .18s;
}
.ccard:hover {
  transform: translateY(-3px);
  background: var(--glass-hover);
  border-color: rgba(var(--a1-rgb), .42);
  box-shadow: 0 20px 40px -24px rgba(0,0,0,.95), 0 0 0 1px rgba(var(--a1-rgb), .14);
}
/* A sheen that sweeps across on hover — the "liquid" part of liquid glass. */
.ccard::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 32%, rgba(255,255,255,.16) 48%, transparent 62%);
  transform: translateX(-120%);
  transition: transform .7s cubic-bezier(.22,.61,.36,1);
  pointer-events: none;
}
.ccard:hover::after { transform: translateX(120%); }

.ccard b {
  display: block;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -.012em;
  margin-bottom: 3px;
}
.ccard span { font-size: 12.5px; color: var(--muted); }
.ccard:hover b { color: var(--brand); }

/* ---------------------------------------------------------------------------
   Related links
   ------------------------------------------------------------------------ */
.rel { columns: 2; padding-left: 20px; margin: 0; color: var(--muted); }
.rel li { margin: 0 0 8px; break-inside: avoid; }
.rel a { text-decoration: none; }
.rel a:hover { text-decoration: underline; }
@media (max-width: 560px) { .rel { columns: 1; } }

/* ---------------------------------------------------------------------------
   Responsive
   ------------------------------------------------------------------------ */
@media (max-width: 640px) {
  .cmp th, .cmp td { padding: 11px 13px; }
  .card { padding: 17px; }
  form { gap: 8px; }
  input { flex: 1 1 100%; }
  button[type=submit] { flex: 1 1 auto; justify-content: center; }
  footer { margin-top: 48px; }
}

/* ---------------------------------------------------------------------------
   Reduced motion + no-backdrop-filter fallbacks
   --------------------------------------------------------------------------
   Without the opaque fallback a browser that does not support backdrop-filter
   (older Firefox with the pref off) renders near-transparent panels over a
   busy backdrop, which is unreadable rather than merely plainer.
   ------------------------------------------------------------------------ */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  nav.top { background: #0c1424; }
  .card, .tw, input, button.ghost { background: #111a2b; }
  .cmp thead th { background: #111a2b; }
  @media (prefers-color-scheme: light) {
    :root:not([data-theme=dark]) nav.top,
    :root:not([data-theme=dark]) .card,
    :root:not([data-theme=dark]) .tw,
    :root:not([data-theme=dark]) input,
    :root:not([data-theme=dark]) button.ghost,
    :root:not([data-theme=dark]) .cmp thead th { background: #fff; }
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .ccard::after { display: none; }
  .card:hover, .ccard:hover, button:hover, .btn:hover { transform: none; }
}
