Spaces:
Running
Running
Download leaderboard.py from DedeProGames/SLM-Tetris-Arena: direct link, hf CLI and curl.
- Browser
- Download file 25 kB
-
https://huggingface.co/spaces/DedeProGames/SLM-Tetris-Arena/resolve/main/leaderboard.py
- Command line
-
hf download hf://spaces/DedeProGames/SLM-Tetris-Arena/leaderboard.py
-
curl -L -o leaderboard.py https://huggingface.co/spaces/DedeProGames/SLM-Tetris-Arena/resolve/main/leaderboard.py
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%}} | |
| """ | |