Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, senior full-stack software engineer. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

[email protected]
Back to projects

Iurii Haunts: halloween.js — Zero-Dependency Halloween Effects for Any Website

August 26, 2026

Zero-dependency Halloween screen-corner decorations and ambient page effects — one script tag or npm install, class-driven, seasonal by default, under 10 KB gzipped.

Live Demo Source code

Stack

TypeScript

Services

npmGitHub ActionsGitHub Pages

Topics

Open SourceVanilla JSAnimationAccessibility

Key Results

  • Zero runtime dependencies — under 10 KB gzipped for the full drop-in IIFE bundle
  • One class to flip — `<body class="halloween">` turns everything on, no JS call required
  • Seasonal by default — auto-disables outside a configurable ~2-week Halloween window
  • Fully accessible — every animation respects `prefers-reduced-motion`, live
  • 130 tests passing across the full effect and configuration surface
  • Published to npm with trusted publishing (OIDC) — no long-lived npm token in CI

npm

License

Updated

The Business Problem

Every year, sites that want a Halloween touch end up choosing between two bad options: a heavyweight jQuery-era plugin dragging in dependencies and doing DOM surgery on the page, or a developer hand-rolling one-off canvas/SVG animations that get ripped out again come November — and forgotten about, so they never come back next year either.

Neither approach fits a modern site. Marketing and content teams on WordPress or a no-build CMS need something they can drop in without a deploy pipeline. Product teams on a modern stack need something that won't touch their bundle budget or fight their build tooling. And nobody wants to remember to manually disable a seasonal decoration on November 3rd.

halloween.js is the drop-in fix: a single script tag or npm import, controlled by one CSS class, that decorates a page for Halloween and quietly turns itself off the rest of the year.

The Solution

I built halloween.js as a zero-dependency vanilla JS library with a single master switch: adding class="halloween" to <body> turns the whole system on, and a MutationObserver keeps it in sync with the page automatically — no imperative init call needed for the common case. Additional classes opt into specific decorations: four static screen-corner spider webs, blinking eyes, flying witches, a dropping spider, and a rising tombstone, each with subtle/normal/party intensity presets.

The library is seasonal by default — effects only run inside a configurable ~2-week window around Halloween (18 Oct – 2 Nov), validated against the real calendar rather than naive string matching, so a site owner never has to remember to switch it off. Color inherits currentColor by default via a CSS custom property, so it matches the host page's theme without configuration on both light and dark sites. Every animation respects prefers-reduced-motion, including live changes to that preference while the page stays open.

It ships three ways from one TypeScript source: an IIFE bundle for the no-build case (WordPress, plain HTML, any CMS with a script-tag field), and ESM/CJS builds with full .d.ts types for npm/bundler consumers — all under 10 KB gzipped, zero runtime dependencies.

Results

MetricValue
Bundle size9,102 B gzip (IIFE, styles included)
Runtime dependencies0
Tests130, passing on Node 22 and 24 in CI
Public API surface3 exports (default, halloween, pageEffects)
Distribution formatsESM, CJS, IIFE, with TypeScript declarations for each
PublishingTrusted OIDC publishing to npm on tagged release

Under the Hood

For the technically curious, here is how the class-driven sync, season gating, and release pipeline work.

Class-driven auto-sync

The library never requires an explicit init call for the common path. A MutationObserver watches <body>'s class attribute and re-evaluates the master switch and every effect class on every change, so toggling document.body.classList.toggle("halloween-eyes", condition) from application code — including SPA route changes — is enough on its own. An exported halloween() function exists only for forcing an immediate re-sync ahead of the next automatic check; it re-runs the same master-switch and season checks rather than bypassing them.

Season window validation

The default window is 18 Oct – 2 Nov, overridable via data-halloween-start / data-halloween-end attributes on <body>, or — for the no-build script tag specifically — ?s=/?e= query params on the script's own src, with attribute values taking precedence over query params, each resolved independently. Dates are validated against the real calendar rather than simple string parsing: 31-04 is rejected as invalid, while 29-02 is accepted year-round and resolves to 28 Feb in non-leap years. A malformed (but non-empty) value fails open — runs as if unrestricted — rather than silently disabling the whole library, since a config typo shouldn't be able to take out an otherwise-working page.

Reduced motion, live

Ambient effects (eyes, witches, spider-drop, tombstones) stop spawning the instant prefers-reduced-motion: reduce becomes true, and any effect currently on screen is removed immediately rather than waiting for its own animation to finish — checked via a live media-query listener, not just read once on load. Static corner webs are unaffected since they were never animated. All decorative SVGs carry aria-hidden.

Multi-target build

Source is TypeScript, built with tsup into three targets from one entry point: ESM and CJS for bundler/npm consumers (with separate .d.ts / .d.cts declaration files so both module systems get correctly typed imports), and a single self-contained IIFE — styles included, no separate CSS file — for the script-tag case. sideEffects is declared explicitly in package.json so bundlers don't tree-shake away the auto-init behavior a plain import "halloween.js" relies on.

Release pipeline

CI runs the full check suite (typecheck, tests, build, package-export verification) on Node 22 and 24 for every push and PR. Publishing to npm is gated behind pushing a v* tag and runs as a separate workflow that first verifies the tag matches package.json's version, then publishes using npm's trusted publishing via GitHub Actions OIDC — no long-lived npm token stored in CI secrets. The interactive demo (demo/) deploys to GitHub Pages on its own workflow, served on a custom domain (halloween.js.org) via the js.org program.

Iurii RoguliaAvailable

Need something similar?

I build custom solutions — from APIs to full products. Let's talk about your project.

View all projects

Related projects

n8n Verified Community Node — EU VAT Validation in Workflows
n8n Verified Community Node — EU VAT Validation in Workflows
August 6, 2026
n8n Verified Community Node — EU VAT Validation in Workflows

Verified n8n node that puts VIES VAT validation and EU rate lookups inside a workflow, so onboarding or invoicing can decide on a real VAT number instead of a

Stack

TypeScriptNode.jsn8n

Services

npmGitHub Actionsvatnode API

Topics

Workflow AutomationNo-CodeIntegrationsDeveloper ToolsOpen SourceTax/VAT
eu-vat-rates-data — Free & Open-Source EU VAT Rates Dataset
eu-vat-rates-data — Free & Open-Source EU VAT Rates Dataset
February 25, 2026
eu-vat-rates-data — Free & Open-Source EU VAT Rates Dataset

Free, open-source EU VAT rates for all 27 member states + UK. Published as native packages for npm, PyPI, Packagist, Go, and RubyGems.

Stack

TypeScriptPythonPHPGoRuby

Services

GitHub ActionsnpmPyPIPackagistRubyGems

Topics

Open SourceTax/VATAutomation

Related posts

Publishing One Package to Five Registries with GitHub Actions
March 27, 2026· 10 min
Publishing One Package to Five Registries with GitHub Actions

How to publish one dataset to npm, PyPI, Go Module, RubyGems, and Packagist automatically with GitHub Actions — architecture, versioning, and per-ecosystem

Stack

TypeScriptPythonPHPGoRuby

Services

GitHub ActionsnpmPyPIPackagistRubyGems

Topics

Open SourceTax/VATAutomation
IndexNow in Next.js: Instant Indexing After Every Deploy
April 14, 2026· 18 min
IndexNow in Next.js: Instant Indexing After Every Deploy

IndexNow implementation guide for Next.js: key generation, TypeScript client with retry logic, GitHub Actions workflow, and pitfalls that break submissions

Stack

TypeScriptNode.js

Services

GitHub Actions

Topics

SEOAutomationPerformanceArchitecture