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:
+14
-14
@@ -60,7 +60,7 @@
|
||||
background: rgba(15,23,42,0.05); color: #64748B;
|
||||
}
|
||||
.sc-tag-mode { background: rgba(155,93,229,0.08); color: var(--violet); }
|
||||
.sc-qcount { font-size: 0.72rem; color: #8898AA; font-weight: 600; }
|
||||
.sc-qcount { font-size: 0.72rem; color: var(--text-3); font-weight: 600; }
|
||||
.sc-chevron {
|
||||
width: 20px; height: 20px; color: #cbd5e1; transition: transform 0.2s; flex-shrink: 0;
|
||||
}
|
||||
@@ -89,7 +89,7 @@
|
||||
.sc-fields { display: flex; flex-direction: column; gap: 12px; }
|
||||
.sc-field { display: flex; align-items: center; gap: 10px; }
|
||||
.sc-label {
|
||||
font-size: 0.72rem; color: #8898AA; font-weight: 700; white-space: nowrap;
|
||||
font-size: 0.72rem; color: var(--text-3); font-weight: 700; white-space: nowrap;
|
||||
text-transform: uppercase; letter-spacing: 0.04em; min-width: 68px;
|
||||
}
|
||||
.sc-select {
|
||||
@@ -110,7 +110,7 @@
|
||||
.sc-src-btn {
|
||||
flex: 1; padding: 6px 12px; border: none; border-radius: 8px; background: transparent;
|
||||
font-family: 'Manrope', sans-serif; font-size: 0.76rem; font-weight: 600;
|
||||
color: #8898AA; cursor: pointer; transition: all 0.15s; text-align: center;
|
||||
color: var(--text-3); cursor: pointer; transition: all 0.15s; text-align: center;
|
||||
}
|
||||
.sc-src-btn.active { background: #fff; color: var(--violet); box-shadow: 0 1px 4px rgba(15,23,42,0.08); }
|
||||
.sc-test-pick { display: none; flex-direction: column; gap: 10px; }
|
||||
@@ -661,7 +661,7 @@
|
||||
font-size: 0.78rem; color: #64748B; font-family: monospace;
|
||||
}
|
||||
.sl-assignment { font-weight: 600; color: #3D4F6B; }
|
||||
.sl-class { font-size: 0.78rem; color: #8898AA; }
|
||||
.sl-class { font-size: 0.78rem; color: var(--text-3); }
|
||||
|
||||
.sl-status {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
@@ -696,7 +696,7 @@
|
||||
.sl-role-student { background: rgba(6,214,224,0.1); color: #06aab3; }
|
||||
|
||||
.sl-empty {
|
||||
padding: 48px 24px; text-align: center; color: #8898AA; font-size: 0.88rem;
|
||||
padding: 48px 24px; text-align: center; color: var(--text-3); font-size: 0.88rem;
|
||||
}
|
||||
.sl-empty-icon { margin-bottom: 12px; opacity: 0.3; }
|
||||
|
||||
@@ -708,7 +708,7 @@
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.sl-filter-select:focus { border-color: var(--violet); outline: none; }
|
||||
.sl-count { font-size: 0.78rem; color: #8898AA; font-weight: 600; }
|
||||
.sl-count { font-size: 0.78rem; color: var(--text-3); font-weight: 600; }
|
||||
|
||||
/* ══════════ CLASSROOM ADMIN TAB ══════════ */
|
||||
.cr-admin-section { margin-bottom: 40px; }
|
||||
@@ -3454,17 +3454,17 @@
|
||||
|
||||
function renderAcFiles(q) {
|
||||
const el = document.getElementById('acf-file-list');
|
||||
if (!_acAllFiles) { el.innerHTML = '<div style="padding:10px;color:#8898AA;font-size:.82rem;text-align:center">Загрузка…</div>'; return; }
|
||||
if (!_acAllFiles) { el.innerHTML = '<div style="padding:10px;color:var(--text-3);font-size:.82rem;text-align:center">Загрузка…</div>'; return; }
|
||||
const lq = q.toLowerCase();
|
||||
const items = q ? _acAllFiles.filter(f => (f.title||'').toLowerCase().includes(lq)) : _acAllFiles;
|
||||
const SUBJ = { bio:'Биология', chem:'Химия', math:'Математика', phys:'Физика' };
|
||||
if (!items.length) { el.innerHTML = '<div style="padding:10px;color:#8898AA;font-size:.82rem;text-align:center">Нет файлов</div>'; return; }
|
||||
if (!items.length) { el.innerHTML = '<div style="padding:10px;color:var(--text-3);font-size:.82rem;text-align:center">Нет файлов</div>'; return; }
|
||||
el.innerHTML = items.map(f => `
|
||||
<div onclick="selectAcFile(${f.id},'${esc(f.title||'Файл')}','${f.subject_slug||''}')"
|
||||
style="padding:9px 12px;cursor:pointer;border-bottom:1px solid rgba(15,23,42,0.07);display:flex;align-items:center;gap:8px;${_acFileId===f.id?'background:rgba(155,93,229,0.08);':''} transition:background .15s">
|
||||
<div style="flex:1">
|
||||
<div style="font-size:.84rem;font-weight:600">${esc(f.title||'Файл')}</div>
|
||||
<div style="font-size:.74rem;color:#8898AA">${SUBJ[f.subject_slug]||f.subject_slug||''}</div>
|
||||
<div style="font-size:.74rem;color:var(--text-3)">${SUBJ[f.subject_slug]||f.subject_slug||''}</div>
|
||||
</div>
|
||||
${_acFileId===f.id ? '<span style="color:var(--violet)"><i data-lucide="check" style="width:15px;height:15px"></i></span>' : ''}
|
||||
</div>`).join('');
|
||||
@@ -4819,7 +4819,7 @@
|
||||
try {
|
||||
const rows = await LS.api(`/api/admin/topics?subject_id=${subjId}`);
|
||||
document.getElementById('topics-count').textContent = rows.length + ' тем';
|
||||
if (!rows.length) { el.innerHTML = '<div style="padding:32px;text-align:center;color:#8898AA">Тем нет</div>'; return; }
|
||||
if (!rows.length) { el.innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-3)">Тем нет</div>'; return; }
|
||||
el.innerHTML = '<div style="display:flex;flex-direction:column;gap:6px">' + rows.map(t => `
|
||||
<div class="adm-panel" style="padding:12px 18px;margin:0;display:flex;align-items:center;gap:14px">
|
||||
<span style="font-size:0.75rem;color:var(--text-3);font-weight:700;min-width:28px">#${t.order_index}</span>
|
||||
@@ -4887,7 +4887,7 @@
|
||||
el.innerHTML = LS.skeleton(5, 'row');
|
||||
try {
|
||||
const rows = await LS.api('/api/admin/audit-log?limit=200');
|
||||
if (!rows.length) { el.innerHTML = '<div style="padding:32px;text-align:center;color:#8898AA">Журнал пуст</div>'; return; }
|
||||
if (!rows.length) { el.innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-3)">Журнал пуст</div>'; return; }
|
||||
const ACTION_LABELS = {
|
||||
'user.role_change': 'Смена роли', 'user.edit': 'Редактирование', 'user.ban': 'Блокировка',
|
||||
'user.unban': 'Разблокировка', 'user.delete': 'Удаление', 'user.clear_sessions': 'Очистка истории',
|
||||
@@ -4918,7 +4918,7 @@
|
||||
if (!await LS.confirm('Очистить весь аудит-лог?', { danger: true })) return;
|
||||
try {
|
||||
await LS.api('/api/admin/audit-log', { method:'DELETE' });
|
||||
document.getElementById('audit-list').innerHTML = '<div style="padding:32px;text-align:center;color:#8898AA">Журнал очищен</div>';
|
||||
document.getElementById('audit-list').innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-3)">Журнал очищен</div>';
|
||||
LS.toast('Журнал очищен', 'success');
|
||||
} catch (e) { LS.toast(e.message, 'error'); }
|
||||
}
|
||||
@@ -4929,7 +4929,7 @@
|
||||
el.innerHTML = LS.skeleton(3, 'row');
|
||||
try {
|
||||
const rows = await LS.api('/api/admin/error-log?limit=200');
|
||||
if (!rows.length) { el.innerHTML = '<div style="padding:32px;text-align:center;color:#8898AA;font-size:0.88rem">Ошибок нет</div>'; return; }
|
||||
if (!rows.length) { el.innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-3);font-size:0.88rem">Ошибок нет</div>'; return; }
|
||||
el.innerHTML = rows.map(r => {
|
||||
const dt = new Date(r.created_at);
|
||||
const ds = dt.toLocaleDateString('ru',{day:'numeric',month:'short'}) + ' ' + dt.toLocaleTimeString('ru',{hour:'2-digit',minute:'2-digit'});
|
||||
@@ -4949,7 +4949,7 @@
|
||||
if (!await LS.confirm('Очистить журнал ошибок?', { danger: true })) return;
|
||||
try {
|
||||
await LS.api('/api/admin/error-log', { method:'DELETE' });
|
||||
document.getElementById('errors-list').innerHTML = '<div style="padding:32px;text-align:center;color:#8898AA">Журнал очищен</div>';
|
||||
document.getElementById('errors-list').innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-3)">Журнал очищен</div>';
|
||||
LS.toast('Журнал очищен', 'success');
|
||||
} catch (e) { LS.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user