A production-ready glassmorphic design system for cinematic, cyberpunk-inspired applications. Built as a drop-in CSS framework with JS enhancements, Docker showcase, and npm distribution standards on par with Meta/Google/Netflix internal libraries.
🔗 Live Examples: corrupted.whykusanagi.xyz — every demo in examples/ served on Cloudflare Workers. Start at the Examples Index or jump straight to Buffer Corruption (with 18+ toggle), Character Corruption, GLSL Vortex, or Particle Background.
- Overview
- Installation
- CDN Distribution
- Project Architecture
- Container Layout (0.2.0 Breaking Change)
- Canonical Data (JSON Source of Truth)
- NSFW Option Canonicalization
- Base Layout & Background
- CSS & JS Imports
- Component Quick Reference
- Interactive Components
- Animations & Experience Layer
- Lifecycle Management
- Nikke Utilities
- Extension Components
- Customization & Tokens
- Coding Standards
- Development Workflow
- Testing & QA Expectations
- Support
- Celeste Widget Integration
- License
- Glassmorphism-first visual language with layered depth, gradients, and scanlines.
- Systemized tokens (
src/css/variables.css) for colors, typography, spacing, motion, and elevation. - Bootstrap-scale coverage of components (navigation, forms, data display, API docs, Nikke-specific UI).
- Buffer corruption effects with SFW/NSFW phrase modes (Pattern 1 & 2 from spec).
- WCAG AA compliance, motion safety, and keyboard support baked in.
- Dockerized showcase at
examples/showcase-complete.htmlfor instant QA.
This package includes two corruption animation modes:
SFW Mode (Default)
- Playful anime-style expressions
- Cute/teasing phrases
- Atmospheric corruption themes
- Safe for professional and public projects
NSFW Mode (Opt-in Required)
⚠️ 18+ Content Warning- Explicit intimate/sexual phrases
- Loss of control themes
- NOT suitable for:
- Professional/corporate projects
- Public streams without 18+ rating
- Educational contexts
- All-ages content
All examples and default behavior use SFW mode. NSFW requires explicit { nsfw: true } configuration.
npm install @whykusanagi/corrupted-theme@import '@whykusanagi/corrupted-theme';
/* or scoped imports */
@import '@whykusanagi/corrupted-theme/variables';
@import '@whykusanagi/corrupted-theme/components';Tip: make sure you are logged in with
npm loginif the package is private. No.npmrctoken is needed for the public release.
<!-- Pinned version (recommended for production) -->
<link rel="stylesheet"
href="https://cdn.nikkers.cc/corrupted-theme/@0.2.0/dist/theme.min.css">
<!-- Floating @latest (use only for sites you control and update together) -->
<link rel="stylesheet"
href="https://cdn.whykusanagi.xyz/corrupted-theme/@latest/dist/theme.min.css">Use cdn.nikkers.cc for sites under nikkers.cc, and cdn.whykusanagi.xyz for sites under whykusanagi.xyz — same-origin loads skip CORS and need no extra CSP entries. See CDN Distribution and docs/CDN_CONSUMPTION.md for full guidance.
Copy src/css into your project (or use dist/theme.min.css) and import it locally.
corrupted-theme is served from a Cloudflare R2 bucket bound to two domains:
cdn.whykusanagi.xyz— for sites underwhykusanagi.xyzcdn.nikkers.cc— for sites undernikkers.cc
Both domains serve the same content. Use the domain that matches your site's root to keep the load same-origin, which avoids CORS preflights and simplifies CSP policy.
Pinned version (production-safe — breaking changes never auto-propagate):
<link rel="stylesheet"
href="https://cdn.nikkers.cc/corrupted-theme/@0.2.0/dist/theme.min.css">
<script type="module"
src="https://cdn.nikkers.cc/corrupted-theme/@0.2.0/dist/corrupted-text.min.js"></script>Floating @latest (first-party sites that publish together — updates within ~5 minutes):
<link rel="stylesheet"
href="https://cdn.whykusanagi.xyz/corrupted-theme/@latest/dist/theme.min.css">For production hardening, add SRI hashes (published in CHANGELOG.md for each release; regenerate with npm run generate-sri). See docs/CDN_CONSUMPTION.md for the same-origin rule, CSP guidance, CORS allowlist, and JSON data fetching.
.
├── src/
│ ├── css/
│ │ ├── variables.css # design tokens
│ │ ├── typography.css # font stack + hierarchy
│ │ ├── glassmorphism.css # glass utilities (dark/subtle/gradient + modifiers)
│ │ ├── animations.css # motion + corruption keyframes (.glitch-word etc.)
│ │ ├── components.css # UI primitives + layouts (incl. carousel)
│ │ ├── utilities.css # spacing, flex, layout utilities
│ │ ├── toast.css # Toast notification styles (0.2.0)
│ │ ├── seamless-background.css # multi-layer parallax tiled background (0.2.0)
│ │ └── theme.css # entry point (imports all modules)
│ ├── data/ # canonical JSON source of truth (0.2.0)
│ │ ├── phrases.json # SFW+NSFW phrases, 6 context pools × 3 languages
│ │ ├── charsets.json # katakana / hiragana / kanji / symbols / blocks
│ │ ├── colors.json # 6-hex palette + semantic-use map
│ │ └── schemas/ # AJV schemas for each JSON file
│ ├── core/
│ │ ├── timer-registry.js # lifecycle: tracked setTimeout/setInterval/rAF
│ │ ├── event-tracker.js # lifecycle: tracked addEventListener
│ │ ├── corruption-charsets.js # CorruptionCharsets registry (0.2.0)
│ │ ├── decrypt-reveal.js # DecryptReveal fixed-length decryption (0.2.0)
│ │ ├── websocket-manager.js # WebSocketManager auto-reconnect (0.2.0)
│ │ ├── random-utils.js # randomPick / randomInt / shuffle … (0.2.0)
│ │ ├── time-utils.js # formatTime / timeAgo / formatDuration … (0.2.0)
│ │ ├── clipboard-helpers.js # copyWithFeedback (0.2.0)
│ │ └── url-state.js # serializeFormToParams / buildShareUrl (0.2.0)
│ └── lib/
│ ├── animation-blocks.js # 10 block classes: TitleDecoder, ProgressBar … (0.2.0)
│ ├── carousel.js # carousel/slideshow with autoplay + swipe
│ ├── celeste-proxy.js # Celeste CLI proxy integration
│ ├── celeste-widget.js # Celeste chat widget
│ ├── character-corruption.js # auto-corruption for individual characters
│ ├── clock-widget.js # multi-timezone cycling clock (0.2.0)
│ ├── components.js # modal, dropdown, tabs, collapse, accordion, toast
│ ├── corrupted-particles-background.js # auto-injector background particles (0.2.0)
│ ├── corrupted-particles.js # Canvas 2D floating phrase particle background
│ ├── corrupted-text.js # multi-language glitch animation
│ ├── corrupted-vortex.js # WebGL raymarched spiral vortex shader
│ ├── corruption-loading.js # cinematic loading curtain
│ ├── countdown-widget.js # event countdown with shapes
│ ├── crt-effects.js # CRT post-processing layer (0.2.0)
│ ├── event-bar.js # scrolling event-ticker banner (0.2.0)
│ ├── gallery.js # gallery grid with filtering + lightbox
│ ├── lightbox.js # standalone lightbox (extracted 0.2.0)
│ ├── logo-banner.js # animated logo/title banner (0.2.0)
│ ├── nsfw-reveal.js # age-gate click-to-reveal overlay (0.2.0)
│ ├── png-export.js # exportElementAsPng (html2canvas peer dep, 0.2.0)
│ └── toast.js # Toast singleton (0.2.0)
├── dist/
│ ├── theme.min.css # postcss + cssnano build output
│ └── timer-registry.global.js # UMD build for IIFE contexts (0.2.0)
├── cdn-worker/ # Cloudflare Worker for @latest CDN routing (0.2.0)
├── examples/
│ ├── showcase-complete.html # full design system demo
│ ├── interactive-components.html # modal, dropdown, tabs, carousel demo
│ ├── basic/
│ │ ├── corrupted-text.html # CorruptedText demo
│ │ ├── multi-gallery.html # multi-instance gallery demo
│ │ └── typing-animation.html # TypingAnimation demo (with NSFW toggle)
│ └── advanced/
│ ├── glsl-vortex.html # CorruptedVortex WebGL demo
│ └── particles-bg.html # CorruptedParticles demo
├── scripts/static-server.js # ESM static server (Docker)
└── docs/
├── COMPONENTS_REFERENCE.md # exhaustive component snippets (28+ entries)
├── CDN_CONSUMPTION.md # CDN setup, same-origin rule, SRI, CSP
├── CROSS_LANGUAGE_CONTRACT.md # phrases/charsets JSON schema + Go embed guide
├── MIGRATION_CONTAINER_0.2.0.md # .container breaking change migration guide
└── governance/ # versioning, design governance, contribution guides
npm scripts
npm run build– compilesdist/theme.min.cssnpm run watch– rebuilds on change (dev use)npm run dev:static– serves/examples(port 8000)npm run dev:proxy– Celeste proxy (port 5000)npm run validate-data– AJV schema validation forsrc/data/*.json(0.2.0)npm run generate-sri– generate SRI hashes for CDN consumers (0.2.0)npm run publish-cdn– uploaddist/+src/data/to R2, bump@latestpointer (0.2.0)
Breaking change. The
.containerclass is now structural-only in 0.2.0 — it appliesmax-width,margin-inline: auto, and horizontal padding, but no grid layout. Sites that previously relied on the implicit grid behavior must add a modifier class.
/* 0.2.0 — base is structural only */
.container { max-width: 1200px; margin-inline: auto; padding-inline: 1rem; }
/* Opt-in modifiers */
.container--grid-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
.container--grid-3col { display: grid; grid-template-columns: repeat(3,1fr); gap: 2rem; }
.container--with-bg { background: var(--glass); border: 1px solid var(--border); border-radius: var(--radius-2xl); backdrop-filter: blur(15px); }
.container--fullscreen { max-width: none; min-height: 100vh; }
.container--centered { display: flex; flex-direction: column; align-items: center; justify-content: center; }Migration: If you were forcing a 2-column grid, add container--grid-2col. If you had workaround rules that unset-ed .container, remove them — the base no longer fights downstream layouts. Pin to ^0.1.9 in the meantime if you cannot migrate yet.
See docs/MIGRATION_CONTAINER_0.2.0.md for the full guide.
0.2.0 introduces a src/data/ layer that all internal corruption modules (and downstream Go consumers via go:embed) read from instead of maintaining inline arrays:
| File | Contents |
|---|---|
src/data/phrases.json |
SFW + NSFW phrases across 6 context pools (data, system, status, void, memory, glitch) × 3 languages |
src/data/charsets.json |
katakana, hiragana, kanji, symbols, blocks |
src/data/colors.json |
6-hex palette + semantic-use map |
The context-aware phrase API:
import { getPhraseByContext } from '@whykusanagi/corrupted-theme/corruption-phrases';
// Pick a phrase from a specific context pool
const phrase = getPhraseByContext('system', false); // SFW system pool
const nsfw = getPhraseByContext('void', true); // NSFW void pool
// Or consume the JSON directly
import phrases from '@whykusanagi/corrupted-theme/data/phrases.json' with { type: 'json' };
const pool = phrases.sfw.japanese.status;Each file has a schemaVersion field. Additive changes (new pool, new charset) bump the minor version; breaking changes bump the major version. Run npm run validate-data to verify the JSON against AJV schemas before publishing.
See docs/CROSS_LANGUAGE_CONTRACT.md for the Go go:embed integration guide and stability guarantees.
0.2.0 canonicalizes the NSFW option name to nsfw across all components. The previous per-component names are deprecated aliases that emit a one-time console.warn and will be removed in 0.3.x.
| Component | Old (deprecated) | New (canonical) |
|---|---|---|
CorruptedParticles |
includeLewd |
nsfw |
animation-blocks |
lewdMode |
nsfw |
TypingAnimation |
nsfw |
nsfw (unchanged) |
DecryptReveal |
— | — |
// Correct — all components now use the same option name
new CorruptedParticles(canvas, { nsfw: false }); // was: { includeLewd: false }
new TitleDecoder(el, { nsfw: false }); // was: { lewdMode: false }All defaults are nsfw: false. Opt-in to NSFW content only in 18+ or explicit-consent contexts.
<body>
<div class="glass-backdrop"></div>
<main class="app-shell"><!-- your glass UI --></main>
</body><body>
<video class="background-media" autoplay muted loop playsinline>
<source src="/media/corruption-loop.mp4" type="video/mp4" />
</video>
<div class="glass-backdrop"></div>
<main class="app-shell"><!-- your glass UI --></main>
</body>html, body { min-height: 100vh; background: var(--bg); margin: 0; }
.background-media { position: fixed; inset: 0; object-fit: cover; z-index: var(--z-negative); }
.glass-backdrop { position: fixed; inset: 0; background: linear-gradient(180deg, rgba(5,0,16,.85), rgba(10,10,10,.9)); z-index: var(--z-background); }
.app-shell { position: relative; z-index: var(--z-elevated); padding: clamp(1.5rem, 3vw, 3rem); backdrop-filter: blur(0); }When using video backgrounds, place the navbar outside .app-shell for proper z-index stacking:
<body>
<!-- Background layer -->
<video class="background-media" autoplay muted loop playsinline>
<source src="/media/corruption-loop.mp4" type="video/mp4" />
</video>
<div class="glass-backdrop"></div>
<!-- Navigation MUST be outside app-shell for proper stacking -->
<nav class="navbar">
<div class="navbar-content">
<a class="navbar-logo" href="/">Brand</a>
<ul class="navbar-links">
<li><a href="#home" class="active">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
</nav>
<!-- Main content -->
<main class="app-shell">
<!-- your glass UI components -->
</main>
</body>The theme uses a systematic z-index scale defined in variables.css:
| Token | Value | Purpose |
|---|---|---|
--z-negative |
-2 |
Background media (video/image) |
--z-background |
-1 |
Glass backdrop overlay |
--z-base |
0 |
Default stacking |
--z-elevated |
1 |
App shell and content |
--z-navbar |
1000 |
Navigation (always above content) |
--z-modal |
1000 |
Modals and overlays |
Important: The navbar uses
z-index: 1000to ensure it always appears above all content, including video backgrounds and elevated containers.
The simplest approach — import everything in one line:
<!-- HTML -->
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/dist/theme.min.css">/* CSS */
@import '@whykusanagi/corrupted-theme';✅ Recommended for most projects. Includes all styles in the correct order automatically.
Import only the modules you need for smaller bundle sizes.
⚠️ CRITICAL: Import order matters! Modules have dependencies that require specific ordering.
/* Correct order (matches theme.css structure) */
@import '@whykusanagi/corrupted-theme/variables'; /* 1. Foundation tokens */
@import '@whykusanagi/corrupted-theme/typography'; /* 2. Font styles */
@import '@whykusanagi/corrupted-theme/glassmorphism'; /* 3. Glass effects */
@import '@whykusanagi/corrupted-theme/animations'; /* 4. Keyframes - MUST come before components */
@import '@whykusanagi/corrupted-theme/components'; /* 5. UI components - MUST come after animations */
@import '@whykusanagi/corrupted-theme/utilities'; /* 6. Utility classes */
/* 0.2.0 additions (opt-in) */
@import '@whykusanagi/corrupted-theme/toast-css'; /* Toast notification styles */
@import '@whykusanagi/corrupted-theme/seamless-background';/* Parallax tiled background */<!-- Correct order (matches theme.css structure) -->
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/src/css/variables.css">
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/src/css/typography.css">
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/src/css/glassmorphism.css">
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/src/css/animations.css"> <!-- MUST come before components -->
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/src/css/components.css"> <!-- MUST come after animations -->
<link rel="stylesheet" href="node_modules/@whykusanagi/corrupted-theme/src/css/utilities.css">components.cssuses animation keyframes defined inanimations.css- If
components.cssloads beforeanimations.css, spinner and loading animations won't work - Always verify order by checking
src/css/theme.css(shows canonical import structure)
// Interactive components (modal, dropdown, tabs, collapse, accordion, toast)
import { initComponents, destroyComponents, showToast, toast }
from '@whykusanagi/corrupted-theme/components-js';
// Carousel (separate module)
import { initCarousel } from '@whykusanagi/corrupted-theme/carousel';
// Corruption effects
import { initCorruptedText } from '@whykusanagi/corrupted-theme/corrupted-text';
import { initGallery, destroyGallery } from '@whykusanagi/corrupted-theme/gallery';
import { initCountdown } from '@whykusanagi/corrupted-theme/countdown';
// 0.2.0 — Drift reconvergence (canonical corruption layer)
import { CorruptionCharsets } from '@whykusanagi/corrupted-theme/corruption-charsets';
import { DecryptReveal, decodeText }
from '@whykusanagi/corrupted-theme/decrypt-reveal';
import { CRTEffects, applyCRTGlow } from '@whykusanagi/corrupted-theme/crt-effects';
import { TitleDecoder, ProgressBar, ScanlineSweep, TerminalBoot, GlitchPulse,
ASCIIBorder, SystemDiagnostic, LoadingBarMulti, DataTransmission, TerminalPrompt,
playParallel, playSequence, playStaggered }
from '@whykusanagi/corrupted-theme/animation-blocks';
import CorruptedParticlesBackground
from '@whykusanagi/corrupted-theme/corrupted-particles-background';
// 0.2.0 — Widgets
import { Toast } from '@whykusanagi/corrupted-theme/toast';
import { ClockWidget } from '@whykusanagi/corrupted-theme/clock-widget';
import { EventBar } from '@whykusanagi/corrupted-theme/event-bar';
import { LogoBanner } from '@whykusanagi/corrupted-theme/logo-banner';
import { Lightbox } from '@whykusanagi/corrupted-theme/lightbox';
import { NsfwReveal } from '@whykusanagi/corrupted-theme/nsfw-reveal';
import { exportElementAsPng } from '@whykusanagi/corrupted-theme/png-export';
import { WebSocketManager } from '@whykusanagi/corrupted-theme/websocket-manager';
// 0.2.0 — Core utilities
import { randomPick, randomInt, shuffle } from '@whykusanagi/corrupted-theme/random-utils';
import { formatTime12h, timeAgo } from '@whykusanagi/corrupted-theme/time-utils';
import { copyWithFeedback } from '@whykusanagi/corrupted-theme/clipboard-helpers';
import { buildShareUrl, applyParamsToForm } from '@whykusanagi/corrupted-theme/url-state';Components auto-initialize on DOMContentLoaded when imported. For manual control:
initComponents(); // scan DOM for data-ct-* attributes
destroyComponents(); // tear down all managers and tracked listenersThe snippets below mirror the canonical showcase. For the full catalog (tabs, modals, tables, API docs, Nikke cards, etc.) keep docs/COMPONENTS_REFERENCE.md open beside this README.
<div class="glass-card p-xl">
<h3>Glass Card</h3>
<p>Enhanced glass depth layer.</p>
<input class="glass-input" placeholder="glass input" />
<button class="glass-button">Primary Action</button>
<pre class="glass-code">const example = 'Hello';</pre>
</div><div class="flex-center gap-md">
<button class="btn">Primary</button>
<button class="btn secondary">Secondary</button>
<button class="btn ghost">Ghost</button>
<button class="btn sm">Compact</button>
<button class="btn lg block">Full Width</button>
</div><form class="glass-card p-xl">
<label>Name</label>
<input type="text" class="glass-input" required>
<label>Message</label>
<textarea class="glass-input" rows="3"></textarea>
<button class="glass-button">Send</button>
</form><nav class="navbar">
<div class="navbar-content">
<a class="navbar-logo" href="/">whyku</a>
<ul class="navbar-links">
<li><a href="#glass" class="active">Glass</a></li>
<li><a href="#components">Components</a></li>
<li class="has-submenu">
<a href="#products">Products <i class="fas fa-chevron-down"></i></a>
<div class="submenu">
<a href="#one">Product 1</a>
<a href="#two">Product 2</a>
</div>
</li>
</ul>
</div>
</nav>
<button class="navbar-toggle" onclick="toggleNavbar(this)">
<span class="icon"><span></span><span></span><span></span></span>
</button>
<ul class="navbar-links"><!-- mobile nav --></ul>function toggleNavbar(button) {
const menu = document.querySelector('.navbar-links');
menu.classList.toggle('active');
button.classList.toggle('active');
}Zero-JS (data attributes):
<div class="dropdown">
<button class="dropdown-toggle" data-ct-toggle="dropdown">Menu</button>
<div class="dropdown-menu">
<a class="dropdown-item">Action</a>
<a class="dropdown-item">Settings</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item">Logout</a>
</div>
</div>Manual JS (legacy):
<div class="dropdown">
<button class="dropdown-toggle" onclick="toggleDropdown(this)">Menu</button>
<div class="dropdown-menu">
<a class="dropdown-item">Action</a>
</div>
</div>function toggleDropdown(button) {
const menu = button.nextElementSibling;
const open = menu.classList.contains('active');
document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('active'));
document.querySelectorAll('.dropdown-toggle').forEach(b => b.classList.remove('active'));
if (!open) { menu.classList.add('active'); button.classList.add('active'); }
}
document.addEventListener('click', e => {
if (!e.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('active'));
document.querySelectorAll('.dropdown-toggle').forEach(b => b.classList.remove('active'));
}
});<table class="table table-striped">
<thead><tr><th>Name</th><th>Email</th><th>Status</th></tr></thead>
<tbody>
<tr><td>Eve</td><td>eve@demo.com</td><td><span class="badge success">Active</span></td></tr>
<tr><td>Dan</td><td>dan@demo.com</td><td><span class="badge warning">Pending</span></td></tr>
</tbody>
</table>
<div class="api-endpoint">
<div class="flex items-center gap-md">
<span class="api-method get">GET</span>
<code class="api-path">/api/v1/units</code>
</div>
<p class="api-description">Retrieve a list of units.</p>
<div class="api-param">
<span class="api-param-name">element</span>
<span class="api-param-type">string</span>
<span class="api-param-required">optional</span>
<p class="api-param-description">Filter by element type.</p>
</div>
<div class="api-response">
<div class="api-response-title">200 OK</div>
<pre class="api-response-code">{"data": []}</pre>
</div>
</div>Pattern 1: Character-Level Corruption (Visual Glitch)
<!-- Multi-language cycling with character-level glitch -->
<span class="corrupted-multilang"
data-english="Hello World"
data-romaji="konnichiwa"
data-hiragana="こんにちは"
data-katakana="コンニチハ"
data-kanji="今日は">
</span>
<script type="module" src="@whykusanagi/corrupted-theme/src/lib/corrupted-text.js"></script>Pattern 2: Phrase Flickering (Buffer Corruption)
<!-- Typing animation with SFW phrase buffer corruption -->
<div class="typing-output" id="typing1"></div>
<script type="module">
import { TypingAnimation } from '@whykusanagi/corrupted-theme/typing-animation';
const typing = new TypingAnimation(document.getElementById('typing1'), {
duration: 2000, // 2s total typing pass
bufferEnabled: true, // always-on phrase buffer (default)
bufferFlickerSpeed: 100, // 100ms between buffer phrase swaps
nsfw: false // SFW mode (default)
});
typing.start('Neural corruption detected... System Online');
</script>- SFW Mode (Default): Cute, playful, atmospheric phrases - safe for all audiences
- NSFW Mode (Opt-in): Explicit 18+ content - requires
{ nsfw: true }flag
See examples/basic/typing-animation.html — it has a toggle switch at the top to opt into NSFW phrases (checkbox resets each page load per the explicit-opt-in spec).
Named charset registry backed by src/data/charsets.json.
import { CorruptionCharsets } from '@whykusanagi/corrupted-theme/corruption-charsets';
CorruptionCharsets.katakana; // primary corruption glyphs
CorruptionCharsets.standard; // katakana + symbols (matrix-style)
CorruptionCharsets.soft; // hiragana only (gentle degradation)
CorruptionCharsets.intense; // kanji + blocks (heavy data-loss)
CorruptionCharsets.all; // union of every setSee COMPONENTS_REFERENCE.md for all sets.
Fixed-length decryption animation. The target string is shown at its final length from frame 1 (scrambled with charset characters), then progressively resolves left-to-right to the target text.
Distinct from TypingAnimation — TypingAnimation grows the string over time (streaming/typed reveal with phrase-buffer flicker in the not-yet-revealed tail). DecryptReveal keeps the string at final length and scrambles unrevealed positions.
import { DecryptReveal, decodeText } from '@whykusanagi/corrupted-theme/decrypt-reveal';
const dr = new DecryptReveal({ charset: CorruptionCharsets.standard });
dr.decode(el, 'SYSTEM READY', { duration: 2000 });
dr.decode(el, 'アクセス許可', { duration: 3000, charset: CorruptionCharsets.kanji });
dr.stop(); // cancel all
dr.destroy(); // full teardown
// One-shot helper (no instance needed) — returns a cancel function
const cancel = decodeText(el, 'NEURAL CORE ONLINE', { duration: 1500 });See COMPONENTS_REFERENCE.md for full method table.
CRT post-processing layer — scanlines, vignette, chromatic aberration, RGB split, screen shake.
import { CRTEffects } from '@whykusanagi/corrupted-theme/crt-effects';
const crt = new CRTEffects(containerEl, { scanlines: true, vignette: true, flicker: false });
crt.start();
crt.screenShake(containerEl, 300, 5); // duration ms, intensity px
crt.destroy();See COMPONENTS_REFERENCE.md for the full options table.
Composable animation blocks that return Promises. Mix with playParallel / playSequence / playStaggered.
import {
TitleDecoder, ProgressBar, ScanlineSweep, TerminalBoot, GlitchPulse,
ASCIIBorder, SystemDiagnostic, LoadingBarMulti, DataTransmission, TerminalPrompt,
playSequence,
} from '@whykusanagi/corrupted-theme/animation-blocks';
await playSequence([
new TitleDecoder(el, { text: 'CORRUPTED STREAM', nsfw: false }),
new ProgressBar(el, { label: 'Loading...', duration: 2000 }),
new TerminalBoot(el, { lines: ['Initializing...', 'Ready.'] }),
]);
lewdModeis a deprecated alias fornsfw— usensfw: truein new code.
See COMPONENTS_REFERENCE.md for all 10 class descriptions.
Auto-injecting background particles (DPR=1 performance mode, sits behind blur layers).
import CorruptedParticlesBackground from '@whykusanagi/corrupted-theme/corrupted-particles-background';
const bg = new CorruptedParticlesBackground({ nsfw: false, count: 25 });
// automatically inserts a fixed canvas before .glass-backdrop
bg.destroy();Singleton notification helper. Auto-mounts its own DOM container on first use.
import { Toast } from '@whykusanagi/corrupted-theme/toast';
Toast.show('Saved');
Toast.success('Submitted!', { duration: 3000 });
Toast.error('Upload failed');
Toast.info('Loading…');Import @whykusanagi/corrupted-theme/toast-css alongside (or include in your theme.css build). See COMPONENTS_REFERENCE.md.
Cycling multi-timezone clock using Intl.DateTimeFormat for DST correctness.
import { ClockWidget } from '@whykusanagi/corrupted-theme/clock-widget';
const widget = new ClockWidget(document.getElementById('clock'), {
timezones: ['America/Los_Angeles', 'Europe/London'],
cycleMs: 10000,
format: '12h',
});
widget.start();
widget.destroy();Scrolling event-ticker banner with label + content + optional icon. Supports live update().
import { EventBar } from '@whykusanagi/corrupted-theme/event-bar';
const eb = new EventBar(document.getElementById('events'), {
items: [{ label: 'Latest Follow', content: '@user1', icon: '★' }],
});
eb.update([{ label: 'Latest Tip', content: '$10.00', icon: '✦' }]);
eb.destroy();Animated logo/title banner with five position presets and reveal animation.
import { LogoBanner } from '@whykusanagi/corrupted-theme/logo-banner';
const banner = new LogoBanner(el, {
src: '/assets/logo.png', subtitle: 'LIVE', position: 'top-right', animation: 'fade',
});
banner.show();
banner.update({ position: 'center' });
banner.destroy();Extracted from gallery.js as its own module. Keyboard (Escape/arrows) + touch-swipe. gallery.js re-exports it for backward compat.
import { Lightbox } from '@whykusanagi/corrupted-theme/lightbox';
const lb = new Lightbox(null, { onOpen: (img, i) => {}, onClose: () => {} });
lb.setImages([{ src: 'a.jpg', alt: 'A', caption: 'Caption' }]);
lb.open(0);
lb.destroy();Discrete phrase-state cycling. Replaces the entire element's text with phrase A, then phrase B, etc., at a fixed interval, then settles on finalText. Optional loop: true cycles forever without settling.
Distinct from TypingAnimation and DecryptReveal: TypingAnimation grows the string character-by-character (streaming/typed — string length increases over time). DecryptReveal shows the string at final length scrambled with charset characters and resolves left-to-right (char-level). PhraseCycle replaces the entire element text each tick — string length may vary between phrases.
Use for: loading screens (Initializing... → Connecting... → Ready.), boot sequences, "decrypting" preambles before a result is shown, glitch transitions between named states.
import { PhraseCycle } from '@whykusanagi/corrupted-theme/phrase-cycle';
// Settling cycle: one pass then display finalText
const cycle = new PhraseCycle(element, {
phrases: ['Initializing...', 'Connecting...', 'Authenticating...'],
interval: 400, // ms between phrase swaps (default: 200)
finalText: 'Ready.', // text written after cycle ends; null = leave last phrase
});
cycle.start();
// Looping: cycle forever with no settle
const spinner = new PhraseCycle(element, {
phrases: ['Loading.', 'Loading..', 'Loading...'],
interval: 300,
loop: true,
});
spinner.start();
spinner.stop(); // pause; last phrase stays visible; reusable
spinner.destroy(); // teardown + release element referenceSee COMPONENTS_REFERENCE.md and the live demo.
Age-gate blur overlay with click-to-reveal. Session persistence handled by the caller.
import { NsfwReveal } from '@whykusanagi/corrupted-theme/nsfw-reveal';
const nr = new NsfwReveal(document.getElementById('img'), {
warning: 'NSFW — click to reveal',
blurPx: 20,
});
nr.reveal(); // programmatic reveal
nr.destroy(); // restore original stateCapture a DOM element as PNG and trigger a download. Requires optional peer dep html2canvas.
import { exportElementAsPng } from '@whykusanagi/corrupted-theme/png-export';
await exportElementAsPng(document.getElementById('card'), {
filename: 'my-card.png',
scale: 2,
});Install the peer dep only when using: npm install html2canvas. See COMPONENTS_REFERENCE.md.
Auto-reconnect WebSocket wrapper with exponential backoff (capped 30s), event-ID dedup, ACK support, and visibility-aware pause/resume.
import { WebSocketManager } from '@whykusanagi/corrupted-theme/websocket-manager';
const ws = new WebSocketManager({ url: 'wss://your-server/ws', autoConnect: false });
ws.on((msg) => console.log(msg));
ws.connect();
ws.destroy();See COMPONENTS_REFERENCE.md for all options.
All utility modules are pure functions with no DOM dependency — safe in Node.js and SSR contexts.
import { randomPick, randomInt, randomFloat, randomVariance, shuffle, randomSample }
from '@whykusanagi/corrupted-theme/random-utils';
randomPick(['a', 'b', 'c']); // random element
randomVariance(50, 0.2); // 50 ± 20%
shuffle([1, 2, 3]); // Fisher-Yates in-place
randomSample([1,2,3,4], 2); // 2 elements without replacementimport { formatTime12h, formatDate, timeAgo, formatDuration }
from '@whykusanagi/corrupted-theme/time-utils';
formatTime12h(); // "02:32 PM"
timeAgo(new Date(Date.now() - 3e5)); // "5m ago"
formatDuration(3661); // "1h 1m 1s" (input: seconds)import { copyWithFeedback } from '@whykusanagi/corrupted-theme/clipboard-helpers';
await copyWithFeedback(btn, 'text to copy', { successLabel: 'COPIED!', durationMs: 1200 });
// Button label briefly becomes "COPIED!" then reverts. Returns Promise<boolean>.import { buildShareUrl, applyParamsToForm } from '@whykusanagi/corrupted-theme/url-state';
const url = buildShareUrl(form, 'https://example.com/embed');
// → "https://example.com/embed?username=alice&dark=1"
applyParamsToForm(form, new URLSearchParams(window.location.search));.scrollbar-corrupted — styled scrollbar (Firefox + WebKit) matching the theme accent colors. Apply to any scrollable container:
<div class="scrollbar-corrupted" style="overflow-y: auto; max-height: 400px;">…</div>seamless-background.css — multi-layer parallax tiled background. Import the CSS module, add .seamless-bg-host to a parent, and set the --seamless-background-image variable:
<body class="seamless-bg-host" style="--seamless-background-image: url('/tile.png');">
<div class="seamless-background seamless-background-mid"></div>
<!-- content -->
</body>See COMPONENTS_REFERENCE.md for the full modifier class list.
v0.1.8 adds JS-driven interactive components that self-initialize via data-ct-* attributes. Import the components module and everything wires up automatically on DOMContentLoaded.
<script type="module">
import '@whykusanagi/corrupted-theme/components-js';
import { initCarousel } from '@whykusanagi/corrupted-theme/carousel';
</script><button class="btn" data-ct-toggle="modal" data-ct-target="#my-modal">Open</button>
<div class="modal-overlay" id="my-modal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">Title</h3>
<button class="modal-close">×</button>
</div>
<div class="modal-body"><p>Content here.</p></div>
<div class="modal-footer">
<button class="btn secondary" data-ct-toggle="modal" data-ct-target="#my-modal">Cancel</button>
<button class="btn" data-ct-toggle="modal" data-ct-target="#my-modal">Confirm</button>
</div>
</div>
</div>Closes via X button, overlay click, or Escape key. Dispatches modal:open / modal:close custom events.
<div class="tabs">
<button class="tab active" data-ct-target="#panel-1">Tab 1</button>
<button class="tab" data-ct-target="#panel-2">Tab 2</button>
</div>
<div class="tab-content active" id="panel-1">Panel 1 content</div>
<div class="tab-content" id="panel-2">Panel 2 content</div><button class="btn" data-ct-toggle="collapse" data-ct-target="#details">Toggle</button>
<div class="collapse" id="details">
<p>Collapsible content.</p>
</div><div class="carousel" data-ct-autoplay data-ct-interval="4000">
<div class="carousel-inner">
<div class="carousel-slide active">Slide 1</div>
<div class="carousel-slide">Slide 2</div>
<div class="carousel-slide">Slide 3</div>
</div>
</div>Features: autoplay, prev/next controls, dot indicators, touch/swipe, keyboard navigation (ArrowLeft/Right). Import separately:
import { initCarousel } from '@whykusanagi/corrupted-theme/carousel';<div class="accordion">
<div class="accordion-item active">
<div class="accordion-header">Question 1</div>
<div class="accordion-body"><p>Answer 1</p></div>
</div>
<div class="accordion-item">
<div class="accordion-header">Question 2</div>
<div class="accordion-body"><p>Answer 2</p></div>
</div>
</div>import { toast } from '@whykusanagi/corrupted-theme/components-js';
toast.success('Operation completed.');
toast.warning('Check your input.');
toast.error('Something went wrong.');
toast.info('FYI: update available.');See examples/interactive-components.html for a live demo of all interactive components.
| Class | Behavior |
|---|---|
.fade-in, .fade-up, .slide-in-left/right, .scale-in |
Standard entrance motions synchronized to var(--transition) |
.glitch-word, .glitch-kanji |
Fragmented glitch with horizontal scanlines + JP overlays |
.corrupted-text, .corrupted-strong |
Brute-force corruption effect for headings and pills |
.scanlines, .tear, .data-corrupt |
Utility effects inspired by whykusanagi.xyz hero |
.spinner, .loading-bar, .progress-bar |
Loading indicators with shimmer + accent variants |
All JS corruption components follow the same lifecycle: new Class(element, options) with start(), stop(), destroy() methods. Full API details are in COMPONENTS_REFERENCE.md.
CorruptedText - Pattern 1: Character-Level Corruption
- Visual glitch effect using random characters (Katakana, Hiragana, symbols)
- Always SFW (no phrases, just character-level noise)
- Cycles through multi-language variants
- Class:
.corrupted-multilang - Demo:
examples/basic/corrupted-text.html
import { initCorruptedText } from '@whykusanagi/corrupted-theme/corrupted-text';
// Auto-init all .corrupted-multilang elements
initCorruptedText();
// Or manual:
const ct = new CorruptedText(element, {
duration: 3000, // total animation duration (ms)
cycleDelay: 100, // delay between corruption steps (ms)
startDelay: 0, // initial delay before animation
loop: true, // loop or settle on final text
finalText: null // text to settle on (null = english variant)
});
ct.start(); // begin animation
ct.stop(); // pause
ct.restart(); // reset to first variant
ct.settle('Hello'); // stop and settle to specific text
ct.destroy(); // full teardownTypingAnimation - Pattern 2: Phrase Flickering (Buffer Corruption)
- Simulates neural network decoding corrupted data buffer
- Phrases flicker through before revealing final text
- SFW mode (default): Cute, playful, atmospheric phrases
- NSFW mode (opt-in): Explicit 18+ content with
{ nsfw: true } - Color: Magenta (#d94f90) for SFW, Purple (#8b5cf6) for NSFW
- Demo:
examples/basic/typing-animation.html
import { TypingAnimation } from '@whykusanagi/corrupted-theme/typing-animation';
const typing = new TypingAnimation(element, {
duration: 2000, // 2s total typing pass
bufferEnabled: true, // always-on phrase buffer (default)
bufferFlickerSpeed: 100, // ms between buffer phrase swaps
nsfw: false, // enable 18+ phrase mode
});
typing.start('Neural corruption detected...');
typing.stop();
typing.settle('READY'); // skip remaining type-out, write final textCorruptedParticles - Canvas 2D Floating Phrase Background
- Floating Japanese phrase particles across three depth layers
- Mouse hover repel, click burst (6 particles), connection lines between nearby particles
- SFW/NSFW phrase modes via
nsfwtoggle (includeLewdis a deprecated alias) - Demo:
examples/advanced/particles-bg.html
import CorruptedParticles from '@whykusanagi/corrupted-theme/corrupted-particles';
const particles = new CorruptedParticles(canvas, {
count: 60, // number of particles
speed: 1.0, // global speed multiplier
lineDistance: 150, // max distance for connection lines (px)
nsfw: false // enable 18+ phrases (default: off)
});
// auto-starts on construction
particles.stop(); // pause animation
particles.start(); // resume
particles.destroy(); // full teardownCorruptedVortex - WebGL Raymarched Spiral Vortex
- WebGL1 raymarched black hole accretion disk shader
- Quasar mode (hue: null) cycles yellow-to-magenta by depth
- Fixed hue mode locks to a single color
- Throttled to ~30fps for GPU efficiency
- Demo:
examples/advanced/glsl-vortex.html
import CorruptedVortex from '@whykusanagi/corrupted-theme/corrupted-vortex';
const vortex = new CorruptedVortex(canvas, {
speed: 1.0, // animation speed multiplier
intensity: 1.0, // brightness/glow intensity
rotationRate: 1.0, // rotation speed of spiral
hue: null // null = quasar multi-color, 0-1 = fixed hue
});
// auto-starts on construction
vortex.stop();
vortex.start();
vortex.destroy();Character Corruption - Auto-Corruption for Individual Characters
- Replaces English characters with Japanese (Katakana, Hiragana, Kanji)
- Configurable intensity levels from
NONE(0) toMAX_READABLE(0.45) - Auto-corruption via
.auto-corruptclass withdata-text,data-intensity,data-intervalattributes - Includes phrase libraries (technical UI + personality/demon phrases)
import {
corruptTextJapanese,
initAutoCorruption,
stopAutoCorruption,
destroyAllAutoCorruption,
createCorruptedElement,
getRandomPhrase,
INTENSITY
} from '@whykusanagi/corrupted-theme/character-corruption';
// Corrupt a string at medium intensity
corruptTextJapanese('Hello World', INTENSITY.MEDIUM); // → "Heアロo ワoケld"
// Auto-init all .auto-corrupt elements on page
initAutoCorruption();
// Create a new auto-corrupting element
const el = createCorruptedElement('System Online', {
intensity: 0.35,
interval: 2000,
className: 'status-text',
tag: 'span'
});
// Get a random phrase from the library
getRandomPhrase('loading'); // → "initializing neural link..."
getRandomPhrase('personality', 'japanese'); // → "データ破損検出..."
// Cleanup
stopAutoCorruption(element); // stop one element
destroyAllAutoCorruption(); // stop allCorruption Loading Screen - Cinematic Loading Curtain
- Full-screen dramatic loading animation
- Module:
src/lib/corruption-loading.js
Corruption Phrases Library
- Normalized SFW/NSFW phrase sets
- Separate exports for each mode
- Helper functions for random phrase selection
- Module:
src/core/corruption-phrases.js
All JS components follow a consistent init/destroy pattern to prevent memory leaks in SPAs and dynamic UIs.
import { destroyComponents } from '@whykusanagi/corrupted-theme/components-js';
import { destroyGallery } from '@whykusanagi/corrupted-theme/gallery';
// Tear down all interactive components (modal, dropdown, tabs, accordion, toast)
destroyComponents();
// Destroy default gallery instance
destroyGallery();import { initGallery } from '@whykusanagi/corrupted-theme/gallery';
const g1 = initGallery('#gallery-1', { enableLightbox: true });
const g2 = initGallery('#gallery-2', { enableLightbox: true });
// Each instance has independent filters, lightbox, and state
g1.destroy(); // only tears down gallery-1See examples/basic/multi-gallery.html for a working multi-instance demo.
The src/core/ directory provides lifecycle primitives used by all components:
- TimerRegistry (
timer-registry.js) — wrapssetTimeout,setInterval, andrequestAnimationFramewith bulkclearAll() - EventTracker (
event-tracker.js) — wrapsaddEventListenerwith bulkremoveAll()
These ensure no orphaned timers or listeners remain after destroy().
<div class="element-pills">
<button class="element-pill water">Water</button>
<button class="element-pill wind active">Wind</button>
<button class="element-pill iron">Iron</button>
<button class="element-pill electric">Electric</button>
<button class="element-pill fire">Fire</button>
</div>
<div class="team-position-cards">
<div class="position-card filled">
<div class="position-header">
<span class="position-number">1</span>
<span class="position-label">Front Left</span>
</div>
<div class="position-content">
<div class="unit-display">
<div class="unit-name">Scarlet Priest Abe</div>
</div>
<button class="remove-unit" aria-label="Remove unit">×</button>
</div>
</div>
</div>All Nikke-specific helpers live alongside the main utilities (src/css/nikke-utilities.css) and observe the same token set, so there are no visual disconnects between game-specific and general UI.
Production-tested components from whykusanagi.xyz for galleries, social links, countdowns, and more. All extension styles are included in theme.css or can be imported separately via extensions.css.
Responsive gallery grid with filtering, lightbox, and NSFW content support. Supports multiple independent instances on the same page.
<div class="filter-bar">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="photos">Photos</button>
</div>
<div class="gallery-container" id="my-gallery">
<div class="gallery-item" data-tags="photos">
<img src="image.jpg" alt="Description">
<div class="gallery-caption">Caption text</div>
</div>
</div>import { initGallery, destroyGallery } from '@whykusanagi/corrupted-theme/gallery';
const gallery = initGallery('#my-gallery', {
enableLightbox: true,
enableNsfw: true,
filterAnimation: true
});
gallery.filter('photos'); // Filter by category
gallery.openLightbox(0); // Open first image
gallery.destroy(); // Clean up (removes lightbox, listeners, timers)Link-in-bio style layout with branded platform colors.
<div class="social-links-container">
<img src="avatar.jpg" class="profile-avatar">
<h1 class="profile-name">@username</h1>
<p class="profile-bio">Your bio here</p>
<div class="link-list">
<a href="#" class="link-item twitter"><i class="fab fa-twitter"></i> Twitter</a>
<a href="#" class="link-item github"><i class="fab fa-github"></i> GitHub</a>
</div>
</div>Event countdown with configurable shapes (diamond, circle, heart, star, hexagon, octagon).
import { initCountdown } from '@whykusanagi/corrupted-theme/countdown';
initCountdown({
config: {
title: 'Launch Countdown',
eventDate: '2025-04-01T00:00:00-07:00',
completedMessage: 'Now Live!',
character: {
image: 'character.png',
background: { type: 'diamond' }
}
}
});Click-to-reveal overlay for sensitive content.
<div class="gallery-item nsfw-content" data-warning="18+ Click to View">
<img src="sensitive-image.jpg" alt="Description">
</div>See examples/extensions-showcase.html for interactive demos and docs/COMPONENTS_REFERENCE.md for complete API documentation.
Override only the tokens you need. The defaults intentionally mirror the showcase.
:root {
--accent: #ff5fb0;
--accent-light: #ff8cd0;
--accent-dark: #c71c7d;
--glass: rgba(15, 10, 25, 0.65);
--glass-light: rgba(24, 14, 42, 0.45);
--glass-darker: rgba(10, 5, 20, 0.65);
--text: #f4e9ff;
--text-secondary: #b8afc8;
--transition-normal: 0.3s ease;
--transition-fast: 0.15s ease;
}Utilities (src/css/utilities.css) provide spacing (.p-xl, .gap-md), layout (.flex, .grid), and elevation helpers so you rarely write bespoke CSS.
These guidelines keep contributions aligned with enterprise frameworks:
- CSS architecture: component classes are flat (
.btn.secondary), tokens live invariables.css, and utilities never override component styles. - Naming: prefer descriptive nouns (e.g.,
.api-endpoint,.glass-card) and suffix modifiers with dot notation (.btn.secondary). Avoid chained hyphen variants unless the base class does not apply (.badge-method). - JavaScript: all helper scripts are ES modules, side-effect-free, and expose initialization functions. Never mutate global scope outside a guarded
DOMContentLoadedblock. - Accessibility: every interactive component includes focus styles, ARIA labels where relevant, and respects
prefers-reduced-motionfallbacks. - Performance: animations use
transform/opacity, heavy filters are scoped to small elements, and layout thrashers are avoided. - Documentation: README +
docs/COMPONENTS_REFERENCE.mdare the single sources of truth. Additions must include runnable snippets and token usage notes. - Versioning: update
CHANGELOG.mdwith semantic sections (Added,Changed,Fixed) before publishing.
npm install # install dependencies
npm run build # compile CSS bundle
npm run watch # dev rebuild loop
npm run dev:static # serve /examples on :8000
npm run dev:proxy # optional Celeste proxy on :5000
npm run validate-data # AJV schema validation for src/data/*.json
npm run generate-sri # generate SRI hashes for CDN consumers
npm run publish-cdn # upload dist/ + src/data/ to R2, bump @latest pointer
# Docker showcase
docker build -t corrupted-theme:latest .
docker run -d -p 8000:8000 --name corrupted-theme corrupted-theme:latest- The Docker container automatically serves
examples/showcase-complete.html. - Provide
CELESTE_*env vars to exercise the widget proxy inside the same container.
- Visual regression: validate against
examples/showcase-complete.htmlin latest Chrome, Firefox, Safari, and a mobile viewport. - Accessibility: run
tabsweeps, screen reader spot checks, andprefers-reduced-motiontoggles. - Performance: Lighthouse performance budget ≥ 90, no long-running animations on background threads.
- Animation review: ensure the first-visit corrupted text + loader remain opt-in via JS flags for SPA integrations.
- GitHub Issues: corrupted-theme/issues
- Email: contact@whykusanagi.xyz
The theme ships with an optional Celeste AI widget that never exposes credentials to the browser. It relies on the hardened proxy bundle located in celeste_widget_pack/.
| Variable | Purpose |
|---|---|
CELESTE_AGENT_KEY |
Bearer token for the Celeste API |
CELESTE_AGENT_ID |
Agent identifier (UUID) |
CELESTE_AGENT_BASE_URL |
API endpoint root |
Store these via your platform’s secret manager (Vault, Doppler, AWS Secrets Manager, etc.). Never commit them or inject them into client-side code.
docker build -t corrupted-theme:latest .
docker run -d \
-p 8000:8000 \ # static showcase
-p 5001:5000 \ # exposes proxy externally
-e CELESTE_AGENT_KEY="$CELESTE_AGENT_KEY" \
-e CELESTE_AGENT_ID="$CELESTE_AGENT_ID" \
-e CELESTE_AGENT_BASE_URL="https://api.your-domain.com" \
corrupted-theme:latest
# Visit http://localhost:8000 (UI) and http://localhost:5001/api/health (proxy)# Terminal 1 – proxy server (port defaults to 5000 inside container)
CELESTE_AGENT_KEY="..." \
CELESTE_AGENT_ID="..." \
CELESTE_AGENT_BASE_URL="..." \
PROXY_PORT=5000 node scripts/celeste-proxy-server.js
# Terminal 2 – static showcase
STATIC_PORT=8000 node scripts/static-server.js- Browser never receives
CELESTE_*variables (verified via DevTools/HTML scans) - All outbound API calls originate from the proxy (
/api/chat,/api/health) - Health endpoint surfaces status without leaking secrets
- Ready for Cloudflare Worker / Pages deployment (see secure pack doc)
For the full hardening guide—including architecture diagrams, Cloudflare steps, and troubleshooting—see celeste_widget_pack/docs/CELESTE_WIDGET_SECURE_SETUP.md.
MIT © whykusanagi
Made with 💎 by whykusanagi
