Skip to content

Commit 3fef4ce

Browse files
Not tested
1 parent 2b2b954 commit 3fef4ce

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

mstr-generator.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* Generates an SVG animation that displays a sequence of words,
3+
* with the letters 'm', 's', 't', 'r' (in that order) highlighted in bold.
4+
*
5+
* @param {string[]} wordList - Array of words to animate (must contain 'm','s','t','r' in order)
6+
* @param {Object} options - Optional configuration parameters
7+
* @param {number} options.width - SVG width (default: 500)
8+
* @param {number} options.height - SVG height (default: 150)
9+
* @param {number} options.fontSize - Font size in pixels (default: 40)
10+
* @param {number} options.duration - Duration per word in seconds (default: 2)
11+
* @param {string} options.fontFamily - Font family (default: "Times New Roman, serif")
12+
* @returns {string} - SVG markup as a string
13+
*/
14+
function generateMstrAnimation(wordList, options = {}) {
15+
// Default options
16+
const config = {
17+
width: options.width || 500,
18+
height: options.height || 150,
19+
fontSize: options.fontSize || 40,
20+
duration: options.duration || 2,
21+
fontFamily: options.fontFamily || "Times New Roman, serif"
22+
};
23+
24+
// Validate words contain 'm', 's', 't', 'r' in that order
25+
const validWords = wordList.filter(word => {
26+
const lowerWord = word.toLowerCase();
27+
28+
// Check if word contains 'm', 's', 't', 'r' in that order
29+
let mIndex = lowerWord.indexOf('m');
30+
if (mIndex === -1) return false;
31+
32+
let sIndex = lowerWord.indexOf('s', mIndex + 1);
33+
if (sIndex === -1) return false;
34+
35+
let tIndex = lowerWord.indexOf('t', sIndex + 1);
36+
if (tIndex === -1) return false;
37+
38+
let rIndex = lowerWord.indexOf('r', tIndex + 1);
39+
if (rIndex === -1) return false;
40+
41+
return true;
42+
});
43+
44+
// If no valid words, return a message
45+
if (validWords.length === 0) {
46+
return `<svg viewBox="0 0 ${config.width} ${config.height}" xmlns="http://www.w3.org/2000/svg">
47+
<text x="${config.width/2}" y="${config.height/2}" text-anchor="middle" font-family="${config.fontFamily}" font-size="${config.fontSize/2}">
48+
No valid words containing 'm', 's', 't', 'r' in that order
49+
</text>
50+
</svg>`;
51+
}
52+
53+
// Calculate animation timing
54+
const totalDuration = config.duration * validWords.length;
55+
const singleWordPercentage = 100 / validWords.length;
56+
const fadePercentage = singleWordPercentage * 0.2; // 20% of the word's time for fading
57+
58+
// Generate SVG with styles and animations
59+
let svg = `<svg viewBox="0 0 ${config.width} ${config.height}" xmlns="http://www.w3.org/2000/svg">
60+
<style>
61+
@font-face {
62+
font-family: 'Times New Roman';
63+
font-style: normal;
64+
font-weight: normal;
65+
}
66+
67+
text {
68+
font-family: ${config.fontFamily};
69+
font-size: ${config.fontSize}px;
70+
dominant-baseline: middle;
71+
text-anchor: middle;
72+
}
73+
74+
.bold {
75+
font-weight: bold;
76+
}
77+
78+
.regular {
79+
font-weight: normal;
80+
}
81+
82+
@keyframes wordAnimation {
83+
0%, ${singleWordPercentage - fadePercentage}% {
84+
opacity: 1;
85+
}
86+
${singleWordPercentage}%, 100% {
87+
opacity: 0;
88+
}
89+
}
90+
91+
${validWords.map((_, i) => `
92+
#word${i+1} {
93+
opacity: 0;
94+
animation: wordAnimation ${totalDuration}s infinite;
95+
animation-delay: ${i * config.duration}s;
96+
}`).join('\n')}
97+
</style>
98+
99+
<g id="animated-text">`;
100+
101+
// Add each word with properly highlighted letters
102+
validWords.forEach((word, index) => {
103+
const lowerWord = word.toLowerCase();
104+
105+
// Find indices of m, s, t, r
106+
let mIndex = lowerWord.indexOf('m');
107+
let sIndex = lowerWord.indexOf('s', mIndex + 1);
108+
let tIndex = lowerWord.indexOf('t', sIndex + 1);
109+
let rIndex = lowerWord.indexOf('r', tIndex + 1);
110+
111+
// Split the word into segments
112+
const segments = [
113+
{ text: word.substring(0, mIndex), bold: false },
114+
{ text: word.charAt(mIndex), bold: true },
115+
{ text: word.substring(mIndex + 1, sIndex), bold: false },
116+
{ text: word.charAt(sIndex), bold: true },
117+
{ text: word.substring(sIndex + 1, tIndex), bold: false },
118+
{ text: word.charAt(tIndex), bold: true },
119+
{ text: word.substring(tIndex + 1, rIndex), bold: false },
120+
{ text: word.charAt(rIndex), bold: true },
121+
{ text: word.substring(rIndex + 1), bold: false }
122+
];
123+
124+
// Filter out empty segments
125+
const filteredSegments = segments.filter(segment => segment.text.length > 0);
126+
127+
// Generate text element with spans
128+
svg += `
129+
<text id="word${index+1}" x="${config.width/2}" y="${config.height/2}">
130+
${filteredSegments.map(segment =>
131+
`<tspan class="${segment.bold ? 'bold' : 'regular'}">${segment.text}</tspan>`
132+
).join('')}
133+
</text>`;
134+
});
135+
136+
// Close SVG
137+
svg += `
138+
</g>
139+
</svg>`;
140+
141+
return svg;
142+
}
143+
144+
// Example usage
145+
// const words = ["mstr", "hamster", "mester", "monstrum", "mønster", "master", "mystery"];
146+
// const animationSvg = generateMstrAnimation(words, {
147+
// duration: 2,
148+
// width: 400,
149+
// height: 100
150+
// });
151+
//
152+
// console.log(animationSvg);

0 commit comments

Comments
 (0)