XTaylorSpenceX

Phantom Frequency Frenzy

Sep 29th, 2025
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 39.79 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>Phantom Frequency Frenzy</title>
  7.     <style>
  8.         * {
  9.             margin: 0;
  10.             padding: 0;
  11.             box-sizing: border-box;
  12.         }
  13.  
  14.         body {
  15.             background: linear-gradient(180deg, #0a0a0a 0%, #1a0f1f 100%);
  16.             color: #e0e0e0;
  17.             font-family: 'Courier New', monospace;
  18.             height: 100vh;
  19.             overflow: hidden;
  20.             display: flex;
  21.             flex-direction: column;
  22.             align-items: center;
  23.             justify-content: center;
  24.         }
  25.  
  26.         #gameContainer {
  27.             width: 90%;
  28.             max-width: 800px;
  29.             position: relative;
  30.         }
  31.  
  32.         #radio {
  33.             background: linear-gradient(145deg, #2a2a3a, #1a1a2a);
  34.             border-radius: 20px;
  35.             padding: 30px;
  36.             box-shadow: 0 20px 40px rgba(0, 0, 0, 0.7),
  37.                         inset 0 2px 4px rgba(255, 255, 255, 0.1);
  38.             position: relative;
  39.         }
  40.  
  41.         #display {
  42.             background: radial-gradient(ellipse at center, #001122, #000511);
  43.             border: 3px solid #3a4a5a;
  44.             border-radius: 10px;
  45.             height: 200px;
  46.             margin-bottom: 20px;
  47.             position: relative;
  48.             overflow: hidden;
  49.         }
  50.  
  51.         #frequencyDisplay {
  52.             position: absolute;
  53.             top: 10px;
  54.             left: 20px;
  55.             font-size: 24px;
  56.             color: #00ff88;
  57.             text-shadow: 0 0 10px #00ff88;
  58.             font-weight: bold;
  59.             z-index: 10;
  60.         }
  61.  
  62.         #spectrum {
  63.             width: 100%;
  64.             height: 100%;
  65.             position: relative;
  66.         }
  67.  
  68.         .wave {
  69.             position: absolute;
  70.             bottom: 0;
  71.             width: 2%;
  72.             background: linear-gradient(to top, transparent, #00ffaa, transparent);
  73.             animation: pulse 0.5s infinite;
  74.             transition: all 0.1s;
  75.             opacity: 0.7;
  76.         }
  77.  
  78.         @keyframes pulse {
  79.             0%, 100% { opacity: 0.7; }
  80.             50% { opacity: 1; }
  81.         }
  82.  
  83.         .ghost-particle {
  84.             position: absolute;
  85.             width: 4px;
  86.             height: 4px;
  87.             background: radial-gradient(circle, #88ffcc, transparent);
  88.             border-radius: 50%;
  89.             animation: float 3s infinite ease-in-out;
  90.         }
  91.  
  92.         @keyframes float {
  93.             0%, 100% { transform: translateY(0) translateX(0); opacity: 0; }
  94.             50% { transform: translateY(-50px) translateX(20px); opacity: 1; }
  95.         }
  96.  
  97.         #tuner {
  98.             position: relative;
  99.             height: 80px;
  100.             background: linear-gradient(90deg, #1a1a2a, #2a2a3a, #1a1a2a);
  101.             border-radius: 10px;
  102.             margin-bottom: 20px;
  103.             display: flex;
  104.             align-items: center;
  105.             padding: 0 20px;
  106.         }
  107.  
  108.         #dialKnob {
  109.             width: 60px;
  110.             height: 60px;
  111.             background: radial-gradient(circle at 30% 30%, #5a6a7a, #2a3a4a);
  112.             border-radius: 50%;
  113.             position: relative;
  114.             cursor: pointer;
  115.             box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
  116.             transition: transform 0.1s;
  117.         }
  118.  
  119.         #dialKnob::before {
  120.             content: '';
  121.             position: absolute;
  122.             width: 4px;
  123.             height: 25px;
  124.             background: #00ff88;
  125.             left: 50%;
  126.             top: 5px;
  127.             transform: translateX(-50%);
  128.             box-shadow: 0 0 10px #00ff88;
  129.         }
  130.  
  131.         #dialKnob:active {
  132.             transform: scale(0.95);
  133.         }
  134.  
  135.         #frequencyBar {
  136.             flex: 1;
  137.             height: 6px;
  138.             background: #1a1a2a;
  139.             margin-left: 20px;
  140.             border-radius: 3px;
  141.             position: relative;
  142.             overflow: hidden;
  143.         }
  144.  
  145.         #frequencyIndicator {
  146.             position: absolute;
  147.             width: 20px;
  148.             height: 20px;
  149.             background: radial-gradient(circle, #00ff88, #00aa55);
  150.             border-radius: 50%;
  151.             top: 50%;
  152.             transform: translateY(-50%);
  153.             box-shadow: 0 0 20px #00ff88;
  154.             transition: left 0.1s;
  155.         }
  156.  
  157.         #ghostMessages {
  158.             background: rgba(0, 0, 0, 0.7);
  159.             border: 1px solid #3a4a5a;
  160.             border-radius: 10px;
  161.             padding: 15px;
  162.             height: 120px;
  163.             overflow-y: auto;
  164.             margin-bottom: 20px;
  165.             position: relative;
  166.         }
  167.  
  168.         .ghost-message {
  169.             color: #88ffcc;
  170.             margin: 5px 0;
  171.             opacity: 0;
  172.             animation: fadeInGlow 1s forwards;
  173.             text-shadow: 0 0 5px currentColor;
  174.         }
  175.  
  176.         @keyframes fadeInGlow {
  177.             to {
  178.                 opacity: 1;
  179.                 text-shadow: 0 0 10px currentColor;
  180.             }
  181.         }
  182.  
  183.         #controls {
  184.             display: flex;
  185.             justify-content: space-between;
  186.             gap: 10px;
  187.         }
  188.  
  189.         button {
  190.             background: linear-gradient(145deg, #3a4a5a, #2a3a4a);
  191.             color: #00ff88;
  192.             border: 2px solid #00ff88;
  193.             padding: 10px 20px;
  194.             border-radius: 5px;
  195.             cursor: pointer;
  196.             font-family: inherit;
  197.             font-size: 14px;
  198.             transition: all 0.3s;
  199.             flex: 1;
  200.         }
  201.  
  202.         button:hover {
  203.             background: linear-gradient(145deg, #4a5a6a, #3a4a5a);
  204.             box-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
  205.             transform: translateY(-2px);
  206.         }
  207.  
  208.         #stats {
  209.             display: flex;
  210.             justify-content: space-around;
  211.             margin-top: 20px;
  212.             padding: 15px;
  213.             background: rgba(0, 20, 40, 0.5);
  214.             border-radius: 10px;
  215.         }
  216.  
  217.         .stat {
  218.             text-align: center;
  219.         }
  220.  
  221.         .stat-value {
  222.             font-size: 24px;
  223.             color: #00ff88;
  224.             text-shadow: 0 0 10px #00ff88;
  225.         }
  226.  
  227.         .stat-label {
  228.             font-size: 12px;
  229.             color: #88aacc;
  230.             margin-top: 5px;
  231.         }
  232.  
  233.         .static-overlay {
  234.             position: absolute;
  235.             top: 0;
  236.             left: 0;
  237.             right: 0;
  238.             bottom: 0;
  239.             background: repeating-linear-gradient(
  240.                 0deg,
  241.                 rgba(255, 255, 255, 0.03),
  242.                 rgba(255, 255, 255, 0.03) 1px,
  243.                 transparent 1px,
  244.                 transparent 2px
  245.             );
  246.             pointer-events: none;
  247.             opacity: 0;
  248.             animation: staticNoise 0.1s infinite;
  249.         }
  250.  
  251.         @keyframes staticNoise {
  252.             0%, 100% { opacity: 0.1; }
  253.             50% { opacity: 0.05; }
  254.         }
  255.  
  256.         .ghost-captured {
  257.             animation: captureFlash 0.5s;
  258.         }
  259.  
  260.         @keyframes captureFlash {
  261.             0%, 100% { background: transparent; }
  262.             50% { background: rgba(0, 255, 136, 0.2); }
  263.         }
  264.  
  265.         #title {
  266.             text-align: center;
  267.             margin-bottom: 30px;
  268.             font-size: 32px;
  269.             background: linear-gradient(90deg, #00ff88, #88ffcc, #00ff88);
  270.             -webkit-background-clip: text;
  271.             -webkit-text-fill-color: transparent;
  272.             background-clip: text;
  273.             text-shadow: 0 0 30px rgba(0, 255, 136, 0.5);
  274.             animation: titleGlow 2s infinite alternate;
  275.         }
  276.  
  277.         @keyframes titleGlow {
  278.             from { filter: brightness(1); }
  279.             to { filter: brightness(1.3); }
  280.         }
  281.  
  282.         .scanning {
  283.             animation: scan 2s infinite;
  284.         }
  285.  
  286.         @keyframes scan {
  287.             0%, 100% { transform: translateX(0); }
  288.             50% { transform: translateX(10px); }
  289.         }
  290.     </style>
  291. </head>
  292. <body>
  293.     <div id="gameContainer">
  294.         <h1 id="title">PHANTOM FREQUENCY FRENZY</h1>
  295.        
  296.         <div id="radio">
  297.             <div id="display">
  298.                 <div id="frequencyDisplay">87.5 MHz</div>
  299.                 <canvas id="spectrum"></canvas>
  300.                 <div class="static-overlay"></div>
  301.             </div>
  302.            
  303.             <div id="tuner">
  304.                 <div id="dialKnob"></div>
  305.                 <div id="frequencyBar">
  306.                     <div id="frequencyIndicator" style="left: 0%;"></div>
  307.                 </div>
  308.             </div>
  309.            
  310.             <div id="ghostMessages">
  311.                 <div style="color: #88aacc; opacity: 0.5;">Tuning into the spectral realm...</div>
  312.             </div>
  313.            
  314.             <div id="controls">
  315.                 <button id="scanBtn">Auto Scan</button>
  316.                 <button id="filterBtn">Filter Static</button>
  317.                 <button id="captureBtn">Capture Ghost</button>
  318.             </div>
  319.            
  320.             <div id="stats">
  321.                 <div class="stat">
  322.                     <div class="stat-value" id="ghostCount">0/10</div>
  323.                     <div class="stat-label">Ghosts Found</div>
  324.                 </div>
  325.                 <div class="stat">
  326.                     <div class="stat-value" id="signalStrength">0%</div>
  327.                     <div class="stat-label">Signal Strength</div>
  328.                 </div>
  329.                 <div class="stat">
  330.                     <div class="stat-value" id="chorusSize">0</div>
  331.                     <div class="stat-label">Chorus Voices</div>
  332.                 </div>
  333.             </div>
  334.         </div>
  335.     </div>
  336.  
  337.     <script>
  338.         class PhantomRadio {
  339.             constructor() {
  340.                 this.frequency = 87.5;
  341.                 this.minFreq = 87.5;
  342.                 this.maxFreq = 108.0;
  343.                 this.ghosts = this.generateGhosts();
  344.                 this.capturedGhosts = new Set();
  345.                 this.totalChorusVoices = 0;
  346.                 this.signalStrength = 0;
  347.                 this.isScanning = false;
  348.                 this.staticFiltered = false;
  349.                 this.currentGhost = null;
  350.                
  351.                 // Power-ups from enhanced version
  352.                 this.powerUps = {
  353.                     signalBoost: { active: false, duration: 0, cooldown: 0 },
  354.                     ghostMagnet: { active: false, duration: 0, cooldown: 0 },
  355.                     staticShield: { active: false, duration: 0, cooldown: 0 }
  356.                 };
  357.                
  358.                 this.canvas = document.getElementById('spectrum');
  359.                 this.ctx = this.canvas.getContext('2d');
  360.                 this.setupCanvas();
  361.                 this.setupControls();
  362.                 this.setupPowerUps();
  363.                 this.startPowerUpTimer();
  364.                 this.animate();
  365.             }
  366.  
  367.             generateGhosts() {
  368.                 const ghostData = [
  369.                     // 1 ghost with 2 voices
  370.                     { freq: 88.3, name: "The Whisperer", message: "Can you... hear... me... through the static...", chorusVoices: 2 },
  371.                    
  372.                     // 4 ghosts with 2-3 voices
  373.                     { freq: 90.1, name: "Lost Child", message: "Where... where is everyone... it's so cold...", chorusVoices: 3 },
  374.                     { freq: 92.7, name: "The Guardian", message: "Beware... the frequency... 96.6...", chorusVoices: 2 },
  375.                     { freq: 94.4, name: "Echo of Time", message: "1952... the accident... we never left...", chorusVoices: 3 },
  376.                     { freq: 98.9, name: "Memory Fragment", message: "Remember us... remember what happened here...", chorusVoices: 2 },
  377.                    
  378.                     // 3 ghosts with 4-6 voices
  379.                     { freq: 101.3, name: "The Scientist", message: "The experiment... it opened a door... we couldn't close it...", chorusVoices: 5 },
  380.                     { freq: 103.7, name: "Sorrow's Voice", message: "They promised we'd be together... lies... all lies...", chorusVoices: 4 },
  381.                     { freq: 105.5, name: "The Warning", message: "Don't trust the voices at 96.6... they're not who they claim...", chorusVoices: 6 },
  382.                    
  383.                     // 2 ghosts with 3-8 voices (correcting to match requirement)
  384.                     { freq: 96.6, name: "The Corrupted", message: "JOIN US... IN THE STATIC... FOREVER...", chorusVoices: 7 },
  385.                     { freq: 107.2, name: "The Keeper", message: "I guard the threshold... between your world and ours...", chorusVoices: 7 }
  386.                 ];
  387.                 return ghostData;
  388.             }
  389.  
  390.             setupCanvas() {
  391.                 this.canvas.width = this.canvas.offsetWidth;
  392.                 this.canvas.height = this.canvas.offsetHeight;
  393.             }
  394.  
  395.             setupControls() {
  396.                 const dialKnob = document.getElementById('dialKnob');
  397.                 const scanBtn = document.getElementById('scanBtn');
  398.                 const filterBtn = document.getElementById('filterBtn');
  399.                 const captureBtn = document.getElementById('captureBtn');
  400.  
  401.                 let isDragging = false;
  402.                 let startX = 0;
  403.                 let startY = 0;
  404.                 let currentRotation = 0;
  405.                 let lastAngle = 0;
  406.  
  407.                 // Helper function to calculate angle from center
  408.                 const getAngleFromCenter = (e, element) => {
  409.                     const rect = element.getBoundingClientRect();
  410.                     const centerX = rect.left + rect.width / 2;
  411.                     const centerY = rect.top + rect.height / 2;
  412.                     const angle = Math.atan2(e.clientY - centerY, e.clientX - centerX);
  413.                     return angle * (180 / Math.PI);
  414.                 };
  415.  
  416.                 dialKnob.addEventListener('mousedown', (e) => {
  417.                     isDragging = true;
  418.                     startX = e.clientX;
  419.                     startY = e.clientY;
  420.                     lastAngle = getAngleFromCenter(e, dialKnob);
  421.                     e.preventDefault();
  422.                 });
  423.  
  424.                 document.addEventListener('mousemove', (e) => {
  425.                     if (isDragging) {
  426.                         const currentAngle = getAngleFromCenter(e, dialKnob);
  427.                         let deltaAngle = currentAngle - lastAngle;
  428.                        
  429.                         // Handle angle wrapping (when crossing from -180 to 180 or vice versa)
  430.                         if (deltaAngle > 180) deltaAngle -= 360;
  431.                         if (deltaAngle < -180) deltaAngle += 360;
  432.                        
  433.                        // Update rotation and frequency based on angle change
  434.                        currentRotation += deltaAngle;
  435.                        dialKnob.style.transform = `rotate(${currentRotation}deg)`;
  436.                        
  437.                        // Adjust frequency based on rotation direction and amount
  438.                        this.adjustFrequency(deltaAngle * 0.05);
  439.                        
  440.                        lastAngle = currentAngle;
  441.                    }
  442.                });
  443.  
  444.                document.addEventListener('mouseup', () => {
  445.                     isDragging = false;
  446.                 });
  447.  
  448.                 // Mouse wheel control remains the same
  449.                 dialKnob.addEventListener('wheel', (e) => {
  450.                     e.preventDefault();
  451.                     const delta = -e.deltaY * 0.01;
  452.                     currentRotation += delta * 10;
  453.                     dialKnob.style.transform = `rotate(${currentRotation}deg)`;
  454.                     this.adjustFrequency(delta);
  455.                 });
  456.  
  457.                 scanBtn.addEventListener('click', () => this.autoScan());
  458.                 filterBtn.addEventListener('click', () => this.toggleFilter());
  459.                 captureBtn.addEventListener('click', () => this.captureGhost());
  460.             }
  461.  
  462.             adjustFrequency(delta) {
  463.                 this.frequency = Math.max(this.minFreq, Math.min(this.maxFreq, this.frequency + delta));
  464.                 this.updateDisplay();
  465.                 this.checkForGhosts();
  466.             }
  467.  
  468.             updateDisplay() {
  469.                 const freqDisplay = document.getElementById('frequencyDisplay');
  470.                 freqDisplay.textContent = `${this.frequency.toFixed(1)} MHz`;
  471.                
  472.                 const indicator = document.getElementById('frequencyIndicator');
  473.                 const percentage = ((this.frequency - this.minFreq) / (this.maxFreq - this.minFreq)) * 100;
  474.                 indicator.style.left = `${percentage}%`;
  475.             }
  476.  
  477.             checkForGhosts() {
  478.                 this.currentGhost = null;
  479.                 this.signalStrength = 0;
  480.  
  481.                 // Base detection range
  482.                 let detectionRange = 0.5;
  483.                
  484.                 // Chorus voices boost detection range (more voices = easier to find)
  485.                 const chorusBoost = Math.min(this.totalChorusVoices * 0.02, 0.3);
  486.                 detectionRange += chorusBoost;
  487.                
  488.                 // Ghost magnet power-up increases detection range
  489.                 if (this.powerUps.ghostMagnet.active) {
  490.                     detectionRange += 0.5;
  491.                 }
  492.  
  493.                 for (const ghost of this.ghosts) {
  494.                     const distance = Math.abs(this.frequency - ghost.freq);
  495.                     if (distance < detectionRange) {
  496.                        // Base signal calculation
  497.                        let strength = Math.max(0, 100 - (distance * 200 / detectionRange));
  498.                        
  499.                        // Chorus voices increase signal strength
  500.                        strength += this.totalChorusVoices * 2;
  501.                        
  502.                        // Signal boost power-up
  503.                        if (this.powerUps.signalBoost.active) {
  504.                            strength *= 1.25;
  505.                        }
  506.                        
  507.                        strength = Math.min(100, strength);
  508.                        this.signalStrength = strength;
  509.                        this.currentGhost = ghost;
  510.                        
  511.                        if (distance < 0.1 && !this.capturedGhosts.has(ghost.freq)) {
  512.                            this.showGhostMessage(ghost);
  513.                        }
  514.                        break;
  515.                    }
  516.                }
  517.  
  518.                document.getElementById('signalStrength').textContent = `${Math.round(this.signalStrength)}%`;
  519.                this.updateStaticEffect();
  520.            }
  521.  
  522.            showGhostMessage(ghost) {
  523.                const messagesDiv = document.getElementById('ghostMessages');
  524.                const messageDiv = document.createElement('div');
  525.                messageDiv.className = 'ghost-message';
  526.                
  527.                const scrambled = (this.staticFiltered || this.powerUps.staticShield.active) ?
  528.                    ghost.message :
  529.                    this.scrambleMessage(ghost.message, this.signalStrength);
  530.                
  531.                messageDiv.textContent = `[${ghost.name}]: ${scrambled}`;
  532.                messagesDiv.appendChild(messageDiv);
  533.                messagesDiv.scrollTop = messagesDiv.scrollHeight;
  534.                
  535.                // Limit messages
  536.                while (messagesDiv.children.length > 5) {
  537.                     messagesDiv.removeChild(messagesDiv.children[1]);
  538.                 }
  539.             }
  540.  
  541.             scrambleMessage(message, strength) {
  542.                 if (strength > 80) return message;
  543.                
  544.                 const chars = message.split('');
  545.                 const scrambleRate = 1 - (strength / 100);
  546.                
  547.                 return chars.map(char => {
  548.                     if (char === ' ') return ' ';
  549.                     return Math.random() < scrambleRate ?
  550.                        String.fromCharCode(33 + Math.floor(Math.random() * 93)) :
  551.                        char;
  552.                }).join('');
  553.            }
  554.  
  555.            captureGhost() {
  556.                if (this.currentGhost && this.signalStrength > 70) {
  557.                     if (!this.capturedGhosts.has(this.currentGhost.freq)) {
  558.                         this.capturedGhosts.add(this.currentGhost.freq);
  559.                         this.totalChorusVoices += this.currentGhost.chorusVoices;
  560.                        
  561.                         // Update displays
  562.                         document.getElementById('ghostCount').textContent = `${this.capturedGhosts.size}/10`;
  563.                         document.getElementById('chorusSize').textContent = this.totalChorusVoices;
  564.                        
  565.                         const radio = document.getElementById('radio');
  566.                         radio.classList.add('ghost-captured');
  567.                         setTimeout(() => radio.classList.remove('ghost-captured'), 500);
  568.                        
  569.                         this.playGhostChorus();
  570.                        
  571.                         // Show ghost details in message
  572.                         const messagesDiv = document.getElementById('ghostMessages');
  573.                         const captureMsg = document.createElement('div');
  574.                         captureMsg.className = 'ghost-message';
  575.                         captureMsg.style.color = '#ffaa00';
  576.                         captureMsg.textContent = `✓ Captured ${this.currentGhost.name}! (+${this.currentGhost.chorusVoices} chorus voices)`;
  577.                         messagesDiv.appendChild(captureMsg);
  578.                         messagesDiv.scrollTop = messagesDiv.scrollHeight;
  579.                        
  580.                         // Check for victory
  581.                         if (this.capturedGhosts.size === 10) {
  582.                             this.victorySequence();
  583.                         }
  584.                     }
  585.                 }
  586.             }
  587.            
  588.             victorySequence() {
  589.                 // Victory message overlay
  590.                 const gameContainer = document.getElementById('gameContainer');
  591.                 const victoryOverlay = document.createElement('div');
  592.                 victoryOverlay.style.cssText = `
  593.                     position: fixed;
  594.                     top: 0;
  595.                     left: 0;
  596.                     width: 100%;
  597.                     height: 100%;
  598.                     background: rgba(0, 0, 0, 0.9);
  599.                     display: flex;
  600.                     flex-direction: column;
  601.                     align-items: center;
  602.                     justify-content: center;
  603.                     z-index: 1000;
  604.                     animation: fadeIn 1s;
  605.                 `;
  606.                
  607.                 const victoryContent = document.createElement('div');
  608.                 victoryContent.style.cssText = `
  609.                     text-align: center;
  610.                     padding: 40px;
  611.                     background: linear-gradient(145deg, #2a3a4a, #1a2a3a);
  612.                     border-radius: 20px;
  613.                     border: 3px solid #00ff88;
  614.                     box-shadow: 0 0 50px rgba(0, 255, 136, 0.5);
  615.                     max-width: 500px;
  616.                 `;
  617.                
  618.                 victoryContent.innerHTML = `
  619.                     <h1 style="color: #00ff88; font-size: 36px; margin-bottom: 20px; text-shadow: 0 0 20px #00ff88;">
  620.                         🎉 CONGRATULATIONS! 🎉
  621.                     </h1>
  622.                     <p style="color: #88ffcc; font-size: 20px; margin: 10px 0;">
  623.                         You've captured all 10/10 ghosts!
  624.                     </p>
  625.                     <p style="color: #88aacc; font-size: 16px; margin: 20px 0;">
  626.                         Total Chorus Voices Collected: <span style="color: #00ff88; font-size: 24px; font-weight: bold;">${this.totalChorusVoices}</span>
  627.                     </p>
  628.                     <p style="color: #88aacc; font-size: 14px; margin: 20px 0; font-style: italic;">
  629.                         The spectral realm is now at peace. The ghosts thank you for freeing them from their eternal static prison.
  630.                     </p>
  631.                     <button onclick="location.reload()" style="
  632.                        margin-top: 30px;
  633.                        padding: 15px 30px;
  634.                        background: linear-gradient(145deg, #00aa55, #00ff88);
  635.                        color: #000;
  636.                        border: none;
  637.                        border-radius: 10px;
  638.                        font-size: 18px;
  639.                        font-weight: bold;
  640.                        cursor: pointer;
  641.                        box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
  642.                        transition: all 0.3s;
  643.                    " onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
  644.                         PLAY AGAIN
  645.                     </button>
  646.                 `;
  647.                
  648.                 victoryOverlay.appendChild(victoryContent);
  649.                 document.body.appendChild(victoryOverlay);
  650.                
  651.                 // Add CSS animation
  652.                 const style = document.createElement('style');
  653.                 style.textContent = `
  654.                     @keyframes fadeIn {
  655.                         from { opacity: 0; }
  656.                         to { opacity: 1; }
  657.                     }
  658.                 `;
  659.                 document.head.appendChild(style);
  660.                
  661.                 // Celebration effects
  662.                 this.playCelebration();
  663.             }
  664.            
  665.             playCelebration() {
  666.                 const display = document.getElementById('display');
  667.                
  668.                 // Create celebration particles
  669.                 for (let i = 0; i < 50; i++) {
  670.                    setTimeout(() => {
  671.                         const particle = document.createElement('div');
  672.                         particle.className = 'ghost-particle';
  673.                         particle.style.left = `${Math.random() * 100}%`;
  674.                         particle.style.bottom = '0';
  675.                         particle.style.background = `radial-gradient(circle, hsl(${Math.random() * 360}, 100%, 70%), transparent)`;
  676.                         particle.style.animationDelay = '0s';
  677.                         particle.style.animationDuration = `${2 + Math.random() * 2}s`;
  678.                         display.appendChild(particle);
  679.                        
  680.                         setTimeout(() => particle.remove(), 4000);
  681.                     }, i * 50);
  682.                 }
  683.             }
  684.  
  685.             playGhostChorus() {
  686.                 // Visual feedback for chorus
  687.                 const particles = 20;
  688.                 const display = document.getElementById('display');
  689.                
  690.                 for (let i = 0; i < particles; i++) {
  691.                    const particle = document.createElement('div');
  692.                    particle.className = 'ghost-particle';
  693.                    particle.style.left = `${Math.random() * 100}%`;
  694.                    particle.style.bottom = '0';
  695.                    particle.style.animationDelay = `${Math.random() * 2}s`;
  696.                    display.appendChild(particle);
  697.                    
  698.                    setTimeout(() => particle.remove(), 3000);
  699.                 }
  700.             }
  701.  
  702.             setupPowerUps() {
  703.                 // Create power-ups section
  704.                 const radio = document.getElementById('radio');
  705.                 const powerUpSection = document.createElement('div');
  706.                 powerUpSection.id = 'powerUps';
  707.                 powerUpSection.style.cssText = `
  708.                     display: flex;
  709.                     justify-content: space-between;
  710.                     margin-top: 15px;
  711.                     gap: 10px;
  712.                 `;
  713.                
  714.                 // Create three power-up cards
  715.                 const powerUp1 = this.createPowerUpCard('powerUp1', 'Signal Boost', '+25% signal strength for 10s');
  716.                 const powerUp2 = this.createPowerUpCard('powerUp2', 'Ghost Magnet', 'Reveals nearby ghosts');
  717.                 const powerUp3 = this.createPowerUpCard('powerUp3', 'Static Shield', 'Reduces static for 15s');
  718.                
  719.                 powerUpSection.appendChild(powerUp1);
  720.                 powerUpSection.appendChild(powerUp2);
  721.                 powerUpSection.appendChild(powerUp3);
  722.                 radio.appendChild(powerUpSection);
  723.                
  724.                 // Add event listeners
  725.                 powerUp1.addEventListener('click', () => this.activatePowerUp('signalBoost'));
  726.                 powerUp2.addEventListener('click', () => this.activatePowerUp('ghostMagnet'));
  727.                 powerUp3.addEventListener('click', () => this.activatePowerUp('staticShield'));
  728.             }
  729.            
  730.             createPowerUpCard(id, name, description) {
  731.                 const card = document.createElement('div');
  732.                 card.id = id;
  733.                 card.className = 'power-up';
  734.                 card.style.cssText = `
  735.                     flex: 1;
  736.                     background: rgba(0, 40, 80, 0.7);
  737.                     border-radius: 8px;
  738.                     padding: 12px;
  739.                     text-align: center;
  740.                     border: 2px solid #3a4a5a;
  741.                     cursor: pointer;
  742.                     transition: all 0.3s;
  743.                 `;
  744.                
  745.                 const nameDiv = document.createElement('div');
  746.                 nameDiv.className = 'power-up-name';
  747.                 nameDiv.style.cssText = `
  748.                     font-size: 13px;
  749.                     color: #00ff88;
  750.                     margin-bottom: 5px;
  751.                     font-weight: bold;
  752.                     text-transform: uppercase;
  753.                 `;
  754.                 nameDiv.textContent = name;
  755.                
  756.                 const descDiv = document.createElement('div');
  757.                 descDiv.className = 'power-up-desc';
  758.                 descDiv.style.cssText = `
  759.                     font-size: 11px;
  760.                     color: #88aacc;
  761.                 `;
  762.                 descDiv.textContent = description;
  763.                
  764.                 card.appendChild(nameDiv);
  765.                 card.appendChild(descDiv);
  766.                
  767.                 card.addEventListener('mouseenter', () => {
  768.                     if (!card.disabled) {
  769.                         card.style.background = 'rgba(0, 60, 120, 0.8)';
  770.                         card.style.transform = 'translateY(-2px)';
  771.                         card.style.boxShadow = '0 5px 15px rgba(0, 255, 136, 0.3)';
  772.                     }
  773.                 });
  774.                
  775.                 card.addEventListener('mouseleave', () => {
  776.                     if (!card.disabled) {
  777.                         card.style.background = 'rgba(0, 40, 80, 0.7)';
  778.                         card.style.transform = 'translateY(0)';
  779.                         card.style.boxShadow = 'none';
  780.                     }
  781.                 });
  782.                
  783.                 return card;
  784.             }
  785.            
  786.             activatePowerUp(type) {
  787.                 const powerUp = this.powerUps[type];
  788.                 if (powerUp.active || powerUp.cooldown > 0) return;
  789.                
  790.                 powerUp.active = true;
  791.                
  792.                 switch(type) {
  793.                     case 'signalBoost':
  794.                         powerUp.duration = 10;
  795.                         powerUp.cooldown = 30;
  796.                         break;
  797.                     case 'ghostMagnet':
  798.                         powerUp.duration = 15;
  799.                         powerUp.cooldown = 45;
  800.                         break;
  801.                     case 'staticShield':
  802.                         powerUp.duration = 15;
  803.                         powerUp.cooldown = 40;
  804.                         break;
  805.                 }
  806.                
  807.                 this.updatePowerUpDisplay();
  808.                 this.checkForGhosts();
  809.             }
  810.            
  811.             updatePowerUpDisplay() {
  812.                 const powerUp1 = document.getElementById('powerUp1');
  813.                 const powerUp2 = document.getElementById('powerUp2');
  814.                 const powerUp3 = document.getElementById('powerUp3');
  815.                
  816.                 // Update Signal Boost
  817.                 this.updatePowerUpCard(powerUp1, 'signalBoost', 'Signal Boost', '+25% signal strength for 10s');
  818.                
  819.                 // Update Ghost Magnet
  820.                 this.updatePowerUpCard(powerUp2, 'ghostMagnet', 'Ghost Magnet', 'Reveals nearby ghosts');
  821.                
  822.                 // Update Static Shield
  823.                 this.updatePowerUpCard(powerUp3, 'staticShield', 'Static Shield', 'Reduces static for 15s');
  824.             }
  825.            
  826.             updatePowerUpCard(card, powerUpKey, name, description) {
  827.                 const powerUp = this.powerUps[powerUpKey];
  828.                 const nameDiv = card.querySelector('.power-up-name');
  829.                 const descDiv = card.querySelector('.power-up-desc');
  830.                
  831.                 if (powerUp.active) {
  832.                     card.style.background = 'rgba(0, 100, 50, 0.8)';
  833.                     card.style.borderColor = '#00ff88';
  834.                     card.style.boxShadow = '0 0 20px rgba(0, 255, 136, 0.5)';
  835.                     nameDiv.style.color = '#00ff88';
  836.                     nameDiv.textContent = `${name} ACTIVE`;
  837.                     descDiv.textContent = `Active for ${powerUp.duration}s`;
  838.                     descDiv.style.color = '#00ff88';
  839.                     card.disabled = true;
  840.                     card.style.cursor = 'not-allowed';
  841.                 } else if (powerUp.cooldown > 0) {
  842.                     card.style.background = 'rgba(60, 20, 20, 0.7)';
  843.                     card.style.borderColor = '#666';
  844.                     card.style.boxShadow = 'none';
  845.                     nameDiv.style.color = '#666';
  846.                     nameDiv.textContent = name;
  847.                     descDiv.textContent = `Cooldown: ${powerUp.cooldown}s`;
  848.                     descDiv.style.color = '#666';
  849.                     card.disabled = true;
  850.                     card.style.cursor = 'not-allowed';
  851.                 } else {
  852.                     card.style.background = 'rgba(0, 40, 80, 0.7)';
  853.                     card.style.borderColor = '#3a4a5a';
  854.                     card.style.boxShadow = 'none';
  855.                     nameDiv.style.color = '#00ff88';
  856.                     nameDiv.textContent = name;
  857.                     descDiv.textContent = description;
  858.                     descDiv.style.color = '#88aacc';
  859.                     card.disabled = false;
  860.                     card.style.cursor = 'pointer';
  861.                 }
  862.             }
  863.            
  864.             startPowerUpTimer() {
  865.                 setInterval(() => {
  866.                     // Update power-up durations and cooldowns
  867.                     for (const key in this.powerUps) {
  868.                         const powerUp = this.powerUps[key];
  869.                        
  870.                         if (powerUp.active) {
  871.                             powerUp.duration--;
  872.                             if (powerUp.duration <= 0) {
  873.                                powerUp.active = false;
  874.                            }
  875.                        }
  876.                        
  877.                        if (powerUp.cooldown > 0) {
  878.                             powerUp.cooldown--;
  879.                         }
  880.                     }
  881.                    
  882.                     this.updatePowerUpDisplay();
  883.                 }, 1000);
  884.             }
  885.  
  886.             toggleFilter() {
  887.                 this.staticFiltered = !this.staticFiltered;
  888.                 const filterBtn = document.getElementById('filterBtn');
  889.                 filterBtn.textContent = this.staticFiltered ? 'Filter: ON' : 'Filter: OFF';
  890.                 filterBtn.style.borderColor = this.staticFiltered ? '#ffaa00' : '#00ff88';
  891.                 filterBtn.style.color = this.staticFiltered ? '#ffaa00' : '#00ff88';
  892.             }
  893.  
  894.             updateStaticEffect() {
  895.                 const staticOverlay = document.querySelector('.static-overlay');
  896.                 let opacity = (1 - this.signalStrength / 100) * 0.3;
  897.                
  898.                 // Static shield reduces static
  899.                 if (this.powerUps.staticShield.active) {
  900.                     opacity *= 0.3;
  901.                 } else if (this.staticFiltered) {
  902.                     opacity *= 0.5;
  903.                 }
  904.                
  905.                 staticOverlay.style.opacity = opacity;
  906.             }
  907.            
  908.             autoScan() {
  909.                 if (this.isScanning) return;
  910.                
  911.                 this.isScanning = true;
  912.                 const scanBtn = document.getElementById('scanBtn');
  913.                 scanBtn.textContent = 'Scanning...';
  914.                 scanBtn.classList.add('scanning');
  915.                
  916.                 const scanInterval = setInterval(() => {
  917.                     this.frequency += 0.2;
  918.                     if (this.frequency > this.maxFreq) {
  919.                         this.frequency = this.minFreq;
  920.                     }
  921.                    
  922.                     this.updateDisplay();
  923.                     this.checkForGhosts();
  924.                    
  925.                     if (this.currentGhost && this.signalStrength > 90) {
  926.                        clearInterval(scanInterval);
  927.                         this.isScanning = false;
  928.                         scanBtn.textContent = 'Auto Scan';
  929.                         scanBtn.classList.remove('scanning');
  930.                     }
  931.                 }, 50);
  932.                
  933.                 setTimeout(() => {
  934.                     if (this.isScanning) {
  935.                         clearInterval(scanInterval);
  936.                         this.isScanning = false;
  937.                         scanBtn.textContent = 'Auto Scan';
  938.                         scanBtn.classList.remove('scanning');
  939.                     }
  940.                 }, 10000);
  941.             }
  942.  
  943.             drawSpectrum() {
  944.                 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  945.                
  946.                 const bars = 50;
  947.                 const barWidth = this.canvas.width / bars;
  948.                
  949.                 for (let i = 0; i < bars; i++) {
  950.                    const height = Math.random() * this.canvas.height * 0.7;
  951.                    const intensity = this.signalStrength / 100;
  952.                    
  953.                    // Ghost presence creates pattern
  954.                    const ghostEffect = this.currentGhost ?
  955.                        Math.sin(Date.now() * 0.001 + i * 0.5) * 0.5 + 0.5 :
  956.                        0;
  957.                    
  958.                    const finalHeight = height * (0.3 + intensity * 0.7) + ghostEffect * 50;
  959.                    
  960.                    const hue = 160 + intensity * 20;
  961.                    const lightness = 50 + intensity * 30;
  962.                    
  963.                    this.ctx.fillStyle = `hsla(${hue}, 100%, ${lightness}%, ${0.3 + intensity * 0.5})`;
  964.                    this.ctx.fillRect(
  965.                        i * barWidth,
  966.                        this.canvas.height - finalHeight,
  967.                        barWidth - 2,
  968.                        finalHeight
  969.                    );
  970.                    
  971.                    // Add glow effect
  972.                    if (intensity > 0.5) {
  973.                         this.ctx.shadowBlur = 10;
  974.                         this.ctx.shadowColor = `hsla(${hue}, 100%, 70%, ${intensity})`;
  975.                         this.ctx.fillRect(
  976.                             i * barWidth,
  977.                             this.canvas.height - finalHeight,
  978.                             barWidth - 2,
  979.                             2
  980.                         );
  981.                         this.ctx.shadowBlur = 0;
  982.                     }
  983.                 }
  984.             }
  985.  
  986.             animate() {
  987.                 this.drawSpectrum();
  988.                 requestAnimationFrame(() => this.animate());
  989.             }
  990.         }
  991.  
  992.         // Initialize game
  993.         const game = new PhantomRadio();
  994.     </script>
  995. </body>
  996. </html>
  997.  
Advertisement
Add Comment
Please, Sign In to add comment