|
|
@@ -91,6 +91,76 @@
|
|
|
background:radial-gradient(circle,#fff,var(--neon-pink));
|
|
|
box-shadow:0 0 20px var(--neon-pink);opacity:0;}
|
|
|
.tube .impulse.run{animation:run linear forwards;}
|
|
|
+ /* 🌬️ Воздух — голубой полупрозрачный с облачками */
|
|
|
+ .tube.air{
|
|
|
+ background:
|
|
|
+ radial-gradient(circle at 20% 40%, rgba(255,255,255,.25) 0%, transparent 40%),
|
|
|
+ radial-gradient(circle at 70% 60%, rgba(255,255,255,.18) 0%, transparent 35%),
|
|
|
+ linear-gradient(90deg, rgba(180,220,255,.15), rgba(140,200,255,.25), rgba(180,220,255,.15));
|
|
|
+ border-color: rgba(180,220,255,.6);
|
|
|
+ box-shadow: inset 0 0 25px rgba(180,220,255,.25);
|
|
|
+ }
|
|
|
+ .tube.air .label{color:#bfe3ff; text-shadow:0 0 8px #bfe3ff;}
|
|
|
+
|
|
|
+ /* 💧 Вода — сине-бирюзовая с волнами */
|
|
|
+ .tube.water{
|
|
|
+ background:
|
|
|
+ radial-gradient(ellipse at 30% 20%, rgba(0,229,255,.35) 0%, transparent 55%),
|
|
|
+ radial-gradient(ellipse at 75% 80%, rgba(0,140,255,.35) 0%, transparent 50%),
|
|
|
+ linear-gradient(90deg, rgba(0,90,180,.5), rgba(0,180,220,.55), rgba(0,90,180,.5));
|
|
|
+ border-color: rgba(0,229,255,.7);
|
|
|
+ box-shadow: inset 0 0 30px rgba(0,180,255,.4);
|
|
|
+ position:relative; overflow:hidden;
|
|
|
+ }
|
|
|
+ .tube.water::after{
|
|
|
+ content:'';
|
|
|
+ position:absolute;left:-50%;top:50%;width:200%;height:200%;
|
|
|
+ background:
|
|
|
+ radial-gradient(ellipse at 20% 50%, rgba(255,255,255,.15) 0%, transparent 40%),
|
|
|
+ radial-gradient(ellipse at 60% 50%, rgba(255,255,255,.12) 0%, transparent 35%),
|
|
|
+ radial-gradient(ellipse at 90% 50%, rgba(255,255,255,.15) 0%, transparent 40%);
|
|
|
+ animation:waterShift 6s linear infinite;
|
|
|
+ pointer-events:none;
|
|
|
+ }
|
|
|
+ @keyframes waterShift{
|
|
|
+ 0%{transform:translateX(0);}
|
|
|
+ 100%{transform:translateX(25%);}
|
|
|
+ }
|
|
|
+ .tube.water .label{color:#cffaff; text-shadow:0 0 10px #00e5ff;}
|
|
|
+
|
|
|
+ /* ⚙️ Сталь — серебристо-металлическая с бликом */
|
|
|
+ .tube.steel{
|
|
|
+ background:
|
|
|
+ linear-gradient(90deg,
|
|
|
+ rgba(120,130,150,.55) 0%,
|
|
|
+ rgba(200,210,230,.75) 25%,
|
|
|
+ rgba(160,170,190,.65) 50%,
|
|
|
+ rgba(220,230,245,.8) 75%,
|
|
|
+ rgba(120,130,150,.55) 100%);
|
|
|
+ border-color: rgba(200,215,235,.85);
|
|
|
+ box-shadow:
|
|
|
+ inset 0 0 25px rgba(255,255,255,.25),
|
|
|
+ inset 0 2px 4px rgba(255,255,255,.4),
|
|
|
+ inset 0 -2px 4px rgba(0,0,0,.4),
|
|
|
+ 0 0 15px rgba(200,220,255,.35);
|
|
|
+ position:relative; overflow:hidden;
|
|
|
+ }
|
|
|
+ .tube.steel::after{
|
|
|
+ content:'';
|
|
|
+ position:absolute;left:0;top:0;bottom:0;width:30%;
|
|
|
+ background:linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);
|
|
|
+ animation:steelShine 3.5s ease-in-out infinite;
|
|
|
+ pointer-events:none;
|
|
|
+ }
|
|
|
+ @keyframes steelShine{
|
|
|
+ 0%{left:-30%;}
|
|
|
+ 50%{left:100%;}
|
|
|
+ 100%{left:100%;}
|
|
|
+ }
|
|
|
+ .tube.steel .label{
|
|
|
+ color:#fff; text-shadow:0 0 8px #ffffff, 0 0 4px #000;
|
|
|
+ font-weight:800;
|
|
|
+ }
|
|
|
@keyframes run{0%{left:0;opacity:1;}100%{left:100%;opacity:1;}}
|
|
|
.quiz-options{display:flex;flex-wrap:wrap;gap:12px;justify-content:center;margin-top:16px;}
|
|
|
.quiz-options .btn{min-width:120px;}
|
|
|
@@ -143,6 +213,60 @@
|
|
|
.question{font-size:1.15rem;text-align:center;max-width:700px;margin:16px 0;line-height:1.5;}
|
|
|
.result-score{font-size:4rem;font-weight:900;color:#fff;
|
|
|
text-shadow:0 0 20px var(--neon-pink),0 0 40px var(--neon-purple);}
|
|
|
+ /* 🎉 финальный экран */
|
|
|
+ #fxCanvas{
|
|
|
+ position:absolute;inset:0;width:100%;height:100%;
|
|
|
+ pointer-events:none;z-index:5;
|
|
|
+ }
|
|
|
+ .final-badge{
|
|
|
+ font-size:5rem;font-weight:900;
|
|
|
+ letter-spacing:4px;text-transform:uppercase;
|
|
|
+ text-align:center;margin-bottom:8px;
|
|
|
+ animation:pop .6s cubic-bezier(.2,1.4,.5,1) both;
|
|
|
+ }
|
|
|
+ .final-badge.win{
|
|
|
+ color:#fff;
|
|
|
+ text-shadow:
|
|
|
+ 0 0 10px var(--neon-green),
|
|
|
+ 0 0 30px var(--neon-green),
|
|
|
+ 0 0 60px var(--neon-cyan),
|
|
|
+ 0 0 100px var(--neon-pink);
|
|
|
+ animation:pop .6s cubic-bezier(.2,1.4,.5,1) both,
|
|
|
+ winPulse 1.4s ease-in-out infinite .6s;
|
|
|
+ }
|
|
|
+ .final-badge.good{
|
|
|
+ color:#fff;
|
|
|
+ text-shadow:
|
|
|
+ 0 0 10px var(--neon-cyan),
|
|
|
+ 0 0 30px var(--neon-cyan),
|
|
|
+ 0 0 60px var(--neon-purple);
|
|
|
+ animation:pop .6s cubic-bezier(.2,1.4,.5,1) both;
|
|
|
+ }
|
|
|
+ .final-badge.meh{
|
|
|
+ color:#fff;
|
|
|
+ text-shadow:0 0 12px var(--neon-purple),0 0 30px var(--neon-purple);
|
|
|
+ animation:pop .6s cubic-bezier(.2,1.4,.5,1) both;
|
|
|
+ }
|
|
|
+ .final-badge.lose{
|
|
|
+ color:#fff;
|
|
|
+ text-shadow:0 0 10px #ff2e4c,0 0 30px #ff2e4c,0 0 60px #ff2e4c;
|
|
|
+ animation:pop .6s cubic-bezier(.2,1.4,.5,1) both,
|
|
|
+ losePulse 1.2s ease-in-out infinite .6s;
|
|
|
+ }
|
|
|
+ @keyframes pop{
|
|
|
+ 0%{transform:scale(.3);opacity:0;}
|
|
|
+ 60%{transform:scale(1.15);opacity:1;}
|
|
|
+ 100%{transform:scale(1);opacity:1;}
|
|
|
+ }
|
|
|
+ @keyframes winPulse{
|
|
|
+ 0%,100%{transform:scale(1);}
|
|
|
+ 50%{transform:scale(1.06);}
|
|
|
+ }
|
|
|
+ @keyframes losePulse{
|
|
|
+ 0%,100%{opacity:1;}
|
|
|
+ 50%{opacity:.7;}
|
|
|
+ }
|
|
|
+ #quizResult{position:relative;z-index:10;}
|
|
|
.hint{font-size:.8rem;color:var(--neon-pink);letter-spacing:1px;
|
|
|
margin-top:8px;min-height:1.2em;}
|
|
|
.table{font-size:.85rem;color:#cfeaff;margin-top:8px;text-align:center;line-height:1.8;}
|
|
|
@@ -156,6 +280,8 @@
|
|
|
<div class="progress"><div id="progressBar"></div></div>
|
|
|
<button class="icon-btn" id="soundToggle" type="button">🔊 ЗВУК</button>
|
|
|
</div>
|
|
|
+ <!-- 🎆 canvas для спецэффектов -->
|
|
|
+ <canvas id="fxCanvas"></canvas>
|
|
|
|
|
|
<!-- SCENE 1 -->
|
|
|
<section class="scene active" id="scene1">
|
|
|
@@ -195,11 +321,11 @@
|
|
|
<!-- SCENE 3 -->
|
|
|
<section class="scene" id="scene3">
|
|
|
<h2>Среда распространения. Скорость звука</h2>
|
|
|
- <p>Чем плотнее и упруже среда — тем быстрее идёт звук.</p>
|
|
|
- <div class="tubes">
|
|
|
- <div class="tube"><span class="label">Воздух ≈ 343 м/с</span><div class="impulse" data-speed="2.4"></div></div>
|
|
|
- <div class="tube"><span class="label">Вода ≈ 1480 м/с</span><div class="impulse" data-speed="1.2"></div></div>
|
|
|
- <div class="tube"><span class="label">Сталь ≈ 5100 м/с</span><div class="impulse" data-speed="0.5"></div></div>
|
|
|
+ <p>Чем плотнее среда — тем быстрее идёт звук.</p>
|
|
|
+ <div class="tubes">
|
|
|
+ <div class="tube air"><span class="label">🌬️ Воздух ≈ 343 м/с</span><div class="impulse" data-speed="2.4"></div></div>
|
|
|
+ <div class="tube water"><span class="label">💧 Вода ≈ 1480 м/с</span><div class="impulse" data-speed="1.2"></div></div>
|
|
|
+ <div class="tube steel"><span class="label">⚙️ Сталь ≈ 5100 м/с</span><div class="impulse" data-speed="0.5"></div></div>
|
|
|
</div>
|
|
|
<button class="btn" id="btnImpulse" type="button">Запустить импульс</button>
|
|
|
<p class="question" style="margin-top:20px;">В какой среде звук распространяется быстрее всего?</p>
|
|
|
@@ -241,10 +367,21 @@
|
|
|
<button class="btn pink" id="btnTrans" type="button">Поперечная</button>
|
|
|
</div>
|
|
|
<div class="canvas-wrap"><canvas id="typeCanvas" width="800" height="200"></canvas></div>
|
|
|
- <p>Собери «звук»: кликни по молекуле — она встанет в ближайшую позицию сжатия/разрежения.</p>
|
|
|
- <div class="molecules" id="molecules"></div>
|
|
|
- <p class="hint" id="scene5Hint">Размещено: 0 / 6</p>
|
|
|
- <button class="btn" id="btnNext5" type="button" disabled>Далее →</button>
|
|
|
+
|
|
|
+ <div class="controls">
|
|
|
+ <div class="slider-box">
|
|
|
+ <label>Частота волны: <span id="typeFreqVal">3</span> Гц</label>
|
|
|
+ <input type="range" id="typeFreqSlider" min="1" max="10" value="3">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="table" style="max-width:720px;">
|
|
|
+ 💡 <b>Главное:</b> молекулы среды <span>колеблются на месте</span>, а волна переносит
|
|
|
+ <span>энергию</span>, а не вещество.<br>
|
|
|
+ 🔊 В продольной волне чередуются <span>сжатия</span> и <span>разрежения</span> — так звук идёт через воздух.
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button class="btn" id="btnNext5" type="button">Далее →</button>
|
|
|
</section>
|
|
|
|
|
|
<!-- SCENE 6 -->
|
|
|
@@ -284,7 +421,8 @@
|
|
|
<div class="progress-quiz"><div id="quizProgress"></div></div>
|
|
|
<p class="question" id="quizQuestion">…</p>
|
|
|
<div class="quiz-options" id="quizOptions"></div>
|
|
|
- <div id="quizResult" style="display:none;flex-direction:column;align-items:center;">
|
|
|
+ <div id="quizResult" style="display:none;flex-direction:column;align-items:center;">
|
|
|
+ <div class="final-badge" id="finalBadge">…</div>
|
|
|
<div class="result-score" id="finalScore">0</div>
|
|
|
<p id="finalText" style="margin:16px 0;"></p>
|
|
|
<button class="btn pink" id="btnRestart" type="button">Пройти заново</button>
|
|
|
@@ -575,25 +713,38 @@ function startOscillators(){
|
|
|
}
|
|
|
|
|
|
function drawOsc(){
|
|
|
- oscT += 0.04;
|
|
|
+ // ⏱️ Шаг времени. Период управляет скоростью фазы.
|
|
|
+ var dt = 0.03;
|
|
|
+ oscT += dt;
|
|
|
+
|
|
|
var W = oscCanvas.width, H = oscCanvas.height;
|
|
|
oscCtx.clearRect(0,0,W,H);
|
|
|
- // ось
|
|
|
+
|
|
|
+ // ================= ЛЕВЫЙ CANVAS: МАЯТНИК =================
|
|
|
+ // Ось подвеса
|
|
|
oscCtx.strokeStyle = 'rgba(0,229,255,.3)';
|
|
|
oscCtx.lineWidth = 1;
|
|
|
- oscCtx.beginPath(); oscCtx.moveTo(W/2,30); oscCtx.lineTo(W/2,H-30); oscCtx.stroke();
|
|
|
+ oscCtx.beginPath(); oscCtx.moveTo(W/2, 30); oscCtx.lineTo(W/2, H-30); oscCtx.stroke();
|
|
|
+
|
|
|
+ // 🕒 Период управляет скоростью качания (фаза)
|
|
|
+ var phase = (2 * Math.PI / oscPeriod) * oscT;
|
|
|
+
|
|
|
+ // 🎚️ Амплитуда управляет ШИРИНОЙ колебания (углом отклонения)
|
|
|
+ // oscAmp: 20..100 → maxAngle: 0.08..0.55 рад (≈5°…32°)
|
|
|
+ var maxAngle = (oscAmp / 100) * 0.55;
|
|
|
+ var angle = Math.sin(phase) * maxAngle;
|
|
|
|
|
|
- // маятник
|
|
|
- var ang = Math.sin(oscT * (2*Math.PI/oscPeriod)) * (oscAmp/200);
|
|
|
var len = 120, cx = W/2, cy = 40;
|
|
|
- var bx = cx + Math.sin(ang)*len;
|
|
|
- var by = cy + Math.cos(ang)*len;
|
|
|
+ var bx = cx + Math.sin(angle) * len;
|
|
|
+ var by = cy + Math.cos(angle) * len;
|
|
|
|
|
|
+ // Нить
|
|
|
oscCtx.strokeStyle = '#a259ff';
|
|
|
oscCtx.lineWidth = 2;
|
|
|
oscCtx.shadowBlur = 12; oscCtx.shadowColor = '#a259ff';
|
|
|
oscCtx.beginPath(); oscCtx.moveTo(cx, cy); oscCtx.lineTo(bx, by); oscCtx.stroke();
|
|
|
|
|
|
+ // Груз
|
|
|
oscCtx.beginPath();
|
|
|
oscCtx.fillStyle = '#ff2ec4';
|
|
|
oscCtx.shadowBlur = 25; oscCtx.shadowColor = '#ff2ec4';
|
|
|
@@ -601,19 +752,50 @@ function drawOsc(){
|
|
|
oscCtx.fill();
|
|
|
oscCtx.shadowBlur = 0;
|
|
|
|
|
|
- // правый — осциллограмма волны
|
|
|
+ // Пунктирные «границы» размаха — показывают, где амплитуда
|
|
|
+ oscCtx.strokeStyle = 'rgba(255,46,196,.25)';
|
|
|
+ oscCtx.setLineDash([4,4]);
|
|
|
+ oscCtx.lineWidth = 1;
|
|
|
+ var leftX = cx + Math.sin(-maxAngle) * len;
|
|
|
+ var rightX = cx + Math.sin( maxAngle) * len;
|
|
|
+ oscCtx.beginPath();
|
|
|
+ oscCtx.moveTo(leftX, 30); oscCtx.lineTo(leftX, H-30);
|
|
|
+ oscCtx.moveTo(rightX, 30); oscCtx.lineTo(rightX, H-30);
|
|
|
+ oscCtx.stroke();
|
|
|
+ oscCtx.setLineDash([]);
|
|
|
+
|
|
|
+ // ================= ПРАВЫЙ CANVAS: ВОЛНА =================
|
|
|
var WW = waveCanvas.width, WH = waveCanvas.height;
|
|
|
waveCtx.clearRect(0,0,WW,WH);
|
|
|
+
|
|
|
+ // Нулевая линия
|
|
|
waveCtx.strokeStyle = 'rgba(0,229,255,.2)';
|
|
|
waveCtx.lineWidth = 1;
|
|
|
waveCtx.beginPath(); waveCtx.moveTo(0, WH/2); waveCtx.lineTo(WW, WH/2); waveCtx.stroke();
|
|
|
|
|
|
+ // 🎚️ Амплитуда = ВЫСОТА волны (размах по вертикали)
|
|
|
+ // oscAmp: 20..100 → pixelAmp: 20..110 px (не выходит за пределы холста)
|
|
|
+ var pixelAmp = oscAmp * 1.1;
|
|
|
+
|
|
|
+ // Пунктирные границы размаха — видно, как амплитуда расширяется/сужается
|
|
|
+ waveCtx.strokeStyle = 'rgba(255,46,196,.25)';
|
|
|
+ waveCtx.setLineDash([4,4]);
|
|
|
+ waveCtx.lineWidth = 1;
|
|
|
+ waveCtx.beginPath();
|
|
|
+ waveCtx.moveTo(0, WH/2 - pixelAmp); waveCtx.lineTo(WW, WH/2 - pixelAmp);
|
|
|
+ waveCtx.moveTo(0, WH/2 + pixelAmp); waveCtx.lineTo(WW, WH/2 + pixelAmp);
|
|
|
+ waveCtx.stroke();
|
|
|
+ waveCtx.setLineDash([]);
|
|
|
+
|
|
|
+ // 🕒 Период = скорость бега волны
|
|
|
+ // 🎚️ Амплитуда = высота (размах)
|
|
|
waveCtx.strokeStyle = '#00e5ff';
|
|
|
waveCtx.lineWidth = 2;
|
|
|
waveCtx.shadowBlur = 15; waveCtx.shadowColor = '#00e5ff';
|
|
|
waveCtx.beginPath();
|
|
|
for(var x=0; x<WW; x++){
|
|
|
- var y = WH/2 + Math.sin((x/WW)*Math.PI*4 + oscT*(2*Math.PI/oscPeriod)) * oscAmp;
|
|
|
+ var phaseX = (x / WW) * Math.PI * 4 + (2 * Math.PI / oscPeriod) * oscT;
|
|
|
+ var y = WH/2 + Math.sin(phaseX) * pixelAmp; // ← размах = амплитуда
|
|
|
if(x===0) waveCtx.moveTo(x,y); else waveCtx.lineTo(x,y);
|
|
|
}
|
|
|
waveCtx.stroke();
|
|
|
@@ -621,7 +803,6 @@ function drawOsc(){
|
|
|
|
|
|
requestAnimationFrame(drawOsc);
|
|
|
}
|
|
|
-
|
|
|
document.getElementById('btnCheck4').addEventListener('click', function(){
|
|
|
ensureAudio();
|
|
|
openModal(
|
|
|
@@ -633,135 +814,163 @@ document.getElementById('btnCheck4').addEventListener('click', function(){
|
|
|
});
|
|
|
document.getElementById('btnNext4').addEventListener('click', function(){ goToScene(4); });
|
|
|
|
|
|
-/* ================= SCENE 5: виды волн + молекулы ================= */
|
|
|
+/* ================= SCENE 5: виды волн ================= */
|
|
|
var typeCanvas = document.getElementById('typeCanvas');
|
|
|
var typeCtx = typeCanvas.getContext('2d');
|
|
|
var typeT = 0;
|
|
|
-var waveType = 'long'; // long | trans
|
|
|
+var waveType = 'long'; // 'long' | 'trans'
|
|
|
+var typeFreq = 3; // 🎚️ частота волны (1–10 Гц)
|
|
|
|
|
|
+// Слайдер частоты продольной/поперечной волны
|
|
|
+document.getElementById('typeFreqSlider').addEventListener('input', function(e){
|
|
|
+ typeFreq = +e.target.value;
|
|
|
+ document.getElementById('typeFreqVal').textContent = typeFreq;
|
|
|
+ clickSound();
|
|
|
+});
|
|
|
+
|
|
|
+// Переключатели вида волны
|
|
|
document.getElementById('btnLong').addEventListener('click', function(){
|
|
|
- waveType = 'long'; clickSound(); initMolecules(); resetNext5();
|
|
|
+ waveType = 'long'; clickSound();
|
|
|
});
|
|
|
document.getElementById('btnTrans').addEventListener('click', function(){
|
|
|
- waveType = 'trans'; clickSound(); initMolecules(); resetNext5();
|
|
|
+ waveType = 'trans'; clickSound();
|
|
|
});
|
|
|
|
|
|
-function resetNext5(){
|
|
|
- document.getElementById('btnNext5').disabled = true;
|
|
|
- document.getElementById('scene5Hint').textContent = 'Размещено: 0 / 6';
|
|
|
-}
|
|
|
+// Кнопка «Далее»
|
|
|
+document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
|
|
|
|
|
|
+// 🔁 Основной цикл анимации
|
|
|
function drawType(){
|
|
|
var W = typeCanvas.width, H = typeCanvas.height;
|
|
|
typeCtx.clearRect(0,0,W,H);
|
|
|
- typeT += 0.05;
|
|
|
+
|
|
|
+ // 🎚️ Скорость фазы зависит от частоты (чем больше Гц — тем быстрее бежит волна)
|
|
|
+ typeT += 0.02 * typeFreq;
|
|
|
|
|
|
if(waveType === 'long'){
|
|
|
- // продольная — сжатия/разрежения
|
|
|
- var N = 40;
|
|
|
- for(var i=0;i<N;i++){
|
|
|
- var baseX = (i+1) * (W/(N+1));
|
|
|
- var shift = Math.sin(typeT - i*0.3) * 12;
|
|
|
- var x = baseX + shift;
|
|
|
- var density = 0.5 + 0.5 * Math.sin(typeT - i*0.3);
|
|
|
- typeCtx.beginPath();
|
|
|
- typeCtx.fillStyle = 'rgba(255,46,196,' + (0.4 + density*0.6) + ')';
|
|
|
- typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#ff2ec4';
|
|
|
- typeCtx.arc(x, H/2, 6 + density*3, 0, Math.PI*2);
|
|
|
- typeCtx.fill();
|
|
|
- }
|
|
|
- typeCtx.shadowBlur = 0;
|
|
|
- typeCtx.fillStyle = '#00e5ff';
|
|
|
- typeCtx.font = '14px Segoe UI';
|
|
|
- typeCtx.fillText('Продольная (звук в воздухе)', 20, 30);
|
|
|
+ drawLongitudinal(W, H);
|
|
|
} else {
|
|
|
- // поперечная — синусоида
|
|
|
- typeCtx.strokeStyle = 'rgba(0,229,255,.4)';
|
|
|
- typeCtx.lineWidth = 2;
|
|
|
+ drawTransverse(W, H);
|
|
|
+ }
|
|
|
+ requestAnimationFrame(drawType);
|
|
|
+}
|
|
|
+
|
|
|
+// 🌊 ПРОДОЛЬНАЯ ВОЛНА — сжатия и разрежения
|
|
|
+function drawLongitudinal(W, H){
|
|
|
+ var N = 45;
|
|
|
+ var spacing = W / (N + 1);
|
|
|
+
|
|
|
+ // Рисуем «коридор» среды
|
|
|
+ typeCtx.fillStyle = 'rgba(0,229,255,.04)';
|
|
|
+ typeCtx.fillRect(0, H/2 - 40, W, 80);
|
|
|
+
|
|
|
+ for(var i=0;i<N;i++){
|
|
|
+ var baseX = spacing * (i + 1);
|
|
|
+
|
|
|
+ // Смещение молекулы: колеблется ВОКРУГ своей позиции, но не уезжает вместе с волной
|
|
|
+ var shift = Math.sin(typeT - i * 0.35) * 12;
|
|
|
+ var x = baseX + shift;
|
|
|
+
|
|
|
+ // Плотность: сжатие / разрежение (используется для радиуса и яркости)
|
|
|
+ var density = 0.5 + 0.5 * Math.sin(typeT - i * 0.35); // 0..1
|
|
|
+ var r = 5 + density * 3; // сжатие → крупнее и ярче
|
|
|
+ var alpha = 0.35 + density * 0.65;
|
|
|
+
|
|
|
typeCtx.beginPath();
|
|
|
- for(var x2=0; x2<W; x2+=4){
|
|
|
- var y2 = H/2 + Math.sin(x2*0.02 + typeT) * 50;
|
|
|
- if(x2===0) typeCtx.moveTo(x2,y2); else typeCtx.lineTo(x2,y2);
|
|
|
- }
|
|
|
+ typeCtx.fillStyle = 'rgba(255,46,196,' + alpha + ')';
|
|
|
+ typeCtx.shadowBlur = 12; typeCtx.shadowColor = '#ff2ec4';
|
|
|
+ typeCtx.arc(x, H/2, r, 0, Math.PI*2);
|
|
|
+ typeCtx.fill();
|
|
|
+ }
|
|
|
+ typeCtx.shadowBlur = 0;
|
|
|
+
|
|
|
+ // Подписи
|
|
|
+ typeCtx.fillStyle = '#00e5ff';
|
|
|
+ typeCtx.font = 'bold 14px Segoe UI';
|
|
|
+ typeCtx.fillText('Продольная (звук в воздухе)', 20, 30);
|
|
|
+
|
|
|
+ // Стрелка направления движения волны
|
|
|
+ typeCtx.strokeStyle = '#39ff14';
|
|
|
+ typeCtx.lineWidth = 2;
|
|
|
+ typeCtx.shadowBlur = 12; typeCtx.shadowColor = '#39ff14';
|
|
|
+ typeCtx.beginPath();
|
|
|
+ typeCtx.moveTo(W - 140, 30);
|
|
|
+ typeCtx.lineTo(W - 40, 30);
|
|
|
+ typeCtx.lineTo(W - 55, 22);
|
|
|
+ typeCtx.moveTo(W - 40, 30);
|
|
|
+ typeCtx.lineTo(W - 55, 38);
|
|
|
+ typeCtx.stroke();
|
|
|
+ typeCtx.shadowBlur = 0;
|
|
|
+ typeCtx.fillStyle = '#39ff14';
|
|
|
+ typeCtx.font = '12px Segoe UI';
|
|
|
+ typeCtx.fillText('волна →', W - 135, 20);
|
|
|
+
|
|
|
+ // Пунктирная разметка «сжатие / разрежение»
|
|
|
+ typeCtx.strokeStyle = 'rgba(255,255,255,.08)';
|
|
|
+ typeCtx.setLineDash([4,6]);
|
|
|
+ typeCtx.lineWidth = 1;
|
|
|
+ for(var k=0;k<5;k++){
|
|
|
+ var cx = 60 + k * (W - 120) / 4;
|
|
|
+ typeCtx.beginPath();
|
|
|
+ typeCtx.moveTo(cx, H/2 - 45);
|
|
|
+ typeCtx.lineTo(cx, H/2 + 45);
|
|
|
typeCtx.stroke();
|
|
|
- for(var k=0;k<20;k++){
|
|
|
- var xx = (k+1) * (W/21);
|
|
|
- var yy = H/2 + Math.sin(xx*0.02 + typeT) * 50;
|
|
|
- typeCtx.beginPath();
|
|
|
- typeCtx.fillStyle = '#ff2ec4';
|
|
|
- typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#ff2ec4';
|
|
|
- typeCtx.arc(xx, yy, 6, 0, Math.PI*2);
|
|
|
- typeCtx.fill();
|
|
|
- }
|
|
|
- typeCtx.shadowBlur = 0;
|
|
|
- typeCtx.fillStyle = '#00e5ff';
|
|
|
- typeCtx.font = '14px Segoe UI';
|
|
|
- typeCtx.fillText('Поперечная (волны на воде, струна)', 20, 30);
|
|
|
}
|
|
|
- requestAnimationFrame(drawType);
|
|
|
+ typeCtx.setLineDash([]);
|
|
|
+
|
|
|
+ // Подписи сжатие/разрежение (плавно переключаются)
|
|
|
+ var labels = ['сжатие', 'разрежение'];
|
|
|
+ for(var m=0;m<4;m++){
|
|
|
+ var lx = 60 + m * (W - 120) / 4 + 30;
|
|
|
+ var phase = Math.sin(typeT - m * 1.4);
|
|
|
+ var lbl = phase > 0 ? 'сжатие' : 'разрежение';
|
|
|
+ typeCtx.fillStyle = phase > 0 ? 'rgba(255,46,196,.85)' : 'rgba(0,229,255,.85)';
|
|
|
+ typeCtx.font = '11px Segoe UI';
|
|
|
+ typeCtx.fillText(lbl, lx, H/2 + 60);
|
|
|
+ }
|
|
|
}
|
|
|
-drawType();
|
|
|
|
|
|
-/* молекулы */
|
|
|
-var molPlaced = 0;
|
|
|
-function initMolecules(){
|
|
|
- var box = document.getElementById('molecules');
|
|
|
- box.innerHTML = '';
|
|
|
- molPlaced = 0;
|
|
|
- var W = box.clientWidth || 700;
|
|
|
-
|
|
|
- // цели (позиции сжатия/разрежения)
|
|
|
- var targetPos = [0.12, 0.25, 0.40, 0.55, 0.70, 0.85];
|
|
|
- window._molTargets = targetPos.map(function(p){ return p*W; });
|
|
|
-
|
|
|
- // пунктирные цели
|
|
|
- targetPos.forEach(function(p){
|
|
|
- var t = document.createElement('div');
|
|
|
- t.className = 'target';
|
|
|
- t.style.left = (p*100) + '%';
|
|
|
- box.appendChild(t);
|
|
|
- });
|
|
|
+// 🌊 ПОПЕРЕЧНАЯ ВОЛНА — синусоида
|
|
|
+function drawTransverse(W, H){
|
|
|
+ // Ось
|
|
|
+ typeCtx.strokeStyle = 'rgba(0,229,255,.15)';
|
|
|
+ typeCtx.lineWidth = 1;
|
|
|
+ typeCtx.beginPath();
|
|
|
+ typeCtx.moveTo(0, H/2);
|
|
|
+ typeCtx.lineTo(W, H/2);
|
|
|
+ typeCtx.stroke();
|
|
|
|
|
|
- // молекулы в случайных позициях
|
|
|
- for(var i=0;i<6;i++){
|
|
|
- (function(idx){
|
|
|
- var m = document.createElement('div');
|
|
|
- m.className = 'molecule';
|
|
|
- var startPct = [0.05, 0.20, 0.35, 0.50, 0.65, 0.80][idx] * 100;
|
|
|
- // перемешаем стартовые
|
|
|
- startPct = 5 + Math.random()*90;
|
|
|
- m.style.left = startPct + '%';
|
|
|
- m.dataset.idx = idx;
|
|
|
- m.addEventListener('click', function(){
|
|
|
- if(m.classList.contains('placed')) return;
|
|
|
- // ставим в ближайшую незанятую цель
|
|
|
- var used = {};
|
|
|
- var placed = document.querySelectorAll('#molecules .molecule.placed');
|
|
|
- for(var j=0;j<placed.length;j++) used[placed[j].dataset.target] = true;
|
|
|
- var best = -1, bestDist = 1e9;
|
|
|
- for(var k=0;k<targetPos.length;k++){
|
|
|
- if(used[k]) continue;
|
|
|
- var d = Math.abs(targetPos[k]*100 - parseFloat(m.style.left));
|
|
|
- if(d < bestDist){ bestDist = d; best = k; }
|
|
|
- }
|
|
|
- if(best === -1) return;
|
|
|
- m.style.left = (targetPos[best]*100) + '%';
|
|
|
- m.dataset.target = best;
|
|
|
- m.classList.add('placed');
|
|
|
- molPlaced++;
|
|
|
- beep(500 + molPlaced*80, 0.1, 'triangle', 0.15);
|
|
|
- document.getElementById('scene5Hint').textContent = 'Размещено: ' + molPlaced + ' / 6';
|
|
|
- if(molPlaced >= 6){
|
|
|
- document.getElementById('btnNext5').disabled = false;
|
|
|
- document.getElementById('scene5Hint').textContent = 'Отлично! Звук собран 🎵';
|
|
|
- correctSound();
|
|
|
- }
|
|
|
- });
|
|
|
- box.appendChild(m);
|
|
|
- })(i);
|
|
|
+ // Сама волна
|
|
|
+ typeCtx.strokeStyle = '#00e5ff';
|
|
|
+ typeCtx.lineWidth = 2;
|
|
|
+ typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#00e5ff';
|
|
|
+ typeCtx.beginPath();
|
|
|
+ for(var x=0; x<W; x+=4){
|
|
|
+ var y = H/2 + Math.sin(x * 0.02 + typeT) * 55;
|
|
|
+ if(x===0) typeCtx.moveTo(x,y); else typeCtx.lineTo(x,y);
|
|
|
}
|
|
|
+ typeCtx.stroke();
|
|
|
+ typeCtx.shadowBlur = 0;
|
|
|
+
|
|
|
+ // Частицы на гребнях
|
|
|
+ for(var k=0;k<20;k++){
|
|
|
+ var xx = (k+1) * (W/21);
|
|
|
+ var yy = H/2 + Math.sin(xx * 0.02 + typeT) * 55;
|
|
|
+ typeCtx.beginPath();
|
|
|
+ typeCtx.fillStyle = '#ff2ec4';
|
|
|
+ typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#ff2ec4';
|
|
|
+ typeCtx.arc(xx, yy, 6, 0, Math.PI*2);
|
|
|
+ typeCtx.fill();
|
|
|
+ }
|
|
|
+ typeCtx.shadowBlur = 0;
|
|
|
+
|
|
|
+ typeCtx.fillStyle = '#00e5ff';
|
|
|
+ typeCtx.font = 'bold 14px Segoe UI';
|
|
|
+ typeCtx.fillText('Поперечная (волны на воде, струна)', 20, 30);
|
|
|
}
|
|
|
-document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
|
|
|
+
|
|
|
+// Запуск
|
|
|
+drawType();
|
|
|
|
|
|
/* ================= SCENE 6: карточки + пары ================= */
|
|
|
var pairData = [
|
|
|
@@ -849,16 +1058,15 @@ document.getElementById('btnNext6').addEventListener('click', function(){ goToSc
|
|
|
var quizQuestions = [
|
|
|
{ q: 'Что переносит механическая волна?', a: ['Вещество', 'Энергию', 'Заряд', 'Массу'], c: 1 },
|
|
|
{ q: 'В какой среде звук распространяется быстрее всего?', a: ['Воздух', 'Вода', 'Сталь', 'Вакуум'], c: 2 },
|
|
|
- { q: 'Формула связи периода и частоты:', a: ['T = ν', 'T = 1/ν', 'T = ν²', 'T = 2ν'], c: 1 },
|
|
|
+ { q: 'Формула связи периода и частоты:', a: ['T = \u03BD', 'T = 1/\u03BD', 'T = \u03BD²', 'T = 2\u03BD'], c: 1 },
|
|
|
{ q: 'Звук — это какая волна?', a: ['Поперечная', 'Продольная', 'Электромагнитная', 'Стоячая'], c: 1 },
|
|
|
{ q: 'Скорость звука в воздухе примерно равна:', a: ['34 м/с', '343 м/с', '3430 м/с', '3 м/с'], c: 1 },
|
|
|
{ q: 'Что такое амплитуда?', a: ['Число колебаний в секунду', 'Максимальное отклонение от равновесия', 'Время одного колебания', 'Длина волны'], c: 1 },
|
|
|
{ q: 'Единица измерения частоты:', a: ['Гц', 'м', 'с', 'кг'], c: 0 },
|
|
|
{ q: 'Могут ли механические волны распространяться в вакууме?', a: ['Да', 'Нет', 'Только звук', 'Только свет'], c: 1 },
|
|
|
- { q: 'Связь скорости, длины волны и частоты:', a: ['v = λ/ν', 'v = λ·ν', 'v = ν/λ', 'v = λ+ν'], c: 1 },
|
|
|
- { q: 'Ультразвук — это:', a: ['ν < 20 Гц', 'ν > 20 кГц', 'ν = 20 Гц', 'Любой звук'], c: 1 }
|
|
|
+ { q: 'Связь скорости, длины волны и частоты:', a: ['v = \u03BB/\u03BD', 'v = \u03BB·\u03BD', 'v = \u03BD/\u03BB', 'v = \u03BB+\u03BD'], c: 1 },
|
|
|
+ { q: 'Ультразвук — это:', a: ['\u03BD < 20 Гц', '\u03BD > 20 кГц', '\u03BD = 20 Гц', 'Любой звук'], c: 1 }
|
|
|
];
|
|
|
-
|
|
|
var qIndex = 0, qScore = 0;
|
|
|
|
|
|
function startQuiz(){
|
|
|
@@ -877,10 +1085,12 @@ function showQuestion(){
|
|
|
var opts = document.getElementById('quizOptions');
|
|
|
opts.innerHTML = '';
|
|
|
q.a.forEach(function(text, i){
|
|
|
- var b = document.createElement('button');
|
|
|
+ var b = document.createElement('button');
|
|
|
b.className = 'btn';
|
|
|
b.type = 'button';
|
|
|
- b.textContent = text;
|
|
|
+ b.style.textTransform = 'none'; // ✅ отключаем КАПС
|
|
|
+ b.style.letterSpacing = 'normal'; // ✅ буквы без разрядки
|
|
|
+ b.innerHTML = text;
|
|
|
b.addEventListener('click', function(){
|
|
|
// блокируем остальные
|
|
|
var all = opts.querySelectorAll('.btn');
|
|
|
@@ -908,16 +1118,255 @@ function showQuestion(){
|
|
|
}
|
|
|
|
|
|
function showResult(){
|
|
|
+/* =========================================================
|
|
|
+ 🎆 СИСТЕМА ЧАСТИЦ (конфетти + фейерверк)
|
|
|
+ ========================================================= */
|
|
|
+var fxCanvas = document.getElementById('fxCanvas');
|
|
|
+var fxCtx = fxCanvas.getContext('2d');
|
|
|
+var particles = [];
|
|
|
+var fxRunning = false;
|
|
|
+var fxMode = null; // 'win' | 'good' | 'meh' | 'lose' | null
|
|
|
+
|
|
|
+function resizeFx(){
|
|
|
+ fxCanvas.width = window.innerWidth;
|
|
|
+ fxCanvas.height = window.innerHeight;
|
|
|
+}
|
|
|
+resizeFx();
|
|
|
+window.addEventListener('resize', resizeFx);
|
|
|
+
|
|
|
+// Одна частица
|
|
|
+function Particle(x, y, opts){
|
|
|
+ opts = opts || {};
|
|
|
+ this.x = x; this.y = y;
|
|
|
+ var angle = opts.angle !== undefined ? opts.angle : Math.random() * Math.PI * 2;
|
|
|
+ var speed = opts.speed !== undefined ? opts.speed : 2 + Math.random() * 5;
|
|
|
+ this.vx = Math.cos(angle) * speed;
|
|
|
+ this.vy = Math.sin(angle) * speed - (opts.lift || 0);
|
|
|
+ this.g = opts.g !== undefined ? opts.g : 0.12;
|
|
|
+ this.life = opts.life || 80 + Math.random() * 40;
|
|
|
+ this.maxLife = this.life;
|
|
|
+ this.size = opts.size || 3 + Math.random() * 4;
|
|
|
+ this.color = opts.color || ('hsl(' + (Math.random()*360) + ',100%,60%)');
|
|
|
+ this.shape = opts.shape || 'rect';
|
|
|
+ this.rot = Math.random() * Math.PI;
|
|
|
+ this.vr = (Math.random() - 0.5) * 0.3;
|
|
|
+}
|
|
|
+
|
|
|
+Particle.prototype.update = function(){
|
|
|
+ this.x += this.vx;
|
|
|
+ this.y += this.vy;
|
|
|
+ this.vy += this.g;
|
|
|
+ this.vx *= 0.995;
|
|
|
+ this.rot += this.vr;
|
|
|
+ this.life--;
|
|
|
+};
|
|
|
+
|
|
|
+Particle.prototype.draw = function(ctx){
|
|
|
+ var a = this.life / this.maxLife;
|
|
|
+ ctx.save();
|
|
|
+ ctx.globalAlpha = a;
|
|
|
+ ctx.translate(this.x, this.y);
|
|
|
+ ctx.rotate(this.rot);
|
|
|
+ ctx.fillStyle = this.color;
|
|
|
+ ctx.shadowBlur = 12;
|
|
|
+ ctx.shadowColor = this.color;
|
|
|
+ if(this.shape === 'circle'){
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
|
|
+ ctx.fill();
|
|
|
+ } else {
|
|
|
+ ctx.fillRect(-this.size/2, -this.size/2, this.size, this.size * 1.6);
|
|
|
+ }
|
|
|
+ ctx.restore();
|
|
|
+};
|
|
|
+
|
|
|
+// 🌟 Взрыв фейерверка в точке (x, y)
|
|
|
+function fireworkBurst(x, y, hue){
|
|
|
+ var count = 50 + Math.floor(Math.random() * 30);
|
|
|
+ for(var i=0;i<count;i++){
|
|
|
+ var ang = (i / count) * Math.PI * 2 + Math.random() * 0.3;
|
|
|
+ var sp = 2 + Math.random() * 6;
|
|
|
+ particles.push(new Particle(x, y, {
|
|
|
+ angle: ang, speed: sp, g: 0.15, life: 70 + Math.random() * 50,
|
|
|
+ size: 3 + Math.random() * 3,
|
|
|
+ color: 'hsl(' + (hue + Math.random() * 40) + ',100%,' + (55 + Math.random()*20) + '%)',
|
|
|
+ shape: Math.random() > 0.5 ? 'circle' : 'rect'
|
|
|
+ }));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 🎊 Конфетти, падающее сверху
|
|
|
+function confettiRain(count){
|
|
|
+ for(var i=0;i<count;i++){
|
|
|
+ particles.push(new Particle(
|
|
|
+ Math.random() * fxCanvas.width,
|
|
|
+ -10 - Math.random() * 100,
|
|
|
+ {
|
|
|
+ angle: Math.PI / 2,
|
|
|
+ speed: 1 + Math.random() * 3,
|
|
|
+ g: 0.05,
|
|
|
+ life: 200 + Math.random() * 100,
|
|
|
+ size: 3 + Math.random() * 5,
|
|
|
+ color: 'hsl(' + (Math.random()*360) + ',100%,65%)',
|
|
|
+ shape: 'rect'
|
|
|
+ }
|
|
|
+ ));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 💔 Грустные «капли» — вниз и медленно гаснут
|
|
|
+function sadRain(count){
|
|
|
+ for(var i=0;i<count;i++){
|
|
|
+ particles.push(new Particle(
|
|
|
+ Math.random() * fxCanvas.width,
|
|
|
+ -10,
|
|
|
+ {
|
|
|
+ angle: Math.PI / 2,
|
|
|
+ speed: 0.5 + Math.random() * 1.5,
|
|
|
+ g: 0.03,
|
|
|
+ life: 250,
|
|
|
+ size: 3 + Math.random() * 4,
|
|
|
+ color: 'hsl(' + (350 + Math.random()*20) + ',80%,55%)',
|
|
|
+ shape: 'circle'
|
|
|
+ }
|
|
|
+ ));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 🎬 Основной цикл отрисовки эффектов
|
|
|
+function fxLoop(){
|
|
|
+ fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
|
|
|
+ for(var i = particles.length - 1; i >= 0; i--){
|
|
|
+ var p = particles[i];
|
|
|
+ p.update();
|
|
|
+ p.draw(fxCtx);
|
|
|
+ if(p.life <= 0 || p.y > fxCanvas.height + 50){
|
|
|
+ particles.splice(i, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(particles.length > 0 || fxRunning){
|
|
|
+ requestAnimationFrame(fxLoop);
|
|
|
+ } else {
|
|
|
+ fxRunning = false;
|
|
|
+ fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function startFxLoop(){
|
|
|
+ if(fxRunning) return;
|
|
|
+ fxRunning = true;
|
|
|
+ fxLoop();
|
|
|
+}
|
|
|
+
|
|
|
+// 🚀 Запуск салюта (серия взрывов в течение 2 секунд)
|
|
|
+function startFireworkShow(duration){
|
|
|
+ startFxLoop();
|
|
|
+ var startTime = performance.now();
|
|
|
+ function scheduleBurst(){
|
|
|
+ if(performance.now() - startTime > duration) return;
|
|
|
+ var x = fxCanvas.width * (0.2 + Math.random() * 0.6);
|
|
|
+ var y = fxCanvas.height * (0.2 + Math.random() * 0.4);
|
|
|
+ var hue = Math.random() * 360;
|
|
|
+ fireworkBurst(x, y, hue);
|
|
|
+ beep(180 + Math.random() * 120, 0.08, 'sawtooth', 0.06); // «бум»
|
|
|
+ setTimeout(scheduleBurst, 180 + Math.random() * 280);
|
|
|
+ }
|
|
|
+ scheduleBurst();
|
|
|
+}
|
|
|
+
|
|
|
+// =========================================================
|
|
|
+// 🔊 ЗВУКИ ПОБЕДЫ / ПРОИГРЫША
|
|
|
+// =========================================================
|
|
|
+function playVictorySound(){
|
|
|
+ if(!soundOn) return;
|
|
|
+ ensureAudio();
|
|
|
+ if(!audioCtx) return;
|
|
|
+ // мажорный аккорд-фанфары: C-E-G-C
|
|
|
+ var notes = [523.25, 659.25, 783.99, 1046.50];
|
|
|
+ notes.forEach(function(f, i){
|
|
|
+ setTimeout(function(){ beep(f, 0.35, 'triangle', 0.18); }, i * 140);
|
|
|
+ });
|
|
|
+ setTimeout(function(){
|
|
|
+ // финальный подъём
|
|
|
+ beep(1567.98, 0.6, 'triangle', 0.20);
|
|
|
+ }, 700);
|
|
|
+}
|
|
|
+
|
|
|
+function playGoodSound(){
|
|
|
+ if(!soundOn) return;
|
|
|
+ beep(523.25, 0.20, 'triangle', 0.18);
|
|
|
+ setTimeout(function(){ beep(783.99, 0.30, 'triangle', 0.18); }, 160);
|
|
|
+}
|
|
|
+
|
|
|
+function playMehSound(){
|
|
|
+ if(!soundOn) return;
|
|
|
+ beep(440, 0.25, 'sine', 0.15);
|
|
|
+ setTimeout(function(){ beep(494, 0.30, 'sine', 0.15); }, 180);
|
|
|
+}
|
|
|
+
|
|
|
+function playLoseSound(){
|
|
|
+ if(!soundOn) return;
|
|
|
+ ensureAudio();
|
|
|
+ if(!audioCtx) return;
|
|
|
+ // нисходящая грустная гамма
|
|
|
+ var notes = [440, 392, 349.23, 293.66, 261.63];
|
|
|
+ notes.forEach(function(f, i){
|
|
|
+ setTimeout(function(){ beep(f, 0.30, 'sawtooth', 0.14); }, i * 180);
|
|
|
+ });
|
|
|
+}
|
|
|
document.getElementById('quizProgress').style.width = '100%';
|
|
|
document.getElementById('quizQuestion').style.display = 'none';
|
|
|
document.getElementById('quizOptions').style.display = 'none';
|
|
|
var res = document.getElementById('quizResult');
|
|
|
res.style.display = 'flex';
|
|
|
+
|
|
|
var pct = Math.round(qScore / quizQuestions.length * 100);
|
|
|
document.getElementById('finalScore').textContent = qScore + ' / ' + quizQuestions.length;
|
|
|
- var grade = pct >= 90 ? 'Отлично! 🏆' : pct >= 70 ? 'Хорошо! 👍' : pct >= 50 ? 'Неплохо, но повтори материал.' : 'Попробуй пройти курс заново.';
|
|
|
- document.getElementById('finalText').textContent = pct + '% — ' + grade;
|
|
|
- if(pct >= 70) correctSound(); else wrongSound();
|
|
|
+
|
|
|
+ var badge = document.getElementById('finalBadge');
|
|
|
+
|
|
|
+ // 🎆 очищаем возможные старые частицы
|
|
|
+ particles = [];
|
|
|
+ fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
|
|
|
+
|
|
|
+ if(pct >= 90){
|
|
|
+ // 🏆 ПОБЕДА
|
|
|
+ badge.className = 'final-badge win';
|
|
|
+ badge.textContent = '🏆 ПОБЕДА!';
|
|
|
+ document.getElementById('finalText').textContent = pct + '% — Отлично! Ты отлично разбираешься в волнах!';
|
|
|
+ playVictorySound();
|
|
|
+ // залп салюта + конфетти
|
|
|
+ startFireworkShow(2200);
|
|
|
+ confettiRain(120);
|
|
|
+ setTimeout(function(){ confettiRain(80); }, 500);
|
|
|
+ }
|
|
|
+ else if(pct >= 70){
|
|
|
+ // 👍 ХОРОШО
|
|
|
+ badge.className = 'final-badge good';
|
|
|
+ badge.textContent = '👍 ХОРОШО!';
|
|
|
+ document.getElementById('finalText').textContent = pct + '% — Хороший результат! Ещё чуть-чуть до идеала.';
|
|
|
+ playGoodSound();
|
|
|
+ confettiRain(70);
|
|
|
+ startFxLoop();
|
|
|
+ }
|
|
|
+ else if(pct >= 50){
|
|
|
+ // 😕 СРЕДНЕ
|
|
|
+ badge.className = 'final-badge meh';
|
|
|
+ badge.textContent = '😕 МОЖНО ЛУЧШЕ';
|
|
|
+ document.getElementById('finalText').textContent = pct + '% — Неплохо, но стоит повторить материал.';
|
|
|
+ playMehSound();
|
|
|
+ confettiRain(25);
|
|
|
+ startFxLoop();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 💔 ПРОВАЛ
|
|
|
+ badge.className = 'final-badge lose';
|
|
|
+ badge.textContent = '💔 ПОПРОБУЙ ЕЩЁ';
|
|
|
+ document.getElementById('finalText').textContent = pct + '% — Материал стоит пройти заново, ты справишься!';
|
|
|
+ playLoseSound();
|
|
|
+ sadRain(60);
|
|
|
+ startFxLoop();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
document.getElementById('btnRestart').addEventListener('click', function(){
|