Release Notes

Corrupted Theme 0.2.1: 14 new components, a real CDN, and security hardening

Released: May 25, 2026 · npm: @whykusanagi/corrupted-theme · Demo: corrupted.whykusanagi.xyz

Corrupted Theme is a dark, glassmorphic design system with pink/purple accents — built for modern, visually distinctive web applications. The 0.2.0 and 0.2.1 releases together push the project from "library of styles" to "production-ready framework" with a real CDN, a live demo, and an end-to-end security pass.

What's new

  • 14 new components — Toast, ClockWidget, EventBar, NsfwReveal, WebSocketManager, LogoBanner, standalone Lightbox, plus CRTEffects, PhraseCycle, DecryptReveal, and a 10-class animation-blocks library covering boot sequences, terminal effects, and decoders.
  • Same-origin CDN distribution at cdn.whykusanagi.xyz and cdn.nikkers.cc with published SRI hashes. One <link> tag, no build step, no third-party trust.
  • Live demo site at corrupted.whykusanagi.xyz showing every component in production — auto-deployed from the package's main branch.
  • Canonical data layer — phrase pools, character sets, and design tokens live in versioned JSON, validated in CI, and consumable across languages (JS, Go via go:embed, and any other runtime that can read JSON).
  • Security pass — CodeQL default setup wired up, three findings closed, secret scanning and Dependabot enabled, all shipped sources hardened.
  • One breaking change.container is now structural-only; opt into glass panels, grids, and centered layouts via modifier classes. Migration is one CSS class per usage. Details below.

14 new components

Each ships as a tree-shakable ES module from @whykusanagi/corrupted-theme with a deep-import path. All components are documented in docs/COMPONENTS_REFERENCE.md and demonstrated on the live demo site.

Interactive widgets

  • Toast — singleton notification helper with Toast.show / success / error / info. Drop-in for any "Copied!", form-saved, or error-banner pattern.
  • ClockWidget — cycling multi-timezone clock built on Intl.DateTimeFormat for DST correctness. Configure 2–4 timezones and a cycle interval, get a stream-overlay-ready widget.
  • EventBar — labelled item list with optional icons. Use it as a site-wide announcement bar, a feature ticker, or a notice strip above the navbar.
  • NsfwReveal — age-gate overlay wrapping any content block, with session-persistent opt-in. Pairs naturally with the package's nsfw: true global setting.
  • Lightbox (standalone) — extracted from the existing gallery component as its own export, so you can use it without the gallery wrapper. The gallery still re-exports it for backwards compatibility.
  • LogoBanner — animated logo or title banner with a corruption flicker effect on mount. Plug into any hero section.
  • WebSocketManager — auto-reconnect wrapper with exponential backoff (capped at 30s), event-ID deduplication, visibility-aware pause/resume, and ACK support. Removes the "now I need to write the reconnect logic" hurdle for any realtime feature.

Animation primitives

  • CRTEffects — post-processing layer with independently-configurable scanlines, chromatic aberration, flicker, screen shake, and RGB split. Attach to document.body for an ambient layer or to any element for a contained effect.
  • animation-blocks — 10 structured animation classes covering common UI moments: TitleDecoder, ProgressBar, ScanlineSweep, TerminalBoot, GlitchPulse, ASCIIBorder, SystemDiagnostic, LoadingBarMulti, DataTransmission, TerminalPrompt.
  • corrupted-particles-background — auto-injector for the package's behind-blur particle layer. DPR=1 performance mode means it stays cheap on high-density displays.

Text-animation distinction (a naming consolidation)

The package historically had overlapping classes that all did "text effect, sort of" but in different ways. 0.2.0 sorted the naming into three clearly-distinct primitives that cover the actual use cases:

  • TypingAnimation — streaming/typed pattern. Use for terminal output, chat reveal, anywhere text arrives a character at a time.
  • DecryptReveal — fixed-length character scramble that resolves to the final text. Use for headings on scroll, "decoding" preambles, mission briefings.
  • PhraseCycle — discrete phrase-state cycling. Replaces an element's text with phrase A, then phrase B, etc., then settles. Use for loading screens, boot sequences, "thinking" indicators.

