113 lines
5.4 KiB
JavaScript
113 lines
5.4 KiB
JavaScript
'use strict';
|
||
/* ════════════════════════════════════════════════════════════════════════
|
||
_sim_demo — рукописная демо-спека «Бросок тела» (projectile) для проверки
|
||
рантайма Фазы 0. Регистрируется как id 'customdemo' ТОЛЬКО за флагом — не
|
||
светится ученикам в каталоге (карточка не добавляется в SIMS).
|
||
|
||
Включение для проверки (любой из вариантов):
|
||
- URL: /lab?simdemo=1 (или ?sim=customdemo прямой deep-link)
|
||
- глоб: window.LAB_SHOW_SPEC_DEMO = true (до загрузки labs-скриптов)
|
||
- localStorage.setItem('lab-spec-demo','1')
|
||
|
||
Проверка: открыть /lab?simdemo=1 -> карточка появится; либо открыть
|
||
/lab?sim=customdemo напрямую. Слайдеры угла/скорости меняют траекторию,
|
||
play/pause/reset работают. Это ВРЕМЕННЫЙ раздел (удалить после Фазы 4).
|
||
════════════════════════════════════════════════════════════════════════ */
|
||
(function (global) {
|
||
function demoEnabled() {
|
||
try {
|
||
var qs = (global.location && global.location.search) || '';
|
||
if (/[?&]simdemo=1\b/.test(qs)) return true;
|
||
if (/[?&]sim=customdemo\b/.test(qs)) return true;
|
||
if (global.LAB_SHOW_SPEC_DEMO === true) return true;
|
||
if (global.localStorage && global.localStorage.getItem('lab-spec-demo') === '1') return true;
|
||
} catch (e) { /* noop */ }
|
||
return false;
|
||
}
|
||
|
||
// Спека v1: бросок тела. g фиксирован 10 -> y = v*sin(θ)*t - 5*t^2.
|
||
var PROJECTILE_DEMO = {
|
||
id: 'customdemo',
|
||
cat: 'phys',
|
||
meta: { title: 'Демо: бросок тела', desc: 'Спек-симуляция (Фаза 0). Угол и скорость — слайдеры.' },
|
||
viewport: { xmin: 0, xmax: 60, ymin: 0, ymax: 30, grid: true, axes: true, bg: '#0D0D1A' },
|
||
time: { autoplay: false, loop: true, duration: 8, speed: 1 },
|
||
params: [
|
||
{ name: 'theta', label: 'Угол θ', min: 0, max: 90, step: 1, value: 45, unit: '°' },
|
||
{ name: 'v', label: 'Скорость v', min: 0, max: 30, step: 0.5, value: 20, unit: 'м/с' }
|
||
],
|
||
objects: [
|
||
// снаряд: x = v*cos(θ)*t, y = v*sin(θ)*t - 5 t^2 (но не ниже 0)
|
||
{
|
||
id: 'ball', type: 'point',
|
||
x: 'v*cos(theta*pi/180)*t',
|
||
y: 'max(0, v*sin(theta*pi/180)*t - 5*t^2)',
|
||
r: 7, color: '#06D6E0', trail: true, trailColor: '#9B5DE5'
|
||
},
|
||
// вектор начальной скорости из старта
|
||
{
|
||
type: 'vector', x1: 0, y1: 0,
|
||
x2: 'cos(theta*pi/180)*v*0.4',
|
||
y2: 'sin(theta*pi/180)*v*0.4',
|
||
color: '#FFD166', width: 3
|
||
},
|
||
// земля
|
||
{ type: 'segment', x1: 0, y1: 0, x2: 60, y2: 0, color: 'rgba(255,255,255,0.35)', width: 2 },
|
||
// подпись над снарядом
|
||
{
|
||
type: 'label', latex: true,
|
||
x: 'ball.x', y: 'ball.y + 2.5',
|
||
text: 'v_0', color: '#06D6E0', size: 15
|
||
}
|
||
]
|
||
};
|
||
|
||
function tryRegister() {
|
||
if (!demoEnabled()) return;
|
||
if (typeof global.registerSpecSim !== 'function') {
|
||
if (global.console) console.warn('[sim-demo] registerSpecSim недоступен');
|
||
return;
|
||
}
|
||
global.registerSpecSim(PROJECTILE_DEMO);
|
||
|
||
// Если каталог уже отрисован, добавить карточку демо вручную (минимально-
|
||
// инвазивно: только когда флаг включён; не трогаем SIMS/каталожный рендер).
|
||
addDemoCardIfNeeded();
|
||
}
|
||
|
||
function addDemoCardIfNeeded() {
|
||
var grid = document.getElementById('sim-grid');
|
||
if (!grid) return;
|
||
if (document.getElementById('sim-card-customdemo')) return;
|
||
var m = global.LabRegistry && global.LabRegistry.get('customdemo');
|
||
if (!m) return;
|
||
var preview = global.LabRegistry.resolvePreview(m);
|
||
var card = document.createElement('div');
|
||
card.id = 'sim-card-customdemo';
|
||
card.className = 'sim-card';
|
||
card.setAttribute('onclick', "openSim('customdemo')");
|
||
card.innerHTML = preview +
|
||
'<div class="sim-body">' +
|
||
'<span class="sim-cat ' + (m.cat || 'phys') + '">демо</span>' +
|
||
'<div class="sim-title">' + esc(m.title) + '</div>' +
|
||
'<div class="sim-desc">' + esc(m.desc || '') + '</div>' +
|
||
'</div>';
|
||
grid.appendChild(card);
|
||
}
|
||
|
||
function esc(s) {
|
||
return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
}
|
||
|
||
// Зарегистрировать после готовности DOM/реестра. _register-all.js грузится
|
||
// последним (defer); этот файл — после него, поэтому LabRegistry уже есть.
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', tryRegister);
|
||
} else {
|
||
tryRegister();
|
||
}
|
||
|
||
// экспонируем для ручной проверки из консоли
|
||
global.LAB_SPEC_DEMO = PROJECTILE_DEMO;
|
||
})(typeof window !== 'undefined' ? window : globalThis);
|