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
+14 -14
View File
@@ -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'); }
}
+16 -16
View File
@@ -55,7 +55,7 @@
.an-chip-val {
font-family: 'Unbounded', sans-serif; font-size: 1.5rem; font-weight: 800;
}
.an-chip-label { font-size: 0.72rem; color: #8898AA; font-weight: 600; margin-top: 4px; }
.an-chip-label { font-size: 0.72rem; color: var(--text-3); font-weight: 600; margin-top: 4px; }
/* ── content container ── */
.an-container { max-width: 1200px; margin: 0 auto; padding: 28px 28px 80px; }
@@ -82,7 +82,7 @@
.hq-table thead th {
padding: 10px 14px; text-align: left;
background: #f8f9fc; border-bottom: 1.5px solid rgba(15,23,42,0.08);
font-size: 0.7rem; font-weight: 700; color: #8898AA;
font-size: 0.7rem; font-weight: 700; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.04em;
}
.hq-table tbody td {
@@ -100,7 +100,7 @@
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden; font-weight: 600; color: #0F172A;
}
.hq-topic { font-size: 0.72rem; color: #8898AA; margin-top: 2px; }
.hq-topic { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; }
.diff-badge {
display: inline-flex; align-items: center;
padding: 3px 10px; border-radius: 999px;
@@ -137,20 +137,20 @@
.hm-4 { background: rgba(155,93,229,0.80); }
.heatmap-legend {
display: flex; align-items: center; gap: 6px;
margin-top: 10px; font-size: 0.7rem; color: #8898AA; font-weight: 600;
margin-top: 10px; font-size: 0.7rem; color: var(--text-3); font-weight: 600;
}
.hm-legend-cell {
width: 12px; height: 12px; border-radius: 2px;
}
.heatmap-days {
display: grid; grid-template-rows: repeat(7, 14px); gap: 3px;
font-size: 0.62rem; color: #8898AA; font-weight: 700;
font-size: 0.62rem; color: var(--text-3); font-weight: 700;
margin-right: 8px; flex-shrink: 0;
}
.heatmap-row { display: flex; align-items: flex-start; }
.heatmap-months {
display: flex; margin-left: 30px; margin-bottom: 4px;
font-size: 0.65rem; color: #8898AA; font-weight: 700;
font-size: 0.65rem; color: var(--text-3); font-weight: 700;
}
.hm-month { flex-shrink: 0; }
@@ -177,12 +177,12 @@
color: var(--violet);
}
.asgn-deadline {
font-size: 0.7rem; color: #8898AA; flex-shrink: 0; min-width: 80px;
font-size: 0.7rem; color: var(--text-3); flex-shrink: 0; min-width: 80px;
}
/* ── empty state ── */
.an-empty {
text-align: center; padding: 80px 20px; color: #8898AA; font-size: 0.9rem;
text-align: center; padding: 80px 20px; color: var(--text-3); font-size: 0.9rem;
}
.an-empty-icon { margin-bottom: 14px; opacity: 0.25; }
@@ -449,7 +449,7 @@
<td class="hq-text">
<div class="hq-text-inner">${esc(q.text)}</div>
</td>
<td><span style="font-size:0.75rem;color:#8898AA;font-weight:600">${esc(q.topic || '—')}</span></td>
<td><span style="font-size:0.75rem;color:var(--text-3);font-weight:600">${esc(q.topic || '—')}</span></td>
<td><span class="diff-badge ${diffCls}">${diffLabel}</span></td>
<td><span class="hq-pct ${errCls}">${errPct}%</span></td>
<td style="font-weight:600">${q.attempts || 0}</td>
@@ -457,7 +457,7 @@
});
html += '</tbody></table></div>';
} else {
html += '<div style="padding:40px;text-align:center;color:#8898AA;font-size:0.88rem">Нет данных о сложных вопросах</div>';
html += '<div style="padding:40px;text-align:center;color:var(--text-3);font-size:0.88rem">Нет данных о сложных вопросах</div>';
}
html += '</div>';
@@ -492,7 +492,7 @@
});
html += '</div>';
} else {
html += '<div style="text-align:center;color:#8898AA;padding:30px;font-size:0.88rem">Нет заданий</div>';
html += '<div style="text-align:center;color:var(--text-3);padding:30px;font-size:0.88rem">Нет заданий</div>';
}
html += '</div>';
@@ -544,26 +544,26 @@
scales: {
x: {
grid: { color: 'rgba(15,23,42,0.05)' },
ticks: { font: { family: 'Manrope', size: 11 }, color: '#8898AA' },
ticks: { font: { family: 'Manrope', size: 11 }, color: '#56687A' },
},
y: {
min: 0, max: 100,
grid: { color: 'rgba(15,23,42,0.05)' },
ticks: {
font: { family: 'Manrope', size: 11 }, color: '#8898AA',
font: { family: 'Manrope', size: 11 }, color: '#56687A',
callback: v => v + '%',
},
},
y2: {
position: 'right',
grid: { display: false },
ticks: { font: { family: 'Manrope', size: 11 }, color: '#8898AA' },
ticks: { font: { family: 'Manrope', size: 11 }, color: '#56687A' },
},
},
},
});
} else if (canvas) {
canvas.parentElement.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#8898AA;font-size:0.88rem">Нет данных за последние недели</div>';
canvas.parentElement.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-3);font-size:0.88rem">Нет данных за последние недели</div>';
}
// render heatmap
@@ -644,7 +644,7 @@
area.innerHTML = `
<div style="display:flex;gap:0">
<div class="heatmap-days">
${days.map((d, i) => `<div style="display:flex;align-items:center;font-size:0.62rem;color:#8898AA;font-weight:700">${i % 2 === 1 ? d : ''}</div>`).join('')}
${days.map((d, i) => `<div style="display:flex;align-items:center;font-size:0.62rem;color:var(--text-3);font-weight:700">${i % 2 === 1 ? d : ''}</div>`).join('')}
</div>
<div>
<div class="heatmap-months" style="margin-left:0">${monthHtml}</div>
+5 -3
View File
@@ -714,7 +714,8 @@
/* ════════════════════════════════════════
ANNOUNCEMENT CARD
════════════════════════════════════════ */
const EMOJIS = ['<svg class="ic" viewBox="0 0 24 24"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>', '<svg class="ic" viewBox="0 0 24 24"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>', '<svg class="ic" viewBox="0 0 24 24"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 3z"/></svg>'];
const EMOJIS = ['<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>', '<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>', '<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 3z"/></svg>'];
const REACTION_LABELS = ['Нравится', 'Обожаю', 'Огонь'];
function renderAnnouncement(a, delay, fresh) {
const key = `ann_${a.id}`;
@@ -725,11 +726,12 @@
const rxHtml = EMOJIS.map((em, idx) => {
const count = rxState[idx] ? 1 : 0;
return `<button class="reaction-btn ${rxState[idx] ? 'active' : ''}" onclick="toggleReaction(${a.id},${idx},this)">${em}${count ? ` ${count}` : ''}</button>`;
const lbl = rxState[idx] ? `Убрать «${REACTION_LABELS[idx]}»` : `${REACTION_LABELS[idx]}`;
return `<button class="reaction-btn ${rxState[idx] ? 'active' : ''}" onclick="toggleReaction(${a.id},${idx},this)" aria-label="${lbl}" aria-pressed="${rxState[idx] ? 'true' : 'false'}">${em}${count ? ` <span aria-hidden="true">${count}</span>` : ''}</button>`;
}).join('');
const delBtn = isTeacher
? `<button class="btn-del-ann" onclick="deleteAnnouncement(${a.id})" title="Удалить"><i data-lucide="x" style="width:13px;height:13px"></i></button>` : '';
? `<button class="btn-del-ann" onclick="deleteAnnouncement(${a.id})" aria-label="Удалить объявление"><i data-lucide="x" style="width:13px;height:13px" aria-hidden="true"></i></button>` : '';
return `<div class="card card-announcement" style="${delay}">
<div class="card-strip"></div>
+10 -10
View File
@@ -27,12 +27,12 @@
.cl-avatar { width: 44px; height: 44px; border-radius: 13px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 800; color: #fff; }
.cl-info { flex: 1; min-width: 0; }
.cl-name { font-size: 0.88rem; font-weight: 700; color: #0F172A; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.25; }
.cl-meta { font-size: 0.73rem; color: #8898AA; margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cl-meta { font-size: 0.73rem; color: var(--text-3); margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cl-chevron { color: #cbd5e1; flex-shrink: 0; transition: color 0.15s; }
.cl-item.active .cl-chevron { color: var(--violet); }
.cl-item:hover .cl-chevron { color: #94a3b8; }
.cl-works-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--pink); flex-shrink: 0; }
.cl-empty-side { padding: 40px 16px; text-align: center; color: #8898AA; font-size: 0.8rem; line-height: 1.6; }
.cl-empty-side { padding: 40px 16px; text-align: center; color: var(--text-3); font-size: 0.8rem; line-height: 1.6; }
/* Right: class detail */
.cl-main { flex: 1; overflow-y: auto; display: flex; flex-direction: column; min-width: 0; }
@@ -47,7 +47,7 @@
.cl-placeholder-icon { width: 72px; height: 72px; border-radius: 22px; background: linear-gradient(135deg, rgba(155,93,229,0.12), rgba(6,214,224,0.08)); display: flex; align-items: center; justify-content: center; margin-bottom: 4px; }
.cl-placeholder-icon svg { width: 32px; height: 32px; stroke: var(--violet); stroke-width: 1.6; }
.cl-placeholder-title { font-family: 'Unbounded', sans-serif; font-size: 0.95rem; font-weight: 800; color: #0F172A; }
.cl-placeholder-sub { font-size: 0.82rem; color: #8898AA; max-width: 220px; line-height: 1.6; }
.cl-placeholder-sub { font-size: 0.82rem; color: var(--text-3); max-width: 220px; line-height: 1.6; }
.cl-detail-wrap { padding: 28px 32px 60px; flex: 1; }
.btn-primary { padding: 10px 24px; border: none; border-radius: var(--r-pill); background: var(--grad-1); color: #fff; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 700; cursor: pointer; transition: transform var(--tr); }
@@ -392,7 +392,7 @@
.works-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; align-items: center; }
.works-chip {
padding: 4px 14px; border-radius: 99px; border: 1.5px solid rgba(15,23,42,0.1);
background: #fff; color: #8898AA;
background: #fff; color: var(--text-3);
font-family: 'Manrope', sans-serif; font-size: 0.74rem; font-weight: 700;
cursor: pointer; transition: all 0.15s;
}
@@ -420,10 +420,10 @@
.work-body { flex: 1; min-width: 0; }
.work-student { font-size: 0.88rem; font-weight: 700; color: #0F172A; margin-bottom: 2px; }
.work-assign { font-size: 0.76rem; color: var(--violet); font-weight: 600; margin-bottom: 3px; }
.work-file { font-size: 0.74rem; color: #8898AA; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.work-file { font-size: 0.74rem; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.work-msg { font-size: 0.76rem; color: #3D4F6B; margin-top: 5px; line-height: 1.4; font-style: italic; }
.work-note { font-size: 0.74rem; color: #059652; margin-top: 4px; font-style: italic; }
.work-meta { font-size: 0.70rem; color: #8898AA; white-space: nowrap; flex-shrink: 0; text-align: right; }
.work-meta { font-size: 0.70rem; color: var(--text-3); white-space: nowrap; flex-shrink: 0; text-align: right; }
.work-status {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 9px; border-radius: 99px; font-size: 0.70rem; font-weight: 700;
@@ -2359,7 +2359,7 @@
let html = `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-wrap:wrap;gap:8px">
<div style="font-family:'Unbounded',sans-serif;font-size:0.82rem;font-weight:800;color:#0F172A">
Курсы класса <span style="color:#8898AA;font-weight:600">(${(courses||[]).length})</span>
Курсы класса <span style="color:var(--text-3);font-weight:600">(${(courses||[]).length})</span>
</div>
${available.length ? `<button class="btn-ghost" onclick="openAssignCourseModal()" style="font-size:0.8rem">
<i data-lucide="plus" style="width:13px;height:13px;vertical-align:-2px"></i> Добавить курс
@@ -2367,7 +2367,7 @@
</div>`;
if (!(courses||[]).length) {
html += `<div style="text-align:center;padding:40px;color:#8898AA;font-size:0.85rem;background:#fff;border-radius:16px;border:1.5px dashed rgba(155,93,229,0.15)">
html += `<div style="text-align:center;padding:40px;color:var(--text-3);font-size:0.85rem;background:#fff;border-radius:16px;border:1.5px dashed rgba(155,93,229,0.15)">
Нет назначенных курсов. Нажмите «Добавить курс», чтобы назначить курс классу.
</div>`;
} else {
@@ -2380,7 +2380,7 @@
<span style="font-size:1.6rem">${c.coverEmoji || LS.icon('book-open',20)}</span>
<div style="flex:1;min-width:0">
<div style="font-size:0.9rem;font-weight:700;color:#0F172A;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(c.title)}</div>
<div style="font-size:0.74rem;color:#8898AA;margin-top:2px">${esc(SUBJ_LABEL[c.subjectSlug] || c.subjectSlug || '')} · ${c.lessonCount} уроков</div>
<div style="font-size:0.74rem;color:var(--text-3);margin-top:2px">${esc(SUBJ_LABEL[c.subjectSlug] || c.subjectSlug || '')} · ${c.lessonCount} уроков</div>
</div>
<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
<div style="width:80px;height:5px;border-radius:99px;background:rgba(15,23,42,0.07)">
@@ -2438,7 +2438,7 @@
<div style="font-family:'Unbounded',sans-serif;font-size:0.92rem;font-weight:800;margin-bottom:20px">Назначить курс классу</div>
<div id="assign-course-sel-wrap"></div>
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:4px">
<button id="assign-cancel-btn" style="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">Отмена</button>
<button id="assign-cancel-btn" style="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">Отмена</button>
<button id="assign-confirm-btn" style="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">Назначить</button>
</div>
</div>`;
+19 -19
View File
@@ -78,7 +78,7 @@
font-family: 'Unbounded',sans-serif; font-size: 1.1rem; font-weight: 800;
color: #fff; margin: 0;
}
.cr-idle p { color: #8898AA; font-size: 0.85rem; margin: 0; max-width: 340px; line-height: 1.6; }
.cr-idle p { color: var(--text-3); font-size: 0.85rem; margin: 0; max-width: 340px; line-height: 1.6; }
.cr-start-btn {
display: flex; align-items: center; gap: 8px;
padding: 12px 28px; border-radius: 99px;
@@ -672,7 +672,7 @@
.mic-off { color: rgba(255,255,255,0.18); }
.cr-p-mute-btn {
background: none; border: none; cursor: pointer; padding: 2px; border-radius: 4px;
color: #8898AA; display: flex; align-items: center; opacity: 0; transition: opacity .15s, color .15s;
color: var(--text-3); display: flex; align-items: center; opacity: 0; transition: opacity .15s, color .15s;
}
.cr-participant:hover .cr-p-mute-btn { opacity: 1; }
@@ -733,7 +733,7 @@
display: flex; align-items: center; gap: 4px;
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px; padding: 2px 7px 2px 5px;
cursor: pointer; font-size: 0.67rem; font-weight: 600; color: #8898AA;
cursor: pointer; font-size: 0.67rem; font-weight: 600; color: var(--text-3);
transition: background .15s, border-color .15s, color .15s;
white-space: nowrap;
}
@@ -792,7 +792,7 @@
.cr-msg-row.mine .cr-msg-time { text-align: right; }
.cr-msg-pin-badge { color: #9B5DE5; opacity: 0.8; display: flex; align-items: center; }
.cr-msg-pin-btn {
background: none; border: none; cursor: pointer; color: #8898AA;
background: none; border: none; cursor: pointer; color: var(--text-3);
padding: 1px; border-radius: 4px; display: flex; align-items: center;
opacity: 0; transition: opacity .15s, color .15s;
}
@@ -1030,7 +1030,7 @@
padding: 20px; text-align: center;
}
.cr-no-session svg { width: 32px; height: 32px; stroke: #8898AA; }
.cr-no-session p { font-size: 0.78rem; color: #8898AA; line-height: 1.5; margin: 0; }
.cr-no-session p { font-size: 0.78rem; color: var(--text-3); line-height: 1.5; margin: 0; }
/* ── Modal: start session ── */
.cr-modal-overlay {
@@ -1050,13 +1050,13 @@
font-family: 'Unbounded',sans-serif; font-size: 1rem; font-weight: 800;
color: #fff; margin-bottom: 6px;
}
.cr-modal-sub { font-size: 0.8rem; color: #8898AA; margin-bottom: 20px; }
.cr-modal-sub { font-size: 0.8rem; color: var(--text-3); margin-bottom: 20px; }
.cr-mode-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
.cr-mode-tab {
flex: 1; padding: 9px; border-radius: 10px;
border: 1.5px solid rgba(255,255,255,0.1); background: transparent;
color: #8898AA; font-family: 'Manrope',sans-serif; font-size: 0.78rem;
color: var(--text-3); font-family: 'Manrope',sans-serif; font-size: 0.78rem;
font-weight: 700; cursor: pointer; transition: all .15s;
}
.cr-mode-tab.active {
@@ -1064,7 +1064,7 @@
}
.cr-field { margin-bottom: 14px; }
.cr-label { font-size: 0.75rem; font-weight: 700; color: #8898AA; margin-bottom: 6px; display: block; }
.cr-label { font-size: 0.75rem; font-weight: 700; color: var(--text-3); margin-bottom: 6px; display: block; }
.cr-input {
width: 100%; background: rgba(255,255,255,0.06);
border: 1.5px solid rgba(255,255,255,0.1); border-radius: 10px;
@@ -1113,7 +1113,7 @@
.cr-online-empty { padding: 16px 12px; text-align: center; font-size: .8rem; color: #475569; }
.cr-online-refresh {
display: flex; align-items: center; gap: 6px; margin-bottom: 8px;
font-size: .72rem; font-weight: 700; color: #8898AA; cursor: pointer;
font-size: .72rem; font-weight: 700; color: var(--text-3); cursor: pointer;
background: none; border: none; padding: 0; font-family: 'Manrope',sans-serif;
}
.cr-online-refresh:hover { color: #c4b5fd; }
@@ -1135,7 +1135,7 @@
.cr-modal-cancel {
flex: 1; padding: 11px; border-radius: 10px;
border: 1.5px solid rgba(255,255,255,0.1); background: transparent;
color: #8898AA; font-family: 'Manrope',sans-serif; font-size: 0.85rem;
color: var(--text-3); font-family: 'Manrope',sans-serif; font-size: 0.85rem;
font-weight: 700; cursor: pointer; transition: all .15s;
}
.cr-modal-cancel:hover { border-color: rgba(255,255,255,0.25); color: #fff; }
@@ -1199,7 +1199,7 @@
font-family: 'Unbounded',sans-serif; font-size: 1rem; font-weight: 800;
color: #fff; margin: 0;
}
.cr-join-banner p { font-size: 0.82rem; color: #8898AA; margin: 0; line-height: 1.6; }
.cr-join-banner p { font-size: 0.82rem; color: var(--text-3); margin: 0; line-height: 1.6; }
.cr-join-btn {
padding: 11px 32px; border-radius: 99px;
background: linear-gradient(135deg, #9B5DE5, #7B3FC5);
@@ -1835,13 +1835,13 @@
color: #fff; margin: 0 0 8px;
}
.cr-dlg-msg {
font-size: 0.83rem; color: #8898AA; margin: 0 0 24px; line-height: 1.65;
font-size: 0.83rem; color: var(--text-3); margin: 0 0 24px; line-height: 1.65;
}
.cr-dlg-actions { display: flex; gap: 10px; }
.cr-dlg-cancel {
flex: 1; padding: 10px; border-radius: 10px;
border: 1.5px solid rgba(255,255,255,0.1); background: transparent;
color: #8898AA; font-family: 'Manrope',sans-serif; font-size: 0.85rem;
color: var(--text-3); font-family: 'Manrope',sans-serif; font-size: 0.85rem;
font-weight: 700; cursor: pointer; transition: all .15s;
}
.cr-dlg-cancel:hover { border-color: rgba(255,255,255,0.28); color: #fff; }
@@ -1885,7 +1885,7 @@
}
.cr-sp-close {
background: none; border: none; cursor: pointer; padding: 6px;
border-radius: 8px; color: #8898AA; transition: color .15s, background .15s;
border-radius: 8px; color: var(--text-3); transition: color .15s, background .15s;
display: flex; align-items: center;
}
.cr-sp-close:hover { background: rgba(255,255,255,0.07); color: #fff; }
@@ -1894,7 +1894,7 @@
}
.cr-sp-tab {
flex: 1; padding: 10px 6px; font-family: 'Manrope',sans-serif;
font-size: 0.7rem; font-weight: 700; color: #8898AA;
font-size: 0.7rem; font-weight: 700; color: var(--text-3);
background: none; border: none; cursor: pointer;
border-bottom: 2px solid transparent; transition: color .15s;
}
@@ -1908,11 +1908,11 @@
.cr-sp-section { display: flex; flex-direction: column; gap: 10px; }
.cr-sp-section-label {
font-family: 'Manrope',sans-serif; font-size: 0.68rem; font-weight: 700;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em;
}
.cr-sp-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cr-sp-row-lbl { font-family: 'Manrope',sans-serif; font-size: 0.82rem; font-weight: 600; color: #c8d0db; flex: 1; line-height: 1.4; }
.cr-sp-row-sub { font-size: 0.69rem; color: #8898AA; margin-top: 2px; }
.cr-sp-row-sub { font-size: 0.69rem; color: var(--text-3); margin-top: 2px; }
/* Toggle */
.cr-toggle { position: relative; width: 38px; height: 22px; flex-shrink: 0; }
.cr-toggle input { opacity: 0; width: 0; height: 0; }
@@ -1931,7 +1931,7 @@
.cr-seg button {
flex: 1; padding: 7px 8px; border: none; background: none; cursor: pointer;
font-family: 'Manrope',sans-serif; font-size: 0.72rem; font-weight: 700;
color: #8898AA; transition: all .15s;
color: var(--text-3); transition: all .15s;
}
.cr-seg button.active { background: #9B5DE5; color: #fff; border-radius: 6px; }
/* Mic test */
@@ -1946,7 +1946,7 @@
.cr-sp-btn.cyan { border-color: rgba(6,214,160,0.4); background: rgba(6,214,160,0.08); color: #06D6A0; }
.cr-sp-btn.cyan:hover { background: rgba(6,214,160,0.16); }
.cr-sp-btn.cyan.granted { opacity: 0.6; cursor: default; pointer-events: none; }
.cr-sp-note { font-family: 'Manrope',sans-serif; font-size: 0.72rem; color: #8898AA; line-height: 1.5; }
.cr-sp-note { font-family: 'Manrope',sans-serif; font-size: 0.72rem; color: var(--text-3); line-height: 1.5; }
/* Chat font size */
#cr-messages .cr-msg-text { font-size: 0.82rem; }
.cr-chat-fs-small #cr-messages .cr-msg-text { font-size: 0.74rem; }
+15 -15
View File
@@ -125,7 +125,7 @@
}
.section-title {
font-family: 'Unbounded', sans-serif; font-size: 0.72rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.07em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.07em;
display: flex; align-items: center; gap: 7px;
}
.section-title::before {
@@ -151,7 +151,7 @@
background: rgba(15,23,42,0.05); border: 1.5px solid rgba(15,23,42,0.08);
display: flex; align-items: center; justify-content: center;
font-family: 'Unbounded', sans-serif; font-size: 0.72rem; font-weight: 800;
color: #8898AA; flex-shrink: 0;
color: var(--text-3); flex-shrink: 0;
}
.lesson-num.done {
background: rgba(5,150,82,0.1); border-color: rgba(5,150,82,0.2); color: #059652;
@@ -160,11 +160,11 @@
.lesson-title { font-size: 0.92rem; font-weight: 700; color: #0F172A; }
.lesson-meta-row { display: flex; align-items: center; gap: 8px; margin-top: 3px; }
.lesson-draft-lbl {
font-size: 0.68rem; font-weight: 700; color: #8898AA;
font-size: 0.68rem; font-weight: 700; color: var(--text-3);
background: rgba(15,23,42,0.05); padding: 2px 7px; border-radius: 99px;
}
.lesson-read-time {
font-size: 0.68rem; color: #8898AA; display: flex; align-items: center; gap: 3px;
font-size: 0.68rem; color: var(--text-3); display: flex; align-items: center; gap: 3px;
}
.lesson-stat-lbl {
font-size: 0.7rem; font-weight: 700; color: #06D6A0;
@@ -204,9 +204,9 @@
background: #f8f9fc; border: 1.5px solid rgba(15,23,42,0.07);
border-radius: 14px; padding: 14px 16px;
}
.stat-chip-label { font-size: 0.72rem; color: #8898AA; font-weight: 600; margin-bottom: 6px; }
.stat-chip-label { font-size: 0.72rem; color: var(--text-3); font-weight: 600; margin-bottom: 6px; }
.stat-chip-val { font-family: 'Unbounded', sans-serif; font-size: 1.1rem; font-weight: 800; color: #0F172A; }
.stat-chip-sub { font-size: 0.7rem; color: #8898AA; margin-top: 2px; }
.stat-chip-sub { font-size: 0.7rem; color: var(--text-3); margin-top: 2px; }
/* ── analytics dashboard ── */
.analytics-panel {
@@ -229,11 +229,11 @@
.an-chip-val {
font-family: 'Unbounded', sans-serif; font-size: 1.3rem; font-weight: 800;
}
.an-chip-label { font-size: 0.72rem; color: #8898AA; font-weight: 600; margin-top: 4px; }
.an-chip-label { font-size: 0.72rem; color: var(--text-3); font-weight: 600; margin-top: 4px; }
/* lesson bars */
.an-lessons-title {
font-size: 0.76rem; font-weight: 700; color: #8898AA; text-transform: uppercase;
font-size: 0.76rem; font-weight: 700; color: var(--text-3); text-transform: uppercase;
letter-spacing: 0.06em; margin-bottom: 12px;
}
.an-lesson-row {
@@ -276,14 +276,14 @@
}
.an-stuck-info { flex: 1; }
.an-stuck-name { font-size: 0.82rem; font-weight: 700; color: #0F172A; }
.an-stuck-detail { font-size: 0.72rem; color: #8898AA; margin-top: 2px; }
.an-stuck-detail { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; }
/* students table */
.an-students-section { margin-top: 20px; }
.an-students-toggle {
background: none; border: 1.5px solid rgba(15,23,42,0.1); border-radius: 10px;
padding: 8px 16px; font-family: 'Manrope', sans-serif; font-size: 0.78rem;
font-weight: 700; color: #8898AA; cursor: pointer; transition: all 0.15s;
font-weight: 700; color: var(--text-3); cursor: pointer; transition: all 0.15s;
display: flex; align-items: center; gap: 6px;
}
.an-students-toggle:hover { border-color: var(--violet); color: var(--violet); }
@@ -293,7 +293,7 @@
}
.an-students-table th {
text-align: left; padding: 8px 10px; font-size: 0.7rem; font-weight: 700;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.05em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.05em;
border-bottom: 1.5px solid rgba(15,23,42,0.08);
}
.an-students-table td {
@@ -350,7 +350,7 @@
.form-input { width: 100%; padding: 11px 14px; border: 1.5px solid rgba(15,23,42,0.15); border-radius: 12px; font-family: 'Manrope', sans-serif; font-size: 0.92rem; color: var(--text); background: #f8f9fc; transition: border-color 0.2s; box-sizing: border-box; }
.form-input:focus { outline: none; border-color: var(--violet); background: #fff; }
.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; }
@@ -702,7 +702,7 @@
document.getElementById('lessons-count').textContent = `Уроки · ${lessons.length}`;
if (!lessons.length) {
list.innerHTML = `<div style="text-align:center;padding:40px;color:#8898AA;font-size:0.86rem">
list.innerHTML = `<div style="text-align:center;padding:40px;color:var(--text-3);font-size:0.86rem">
${isTeacher ? 'Нажмите «Урок», чтобы создать первый урок' : 'В курсе пока нет уроков'}
</div>`;
return;
@@ -795,7 +795,7 @@
const data = await LS.api(url);
if (!data.totalStudents && !data.lessons?.length) {
body.innerHTML = '<div style="text-align:center;padding:20px;color:#8898AA;font-size:0.84rem">Нет данных. Назначьте курс классу, чтобы видеть аналитику.</div>';
body.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-3);font-size:0.84rem">Нет данных. Назначьте курс классу, чтобы видеть аналитику.</div>';
return;
}
@@ -886,7 +886,7 @@
body.innerHTML = html;
lucide.createIcons();
} catch (e) {
body.innerHTML = '<div style="text-align:center;padding:20px;color:#8898AA;font-size:0.84rem">Ошибка загрузки аналитики</div>';
body.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-3);font-size:0.84rem">Ошибка загрузки аналитики</div>';
}
}
+23 -6
View File
@@ -13,7 +13,7 @@
--border-h: rgba(15,23,42,0.20);
--text: #0F172A;
--text-2: #3D4F6B;
--text-3: #8898AA;
--text-3: #56687A; /* WCAG AA: ~5.1:1 on white, ~4.6:1 on --bg */
--violet: #9B5DE5;
--cyan: #06D6E0;
@@ -53,6 +53,20 @@ body {
/* ── Focus ring ── */
:focus-visible { outline: 2px solid var(--violet); outline-offset: 3px; }
/* ── Icon-only button (WCAG 2.5.5: 44×44 tap area) ── */
.icon-btn {
display: inline-flex; align-items: center; justify-content: center;
min-width: 44px; min-height: 44px;
border: none; border-radius: 10px;
background: transparent;
cursor: pointer;
transition: background var(--tr), color var(--tr);
color: var(--text-2);
flex-shrink: 0;
}
.icon-btn:hover { background: rgba(155,93,229,0.08); color: var(--violet); }
.icon-btn svg { width: 20px; height: 20px; }
/* ── Navbar ── */
.nav {
position: sticky; top: 0; z-index: 100;
@@ -122,6 +136,7 @@ body {
.btn-primary {
position: relative; overflow: hidden;
padding: 10px 26px;
min-height: 44px; /* WCAG 2.5.5 touch target */
border: none; border-radius: var(--r-pill);
background: var(--grad-1);
color: #fff;
@@ -145,6 +160,7 @@ body {
/* ── Ghost & danger buttons ── */
.btn-ghost {
padding: 8px 18px;
min-height: 44px; /* WCAG 2.5.5 touch target */
border: 1.5px solid var(--border-h); border-radius: var(--r-pill);
background: transparent;
font-family: 'Manrope', sans-serif; font-size: 0.82rem; font-weight: 600;
@@ -306,6 +322,7 @@ body {
align-items: center;
gap: 10px;
padding: 9px 12px;
min-height: 44px; /* WCAG 2.5.5 touch target */
border-radius: 12px;
text-decoration: none;
font-size: 0.875rem;
@@ -745,25 +762,25 @@ body {
display: flex; align-items: center; gap: 10px;
padding: 16px 20px; border-bottom: 1px solid rgba(15,23,42,0.08);
}
.gs-input-wrap svg { flex-shrink: 0; color: #8898AA; }
.gs-input-wrap svg { flex-shrink: 0; color: var(--text-3); }
.gs-input {
flex: 1; border: none; outline: none; font-family: 'Manrope', sans-serif;
font-size: 0.95rem; font-weight: 500; color: #0F172A; background: transparent;
}
.gs-input::placeholder { color: #B0BEC5; }
.gs-kbd {
font-size: 0.65rem; font-weight: 700; color: #8898AA; background: rgba(15,23,42,0.06);
font-size: 0.65rem; font-weight: 700; color: var(--text-3); background: rgba(15,23,42,0.06);
padding: 3px 7px; border-radius: 5px; line-height: 1;
}
.gs-results {
max-height: 380px; overflow-y: auto; padding: 8px;
}
.gs-empty {
text-align: center; padding: 40px 20px; color: #8898AA; font-size: 0.85rem;
text-align: center; padding: 40px 20px; color: var(--text-3); font-size: 0.85rem;
}
.gs-group-label {
font-size: 0.65rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: #8898AA; padding: 10px 12px 4px;
letter-spacing: 0.06em; color: var(--text-3); padding: 10px 12px 4px;
}
.gs-item {
display: flex; align-items: center; gap: 12px;
@@ -786,7 +803,7 @@ body {
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gs-item-sub {
font-size: 0.7rem; color: #8898AA; margin-top: 1px;
font-size: 0.7rem; color: var(--text-3); margin-top: 1px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gs-item-arrow { color: #ccc; flex-shrink: 0; }
+61 -61
View File
@@ -34,10 +34,10 @@
color: #0F172A; letter-spacing: -0.02em;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dh-sub { font-size: 0.78rem; color: #8898AA; font-weight: 500; margin-top: 2px; }
.dh-sub { font-size: 0.78rem; color: var(--text-3); font-weight: 500; margin-top: 2px; }
.dh-stats { display: flex; gap: 14px; flex-shrink: 0; }
.stat-ring { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.stat-ring .sr-label { font-size: 0.62rem; color: #8898AA; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.stat-ring .sr-label { font-size: 0.62rem; color: var(--text-3); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
/* ── ZONE 2: Action Banner + Cards ── */
.action-zone { margin-bottom: 22px; }
@@ -79,7 +79,7 @@
.ac-emoji { font-size: 1.4rem; flex-shrink: 0; }
.ac-body { flex: 1; min-width: 0; }
.ac-title { font-size: 0.84rem; font-weight: 700; color: #0F172A; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ac-sub { font-size: 0.72rem; color: #8898AA; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ac-sub { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ac-badge { font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 800; color: var(--violet); flex-shrink: 0; }
/* ── ZONE 3: Three-Column Grid ── */
@@ -117,7 +117,7 @@
}
.w-title {
font-family: 'Unbounded', sans-serif; font-size: 0.72rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em;
display: flex; align-items: center; gap: 8px;
}
.w-title::before {
@@ -142,14 +142,14 @@
.grade-pct { font-family: 'Unbounded', sans-serif; font-size: 0.88rem; font-weight: 900; min-width: 42px; text-align: center; }
.grade-body { flex: 1; min-width: 0; }
.grade-subj { font-size: 0.82rem; font-weight: 700; color: #0F172A; }
.grade-date { font-size: 0.7rem; color: #8898AA; }
.grade-date { font-size: 0.7rem; color: var(--text-3); }
/* ── activity widget tabs ── */
.act-tabs { display: flex; gap: 4px; }
.act-tab {
padding: 3px 12px; border-radius: 99px; border: none;
font-family: 'Manrope', sans-serif; font-size: 0.68rem; font-weight: 700;
color: #8898AA; background: transparent; cursor: pointer; transition: all 0.15s;
color: var(--text-3); background: transparent; cursor: pointer; transition: all 0.15s;
}
.act-tab:hover { color: var(--violet); }
.act-tab.active { background: #0F172A; color: #fff; }
@@ -157,7 +157,7 @@
.act-scale-btn {
padding: 2px 8px; border-radius: 6px; border: 1px solid rgba(15,23,42,0.08);
font-family: 'Manrope', sans-serif; font-size: 0.62rem; font-weight: 700;
color: #8898AA; background: transparent; cursor: pointer; transition: all 0.12s;
color: var(--text-3); background: transparent; cursor: pointer; transition: all 0.12s;
}
.act-scale-btn:hover { border-color: rgba(155,93,229,0.3); color: var(--violet); }
.act-scale-btn.active { background: rgba(155,93,229,0.08); border-color: rgba(155,93,229,0.25); color: var(--violet); }
@@ -166,10 +166,10 @@
/* ── mini heatmap (redesigned) ── */
.hm-months { display: flex; margin-bottom: 2px; padding-left: 22px; }
.hm-month-label { font-size: 0.56rem; font-weight: 700; color: #8898AA; text-transform: uppercase; letter-spacing: 0.02em; overflow: hidden; white-space: nowrap; }
.hm-month-label { font-size: 0.56rem; font-weight: 700; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.02em; overflow: hidden; white-space: nowrap; }
.hm-body { display: flex; gap: 0; }
.hm-weekdays { display: flex; flex-direction: column; gap: 2px; width: 22px; flex-shrink: 0; padding-top: 1px; }
.hm-wd { font-size: 0.5rem; font-weight: 700; color: #8898AA; height: 14px; line-height: 14px; }
.hm-wd { font-size: 0.5rem; font-weight: 700; color: var(--text-3); height: 14px; line-height: 14px; }
.mini-heatmap { display: grid; grid-template-rows: repeat(7, 14px); grid-auto-flow: column; grid-auto-columns: 14px; gap: 2px; }
.mhm-cell {
width: 14px; height: 14px; border-radius: 50%; background: rgba(15,23,42,0.05);
@@ -191,7 +191,7 @@
.hm-footer {
display: flex; align-items: center; gap: 14px; margin-top: 8px; padding-top: 8px;
border-top: 1px solid rgba(15,23,42,0.05);
font-size: 0.68rem; color: #8898AA; font-weight: 600; flex-wrap: wrap;
font-size: 0.68rem; color: var(--text-3); font-weight: 600; flex-wrap: wrap;
}
.hm-footer strong { color: #0F172A; font-weight: 800; }
.hm-legend { display: flex; align-items: center; gap: 4px; margin-left: auto; }
@@ -217,8 +217,8 @@
.hm-day-popup .hdp-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.hm-day-popup .hdp-subj { flex: 1; font-weight: 600; color: #0F172A; }
.hm-day-popup .hdp-score { font-family: 'Unbounded', sans-serif; font-size: 0.72rem; font-weight: 900; }
.hm-day-popup .hdp-mode { font-size: 0.66rem; color: #8898AA; }
.hm-day-popup .hdp-empty { font-size: 0.75rem; color: #8898AA; padding: 4px 0; }
.hm-day-popup .hdp-mode { font-size: 0.66rem; color: var(--text-3); }
.hm-day-popup .hdp-empty { font-size: 0.75rem; color: var(--text-3); padding: 4px 0; }
/* Dark mode */
.app-layout.dark .act-tab.active { background: #E8ECF2; color: #0F172A; }
@@ -242,7 +242,7 @@
.cont-emoji { font-size: 1.5rem; flex-shrink: 0; }
.cont-info { flex: 1; min-width: 0; }
.cont-title { font-size: 0.85rem; font-weight: 700; color: #0F172A; }
.cont-sub { font-size: 0.72rem; color: #8898AA; margin-top: 2px; }
.cont-sub { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; }
.cont-pct { font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 800; color: var(--violet); }
/* ── subjects progress bars ── */
@@ -274,8 +274,8 @@
.smc-icon svg, .smc-icon i { width: 20px; height: 20px; stroke: #fff; stroke-width: 1.8; }
.smc-body { flex: 1; min-width: 0; }
.smc-name { font-size: 0.86rem; font-weight: 700; color: #0F172A; }
.smc-meta { font-size: 0.72rem; color: #8898AA; margin-top: 2px; }
.smc-arrow { width: 18px; height: 18px; color: #8898AA; flex-shrink: 0; }
.smc-meta { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; }
.smc-arrow { width: 18px; height: 18px; color: var(--text-3); flex-shrink: 0; }
/* ── animations ── */
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@@ -355,7 +355,7 @@
.streak-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.sc-day {
aspect-ratio: 1; border-radius: 6px; display: flex; align-items: center; justify-content: center;
font-size: 0.62rem; font-weight: 700; color: #8898AA; background: rgba(15,23,42,0.03);
font-size: 0.62rem; font-weight: 700; color: var(--text-3); background: rgba(15,23,42,0.03);
transition: transform 0.12s;
}
.sc-day.today { border: 1.5px solid var(--violet); color: var(--violet); font-weight: 800; }
@@ -372,7 +372,7 @@
.sc-month { font-family: 'Unbounded', sans-serif; font-size: 0.7rem; font-weight: 800; color: #0F172A; }
.sc-streak-badge { font-family: 'Unbounded', sans-serif; font-size: 0.7rem; font-weight: 800; color: #F59E0B; display: flex; align-items: center; gap: 4px; }
.sc-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; margin-bottom: 3px; }
.sc-wd { font-size: 0.56rem; color: #8898AA; font-weight: 700; text-align: center; text-transform: uppercase; }
.sc-wd { font-size: 0.56rem; color: var(--text-3); font-weight: 700; text-align: center; text-transform: uppercase; }
/* C4: Keyboard shortcuts hint */
.kb-hint {
@@ -400,7 +400,7 @@
.qs-subj-icon svg { width: 14px; height: 14px; stroke: #fff; stroke-width: 2; }
.qs-options { display: flex; flex-direction: column; gap: 12px; }
.qs-row { display: flex; align-items: center; gap: 12px; }
.qs-label { font-size: 0.78rem; font-weight: 700; color: #8898AA; min-width: 80px; }
.qs-label { font-size: 0.78rem; font-weight: 700; color: var(--text-3); min-width: 80px; }
.qs-select, .qs-input {
flex: 1; padding: 8px 12px; border: 1.5px solid rgba(15,23,42,0.1);
border-radius: 8px; font-family: 'Manrope', sans-serif; font-size: 0.82rem;
@@ -441,7 +441,7 @@
display: flex; align-items: center; gap: 8px;
padding: 6px 14px; border-radius: 10px;
background: rgba(15,23,42,0.03); border: 1px solid rgba(15,23,42,0.06);
font-family: 'Manrope', sans-serif; font-size: 0.75rem; font-weight: 600; color: #8898AA;
font-family: 'Manrope', sans-serif; font-size: 0.75rem; font-weight: 600; color: var(--text-3);
}
.adm-stat-val {
font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 900;
@@ -466,7 +466,7 @@
}
.adm-sess-row:last-child { border-bottom: none; }
.adm-sess-name { flex: 1; font-weight: 600; color: #0F172A; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.adm-sess-subj { color: #8898AA; font-weight: 600; min-width: 60px; }
.adm-sess-subj { color: var(--text-3); font-weight: 600; min-width: 60px; }
.adm-sess-pct { font-family: 'Unbounded', sans-serif; font-weight: 900; font-size: 0.74rem; min-width: 36px; text-align: right; }
/* Dark mode for admin */
@@ -484,7 +484,7 @@
.cs-avatar { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.1rem; flex-shrink: 0; }
.cs-body { flex: 1; min-width: 0; }
.cs-name { font-size: 0.84rem; font-weight: 700; color: #0F172A; }
.cs-stats { font-size: 0.72rem; color: #8898AA; margin-top: 2px; display: flex; gap: 10px; }
.cs-stats { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; display: flex; gap: 10px; }
.cs-stats span { display: flex; align-items: center; gap: 3px; }
.cs-bar { width: 60px; height: 5px; border-radius: 99px; background: rgba(15,23,42,0.07); overflow: hidden; flex-shrink: 0; }
.cs-fill { height: 100%; border-radius: 99px; background: var(--violet); }
@@ -496,7 +496,7 @@
}
.section-title {
font-family: 'Unbounded', sans-serif; font-size: 0.78rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.09em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.09em;
display: flex; align-items: center; gap: 8px;
}
.section-title::before {
@@ -518,7 +518,7 @@
}
.assign-chip:hover { border-color: rgba(155,93,229,0.3); color: var(--violet); }
.assign-chip.active { background: #0F172A; color: #fff; border-color: #0F172A; }
.assign-stats { font-size: 0.75rem; color: #8898AA; font-weight: 600; display: flex; gap: 14px; flex-wrap: wrap; }
.assign-stats { font-size: 0.75rem; color: var(--text-3); font-weight: 600; display: flex; gap: 14px; flex-wrap: wrap; }
.assign-stats .s-active { color: var(--violet); }
.assign-stats .s-overdue { color: var(--pink); }
.assign-stats .s-done { color: #059652; }
@@ -527,7 +527,7 @@
.subj-filter-row { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 14px; }
.sf-chip {
padding: 3px 11px 3px 8px; border-radius: 99px; border: 1.5px solid rgba(15,23,42,0.09);
background: #fff; color: #8898AA;
background: #fff; color: var(--text-3);
font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 700;
cursor: pointer; transition: all 0.15s; display: inline-flex; align-items: center; gap: 5px;
white-space: nowrap;
@@ -540,7 +540,7 @@
.assign-group-hdr {
display: flex; align-items: center; gap: 10px;
font-family: 'Unbounded', sans-serif; font-size: 0.72rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em;
margin: 8px 0 4px; cursor: pointer; user-select: none;
}
.assign-group-hdr::before { content: ''; display: inline-block; width: 3px; height: 13px; border-radius: 99px; flex-shrink: 0; }
@@ -569,7 +569,7 @@
.ae-pills { display: flex; gap: 6px; flex-wrap: wrap; flex: 1; }
.ae-pill { padding: 3px 10px; border-radius: 99px; background: rgba(15,23,42,0.06); color: #6B7A8E; font-size: 0.72rem; font-weight: 600; }
.ae-dl { flex: 1; }
.ae-dl-label { font-size: 0.70rem; color: #8898AA; margin-bottom: 5px; display: flex; justify-content: space-between; }
.ae-dl-label { font-size: 0.70rem; color: var(--text-3); margin-bottom: 5px; display: flex; justify-content: space-between; }
.ae-dl-bar { height: 4px; border-radius: 99px; background: rgba(15,23,42,0.08); overflow: hidden; }
.ae-dl-fill { height: 100%; border-radius: 99px; transition: width 0.4s; }
.ae-btn {
@@ -619,7 +619,7 @@
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2;
}
.ar-meta {
font-size: 0.71rem; color: #8898AA; margin-top: 3px;
font-size: 0.71rem; color: var(--text-3); margin-top: 3px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ar-meta .ar-tag-urgent { color: #E83A1E; font-weight: 700; }
@@ -635,7 +635,7 @@
.ar-progress { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.ar-prog-bar { width: 72px; height: 4px; border-radius: 99px; background: rgba(15,23,42,0.08); overflow: hidden; }
.ar-prog-fill { height: 100%; border-radius: 99px; }
.ar-prog-text { font-size: 0.71rem; color: #8898AA; white-space: nowrap; min-width: 36px; }
.ar-prog-text { font-size: 0.71rem; color: var(--text-3); white-space: nowrap; min-width: 36px; }
.ar-btn {
padding: 6px 16px; border: none; border-radius: 99px;
@@ -655,7 +655,7 @@
.ar-btn-result:hover { background: rgba(6,214,100,0.14); }
.ar-btn-ghost {
padding: 6px 16px; border: 1.5px solid rgba(15,23,42,0.12); border-radius: 99px;
background: transparent; color: #8898AA;
background: transparent; color: var(--text-3);
font-family: 'Manrope', sans-serif; font-size: 0.76rem; font-weight: 600;
text-decoration: none; display: inline-flex; align-items: center;
white-space: nowrap; transition: all 0.15s;
@@ -684,13 +684,13 @@
.hist-pct.lo { color: var(--pink); }
.hist-info { flex: 1; }
.hist-subj { font-size: 0.88rem; font-weight: 700; margin-bottom: 2px; color: #0F172A; }
.hist-meta { font-size: 0.75rem; color: #8898AA; }
.hist-meta { font-size: 0.75rem; color: var(--text-3); }
.hist-score { font-size: 0.8rem; color: #3D4F6B; font-weight: 600; white-space: nowrap; }
/* ── progress tab ── */
.chart-section-title {
font-family: 'Unbounded', sans-serif; font-size: 0.76rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.09em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.09em;
margin: 36px 0 16px;
display: flex; align-items: center; gap: 8px;
}
@@ -716,7 +716,7 @@
}
.subj-chart-card:hover { border-color: rgba(15,23,42,0.14); transform: translateY(-3px); box-shadow: 0 10px 32px rgba(15,23,42,0.1); }
.subj-chart-name { font-family: 'Unbounded', sans-serif; font-size: 0.76rem; font-weight: 800; color: #0F172A; }
.subj-chart-sessions { font-size: 0.72rem; color: #8898AA; }
.subj-chart-sessions { font-size: 0.72rem; color: var(--text-3); }
.canvas-wrap { position: relative; width: 130px; height: 130px; margin: 4px 0; }
/* ── weak topics ── */
@@ -731,7 +731,7 @@
.weak-item:hover { border-color: rgba(15,23,42,0.12); transform: translateX(4px); box-shadow: 0 6px 24px rgba(15,23,42,0.08); }
.weak-bar-wrap { flex: 1; }
.weak-name { font-size: 0.88rem; font-weight: 700; margin-bottom: 2px; color: #0F172A; }
.weak-meta { font-size: 0.75rem; color: #8898AA; }
.weak-meta { font-size: 0.75rem; color: var(--text-3); }
.weak-bar { height: 7px; background: rgba(15,23,42,0.07); border-radius: 99px; overflow: hidden; margin-top: 8px; }
.weak-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, #FF9F1C, #F15BB5); transition: width 0.8s cubic-bezier(0.34,1.2,0.64,1); }
.weak-pct { font-family: 'Unbounded', sans-serif; font-size: 1rem; font-weight: 900; color: #E0335E; min-width: 48px; text-align: right; }
@@ -741,7 +741,7 @@
display: flex; align-items: center; gap: 10px;
padding-top: 8px; border-top: 1px solid rgba(15,23,42,0.06); flex-wrap: wrap;
}
.ae-submit-label { font-size: 0.72rem; color: #8898AA; font-weight: 600; flex-shrink: 0; }
.ae-submit-label { font-size: 0.72rem; color: var(--text-3); font-weight: 600; flex-shrink: 0; }
.ae-submit-status {
display: inline-flex; align-items: center; gap: 5px;
padding: 3px 10px; border-radius: 99px; font-size: 0.72rem; font-weight: 700;
@@ -775,7 +775,7 @@
.ms-row:hover { transform: translateX(3px); box-shadow: 0 4px 16px rgba(15,23,42,0.07); }
.ms-title { flex: 1; font-size: 0.82rem; font-weight: 600; color: #0F172A;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-file { font-size: 0.7rem; color: #8898AA; flex-shrink: 0; max-width: 120px;
.ms-file { font-size: 0.7rem; color: var(--text-3); flex-shrink: 0; max-width: 120px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-grade { font-weight: 800; font-size: 0.78rem; flex-shrink: 0; }
.ms-more { display: inline-block; margin-top: 8px; font-size: 0.78rem; font-weight: 700; color: var(--violet); text-decoration: none; }
@@ -807,7 +807,7 @@
/* ── spinner / empty ── */
.spinner { width: 36px; height: 36px; border: 3px solid rgba(15,23,42,0.1); border-top-color: var(--violet); border-radius: 50%; animation: spin 0.75s linear infinite; margin: 48px auto; display: block; }
@keyframes spin { to { transform: rotate(360deg); } }
.empty { text-align: center; padding: 36px; color: #8898AA; font-size: 0.88rem; }
.empty { text-align: center; padding: 36px; color: var(--text-3); font-size: 0.88rem; }
/* ── empty CTA ── */
.empty-cta {
@@ -850,7 +850,7 @@
.form-input { width: 100%; padding: 12px 16px; border: 1.5px solid rgba(15,23,42,0.15); border-radius: 12px; font-family: 'Manrope', sans-serif; font-size: 0.95rem; color: var(--text); background: #f8f9fc; transition: border-color 0.2s; box-sizing: border-box; }
.form-input:focus { outline: none; border-color: var(--violet); background: #fff; }
.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-join { padding: 10px 28px; border: none; border-radius: 999px; background: #0F172A; color: #fff; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 700; cursor: pointer; box-shadow: 0 2px 10px rgba(15,23,42,0.2); transition: all 0.18s; }
.btn-join:hover { background: #1E2D4A; box-shadow: 0 6px 20px rgba(15,23,42,0.25); }
@@ -860,7 +860,7 @@
/* ── history timeline ── */
.hist-date-sep {
font-family: 'Unbounded', sans-serif; font-size: 0.68rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em;
margin: 22px 0 10px; display: flex; align-items: center; gap: 12px;
}
.hist-date-sep:first-child { margin-top: 0; }
@@ -876,7 +876,7 @@
border-radius: 18px; padding: 18px 20px;
}
.stats-chart-title {
font-size: 0.72rem; font-weight: 700; color: #8898AA;
font-size: 0.72rem; font-weight: 700; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 12px;
}
.stats-bar-chart { display: flex; align-items: flex-end; gap: 4px; height: 100px; }
@@ -909,7 +909,7 @@
font-family: 'Unbounded', sans-serif; font-size: 1.1rem; font-weight: 800;
color: #0F172A; line-height: 1;
}
.stats-chip-lbl { font-size: 0.65rem; color: #8898AA; margin-top: 3px; font-weight: 600; }
.stats-chip-lbl { font-size: 0.65rem; color: var(--text-3); margin-top: 3px; font-weight: 600; }
/* ── activity heatmap ── */
.heatmap-section {
@@ -924,7 +924,7 @@
.hm-cell[data-n="3"] { background: rgba(155,93,229,0.65); }
.hm-cell[data-n="4"] { background: rgba(155,93,229,0.88); }
.hm-cell:hover { transform: scale(1.5); z-index: 5; position: relative; }
.heatmap-legend { display: flex; align-items: center; gap: 5px; margin-top: 14px; font-size: 0.72rem; color: #8898AA; }
.heatmap-legend { display: flex; align-items: center; gap: 5px; margin-top: 14px; font-size: 0.72rem; color: var(--text-3); }
.hm-leg { width: 14px; height: 14px; border-radius: 3px; }
/* ── subject card grid (legacy, kept for compatibility) ── */
@@ -945,12 +945,12 @@
.tc-emoji { font-size: 1.6rem; line-height: 1; }
.tc-info { flex: 1; }
.tc-title { font-size: 0.88rem; font-weight: 700; color: #0F172A; line-height: 1.35; }
.tc-subj { font-size: 0.7rem; font-weight: 700; color: #8898AA; margin-top: 2px; text-transform: uppercase; letter-spacing: 0.06em; }
.tc-subj { font-size: 0.7rem; font-weight: 700; color: var(--text-3); margin-top: 2px; text-transform: uppercase; letter-spacing: 0.06em; }
.tc-progress { display: flex; align-items: center; gap: 8px; }
.tc-bar { flex: 1; height: 5px; border-radius: 99px; background: rgba(15,23,42,0.07); }
.tc-fill { height: 100%; border-radius: 99px; background: var(--violet); }
.tc-pct { font-size: 0.72rem; font-weight: 700; color: var(--violet); flex-shrink: 0; }
.tc-meta { font-size: 0.74rem; color: #8898AA; }
.tc-meta { font-size: 0.74rem; color: var(--text-3); }
/* ── Leaderboard widget ── */
.lb-widget {
@@ -964,7 +964,7 @@
.lb-tab {
padding: 5px 12px; border-radius: 99px; border: none;
font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 700;
background: transparent; color: #8898AA; cursor: pointer; transition: all 0.15s;
background: transparent; color: var(--text-3); cursor: pointer; transition: all 0.15s;
}
.lb-tab.active { background: linear-gradient(135deg, rgba(155,93,229,0.1), rgba(6,214,224,0.08)); color: #9B5DE5; }
.lb-list { display: flex; flex-direction: column; gap: 4px; }
@@ -978,7 +978,7 @@
width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
font-family: 'Unbounded', sans-serif; font-size: 0.68rem; font-weight: 900;
background: rgba(15,23,42,0.06); color: #8898AA;
background: rgba(15,23,42,0.06); color: var(--text-3);
}
.lb-pos.gold { background: linear-gradient(135deg, #FFD700, #FFA500); color: #fff; }
.lb-pos.silver { background: linear-gradient(135deg, #C0C0C0, #A0A0A0); color: #fff; }
@@ -991,9 +991,9 @@
}
.lb-info { flex: 1; min-width: 0; }
.lb-name { font-size: 0.82rem; font-weight: 700; color: #0F172A; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-rank { font-size: 0.64rem; color: #8898AA; font-weight: 600; }
.lb-rank { font-size: 0.64rem; color: var(--text-3); font-weight: 600; }
.lb-xp { font-family: 'Unbounded', sans-serif; font-size: 0.78rem; font-weight: 800; color: #9B5DE5; flex-shrink: 0; }
.lb-empty { text-align: center; padding: 20px; color: #8898AA; font-size: 0.82rem; }
.lb-empty { text-align: center; padding: 20px; color: var(--text-3); font-size: 0.82rem; }
.lb-class-sel {
padding: 5px 10px; border-radius: 10px; border: 1.5px solid rgba(15,23,42,0.1);
background: #fff; font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 600;
@@ -1014,7 +1014,7 @@
.ch-icon { font-size: 1.3rem; flex-shrink: 0; }
.ch-body { flex: 1; min-width: 0; }
.ch-title { font-size: 0.84rem; font-weight: 700; color: #0F172A; }
.ch-desc { font-size: 0.72rem; color: #8898AA; margin-top: 2px; }
.ch-desc { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; }
.ch-prog { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.ch-bar { flex: 1; height: 5px; border-radius: 99px; background: rgba(15,23,42,0.07); overflow: hidden; }
.ch-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, #9B5DE5, #06D6E0); transition: width 0.4s; }
@@ -1047,7 +1047,7 @@
.gam-main { flex: 1; min-width: 0; }
.gam-top { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.gam-rank { font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 800; color: #0F172A; }
.gam-xp-text { font-size: 0.72rem; color: #8898AA; font-weight: 600; }
.gam-xp-text { font-size: 0.72rem; color: var(--text-3); font-weight: 600; }
.gam-progress { height: 8px; border-radius: 99px; background: rgba(15,23,42,0.07); overflow: hidden; }
.gam-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, #9B5DE5, #06D6E0); transition: width 0.6s ease; }
.gam-chips { display: flex; gap: 10px; flex-shrink: 0; }
@@ -1059,7 +1059,7 @@
}
.gam-chip-icon { font-size: 1.1rem; line-height: 1; }
.gam-chip-val { font-family: 'Unbounded', sans-serif; font-size: 0.78rem; font-weight: 800; color: #0F172A; }
.gam-chip-lbl { font-size: 0.58rem; color: #8898AA; font-weight: 600; text-transform: uppercase; }
.gam-chip-lbl { font-size: 0.58rem; color: var(--text-3); font-weight: 600; text-transform: uppercase; }
.gam-goal-ring { position: relative; width: 40px; height: 40px; }
.gam-goal-ring svg { width: 40px; height: 40px; }
@@ -2640,11 +2640,11 @@
y: {
min: 0, max: 100,
grid: { color: 'rgba(15,23,42,0.05)' },
ticks: { callback: v => v + '%', font: { family: 'Manrope', size: 11 }, color: '#8898AA' }
ticks: { callback: v => v + '%', font: { family: 'Manrope', size: 11 }, color: '#56687A' }
},
x: {
grid: { display: false },
ticks: { font: { family: 'Manrope', size: 11 }, color: '#8898AA' }
ticks: { font: { family: 'Manrope', size: 11 }, color: '#56687A' }
}
}
}
@@ -2822,7 +2822,7 @@
<span class="ae-submit-label">Работа:</span>
<span class="ae-submit-status ${st.cls}">${lci(st.icon,'width:14px;height:14px')} ${st.label}</span>
${gradeHtml}
<span style="font-size:0.72rem;color:#8898AA;flex:1">${esc(sub.original_name)}</span>
<span style="font-size:0.72rem;color:var(--text-3);flex:1">${esc(sub.original_name)}</span>
${noteHtml}
${resubBtn}
${delBtn}
@@ -2843,7 +2843,7 @@
if (!wrap || !list || isTeacher) return;
if (!_allSubmissions.length) {
list.innerHTML = `<div style="padding:18px 0;text-align:center;color:#8898AA;font-size:0.82rem">
list.innerHTML = `<div style="padding:18px 0;text-align:center;color:var(--text-3);font-size:0.82rem">
${lci('file-plus','width:24px;height:24px;opacity:0.4;display:block;margin:0 auto 8px')}
Сданных работ пока нет.<br>
<span style="font-size:0.74rem">Когда учитель назначит задание с прикреплением файла, вы сможете сдать его прямо здесь.</span>
@@ -3354,7 +3354,7 @@
<span class="sc-month">${monthNames[month]}</span>
<span style="display:flex;gap:10px;align-items:center">
<span class="sc-streak-badge">${lci('flame', 16)} ${streak}</span>
${bestStreak > streak ? `<span style="font-size:0.6rem;color:#8898AA;font-weight:600">рекорд ${bestStreak}</span>` : ''}
${bestStreak > streak ? `<span style="font-size:0.6rem;color:var(--text-3);font-weight:600">рекорд ${bestStreak}</span>` : ''}
</span>
</div>`;
html += `<div class="sc-weekdays">${wdNames.map(d => `<span class="sc-wd">${d}</span>`).join('')}</div>`;
@@ -3454,7 +3454,7 @@
try {
const classes = await LS.api('/api/classes');
if (!classes || !classes.length) {
body.innerHTML = '<div style="font-size:0.8rem;color:#8898AA;padding:8px 0">Нет классов</div>';
body.innerHTML = '<div style="font-size:0.8rem;color:var(--text-3);padding:8px 0">Нет классов</div>';
return;
}
const colors = ['#9B5DE5','#06D6A0','#F59E0B','#06B6D4','#E0335E'];
@@ -3475,7 +3475,7 @@
}).join('');
reIcons();
} catch {
body.innerHTML = '<div style="font-size:0.8rem;color:#8898AA;padding:8px 0">Ошибка загрузки</div>';
body.innerHTML = '<div style="font-size:0.8rem;color:var(--text-3);padding:8px 0">Ошибка загрузки</div>';
}
}
@@ -3488,7 +3488,7 @@
const data = await LS.api('/api/admin/sessions?limit=8');
const rows = Array.isArray(data) ? data : (data.rows || []);
if (!rows.length) {
body.innerHTML = '<div style="font-size:0.8rem;color:#8898AA;padding:8px 0">Нет сессий</div>';
body.innerHTML = '<div style="font-size:0.8rem;color:var(--text-3);padding:8px 0">Нет сессий</div>';
return;
}
body.innerHTML = rows.slice(0, 8).map(s => {
@@ -3502,7 +3502,7 @@
</div>`;
}).join('');
} catch {
body.innerHTML = '<div style="font-size:0.8rem;color:#8898AA;padding:8px 0">Ошибка</div>';
body.innerHTML = '<div style="font-size:0.8rem;color:var(--text-3);padding:8px 0">Ошибка</div>';
}
}
@@ -3514,7 +3514,7 @@
try {
const list = await LS.teacherAssignments();
if (!list.length) {
listEl.innerHTML = '<div style="font-size:0.8rem;color:#8898AA;padding:8px 0">Заданий пока нет</div>';
listEl.innerHTML = '<div style="font-size:0.8rem;color:var(--text-3);padding:8px 0">Заданий пока нет</div>';
return;
}
const sorted = [...list].sort((a, b) => urgencyScore(a) - urgencyScore(b));
@@ -3524,7 +3524,7 @@
}
reIcons();
} catch {
listEl.innerHTML = '<div style="font-size:0.8rem;color:#8898AA;padding:8px 0">Ошибка загрузки</div>';
listEl.innerHTML = '<div style="font-size:0.8rem;color:var(--text-3);padding:8px 0">Ошибка загрузки</div>';
}
}
+5 -5
View File
@@ -62,7 +62,7 @@
.gb-chip-val {
font-family: 'Unbounded', sans-serif; font-size: 1.3rem; font-weight: 800;
}
.gb-chip-label { font-size: 0.72rem; color: #8898AA; font-weight: 600; margin-top: 4px; }
.gb-chip-label { font-size: 0.72rem; color: var(--text-3); font-weight: 600; margin-top: 4px; }
/* ── table container ── */
.gb-container { max-width: 1200px; margin: 0 auto; padding: 24px 28px 80px; }
@@ -82,7 +82,7 @@
position: sticky; top: 0; z-index: 10;
background: #f8f9fc; border-bottom: 1.5px solid rgba(15,23,42,0.08);
padding: 12px 14px; text-align: left;
font-size: 0.7rem; font-weight: 700; color: #8898AA;
font-size: 0.7rem; font-weight: 700; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.04em;
white-space: nowrap;
}
@@ -134,7 +134,7 @@
flex-shrink: 0;
}
.gb-student-name { font-weight: 700; font-size: 0.82rem; }
.gb-student-email { font-size: 0.68rem; color: #8898AA; font-weight: 500; }
.gb-student-email { font-size: 0.68rem; color: var(--text-3); font-weight: 500; }
/* ── footer row ── */
.gb-table tfoot td {
@@ -155,7 +155,7 @@
/* ── empty state ── */
.gb-empty {
text-align: center; padding: 60px 20px; color: #8898AA; font-size: 0.88rem;
text-align: center; padding: 60px 20px; color: var(--text-3); font-size: 0.88rem;
}
.gb-empty-icon { font-size: 3rem; margin-bottom: 12px; opacity: 0.3; }
@@ -448,7 +448,7 @@
html += `<td class="gb-td-avg">${gradeCell(classAvg)}</td></tr></tfoot>`;
html += `</table></div></div>`;
} else {
html += `<div style="background:#fff;border:1.5px solid rgba(15,23,42,0.07);border-radius:16px;padding:40px;text-align:center;color:#8898AA;margin-bottom:20px">
html += `<div style="background:#fff;border:1.5px solid rgba(15,23,42,0.07);border-radius:16px;padding:40px;text-align:center;color:var(--text-3);margin-bottom:20px">
Нет заданий в этом классе. <a href="/classes" style="color:var(--violet)">Создать задание <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg></a>
</div>`;
}
+8 -8
View File
@@ -15,7 +15,7 @@
font-family: 'Unbounded', sans-serif; font-size: 1.1rem; font-weight: 800;
color: #0F172A; margin-bottom: 6px;
}
.page-sub { font-size: 0.82rem; color: #8898AA; margin-bottom: 24px; }
.page-sub { font-size: 0.82rem; color: var(--text-3); margin-bottom: 24px; }
/* ── top bar with class selector ── */
.hw-top { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; flex-wrap: wrap; }
@@ -28,7 +28,7 @@
.hw-sf-btn {
padding: 6px 14px; border-radius: 999px; border: 1.5px solid rgba(15,23,42,0.1);
background: transparent; font-family: 'Manrope', sans-serif; font-size: 0.75rem;
font-weight: 600; color: #8898AA; cursor: pointer; transition: all .15s;
font-weight: 600; color: var(--text-3); cursor: pointer; transition: all .15s;
}
.hw-sf-btn:hover { border-color: var(--violet); color: var(--violet); }
.hw-sf-btn.active { background: rgba(155,93,229,0.08); border-color: var(--violet); color: var(--violet); }
@@ -48,7 +48,7 @@
}
.hw-card-body { flex: 1; min-width: 0; }
.hw-card-title { font-size: 0.88rem; font-weight: 700; color: #0F172A; margin-bottom: 4px; }
.hw-card-meta { font-size: 0.75rem; color: #8898AA; display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.hw-card-meta { font-size: 0.75rem; color: var(--text-3); display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.hw-card-note { font-size: 0.78rem; color: #3D4F6B; background: rgba(15,23,42,0.03); padding: 8px 12px; border-radius: 10px; margin-top: 8px; line-height: 1.5; }
.hw-card-note strong { color: #0F172A; }
.hw-card-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
@@ -99,9 +99,9 @@
.hw-upload-area:hover, .hw-upload-area.dragover {
border-color: var(--violet); background: rgba(155,93,229,0.02);
}
.hw-upload-icon { color: #8898AA; margin-bottom: 8px; }
.hw-upload-icon { color: var(--text-3); margin-bottom: 8px; }
.hw-upload-text { font-size: 0.85rem; font-weight: 600; color: #3D4F6B; margin-bottom: 4px; }
.hw-upload-hint { font-size: 0.72rem; color: #8898AA; }
.hw-upload-hint { font-size: 0.72rem; color: var(--text-3); }
/* review modal (inline) */
.hw-review-panel {
@@ -118,7 +118,7 @@
.hw-review-panel textarea { min-height: 60px; resize: vertical; }
/* empty state */
.hw-empty { text-align: center; padding: 60px 20px; color: #8898AA; }
.hw-empty { text-align: center; padding: 60px 20px; color: var(--text-3); }
.hw-empty-icon { font-size: 3rem; margin-bottom: 12px; opacity: 0.3; }
.hw-empty-text { font-size: 0.88rem; font-weight: 600; }
@@ -164,13 +164,13 @@
accept=".pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.png,.jpg,.jpeg,.gif,.webp,.txt" />
<div style="display:flex;gap:10px;margin-bottom:24px;flex-wrap:wrap;align-items:flex-end">
<div style="flex:1;min-width:200px">
<label style="font-size:.72rem;font-weight:700;color:#8898AA;display:block;margin-bottom:4px">Задание (необязательно)</label>
<label style="font-size:.72rem;font-weight:700;color:var(--text-3);display:block;margin-bottom:4px">Задание (необязательно)</label>
<select id="hw-assignment-sel" class="hw-class-sel" style="width:100%">
<option value="">— Без привязки к заданию —</option>
</select>
</div>
<div style="flex:1;min-width:200px">
<label style="font-size:.72rem;font-weight:700;color:#8898AA;display:block;margin-bottom:4px">Комментарий</label>
<label style="font-size:.72rem;font-weight:700;color:var(--text-3);display:block;margin-bottom:4px">Комментарий</label>
<input type="text" id="hw-message" class="hw-class-sel" style="width:100%" placeholder="Опишите работу…" />
</div>
<button class="hw-btn hw-btn-primary" id="hw-submit-btn" onclick="submitHomework()" disabled>
+5 -5
View File
@@ -204,7 +204,7 @@
.km-qtest-close {
width: 32px; height: 32px; border-radius: 9px; border: 1.5px solid #E8EBF0;
background: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center;
color: #8898AA; transition: all .15s;
color: var(--text-3); transition: all .15s;
}
.km-qtest-close:hover { border-color: #ef4444; color: #ef4444; }
.km-qtest-close svg { width: 16px; height: 16px; stroke: currentColor; stroke-width: 2; fill: none; }
@@ -214,7 +214,7 @@
}
.km-qtest-pbar { flex: 1; height: 4px; border-radius: 99px; background: #E8EBF0; overflow: hidden; }
.km-qtest-pfill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, #9B5DE5, #06D6E0); transition: width .3s; }
.km-qtest-ptext { font-size: 0.72rem; font-weight: 700; color: #8898AA; min-width: 36px; text-align: right; }
.km-qtest-ptext { font-size: 0.72rem; font-weight: 700; color: var(--text-3); min-width: 36px; text-align: right; }
.km-qtest-q {
font-size: 0.95rem; font-weight: 600; color: #0F172A; line-height: 1.6;
margin-bottom: 16px;
@@ -249,7 +249,7 @@
font-family: 'Unbounded', sans-serif; font-size: 2rem; font-weight: 800;
margin-bottom: 6px;
}
.km-qtest-result-label { font-size: 0.88rem; color: #8898AA; margin-bottom: 20px; }
.km-qtest-result-label { font-size: 0.88rem; color: var(--text-3); margin-bottom: 20px; }
.km-qtest-result-btn {
padding: 12px 28px; border-radius: 99px; border: none;
background: linear-gradient(135deg, #9B5DE5, #7C3AED);
@@ -1681,7 +1681,7 @@ async function startQuickTest(node) {
const topicId = node.id.replace('topic_', '');
document.getElementById('qtest-title').textContent = 'Тест: ' + node.label;
document.getElementById('qtest-body').innerHTML =
'<div style="text-align:center;padding:40px;color:#8898AA"><div class="km-spinner" style="margin:0 auto 12px;width:24px;height:24px;border-width:3px"></div>Загрузка вопросов...</div>';
'<div style="text-align:center;padding:40px;color:var(--text-3)"><div class="km-spinner" style="margin:0 auto 12px;width:24px;height:24px;border-width:3px"></div>Загрузка вопросов...</div>';
document.getElementById('km-qtest').classList.add('open');
try {
@@ -1696,7 +1696,7 @@ async function startQuickTest(node) {
};
if (!_qt.questions.length) {
document.getElementById('qtest-body').innerHTML =
'<div style="text-align:center;padding:40px;color:#8898AA">Нет вопросов по этой теме</div>';
'<div style="text-align:center;padding:40px;color:var(--text-3)">Нет вопросов по этой теме</div>';
return;
}
renderQuestion();
+2 -2
View File
@@ -9213,7 +9213,7 @@
const p = permData.permissions?.find(p => p.key === 'simulations.access');
if (p && p.effective === false) {
document.getElementById('sim-grid').innerHTML =
`<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:#8898AA">
`<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:var(--text-3)">
<div style="font-size:2rem;margin-bottom:12px"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div style="font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:#0F172A;margin-bottom:6px">Доступ к симуляциям закрыт</div>
<div style="font-size:.88rem">Администратор ограничил доступ к лаборатории</div>
@@ -9229,7 +9229,7 @@
if (_simModuleDisabled) {
document.getElementById('sim-grid').innerHTML =
`<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:#8898AA">
`<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:var(--text-3)">
<div style="font-size:2rem;margin-bottom:12px"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div style="font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:#0F172A;margin-bottom:6px">Модуль симуляций отключён</div>
<div style="font-size:.88rem">Администратор временно отключил лабораторию</div>
+31 -31
View File
@@ -33,7 +33,7 @@
}
.etb-back {
display: flex; align-items: center; gap: 6px; text-decoration: none;
font-size: 0.8rem; font-weight: 700; color: #8898AA; transition: color 0.15s;
font-size: 0.8rem; font-weight: 700; color: var(--text-3); transition: color 0.15s;
flex-shrink: 0;
}
.etb-back:hover { color: var(--violet); }
@@ -54,19 +54,19 @@
.etb-select:focus { border-color: var(--violet); }
.etb-spacer { flex: 1; }
.etb-word-count {
font-size: 0.74rem; color: #8898AA; font-weight: 600; white-space: nowrap;
font-size: 0.74rem; color: var(--text-3); font-weight: 600; white-space: nowrap;
background: rgba(15,23,42,0.04); border-radius: 8px; padding: 4px 9px;
flex-shrink: 0;
}
.etb-actions { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
.etb-status {
font-size: 0.76rem; color: #8898AA; font-weight: 600;
font-size: 0.76rem; color: var(--text-3); font-weight: 600;
transition: color 0.3s; white-space: nowrap;
}
.etb-status.saved { color: #059652; }
.etb-icon-btn {
width: 30px; height: 30px; border: 1.5px solid rgba(15,23,42,0.1); border-radius: 8px;
background: #f8f9fc; color: #8898AA; cursor: pointer;
background: #f8f9fc; color: var(--text-3); cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all 0.12s; flex-shrink: 0;
}
@@ -130,7 +130,7 @@
.palette-search:focus { border-color: var(--violet); background: #fff; }
.palette-label {
font-size: 0.66rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em;
color: #8898AA; margin-bottom: 8px; padding: 0 4px;
color: var(--text-3); margin-bottom: 8px; padding: 0 4px;
}
.palette-btn {
width: 100%; padding: 8px 10px; border-radius: 10px;
@@ -182,7 +182,7 @@
.block-header {
display: flex; align-items: center; gap: 8px;
padding: 10px 14px 0; color: #8898AA;
padding: 10px 14px 0; color: var(--text-3);
}
.block-drag-handle {
cursor: grab; color: #CBD5E1; padding: 2px; flex-shrink: 0;
@@ -191,7 +191,7 @@
.block-drag-handle:active { cursor: grabbing; }
.block-type-label {
font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.07em; color: #8898AA; flex: 1;
letter-spacing: 0.07em; color: var(--text-3); flex: 1;
}
.block-actions { display: flex; gap: 4px; }
.block-action-btn {
@@ -251,7 +251,7 @@
.sym-cat-row { display: flex; gap: 4px; margin-bottom: 5px; flex-wrap: wrap; }
.sym-cat-btn { padding: 2px 8px; border-radius: 5px; border: 1px solid rgba(15,23,42,0.1);
background: none; cursor: pointer; font-size: .72rem; font-weight: 600;
color: #8898AA; transition: .12s; }
color: var(--text-3); transition: .12s; }
.sym-cat-btn.active { background: rgba(155,93,229,0.1); border-color: rgba(155,93,229,0.3); color: #9B5DE5; }
/* ── heading level selector ── */
@@ -283,7 +283,7 @@
.canvas-hint {
text-align: center; padding: 60px 28px;
border: 2px dashed rgba(155,93,229,0.15); border-radius: 20px;
color: #8898AA; font-size: 0.88rem; line-height: 1.8;
color: var(--text-3); font-size: 0.88rem; line-height: 1.8;
}
.canvas-hint-title { font-family: 'Unbounded', sans-serif; font-size: 0.95rem; font-weight: 800; color: #0F172A; margin-bottom: 8px; }
@@ -465,7 +465,7 @@
}
.img-upload-btn:hover { background: rgba(6,214,160,0.14); border-color: #06D6A0; }
.img-upload-progress {
font-size: 0.78rem; color: #8898AA; font-weight: 600;
font-size: 0.78rem; color: var(--text-3); font-weight: 600;
}
/* ── code preview ── */
@@ -519,7 +519,7 @@
.preview-block .pv-img-wrap.align-right .pv-image { max-width: 55%; }
.preview-block .pv-img-wrap.align-full .pv-image { width: 100%; }
.preview-block .pv-image-caption {
text-align: center; font-size: 0.82rem; color: #8898AA; margin-top: 6px;
text-align: center; font-size: 0.82rem; color: var(--text-3); margin-top: 6px;
}
.align-btns { display: flex; gap: 4px; margin-bottom: 10px; }
.align-btn {
@@ -581,7 +581,7 @@
transform: rotateY(180deg);
}
.pv-fc-label {
font-size: 0.65rem; font-weight: 800; color: #8898AA; text-transform: uppercase;
font-size: 0.65rem; font-weight: 800; color: var(--text-3); text-transform: uppercase;
letter-spacing: 0.08em; margin-bottom: 10px;
}
.pv-fc-text { font-size: 1rem; font-weight: 700; color: #0F172A; line-height: 1.55; }
@@ -721,7 +721,7 @@
background: rgba(155,93,229,0.04); border-radius: 0 12px 12px 0;
}
.preview-block .pv-quote-text { font-size: 1.05rem; font-style: italic; color: #1E293B; line-height: 1.7; }
.preview-block .pv-quote-author { font-size: 0.82rem; font-weight: 700; color: #8898AA; margin-top: 8px; }
.preview-block .pv-quote-author { font-size: 0.82rem; font-weight: 700; color: var(--text-3); margin-top: 8px; }
/* ── C2: checklist ── */
.checklist-item-row { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
@@ -732,7 +732,7 @@
font-size: 0.9rem; border-bottom: 1px solid rgba(15,23,42,0.05); cursor: pointer;
}
.preview-block .pv-checklist-item:last-child { border-bottom: none; }
.preview-block .pv-checklist-item.checked { color: #8898AA; text-decoration: line-through; }
.preview-block .pv-checklist-item.checked { color: var(--text-3); text-decoration: line-through; }
.preview-block .pv-checklist-cb { width: 17px; height: 17px; accent-color: var(--violet); cursor: pointer; flex-shrink: 0; }
/* ── C3: button/CTA ── */
@@ -759,7 +759,7 @@
.outline-panel.open { display: block; }
.outline-panel-title {
font-size: 0.66rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em;
color: #8898AA; margin-bottom: 10px; padding: 0 4px;
color: var(--text-3); margin-bottom: 10px; padding: 0 4px;
}
.outline-item {
display: block; width: 100%; text-align: left; border: none; background: none;
@@ -813,7 +813,7 @@
/* ── E5: block type select ── */
.block-type-select {
font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em;
color: #8898AA; background: none; border: none; cursor: pointer;
color: var(--text-3); background: none; border: none; cursor: pointer;
font-family: 'Manrope', sans-serif; padding: 2px 4px; border-radius: 4px;
flex: 1; max-width: 110px;
}
@@ -830,7 +830,7 @@
<div class="etb-title" id="etb-title">Загрузка…</div>
<div class="etb-section-wrap" id="etb-section-wrap" style="display:none">
<label style="font-size:0.74rem;color:#8898AA;font-weight:600">Раздел:</label>
<label style="font-size:0.74rem;color:var(--text-3);font-weight:600">Раздел:</label>
<select id="section-select" class="etb-select" onchange="assignSection(this.value)">
<option value="">— без раздела —</option>
</select>
@@ -1662,7 +1662,7 @@
case 'button':
return renderButtonEditor(b);
default: return '<span style="color:#8898AA;font-size:0.82rem">Неизвестный тип блока</span>';
default: return '<span style="color:var(--text-3);font-size:0.82rem">Неизвестный тип блока</span>';
}
}
@@ -1855,7 +1855,7 @@
<button class="btn-add-item" onclick="document.getElementById('vfile-${bid}').click()">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="display:inline-block;vertical-align:middle;margin-right:4px"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>Загрузить файл
</button>
<span id="vfile-name-${bid}" style="font-size:0.78rem;color:#8898AA"></span>
<span id="vfile-name-${bid}" style="font-size:0.78rem;color:var(--text-3)"></span>
</div>
</div>
<div class="block-row">
@@ -2085,7 +2085,7 @@
${pairs.map((p, i) => `<div class="matching-pair-row">
<input class="block-input" type="text" placeholder="Левая ${i+1}" value="${escAttr(p.left||'')}"
oninput="updateMatchingPair('${bid}',${i},'left',this.value)" />
<span style="color:#8898AA;font-weight:700;flex-shrink:0"><svg class="ic" viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><polyline points="8 17 3 12 8 7"/><polyline points="16 7 21 12 16 17"/></svg></span>
<span style="color:var(--text-3);font-weight:700;flex-shrink:0"><svg class="ic" viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><polyline points="8 17 3 12 8 7"/><polyline points="16 7 21 12 16 17"/></svg></span>
<input class="block-input" type="text" placeholder="Правая ${i+1}" value="${escAttr(p.right||'')}"
oninput="updateMatchingPair('${bid}',${i},'right',this.value)" />
<button class="block-action-btn danger" onclick="removeMatchingPair('${bid}',${i})" title="Удалить пару">
@@ -2129,7 +2129,7 @@
return `<div>
<div class="block-field">
<div class="block-row-label">Текст с пропусками</div>
<div style="font-size:0.74rem;color:#8898AA;margin-bottom:6px">Оберните ответы в фигурные скобки: <code style="background:rgba(155,93,229,0.1);padding:1px 5px;border-radius:4px;font-size:0.74rem">{ответ}</code></div>
<div style="font-size:0.74rem;color:var(--text-3);margin-bottom:6px">Оберните ответы в фигурные скобки: <code style="background:rgba(155,93,229,0.1);padding:1px 5px;border-radius:4px;font-size:0.74rem">{ответ}</code></div>
<button class="btn-add-opt" style="margin-bottom:6px"
onmousedown="event.preventDefault()"
onclick="wrapFillBlank('${bid}')">
@@ -2180,10 +2180,10 @@
oninput="updateBlockData('${bid}','question',this.value);markDirty()" />
</div>
<div class="block-row-label" style="margin-top:6px">Элементы (текущий порядок = правильный)</div>
<div class="ordering-hint" style="font-size:0.74rem;color:#8898AA;margin-bottom:6px">Элементы будут перемешаны для ученика.</div>
<div class="ordering-hint" style="font-size:0.74rem;color:var(--text-3);margin-bottom:6px">Элементы будут перемешаны для ученика.</div>
<div id="ordering-items-${bid}">
${items.map((item, i) => `<div class="ordering-item-row">
<span style="color:#8898AA;font-weight:700;font-size:0.8rem;width:22px;text-align:center;flex-shrink:0">${i+1}</span>
<span style="color:var(--text-3);font-weight:700;font-size:0.8rem;width:22px;text-align:center;flex-shrink:0">${i+1}</span>
<input class="block-input" type="text" placeholder="Элемент ${i+1}" value="${escAttr(item||'')}"
oninput="updateOrderingItem('${bid}',${i},this.value)" />
<button class="block-action-btn danger" onclick="removeOrderingItem('${bid}',${i})" title="Удалить">
@@ -2295,7 +2295,7 @@
return `<div>
<div class="block-field">
<div class="block-row-label">Mermaid-код диаграммы</div>
<div style="font-size:0.74rem;color:#8898AA;margin-bottom:6px">
<div style="font-size:0.74rem;color:var(--text-3);margin-bottom:6px">
Синтаксис: <a href="https://mermaid.js.org/syntax/flowchart.html" target="_blank" style="color:var(--violet)">mermaid.js.org</a>
</div>
<textarea class="block-textarea" rows="6" style="font-family:monospace;font-size:0.84rem"
@@ -2331,7 +2331,7 @@
return `<div>
<div class="block-field">
<div class="block-row-label">ID материала GeoGebra</div>
<div style="font-size:0.74rem;color:#8898AA;margin-bottom:6px">
<div style="font-size:0.74rem;color:var(--text-3);margin-bottom:6px">
Введите ID из URL: geogebra.org/m/<b>abc123</b>
</div>
<input class="block-input" type="text" placeholder="abc123" value="${escAttr(d.materialId||'')}"
@@ -3080,7 +3080,7 @@
return `<div class="preview-block"><div class="pv-callout pv-${d.style||'info'}">${d.title?`<strong>${esc(d.title)}</strong><br>`:''}${d.text||''}</div></div>`;
case 'video': {
const embed = getEmbedUrl(d.url||'');
return `<div class="preview-block">${embed ? `<div style="aspect-ratio:16/9;border-radius:12px;overflow:hidden"><iframe src="${escAttr(embed)}" style="width:100%;height:100%;border:none" allowfullscreen></iframe></div>` : ''}${d.caption ? `<div style="font-size:0.82rem;color:#8898AA;margin-top:6px">${esc(d.caption)}</div>` : ''}</div>`;
return `<div class="preview-block">${embed ? `<div style="aspect-ratio:16/9;border-radius:12px;overflow:hidden"><iframe src="${escAttr(embed)}" style="width:100%;height:100%;border:none" allowfullscreen></iframe></div>` : ''}${d.caption ? `<div style="font-size:0.82rem;color:var(--text-3);margin-top:6px">${esc(d.caption)}</div>` : ''}</div>`;
}
case 'table': {
const rows = Array.isArray(d.rows) ? d.rows : [];
@@ -3129,11 +3129,11 @@
return `<div class="preview-block">${items.map(it => `<div style="display:flex;gap:12px;margin-bottom:14px"><div style="display:flex;flex-direction:column;align-items:center"><div style="width:12px;height:12px;border-radius:50%;background:#0EA5E9;flex-shrink:0"></div><div style="width:2px;flex:1;background:rgba(14,165,233,0.2)"></div></div><div><div style="font-size:0.78rem;font-weight:700;color:#0EA5E9">${esc(it.date||'')}</div><div style="font-weight:700;color:#0F172A">${esc(it.title||'')}</div>${it.text?`<div style="font-size:0.85rem;color:#6B7A8E;margin-top:2px">${esc(it.text)}</div>`:''}</div></div>`).join('')}</div>`;
}
case 'diagram':
return `<div class="preview-block"><div class="diagram-preview"><div class="mermaid">${esc(d.code||'')}</div></div>${d.caption?`<div style="text-align:center;font-size:0.82rem;color:#8898AA;margin-top:6px">${esc(d.caption)}</div>`:''}</div>`;
return `<div class="preview-block"><div class="diagram-preview"><div class="mermaid">${esc(d.code||'')}</div></div>${d.caption?`<div style="text-align:center;font-size:0.82rem;color:var(--text-3);margin-top:6px">${esc(d.caption)}</div>`:''}</div>`;
case 'geogebra':
return `<div class="preview-block">${d.materialId?`<div class="geogebra-preview"><iframe src="https://www.geogebra.org/material/iframe/id/${escAttr(d.materialId)}/width/800/height/500/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/false/rc/false/ld/false/sdz/false/ctl/false" allowfullscreen></iframe></div>`:'<div style="color:#8898AA;text-align:center;padding:20px">Не указан ID материала GeoGebra</div>'}${d.caption?`<div style="text-align:center;font-size:0.82rem;color:#8898AA;margin-top:6px">${esc(d.caption)}</div>`:''}</div>`;
return `<div class="preview-block">${d.materialId?`<div class="geogebra-preview"><iframe src="https://www.geogebra.org/material/iframe/id/${escAttr(d.materialId)}/width/800/height/500/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/false/rc/false/ld/false/sdz/false/ctl/false" allowfullscreen></iframe></div>`:'<div style="color:var(--text-3);text-align:center;padding:20px">Не указан ID материала GeoGebra</div>'}${d.caption?`<div style="text-align:center;font-size:0.82rem;color:var(--text-3);margin-top:6px">${esc(d.caption)}</div>`:''}</div>`;
case 'audio':
return `<div class="preview-block">${d.url?`<audio controls src="${escAttr(d.url)}" style="width:100%"></audio>`:''}${d.caption?`<div style="font-size:0.82rem;color:#8898AA;margin-top:6px">${esc(d.caption)}</div>`:''}</div>`;
return `<div class="preview-block">${d.url?`<audio controls src="${escAttr(d.url)}" style="width:100%"></audio>`:''}${d.caption?`<div style="font-size:0.82rem;color:var(--text-3);margin-top:6px">${esc(d.caption)}</div>`:''}</div>`;
case 'columns': {
const cols = Array.isArray(d.cols) ? d.cols : [];
return `<div class="preview-block"><div style="display:grid;grid-template-columns:repeat(${cols.length},1fr);gap:16px">${cols.map(c => `<div style="font-size:0.88rem;line-height:1.7;color:#3D4F6B">${c.content||'<span style="color:#ccc">Пусто</span>'}</div>`).join('')}</div></div>`;
@@ -3212,14 +3212,14 @@
'.tpl-card{background:#f8f9fc;border:1.5px solid rgba(15,23,42,0.08);border-radius:14px;padding:14px 16px;cursor:pointer;transition:all .15s;}' +
'.tpl-card:hover{border-color:rgba(155,93,229,0.3);background:rgba(155,93,229,0.04);transform:translateY(-2px);box-shadow:0 4px 16px rgba(15,23,42,0.08);}' +
'.tpl-card-title{font-size:0.88rem;font-weight:700;color:#0F172A;margin-bottom:4px;display:flex;align-items:center;gap:6px;}' +
'.tpl-card-meta{font-size:0.72rem;color:#8898AA;display:flex;align-items:center;gap:8px;}' +
'.tpl-card-meta{font-size:0.72rem;color:var(--text-3);display:flex;align-items:center;gap:8px;}' +
'.tpl-card-cat{font-size:0.66rem;font-weight:700;padding:2px 8px;border-radius:99px;background:rgba(155,93,229,0.08);color:var(--violet);}' +
'.tpl-card-actions{display:flex;gap:4px;margin-top:8px;}' +
'.tpl-card-btn{padding:5px 12px;border:1.5px solid rgba(155,93,229,0.2);border-radius:99px;background:rgba(155,93,229,0.06);color:var(--violet);font-family:"Manrope",sans-serif;font-size:0.74rem;font-weight:700;cursor:pointer;transition:all .12s;}' +
'.tpl-card-btn:hover{background:var(--violet);color:#fff;}' +
'.tpl-card-btn.danger{border-color:rgba(241,91,181,0.2);background:rgba(241,91,181,0.06);color:#E0335E;}' +
'.tpl-card-btn.danger:hover{background:#E0335E;color:#fff;}' +
'.tpl-empty{text-align:center;padding:32px;color:#8898AA;font-size:0.86rem;}' +
'.tpl-empty{text-align:center;padding:32px;color:var(--text-3);font-size:0.86rem;}' +
'</style>');
const CAT_LABELS = { general:'Общее', lecture:'Лекция', practice:'Практика', lab:'Лабораторная', test:'Контроль' };
+24 -24
View File
@@ -52,7 +52,7 @@
}
.lesson-topbar-back {
display: flex; align-items: center; gap: 6px;
text-decoration: none; color: #8898AA; font-size: 0.8rem; font-weight: 700;
text-decoration: none; color: var(--text-3); font-size: 0.8rem; font-weight: 700;
transition: color 0.15s; flex-shrink: 0;
}
.lesson-topbar-back:hover { color: var(--violet); }
@@ -71,7 +71,7 @@
.lesson-edit-btn:hover { background: rgba(155,93,229,0.12); border-color: var(--violet); }
.lesson-bm-btn {
padding: 6px 10px; border: 1.5px solid rgba(15,23,42,0.12); border-radius: 999px;
background: transparent; color: #8898AA; cursor: pointer; display: flex; align-items: center; gap: 4px;
background: transparent; color: var(--text-3); cursor: pointer; display: flex; align-items: center; gap: 4px;
font-family: 'Manrope', sans-serif; font-size: 0.78rem; font-weight: 700; transition: all 0.15s;
}
.lesson-bm-btn:hover { border-color: #FFD166; color: #FFD166; }
@@ -92,7 +92,7 @@
color: #0F172A; letter-spacing: -0.03em; line-height: 1.25; margin-bottom: 10px;
}
.lesson-course-crumb {
font-size: 0.78rem; color: #8898AA; display: flex; align-items: center; gap: 6px;
font-size: 0.78rem; color: var(--text-3); display: flex; align-items: center; gap: 6px;
}
.lesson-course-crumb a { color: var(--violet); text-decoration: none; }
.lesson-course-crumb a:hover { text-decoration: underline; }
@@ -133,7 +133,7 @@
border: 1px solid rgba(15,23,42,0.08);
}
.block-image-caption {
text-align: center; font-size: 0.76rem; color: #8898AA;
text-align: center; font-size: 0.76rem; color: var(--text-3);
margin-top: 8px; font-style: italic;
}
@@ -187,7 +187,7 @@
padding: 16px; background: #fff; border-radius: 14px;
border: 1.5px solid rgba(168,85,247,0.12); overflow-x: auto;
}
.diagram-caption { font-size: 0.82rem; color: #8898AA; margin-top: 8px; }
.diagram-caption { font-size: 0.82rem; color: var(--text-3); margin-top: 8px; }
/* ── geogebra block ── */
.geogebra-embed {
@@ -195,13 +195,13 @@
border: 1.5px solid rgba(34,197,94,0.15);
}
.geogebra-embed iframe { width: 100%; height: 100%; border: none; }
.geogebra-caption { font-size: 0.82rem; color: #8898AA; margin-top: 8px; text-align: center; }
.geogebra-caption { font-size: 0.82rem; color: var(--text-3); margin-top: 8px; text-align: center; }
/* ── audio block ── */
.block-audio audio {
width: 100%; border-radius: 10px;
}
.audio-caption { font-size: 0.82rem; color: #8898AA; margin-top: 6px; }
.audio-caption { font-size: 0.82rem; color: var(--text-3); margin-top: 6px; }
/* ── columns block ── */
.block-columns {
@@ -275,7 +275,7 @@
.lesson-nav-btn:hover { border-color: var(--violet); color: var(--violet); box-shadow: 0 4px 14px rgba(15,23,42,0.09); }
.lesson-nav-btn-prev { justify-content: flex-start; }
.lesson-nav-btn-next { justify-content: flex-end; margin-left: auto; }
.lesson-nav-btn-label { font-size: 0.7rem; font-weight: 600; color: #8898AA; display: block; }
.lesson-nav-btn-label { font-size: 0.7rem; font-weight: 600; color: var(--text-3); display: block; }
.lesson-nav-btn-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; }
/* ── complete button ── */
@@ -313,7 +313,7 @@
/* ── video block ── */
.block-video { border-radius: 16px; overflow: hidden; background: #0F172A; }
.block-video iframe { width: 100%; aspect-ratio: 16/9; border: none; display: block; }
.block-video-caption { text-align: center; font-size: 0.76rem; color: #8898AA; margin-top: 8px; font-style: italic; }
.block-video-caption { text-align: center; font-size: 0.76rem; color: var(--text-3); margin-top: 8px; font-style: italic; }
/* ── table block ── */
.block-table { overflow-x: auto; border-radius: 14px; border: 1.5px solid rgba(15,23,42,0.09); }
@@ -352,7 +352,7 @@
transform: rotateY(180deg);
}
.flashcard-hint {
text-align: center; font-size: 0.7rem; color: #8898AA; margin-top: 8px;
text-align: center; font-size: 0.7rem; color: var(--text-3); margin-top: 8px;
display: flex; align-items: center; justify-content: center; gap: 4px;
}
@@ -371,7 +371,7 @@
padding: 10px 16px; background: rgba(15,23,42,0.03);
border-top: 1px solid rgba(155,93,229,0.1);
}
.sim-caption { font-size: 0.78rem; color: #8898AA; font-weight: 600; }
.sim-caption { font-size: 0.78rem; color: var(--text-3); font-weight: 600; }
.sim-fullscreen-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 14px; border-radius: 999px; border: 1.5px solid rgba(155,93,229,0.25);
@@ -387,7 +387,7 @@
}
.notes-toggle {
display: flex; align-items: center; gap: 8px;
font-size: 0.8rem; font-weight: 700; color: #8898AA;
font-size: 0.8rem; font-weight: 700; color: var(--text-3);
cursor: pointer; background: none; border: none;
padding: 8px 0; transition: color 0.15s; font-family: 'Manrope', sans-serif;
}
@@ -415,13 +415,13 @@
/* ── read time in topbar ── */
.topbar-read-time {
font-size: 0.74rem; color: #8898AA; display: flex; align-items: center; gap: 4px;
font-size: 0.74rem; color: var(--text-3); display: flex; align-items: center; gap: 4px;
}
/* ── toc ── */
.toc-title {
font-family: 'Unbounded', sans-serif; font-size: 0.67rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em;
padding: 0 16px; margin-bottom: 14px;
display: flex; align-items: center; gap: 8px;
}
@@ -435,7 +435,7 @@
text-decoration: none; font-size: 0.79rem; color: #6B7A8E; font-weight: 600;
line-height: 1.45; transition: all 0.12s; margin-bottom: 1px;
}
.toc-item.h3 { padding-left: 28px; font-size: 0.75rem; color: #8898AA; font-weight: 500; }
.toc-item.h3 { padding-left: 28px; font-size: 0.75rem; color: var(--text-3); font-weight: 500; }
.toc-item:hover { color: var(--violet); border-left-color: rgba(155,93,229,0.3); background: rgba(155,93,229,0.04); }
.toc-item.active { color: var(--violet); border-left-color: var(--violet); background: rgba(155,93,229,0.06); font-weight: 700; }
@@ -563,7 +563,7 @@
}
.ordering-item.dragging { opacity: 0.4; }
.ordering-grip {
color: #8898AA; flex-shrink: 0; display: flex; align-items: center;
color: var(--text-3); flex-shrink: 0; display: flex; align-items: center;
}
.ordering-item.correct {
border-color: #06D6A0 !important; background: rgba(6,214,160,0.07) !important; color: #047857 !important;
@@ -594,7 +594,7 @@
}
.comments-count {
font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 700;
color: #8898AA; background: rgba(15,23,42,0.06); padding: 2px 8px; border-radius: 99px;
color: var(--text-3); background: rgba(15,23,42,0.06); padding: 2px 8px; border-radius: 99px;
}
/* compose */
.comment-compose {
@@ -648,12 +648,12 @@
}
.ci-role-teacher { background: rgba(6,214,160,0.12); color: #059652; }
.ci-role-admin { background: rgba(239,71,111,0.1); color: #EF476F; }
.ci-time { font-size: 0.7rem; color: #8898AA; }
.ci-time { font-size: 0.7rem; color: var(--text-3); }
.ci-text { font-size: 0.86rem; line-height: 1.6; color: #3D4F6B; white-space: pre-wrap; word-wrap: break-word; }
.ci-actions { display: flex; gap: 12px; margin-top: 6px; }
.ci-action-btn {
background: none; border: none; font-size: 0.74rem; font-weight: 600;
color: #8898AA; cursor: pointer; padding: 0;
color: var(--text-3); cursor: pointer; padding: 0;
font-family: 'Manrope', sans-serif; transition: color 0.12s;
}
.ci-action-btn:hover { color: var(--violet); }
@@ -683,7 +683,7 @@
}
.ci-reply-send:disabled { opacity: 0.4; }
.comments-empty {
text-align: center; padding: 28px; color: #8898AA; font-size: 0.84rem;
text-align: center; padding: 28px; color: var(--text-3); font-size: 0.84rem;
}
/* ── Mobile responsive ── */
@@ -1198,7 +1198,7 @@
}
// already embed or direct
if (!embedUrl && (url.includes('/embed/') || url.includes('player.'))) embedUrl = url;
if (!embedUrl) return `<div class="lesson-block block-sim"><div style="color:#8898AA;font-size:0.84rem">Видео: ${esc(url)}</div></div>`;
if (!embedUrl) return `<div class="lesson-block block-sim"><div style="color:var(--text-3);font-size:0.84rem">Видео: ${esc(url)}</div></div>`;
return `<div class="lesson-block block-video">
<iframe src="${esc(embedUrl)}" allowfullscreen loading="lazy"></iframe>
${d.caption ? `<div class="block-video-caption">${esc(d.caption)}</div>` : ''}
@@ -1240,7 +1240,7 @@
return `<div class="lesson-block block-sim">
${embedUrl
? `<iframe class="sim-embed-frame" src="${embedUrl}" loading="lazy" allow="fullscreen"></iframe>`
: `<div style="height:200px;display:flex;align-items:center;justify-content:center;color:#8898AA">Симуляция не указана</div>`}
: `<div style="height:200px;display:flex;align-items:center;justify-content:center;color:var(--text-3)">Симуляция не указана</div>`}
<div class="sim-embed-bar">
<span class="sim-caption">${caption ? esc(caption) : (simId ? '<svg class="ic" viewBox="0 0 24 24"><path d="M6 18h8"/><path d="M3 22h18"/><path d="M14 22a7 7 0 1 0 0-14h-1"/><path d="M9 14l2-7"/><path d="M12 14l2-7"/></svg> Интерактивная симуляция' : '')}</span>
<a class="sim-fullscreen-btn" href="${fullUrl}" target="_blank">
@@ -1354,7 +1354,7 @@
case 'geogebra': {
const mid = d.materialId || '';
return `<div class="lesson-block block-geogebra">
${mid ? `<div class="geogebra-embed"><iframe src="https://www.geogebra.org/material/iframe/id/${escAll(mid)}/width/800/height/500/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/false/rc/false/ld/false/sdz/false/ctl/false" allowfullscreen></iframe></div>` : '<div style="text-align:center;padding:20px;color:#8898AA">Не указан ID материала GeoGebra</div>'}
${mid ? `<div class="geogebra-embed"><iframe src="https://www.geogebra.org/material/iframe/id/${escAll(mid)}/width/800/height/500/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/false/rc/false/ld/false/sdz/false/ctl/false" allowfullscreen></iframe></div>` : '<div style="text-align:center;padding:20px;color:var(--text-3)">Не указан ID материала GeoGebra</div>'}
${d.caption ? `<div class="geogebra-caption">${esc(d.caption)}</div>` : ''}
</div>`;
}
@@ -1466,7 +1466,7 @@
try {
lesson = await LS.api('/api/lessons/' + lessonId);
} catch (e) {
document.getElementById('lesson-body').innerHTML = '<div style="text-align:center;padding:60px;color:#8898AA">Урок не найден</div>';
document.getElementById('lesson-body').innerHTML = '<div style="text-align:center;padding:60px;color:var(--text-3)">Урок не найден</div>';
return;
}
+18 -18
View File
@@ -79,7 +79,7 @@
flex-shrink: 0;
}
.class-card-name { font-size: 0.85rem; font-weight: 700; color: #0F172A; }
.class-card-meta { font-size: 0.72rem; color: #8898AA; margin-top: 2px; }
.class-card-meta { font-size: 0.72rem; color: var(--text-3); margin-top: 2px; }
.lq-session-area { padding: 14px 12px; border-top: 1px solid rgba(15,23,42,0.07); }
.btn-start {
@@ -104,7 +104,7 @@
background: rgba(6,214,160,0.08); border: 1.5px solid rgba(6,214,160,0.25);
margin-bottom: 8px;
}
.as-label { font-size: 0.7rem; font-weight: 700; color: #8898AA; margin-bottom: 3px; }
.as-label { font-size: 0.7rem; font-weight: 700; color: var(--text-3); margin-bottom: 3px; }
.as-val {
font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 800;
color: #059652; display: flex; align-items: center; gap: 6px;
@@ -125,7 +125,7 @@
.lq-search:focus { border-color: var(--violet); }
.lq-search-icon {
position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
color: #8898AA; pointer-events: none; width: 16px; height: 16px;
color: var(--text-3); pointer-events: none; width: 16px; height: 16px;
}
/* section header */
@@ -148,7 +148,7 @@
background-repeat: no-repeat; background-position: right 8px center; padding-right: 24px;
}
.lq-filter-select:focus { border-color: var(--violet); }
.lq-q-count { font-size: 0.7rem; color: #8898AA; font-weight: 600; white-space: nowrap; margin-bottom: 12px; }
.lq-q-count { font-size: 0.7rem; color: var(--text-3); font-weight: 600; white-space: nowrap; margin-bottom: 12px; }
/* load more */
.btn-load-more {
@@ -164,7 +164,7 @@
.lq-result-stats { display: flex; gap: 8px; margin-bottom: 14px; }
.lq-result-stat { flex: 1; padding: 10px 12px; border-radius: 12px; background: rgba(15,23,42,0.04); text-align: center; }
.lq-result-stat-val { font-family: 'Unbounded', sans-serif; font-size: 1.1rem; font-weight: 900; color: #0F172A; }
.lq-result-stat-lbl { font-size: 0.64rem; color: #8898AA; margin-top: 2px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.lq-result-stat-lbl { font-size: 0.64rem; color: var(--text-3); margin-top: 2px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.lq-result-stat.rs-correct { background: rgba(6,214,160,0.08); }
.lq-result-stat.rs-correct .lq-result-stat-val { color: #059652; }
.lq-result-stat.rs-wrong { background: rgba(239,71,111,0.07); }
@@ -192,7 +192,7 @@
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden; line-height: 1.5; min-height: 1.5em;
}
.lq-q-meta { font-size: 0.7rem; color: #8898AA; margin-top: 5px; display: flex; gap: 8px; flex-wrap: wrap; }
.lq-q-meta { font-size: 0.7rem; color: var(--text-3); margin-top: 5px; display: flex; gap: 8px; flex-wrap: wrap; }
.btn-launch {
padding: 7px 16px; border: none; border-radius: 999px;
background: var(--grad-1); color: #fff;
@@ -233,7 +233,7 @@
width: 22px; height: 22px; border-radius: 6px;
background: rgba(15,23,42,0.07); flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
font-size: 0.72rem; font-weight: 800; color: #8898AA;
font-size: 0.72rem; font-weight: 800; color: var(--text-3);
}
.lq-active-opt.correct .lq-opt-letter { background: #06D6A0; color: #fff; }
@@ -247,7 +247,7 @@
font-family: 'Unbounded', sans-serif; font-size: 1.4rem; font-weight: 900;
color: var(--violet);
}
.lq-counter-label { font-size: 0.78rem; color: #8898AA; font-weight: 600; }
.lq-counter-label { font-size: 0.78rem; color: var(--text-3); font-weight: 600; }
.lq-counter-bar-wrap {
flex: 1; height: 8px; background: rgba(155,93,229,0.1); border-radius: 999px; overflow: hidden;
}
@@ -296,7 +296,7 @@
/* no session state */
.lq-no-session {
text-align: center; padding: 80px 20px; color: #8898AA;
text-align: center; padding: 80px 20px; color: var(--text-3);
}
.lq-no-session-icon { margin-bottom: 14px; opacity: 0.2; }
@@ -362,7 +362,7 @@
Выберите класс
</div>
<div class="class-list" id="class-list">
<div style="padding:30px;text-align:center;color:#8898AA;font-size:0.82rem">
<div style="padding:30px;text-align:center;color:var(--text-3);font-size:0.82rem">
<div class="spinner" style="margin:0 auto 10px"></div>
Загрузка классов…
</div>
@@ -433,7 +433,7 @@
</div>
<div class="lq-q-count" id="q-count" style="display:none"></div>
<div class="lq-q-list" id="q-list">
<div style="padding:30px;text-align:center;color:#8898AA;font-size:0.82rem">
<div style="padding:30px;text-align:center;color:var(--text-3);font-size:0.82rem">
<div class="spinner" style="margin:0 auto 10px"></div>
Загрузка вопросов…
</div>
@@ -560,7 +560,7 @@
const classes = await LS.api('/api/classes');
const list = document.getElementById('class-list');
if (!(classes || []).length) {
list.innerHTML = '<div style="padding:30px;text-align:center;color:#8898AA;font-size:0.82rem">Нет доступных классов</div>';
list.innerHTML = '<div style="padding:30px;text-align:center;color:var(--text-3);font-size:0.82rem">Нет доступных классов</div>';
lucide.createIcons();
return;
}
@@ -578,7 +578,7 @@
list.innerHTML = html;
lucide.createIcons();
} catch {
document.getElementById('class-list').innerHTML = '<div style="padding:20px;text-align:center;color:#8898AA;font-size:0.82rem">Ошибка загрузки</div>';
document.getElementById('class-list').innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-3);font-size:0.82rem">Ошибка загрузки</div>';
}
}
@@ -713,7 +713,7 @@
/* ── load questions ── */
async function loadQuestions(reset = true) {
if (reset) { _qPage = 0; allQuestions = []; }
if (reset) document.getElementById('q-list').innerHTML = '<div style="padding:20px;text-align:center;color:#8898AA"><div class="spinner" style="margin:0 auto 10px"></div> Загрузка…</div>';
if (reset) document.getElementById('q-list').innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-3)"><div class="spinner" style="margin:0 auto 10px"></div> Загрузка…</div>';
const params = new URLSearchParams({ limit: Q_LIMIT, offset: _qPage * Q_LIMIT });
if (_topicFilter) params.set('topic_id', _topicFilter);
if (_diffFilter) params.set('difficulty', _diffFilter);
@@ -730,7 +730,7 @@
if (btnMore) btnMore.style.display = allQuestions.length < _totalQ ? '' : 'none';
if (countEl) { countEl.textContent = `Показано ${allQuestions.length} из ${_totalQ}`; countEl.style.display = ''; }
} catch {
if (reset) document.getElementById('q-list').innerHTML = '<div style="padding:20px;text-align:center;color:#8898AA;font-size:0.82rem">Ошибка загрузки вопросов</div>';
if (reset) document.getElementById('q-list').innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-3);font-size:0.82rem">Ошибка загрузки вопросов</div>';
}
}
@@ -748,7 +748,7 @@
function renderQuestionList() {
const list = document.getElementById('q-list');
if (!allQuestions.length) {
list.innerHTML = '<div style="padding:30px;text-align:center;color:#8898AA;font-size:0.82rem">Вопросов не найдено</div>';
list.innerHTML = '<div style="padding:30px;text-align:center;color:var(--text-3);font-size:0.82rem">Вопросов не найдено</div>';
lucide.createIcons();
return;
}
@@ -864,7 +864,7 @@
renderResults(data, resultsArea);
if (window.LS && LS.sfx) LS.sfx.play('quiz_end');
} catch (e) {
resultsArea.innerHTML = `<div style="padding:20px;text-align:center;color:#8898AA;font-size:0.84rem">${esc(e.message || 'Ошибка загрузки результатов')}</div>`;
resultsArea.innerHTML = `<div style="padding:20px;text-align:center;color:var(--text-3);font-size:0.84rem">${esc(e.message || 'Ошибка загрузки результатов')}</div>`;
}
}
@@ -877,7 +877,7 @@
const maxCount = Math.max(...opts.map(o => o.chosen_count || 0), 1);
if (!opts.length) {
container.innerHTML = '<div style="padding:16px;text-align:center;color:#8898AA;font-size:0.84rem">Нет данных о вариантах ответа</div>';
container.innerHTML = '<div style="padding:16px;text-align:center;color:var(--text-3);font-size:0.84rem">Нет данных о вариантах ответа</div>';
return;
}
+1 -1
View File
@@ -10,7 +10,7 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--violet: #9B5DE5; --cyan: #06D6E0; --bg: #F8F9FB; --text: #0F172A;
--text-2: #475569; --text-3: #8898AA; --border: #E8EBF0;
--text-2: #475569; --text-3: #56687A; --border: #E8EBF0;
--card: #fff; --shadow: 0 2px 16px rgba(15,23,42,0.06);
--green: #22c55e; --amber: #f59e0b; --red: #ef4444; --blue: #3b82f6;
}
+4 -4
View File
@@ -267,7 +267,7 @@
.bm-filter {
padding: 5px 14px; border-radius: 999px; border: 1.5px solid rgba(15,23,42,0.1);
background: transparent; font-family: 'Manrope', sans-serif; font-size: 0.75rem;
font-weight: 600; color: #8898AA; cursor: pointer; transition: all .15s;
font-weight: 600; color: var(--text-3); cursor: pointer; transition: all .15s;
}
.bm-filter:hover { border-color: var(--violet); color: var(--violet); }
.bm-filter.active { background: rgba(155,93,229,0.08); border-color: var(--violet); color: var(--violet); }
@@ -1595,7 +1595,7 @@
_allBookmarks = await LS.getBookmarks();
renderBookmarks();
} catch {
list.innerHTML = '<div style="color:#8898AA;font-size:.85rem;padding:12px">Не удалось загрузить закладки</div>';
list.innerHTML = '<div style="color:var(--text-3);font-size:.85rem;padding:12px">Не удалось загрузить закладки</div>';
}
}
@@ -1610,7 +1610,7 @@
const list = document.getElementById('bookmarks-list');
const filtered = _bmFilter ? _allBookmarks.filter(b => b.entity_type === _bmFilter) : _allBookmarks;
if (!filtered.length) {
list.innerHTML = '<div style="text-align:center;padding:32px 0;color:#8898AA;font-size:.85rem">Закладок пока нет</div>';
list.innerHTML = '<div style="text-align:center;padding:32px 0;color:var(--text-3);font-size:.85rem">Закладок пока нет</div>';
return;
}
const ICONS = { lesson: 'book-open', course: 'graduation-cap', file: 'file-text', question: 'help-circle' };
@@ -1670,7 +1670,7 @@
_parentLinksData = links;
renderParentLinks(links);
} catch (e) {
list.innerHTML = '<div style="color:#8898AA;font-size:.85rem;padding:12px">Не удалось загрузить</div>';
list.innerHTML = '<div style="color:var(--text-3);font-size:.85rem;padding:12px">Не удалось загрузить</div>';
}
}
+6 -6
View File
@@ -48,7 +48,7 @@
}
.filter-section { display: flex; flex-direction: column; gap: 8px; }
.filter-label {
font-size: 0.7rem; font-weight: 700; color: #8898AA;
font-size: 0.7rem; font-weight: 700; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 2px;
}
.filter-search {
@@ -125,11 +125,11 @@
}
.qc-footer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.qc-topic {
font-size: 0.7rem; font-weight: 700; color: #8898AA;
font-size: 0.7rem; font-weight: 700; color: var(--text-3);
display: flex; align-items: center; gap: 4px;
}
.qc-opts-count {
margin-left: auto; font-size: 0.7rem; color: #8898AA; font-weight: 600;
margin-left: auto; font-size: 0.7rem; color: var(--text-3); font-weight: 600;
display: flex; align-items: center; gap: 3px;
}
@@ -169,7 +169,7 @@
background: #06D6A0; border-color: #06D6A0; color: #fff;
}
.qc-explanation {
font-size: 0.78rem; color: #8898AA; font-style: italic; line-height: 1.5;
font-size: 0.78rem; color: var(--text-3); font-style: italic; line-height: 1.5;
background: rgba(155,93,229,0.04); border-left: 3px solid rgba(155,93,229,0.3);
padding: 8px 12px; border-radius: 0 8px 8px 0;
}
@@ -187,11 +187,11 @@
}
.pag-btn:hover:not(:disabled) { border-color: var(--violet); color: var(--violet); }
.pag-btn:disabled { opacity: 0.4; cursor: default; }
.pag-info { font-size: 0.82rem; font-weight: 600; color: #8898AA; }
.pag-info { font-size: 0.82rem; font-weight: 600; color: var(--text-3); }
/* ── empty state ── */
.qb-empty {
text-align: center; padding: 80px 20px; color: #8898AA; font-size: 0.9rem;
text-align: center; padding: 80px 20px; color: var(--text-3); font-size: 0.9rem;
}
.qb-empty-icon { margin-bottom: 14px; opacity: 0.25; }
+12 -12
View File
@@ -35,7 +35,7 @@
.tg-nav-header { padding: 0 16px 16px; border-bottom: 1px solid rgba(15,23,42,0.08); margin-bottom: 12px; }
.tg-nav-title {
font-family: 'Unbounded', sans-serif; font-size: 0.72rem; font-weight: 800;
color: #8898AA; text-transform: uppercase; letter-spacing: 0.1em;
color: var(--text-3); text-transform: uppercase; letter-spacing: 0.1em;
margin-bottom: 10px;
}
.tg-progress-wrap { margin-bottom: 6px; }
@@ -46,7 +46,7 @@
height: 100%; background: linear-gradient(90deg, #9B5DE5, #06D6E0);
border-radius: 99px; width: 0%; transition: width 0.3s;
}
.tg-progress-text { font-size: 0.68rem; color: #8898AA; margin-top: 5px; }
.tg-progress-text { font-size: 0.68rem; color: var(--text-3); margin-top: 5px; }
/* Search in nav */
.tg-nav-search {
@@ -78,7 +78,7 @@
width: 26px; height: 26px; border-radius: 8px; flex-shrink: 0;
background: rgba(15,23,42,0.06); display: flex; align-items: center; justify-content: center;
}
.tg-nav-ch-icon svg { width: 13px; height: 13px; color: #8898AA; stroke: #8898AA; }
.tg-nav-ch-icon svg { width: 13px; height: 13px; color: var(--text-3); stroke: var(--text-3); }
.tg-nav-ch-btn.active .tg-nav-ch-icon { background: rgba(155,93,229,0.12); }
.tg-nav-ch-btn.active .tg-nav-ch-icon svg { color: #9B5DE5; stroke: #9B5DE5; }
.tg-nav-ch-label {
@@ -89,7 +89,7 @@
.tg-nav-ch-status svg { width: 14px; height: 14px; color: #06D664; stroke: #06D664; display: none; }
.tg-nav-ch-btn.read .tg-nav-ch-status svg { display: block; }
.tg-nav-ch-chevron { flex-shrink: 0; transition: transform 0.2s; }
.tg-nav-ch-chevron svg { width: 12px; height: 12px; color: #8898AA; stroke: #8898AA; }
.tg-nav-ch-chevron svg { width: 12px; height: 12px; color: var(--text-3); stroke: var(--text-3); }
.tg-nav-chapter.open .tg-nav-ch-chevron { transform: rotate(90deg); }
/* Sub-sections */
@@ -98,7 +98,7 @@
.tg-nav-sec-link {
display: flex; align-items: center; gap: 8px;
padding: 5px 16px 5px 51px;
font-size: 0.74rem; color: #8898AA; text-decoration: none;
font-size: 0.74rem; color: var(--text-3); text-decoration: none;
border-radius: 8px; transition: all 0.15s; cursor: pointer;
}
.tg-nav-sec-link:hover { color: #0F172A; background: rgba(15,23,42,0.04); }
@@ -177,7 +177,7 @@
.tg-chapter-icon svg { width: 24px; height: 24px; color: #9B5DE5; stroke: #9B5DE5; }
.tg-chapter-meta { flex: 1; }
.tg-chapter-num {
font-size: 0.66rem; font-weight: 700; color: #8898AA;
font-size: 0.66rem; font-weight: 700; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 4px;
}
.tg-chapter-title {
@@ -266,9 +266,9 @@
padding: 3px 9px; border-radius: 99px; flex-shrink: 0;
}
.tg-acc-name { font-size: 0.86rem; font-weight: 700; color: #0F172A; flex: 1; }
.tg-acc-desc-short { font-size: 0.78rem; color: #8898AA; }
.tg-acc-desc-short { font-size: 0.78rem; color: var(--text-3); }
.tg-acc-chevron { transition: transform 0.2s; }
.tg-acc-chevron svg { width: 14px; height: 14px; color: #8898AA; stroke: #8898AA; }
.tg-acc-chevron svg { width: 14px; height: 14px; color: var(--text-3); stroke: var(--text-3); }
.tg-acc-item.open .tg-acc-chevron { transform: rotate(180deg); }
.tg-acc-body {
max-height: 0; overflow: hidden;
@@ -283,7 +283,7 @@
.tg-checklist { background: #fff; border: 1.5px solid rgba(15,23,42,0.07); border-radius: 18px; overflow: hidden; box-shadow: 0 2px 8px rgba(15,23,42,0.04); }
.tg-checklist-header { padding: 18px 22px 14px; border-bottom: 1px solid rgba(15,23,42,0.07); display: flex; align-items: center; justify-content: space-between; }
.tg-checklist-title { font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 800; color: #0F172A; }
.tg-checklist-prog { font-size: 0.72rem; color: #8898AA; }
.tg-checklist-prog { font-size: 0.72rem; color: var(--text-3); }
.tg-checklist-bar { height: 4px; background: rgba(15,23,42,0.07); }
.tg-checklist-bar-inner { height: 100%; background: linear-gradient(90deg, #9B5DE5, #06D6E0); width: 0%; transition: width 0.4s; }
.tg-checklist-items { padding: 10px 12px 12px; }
@@ -294,7 +294,7 @@
.tg-cl-item:hover { background: rgba(155,93,229,0.05); }
.tg-cl-item input[type="checkbox"] { width: 17px; height: 17px; accent-color: #9B5DE5; cursor: pointer; flex-shrink: 0; }
.tg-cl-label { flex: 1; font-size: 0.84rem; color: #0F172A; font-weight: 500; }
.tg-cl-item.checked .tg-cl-label { text-decoration: line-through; color: #8898AA; }
.tg-cl-item.checked .tg-cl-label { text-decoration: line-through; color: var(--text-3); }
.tg-cl-link { font-size: 0.72rem; color: #9B5DE5; text-decoration: none; white-space: nowrap; }
.tg-cl-link:hover { text-decoration: underline; }
@@ -339,7 +339,7 @@
}
.tg-tool-icon svg { width: 16px; height: 16px; color: #9B5DE5; stroke: #9B5DE5; }
.tg-tool-name { font-size: 0.8rem; font-weight: 700; color: #0F172A; }
.tg-tool-desc { font-size: 0.7rem; color: #8898AA; margin-top: 1px; }
.tg-tool-desc { font-size: 0.7rem; color: var(--text-3); margin-top: 1px; }
/* Chapter nav */
.tg-chapter-nav { display: flex; gap: 12px; margin-top: 40px; padding-top: 24px; border-top: 1.5px solid rgba(15,23,42,0.08); }
@@ -354,7 +354,7 @@
.tg-ch-nav-btn.next { flex-direction: row-reverse; text-align: right; }
.tg-ch-nav-icon { 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; }
.tg-ch-nav-icon svg { width: 16px; height: 16px; color: #9B5DE5; stroke: #9B5DE5; }
.tg-ch-nav-label { font-size: 0.68rem; color: #8898AA; text-transform: uppercase; letter-spacing: 0.07em; font-weight: 700; }
.tg-ch-nav-label { font-size: 0.68rem; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.07em; font-weight: 700; }
.tg-ch-nav-title { font-size: 0.84rem; font-weight: 700; color: #0F172A; margin-top: 2px; }
/* Divider */
+29 -12
View File
@@ -111,9 +111,9 @@
.confirm-desc { font-size: 0.88rem; color: var(--text-2); line-height: 1.6; margin-bottom: 24px; }
.confirm-skipped { display: inline-block; margin: 8px 0 4px; padding: 6px 16px; border-radius: var(--r-pill); background: rgba(255,179,71,0.12); color: #c47f00; font-family: 'Unbounded', sans-serif; font-size: 0.82rem; font-weight: 700; }
.confirm-actions { display: flex; gap: 10px; justify-content: center; }
.confirm-cancel { padding: 11px 26px; border: 1.5px solid var(--border-h); border-radius: var(--r-pill); background: transparent; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 600; color: var(--text-3); cursor: pointer; transition: all var(--tr); }
.confirm-cancel { padding: 11px 26px; min-height: 44px; border: 1.5px solid var(--border-h); border-radius: var(--r-pill); background: transparent; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 600; color: var(--text-3); cursor: pointer; transition: all var(--tr); }
.confirm-cancel:hover { border-color: var(--violet); color: var(--violet); }
.confirm-ok { padding: 11px 26px; border: none; border-radius: var(--r-pill); background: var(--grad-1); color: #fff; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 700; cursor: pointer; transition: transform var(--tr); }
.confirm-ok { padding: 11px 26px; min-height: 44px; border: none; border-radius: var(--r-pill); background: var(--grad-1); color: #fff; font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 700; cursor: pointer; transition: transform var(--tr); }
.confirm-ok:hover { transform: translateY(-1px); }
/* ── btn-finish counter ── */
@@ -201,13 +201,13 @@
</div>
<!-- Confirm finish modal -->
<div class="confirm-overlay" id="confirm-overlay" onclick="if(event.target===this)closeConfirm()">
<div class="confirm-overlay" id="confirm-overlay" role="dialog" aria-modal="true" aria-labelledby="confirm-dlg-title" onclick="if(event.target===this)closeConfirm()">
<div class="confirm-box">
<div class="confirm-icon" id="confirm-icon"></div>
<div class="confirm-title">Завершить тест?</div>
<div class="confirm-title" id="confirm-dlg-title">Завершить тест?</div>
<div class="confirm-desc" id="confirm-desc"></div>
<div class="confirm-actions">
<button class="confirm-cancel" onclick="closeConfirm()">Вернуться</button>
<button class="confirm-cancel" id="confirm-cancel-btn" onclick="closeConfirm()">Вернуться</button>
<button class="confirm-ok" onclick="doFinish()">Завершить</button>
</div>
</div>
@@ -403,12 +403,16 @@
? (Array.isArray(answers[q.id]) && answers[q.id].includes(opt.id))
: answers[q.id] === opt.id;
const keyLabel = isMulti ? (sel ? lsIcon('check', 14) : lsIcon('square', 14)) : String.fromCharCode(65 + i);
return `<div class="q-opt${sel ? ' selected' : ''}" data-opt-id="${opt.id}" data-i="${i}">
<div class="q-opt-key">${keyLabel}</div>
return `<div class="q-opt${sel ? ' selected' : ''}"
role="${isMulti ? 'checkbox' : 'radio'}"
aria-checked="${sel}"
tabindex="0"
data-opt-id="${opt.id}" data-i="${i}">
<div class="q-opt-key" aria-hidden="true">${keyLabel}</div>
<div class="q-opt-text">${esc(opt.text)}</div>
</div>`;
}).join('');
bodyHtml = `<div class="q-options" id="opts">${optHtml}</div>`;
bodyHtml = `<div class="q-options" id="opts" role="${isMulti ? 'group' : 'radiogroup'}" aria-label="Варианты ответа">${optHtml}</div>`;
if (isMulti) {
bodyHtml += `<div class="q-hint" style="margin-top:10px">Можно выбрать несколько вариантов</div>`;
}
@@ -421,7 +425,7 @@
document.getElementById('q-area').innerHTML = `
<div class="q-card active">
<button class="btn-flag${flags[q.id] ? ' flagged' : ''}" id="btn-flag-${q.id}" title="Отметить для проверки" onclick="toggleFlag(${q.id})">
<button class="btn-flag${flags[q.id] ? ' flagged' : ''}" id="btn-flag-${q.id}" aria-label="${flags[q.id] ? 'Снять отметку' : 'Отметить для проверки'}" aria-pressed="${flags[q.id] ? 'true' : 'false'}" onclick="toggleFlag(${q.id})">
<svg viewBox="0 0 24 24" width="16" height="16" fill="${flags[q.id] ? '#f59e0b' : 'none'}" stroke="${flags[q.id] ? '#f59e0b' : 'currentColor'}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>
</button>
<div class="q-num-badge">
@@ -455,10 +459,14 @@
} else {
const isMulti = type === 'multi';
document.querySelectorAll('.q-opt').forEach(el => {
el.addEventListener('click', () => {
const handleSelect = () => {
const optId = Number(el.dataset.optId);
if (isMulti) toggleMultiOpt(q, optId);
else selectSingleOpt(q, optId);
};
el.addEventListener('click', handleSelect);
el.addEventListener('keydown', e => {
if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); handleSelect(); }
});
});
}
@@ -518,6 +526,7 @@
document.querySelectorAll('.q-opt').forEach((el, i) => {
const sel = q.options[i].id === option_id;
el.classList.toggle('selected', sel);
el.setAttribute('aria-checked', sel ? 'true' : 'false');
el.querySelector('.q-opt-key').innerHTML = sel ? lsIcon('check', 14) : String.fromCharCode(65 + i);
if (sel) {
// attach progress ring svg
@@ -558,6 +567,7 @@
const id = Number(el.dataset.optId);
const sel = arr.includes(id);
el.classList.toggle('selected', sel);
el.setAttribute('aria-checked', sel ? 'true' : 'false');
el.querySelector('.q-opt-key').innerHTML = sel ? lsIcon('check', 14) : lsIcon('square', 14);
});
@@ -603,7 +613,9 @@
desc.innerHTML = skipped === total
? `Вы не ответили ни на один вопрос.<br>Результат будет нулевым.`
: `<span class="confirm-skipped">${lsIcon('warning', 16)} ${skipped} ${skipped === 1 ? 'вопрос без ответа' : skipped < 5 ? 'вопроса без ответа' : 'вопросов без ответа'}</span><br>Пропущенные вопросы будут засчитаны как неверные.`;
document.getElementById('confirm-overlay').classList.add('open');
const ov = document.getElementById('confirm-overlay');
ov.classList.add('open');
setTimeout(() => document.getElementById('confirm-cancel-btn')?.focus(), 50);
return;
}
@@ -722,6 +734,7 @@
const dot = document.createElement('button');
dot.className = 'dot';
dot.textContent = i + 1;
dot.setAttribute('aria-label', `Вопрос ${i + 1}`);
dot.addEventListener('click', () => { cancelAutoAdvance(); renderQuestion(i); });
el.appendChild(dot);
});
@@ -729,9 +742,11 @@
function updateDots() {
document.querySelectorAll('.dot').forEach((dot, i) => {
dot.classList.toggle('current', i === currentIdx);
const isCurrent = i === currentIdx;
dot.classList.toggle('current', isCurrent);
dot.classList.toggle('answered', isAnswered(questions[i]));
dot.classList.toggle('flagged', !!flags[questions[i].id]);
dot.setAttribute('aria-current', isCurrent ? 'step' : 'false');
});
}
@@ -742,6 +757,8 @@
const btn = document.getElementById(`btn-flag-${qid}`);
if (btn) {
btn.classList.toggle('flagged', flags[qid]);
btn.setAttribute('aria-pressed', flags[qid] ? 'true' : 'false');
btn.setAttribute('aria-label', flags[qid] ? 'Снять отметку' : 'Отметить для проверки');
btn.querySelector('svg').setAttribute('fill', flags[qid] ? '#f59e0b' : 'none');
btn.querySelector('svg').setAttribute('stroke', flags[qid] ? '#f59e0b' : 'currentColor');
}
+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()">
+45 -18
View File
@@ -338,12 +338,19 @@ function lsToast(message, type = 'info', duration = 3500) {
document.head.appendChild(s);
}
let wrap = document.getElementById('ls-toast-wrap');
if (!wrap) { wrap = document.createElement('div'); wrap.id = 'ls-toast-wrap'; document.body.appendChild(wrap); }
if (!wrap) {
wrap = document.createElement('div');
wrap.id = 'ls-toast-wrap';
wrap.setAttribute('aria-live', 'polite');
wrap.setAttribute('aria-atomic', 'false');
document.body.appendChild(wrap);
}
const _tIcons = { success: 'check-circle', error: 'x-close', info: 'info', warn: 'warning' };
const el = document.createElement('div');
el.className = `ls-toast ${type}`;
el.innerHTML = `<span class="ls-toast-icon">${lsIcon(_tIcons[type] || 'info', 18)}</span><span class="ls-toast-msg"></span><button class="ls-toast-close" onclick="this.closest('.ls-toast').remove()">${lsIcon('x-close', 14)}</button>`;
el.setAttribute('role', type === 'error' ? 'alert' : 'status');
el.innerHTML = `<span class="ls-toast-icon">${lsIcon(_tIcons[type] || 'info', 18)}</span><span class="ls-toast-msg"></span><button class="ls-toast-close" aria-label="Закрыть уведомление" onclick="this.closest('.ls-toast').remove()">${lsIcon('x-close', 14)}</button>`;
el.querySelector('.ls-toast-msg').textContent = message;
wrap.appendChild(el);
requestAnimationFrame(() => requestAnimationFrame(() => el.classList.add('show')));
@@ -417,10 +424,10 @@ function lsConfirm(message, { title = 'Подтверждение', confirmText
.ls-title{font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:#0F172A;margin-bottom:10px;}
.ls-msg{font-size:0.88rem;color:#3D4F6B;line-height:1.65;white-space:pre-line;margin-bottom:28px;}
.ls-btns{display:flex;gap:10px;justify-content:center;}
.ls-cancel{padding:10px 26px;border:1.5px solid rgba(15,23,42,0.2);border-radius:999px;background:transparent;
font-family:'Manrope',sans-serif;font-size:0.88rem;font-weight:600;color:#8898AA;cursor:pointer;transition:all .2s;}
.ls-cancel{padding:10px 26px;min-height:44px;border:1.5px solid rgba(15,23,42,0.2);border-radius:999px;background:transparent;
font-family:'Manrope',sans-serif;font-size:0.88rem;font-weight:600;color:#56687A;cursor:pointer;transition:all .2s;}
.ls-cancel:hover{border-color:#9B5DE5;color:#9B5DE5;}
.ls-ok{padding:10px 28px;border:none;border-radius:999px;color:#fff;
.ls-ok{padding:10px 28px;min-height:44px;border:none;border-radius:999px;color:#fff;
font-family:'Manrope',sans-serif;font-size:0.88rem;font-weight:700;cursor:pointer;transition:opacity .2s;
background:linear-gradient(135deg,#06D6E0,#9B5DE5);}
.ls-ok.danger{background:linear-gradient(135deg,#F15BB5,#9B5DE5);}
@@ -429,13 +436,16 @@ function lsConfirm(message, { title = 'Подтверждение', confirmText
document.head.appendChild(s);
}
const prevFocus = document.activeElement;
const el = document.createElement('div');
el.className = 'ls-ov';
el.setAttribute('tabindex', '-1');
el.setAttribute('role', 'dialog');
el.setAttribute('aria-modal', 'true');
el.setAttribute('aria-labelledby', 'ls-dlg-title');
el.innerHTML = `
<div class="ls-box">
<div class="ls-icon">${danger ? lsIcon('trash', 36) : lsIcon('help-circle', 36)}</div>
<div class="ls-title"></div>
<div class="ls-title" id="ls-dlg-title"></div>
<div class="ls-msg"></div>
<div class="ls-btns">
<button class="ls-cancel">Отмена</button>
@@ -450,7 +460,7 @@ function lsConfirm(message, { title = 'Подтверждение', confirmText
const done = result => {
el.classList.remove('open');
setTimeout(() => el.remove(), 230);
setTimeout(() => { el.remove(); prevFocus?.focus(); }, 230);
resolve(result);
};
@@ -458,10 +468,18 @@ function lsConfirm(message, { title = 'Подтверждение', confirmText
el.querySelector('.ls-ok').onclick = () => done(true);
el.addEventListener('click', e => { if (e.target === el) done(false); });
el.addEventListener('keydown', e => {
if (e.key === 'Enter') done(true);
if (e.key === 'Escape') done(false);
if (e.key === 'Tab') {
const btns = [...el.querySelectorAll('button')];
if (e.shiftKey && document.activeElement === btns[0]) {
e.preventDefault(); btns[btns.length - 1].focus();
} else if (!e.shiftKey && document.activeElement === btns[btns.length - 1]) {
e.preventDefault(); btns[0].focus();
}
}
if (e.key === 'Enter') { e.preventDefault(); done(true); }
if (e.key === 'Escape') { e.preventDefault(); done(false); }
});
setTimeout(() => el.focus(), 10);
setTimeout(() => el.querySelector('.ls-cancel').focus(), 10);
});
}
@@ -1110,7 +1128,7 @@ async function adminGamGetUser(id) { return req('GET', `/gamifi
.ls-live-opt.selected .ls-live-opt-key{background:#06D6E0;color:#fff;}
.ls-live-opt.correct .ls-live-opt-key{background:#06D6A0;color:#fff;}
.ls-live-opt.wrong .ls-live-opt-key{background:#EF476F;color:#fff;}
.ls-live-status{text-align:center;font-size:.84rem;color:#8898AA;padding:8px 0;}
.ls-live-status{text-align:center;font-size:.84rem;color:var(--text-3);padding:8px 0;}
.ls-live-result-bar-wrap{margin:4px 0;}
.ls-live-result-bar{height:8px;border-radius:99px;background:#E2E8F0;margin-top:4px;overflow:hidden;}
.ls-live-result-fill{height:100%;border-radius:99px;background:#9B5DE5;transition:width .6s ease;}
@@ -1120,11 +1138,14 @@ async function adminGamGetUser(id) { return req('GET', `/gamifi
const el = document.createElement('div');
el.id = 'ls-live-overlay';
el.setAttribute('role', 'dialog');
el.setAttribute('aria-modal', 'true');
el.setAttribute('aria-labelledby', 'lslq-text');
el.innerHTML = `<div class="ls-live-box">
<div class="ls-live-badge"><svg class="ic" viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg> Live Quiz</div>
<div class="ls-live-badge" aria-hidden="true"><svg class="ic" viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg> Live Quiz</div>
<div class="ls-live-q" id="lslq-text"></div>
<div class="ls-live-opts" id="lslq-opts"></div>
<div class="ls-live-status" id="lslq-status"></div>
<div class="ls-live-opts" id="lslq-opts" role="radiogroup" aria-label="Варианты ответа"></div>
<div class="ls-live-status" id="lslq-status" aria-live="polite"></div>
</div>`;
const styleEl = document.createElement('style');
@@ -1168,8 +1189,10 @@ async function adminGamGetUser(id) { return req('GET', `/gamifi
document.getElementById('lslq-text').innerHTML = _mathHtml(question.text);
const keys = 'АБВГДЕ';
document.getElementById('lslq-opts').innerHTML = (options || []).map((o, i) => `
<div class="ls-live-opt" data-id="${o.id}" onclick="window._lsLiveAnswer(${liveId},${o.id},this)">
<span class="ls-live-opt-key">${keys[i] || i+1}</span>
<div class="ls-live-opt" role="radio" aria-checked="false" tabindex="0" data-id="${o.id}"
onclick="window._lsLiveAnswer(${liveId},${o.id},this)"
onkeydown="if(event.key===' '||event.key==='Enter'){event.preventDefault();window._lsLiveAnswer(${liveId},${o.id},this)}">
<span class="ls-live-opt-key" aria-hidden="true">${keys[i] || i+1}</span>
<span>${_mathHtml(o.text)}</span>
</div>`).join('');
document.getElementById('lslq-status').textContent = 'Выберите ответ';
@@ -1201,8 +1224,12 @@ async function adminGamGetUser(id) { return req('GET', `/gamifi
window._lsLiveAnswer = async function(liveId, optionId, el) {
if (answered) return;
answered = true;
document.querySelectorAll('.ls-live-opt').forEach(o => { o.onclick = null; o.style.cursor = 'default'; });
document.querySelectorAll('.ls-live-opt').forEach(o => {
o.onclick = null; o.onkeydown = null; o.style.cursor = 'default';
o.setAttribute('aria-checked', 'false');
});
el.classList.add('selected');
el.setAttribute('aria-checked', 'true');
document.getElementById('lslq-status').innerHTML = 'Ответ отправлен <svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
try {
const r = await apiFetch(`/api/live/${liveId}/answer`, { method: 'POST', body: JSON.stringify({ option_id: optionId }) });