Picking one is now a one-question choice ("is the text streaming in, scrambling, or cycling through phrases?") instead of "which one of these similarly-named classes am I supposed to use again?"

Core utilities

Five small stateless modules covering helpers consumers were re-implementing anyway: random-utils (typed random helpers), time-utils (12h/24h, durations, time-ago), clipboard-helpers (copy-with-feedback), url-state (form ↔ URL params), and PngExport (canvas-to-PNG via optional html2canvas peer dep — base install stays dep-free).

CDN distribution that actually works

Corrupted Theme now ships from a Cloudflare R2 bucket bound to two custom domains: cdn.whykusanagi.xyz and cdn.nikkers.cc. The dual-domain setup means you can pick the one that matches your site's registrable domain and load the stylesheet same-origin — no CORS preflight, no third-party CDN trust, no CSP gymnastics.

Production adoption is one tag:

<link rel="stylesheet"
      href="https://cdn.whykusanagi.xyz/corrupted-theme/@0.2.1/dist/theme.min.css"
      integrity="sha384-5TwIbFKBuga57t3wFR0Pnk6ZMMW9FnF+vQtBzW9XVjrFMdzu85JDRchgdI51mLVd"
      crossorigin="anonymous">

Pinned versions are immutable — once @0.2.1/ uploads, that path is frozen and the SRI hash will always match. A floating @latest route is available via a Cloudflare Worker with a KV version pointer if you want to track releases automatically. SRI hashes for every release are published in the CHANGELOG.

0.2.1's headline fix is the bundle itself: 0.2.0 shipped a build that preserved @import statements verbatim instead of inlining them, so the CDN's dist/theme.min.css was effectively empty (~4 kB). 0.2.1 adds postcss-import to the build and the dist file is now the real 79.5 kB bundle that consumers were expecting. toast.css and seamless-background.css — previously deep-import-only — are now part of the main bundle too.

See docs/CDN_CONSUMPTION.md for the full guide: pinned vs. @latest trade-offs, CSP guidance, the dual-domain CORS allowlist, and the architecture behind the Worker that rewrites @latest to the current version.

Live demo site

Every component in the package is demonstrated at corrupted.whykusanagi.xyz. It's deployed from examples/ on every merge to main, so it stays current with whatever the package ships. The Cloudflare Worker that serves it also applies a complete set of security-response headers (HSTS, CSP, X-Frame-Options DENY, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) — useful as a reference for anyone configuring those headers on their own site.

If you want to see what the package looks like in production rather than reading about it, that's the place to start. There's also a working consumer at whykusanagi.xyz — this site — which uses Corrupted Theme as its design system and integrates several of the 0.2.0/0.2.1 components live.

Canonical data layer

One of the more impactful design decisions in 0.2.0 was moving the package's content data — phrase pools, character sets, design-token colors — out of inline arrays and into versioned JSON files at src/data/{phrases,charsets,colors}.json. Each file has an AJV schema and a npm run validate-data CI step.

Two consequences worth knowing about:

  • One source of truth across languages. The Go-based celeste-cli consumes the same JSON via go:embed. Any future consumer in any language can read the JSON without taking a JavaScript dependency. The cross-language contract is documented in docs/CROSS_LANGUAGE_CONTRACT.md.
  • Context-aware phrase API. The new getPhraseByContext(word, nsfw) helper exposes six semantic pools — data, system, status, void, memory, glitch — instead of one flat list. The phrase library also gained a Romaji-kanji pairing convention so consumers can render the same phrase in multiple scripts.

0.2.1 added a small build-time step that emits sibling *.data.js modules from each JSON file (via npm run data:generate, wired into prebuild/pretest/prepublishOnly). The internal JS modules now import from those, which fixes a cross-browser regression where the JSON-import-attributes syntax used in 0.2.0 only parsed in Chromium 123+ and raised SyntaxError in Safari and Firefox. The canonical JSON stays the source of truth — cross-language consumers see no change.

NSFW handling: one option, one name

Earlier versions of the package had drifted into using different option names for the same concept across components. corrupted-particles took includeLewd; animation-blocks took lewdMode. They controlled the same thing — whether NSFW phrases and visual variants were eligible — but consumers had to remember which name applied where.

