fix: accessibility & quality audit pass 4+5

CSS:
- Add :active/:focus-visible to .lt-modal-close, .lt-drawer-right-close,
  .lt-notif-panel-clear, .lt-file-item-remove
- Add :focus-visible to .lt-accordion-header, .lt-tag-remove,
  .lt-combobox-tag-remove
- Add .lt-cmd-input-wrap:focus-within focus indicator (outline:none compensation)
- Add will-change: stroke-dashoffset to .lt-gauge-fill
- Add range slider :focus-visible thumb ring
- Fix .tok-cmt hardcoded #5c8c6a → var(--color-tok-cmt) w/ light-mode override
- Add .lt-skip-link component (visible on focus)
- Fix .lt-filter-group fieldset UA border reset

JS:
- Fix infinite scroll: store throttled handler ref so removeEventListener works
- Fix right drawer: remove close-button listeners in _rdClose (were never removed)
- Fix right drawer: add Tab focus trap (matches modal behaviour)
- Fix _cmdPaletteClose: restore focus to element that opened the palette
- Fix initSortTable: set aria-sort="ascending"/"descending"/"none" on th elements
- Fix switchTab: set aria-selected="true"/"false" on .lt-tab[data-tab] buttons
- Fix copy button timeout: guard with document.contains() before DOM mutation
- Fix combobox: add role=combobox, aria-expanded, aria-controls, role=listbox;
  toggle aria-expanded on open/close

HTML:
- Add skip nav link + id="main-content" on <main>
- Primary tab nav: add role=tablist, role=tab, aria-selected, aria-controls,
  id attrs; tab panels get role=tabpanel + aria-labelledby
