ritual.sh/static/js/button-generator/effects/spotlight.js
2026-01-09 13:20:06 +00:00

199 lines
5.6 KiB
JavaScript

/**
* EXAMPLE EFFECT
*
* This is a template for creating new effects.
* Copy this file and modify it to create your own custom effects.
*
* This example creates a "spotlight" effect that highlights a circular area
* and darkens the rest of the button.
*/
import { ButtonEffect } from "../effect-base.js";
/**
* Spotlight Effect
* Creates a moving circular spotlight that highlights different areas
*/
export class SpotlightEffect extends ButtonEffect {
constructor() {
super({
// Unique ID for this effect (used in control IDs)
id: "spotlight",
// Display name shown in UI
name: "Spotlight",
// Effect type determines render order category
// Options: 'background', 'border', 'text', 'text2', 'general'
type: "general",
// Category for organizing effects in UI
category: "Visual Effects",
// Render order within type (lower = earlier)
// 1-9: backgrounds, 10-19: borders, 20-29: transforms,
// 30-49: text, 50-79: overlays, 80-99: post-processing
renderOrder: 60,
});
}
/**
* Define UI controls for this effect
* These controls will be automatically bound to the generator
*/
defineControls() {
return [
// Main enable/disable checkbox
{
id: "animate-spotlight",
type: "checkbox",
label: "Spotlight Effect",
defaultValue: false,
description: "Moving circular spotlight",
},
// Spotlight size control
{
id: "spotlight-size",
type: "range",
label: "Spotlight Size",
defaultValue: 20,
min: 10,
max: 50,
step: 1,
showWhen: "animate-spotlight", // Only show when checkbox is enabled
description: "Radius of the spotlight",
},
// Darkness of the vignette
{
id: "spotlight-darkness",
type: "range",
label: "Darkness",
defaultValue: 1,
min: 0,
max: 1,
step: 0.1,
showWhen: "animate-spotlight",
description: "How dark the non-spotlight area should be",
},
// Speed of movement
{
id: "spotlight-speed",
type: "range",
label: "Movement Speed",
defaultValue: 1,
min: 1,
max: 3,
step: 1,
showWhen: "animate-spotlight",
description: "Speed of spotlight movement",
},
];
}
/**
* Determine if this effect should be applied
* @param {Object} controlValues - Current values of all controls
* @returns {boolean}
*/
isEnabled(controlValues) {
return controlValues["animate-spotlight"] === true;
}
/**
* Apply the effect to the canvas
*
* @param {CanvasRenderingContext2D} context - Canvas 2D rendering context
* @param {Object} controlValues - Current values of all controls
* @param {AnimationState|null} animState - Animation state (null for static render)
* @param {Object} renderData - Render information: { width, height, centerX, centerY }
*/
apply(context, controlValues, animState, renderData) {
// Skip if no animation (spotlight needs movement)
if (!animState) return;
// Get control values
const size = controlValues["spotlight-size"] || 20;
const darkness = controlValues["spotlight-darkness"] || 0.5;
const speed = controlValues["spotlight-speed"] || 1;
// Calculate spotlight position
// Move in a circular pattern using animation phase
const phase = animState.getPhase(speed);
const spotX = renderData.centerX + Math.cos(phase) * 20;
const spotY = renderData.centerY + Math.sin(phase) * 10;
// Create radial gradient for spotlight effect
const gradient = context.createRadialGradient(
spotX,
spotY,
0, // Inner circle (center of spotlight)
spotX,
spotY,
size, // Outer circle (edge of spotlight)
);
// Center is transparent (spotlight is bright)
gradient.addColorStop(0, `rgba(0, 0, 0, 0)`);
// Edge fades to dark
gradient.addColorStop(0.5, `rgba(0, 0, 0, ${darkness * 0.3})`);
gradient.addColorStop(1, `rgba(0, 0, 0, ${darkness})`);
// Apply the gradient as an overlay
context.fillStyle = gradient;
context.fillRect(0, 0, renderData.width, renderData.height);
// Optional: Add a bright center dot
context.fillStyle = "rgba(255, 255, 255, 0.3)";
context.beginPath();
context.arc(spotX, spotY, 2, 0, Math.PI * 2);
context.fill();
}
/**
* Optional: Add helper methods for your effect
*/
calculateSpotlightPath(progress, width, height) {
// Example helper method
return {
x: width * progress,
y: height / 2,
};
}
}
/**
* Registration function
* This is called to add the effect to the generator
*
* @param {ButtonGenerator} generator - The button generator instance
*/
export function register(generator) {
generator.registerEffect(new SpotlightEffect());
}
/**
* USAGE:
*
* 1. Copy this file to a new name (e.g., my-effect.js)
* 2. Modify the class name, id, and effect logic
* 3. Import in main.js:
* import * as myEffect from './effects/my-effect.js';
* 4. Register in setupApp():
* myEffect.register(generator);
* 5. Add HTML controls with matching IDs
*/
/**
* TIPS:
*
* - Use animState.progress for linear animations (0 to 1)
* - Use animState.getPhase(speed) for periodic animations (0 to 2π)
* - Use Math.sin/cos for smooth periodic motion
* - Check if (!animState) at the start if your effect requires animation
* - The context is automatically saved/restored, so feel free to transform
* - Use renderData for canvas dimensions and center point
* - Look at existing effects for more examples
*/