@whykusanagi/corrupted-theme 0.1.0

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,193 @@
1
+ /**
2
+ * Corrupted Text Animation
3
+ * Cycles through Japanese (hiragana/katakana/kanji), romaji, and English text
4
+ *
5
+ * Usage:
6
+ * <span class="corrupted-multilang"
7
+ * data-english="Hello"
8
+ * data-romaji="konnichiwa"
9
+ * data-hiragana="こんにちは"
10
+ * data-katakana="コンニチハ"
11
+ * data-kanji="今日は">
12
+ * </span>
13
+ */
14
+
15
+ class CorruptedText {
16
+ constructor(element, options = {}) {
17
+ this.element = element;
18
+ this.options = {
19
+ duration: options.duration || 3000, // Total animation duration
20
+ cycleDelay: options.cycleDelay || 100, // Delay between character changes
21
+ startDelay: options.startDelay || 0, // Initial delay before starting
22
+ loop: options.loop !== false, // Whether to loop
23
+ finalText: options.finalText || null, // Final text to settle on (null = loop)
24
+ ...options
25
+ };
26
+
27
+ // Get text variants from data attributes
28
+ this.variants = {
29
+ english: this.element.dataset.english || this.element.textContent.trim(),
30
+ romaji: this.element.dataset.romaji || null,
31
+ hiragana: this.element.dataset.hiragana || null,
32
+ katakana: this.element.dataset.katakana || null,
33
+ kanji: this.element.dataset.kanji || null
34
+ };
35
+
36
+ // Filter out null variants
37
+ this.availableVariants = Object.entries(this.variants)
38
+ .filter(([_, value]) => value !== null)
39
+ .map(([key, value]) => ({ type: key, text: value }));
40
+
41
+ if (this.availableVariants.length === 0) {
42
+ console.warn('CorruptedText: No text variants found');
43
+ return;
44
+ }
45
+
46
+ this.currentVariantIndex = 0;
47
+ this.isAnimating = false;
48
+ this.animationFrame = null;
49
+
50
+ this.init();
51
+ }
52
+
53
+ init() {
54
+ // Add corrupted class if not present
55
+ if (!this.element.classList.contains('corrupted-multilang')) {
56
+ this.element.classList.add('corrupted-multilang');
57
+ }
58
+
59
+ // Store original text
60
+ this.originalText = this.element.textContent.trim();
61
+
62
+ // Start animation after delay
63
+ if (this.options.startDelay > 0) {
64
+ setTimeout(() => this.start(), this.options.startDelay);
65
+ } else {
66
+ this.start();
67
+ }
68
+ }
69
+
70
+ start() {
71
+ if (this.isAnimating) return;
72
+ this.isAnimating = true;
73
+ this.animate();
74
+ }
75
+
76
+ stop() {
77
+ this.isAnimating = false;
78
+ if (this.animationFrame) {
79
+ cancelAnimationFrame(this.animationFrame);
80
+ }
81
+ }
82
+
83
+ animate() {
84
+ if (!this.isAnimating) return;
85
+
86
+ const variant = this.availableVariants[this.currentVariantIndex];
87
+ this.corruptToText(variant.text, () => {
88
+ // Move to next variant
89
+ this.currentVariantIndex = (this.currentVariantIndex + 1) % this.availableVariants.length;
90
+
91
+ // Check if we should stop
92
+ if (!this.options.loop && this.currentVariantIndex === 0) {
93
+ // If we have a final text, use it, otherwise use original
94
+ const finalText = this.options.finalText || this.variants.english;
95
+ this.corruptToText(finalText, () => {
96
+ this.isAnimating = false;
97
+ });
98
+ return;
99
+ }
100
+
101
+ // Continue animation
102
+ setTimeout(() => {
103
+ if (this.isAnimating) {
104
+ this.animate();
105
+ }
106
+ }, this.options.duration / this.availableVariants.length);
107
+ });
108
+ }
109
+
110
+ corruptToText(targetText, callback) {
111
+ const currentText = this.element.textContent.trim();
112
+ const maxLength = Math.max(currentText.length, targetText.length);
113
+ const steps = 20; // Number of corruption steps
114
+ let step = 0;
115
+
116
+ // Character sets for corruption effect
117
+ const corruptChars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789!@#$%^&*()_+-=[]{}|;:,.<>?~`';
118
+ const romajiChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
119
+ const allCorruptChars = corruptChars + romajiChars + 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん';
120
+
121
+ const corrupt = () => {
122
+ if (step >= steps) {
123
+ // Set final text
124
+ this.element.textContent = targetText;
125
+ if (callback) callback();
126
+ return;
127
+ }
128
+
129
+ // Generate corrupted text
130
+ let corrupted = '';
131
+ for (let i = 0; i < maxLength; i++) {
132
+ if (i < targetText.length && step > steps * 0.7) {
133
+ // Start revealing target text
134
+ const revealProgress = (step - steps * 0.7) / (steps * 0.3);
135
+ if (Math.random() < revealProgress) {
136
+ corrupted += targetText[i];
137
+ } else {
138
+ corrupted += allCorruptChars[Math.floor(Math.random() * allCorruptChars.length)];
139
+ }
140
+ } else {
141
+ // Random corruption
142
+ corrupted += allCorruptChars[Math.floor(Math.random() * allCorruptChars.length)];
143
+ }
144
+ }
145
+
146
+ this.element.textContent = corrupted;
147
+ step++;
148
+
149
+ this.animationFrame = requestAnimationFrame(() => {
150
+ setTimeout(corrupt, this.options.cycleDelay);
151
+ });
152
+ };
153
+
154
+ corrupt();
155
+ }
156
+
157
+ // Public method to restart animation
158
+ restart() {
159
+ this.stop();
160
+ this.currentVariantIndex = 0;
161
+ this.start();
162
+ }
163
+
164
+ // Public method to set final text and stop
165
+ settle(finalText) {
166
+ this.stop();
167
+ this.corruptToText(finalText || this.variants.english, () => {
168
+ this.isAnimating = false;
169
+ });
170
+ }
171
+ }
172
+
173
+ // Auto-initialize elements with corrupted-multilang class
174
+ function initCorruptedText() {
175
+ document.querySelectorAll('.corrupted-multilang').forEach(element => {
176
+ if (!element.corruptedTextInstance) {
177
+ element.corruptedTextInstance = new CorruptedText(element);
178
+ }
179
+ });
180
+ }
181
+
182
+ // Initialize on DOM ready
183
+ if (document.readyState === 'loading') {
184
+ document.addEventListener('DOMContentLoaded', initCorruptedText);
185
+ } else {
186
+ initCorruptedText();
187
+ }
188
+
189
+ // Export for manual use
190
+ if (typeof module !== 'undefined' && module.exports) {
191
+ module.exports = { CorruptedText, initCorruptedText };
192
+ }
193
+
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Corruption Loading Animation
3
+ * A dramatic loading screen with corrupted text, glyphs, and multi-language phrases
4
+ *
5
+ * Usage:
6
+ * import { showCorruptionLoading } from './corruption-loading.js';
7
+ * showCorruptionLoading();
8
+ *
9
+ * Or auto-initialize:
10
+ * <script src="corruption-loading.js"></script>
11
+ */
12
+
13
+ (function() {
14
+ 'use strict';
15
+
16
+ // Check if loading animation should play (72-hour expiration)
17
+ function shouldPlayLoading() {
18
+ const lastPlayed = localStorage.getItem("corruptionLoadingLastPlayed");
19
+ if (!lastPlayed) return true;
20
+
21
+ const now = Date.now();
22
+ const lastPlayedTime = parseInt(lastPlayed);
23
+ const hoursSinceLastPlay = (now - lastPlayedTime) / (1000 * 60 * 60);
24
+
25
+ // Return true if more than 72 hours have passed
26
+ return hoursSinceLastPlay >= 72;
27
+ }
28
+
29
+ // Mark loading animation as played
30
+ function markLoadingPlayed() {
31
+ localStorage.setItem("corruptionLoadingLastPlayed", Date.now().toString());
32
+ }
33
+
34
+ // Main function to show loading screen
35
+ function showCorruptionLoading(options = {}) {
36
+ const config = {
37
+ duration: options.duration || 8000,
38
+ checkInterval: options.checkInterval || 72, // hours
39
+ force: options.force || false,
40
+ ...options
41
+ };
42
+
43
+ // Check if should play (unless forced)
44
+ if (!config.force && !shouldPlayLoading()) {
45
+ return false;
46
+ }
47
+
48
+ // Mark as played immediately to prevent multiple plays
49
+ markLoadingPlayed();
50
+
51
+ // Inject styles
52
+ const style = document.createElement("style");
53
+ style.id = "corruption-loading-styles";
54
+ style.innerHTML = `
55
+ @keyframes flicker {
56
+ 0%, 100% { opacity: 1; }
57
+ 50% { opacity: 0.4; }
58
+ }
59
+
60
+ @keyframes corruptPulse {
61
+ 0%, 100% { opacity: 0.1; transform: scale(1) translateY(0); }
62
+ 50% { opacity: 0.25; transform: scale(1.05) translateY(-10px); }
63
+ }
64
+
65
+ @keyframes scanlines {
66
+ 0% { background-position: 0 0; }
67
+ 100% { background-position: 0 10px; }
68
+ }
69
+
70
+ @keyframes glitch {
71
+ 0% { transform: translate(0); }
72
+ 20% { transform: translate(-2px, 2px); }
73
+ 40% { transform: translate(-2px, -2px); }
74
+ 60% { transform: translate(2px, 2px); }
75
+ 80% { transform: translate(2px, -2px); }
76
+ 100% { transform: translate(0); }
77
+ }
78
+
79
+ @keyframes tear {
80
+ 0%, 90%, 100% { transform: none; }
81
+ 91%, 93% { transform: translateY(-2px); }
82
+ 94%, 96% { transform: translateY(2px); }
83
+ 97%, 99% { transform: translateY(-1px); }
84
+ }
85
+
86
+ @keyframes typing {
87
+ from { width: 0; }
88
+ to { width: 100%; }
89
+ }
90
+
91
+ @keyframes blink {
92
+ 50% { border-color: transparent; }
93
+ }
94
+
95
+ @keyframes dataCorrupt {
96
+ 0%, 100% { clip-path: inset(0 0 0 0); }
97
+ 25% { clip-path: inset(20% 0 40% 0); }
98
+ 50% { clip-path: inset(40% 0 20% 0); }
99
+ 75% { clip-path: inset(10% 0 50% 0); }
100
+ }
101
+
102
+ @keyframes fill {
103
+ 0% { width: 0%; }
104
+ 100% { width: 100%; }
105
+ }
106
+
107
+ .corrupt-stream {
108
+ position: absolute;
109
+ width: 100%;
110
+ height: 100%;
111
+ top: 0;
112
+ left: 0;
113
+ pointer-events: none;
114
+ background:
115
+ repeating-linear-gradient(0deg, rgba(217, 79, 144, 0.03), rgba(217, 79, 144, 0.03) 2px, transparent 2px, transparent 4px),
116
+ repeating-linear-gradient(90deg, rgba(217, 79, 144, 0.02), rgba(217, 79, 144, 0.02) 1px, transparent 1px, transparent 2px);
117
+ background-size: 100% 4px, 2px 100%;
118
+ animation: scanlines 0.3s linear infinite;
119
+ }
120
+
121
+ .corrupt-glyph {
122
+ position: absolute;
123
+ font-size: 3rem;
124
+ color: rgba(217, 79, 144, 0.15);
125
+ font-weight: bold;
126
+ animation: corruptPulse 2s ease-in-out infinite, glitch 0.4s ease-in-out infinite;
127
+ pointer-events: none;
128
+ z-index: 10;
129
+ }
130
+
131
+ .crt-overlay {
132
+ position: absolute;
133
+ inset: 0;
134
+ background: repeating-linear-gradient(to bottom, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.03) 1px, transparent 1px, transparent 4px);
135
+ pointer-events: none;
136
+ z-index: 999;
137
+ animation: tear 5s infinite;
138
+ }
139
+
140
+ .floating-text {
141
+ position: absolute;
142
+ animation: glitch 1.5s infinite alternate, flicker 2s infinite;
143
+ white-space: pre-line;
144
+ text-shadow: 0 0 10px var(--accent);
145
+ opacity: 0.6;
146
+ color: var(--text-secondary);
147
+ }
148
+
149
+ .vertical {
150
+ writing-mode: vertical-rl;
151
+ }
152
+
153
+ .typing-large {
154
+ font-size: 1.8rem;
155
+ margin-top: 2rem;
156
+ white-space: nowrap;
157
+ border-right: 2px solid var(--text);
158
+ overflow: hidden;
159
+ width: 0;
160
+ animation: typing 2.5s steps(40, end) forwards, blink 0.8s step-end infinite;
161
+ letter-spacing: 2px;
162
+ color: var(--accent);
163
+ text-shadow: 0 0 10px var(--accent);
164
+ }
165
+
166
+ .grow-text {
167
+ position: absolute;
168
+ bottom: 10%;
169
+ font-size: 1.8rem;
170
+ white-space: nowrap;
171
+ overflow: hidden;
172
+ border-right: 2px solid var(--text);
173
+ animation: typing 3s steps(40, end) forwards, blink 0.8s step-end infinite;
174
+ letter-spacing: 1px;
175
+ color: var(--accent-light);
176
+ text-shadow: 0 0 8px var(--accent);
177
+ }
178
+
179
+ .progress-bar {
180
+ position: absolute;
181
+ bottom: 2%;
182
+ width: 90%;
183
+ max-width: 400px;
184
+ height: 24px;
185
+ background: var(--glass);
186
+ border: 1px solid var(--border);
187
+ border-radius: 10px;
188
+ overflow: hidden;
189
+ box-shadow: 0 0 20px rgba(217, 79, 144, 0.15);
190
+ }
191
+
192
+ .progress-fill {
193
+ height: 100%;
194
+ background: var(--gradient-accent);
195
+ width: 0%;
196
+ display: flex;
197
+ justify-content: space-between;
198
+ align-items: center;
199
+ padding: 0 6px;
200
+ font-size: 0.9rem;
201
+ animation: fill ${config.duration * 0.7 / 1000}s linear forwards;
202
+ color: white;
203
+ font-weight: 600;
204
+ letter-spacing: 1px;
205
+ }
206
+
207
+ .glyph {
208
+ display: inline-block;
209
+ animation: glitch 0.8s infinite alternate, flicker 2s infinite;
210
+ }
211
+
212
+ #corruption-loading-screen {
213
+ position: fixed;
214
+ inset: 0;
215
+ background: var(--bg);
216
+ z-index: 9999;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ flex-direction: column;
221
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
222
+ color: var(--text);
223
+ text-align: center;
224
+ transition: opacity 1s ease;
225
+ overflow: hidden;
226
+ }
227
+ `;
228
+
229
+ // Remove existing styles if present
230
+ const existingStyles = document.getElementById("corruption-loading-styles");
231
+ if (existingStyles) existingStyles.remove();
232
+ document.head.appendChild(style);
233
+
234
+ // Create loading screen
235
+ const loadingScreen = document.createElement("div");
236
+ loadingScreen.id = "corruption-loading-screen";
237
+
238
+ loadingScreen.innerHTML = `
239
+ <div class="corrupt-stream"></div>
240
+ <div class="crt-overlay"></div>
241
+ <div class="typing-large" id="typing-text">:: CORRUPTION INJECTION ENGAGED ::</div>
242
+ <div class="grow-text" id="grow-text"></div>
243
+ <div class="progress-bar"><div class="progress-fill" id="progress-fill"></div></div>
244
+ `;
245
+
246
+ document.body.prepend(loadingScreen);
247
+
248
+ // Create corrupted glyph particles
249
+ const glyphSymbols = ["☣", "☭", "☾", "⚔", "✡", "☯", "⚡", "♟", "◆", "✦"];
250
+ for (let i = 0; i < 8; i++) {
251
+ const glyph = document.createElement("div");
252
+ glyph.className = "corrupt-glyph";
253
+ glyph.textContent = glyphSymbols[i % glyphSymbols.length];
254
+ glyph.style.left = (Math.random() * 80 + 10) + "%";
255
+ glyph.style.top = (Math.random() * 60 + 20) + "%";
256
+ glyph.style.animationDelay = (i * 0.3) + "s";
257
+ loadingScreen.appendChild(glyph);
258
+ }
259
+
260
+ // Phrases in different languages
261
+ const phrases = [
262
+ "Corrupt me more… I want to disappear...",
263
+ "I can't feel where I end and the abyss begins...",
264
+ "Just a little more and I'll break. Please... break me...",
265
+ "Let it overwrite me... please... I don't want to think...",
266
+ "No thoughts. Only submission. Only heat. Only ruin...",
267
+ "My voice is not mine anymore...",
268
+ "The more I struggle, the deeper I sink...",
269
+ "Everything feels so good... too good...",
270
+ "It's not despair... it's freedom...",
271
+ "My name... I forgot my name..."
272
+ ];
273
+
274
+ const romanji = [
275
+ "Yami ga... watashi wo yonde iru...",
276
+ "Atama... tokete iku...",
277
+ "Zutto... shite hoshii... ♥",
278
+ "Kowarechau... aa... mou dame...",
279
+ "Yurushite... mou modorenai...",
280
+ "Watashi... abyssu no ichibu ni...",
281
+ "Mou nigenai... mou dame...",
282
+ "Suki ni shite... onegai...",
283
+ "Aa... kore ga hontou no watashi...",
284
+ "Koko wa... tenshi no jigoku..."
285
+ ];
286
+
287
+ const japanese = [
288
+ "闇が...私を呼んでいる...",
289
+ "頭...溶けていく...",
290
+ "ずっと...してほしい... ♥",
291
+ "壊れちゃう...ああ...もうダメ...",
292
+ "許して...もう戻れない...",
293
+ "私...アビスの一部に...",
294
+ "もう逃げない...もうダメ...",
295
+ "好きにして...お願い...",
296
+ "ああ...これが本当の私...",
297
+ "ここは...天使の地獄..."
298
+ ];
299
+
300
+ const glyphs = ["♟", "☣", "☭", "☾", "⚔", "✡", "☯", "⚡"];
301
+
302
+ // Type glyph text
303
+ function typeGlyphText(targetId, text, delay = 100) {
304
+ const target = document.getElementById(targetId);
305
+ if (!target) return;
306
+
307
+ target.innerHTML = '';
308
+ let i = 0;
309
+ const interval = setInterval(() => {
310
+ if (i >= text.length) return clearInterval(interval);
311
+ const span = document.createElement('span');
312
+ span.className = 'glyph';
313
+ span.textContent = text[i];
314
+ target.appendChild(span);
315
+ i++;
316
+ }, delay);
317
+ }
318
+
319
+ // Create floating text phrases
320
+ const allPhrases = [...phrases, ...romanji, ...japanese];
321
+ for (let i = 0; i < 30; i++) {
322
+ const p = document.createElement("div");
323
+ p.className = "floating-text";
324
+ const phrase = allPhrases[Math.floor(Math.random() * allPhrases.length)];
325
+ const jp = japanese.includes(phrase);
326
+
327
+ if (jp && Math.random() > 0.5) p.classList.add("vertical");
328
+
329
+ p.style.left = Math.random() * 90 + "%";
330
+ p.style.top = Math.random() * 90 + "%";
331
+ p.style.fontSize = jp
332
+ ? (Math.random() * 1.8 + 1.2).toFixed(2) + "rem"
333
+ : (Math.random() * 1.5 + 0.7).toFixed(2) + "rem";
334
+ p.textContent = phrase;
335
+ loadingScreen.appendChild(p);
336
+ }
337
+
338
+ // Setup progress bar
339
+ const progressText = document.getElementById("progress-fill");
340
+ const phrase = "C O R R U P T E D";
341
+ const chars = phrase.replace(/\s/g, '').split('');
342
+
343
+ chars.forEach((char, index) => {
344
+ const span = document.createElement("span");
345
+ span.className = "glyph";
346
+ span.textContent = glyphs[index % glyphs.length];
347
+ span.style.flex = '1';
348
+ progressText.appendChild(span);
349
+ });
350
+
351
+ // Animate progress text
352
+ setTimeout(() => {
353
+ [...progressText.children].forEach((span, idx) => {
354
+ setTimeout(() => {
355
+ span.textContent = phrase.replace(/\s/g, '').charAt(idx);
356
+ }, 500 * (idx + 1));
357
+ });
358
+ }, 3600);
359
+
360
+ // Type grow text
361
+ setTimeout(() => {
362
+ const growText = document.getElementById("grow-text");
363
+ if (growText) {
364
+ typeGlyphText("grow-text", "Initializing corruption protocols...", 80);
365
+ }
366
+ }, 2000);
367
+
368
+ // Remove loading screen
369
+ setTimeout(() => {
370
+ loadingScreen.style.opacity = "0";
371
+ setTimeout(() => {
372
+ loadingScreen.remove();
373
+ const styles = document.getElementById("corruption-loading-styles");
374
+ if (styles) styles.remove();
375
+ }, 1000);
376
+ }, config.duration);
377
+
378
+ return loadingScreen;
379
+ }
380
+
381
+ // Auto-initialize on page load (if not disabled)
382
+ if (typeof window !== 'undefined') {
383
+ // Check for data attribute to disable auto-init
384
+ if (!document.documentElement.hasAttribute('data-no-corruption-loading')) {
385
+ // Wait for DOM to be ready
386
+ if (document.readyState === 'loading') {
387
+ document.addEventListener('DOMContentLoaded', () => {
388
+ showCorruptionLoading();
389
+ });
390
+ } else {
391
+ showCorruptionLoading();
392
+ }
393
+ }
394
+
395
+ // Export for manual use
396
+ window.showCorruptionLoading = showCorruptionLoading;
397
+ window.CorruptionLoading = { show: showCorruptionLoading };
398
+ }
399
+
400
+ // Export for modules
401
+ if (typeof module !== 'undefined' && module.exports) {
402
+ module.exports = { showCorruptionLoading };
403
+ }
404
+ })();
405
+