added display FX, moved IDEAS for future reference
This commit is contained in:
parent
c10cd26e1d
commit
62eba51514
416
index.html
416
index.html
@ -167,6 +167,7 @@
|
|||||||
linear-gradient(60deg, transparent 0%, transparent 68%, rgba(255,255,255,0.03) 68.5%, transparent 69%),
|
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);
|
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.6) 2px, rgba(0,0,0,0.6) 3px);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Flickering scanlines */
|
/* 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);
|
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;
|
pointer-events: none;
|
||||||
animation: flicker 4s infinite;
|
animation: flicker 4s infinite;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes flicker {
|
@keyframes flicker {
|
||||||
@ -189,6 +191,228 @@
|
|||||||
52% { opacity: 0.7; }
|
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) */
|
/* Track name text - scrolls when playing (JS controlled) */
|
||||||
.display-text {
|
.display-text {
|
||||||
color: #00ff00;
|
color: #00ff00;
|
||||||
@ -1066,7 +1290,17 @@
|
|||||||
<button class="eject-btn" id="ejectBtn" title="Eject"></button>
|
<button class="eject-btn" id="ejectBtn" title="Eject"></button>
|
||||||
<button class="lightning-btn" id="lightningBtn" title="Menu">⚡</button>
|
<button class="lightning-btn" id="lightningBtn" title="Menu">⚡</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 -->
|
<!-- 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="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>
|
<div class="time-display" id="timeDisplay">00:00 / 00:00</div>
|
||||||
@ -1602,6 +1836,186 @@
|
|||||||
// SOUND EFFECTS MODULE - END
|
// 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
|
// BUTTON CLICK SOUNDS - Add to all buttons
|
||||||
// Uses mousedown for immediate tactile feedback
|
// Uses mousedown for immediate tactile feedback
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user