- Tab bar demo: same ARIA wiring + aria-controls + role/labelledby on panels
- Sidebar filters: convert div+span to fieldset+legend for proper grouping
- Table sort headers: add aria-sort="none" (JS updates on click)
- Accordion: add aria-controls on headers, IDs on bodies
- Wizard: add aria-current="step" on active step indicator
- Table th: scope="col" on all column headers
- Row checkboxes: aria-label per ticket ID
- Worker metrics table: add <tbody>
- Progress bars: role=progressbar + aria-valuenow/min/max + aria-label
- Export + keyboard shortcuts modals: role=dialog, aria-modal, aria-labelledby

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 18:22:53 -04:00
parent 8585993602
commit fdcadad23b
3 changed files with 400 additions and 138 deletions
+196 -24
View File
@@ -175,7 +175,16 @@
--z-popover: 10012;
--z-tooltip: 10013;
--z-toast: 10014;
--z-panel: 10020; /* notification / generic dropdown panels — above everything */
--z-overlay: 9999; /* scanlines / CRT effects */
/* --- Corner cuts (clip-path polygon notches) --- */
--corner-cut: 8px;
--corner-cut-sm: 5px;
--corner-cut-lg: 16px;
/* --- Syntax highlight --- */
--color-tok-cmt: #5c8c6a;
}
@@ -763,6 +772,12 @@ hr {
.lt-btn:active { transform: translateY(1px); }
.lt-btn:focus-visible {
outline: 2px solid var(--accent-cyan);
outline-offset: 2px;
box-shadow: var(--box-glow-cyan);
}
.lt-btn:disabled,
.lt-btn[disabled] {
opacity: 0.30;
@@ -859,10 +874,27 @@ hr {
.lt-input:focus,
.lt-select:focus,
.lt-textarea:focus {
.lt-textarea:focus,
.lt-input:focus-visible,
.lt-select:focus-visible,
.lt-textarea:focus-visible {
border-color: var(--accent-cyan);
box-shadow: var(--box-glow-cyan);
background: rgba(0,212,255,0.025);
outline: 2px solid var(--accent-cyan);
outline-offset: 1px;
}
.lt-input:disabled,
.lt-select:disabled,
.lt-textarea:disabled,
.lt-input[readonly],
.lt-textarea[readonly] {
opacity: 0.45;
cursor: not-allowed;
background: var(--bg-tertiary);
color: var(--text-muted);
border-color: var(--border-dim);
}
.lt-input::placeholder,
@@ -949,6 +981,47 @@ select option:checked {
text-shadow: var(--glow-cyan);
}
.lt-checkbox:hover { border-color: var(--accent-cyan); }
.lt-checkbox:focus-visible {
outline: 2px solid var(--accent-cyan);
outline-offset: 2px;
}
.lt-checkbox:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Radio (same visual language as checkbox) */
.lt-radio {
appearance: none;
width: 14px;
height: 14px;
border: 1px solid var(--accent-cyan-border);
border-radius: 50%;
background: var(--bg-terminal);
cursor: pointer;
flex-shrink: 0;
vertical-align: middle;
transition: var(--transition-fast);
position: relative;
}
.lt-radio:checked {
border-color: var(--accent-cyan);
background: var(--accent-cyan-dim);
}
.lt-radio:checked::after {
content: '';
position: absolute;
inset: 3px;
border-radius: 50%;
background: var(--accent-cyan);
box-shadow: var(--glow-cyan);
}
.lt-radio:hover { border-color: var(--accent-cyan); }
.lt-radio:focus-visible {
outline: 2px solid var(--accent-cyan);
outline-offset: 2px;
}
.lt-radio:disabled { opacity: 0.4; cursor: not-allowed; }
/* ----------------------------------------------------------------
@@ -1055,7 +1128,7 @@ select option:checked {
.lt-status-closed { color: var(--status-closed); background: rgba(255,45,85,0.07); }
.lt-status-online { color: var(--status-online); background: rgba(0,255,136,0.07); text-shadow: var(--glow-green); }
.lt-status-offline { color: var(--status-offline); background: rgba(255,45,85,0.07); }
.lt-status-running { color: var(--status-running); background: rgba(255,179,0,0.07); animation: pulse-amber 2s infinite; }
.lt-status-running { color: var(--status-running); background: rgba(255,179,0,0.07); animation: pulse-amber 2s infinite; will-change: color; }
.lt-status-completed { color: var(--status-completed); background: rgba(0,212,255,0.07); }
.lt-status-failed { color: var(--status-failed); background: rgba(255,45,85,0.07); }
@@ -1107,6 +1180,17 @@ select option:checked {
.lt-badge-amber { color: var(--accent-amber); }
.lt-badge-red { color: var(--accent-red); }
/* Status + priority badge variants (dark-mode base) */
.lt-badge-open { color: var(--accent-green); background: rgba(0,255,136,0.08); border-color: rgba(0,255,136,0.35); text-shadow: var(--glow-green); }
.lt-badge-closed { color: var(--text-muted); background: rgba(74,90,112,0.10); border-color: rgba(74,90,112,0.35); }
.lt-badge-in-progress,
.lt-badge-progress { color: var(--accent-amber); background: rgba(255,179,0,0.08); border-color: rgba(255,179,0,0.35); }
.lt-badge-pending { color: var(--accent-purple); background: rgba(191,95,255,0.08); border-color: rgba(191,95,255,0.35); }
.lt-badge-p1 { color: var(--accent-red); background: rgba(255,45,85,0.09); border-color: rgba(255,45,85,0.40); text-shadow: var(--glow-red); }
.lt-badge-p2 { color: var(--accent-orange); background: rgba(255,107,0,0.09); border-color: rgba(255,107,0,0.38); }
.lt-badge-p3 { color: var(--accent-cyan); background: rgba(0,212,255,0.07); border-color: rgba(0,212,255,0.30); }
.lt-badge-p4 { color: var(--accent-green); background: rgba(0,255,136,0.07); border-color: rgba(0,255,136,0.30); }
/* Status dots */
.lt-dot {
display: inline-block;
@@ -1115,9 +1199,9 @@ select option:checked {
border-radius: 50%;
flex-shrink: 0;
}
.lt-dot-up { background: var(--accent-green); box-shadow: 0 0 6px var(--accent-green), 0 0 14px var(--accent-green); animation: pulse-green 2.2s ease-in-out infinite; }
.lt-dot-up { background: var(--accent-green); box-shadow: 0 0 6px var(--accent-green), 0 0 14px var(--accent-green); animation: pulse-green 2.2s ease-in-out infinite; will-change: box-shadow; }
.lt-dot-down { background: var(--accent-red); box-shadow: 0 0 6px var(--accent-red), 0 0 12px var(--accent-red); }
.lt-dot-warn { background: var(--accent-amber); box-shadow: 0 0 6px var(--accent-amber), 0 0 12px var(--accent-amber); animation: pulse-amber 2.2s ease-in-out infinite; }
.lt-dot-warn { background: var(--accent-amber); box-shadow: 0 0 6px var(--accent-amber), 0 0 12px var(--accent-amber); animation: pulse-amber 2.2s ease-in-out infinite; will-change: box-shadow; }
.lt-dot-idle { background: var(--text-muted); box-shadow: none; }
@@ -1193,7 +1277,9 @@ select option:checked {
padding: 0.2rem 0.4rem;
transition: var(--transition-fast);
}
.lt-modal-close:hover { color: var(--accent-red); text-shadow: var(--glow-red); }
.lt-modal-close:hover { color: var(--accent-red); text-shadow: var(--glow-red); }
.lt-modal-close:active { color: var(--accent-red); opacity: 0.7; }
.lt-modal-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
.lt-modal-body {
padding: var(--space-lg);
@@ -1292,6 +1378,10 @@ select option:checked {
border-color: var(--accent-orange-border);
border-bottom-color: var(--bg-card);
}
.lt-tab:focus-visible {
outline: 2px solid var(--accent-orange);
outline-offset: -2px;
}
.lt-tab-panel { display: none; }
.lt-tab-panel.active { display: block; }
@@ -1339,8 +1429,10 @@ select option:checked {
.lt-filter-group {
margin-bottom: var(--space-md);
padding-bottom: var(--space-md);
padding: 0 0 var(--space-md) 0;
border: none;
border-bottom: 1px solid var(--border-color-dim);
min-width: 0; /* fieldset UA reset */
}
.lt-filter-group:last-child { border-bottom: none; }
@@ -2195,7 +2287,35 @@ select option:checked {
.lt-text-upper { text-transform: uppercase; letter-spacing: 0.1em; }
.lt-hidden { display: none !important; }
.lt-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }
/* Skip navigation link — visible only on focus */
.lt-skip-link {
position: absolute;
top: -100%;
left: var(--space-sm);
padding: var(--space-xs) var(--space-md);
background: var(--bg-terminal);
color: var(--accent-cyan);
border: 1px solid var(--accent-cyan);
font-family: var(--font-mono);
font-size: 0.8rem;
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.05em;
z-index: var(--z-toast);
transition: top 0.1s;
}
.lt-skip-link:focus { top: var(--space-sm); }
.lt-sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip-path: inset(50%); /* replaces deprecated clip: rect(0,0,0,0) */
white-space: nowrap;
border: 0;
}
/* Cursor blink */
.lt-cursor::after {
@@ -2206,6 +2326,9 @@ select option:checked {
font-size: 0.85em;
margin-left: 1px;
}
@media (prefers-reduced-motion: reduce) {
.lt-cursor::after { animation: none; }
}
/* Glitch text effect */
.lt-glitch { position: relative; }
@@ -2221,11 +2344,13 @@ select option:checked {
color: var(--accent-cyan);
opacity: 0.65;
animation: glitch-1 4s infinite;
will-change: clip-path, transform;
}
.lt-glitch::after {
color: var(--accent-orange);
opacity: 0.65;
animation: glitch-2 4s 0.12s infinite;
will-change: clip-path, transform;
}
@@ -2459,6 +2584,7 @@ select option:checked {
text-align: left;
}
.lt-accordion-header:hover { background: var(--bg-tertiary); color: var(--accent-cyan); }
.lt-accordion-header:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; }
.lt-accordion-header[aria-expanded="true"] { color: var(--accent-orange); }
.lt-accordion-icon {
width: 14px;
@@ -2573,9 +2699,17 @@ input[type="range"].lt-range {
height: 4px;
background: var(--bg-tertiary);
border: 1px solid var(--border-dim);
outline: none;
outline: none; /* reset; :focus-visible below provides keyboard ring */
cursor: pointer;
}
input[type="range"].lt-range:focus-visible {
outline: 2px solid var(--accent-orange);
outline-offset: 4px;
}
input[type="range"].lt-range:disabled {
opacity: 0.4;
cursor: not-allowed;
}
input[type="range"].lt-range::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px; height: 14px;
@@ -2586,6 +2720,7 @@ input[type="range"].lt-range::-webkit-slider-thumb {
transition: transform 0.15s;
}
input[type="range"].lt-range::-webkit-slider-thumb:hover { transform: scale(1.3); }
input[type="range"].lt-range:focus-visible::-webkit-slider-thumb { transform: scale(1.3); box-shadow: 0 0 0 3px var(--bg-primary), 0 0 0 5px var(--accent-cyan); }
input[type="range"].lt-range::-moz-range-thumb {
width: 14px; height: 14px;
background: var(--accent-orange);
@@ -2638,6 +2773,8 @@ input[type="range"].lt-range::-moz-range-thumb {
.lt-file-item-name { color: var(--text-secondary); }
.lt-file-item-size { color: var(--text-dim); }
.lt-file-item-remove { background: none; border: none; color: var(--accent-red); cursor: pointer; padding: 0; font-size: 0.9rem; }
.lt-file-item-remove:hover { opacity: 0.75; text-shadow: var(--glow-red); }
.lt-file-item-remove:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
/* ----------------------------------------------------------------
@@ -2681,6 +2818,7 @@ input[type="range"].lt-range::-moz-range-thumb {
font-size: 0.9rem;
caret-color: var(--accent-orange);
}
.lt-cmd-input-wrap:focus-within { border-bottom-color: var(--accent-cyan); }
.lt-cmd-results { max-height: 320px; overflow-y: auto; }
.lt-cmd-group-label {
padding: var(--space-xs) var(--space-md);
@@ -2788,7 +2926,7 @@ input[type="range"].lt-range::-moz-range-thumb {
.tok-kw { color: var(--accent-cyan); }
.tok-str { color: var(--accent-green); }
.tok-num { color: var(--accent-orange); }
.tok-cmt { color: #5c8c6a; font-style: italic; }
.tok-cmt { color: var(--color-tok-cmt); font-style: italic; }
.tok-fn { color: var(--accent-purple); }
@@ -2827,6 +2965,7 @@ input[type="range"].lt-range::-moz-range-thumb {
line-height: 1;
}
.lt-tag-remove:hover { opacity: 1; }
.lt-tag-remove:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; opacity: 1; }
/* ----------------------------------------------------------------
@@ -3113,7 +3252,8 @@ input[type="range"].lt-range::-moz-range-thumb {
.lt-gauge svg { overflow: visible; }
.lt-gauge-track { fill: none; stroke: var(--bg-tertiary); stroke-width: 8; }
.lt-gauge-fill { fill: none; stroke: var(--accent-orange); stroke-width: 8; stroke-linecap: butt;
transition: stroke-dashoffset 0.6s cubic-bezier(0.4,0,0.2,1); }
transition: stroke-dashoffset 0.6s cubic-bezier(0.4,0,0.2,1);
will-change: stroke-dashoffset; }
.lt-gauge-label {
position: absolute;
bottom: 0; left: 50%;
@@ -3138,6 +3278,7 @@ input[type="range"].lt-range::-moz-range-thumb {
border-top-color: var(--accent-orange);
border-radius: 50%;
animation: lt-spin 0.7s linear infinite;
will-change: transform;
}
.lt-spinner--cyan { border-top-color: var(--accent-cyan); }
.lt-spinner--green { border-top-color: var(--accent-green); }
@@ -3247,7 +3388,6 @@ input[type="range"].lt-range::-moz-range-thumb {
.lt-border-red { border-color: var(--accent-red) !important; }
/* Display */
.lt-hidden { display: none !important; }
.lt-visible { display: block !important; }
.lt-flex { display: flex; }
.lt-grid { display: grid; }
@@ -3404,6 +3544,9 @@ html[data-theme="light"] {
--box-glow-red: 0 0 0 2px rgba(181,0,31,0.22), 0 2px 8px rgba(181,0,31,0.12);
--box-glow-amber: 0 0 0 2px rgba(138,90,0,0.22), 0 2px 8px rgba(138,90,0,0.12);
/* — Syntax highlight — */
--color-tok-cmt: #2e6540;
color-scheme: light;
}
@@ -3504,13 +3647,16 @@ html[data-theme="light"] .lt-table-wrap { border-color: var(--border-color);
/* — Badges & status — */
html[data-theme="light"] .lt-badge { background: var(--bg-tertiary); color: var(--text-secondary); }
html[data-theme="light"] .lt-badge-open { background: rgba(0,109,53,0.12); color: #006d35; }
html[data-theme="light"] .lt-badge-closed { background: rgba(90,110,150,0.12); color: #5a6e8c; }
html[data-theme="light"] .lt-badge-p1 { background: rgba(181,0,31,0.12); color: #b5001f; }
html[data-theme="light"] .lt-badge-p2 { background: rgba(196,78,0,0.12); color: #c44e00; }
html[data-theme="light"] .lt-badge-p3 { background: rgba(138,90,0,0.12); color: #8a5a00; }
html[data-theme="light"] .lt-badge-p4 { background: rgba(50,80,130,0.10); color: #2d3d56; }
html[data-theme="light"] .lt-badge-admin { background: rgba(107,47,184,0.12); color: #6b2fb8; }
html[data-theme="light"] .lt-badge-open { background: rgba(0,109,53,0.12); color: #006d35; }
html[data-theme="light"] .lt-badge-closed { background: rgba(90,110,150,0.12); color: #5a6e8c; }
html[data-theme="light"] .lt-badge-in-progress,
html[data-theme="light"] .lt-badge-progress { background: rgba(138,90,0,0.12); color: #8a5a00; text-shadow: none; }
html[data-theme="light"] .lt-badge-pending { background: rgba(107,47,184,0.10); color: #7c22cc; }
html[data-theme="light"] .lt-badge-p1 { background: rgba(181,0,31,0.12); color: #b5001f; text-shadow: none; }
html[data-theme="light"] .lt-badge-p2 { background: rgba(196,78,0,0.12); color: #c44e00; }
html[data-theme="light"] .lt-badge-p3 { background: rgba(138,90,0,0.12); color: #8a5a00; }
html[data-theme="light"] .lt-badge-p4 { background: rgba(50,80,130,0.10); color: #2d3d56; }
html[data-theme="light"] .lt-badge-admin { background: rgba(107,47,184,0.12); color: #6b2fb8; }
/* — Toast — */
html[data-theme="light"] .lt-toast {
@@ -3614,8 +3760,8 @@ html[data-theme="light"] .lt-divider-label { color: var(--text-dim); }
html[data-theme="light"] .lt-tag { background: var(--bg-tertiary); border-color: var(--border-color); color: var(--text-secondary); }
/* — Tooltips — */
html[data-theme="light"] [data-tooltip]::before { background: #1a2035; color: #fff; }
html[data-theme="light"] [data-tooltip]::after { border-top-color: #1a2035; }
html[data-theme="light"] [data-tooltip]::before { background: var(--bg-terminal); color: var(--text-primary); }
html[data-theme="light"] [data-tooltip]::after { border-top-color: var(--bg-terminal); }
/* — Pagination — */
html[data-theme="light"] .lt-page-btn { background: var(--bg-card); border-color: var(--border-color); color: var(--text-secondary); }
@@ -3766,6 +3912,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
animation: lt-shimmer 1.6s ease-in-out infinite;
border-radius: 2px;
display: block;
will-change: opacity;
}
@keyframes lt-shimmer {
0% { background-position: 200% 0; }
@@ -3941,7 +4088,8 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
border-radius: 2px;
transition: var(--transition-fast);
}
.lt-drawer-right-close:hover { color: var(--accent-red); border-color: var(--accent-red); }
.lt-drawer-right-close:hover { color: var(--accent-red); border-color: var(--accent-red); }
.lt-drawer-right-close:active { color: var(--accent-red); opacity: 0.7; }
.lt-drawer-right-close:focus-visible { outline: 1px solid var(--accent-cyan); outline-offset: 2px; }
.lt-drawer-right-body {
flex: 1;
@@ -4056,6 +4204,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
display: flex; align-items: center;
}
.lt-combobox-tag-remove:hover { color: var(--accent-red); }
.lt-combobox-tag-remove:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; }
/* Dropdown list */
.lt-combobox-dropdown {
position: absolute;
@@ -4908,7 +5057,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
background: var(--bg-secondary);
border: 1px solid var(--border-color);
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%);
z-index: 10020;
z-index: var(--z-panel);
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
transform-origin: top right;
transform: scale(0.95);
@@ -4946,7 +5095,9 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
text-transform: uppercase;
letter-spacing: 0.04em;
}
.lt-notif-panel-clear:hover { text-decoration: underline; }
.lt-notif-panel-clear:hover { text-decoration: underline; }
.lt-notif-panel-clear:active { opacity: 0.7; }
.lt-notif-panel-clear:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
.lt-notif-panel-list { max-height: min(280px, calc(80vh - 110px)); overflow-y: auto; }
@@ -5015,7 +5166,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
background: var(--bg-secondary);
border: 1px solid var(--border-color);
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 0 100%);
z-index: 10020;
z-index: var(--z-panel);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
transform-origin: top left;
transform: scale(0.95);
@@ -5250,3 +5401,24 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
.lt-stats-grid { grid-template-columns: repeat(8, 1fr); max-width: 100%; }
.lt-modal { max-width: 800px; }
}
/* ----------------------------------------------------------------
79. FOOTER
---------------------------------------------------------------- */
.lt-footer {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: var(--space-sm);
padding: var(--space-md) var(--space-lg);
border-top: 1px solid var(--border-dim);
margin-top: auto;
background: var(--bg-secondary);
color: var(--text-muted);
font-size: 0.7rem;
font-family: var(--font-mono);
}
@media (max-width: 479px) {
.lt-footer { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
}