Skip to main content

special-occasions.js

Path: js/special-occasions.js | Language: JavaScript | Lines: ~692

Date-driven holiday theming system for Halloween, Christmas, Easter, and April Fools


Overview

This module applies seasonal visual themes to gwern.net based on the current date and time. It supports four holidays: Halloween, Christmas, Easter, and April Fools' Day. Each occasion has its own date-detection logic, CSS class additions, logo replacements, and optional element colorization.

The system activates automatically when the page loads by checking each occasion's test function. If active, it applies body classes like special-halloween-dark or special-christmas-light that trigger corresponding CSS rules. The module also handles logo replacement with themed SVG or bitmap alternatives, supporting randomized or sequenced logo selection across page loads via localStorage.

A key design decision is that special occasions are mode-aware: Halloween and Christmas have separate light/dark variants, and the theme re-applies whenever DarkMode.computedModeDidChange fires. This ensures the correct variant is always shown even if the user toggles dark mode mid-session.


Public API

colorizeElements(colorizationSpecs, container?)

Colorizes inline SVG icons by transforming color values in CSS custom properties.

colorizeElements([
[ "li:nth-of-type(odd)", "--list-bullet", "#f00" ],
[ "li:nth-of-type(even)", "--list-bullet", "#0f0" ]
]);

Each spec is [selector, cssVariable, referenceColor, undesiredDarkModeProperty?]. The function finds elements matching the selector, extracts the SVG from the CSS variable's url() value, applies color transforms, and sets the result as an inline style.

Called by: Christmas occasion handler Calls: colorizeElement(), Color.processColorValue()


uncolorizeElements(uncolorizationSpecs, container?)

Reverses colorizeElements() by removing inline style overrides.

Called by: Christmas cancellation handler Calls: uncolorizeElement()


injectSpecialPageLogo(logoType, options)

Replaces the sidebar logo with a themed variant. Supports multiple selection modes:

OptionDescription
mode"light", "dark", or null for single-mode logos
identifierSpecific numbered logo (e.g., "3")
randomizeBoolean; pick randomly from available logos
sequence"nextAfterSaved", "previousBeforeSaved", "nextAfterCurrent", "previousBeforeCurrent"
linkURL to point the logo link to (default: unchanged)

Logo files must follow the naming convention:

  • SVG: /static/img/logo/{type}/logo-{type}.svg or /static/img/logo/{type}/{mode}/logo-{type}-{mode}-{n}.svg
  • Bitmap: logo-{type}-small-{scale}x.{png|jpg|webp} where scale is 1-3

Called by: Halloween handler, Christmas handler Calls: replacePageLogoWhenPossible(), getAssetPathname(), versionedAssetURL(), processAssetSequenceOptions()


Restores the default logo (/static/img/logo/logo-smooth.svg) and resets the logo link to /index.

Called by: Occasion cancellation handlers Calls: replacePageLogoWhenPossible(), versionedAssetURL()


resetPageLogoSequenceIndex(logoType)

Clears the localStorage key tracking logo sequence position for a given type.

Called by: Halloween/Christmas cancellation handlers


toggleSpecialOccasionTest(specialOccasionName?, enable?)

Debug utility to force-enable a special occasion regardless of date.

toggleSpecialOccasionTest("halloween", true);  // Enable Halloween mode
toggleSpecialOccasionTest("halloween", false); // Disable
toggleSpecialOccasionTest(); // Disable all

Uses localStorage keys like special-occasion-test-halloween.


Date Detection Functions

FunctionActive Period
isItHalloween()Oct 31 6PM – Nov 1 6AM (Halloween-celebrating locales only)
isItChristmas()Dec 24 6PM – end of Dec 25
isItAprilFools()Apr 1 8AM – 3PM
isItEaster()Hardcoded dates 2024–2050 (all day)

Each also returns true if:

  • The body has class test-{occasion} (test pages like /lorem-halloween)
  • localStorage has special-occasion-test-{occasion} set to "true"

Internal Architecture

Occasion Registry

GW.specialOccasions = [
[ name, testFn, applyFn, removeFn ],
...
];

Each entry defines:

  1. name - Identifier string ("halloween", "christmas", etc.)
  2. testFn - Returns boolean; checks if occasion is active
  3. applyFn - Called when active; adds classes, replaces logo, colorizes elements
  4. removeFn - Called when inactive; cleans up all modifications

