| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662 |
- <!DOCTYPE html>
- <html lang="ru">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Механические волны — неон</title>
- <style>
- :root{
- --neon-pink:#ff2ec4;
- --neon-cyan:#00e5ff;
- --neon-purple:#a259ff;
- --neon-green:#39ff14;
- --text:#e8e6ff;
- }
- *{box-sizing:border-box;margin:0;padding:0;}
- html,body{height:100%;}
- body{
- background:radial-gradient(circle at 20% 20%, #1a0033 0%, #0a0014 60%, #000 100%);
- color:var(--text);
- font-family:'Segoe UI','Trebuchet MS',sans-serif;
- overflow:hidden;
- }
- #app{position:relative;width:100%;height:100vh;overflow:hidden;}
- .scene{
- position:absolute;inset:0;display:none;flex-direction:column;
- align-items:center;justify-content:center;padding:80px 24px 24px;
- animation:fadeIn .6s ease;overflow-y:auto;
- }
- .scene.active{display:flex;}
- @keyframes fadeIn{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:none;}}
- h1,h2{font-weight:800;letter-spacing:2px;text-transform:uppercase;text-align:center;}
- h1{font-size:clamp(1.8rem,5vw,3.5rem);color:#fff;
- text-shadow:0 0 10px var(--neon-pink),0 0 30px var(--neon-pink),0 0 60px var(--neon-purple);
- margin-bottom:20px;}
- h2{font-size:clamp(1.3rem,3.5vw,2rem);color:#fff;
- text-shadow:0 0 8px var(--neon-cyan),0 0 20px var(--neon-cyan);margin-bottom:16px;}
- p{max-width:720px;text-align:center;line-height:1.6;opacity:.9;}
- .btn{
- background:transparent;color:var(--neon-cyan);
- border:2px solid var(--neon-cyan);padding:12px 28px;margin:8px;
- font-size:1rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;
- border-radius:8px;cursor:pointer;transition:.25s;
- box-shadow:0 0 12px rgba(0,229,255,.6), inset 0 0 12px rgba(0,229,255,.15);
- }
- .btn:hover{background:var(--neon-cyan);color:#000;
- box-shadow:0 0 25px var(--neon-cyan),0 0 60px var(--neon-cyan);transform:translateY(-2px);}
- .btn.pink{color:var(--neon-pink);border-color:var(--neon-pink);
- box-shadow:0 0 12px rgba(255,46,196,.6), inset 0 0 12px rgba(255,46,196,.15);}
- .btn.pink:hover{background:var(--neon-pink);color:#000;box-shadow:0 0 25px var(--neon-pink);}
- .btn.green{color:var(--neon-green);border-color:var(--neon-green);}
- .btn.green:hover{background:var(--neon-green);color:#000;box-shadow:0 0 25px var(--neon-green);}
- .btn:disabled{opacity:.35;cursor:not-allowed;transform:none;}
- .topbar{
- position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;
- align-items:center;padding:10px 16px;z-index:10;font-size:.8rem;
- letter-spacing:2px;text-transform:uppercase;color:var(--neon-cyan);
- text-shadow:0 0 8px var(--neon-cyan);
- }
- .topbar .progress{flex:1;height:4px;margin:0 16px;background:rgba(255,255,255,.1);
- border-radius:2px;overflow:hidden;}
- .topbar .progress div{height:100%;width:0%;
- background:linear-gradient(90deg,var(--neon-pink),var(--neon-cyan));
- box-shadow:0 0 12px var(--neon-cyan);transition:width .5s;}
- .icon-btn{background:transparent;border:1px solid var(--neon-cyan);color:var(--neon-cyan);
- border-radius:6px;padding:4px 10px;cursor:pointer;font-size:.75rem;}
- .icon-btn:hover{background:var(--neon-cyan);color:#000;}
- canvas{display:block;max-width:100%;}
- .canvas-wrap{border:1px solid rgba(0,229,255,.3);border-radius:12px;
- box-shadow:0 0 30px rgba(162,89,255,.35), inset 0 0 30px rgba(0,229,255,.1);
- background:rgba(0,0,0,.35);padding:8px;margin:16px 0;}
- .controls{display:flex;flex-wrap:wrap;justify-content:center;gap:12px;margin:16px 0;}
- .slider-box{display:flex;flex-direction:column;align-items:center;
- background:rgba(255,255,255,.04);padding:10px 16px;border-radius:10px;
- border:1px solid rgba(255,46,196,.35);}
- .slider-box label{font-size:.75rem;letter-spacing:2px;text-transform:uppercase;
- color:var(--neon-pink);margin-bottom:6px;}
- input[type=range]{-webkit-appearance:none;width:160px;height:4px;
- background:linear-gradient(90deg,var(--neon-pink),var(--neon-cyan));
- border-radius:2px;outline:none;}
- input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;
- border-radius:50%;background:#fff;box-shadow:0 0 12px var(--neon-cyan);cursor:pointer;}
- input[type=range]::-moz-range-thumb{width:16px;height:16px;border:none;border-radius:50%;
- background:#fff;box-shadow:0 0 12px var(--neon-cyan);cursor:pointer;}
- .tubes{display:flex;flex-direction:column;gap:12px;width:min(760px,100%);margin:12px 0;}
- .tube{position:relative;height:44px;border-radius:22px;background:rgba(255,255,255,.05);
- border:1px solid rgba(0,229,255,.3);overflow:hidden;}
- .tube .label{position:absolute;left:12px;top:50%;transform:translateY(-50%);
- font-size:.8rem;letter-spacing:2px;color:var(--neon-cyan);z-index:2;}
- .tube .impulse{position:absolute;top:50%;left:0;transform:translate(-50%,-50%);
- width:20px;height:20px;border-radius:50%;
- 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;}
- .quiz-options .btn.correct{background:var(--neon-green);color:#000;
- border-color:var(--neon-green);box-shadow:0 0 25px var(--neon-green);}
- .quiz-options .btn.wrong{background:#ff2e4c;color:#fff;border-color:#ff2e4c;}
- .modal{position:absolute;inset:0;background:rgba(0,0,0,.75);display:none;
- align-items:center;justify-content:center;z-index:50;backdrop-filter:blur(4px);}
- .modal.active{display:flex;}
- .modal-content{background:linear-gradient(145deg,#160028,#0a0014);
- border:1px solid var(--neon-cyan);border-radius:14px;padding:28px;
- max-width:520px;width:90%;box-shadow:0 0 40px rgba(0,229,255,.5);text-align:center;}
- .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
- gap:14px;width:min(800px,100%);margin:16px 0;}
- .card{perspective:1000px;height:130px;cursor:pointer;}
- .card-inner{position:relative;width:100%;height:100%;transition:transform .6s;
- transform-style:preserve-3d;}
- .card.flipped .card-inner{transform:rotateY(180deg);}
- .card-face{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
- border-radius:12px;padding:12px;text-align:center;backface-visibility:hidden;
- border:1px solid var(--neon-purple);
- 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;}
- /* 🎨 Карточки с 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;
- border-color:var(--neon-cyan);color:#cfeaff;}
- .pairs{display:grid;grid-template-columns:1fr 1fr;gap:24px;width:min(700px,100%);margin:16px 0;}
- .pair-col{display:flex;flex-direction:column;gap:10px;}
- .pair-item{padding:10px 14px;border-radius:8px;cursor:pointer;
- border:1px solid var(--neon-cyan);background:rgba(0,229,255,.05);
- text-align:center;font-size:.9rem;transition:.2s;}
- .pair-item:hover{background:rgba(0,229,255,.2);}
- .pair-item.selected{background:var(--neon-pink);color:#000;}
- .pair-item.matched{opacity:.35;pointer-events:none;border-color:var(--neon-green);}
- .molecules{position:relative;width:min(700px,100%);height:120px;
- border:1px dashed rgba(0,229,255,.4);border-radius:12px;margin:16px 0;
- background:rgba(0,0,0,.3);}
- .molecule{position:absolute;top:50%;transform:translate(-50%,-50%);
- width:26px;height:26px;border-radius:50%;
- background:radial-gradient(circle,#fff,var(--neon-cyan));
- box-shadow:0 0 15px var(--neon-cyan);cursor:pointer;transition:left .3s;}
- .molecule.placed{background:radial-gradient(circle,#fff,var(--neon-green));
- box-shadow:0 0 15px var(--neon-green);}
- .target{position:absolute;top:50%;transform:translate(-50%,-50%);
- width:30px;height:30px;border-radius:50%;border:2px dashed var(--neon-pink);}
- .progress-quiz{width:min(600px,100%);height:6px;border-radius:3px;
- background:rgba(255,255,255,.1);margin:16px 0;overflow:hidden;}
- .progress-quiz div{height:100%;width:0;
- background:linear-gradient(90deg,var(--neon-pink),var(--neon-cyan));
- transition:width .4s;}
- .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;}
- .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 span{color:var(--neon-cyan);font-weight:700;}
- </style>
- </head>
- <body>
- <div id="app">
- <div class="topbar">
- <span id="sceneLabel">СЦЕНА 1 / 7</span>
- <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">
- <canvas id="bgCanvas" width="900" height="500"
- style="position:absolute;top:0;left:0;width:100%;height:100%;opacity:.35;pointer-events:none;"></canvas>
- <div style="position:relative;z-index:2;display:flex;flex-direction:column;align-items:center;">
- <h1>Механические волны</h1>
- <p>Интерактивный курс: колебания, звук и среды распространения.</p>
- <p class="hint">Наушники приветствуются 🎧</p>
- <div style="margin-top:24px;">
- <button class="btn" id="btnStart" type="button">▶ Начать курс</button>
- <button class="btn pink" id="btnWhat" type="button">Что такое волна?</button>
- </div>
- </div>
- </section>
- <!-- SCENE 2 -->
- <section class="scene" id="scene2">
- <h2>Что такое механическая волна?</h2>
- <p>Это распространение колебаний в упругой среде. Частицы колеблются, но не переносятся вместе с волной.</p>
- <div class="canvas-wrap"><canvas id="chainCanvas" width="800" height="200"></canvas></div>
- <div class="controls">
- <div class="slider-box">
- <label>Частота: <span id="freqVal">3</span> Гц</label>
- <input type="range" id="freqSlider" min="1" max="10" value="3">
- </div>
- <div class="slider-box">
- <label>Амплитуда: <span id="ampVal">25</span> px · 🔊 <span id="ampVol">21</span>%</label>
- <input type="range" id="ampSlider" min="5" max="40" value="25">
- </div>
- <button class="btn green" id="btnPush" type="button">Толкнуть</button>
- </div>
- <p class="hint" id="scene2Hint">Толчков: 0 / 3</p>
- <button class="btn" id="btnNext2" type="button" disabled>Далее →</button>
- </section>
- <!-- SCENE 3 -->
- <section class="scene" id="scene3">
- <h2>Среда распространения. Скорость звука</h2>
- <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>
- <div class="quiz-options" id="quiz3">
- <button class="btn" data-ans="air" type="button">Воздух</button>
- <button class="btn" data-ans="water" type="button">Вода</button>
- <button class="btn" data-ans="steel" type="button">Сталь</button>
- </div>
- <button class="btn" id="btnNext3" type="button" disabled style="margin-top:20px;">Далее →</button>
- </section>
- <!-- SCENE 4 -->
- <section class="scene" id="scene4">
- <h2>Колебания: период, частота, амплитуда</h2>
- <div style="display:flex;flex-wrap:wrap;justify-content:center;gap:16px;">
- <div class="canvas-wrap"><canvas id="oscCanvas" width="360" height="260"></canvas></div>
- <div class="canvas-wrap"><canvas id="waveCanvas" width="360" height="260"></canvas></div>
- </div>
- <div class="controls">
- <div class="slider-box">
- <label>Амплитуда: <span id="ampVal2">60</span></label>
- <input type="range" id="ampSlider2" min="20" max="100" value="60">
- </div>
- <div class="slider-box">
- <label>Период: <span id="perVal">1.2</span> с</label>
- <input type="range" id="perSlider" min="4" max="30" value="12">
- </div>
- <button class="btn pink" id="btnCheck4" type="button">Проверить понимание</button>
- </div>
- <p class="table">Формулы: <span>T = 1/ν</span> · <span>ν = 1/T</span> · <span>v = λν</span></p>
- <button class="btn" id="btnNext4" type="button" disabled>Далее →</button>
- </section>
- <!-- SCENE 5 -->
- <section class="scene" id="scene5">
- <h2>Виды волн. Звук — продольная волна</h2>
- <div class="canvas-wrap">
- <canvas id="typeCanvas" width="800" height="320"></canvas>
- </div>
- <!-- 🎮 Мини-игра «Собери звуковую дорожку» -->
- <div id="waveGame" style="max-width:760px;text-align:center;">
- <p style="margin-bottom:8px;">
- 🎧 <b>Мини-игра:</b> нажми на каждый инструмент по очереди — собери <b>дорожку из 4 звуков</b>.
- Смотри, как меняется волна!
- </p>
- <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>
- <p class="hint" id="scene5Hint">Собрано звуков: 0 / 4</p>
- </div>
- <!-- 💡 Пояснение (появляется после победы) -->
- <div id="waveFact" class="table" style="max-width:720px;display:none;">
- 🎓 <b>Разные звуки = разные волны.</b><br>
- 🔵 <b>Продольная</b> (сверху) — сжатия и разрежения. Так идёт звук в воздухе.<br>
- 🟢 <b>Поперечная</b> (снизу) — гребни и впадины. Так колеблется струна или волна на воде.<br>
- 🔊 Обе волны переносят <span>энергию</span>, а частицы среды <span>колеблются на месте</span>.
- </div>
- <button class="btn" id="btnNext5" type="button" disabled>Далее →</button>
- </section>
-
- <!-- SCENE 6 -->
- <section class="scene" id="scene6">
- <h2>Применение звуковых волн</h2>
- <p>Нажми на карточку, чтобы узнать подробнее.</p>
- <div class="cards" id="cards">
- <div class="card eco"><div class="card-inner">
- <div class="card-face">
- <img class="card-img"
- src=
- 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=[616554.png](http://)
- alt="Эхолокация"
- onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🦇',style:'font-size:1.4rem;line-height:1;'}));" />
- <div>Летучие мыши и дельфины определяют препятствия по отражённому ультразвуку.</div>
- </div>
- </div></div>
- <div class="card uzi"><div class="card-inner">
- <div class="card-face">
- <img class="card-img"
- src=[2966327.png](http://)
- 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=[2966327.png](http://)
- alt="УЗИ"
- onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🩻',style:'font-size:1.4rem;line-height:1;'}));" />
- <div>Медицинская визуализация: ультразвук отражается от границ тканей.</div>
- </div>
- </div></div>
- <div class="card seismo"><div class="card-inner">
- <div class="card-face">
- <img class="card-img"
- src=[2909760.png](http://)
- 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=[2909760.png](http://)
- alt="Сейсмология"
- onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🌍',style:'font-size:1.4rem;line-height:1;'}));" />
- <div>По сейсмическим волнам изучают строение Земли и землетрясения.</div>
- </div>
- </div></div>
- <div class="card music"><div class="card-inner">
- <div class="card-face">
- <img class="card-img"
- src=[384281.png](http://)
- 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=[384281.png](http://)
- alt="Музыка"
- onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🎸',style:'font-size:1.4rem;line-height:1;'}));" />
- <div>Струны и столбы воздуха создают стоячие волны — ноты.</div>
- </div>
- </div></div>
- </div>
- <p>Собери пару: пример ↔ физическое явление.</p>
- <div class="pairs">
- <div class="pair-col" id="pairLeft"></div>
- <div class="pair-col" id="pairRight"></div>
- </div>
- <p class="hint" id="scene6Hint">Собрано пар: 0 / 4</p>
- <button class="btn" id="btnNext6" type="button" disabled>К тестированию →</button>
- </section>
- <!-- SCENE 7 -->
- <section class="scene" id="scene7">
- <h2 id="quizTitle">Итоговое тестирование</h2>
- <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 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>
- </div>
- </section>
- <!-- MODAL -->
- <div class="modal" id="modal">
- <div class="modal-content">
- <h2 id="modalTitle">…</h2>
- <p id="modalText" style="margin:16px 0;">…</p>
- <button class="btn" id="modalClose" type="button">Закрыть</button>
- </div>
- </div>
- </div>
- <script>
- /* ================= ЯДРО: НАВИГАЦИЯ + ЗВУК + МОДАЛКА ================= */
- var AudioCtx = window.AudioContext || window.webkitAudioContext;
- var audioCtx = null;
- var soundOn = true;
- function ensureAudio(){
- try{
- if(!audioCtx) audioCtx = new AudioCtx();
- if(audioCtx.state === 'suspended') audioCtx.resume();
- }catch(e){}
- }
- function beep(freq, dur, type, vol){
- if(!soundOn || !audioCtx) return;
- try{
- var o = audioCtx.createOscillator();
- var g = audioCtx.createGain();
- o.type = type || 'sine';
- o.frequency.value = freq || 440;
- g.gain.setValueAtTime(vol || 0.12, audioCtx.currentTime);
- g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + (dur || 0.15));
- o.connect(g); g.connect(audioCtx.destination);
- o.start(); o.stop(audioCtx.currentTime + (dur || 0.15));
- }catch(e){}
- }
- function correctSound(){ beep(880,0.12,'triangle',0.18); setTimeout(function(){ beep(1320,0.15,'triangle',0.18); },110); }
- function wrongSound(){ beep(220,0.25,'sawtooth',0.15); }
- function clickSound(){ beep(600,0.06,'square',0.08); }
- var scenes = ['scene1','scene2','scene3','scene4','scene5','scene6','scene7'];
- var currentScene = 0;
- function goToScene(idx){
- if(idx < 0 || idx >= scenes.length) return;
- var all = document.querySelectorAll('.scene');
- for(var i=0;i<all.length;i++) all[i].classList.remove('active');
- document.getElementById(scenes[idx]).classList.add('active');
- currentScene = idx;
- document.getElementById('sceneLabel').textContent = 'СЦЕНА ' + (idx+1) + ' / ' + scenes.length;
- document.getElementById('progressBar').style.width = (idx/(scenes.length-1)*100) + '%';
- clickSound();
- if(idx === 3 && typeof startOscillators === 'function') startOscillators();
- if(idx === 4 && typeof initMolecules === 'function') initMolecules();
- if(idx === 5 && typeof initPairs === 'function') initPairs();
- if(idx === 6 && typeof startQuiz === 'function') startQuiz();
- }
- var modal = document.getElementById('modal');
- function openModal(title, text){
- document.getElementById('modalTitle').textContent = title;
- document.getElementById('modalText').textContent = text;
- modal.classList.add('active');
- }
- document.getElementById('modalClose').addEventListener('click', function(){
- modal.classList.remove('active'); clickSound();
- });
- modal.addEventListener('click', function(e){ if(e.target === modal) modal.classList.remove('active'); });
- document.getElementById('soundToggle').addEventListener('click', function(e){
- soundOn = !soundOn;
- e.target.textContent = soundOn ? '🔊 ЗВУК' : '🔇 ВЫКЛ';
- if(soundOn){ ensureAudio(); clickSound(); }
- });
- /* ================= ФОН ================= */
- var bgCanvas = document.getElementById('bgCanvas');
- var bgCtx = bgCanvas.getContext('2d');
- var bgT = 0;
- function drawBg(){
- var W = bgCanvas.width, H = bgCanvas.height;
- bgCtx.clearRect(0,0,W,H);
- for(var k=0;k<3;k++){
- bgCtx.beginPath();
- bgCtx.strokeStyle = k===0 ? 'rgba(255,46,196,.5)' : k===1 ? 'rgba(0,229,255,.5)' : 'rgba(162,89,255,.5)';
- bgCtx.lineWidth = 2;
- bgCtx.shadowBlur = 20;
- bgCtx.shadowColor = bgCtx.strokeStyle;
- for(var x=0;x<W;x+=4){
- var y = H/2 + Math.sin(x*0.02 + bgT + k*1.3) * (60 + k*20);
- if(x===0) bgCtx.moveTo(x,y); else bgCtx.lineTo(x,y);
- }
- bgCtx.stroke();
- }
- bgCtx.shadowBlur = 0;
- bgT += 0.03;
- requestAnimationFrame(drawBg);
- }
- drawBg();
- /* ================= ГЛАВНАЯ: КНОПКИ ================= */
- document.getElementById('btnStart').addEventListener('click', function(){
- ensureAudio();
- clickSound();
- goToScene(1); // ← ПЕРЕХОД НА СЦЕНУ 2 (начало курса)
- });
- document.getElementById('btnWhat').addEventListener('click', function(){
- ensureAudio();
- clickSound();
- openModal(
- 'Что такое волна?',
- 'Механическая волна — это процесс распространения колебаний в упругой среде. Частицы среды колеблются около положения равновесия, а сама волна переносит энергию, но не вещество. Примеры: звук в воздухе, волны на воде, сейсмические волны в Земле.'
- );
- });
- /* ================= SCENE 2: цепочка ================= */
- (function(){
- var c = document.getElementById('chainCanvas');
- var ctx = c.getContext('2d');
- var t = 0, amp = 25, freq = 3, pushTime = -999, pushes = 0;
- // 🔊 Обновление звука при движении бегунка частоты:
- // 1 Гц → 440 Гц, 10 Гц → 4400 Гц (линейно: 440 × freq)
- function playFreqTone(){
- if(!soundOn) return;
- ensureAudio();
- if(!audioCtx) return;
- // останавливаем предыдущий тон
- if(freqTone){
- try{ freqTone.stop(); }catch(e){}
- freqTone = null;
- }
- var soundFreq = 440 * freq;
- var vol = ampToVolume(); // 🔊 громкость по амплитуде
- var o = audioCtx.createOscillator();
- var g = audioCtx.createGain();
- o.type = 'sine';
- o.frequency.value = soundFreq;
- var now = audioCtx.currentTime;
- g.gain.setValueAtTime(0, now);
- g.gain.linearRampToValueAtTime(vol, now + 0.02);
- g.gain.setValueAtTime(vol, now + 0.20);
- g.gain.exponentialRampToValueAtTime(0.001, now + 0.35);
- o.connect(g); g.connect(audioCtx.destination);
- o.start(now);
- o.stop(now + 0.4);
- freqTone = o;
- }
- document.getElementById('freqSlider').addEventListener('input', function(e){
- freq = +e.target.value;
- document.getElementById('freqVal').textContent = freq;
- document.getElementById('freqSound').textContent = 440 * freq;
- playFreqTone();
- });
- // 🔊 Громкость зависит от амплитуды: 5 px → 0.02, 40 px → 0.35
- function ampToVolume(){
- var minA = 5, maxA = 40;
- var minV = 0.02, maxV = 0.35;
- var k = (amp - minA) / (maxA - minA);
- if(k < 0) k = 0;
- if(k > 1) k = 1;
- return minV + k * (maxV - minV);
- }
- document.getElementById('ampSlider').addEventListener('input', function(e){
- amp = +e.target.value;
- document.getElementById('ampVal').textContent = amp;
- document.getElementById('ampVol').textContent = Math.round(ampToVolume() / 0.35 * 100);
- // 🔊 короткий пробный тон с новой громкостью
- if(soundOn){
- ensureAudio();
- beep(440 * freq, 0.25, 'sine', ampToVolume());
- }
- });
- document.getElementById('btnPush').addEventListener('click', function(){
- pushTime = t; pushes++;
- document.getElementById('scene2Hint').textContent = 'Толчков: ' + pushes + ' / 3';
- // 🔊 Частота — по бегунку частоты, громкость — по бегунку амплитуды
- beep(440 * freq, 0.35, 'sine', ampToVolume());
- if(pushes >= 3){
- document.getElementById('btnNext2').disabled = false;
- document.getElementById('scene2Hint').textContent = 'Отлично! Можно идти дальше.';
- }
- });
- document.getElementById('btnNext2').addEventListener('click', function(){ goToScene(2); });
- function loop(){
- var W = c.width, H = c.height;
- ctx.clearRect(0,0,W,H);
- var N = 12, spacing = W/(N+1);
- t += 0.03;
- var pts = [];
- for(var i=0;i<N;i++){
- var x = spacing*(i+1);
- var delay = i*0.25/Math.max(freq,1);
- var dt = t - pushTime - delay;
- var off;
- if(dt > 0){
- off = Math.sin(dt*freq*2*Math.PI) * amp * Math.exp(-dt*1.2);
- } else {
- off = Math.sin(t*freq*0.5) * amp * 0.15;
- }
- pts.push({x:x, y:H/2 + off});
- }
- ctx.strokeStyle = 'rgba(0,229,255,.5)';
- ctx.lineWidth = 2;
- ctx.shadowBlur = 10; ctx.shadowColor = '#00e5ff';
- for(var j=0;j<pts.length-1;j++){
- ctx.beginPath();
- ctx.moveTo(pts[j].x, pts[j].y);
- ctx.lineTo(pts[j+1].x, pts[j+1].y);
- ctx.stroke();
- }
- for(var k=0;k<pts.length;k++){
- ctx.beginPath();
- ctx.fillStyle = '#ff2ec4';
- ctx.shadowBlur = 20; ctx.shadowColor = '#ff2ec4';
- ctx.arc(pts[k].x, pts[k].y, 10, 0, Math.PI*2);
- ctx.fill();
- }
- ctx.shadowBlur = 0;
- requestAnimationFrame(loop);
- }
- loop();
- })();
- /* ================= SCENE 3: трубы + квиз ================= */
- document.getElementById('btnImpulse').addEventListener('click', function(){
- clickSound();
- var imps = document.querySelectorAll('#scene3 .impulse');
- for(var i=0;i<imps.length;i++){
- imps[i].style.animationDuration = imps[i].dataset.speed + 's';
- imps[i].classList.remove('run');
- void imps[i].offsetWidth;
- imps[i].classList.add('run');
- }
- });
- var q3 = document.querySelectorAll('#quiz3 .btn');
- for(var qi=0; qi<q3.length; qi++){
- (function(b){
- b.addEventListener('click', function(){
- if(b.dataset.ans === 'steel'){
- b.classList.add('correct'); correctSound();
- document.getElementById('btnNext3').disabled = false;
- } else {
- b.classList.add('wrong'); wrongSound();
- }
- });
- })(q3[qi]);
- }
- document.getElementById('btnNext3').addEventListener('click', function(){ goToScene(3); });
- /* ================= SCENE 4: маятник + волна ================= */
- var oscCanvas = document.getElementById('oscCanvas');
- var oscCtx = oscCanvas.getContext('2d');
- var waveCanvas = document.getElementById('waveCanvas');
- var waveCtx = waveCanvas.getContext('2d');
- var oscT = 0, oscAmp = 60, oscPeriod = 1.2;
- var oscRunning = false;
- document.getElementById('ampSlider2').addEventListener('input', function(e){
- oscAmp = +e.target.value;
- document.getElementById('ampVal2').textContent = oscAmp;
- });
- document.getElementById('perSlider').addEventListener('input', function(e){
- oscPeriod = +e.target.value / 10;
- document.getElementById('perVal').textContent = oscPeriod.toFixed(1);
- });
- function startOscillators(){
- if(oscRunning) return;
- oscRunning = true;
- oscT = 0;
- requestAnimationFrame(drawOsc);
- }
- function drawOsc(){
- // ⏱️ Шаг времени. Период управляет скоростью фазы.
- 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();
- // 🕒 Период управляет скоростью качания (фаза)
- 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 len = 120, cx = W/2, cy = 40;
- 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';
- oscCtx.arc(bx, by, 14, 0, Math.PI*2);
- 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 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();
- waveCtx.shadowBlur = 0;
- requestAnimationFrame(drawOsc);
- }
- document.getElementById('btnCheck4').addEventListener('click', function(){
- ensureAudio();
- openModal(
- 'Проверь себя',
- 'Если период T = 2 с, то частота ν = 1/T = 0,5 Гц. А если частота ν = 4 Гц, то период T = 1/ν = 0,25 с. Запомни: T и ν — взаимно обратные величины.'
- );
- document.getElementById('btnNext4').disabled = false;
- correctSound();
- });
- document.getElementById('btnNext4').addEventListener('click', function(){ goToScene(4); });
- /* ================= SCENE 5: две волны одновременно + мини-игра ================= */
- var typeCanvas = document.getElementById('typeCanvas');
- var typeCtx = typeCanvas.getContext('2d');
- var typeT = 0;
- // 🎼 Инструменты
- 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 Гц · До малой октавы (низкий)' }
- };
- 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;
- document.getElementById('scene5Hint').textContent =
- 'Собрано звуков: ' + 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 {
- 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(){
- var W = typeCanvas.width, H = typeCanvas.height;
- typeCtx.clearRect(0,0,W,H);
- typeT += 0.04;
- // ============== ВЕРХНЯЯ ПОЛОВИНА: ПРОДОЛЬНАЯ ==============
- var rowLong = H * 0.30; // центр продольной волны (30% высоты)
- drawLongitudinal(W, rowLong);
- // Разделитель
- 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([]);
- // ============== НИЖНЯЯ ПОЛОВИНА: ПОПЕРЕЧНАЯ ==============
- var rowTrans = H * 0.72; // центр поперечной волны (72% высоты)
- drawTransverse(W, rowTrans);
- // Общие иконки "источник → ухо"
- 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);
- requestAnimationFrame(drawType);
- }
- // 🌊 ВЕРХНЯЯ: продольная волна — сжатия/разрежения
- function drawLongitudinal(W, y0){
- var N = 55;
- var spacing = W / (N + 1);
- // Фоновый "коридор"
- 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 density = 0.5 + 0.5 * Math.sin(typeT - i * k);
- var r = 3.5 + density * 3.5;
- var alpha = 0.35 + density * 0.65;
- typeCtx.beginPath();
- typeCtx.fillStyle = 'rgba(255,46,196,' + alpha + ')';
- typeCtx.shadowBlur = 10; typeCtx.shadowColor = '#ff2ec4';
- typeCtx.arc(x, y0, r, 0, Math.PI*2);
- typeCtx.fill();
- }
- typeCtx.shadowBlur = 0;
- // Подпись
- typeCtx.fillStyle = '#ff2ec4';
- typeCtx.font = 'bold 13px Segoe UI';
- typeCtx.fillText('🔴 Продольная (звук в воздухе)', 50, y0 - 42);
- }
- // 🌊 НИЖНЯЯ: поперечная волна — синусоида (или волна выбранного инструмента)
- function drawTransverse(W, y0){
- // Ось
- typeCtx.strokeStyle = 'rgba(0,229,255,.12)';
- typeCtx.lineWidth = 1;
- typeCtx.beginPath();
- typeCtx.moveTo(30, y0);
- typeCtx.lineTo(W - 30, y0);
- typeCtx.stroke();
- var amp = 40;
- // Если выбран инструмент — рисуем ЕГО волну
- 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;
- 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();
- 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 13px Segoe UI';
- typeCtx.fillText('🟢 Поперечная (струна, волна на воде)', 50, y0 - 42);
- }
- }
- // ▶️ Далее
- document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
- // Запуск
- drawType();
- /* ================= SCENE 6: карточки + пары ================= */
- var pairData = [
- { left: 'Эхолокация', right: 'Отражение ультразвука' },
- { left: 'УЗИ', right: 'Отражение от тканей' },
- { left: 'Сейсмология', right: 'Сейсмические волны' },
- { left: 'Музыка', right: 'Стоячие волны' }
- ];
- var pairsDone = 0;
- var selectedLeft = null;
- function initPairs(){
- pairsDone = 0;
- selectedLeft = null;
- document.getElementById('scene6Hint').textContent = 'Собрано пар: 0 / 4';
- document.getElementById('btnNext6').disabled = true;
- var leftCol = document.getElementById('pairLeft');
- var rightCol = document.getElementById('pairRight');
- leftCol.innerHTML = ''; rightCol.innerHTML = '';
- // левая колонка по порядку
- pairData.forEach(function(p, i){
- var d = document.createElement('div');
- d.className = 'pair-item';
- d.textContent = p.left;
- d.dataset.idx = i;
- d.dataset.side = 'left';
- d.addEventListener('click', function(){
- if(d.classList.contains('matched')) return;
- var sel = document.querySelectorAll('#pairLeft .pair-item.selected');
- for(var s=0;s<sel.length;s++) sel[s].classList.remove('selected');
- d.classList.add('selected');
- selectedLeft = d;
- clickSound();
- });
- leftCol.appendChild(d);
- });
- // правая колонка перемешана
- var shuffled = pairData.map(function(p,i){ return {text:p.right, idx:i}; });
- shuffled.sort(function(){ return Math.random()-0.5; });
- shuffled.forEach(function(item){
- var d = document.createElement('div');
- d.className = 'pair-item';
- d.textContent = item.text;
- d.dataset.idx = item.idx;
- d.dataset.side = 'right';
- d.addEventListener('click', function(){
- if(d.classList.contains('matched')) return;
- if(!selectedLeft){ wrongSound(); return; }
- if(selectedLeft.dataset.idx === d.dataset.idx){
- selectedLeft.classList.add('matched');
- selectedLeft.classList.remove('selected');
- d.classList.add('matched');
- selectedLeft = null;
- pairsDone++;
- correctSound();
- document.getElementById('scene6Hint').textContent = 'Собрано пар: ' + pairsDone + ' / 4';
- if(pairsDone >= 4){
- document.getElementById('btnNext6').disabled = false;
- document.getElementById('scene6Hint').textContent = 'Все пары собраны! 🎉';
- }
- } else {
- wrongSound();
- d.classList.add('wrong');
- setTimeout(function(){ d.classList.remove('wrong'); }, 400);
- }
- });
- rightCol.appendChild(d);
- });
- }
- // карточки — переворот
- var cards = document.querySelectorAll('#cards .card');
- for(var ci=0; ci<cards.length; ci++){
- cards[ci].addEventListener('click', function(){
- this.classList.toggle('flipped');
- clickSound();
- });
- }
- document.getElementById('btnNext6').addEventListener('click', function(){ goToScene(6); });
- /* ================= SCENE 7: ТЕСТ ================= */
- var quizQuestions = [
- { q: 'Что переносит механическая волна?', a: ['Вещество', 'Энергию', 'Заряд', 'Массу'], c: 1 },
- { q: 'В какой среде звук распространяется быстрее всего?', a: ['Воздух', 'Вода', 'Сталь', 'Вакуум'], c: 2 },
- { 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 = \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(){
- qIndex = 0; qScore = 0;
- document.getElementById('quizResult').style.display = 'none';
- document.getElementById('quizQuestion').style.display = 'block';
- document.getElementById('quizOptions').style.display = 'flex';
- document.getElementById('quizTitle').textContent = 'Итоговое тестирование';
- showQuestion();
- }
- function showQuestion(){
- var q = quizQuestions[qIndex];
- document.getElementById('quizProgress').style.width = (qIndex / quizQuestions.length * 100) + '%';
- document.getElementById('quizQuestion').textContent = (qIndex+1) + '. ' + q.q;
- var opts = document.getElementById('quizOptions');
- opts.innerHTML = '';
- q.a.forEach(function(text, i){
- var b = document.createElement('button');
- b.className = 'btn';
- b.type = 'button';
- b.style.textTransform = 'none'; // ✅ отключаем КАПС
- b.style.letterSpacing = 'normal'; // ✅ буквы без разрядки
- b.innerHTML = text;
- b.addEventListener('click', function(){
- // блокируем остальные
- var all = opts.querySelectorAll('.btn');
- for(var k=0;k<all.length;k++) all[k].disabled = true;
- if(i === q.c){
- b.classList.add('correct');
- qScore++;
- correctSound();
- } else {
- b.classList.add('wrong');
- all[q.c].classList.add('correct');
- wrongSound();
- }
- setTimeout(function(){
- qIndex++;
- if(qIndex < quizQuestions.length){
- showQuestion();
- } else {
- showResult();
- }
- }, 900);
- });
- opts.appendChild(b);
- });
- }
- 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 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(){
- clickSound();
- goToScene(0);
- });
- </script>
|