diff --git a/IDEAS.md b/archive/IDEAS.md similarity index 100% rename from IDEAS.md rename to archive/IDEAS.md diff --git a/index.html b/index.html index 122bee5..f34c892 100644 --- a/index.html +++ b/index.html @@ -167,6 +167,7 @@ linear-gradient(60deg, transparent 0%, transparent 68%, rgba(255,255,255,0.03) 68.5%, transparent 69%), repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.6) 2px, rgba(0,0,0,0.6) 3px); pointer-events: none; + z-index: 2; } /* Flickering scanlines */ @@ -180,6 +181,7 @@ background: repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,0.5) 1px, rgba(0,0,0,0.5) 2px); pointer-events: none; animation: flicker 4s infinite; + z-index: 3; } @keyframes flicker { @@ -189,6 +191,228 @@ 52% { opacity: 0.7; } } + /* ======================================== + DISPLAY GLITCH EFFECTS - START + Random visual glitches for dystopian aesthetic + ======================================== */ + + /* RGB Chromatic Aberration Glitch */ + .display.glitch-rgb .display-text-inner, + .display.glitch-rgb .time-display { + animation: rgbGlitch 0.08s steps(2) infinite; + } + + @keyframes rgbGlitch { + 0% { + text-shadow: + -2px 0 #ff0000, + 2px 0 #00ffff, + 0 0 10px #00ff00, + 0 0 20px #00ff00; + transform: translateX(-1px); + } + 50% { + text-shadow: + 2px 0 #ff0000, + -2px 0 #00ffff, + 0 0 10px #00ff00, + 0 0 20px #00ff00; + transform: translateX(1px); + } + 100% { + text-shadow: + -1px 0 #ff0000, + 1px 0 #00ffff, + 0 0 10px #00ff00, + 0 0 20px #00ff00; + transform: translateX(0); + } + } + + /* Intermittent Display Failure (Blackout) */ + .display.blackout { + filter: brightness(0); + } + + .display.blackout .display-text, + .display.blackout .display-text-inner, + .display.blackout .time-display { + opacity: 0 !important; + text-shadow: none !important; + } + + /* VHS Tracking Lines Overlay */ + .vhs-tracking { + position: absolute; + top: 0; + left: 0; + pointer-events: none; + opacity: 0; + z-index: 15; + } + + /* Vertical tracking (default) - lines scroll up */ + .vhs-tracking.vertical { + right: 0; + bottom: auto; + width: 100%; + height: 200%; + background: repeating-linear-gradient( + 0deg, + transparent 0px, + transparent 8px, + rgba(255, 255, 255, 0.06) 8px, + rgba(255, 255, 255, 0.06) 12px, + transparent 12px, + transparent 25px, + rgba(0, 255, 0, 0.03) 25px, + rgba(0, 255, 0, 0.03) 28px + ); + } + + /* Horizontal tracking - lines scroll left */ + .vhs-tracking.horizontal { + bottom: 0; + right: auto; + width: 200%; + height: 100%; + background: repeating-linear-gradient( + 90deg, + transparent 0px, + transparent 8px, + rgba(255, 255, 255, 0.06) 8px, + rgba(255, 255, 255, 0.06) 12px, + transparent 12px, + transparent 25px, + rgba(0, 255, 0, 0.03) 25px, + rgba(0, 255, 0, 0.03) 28px + ); + } + + .vhs-tracking.active.vertical { + opacity: 1; + animation: vhsScrollVertical 0.4s linear; + } + + .vhs-tracking.active.horizontal { + opacity: 1; + animation: vhsScrollHorizontal 0.4s linear; + } + + @keyframes vhsScrollVertical { + from { transform: translateY(-50%); } + to { transform: translateY(0); } + } + + @keyframes vhsScrollHorizontal { + from { transform: translateX(-50%); } + to { transform: translateX(0); } + } + + /* Dim Spots - worn/aged phosphor effect on CRT */ + .dim-spots-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + z-index: 20; + overflow: hidden; + } + + .dim-spot { + position: absolute; + pointer-events: none; + will-change: transform; + } + + /* Individual spot styles and animations */ + .dim-spot-1 { + top: 10%; + left: 0%; + width: 50%; + height: 60%; + background: radial-gradient(ellipse 70% 75% at center, rgba(0, 0, 0, 0.55) 0%, transparent 70%); + animation: dimSpotDrift1 45s ease-in-out infinite; + } + + .dim-spot-2 { + top: 5%; + right: 0%; + width: 40%; + height: 50%; + background: radial-gradient(ellipse 75% 80% at center, rgba(0, 0, 0, 0.4) 0%, transparent 65%); + animation: dimSpotDrift2 55s ease-in-out infinite; + } + + .dim-spot-3 { + bottom: 5%; + left: 10%; + width: 35%; + height: 45%; + background: radial-gradient(ellipse 70% 75% at center, rgba(0, 0, 0, 0.45) 0%, transparent 60%); + animation: dimSpotDrift3 50s ease-in-out infinite; + } + + .dim-spot-4 { + bottom: 0%; + right: 0%; + width: 45%; + height: 55%; + background: radial-gradient(ellipse 80% 90% at center, rgba(0, 0, 0, 0.5) 0%, transparent 65%); + animation: dimSpotDrift4 60s ease-in-out infinite; + } + + .dim-spot-5 { + top: 35%; + left: 55%; + width: 30%; + height: 35%; + background: radial-gradient(circle at center, rgba(0, 0, 0, 0.35) 0%, transparent 60%); + animation: dimSpotDrift5 40s ease-in-out infinite; + } + + /* Slow drifting animations - each spot moves in a different pattern */ + @keyframes dimSpotDrift1 { + 0%, 100% { transform: translate(0%, 0%); } + 25% { transform: translate(8%, 5%); } + 50% { transform: translate(5%, -8%); } + 75% { transform: translate(-5%, 3%); } + } + + @keyframes dimSpotDrift2 { + 0%, 100% { transform: translate(0%, 0%); } + 25% { transform: translate(-10%, 6%); } + 50% { transform: translate(-5%, 10%); } + 75% { transform: translate(5%, -5%); } + } + + @keyframes dimSpotDrift3 { + 0%, 100% { transform: translate(0%, 0%); } + 25% { transform: translate(6%, -8%); } + 50% { transform: translate(12%, -3%); } + 75% { transform: translate(-4%, -10%); } + } + + @keyframes dimSpotDrift4 { + 0%, 100% { transform: translate(0%, 0%); } + 25% { transform: translate(-8%, -6%); } + 50% { transform: translate(-12%, 4%); } + 75% { transform: translate(4%, -8%); } + } + + @keyframes dimSpotDrift5 { + 0%, 100% { transform: translate(0%, 0%); } + 25% { transform: translate(-15%, 10%); } + 50% { transform: translate(10%, 15%); } + 75% { transform: translate(15%, -10%); } + } + + /* ======================================== + DISPLAY GLITCH EFFECTS - END + ======================================== */ + /* Track name text - scrolls when playing (JS controlled) */ .display-text { color: #00ff00; @@ -1066,7 +1290,17 @@ -
+
+ +
+
+
+
+
+
+
+ +
TRACK 1
00:00 / 00:00
@@ -1602,6 +1836,186 @@ // SOUND EFFECTS MODULE - END // ======================================== + // ======================================== + // DISPLAY GLITCH MODULE - START + // Random visual glitches for dystopian CRT effect + // ======================================== + const DisplayGlitch = { + display: null, + vhsTracking: null, + isEnabled: true, + + // Timing ranges (in milliseconds) + RGB_GLITCH_MIN_INTERVAL: 5000, // 5 seconds + RGB_GLITCH_MAX_INTERVAL: 15000, // 15 seconds + RGB_GLITCH_DURATION_MIN: 80, // 80ms + RGB_GLITCH_DURATION_MAX: 250, // 250ms + + BLACKOUT_MIN_INTERVAL: 30000, // 30 seconds + BLACKOUT_MAX_INTERVAL: 90000, // 90 seconds + BLACKOUT_DURATION: 60, // 60ms per flicker + + VHS_MIN_INTERVAL: 10000, // 10 seconds + VHS_MAX_INTERVAL: 30000, // 30 seconds + VHS_DURATION: 400, // 400ms + + /** + * Initialize the glitch module + * Gets DOM references and starts random glitch intervals + */ + init() { + this.display = document.getElementById('display'); + this.vhsTracking = document.getElementById('vhsTracking'); + + if (!this.display || !this.vhsTracking) { + console.warn('DisplayGlitch: Could not find display elements'); + return; + } + + // Start the random glitch schedulers + this.scheduleNext('rgb'); + this.scheduleNext('blackout'); + this.scheduleNext('vhs'); + + console.log('DisplayGlitch: Initialized'); + }, + + /** + * Get a random value between min and max + */ + randomBetween(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + + /** + * Trigger RGB chromatic aberration glitch + * Adds class that causes RGB channel separation animation + */ + triggerRgbGlitch() { + if (!this.isEnabled || !this.display) return; + + const duration = this.randomBetween( + this.RGB_GLITCH_DURATION_MIN, + this.RGB_GLITCH_DURATION_MAX + ); + + this.display.classList.add('glitch-rgb'); + + setTimeout(() => { + this.display.classList.remove('glitch-rgb'); + }, duration); + }, + + /** + * Trigger intermittent display failure (blackout) + * Creates 1-3 quick flickers for realistic CRT failure + */ + triggerBlackout() { + if (!this.isEnabled || !this.display) return; + + // Random number of flickers (1-3) + const flickerCount = this.randomBetween(1, 3); + let currentFlicker = 0; + + const doFlicker = () => { + this.display.classList.add('blackout'); + + setTimeout(() => { + this.display.classList.remove('blackout'); + currentFlicker++; + + // If more flickers needed, do them after a short gap + if (currentFlicker < flickerCount) { + setTimeout(doFlicker, this.randomBetween(30, 80)); + } + }, this.BLACKOUT_DURATION); + }; + + doFlicker(); + }, + + /** + * Trigger VHS tracking lines + * Randomly shows vertical (scrolling up) or horizontal (scrolling left) tracking lines + */ + triggerVhsTracking() { + if (!this.isEnabled || !this.vhsTracking) return; + + // Remove all classes to reset + this.vhsTracking.classList.remove('active', 'vertical', 'horizontal'); + + // Force reflow to restart animation + void this.vhsTracking.offsetWidth; + + // Randomly choose direction (50/50 chance) + const direction = Math.random() < 0.5 ? 'vertical' : 'horizontal'; + + this.vhsTracking.classList.add(direction); + this.vhsTracking.classList.add('active'); + + // Remove classes after animation completes + setTimeout(() => { + this.vhsTracking.classList.remove('active', 'vertical', 'horizontal'); + + // 30% chance of a second burst shortly after + if (Math.random() < 0.3) { + setTimeout(() => { + this.triggerVhsTracking(); + }, this.randomBetween(100, 300)); + } + }, this.VHS_DURATION); + }, + + /** + * Schedule the next occurrence of a glitch type + * @param {string} type - 'rgb', 'blackout', or 'vhs' + */ + scheduleNext(type) { + let minInterval, maxInterval, triggerFn; + + switch (type) { + case 'rgb': + minInterval = this.RGB_GLITCH_MIN_INTERVAL; + maxInterval = this.RGB_GLITCH_MAX_INTERVAL; + triggerFn = () => this.triggerRgbGlitch(); + break; + case 'blackout': + minInterval = this.BLACKOUT_MIN_INTERVAL; + maxInterval = this.BLACKOUT_MAX_INTERVAL; + triggerFn = () => this.triggerBlackout(); + break; + case 'vhs': + minInterval = this.VHS_MIN_INTERVAL; + maxInterval = this.VHS_MAX_INTERVAL; + triggerFn = () => this.triggerVhsTracking(); + break; + default: + return; + } + + const delay = this.randomBetween(minInterval, maxInterval); + + setTimeout(() => { + triggerFn(); + this.scheduleNext(type); // Schedule next occurrence + }, delay); + }, + + /** + * Enable or disable glitch effects + * @param {boolean} enabled + */ + setEnabled(enabled) { + this.isEnabled = enabled; + } + }; + + // Initialize glitch effects after a short delay + setTimeout(() => DisplayGlitch.init(), 1000); + // ======================================== + // DISPLAY GLITCH MODULE - END + // ======================================== + // ======================================== // BUTTON CLICK SOUNDS - Add to all buttons // Uses mousedown for immediate tactile feedback