|
@@ -184,6 +184,96 @@
|
|
|
border:1px solid var(--neon-purple);
|
|
border:1px solid var(--neon-purple);
|
|
|
background:linear-gradient(145deg,rgba(162,89,255,.15),rgba(0,229,255,.08));
|
|
background:linear-gradient(145deg,rgba(162,89,255,.15),rgba(0,229,255,.08));
|
|
|
box-shadow:0 0 20px rgba(162,89,255,.4);font-weight:700;}
|
|
box-shadow:0 0 20px rgba(162,89,255,.4);font-weight:700;}
|
|
|
|
|
+ /* 🎨 Карточки с PNG-картинками */
|
|
|
|
|
+ .card-face{
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .card-face .card-img{
|
|
|
|
|
+ width: 72px;
|
|
|
|
|
+ height: 72px;
|
|
|
|
|
+ object-fit: contain;
|
|
|
|
|
+ animation: cardFloat 3s ease-in-out infinite;
|
|
|
|
|
+ transition: transform .3s;
|
|
|
|
|
+ }
|
|
|
|
|
+ @keyframes cardFloat{
|
|
|
|
|
+ 0%,100%{transform: translateY(0) scale(1);}
|
|
|
|
|
+ 50%{transform: translateY(-4px) scale(1.04);}
|
|
|
|
|
+ }
|
|
|
|
|
+ .card-face .card-title{
|
|
|
|
|
+ font-size: .95rem;
|
|
|
|
|
+ letter-spacing: 1.5px;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 🦇 Эхолокация — зелёный неон */
|
|
|
|
|
+ .card.eco .card-face{
|
|
|
|
|
+ background: linear-gradient(145deg, rgba(57,255,20,.15), rgba(0,229,255,.06));
|
|
|
|
|
+ border-color: var(--neon-green);
|
|
|
|
|
+ box-shadow: 0 0 25px rgba(57,255,20,.35);
|
|
|
|
|
+ color: var(--neon-green);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.eco .card-img{
|
|
|
|
|
+ filter: drop-shadow(0 0 14px #39ff14) drop-shadow(0 0 6px #39ff14);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.eco .card-face.back{
|
|
|
|
|
+ color: #d8ffd0;
|
|
|
|
|
+ border-color: var(--neon-cyan);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 🩻 УЗИ — циан */
|
|
|
|
|
+ .card.uzi .card-face{
|
|
|
|
|
+ background: linear-gradient(145deg, rgba(0,229,255,.15), rgba(162,89,255,.06));
|
|
|
|
|
+ border-color: var(--neon-cyan);
|
|
|
|
|
+ box-shadow: 0 0 25px rgba(0,229,255,.35);
|
|
|
|
|
+ color: var(--neon-cyan);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.uzi .card-img{
|
|
|
|
|
+ filter: drop-shadow(0 0 14px #00e5ff) drop-shadow(0 0 6px #00e5ff);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.uzi .card-face.back{
|
|
|
|
|
+ color: #cfeaff;
|
|
|
|
|
+ border-color: var(--neon-purple);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 🌍 Сейсмология — фиолетовый */
|
|
|
|
|
+ .card.seismo .card-face{
|
|
|
|
|
+ background: linear-gradient(145deg, rgba(162,89,255,.15), rgba(255,46,196,.06));
|
|
|
|
|
+ border-color: var(--neon-purple);
|
|
|
|
|
+ box-shadow: 0 0 25px rgba(162,89,255,.35);
|
|
|
|
|
+ color: var(--neon-purple);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.seismo .card-img{
|
|
|
|
|
+ filter: drop-shadow(0 0 14px #a259ff) drop-shadow(0 0 6px #a259ff);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.seismo .card-face.back{
|
|
|
|
|
+ color: #e8d5ff;
|
|
|
|
|
+ border-color: var(--neon-pink);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 🎸 Музыка — розовый */
|
|
|
|
|
+ .card.music .card-face{
|
|
|
|
|
+ background: linear-gradient(145deg, rgba(255,46,196,.15), rgba(0,229,255,.06));
|
|
|
|
|
+ border-color: var(--neon-pink);
|
|
|
|
|
+ box-shadow: 0 0 25px rgba(255,46,196,.35);
|
|
|
|
|
+ color: var(--neon-pink);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.music .card-img{
|
|
|
|
|
+ filter: drop-shadow(0 0 14px #ff2ec4) drop-shadow(0 0 6px #ff2ec4);
|
|
|
|
|
+ }
|
|
|
|
|
+ .card.music .card-face.back{
|
|
|
|
|
+ color: #ffd5f4;
|
|
|
|
|
+ border-color: var(--neon-cyan);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Задняя сторона — без покачивания */
|
|
|
|
|
+ .card-face.back .card-img{
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ animation: none;
|
|
|
|
|
+ }
|
|
|
.card-face.back{transform:rotateY(180deg);font-weight:400;font-size:.85rem;
|
|
.card-face.back{transform:rotateY(180deg);font-weight:400;font-size:.85rem;
|
|
|
border-color:var(--neon-cyan);color:#cfeaff;}
|
|
border-color:var(--neon-cyan);color:#cfeaff;}
|
|
|
.pairs{display:grid;grid-template-columns:1fr 1fr;gap:24px;width:min(700px,100%);margin:16px 0;}
|
|
.pairs{display:grid;grid-template-columns:1fr 1fr;gap:24px;width:min(700px,100%);margin:16px 0;}
|
|
@@ -269,6 +359,28 @@
|
|
|
#quizResult{position:relative;z-index:10;}
|
|
#quizResult{position:relative;z-index:10;}
|
|
|
.hint{font-size:.8rem;color:var(--neon-pink);letter-spacing:1px;
|
|
.hint{font-size:.8rem;color:var(--neon-pink);letter-spacing:1px;
|
|
|
margin-top:8px;min-height:1.2em;}
|
|
margin-top:8px;min-height:1.2em;}
|
|
|
|
|
+ .track-slot{
|
|
|
|
|
+ width:70px;height:70px;border-radius:12px;
|
|
|
|
|
+ display:flex;align-items:center;justify-content:center;
|
|
|
|
|
+ font-size:2rem;
|
|
|
|
|
+ border:2px dashed rgba(0,229,255,.4);
|
|
|
|
|
+ background:rgba(0,229,255,.05);
|
|
|
|
|
+ color:rgba(200,220,255,.3);
|
|
|
|
|
+ transition:.3s;
|
|
|
|
|
+ }
|
|
|
|
|
+ .track-slot.filled{
|
|
|
|
|
+ border-style:solid;
|
|
|
|
|
+ border-color:var(--neon-green);
|
|
|
|
|
+ background:rgba(57,255,20,.1);
|
|
|
|
|
+ box-shadow:0 0 20px rgba(57,255,20,.5);
|
|
|
|
|
+ color:#fff;
|
|
|
|
|
+ animation:pop .4s cubic-bezier(.2,1.4,.5,1);
|
|
|
|
|
+ }
|
|
|
|
|
+ #instrBtns .btn.active{
|
|
|
|
|
+ background:var(--neon-pink);
|
|
|
|
|
+ color:#000;
|
|
|
|
|
+ box-shadow:0 0 25px var(--neon-pink);
|
|
|
|
|
+ }
|
|
|
.table{font-size:.85rem;color:#cfeaff;margin-top:8px;text-align:center;line-height:1.8;}
|
|
.table{font-size:.85rem;color:#cfeaff;margin-top:8px;text-align:center;line-height:1.8;}
|
|
|
.table span{color:var(--neon-cyan);font-weight:700;}
|
|
.table span{color:var(--neon-cyan);font-weight:700;}
|
|
|
</style>
|
|
</style>
|
|
@@ -359,66 +471,128 @@
|
|
|
<button class="btn" id="btnNext4" type="button" disabled>Далее →</button>
|
|
<button class="btn" id="btnNext4" type="button" disabled>Далее →</button>
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
- <!-- SCENE 5 -->
|
|
|
|
|
|
|
+ <!-- SCENE 5 -->
|
|
|
<section class="scene" id="scene5">
|
|
<section class="scene" id="scene5">
|
|
|
<h2>Виды волн. Звук — продольная волна</h2>
|
|
<h2>Виды волн. Звук — продольная волна</h2>
|
|
|
|
|
|
|
|
- <div class="controls">
|
|
|
|
|
- <button class="btn" id="btnLong" type="button">Продольная</button>
|
|
|
|
|
- <button class="btn pink" id="btnTrans" type="button">Поперечная</button>
|
|
|
|
|
|
|
+ <div class="canvas-wrap">
|
|
|
|
|
+ <canvas id="typeCanvas" width="800" height="320"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="canvas-wrap" style="position:relative;">
|
|
|
|
|
- <canvas id="typeCanvas" width="800" height="220"></canvas>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 🎮 Мини-игра -->
|
|
|
|
|
- <div id="waveGame" style="max-width:720px;text-align:center;">
|
|
|
|
|
|
|
+ <!-- 🎮 Мини-игра «Собери звуковую дорожку» -->
|
|
|
|
|
+ <div id="waveGame" style="max-width:760px;text-align:center;">
|
|
|
<p style="margin-bottom:8px;">
|
|
<p style="margin-bottom:8px;">
|
|
|
- 🎯 <b>Мини-игра:</b> нажми <b>«Толкнуть поршень»</b> и кликай по
|
|
|
|
|
- <span style="color:var(--neon-pink);font-weight:800;">розовым сгусткам (сжатиям)</span>,
|
|
|
|
|
- когда они проплывают мимо.
|
|
|
|
|
|
|
+ 🎧 <b>Мини-игра:</b> нажми на каждый инструмент по очереди — собери <b>дорожку из 4 звуков</b>.
|
|
|
|
|
+ Смотри, как меняется волна!
|
|
|
</p>
|
|
</p>
|
|
|
- <div class="controls">
|
|
|
|
|
- <button class="btn green" id="btnPiston" type="button">🔊 Толкнуть поршень</button>
|
|
|
|
|
- <button class="btn pink" id="btnReset5" type="button">🔄 Сброс</button>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="controls" id="instrBtns">
|
|
|
|
|
+ <button class="btn" data-instr="piano" type="button">🎹 Пианино</button>
|
|
|
|
|
+ <button class="btn" data-instr="drum" type="button">🥁 Барабан</button>
|
|
|
|
|
+ <button class="btn" data-instr="whistle" type="button">🎺 Свисток</button>
|
|
|
|
|
+ <button class="btn" data-instr="bass" type="button">🎸 Бас</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div id="instrFreq" class="hint" style="font-size:.95rem;">
|
|
|
|
|
+ 🎵 Нажми на инструмент, чтобы услышать и увидеть волну
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Дорожка со слотами -->
|
|
|
|
|
+ <div id="track" style="display:flex;gap:10px;justify-content:center;margin:16px 0;">
|
|
|
|
|
+ <div class="track-slot" data-slot="0">—</div>
|
|
|
|
|
+ <div class="track-slot" data-slot="1">—</div>
|
|
|
|
|
+ <div class="track-slot" data-slot="2">—</div>
|
|
|
|
|
+ <div class="track-slot" data-slot="3">—</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <p class="hint" id="scene5Hint">Поймано сжатий: 0 / 5</p>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <p class="hint" id="scene5Hint">Собрано звуков: 0 / 4</p>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 💡 Пояснение (появляется после победы) -->
|
|
<!-- 💡 Пояснение (появляется после победы) -->
|
|
|
<div id="waveFact" class="table" style="max-width:720px;display:none;">
|
|
<div id="waveFact" class="table" style="max-width:720px;display:none;">
|
|
|
- 🎓 <b>Что ты сейчас сделал:</b><br>
|
|
|
|
|
- Поршень толкает молекулы → они <span>сжимаются</span>, потом расходятся → возникает
|
|
|
|
|
- <span>разрежение</span>. Волна <span>бежит вправо</span>, а каждая молекула
|
|
|
|
|
- <span>колеблется на месте</span> и никуда не улетает.<br>
|
|
|
|
|
- 🔊 Именно так звук распространяется через воздух — продольной волной!
|
|
|
|
|
|
|
+ 🎓 <b>Разные звуки = разные волны.</b><br>
|
|
|
|
|
+ 🔵 <b>Продольная</b> (сверху) — сжатия и разрежения. Так идёт звук в воздухе.<br>
|
|
|
|
|
+ 🟢 <b>Поперечная</b> (снизу) — гребни и впадины. Так колеблется струна или волна на воде.<br>
|
|
|
|
|
+ 🔊 Обе волны переносят <span>энергию</span>, а частицы среды <span>колеблются на месте</span>.
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<button class="btn" id="btnNext5" type="button" disabled>Далее →</button>
|
|
<button class="btn" id="btnNext5" type="button" disabled>Далее →</button>
|
|
|
</section>
|
|
</section>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<!-- SCENE 6 -->
|
|
<!-- SCENE 6 -->
|
|
|
<section class="scene" id="scene6">
|
|
<section class="scene" id="scene6">
|
|
|
<h2>Применение звуковых волн</h2>
|
|
<h2>Применение звуковых волн</h2>
|
|
|
<p>Нажми на карточку, чтобы узнать подробнее.</p>
|
|
<p>Нажми на карточку, чтобы узнать подробнее.</p>
|
|
|
- <div class="cards" id="cards">
|
|
|
|
|
- <div class="card"><div class="card-inner">
|
|
|
|
|
- <div class="card-face">Эхолокация</div>
|
|
|
|
|
- <div class="card-face back">Летучие мыши и дельфины определяют препятствия по отражённому ультразвуку.</div>
|
|
|
|
|
|
|
+ <div class="cards" id="cards">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="card eco"><div class="card-inner">
|
|
|
|
|
+ <div class="card-face">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/616/616554.png"
|
|
|
|
|
+ alt="Эхолокация"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🦇',style:'font-size:3.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div class="card-title">Эхолокация</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-face back">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/616/616554.png"
|
|
|
|
|
+ alt="Эхолокация"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🦇',style:'font-size:1.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div>Летучие мыши и дельфины определяют препятствия по отражённому ультразвуку.</div>
|
|
|
|
|
+ </div>
|
|
|
</div></div>
|
|
</div></div>
|
|
|
- <div class="card"><div class="card-inner">
|
|
|
|
|
- <div class="card-face">УЗИ</div>
|
|
|
|
|
- <div class="card-face back">Медицинская визуализация: ультразвук отражается от границ тканей.</div>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="card uzi"><div class="card-inner">
|
|
|
|
|
+ <div class="card-face">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/2966/2966327.png"
|
|
|
|
|
+ alt="УЗИ"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🩻',style:'font-size:3.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div class="card-title">УЗИ</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-face back">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/2966/2966327.png"
|
|
|
|
|
+ alt="УЗИ"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🩻',style:'font-size:1.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div>Медицинская визуализация: ультразвук отражается от границ тканей.</div>
|
|
|
|
|
+ </div>
|
|
|
</div></div>
|
|
</div></div>
|
|
|
- <div class="card"><div class="card-inner">
|
|
|
|
|
- <div class="card-face">Сейсмология</div>
|
|
|
|
|
- <div class="card-face back">По сейсмическим волнам изучают строение Земли и землетрясения.</div>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="card seismo"><div class="card-inner">
|
|
|
|
|
+ <div class="card-face">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/2909/2909760.png"
|
|
|
|
|
+ alt="Сейсмология"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🌍',style:'font-size:3.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div class="card-title">Сейсмология</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-face back">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/2909/2909760.png"
|
|
|
|
|
+ alt="Сейсмология"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🌍',style:'font-size:1.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div>По сейсмическим волнам изучают строение Земли и землетрясения.</div>
|
|
|
|
|
+ </div>
|
|
|
</div></div>
|
|
</div></div>
|
|
|
- <div class="card"><div class="card-inner">
|
|
|
|
|
- <div class="card-face">Музыка</div>
|
|
|
|
|
- <div class="card-face back">Струны и столбы воздуха создают стоячие волны — ноты.</div>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="card music"><div class="card-inner">
|
|
|
|
|
+ <div class="card-face">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/384/384281.png"
|
|
|
|
|
+ alt="Музыка"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🎸',style:'font-size:3.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div class="card-title">Музыка</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-face back">
|
|
|
|
|
+ <img class="card-img"
|
|
|
|
|
+ src="https://cdn-icons-png.flaticon.com/512/384/384281.png"
|
|
|
|
|
+ alt="Музыка"
|
|
|
|
|
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🎸',style:'font-size:1.4rem;line-height:1;'}));" />
|
|
|
|
|
+ <div>Струны и столбы воздуха создают стоячие волны — ноты.</div>
|
|
|
|
|
+ </div>
|
|
|
</div></div>
|
|
</div></div>
|
|
|
|
|
+
|
|
|
</div>
|
|
</div>
|
|
|
<p>Собери пару: пример ↔ физическое явление.</p>
|
|
<p>Собери пару: пример ↔ физическое явление.</p>
|
|
|
<div class="pairs">
|
|
<div class="pairs">
|
|
@@ -828,278 +1002,260 @@ document.getElementById('btnCheck4').addEventListener('click', function(){
|
|
|
});
|
|
});
|
|
|
document.getElementById('btnNext4').addEventListener('click', function(){ goToScene(4); });
|
|
document.getElementById('btnNext4').addEventListener('click', function(){ goToScene(4); });
|
|
|
|
|
|
|
|
-/* ================= SCENE 5: виды волн + мини-игра ================= */
|
|
|
|
|
|
|
+/* ================= SCENE 5: две волны одновременно + мини-игра ================= */
|
|
|
var typeCanvas = document.getElementById('typeCanvas');
|
|
var typeCanvas = document.getElementById('typeCanvas');
|
|
|
var typeCtx = typeCanvas.getContext('2d');
|
|
var typeCtx = typeCanvas.getContext('2d');
|
|
|
var typeT = 0;
|
|
var typeT = 0;
|
|
|
-var waveType = 'long'; // 'long' | 'trans'
|
|
|
|
|
-
|
|
|
|
|
-// ---- Мини-игра ----
|
|
|
|
|
-var gameActive = false; // идёт ли волна сейчас
|
|
|
|
|
-var gameCaught = 0; // сколько сжатий поймано
|
|
|
|
|
-var gamePhase = 0; // фаза волны (0..2π повторяется)
|
|
|
|
|
-var gameSpeed = 0.06; // скорость бега волны
|
|
|
|
|
-var gameBursts = []; // визуальные "вспышки" от кликов
|
|
|
|
|
-var gameZones = []; // текущие зоны сжатий (пересчитывается каждый кадр)
|
|
|
|
|
-
|
|
|
|
|
-// Параметры волны
|
|
|
|
|
-var WAVE_N = 50; // сколько молекул
|
|
|
|
|
-var WAVE_LEN = 4; // сколько длин волн на экране
|
|
|
|
|
-
|
|
|
|
|
-// 🔘 Переключатели вида волны
|
|
|
|
|
-document.getElementById('btnLong').addEventListener('click', function(){
|
|
|
|
|
- waveType = 'long'; clickSound();
|
|
|
|
|
- document.getElementById('waveGame').style.display = 'block';
|
|
|
|
|
-});
|
|
|
|
|
-document.getElementById('btnTrans').addEventListener('click', function(){
|
|
|
|
|
- waveType = 'trans'; clickSound();
|
|
|
|
|
- // В поперечной волне мини-игра не нужна — прячем
|
|
|
|
|
- document.getElementById('waveGame').style.display = 'none';
|
|
|
|
|
-});
|
|
|
|
|
|
|
|
|
|
-// 🎮 Кнопка "Толкнуть поршень"
|
|
|
|
|
-document.getElementById('btnPiston').addEventListener('click', function(){
|
|
|
|
|
- if(gameActive) return; // уже идёт
|
|
|
|
|
- gameActive = true;
|
|
|
|
|
- gamePhase = 0;
|
|
|
|
|
- gameCaught = 0;
|
|
|
|
|
- gameBursts = [];
|
|
|
|
|
- document.getElementById('scene5Hint').textContent = 'Поймано сжатий: 0 / 5';
|
|
|
|
|
- document.getElementById('waveFact').style.display = 'none';
|
|
|
|
|
- document.getElementById('btnNext5').disabled = true;
|
|
|
|
|
- ensureAudio();
|
|
|
|
|
- beep(180, 0.25, 'sawtooth', 0.20); // низкий "толчок поршня"
|
|
|
|
|
-});
|
|
|
|
|
|
|
+// 🎼 Инструменты
|
|
|
|
|
+var INSTRUMENTS = {
|
|
|
|
|
+ piano: { name:'Пианино', emoji:'🎹', freq:261.63, wave:'sine', color:'#00e5ff', desc:'261 Гц · До первой октавы' },
|
|
|
|
|
+ drum: { name:'Барабан', emoji:'🥁', freq:80, wave:'noise', color:'#ff2ec4', desc:'~80 Гц · шумный удар' },
|
|
|
|
|
+ whistle: { name:'Свисток', emoji:'🎺', freq:1046.50, wave:'sine', color:'#39ff14', desc:'1046 Гц · До второй октавы (высокий)' },
|
|
|
|
|
+ bass: { name:'Бас', emoji:'🎸', freq:130.81, wave:'square', color:'#a259ff', desc:'130 Гц · До малой октавы (низкий)' }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
-// 🔄 Кнопка "Сброс"
|
|
|
|
|
-document.getElementById('btnReset5').addEventListener('click', function(){
|
|
|
|
|
- gameActive = false;
|
|
|
|
|
- gameCaught = 0;
|
|
|
|
|
- gamePhase = 0;
|
|
|
|
|
- gameBursts = [];
|
|
|
|
|
- document.getElementById('scene5Hint').textContent = 'Поймано сжатий: 0 / 5';
|
|
|
|
|
- document.getElementById('waveFact').style.display = 'none';
|
|
|
|
|
- document.getElementById('btnNext5').disabled = true;
|
|
|
|
|
- clickSound();
|
|
|
|
|
-});
|
|
|
|
|
|
|
+var track = [];
|
|
|
|
|
+var currentInstr = null;
|
|
|
|
|
+var gameWin = false;
|
|
|
|
|
+
|
|
|
|
|
+// 🔘 Клики по инструментам
|
|
|
|
|
+var instrBtns = document.querySelectorAll('#instrBtns .btn');
|
|
|
|
|
+for(var ib=0; ib<instrBtns.length; ib++){
|
|
|
|
|
+ (function(btn){
|
|
|
|
|
+ btn.addEventListener('click', function(){
|
|
|
|
|
+ var key = btn.dataset.instr;
|
|
|
|
|
+ var instr = INSTRUMENTS[key];
|
|
|
|
|
+ if(!instr) return;
|
|
|
|
|
+ // Уже нажат — просто заменим волну, не добавляем в дорожку повторно
|
|
|
|
|
+ var alreadyIn = track.indexOf(key) !== -1;
|
|
|
|
|
+
|
|
|
|
|
+ // 🌊 Всегда переключаем текущий инструмент — волна меняется сразу
|
|
|
|
|
+ currentInstr = key;
|
|
|
|
|
+
|
|
|
|
|
+ // 📥 Добавляем в дорожку ТОЛЬКО если ещё не было
|
|
|
|
|
+ if(!alreadyIn){
|
|
|
|
|
+ track.push(key);
|
|
|
|
|
+
|
|
|
|
|
+ var slot = document.querySelector('.track-slot[data-slot="' + (track.length - 1) + '"]');
|
|
|
|
|
+ if(slot){
|
|
|
|
|
+ slot.textContent = instr.emoji;
|
|
|
|
|
+ slot.classList.add('filled');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 🎨 Подсветка кнопки
|
|
|
|
|
+ for(var k=0;k<instrBtns.length;k++) instrBtns[k].classList.remove('active');
|
|
|
|
|
+ btn.classList.add('active');
|
|
|
|
|
+
|
|
|
|
|
+ // 🔊 Звук играем всегда (даже при повторном клике)
|
|
|
|
|
+ playInstrumentSound(key);
|
|
|
|
|
+
|
|
|
|
|
+ // 📝 Инфо-подпись
|
|
|
|
|
+ document.getElementById('instrFreq').textContent =
|
|
|
|
|
+ instr.emoji + ' ' + instr.name + ' — ' + instr.desc;
|
|
|
|
|
|
|
|
-// 👆 Клик по canvas — попытка поймать сжатие
|
|
|
|
|
-typeCanvas.addEventListener('click', function(e){
|
|
|
|
|
- if(!gameActive || waveType !== 'long') return;
|
|
|
|
|
- var rect = typeCanvas.getBoundingClientRect();
|
|
|
|
|
- var scaleX = typeCanvas.width / rect.width;
|
|
|
|
|
- var clickX = (e.clientX - rect.left) * scaleX;
|
|
|
|
|
-
|
|
|
|
|
- // Ищем, попал ли клик в зону сжатия
|
|
|
|
|
- var hit = false;
|
|
|
|
|
- for(var i=0; i<gameZones.length; i++){
|
|
|
|
|
- var z = gameZones[i];
|
|
|
|
|
- if(clickX >= z.x - z.w/2 && clickX <= z.x + z.w/2){
|
|
|
|
|
- hit = true;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if(hit){
|
|
|
|
|
- gameCaught++;
|
|
|
|
|
- correctSound();
|
|
|
|
|
- gameBursts.push({ x: clickX, y: typeCanvas.height/2, r: 10, alpha: 1 });
|
|
|
|
|
- document.getElementById('scene5Hint').textContent =
|
|
|
|
|
- 'Поймано сжатий: ' + gameCaught + ' / 5';
|
|
|
|
|
- if(gameCaught >= 5){
|
|
|
|
|
- gameActive = false;
|
|
|
|
|
document.getElementById('scene5Hint').textContent =
|
|
document.getElementById('scene5Hint').textContent =
|
|
|
- '🎉 Отлично! Ты собрал продольную волну!';
|
|
|
|
|
- document.getElementById('waveFact').style.display = 'block';
|
|
|
|
|
- document.getElementById('btnNext5').disabled = false;
|
|
|
|
|
- correctSound();
|
|
|
|
|
- setTimeout(function(){ beep(1320, 0.4, 'triangle', 0.2); }, 200);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 'Собрано звуков: ' + track.length + ' / 4';
|
|
|
|
|
+
|
|
|
|
|
+ // 🎉 Победа — только один раз
|
|
|
|
|
+ if(track.length >= 4 && !gameWin){
|
|
|
|
|
+ gameWin = true;
|
|
|
|
|
+ setTimeout(function(){
|
|
|
|
|
+ document.getElementById('scene5Hint').textContent =
|
|
|
|
|
+ '🎉 Мелодия собрана! Меняй инструменты — волна меняется!';
|
|
|
|
|
+ document.getElementById('waveFact').style.display = 'block';
|
|
|
|
|
+ document.getElementById('btnNext5').disabled = false;
|
|
|
|
|
+ playMelody();
|
|
|
|
|
+ }, 400);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ })(instrBtns[ib]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 🔊 Синтез звука инструмента
|
|
|
|
|
+function playInstrumentSound(key){
|
|
|
|
|
+ if(!soundOn) return;
|
|
|
|
|
+ ensureAudio();
|
|
|
|
|
+ if(!audioCtx) return;
|
|
|
|
|
+ var instr = INSTRUMENTS[key];
|
|
|
|
|
+
|
|
|
|
|
+ if(instr.wave === 'noise'){
|
|
|
|
|
+ var bufferSize = audioCtx.sampleRate * 0.2;
|
|
|
|
|
+ var buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
|
|
|
|
|
+ var data = buffer.getChannelData(0);
|
|
|
|
|
+ for(var i=0;i<bufferSize;i++) data[i] = (Math.random()*2 - 1);
|
|
|
|
|
+ var src = audioCtx.createBufferSource();
|
|
|
|
|
+ src.buffer = buffer;
|
|
|
|
|
+ var g = audioCtx.createGain();
|
|
|
|
|
+ g.gain.setValueAtTime(0.25, audioCtx.currentTime);
|
|
|
|
|
+ g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.2);
|
|
|
|
|
+ src.connect(g); g.connect(audioCtx.destination);
|
|
|
|
|
+ src.start();
|
|
|
} else {
|
|
} else {
|
|
|
- wrongSound();
|
|
|
|
|
|
|
+ var o = audioCtx.createOscillator();
|
|
|
|
|
+ var g2 = audioCtx.createGain();
|
|
|
|
|
+ o.type = instr.wave;
|
|
|
|
|
+ o.frequency.value = instr.freq;
|
|
|
|
|
+ g2.gain.setValueAtTime(0, audioCtx.currentTime);
|
|
|
|
|
+ g2.gain.linearRampToValueAtTime(0.2, audioCtx.currentTime + 0.03);
|
|
|
|
|
+ g2.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.7);
|
|
|
|
|
+ o.connect(g2); g2.connect(audioCtx.destination);
|
|
|
|
|
+ o.start();
|
|
|
|
|
+ o.stop(audioCtx.currentTime + 0.8);
|
|
|
}
|
|
}
|
|
|
-});
|
|
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 🎵 Победная мелодия
|
|
|
|
|
+function playMelody(){
|
|
|
|
|
+ if(!soundOn) return;
|
|
|
|
|
+ ensureAudio();
|
|
|
|
|
+ if(!audioCtx) return;
|
|
|
|
|
+ var seq = track.map(function(k){ return INSTRUMENTS[k].freq; });
|
|
|
|
|
+ seq.forEach(function(f, i){
|
|
|
|
|
+ setTimeout(function(){
|
|
|
|
|
+ var o = audioCtx.createOscillator();
|
|
|
|
|
+ var g = audioCtx.createGain();
|
|
|
|
|
+ o.type = 'triangle';
|
|
|
|
|
+ o.frequency.value = f;
|
|
|
|
|
+ g.gain.setValueAtTime(0, audioCtx.currentTime);
|
|
|
|
|
+ g.gain.linearRampToValueAtTime(0.2, audioCtx.currentTime + 0.02);
|
|
|
|
|
+ g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5);
|
|
|
|
|
+ o.connect(g); g.connect(audioCtx.destination);
|
|
|
|
|
+ o.start();
|
|
|
|
|
+ o.stop(audioCtx.currentTime + 0.55);
|
|
|
|
|
+ }, i * 220);
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-// 🔁 Основной цикл анимации
|
|
|
|
|
|
|
+// 🔁 Основной цикл — РИСУЕМ ОБЕ ВОЛНЫ
|
|
|
function drawType(){
|
|
function drawType(){
|
|
|
var W = typeCanvas.width, H = typeCanvas.height;
|
|
var W = typeCanvas.width, H = typeCanvas.height;
|
|
|
typeCtx.clearRect(0,0,W,H);
|
|
typeCtx.clearRect(0,0,W,H);
|
|
|
|
|
+ typeT += 0.04;
|
|
|
|
|
|
|
|
- if(waveType === 'long'){
|
|
|
|
|
- drawLongitudinal(W, H);
|
|
|
|
|
- } else {
|
|
|
|
|
- drawTransverse(W, H);
|
|
|
|
|
- }
|
|
|
|
|
- requestAnimationFrame(drawType);
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ // ============== ВЕРХНЯЯ ПОЛОВИНА: ПРОДОЛЬНАЯ ==============
|
|
|
|
|
+ var rowLong = H * 0.30; // центр продольной волны (30% высоты)
|
|
|
|
|
+ drawLongitudinal(W, rowLong);
|
|
|
|
|
|
|
|
-// 🌊 ПРОДОЛЬНАЯ ВОЛНА — сжатия/разрежения + мини-игра
|
|
|
|
|
-function drawLongitudinal(W, H){
|
|
|
|
|
- var spacing = W / (WAVE_N + 1);
|
|
|
|
|
|
|
+ // Разделитель
|
|
|
|
|
+ typeCtx.strokeStyle = 'rgba(255,255,255,.08)';
|
|
|
|
|
+ typeCtx.setLineDash([6,8]);
|
|
|
|
|
+ typeCtx.lineWidth = 1;
|
|
|
|
|
+ typeCtx.beginPath();
|
|
|
|
|
+ typeCtx.moveTo(20, H / 2);
|
|
|
|
|
+ typeCtx.lineTo(W - 20, H / 2);
|
|
|
|
|
+ typeCtx.stroke();
|
|
|
|
|
+ typeCtx.setLineDash([]);
|
|
|
|
|
|
|
|
- // Если игра активна — фаза бежит. Иначе — статичная картинка.
|
|
|
|
|
- if(gameActive){
|
|
|
|
|
- gamePhase += gameSpeed;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // ============== НИЖНЯЯ ПОЛОВИНА: ПОПЕРЕЧНАЯ ==============
|
|
|
|
|
+ var rowTrans = H * 0.72; // центр поперечной волны (72% высоты)
|
|
|
|
|
+ drawTransverse(W, rowTrans);
|
|
|
|
|
|
|
|
- // Фоновый "коридор" среды
|
|
|
|
|
- typeCtx.fillStyle = 'rgba(0,229,255,.04)';
|
|
|
|
|
- typeCtx.fillRect(0, H/2 - 45, W, 90);
|
|
|
|
|
|
|
+ // Общие иконки "источник → ухо"
|
|
|
|
|
+ typeCtx.font = 'bold 18px Segoe UI';
|
|
|
|
|
+ typeCtx.fillStyle = 'rgba(57,255,20,.85)';
|
|
|
|
|
+ typeCtx.fillText('🔊', 4, rowLong + 6);
|
|
|
|
|
+ typeCtx.fillText('🔊', 4, rowTrans + 6);
|
|
|
|
|
+ typeCtx.fillStyle = 'rgba(0,229,255,.85)';
|
|
|
|
|
+ typeCtx.fillText('👂', W - 28, rowLong + 6);
|
|
|
|
|
+ typeCtx.fillText('👂', W - 28, rowTrans + 6);
|
|
|
|
|
|
|
|
- // Определяем зоны сжатий (там, где sin > 0.7)
|
|
|
|
|
- gameZones = [];
|
|
|
|
|
|
|
+ requestAnimationFrame(drawType);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // Рисуем молекулы
|
|
|
|
|
- for(var i=0; i<WAVE_N; i++){
|
|
|
|
|
- var baseX = spacing * (i + 1);
|
|
|
|
|
|
|
+// 🌊 ВЕРХНЯЯ: продольная волна — сжатия/разрежения
|
|
|
|
|
+function drawLongitudinal(W, y0){
|
|
|
|
|
+ var N = 55;
|
|
|
|
|
+ var spacing = W / (N + 1);
|
|
|
|
|
|
|
|
- // Смещение молекул: колеблются вокруг своей позиции.
|
|
|
|
|
- // Формула: shift = sin(phase - i * k) — классическая продольная волна.
|
|
|
|
|
- var k = (2 * Math.PI * WAVE_LEN) / WAVE_N;
|
|
|
|
|
- var shift = Math.sin(gamePhase - i * k) * 14;
|
|
|
|
|
|
|
+ // Фоновый "коридор"
|
|
|
|
|
+ typeCtx.fillStyle = 'rgba(255,46,196,.04)';
|
|
|
|
|
+ typeCtx.fillRect(30, y0 - 30, W - 60, 60);
|
|
|
|
|
|
|
|
|
|
+ for(var i=0;i<N;i++){
|
|
|
|
|
+ var baseX = spacing * (i + 1);
|
|
|
|
|
+ var k = (2 * Math.PI * 4) / N;
|
|
|
|
|
+ var shift = Math.sin(typeT - i * k) * 11;
|
|
|
var x = baseX + shift;
|
|
var x = baseX + shift;
|
|
|
-
|
|
|
|
|
- // Плотность: сжатие / разрежение
|
|
|
|
|
- var density = 0.5 + 0.5 * Math.sin(gamePhase - i * k); // 0..1
|
|
|
|
|
- var r = 4 + density * 4; // сжатие = крупнее
|
|
|
|
|
|
|
+ var density = 0.5 + 0.5 * Math.sin(typeT - i * k);
|
|
|
|
|
+ var r = 3.5 + density * 3.5;
|
|
|
var alpha = 0.35 + density * 0.65;
|
|
var alpha = 0.35 + density * 0.65;
|
|
|
|
|
|
|
|
typeCtx.beginPath();
|
|
typeCtx.beginPath();
|
|
|
typeCtx.fillStyle = 'rgba(255,46,196,' + alpha + ')';
|
|
typeCtx.fillStyle = 'rgba(255,46,196,' + alpha + ')';
|
|
|
- typeCtx.shadowBlur = 12; typeCtx.shadowColor = '#ff2ec4';
|
|
|
|
|
- typeCtx.arc(x, H/2, r, 0, Math.PI*2);
|
|
|
|
|
|
|
+ typeCtx.shadowBlur = 10; typeCtx.shadowColor = '#ff2ec4';
|
|
|
|
|
+ typeCtx.arc(x, y0, r, 0, Math.PI*2);
|
|
|
typeCtx.fill();
|
|
typeCtx.fill();
|
|
|
-
|
|
|
|
|
- // Запоминаем позиции сжатий (для игры)
|
|
|
|
|
- if(density > 0.85){
|
|
|
|
|
- gameZones.push({ x: x, w: spacing * 1.6 });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- typeCtx.shadowBlur = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 🎯 Подсветка зон сжатий (пульсирующий кружок)
|
|
|
|
|
- if(gameActive){
|
|
|
|
|
- for(var g=0; g<gameZones.length; g++){
|
|
|
|
|
- var z = gameZones[g];
|
|
|
|
|
- var pulse = 1 + 0.3 * Math.sin(typeT * 6);
|
|
|
|
|
- typeCtx.beginPath();
|
|
|
|
|
- typeCtx.strokeStyle = 'rgba(57,255,20,.6)';
|
|
|
|
|
- typeCtx.lineWidth = 2;
|
|
|
|
|
- typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#39ff14';
|
|
|
|
|
- typeCtx.arc(z.x, H/2, 22 * pulse, 0, Math.PI*2);
|
|
|
|
|
- typeCtx.stroke();
|
|
|
|
|
- }
|
|
|
|
|
- typeCtx.shadowBlur = 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // ✨ Вспышки от успешных кликов
|
|
|
|
|
- for(var b=gameBursts.length - 1; b >= 0; b--){
|
|
|
|
|
- var burst = gameBursts[b];
|
|
|
|
|
- typeCtx.beginPath();
|
|
|
|
|
- typeCtx.strokeStyle = 'rgba(57,255,20,' + burst.alpha + ')';
|
|
|
|
|
- typeCtx.lineWidth = 3;
|
|
|
|
|
- typeCtx.shadowBlur = 25; typeCtx.shadowColor = '#39ff14';
|
|
|
|
|
- typeCtx.arc(burst.x, burst.y, burst.r, 0, Math.PI*2);
|
|
|
|
|
- typeCtx.stroke();
|
|
|
|
|
- burst.r += 4;
|
|
|
|
|
- burst.alpha -= 0.05;
|
|
|
|
|
- if(burst.alpha <= 0) gameBursts.splice(b, 1);
|
|
|
|
|
}
|
|
}
|
|
|
typeCtx.shadowBlur = 0;
|
|
typeCtx.shadowBlur = 0;
|
|
|
|
|
|
|
|
- // 🎚️ ПОРШЕНЬ слева
|
|
|
|
|
- typeCtx.fillStyle = '#39ff14';
|
|
|
|
|
- typeCtx.shadowBlur = 20; typeCtx.shadowColor = '#39ff14';
|
|
|
|
|
- typeCtx.fillRect(10, H/2 - 30, 22, 60);
|
|
|
|
|
- typeCtx.shadowBlur = 0;
|
|
|
|
|
- typeCtx.fillStyle = '#39ff14';
|
|
|
|
|
- typeCtx.font = 'bold 12px Segoe UI';
|
|
|
|
|
- typeCtx.fillText('🔊', 14, H/2 + 5);
|
|
|
|
|
-
|
|
|
|
|
- // 👂 УХО справа
|
|
|
|
|
- typeCtx.fillStyle = '#00e5ff';
|
|
|
|
|
- typeCtx.font = 'bold 22px Segoe UI';
|
|
|
|
|
- typeCtx.fillText('👂', W - 35, H/2 + 8);
|
|
|
|
|
-
|
|
|
|
|
- // Подписи
|
|
|
|
|
- typeCtx.fillStyle = '#00e5ff';
|
|
|
|
|
- typeCtx.font = 'bold 14px Segoe UI';
|
|
|
|
|
- typeCtx.fillText('Продольная (звук в воздухе)', 50, 25);
|
|
|
|
|
-
|
|
|
|
|
- // Стрелка направления волны
|
|
|
|
|
- typeCtx.strokeStyle = '#39ff14';
|
|
|
|
|
- typeCtx.lineWidth = 2;
|
|
|
|
|
- typeCtx.beginPath();
|
|
|
|
|
- typeCtx.moveTo(W - 140, 25);
|
|
|
|
|
- typeCtx.lineTo(W - 45, 25);
|
|
|
|
|
- typeCtx.lineTo(W - 60, 18);
|
|
|
|
|
- typeCtx.moveTo(W - 45, 25);
|
|
|
|
|
- typeCtx.lineTo(W - 60, 32);
|
|
|
|
|
- typeCtx.stroke();
|
|
|
|
|
-
|
|
|
|
|
- typeT += 0.03;
|
|
|
|
|
|
|
+ // Подпись
|
|
|
|
|
+ typeCtx.fillStyle = '#ff2ec4';
|
|
|
|
|
+ typeCtx.font = 'bold 13px Segoe UI';
|
|
|
|
|
+ typeCtx.fillText('🔴 Продольная (звук в воздухе)', 50, y0 - 42);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 🌊 ПОПЕРЕЧНАЯ ВОЛНА — синусоида, ширина зависит от периода
|
|
|
|
|
-function drawTransverse(W, H){
|
|
|
|
|
|
|
+// 🌊 НИЖНЯЯ: поперечная волна — синусоида (или волна выбранного инструмента)
|
|
|
|
|
+function drawTransverse(W, y0){
|
|
|
// Ось
|
|
// Ось
|
|
|
- typeCtx.strokeStyle = 'rgba(0,229,255,.15)';
|
|
|
|
|
|
|
+ typeCtx.strokeStyle = 'rgba(0,229,255,.12)';
|
|
|
typeCtx.lineWidth = 1;
|
|
typeCtx.lineWidth = 1;
|
|
|
typeCtx.beginPath();
|
|
typeCtx.beginPath();
|
|
|
- typeCtx.moveTo(0, H/2);
|
|
|
|
|
- typeCtx.lineTo(W, H/2);
|
|
|
|
|
|
|
+ typeCtx.moveTo(30, y0);
|
|
|
|
|
+ typeCtx.lineTo(W - 30, y0);
|
|
|
typeCtx.stroke();
|
|
typeCtx.stroke();
|
|
|
|
|
|
|
|
- // 4 гребня на экране
|
|
|
|
|
- var wavesAcross = 4;
|
|
|
|
|
- var pixelAmp = 55;
|
|
|
|
|
|
|
+ var amp = 40;
|
|
|
|
|
|
|
|
- // Пунктирные границы
|
|
|
|
|
- typeCtx.strokeStyle = 'rgba(255,46,196,.2)';
|
|
|
|
|
- typeCtx.setLineDash([4,4]);
|
|
|
|
|
- typeCtx.lineWidth = 1;
|
|
|
|
|
- typeCtx.beginPath();
|
|
|
|
|
- typeCtx.moveTo(0, H/2 - pixelAmp); typeCtx.lineTo(W, H/2 - pixelAmp);
|
|
|
|
|
- typeCtx.moveTo(0, H/2 + pixelAmp); typeCtx.lineTo(W, H/2 + pixelAmp);
|
|
|
|
|
- typeCtx.stroke();
|
|
|
|
|
- typeCtx.setLineDash([]);
|
|
|
|
|
-
|
|
|
|
|
- // Сама волна
|
|
|
|
|
- typeCtx.strokeStyle = '#00e5ff';
|
|
|
|
|
- typeCtx.lineWidth = 2;
|
|
|
|
|
- typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#00e5ff';
|
|
|
|
|
- typeCtx.beginPath();
|
|
|
|
|
- for(var x=0; x<W; x+=3){
|
|
|
|
|
- var phaseX = (x / W) * Math.PI * 2 * wavesAcross;
|
|
|
|
|
- var y = H/2 + Math.sin(phaseX + typeT * 0.8) * pixelAmp;
|
|
|
|
|
- if(x===0) typeCtx.moveTo(x,y); else typeCtx.lineTo(x,y);
|
|
|
|
|
- }
|
|
|
|
|
- typeCtx.stroke();
|
|
|
|
|
- typeCtx.shadowBlur = 0;
|
|
|
|
|
|
|
+ // Если выбран инструмент — рисуем ЕГО волну
|
|
|
|
|
+ if(currentInstr && INSTRUMENTS[currentInstr]){
|
|
|
|
|
+ var instr = INSTRUMENTS[currentInstr];
|
|
|
|
|
+ typeCtx.strokeStyle = instr.color;
|
|
|
|
|
+ typeCtx.lineWidth = 2.5;
|
|
|
|
|
+ typeCtx.shadowBlur = 18; typeCtx.shadowColor = instr.color;
|
|
|
|
|
+ typeCtx.beginPath();
|
|
|
|
|
+ for(var x=30; x<W-30; x+=3){
|
|
|
|
|
+ var phase = (x / W) * Math.PI * 2 * 5 + typeT * 2;
|
|
|
|
|
+ var y;
|
|
|
|
|
+ if(instr.wave === 'sine'){
|
|
|
|
|
+ y = y0 + Math.sin(phase) * amp;
|
|
|
|
|
+ } else if(instr.wave === 'square'){
|
|
|
|
|
+ y = y0 + Math.sign(Math.sin(phase)) * amp * 0.8;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ y = y0 + (Math.random()*2 - 1) * amp * 0.85;
|
|
|
|
|
+ }
|
|
|
|
|
+ if(x===30) typeCtx.moveTo(x,y); else typeCtx.lineTo(x,y);
|
|
|
|
|
+ }
|
|
|
|
|
+ typeCtx.stroke();
|
|
|
|
|
+ typeCtx.shadowBlur = 0;
|
|
|
|
|
|
|
|
- // Частицы на гребнях
|
|
|
|
|
- var dotsCount = Math.round(wavesAcross * 6);
|
|
|
|
|
- for(var k=0;k<dotsCount;k++){
|
|
|
|
|
- var xx = (k + 0.5) * (W / dotsCount);
|
|
|
|
|
- var phaseXDot = (xx / W) * Math.PI * 2 * wavesAcross;
|
|
|
|
|
- var yy = H/2 + Math.sin(phaseXDot + typeT * 0.8) * pixelAmp;
|
|
|
|
|
|
|
+ typeCtx.fillStyle = instr.color;
|
|
|
|
|
+ typeCtx.font = 'bold 13px Segoe UI';
|
|
|
|
|
+ typeCtx.fillText(instr.emoji + ' ' + instr.name + ' · ' + instr.freq + ' Гц', 50, y0 - 42);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Обычная синусоида
|
|
|
|
|
+ typeCtx.strokeStyle = '#00e5ff';
|
|
|
|
|
+ typeCtx.lineWidth = 2;
|
|
|
|
|
+ typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#00e5ff';
|
|
|
typeCtx.beginPath();
|
|
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;
|
|
|
|
|
|
|
+ for(var x2=30; x2<W-30; x2+=3){
|
|
|
|
|
+ var y2 = y0 + Math.sin((x2/W)*Math.PI*2*5 + typeT * 2) * amp;
|
|
|
|
|
+ if(x2===30) typeCtx.moveTo(x2,y2); else typeCtx.lineTo(x2,y2);
|
|
|
|
|
+ }
|
|
|
|
|
+ typeCtx.stroke();
|
|
|
|
|
+ typeCtx.shadowBlur = 0;
|
|
|
|
|
|
|
|
- typeCtx.fillStyle = '#00e5ff';
|
|
|
|
|
- typeCtx.font = 'bold 14px Segoe UI';
|
|
|
|
|
- typeCtx.fillText('Поперечная (волны на воде, струна)', 20, 30);
|
|
|
|
|
|
|
+ typeCtx.fillStyle = '#00e5ff';
|
|
|
|
|
+ typeCtx.font = 'bold 13px Segoe UI';
|
|
|
|
|
+ typeCtx.fillText('🟢 Поперечная (струна, волна на воде)', 50, y0 - 42);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ▶️ Кнопка Далее
|
|
|
|
|
|
|
+// ▶️ Далее
|
|
|
document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
|
|
document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
|
|
|
|
|
|
|
|
-// Запуск цикла
|
|
|
|
|
|
|
+// Запуск
|
|
|
drawType();
|
|
drawType();
|
|
|
|
|
|
|
|
/* ================= SCENE 6: карточки + пары ================= */
|
|
/* ================= SCENE 6: карточки + пары ================= */
|