Compare commits

...

2 Commits

Author SHA1 Message Date
cottongin
62eba51514
added display FX, moved IDEAS for future reference 2026-01-17 15:12:54 -05:00
cottongin
c10cd26e1d
initial ideas 2026-01-17 14:18:25 -05:00
2 changed files with 509 additions and 1 deletions

94
archive/IDEAS.md Normal file
View File

@ -0,0 +1,94 @@
Let me take a look at what's currently in the project to understand the existing features and theme better.
[1 tool called]
This is a really well-crafted project with a lot of nice details already. Let me put together some ideas for easter eggs and tweaks that would fit the retro dystopian/broken cassette player theme.
Based on what I see in the code, you already have:
- CRT scanlines and flicker effects
- Cracked display overlay
- Worn/faded button icons
- Damage texture overlay
- Tape wind sound effects
- Bouncing track name marquee
Here are some ideas that would complement the existing aesthetic:
---
## Visual Glitch / Distortion Effects
1. **Random screen glitches** - Occasional RGB channel separation or horizontal displacement that happens randomly or when interacting with controls (like the display is malfunctioning)
2. **Dead pixels** - A few permanently "stuck" green pixels on the CRT display
3. **Screen burn-in** - Faint ghost image of "PLAY" or old track names that lingers on the display
4. **Intermittent display failure** - Rare chance of the display going dark for a split second then flickering back on
5. **VHS tracking lines** - Occasional horizontal noise bars that scroll up the display
---
## Audio Quirks
6. **Tape flutter/wow** - Subtle pitch wobble effect using Web Audio API's `playbackRate` with slight random variations, like a worn tape mechanism
7. **Random audio dropouts** - Very brief moments where audio cuts out or gets muffled (like dirty tape heads)
8. **Static between tracks** - White noise/hiss during track transitions
9. **"Tape eating" event** - Rare chance when pressing play/stop that triggers a tape-eating sound effect and animation where the tape visually tangles
---
## Hidden Interactions
10. **Konami code** - Entering the classic code unlocks a secret mode (different color scheme, hidden track, or "repaired" mode where everything works perfectly)
11. **Thump to fix** - Double-clicking/tapping the cassette housing "thumps" the player, which could temporarily fix glitches or cause new ones
12. **Secret button combo** - Holding certain buttons together reveals hidden content or debug info
13. **Dust accumulation** - Animated dust particles that slowly build up on the display, clickable to "wipe clean"
---
## Dystopian Theme Additions
14. **Redacted track names** - Occasional text that appears with black boxes like "[REDACTED]" or "TRACK ██ - ████████"
15. **Surveillance indicator** - Small red "REC" light in the corner that occasionally blinks, or a subtle camera icon
16. **Propaganda interruptions** - Random brief messages that flash on screen like "COMPLIANCE IS FREEDOM" or "REPORT SUSPICIOUS ACTIVITY"
17. **Emergency broadcast** - Rare event where audio cuts to an emergency alert tone with scrolling text
18. **Signal interference** - Brief moments where the display shows "NO SIGNAL" or "WEAK SIGNAL"
---
## Time-Based Behaviors
19. **Midnight mode** - Different behavior or color scheme between certain hours (dimmer, more glitchy, or eerier)
20. **Battery drain simulation** - After extended use, everything gradually slows down and gets distorted until "recharged" (refreshing the page)
---
## Fun Visual Additions
21. **Tape label graffiti/stickers** - Small worn stickers or handwritten notes on the cassette
22. **Cigarette burn marks** - Subtle burn marks on the player casing
23. **Fingerprint smudges** - Subtle smudges on the tape window or display
24. **"Property of..." label** - A worn/scratched identification label with mysterious text
---
Would you like me to create a plan to implement some of these? If so, which ones appeal to you most? I can group them into:
- **Quick wins** (CSS-only or simple JS)
- **Medium effort** (Web Audio effects, animations)
- **Larger features** (Konami code, emergency broadcast system)

View File

@ -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 @@
<button class="eject-btn" id="ejectBtn" title="Eject"></button>
<button class="lightning-btn" id="lightningBtn" title="Menu">&#9889;</button>
<div class="display">
<div class="display" id="display">
<!-- Dim spots - worn phosphor effect (slowly drifting) -->
<div class="dim-spots-container">
<div class="dim-spot dim-spot-1"></div>
<div class="dim-spot dim-spot-2"></div>
<div class="dim-spot dim-spot-3"></div>
<div class="dim-spot dim-spot-4"></div>
<div class="dim-spot dim-spot-5"></div>
</div>
<!-- VHS tracking lines overlay (controlled by DisplayGlitch module) -->
<div class="vhs-tracking" id="vhsTracking"></div>
<!-- Track name with inner span for marquee scrolling -->
<div class="display-text" id="trackName"><span class="display-text-inner" id="trackNameInner">TRACK 1</span></div>
<div class="time-display" id="timeDisplay">00:00 / 00:00</div>
@ -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