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

243 lines
7.3 KiB
JavaScript

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));
}