@whykusanagi/corrupted-theme 0.1.4 → 0.1.5
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.
- package/CHANGELOG.md +21 -7
- package/README.md +79 -15
- package/examples/advanced/nsfw-corruption.html +348 -0
- package/examples/basic/corrupted-text.html +254 -0
- package/examples/basic/typing-animation.html +344 -0
- package/examples/button.html +1 -1
- package/examples/card.html +1 -1
- package/examples/extensions-showcase.html +1 -1
- package/examples/form.html +1 -1
- package/examples/index.html +29 -3
- package/examples/layout.html +1 -1
- package/examples/nikke-team-builder.html +2 -2
- package/examples/showcase-complete.html +2 -2
- package/examples/showcase.html +2 -2
- package/package.json +1 -1
- package/src/core/corrupted-text.js +280 -0
- package/src/core/corruption-phrases.js +285 -0
- package/src/core/typing-animation.js +390 -0
|
@@ -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 };
|