@whykusanagi/corrupted-theme 0.1.7 → 0.1.9
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 +28 -0
- package/README.md +140 -4
- package/docs/COMPONENTS_REFERENCE.md +205 -2
- package/docs/governance/VERSION_MANAGEMENT.md +2 -2
- package/docs/governance/VERSION_REFERENCES.md +65 -202
- package/docs/platforms/NPM_PACKAGE.md +8 -6
- package/examples/advanced/glsl-vortex.html +297 -0
- package/examples/advanced/particles-bg.html +263 -0
- package/examples/basic/corrupted-text.html +2 -3
- package/examples/basic/typing-animation.html +136 -55
- package/examples/button.html +1 -2
- package/examples/card.html +1 -2
- package/examples/extensions-showcase.html +36 -1
- package/examples/form.html +1 -2
- package/examples/index.html +28 -4
- package/examples/layout.html +1 -2
- package/examples/nikke-team-builder.html +1 -2
- package/examples/showcase-complete.html +2 -3
- package/examples/showcase.html +1 -2
- package/package.json +4 -3
- package/src/core/typing-animation.js +274 -106
- package/src/css/theme.css +0 -15
- package/src/lib/corrupted-particles.js +309 -0
- package/src/lib/corrupted-text.js +127 -36
- package/src/lib/corrupted-vortex.js +329 -0
- package/docs/ROADMAP.md +0 -266
- package/examples/advanced/nsfw-corruption.html +0 -348
- package/src/core/corrupted-text.js +0 -300
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>NSFW Corruption (18+) - Corrupted Theme</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
|
8
|
-
<link rel="stylesheet" href="../../src/css/theme.css">
|
|
9
|
-
<style>
|
|
10
|
-
.container {
|
|
11
|
-
max-width: 1000px;
|
|
12
|
-
margin: 0 auto;
|
|
13
|
-
padding: var(--spacing-lg);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.example-group {
|
|
17
|
-
margin-bottom: var(--spacing-2xl);
|
|
18
|
-
background: var(--glass);
|
|
19
|
-
border: 1px solid var(--border);
|
|
20
|
-
border-radius: var(--radius-lg);
|
|
21
|
-
padding: var(--spacing-lg);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.example-group h3 {
|
|
25
|
-
color: var(--accent);
|
|
26
|
-
margin-bottom: var(--spacing-lg);
|
|
27
|
-
font-size: 1.25rem;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.warning-box {
|
|
31
|
-
background: rgba(255, 0, 0, 0.1);
|
|
32
|
-
border: 3px solid var(--corrupted-red);
|
|
33
|
-
border-radius: var(--radius-lg);
|
|
34
|
-
padding: var(--spacing-xl);
|
|
35
|
-
margin-bottom: var(--spacing-2xl);
|
|
36
|
-
box-shadow: 0 0 30px rgba(255, 0, 0, 0.3);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.warning-box h2 {
|
|
40
|
-
color: var(--corrupted-red);
|
|
41
|
-
margin-top: 0;
|
|
42
|
-
font-size: 1.75rem;
|
|
43
|
-
text-align: center;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.warning-box p {
|
|
47
|
-
color: var(--text);
|
|
48
|
-
line-height: 1.6;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.warning-box ul {
|
|
52
|
-
color: var(--text-secondary);
|
|
53
|
-
line-height: 1.8;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.demo-area {
|
|
57
|
-
background: var(--bg-secondary);
|
|
58
|
-
border: 1px solid var(--border);
|
|
59
|
-
border-radius: var(--radius-md);
|
|
60
|
-
padding: var(--spacing-xl);
|
|
61
|
-
margin-bottom: var(--spacing-md);
|
|
62
|
-
min-height: 100px;
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
justify-content: center;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.typing-output {
|
|
69
|
-
font-size: 1.25rem;
|
|
70
|
-
min-height: 40px;
|
|
71
|
-
letter-spacing: 1px;
|
|
72
|
-
font-family: 'Courier New', monospace;
|
|
73
|
-
text-align: center;
|
|
74
|
-
width: 100%;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.controls {
|
|
78
|
-
display: flex;
|
|
79
|
-
gap: var(--spacing-md);
|
|
80
|
-
margin-top: var(--spacing-md);
|
|
81
|
-
flex-wrap: wrap;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.code-block {
|
|
85
|
-
background: var(--bg);
|
|
86
|
-
border: 1px solid var(--border);
|
|
87
|
-
border-radius: var(--radius-md);
|
|
88
|
-
padding: var(--spacing-md);
|
|
89
|
-
margin-top: var(--spacing-md);
|
|
90
|
-
font-family: 'Courier New', monospace;
|
|
91
|
-
font-size: 0.875rem;
|
|
92
|
-
color: var(--text-secondary);
|
|
93
|
-
overflow-x: auto;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.info-badge {
|
|
97
|
-
display: inline-flex;
|
|
98
|
-
align-items: center;
|
|
99
|
-
gap: var(--spacing-sm);
|
|
100
|
-
background: var(--glass);
|
|
101
|
-
border: 1px solid var(--border);
|
|
102
|
-
border-radius: var(--radius-full);
|
|
103
|
-
padding: var(--spacing-sm) var(--spacing-md);
|
|
104
|
-
font-size: 0.875rem;
|
|
105
|
-
color: var(--text-secondary);
|
|
106
|
-
margin-bottom: var(--spacing-md);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.nsfw-badge {
|
|
110
|
-
background: rgba(255, 0, 0, 0.2);
|
|
111
|
-
border-color: var(--corrupted-red);
|
|
112
|
-
color: var(--corrupted-red);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.comparison-grid {
|
|
116
|
-
display: grid;
|
|
117
|
-
grid-template-columns: 1fr 1fr;
|
|
118
|
-
gap: var(--spacing-lg);
|
|
119
|
-
margin-bottom: var(--spacing-md);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.comparison-box {
|
|
123
|
-
background: var(--bg-secondary);
|
|
124
|
-
border: 2px solid;
|
|
125
|
-
border-radius: var(--radius-md);
|
|
126
|
-
padding: var(--spacing-lg);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.comparison-box.sfw {
|
|
130
|
-
border-color: var(--corrupted-magenta2);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.comparison-box.nsfw {
|
|
134
|
-
border-color: var(--corrupted-purple);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.comparison-box h4 {
|
|
138
|
-
margin-top: 0;
|
|
139
|
-
font-size: 1rem;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.comparison-box.sfw h4 {
|
|
143
|
-
color: var(--corrupted-magenta2);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.comparison-box.nsfw h4 {
|
|
147
|
-
color: var(--corrupted-purple);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
@media (max-width: 768px) {
|
|
151
|
-
.comparison-grid {
|
|
152
|
-
grid-template-columns: 1fr;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
</style>
|
|
156
|
-
</head>
|
|
157
|
-
<body>
|
|
158
|
-
<!-- Global Navigation -->
|
|
159
|
-
<nav class="navbar">
|
|
160
|
-
<div class="navbar-content">
|
|
161
|
-
<a href="../index.html" class="navbar-logo"><i class="fas fa-palette"></i> Corrupted Theme</a>
|
|
162
|
-
<ul class="navbar-links">
|
|
163
|
-
<li><a href="../index.html"><i class="fas fa-home"></i> Home</a></li>
|
|
164
|
-
<li><a href="../showcase-complete.html"><i class="fas fa-cube"></i> Components</a></li>
|
|
165
|
-
<li><a href="../extensions-showcase.html"><i class="fas fa-puzzle-piece"></i> Extensions</a></li>
|
|
166
|
-
<li class="has-submenu">
|
|
167
|
-
<a href="#" class="active">
|
|
168
|
-
<i class="fas fa-flask"></i> Examples
|
|
169
|
-
<i class="fas fa-chevron-down" style="font-size: 0.7em; margin-left: 4px;"></i>
|
|
170
|
-
</a>
|
|
171
|
-
<div class="submenu">
|
|
172
|
-
<a href="../nikke-team-builder.html"><i class="fas fa-users"></i> Nikke Team Builder</a>
|
|
173
|
-
<a href="../button.html"><i class="fas fa-hand-pointer"></i> Buttons</a>
|
|
174
|
-
<a href="../card.html"><i class="fas fa-square"></i> Cards</a>
|
|
175
|
-
<a href="../form.html"><i class="fas fa-edit"></i> Forms</a>
|
|
176
|
-
<a href="../layout.html"><i class="fas fa-columns"></i> Layouts</a>
|
|
177
|
-
<a href="../basic/corrupted-text.html"><i class="fas fa-terminal"></i> Character Corruption</a>
|
|
178
|
-
<a href="../basic/typing-animation.html"><i class="fas fa-keyboard"></i> Buffer Corruption</a>
|
|
179
|
-
<a href="nsfw-corruption.html" class="active"><i class="fas fa-exclamation-triangle"></i> NSFW (18+)</a>
|
|
180
|
-
</div>
|
|
181
|
-
</li>
|
|
182
|
-
<li><a href="../showcase-complete.html"><i class="fas fa-book"></i> Docs</a></li>
|
|
183
|
-
</ul>
|
|
184
|
-
</div>
|
|
185
|
-
</nav>
|
|
186
|
-
|
|
187
|
-
<div class="container">
|
|
188
|
-
<!-- Content Warning -->
|
|
189
|
-
<div class="warning-box">
|
|
190
|
-
<h2><i class="fas fa-exclamation-triangle"></i> 18+ Content Warning</h2>
|
|
191
|
-
<p style="text-align: center;"><strong>This page contains explicit mature content (NSFW mode).</strong></p>
|
|
192
|
-
<p>This example demonstrates the NSFW mode of TypingAnimation, which includes:</p>
|
|
193
|
-
<ul>
|
|
194
|
-
<li>Explicit intimate/sexual phrases</li>
|
|
195
|
-
<li>Loss of control themes</li>
|
|
196
|
-
<li>Adult-oriented corruption aesthetics</li>
|
|
197
|
-
</ul>
|
|
198
|
-
<p><strong>NOT suitable for:</strong></p>
|
|
199
|
-
<ul>
|
|
200
|
-
<li>Professional/corporate projects</li>
|
|
201
|
-
<li>Public streams without 18+ rating</li>
|
|
202
|
-
<li>Educational contexts</li>
|
|
203
|
-
<li>All-ages content</li>
|
|
204
|
-
</ul>
|
|
205
|
-
<p style="text-align: center; margin-top: var(--spacing-lg); font-weight: bold;">
|
|
206
|
-
By continuing, you confirm you are 18+ and accept viewing explicit content.
|
|
207
|
-
</p>
|
|
208
|
-
</div>
|
|
209
|
-
|
|
210
|
-
<h1><i class="fas fa-exclamation-triangle"></i> NSFW Buffer Corruption</h1>
|
|
211
|
-
<p style="color: var(--text-secondary); margin-bottom: var(--spacing-lg);">
|
|
212
|
-
Pattern 2: Phrase flickering with explicit adult content (18+ only).
|
|
213
|
-
</p>
|
|
214
|
-
|
|
215
|
-
<div class="info-badge nsfw-badge">
|
|
216
|
-
<i class="fas fa-exclamation-triangle"></i>
|
|
217
|
-
<span>18+ ONLY - Explicit Content</span>
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
<!-- SFW vs NSFW Comparison -->
|
|
221
|
-
<section id="comparison" class="example-group">
|
|
222
|
-
<h3>SFW vs NSFW Comparison</h3>
|
|
223
|
-
<p>See the difference between default SFW mode and explicit NSFW mode side-by-side.</p>
|
|
224
|
-
|
|
225
|
-
<div class="comparison-grid">
|
|
226
|
-
<div class="comparison-box sfw">
|
|
227
|
-
<h4><i class="fas fa-check-circle"></i> SFW Mode (Default)</h4>
|
|
228
|
-
<div class="demo-area">
|
|
229
|
-
<div class="typing-output" id="outputSFW"></div>
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
<div class="comparison-box nsfw">
|
|
233
|
-
<h4><i class="fas fa-exclamation-triangle"></i> NSFW Mode (Explicit)</h4>
|
|
234
|
-
<div class="demo-area">
|
|
235
|
-
<div class="typing-output" id="outputNSFW"></div>
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<div class="controls">
|
|
241
|
-
<button class="btn" onclick="startComparison()">
|
|
242
|
-
<i class="fas fa-play"></i> Start Comparison
|
|
243
|
-
</button>
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
<div class="code-block">// SFW mode (default - safe for all audiences)
|
|
247
|
-
const typingSFW = new TypingAnimation(element, {
|
|
248
|
-
nsfw: false // Default behavior
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// NSFW mode (explicit opt-in required)
|
|
252
|
-
const typingNSFW = new TypingAnimation(element, {
|
|
253
|
-
nsfw: true // ⚠️ Enables 18+ explicit content
|
|
254
|
-
});</div>
|
|
255
|
-
</section>
|
|
256
|
-
|
|
257
|
-
<!-- NSFW-Only Example -->
|
|
258
|
-
<section id="nsfw-only" class="example-group">
|
|
259
|
-
<h3>NSFW-Only Example</h3>
|
|
260
|
-
<p>Pure NSFW buffer corruption with explicit phrases in purple (#8b5cf6).</p>
|
|
261
|
-
|
|
262
|
-
<div class="demo-area">
|
|
263
|
-
<div class="typing-output" id="outputNSFWOnly"></div>
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
<div class="controls">
|
|
267
|
-
<button class="btn" onclick="startNSFWOnly()">
|
|
268
|
-
<i class="fas fa-play"></i> Start NSFW Animation
|
|
269
|
-
</button>
|
|
270
|
-
<button class="btn btn-secondary" onclick="stopNSFWOnly()">
|
|
271
|
-
<i class="fas fa-stop"></i> Stop
|
|
272
|
-
</button>
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
<div class="code-block">const typing = new TypingAnimation(element, {
|
|
276
|
-
nsfw: true, // ⚠️ NSFW mode enabled
|
|
277
|
-
typingSpeed: 35,
|
|
278
|
-
glitchChance: 0.12
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
typing.start('Neural corruption detected...');</div>
|
|
282
|
-
</section>
|
|
283
|
-
|
|
284
|
-
<!-- Component Info -->
|
|
285
|
-
<section class="example-group">
|
|
286
|
-
<h3><i class="fas fa-info-circle"></i> NSFW Mode Configuration</h3>
|
|
287
|
-
<ul style="color: var(--text-secondary); line-height: 1.8;">
|
|
288
|
-
<li><strong>Requires Explicit Opt-in:</strong> <code>{ nsfw: true }</code></li>
|
|
289
|
-
<li><strong>Phrase Examples:</strong> "壊れちゃう...", "Pleasure protocols loading...", "変態", "えっち"</li>
|
|
290
|
-
<li><strong>Color:</strong> Deep Purple (#8b5cf6) for NSFW corruption</li>
|
|
291
|
-
<li><strong>Default Behavior:</strong> SFW mode (safe for all audiences)</li>
|
|
292
|
-
<li><strong>Content Rating:</strong> 18+ only, explicit intimate content</li>
|
|
293
|
-
</ul>
|
|
294
|
-
|
|
295
|
-
<div class="alert alert-warning" style="margin-top: var(--spacing-md);">
|
|
296
|
-
<i class="fas fa-shield-alt"></i>
|
|
297
|
-
<strong>Important:</strong> NSFW phrases are ONLY shown when <code>nsfw: true</code> is explicitly set.
|
|
298
|
-
The default behavior is always SFW for safety.
|
|
299
|
-
</div>
|
|
300
|
-
</section>
|
|
301
|
-
</div>
|
|
302
|
-
|
|
303
|
-
<script type="module">
|
|
304
|
-
import { TypingAnimation } from '../../src/core/typing-animation.js';
|
|
305
|
-
|
|
306
|
-
// SFW vs NSFW Comparison
|
|
307
|
-
const outputSFW = document.getElementById('outputSFW');
|
|
308
|
-
const outputNSFW = document.getElementById('outputNSFW');
|
|
309
|
-
|
|
310
|
-
window.startComparison = () => {
|
|
311
|
-
// SFW instance
|
|
312
|
-
const typingSFW = new TypingAnimation(outputSFW, {
|
|
313
|
-
nsfw: false,
|
|
314
|
-
glitchChance: 0.10
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// NSFW instance
|
|
318
|
-
const typingNSFW = new TypingAnimation(outputNSFW, {
|
|
319
|
-
nsfw: true,
|
|
320
|
-
glitchChance: 0.10
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// Start both simultaneously
|
|
324
|
-
typingSFW.start('System Online');
|
|
325
|
-
typingNSFW.start('System Online');
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// NSFW-only example
|
|
329
|
-
const outputNSFWOnly = document.getElementById('outputNSFWOnly');
|
|
330
|
-
let typingNSFWOnly = new TypingAnimation(outputNSFWOnly, {
|
|
331
|
-
nsfw: true,
|
|
332
|
-
typingSpeed: 35,
|
|
333
|
-
glitchChance: 0.12
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
window.startNSFWOnly = () => {
|
|
337
|
-
typingNSFWOnly.start('Neural corruption detected... Decoding data buffer...');
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
window.stopNSFWOnly = () => {
|
|
341
|
-
typingNSFWOnly.stop();
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// Auto-start comparison on load
|
|
345
|
-
setTimeout(() => startComparison(), 1000);
|
|
346
|
-
</script>
|
|
347
|
-
</body>
|
|
348
|
-
</html>
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Corrupted Text Animation
|
|
3
|
-
*
|
|
4
|
-
* Cycles through Japanese (hiragana/katakana/kanji), romaji, and English text variants
|
|
5
|
-
* with character-level corruption effects. The text progressively corrupts from one
|
|
6
|
-
* variant to another, creating a glitchy Matrix-style transformation effect.
|
|
7
|
-
*
|
|
8
|
-
* @class CorruptedText
|
|
9
|
-
* @version 1.0.0
|
|
10
|
-
* @author whykusanagi
|
|
11
|
-
* @license MIT
|
|
12
|
-
*
|
|
13
|
-
* @example Basic Usage (Auto-initialization via data attributes)
|
|
14
|
-
* ```html
|
|
15
|
-
* <span class="corrupted-multilang"
|
|
16
|
-
* data-english="Hello World"
|
|
17
|
-
* data-romaji="konnichiwa"
|
|
18
|
-
* data-hiragana="こんにちは"
|
|
19
|
-
* data-katakana="コンニチハ"
|
|
20
|
-
* data-kanji="今日は">
|
|
21
|
-
* </span>
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* @example Manual Initialization
|
|
25
|
-
* ```javascript
|
|
26
|
-
* const element = document.querySelector('.my-text');
|
|
27
|
-
* const corrupted = new CorruptedText(element, {
|
|
28
|
-
* duration: 3000,
|
|
29
|
-
* cycleDelay: 100,
|
|
30
|
-
* loop: true
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* // Control playback
|
|
34
|
-
* corrupted.start();
|
|
35
|
-
* corrupted.stop();
|
|
36
|
-
* corrupted.restart();
|
|
37
|
-
* corrupted.settle('Final Text');
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* @see https://github.com/whykusanagi/corrupted-theme
|
|
41
|
-
* @see CORRUPTED_THEME_SPEC.md - Character-by-Character Decoding pattern
|
|
42
|
-
*/
|
|
43
|
-
class CorruptedText {
|
|
44
|
-
/**
|
|
45
|
-
* Creates a new CorruptedText animation instance
|
|
46
|
-
*
|
|
47
|
-
* @param {HTMLElement} element - The DOM element to animate
|
|
48
|
-
* @param {Object} [options={}] - Configuration options
|
|
49
|
-
* @param {number} [options.duration=3000] - Total animation duration in milliseconds
|
|
50
|
-
* @param {number} [options.cycleDelay=100] - Delay between character corruption steps (ms)
|
|
51
|
-
* @param {number} [options.startDelay=0] - Initial delay before animation starts (ms)
|
|
52
|
-
* @param {boolean} [options.loop=true] - Whether to loop through variants continuously
|
|
53
|
-
* @param {string|null} [options.finalText=null] - Text to settle on after cycle (if loop=false)
|
|
54
|
-
*/
|
|
55
|
-
constructor(element, options = {}) {
|
|
56
|
-
this.element = element;
|
|
57
|
-
this.options = {
|
|
58
|
-
duration: options.duration || 3000,
|
|
59
|
-
cycleDelay: options.cycleDelay || 100,
|
|
60
|
-
startDelay: options.startDelay || 0,
|
|
61
|
-
loop: options.loop !== false,
|
|
62
|
-
finalText: options.finalText || null,
|
|
63
|
-
...options
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Get text variants from data attributes
|
|
67
|
-
this.variants = {
|
|
68
|
-
english: this.element.dataset.english || this.element.textContent.trim(),
|
|
69
|
-
romaji: this.element.dataset.romaji || null,
|
|
70
|
-
hiragana: this.element.dataset.hiragana || null,
|
|
71
|
-
katakana: this.element.dataset.katakana || null,
|
|
72
|
-
kanji: this.element.dataset.kanji || null
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Filter out null variants and create array of available variants
|
|
76
|
-
this.availableVariants = Object.entries(this.variants)
|
|
77
|
-
.filter(([_, value]) => value !== null)
|
|
78
|
-
.map(([key, value]) => ({ type: key, text: value }));
|
|
79
|
-
|
|
80
|
-
if (this.availableVariants.length === 0) {
|
|
81
|
-
console.warn('CorruptedText: No text variants found for element', element);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.currentVariantIndex = 0;
|
|
86
|
-
this.isAnimating = false;
|
|
87
|
-
this.animationFrame = null;
|
|
88
|
-
this._startDelayId = null;
|
|
89
|
-
this._animateTimeoutId = null;
|
|
90
|
-
this._corruptTimeoutId = null;
|
|
91
|
-
|
|
92
|
-
this.init();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Initialize the corruption animation
|
|
97
|
-
* @private
|
|
98
|
-
*/
|
|
99
|
-
init() {
|
|
100
|
-
// Add corrupted class for styling
|
|
101
|
-
if (!this.element.classList.contains('corrupted-multilang')) {
|
|
102
|
-
this.element.classList.add('corrupted-multilang');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Store original text
|
|
106
|
-
this.originalText = this.element.textContent.trim();
|
|
107
|
-
|
|
108
|
-
// Start animation after configured delay
|
|
109
|
-
if (this.options.startDelay > 0) {
|
|
110
|
-
this._startDelayId = setTimeout(() => this.start(), this.options.startDelay);
|
|
111
|
-
} else {
|
|
112
|
-
this.start();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Start the corruption animation
|
|
118
|
-
* @public
|
|
119
|
-
*/
|
|
120
|
-
start() {
|
|
121
|
-
if (this.isAnimating) return;
|
|
122
|
-
this.isAnimating = true;
|
|
123
|
-
this.animate();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Stop the corruption animation
|
|
128
|
-
* @public
|
|
129
|
-
*/
|
|
130
|
-
stop() {
|
|
131
|
-
this.isAnimating = false;
|
|
132
|
-
if (this.animationFrame) cancelAnimationFrame(this.animationFrame);
|
|
133
|
-
if (this._startDelayId) clearTimeout(this._startDelayId);
|
|
134
|
-
if (this._animateTimeoutId) clearTimeout(this._animateTimeoutId);
|
|
135
|
-
if (this._corruptTimeoutId) clearTimeout(this._corruptTimeoutId);
|
|
136
|
-
this.animationFrame = null;
|
|
137
|
-
this._startDelayId = null;
|
|
138
|
-
this._animateTimeoutId = null;
|
|
139
|
-
this._corruptTimeoutId = null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Fully tear down this instance: stop animation and release element reference.
|
|
144
|
-
* @public
|
|
145
|
-
*/
|
|
146
|
-
destroy() {
|
|
147
|
-
this.stop();
|
|
148
|
-
if (this.element && this.element.corruptedTextInstance === this) {
|
|
149
|
-
delete this.element.corruptedTextInstance;
|
|
150
|
-
}
|
|
151
|
-
this.element = null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Main animation loop - cycles through text variants
|
|
156
|
-
* @private
|
|
157
|
-
*/
|
|
158
|
-
animate() {
|
|
159
|
-
if (!this.isAnimating) return;
|
|
160
|
-
|
|
161
|
-
const variant = this.availableVariants[this.currentVariantIndex];
|
|
162
|
-
this.corruptToText(variant.text, () => {
|
|
163
|
-
// Move to next variant in cycle
|
|
164
|
-
this.currentVariantIndex = (this.currentVariantIndex + 1) % this.availableVariants.length;
|
|
165
|
-
|
|
166
|
-
// Check if animation should stop
|
|
167
|
-
if (!this.options.loop && this.currentVariantIndex === 0) {
|
|
168
|
-
// One full cycle complete - settle on final text
|
|
169
|
-
const finalText = this.options.finalText || this.variants.english;
|
|
170
|
-
this.corruptToText(finalText, () => {
|
|
171
|
-
this.isAnimating = false;
|
|
172
|
-
});
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Continue animation to next variant
|
|
177
|
-
this._animateTimeoutId = setTimeout(() => {
|
|
178
|
-
if (this.isAnimating) {
|
|
179
|
-
this.animate();
|
|
180
|
-
}
|
|
181
|
-
}, this.options.duration / this.availableVariants.length);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Corrupt the current text to a target text with progressive reveal
|
|
187
|
-
*
|
|
188
|
-
* @param {string} targetText - The text to reveal through corruption
|
|
189
|
-
* @param {Function} callback - Called when corruption is complete
|
|
190
|
-
* @private
|
|
191
|
-
*/
|
|
192
|
-
corruptToText(targetText, callback) {
|
|
193
|
-
const currentText = this.element.textContent.trim();
|
|
194
|
-
const maxLength = Math.max(currentText.length, targetText.length);
|
|
195
|
-
const steps = 20; // Number of corruption animation steps
|
|
196
|
-
let step = 0;
|
|
197
|
-
|
|
198
|
-
// Character sets for corruption effect (from CORRUPTED_THEME_SPEC.md)
|
|
199
|
-
const katakana = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン';
|
|
200
|
-
const hiragana = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん';
|
|
201
|
-
const romaji = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
202
|
-
const symbols = '0123456789!@#$%^&*()_+-=[]{}|;:,.<>?~`';
|
|
203
|
-
|
|
204
|
-
// Combined corruption character set
|
|
205
|
-
const allCorruptChars = katakana + hiragana + romaji + symbols;
|
|
206
|
-
|
|
207
|
-
const corrupt = () => {
|
|
208
|
-
if (step >= steps) {
|
|
209
|
-
// Animation complete - set final text
|
|
210
|
-
this.element.textContent = targetText;
|
|
211
|
-
if (callback) callback();
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Generate corrupted text with progressive reveal
|
|
216
|
-
let corrupted = '';
|
|
217
|
-
for (let i = 0; i < maxLength; i++) {
|
|
218
|
-
if (i < targetText.length && step > steps * 0.7) {
|
|
219
|
-
// Last 30% of animation - start revealing target text
|
|
220
|
-
const revealProgress = (step - steps * 0.7) / (steps * 0.3);
|
|
221
|
-
if (Math.random() < revealProgress) {
|
|
222
|
-
corrupted += targetText[i];
|
|
223
|
-
} else {
|
|
224
|
-
corrupted += allCorruptChars[Math.floor(Math.random() * allCorruptChars.length)];
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
// First 70% - full random corruption
|
|
228
|
-
corrupted += allCorruptChars[Math.floor(Math.random() * allCorruptChars.length)];
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
this.element.textContent = corrupted;
|
|
233
|
-
step++;
|
|
234
|
-
|
|
235
|
-
// Schedule next corruption step
|
|
236
|
-
this.animationFrame = requestAnimationFrame(() => {
|
|
237
|
-
this._corruptTimeoutId = setTimeout(corrupt, this.options.cycleDelay);
|
|
238
|
-
});
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
corrupt();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Restart the animation from the beginning
|
|
246
|
-
* @public
|
|
247
|
-
*/
|
|
248
|
-
restart() {
|
|
249
|
-
this.stop();
|
|
250
|
-
this.currentVariantIndex = 0;
|
|
251
|
-
this.start();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Stop animation and settle on a specific text
|
|
256
|
-
*
|
|
257
|
-
* @param {string} [finalText] - Text to settle on (defaults to english variant)
|
|
258
|
-
* @public
|
|
259
|
-
*/
|
|
260
|
-
settle(finalText) {
|
|
261
|
-
this.stop();
|
|
262
|
-
this.corruptToText(finalText || this.variants.english, () => {
|
|
263
|
-
this.isAnimating = false;
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Auto-initialize all elements with the 'corrupted-multilang' class
|
|
270
|
-
*
|
|
271
|
-
* This function is automatically called on DOM ready and can be called
|
|
272
|
-
* manually to initialize dynamically added elements.
|
|
273
|
-
*
|
|
274
|
-
* @public
|
|
275
|
-
*/
|
|
276
|
-
function initCorruptedText() {
|
|
277
|
-
document.querySelectorAll('.corrupted-multilang').forEach(element => {
|
|
278
|
-
if (!element.corruptedTextInstance) {
|
|
279
|
-
element.corruptedTextInstance = new CorruptedText(element);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Auto-initialize on DOM ready
|
|
285
|
-
if (document.readyState === 'loading') {
|
|
286
|
-
document.addEventListener('DOMContentLoaded', initCorruptedText);
|
|
287
|
-
} else {
|
|
288
|
-
initCorruptedText();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Export for both ES6 modules and CommonJS
|
|
292
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
293
|
-
module.exports = { CorruptedText, initCorruptedText };
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Export for ES6 modules
|
|
297
|
-
if (typeof exports !== 'undefined') {
|
|
298
|
-
exports.CorruptedText = CorruptedText;
|
|
299
|
-
exports.initCorruptedText = initCorruptedText;
|
|
300
|
-
}
|