0.2.0 canonicalized this to a single option, nsfw, across every component that exposes the toggle. The old names continue to work in 0.2.x — they emit a one-time console.warn on first use to give consumers time to migrate — and are scheduled for removal in 0.3.x. If you set nsfw: true at the site level (or per-instance on any component), every NSFW-aware piece behaves consistently.

Security review and hardening

0.2.1 wired up GitHub's default CodeQL setup (JavaScript/TypeScript + GitHub Actions). Three findings were closed in shipped code:

  • Predictable session IDs replaced with crypto.randomUUID() in celeste-widget.js. The session ID is not an auth token (the proxy validates credentials server-side), but removing the predictable-token surface closes js/insecure-randomness and is the right primitive to use.
  • Example pages rebuilt to use safe DOM APIs. The WebSocket example now appends log entries with textContent instead of innerHTML, so an incoming frame cannot inject markup. The PNG-export example pins its html2canvas CDN load with an SRI integrity hash. Both close CodeQL findings (js/xss-through-dom, js/functionality-from-untrusted-source) and serve as reference patterns for consumers.

Infrastructure-side: the CI workflow now declares least-privilege permissions: contents: read; GitHub secret scanning, secret-scanning push protection, and Dependabot security updates are all on; CodeQL default setup covers JS/TS and Actions. npm audit is clean.

The package also ships a SECURITY.md with the threat model, vulnerability reporting address, and a CDN distribution threat-model section covering the R2 bucket, the Cloudflare Worker that handles @latest rewrites, and the SRI guarantee for pinned versions.

The .container migration (one breaking change)

0.2.0's only breaking change splits .container into a structural-only base class plus opt-in layout modifiers. The base now does max-width and horizontal centering — nothing more. Opinionated layouts (grid columns, glass-panel backgrounds, full-screen sections, centered content) opt in via modifier classes.

The motivation: in 0.1.x, downstream sites were spending CSS overriding .container's presumed grid behavior in contexts where they wanted a different layout. The package was fighting its consumers. The new shape lets consumers express intent declaratively and the package stays out of their way.

Migration is one CSS class per usage:

<!-- before 0.2.0 -->
<div class="container">...</div>

<!-- after 0.2.0 / 0.2.1: glass panel with background -->
<div class="container container--with-bg">...</div>

<!-- 2-column grid layout -->
<div class="container container--grid-2col">...</div>

Available modifiers: container--with-bg, container--transparent, container--grid-2col, container--grid-3col, container--fullscreen, container--centered. The full guide is at docs/MIGRATION_CONTAINER_0.2.0.md in the package. The pre-1.0 status of the project is what makes a breaking change in a minor version acceptable; pin to ^0.1.9 if you're not ready to migrate yet.

Getting started

Install from npm:

npm install @whykusanagi/corrupted-theme

Or skip the build step and load from the CDN:

<link rel="stylesheet"
      href="https://cdn.whykusanagi.xyz/corrupted-theme/@0.2.1/dist/theme.min.css"
      integrity="sha384-5TwIbFKBuga57t3wFR0Pnk6ZMMW9FnF+vQtBzW9XVjrFMdzu85JDRchgdI51mLVd"
      crossorigin="anonymous">

Components import as named exports from the package:

import { Toast } from '@whykusanagi/corrupted-theme/toast';
import { ClockWidget } from '@whykusanagi/corrupted-theme/clock-widget';
import { CRTEffects } from '@whykusanagi/corrupted-theme/crt-effects';

Each component is a separate deep-import path so you only pay for what you use. The full export list is in the package's package.json.

What's next

Three items on the post-0.2.1 roadmap:

  • ES module CDN distribution. The CDN currently serves the CSS bundle and the UMD timer-registry. Once the ES module components are part of the CDN distribution, consumers can import them directly from cdn.whykusanagi.xyz without a bundler or local vendoring.
  • Figma design system. Component library in Figma matching the CSS token definitions — useful for design work before implementation.
  • Storybook integration. Interactive component documentation. The examples currently live as static HTML at corrupted.whykusanagi.xyz; Storybook would make them searchable and interactive.
Install from npm Live demo Source & docs Earlier overview