npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

halloween.js

v1.0.3

Published

Zero-dependency Halloween screen-corner decorations and ambient page effects for any website.

Readme

🎃 halloween.js

npm version bundle size license

Zero-dependency Halloween screen-corner decorations and ambient page effects for any website.

Live demo

halloween.js cover

Install

npm install halloween.js
import halloween, { pageEffects } from "halloween.js";

A plain import runs the library's auto-init side effect immediately — it starts watching <body> for the master halloween class as soon as the module loads, no separate init call needed. TypeScript types ship with the package.

For a no-build-step site (WordPress Theme Editor → header, any "custom HTML/scripts" plugin, plain HTML), a single script tag is still the simplest way to install it — no npm, no bundler, no separate CSS file, the script injects its own styles and auto-initializes the same way:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/halloween.iife.js"></script>

After the script tag, the same imperative API is available as Halloween.pageEffects.start("eyes") etc. — see Advanced: JS API below. Always pin an exact released version (like @1.0.2 above) — jsDelivr caches an npm version forever, so it can never change under you.

Platform guides

The same script tag works everywhere — only where you paste it, and how you set the halloween body class, changes per platform.

WordPress

  • Without plugins: Appearance → Theme Editor → header.php, paste the script tag right before </head>. Only works on themes that allow file editing (not on WordPress.com's basic plan or block themes with editing disabled).
  • With a "Insert Headers and Footers"-type plugin (any popular one works): paste the same script tag into its Header field, no theme editing needed.
  • Add class="halloween" to <body> via your theme's body class filter, or add a small custom-HTML block with <script>document.body.classList.add("halloween")</script>.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/halloween.iife.js"></script>

Webflow

  • Project Settings → Custom Code → Head Code applies the script site-wide; use a specific page's Page Settings → Custom Code to scope it to one page instead.
  • Webflow doesn't expose the <body> tag for editing directly — add the master class from the same Custom Code panel (Footer Code) instead:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/halloween.iife.js"></script>
<script>document.body.classList.add("halloween");</script>

Shopify

  • Online Store → Themes → Edit Code → theme.liquid, paste the script tag right before </head>.
  • Shopify's theme.liquid gives you the <body> tag directly, so add the class there:
<body class="halloween">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/halloween.iife.js"></script>

Squarespace

  • Settings → Advanced → Code Injection → Header, paste the script tag. Code Injection only exposes header/footer, not <body> itself.
  • Add the master class from the same Header field instead:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/halloween.iife.js"></script>
<script>document.body.classList.add("halloween");</script>

The master switch

<body class="halloween"> turns the library on. Without it, nothing runs at all — no ambient effects, no screen corners. This is deliberate: one class to flip, everywhere else just uses modifiers. Additional body classes (below) opt in to specific ambient effects and screen corners; the library automatically re-syncs itself whenever <body>'s classes change, so toggling any of these classes from your own code is enough — no JS call required.

<body class="halloween">

Page-level: ambient effects

Add any of these on <body>, alongside the master halloween class — no JS call needed, the library watches for it. Removing a class clears any effect currently on screen immediately, it doesn't wait for it to finish its own fade-out.

<body class="halloween halloween-eyes halloween-witches halloween-spider-drop halloween-tombstones halloween-screen-left-top">
  <!-- blinking eyes fade in and out at random spots; witches fly across the screen at an angle;
       a spider drops from the top at a random horizontal spot and climbs back up;
       a tombstone rises out of the ground at a random horizontal spot, holds, then sinks back;
       a spider web sits fixed in the top-left corner of the viewport -->
</body>

| Class | Effect | | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | halloween-eyes | Blinking eyes fade in/out at random spots | | halloween-witches | Witches fly across at a random angle and direction | | halloween-spider-drop | A spider drops from the top to 75% of viewport height, then climbs back up, at a random horizontal position each time | | halloween-tombstones | A tombstone rises ~80–120px out of a random horizontal spot at the bottom of the viewport, holds briefly, then sinks back down | | halloween-screen-left-top / halloween-screen-right-top / halloween-screen-left-bottom / halloween-screen-right-bottom | A static spider web fixed to that viewport corner — any combination |

Or toggle it from JS, e.g. for SPA routing:

document.body.classList.toggle("halloween-eyes", someCondition);

At most one instance of each ambient effect (eyes, witches, spider-drop, tombstones) is on screen at a time (more with halloween-intensity-party, see below). While the tab is in the background, no new ambient nodes are created — the scheduler keeps running and resumes on the next scheduled tick after the tab becomes visible.

Intensity presets

Add one intensity class alongside the effect classes to control spawn rate, max concurrent nodes, size and page-layer opacity. Use only one at a time — if more than one ends up on <body>, halloween-intensity-party wins over halloween-intensity-subtle. No class at all means normal.

| Class | Spawn delay | Max concurrent per effect | Size | Page opacity | | ------------------------- | ------------ | -------------------------- | ------- | ------------ | | halloween-intensity-subtle | 2.5x slower | 1 | ~75% | 0.65 | | halloween-intensity-normal (default) | 1x | 1 | 100% | 1 | | halloween-intensity-party | 2x faster | 2 | ~115% | 1 |

<body class="halloween halloween-witches halloween-intensity-subtle">
<body class="halloween halloween-eyes halloween-spider-drop halloween-intensity-party">

Season window

Leave the script in the page year-round — by default it only does anything within a ~2 week window around Halloween (18 Oct – 2 Nov). Override it with data-halloween-start/data-halloween-end attributes on <body> — this works exactly the same way whether you're using the script tag or the npm package:

<body
  class="halloween halloween-eyes halloween-screen-left-top"
  data-halloween-start="18-10"
  data-halloween-end="02-11"
>

| Attribute | Meaning | Default | | ------------------------ | --------------------- | -------- | | data-halloween-start | Season start, DD-MM | 18-10 | | data-halloween-end | Season end, DD-MM | 02-11 |

Both attributes are optional and resolved independently — set only the one you need to change, or leave both off entirely to use the default window. Changing either one at runtime (e.g. from your own script) triggers an immediate re-sync, the same as changing the halloween/effect classes does.

Outside the season window, the master switch stays off entirely — same as if halloween weren't on <body> at all. A non-empty but malformed value fails open (runs as if there were no season restriction) rather than silently disabling the library; an empty or whitespace-only attribute is treated as not set. Values are validated against the real calendar (31-04 and the like are rejected); 29-02 is accepted year-round and resolves to 28 Feb in non-leap years — this doesn't change based on where the value came from.

Classic-script query param shorthand

If you're using the plain script tag, ?s=DD-MM&e=DD-MM on the script's own src is also supported, as a shorthand:

<script
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/halloween.iife.js?s=18-10&e=02-11"
></script>

This only works for the classic script tag — there's no script URL for the npm import to read query params from. When a <body> attribute and a query param disagree, the attribute wins:

data-halloween-start/data-halloween-end attribute → script ?s=/?e= query param → library default

(Each end of the window follows this precedence independently — setting only data-halloween-start still lets ?e= or the default apply to the end.)

Color

Spiders, threads, webs and the witch silhouette all read color: var(--halloween-color, currentColor), so by default they inherit whatever text color the page already has — usually the right call on both light and dark sites without any configuration. Eyes keep their own fixed amber, since they're a two-tone illustration, not a single-color decoration.

--halloween-color is the universal way to override it, and works the same for the script tag and the npm package:

:root {
  --halloween-color: #ff6b00;
}

If you're using the plain script tag, ?color= on the script's own src (URL-encoded, e.g. ?color=%23ff6b00) is also supported as a classic-script convenience — there's no equivalent for the npm import.

Ambient effects render in a fixed full-page layer at z-index: var(--halloween-page-z-index, 999), so it can be pushed below a modal or cookie banner if needed:

:root {
  --halloween-page-z-index: 500;
}

Advanced: JS API

The library already auto-syncs itself against <body>'s classes (on load, and on every class change), so most sites never need to call anything directly. These two exports are for the cases where that's not enough. If you're using the plain script tag, the same functions are available as Halloween.halloween and Halloween.pageEffects.

halloween() — an optional, immediate manual sync against <body>'s current classes and the season gate. Takes no arguments. It re-evaluates the same master-switch/season-gate check the automatic sync does — it does not bypass either — so it's useful for syncing sooner than the next automatic check, not for skipping the checks themselves.

import halloween from "halloween.js";

document.body.classList.add("halloween", "halloween-eyes"); // master class required
halloween(); // sync now, instead of waiting for the next automatic check

pageEffects.start(name) / pageEffects.stop(name) — low-level, direct control over one ambient effect ("eyes" | "witches" | "spider-drop" | "tombstones"), bypassing the class-driven system entirely.

import { pageEffects } from "halloween.js";

pageEffects.start("eyes");
pageEffects.stop("witches");

Accessibility

Every animation respects prefers-reduced-motion: reduce, and picks up live changes to it while the page stays open — no reload needed:

  • Ambient effects (eyes, witches, spider-drop, tombstones) don't spawn; any already on screen are removed immediately once the preference turns on.
  • Static screen-corner webs are unaffected — they're not animated to begin with.

All decorative SVGs are aria-hidden.

Bundle

Single IIFE build (styles included) for drop-in use, plus ESM/CJS builds and TypeScript types (dist/halloween.d.ts for ESM, dist/halloween.d.cts for CJS) for npm/bundler consumers. Under 10 KB gzipped, zero runtime dependencies.

Local development

npm install
npm run dev           # tsup --watch
npm run demo:prepare  # build + copy dist into demo/dist for the local demo page

Serve demo/ with any static file server (opening index.html directly via file:// won't work — module/script loading is blocked by the browser's CORS policy for local files).

License

MIT © Iurii Rogulia