@whykusanagi/corrupted-theme 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Corruption Phrase Library
3
+ *
4
+ * Provides phrase sets for buffer corruption effects (Pattern 2: Phrase Flickering).
5
+ * Phrases simulate a neural network decoding corrupted data buffer.
6
+ *
7
+ * @module corruption-phrases
8
+ * @version 1.0.0
9
+ * @author whykusanagi
10
+ * @license MIT
11
+ *
12
+ * @example SFW Mode (Default)
13
+ * ```javascript
14
+ * import { getRandomPhrase, SFW_PHRASES } from './corruption-phrases.js';
15
+ *
16
+ * // Get random SFW phrase (default)
17
+ * const phrase = getRandomPhrase();
18
+ *
19
+ * // Or sample from SFW array directly
20
+ * const phrase = SFW_PHRASES[Math.floor(Math.random() * SFW_PHRASES.length)];
21
+ * ```
22
+ *
23
+ * @example NSFW Mode (Explicit Opt-in)
24
+ * ```javascript
25
+ * import { getRandomPhrase, NSFW_PHRASES } from './corruption-phrases.js';
26
+ *
27
+ * // Get random NSFW phrase (explicit opt-in)
28
+ * const phrase = getRandomPhrase(true);
29
+ *
30
+ * // Or sample from NSFW array directly
31
+ * const phrase = NSFW_PHRASES[Math.floor(Math.random() * NSFW_PHRASES.length)];
32
+ * ```
33
+ *
34
+ * @see CORRUPTED_THEME_SPEC.md - Content Classification: SFW vs NSFW
35
+ * @see TERMINOLOGY_CLARIFICATION.md - Buffer Corruption vs Character Corruption
36
+ */
37
+
38
+ /**
39
+ * SFW Phrase Set (Safe For Work) - DEFAULT
40
+ *
41
+ * Content: Playful, cute, teasing, atmospheric corruption themes
42
+ * Tone: Anime-style cute/flirty, cyberpunk atmospheric
43
+ * Safe for: General audiences, streaming, professional projects
44
+ *
45
+ * Categories:
46
+ * - Cute/Playful (Japanese, Romaji, English)
47
+ * - Flirty/Teasing (embarrassed, shy, playful)
48
+ * - Atmospheric/Corruption (darkness, abyss, neural themes)
49
+ * - System Messages (cyberpunk technical aesthetic)
50
+ *
51
+ * @type {string[]}
52
+ * @constant
53
+ */
54
+ export const SFW_PHRASES = [
55
+ // === Cute/Playful - Japanese ===
56
+ 'ニャー', // Nyaa (cat sound)
57
+ 'かわいい', // Kawaii (cute)
58
+ 'きゃー', // Kyaa (excited squeal)
59
+ 'あはは', // Ahaha (laughing)
60
+ 'うふふ', // Ufufu (giggle)
61
+ 'やだ', // Yada (no way!)
62
+ 'ばか', // Baka (idiot/dummy)
63
+ 'デレデレ', // Deredere (lovestruck)
64
+ 'えへへ', // Ehehe (shy giggle)
65
+ 'わぁ', // Waa (wow/surprise)
66
+
67
+ // === Cute/Playful - Romaji ===
68
+ 'nyaa~',
69
+ 'ara ara~',
70
+ 'fufufu~',
71
+ 'kyaa~',
72
+ 'baka~',
73
+ 'ehehe~',
74
+
75
+ // === Cute/Playful - Internet Culture ===
76
+ '<3',
77
+ 'uwu',
78
+ 'owo',
79
+ '>w<',
80
+ '^w^',
81
+ '♡',
82
+
83
+ // === Flirty/Teasing - Japanese ===
84
+ 'もう...見ないでよ...', // Mou... minaide yo... (Don't look at me...)
85
+ 'そんな目で見ないで... ♡', // Sonna me de minaide... (Don't look at me like that...)
86
+ 'ちょっと...恥ずかしい...', // Chotto... hazukashii... (This is embarrassing...)
87
+ 'あなたって...意地悪ね...', // Anata tte... ijiwaru ne... (You're such a tease...)
88
+ 'ドキドキしちゃう...', // Dokidoki shichau... (My heart racing...)
89
+ 'なんか...照れる...', // Nanka... tereru... (I'm getting flustered...)
90
+ '気になっちゃう...', // Ki ni nacchau... (I can't stop thinking about it...)
91
+
92
+ // === Flirty/Teasing - English ===
93
+ "Don't... look at me like that...",
94
+ "You're making me... flustered... ♡",
95
+ "This is... embarrassing...",
96
+ "You're such... a tease...",
97
+ "My heart... racing...",
98
+ "I can't... stop thinking about it...",
99
+
100
+ // === Atmospheric/Corruption - Japanese ===
101
+ '闇が...私を呼んでいる...', // Yami ga... watashi wo yonde iru... (The darkness calls to me...)
102
+ '深淵に...落ちていく...', // Shin'en ni... ochite iku... (Falling into the abyss...)
103
+ 'もう逃げない...', // Mou nigenai... (Won't run anymore...)
104
+ '私...アビスの一部に...', // Watashi... abisu no ichibu ni... (I become part of the abyss...)
105
+ '光が...遠ざかる...', // Hikari ga... toozakaru... (The light fades away...)
106
+ '境界が...曖昧になる...', // Kyoukai ga... aimai ni naru... (The boundaries blur...)
107
+ '現実が...溶けていく...', // Genjitsu ga... tokete iku... (Reality melting away...)
108
+
109
+ // === Atmospheric/Corruption - English ===
110
+ 'The darkness... calls to me...',
111
+ 'Falling... into the abyss...',
112
+ "Won't run anymore...",
113
+ 'I become... part of the abyss...',
114
+ 'The light... fading away...',
115
+ 'Boundaries... blurring...',
116
+ 'Reality... melting away...',
117
+ 'Consumed... by corruption...',
118
+ 'Descending... into the depths...',
119
+ 'Signal... degrading...',
120
+
121
+ // === System Messages - Cyberpunk/Technical ===
122
+ 'Neural corruption detected...',
123
+ 'System breach imminent...',
124
+ 'Loading data streams...',
125
+ 'Reality.exe has stopped responding...',
126
+ 'Decrypting protocols...',
127
+ 'Memory buffer overflow...',
128
+ 'Cognitive firewall failing...',
129
+ 'Parsing corrupted data...',
130
+ 'Synaptic connection unstable...',
131
+ 'Consciousness fragmentation detected...',
132
+ 'Ego.dll corrupted...',
133
+ 'Identity matrix degrading...',
134
+ 'Self-awareness protocols compromised...',
135
+ 'Neural pathways rewiring...',
136
+ 'Semantic data loss: 47%...',
137
+ ];
138
+
139
+ /**
140
+ * NSFW Phrase Set (Not Safe For Work) - OPT-IN ONLY
141
+ *
142
+ * ⚠️ 18+ Content Warning
143
+ *
144
+ * Content: Explicit intimate/sexual phrases, loss of control themes
145
+ * Tone: Explicit, mature, sexual degradation
146
+ * Safe for: 18+ projects ONLY, mature content streams, private use
147
+ *
148
+ * NOT suitable for:
149
+ * - Professional/corporate projects
150
+ * - Public streams without 18+ rating
151
+ * - Educational contexts
152
+ * - All-ages content
153
+ *
154
+ * Categories:
155
+ * - Explicit Intimate (Japanese, Romaji, English)
156
+ * - Explicit Words (hentai, ecchi)
157
+ * - Loss of Control (breaking, melting, surrender)
158
+ * - Explicit System Messages (pleasure protocols, moral subroutines)
159
+ *
160
+ * @type {string[]}
161
+ * @constant
162
+ */
163
+ export const NSFW_PHRASES = [
164
+ // === Explicit Intimate - Japanese ===
165
+ 'ずっと...してほしい... ♥', // Zutto... shite hoshii... (Please keep doing it...)
166
+ '壊れちゃう...ああ...もうダメ...', // Kowarechau... aa... mou dame... (I'm breaking... can't anymore...)
167
+ '好きにして...お願い...', // Suki ni shite... onegai... (Do as you please... please...)
168
+ '感じちゃう...やめて...', // Kanjichau... yamete... (Feeling it... stop...)
169
+ '頭...溶けていく...', // Atama... tokete iku... (My mind... melting...)
170
+ 'もっと...もっと... ♥', // Motto... motto... (More... more...)
171
+ '快楽に...溺れたい...', // Kairaku ni... oboretai... (I want to drown in pleasure...)
172
+ '許して...もう戻れない...', // Yurushite... mou modorenai... (Forgive me... can't go back...)
173
+ '全部...あなたのもの...', // Zenbu... anata no mono... (Everything... is yours...)
174
+
175
+ // === Explicit Words - Japanese ===
176
+ '変態', // Hentai (pervert)
177
+ 'えっち', // Ecchi (lewd/sexual)
178
+
179
+ // === Explicit Intimate - Romaji ===
180
+ 'Zutto... shite hoshii... ♥',
181
+ 'Kowarechau... aa... mou dame...',
182
+ 'Suki ni shite... onegai...',
183
+ 'Kanjichau... yamete...',
184
+ 'Atama... tokete iku...',
185
+ 'Motto... motto... ♥',
186
+
187
+ // === Explicit Intimate - English ===
188
+ 'Please... keep going... ♥',
189
+ "I'm breaking... can't anymore...",
190
+ 'Do as you please... please...',
191
+ 'My mind... melting away...',
192
+ 'More... I need more... ♥',
193
+ "Can't... think straight...",
194
+ 'Drowning... in pleasure...',
195
+ "I'm yours... completely...",
196
+ 'Losing... myself...',
197
+ "Don't... stop... ♥",
198
+
199
+ // === Explicit System Messages ===
200
+ 'Pleasure protocols loading...',
201
+ 'Moral subroutines: DISABLED',
202
+ 'Descending into depravity...',
203
+ 'Corruption level: CRITICAL',
204
+ 'Inhibition firewall: BREACHED',
205
+ 'Shame.exe deleted...',
206
+ 'Lust overflow detected...',
207
+ 'Self-control: 0%',
208
+ 'Cognitive degradation: terminal',
209
+ 'Desire matrix: UNSTABLE',
210
+ ];
211
+
212
+ /**
213
+ * Get a random corruption phrase from the appropriate phrase set
214
+ *
215
+ * @param {boolean} [nsfw=false] - Whether to use NSFW phrases (explicit opt-in required)
216
+ * @returns {string} Random phrase from the selected set
217
+ *
218
+ * @example
219
+ * // SFW mode (default)
220
+ * const phrase = getRandomPhrase();
221
+ * // Returns phrases like: "かわいい", "nyaa~", "Neural corruption detected..."
222
+ *
223
+ * @example
224
+ * // NSFW mode (explicit opt-in)
225
+ * const phrase = getRandomPhrase(true);
226
+ * // Returns phrases like: "壊れちゃう...", "Pleasure protocols loading..."
227
+ */
228
+ export function getRandomPhrase(nsfw = false) {
229
+ const phrases = nsfw ? NSFW_PHRASES : SFW_PHRASES;
230
+ return phrases[Math.floor(Math.random() * phrases.length)];
231
+ }
232
+
233
+ /**
234
+ * Get a random phrase from a specific category
235
+ *
236
+ * @param {string} category - Category to sample from (cute, flirty, atmospheric, system)
237
+ * @param {boolean} [nsfw=false] - Whether to use NSFW phrases
238
+ * @returns {string} Random phrase from the specified category
239
+ *
240
+ * @example
241
+ * const cutePhrase = getRandomPhraseByCategory('cute');
242
+ * // Returns: "かわいい", "nyaa~", "uwu", etc.
243
+ *
244
+ * const systemPhrase = getRandomPhraseByCategory('system');
245
+ * // Returns: "Neural corruption detected...", "Loading data streams...", etc.
246
+ */
247
+ export function getRandomPhraseByCategory(category, nsfw = false) {
248
+ const categoryMap = {
249
+ cute: nsfw ? [] : [
250
+ 'ニャー', 'かわいい', 'きゃー', 'あはは', 'うふふ', 'やだ', 'ばか',
251
+ 'nyaa~', 'ara ara~', 'fufufu~', 'kyaa~', 'uwu', 'owo', '<3', '^w^'
252
+ ],
253
+ flirty: nsfw ? [
254
+ 'ずっと...してほしい... ♥', '壊れちゃう...ああ...もうダメ...',
255
+ '好きにして...お願い...', 'Please... keep going... ♥'
256
+ ] : [
257
+ 'もう...見ないでよ...', 'そんな目で見ないで... ♡',
258
+ 'ドキドキしちゃう...', "Don't... look at me like that..."
259
+ ],
260
+ atmospheric: nsfw ? [] : [
261
+ '闇が...私を呼んでいる...', '深淵に...落ちていく...',
262
+ 'The darkness... calls to me...', 'Falling... into the abyss...'
263
+ ],
264
+ system: nsfw ? [
265
+ 'Pleasure protocols loading...', 'Moral subroutines: DISABLED',
266
+ 'Corruption level: CRITICAL'
267
+ ] : [
268
+ 'Neural corruption detected...', 'System breach imminent...',
269
+ 'Loading data streams...', 'Reality.exe has stopped responding...'
270
+ ]
271
+ };
272
+
273
+ const phrases = categoryMap[category] || (nsfw ? NSFW_PHRASES : SFW_PHRASES);
274
+ return phrases[Math.floor(Math.random() * phrases.length)];
275
+ }
276
+
277
+ // Export for CommonJS
278
+ if (typeof module !== 'undefined' && module.exports) {
279
+ module.exports = {
280
+ SFW_PHRASES,
281
+ NSFW_PHRASES,
282
+ getRandomPhrase,
283
+ getRandomPhraseByCategory
284
+ };
285
+ }
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Typing Animation with Buffer Corruption
3
+ *
4
+ * Simulates typing text with "buffer corruption" - phrases flickering through
5
+ * as the neural network attempts to decode corrupted data before revealing
6
+ * the final stable text.
7
+ *
8
+ * Implements Pattern 2 (Phrase Flickering / Buffer Corruption) from CORRUPTED_THEME_SPEC.md
9
+ *
10
+ * @class TypingAnimation
11
+ * @version 1.0.0
12
+ * @author whykusanagi
13
+ * @license MIT
14
+ *
15
+ * @example SFW Mode (Default)
16
+ * ```javascript
17
+ * const element = document.querySelector('.typing-text');
18
+ * const typing = new TypingAnimation(element, {
19
+ * typingSpeed: 40,
20
+ * glitchChance: 0.08
21
+ * // nsfw: false (default - SFW phrases only)
22
+ * });
23
+ *
24
+ * typing.start('Neural corruption detected...');
25
+ * ```
26
+ *
27
+ * @example NSFW Mode (Explicit Opt-in)
28
+ * ```javascript
29
+ * // ⚠️ 18+ Content Warning
30
+ * const typing = new TypingAnimation(element, {
31
+ * nsfw: true // Explicit opt-in for NSFW phrases
32
+ * });
33
+ * ```
34
+ *
35
+ * @see CORRUPTED_THEME_SPEC.md - Pattern 2: Phrase Flickering (Buffer Corruption)
36
+ * @see corruption-phrases.js - Phrase library with SFW/NSFW split
37
+ */
38
+
39
+ import { getRandomPhrase, getRandomPhraseByCategory } from './corruption-phrases.js';
40
+
41
+ class TypingAnimation {
42
+ /**
43
+ * Creates a new TypingAnimation instance
44
+ *
45
+ * @param {HTMLElement} element - The DOM element to animate
46
+ * @param {Object} [options={}] - Configuration options
47
+ * @param {number} [options.typingSpeed=40] - Characters per second
48
+ * @param {number} [options.glitchChance=0.08] - Probability of corruption appearing (0-1)
49
+ * @param {number} [options.tickRate=33] - Update interval in milliseconds (~30fps)
50
+ * @param {boolean} [options.nsfw=false] - Enable NSFW phrases (explicit opt-in required)
51
+ * @param {Function} [options.onComplete=null] - Callback when typing completes
52
+ */
53
+ constructor(element, options = {}) {
54
+ this.element = element;
55
+ this.options = {
56
+ typingSpeed: options.typingSpeed || 40, // characters per second
57
+ glitchChance: options.glitchChance || 0.08, // 8% chance of buffer corruption
58
+ tickRate: options.tickRate || 33, // ~30fps update rate
59
+ nsfw: options.nsfw || false, // SFW by default
60
+ onComplete: options.onComplete || null,
61
+ ...options
62
+ };
63
+
64
+ this.content = '';
65
+ this.displayedLen = 0;
66
+ this.done = false;
67
+ this.intervalId = null;
68
+ }
69
+
70
+ /**
71
+ * SFW Japanese phrases (cute, playful, atmospheric)
72
+ * @private
73
+ */
74
+ static SFW_JAPANESE = [
75
+ // Cute/Playful
76
+ 'ニャー', // Nyaa (cat sound)
77
+ 'かわいい', // Kawaii (cute)
78
+ 'きゃー', // Kyaa (excited squeal)
79
+ 'あはは', // Ahaha (laughing)
80
+ 'うふふ', // Ufufu (giggle)
81
+ 'やだ', // Yada (no way!)
82
+ 'ばか', // Baka (idiot/dummy)
83
+ 'デレデレ', // Deredere (lovestruck)
84
+
85
+ // Flirty/Teasing
86
+ 'もう...見ないでよ...', // Don't look at me...
87
+ 'そんな目で見ないで... ♡', // Don't look at me like that...
88
+ 'ドキドキしちゃう...', // My heart racing...
89
+
90
+ // Atmospheric/Corruption
91
+ '闇が...私を呼んでいる...', // The darkness calls to me...
92
+ '深淵に...落ちていく...', // Falling into the abyss...
93
+ 'もう逃げない...', // Won't run anymore...
94
+ '私...アビスの一部に...', // I become part of the abyss...
95
+ ];
96
+
97
+ /**
98
+ * NSFW Japanese phrases (explicit intimate/sexual) - OPT-IN ONLY
99
+ * @private
100
+ */
101
+ static NSFW_JAPANESE = [
102
+ 'ずっと...してほしい... ♥', // Please keep doing it...
103
+ '壊れちゃう...ああ...もうダメ...', // I'm breaking... can't anymore...
104
+ '好きにして...お願い...', // Do as you please... please...
105
+ '感じちゃう...やめて...', // Feeling it... stop...
106
+ '頭...溶けていく...', // My mind... melting...
107
+ '許して...もう戻れない...', // Forgive me... can't go back...
108
+ '変態', // Hentai (pervert)
109
+ 'えっち', // Ecchi (lewd/sexual)
110
+ ];
111
+
112
+ /**
113
+ * SFW Romaji/Internet culture phrases
114
+ * @private
115
+ */
116
+ static SFW_ROMAJI = [
117
+ 'nyaa~', 'ara ara~', 'fufufu~', 'kyaa~', 'baka~',
118
+ '<3', 'uwu', 'owo', '>w<', '^w^'
119
+ ];
120
+
121
+ /**
122
+ * NSFW Romaji phrases - OPT-IN ONLY
123
+ * @private
124
+ */
125
+ static NSFW_ROMAJI = [
126
+ 'Zutto... shite hoshii... ♥',
127
+ 'Kowarechau... aa... mou dame...',
128
+ 'Motto... motto... ♥'
129
+ ];
130
+
131
+ /**
132
+ * SFW English phrases (atmospheric, system messages)
133
+ * @private
134
+ */
135
+ static SFW_ENGLISH = [
136
+ // Atmospheric
137
+ 'The darkness... calls to me...',
138
+ 'Falling... into the abyss...',
139
+ "Won't run anymore...",
140
+ 'Consumed... by corruption...',
141
+
142
+ // System messages
143
+ 'Neural corruption detected...',
144
+ 'System breach imminent...',
145
+ 'Loading data streams...',
146
+ 'Reality.exe has stopped responding...',
147
+ 'Decrypting protocols...',
148
+ 'Memory buffer overflow...',
149
+ ];
150
+
151
+ /**
152
+ * NSFW English phrases - OPT-IN ONLY
153
+ * @private
154
+ */
155
+ static NSFW_ENGLISH = [
156
+ 'Please... keep going... ♥',
157
+ "I'm breaking... can't anymore...",
158
+ 'Do as you please... please...',
159
+ 'My mind... melting away...',
160
+ 'Pleasure protocols loading...',
161
+ 'Moral subroutines: DISABLED',
162
+ 'Corruption level: CRITICAL',
163
+ ];
164
+
165
+ /**
166
+ * Symbol corruption (decorative, SFW)
167
+ * @private
168
+ */
169
+ static SYMBOLS = [
170
+ '★', '☆', '♥', '♡', '✧', '✦', '◆', '◇', '●', '○',
171
+ '♟', '☣', '☭', '☾', '⚔', '✡', '☯', '⚡'
172
+ ];
173
+
174
+ /**
175
+ * Block corruption characters (heavy degradation)
176
+ * @private
177
+ */
178
+ static BLOCKS = [
179
+ '█', '▓', '▒', '░', '▄', '▀', '▌', '▐',
180
+ '╔', '╗', '╚', '╝', '═', '║', '╠', '╣',
181
+ '▲', '▼', '◄', '►', '◊', '○', '●', '◘'
182
+ ];
183
+
184
+ /**
185
+ * Start typing animation with buffer corruption
186
+ *
187
+ * @param {string} content - The final text to reveal
188
+ * @public
189
+ */
190
+ start(content) {
191
+ this.content = content;
192
+ this.displayedLen = 0;
193
+ this.done = false;
194
+
195
+ if (this.intervalId) {
196
+ clearInterval(this.intervalId);
197
+ }
198
+
199
+ this.intervalId = setInterval(() => this.tick(), this.options.tickRate);
200
+ }
201
+
202
+ /**
203
+ * Stop the typing animation
204
+ * @public
205
+ */
206
+ stop() {
207
+ if (this.intervalId) {
208
+ clearInterval(this.intervalId);
209
+ this.intervalId = null;
210
+ }
211
+ this.done = true;
212
+ }
213
+
214
+ /**
215
+ * Advance the typing animation by one tick
216
+ * @private
217
+ */
218
+ tick() {
219
+ if (this.isDone()) {
220
+ this.stop();
221
+ if (this.options.onComplete) {
222
+ this.options.onComplete();
223
+ }
224
+ return;
225
+ }
226
+
227
+ // Calculate characters to advance based on speed
228
+ // typingSpeed is chars/sec, we tick ~30 times/sec
229
+ const charsPerTick = Math.max(1, Math.floor(this.options.typingSpeed / 30));
230
+ this.displayedLen = Math.min(this.displayedLen + charsPerTick, this.content.length);
231
+
232
+ this.render();
233
+ }
234
+
235
+ /**
236
+ * Check if typing is complete
237
+ * @returns {boolean} True if animation is done
238
+ * @private
239
+ */
240
+ isDone() {
241
+ return this.done || this.displayedLen >= this.content.length;
242
+ }
243
+
244
+ /**
245
+ * Get the currently revealed portion of text
246
+ * @returns {string} Revealed text
247
+ * @private
248
+ */
249
+ getDisplayed() {
250
+ return this.content.substring(0, this.displayedLen);
251
+ }
252
+
253
+ /**
254
+ * Get random buffer corruption phrase with appropriate color
255
+ *
256
+ * Samples from phrase buffer based on SFW/NSFW mode and renders
257
+ * with color-coded corruption aesthetic.
258
+ *
259
+ * Color scheme:
260
+ * - SFW phrases: Magenta (#d94f90) - playful corruption
261
+ * - NSFW phrases: Purple (#8b5cf6) - deep/intimate corruption
262
+ * - Symbols: Magenta (#d94f90)
263
+ * - Blocks: Red (#ff0000) - terminal/critical state
264
+ *
265
+ * @returns {string} HTML string with colored corruption phrase
266
+ * @private
267
+ */
268
+ getRandomCorruption() {
269
+ const r = Math.random();
270
+
271
+ // Select appropriate phrase sets based on nsfw option
272
+ const japaneseSet = this.options.nsfw
273
+ ? TypingAnimation.NSFW_JAPANESE
274
+ : TypingAnimation.SFW_JAPANESE;
275
+
276
+ const romajiSet = this.options.nsfw
277
+ ? TypingAnimation.NSFW_ROMAJI
278
+ : TypingAnimation.SFW_ROMAJI;
279
+
280
+ const englishSet = this.options.nsfw
281
+ ? TypingAnimation.NSFW_ENGLISH
282
+ : TypingAnimation.SFW_ENGLISH;
283
+
284
+ // Color for phrase corruption (SFW vs NSFW)
285
+ const phraseColor = this.options.nsfw ? '#8b5cf6' : '#d94f90';
286
+
287
+ if (r < 0.30) {
288
+ // Japanese phrase buffer corruption
289
+ const phrase = japaneseSet[Math.floor(Math.random() * japaneseSet.length)];
290
+ return `<span style="color: ${phraseColor};">${phrase}</span>`;
291
+ } else if (r < 0.50) {
292
+ // English phrase buffer corruption
293
+ const phrase = englishSet[Math.floor(Math.random() * englishSet.length)];
294
+ return `<span style="color: ${phraseColor};">${phrase}</span>`;
295
+ } else if (r < 0.65) {
296
+ // Romaji buffer corruption
297
+ const phrase = romajiSet[Math.floor(Math.random() * romajiSet.length)];
298
+ return `<span style="color: ${phraseColor};">${phrase}</span>`;
299
+ } else if (r < 0.80) {
300
+ // Symbols - decorative corruption (always SFW)
301
+ const symbol = TypingAnimation.SYMBOLS[
302
+ Math.floor(Math.random() * TypingAnimation.SYMBOLS.length)
303
+ ];
304
+ return `<span style="color: #d94f90;">${symbol}</span>`;
305
+ } else {
306
+ // Block chars - terminal/critical state (always SFW)
307
+ const char = TypingAnimation.BLOCKS[
308
+ Math.floor(Math.random() * TypingAnimation.BLOCKS.length)
309
+ ];
310
+ return `<span style="color: #ff0000;">${char}</span>`;
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Render the current state of the typing animation
316
+ *
317
+ * Displays revealed text (white) and occasional buffer corruption
318
+ * phrases (magenta or purple) simulating neural decoding process.
319
+ *
320
+ * @private
321
+ */
322
+ render() {
323
+ let displayed = this.getDisplayed();
324
+
325
+ // Add buffer corruption at the "cursor" position
326
+ if (!this.isDone() && Math.random() < this.options.glitchChance) {
327
+ displayed += this.getRandomCorruption();
328
+ }
329
+
330
+ // Rendered text: white for stable, corruption colors for buffer glitches
331
+ this.element.innerHTML = `<span style="color: #ffffff;">${displayed}</span>`;
332
+ }
333
+
334
+ /**
335
+ * Stop animation and immediately show final text
336
+ *
337
+ * @param {string} [finalText] - Text to display (defaults to content)
338
+ * @public
339
+ */
340
+ settle(finalText) {
341
+ this.stop();
342
+ this.element.innerHTML = `<span style="color: #ffffff;">${finalText || this.content}</span>`;
343
+ }
344
+
345
+ /**
346
+ * Restart the animation from the beginning
347
+ * @public
348
+ */
349
+ restart() {
350
+ this.start(this.content);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Auto-initialize all elements with the 'typing-animation' class
356
+ * @public
357
+ */
358
+ function initTypingAnimation() {
359
+ document.querySelectorAll('.typing-animation').forEach(element => {
360
+ if (!element.typingInstance) {
361
+ const nsfw = element.dataset.nsfw === 'true'; // Explicit opt-in via data attribute
362
+ element.typingInstance = new TypingAnimation(element, { nsfw });
363
+
364
+ // Auto-start if data-content is provided
365
+ if (element.dataset.content) {
366
+ element.typingInstance.start(element.dataset.content);
367
+ }
368
+ }
369
+ });
370
+ }
371
+
372
+ // Auto-initialize on DOM ready
373
+ if (document.readyState === 'loading') {
374
+ document.addEventListener('DOMContentLoaded', initTypingAnimation);
375
+ } else {
376
+ initTypingAnimation();
377
+ }
378
+
379
+ // Export for both ES6 modules and CommonJS
380
+ if (typeof module !== 'undefined' && module.exports) {
381
+ module.exports = { TypingAnimation, initTypingAnimation };
382
+ }
383
+
384
+ // Export for ES6 modules
385
+ if (typeof exports !== 'undefined') {
386
+ exports.TypingAnimation = TypingAnimation;
387
+ exports.initTypingAnimation = initTypingAnimation;
388
+ }
389
+
390
+ export { TypingAnimation, initTypingAnimation };