cleanup: stats top-streamers bar list — extract inline styles

Second pass on the Statistik tab. The top-10 streamers-by-size
list rendered each row as a 6-inline-style template (margin,
two flex containers, two span colour overrides, two bar wrappers,
two bar fills with hard-coded gradient).

Extracted to a .stats-top-* family in styles.css:
- .stats-top-row — outer row spacing
- .stats-top-meta + .stats-top-meta-sub for the label/byte-size
  flex header
- .stats-top-share for the muted (X.Y%) suffix
- .stats-top-bar-track + .stats-top-bar-fill for the gradient
  progress bar (now with a width-transition for the streamer-by-
  streamer animation when the data refreshes)
- .stats-top-bar-labels for the overlaid LIVE/VOD breakdown that
  gets pointer-events: none so the bar isn't accidentally hover-
  blocked

Also picked up the "no top streamers" empty-state message and
swapped its inline-style div for the existing .form-note utility
class introduced in 4.6.42.

Top streamers row hover state intentionally NOT added — these are
read-only summary rows, not interactive ones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 04:54:56 +02:00
parent 7ffd52a901
commit abc983c035
2 changed files with 66 additions and 8 deletions

View File

@ -71,7 +71,7 @@ function renderStatsTopStreamers(top: ArchiveStatsTopStreamer[], totalBytes: num
if (!container) return; if (!container) return;
if (top.length === 0) { if (top.length === 0) {
applyHtml(container, `<div style="color: var(--text-secondary);">${escapeStatsHtml(UI_TEXT.static.statsEmpty)}</div>`); applyHtml(container, `<div class="form-note">${escapeStatsHtml(UI_TEXT.static.statsEmpty)}</div>`);
return; return;
} }
@ -80,14 +80,14 @@ function renderStatsTopStreamers(top: ArchiveStatsTopStreamer[], totalBytes: num
const pct = Math.max(2, Math.round((s.bytes / maxBytes) * 100)); const pct = Math.max(2, Math.round((s.bytes / maxBytes) * 100));
const sharePct = totalBytes > 0 ? ((s.bytes / totalBytes) * 100).toFixed(1) : '0'; const sharePct = totalBytes > 0 ? ((s.bytes / totalBytes) * 100).toFixed(1) : '0';
return ` return `
<div style="margin-bottom: 10px;"> <div class="stats-top-row">
<div style="display:flex; justify-content:space-between; font-size:13px; margin-bottom:4px;"> <div class="stats-top-meta">
<span><strong>${escapeStatsHtml(s.streamer)}</strong> <span style="color:var(--text-secondary);">&middot; ${s.fileCount} ${escapeStatsHtml(UI_TEXT.static.statsFiles)}</span></span> <span><strong>${escapeStatsHtml(s.streamer)}</strong> <span class="stats-top-meta-sub">&middot; ${s.fileCount} ${escapeStatsHtml(UI_TEXT.static.statsFiles)}</span></span>
<span style="color:var(--text-secondary);">${formatBytesForStats(s.bytes)} <span style="opacity:0.7;">(${sharePct}%)</span></span> <span class="stats-top-meta-sub">${formatBytesForStats(s.bytes)} <span class="stats-top-share">(${sharePct}%)</span></span>
</div> </div>
<div style="background: var(--bg-elevated); border-radius: 3px; height: 18px; overflow: hidden; position: relative;"> <div class="stats-top-bar-track">
<div style="width: ${pct}%; height: 100%; background: linear-gradient(90deg, #9146ff 0%, #00c853 100%);"></div> <div class="stats-top-bar-fill" style="width: ${pct}%;"></div>
${(s.liveBytes > 0 || s.vodBytes > 0) ? `<div style="position:absolute; top:0; left:8px; right:8px; height:100%; display:flex; align-items:center; gap:8px; font-size:10px; color:rgba(255,255,255,0.92); font-weight:600;"> ${(s.liveBytes > 0 || s.vodBytes > 0) ? `<div class="stats-top-bar-labels">
${s.liveBytes > 0 ? `LIVE ${formatBytesForStats(s.liveBytes)}` : ''} ${s.liveBytes > 0 ? `LIVE ${formatBytesForStats(s.liveBytes)}` : ''}
${s.vodBytes > 0 ? `VOD ${formatBytesForStats(s.vodBytes)}` : ''} ${s.vodBytes > 0 ? `VOD ${formatBytesForStats(s.vodBytes)}` : ''}
</div>` : ''} </div>` : ''}

View File

@ -2253,6 +2253,64 @@ select option {
color: var(--text-secondary); color: var(--text-secondary);
} }
/* Top-streamers bar list one row per streamer, label row above a
purple-to-green gradient bar. Live/VOD breakdown labels sit
overlaid on top of the bar for a compact two-column read. */
.stats-top-row {
margin-bottom: 10px;
}
.stats-top-row:last-child {
margin-bottom: 0;
}
.stats-top-meta {
display: flex;
justify-content: space-between;
font-size: 13px;
margin-bottom: 4px;
gap: 8px;
}
.stats-top-meta-sub {
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
.stats-top-share {
opacity: 0.7;
}
.stats-top-bar-track {
background: var(--bg-elevated);
border-radius: 3px;
height: 18px;
overflow: hidden;
position: relative;
}
.stats-top-bar-fill {
height: 100%;
background: linear-gradient(90deg, #9146ff 0%, #00c853 100%);
transition: width 0.4s ease-out;
}
.stats-top-bar-labels {
position: absolute;
top: 0;
left: 8px;
right: 8px;
height: 100%;
display: flex;
align-items: center;
gap: 8px;
font-size: 10px;
color: rgba(255, 255, 255, 0.92);
font-weight: 600;
letter-spacing: 0.3px;
pointer-events: none;
}
/* Old generic scrollbar rules were dead superseded by the /* Old generic scrollbar rules were dead superseded by the
purple-themed *::-webkit-scrollbar block further down the file. purple-themed *::-webkit-scrollbar block further down the file.
Removed to avoid confusion when someone greps for scrollbar styles. */ Removed to avoid confusion when someone greps for scrollbar styles. */