@whykusanagi/corrupted-theme 0.1.3 → 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.
@@ -0,0 +1,280 @@
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
+
89
+ this.init();
90
+ }
91
+
92
+ /**
93
+ * Initialize the corruption animation
94
+ * @private
95
+ */
96
+ init() {
97
+ // Add corrupted class for styling
98
+ if (!this.element.classList.contains('corrupted-multilang')) {
99
+ this.element.classList.add('corrupted-multilang');
100
+ }
101
+
102
+ // Store original text
103
+ this.originalText = this.element.textContent.trim();
104
+
105
+ // Start animation after configured delay
106
+ if (this.options.startDelay > 0) {
107
+ setTimeout(() => this.start(), this.options.startDelay);
108
+ } else {
109
+ this.start();
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Start the corruption animation
115
+ * @public
116
+ */
117
+ start() {
118
+ if (this.isAnimating) return;
119
+ this.isAnimating = true;
120
+ this.animate();
121
+ }
122
+
123
+ /**
124
+ * Stop the corruption animation
125
+ * @public
126
+ */
127
+ stop() {
128
+ this.isAnimating = false;
129
+ if (this.animationFrame) {
130
+ cancelAnimationFrame(this.animationFrame);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Main animation loop - cycles through text variants
136
+ * @private
137
+ */
138
+ animate() {
139
+ if (!this.isAnimating) return;
140
+
141
+ const variant = this.availableVariants[this.currentVariantIndex];
142
+ this.corruptToText(variant.text, () => {
143
+ // Move to next variant in cycle
144
+ this.currentVariantIndex = (this.currentVariantIndex + 1) % this.availableVariants.length;
145
+
146
+ // Check if animation should stop
147
+ if (!this.options.loop && this.currentVariantIndex === 0) {
148
+ // One full cycle complete - settle on final text
149
+ const finalText = this.options.finalText || this.variants.english;
150
+ this.corruptToText(finalText, () => {
151
+ this.isAnimating = false;
152
+ });
153
+ return;
154
+ }
155
+
156
+ // Continue animation to next variant
157
+ setTimeout(() => {
158
+ if (this.isAnimating) {
159
+ this.animate();
160
+ }
161
+ }, this.options.duration / this.availableVariants.length);
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Corrupt the current text to a target text with progressive reveal
167
+ *
168
+ * @param {string} targetText - The text to reveal through corruption
169
+ * @param {Function} callback - Called when corruption is complete
170
+ * @private
171
+ */
172
+ corruptToText(targetText, callback) {
173
+ const currentText = this.element.textContent.trim();
174
+ const maxLength = Math.max(currentText.length, targetText.length);
175
+ const steps = 20; // Number of corruption animation steps
176
+ let step = 0;
177
+
178
+ // Character sets for corruption effect (from CORRUPTED_THEME_SPEC.md)
179
+ const katakana = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン';
180
+ const hiragana = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん';
181
+ const romaji = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
182
+ const symbols = '0123456789!@#$%^&*()_+-=[]{}|;:,.<>?~`';
183
+
184
+ // Combined corruption character set
185
+ const allCorruptChars = katakana + hiragana + romaji + symbols;
186
+
187
+ const corrupt = () => {
188
+ if (step >= steps) {
189
+ // Animation complete - set final text
190
+ this.element.textContent = targetText;
191
+ if (callback) callback();
192
+ return;
193
+ }
194
+
195
+ // Generate corrupted text with progressive reveal
196
+ let corrupted = '';
197
+ for (let i = 0; i < maxLength; i++) {
198
+ if (i < targetText.length && step > steps * 0.7) {
199
+ // Last 30% of animation - start revealing target text
200
+ const revealProgress = (step - steps * 0.7) / (steps * 0.3);
201
+ if (Math.random() < revealProgress) {
202
+ corrupted += targetText[i];
203
+ } else {
204
+ corrupted += allCorruptChars[Math.floor(Math.random() * allCorruptChars.length)];
205
+ }
206
+ } else {
207
+ // First 70% - full random corruption
208
+ corrupted += allCorruptChars[Math.floor(Math.random() * allCorruptChars.length)];
209
+ }
210
+ }
211
+
212
+ this.element.textContent = corrupted;
213
+ step++;
214
+
215
+ // Schedule next corruption step
216
+ this.animationFrame = requestAnimationFrame(() => {
217
+ setTimeout(corrupt, this.options.cycleDelay);
218
+ });
219
+ };
220
+
221
+ corrupt();
222
+ }
223
+
224
+ /**
225
+ * Restart the animation from the beginning
226
+ * @public
227
+ */
228
+ restart() {
229
+ this.stop();
230
+ this.currentVariantIndex = 0;
231
+ this.start();
232
+ }
233
+
234
+ /**
235
+ * Stop animation and settle on a specific text
236
+ *
237
+ * @param {string} [finalText] - Text to settle on (defaults to english variant)
238
+ * @public
239
+ */
240
+ settle(finalText) {
241
+ this.stop();
242
+ this.corruptToText(finalText || this.variants.english, () => {
243
+ this.isAnimating = false;
244
+ });
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Auto-initialize all elements with the 'corrupted-multilang' class
250
+ *
251
+ * This function is automatically called on DOM ready and can be called
252
+ * manually to initialize dynamically added elements.
253
+ *
254
+ * @public
255
+ */
256
+ function initCorruptedText() {
257
+ document.querySelectorAll('.corrupted-multilang').forEach(element => {
258
+ if (!element.corruptedTextInstance) {
259
+ element.corruptedTextInstance = new CorruptedText(element);
260
+ }
261
+ });
262
+ }
263
+
264
+ // Auto-initialize on DOM ready
265
+ if (document.readyState === 'loading') {
266
+ document.addEventListener('DOMContentLoaded', initCorruptedText);
267
+ } else {
268
+ initCorruptedText();
269
+ }
270
+
271
+ // Export for both ES6 modules and CommonJS
272
+ if (typeof module !== 'undefined' && module.exports) {
273
+ module.exports = { CorruptedText, initCorruptedText };
274
+ }
275
+
276
+ // Export for ES6 modules
277
+ if (typeof exports !== 'undefined') {
278
+ exports.CorruptedText = CorruptedText;
279
+ exports.initCorruptedText = initCorruptedText;
280
+ }
@@ -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
+ }