312 lines
10 KiB
JavaScript
312 lines
10 KiB
JavaScript
import { BaseModule } from './base-module.js';
|
|
import { moduleRegistry } from './module-registry.js';
|
|
|
|
class UIEffects extends BaseModule {
|
|
constructor() {
|
|
super('ui-effects', 'UI Effects');
|
|
|
|
// No external dependencies
|
|
this.dependencies = [];
|
|
|
|
// Effects state
|
|
this.activeEffects = new Map();
|
|
this.ambientEffectsActive = false;
|
|
|
|
// Effects configuration - use the config object from BaseModule
|
|
this.updateConfig({
|
|
candleFlicker: {
|
|
intensity: 0.5,
|
|
speed: 0.8
|
|
},
|
|
textShadow: {
|
|
enabled: true,
|
|
color: 'rgba(0, 0, 0, 0.5)'
|
|
},
|
|
backgroundEffects: {
|
|
enabled: true
|
|
}
|
|
});
|
|
|
|
// Use bindMethods from parent class
|
|
this.bindMethods([
|
|
'updateCandleEffect',
|
|
'setupEffectElements',
|
|
'createEffectsOverlay',
|
|
'createCandleEffect',
|
|
'createLightingElement',
|
|
'setupAmbientEffects',
|
|
'setupCandleFlickerEffect',
|
|
'startAmbientEffects',
|
|
'stopAmbientEffects',
|
|
'applyEffect',
|
|
'applyShakeEffect',
|
|
'applyFlashEffect',
|
|
'applyTextEmphasis',
|
|
'processCommand'
|
|
]);
|
|
|
|
console.log('UIEffects: Constructor initialized');
|
|
}
|
|
|
|
async initialize() {
|
|
this.reportProgress(0, 'Initializing UI Effects');
|
|
|
|
try {
|
|
this.reportProgress(30, 'Setting up effect elements');
|
|
|
|
// Create or get effect elements
|
|
this.setupEffectElements();
|
|
|
|
this.reportProgress(60, 'Preparing ambient effects');
|
|
|
|
// Set up ambient effect animations
|
|
this.setupAmbientEffects();
|
|
|
|
this.reportProgress(100, 'UI Effects ready');
|
|
|
|
// Use the parent's dispatchEvent method
|
|
this.dispatchEvent('ui:effects:ready', {});
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error initializing UI Effects:', error);
|
|
this.changeState('ERROR');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
setupEffectElements() {
|
|
console.log('UIEffects: Setting up effect elements');
|
|
// Create overlay for effects if it doesn't exist
|
|
this.effectsOverlay = document.getElementById('effects-overlay') || this.createEffectsOverlay();
|
|
|
|
// Create candle effect element
|
|
this.candleEffectElement = document.getElementById('candle-effect') || this.createCandleEffect();
|
|
|
|
// Ensure lighting element exists
|
|
this.lightingElement = document.getElementById('lighting') || this.createLightingElement();
|
|
}
|
|
|
|
createEffectsOverlay() {
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'effects-overlay';
|
|
overlay.className = 'effects-overlay';
|
|
document.body.appendChild(overlay);
|
|
return overlay;
|
|
}
|
|
|
|
createCandleEffect() {
|
|
const candleEffect = document.createElement('div');
|
|
candleEffect.id = 'candle-effect';
|
|
candleEffect.className = 'candle-effect';
|
|
if (this.effectsOverlay) {
|
|
this.effectsOverlay.appendChild(candleEffect);
|
|
} else {
|
|
document.body.appendChild(candleEffect);
|
|
}
|
|
return candleEffect;
|
|
}
|
|
|
|
createLightingElement() {
|
|
const lighting = document.createElement('div');
|
|
lighting.id = 'lighting';
|
|
document.body.appendChild(lighting);
|
|
return lighting;
|
|
}
|
|
|
|
setupAmbientEffects() {
|
|
// Initialize candle flicker effect
|
|
if (this.candleEffectElement && this.config.candleFlicker.enabled !== false) {
|
|
this.setupCandleFlickerEffect();
|
|
}
|
|
}
|
|
|
|
setupCandleFlickerEffect() {
|
|
// Store the animation frame ID for later cancellation
|
|
this.candleAnimationId = null;
|
|
}
|
|
|
|
updateCandleEffect() {
|
|
if (!this.candleEffectElement || !this.ambientEffectsActive) return;
|
|
|
|
const { intensity, speed } = this.config.candleFlicker;
|
|
|
|
// Create subtle random flickering effect
|
|
const flickerAmount = Math.random() * intensity;
|
|
const opacity = 0.2 + flickerAmount * 0.2;
|
|
|
|
this.candleEffectElement.style.opacity = opacity;
|
|
|
|
// Schedule next update
|
|
this.candleAnimationId = requestAnimationFrame(this.updateCandleEffect);
|
|
}
|
|
|
|
// Public methods
|
|
|
|
startAmbientEffects() {
|
|
if (this.ambientEffectsActive) return;
|
|
|
|
this.ambientEffectsActive = true;
|
|
|
|
// Start candle flicker
|
|
if (this.candleEffectElement) {
|
|
this.updateCandleEffect();
|
|
}
|
|
|
|
// Apply lighting animation
|
|
if (this.lightingElement) {
|
|
this.lightingElement.style.animation = 'gradient-animation-shrink 2s infinite alternate';
|
|
}
|
|
}
|
|
|
|
stopAmbientEffects() {
|
|
this.ambientEffectsActive = false;
|
|
|
|
// Stop candle flicker
|
|
if (this.candleAnimationId) {
|
|
cancelAnimationFrame(this.candleAnimationId);
|
|
this.candleAnimationId = null;
|
|
}
|
|
|
|
// Stop lighting animation
|
|
if (this.lightingElement) {
|
|
this.lightingElement.style.animation = '';
|
|
}
|
|
}
|
|
|
|
applyEffect(effectName, options = {}) {
|
|
switch (effectName) {
|
|
case 'shake':
|
|
return this.applyShakeEffect(options);
|
|
case 'flash':
|
|
return this.applyFlashEffect(options);
|
|
case 'emphasis':
|
|
return this.applyTextEmphasis(options.text, options);
|
|
default:
|
|
console.warn(`Unknown effect: ${effectName}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
applyShakeEffect(options = {}) {
|
|
const target = options.target || document.getElementById('book');
|
|
if (!target) return null;
|
|
|
|
const intensity = options.intensity || 'medium';
|
|
const duration = options.duration || 500;
|
|
|
|
// Store original styles
|
|
const originalTransition = target.style.transition;
|
|
const originalTransform = target.style.transform;
|
|
|
|
// Apply the shake animation
|
|
target.style.transition = `transform ${duration}ms ease`;
|
|
target.style.transform = 'translate(0, 0)';
|
|
target.style.animation = `shake ${duration}ms 1`;
|
|
|
|
// Return to normal after animation
|
|
const effectId = setTimeout(() => {
|
|
target.style.transition = originalTransition;
|
|
target.style.transform = originalTransform;
|
|
target.style.animation = '';
|
|
this.activeEffects.delete(effectId);
|
|
}, duration);
|
|
|
|
this.activeEffects.set(effectId, { name: 'shake', target });
|
|
return effectId;
|
|
}
|
|
|
|
applyFlashEffect(options = {}) {
|
|
const color = options.color || 'white';
|
|
const duration = options.duration || 300;
|
|
|
|
// Create flash overlay if not exists
|
|
let flashOverlay = document.getElementById('flash-overlay');
|
|
if (!flashOverlay) {
|
|
flashOverlay = document.createElement('div');
|
|
flashOverlay.id = 'flash-overlay';
|
|
flashOverlay.style.position = 'fixed';
|
|
flashOverlay.style.top = '0';
|
|
flashOverlay.style.left = '0';
|
|
flashOverlay.style.width = '100%';
|
|
flashOverlay.style.height = '100%';
|
|
flashOverlay.style.pointerEvents = 'none';
|
|
flashOverlay.style.zIndex = '9999';
|
|
flashOverlay.style.opacity = '0';
|
|
flashOverlay.style.transition = `opacity ${duration / 2}ms ease-in-out`;
|
|
document.body.appendChild(flashOverlay);
|
|
}
|
|
|
|
// Set color and make visible
|
|
flashOverlay.style.backgroundColor = color;
|
|
|
|
// Start animation
|
|
setTimeout(() => { flashOverlay.style.opacity = '0.8'; }, 10);
|
|
|
|
const effectId = setTimeout(() => {
|
|
flashOverlay.style.opacity = '0';
|
|
this.activeEffects.delete(effectId);
|
|
|
|
// Remove element after fade out
|
|
setTimeout(() => {
|
|
if (flashOverlay.parentNode) {
|
|
flashOverlay.parentNode.removeChild(flashOverlay);
|
|
}
|
|
}, duration / 2);
|
|
}, duration / 2);
|
|
|
|
this.activeEffects.set(effectId, { name: 'flash' });
|
|
return effectId;
|
|
}
|
|
|
|
applyTextEmphasis(text, options = {}) {
|
|
// Use existing display handler to show emphasized text
|
|
const displayHandler = moduleRegistry.getModule('ui-display-handler');
|
|
if (!displayHandler) return null;
|
|
|
|
const style = {
|
|
fontWeight: 'bold',
|
|
color: options.color || '#990000',
|
|
fontSize: options.size || '1.2em'
|
|
};
|
|
|
|
return displayHandler.displayText(text, { style, speak: true });
|
|
}
|
|
|
|
processCommand(command) {
|
|
switch (command.action) {
|
|
case 'apply':
|
|
return this.applyEffect(command.effect, command.options);
|
|
case 'cancel':
|
|
const effectId = command.effectId;
|
|
if (this.activeEffects.has(effectId)) {
|
|
clearTimeout(effectId);
|
|
this.activeEffects.delete(effectId);
|
|
}
|
|
break;
|
|
case 'ambient':
|
|
if (command.state === 'start') {
|
|
this.startAmbientEffects();
|
|
} else if (command.state === 'stop') {
|
|
this.stopAmbientEffects();
|
|
}
|
|
break;
|
|
default:
|
|
console.warn(`Unknown effect command: ${command.action}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the singleton instance
|
|
const uiEffects = new UIEffects();
|
|
|
|
// Register with the module registry
|
|
moduleRegistry.register(uiEffects);
|
|
|
|
// Export the module
|
|
export { uiEffects as UIEffects };
|
|
|
|
// Keep a reference in window for loader system
|
|
console.log('UIEffects: Registering with window');
|
|
window.UIEffects = uiEffects;
|