Spaces:
Running
Running
Toolbar: display-styled labels, bordered CSV glyph, hover on controls
Browse files
app.py
CHANGED
|
@@ -402,6 +402,7 @@ TABLE_CSS = """
|
|
| 402 |
--lb-ink-3:#9ca3af; --lb-ink-4:#c3c8d0; --lb-empty:#cbd0d6; --lb-accent:#7b61c9;
|
| 403 |
--lb-row-line:#f1f2f4; --lb-row-alt:#fafafa; --lb-tip-bg:#1f2328; --lb-tip-ink:#f5f6f7;
|
| 404 |
--lb-tip-border:#1f2328; --lb-shadow:rgba(0,0,0,.18); --lb-ramp:@RAMP_LIGHT@;
|
|
|
|
| 405 |
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
| 406 |
color:var(--lb-ink); max-width:100%; margin:0; color-scheme:light;
|
| 407 |
background:var(--lb-surface); border:1px solid var(--lb-border); border-radius:12px; padding:16px 18px; }
|
|
@@ -409,6 +410,7 @@ TABLE_CSS = """
|
|
| 409 |
--lb-ink-3:#9098a4; --lb-ink-4:#67707e; --lb-empty:#3f444d; --lb-accent:#9c83d6;
|
| 410 |
--lb-row-line:#26292e; --lb-row-alt:#202327; --lb-tip-bg:#262e3a; --lb-tip-ink:#e7eaef;
|
| 411 |
--lb-tip-border:#39424f; --lb-shadow:rgba(0,0,0,.55); --lb-ramp:@RAMP_DARK@;
|
|
|
|
| 412 |
color-scheme:dark; }
|
| 413 |
.ttslb .tbl-wrap { overflow-x:auto; }
|
| 414 |
/* Natural table width + pinned widths keep the rank/provider/License columns the
|
|
@@ -430,7 +432,7 @@ TABLE_CSS = """
|
|
| 430 |
sit in low rows, where a downward bubble would poke past the table). */
|
| 431 |
.ttslb .info { position:relative; margin-left:5px; color:var(--lb-ink-4); cursor:help; font-weight:400; }
|
| 432 |
.ttslb .info:hover { color:var(--lb-ink-2); }
|
| 433 |
-
.ttslb .info::after, .ttslb .notemark[data-tip]::after
|
| 434 |
content:attr(data-tip); position:absolute; display:none; width:260px;
|
| 435 |
background:var(--lb-tip-bg); color:var(--lb-tip-ink); font-size:11.5px;
|
| 436 |
font-weight:400; letter-spacing:normal; line-height:1.45; text-align:left; white-space:normal;
|
|
@@ -438,9 +440,7 @@ TABLE_CSS = """
|
|
| 438 |
box-shadow:0 4px 12px var(--lb-shadow); z-index:20; pointer-events:none; }
|
| 439 |
.ttslb .info::after { top:calc(100% + 6px); right:-12px; }
|
| 440 |
.ttslb .notemark[data-tip]::after { bottom:calc(100% + 8px); left:50%; transform:translateX(-50%); width:240px; }
|
| 441 |
-
.ttslb .csv-btn[data-tip]::after { top:calc(100% + 6px); right:0; width:auto; white-space:nowrap; }
|
| 442 |
.ttslb .info:hover::after { display:block; }
|
| 443 |
-
.ttslb .csv-btn:hover::after { display:block; }
|
| 444 |
.ttslb td.notecell { cursor:help; }
|
| 445 |
.ttslb td.notecell:hover .notemark[data-tip]::after { display:block; }
|
| 446 |
.ttslb table.lb th.rank { width:34px; text-align:right; }
|
|
@@ -455,6 +455,11 @@ TABLE_CSS = """
|
|
| 455 |
font-variant-numeric:tabular-nums; font-weight:500;
|
| 456 |
background:var(--hm-bg); color:var(--hm-fg); }
|
| 457 |
.dark .ttslb .pill { background:var(--hm-bg-dark); color:var(--hm-fg-dark); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
.ttslb .pill.primary { min-width:46px; font-weight:700; }
|
| 459 |
.ttslb .cov-count { color:var(--lb-ink-2); font-size:12px; font-variant-numeric:tabular-nums; }
|
| 460 |
.ttslb .empty { color:var(--lb-empty); }
|
|
@@ -474,22 +479,38 @@ TABLE_CSS = """
|
|
| 474 |
.ttslb .legend { display:flex; align-items:center; gap:7px; font-size:11.5px; color:var(--lb-ink-2); }
|
| 475 |
.ttslb .legend-bar { width:160px; height:10px; border-radius:5px; background:var(--lb-ramp); }
|
| 476 |
.ttslb .legend-end { color:var(--lb-ink-3); font-variant-numeric:tabular-nums; }
|
| 477 |
-
/*
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
/* per-tab Sample Generations section: factor category tabs + player rows */
|
| 494 |
.ttslb .smp-h { margin:2px 2px 10px; font-size:15px; }
|
| 495 |
.ttslb .smp-tabs { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:8px; }
|
|
@@ -624,13 +645,18 @@ TABLE_CSS = (TABLE_CSS.replace("@RAMP_LIGHT@", _ramp_css(_PALETTE))
|
|
| 624 |
|
| 625 |
|
| 626 |
# Behaviour, injected into <head> (a <script> inside gr.HTML would not execute);
|
| 627 |
-
# delegated listeners survive filter re-renders.
|
| 628 |
# * click-to-sort β every row stays visible: rows with a value in the active
|
| 629 |
# column are sorted and renumbered in the `#` column; rows without one sink
|
| 630 |
# to the bottom, unranked ("β"). Striping is reapplied in display order. Idle
|
| 631 |
# sortable headers keep a faint β glyph; the active column shows β²/βΌ instead.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
# * CSV download β serialises the button's own table exactly as displayed
|
| 633 |
# (current sort order, current license filter, raw data-v values).
|
|
|
|
| 634 |
# * sample category tabs β one factor panel visible at a time per section.
|
| 635 |
APP_JS = """
|
| 636 |
<script>
|
|
@@ -673,6 +699,18 @@ APP_JS = """
|
|
| 673 |
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
|
| 674 |
return;
|
| 675 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
var stab = e.target.closest('.ttslb .smp-tab');
|
| 677 |
if (stab) {
|
| 678 |
var wrap = stab.closest('.smp');
|
|
@@ -731,13 +769,22 @@ APP_JS = """
|
|
| 731 |
if (rk) rk.textContent = i < ranked.length ? i + 1 : 'β';
|
| 732 |
tbody.appendChild(r);
|
| 733 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
});
|
| 735 |
})();
|
| 736 |
</script>
|
| 737 |
"""
|
| 738 |
|
| 739 |
|
| 740 |
-
def _row_html(p, heat_cols, primary_col, cov_col, rank_label, color_fn, marker_of=None
|
|
|
|
| 741 |
m = p.get("metrics", {}) or {}
|
| 742 |
model_raw = str(p.get("model", ""))
|
| 743 |
lic_raw = (p.get("license") or "").strip() or "β"
|
|
@@ -765,7 +812,11 @@ def _row_html(p, heat_cols, primary_col, cov_col, rank_label, color_fn, marker_o
|
|
| 765 |
else:
|
| 766 |
(bg, fg), (bgd, fgd) = color_fn(c, v)
|
| 767 |
cls = "pill primary" if c is primary_col else "pill"
|
| 768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
# unit suffix ("%", "x") rides along in the pill; data-v stays the
|
| 770 |
# bare number so sorting and CSV export keep working
|
| 771 |
txt = f"{v:.2f}{html.escape(c.get('unit') or '')}"
|
|
@@ -857,7 +908,7 @@ def render_board_html(board: dict, lic: str = "all", fname: str = "board") -> st
|
|
| 857 |
for i, p in enumerate(ranked + unranked):
|
| 858 |
alt = "alt" if (i + 1) % 2 == 0 else ""
|
| 859 |
label = i + 1 if i < len(ranked) else "β"
|
| 860 |
-
body.append(f"<tr class='{alt}'>{_row_html(p, heat_cols, primary_col, cov_col, label, color_fn, marker_of)}</tr>")
|
| 861 |
|
| 862 |
if not body:
|
| 863 |
return "<div class='ttslb'><p class='nomatch'>No providers match this filter.</p></div>"
|
|
@@ -868,9 +919,10 @@ def render_board_html(board: dict, lic: str = "all", fname: str = "board") -> st
|
|
| 868 |
f"<div><span class='notemark'>{mark}</span> {html.escape(note)}</div>"
|
| 869 |
for note, mark in marker_of.items()) + "</div>")
|
| 870 |
|
| 871 |
-
# Toolbar: the score-scale legend and
|
| 872 |
-
#
|
| 873 |
-
#
|
|
|
|
| 874 |
# 1β5 rater scale by default, the board's own value anchors for "absolute"
|
| 875 |
# boards (ends flipped for asc columns so red stays on the worst end), and a
|
| 876 |
# generic worstβbest for "normalized" boards.
|
|
@@ -895,8 +947,13 @@ def render_board_html(board: dict, lic: str = "all", fname: str = "board") -> st
|
|
| 895 |
"<span class='legend-bar'></span><span class='legend-end'>5</span>"
|
| 896 |
"<span>mean human rating</span></div>")
|
| 897 |
toolbar = (f"<div class='lb-toolbar'>{legend}"
|
| 898 |
-
|
| 899 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
|
| 901 |
return (
|
| 902 |
"<div class='ttslb'><div class='tbl-box'>"
|
|
|
|
| 402 |
--lb-ink-3:#9ca3af; --lb-ink-4:#c3c8d0; --lb-empty:#cbd0d6; --lb-accent:#7b61c9;
|
| 403 |
--lb-row-line:#f1f2f4; --lb-row-alt:#fafafa; --lb-tip-bg:#1f2328; --lb-tip-ink:#f5f6f7;
|
| 404 |
--lb-tip-border:#1f2328; --lb-shadow:rgba(0,0,0,.18); --lb-ramp:@RAMP_LIGHT@;
|
| 405 |
+
--lb-dim-bg:#eef0f2; --lb-dim-ink:#9aa1ab;
|
| 406 |
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
| 407 |
color:var(--lb-ink); max-width:100%; margin:0; color-scheme:light;
|
| 408 |
background:var(--lb-surface); border:1px solid var(--lb-border); border-radius:12px; padding:16px 18px; }
|
|
|
|
| 410 |
--lb-ink-3:#9098a4; --lb-ink-4:#67707e; --lb-empty:#3f444d; --lb-accent:#9c83d6;
|
| 411 |
--lb-row-line:#26292e; --lb-row-alt:#202327; --lb-tip-bg:#262e3a; --lb-tip-ink:#e7eaef;
|
| 412 |
--lb-tip-border:#39424f; --lb-shadow:rgba(0,0,0,.55); --lb-ramp:@RAMP_DARK@;
|
| 413 |
+
--lb-dim-bg:#22262d; --lb-dim-ink:#79818d;
|
| 414 |
color-scheme:dark; }
|
| 415 |
.ttslb .tbl-wrap { overflow-x:auto; }
|
| 416 |
/* Natural table width + pinned widths keep the rank/provider/License columns the
|
|
|
|
| 432 |
sit in low rows, where a downward bubble would poke past the table). */
|
| 433 |
.ttslb .info { position:relative; margin-left:5px; color:var(--lb-ink-4); cursor:help; font-weight:400; }
|
| 434 |
.ttslb .info:hover { color:var(--lb-ink-2); }
|
| 435 |
+
.ttslb .info::after, .ttslb .notemark[data-tip]::after {
|
| 436 |
content:attr(data-tip); position:absolute; display:none; width:260px;
|
| 437 |
background:var(--lb-tip-bg); color:var(--lb-tip-ink); font-size:11.5px;
|
| 438 |
font-weight:400; letter-spacing:normal; line-height:1.45; text-align:left; white-space:normal;
|
|
|
|
| 440 |
box-shadow:0 4px 12px var(--lb-shadow); z-index:20; pointer-events:none; }
|
| 441 |
.ttslb .info::after { top:calc(100% + 6px); right:-12px; }
|
| 442 |
.ttslb .notemark[data-tip]::after { bottom:calc(100% + 8px); left:50%; transform:translateX(-50%); width:240px; }
|
|
|
|
| 443 |
.ttslb .info:hover::after { display:block; }
|
|
|
|
| 444 |
.ttslb td.notecell { cursor:help; }
|
| 445 |
.ttslb td.notecell:hover .notemark[data-tip]::after { display:block; }
|
| 446 |
.ttslb table.lb th.rank { width:34px; text-align:right; }
|
|
|
|
| 455 |
font-variant-numeric:tabular-nums; font-weight:500;
|
| 456 |
background:var(--hm-bg); color:var(--hm-fg); }
|
| 457 |
.dark .ttslb .pill { background:var(--hm-bg-dark); color:var(--hm-fg-dark); }
|
| 458 |
+
/* focus mode (default): only the active sort column keeps its heatmap β pills
|
| 459 |
+
in other columns grey out with dimmer numbers. The sort JS moves td.dim as
|
| 460 |
+
the active column changes; the toggle-all button puts .allcolors on the
|
| 461 |
+
table, which disables this rule and restores every column's colors. */
|
| 462 |
+
.ttslb table.lb:not(.allcolors) td.dim .pill { background:var(--lb-dim-bg); color:var(--lb-dim-ink); }
|
| 463 |
.ttslb .pill.primary { min-width:46px; font-weight:700; }
|
| 464 |
.ttslb .cov-count { color:var(--lb-ink-2); font-size:12px; font-variant-numeric:tabular-nums; }
|
| 465 |
.ttslb .empty { color:var(--lb-empty); }
|
|
|
|
| 479 |
.ttslb .legend { display:flex; align-items:center; gap:7px; font-size:11.5px; color:var(--lb-ink-2); }
|
| 480 |
.ttslb .legend-bar { width:160px; height:10px; border-radius:5px; background:var(--lb-ramp); }
|
| 481 |
.ttslb .legend-end { color:var(--lb-ink-3); font-variant-numeric:tabular-nums; }
|
| 482 |
+
/* toolbar controls (right side): a vertical stack β the "Toggle colors"
|
| 483 |
+
switch on top, "Download CSV" + bordered glyph button below, labels left /
|
| 484 |
+
controls right-aligned (track and box are both 26px wide, so their right
|
| 485 |
+
edges line up). The labels are display-styled β muted ink, no hover cue β
|
| 486 |
+
but stay inside the buttons, so they still enlarge the hit target. Buttons
|
| 487 |
+
inside gr.HTML get gradio's own button skin, so every contested property
|
| 488 |
+
here (and on .smp-tab) is element-qualified and !important. */
|
| 489 |
+
.ttslb .lb-toolbar .btns { display:flex; flex-direction:column; align-items:flex-end; gap:7px; }
|
| 490 |
+
.ttslb button.hm-btn, .ttslb button.csv-btn { display:inline-flex; align-items:center; gap:7px;
|
| 491 |
+
margin:0; appearance:none; cursor:pointer; font-family:inherit; line-height:1;
|
| 492 |
+
border:0 !important; box-shadow:none !important; background:transparent !important;
|
| 493 |
+
padding:0 !important; color:var(--lb-ink-2) !important;
|
| 494 |
+
font-size:11.5px !important; font-weight:600 !important; }
|
| 495 |
+
/* the switch: off = focus mode (only the sorted column colored); on (.on,
|
| 496 |
+
accent track, knob right) = every column's heatmap shown */
|
| 497 |
+
.ttslb .hm-track { flex:none; position:relative; width:26px; height:14px;
|
| 498 |
+
border-radius:999px; background:var(--lb-ink-4); transition:background .15s; }
|
| 499 |
+
.ttslb button.hm-btn:hover .hm-track { background:var(--lb-ink-3); }
|
| 500 |
+
.ttslb .hm-knob { position:absolute; top:2px; left:2px; width:10px; height:10px;
|
| 501 |
+
border-radius:50%; background:#ffffff; transition:left .15s; }
|
| 502 |
+
.ttslb button.hm-btn.on .hm-track, .ttslb button.hm-btn.on:hover .hm-track { background:var(--lb-accent); }
|
| 503 |
+
.ttslb button.hm-btn.on .hm-knob { left:14px; }
|
| 504 |
+
/* the download glyph in its bordered box (the affordance lives on the box,
|
| 505 |
+
not the label): a data-URI SVG background (an inline <svg> in gr.HTML would
|
| 506 |
+
risk the sanitizer). Stroke colors are baked per state/theme below β url()
|
| 507 |
+
can't read tokens β tracking --lb-ink-2 (idle) / --lb-ink (hover). */
|
| 508 |
+
.ttslb .csv-ico { flex:none; width:26px; height:26px; border:1px solid var(--lb-border);
|
| 509 |
+
border-radius:6px; background-image:@CSV_ICON_L@;
|
| 510 |
+
background-position:center; background-size:14px 14px; background-repeat:no-repeat; }
|
| 511 |
+
.ttslb button.csv-btn:hover .csv-ico { border-color:var(--lb-ink-3); background-image:@CSV_ICON_LH@; }
|
| 512 |
+
.dark .ttslb .csv-ico { background-image:@CSV_ICON_D@; }
|
| 513 |
+
.dark .ttslb button.csv-btn:hover .csv-ico { background-image:@CSV_ICON_DH@; }
|
| 514 |
/* per-tab Sample Generations section: factor category tabs + player rows */
|
| 515 |
.ttslb .smp-h { margin:2px 2px 10px; font-size:15px; }
|
| 516 |
.ttslb .smp-tabs { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:8px; }
|
|
|
|
| 645 |
|
| 646 |
|
| 647 |
# Behaviour, injected into <head> (a <script> inside gr.HTML would not execute);
|
| 648 |
+
# delegated listeners survive filter re-renders. Concerns:
|
| 649 |
# * click-to-sort β every row stays visible: rows with a value in the active
|
| 650 |
# column are sorted and renumbered in the `#` column; rows without one sink
|
| 651 |
# to the bottom, unranked ("β"). Striping is reapplied in display order. Idle
|
| 652 |
# sortable headers keep a faint β glyph; the active column shows β²/βΌ instead.
|
| 653 |
+
# Focus mode follows: the active column's pills keep their heatmap, all
|
| 654 |
+
# other pill cells are marked .dim (greyed unless .allcolors is on).
|
| 655 |
+
# * toggle-all-colors β the toolbar's grid button flips .allcolors on the
|
| 656 |
+
# table, restoring (or re-hiding) every column's heatmap at once.
|
| 657 |
# * CSV download β serialises the button's own table exactly as displayed
|
| 658 |
# (current sort order, current license filter, raw data-v values).
|
| 659 |
+
# * About-tab panel cards β click (or Enter/Space) jumps to that tab.
|
| 660 |
# * sample category tabs β one factor panel visible at a time per section.
|
| 661 |
APP_JS = """
|
| 662 |
<script>
|
|
|
|
| 699 |
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
|
| 700 |
return;
|
| 701 |
}
|
| 702 |
+
var hmb = e.target.closest('.ttslb .hm-btn');
|
| 703 |
+
if (hmb) { // "Toggle colors" switch: all-colors mode for this card's table
|
| 704 |
+
var hcard = hmb.closest('.ttslb');
|
| 705 |
+
var htable = hcard && hcard.querySelector('table.lb');
|
| 706 |
+
if (htable) {
|
| 707 |
+
htable.classList.toggle('allcolors');
|
| 708 |
+
var on = htable.classList.contains('allcolors');
|
| 709 |
+
hmb.classList.toggle('on', on);
|
| 710 |
+
hmb.setAttribute('aria-checked', on ? 'true' : 'false');
|
| 711 |
+
}
|
| 712 |
+
return;
|
| 713 |
+
}
|
| 714 |
var stab = e.target.closest('.ttslb .smp-tab');
|
| 715 |
if (stab) {
|
| 716 |
var wrap = stab.closest('.smp');
|
|
|
|
| 769 |
if (rk) rk.textContent = i < ranked.length ? i + 1 : 'β';
|
| 770 |
tbody.appendChild(r);
|
| 771 |
});
|
| 772 |
+
// focus mode follows the sort: only the active column's pills stay
|
| 773 |
+
// colored; every other pill cell is marked dim (a no-op visually while
|
| 774 |
+
// the table is in .allcolors mode)
|
| 775 |
+
Array.prototype.forEach.call(tbody.rows, function (r) {
|
| 776 |
+
Array.prototype.forEach.call(r.cells, function (td, i) {
|
| 777 |
+
if (td.querySelector('.pill')) td.classList.toggle('dim', i !== ci);
|
| 778 |
+
});
|
| 779 |
+
});
|
| 780 |
});
|
| 781 |
})();
|
| 782 |
</script>
|
| 783 |
"""
|
| 784 |
|
| 785 |
|
| 786 |
+
def _row_html(p, heat_cols, primary_col, cov_col, rank_label, color_fn, marker_of=None,
|
| 787 |
+
active_col=None) -> str:
|
| 788 |
m = p.get("metrics", {}) or {}
|
| 789 |
model_raw = str(p.get("model", ""))
|
| 790 |
lic_raw = (p.get("license") or "").strip() or "β"
|
|
|
|
| 812 |
else:
|
| 813 |
(bg, fg), (bgd, fgd) = color_fn(c, v)
|
| 814 |
cls = "pill primary" if c is primary_col else "pill"
|
| 815 |
+
# dim = outside the initially-sorted column (focus mode); the sort
|
| 816 |
+
# JS re-marks these as the active column changes
|
| 817 |
+
td_classes = ((["notecell"] if mark else [])
|
| 818 |
+
+ (["dim"] if active_col is not None and c is not active_col else []))
|
| 819 |
+
td_cls = f" class='{' '.join(td_classes)}'" if td_classes else ""
|
| 820 |
# unit suffix ("%", "x") rides along in the pill; data-v stays the
|
| 821 |
# bare number so sorting and CSV export keep working
|
| 822 |
txt = f"{v:.2f}{html.escape(c.get('unit') or '')}"
|
|
|
|
| 908 |
for i, p in enumerate(ranked + unranked):
|
| 909 |
alt = "alt" if (i + 1) % 2 == 0 else ""
|
| 910 |
label = i + 1 if i < len(ranked) else "β"
|
| 911 |
+
body.append(f"<tr class='{alt}'>{_row_html(p, heat_cols, primary_col, cov_col, label, color_fn, marker_of, first_col)}</tr>")
|
| 912 |
|
| 913 |
if not body:
|
| 914 |
return "<div class='ttslb'><p class='nomatch'>No providers match this filter.</p></div>"
|
|
|
|
| 919 |
f"<div><span class='notemark'>{mark}</span> {html.escape(note)}</div>"
|
| 920 |
for note, mark in marker_of.items()) + "</div>")
|
| 921 |
|
| 922 |
+
# Toolbar: the score-scale legend (left) and a vertical stack of labelled
|
| 923 |
+
# controls (right): the "Toggle colors" switch over "Download CSV", both
|
| 924 |
+
# handled in APP_JS. All sit inside .tbl-box, so they align with the
|
| 925 |
+
# table's edges. The legend follows the heatmap mode:
|
| 926 |
# 1β5 rater scale by default, the board's own value anchors for "absolute"
|
| 927 |
# boards (ends flipped for asc columns so red stays on the worst end), and a
|
| 928 |
# generic worstβbest for "normalized" boards.
|
|
|
|
| 947 |
"<span class='legend-bar'></span><span class='legend-end'>5</span>"
|
| 948 |
"<span>mean human rating</span></div>")
|
| 949 |
toolbar = (f"<div class='lb-toolbar'>{legend}"
|
| 950 |
+
"<div class='btns'>"
|
| 951 |
+
"<button class='hm-btn' role='switch' aria-checked='false'>"
|
| 952 |
+
"<span class='hm-lbl'>Toggle colors</span>"
|
| 953 |
+
"<span class='hm-track'><span class='hm-knob'></span></span></button>"
|
| 954 |
+
f"<button class='csv-btn' data-fname='{html.escape(fname, quote=True)}'>"
|
| 955 |
+
"<span class='csv-lbl'>Download CSV</span><span class='csv-ico'></span></button>"
|
| 956 |
+
"</div></div>")
|
| 957 |
|
| 958 |
return (
|
| 959 |
"<div class='ttslb'><div class='tbl-box'>"
|