a11y: bring UI to WCAG 2.2 AAA compliance

Semantic HTML: lang attr, landmarks (header/main/region/complementary),
heading hierarchy (h1-h3), dl/dt/dd for info panel.

ARIA: labels on all icon buttons, aria-hidden on decorative icons,
progressbar role with dynamic aria-valuenow, aria-haspopup/expanded
on all menu triggers, role=listbox/option on playlist, aria-selected,
computed aria-labels on playlist rows.

Contrast: raised --textMuted/--textDim/--icon to AAA 7:1 ratios.

Focus: global :focus-visible outline, slider thumb glow, menu item
highlight, switch focus-within, row focus styles.

Target sizes: 44x44 hit areas on zoom/window/remove buttons via
::before pseudo-elements.

Keyboard: playlist arrow nav + Enter/Space activate + Alt+Arrow
reorder with live region announcements + move buttons. Speed menu,
subtitles menu, and recent menu all keyboard-navigable with
Arrow/Enter/Space/Escape. Dividers resizable via Arrow keys.

Dynamic document.title updates on video/folder load.
This commit is contained in:
Your Name
2026-02-19 16:35:19 +02:00
parent 290ef82176
commit 52934d15d6
11 changed files with 956 additions and 662 deletions

View File

@@ -31,70 +31,89 @@
border-radius:2px;
pointer-events:none;
opacity:0;
transition:opacity .4s ease;
transition:opacity .4s ease, width .2s var(--ease-bounce);
z-index:10;
}
.listWrap:hover .listScrollbar, .listScrollbar.active{opacity:1;}
.listWrap:hover .listScrollbar{width:5px;}
.listScrollbarThumb{
position:absolute;
top:0; left:0; right:0;
min-height:24px;
background:linear-gradient(180deg, rgba(95,175,255,.3), rgba(95,175,255,.15));
border:1px solid rgba(95,175,255,.2);
background:rgba(136,164,196,.15);
border:none;
border-radius:2px;
box-shadow:0 2px 8px rgba(0,0,0,.2);
transition:background .2s ease, border-color .2s ease, box-shadow .2s ease;
transition:background .2s ease;
cursor:grab;
}
.listScrollbarThumb:hover{
background:linear-gradient(180deg, rgba(95,175,255,.45), rgba(95,175,255,.25));
border-color:rgba(95,175,255,.35);
background:rgba(136,164,196,.30);
}
.listScrollbarThumb:active{
cursor:grabbing;
background:rgba(136,164,196,.35);
}
.listScrollbar.active .listScrollbarThumb{
background:linear-gradient(180deg, rgba(95,175,255,.5), rgba(95,175,255,.3));
border-color:rgba(95,175,255,.4);
box-shadow:0 2px 12px rgba(95,175,255,.15);
background:rgba(136,164,196,.30);
}
.row{position:relative; display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:11px 12px; border-bottom:1px solid var(--strokeLight); cursor:pointer; user-select:none; transition:background .2s ease, box-shadow .2s ease; box-shadow:inset 3px 0 0 transparent;}
.row:hover{background:var(--surfaceHover); box-shadow:inset 3px 0 0 rgba(95,175,255,.45);}
.row:active{transform:none;}
.row.active{background:linear-gradient(90deg, var(--accentBg), transparent); box-shadow:inset 3px 0 0 rgba(95,175,255,.7), 0 0 0 1px var(--accentGlow) inset;}
/* Row — no transform/padding changes (preserves drag-and-drop) */
.row{position:relative; display:flex; align-items:flex-start; justify-content:space-between; gap:10px; padding:9px 12px; border-bottom:1px solid var(--strokeLight); cursor:pointer; user-select:none; transition:background .2s ease, box-shadow .2s ease; box-shadow:inset 3px 0 0 transparent;}
.row:hover{background:var(--surfaceHover); box-shadow:inset 3px 0 0 rgba(136,164,196,.40);}
.row:active{background:var(--surfaceActive);}
.row.active{background:var(--accentBg); box-shadow:inset 3px 0 0 rgba(136,164,196,.65);}
.row:focus-visible{outline-offset:-2px; background:var(--surfaceHover); box-shadow:inset 3px 0 0 rgba(136,164,196,.40);}
.row.dragging{opacity:.55;}
.left{min-width:0; display:flex; align-items:flex-start; gap:10px; flex:1 1 auto;}
.numBadge{flex:0 0 auto; min-width:38px; height:22px; padding:0 8px; border-radius:999px; border:1px solid var(--strokeMed); background:radial-gradient(200px 60px at 20% 0%, var(--accentGlow), transparent 55%), var(--surface); box-shadow:var(--shadow2); display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-size:11.8px; letter-spacing:.08px; color:var(--text); font-variant-numeric:tabular-nums; margin-top:1px; transition:all .2s ease; transform:translateX(0);}
.row:hover .numBadge{border-color:var(--accentBorder); background:radial-gradient(200px 60px at 20% 0%, var(--accentGlow), transparent 50%), var(--surfaceHover); transform:translateX(4px);}
.left{min-width:0; display:flex; align-items:flex-start; gap:8px; flex:1 1 auto;}
.numBadge{flex:0 0 auto; min-width:38px; height:22px; padding:0 8px; border-radius:var(--r2); border:none; background:var(--surface-2); box-shadow:var(--shadow3); display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-size:12px; letter-spacing:.02em; color:var(--text); font-variant-numeric:tabular-nums; margin-top:1px; transition:all .2s var(--ease-bounce);}
.row:hover .numBadge{background:var(--surface-3); transform:scale(1.06);}
/* Tree connectors — fully static, opaque, dark lines */
.treeSvg{flex:0 0 auto; margin-top:1px; overflow:visible;}
.treeSvg line{stroke:rgb(65,75,95); stroke-width:1.5;}
.treeSvg circle{fill:rgba(230,240,255,.70); stroke:rgba(100,180,255,.22); stroke-width:1; transition:all .15s ease;}
.row:hover .treeSvg circle{fill:rgba(240,250,255,.85); stroke:rgba(100,180,255,.35);}
.treeSvg line{stroke:rgb(40,46,60); stroke-width:1.5;}
.treeSvg circle{fill:rgba(200,212,238,.60); stroke:rgba(136,164,196,.18); stroke-width:1;}
.textWrap{min-width:0; flex:1 1 auto; transition:transform .2s ease; transform:translateX(0);}
.row:hover .textWrap{transform:translateX(4px);}
.name{font-size:12.9px; font-weight:820; letter-spacing:.12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .15s ease;}
.row:hover .name{color:rgba(255,255,255,.98);}
.small{margin-top:4px; font-size:11.4px; color:var(--textMuted); font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .15s ease;}
.row:hover .small{color:rgba(175,185,210,.85);}
.textWrap{min-width:0; flex:1 1 auto;}
.name{font-size:13px; font-weight:700; letter-spacing:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .2s var(--ease-spring), text-shadow .2s ease, transform .2s var(--ease-bounce); transform:translateX(0);}
.row:hover .name{color:rgba(235,240,252,.96); text-shadow:0 0 20px rgba(136,164,196,.08); transform:translateX(4px);}
.small{margin-top:4px; font-size:11px; color:var(--textMuted); font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; transition:color .2s ease;}
.row:hover .small{color:rgba(170,182,210,.75);}
.tag{flex:0 0 auto; display:inline-flex; align-items:center; padding:5px 9px; border-radius:999px; border:1px solid var(--stroke); background:var(--surface); color:var(--textMuted); font-size:11px; font-weight:820; letter-spacing:.12px; text-transform:uppercase; margin-top:2px; transition:all .15s ease;}
.tag.now{border-color:var(--accentBorder); color:rgba(215,240,255,.90); background:var(--accentBg);}
.tag.done{border-color:var(--successBorder); color:rgba(210,255,230,.88); background:var(--successBg);}
.tag{flex:0 0 auto; display:inline-flex; align-items:center; padding:5px 9px; border-radius:var(--r2); border:none; background:var(--surface-2); color:var(--textMuted); font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; margin-top:2px; transition:all .2s var(--ease-bounce);}
.row:hover .tag{transform:scale(1.05);}
.tag.now{color:rgba(170,195,230,.88); background:var(--accentBg); animation:tagGlow 3s ease-in-out infinite;}
@keyframes tagGlow{
0%,100%{box-shadow:0 0 0 0 rgba(136,164,196,0);}
50%{box-shadow:0 0 8px 0 rgba(136,164,196,.12);}
}
.tag.done{color:rgba(160,195,160,.78); background:var(--successBg);}
.row:hover .tag.done{color:rgba(175,210,175,.88);}
.tag.hidden{display:none;}
.row:hover .tag{transform:scale(1.02);}
.row.drop-before::before,.row.drop-after::after{
content:""; position:absolute; left:10px; right:10px; border-top:2px solid var(--accent);
pointer-events:none; filter:drop-shadow(0 0 10px var(--accentGlow));
pointer-events:none;
animation:dropLineFlash .6s ease-in-out infinite;
}
@keyframes dropLineFlash{
0%,100%{opacity:.6;}
50%{opacity:1;}
}
.row.drop-before::before{top:-1px;}
.row.drop-after::after{bottom:-1px;}
.empty{padding:14px 12px; color:var(--textMuted); font-size:12.5px; line-height:1.4;}
.empty{padding:10px 12px; color:var(--textMuted); font-size:13px; line-height:1.4;}
.playlistHeader{font-weight:900; letter-spacing:.16px; font-size:13.8px; cursor:help; display:flex; align-items:center; gap:10px;}
.playlistHeader .fa{color:var(--iconStrong)!important; opacity:.92;}
.playlistHeader{font-family:var(--brand); font-weight:800; letter-spacing:-.01em; font-size:14px; cursor:help; display:flex; align-items:center; gap:10px; transition:color .2s ease;}
.playlistHeader:hover{color:rgba(235,240,252,.98);}
.playlistHeader .fa{color:var(--iconStrong)!important; opacity:.92; transition:transform .3s var(--ease-bounce), opacity .2s ease;}
.playlistHeader:hover .fa{transform:rotate(-8deg) scale(1.12); opacity:1;}
.moveWrap{display:flex; flex-direction:column; gap:2px; flex:0 0 auto; opacity:0; transition:opacity .2s ease;}
.row:hover .moveWrap, .row:focus-within .moveWrap{opacity:1;}
.moveBtn{width:22px; height:18px; border:none; background:var(--surface-2); border-radius:var(--r3); cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all .15s var(--ease-bounce); padding:0;}
.moveBtn:hover{background:var(--surface-3); transform:scale(1.1);}
.moveBtn:active{transform:scale(.9); transition-duration:.08s;}
.moveBtn .fa{font-size:9px; color:var(--iconStrong)!important; opacity:.7;}
.moveBtn:hover .fa{opacity:1;}