bc22715734
Новый общий компонент LS.modal (api.js) — companion к LS.confirm.
Универсальная form/content-модалка с консистентным поведением:
LS.modal({
title, content, size: 'sm'|'md'|'lg',
actions: [{label, primary, danger, onClick}],
onClose,
});
// Returns { close, root, body, setBody, setActions, setError }
Стандартное поведение:
- ESC и backdrop-click закрывают (опциональный dismissible:false)
- z-index 9000 (тот же что LS.confirm — без конфликтов)
- Auto-focus первого input/select/textarea/button в body
- prevFocus restore при закрытии
- Анимация scale+translateY .22s
- Адаптив: на мобилках padding уменьшается
CSS-классы .ls-mov / .ls-mod / .ls-mod-hdr / .ls-mod-body / .ls-mod-act
впрыскиваются один раз из api.js (id=ls-modal-style), как и стили
toast/confirm.
Миграция exam9 «Назначить вариант»:
- Убран inline <div class="ex-overlay" id="assign-overlay">…</div>
- Убраны .ax-actions, .ax-btn, .ax-btn-primary, .ax-error, .ax-success
CSS (теперь в общих .ls-mod-* стилях)
- openAssignModal → LS.modal({ title, content: form, actions: [...] })
- Удалены closeAssignModal/onAssignOverlayClick/onAssignEsc — теперь
handle'ит LS.modal
- Удалена unused переменная assignVariantNum (closure теперь над varNum)
exam9.html: −53 строк (CSS + HTML модалки)
app.js: переписан 90 строк → 70 строк
Миграция my-students «Убрать ученика»:
- native confirm() → LS.confirm() с danger-стилизацией
- alert() → LS.toast() для согласованности
Сохранён классroom-овский «ex-overlay»/«ex-panel» CSS (используется
для picker'а вариантов в exam9). Не трогаем classroom.html — у него
своя ecosystem cr-*-overlay.
Дальше — postupенная миграция модалок в textbooks/classes/admin
по мере касания этих страниц. Шаблон установлен.
383 lines
17 KiB
HTML
383 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Экзамен 9 класс — Математика — LearnSpace</title>
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;700;800&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="/css/ls.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" />
|
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
|
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" onload="onKatexLoad()"></script>
|
|
<style>
|
|
.sb-content { padding: 0; overflow-y: auto; }
|
|
.ex-wrap { max-width: 920px; margin: 0 auto; padding: 32px 24px 80px; width: 100%; }
|
|
|
|
/* ── Header ── */
|
|
.ex-header { display:flex; align-items:center; gap:14px; margin-bottom:24px; flex-wrap:wrap; }
|
|
.ex-icon {
|
|
width:52px; height:52px; border-radius:14px; flex-shrink:0;
|
|
background: linear-gradient(135deg, rgba(155,93,229,.25), rgba(6,214,224,.18));
|
|
border:1.5px solid rgba(255,255,255,.1);
|
|
display:flex; align-items:center; justify-content:center;
|
|
}
|
|
.ex-icon svg { width:26px; height:26px; stroke:#9B5DE5; stroke-width:1.8; fill:none; }
|
|
.ex-title { font-family:'Unbounded',sans-serif; font-size:1.35rem; font-weight:800; letter-spacing:-.02em; }
|
|
.ex-sub { font-size:.82rem; color:var(--text-2); margin-top:2px; }
|
|
.ex-header-right { margin-left:auto; display:flex; align-items:center; gap:8px; }
|
|
|
|
.ex-picker-btn {
|
|
display:flex; align-items:center; gap:8px;
|
|
padding:9px 16px; border-radius:10px;
|
|
border:1.5px solid var(--border-h); background:var(--surface); color:var(--text);
|
|
font-family:'Manrope',sans-serif; font-size:.88rem; font-weight:700;
|
|
cursor:pointer; transition:all .15s; white-space:nowrap;
|
|
}
|
|
.ex-picker-btn:hover { border-color:var(--violet); color:var(--violet); }
|
|
.ex-picker-btn .ex-chev { width:14px; height:14px; transition:transform .2s; }
|
|
.ex-picker-btn.open .ex-chev { transform:rotate(180deg); }
|
|
|
|
/* ── Picker overlay ── */
|
|
.ex-overlay {
|
|
display:none; position:fixed; inset:0;
|
|
background:rgba(15,23,42,.55); z-index:300;
|
|
align-items:flex-start; justify-content:center; padding-top:80px;
|
|
backdrop-filter:blur(2px);
|
|
}
|
|
.ex-overlay.visible { display:flex; }
|
|
.ex-panel {
|
|
background:var(--surface); border:1.5px solid var(--border);
|
|
border-radius:16px; box-shadow:0 24px 64px rgba(0,0,0,.32);
|
|
width:min(680px, 94vw); max-height:calc(100vh - 120px);
|
|
overflow-y:auto; padding:22px 22px 26px;
|
|
animation: panelIn .18s ease;
|
|
}
|
|
@keyframes panelIn {
|
|
from { opacity:0; transform:translateY(-12px); }
|
|
to { opacity:1; transform:translateY(0); }
|
|
}
|
|
.ex-panel-head {
|
|
display:flex; align-items:center; justify-content:space-between; margin-bottom:18px;
|
|
}
|
|
.ex-panel-head h2 { font-family:'Unbounded',sans-serif; font-size:1rem; font-weight:800; }
|
|
.ex-panel-close {
|
|
width:32px; height:32px; border:none; background:none;
|
|
color:var(--text-2); cursor:pointer; border-radius:8px;
|
|
display:flex; align-items:center; justify-content:center; transition:background .15s;
|
|
}
|
|
.ex-panel-close:hover { background:var(--border); color:var(--text); }
|
|
.ex-panel-close svg { width:18px; height:18px; }
|
|
|
|
/* ── Variants grid ── */
|
|
.ex-grid {
|
|
display:grid; grid-template-columns:repeat(10, 1fr); gap:6px;
|
|
}
|
|
.vg-btn {
|
|
aspect-ratio:1; border:1.5px solid var(--border-h); border-radius:8px;
|
|
background:transparent; color:var(--text-2);
|
|
font-family:'Manrope',sans-serif; font-size:.82rem; font-weight:700;
|
|
cursor:pointer; transition:all .12s; line-height:1;
|
|
}
|
|
.vg-btn:hover { border-color:var(--violet); color:var(--violet); }
|
|
.vg-btn.active { background:var(--violet); border-color:var(--violet); color:#fff; }
|
|
.vg-btn.done { background:rgba(6,214,160,.15); border-color:#06D6A0; color:#06D6A0; }
|
|
.vg-btn.done:hover { background:rgba(6,214,160,.25); }
|
|
.vg-btn.done.active { background:#06D6A0; border-color:#06D6A0; color:#fff; }
|
|
.vg-btn.partial { background:rgba(248,150,30,.13); border-color:#F8961E; color:#F8961E; }
|
|
.vg-btn.partial:hover { background:rgba(248,150,30,.22); }
|
|
|
|
@media (max-width: 540px) {
|
|
.ex-grid { grid-template-columns:repeat(8, 1fr); }
|
|
}
|
|
|
|
/* ── Variant header + tasks ── */
|
|
.variant-title {
|
|
font-family:'Unbounded',sans-serif; font-size:1.45rem; font-weight:800;
|
|
letter-spacing:-.02em; margin-bottom:22px;
|
|
}
|
|
.variant-title small {
|
|
font-family:'Manrope',sans-serif; font-size:.85rem; font-weight:500;
|
|
color:var(--text-2); margin-left:10px;
|
|
}
|
|
|
|
.task-card {
|
|
background:var(--surface); border:1.5px solid var(--border);
|
|
border-radius:14px; margin-bottom:16px; overflow:hidden;
|
|
transition:border-color .15s;
|
|
}
|
|
.task-card:hover { border-color:var(--border-h); }
|
|
|
|
.task-header {
|
|
display:flex; align-items:center; gap:12px;
|
|
padding:12px 22px; background:rgba(155,93,229,.04);
|
|
border-bottom:1.5px solid var(--border);
|
|
}
|
|
.task-num {
|
|
width:30px; height:30px; border-radius:50%;
|
|
background:var(--violet); color:#fff;
|
|
font-family:'Unbounded',sans-serif; font-size:.82rem; font-weight:800;
|
|
display:flex; align-items:center; justify-content:center; flex-shrink:0;
|
|
}
|
|
.task-label {
|
|
font-family:'Unbounded',sans-serif; font-size:.85rem; font-weight:700;
|
|
color:var(--text-2); letter-spacing:.02em;
|
|
}
|
|
|
|
.task-body { padding:20px 26px; font-size:1rem; line-height:1.8; }
|
|
.task-text .katex-display { margin:14px 0 8px; overflow-x:auto; }
|
|
|
|
.opts {
|
|
display:flex; flex-wrap:wrap; gap:10px 32px;
|
|
margin-top:16px; padding-top:14px; border-top:1px solid var(--border);
|
|
align-items:center;
|
|
}
|
|
.opts-vertical {
|
|
display:flex; flex-direction:column; gap:8px;
|
|
margin-top:14px; padding-top:14px; border-top:1px solid var(--border);
|
|
}
|
|
.opt { display:inline-flex; align-items:flex-start; gap:6px; }
|
|
.opt-lbl {
|
|
font-family:'Unbounded',sans-serif; font-weight:800;
|
|
color:var(--violet); font-size:.92rem; white-space:nowrap;
|
|
}
|
|
|
|
.task-figure { margin:14px 0 4px; }
|
|
.task-fig { max-width:100%; height:auto; border-radius:6px; }
|
|
|
|
/* ── Solutions ── */
|
|
.sol-wrap { padding:0 22px 16px; }
|
|
.sol-btn {
|
|
display:inline-flex; align-items:center; gap:7px;
|
|
padding:6px 14px; border:1.5px solid #06D6A0; border-radius:8px;
|
|
background:transparent; color:#06D6A0;
|
|
font-family:'Manrope',sans-serif; font-size:.85rem; font-weight:700;
|
|
cursor:pointer; transition:all .15s;
|
|
}
|
|
.sol-btn:hover { background:rgba(6,214,160,.12); }
|
|
.sol-btn.open { background:#06D6A0; border-color:#06D6A0; color:#fff; }
|
|
.sol-btn svg { width:13px; height:13px; transition:transform .2s; }
|
|
.sol-btn.open svg { transform:rotate(90deg); }
|
|
|
|
.sol-panel {
|
|
display:none; margin-top:14px; padding:18px 22px;
|
|
background:rgba(6,214,160,.06); border-radius:10px;
|
|
border-left:3px solid #06D6A0;
|
|
line-height:1.85; font-size:.96rem;
|
|
}
|
|
.sol-panel.visible { display:block; }
|
|
.sol-ans {
|
|
display:inline-block; margin-top:12px; padding:4px 14px;
|
|
background:rgba(6,214,160,.2); border-radius:6px;
|
|
font-family:'Unbounded',sans-serif; font-weight:800; color:#06D6A0;
|
|
}
|
|
.sol-panel ul { margin:6px 0 6px 22px; }
|
|
.sol-panel li { margin:3px 0; }
|
|
.sol-panel .katex-display { margin:10px 0 6px; overflow-x:auto; }
|
|
|
|
/* Empty state */
|
|
.ex-empty {
|
|
padding:60px 20px; text-align:center; color:var(--text-3);
|
|
}
|
|
.ex-empty svg { width:48px; height:48px; opacity:.5; margin-bottom:14px; stroke:var(--text-3); }
|
|
|
|
/* ── Assign button + modal ── */
|
|
.ex-assign-row {
|
|
display:flex; align-items:center; gap:12px; margin-bottom:22px; flex-wrap:wrap;
|
|
}
|
|
.ex-assign-btn {
|
|
display:inline-flex; align-items:center; gap:7px;
|
|
padding:8px 16px; border:1.5px solid var(--violet); border-radius:10px;
|
|
background:rgba(155,93,229,.08); color:var(--violet);
|
|
font-family:'Manrope',sans-serif; font-size:.85rem; font-weight:700;
|
|
cursor:pointer; transition:all .15s;
|
|
}
|
|
.ex-assign-btn:hover { background:var(--violet); color:#fff; }
|
|
.ex-assign-btn:disabled {
|
|
opacity:.5; cursor:not-allowed; background:transparent; color:var(--text-3); border-color:var(--border);
|
|
}
|
|
.ex-assign-btn svg { width:14px; height:14px; }
|
|
.ex-assign-note {
|
|
font-size:.78rem; color:var(--text-3);
|
|
}
|
|
|
|
.ax-form { display:flex; flex-direction:column; gap:14px; }
|
|
.ax-field label {
|
|
display:block; font-size:.78rem; font-weight:700; color:var(--text-2);
|
|
text-transform:uppercase; letter-spacing:.05em; margin-bottom:6px;
|
|
}
|
|
.ax-classes {
|
|
display:flex; flex-direction:column; gap:6px; max-height:240px; overflow-y:auto;
|
|
border:1.5px solid var(--border); border-radius:10px; padding:8px;
|
|
}
|
|
.ax-class {
|
|
display:flex; align-items:center; gap:10px; padding:8px 10px;
|
|
border-radius:8px; cursor:pointer; transition:background .12s;
|
|
font-size:.9rem;
|
|
}
|
|
.ax-class:hover { background:var(--border); }
|
|
.ax-class input { accent-color:var(--violet); flex-shrink:0; }
|
|
.ax-class .ax-cname { font-weight:600; }
|
|
.ax-class .ax-cmeta { font-size:.78rem; color:var(--text-3); margin-left:auto; }
|
|
.ax-input {
|
|
width:100%; padding:9px 12px; border:1.5px solid var(--border-h);
|
|
border-radius:9px; background:var(--surface); color:var(--text);
|
|
font-family:'Manrope',sans-serif; font-size:.9rem;
|
|
}
|
|
.ax-input:focus { outline:none; border-color:var(--violet); }
|
|
@media (max-width: 600px) {
|
|
.ex-wrap { padding:20px 16px 60px; }
|
|
.ex-title { font-size:1.15rem; }
|
|
.task-body { padding:16px 18px; }
|
|
.sol-wrap { padding:0 18px 14px; }
|
|
.sol-panel { padding:14px 16px; }
|
|
.opts { gap:8px 18px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app-layout">
|
|
<aside class="sidebar" id="app-sidebar"></aside>
|
|
|
|
<div class="sb-content">
|
|
<div class="ex-wrap">
|
|
|
|
<header class="ex-header">
|
|
<div class="ex-icon">
|
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="9" y="2" width="6" height="4" rx="1"/>
|
|
<path d="M9 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-3"/>
|
|
<path d="m9 14 2 2 4-4"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="ex-title">Экзамен 9 класс — Математика</div>
|
|
<div class="ex-sub">2025 · 80 вариантов · решения с разбором</div>
|
|
</div>
|
|
<div class="ex-header-right">
|
|
<button class="ex-picker-btn" id="picker-btn" onclick="togglePicker()">
|
|
<span id="picker-label">Выбрать вариант</span>
|
|
<svg class="ex-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="ex-main">
|
|
<div class="ex-empty">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><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"/></svg>
|
|
<div>Загрузка вариантов…</div>
|
|
</div>
|
|
</main>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ex-overlay" id="picker-overlay" onclick="onOverlayClick(event)">
|
|
<div class="ex-panel" onclick="event.stopPropagation()">
|
|
<div class="ex-panel-head">
|
|
<h2>Выберите вариант</h2>
|
|
<button class="ex-panel-close" onclick="closePicker()" title="Закрыть">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="ex-grid" id="variant-grid"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
|
<script src="/js/api.js"></script>
|
|
<script src="/js/sidebar.js"></script>
|
|
<script src="/js/notifications.js"></script>
|
|
<script src="/js/search.js"></script>
|
|
<script src="/js/mobile.js"></script>
|
|
<script>
|
|
LS.initPage();
|
|
LS.showBoardIfAllowed();
|
|
LS.hideDisabledFeatures();
|
|
window.VARIANTS = {};
|
|
</script>
|
|
<script src="/js/exam9/variants/v01.js"></script>
|
|
<script src="/js/exam9/variants/v02.js"></script>
|
|
<script src="/js/exam9/variants/v03.js"></script>
|
|
<script src="/js/exam9/variants/v04.js"></script>
|
|
<script src="/js/exam9/variants/v05.js"></script>
|
|
<script src="/js/exam9/variants/v06.js"></script>
|
|
<script src="/js/exam9/variants/v07.js"></script>
|
|
<script src="/js/exam9/variants/v08.js"></script>
|
|
<script src="/js/exam9/variants/v09.js"></script>
|
|
<script src="/js/exam9/variants/v10.js"></script>
|
|
<script src="/js/exam9/variants/v11.js"></script>
|
|
<script src="/js/exam9/variants/v12.js"></script>
|
|
<script src="/js/exam9/variants/v13.js"></script>
|
|
<script src="/js/exam9/variants/v14.js"></script>
|
|
<script src="/js/exam9/variants/v15.js"></script>
|
|
<script src="/js/exam9/variants/v16.js"></script>
|
|
<script src="/js/exam9/variants/v17.js"></script>
|
|
<script src="/js/exam9/variants/v18.js"></script>
|
|
<script src="/js/exam9/variants/v19.js"></script>
|
|
<script src="/js/exam9/variants/v20.js"></script>
|
|
<script src="/js/exam9/variants/v21.js"></script>
|
|
<script src="/js/exam9/variants/v22.js"></script>
|
|
<script src="/js/exam9/variants/v23.js"></script>
|
|
<script src="/js/exam9/variants/v24.js"></script>
|
|
<script src="/js/exam9/variants/v25.js"></script>
|
|
<script src="/js/exam9/variants/v26.js"></script>
|
|
<script src="/js/exam9/variants/v27.js"></script>
|
|
<script src="/js/exam9/variants/v28.js"></script>
|
|
<script src="/js/exam9/variants/v29.js"></script>
|
|
<script src="/js/exam9/variants/v30.js"></script>
|
|
<script src="/js/exam9/variants/v31.js"></script>
|
|
<script src="/js/exam9/variants/v32.js"></script>
|
|
<script src="/js/exam9/variants/v33.js"></script>
|
|
<script src="/js/exam9/variants/v34.js"></script>
|
|
<script src="/js/exam9/variants/v35.js"></script>
|
|
<script src="/js/exam9/variants/v36.js"></script>
|
|
<script src="/js/exam9/variants/v37.js"></script>
|
|
<script src="/js/exam9/variants/v38.js"></script>
|
|
<script src="/js/exam9/variants/v39.js"></script>
|
|
<script src="/js/exam9/variants/v40.js"></script>
|
|
<script src="/js/exam9/variants/v41.js"></script>
|
|
<script src="/js/exam9/variants/v42.js"></script>
|
|
<script src="/js/exam9/variants/v43.js"></script>
|
|
<script src="/js/exam9/variants/v44.js"></script>
|
|
<script src="/js/exam9/variants/v45.js"></script>
|
|
<script src="/js/exam9/variants/v46.js"></script>
|
|
<script src="/js/exam9/variants/v47.js"></script>
|
|
<script src="/js/exam9/variants/v48.js"></script>
|
|
<script src="/js/exam9/variants/v49.js"></script>
|
|
<script src="/js/exam9/variants/v50.js"></script>
|
|
<script src="/js/exam9/variants/v51.js"></script>
|
|
<script src="/js/exam9/variants/v52.js"></script>
|
|
<script src="/js/exam9/variants/v53.js"></script>
|
|
<script src="/js/exam9/variants/v54.js"></script>
|
|
<script src="/js/exam9/variants/v55.js"></script>
|
|
<script src="/js/exam9/variants/v56.js"></script>
|
|
<script src="/js/exam9/variants/v57.js"></script>
|
|
<script src="/js/exam9/variants/v58.js"></script>
|
|
<script src="/js/exam9/variants/v59.js"></script>
|
|
<script src="/js/exam9/variants/v60.js"></script>
|
|
<script src="/js/exam9/variants/v61.js"></script>
|
|
<script src="/js/exam9/variants/v62.js"></script>
|
|
<script src="/js/exam9/variants/v63.js"></script>
|
|
<script src="/js/exam9/variants/v64.js"></script>
|
|
<script src="/js/exam9/variants/v65.js"></script>
|
|
<script src="/js/exam9/variants/v66.js"></script>
|
|
<script src="/js/exam9/variants/v67.js"></script>
|
|
<script src="/js/exam9/variants/v68.js"></script>
|
|
<script src="/js/exam9/variants/v69.js"></script>
|
|
<script src="/js/exam9/variants/v70.js"></script>
|
|
<script src="/js/exam9/variants/v71.js"></script>
|
|
<script src="/js/exam9/variants/v72.js"></script>
|
|
<script src="/js/exam9/variants/v73.js"></script>
|
|
<script src="/js/exam9/variants/v74.js"></script>
|
|
<script src="/js/exam9/variants/v75.js"></script>
|
|
<script src="/js/exam9/variants/v76.js"></script>
|
|
<script src="/js/exam9/variants/v77.js"></script>
|
|
<script src="/js/exam9/variants/v78.js"></script>
|
|
<script src="/js/exam9/variants/v79.js"></script>
|
|
<script src="/js/exam9/variants/v80.js"></script>
|
|
<script src="/js/exam9/app.js"></script>
|
|
</body>
|
|
</html>
|