import { ButtonEffect } from "../effect-base.js"; /** * Standard text rendering effect * Renders static text (when no animations are active) */ export class StandardTextEffect extends ButtonEffect { constructor(textLineNumber = 1) { const suffix = textLineNumber === 1 ? "" : "2"; super({ id: `text-standard${suffix}`, name: `Standard Text ${textLineNumber}`, type: textLineNumber === 1 ? "text" : "text2", category: textLineNumber === 1 ? "Text Line 1" : "Text Line 2", renderOrder: 20, // After animations textLineNumber: textLineNumber, // Pass through config so defineControls can access it }); this.textLineNumber = textLineNumber; } defineControls() { // Access from config since this is called before constructor completes const textLineNumber = this.textLineNumber || this.config?.textLineNumber || 1; const suffix = textLineNumber === 1 ? "" : "2"; return [ { id: `button-text${suffix}`, type: "text", label: `Text Line ${textLineNumber}`, defaultValue: textLineNumber === 1 ? "RITUAL.SH" : "", }, { id: `font-size${suffix}`, type: "range", label: "Font Size", min: 6, max: 24, defaultValue: textLineNumber === 1 ? 14 : 12, }, { id: `text${suffix}-x`, type: "range", label: "Horizontal Position", min: 0, max: 100, defaultValue: 50, description: "Percentage from left", }, { id: `text${suffix}-y`, type: "range", label: "Vertical Position", min: 0, max: 100, defaultValue: textLineNumber === 1 ? 35 : 65, description: "Percentage from top", }, { id: `text${suffix}-color-type`, type: "select", label: "Color Type", defaultValue: "solid", options: [ { value: "solid", label: "Solid Color" }, { value: "gradient", label: "Gradient" }, ], }, { id: `text${suffix}-color`, type: "color", label: "Text Color", defaultValue: "#ffffff", showWhen: `text${suffix}-color-type`, }, { id: `text${suffix}-gradient-color1`, type: "color", label: "Gradient Color 1", defaultValue: "#ffffff", showWhen: `text${suffix}-color-type`, }, { id: `text${suffix}-gradient-color2`, type: "color", label: "Gradient Color 2", defaultValue: "#00ffff", showWhen: `text${suffix}-color-type`, }, { id: `text${suffix}-gradient-angle`, type: "range", label: "Gradient Angle", min: 0, max: 360, defaultValue: 0, showWhen: `text${suffix}-color-type`, }, { id: `text${suffix}-outline`, type: "checkbox", label: "Outline", defaultValue: false, }, { id: `outline${suffix}-color`, type: "color", label: "Outline Color", defaultValue: "#000000", showWhen: `text${suffix}-outline`, }, { id: `font-family${suffix}`, type: "select", label: "Font", defaultValue: "Lato", options: [ { value: "Lato", label: "Lato" }, { value: "Roboto", label: "Roboto" }, { value: "Open Sans", label: "Open Sans" }, { value: "Montserrat", label: "Montserrat" }, { value: "Oswald", label: "Oswald" }, { value: "Bebas Neue", label: "Bebas Neue" }, { value: "Roboto Mono", label: "Roboto Mono" }, { value: "VT323", label: "VT323" }, { value: "Press Start 2P", label: "Press Start 2P" }, { value: "DSEG7-Classic", label: "DSEG7" }, ], }, { id: `font-bold${suffix}`, type: "checkbox", label: "Bold", defaultValue: false, }, { id: `font-italic${suffix}`, type: "checkbox", label: "Italic", defaultValue: false, }, ]; } isEnabled(controlValues) { const suffix = this.textLineNumber === 1 ? "" : "2"; const text = controlValues[`button-text${suffix}`]; // Only render if text exists and no animations are active on this text const waveActive = controlValues[`animate-text-wave${suffix}`]; const rainbowActive = controlValues[`animate-text-rainbow${suffix}`]; const spinActive = controlValues[`animate-text-spin${suffix}`]; return text && text.trim() !== "" && !waveActive && !rainbowActive && !spinActive; } apply(context, controlValues, animState, renderData) { const suffix = this.textLineNumber === 1 ? "" : "2"; const text = controlValues[`button-text${suffix}`]; if (!text) return; const fontSize = controlValues[`font-size${suffix}`] || 12; const fontWeight = controlValues[`font-bold${suffix}`] ? "bold" : "normal"; const fontStyle = controlValues[`font-italic${suffix}`] ? "italic" : "normal"; const fontFamily = controlValues[`font-family${suffix}`] || "Arial"; const x = (controlValues[`text${suffix}-x`] / 100) * renderData.width; const y = (controlValues[`text${suffix}-y`] / 100) * renderData.height; // Set font context.font = `${fontStyle} ${fontWeight} ${fontSize}px "${fontFamily}"`; context.textAlign = "center"; context.textBaseline = "middle"; // Get colors const colors = this.getTextColors( context, controlValues, text, x, y, fontSize, ); // Draw outline if enabled if (controlValues[`text${suffix}-outline`]) { context.strokeStyle = colors.strokeStyle; context.lineWidth = 2; context.strokeText(text, x, y); } // Draw text context.fillStyle = colors.fillStyle; context.fillText(text, x, y); } /** * Get text colors (solid or gradient) */ getTextColors(context, controlValues, text, x, y, fontSize) { const suffix = this.textLineNumber === 1 ? "" : "2"; const colorType = controlValues[`text${suffix}-color-type`] || "solid"; let fillStyle, strokeStyle; if (colorType === "solid") { fillStyle = controlValues[`text${suffix}-color`] || "#ffffff"; strokeStyle = controlValues[`outline${suffix}-color`] || "#000000"; } else { // Gradient const angle = (controlValues[`text${suffix}-gradient-angle`] || 0) * (Math.PI / 180); const textWidth = context.measureText(text).width; const x1 = x - textWidth / 2 + (Math.cos(angle) * textWidth) / 2; const y1 = y - fontSize / 2 + (Math.sin(angle) * fontSize) / 2; const x2 = x + textWidth / 2 - (Math.cos(angle) * textWidth) / 2; const y2 = y + fontSize / 2 - (Math.sin(angle) * fontSize) / 2; const gradient = context.createLinearGradient(x1, y1, x2, y2); gradient.addColorStop( 0, controlValues[`text${suffix}-gradient-color1`] || "#ffffff", ); gradient.addColorStop( 1, controlValues[`text${suffix}-gradient-color2`] || "#00ffff", ); fillStyle = gradient; strokeStyle = controlValues[`outline${suffix}-color`] || "#000000"; } return { fillStyle, strokeStyle }; } } // Auto-register effect export function register(generator) { generator.registerEffect(new StandardTextEffect(1)); generator.registerEffect(new StandardTextEffect(2)); }