feat(labs): новая симуляция «Гонка с задачами» — кинематика 1D с геймификацией

race.js (1357 строк):
- 8 сценариев: встречи (поезд+машина, 2 лодки), догон (мотоциклист, поезда), кто первый (авто vs поезд, 3 спортсмена), свободное падение vs парашют, обгон с разгоном
- Иконки movers inline SVG: car, train, bike, moto, runner, ball, boat
- Аналитический поиск точки встречи: линейный + квадратный + численный (если задержка)
- Стробоскоп положений каждые 0.5-1 с
- Canvas-графики x(t) и v(t) с маркером встречи (красная точка + бейдж)
- Проверка ответа с tolerance ±5%, verdict зелёный/красный
- Слайдеры x₀/v₀/a для каждого мовера + кнопка 'Сброс к сценарию'
- Stats bar 5 ячеек: Время, t_встречи, x_встречи, Лидер, Расстояние между

UI (lab.html):
- Sticky quick-bar: Старт/Пауза/Сброс
- Карточка вопроса вверху + answer-bar внизу с input + verdict
- Collapsible-секции (race-acc): Параметры мовера 1, 2, 3, Настройки

Интеграция:
- lab-init.js: 'sim-race' в ALL_SIM_BODIES + роутинг _openRace
- admin/sims.js: запись в ADMIN_SIMS (cat: Физика, title: 'Гонка с задачами')
- lab-glue.js: P_RACE preset с SVG-превью (дорожка + кривые x(t))
- lab.css: ~200 строк стилей .race-* по паттерну elec/geo/dyn-acc
This commit is contained in:
Maxim Dolgolyov
2026-05-26 19:49:08 +03:00
parent 218baef4ad
commit af46290ca3
6 changed files with 1688 additions and 0 deletions
+306
View File
@@ -2293,3 +2293,309 @@ canvas[data-draggable]:active { cursor: grabbing; }
border-color: #06D6E0 !important;
box-shadow: 0 0 8px rgba(6,214,224,0.3);
}
/* ═══════════════════════════════════════════════════════════
RACE SIM — Гонка с задачами (кинематика 1D)
═══════════════════════════════════════════════════════════ */
.race-sim-host {
flex-direction: column;
}
.race-root {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: #0b0b1a;
font-family: 'Manrope', system-ui, sans-serif;
overflow: hidden;
}
/* Question bar */
.race-question-bar {
flex: 0 0 auto;
padding: 10px 16px 8px;
background: rgba(155,93,229,0.07);
border-bottom: 1px solid rgba(155,93,229,0.18);
font-size: .88rem;
color: rgba(255,255,255,0.88);
line-height: 1.45;
}
.race-question-text {
display: block;
}
/* Body: panel + scene */
.race-body {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
/* Left panel */
.race-panel {
width: 260px;
flex: 0 0 260px;
overflow-y: auto;
background: rgba(255,255,255,0.018);
border-right: 1px solid var(--border);
padding: 8px 10px 12px;
font-size: .82rem;
display: flex;
flex-direction: column;
gap: 0;
}
/* Quick bar */
.race-quick-bar {
display: flex;
gap: 6px;
margin-bottom: 8px;
}
.race-quick-bar .mag-mode-btn {
flex: 1;
font-size: .80rem;
padding: 8px 4px;
font-weight: 600;
}
/* Accordion */
.race-acc {
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 6px;
background: rgba(255,255,255,0.02);
flex: 0 0 auto;
}
.race-acc > summary {
cursor: pointer;
list-style: none;
padding: 9px 12px 9px 30px;
font-family: 'Unbounded', sans-serif;
font-size: .78rem;
font-weight: 700;
color: var(--text);
letter-spacing: .04em;
text-transform: uppercase;
position: relative;
user-select: none;
transition: background .15s;
border-radius: 10px;
}
.race-acc[open] > summary { border-radius: 10px 10px 0 0; }
.race-acc > summary:hover { background: rgba(255,255,255,0.04); }
.race-acc > summary::-webkit-details-marker { display: none; }
.race-acc > summary::before {
content: '';
position: absolute;
left: 12px;
top: 50%;
width: 0;
height: 0;
border-left: 5px solid currentColor;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
transform: translateY(-50%);
transition: transform .18s;
opacity: .65;
}
.race-acc[open] > summary::before {
transform: translateY(-50%) rotate(90deg);
}
.race-acc-body {
padding: 8px 10px 10px;
border-top: 1px solid var(--border);
}
/* Scenario cards */
.race-scenarios-list {
display: flex;
flex-direction: column;
gap: 5px;
}
.race-scene-card {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 10px;
border-radius: 8px;
border: 1.5px solid transparent;
background: rgba(255,255,255,0.025);
cursor: pointer;
transition: border-color .15s, background .15s;
}
.race-scene-card:hover {
background: rgba(255,255,255,0.05);
border-color: rgba(155,93,229,0.25);
}
.race-scene-card.active {
border-color: rgba(155,93,229,0.55);
background: rgba(155,93,229,0.1);
}
.race-scene-icons {
display: flex;
gap: 3px;
flex-shrink: 0;
}
.race-scene-title {
font-size: .78rem;
font-weight: 600;
color: var(--text);
line-height: 1.3;
}
/* Mover param card */
.race-mover-card {
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 10px;
margin-bottom: 8px;
background: rgba(255,255,255,0.02);
}
.race-mover-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: .80rem;
}
/* Scene: canvas + graph + answer */
.race-scene-wrap {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
.race-canvas-outer {
flex: 1;
position: relative;
min-height: 0;
overflow: hidden;
}
.race-track-canvas {
display: block;
width: 100%;
height: 100%;
}
/* Graph area */
.race-graph-wrap {
flex: 0 0 120px;
border-top: 1px solid var(--border);
background: rgba(8,8,20,0.92);
overflow: hidden;
}
.race-graph-canvas {
display: block;
width: 100%;
height: 100%;
}
/* Answer bar */
.race-answer-bar {
flex: 0 0 auto;
padding: 10px 16px;
border-top: 1px solid var(--border);
background: rgba(255,255,255,0.018);
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.race-answer-inputs {
display: flex;
flex-wrap: wrap;
gap: 10px;
flex: 1;
}
.race-ans-label {
font-size: .82rem;
color: rgba(255,255,255,0.75);
display: flex;
align-items: center;
gap: 6px;
}
.race-ans-inp {
width: 80px;
padding: 5px 8px;
background: rgba(255,255,255,0.06);
border: 1px solid var(--border-h);
border-radius: 6px;
color: var(--text);
font-family: 'Manrope', sans-serif;
font-size: .85rem;
}
.race-ans-inp:focus {
outline: none;
border-color: rgba(155,93,229,0.55);
background: rgba(155,93,229,0.07);
}
.race-answer-btns {
display: flex;
gap: 8px;
}
.race-check-btn {
background: rgba(155,93,229,0.15) !important;
border-color: rgba(155,93,229,0.4) !important;
color: var(--violet) !important;
}
.race-check-btn:hover {
background: rgba(155,93,229,0.28) !important;
}
/* Verdict */
.race-verdict {
width: 100%;
padding: 7px 12px;
border-radius: 8px;
font-size: .83rem;
line-height: 1.5;
}
.race-verdict-ok {
background: rgba(74,222,128,0.12);
border: 1px solid rgba(74,222,128,0.35);
color: #4ADE80;
}
.race-verdict-err {
background: rgba(239,71,111,0.12);
border: 1px solid rgba(239,71,111,0.35);
color: #EF476F;
}
.race-ok { color: #4ADE80; margin-right: 8px; }
.race-err { color: #EF476F; margin-right: 8px; }
/* Stats bar */
.race-stats-bar {
flex: 0 0 auto;
display: flex;
gap: 0;
border-top: 1px solid var(--border);
background: rgba(255,255,255,0.02);
}
.race-stats-bar .pstat {
flex: 1;
text-align: center;
padding: 6px 4px;
border-right: 1px solid var(--border);
min-width: 0;
}
.race-stats-bar .pstat:last-child { border-right: none; }
.race-stats-bar .pstat-label {
font-size: .68rem;
color: rgba(255,255,255,0.4);
text-transform: uppercase;
letter-spacing: .04em;
margin-bottom: 2px;
}
.race-stats-bar .pstat-val {
font-size: .82rem;
font-weight: 700;
font-family: 'Unbounded', sans-serif;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}