a11y: WCAG AA contrast + ARIA roles + focus management across all pages

- css/ls.css: --text-3 #8898AA → #56687A (5.1:1 contrast), min-height 44px on .btn-primary/.btn-ghost/.sb-link, new .icon-btn utility (44×44px)
- js/api.js: lsConfirm — role=dialog, aria-modal, aria-labelledby, Tab focus trap, restore focus on close; lsToast — aria-live=polite on container, role=alert on errors; live quiz — role=dialog, role=radiogroup, role=radio, aria-checked, keyboard support
- test-run.html: q-opt divs — role=radio/checkbox, aria-checked, tabindex, keyboard enter/space; confirm modal — role=dialog, aria-modal; btn-flag — aria-pressed; dots — aria-label, aria-current; touch targets 44px
- board.html: btn-del-ann — aria-label; reaction buttons — aria-label, aria-pressed
- All 18 HTML files: replace hardcoded color:#8898AA with color:var(--text-3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-04-16 11:42:38 +03:00
parent 3a4623a60a
commit 26ba289019
22 changed files with 362 additions and 299 deletions
+9 -9
View File
@@ -124,7 +124,7 @@
.cc-subj-chem { color: #06D6A0; }
.cc-subj-math { color: #06B6D4; }
.cc-subj-phys { color: #F59E0B; }
.cc-subj-other { color: #8898AA; }
.cc-subj-other { color: var(--text-3); }
.cc-title {
font-family: 'Unbounded', sans-serif; font-size: 0.92rem; font-weight: 800;
color: #0F172A; margin-bottom: 8px; line-height: 1.35;
@@ -138,7 +138,7 @@
display: flex; align-items: center; justify-content: space-between;
border-top: 1px solid rgba(15,23,42,0.07); padding-top: 12px;
}
.cc-meta { font-size: 0.76rem; color: #8898AA; display: flex; align-items: center; gap: 5px; }
.cc-meta { font-size: 0.76rem; color: var(--text-3); display: flex; align-items: center; gap: 5px; }
.cc-progress-bar {
height: 4px; border-radius: 99px;
background: rgba(15,23,42,0.07); flex: 1; max-width: 80px;
@@ -181,7 +181,7 @@
.form-input:focus { outline: none; border-color: var(--violet); background: #fff; }
select.form-input { cursor: pointer; }
.modal-footer { display: flex; gap: 10px; justify-content: flex-end; margin-top: 22px; }
.btn-cancel { padding: 10px 22px; border: 1.5px solid rgba(15,23,42,0.15); border-radius: 999px; background: transparent; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 600; color: #8898AA; cursor: pointer; transition: all 0.18s; }
.btn-cancel { padding: 10px 22px; border: 1.5px solid rgba(15,23,42,0.15); border-radius: 999px; background: transparent; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 600; color: var(--text-3); cursor: pointer; transition: all 0.18s; }
.btn-cancel:hover { border-color: rgba(15,23,42,0.3); color: var(--text); }
.btn-primary { padding: 10px 28px; border: none; border-radius: 999px; background: var(--violet); color: #fff; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 700; cursor: pointer; box-shadow: 0 2px 10px rgba(155,93,229,0.3); transition: all 0.18s; }
.btn-primary:hover { background: #8a47d8; }
@@ -431,14 +431,14 @@
let html = courses.map((c, i) => renderCourseCard(c, i)).join('');
if (lessons.length) {
html += `<div style="grid-column:1/-1;margin-top:8px;margin-bottom:2px">
<div style="font-family:'Unbounded',sans-serif;font-size:0.72rem;font-weight:800;color:#8898AA;text-transform:uppercase;letter-spacing:0.07em">Уроки</div>
<div style="font-family:'Unbounded',sans-serif;font-size:0.72rem;font-weight:800;color:var(--text-3);text-transform:uppercase;letter-spacing:0.07em">Уроки</div>
</div>`;
html += lessons.map((l, i) => `
<a class="course-card stagger-item" href="/lesson?id=${l.id}" style="--i:${i};flex-direction:row;padding:16px;gap:14px;border-radius:16px">
<div style="width:36px;height:36px;border-radius:10px;background:rgba(155,93,229,0.1);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem"><svg class="ic" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></div>
<div style="flex:1">
<div style="font-size:0.88rem;font-weight:700;color:#0F172A">${esc(l.title)}</div>
<div style="font-size:0.74rem;color:#8898AA;margin-top:3px">${esc(l.courseTitle || l.course_title || '')}</div>
<div style="font-size:0.74rem;color:var(--text-3);margin-top:3px">${esc(l.courseTitle || l.course_title || '')}</div>
</div>
<i data-lucide="chevron-right" style="width:15px;height:15px;color:#CBD5E1;align-self:center;flex-shrink:0"></i>
</a>`).join('');
@@ -457,7 +457,7 @@
}
lucide.createIcons();
} catch {
grid.innerHTML = '<div style="grid-column:1/-1;text-align:center;color:#8898AA;padding:40px">Ошибка поиска</div>';
grid.innerHTML = '<div style="grid-column:1/-1;text-align:center;color:var(--text-3);padding:40px">Ошибка поиска</div>';
}
}
@@ -552,7 +552,7 @@
renderCourses();
} catch (e) {
document.getElementById('courses-grid').innerHTML =
'<div style="grid-column:1/-1;text-align:center;color:#8898AA;padding:40px">Ошибка загрузки</div>';
'<div style="grid-column:1/-1;text-align:center;color:var(--text-3);padding:40px">Ошибка загрузки</div>';
}
}
loadCourses();
@@ -700,13 +700,13 @@
.tpl-card-subj{font-size:0.66rem;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--violet);margin-bottom:4px;}
.tpl-card-title{font-family:'Unbounded',sans-serif;font-size:0.82rem;font-weight:800;color:#0F172A;margin-bottom:6px;line-height:1.35;}
.tpl-card-desc{font-size:0.76rem;color:#6B7A8E;line-height:1.5;margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.tpl-card-meta{font-size:0.7rem;color:#8898AA;display:flex;gap:8px;margin-bottom:10px;}
.tpl-card-meta{font-size:0.7rem;color:var(--text-3);display:flex;gap:8px;margin-bottom:10px;}
.tpl-card-actions{display:flex;gap:6px;margin-top:auto;}
.tpl-use-btn{flex:1;padding:7px 14px;border:none;border-radius:999px;background:var(--violet);color:#fff;font-family:'Manrope',sans-serif;font-size:0.78rem;font-weight:700;cursor:pointer;transition:all .15s;}
.tpl-use-btn:hover{background:#8a47d8;}
.tpl-del-btn{width:30px;height:30px;border:1.5px solid rgba(241,91,181,0.2);border-radius:999px;background:rgba(241,91,181,0.06);color:#E0335E;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .12s;flex-shrink:0;}
.tpl-del-btn:hover{background:#E0335E;color:#fff;}
.tpl-empty{text-align:center;padding:40px;color:#8898AA;font-size:0.86rem;}
.tpl-empty{text-align:center;padding:40px;color:var(--text-3);font-size:0.86rem;}
</style>
<div class="tpl-modal" id="tpl-browser-modal" onclick="if(event.target===this)closeTplBrowser()">