Skip to content

Commit 7dccc3e

Browse files
committed
GUI a11y/perf polish
Non-blocking webfont load (preload + onload swap, noscript fallback) so text paints immediately; introduce --ok-text for AA-contrast success labels; darken muted/faint tokens; tabular numerics in data-date cells.
1 parent 147803b commit 7dccc3e

1 file changed

Lines changed: 53 additions & 21 deletions

File tree

scripts/gui.html

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
(function(){var t=localStorage.getItem('theme');if(t!=='light'&&t!=='dark'){var h=new Date().getHours();t=(h>=7&&h<19)?'light':'dark';}document.documentElement.setAttribute('data-theme',t);})();</script>
99
<link rel="preconnect" href="https://fonts.googleapis.com">
1010
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11-
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&family=Noto+Sans+SC:wght@400;500;700;900&display=swap" rel="stylesheet">
11+
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&family=Noto+Sans+SC:wght@400;500;700;900&display=swap" onload="this.onload=null;this.rel='stylesheet'">
12+
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&family=Noto+Sans+SC:wght@400;500;700;900&display=swap"></noscript>
1213
<style>
1314
:root{
1415
--brand:#FD4E63; --brand-ink:#D5344E; --brand2:#FF3B3B;
1516
--grad:linear-gradient(135deg,#FB7299 0%,#FF3B3B 100%);
1617
--brand-soft:rgba(253,78,99,.12);
17-
--bili:#FB7299; --yt:#FF0000; --ok:#1F9D55;
18-
--ink:#15181C; --muted:#6A7480; --faint:#9AA4AD;
18+
--bili:#FB7299; --yt:#FF0000; --ok:#1F9D55; --ok-text:#157A42;
19+
--ink:#15181C; --muted:#5E6873; --faint:#7C8893;
1920
--line:#EAEDF0; --card:#FFFFFF; --bg:#F6F7F9;
2021
--nav-bg:rgba(255,255,255,.82);
2122
--input-bg:#FFFFFF; --code-bg:#F0F2F4;
@@ -30,7 +31,7 @@
3031
--brand:#FF6E83; --brand-ink:#FF6E83; --brand2:#FF5A5A;
3132
--grad:linear-gradient(135deg,#FF8FB0 0%,#FF5A5A 100%);
3233
--brand-soft:rgba(255,110,131,.16);
33-
--bili:#FF8FB0; --yt:#FF4D4D; --ok:#37C871;
34+
--bili:#FF8FB0; --yt:#FF4D4D; --ok:#37C871; --ok-text:#37C871;
3435
--ink:#EDF0F3; --muted:#9BA7B3; --faint:#6C7783;
3536
--line:#262C34; --card:#181C22; --bg:#0F1216;
3637
--nav-bg:rgba(18,22,26,.80);
@@ -90,7 +91,7 @@
9091
.hero .meaning{margin-top:16px;font-size:17px;font-weight:600;color:var(--brand-ink)}
9192
.hero .desc{margin-top:8px;font-size:15.5px;color:var(--muted);max-width:680px;line-height:1.7}
9293
.hero .privacy{display:inline-flex;align-items:center;gap:7px;margin-top:18px;font-size:12.5px;
93-
font-weight:600;color:var(--ok);background:rgba(31,157,85,.12);padding:5px 13px;border-radius:999px}
94+
font-weight:600;color:var(--ok-text);background:rgba(31,157,85,.12);padding:5px 13px;border-radius:999px}
9495
.hero .privacy svg{width:13px;height:13px}
9596

9697
/* ——— layout ——— */
@@ -112,7 +113,7 @@
112113
/* ——— pills (status) ——— */
113114
.pill{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;
114115
border-radius:999px;padding:4px 11px;margin-left:8px}
115-
.pill.on{background:rgba(31,157,85,.13);color:var(--ok)}
116+
.pill.on{background:rgba(31,157,85,.13);color:var(--ok-text)}
116117
.pill.off{background:rgba(140,150,160,.16);color:var(--muted)}
117118

118119
/* ——— platform chips ——— */
@@ -196,7 +197,7 @@
196197

197198
/* ——— creators 表的「数据」列 ——— */
198199
.dcell{font-variant-numeric:tabular-nums;font-weight:600;font-size:12.5px;color:var(--ink);white-space:nowrap}
199-
.dcell .ddate{color:var(--faint);font-weight:500}
200+
.dcell .ddate{color:var(--muted);font-weight:500}
200201

201202
/* ——— toolbar ——— */
202203
.toolbar{display:flex;gap:10px;flex-wrap:wrap;margin:16px 0 6px;align-items:center}
@@ -229,7 +230,7 @@
229230
font-variant-numeric:tabular-nums}
230231
.vstats span{display:inline-flex;align-items:center;gap:4px}
231232
.vstats svg{width:13px;height:13px;opacity:.65}
232-
.empty{grid-column:1/-1;text-align:center;color:var(--faint);padding:38px 0;font-size:14px}
233+
.empty{grid-column:1/-1;text-align:center;color:var(--muted);padding:38px 0;font-size:14px}
233234
.more-wrap{text-align:center;margin-top:22px}
234235
#vmore{display:none}
235236

@@ -247,14 +248,29 @@
247248
background:var(--ink);color:var(--bg);font-size:13.5px;font-weight:600;padding:12px 22px;border-radius:13px;
248249
box-shadow:0 16px 44px -16px rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:.25s;z-index:60;max-width:80vw}
249250
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
250-
.foot{text-align:center;color:var(--faint);font-size:12.5px;margin-top:8px}
251+
.foot{text-align:center;color:var(--muted);font-size:12.5px;margin-top:8px}
251252
.foot a{color:var(--brand-ink);font-weight:600}
252253
.foot a:hover{text-decoration:underline}
253254

