Просмотр исходного кода

Обновить 'docs/file.md'

u23-27dybenko 1 неделя назад
Родитель
Сommit
600d527f55
1 измененных файлов с 583 добавлено и 134 удалено
  1. 583 134
      docs/file.md

+ 583 - 134
docs/file.md

@@ -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(){