XTaylorSpenceX

Resonance Rift Rally

Oct 7th, 2025
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 13.99 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Resonance Rift Rally</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13.  
  14. body {
  15. font-family: 'Courier New', monospace;
  16. background: linear-gradient(135deg, #0a0015 0%, #1a0033 100%);
  17. overflow: hidden;
  18. color: #fff;
  19. }
  20.  
  21. #gameCanvas {
  22. display: block;
  23. background: radial-gradient(ellipse at center, #1a0033 0%, #0a0015 100%);
  24. box-shadow: 0 0 50px rgba(138, 43, 226, 0.5);
  25. }
  26.  
  27. .ui-overlay {
  28. position: absolute;
  29. top: 0;
  30. left: 0;
  31. width: 100%;
  32. height: 100%;
  33. pointer-events: none;
  34. z-index: 10;
  35. }
  36.  
  37. .score-display {
  38. position: absolute;
  39. top: 20px;
  40. left: 20px;
  41. font-size: 24px;
  42. color: #00ffff;
  43. text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff;
  44. pointer-events: none;
  45. }
  46.  
  47. .speed-display {
  48. position: absolute;
  49. top: 60px;
  50. left: 20px;
  51. font-size: 18px;
  52. color: #ff00ff;
  53. text-shadow: 0 0 10px #ff00ff;
  54. }
  55.  
  56. .tone-indicator {
  57. position: absolute;
  58. top: 20px;
  59. right: 20px;
  60. display: flex;
  61. gap: 10px;
  62. }
  63.  
  64. .tone-btn {
  65. width: 60px;
  66. height: 60px;
  67. border-radius: 50%;
  68. border: 3px solid #fff;
  69. display: flex;
  70. align-items: center;
  71. justify-content: center;
  72. font-weight: bold;
  73. font-size: 14px;
  74. transition: all 0.2s;
  75. pointer-events: all;
  76. cursor: pointer;
  77. text-shadow: 0 0 5px currentColor;
  78. }
  79.  
  80. .tone-btn.low {
  81. background: rgba(255, 0, 100, 0.3);
  82. border-color: #ff0064;
  83. color: #ff0064;
  84. }
  85.  
  86. .tone-btn.mid {
  87. background: rgba(0, 255, 200, 0.3);
  88. border-color: #00ffc8;
  89. color: #00ffc8;
  90. }
  91.  
  92. .tone-btn.high {
  93. background: rgba(138, 43, 226, 0.3);
  94. border-color: #8a2be2;
  95. color: #8a2be2;
  96. }
  97.  
  98. .tone-btn.active {
  99. transform: scale(1.2);
  100. box-shadow: 0 0 30px currentColor;
  101. }
  102.  
  103. .start-screen {
  104. position: absolute;
  105. top: 50%;
  106. left: 50%;
  107. transform: translate(-50%, -50%);
  108. text-align: center;
  109. pointer-events: all;
  110. z-index: 100;
  111. }
  112.  
  113. .start-screen h1 {
  114. font-size: 48px;
  115. margin-bottom: 20px;
  116. background: linear-gradient(45deg, #ff00ff, #00ffff, #ff00ff);
  117. -webkit-background-clip: text;
  118. -webkit-text-fill-color: transparent;
  119. background-clip: text;
  120. animation: pulse 2s ease-in-out infinite;
  121. }
  122.  
  123. .start-screen button {
  124. padding: 15px 40px;
  125. font-size: 24px;
  126. background: linear-gradient(45deg, #8a2be2, #ff00ff);
  127. border: none;
  128. border-radius: 50px;
  129. color: white;
  130. cursor: pointer;
  131. transition: all 0.3s;
  132. box-shadow: 0 0 20px rgba(138, 43, 226, 0.5);
  133. }
  134.  
  135. .start-screen button:hover {
  136. transform: scale(1.1);
  137. box-shadow: 0 0 40px rgba(138, 43, 226, 0.8);
  138. }
  139.  
  140. .instructions {
  141. margin-top: 20px;
  142. font-size: 16px;
  143. color: #00ffff;
  144. line-height: 1.8;
  145. }
  146.  
  147. .game-over {
  148. display: none;
  149. position: absolute;
  150. top: 50%;
  151. left: 50%;
  152. transform: translate(-50%, -50%);
  153. text-align: center;
  154. pointer-events: all;
  155. z-index: 100;
  156. background: rgba(0, 0, 0, 0.8);
  157. padding: 40px;
  158. border-radius: 20px;
  159. border: 3px solid #ff00ff;
  160. }
  161.  
  162. .game-over h2 {
  163. font-size: 36px;
  164. color: #ff00ff;
  165. margin-bottom: 20px;
  166. }
  167.  
  168. @keyframes pulse {
  169. 0%, 100% { opacity: 1; }
  170. 50% { opacity: 0.7; }
  171. }
  172.  
  173. @keyframes ripple {
  174. 0% {
  175. transform: scale(1);
  176. opacity: 1;
  177. }
  178. 100% {
  179. transform: scale(1.5);
  180. opacity: 0;
  181. }
  182. }
  183. </style>
  184. </head>
  185. <body>
  186. <canvas id="gameCanvas"></canvas>
  187.  
  188. <div class="ui-overlay">
  189. <div class="score-display">
  190. Score: <span id="score">0</span>
  191. </div>
  192. <div class="speed-display">
  193. Speed: <span id="speed">0</span>
  194. </div>
  195. <div class="tone-indicator">
  196. <div class="tone-btn low" data-tone="low">LOW<br>Q</div>
  197. <div class="tone-btn mid" data-tone="mid">MID<br>W</div>
  198. <div class="tone-btn high" data-tone="high">HIGH<br>E</div>
  199. </div>
  200. </div>
  201.  
  202. <div class="start-screen" id="startScreen">
  203. <h1>RESONANCE RIFT RALLY</h1>
  204. <button id="startBtn">START RACE</button>
  205. <div class="instructions">
  206. Match the glowing tunnel tones!<br>
  207. Press Q (Low) · W (Mid) · E (High)<br>
  208. or click the tone buttons<br>
  209. Avoid discordant red zones with your mouse!
  210. </div>
  211. </div>
  212.  
  213. <div class="game-over" id="gameOver">
  214. <h2>RACE COMPLETE!</h2>
  215. <p style="font-size: 24px; color: #00ffff;">Final Score: <span id="finalScore">0</span></p>
  216. <button id="restartBtn" style="margin-top: 20px; padding: 10px 30px; font-size: 18px; background: linear-gradient(45deg, #8a2be2, #ff00ff); border: none; border-radius: 50px; color: white; cursor: pointer;">RACE AGAIN</button>
  217. </div>
  218.  
  219. <script>
  220. const canvas = document.getElementById('gameCanvas');
  221. const ctx = canvas.getContext('2d');
  222. canvas.width = window.innerWidth;
  223. canvas.height = window.innerHeight;
  224.  
  225. // Audio Context
  226. const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  227.  
  228. // Game State
  229. let gameState = 'menu';
  230. let score = 0;
  231. let speed = 0;
  232. let baseSpeed = 3;
  233. let maxSpeed = 12;
  234. let currentTone = 'mid';
  235. let activeTone = null;
  236. let toneMatchTime = 0;
  237.  
  238. // Vehicle
  239. const vehicle = {
  240. x: canvas.width / 2,
  241. y: canvas.height - 150,
  242. width: 40,
  243. height: 60,
  244. vx: 0,
  245. vy: 0
  246. };
  247.  
  248. // Tunnel segments
  249. const segments = [];
  250. const segmentHeight = 100;
  251. let segmentSpeed = 5;
  252.  
  253. // Obstacles
  254. const obstacles = [];
  255.  
  256. // Particles
  257. const particles = [];
  258.  
  259. // Tones
  260. const tones = {
  261. low: { freq: 220, color: '#ff0064', key: 'q' },
  262. mid: { freq: 440, color: '#00ffc8', key: 'w' },
  263. high: { freq: 880, color: '#8a2be2', key: 'e' }
  264. };
  265.  
  266. // Initialize segments
  267. function initSegments() {
  268. segments.length = 0;
  269. for (let i = 0; i < 15; i++) {
  270. segments.push(createSegment(i * segmentHeight));
  271. }
  272. }
  273.  
  274. function createSegment(y) {
  275. const toneKeys = Object.keys(tones);
  276. const tone = toneKeys[Math.floor(Math.random() * toneKeys.length)];
  277. return {
  278. y: y,
  279. tone: tone,
  280. width: canvas.width * 0.6,
  281. x: canvas.width * 0.2,
  282. phase: Math.random() * Math.PI * 2
  283. };
  284. }
  285.  
  286. // Create obstacle
  287. function createObstacle(y) {
  288. obstacles.push({
  289. x: Math.random() * (canvas.width * 0.6) + canvas.width * 0.2,
  290. y: y,
  291. width: 50,
  292. height: 50,
  293. type: 'discord'
  294. });
  295. }
  296.  
  297. // Play tone
  298. function playTone(tone) {
  299. const oscillator = audioCtx.createOscillator();
  300. const gainNode = audioCtx.createGain();
  301.  
  302. oscillator.connect(gainNode);
  303. gainNode.connect(audioCtx.destination);
  304.  
  305. oscillator.frequency.value = tones[tone].freq;
  306. oscillator.type = 'sine';
  307.  
  308. gainNode.gain.setValueAtTime(0.3, audioCtx.currentTime);
  309. gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
  310.  
  311. oscillator.start(audioCtx.currentTime);
  312. oscillator.stop(audioCtx.currentTime + 0.3);
  313. }
  314.  
  315. // Play negative tone on hit
  316. function playNegativeTone() {
  317. const oscillator = audioCtx.createOscillator();
  318. const gainNode = audioCtx.createGain();
  319.  
  320. oscillator.connect(gainNode);
  321. gainNode.connect(audioCtx.destination);
  322.  
  323. oscillator.frequency.value = 100; // Low, dissonant freq for negative feel
  324. oscillator.type = 'sawtooth'; // Gritty waveform
  325.  
  326. gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime);
  327. gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
  328.  
  329. oscillator.start(audioCtx.currentTime);
  330. oscillator.stop(audioCtx.currentTime + 0.5);
  331. }
  332.  
  333. // Create particles
  334. function createParticles(x, y, color) {
  335. for (let i = 0; i < 10; i++) {
  336. particles.push({
  337. x: x,
  338. y: y,
  339. vx: (Math.random() - 0.5) * 5,
  340. vy: (Math.random() - 0.5) * 5,
  341. life: 1,
  342. color: color
  343. });
  344. }
  345. }
  346.  
  347. // Update game
  348. function update() {
  349. if (gameState !== 'playing') return;
  350.  
  351. // Update speed
  352. speed = baseSpeed + (score / 100);
  353. speed = Math.min(speed, maxSpeed);
  354. segmentSpeed = speed;
  355.  
  356. // Move segments
  357. segments.forEach(seg => {
  358. seg.y += segmentSpeed;
  359. seg.phase += 0.05;
  360. });
  361.  
  362. // Remove off-screen segments and add new ones
  363. if (segments[0].y > canvas.height) {
  364. segments.shift();
  365. segments.push(createSegment(segments[segments.length - 1].y - segmentHeight));
  366.  
  367. if (Math.random() < 0.3) {
  368. createObstacle(segments[segments.length - 1].y + 50);
  369. }
  370. }
  371.  
  372. // Current segment tone
  373. const currentSegment = segments.find(seg =>
  374. seg.y < vehicle.y && seg.y + segmentHeight > vehicle.y
  375. );
  376.  
  377. if (currentSegment) {
  378. currentTone = currentSegment.tone;
  379.  
  380. // Check tone match
  381. if (activeTone === currentTone) {
  382. toneMatchTime++;
  383. score += 1;
  384. speed += 0.01;
  385.  
  386. if (toneMatchTime % 10 === 0) {
  387. createParticles(vehicle.x, vehicle.y, tones[currentTone].color);
  388. }
  389. } else {
  390. toneMatchTime = 0;
  391. if (activeTone !== null) {
  392. speed *= 0.98;
  393. }
  394. }
  395. }
  396.  
  397. // Move obstacles
  398. obstacles.forEach((obs, index) => {
  399. obs.y += segmentSpeed;
  400.  
  401. // Collision detection
  402. if (obs.y > vehicle.y - vehicle.height &&
  403. obs.y < vehicle.y + vehicle.height &&
  404. obs.x > vehicle.x - vehicle.width &&
  405. obs.x < vehicle.x + vehicle.width) {
  406. score = Math.max(0, score - 50);
  407. playNegativeTone(); // Added negative tone on hit
  408. obstacles.splice(index, 1);
  409. createParticles(obs.x, obs.y, '#ff0000');
  410. speed *= 0.7;
  411. }
  412.  
  413. if (obs.y > canvas.height) {
  414. obstacles.splice(index, 1);
  415. }
  416. });
  417.  
  418. // Update particles
  419. particles.forEach((p, index) => {
  420. p.x += p.vx;
  421. p.y += p.vy;
  422. p.life -= 0.02;
  423. if (p.life <= 0) {
  424. particles.splice(index, 1);
  425. }
  426. });
  427.  
  428. // Vehicle position is now controlled by mouse on x, no drift
  429. // (Mouse handling added below)
  430.  
  431. // Update UI
  432. document.getElementById('score').textContent = Math.floor(score);
  433. document.getElementById('speed').textContent = speed.toFixed(1);
  434. }
  435.  
  436. // Draw game
  437. function draw() {
  438. ctx.fillStyle = '#0a0015';
  439. ctx.fillRect(0, 0, canvas.width, canvas.height);
  440.  
  441. // Draw segments with soundwave effect
  442. segments.forEach((seg, index) => {
  443. const color = tones[seg.tone].color;
  444. const alpha = seg.tone === currentTone ? 0.3 : 0.1;
  445.  
  446. // Soundwave ripples
  447. for (let wave = 0; wave < 3; wave++) {
  448. ctx.strokeStyle = color + Math.floor(alpha * 255 * (1 - wave * 0.3)).toString(16).padStart(2, '0');
  449. ctx.lineWidth = 3;
  450. ctx.beginPath();
  451.  
  452. for (let x = seg.x; x < seg.x + seg.width; x += 10) {
  453. const waveOffset = Math.sin((x * 0.02) + seg.phase + (wave * 0.5)) * 30;
  454. const y1 = seg.y + waveOffset;
  455. const y2 = seg.y + segmentHeight + waveOffset;
  456.  
  457. if (x === seg.x) {
  458. ctx.moveTo(x, y1);
  459. } else {
  460. ctx.lineTo(x, y1);
  461. }
  462. }
  463. ctx.stroke();
  464. }
  465.  
  466. // Segment glow
  467. if (seg.tone === currentTone && activeTone === currentTone) {
  468. ctx.fillStyle = color + '20';
  469. ctx.fillRect(seg.x, seg.y, seg.width, segmentHeight);
  470. }
  471. });
  472.  
  473. // Draw obstacles
  474. obstacles.forEach(obs => {
  475. ctx.fillStyle = '#ff0000';
  476. ctx.shadowBlur = 20;
  477. ctx.shadowColor = '#ff0000';
  478. ctx.fillRect(obs.x - obs.width / 2, obs.y - obs.height / 2, obs.width, obs.height);
  479.  
  480. // X pattern
  481. ctx.strokeStyle = '#ffffff';
  482. ctx.lineWidth = 3;
  483. ctx.beginPath();
  484. ctx.moveTo(obs.x - obs.width / 2, obs.y - obs.height / 2);
  485. ctx.lineTo(obs.x + obs.width / 2, obs.y + obs.height / 2);
  486. ctx.moveTo(obs.x + obs.width / 2, obs.y - obs.height / 2);
  487. ctx.lineTo(obs.x - obs.width / 2, obs.y + obs.height / 2);
  488. ctx.stroke();
  489. ctx.shadowBlur = 0;
  490. });
  491.  
  492. // Draw particles
  493. particles.forEach(p => {
  494. ctx.fillStyle = p.color + Math.floor(p.life * 255).toString(16).padStart(2, '0');
  495. ctx.beginPath();
  496. ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
  497. ctx.fill();
  498. });
  499.  
  500. // Draw vehicle
  501. ctx.save();
  502. ctx.translate(vehicle.x, vehicle.y);
  503.  
  504. // Vehicle glow
  505. if (activeTone === currentTone) {
  506. ctx.shadowBlur = 30;
  507. ctx.shadowColor = tones[currentTone].color;
  508. }
  509.  
  510. // Vehicle body
  511. ctx.fillStyle = activeTone ? tones[currentTone].color : '#00ffff';
  512. ctx.beginPath();
  513. ctx.moveTo(0, -vehicle.height / 2);
  514. ctx.lineTo(vehicle.width / 2, vehicle.height / 2);
  515. ctx.lineTo(0, vehicle.height / 3);
  516. ctx.lineTo(-vehicle.width / 2, vehicle.height / 2);
  517. ctx.closePath();
  518. ctx.fill();
  519.  
  520. // Vehicle trail
  521. ctx.strokeStyle = activeTone ? tones[currentTone].color : '#00ffff';
  522. ctx.lineWidth = 2;
  523. ctx.beginPath();
  524. ctx.moveTo(0, vehicle.height / 2);
  525. ctx.lineTo(0, vehicle.height / 2 + 20);
  526. ctx.stroke();
  527.  
  528. ctx.restore();
  529. ctx.shadowBlur = 0;
  530. }
  531.  
  532. // Game loop
  533. function gameLoop() {
  534. update();
  535. draw();
  536. requestAnimationFrame(gameLoop);
  537. }
  538.  
  539. // Input handling
  540. document.addEventListener('keydown', (e) => {
  541. if (gameState !== 'playing') return;
  542.  
  543. Object.keys(tones).forEach(tone => {
  544. if (e.key.toLowerCase() === tones[tone].key) {
  545. setActiveTone(tone);
  546. }
  547. });
  548. });
  549.  
  550. document.addEventListener('keyup', (e) => {
  551. if (gameState !== 'playing') return;
  552.  
  553. Object.keys(tones).forEach(tone => {
  554. if (e.key.toLowerCase() === tones[tone].key) {
  555. clearActiveTone(tone);
  556. }
  557. });
  558. });
  559.  
  560. // Tone button handling
  561. document.querySelectorAll('.tone-btn').forEach(btn => {
  562. btn.addEventListener('mousedown', () => {
  563. if (gameState !== 'playing') return;
  564. const tone = btn.dataset.tone;
  565. setActiveTone(tone);
  566. });
  567.  
  568. btn.addEventListener('mouseup', () => {
  569. if (gameState !== 'playing') return;
  570. const tone = btn.dataset.tone;
  571. clearActiveTone(tone);
  572. });
  573.  
  574. btn.addEventListener('mouseleave', () => {
  575. if (gameState !== 'playing') return;
  576. const tone = btn.dataset.tone;
  577. clearActiveTone(tone);
  578. });
  579. });
  580.  
  581. function setActiveTone(tone) {
  582. activeTone = tone;
  583. playTone(tone);
  584. document.querySelector(`.tone-btn[data-tone="${tone}"]`).classList.add('active');
  585. }
  586.  
  587. function clearActiveTone(tone) {
  588. if (activeTone === tone) {
  589. activeTone = null;
  590. }
  591. document.querySelector(`.tone-btn[data-tone="${tone}"]`).classList.remove('active');
  592. }
  593.  
  594. // Mouse control for vehicle x-position
  595. canvas.addEventListener('mousemove', (e) => {
  596. if (gameState !== 'playing') return;
  597. const rect = canvas.getBoundingClientRect();
  598. const mouseX = e.clientX - rect.left;
  599. vehicle.x = Math.max(canvas.width * 0.2, Math.min(canvas.width * 0.8 - vehicle.width, mouseX));
  600. });
  601.  
  602. // Start game
  603. document.getElementById('startBtn').addEventListener('click', () => {
  604. document.getElementById('startScreen').style.display = 'none';
  605. gameState = 'playing';
  606. score = 0;
  607. speed = baseSpeed;
  608. initSegments();
  609. obstacles.length = 0;
  610. particles.length = 0;
  611. });
  612.  
  613. document.getElementById('restartBtn').addEventListener('click', () => {
  614. document.getElementById('gameOver').style.display = 'none';
  615. gameState = 'playing';
  616. score = 0;
  617. speed = baseSpeed;
  618. vehicle.x = canvas.width / 2;
  619. initSegments();
  620. obstacles.length = 0;
  621. particles.length = 0;
  622. });
  623.  
  624. // Window resize
  625. window.addEventListener('resize', () => {
  626. canvas.width = window.innerWidth;
  627. canvas.height = window.innerHeight;
  628. vehicle.x = canvas.width / 2;
  629. vehicle.y = canvas.height - 150; // Keep y tethered
  630. });
  631.  
  632. // Initialize and start
  633. initSegments();
  634. gameLoop();
  635. </script>
  636. </body>
  637. </html>
  638.  
Advertisement
Add Comment
Please, Sign In to add comment