ritual.sh/static/js/button-generator/effects/hologram.js

170 lines
5.6 KiB
JavaScript

import { ButtonEffect } from '../effect-base.js';
/**
* Hologram effect
* Creates a futuristic holographic appearance with glitches and scan lines
*/
export class HologramEffect extends ButtonEffect {
constructor() {
super({
id: 'hologram',
name: 'Hologram',
type: 'general',
category: 'Visual Effects',
renderOrder: 88 // Near the end, after most other effects
});
}
defineControls() {
return [
{
id: 'animate-hologram',
type: 'checkbox',
label: 'Hologram Effect',
defaultValue: false,
description: 'Futuristic holographic appearance'
},
{
id: 'hologram-intensity',
type: 'range',
label: 'Effect Intensity',
defaultValue: 50,
min: 10,
max: 100,
step: 5,
showWhen: 'animate-hologram',
description: 'Strength of hologram effect'
},
{
id: 'hologram-glitch-freq',
type: 'range',
label: 'Glitch Frequency',
defaultValue: 30,
min: 0,
max: 100,
step: 10,
showWhen: 'animate-hologram',
description: 'How often glitches occur'
},
{
id: 'hologram-color',
type: 'color',
label: 'Hologram Tint',
defaultValue: '#00ffff',
showWhen: 'animate-hologram',
description: 'Color tint for hologram effect'
}
];
}
isEnabled(controlValues) {
return controlValues['animate-hologram'] === true;
}
apply(context, controlValues, animState, renderData) {
if (!animState) return;
const intensity = (controlValues['hologram-intensity'] || 50) / 100;
const glitchFreq = (controlValues['hologram-glitch-freq'] || 30) / 100;
const color = controlValues['hologram-color'] || '#00ffff';
// Get current canvas content
const imageData = context.getImageData(0, 0, renderData.width, renderData.height);
const data = imageData.data;
// Parse hologram color for tinting
const hexColor = color.replace('#', '');
const r = parseInt(hexColor.substr(0, 2), 16);
const g = parseInt(hexColor.substr(2, 2), 16);
const b = parseInt(hexColor.substr(4, 2), 16);
// Apply holographic tint
for (let i = 0; i < data.length; i += 4) {
// Mix with hologram color
data[i] = data[i] * (1 - intensity * 0.3) + r * intensity * 0.3; // Red
data[i + 1] = data[i + 1] * (1 - intensity * 0.5) + g * intensity * 0.5; // Green (more cyan)
data[i + 2] = data[i + 2] * (1 - intensity * 0.5) + b * intensity * 0.5; // Blue (more cyan)
}
context.putImageData(imageData, 0, 0);
// Add horizontal scan lines
context.globalAlpha = 0.05 * intensity;
context.fillStyle = '#000000';
for (let y = 0; y < renderData.height; y += 2) {
context.fillRect(0, y, renderData.width, 1);
}
context.globalAlpha = 1.0;
// Add moving highlight scan line
const scanY = (animState.progress * renderData.height) % renderData.height;
const gradient = context.createLinearGradient(0, scanY - 3, 0, scanY + 3);
gradient.addColorStop(0, 'rgba(0, 255, 255, 0)');
gradient.addColorStop(0.5, `rgba(${r}, ${g}, ${b}, ${0.3 * intensity})`);
gradient.addColorStop(1, 'rgba(0, 255, 255, 0)');
context.fillStyle = gradient;
context.fillRect(0, scanY - 3, renderData.width, 6);
// Random glitches
if (Math.random() < glitchFreq * 0.1) {
const glitchY = Math.floor(Math.random() * renderData.height);
const glitchHeight = Math.floor(2 + Math.random() * 4);
const offset = (Math.random() - 0.5) * 6 * intensity;
const sliceData = context.getImageData(0, glitchY, renderData.width, glitchHeight);
context.putImageData(sliceData, offset, glitchY);
}
// Add chromatic aberration on edges
if (intensity > 0.3) {
const originalImage = context.getImageData(0, 0, renderData.width, renderData.height);
const aberration = 2 * intensity;
// Slight red shift right
const redShift = context.getImageData(0, 0, renderData.width, renderData.height);
for (let i = 0; i < redShift.data.length; i += 4) {
const pixelIndex = i / 4;
const x = pixelIndex % renderData.width;
if (x < 3 || x > renderData.width - 3) {
const sourceIndex = ((pixelIndex + Math.floor(aberration)) * 4);
if (sourceIndex < originalImage.data.length) {
redShift.data[i] = originalImage.data[sourceIndex];
}
}
}
// Slight blue shift left
const blueShift = context.getImageData(0, 0, renderData.width, renderData.height);
for (let i = 0; i < blueShift.data.length; i += 4) {
const pixelIndex = i / 4;
const x = pixelIndex % renderData.width;
if (x < 3 || x > renderData.width - 3) {
const sourceIndex = ((pixelIndex - Math.floor(aberration)) * 4);
if (sourceIndex >= 0 && sourceIndex < originalImage.data.length) {
blueShift.data[i + 2] = originalImage.data[sourceIndex + 2];
}
}
}
context.putImageData(redShift, 0, 0);
context.globalCompositeOperation = 'screen';
context.globalAlpha = 0.3;
context.putImageData(blueShift, 0, 0);
context.globalCompositeOperation = 'source-over';
context.globalAlpha = 1.0;
}
// Add flickering effect
if (Math.random() < 0.05) {
context.globalAlpha = 0.9 + Math.random() * 0.1;
context.fillStyle = 'rgba(255, 255, 255, 0.05)';
context.fillRect(0, 0, renderData.width, renderData.height);
context.globalAlpha = 1.0;
}
}
}
// Auto-register effect
export function register(generator) {
generator.registerEffect(new HologramEffect());
}