Control Flow

doWhenBodyExists()
└── applySpecialOccasionClasses()
├── For each occasion in GW.specialOccasions:
│ if testFn() → applyFn()
│ else → removeFn()
└── Subscribe to DarkMode.computedModeDidChange
└── Re-run applySpecialOccasionClasses()

Logo Replacement Pipeline

injectSpecialPageLogo()
└── replacePageLogoWhenPossible()
├── Wait for #sidebar .logo-image and helper functions
├── Build logo pathname pattern with mode/identifier/sequence
├── Call getAssetPathname() to resolve actual file
├── Call versionedAssetURL() for cache-busting
├── Replace <svg> or inject <img> wrapper
├── Update logo link href if specified
└── brightenLogoTemporarily(20s, 1s fade)

Key Patterns

Locale-Aware Halloween

Halloween only activates for users whose browser language suggests they're from a Halloween-celebrating region:

const halloweenLangs = new Set(['en','ga','gd','de','nl','fr','es','ja','ko']);
let langCode = (window.navigator.userLanguage ?? window.navigator.language).slice(0, 2).toLowerCase();

This prevents Halloween styling from appearing to users in regions where Halloween isn't celebrated.

Inline SVG Color Transformation

The colorization system works by:

  1. Extracting SVG source from a CSS url('data:image/svg+xml;utf8,...') value
  2. Parsing hex color codes with regex: /(?<!href=)"(#[0-9A-Fa-f]+)"/g
  3. Applying Color.ColorTransform.COLORIZE to shift hues
  4. Re-encoding the modified SVG as a data URL
  5. Setting it as an inline style

This allows list bullets and horizontal rule icons to be recolored to red/green for Christmas without modifying source SVGs.

Viewport-Responsive Logo Selection

Halloween uses doWhenMatchMedia() to show different logos at different viewport widths:

doWhenMatchMedia(matchMedia("(min-width: 1180px)"), {
ifMatchesOrAlwaysDo: () => { /* sequenced logo for wide screens */ },
otherwiseDo: () => { /* fixed logo-1 for narrow screens */ },
whenCanceledDo: () => { resetPageLogo(); }
});

Configuration

Global Constants

ConstantValuePurpose
GW.specialOccasionTestLocalStorageKeyPrefix"special-occasion-test-"localStorage key prefix for debug mode
GW.specialOccasionTestPageNamePrefix"test-"Body class prefix for test pages
GW.allowedAssetSequencingModesArray of 4 stringsValid values for sequence option

Test Pages

Each occasion has a test page that forces activation regardless of date:

  • /lorem-halloween
  • /lorem-christmas
  • /lorem-april-fools
  • /lorem-easter

Integration Points

Events Listened

EventSourcePurpose
DarkMode.computedModeDidChangedark-mode.jsRe-apply occasion classes for new mode

External Dependencies

FunctionSourcePurpose
Color.processColorValue()color.jsColor transformation for SVG colorization
DarkMode.computedMode()dark-mode.jsDetermine light/dark variant
DarkMode.defaultModedark-mode.jsSet default to dark during Halloween
getAssetPathname()misc.jsResolve logo file from pattern
versionedAssetURL()misc.jsAdd cache-busting version query
processAssetSequenceOptions()misc.jsHandle randomize/sequence logic
doWhenBodyExists()initial.jsLifecycle hook
doWhenPageLoaded()initial.jsLifecycle hook
doWhenMatchMedia()initial.jsMedia query listener
doWhenElementExists()initial.jsElement availability hook
Extracts.addTargetsWithin()extracts.jsApril Fools popup target
Popups.spawnPopup()popups.jsApril Fools popup

Body Classes Added

ClassOccasionCondition
special-halloween-lightHalloweenLight mode active
special-halloween-darkHalloweenDark mode active
special-christmas-lightChristmasLight mode active
special-christmas-darkChristmasDark mode active
special-april-foolsApril FoolsAlways (single variant)
special-easterEasterAlways (currently no styling)

localStorage Keys

Key PatternPurpose
special-occasion-test-{name}Debug mode toggle
logo-sequence-index-{type}Track logo sequence position

See Also