255+
/* ——— 键盘焦点可见环(WCAG 2.4.7):统一所有可聚焦控件 ——— */
256+
:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
257+
input:focus-visible,.sel:focus-visible{outline:none;border-color:var(--brand);
258+
box-shadow:0 0 0 3px var(--brand-soft),0 0 0 1.5px var(--brand)}
259+
.chip:has(input:focus-visible){border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
260+
.vcard:focus-visible{border-color:transparent}
261+
254262
@media (max-width:560px){
255263
.hero h1{font-size:38px} .hero-inner{padding:46px 22px 40px}
256264
.card{padding:22px 18px} .head-actions{width:100%;margin-left:0}
257265
}
266+
267+
/* ——— 尊重系统「减少动态效果」偏好(prefers-reduced-motion)——— */
268+
@media (prefers-reduced-motion: reduce){
269+
html{scroll-behavior:auto}
270+
*,*::before,*::after{animation-duration:.001ms!important;
271+
animation-iteration-count:1!important;transition-duration:.001ms!important}
272+
body.lswitch .hero,body.lswitch .wrap{opacity:1}
273+
}
258274
</style>
259275
</head>
260276
<body>
@@ -304,11 +320,11 @@ <h1>vira<span class="hl">lens</span></h1>
304320
</div>
305321

306322
<div class="key-row" id="row-bili">
307-
<label class="fld" data-i18n="sessLabel"></label>
323+
<label class="fld" for="sessdata" data-i18n="sessLabel"></label>
308324
<input id="sessdata" type="password" autocomplete="off" data-i18n-ph="sessPh">
309325
</div>
310326
<div class="key-row" id="row-yt">
311-
<label class="fld" data-i18n="ytLabel"></label>
327+
<label class="fld" for="ytkey" data-i18n="ytLabel"></label>
312328
<input id="ytkey" type="password" autocomplete="off" data-i18n-ph="ytPh">
313329
</div>
314330

@@ -379,17 +395,17 @@ <h1>vira<span class="hl">lens</span></h1>
379395
<div class="toolbar">
380396
<div class="search">
381397
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
382-
<input id="vsearch" oninput="resetPage();renderGrid()" data-i18n-ph="searchPh">
398+
<input id="vsearch" type="text" oninput="resetPage();renderGrid()" data-i18n-ph="searchPh" data-i18n-aria="searchAria">
383399
</div>
384-
<select class="sel" id="vsort" onchange="resetPage();renderGrid()">
400+
<select class="sel" id="vsort" onchange="resetPage();renderGrid()" data-i18n-aria="sortAria">
385401
<option value="play" data-i18n="sortPlay"></option>
386402
<option value="date" data-i18n="sortDate"></option>
387403
<option value="comment" data-i18n="sortComment"></option>
388404
<option value="like" data-i18n="sortLike"></option>
389405
<option value="danmaku" data-i18n="sortDanmaku"></option>
390406
<option value="duration_sec" data-i18n="sortDur"></option>
391407
</select>
392-
<select class="sel" id="vplat" onchange="resetPage();renderGrid()">
408+
<select class="sel" id="vplat" onchange="resetPage();renderGrid()" data-i18n-aria="platAria">
393409
<option value="all" data-i18n="platAll"></option>
394410
<option value="bilibili">Bilibili</option>
395411
<option value="youtube">YouTube</option>
@@ -454,6 +470,8 @@ <h1>vira<span class="hl">lens</span></h1>
454470
tDoneReport:"跑完了 ✓ 可以开报告", tDoneData:"数据取好了 ✓", tErr:"出错了,看日志",
455471
tNeedPath:"先填文件路径", tImportOk:(c,v)=>`导入成功:${c} 位 · ${v} 条`, tImportErr:e=>"导入失败:"+e,
456472
tPickOne:"至少选一个平台",
473+
searchAria:"搜索视频", sortAria:"排序方式", platAria:"按平台筛选",
474+
themeToDark:"切换到深色", themeToLight:"切换到浅色", tNeedRun:"先点「抓取并分析」生成",
457475
},
458476
en:{
459477
brandChip:"video data lens", langBtn:"中",
@@ -502,6 +520,8 @@ <h1>vira<span class="hl">lens</span></h1>
502520
tDoneReport:"Done ✓ report ready", tDoneData:"Data ready ✓", tErr:"Failed — check the log",
503521
tNeedPath:"Enter a file path first", tImportOk:(c,v)=>`Imported: ${c} creators · ${v} videos`, tImportErr:e=>"Import failed: "+e,
504522
tPickOne:"Pick at least one platform",
523+
searchAria:"Search videos", sortAria:"Sort by", platAria:"Filter by platform",
524+
themeToDark:"Switch to dark", themeToLight:"Switch to light", tNeedRun:"Run “Fetch & Analyze” first",
505525
}
506526
};
507527
/* 分区名:中英双语(原始值来自 creators.py 的 zone 字段) */
@@ -519,6 +539,8 @@ <h1>vira<span class="hl">lens</span></h1>
519539
document.getElementById('langLabel').textContent = L().langBtn;
520540
document.querySelectorAll('[data-i18n]').forEach(el=>{ const v=L()[el.dataset.i18n]; if(typeof v==='string') el.innerHTML=v; });
521541
document.querySelectorAll('[data-i18n-ph]').forEach(el=>{ const v=L()[el.dataset.i18nPh]; if(typeof v==='string') el.placeholder=v; });
542+
document.querySelectorAll('[data-i18n-aria]').forEach(el=>{ const v=L()[el.dataset.i18nAria]; if(typeof v==='string') el.setAttribute('aria-label',v); });
543+
updateThemeBtn();
522544
}
523545
function toggleLang(){
524546
lang = lang==='zh'?'en':'zh'; localStorage.setItem('lang',lang);
@@ -531,7 +553,7 @@ <h1>vira<span class="hl">lens</span></h1>
531553
const ICON_MOON='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>';
532554
const ICON_SUN ='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4.5"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>';
533555
function curTheme(){ return document.documentElement.getAttribute('data-theme')||'light'; }
534-
function updateThemeBtn(){ document.getElementById('themeBtn').innerHTML = curTheme()==='dark' ? ICON_SUN : ICON_MOON; }
556+
function updateThemeBtn(){ const b=document.getElementById('themeBtn'); b.innerHTML = curTheme()==='dark' ? ICON_SUN : ICON_MOON; b.setAttribute('aria-label', curTheme()==='dark' ? L().themeToLight : L().themeToDark); }
535557
function toggleTheme(){
536558
const next = curTheme()==='dark' ? 'light' : 'dark';
537559
const root = document.documentElement;
@@ -623,13 +645,17 @@ <h1>vira<span class="hl">lens</span></h1>
623645
const no_fetch=document.getElementById('nofetch').checked, force=document.getElementById('force').checked;
624646
const platforms=selectedPlatforms();
625647
const log=document.getElementById('log'); log.style.display='block'; log.textContent='…';
626-
const r=await fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},
627-
body:JSON.stringify({mode,no_fetch,force,platforms})});
648+
let r;
649+
try{ r=await fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},
650+
body:JSON.stringify({mode,no_fetch,force,platforms})}); }
651+
catch(e){ log.textContent=''; toast(L().tErr); return; }
628652
if(r.status===409){ toast(L().tRunningWait); return; }
629653
setRunning(true); if(polling) clearInterval(polling); polling=setInterval(poll,900);
630654
}
631655
async function poll(){
632-
const p=await (await fetch('/api/progress')).json();
656+
let p;
657+
try{ p=await (await fetch('/api/progress')).json(); }
658+
catch(e){ clearInterval(polling); polling=null; setRunning(false); toast(L().tErr); return; }
633659
const log=document.getElementById('log'); log.textContent=p.lines.join('\n'); log.scrollTop=log.scrollHeight;
634660
if(!p.running){ clearInterval(polling); polling=null; setRunning(false);
635661
await refreshStatus(); await loadData();
@@ -689,8 +715,14 @@ <h1>vira<span class="hl">lens</span></h1>
689715
else more.style.display='none';
690716
}
691717

692-
function openReport(){ window.open('/report','_blank'); } /* 自包含 HTML 报告(跑过「抓取并分析」后生成) */
693-
function openDiag(){ window.open('/diagnose','_blank'); } /* 单视频体检页(选一条视频逐维度诊断) */
718+
async function openReport(){ /* 自包含 HTML 报告(跑过「抓取并分析」后生成) */
719+
try{ const r=await fetch('/report'); if(r.status===404){ toast(L().tNeedRun); return; } }catch(e){}
720+
window.open('/report','_blank');
721+
}
722+
function openDiag(){ /* 单视频体检页(选一条视频逐维度诊断) */
723+
if(!STATE.total){ toast(L().tNeedRun); return; }
724+
window.open('/diagnose','_blank');
725+
}
694726
async function doImport(){
695727
const path=document.getElementById('imp').value.trim();
696728
if(!path){ toast(L().tNeedPath); return; }
@@ -701,7 +733,7 @@ <h1>vira<span class="hl">lens</span></h1>
701733
}
702734

703735
/* ——————————————————— boot ——————————————————— */
704-
updateThemeBtn(); applyLang(); restorePlatforms(); refreshStatus(); loadData();
736+
updateThemeBtn(); applyLang(); restorePlatforms(); refreshStatus().catch(()=>{}); loadData().catch(()=>{});
705737
</script>
706738
</body>
707739
</html>

0 commit comments

Comments
 (0)