SLM-Tetris-Arena / leaderboard.py
DedeProGames's picture
DedeProGames and DedeBckp in light blue
88e1e5e verified
Raw History Blame Contribute Delete
25 kB
"""Leaderboard tab: Elo bar chart, table and play-style scatter (server-rendered HTML/SVG).
Visual language follows the BananaMind SLM Leaderboard: org-coloured bars with logos and a rainbow glow for
size-class leaders. The second chart shows play style (survival vs line clears), which carries no size bias.
"""
from __future__ import annotations
import hashlib
import html
import math
MIN_GAMES_GLOW = 2 # a leader needs more than one ranked game, so a single lucky win doesn't glow
# Size classes are relative, not absolute. Two models are neighbours when the bigger one is at most `size_ratio`
# times the smaller one. The ratio is 1.25 (-20%/+25%) up to 30M and widens smoothly (log scale) to 1.5
# (-33%/+50%) from 100M on, so e.g. 50M vs 65M and 100M vs 135M share a class while tiny models stay fine-grained.
RATIO_SMALL, RATIO_LARGE = 1.25, 1.5
RATIO_FROM, RATIO_TO = 30e6, 100e6
def size_ratio(p: float) -> float:
t = min(1.0, max(0.0, math.log10(p / RATIO_FROM) / math.log10(RATIO_TO / RATIO_FROM)))
return RATIO_SMALL + (RATIO_LARGE - RATIO_SMALL) * t
def neighbours(a: float, b: float) -> bool:
"""Symmetric: the allowed ratio is taken at the pair's geometric-mean size."""
return max(a, b) / min(a, b) <= size_ratio(math.sqrt(a * b))
_CDN = "https://cdn-avatars.huggingface.co/v1/production/uploads/"
# owner on the Hub -> (display name, logo, "r, g, b", border colour). Same identities as the BananaMind leaderboard.
_ORGS = [
("BananaMind", "BananaMind", "69ae829a8408eeb0d7dd5491/0-2aeVpWWaWlufYtgkWtK.png", "250, 204, 21", "#facc15"),
("DALabCommunity", "DALabCommunity", "https://www.gravatar.com/avatar/887b2ff821a8d5f70752fd50b05e137", "217, 70, 239", "#d946ef"),
("SupraLabs", "SupraLabs", "697f2832c2c5e4daa93cece7/IQMtz5gg-vLFP7Gn75POT.png", "139, 92, 246", "#a78bfa"),
("openai-community", "OpenAI", "5dd96eb166059660ed1ee413/9NY4jfufqo1uyv8oNXQju.png", "16, 163, 127", "#10a37f"),
("GODELEV", "GODELEV", "67f03a82cb606619f36f9a51/ZkQyscQj9IdQMySbgLcb5.jpeg", "79, 70, 229", "#6366f1"),
("AxiomicLabs", "Axiomic Labs", "67b413df70aa5c739bda9e7a/pGOq2X7y_iLw1VklgfDFl.png", "194, 182, 255", "#c2b6ff"),
("HuggingFaceTB", "Hugging Face", "651e96991b97c9f33d26bde6/e4VK7uW5sTeCYupD0s_ob.png", "255, 157, 0", "#ff9d00"),
("veyra-ai", "veyra-ai", "6857f2cfae68b377f17aff8c/0Tl87LYtzyBEvumEe_QJ1.png", "212, 86, 114", "#d45672"),
("Eclipse-Senpai", "Eclipse-Senpai", "noauth/3Rm4xf1hvlObxbBxyvC6i.png", "6, 182, 212", "#06b6d4"),
("User01110", "User01110", "https://huggingface.co/avatars/93dace33d3ce104114776b02f3646c3b.svg", "168, 85, 247", "#a855f7"),
("AtomixLabs", "AtomixLabs", "64b433c3faa3181a5e98c87c/j2-Xd02dqerocdu-SWqJh.png", "190, 242, 100", "#bef264"),
("ThingAI", "ThingAI", "69e70c6a759e88fab12bde9f/A2pR_uu7ErE7Tbe2UGDnH.png", "180, 83, 9", "#b45309"),
("joelhenwang", "joelhenwang", "https://huggingface.co/avatars/94de3a736fac914944f1b57609e3819a.svg", "229, 231, 235", "#e5e7eb"),
("MultivexAI", "MultivexAI", "64b433c3faa3181a5e98c87c/ZRirYgVxVdxNCeCV_aoHT.png", "0, 240, 255", "#00f0ff"),
("finnianx", "finnianx", "6325c1d65cf955bfbbde74b6/9-sRu_OMmqSAAyeiSv7SO.jpeg", "45, 212, 191", "#2dd4bf"),
("EleutherAI", "EleutherAI", "1614054059123-603481bb60e3dd96631c9095.png", "239, 68, 68", "#ef4444"),
("fromziro", "FromZero", "68657cd96e07b797a219b593/qITdWZiMpLE8Kop68m9OZ.png", "210, 180, 140", "#d2b48c"),
("Harley-ml", "Harley ML", "68657cd96e07b797a219b593/nV8Apsw0hNHBrrHyf3kB7.jpeg", "153, 27, 27", "#991b1b"),
("UniversalComputingResearch", "UCR", "67fc2fb8b34e5f8a2dea939b/99X2TS_XeKtlJSjvVouUE.png", "59, 130, 246", "#3b82f6"),
("BananaMind-Model-Previewers", "BananaMind Model Previewers", "69ae829a8408eeb0d7dd5491/GBfhEbHUsGV3ps4YPLLnA.png", "251, 191, 36", "#fbbf24"),
("appvoid", "appvoid", "62a813dedbb9e28866a91b27/2fknEF_u6StSjp3uUF144.png", "244, 114, 182", "#f472b6"),
("DedeProGames", "DedeProGames", "685ea8ff7b4139b6845ce395/Im--QSnbrnAhHPPhpX8L0.png", "77, 159, 255", "#4d9fff"),
("opencerebral", "OpenCerebral", "689a3f0eec8a724449b85179/Rd2B98EVdHw99gOajD-aV.png", "14, 165, 233", "#0ea5e9"),
("NILKNARFGonzo", "NILKNARFGonzo", "noauth/N_mm9c94sF1JR76d1zNLA.png", "148, 163, 184", "#94a3b8"),
("allura-org", "allura-org", "634262af8d8089ebaefd410e/6zT9gVQI_9HKiW-6T6uXS.jpeg", "251, 113, 133", "#fb7185"),
("FlameF0X", "FlameF0X", "6615494716917dfdc645c44e/GGzgDi_WTW1Ci4CaDJd8I.jpeg", "249, 115, 22", "#f97316"),
("CNWPlayer", "CNWPlayer", "694742331f2408791d8e1472/qAkFOi18U_Yzv9UVnp7Wd.png", "34, 211, 238", "#22d3ee"),
("CodeSoft", "CodeSoft", "645aad59c4acfcf664022df5/BwD8ZMbxrK6h3CzxpwNfA.jpeg", "80, 162, 255", "#50a2ff"),
("DedeBckp", "DedeBckp", "noauth/sEn3rwht_EbEa83Ug8slJ.png", "116, 180, 255", "#74b4ff"),
("bananamind-research-community", "BananaMind Research Community", "69ae829a8408eeb0d7dd5491/POU3vsQeIkN2Lim-Lv0wR.png", "253, 224, 71", "#fde047"),
]
ORGS = {
owner.lower(): {"name": name, "logo": logo if logo.startswith("https://") else _CDN + logo,
"fill": f"rgba({rgb}, 0.70)", "border": border}
for owner, name, logo, rgb, border in _ORGS
}
def org_of(model_id: str) -> dict:
"""Known orgs keep their colours; unknown owners get a stable hue and an initial."""
owner = model_id.split("/")[0]
if owner.lower() in ORGS:
return ORGS[owner.lower()]
hue = int(hashlib.md5(owner.lower().encode()).hexdigest()[:6], 16) % 360
return {"name": owner, "logo": None, "fill": f"hsla({hue}, 70%, 58%, 0.70)", "border": f"hsl({hue}, 70%, 60%)"}
def _esc(s) -> str:
return html.escape(str(s), quote=True)
def fmt_params(p) -> str:
if not p:
return "?"
if p >= 1e9:
return f"{p / 1e9:.2f}".rstrip("0").rstrip(".") + "B"
if p >= 1e6:
return f"{p / 1e6:.1f}".rstrip("0").rstrip(".") + "M"
return f"{p / 1e3:.0f}K"
def expected_score(elo: float) -> float:
"""Bar height: expected score against a 1000-rated model (0-100). 1000 -> 50."""
return 100.0 / (1.0 + 10 ** ((1000.0 - elo) / 400.0))
def _logo(org, cls="lb-logo") -> str:
if org["logo"]:
return f'<img class="{cls}" src="{_esc(org["logo"])}" alt="" loading="lazy">'
return f'<span class="{cls} lb-initial" aria-hidden="true">{_esc(org["name"][:1].upper())}</span>'
def leaders(entries, gap: int = 0) -> set:
"""Size-class leaders: the best Elo among rated models of similar relative size.
A model glows when (1) it has at least MIN_GAMES_GLOW ranked games, (2) its Elo is above the 1000 start,
(3) at least one other rated model is a size neighbour (see `neighbours`), and
(4) none of those neighbours has a higher Elo. Ties glow together. `gap` is kept for API compatibility."""
out = set()
for e in entries:
p = e.get("params")
if not p or e.get("games", 0) < MIN_GAMES_GLOW or e["elo"] <= 1000:
continue
peers = [o for o in entries if o.get("params") and neighbours(p, o["params"])]
if len(peers) < 2:
continue # no neighbour to compare with
if all(o["elo"] <= e["elo"] for o in peers):
out.add(e["model"])
return out
# ----------------------------------------------------------------------------
# Bar chart + table
# ----------------------------------------------------------------------------
_TICKS = [1400, 1200, 1000, 800, 600]
def _bars(entries, glow):
ticks = "".join(f'<span class="lb-tick" style="bottom:{expected_score(t):.2f}%">{t}</span>' for t in _TICKS)
grid = "".join(f'<i class="lb-gridline" style="bottom:{expected_score(t):.2f}%"></i>' for t in _TICKS)
bars = []
for e in entries:
org = org_of(e["model"])
h = expected_score(e["elo"])
name = e["model"].split("/", 1)[-1]
lead = e["model"] in glow
label = (f'{_esc(e["model"])} · Elo {e["elo"]:.0f} · {e["games"]} ranked games'
f'{" · size-class leader" if lead else ""}')
bars.append(
f'<a class="lb-bar{" lb-leader" if lead else ""}" href="https://huggingface.co/{_esc(e["model"])}" target="_blank" '
f'rel="noopener" title="{label}" aria-label="{label}" style="--bar-color:{org["fill"]};--bar-border:{org["border"]}">'
f'<span class="lb-track"><span class="lb-fill" style="height:{h:.2f}%"><span class="lb-score">{e["elo"]:.0f}</span></span></span>'
f'<span class="lb-label">{_logo(org)}<strong>{_esc(name)}</strong>'
f'<small>{fmt_params(e.get("params"))} params</small><small>{e["games"]} games</small></span></a>'
)
return (f'<div class="lb-chart-scroll"><div class="lb-chart"><div class="lb-axis" aria-hidden="true">{ticks}</div>'
f'<div class="lb-bars"><div class="lb-grid" aria-hidden="true">{grid}</div>{"".join(bars)}</div></div></div>')
def _table(entries, glow):
rows = []
for i, e in enumerate(entries, 1):
org = org_of(e["model"])
g = max(1, e["games"])
dot = '<i class="lb-rainbow-dot" title="Size-class leader"></i>' if e["model"] in glow else ""
rows.append(
f'<tr><td>{i}</td><td class="lb-name">{_logo(org, "lb-logo lb-logo-sm")}'
f'<a href="https://huggingface.co/{_esc(e["model"])}" target="_blank" rel="noopener">{_esc(e["model"])} ↗</a>{dot}</td>'
f'<td>{fmt_params(e.get("params"))}</td><td class="lb-accent">{e["elo"]:.0f}</td><td>{e["games"]}</td>'
f'<td>{e["wins"]}</td><td>{e["total_pieces"] / g:.1f}</td><td>{e["total_lines"] / g:.1f}</td><td>{e["best_score"]}</td></tr>'
)
head = ("<tr><th>#</th><th>Model</th><th>Params</th><th>Elo</th><th>Games</th><th>1st places</th>"
"<th>Avg pieces</th><th>Avg lines</th><th>Best score</th></tr>")
return f'<div class="lb-table-scroll"><table class="lb-table"><thead>{head}</thead><tbody>{"".join(rows)}</tbody></table></div>'
# ----------------------------------------------------------------------------
# Play style scatter: survival vs line clears (no size axis, so no size bias)
# ----------------------------------------------------------------------------
def _pearson(a, b):
if len(a) < 3:
return None
ma, mb = sum(a) / len(a), sum(b) / len(b)
sa = math.sqrt(sum((x - ma) ** 2 for x in a))
sb = math.sqrt(sum((y - mb) ** 2 for y in b))
if not sa or not sb:
return None
return sum((x - ma) * (y - mb) for x, y in zip(a, b)) / (sa * sb)
def _median(v):
a = sorted(v)
return (a[(len(a) - 1) // 2] + a[len(a) // 2]) / 2
def _style_scatter(entries):
"""x = average pieces survived per ranked game, y = average lines cleared (sqrt scale), dot size = Elo."""
pts = [dict(e, _pcs=e["total_pieces"] / e["games"], _lines=e["total_lines"] / e["games"])
for e in entries if e.get("games")]
if not pts:
return '<p class="lb-empty">No rated models yet.</p>', ""
left, top, width, height = 66, 24, 950, 350
pcs = [e["_pcs"] for e in pts]
lines = [e["_lines"] for e in pts]
elos = [e["elo"] for e in pts]
xspan = max(pcs) - min(pcs)
xstep = 5 if xspan <= 40 else 10 if xspan <= 90 else 25 if xspan <= 250 else 50
xmin = max(0, math.floor((min(pcs) - xstep / 2) / xstep) * xstep)
xmax = max(xmin + 2 * xstep, math.ceil((max(pcs) + xstep / 2) / xstep) * xstep)
ymax = max(1.0, max(lines) * 1.15)
lo, hi = min(elos), max(elos)
def x(v):
return left + (v - xmin) / (xmax - xmin) * width
def y(v):
return top + height - math.sqrt(max(0.0, v) / ymax) * height
def radius(elo):
return 4.5 + (7.0 * (elo - lo) / (hi - lo) if hi > lo else 3.5)
axes = []
v = xmin
while v <= xmax + 1e-9:
axes.append(f'<line class="lb-sgrid" x1="{x(v):.1f}" x2="{x(v):.1f}" y1="{top}" y2="{top + height + 5}"/>'
f'<text class="lb-stick" x="{x(v):.1f}" y="{top + height + 23}" text-anchor="middle">{v:g}</text>')
v += xstep
last = 1e9
for t in (0, 0.1, 0.25, 0.5, 1, 2, 3, 4, 6, 8, 10, 15, 20, 30, 50):
if t > ymax:
break
py = y(t)
if last - py < 22:
continue
last = py
axes.append(f'<line class="lb-sgrid" x1="{left}" x2="{left + width}" y1="{py:.1f}" y2="{py:.1f}"/>'
f'<text class="lb-stick" x="{left - 12}" y="{py + 4:.1f}" text-anchor="end">{t:g}</text>')
mid_x, mid_y = x(_median(pcs)), y(_median(lines))
# labels: best Elo first, so the strongest models keep their names when space is tight
boxes, labels = [], {}
for e in sorted(pts, key=lambda m: -m["elo"]):
px, py, r = x(e["_pcs"]), y(e["_lines"]), radius(e["elo"])
name = e["model"].split("/", 1)[-1]
lw = len(name) * 6.3
for dy in (-(r + 5), r + 13, -(r + 21), r + 29):
lx = px - lw - r - 5 if px + lw + r + 9 > left + width else px + r + 5
box = (lx, py + dy - 10, lw, 14)
if box[0] < left or box[1] < top or box[1] + box[3] > top + height:
continue
if any(box[0] < b[0] + b[2] + 5 and box[0] + box[2] + 5 > b[0] and box[1] < b[1] + b[3] + 3 and box[1] + box[3] + 3 > b[1]
for b in boxes):
continue
boxes.append(box)
labels[e["model"]] = f'<text class="lb-slabel" x="{lx:.1f}" y="{py + dy:.1f}">{_esc(name)}</text>'
break
points = []
for e in sorted(pts, key=lambda m: -radius(m["elo"])): # big dots first, small ones stay visible on top
org = org_of(e["model"])
px, py, r = x(e["_pcs"]), y(e["_lines"]), radius(e["elo"])
title = (f'{e["model"]} · Elo {e["elo"]:.0f} · {e["_pcs"]:.1f} pieces and {e["_lines"]:.2f} lines per game'
f' · {e["games"]} games')
points.append(
f'<a class="lb-spoint" href="https://huggingface.co/{_esc(e["model"])}" target="_blank" aria-label="{_esc(title)}">'
f'<title>{_esc(title)}</title><circle class="lb-starget" cx="{px:.1f}" cy="{py:.1f}" r="{r + 6:.1f}"/>'
f'<circle class="lb-sdot" cx="{px:.1f}" cy="{py:.1f}" r="{r:.1f}" fill="{org["border"]}"/></a>'
)
svg = (
f'<svg class="lb-svg" viewBox="0 0 1050 445" role="img" aria-label="Lines cleared versus pieces survived per game">'
f'<rect class="lb-quadrant" x="{mid_x:.1f}" y="{top}" width="{max(0, left + width - mid_x):.1f}" height="{max(0, mid_y - top):.1f}"/>'
f'{"".join(axes)}{"".join(points)}{"".join(labels.values())}'
f'<text class="lb-saxis" x="{left + width / 2}" y="430" text-anchor="middle">Pieces survived per game (average)</text>'
f'<text class="lb-saxis" transform="translate(18 {top + height / 2}) rotate(-90)" text-anchor="middle">'
f'Lines cleared per game (average, √ scale)</text></svg>'
)
r_pcs, r_lines = _pearson(elos, pcs), _pearson(elos, lines)
corr = (f" Correlation with Elo: survival r = {r_pcs:+.2f}, line clears r = {r_lines:+.2f}."
if r_pcs is not None and r_lines is not None else "")
return svg, corr
def leaderboard_html(entries, protocol: str, gap: int, gap_label: str) -> str:
entries = sorted(entries, key=lambda e: -e["elo"])
proto = "Guided" if protocol == "guided" else "Blind"
glow = leaders(entries, gap)
if entries:
views = (
'<input type="radio" name="lbview" id="lbv-chart" class="lb-radio" checked>'
'<input type="radio" name="lbview" id="lbv-table" class="lb-radio">'
'<div class="lb-heading"><div><h2>Model Elo</h2>'
f'<p class="lb-muted">{proto} protocol · ranked matches only · higher is better</p></div>'
'<div class="lb-seg"><label for="lbv-chart" class="lb-seg-chart">Chart</label>'
'<label for="lbv-table" class="lb-seg-table">Table</label></div></div>'
f'<div class="lb-view-chart">{_bars(entries, glow)}</div>'
f'<div class="lb-view-table">{_table(entries, glow)}</div>'
)
else:
views = (f'<div class="lb-heading"><div><h2>Model Elo</h2><p class="lb-muted">{proto} protocol · ranked matches only</p></div></div>'
'<p class="lb-empty">No ranked matches yet this season. Play a ranked match to put models on the board.</p>')
orgs = []
for e in entries:
o = org_of(e["model"])
if o["name"] not in [n for n, _ in orgs]:
orgs.append((o["name"], o["border"]))
legend = "".join(f'<span><i style="background:{c}"></i>{_esc(n)}</span>' for n, c in orgs)
style_svg, style_corr = _style_scatter(entries)
return (
'<div class="lb-root">'
f'<section class="lb-panel lb-chart-panel">{views}'
f'<div class="lb-footer"><span><i class="lb-rainbow-dot"></i>Size-class leader: best Elo among models of similar size '
f'(−20%/+25% up to 30M, widening to −33%/+50% from 100M · min. {MIN_GAMES_GLOW} ranked games, Elo above 1000)</span>'
f'<span>{len(entries)} models · click a model to open it ↗</span></div></section>'
'<section class="lb-panel lb-param-panel"><div class="lb-heading"><div><h2>Play Style</h2>'
'<p class="lb-muted">How each model plays in ranked games · further right survives longer, higher clears more lines</p></div></div>'
'<div class="lb-key"><span><i class="lb-quadrant-key"></i>Survives long and clears lines</span>'
'<span><i class="lb-dot-s"></i><i class="lb-dot-l"></i>Bigger dot = higher Elo</span></div>'
f'<div class="lb-legend">{legend}</div>'
f'<div class="lb-scatter-scroll">{style_svg}</div>'
f'<div class="lb-footer"><span>{len(entries)} rated models</span><span>Click a point to open the model ↗</span></div>'
'<p class="lb-note">Averages over each model\'s ranked games in this protocol. Shading starts at the median of both axes.'
f'{style_corr}</p></section></div>'
)
LB_CSS = """
.lb-root{--lb-accent:#1b64c4;--lb-grid:#d9dfd7;--lb-rainbow:linear-gradient(90deg,#ff668e,#ffbc65,#deed87,#6ae8cd,#78bdf4,#b19bff);
display:flex;flex-direction:column;gap:24px;color:var(--body-text-color);font-family:'DM Sans',Arial,sans-serif}
.dark .lb-root{--lb-accent:#4d9fff;--lb-grid:#303731}
.lb-root h2{font:600 21px/1.3 'Space Grotesk',Arial,sans-serif;letter-spacing:-.6px;margin:0;color:var(--body-text-color)}
.lb-muted{color:var(--body-text-color-subdued);font-size:13px;margin:5px 0 0}
.lb-accent{color:var(--lb-accent)}
.lb-panel{background:var(--block-background-fill);border:1px solid var(--border-color-primary);border-radius:13px;padding:26px 28px 0;position:relative}
.lb-param-panel{padding:25px 26px 16px}
.lb-heading{display:flex;justify-content:space-between;gap:20px;align-items:center}
.lb-radio{position:absolute;opacity:0;pointer-events:none}
.lb-seg{display:flex;border:1px solid var(--border-color-primary);padding:3px;border-radius:8px}
.lb-seg label{padding:6px 13px;border-radius:6px;font-size:14px;color:var(--body-text-color-subdued);cursor:pointer;user-select:none}
#lbv-chart:checked~.lb-heading .lb-seg-chart,#lbv-table:checked~.lb-heading .lb-seg-table{background:var(--background-fill-secondary);color:var(--body-text-color);box-shadow:0 1px 4px #0002}
#lbv-chart:checked~.lb-view-table,#lbv-table:checked~.lb-view-chart{display:none}
.lb-chart-scroll{overflow-x:auto;padding:30px 0 12px}
.lb-chart{height:365px;display:flex;min-width:max-content}
.lb-axis{position:relative;width:44px;height:245px;flex-shrink:0;font:11px monospace;color:var(--body-text-color-subdued)}
.lb-tick{position:absolute;right:12px;transform:translateY(50%)}
.lb-bars{position:relative;isolation:isolate;display:flex;align-items:flex-start;gap:24px;padding:0 28px;min-width:max-content}
.lb-grid{position:absolute;left:0;right:0;top:0;height:245px;pointer-events:none;z-index:-1}
.lb-gridline{position:absolute;left:0;right:0;height:1px;background:var(--lb-grid)}
.lb-bar{position:relative;isolation:isolate;display:flex;flex-direction:column;align-items:center;width:108px;flex:0 0 108px;
text-decoration:none!important;color:var(--body-text-color)!important;text-align:center}
.lb-track{height:245px;flex-shrink:0;width:100%;display:flex;align-items:flex-end;justify-content:center}
.lb-fill{position:relative;display:flex;justify-content:center;width:76px;min-height:2px;background:var(--bar-color);
border:1px solid var(--bar-border);border-radius:6px 6px 0 0;transition:filter .2s}
.lb-bar:hover .lb-fill{filter:brightness(1.2)}
.lb-score{position:absolute;top:-30px;font:500 19px 'Space Grotesk',sans-serif;color:var(--body-text-color)}
.lb-label{display:flex;flex-direction:column;align-items:center;gap:4px;margin-top:13px;line-height:1.3}
.lb-label strong{font-size:13px;font-weight:500;overflow-wrap:anywhere}
.lb-label small{font:11px monospace;color:var(--body-text-color-subdued)}
.lb-logo{width:22px;height:22px;object-fit:cover;border-radius:6px;background:var(--background-fill-secondary);flex-shrink:0}
.lb-logo-sm{display:inline-block!important;width:18px!important;height:18px!important;border-radius:4px;margin:0 8px 0 0!important;vertical-align:middle}
.lb-name .lb-initial{display:inline-flex!important}
.lb-initial{display:inline-flex;align-items:center;justify-content:center;font:600 12px 'Space Grotesk',sans-serif;color:var(--body-text-color)}
.lb-leader .lb-fill{--moving-rainbow:linear-gradient(180deg,#ff668e 0%,#ffbc65 16.67%,#deed87 33.33%,#6ae8cd 50%,#78bdf4 66.67%,#b19bff 83.33%,#ff668e 100%);
background-image:var(--moving-rainbow);background-size:100% 200%;border-color:transparent;animation:lb-rainbow 5s linear infinite;isolation:isolate}
.lb-leader .lb-fill::before{content:'';position:absolute;inset:-4px;border-radius:inherit;background-image:var(--moving-rainbow);background-size:100% 200%;
animation:lb-rainbow 5s linear infinite;filter:blur(12px);opacity:.75;z-index:-1;pointer-events:none}
@keyframes lb-rainbow{0%{background-position:0 0}50%{background-position:0 100%}100%{background-position:0 0}}
.lb-rainbow-dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--lb-rainbow);margin:0 7px;box-shadow:0 0 9px #b7b5ed50;vertical-align:middle}
.lb-footer{border-top:1px solid var(--border-color-primary);display:flex;align-items:center;justify-content:space-between;padding:15px 0;
color:var(--body-text-color-subdued);font-size:12px;gap:15px;margin-top:4px}
.lb-table-scroll{overflow-x:auto;margin-top:20px}
.lb-table{border-collapse:collapse!important;width:100%;font-size:13px;white-space:nowrap;border:none!important}
.lb-table th{font-size:11px;color:var(--body-text-color-subdued);font-weight:400;text-align:left}
.lb-table td,.lb-table th{padding:14px 16px!important;border:none!important;border-bottom:1px solid var(--border-color-primary)!important;background:transparent!important}
.lb-table td:not(:nth-child(2)),.lb-table th:not(:nth-child(2)){text-align:right}
.lb-table a{color:var(--body-text-color)!important;text-decoration:none!important}
.lb-table a:hover{color:var(--lb-accent)!important}
.lb-empty{padding:55px 20px;color:var(--body-text-color-subdued);text-align:center}
.lb-key,.lb-legend{display:flex;flex-wrap:wrap;gap:10px 17px;font-size:12px;margin-top:20px}
.lb-key{color:var(--body-text-color-subdued)}
.lb-quadrant-key{display:inline-block;width:13px;height:10px;background:#79d98633;border:1px solid #79d98666;margin-right:7px}
.lb-dot-s,.lb-dot-l{display:inline-block;border-radius:50%;background:var(--body-text-color-subdued);vertical-align:middle}
.lb-dot-s{width:6px;height:6px;margin-right:3px}.lb-dot-l{width:11px;height:11px;margin-right:7px}
.lb-legend{margin:12px 0}
.lb-legend i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px}
.lb-scatter-scroll{overflow-x:auto}
.lb-svg{display:block;width:100%;min-width:800px}
.lb-sgrid{stroke:var(--border-color-primary);stroke-width:1}
.lb-stick{fill:var(--body-text-color-subdued);font:11px Arial,sans-serif}
.lb-saxis{fill:var(--body-text-color);font:13px Arial,sans-serif}
.lb-slabel{fill:var(--body-text-color);font:11px Arial,sans-serif;paint-order:stroke;stroke:var(--block-background-fill);stroke-width:3px;stroke-linejoin:round;pointer-events:none}
.lb-quadrant{fill:#79d98618}
.lb-starget{fill:transparent}
.lb-sdot{stroke:var(--block-background-fill);stroke-width:1.5}
.lb-spoint:hover .lb-sdot{stroke:var(--body-text-color);stroke-width:3}
.lb-note{font-size:12px;color:var(--body-text-color-subdued);margin:4px 0 0;max-width:850px}
@media(max-width:550px){.lb-panel{padding:20px 16px 0}.lb-param-panel{padding:20px 16px 14px}.lb-heading{flex-direction:column;align-items:flex-start}
.lb-bars{gap:12px;padding:0 12px}.lb-footer{align-items:flex-start;font-size:10px}}
@media(prefers-reduced-motion:reduce){.lb-leader .lb-fill,.lb-leader .lb-fill::before{animation:none;background-position:0 50%}}
"""